[med-svn] [ushahidi] 02/05: New upstream version 2.7.4

Andreas Tille tille at debian.org
Sat Dec 30 07:06:30 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository ushahidi.

commit 352ca2c22bc3c820ef393460df32c7e8c4ba968e
Author: Andreas Tille <tille at debian.org>
Date:   Sat Dec 30 08:03:41 2017 +0100

    New upstream version 2.7.4
---
 .gitignore                                         |    12 +
 .gitmodules                                        |     3 +
 .htaccess                                          |    30 +
 CHANGELOG.md                                       |   437 +
 CONTRIBUTING.md                                    |    17 +
 License.txt                                        |   200 +
 README.markdown                                    |   161 +
 application/cache/.gitignore                       |     0
 application/config/actions.php                     |    70 +
 application/config/api.php                         |    64 +
 application/config/auth.template.php               |    56 +
 application/config/benchmark.php                   |    39 +
 application/config/cache.php                       |    33 +
 application/config/captcha.php                     |    28 +
 application/config/cdn.php                         |    59 +
 application/config/config.template.php             |   194 +
 application/config/cookie.php                      |    32 +
 application/config/database.template.php           |    32 +
 application/config/email.php                       |    25 +
 application/config/encryption.template.php         |    29 +
 application/config/globalcode.php                  |    33 +
 application/config/image.php                       |     7 +
 application/config/locale.php                      |    29 +
 application/config/map.php                         |    88 +
 application/config/openlayers.ushahidi.cfg         |    51 +
 application/config/plugins.php                     |    14 +
 application/config/requirements.php                |    41 +
 application/config/riverid.php                     |    46 +
 application/config/routes.php                      |    12 +
 application/config/session.php                     |    45 +
 application/config/settings.php                    |    33 +
 application/config/upload.php                      |     9 +
 application/config/version.php                     |    12 +
 application/controllers/admin.php                  |   240 +
 application/controllers/admin/addons.php           |    35 +
 application/controllers/admin/addons/plugins.php   |   204 +
 application/controllers/admin/addons/themes.php    |   101 +
 application/controllers/admin/comments.php         |   192 +
 application/controllers/admin/dashboard.php        |   135 +
 .../controllers/admin/jwysiwyg/filemanager.php     |    72 +
 application/controllers/admin/manage.php           |  1075 ++
 application/controllers/admin/manage/actions.php   |   381 +
 application/controllers/admin/manage/alerts.php    |   166 +
 application/controllers/admin/manage/badges.php    |   231 +
 application/controllers/admin/manage/blocks.php    |   151 +
 application/controllers/admin/manage/forms.php     |  1013 ++
 application/controllers/admin/manage/scheduler.php |   225 +
 application/controllers/admin/messages.php         |   407 +
 .../controllers/admin/messages/reporters.php       |   227 +
 application/controllers/admin/profile.php          |   173 +
 application/controllers/admin/reports.php          |  1528 ++
 application/controllers/admin/settings.php         |  1320 ++
 application/controllers/admin/settings/api.php     |   354 +
 .../controllers/admin/settings/externalapps.php    |   105 +
 .../controllers/admin/settings/facebook.php        |   100 +
 .../controllers/admin/settings/test_email.php      |    66 +
 .../controllers/admin/settings/test_sms.php        |     0
 .../controllers/admin/settings/test_twitter.php    |    52 +
 application/controllers/admin/settings/twitter.php |   116 +
 application/controllers/admin/stats.php            |   546 +
 application/controllers/admin/upgrade.php          |   736 +
 application/controllers/admin/users.php            |   446 +
 application/controllers/alerts.php                 |   320 +
 application/controllers/api.php                    |    74 +
 application/controllers/contact.php                |   113 +
 application/controllers/error.php                  |    37 +
 application/controllers/feed.php                   |   117 +
 application/controllers/feeds.php                  |    81 +
 application/controllers/json.php                   |   877 ++
 application/controllers/login.php                  |  1043 ++
 application/controllers/logout.php                 |    43 +
 application/controllers/main.php                   |   477 +
 application/controllers/media.php                  |   142 +
 application/controllers/members.php                |   131 +
 application/controllers/members/alerts.php         |   132 +
 application/controllers/members/dashboard.php      |   120 +
 application/controllers/members/login.php          |    51 +
 application/controllers/members/private.php        |   310 +
 application/controllers/members/profile.php        |   233 +
 application/controllers/members/reports.php        |   744 +
 application/controllers/page.php                   |    56 +
 application/controllers/profile.php                |   106 +
 application/controllers/reports.php                |   982 ++
 application/controllers/riverid.php                |    58 +
 application/controllers/scheduler.php              |   225 +
 application/controllers/scheduler/s_alerts.php     |   300 +
 application/controllers/scheduler/s_cleanup.php    |   157 +
 application/controllers/scheduler/s_email.php      |   212 +
 application/controllers/scheduler/s_feeds.php      |   147 +
 application/controllers/scheduler/s_twitter.php    |   229 +
 application/controllers/search.php                 |   220 +
 application/controllers/swatch.php                 |   111 +
 application/helpers/MY_file.php                    |    42 +
 application/helpers/MY_html.php                    |   111 +
 application/helpers/MY_remote.php                  |   236 +
 application/helpers/MY_url.php                     |    59 +
 application/helpers/addon.php                      |   111 +
 application/helpers/admin.php                      |   304 +
 application/helpers/alert.php                      |   279 +
 application/helpers/badges.php                     |    56 +
 application/helpers/blocks.php                     |   121 +
 application/helpers/category.php                   |   277 +
 application/helpers/cdn.php                        |   246 +
 application/helpers/customforms.php                |   446 +
 application/helpers/download.php                   |   704 +
 application/helpers/feed.php                       |    67 +
 application/helpers/geocode.php                    |   302 +
 application/helpers/javascriptpacker.php           |   740 +
 application/helpers/map.php                        |   518 +
 application/helpers/members.php                    |   131 +
 application/helpers/nav.php                        |   117 +
 application/helpers/notifications.php              |    52 +
 application/helpers/plugin.php                     |   252 +
 application/helpers/pointinpoly.php                |    72 +
 application/helpers/protochart.php                 |   104 +
 application/helpers/reports.php                    |  1079 ++
 application/helpers/reputation.php                 |   101 +
 application/helpers/sms.php                        |   177 +
 application/helpers/strptime.php                   |    57 +
 application/helpers/testutils.php                  |    38 +
 application/helpers/upload.php                     |   318 +
 application/helpers/ush_locale.php                 |   565 +
 application/helpers/valid.php                      |   400 +
 application/helpers/xml.php                        |   124 +
 application/hooks/1_maintenance.php                |    84 +
 application/hooks/2_settings.php                   |    90 +
 application/hooks/actions.php                      |   899 ++
 application/hooks/browser.php                      |    23 +
 application/hooks/error.php                        |    15 +
 application/hooks/https_check.php                  |    59 +
 application/hooks/page_cache.php                   |    76 +
 application/hooks/register_default_blocks.php      |    62 +
 application/hooks/register_plugins.php             |    87 +
 application/hooks/register_themes.php              |    37 +
 application/libraries/Akismet.php                  |   230 +
 application/libraries/Api_Service.php              |   607 +
 application/libraries/CSSmin.php                   |   741 +
 application/libraries/CSVImporter.php              |   434 +
 application/libraries/Cloudfiles.php               |   159 +
 application/libraries/CronParser.php               |   604 +
 application/libraries/Csvtable.php                 |    68 +
 application/libraries/Database_Expression.php      |   141 +
 application/libraries/Dispatch.php                 |   163 +
 application/libraries/Distance.php                 |    77 +
 application/libraries/Facebook.php                 |  1157 ++
 application/libraries/Geocoder.php                 |    98 +
 application/libraries/Html2Text.php                |   487 +
 application/libraries/HttpClient.php               |   218 +
 application/libraries/IDNA_convert.php             |  1606 ++
 application/libraries/Imap.php                     |   475 +
 application/libraries/JSMin.php                    |   386 +
 application/libraries/MY_Auth.php                  |    96 +
 application/libraries/MY_Controller.php            |    89 +
 application/libraries/MY_Database.php              |    43 +
 application/libraries/MY_View.php                  |    49 +
 application/libraries/Minify_CSS_UriRewriter.php   |   313 +
 application/libraries/OAuth.php                    |   872 ++
 application/libraries/OpenID.php                   |   742 +
 application/libraries/Pclzip.php                   |  5694 +++++++
 application/libraries/PemFtp.php                   |   739 +
 application/libraries/Requirements.php             |  1192 ++
 application/libraries/RiverID.php                  |   533 +
 application/libraries/SimplePie.php                | 15001 +++++++++++++++++++
 application/libraries/Sms_Provider.php             |    23 +
 application/libraries/Themes.php                   |   733 +
 application/libraries/Twitter_Oauth.php            |   245 +
 application/libraries/Upgrade.php                  |   637 +
 application/libraries/Validation.php               |   859 ++
 application/libraries/VideoEmbed.php               |   256 +
 application/libraries/Wkt.php                      |   601 +
 application/libraries/XMLImporter.php              |   981 ++
 application/libraries/api/Api_Object.php           |   456 +
 .../libraries/api/MY_Admin_Category_Api_Object.php |   484 +
 .../libraries/api/MY_Admin_Reports_Api_Object.php  |   548 +
 .../libraries/api/MY_Categories_Api_Object.php     |   224 +
 .../libraries/api/MY_Checkin_Api_Object.php        |    58 +
 .../libraries/api/MY_Comments_Api_Object.php       |   794 +
 .../libraries/api/MY_Countries_Api_Object.php      |   215 +
 .../libraries/api/MY_Customforms_Api_Object.php    |   445 +
 application/libraries/api/MY_Email_Api_Object.php  |   259 +
 .../libraries/api/MY_Incidents_Api_Object.php      |   901 ++
 application/libraries/api/MY_Kml_Api_Object.php    |   198 +
 .../libraries/api/MY_Locations_Api_Object.php      |   137 +
 .../libraries/api/MY_Private_Func_Api_Object.php   |   300 +
 application/libraries/api/MY_Report_Api_Object.php |   160 +
 application/libraries/api/MY_Sms_Api_Object.php    |   234 +
 .../api/MY_Swiftriver_Report_Api_Object.php        |   314 +
 application/libraries/api/MY_System_Api_Object.php |   173 +
 .../libraries/api/MY_Tag_Media_Api_Object.php      |   193 +
 .../libraries/api/MY_Twitter_Api_Object.php        |   247 +
 .../libraries/cloudfiles/CF_Authentication.php     |   229 +
 application/libraries/cloudfiles/CF_Connection.php |   596 +
 application/libraries/cloudfiles/CF_Container.php  |   971 ++
 application/libraries/cloudfiles/CF_Http.php       |  1412 ++
 application/libraries/cloudfiles/CF_Object.php     |   711 +
 application/libraries/drivers/Database.php         |   655 +
 application/libraries/drivers/Database/Mysql.php   |   499 +
 application/libraries/ftp/ftp_class_pure.php       |   163 +
 application/libraries/ftp/ftp_class_sockets.php    |   224 +
 application/libraries/htmlpurifier/CREDITS         |     9 +
 .../libraries/htmlpurifier/HTMLPurifier.auto.php   |    11 +
 .../htmlpurifier/HTMLPurifier.autoload.php         |    26 +
 .../htmlpurifier/HTMLPurifier.composer.php         |     4 +
 .../libraries/htmlpurifier/HTMLPurifier.func.php   |    23 +
 .../htmlpurifier/HTMLPurifier.includes.php         |   222 +
 .../libraries/htmlpurifier/HTMLPurifier.kses.php   |    30 +
 .../libraries/htmlpurifier/HTMLPurifier.path.php   |    11 +
 .../libraries/htmlpurifier/HTMLPurifier.php        |   237 +
 .../htmlpurifier/HTMLPurifier.safe-includes.php    |   216 +
 .../htmlpurifier/HTMLPurifier/AttrCollections.php  |   128 +
 .../htmlpurifier/HTMLPurifier/AttrDef.php          |   123 +
 .../htmlpurifier/HTMLPurifier/AttrDef/CSS.php      |    87 +
 .../HTMLPurifier/AttrDef/CSS/AlphaValue.php        |    21 +
 .../HTMLPurifier/AttrDef/CSS/Background.php        |    87 +
 .../AttrDef/CSS/BackgroundPosition.php             |   133 +
 .../HTMLPurifier/AttrDef/CSS/Border.php            |    43 +
 .../HTMLPurifier/AttrDef/CSS/Color.php             |    78 +
 .../HTMLPurifier/AttrDef/CSS/Composite.php         |    38 +
 .../AttrDef/CSS/DenyElementDecorator.php           |    28 +
 .../HTMLPurifier/AttrDef/CSS/Filter.php            |    54 +
 .../htmlpurifier/HTMLPurifier/AttrDef/CSS/Font.php |   149 +
 .../HTMLPurifier/AttrDef/CSS/FontFamily.php        |   197 +
 .../HTMLPurifier/AttrDef/CSS/Ident.php             |    24 +
 .../AttrDef/CSS/ImportantDecorator.php             |    40 +
 .../HTMLPurifier/AttrDef/CSS/Length.php            |    47 +
 .../HTMLPurifier/AttrDef/CSS/ListStyle.php         |    78 +
 .../HTMLPurifier/AttrDef/CSS/Multiple.php          |    58 +
 .../HTMLPurifier/AttrDef/CSS/Number.php            |    69 +
 .../HTMLPurifier/AttrDef/CSS/Percentage.php        |    40 +
 .../HTMLPurifier/AttrDef/CSS/TextDecoration.php    |    38 +
 .../htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php  |    61 +
 .../htmlpurifier/HTMLPurifier/AttrDef/Clone.php    |    28 +
 .../htmlpurifier/HTMLPurifier/AttrDef/Enum.php     |    65 +
 .../HTMLPurifier/AttrDef/HTML/Bool.php             |    28 +
 .../HTMLPurifier/AttrDef/HTML/Class.php            |    34 +
 .../HTMLPurifier/AttrDef/HTML/Color.php            |    33 +
 .../HTMLPurifier/AttrDef/HTML/FrameTarget.php      |    21 +
 .../htmlpurifier/HTMLPurifier/AttrDef/HTML/ID.php  |    80 +
 .../HTMLPurifier/AttrDef/HTML/Length.php           |    41 +
 .../HTMLPurifier/AttrDef/HTML/LinkTypes.php        |    53 +
 .../HTMLPurifier/AttrDef/HTML/MultiLength.php      |    41 +
 .../HTMLPurifier/AttrDef/HTML/Nmtokens.php         |    52 +
 .../HTMLPurifier/AttrDef/HTML/Pixels.php           |    48 +
 .../htmlpurifier/HTMLPurifier/AttrDef/Integer.php  |    73 +
 .../htmlpurifier/HTMLPurifier/AttrDef/Lang.php     |    73 +
 .../htmlpurifier/HTMLPurifier/AttrDef/Switch.php   |    34 +
 .../htmlpurifier/HTMLPurifier/AttrDef/Text.php     |    15 +
 .../htmlpurifier/HTMLPurifier/AttrDef/URI.php      |    77 +
 .../HTMLPurifier/AttrDef/URI/Email.php             |    17 +
 .../HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php |    21 +
 .../htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php |   101 +
 .../htmlpurifier/HTMLPurifier/AttrDef/URI/IPv4.php |    39 +
 .../htmlpurifier/HTMLPurifier/AttrDef/URI/IPv6.php |    99 +
 .../htmlpurifier/HTMLPurifier/AttrTransform.php    |    56 +
 .../HTMLPurifier/AttrTransform/Background.php      |    23 +
 .../HTMLPurifier/AttrTransform/BdoDir.php          |    19 +
 .../HTMLPurifier/AttrTransform/BgColor.php         |    23 +
 .../HTMLPurifier/AttrTransform/BoolToCSS.php       |    36 +
 .../HTMLPurifier/AttrTransform/Border.php          |    18 +
 .../HTMLPurifier/AttrTransform/EnumToCSS.php       |    58 +
 .../HTMLPurifier/AttrTransform/ImgRequired.php     |    43 +
 .../HTMLPurifier/AttrTransform/ImgSpace.php        |    44 +
 .../HTMLPurifier/AttrTransform/Input.php           |    40 +
 .../HTMLPurifier/AttrTransform/Lang.php            |    28 +
 .../HTMLPurifier/AttrTransform/Length.php          |    27 +
 .../HTMLPurifier/AttrTransform/Name.php            |    21 +
 .../HTMLPurifier/AttrTransform/NameSync.php        |    27 +
 .../HTMLPurifier/AttrTransform/Nofollow.php        |    45 +
 .../HTMLPurifier/AttrTransform/SafeEmbed.php       |    15 +
 .../HTMLPurifier/AttrTransform/SafeObject.php      |    16 +
 .../HTMLPurifier/AttrTransform/SafeParam.php       |    64 +
 .../HTMLPurifier/AttrTransform/ScriptRequired.php  |    16 +
 .../HTMLPurifier/AttrTransform/TargetBlank.php     |    38 +
 .../HTMLPurifier/AttrTransform/Textarea.php        |    18 +
 .../htmlpurifier/HTMLPurifier/AttrTypes.php        |    91 +
 .../htmlpurifier/HTMLPurifier/AttrValidator.php    |   162 +
 .../htmlpurifier/HTMLPurifier/Bootstrap.php        |   109 +
 .../htmlpurifier/HTMLPurifier/CSSDefinition.php    |   328 +
 .../htmlpurifier/HTMLPurifier/ChildDef.php         |    48 +
 .../HTMLPurifier/ChildDef/Chameleon.php            |    48 +
 .../htmlpurifier/HTMLPurifier/ChildDef/Custom.php  |    90 +
 .../htmlpurifier/HTMLPurifier/ChildDef/Empty.php   |    20 +
 .../htmlpurifier/HTMLPurifier/ChildDef/List.php    |   120 +
 .../HTMLPurifier/ChildDef/Optional.php             |    26 +
 .../HTMLPurifier/ChildDef/Required.php             |   117 +
 .../HTMLPurifier/ChildDef/StrictBlockquote.php     |    88 +
 .../htmlpurifier/HTMLPurifier/ChildDef/Table.php   |   227 +
 .../libraries/htmlpurifier/HTMLPurifier/Config.php |   710 +
 .../htmlpurifier/HTMLPurifier/ConfigSchema.php     |   164 +
 .../ConfigSchema/Builder/ConfigSchema.php          |    44 +
 .../HTMLPurifier/ConfigSchema/Builder/Xml.php      |   106 +
 .../HTMLPurifier/ConfigSchema/Exception.php        |    11 +
 .../HTMLPurifier/ConfigSchema/Interchange.php      |    42 +
 .../ConfigSchema/Interchange/Directive.php         |    77 +
 .../HTMLPurifier/ConfigSchema/Interchange/Id.php   |    37 +
 .../ConfigSchema/InterchangeBuilder.php            |   180 +
 .../HTMLPurifier/ConfigSchema/Validator.php        |   206 +
 .../HTMLPurifier/ConfigSchema/ValidatorAtom.php    |    66 +
 .../HTMLPurifier/ConfigSchema/schema.ser           |   Bin 0 -> 14880 bytes
 .../ConfigSchema/schema/Attr.AllowedClasses.txt    |     8 +
 .../schema/Attr.AllowedFrameTargets.txt            |    12 +
 .../ConfigSchema/schema/Attr.AllowedRel.txt        |     9 +
 .../ConfigSchema/schema/Attr.AllowedRev.txt        |     9 +
 .../ConfigSchema/schema/Attr.ClassUseCDATA.txt     |    19 +
 .../ConfigSchema/schema/Attr.DefaultImageAlt.txt   |    11 +
 .../schema/Attr.DefaultInvalidImage.txt            |     9 +
 .../schema/Attr.DefaultInvalidImageAlt.txt         |     8 +
 .../ConfigSchema/schema/Attr.DefaultTextDir.txt    |    10 +
 .../ConfigSchema/schema/Attr.EnableID.txt          |    16 +
 .../ConfigSchema/schema/Attr.ForbiddenClasses.txt  |     8 +
 .../ConfigSchema/schema/Attr.IDBlacklist.txt       |     5 +
 .../ConfigSchema/schema/Attr.IDBlacklistRegexp.txt |     9 +
 .../ConfigSchema/schema/Attr.IDPrefix.txt          |    12 +
 .../ConfigSchema/schema/Attr.IDPrefixLocal.txt     |    14 +
 .../schema/AutoFormat.AutoParagraph.txt            |    31 +
 .../ConfigSchema/schema/AutoFormat.Custom.txt      |    12 +
 .../schema/AutoFormat.DisplayLinkURI.txt           |    11 +
 .../ConfigSchema/schema/AutoFormat.Linkify.txt     |    12 +
 .../schema/AutoFormat.PurifierLinkify.DocURL.txt   |    12 +
 .../schema/AutoFormat.PurifierLinkify.txt          |    12 +
 ...utoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt |    11 +
 .../schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt   |    15 +
 .../ConfigSchema/schema/AutoFormat.RemoveEmpty.txt |    46 +
 .../AutoFormat.RemoveSpansWithoutAttributes.txt    |    11 +
 .../ConfigSchema/schema/CSS.AllowImportant.txt     |     8 +
 .../ConfigSchema/schema/CSS.AllowTricky.txt        |    11 +
 .../ConfigSchema/schema/CSS.AllowedFonts.txt       |    12 +
 .../ConfigSchema/schema/CSS.AllowedProperties.txt  |    18 +
 .../ConfigSchema/schema/CSS.DefinitionRev.txt      |    11 +
 .../schema/CSS.ForbiddenProperties.txt             |    13 +
 .../ConfigSchema/schema/CSS.MaxImgLength.txt       |    16 +
 .../ConfigSchema/schema/CSS.Proprietary.txt        |    10 +
 .../ConfigSchema/schema/CSS.Trusted.txt            |     9 +
 .../ConfigSchema/schema/Cache.DefinitionImpl.txt   |    14 +
 .../ConfigSchema/schema/Cache.SerializerPath.txt   |    13 +
 .../schema/Cache.SerializerPermissions.txt         |    11 +
 .../ConfigSchema/schema/Core.AggressivelyFixLt.txt |    18 +
 .../ConfigSchema/schema/Core.CollectErrors.txt     |    12 +
 .../ConfigSchema/schema/Core.ColorKeywords.txt     |    29 +
 .../schema/Core.ConvertDocumentToFragment.txt      |    14 +
 .../Core.DirectLexLineNumberSyncInterval.txt       |    17 +
 .../ConfigSchema/schema/Core.DisableExcludes.txt   |    14 +
 .../ConfigSchema/schema/Core.EnableIDNA.txt        |     9 +
 .../ConfigSchema/schema/Core.Encoding.txt          |    15 +
 .../schema/Core.EscapeInvalidChildren.txt          |    10 +
 .../ConfigSchema/schema/Core.EscapeInvalidTags.txt |     7 +
 .../schema/Core.EscapeNonASCIICharacters.txt       |    13 +
 .../ConfigSchema/schema/Core.HiddenElements.txt    |    19 +
 .../ConfigSchema/schema/Core.Language.txt          |    10 +
 .../ConfigSchema/schema/Core.LexerImpl.txt         |    34 +
 .../schema/Core.MaintainLineNumbers.txt            |    16 +
 .../ConfigSchema/schema/Core.NormalizeNewlines.txt |    11 +
 .../ConfigSchema/schema/Core.RemoveInvalidImg.txt  |    12 +
 .../schema/Core.RemoveProcessingInstructions.txt   |    11 +
 .../schema/Core.RemoveScriptContents.txt           |    12 +
 .../ConfigSchema/schema/Filter.Custom.txt          |    11 +
 .../schema/Filter.ExtractStyleBlocks.Escaping.txt  |    14 +
 .../schema/Filter.ExtractStyleBlocks.Scope.txt     |    29 +
 .../schema/Filter.ExtractStyleBlocks.TidyImpl.txt  |    16 +
 .../schema/Filter.ExtractStyleBlocks.txt           |    74 +
 .../ConfigSchema/schema/Filter.YouTube.txt         |    16 +
 .../ConfigSchema/schema/HTML.Allowed.txt           |    25 +
 .../ConfigSchema/schema/HTML.AllowedAttributes.txt |    19 +
 .../ConfigSchema/schema/HTML.AllowedComments.txt   |    10 +
 .../schema/HTML.AllowedCommentsRegexp.txt          |    15 +
 .../ConfigSchema/schema/HTML.AllowedElements.txt   |    23 +
 .../ConfigSchema/schema/HTML.AllowedModules.txt    |    20 +
 .../schema/HTML.Attr.Name.UseCDATA.txt             |    11 +
 .../ConfigSchema/schema/HTML.BlockWrapper.txt      |    18 +
 .../ConfigSchema/schema/HTML.CoreModules.txt       |    23 +
 .../ConfigSchema/schema/HTML.CustomDoctype.txt     |     9 +
 .../ConfigSchema/schema/HTML.DefinitionID.txt      |    33 +
 .../ConfigSchema/schema/HTML.DefinitionRev.txt     |    16 +
 .../ConfigSchema/schema/HTML.Doctype.txt           |    11 +
 .../schema/HTML.FlashAllowFullScreen.txt           |    11 +
 .../schema/HTML.ForbiddenAttributes.txt            |    21 +
 .../ConfigSchema/schema/HTML.ForbiddenElements.txt |    20 +
 .../ConfigSchema/schema/HTML.MaxImgLength.txt      |    14 +
 .../ConfigSchema/schema/HTML.Nofollow.txt          |     7 +
 .../ConfigSchema/schema/HTML.Parent.txt            |    12 +
 .../ConfigSchema/schema/HTML.Proprietary.txt       |    12 +
 .../ConfigSchema/schema/HTML.SafeEmbed.txt         |    13 +
 .../ConfigSchema/schema/HTML.SafeIframe.txt        |    13 +
 .../ConfigSchema/schema/HTML.SafeObject.txt        |    13 +
 .../ConfigSchema/schema/HTML.SafeScripting.txt     |    10 +
 .../ConfigSchema/schema/HTML.Strict.txt            |     9 +
 .../ConfigSchema/schema/HTML.TargetBlank.txt       |     8 +
 .../ConfigSchema/schema/HTML.TidyAdd.txt           |     8 +
 .../ConfigSchema/schema/HTML.TidyLevel.txt         |    24 +
 .../ConfigSchema/schema/HTML.TidyRemove.txt        |     8 +
 .../ConfigSchema/schema/HTML.Trusted.txt           |     9 +
 .../ConfigSchema/schema/HTML.XHTML.txt             |    11 +
 .../schema/Output.CommentScriptContents.txt        |    10 +
 .../ConfigSchema/schema/Output.FixInnerHTML.txt    |    15 +
 .../ConfigSchema/schema/Output.FlashCompat.txt     |    11 +
 .../ConfigSchema/schema/Output.Newline.txt         |    13 +
 .../ConfigSchema/schema/Output.SortAttr.txt        |    14 +
 .../ConfigSchema/schema/Output.TidyFormat.txt      |    25 +
 .../ConfigSchema/schema/Test.ForceNoIconv.txt      |     7 +
 .../ConfigSchema/schema/URI.AllowedSchemes.txt     |    17 +
 .../HTMLPurifier/ConfigSchema/schema/URI.Base.txt  |    17 +
 .../ConfigSchema/schema/URI.DefaultScheme.txt      |    10 +
 .../ConfigSchema/schema/URI.DefinitionID.txt       |    11 +
 .../ConfigSchema/schema/URI.DefinitionRev.txt      |    11 +
 .../ConfigSchema/schema/URI.Disable.txt            |    14 +
 .../ConfigSchema/schema/URI.DisableExternal.txt    |    11 +
 .../schema/URI.DisableExternalResources.txt        |    13 +
 .../ConfigSchema/schema/URI.DisableResources.txt   |    15 +
 .../HTMLPurifier/ConfigSchema/schema/URI.Host.txt  |    19 +
 .../ConfigSchema/schema/URI.HostBlacklist.txt      |     9 +
 .../ConfigSchema/schema/URI.MakeAbsolute.txt       |    13 +
 .../HTMLPurifier/ConfigSchema/schema/URI.Munge.txt |    83 +
 .../ConfigSchema/schema/URI.MungeResources.txt     |    17 +
 .../ConfigSchema/schema/URI.MungeSecretKey.txt     |    30 +
 .../schema/URI.OverrideAllowedSchemes.txt          |     9 +
 .../ConfigSchema/schema/URI.SafeIframeRegexp.txt   |    22 +
 .../HTMLPurifier/ConfigSchema/schema/info.ini      |     3 +
 .../htmlpurifier/HTMLPurifier/ContentSets.php      |   155 +
 .../htmlpurifier/HTMLPurifier/Context.php          |    82 +
 .../htmlpurifier/HTMLPurifier/Definition.php       |    50 +
 .../htmlpurifier/HTMLPurifier/DefinitionCache.php  |   108 +
 .../HTMLPurifier/DefinitionCache/Decorator.php     |    62 +
 .../DefinitionCache/Decorator/Cleanup.php          |    43 +
 .../DefinitionCache/Decorator/Memory.php           |    46 +
 .../DefinitionCache/Decorator/Template.php.in      |    47 +
 .../HTMLPurifier/DefinitionCache/Null.php          |    39 +
 .../HTMLPurifier/DefinitionCache/Serializer.php    |   191 +
 ...,13c3933dafea215dd3045ef97f3cf8230304e6ae,1.ser |   Bin 0 -> 16557 bytes
 ...,48c66b36f832d31ddeb0bd1df231a8b404e9ce91,1.ser |   Bin 0 -> 11257 bytes
 ...,bd08c5afbc77123dbd4e9e026a723c450e9f844b,1.ser |   Bin 0 -> 93569 bytes
 ...,c025fd58185e35b4d1117abfd9869ab4087f03ce,1.ser |   Bin 0 -> 6038 bytes
 .../HTMLPurifier/DefinitionCache/Serializer/README |     3 +
 ...,10a7f1a4d1fdb0b461bc5b6e5a9c2f9a4a0ec765,1.ser |   Bin 0 -> 604 bytes
 ...,8d03c8ec0e84e7feb92afd4c0f1735841b5fdacf,1.ser |   Bin 0 -> 516 bytes
 .../HTMLPurifier/DefinitionCacheFactory.php        |    91 +
 .../htmlpurifier/HTMLPurifier/Doctype.php          |    60 +
 .../htmlpurifier/HTMLPurifier/DoctypeRegistry.php  |   103 +
 .../htmlpurifier/HTMLPurifier/ElementDef.php       |   195 +
 .../htmlpurifier/HTMLPurifier/Encoder.php          |   545 +
 .../htmlpurifier/HTMLPurifier/EntityLookup.php     |    44 +
 .../HTMLPurifier/EntityLookup/entities.ser         |     1 +
 .../htmlpurifier/HTMLPurifier/EntityParser.php     |   144 +
 .../htmlpurifier/HTMLPurifier/ErrorCollector.php   |   209 +
 .../htmlpurifier/HTMLPurifier/ErrorStruct.php      |    60 +
 .../htmlpurifier/HTMLPurifier/Exception.php        |    12 +
 .../libraries/htmlpurifier/HTMLPurifier/Filter.php |    46 +
 .../HTMLPurifier/Filter/ExtractStyleBlocks.php     |   289 +
 .../htmlpurifier/HTMLPurifier/Filter/YouTube.php   |    39 +
 .../htmlpurifier/HTMLPurifier/Generator.php        |   254 +
 .../htmlpurifier/HTMLPurifier/HTMLDefinition.php   |   425 +
 .../htmlpurifier/HTMLPurifier/HTMLModule.php       |   244 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php   |    31 +
 .../HTMLPurifier/HTMLModule/CommonAttributes.php   |    26 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/Edit.php  |    38 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/Forms.php |   119 +
 .../HTMLPurifier/HTMLModule/Hypertext.php          |    31 +
 .../HTMLPurifier/HTMLModule/Iframe.php             |    38 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/Image.php |    40 +
 .../HTMLPurifier/HTMLModule/Legacy.php             |   159 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/List.php  |    43 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/Name.php  |    21 +
 .../HTMLPurifier/HTMLModule/Nofollow.php           |    19 +
 .../HTMLModule/NonXMLCommonAttributes.php          |    14 +
 .../HTMLPurifier/HTMLModule/Object.php             |    47 +
 .../HTMLPurifier/HTMLModule/Presentation.php       |    36 +
 .../HTMLPurifier/HTMLModule/Proprietary.php        |    33 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php  |    27 +
 .../HTMLPurifier/HTMLModule/SafeEmbed.php          |    34 +
 .../HTMLPurifier/HTMLModule/SafeObject.php         |    52 +
 .../HTMLPurifier/HTMLModule/SafeScripting.php      |    37 +
 .../HTMLPurifier/HTMLModule/Scripting.php          |    54 +
 .../HTMLPurifier/HTMLModule/StyleAttribute.php     |    24 +
 .../HTMLPurifier/HTMLModule/Tables.php             |    69 +
 .../HTMLPurifier/HTMLModule/Target.php             |    23 +
 .../HTMLPurifier/HTMLModule/TargetBlank.php        |    19 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/Text.php  |    71 +
 .../htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php  |   207 +
 .../HTMLPurifier/HTMLModule/Tidy/Name.php          |    24 +
 .../HTMLPurifier/HTMLModule/Tidy/Proprietary.php   |    24 +
 .../HTMLPurifier/HTMLModule/Tidy/Strict.php        |    21 +
 .../HTMLPurifier/HTMLModule/Tidy/Transitional.php  |     9 +
 .../HTMLPurifier/HTMLModule/Tidy/XHTML.php         |    17 +
 .../HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php |   161 +
 .../HTMLModule/XMLCommonAttributes.php             |    14 +
 .../HTMLPurifier/HTMLModuleManager.php             |   418 +
 .../htmlpurifier/HTMLPurifier/IDAccumulator.php    |    53 +
 .../htmlpurifier/HTMLPurifier/Injector.php         |   239 +
 .../HTMLPurifier/Injector/AutoParagraph.php        |   345 +
 .../HTMLPurifier/Injector/DisplayLinkURI.php       |    26 +
 .../htmlpurifier/HTMLPurifier/Injector/Linkify.php |    46 +
 .../HTMLPurifier/Injector/PurifierLinkify.php      |    45 +
 .../HTMLPurifier/Injector/RemoveEmpty.php          |    54 +
 .../Injector/RemoveSpansWithoutAttributes.php      |    60 +
 .../HTMLPurifier/Injector/SafeObject.php           |    91 +
 .../htmlpurifier/HTMLPurifier/Language.php         |   163 +
 .../HTMLPurifier/Language/classes/en-x-test.php    |    12 +
 .../HTMLPurifier/Language/messages/en-x-test.php   |    11 +
 .../Language/messages/en-x-testmini.php            |    12 +
 .../HTMLPurifier/Language/messages/en.php          |    63 +
 .../htmlpurifier/HTMLPurifier/LanguageFactory.php  |   198 +
 .../libraries/htmlpurifier/HTMLPurifier/Length.php |   115 +
 .../libraries/htmlpurifier/HTMLPurifier/Lexer.php  |   326 +
 .../htmlpurifier/HTMLPurifier/Lexer/DOMLex.php     |   243 +
 .../htmlpurifier/HTMLPurifier/Lexer/DirectLex.php  |   490 +
 .../htmlpurifier/HTMLPurifier/Lexer/PH5P.php       |  3904 +++++
 .../htmlpurifier/HTMLPurifier/PercentEncoder.php   |    98 +
 .../htmlpurifier/HTMLPurifier/Printer.php          |   176 +
 .../HTMLPurifier/Printer/CSSDefinition.php         |    38 +
 .../HTMLPurifier/Printer/ConfigForm.css            |    10 +
 .../HTMLPurifier/Printer/ConfigForm.js             |     5 +
 .../HTMLPurifier/Printer/ConfigForm.php            |   368 +
 .../HTMLPurifier/Printer/HTMLDefinition.php        |   272 +
 .../htmlpurifier/HTMLPurifier/PropertyList.php     |    86 +
 .../HTMLPurifier/PropertyListIterator.php          |    32 +
 .../htmlpurifier/HTMLPurifier/Strategy.php         |    26 +
 .../HTMLPurifier/Strategy/Composite.php            |    23 +
 .../htmlpurifier/HTMLPurifier/Strategy/Core.php    |    18 +
 .../HTMLPurifier/Strategy/FixNesting.php           |   346 +
 .../HTMLPurifier/Strategy/MakeWellFormed.php       |   532 +
 .../Strategy/RemoveForeignElements.php             |   188 +
 .../HTMLPurifier/Strategy/ValidateAttributes.php   |    39 +
 .../htmlpurifier/HTMLPurifier/StringHash.php       |    39 +
 .../htmlpurifier/HTMLPurifier/StringHashParser.php |   110 +
 .../htmlpurifier/HTMLPurifier/TagTransform.php     |    36 +
 .../HTMLPurifier/TagTransform/Font.php             |    98 +
 .../HTMLPurifier/TagTransform/Simple.php           |    35 +
 .../libraries/htmlpurifier/HTMLPurifier/Token.php  |    57 +
 .../htmlpurifier/HTMLPurifier/Token/Comment.php    |    22 +
 .../htmlpurifier/HTMLPurifier/Token/Empty.php      |    11 +
 .../htmlpurifier/HTMLPurifier/Token/End.php        |    19 +
 .../htmlpurifier/HTMLPurifier/Token/Start.php      |    11 +
 .../htmlpurifier/HTMLPurifier/Token/Tag.php        |    57 +
 .../htmlpurifier/HTMLPurifier/Token/Text.php       |    33 +
 .../htmlpurifier/HTMLPurifier/TokenFactory.php     |    94 +
 .../libraries/htmlpurifier/HTMLPurifier/URI.php    |   242 +
 .../htmlpurifier/HTMLPurifier/URIDefinition.php    |   103 +
 .../htmlpurifier/HTMLPurifier/URIFilter.php        |    67 +
 .../HTMLPurifier/URIFilter/DisableExternal.php     |    23 +
 .../URIFilter/DisableExternalResources.php         |    12 +
 .../HTMLPurifier/URIFilter/DisableResources.php    |    11 +
 .../HTMLPurifier/URIFilter/HostBlacklist.php       |    25 +
 .../HTMLPurifier/URIFilter/MakeAbsolute.php        |   114 +
 .../htmlpurifier/HTMLPurifier/URIFilter/Munge.php  |    53 +
 .../HTMLPurifier/URIFilter/SafeIframe.php          |    35 +
 .../htmlpurifier/HTMLPurifier/URIParser.php        |    70 +
 .../htmlpurifier/HTMLPurifier/URIScheme.php        |    95 +
 .../htmlpurifier/HTMLPurifier/URIScheme/data.php   |    98 +
 .../htmlpurifier/HTMLPurifier/URIScheme/file.php   |    32 +
 .../htmlpurifier/HTMLPurifier/URIScheme/ftp.php    |    42 +
 .../htmlpurifier/HTMLPurifier/URIScheme/http.php   |    19 +
 .../htmlpurifier/HTMLPurifier/URIScheme/https.php  |    13 +
 .../htmlpurifier/HTMLPurifier/URIScheme/mailto.php |    27 +
 .../htmlpurifier/HTMLPurifier/URIScheme/news.php   |    22 +
 .../htmlpurifier/HTMLPurifier/URIScheme/nntp.php   |    19 +
 .../HTMLPurifier/URISchemeRegistry.php             |    68 +
 .../htmlpurifier/HTMLPurifier/UnitConverter.php    |   254 +
 .../htmlpurifier/HTMLPurifier/VarParser.php        |   154 +
 .../HTMLPurifier/VarParser/Flexible.php            |   103 +
 .../htmlpurifier/HTMLPurifier/VarParser/Native.php |    26 +
 .../HTMLPurifier/VarParserException.php            |    11 +
 application/libraries/htmlpurifier/INSTALL         |   374 +
 application/libraries/htmlpurifier/LICENSE         |   504 +
 application/libraries/htmlpurifier/NEWS            |  1056 ++
 application/libraries/jwysiwyg/common.php          |   250 +
 application/libraries/jwysiwyg/handlers.php        |   230 +
 application/logs/.gitignore                        |     0
 application/models/actions.php                     |    23 +
 application/models/actions_log.php                 |    21 +
 application/models/alert.php                       |   220 +
 application/models/alert_category.php              |    23 +
 application/models/alert_sent.php                  |    23 +
 application/models/api_banned.php                  |    21 +
 application/models/api_log.php                     |    21 +
 application/models/api_settings.php                |    21 +
 application/models/badge.php                       |    97 +
 application/models/badge_user.php                  |    24 +
 application/models/category.php                    |   289 +
 application/models/category_lang.php               |   134 +
 application/models/city.php                        |    31 +
 application/models/cluster.php                     |    23 +
 application/models/cluster_incident.php            |    23 +
 application/models/comment.php                     |    14 +
 application/models/country.php                     |   104 +
 application/models/externalapp.php                 |    22 +
 application/models/feed.php                        |    61 +
 application/models/feed_item.php                   |    20 +
 application/models/feed_item_category.php          |    48 +
 application/models/form.php                        |    53 +
 application/models/form_field.php                  |   140 +
 application/models/form_field_option.php           |    23 +
 application/models/form_response.php               |    23 +
 application/models/geometry.php                    |    23 +
 application/models/incident.php                    |   591 +
 application/models/incident_category.php           |    48 +
 application/models/incident_lang.php               |    24 +
 application/models/incident_person.php             |    23 +
 application/models/layer.php                       |    90 +
 application/models/level.php                       |    73 +
 application/models/location.php                    |    48 +
 application/models/media.php                       |    65 +
 application/models/message.php                     |    24 +
 application/models/openid.php                      |    13 +
 application/models/page.php                        |    62 +
 application/models/permission.php                  |    31 +
 application/models/plugin.php                      |    21 +
 application/models/private_message.php             |    23 +
 application/models/rating.php                      |    23 +
 application/models/reporter.php                    |    35 +
 application/models/role.php                        |    22 +
 application/models/scheduler.php                   |    23 +
 application/models/scheduler_log.php               |    23 +
 application/models/service.php                     |    41 +
 application/models/settings.php                    |   214 +
 application/models/stats.php                       |   541 +
 application/models/twitter.php                     |    23 +
 application/models/user.php                        |   381 +
 application/models/verify.php                      |    24 +
 application/views/admin/addons/addons_js.php       |    51 +
 application/views/admin/addons/plugin_settings.php |    70 +
 application/views/admin/addons/plugins.php         |   156 +
 application/views/admin/addons/themes.php          |   101 +
 application/views/admin/comments/comments_js.php   |    54 +
 application/views/admin/comments/main.php          |   169 +
 application/views/admin/current_version.php        |    31 +
 application/views/admin/dashboard/main.php         |   195 +
 application/views/admin/layout.php                 |   120 +
 .../views/admin/manage/actions/actions_js.php      |   519 +
 application/views/admin/manage/actions/main.php    |   598 +
 .../views/admin/manage/alerts/alerts_js.php        |    52 +
 application/views/admin/manage/alerts/main.php     |   134 +
 .../views/admin/manage/badges/badges_js.php        |    56 +
 application/views/admin/manage/badges/main.php     |   174 +
 .../views/admin/manage/blocks/blocks_js.php        |    55 +
 application/views/admin/manage/blocks/main.php     |   122 +
 .../admin/manage/categories/categories_js.php      |   102 +
 application/views/admin/manage/categories/main.php |   294 +
 application/views/admin/manage/feeds/feeds_js.php  |    53 +
 application/views/admin/manage/feeds/items.php     |   142 +
 application/views/admin/manage/feeds/items_js.php  |    67 +
 application/views/admin/manage/feeds/main.php      |   148 +
 application/views/admin/manage/forms/forms_js.php  |   151 +
 application/views/admin/manage/forms/main.php      |   215 +
 .../views/admin/manage/layers/layers_js.php        |    40 +
 application/views/admin/manage/layers/main.php     |   196 +
 application/views/admin/manage/pages/main.php      |   156 +
 application/views/admin/manage/pages/pages_js.php  |   154 +
 application/views/admin/manage/publiclisting.php   |    11 +
 application/views/admin/manage/scheduler/log.php   |    96 +
 application/views/admin/manage/scheduler/main.php  |   225 +
 .../views/admin/manage/scheduler/scheduler_js.php  |    27 +
 application/views/admin/messages/main.php          |   251 +
 application/views/admin/messages/messages_js.php   |   111 +
 .../views/admin/messages/messages_twitter.php      |   137 +
 application/views/admin/profile.php                |    92 +
 application/views/admin/reporters/main.php         |   247 +
 application/views/admin/reporters/reporters_js.php |   211 +
 application/views/admin/reports/delete_all.php     |    41 +
 application/views/admin/reports/delete_all_js.php  |    21 +
 application/views/admin/reports/download.php       |   111 +
 application/views/admin/reports/download_js.php    |   109 +
 application/views/admin/reports/edit.php           |   520 +
 application/views/admin/reports/main.php           |   357 +
 application/views/admin/reports/reports_extra.php  |   164 +
 application/views/admin/reports/reports_js.php     |   211 +
 application/views/admin/reports/search_form.php    |    82 +
 application/views/admin/reports/upload.php         |    81 +
 application/views/admin/reports/upload_success.php |    40 +
 application/views/admin/security_info.php          |    50 +
 application/views/admin/settings/api/api_js.php    |    43 +
 application/views/admin/settings/api/banned.php    |   116 +
 application/views/admin/settings/api/banned_js.php |    53 +
 application/views/admin/settings/api/logs.php      |   143 +
 application/views/admin/settings/api/logs_js.php   |    53 +
 application/views/admin/settings/api/main.php      |    99 +
 application/views/admin/settings/cleanurl.php      |    77 +
 application/views/admin/settings/email.php         |   129 +
 application/views/admin/settings/email_js.php      |    13 +
 .../settings/externalapps/externalapps_js.php      |    32 +
 .../views/admin/settings/externalapps/main.php     |   138 +
 application/views/admin/settings/facebook/main.php |    77 +
 application/views/admin/settings/https.php         |    77 +
 application/views/admin/settings/main.php          |   233 +
 application/views/admin/settings/settings_js.php   |   145 +
 application/views/admin/settings/site.php          |   237 +
 application/views/admin/settings/site_js.php       |    71 +
 application/views/admin/settings/sms.php           |    94 +
 application/views/admin/settings/twitter/main.php  |   115 +
 .../views/admin/settings/twitter/twitter_js.php    |    13 +
 application/views/admin/stats/country.php          |   103 +
 application/views/admin/stats/hits.php             |   116 +
 application/views/admin/stats/impact.php           |    61 +
 application/views/admin/stats/main.php             |    38 +
 application/views/admin/stats/punchcard.php        |    37 +
 application/views/admin/stats/reports.php          |   116 +
 application/views/admin/stats/stats_js.php         |    58 +
 application/views/admin/upgrade/upgrade.php        |    90 +
 .../views/admin/upgrade/upgrade_database.php       |    42 +
 application/views/admin/upgrade/upgrade_js.php     |    20 +
 application/views/admin/upgrade/upgrade_status.php |    26 +
 .../views/admin/upgrade/upgrade_status_js.php      |    34 +
 application/views/admin/upgrade/upgrade_table.php  |    33 +
 application/views/admin/users/edit.php             |   148 +
 application/views/admin/users/main.php             |   133 +
 application/views/admin/users/roles.php            |   182 +
 application/views/admin/users/roles_js.php         |    38 +
 application/views/admin/users/users_js.php         |    42 +
 application/views/admin/utils_js.php               |    44 +
 application/views/admin/version_sync.php           |    38 +
 application/views/error.php                        |    15 +
 application/views/help_view_js.php                 |    64 +
 application/views/layout.php                       |     3 +
 application/views/map_common_js.php                |   261 +
 application/views/members/alerts.php               |   126 +
 application/views/members/alerts_js.php            |    77 +
 application/views/members/dashboard.php            |   276 +
 application/views/members/layout.php               |    79 +
 application/views/members/private.php              |   173 +
 application/views/members/private_js.php           |    41 +
 application/views/members/private_send.php         |    74 +
 application/views/members/private_send_js.php      |    14 +
 application/views/members/profile.php              |   136 +
 application/views/members/reports.php              |   178 +
 application/views/members/reports_edit.php         |   437 +
 application/views/new_password.php                 |    44 +
 application/views/pagination/classic.php           |    51 +
 application/views/reset_password.php               |    79 +
 application/views/reset_password_js.php            |    22 +
 application/views/riverid.php                      |     1 +
 debian/changelog                                   |     5 -
 debian/compat                                      |     1 -
 debian/control                                     |    22 -
 debian/copyright                                   |    15 -
 debian/rules                                       |    23 -
 debian/source/format                               |     1 -
 debian/watch                                       |     3 -
 favicon.ico                                        |   Bin 0 -> 1150 bytes
 index.php                                          |   174 +
 installer/index.php                                |    33 +
 installer/mod_rewrite/.htaccess                    |     2 +
 installer/mod_rewrite/test.php                     |     3 +
 installer/pages/adminpassword.php                  |    62 +
 installer/pages/database.php                       |    76 +
 installer/pages/email.php                          |    90 +
 installer/pages/finish.php                         |    34 +
 installer/pages/general.php                        |    88 +
 installer/pages/header.php                         |    10 +
 installer/pages/main.php                           |    25 +
 installer/pages/map.php                            |    85 +
 installer/pages/requirements.php                   |   106 +
 installer/utils.php                                |   132 +
 installer/wizard.php                               |  1129 ++
 maintenance_off.php                                |     1 +
 media/css/admin.css                                |  2641 ++++
 media/css/colorpicker.css                          |   162 +
 media/css/error.css                                |   174 +
 media/css/global.css                               |   185 +
 media/css/ie6.css                                  |    10 +
 media/css/installer.css                            |   210 +
 media/css/jquery-ui-themeroller.css                |   406 +
 media/css/jquery.autocomplete.css                  |    48 +
 media/css/jquery.hovertip-1.0.css                  |    15 +
 media/css/jquery.jqplot.min.css                    |     1 +
 media/css/jquery.treeview.css                      |    70 +
 media/css/login.css                                |   283 +
 media/css/openid.css                               |    87 +
 media/css/openlayers.css                           |   585 +
 media/css/picbox/closebutton.png                   |   Bin 0 -> 742 bytes
 media/css/picbox/loading.gif                       |   Bin 0 -> 6820 bytes
 media/css/picbox/navbtns.png                       |   Bin 0 -> 21061 bytes
 media/css/picbox/picbox.css                        |   108 +
 media/css/readme.css                               |    40 +
 media/img/admin/alerts-icon.png                    |   Bin 0 -> 1316 bytes
 media/img/admin/body-bg.gif                        |   Bin 0 -> 47 bytes
 media/img/admin/border.gif                         |   Bin 0 -> 44 bytes
 media/img/admin/btn-cancel.gif                     |   Bin 0 -> 407 bytes
 media/img/admin/btn-download.gif                   |   Bin 0 -> 559 bytes
 media/img/admin/btn-get-api-key.gif                |   Bin 0 -> 1071 bytes
 media/img/admin/btn-save-and-close.gif             |   Bin 0 -> 542 bytes
 media/img/admin/btn-save-report.gif                |   Bin 0 -> 524 bytes
 media/img/admin/btn-save-settings.gif              |   Bin 0 -> 571 bytes
 media/img/admin/btn-save.gif                       |   Bin 0 -> 362 bytes
 media/img/admin/btn-send.gif                       |   Bin 0 -> 312 bytes
 media/img/admin/category-icon.gif                  |   Bin 0 -> 653 bytes
 media/img/admin/checkins-icon.png                  |   Bin 0 -> 968 bytes
 media/img/admin/content-bg.gif                     |   Bin 0 -> 223 bytes
 media/img/admin/dots.gif                           |   Bin 0 -> 43 bytes
 media/img/admin/download_frontline_engine.gif      |   Bin 0 -> 3681 bytes
 media/img/admin/drag.gif                           |   Bin 0 -> 339 bytes
 media/img/admin/footer-bg.jpg                      |   Bin 0 -> 348 bytes
 media/img/admin/icon-mail.gif                      |   Bin 0 -> 341 bytes
 media/img/admin/icon-none.gif                      |   Bin 0 -> 135 bytes
 media/img/admin/icon-ok.gif                        |   Bin 0 -> 98 bytes
 media/img/admin/icon-phone.gif                     |   Bin 0 -> 166 bytes
 media/img/admin/icon-rss.gif                       |   Bin 0 -> 550 bytes
 media/img/admin/icon-twitter.gif                   |   Bin 0 -> 644 bytes
 media/img/admin/icon-zip.gif                       |   Bin 0 -> 235 bytes
 media/img/admin/icon_sprite.png                    |   Bin 0 -> 1629 bytes
 media/img/admin/img-graph.gif                      |   Bin 0 -> 13845 bytes
 media/img/admin/img-map-zoom.gif                   |   Bin 0 -> 2163 bytes
 media/img/admin/img-map.gif                        |   Bin 0 -> 48408 bytes
 media/img/admin/locations-icon.gif                 |   Bin 0 -> 888 bytes
 media/img/admin/logo.gif                           |   Bin 0 -> 4219 bytes
 media/img/admin/logo_header.gif                    |   Bin 0 -> 2287 bytes
 media/img/admin/logo_login.gif                     |   Bin 0 -> 4587 bytes
 media/img/admin/media-icon.gif                     |   Bin 0 -> 798 bytes
 media/img/admin/messages-icon.gif                  |   Bin 0 -> 827 bytes
 media/img/admin/nairobi_google.gif                 |   Bin 0 -> 34464 bytes
 media/img/admin/nairobi_msn.gif                    |   Bin 0 -> 47229 bytes
 media/img/admin/nairobi_osm.gif                    |   Bin 0 -> 43143 bytes
 media/img/admin/nairobi_yahoo.gif                  |   Bin 0 -> 14489 bytes
 media/img/admin/none.gif                           |   Bin 0 -> 43 bytes
 media/img/admin/report-icon.gif                    |   Bin 0 -> 779 bytes
 media/img/admin/separator-1.gif                    |   Bin 0 -> 47 bytes
 media/img/admin/separator-2.gif                    |   Bin 0 -> 45 bytes
 media/img/admin/separator.gif                      |   Bin 0 -> 46 bytes
 media/img/admin/sharing_down.gif                   |   Bin 0 -> 989 bytes
 media/img/admin/sharing_down_gray.gif              |   Bin 0 -> 1031 bytes
 media/img/admin/sharing_up.gif                     |   Bin 0 -> 954 bytes
 media/img/admin/sharing_up_gray.gif                |   Bin 0 -> 1005 bytes
 media/img/admin/top-separator.gif                  |   Bin 0 -> 66 bytes
 media/img/admin/votes-icon.png                     |   Bin 0 -> 867 bytes
 media/img/arrow-down.gif                           |   Bin 0 -> 50 bytes
 media/img/arrow-play.gif                           |   Bin 0 -> 57 bytes
 media/img/arrow.gif                                |   Bin 0 -> 49 bytes
 media/img/badge_packs/Locations/china.png          |   Bin 0 -> 9405 bytes
 media/img/badge_packs/Locations/france.png         |   Bin 0 -> 5992 bytes
 media/img/badge_packs/Locations/germany.png        |   Bin 0 -> 8478 bytes
 media/img/badge_packs/Locations/greece.png         |   Bin 0 -> 9273 bytes
 media/img/badge_packs/Locations/italy.png          |   Bin 0 -> 8817 bytes
 media/img/badge_packs/Locations/kenya.png          |   Bin 0 -> 6054 bytes
 media/img/badge_packs/Locations/malaysia.png       |   Bin 0 -> 7861 bytes
 media/img/badge_packs/Locations/mexico.png         |   Bin 0 -> 10268 bytes
 media/img/badge_packs/Locations/spain.png          |   Bin 0 -> 9802 bytes
 media/img/badge_packs/Locations/turkey.png         |   Bin 0 -> 9256 bytes
 media/img/badge_packs/Locations/unitedkingdom.png  |   Bin 0 -> 9209 bytes
 media/img/badge_packs/Locations/usa.png            |   Bin 0 -> 6501 bytes
 media/img/badge_packs/Ushahidi/badge_1.png         |   Bin 0 -> 1933 bytes
 media/img/badge_packs/Ushahidi/badge_10.png        |   Bin 0 -> 2895 bytes
 media/img/badge_packs/Ushahidi/badge_11.png        |   Bin 0 -> 2051 bytes
 media/img/badge_packs/Ushahidi/badge_12.png        |   Bin 0 -> 2159 bytes
 media/img/badge_packs/Ushahidi/badge_13.png        |   Bin 0 -> 2264 bytes
 media/img/badge_packs/Ushahidi/badge_14.png        |   Bin 0 -> 2476 bytes
 media/img/badge_packs/Ushahidi/badge_15.png        |   Bin 0 -> 2451 bytes
 media/img/badge_packs/Ushahidi/badge_16.png        |   Bin 0 -> 2474 bytes
 media/img/badge_packs/Ushahidi/badge_17.png        |   Bin 0 -> 2678 bytes
 media/img/badge_packs/Ushahidi/badge_18.png        |   Bin 0 -> 2481 bytes
 media/img/badge_packs/Ushahidi/badge_19.png        |   Bin 0 -> 3118 bytes
 media/img/badge_packs/Ushahidi/badge_2.png         |   Bin 0 -> 2620 bytes
 media/img/badge_packs/Ushahidi/badge_20.png        |   Bin 0 -> 2865 bytes
 media/img/badge_packs/Ushahidi/badge_21.png        |   Bin 0 -> 3126 bytes
 media/img/badge_packs/Ushahidi/badge_22.png        |   Bin 0 -> 3829 bytes
 media/img/badge_packs/Ushahidi/badge_23.png        |   Bin 0 -> 4962 bytes
 media/img/badge_packs/Ushahidi/badge_24.png        |   Bin 0 -> 2700 bytes
 media/img/badge_packs/Ushahidi/badge_25.png        |   Bin 0 -> 1732 bytes
 media/img/badge_packs/Ushahidi/badge_26.png        |   Bin 0 -> 2800 bytes
 media/img/badge_packs/Ushahidi/badge_27.png        |   Bin 0 -> 4498 bytes
 media/img/badge_packs/Ushahidi/badge_3.png         |   Bin 0 -> 2346 bytes
 media/img/badge_packs/Ushahidi/badge_4.png         |   Bin 0 -> 2469 bytes
 media/img/badge_packs/Ushahidi/badge_5.png         |   Bin 0 -> 2484 bytes
 media/img/badge_packs/Ushahidi/badge_6.png         |   Bin 0 -> 2809 bytes
 media/img/badge_packs/Ushahidi/badge_7.png         |   Bin 0 -> 2789 bytes
 media/img/badge_packs/Ushahidi/badge_8.png         |   Bin 0 -> 3308 bytes
 media/img/badge_packs/Ushahidi/badge_9.png         |   Bin 0 -> 2991 bytes
 media/img/big-block-bg.gif                         |   Bin 0 -> 73 bytes
 media/img/big-block-bottom.gif                     |   Bin 0 -> 279 bytes
 media/img/big-block-top.gif                        |   Bin 0 -> 3376 bytes
 media/img/big-map.gif                              |   Bin 0 -> 65420 bytes
 media/img/bkg_login.gif                            |   Bin 0 -> 101570 bytes
 media/img/bkg_login.jpg                            |   Bin 0 -> 18668 bytes
 media/img/btn-blue.gif                             |   Bin 0 -> 834 bytes
 media/img/btn-gray.gif                             |   Bin 0 -> 1482 bytes
 media/img/btn-next.gif                             |   Bin 0 -> 1821 bytes
 media/img/btn-prev.gif                             |   Bin 0 -> 1822 bytes
 media/img/btn_bkg_g.gif                            |   Bin 0 -> 945 bytes
 media/img/category-filter-list.gif                 |   Bin 0 -> 547 bytes
 media/img/category-item-active.gif                 |   Bin 0 -> 1130 bytes
 media/img/color_icon.php                           |    48 +
 media/img/colorpicker/blank.gif                    |   Bin 0 -> 49 bytes
 media/img/colorpicker/colorpicker_background.png   |   Bin 0 -> 1897 bytes
 media/img/colorpicker/colorpicker_hex.png          |   Bin 0 -> 532 bytes
 media/img/colorpicker/colorpicker_hsb_b.png        |   Bin 0 -> 970 bytes
 media/img/colorpicker/colorpicker_hsb_h.png        |   Bin 0 -> 1012 bytes
 media/img/colorpicker/colorpicker_hsb_s.png        |   Bin 0 -> 1171 bytes
 media/img/colorpicker/colorpicker_indic.gif        |   Bin 0 -> 86 bytes
 media/img/colorpicker/colorpicker_overlay.png      |   Bin 0 -> 10355 bytes
 media/img/colorpicker/colorpicker_rgb_b.png        |   Bin 0 -> 970 bytes
 media/img/colorpicker/colorpicker_rgb_g.png        |   Bin 0 -> 1069 bytes
 media/img/colorpicker/colorpicker_rgb_r.png        |   Bin 0 -> 1066 bytes
 media/img/colorpicker/colorpicker_select.gif       |   Bin 0 -> 78 bytes
 media/img/colorpicker/colorpicker_submit.png       |   Bin 0 -> 984 bytes
 media/img/colorpicker/custom_background.png        |   Bin 0 -> 1916 bytes
 media/img/colorpicker/custom_hex.png               |   Bin 0 -> 562 bytes
 media/img/colorpicker/custom_hsb_b.png             |   Bin 0 -> 1097 bytes
 media/img/colorpicker/custom_hsb_h.png             |   Bin 0 -> 970 bytes
 media/img/colorpicker/custom_hsb_s.png             |   Bin 0 -> 1168 bytes
 media/img/colorpicker/custom_indic.gif             |   Bin 0 -> 86 bytes
 media/img/colorpicker/custom_rgb_b.png             |   Bin 0 -> 1008 bytes
 media/img/colorpicker/custom_rgb_g.png             |   Bin 0 -> 1069 bytes
 media/img/colorpicker/custom_rgb_r.png             |   Bin 0 -> 1018 bytes
 media/img/colorpicker/custom_submit.png            |   Bin 0 -> 997 bytes
 media/img/colorpicker/select.png                   |   Bin 0 -> 506 bytes
 media/img/colorpicker/select2.png                  |   Bin 0 -> 518 bytes
 media/img/colorpicker/slider.png                   |   Bin 0 -> 315 bytes
 media/img/content-bottom.gif                       |   Bin 0 -> 256 bytes
 media/img/content-top.gif                          |   Bin 0 -> 743 bytes
 media/img/down.png                                 |   Bin 0 -> 554 bytes
 media/img/experimental.png                         |   Bin 0 -> 468 bytes
 media/img/f-logo.gif                               |   Bin 0 -> 1210 bytes
 media/img/f-nav-separator.gif                      |   Bin 0 -> 44 bytes
 media/img/filter-item-l.gif                        |   Bin 0 -> 98 bytes
 media/img/filter-item-r.gif                        |   Bin 0 -> 97 bytes
 media/img/flags/de_DE.png                          |   Bin 0 -> 545 bytes
 media/img/flags/en_US.png                          |   Bin 0 -> 609 bytes
 media/img/flags/es_ES.png                          |   Bin 0 -> 469 bytes
 media/img/flags/fa_IR.png                          |   Bin 0 -> 512 bytes
 media/img/flags/fr_FR.png                          |   Bin 0 -> 545 bytes
 media/img/flags/it_IT.png                          |   Bin 0 -> 420 bytes
 media/img/flags/nl_NL.png                          |   Bin 0 -> 453 bytes
 media/img/flags/pl_PL.png                          |   Bin 0 -> 374 bytes
 media/img/flags/pt_BR.png                          |   Bin 0 -> 593 bytes
 media/img/flags/pt_PT.png                          |   Bin 0 -> 554 bytes
 media/img/flags/ru_RU.png                          |   Bin 0 -> 420 bytes
 media/img/footer-bg.jpg                            |   Bin 0 -> 1450 bytes
 media/img/footer-bg2.jpg                           |   Bin 0 -> 668 bytes
 media/img/footer-logo.png                          |   Bin 0 -> 1488 bytes
 media/img/gal-big.gif                              |   Bin 0 -> 491 bytes
 media/img/gal-small.gif                            |   Bin 0 -> 97 bytes
 media/img/google-map.jpg                           |   Bin 0 -> 101159 bytes
 media/img/gray_down.png                            |   Bin 0 -> 466 bytes
 media/img/gray_up.png                              |   Bin 0 -> 473 bytes
 media/img/grey-box-bottom.gif                      |   Bin 0 -> 120 bytes
 media/img/grey-box-top.gif                         |   Bin 0 -> 120 bytes
 media/img/grey-btn-l.gif                           |   Bin 0 -> 1584 bytes
 media/img/grey-btn-r.gif                           |   Bin 0 -> 614 bytes
 media/img/ico-01.png                               |   Bin 0 -> 795 bytes
 media/img/ico-02.png                               |   Bin 0 -> 703 bytes
 media/img/ico-03.png                               |   Bin 0 -> 721 bytes
 media/img/ico-04.png                               |   Bin 0 -> 707 bytes
 media/img/ico-05.png                               |   Bin 0 -> 698 bytes
 media/img/ico-06.png                               |   Bin 0 -> 684 bytes
 media/img/ico-07.png                               |   Bin 0 -> 706 bytes
 media/img/ico-08.png                               |   Bin 0 -> 721 bytes
 media/img/ico-09.png                               |   Bin 0 -> 815 bytes
 media/img/ico-10.png                               |   Bin 0 -> 874 bytes
 media/img/ico-info.png                             |   Bin 0 -> 1714 bytes
 media/img/ico-orange.gif                           |   Bin 0 -> 114 bytes
 media/img/ico-red.gif                              |   Bin 0 -> 114 bytes
 media/img/ico-warning.png                          |   Bin 0 -> 1442 bytes
 media/img/icon-calendar.gif                        |   Bin 0 -> 673 bytes
 media/img/icon-feed.png                            |   Bin 0 -> 764 bytes
 media/img/icon-minus.gif                           |   Bin 0 -> 107 bytes
 media/img/icon-plus.gif                            |   Bin 0 -> 110 bytes
 media/img/icon-tick.png                            |   Bin 0 -> 624 bytes
 media/img/icon-warning.png                         |   Bin 0 -> 621 bytes
 media/img/icon_alert_big.gif                       |   Bin 0 -> 4356 bytes
 media/img/icon_sprite.png                          |   Bin 0 -> 1629 bytes
 media/img/image.png                                |   Bin 0 -> 665 bytes
 media/img/incident-pointer.jpg                     |   Bin 0 -> 588 bytes
 media/img/index.html                               |     1 +
 media/img/indicator.gif                            |   Bin 0 -> 1553 bytes
 media/img/install_bg-progress-active.gif           |   Bin 0 -> 476 bytes
 media/img/install_bg-progress-bar.gif              |   Bin 0 -> 856 bytes
 media/img/install_bg-progress-item-number.gif      |   Bin 0 -> 495 bytes
 media/img/lnk-info-l.gif                           |   Bin 0 -> 68 bytes
 media/img/lnk-info-r.gif                           |   Bin 0 -> 69 bytes
 media/img/loading.gif                              |   Bin 0 -> 10819 bytes
 media/img/loading_g.gif                            |   Bin 0 -> 673 bytes
 media/img/loading_g2.gif                           |   Bin 0 -> 6820 bytes
 media/img/loading_y.gif                            |   Bin 0 -> 847 bytes
 media/img/map.gif                                  |   Bin 0 -> 33834 bytes
 media/img/media-image.jpg                          |   Bin 0 -> 7964 bytes
 media/img/media-type-image.jpg                     |   Bin 0 -> 655 bytes
 media/img/media-type-video.jpg                     |   Bin 0 -> 606 bytes
 media/img/nav-active.gif                           |   Bin 0 -> 67 bytes
 media/img/nav-bg.gif                               |   Bin 0 -> 506 bytes
 media/img/nav-first.gif                            |   Bin 0 -> 515 bytes
 media/img/nav-last.gif                             |   Bin 0 -> 517 bytes
 media/img/nav-separator.gif                        |   Bin 0 -> 46 bytes
 media/img/nearby-incident-pointer.jpg              |   Bin 0 -> 540 bytes
 media/img/openid/openid-inputicon.gif              |   Bin 0 -> 237 bytes
 media/img/openid/openid-providers-en.png           |   Bin 0 -> 13991 bytes
 media/img/openid/openid-providers-ru.png           |   Bin 0 -> 15352 bytes
 media/img/openid/openid-providers-uk.png           |   Bin 0 -> 15352 bytes
 media/img/openlayers/404.png                       |   Bin 0 -> 126 bytes
 media/img/openlayers/LICENSE.txt                   |    25 +
 media/img/openlayers/add_point_off.png             |   Bin 0 -> 1616 bytes
 media/img/openlayers/add_point_on.png              |   Bin 0 -> 1464 bytes
 media/img/openlayers/blank.gif                     |   Bin 0 -> 42 bytes
 media/img/openlayers/close.gif                     |   Bin 0 -> 655 bytes
 media/img/openlayers/cloud-popup-relative.png      |   Bin 0 -> 4067 bytes
 media/img/openlayers/drag-rectangle-off.png        |   Bin 0 -> 1024 bytes
 media/img/openlayers/drag-rectangle-on.png         |   Bin 0 -> 1041 bytes
 media/img/openlayers/draw_line_off.png             |   Bin 0 -> 1567 bytes
 media/img/openlayers/draw_line_on.png              |   Bin 0 -> 1399 bytes
 media/img/openlayers/draw_point_off.png            |   Bin 0 -> 1612 bytes
 media/img/openlayers/draw_point_on.png             |   Bin 0 -> 1460 bytes
 media/img/openlayers/draw_polygon_off.png          |   Bin 0 -> 1546 bytes
 media/img/openlayers/draw_polygon_on.png           |   Bin 0 -> 1407 bytes
 media/img/openlayers/east-mini.png                 |   Bin 0 -> 342 bytes
 media/img/openlayers/editing_tool_bar.png          |   Bin 0 -> 3901 bytes
 media/img/openlayers/label-maximize.png            |   Bin 0 -> 418 bytes
 media/img/openlayers/layer-switcher-maximize.png   |   Bin 0 -> 405 bytes
 media/img/openlayers/layer-switcher-minimize.png   |   Bin 0 -> 220 bytes
 media/img/openlayers/loading.gif                   |   Bin 0 -> 150433 bytes
 media/img/openlayers/marker-blue.png               |   Bin 0 -> 758 bytes
 media/img/openlayers/marker-gold.png               |   Bin 0 -> 703 bytes
 media/img/openlayers/marker-green.png              |   Bin 0 -> 753 bytes
 media/img/openlayers/marker.png                    |   Bin 0 -> 601 bytes
 media/img/openlayers/measuring-stick-off.png       |   Bin 0 -> 3028 bytes
 media/img/openlayers/measuring-stick-on.png        |   Bin 0 -> 3725 bytes
 media/img/openlayers/move_feature_off.png          |   Bin 0 -> 1543 bytes
 media/img/openlayers/move_feature_on.png           |   Bin 0 -> 1379 bytes
 media/img/openlayers/navigation_history.png        |   Bin 0 -> 7021 bytes
 media/img/openlayers/north-mini.png                |   Bin 0 -> 378 bytes
 media/img/openlayers/overview_replacement.gif      |   Bin 0 -> 79 bytes
 media/img/openlayers/pan-panel-NOALPHA.png         |   Bin 0 -> 566 bytes
 media/img/openlayers/pan-panel.png                 |   Bin 0 -> 1287 bytes
 media/img/openlayers/pan_off.png                   |   Bin 0 -> 1696 bytes
 media/img/openlayers/pan_on.png                    |   Bin 0 -> 1568 bytes
 media/img/openlayers/panning-hand-off.png          |   Bin 0 -> 3511 bytes
 media/img/openlayers/panning-hand-on.png           |   Bin 0 -> 3565 bytes
 media/img/openlayers/remove_point_off.png          |   Bin 0 -> 1612 bytes
 media/img/openlayers/remove_point_on.png           |   Bin 0 -> 1464 bytes
 media/img/openlayers/ruler.png                     |   Bin 0 -> 1211 bytes
 media/img/openlayers/save_features_off.png         |   Bin 0 -> 357 bytes
 media/img/openlayers/save_features_on.png          |   Bin 0 -> 364 bytes
 media/img/openlayers/slider.png                    |   Bin 0 -> 247 bytes
 media/img/openlayers/south-mini.png                |   Bin 0 -> 373 bytes
 media/img/openlayers/view_next_off.png             |   Bin 0 -> 1644 bytes
 media/img/openlayers/view_next_on.png              |   Bin 0 -> 1686 bytes
 media/img/openlayers/view_previous_off.png         |   Bin 0 -> 1553 bytes
 media/img/openlayers/view_previous_on.png          |   Bin 0 -> 1592 bytes
 media/img/openlayers/west-mini.png                 |   Bin 0 -> 360 bytes
 media/img/openlayers/zoom-minus-mini.png           |   Bin 0 -> 291 bytes
 media/img/openlayers/zoom-panel-NOALPHA.png        |   Bin 0 -> 1173 bytes
 media/img/openlayers/zoom-panel.png                |   Bin 0 -> 1624 bytes
 media/img/openlayers/zoom-plus-mini.png            |   Bin 0 -> 386 bytes
 media/img/openlayers/zoom-world-mini.png           |   Bin 0 -> 882 bytes
 media/img/openlayers/zoombar.png                   |   Bin 0 -> 350 bytes
 media/img/pin_trans.png                            |   Bin 0 -> 753 bytes
 media/img/range-slider.jpg                         |   Bin 0 -> 25228 bytes
 media/img/red-btn-l.gif                            |   Bin 0 -> 1584 bytes
 media/img/red-btn-r.gif                            |   Bin 0 -> 614 bytes
 media/img/report-map.jpg                           |   Bin 0 -> 75386 bytes
 media/img/report-thumb-default.jpg                 |   Bin 0 -> 997 bytes
 media/img/slider-bg-1.png                          |   Bin 0 -> 204 bytes
 media/img/slider-bg-2.png                          |   Bin 0 -> 326 bytes
 media/img/slider-handle-inactive.gif               |   Bin 0 -> 258 bytes
 media/img/slider-handle.gif                        |   Bin 0 -> 176 bytes
 media/img/small-block-bg.gif                       |   Bin 0 -> 64 bytes
 media/img/small-block-bottom.gif                   |   Bin 0 -> 220 bytes
 media/img/small-block-top.gif                      |   Bin 0 -> 1681 bytes
 media/img/small-map.gif                            |   Bin 0 -> 27217 bytes
 media/img/spacer.gif                               |   Bin 0 -> 49 bytes
 media/img/submit-incident.jpg                      |   Bin 0 -> 630 bytes
 .../img/themeroller/ui-bg_flat_0_aaaaaa_40x100.png |   Bin 0 -> 180 bytes
 .../themeroller/ui-bg_flat_75_ffffff_40x100.png    |   Bin 0 -> 178 bytes
 .../themeroller/ui-bg_glass_55_fbf9ee_1x400.png    |   Bin 0 -> 120 bytes
 .../themeroller/ui-bg_glass_65_ffffff_1x400.png    |   Bin 0 -> 105 bytes
 .../themeroller/ui-bg_glass_75_dadada_1x400.png    |   Bin 0 -> 111 bytes
 .../themeroller/ui-bg_glass_75_e6e6e6_1x400.png    |   Bin 0 -> 110 bytes
 .../themeroller/ui-bg_glass_95_fef1ec_1x400.png    |   Bin 0 -> 119 bytes
 .../ui-bg_highlight-soft_75_cccccc_1x100.png       |   Bin 0 -> 101 bytes
 media/img/themeroller/ui-icons_222222_256x240.png  |   Bin 0 -> 4369 bytes
 media/img/themeroller/ui-icons_2e83ff_256x240.png  |   Bin 0 -> 4369 bytes
 media/img/themeroller/ui-icons_454545_256x240.png  |   Bin 0 -> 4369 bytes
 media/img/themeroller/ui-icons_888888_256x240.png  |   Bin 0 -> 4369 bytes
 media/img/themeroller/ui-icons_cd0a0a_256x240.png  |   Bin 0 -> 4369 bytes
 media/img/thumb-down.jpg                           |   Bin 0 -> 693 bytes
 media/img/thumb-up.jpg                             |   Bin 0 -> 685 bytes
 media/img/thumbs.jpg                               |   Bin 0 -> 590 bytes
 media/img/tooltip.gif                              |   Bin 0 -> 55 bytes
 media/img/treeview-default-line.gif                |   Bin 0 -> 1993 bytes
 media/img/treeview-default.gif                     |   Bin 0 -> 1222 bytes
 media/img/up.png                                   |   Bin 0 -> 551 bytes
 media/img/video.png                                |   Bin 0 -> 653 bytes
 media/js/OpenLayers.js                             |  1443 ++
 media/js/OpenStreetMap.js                          |   162 +
 media/js/admin.js                                  |    26 +
 media/js/bugs.js                                   |    51 +
 media/js/colorpicker.js                            |   484 +
 media/js/excanvas.min.js                           |     1 +
 media/js/excanvas.pack.js                          |     1 +
 media/js/global.js                                 |    34 +
 media/js/jqplot.barRenderer.min.js                 |    57 +
 media/js/jqplot.dateAxisRenderer.min.js            |    57 +
 media/js/jqplot.pointLabels.min.js                 |    57 +
 media/js/jquery.autocomplete.pack.js               |    12 +
 media/js/jquery.base64.js                          |   190 +
 media/js/jquery.bgiframe.min.js                    |    39 +
 media/js/jquery.datePicker.js                      |  1735 +++
 media/js/jquery.form.js                            |   601 +
 media/js/jquery.hovertip-1.0.js                    |    91 +
 media/js/jquery.jqplot.min.js                      |    57 +
 media/js/jquery.js                                 |     4 +
 media/js/jquery.pngFix.pack.js                     |    11 +
 media/js/jquery.tablednd_0_5.js                    |   382 +
 media/js/jquery.timeago.js                         |   147 +
 media/js/jquery.treeview.js                        |   251 +
 media/js/jquery.ui.min.js                          |    37 +
 media/js/jquery.validate.min.js                    |    16 +
 media/js/json2.js                                  |    25 +
 media/js/jwysiwyg/CHANGES.markdown                 |   164 +
 media/js/jwysiwyg/GPL-LICENSE.txt                  |   278 +
 media/js/jwysiwyg/MIT-LICENSE.txt                  |    20 +
 media/js/jwysiwyg/README.rst                       |   615 +
 media/js/jwysiwyg/ajax-loader.gif                  |   Bin 0 -> 1849 bytes
 media/js/jwysiwyg/controls/wysiwyg.colorpicker.js  |   278 +
 media/js/jwysiwyg/controls/wysiwyg.cssWrap.js      |   134 +
 media/js/jwysiwyg/controls/wysiwyg.image.js        |   285 +
 media/js/jwysiwyg/controls/wysiwyg.link.js         |   267 +
 media/js/jwysiwyg/controls/wysiwyg.table.js        |   129 +
 media/js/jwysiwyg/i18n/lang.ca.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.cs.js                  |   116 +
 media/js/jwysiwyg/i18n/lang.de.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.en.js                  |   117 +
 media/js/jwysiwyg/i18n/lang.es.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.fr.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.he.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.hr.js                  |    98 +
 media/js/jwysiwyg/i18n/lang.it.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.ja.js                  |    95 +
 media/js/jwysiwyg/i18n/lang.nb.js                  |   116 +
 media/js/jwysiwyg/i18n/lang.nl.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.pl.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.pt_br.js               |    94 +
 media/js/jwysiwyg/i18n/lang.ru.js                  |   117 +
 media/js/jwysiwyg/i18n/lang.se.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.sl.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.tr.js                  |    94 +
 media/js/jwysiwyg/i18n/lang.zh-cn.js               |    93 +
 media/js/jwysiwyg/jquery.wysiwyg.bg.png            |   Bin 0 -> 222 bytes
 media/js/jwysiwyg/jquery.wysiwyg.css               |    89 +
 media/js/jwysiwyg/jquery.wysiwyg.gif               |   Bin 0 -> 5243 bytes
 media/js/jwysiwyg/jquery.wysiwyg.jpg               |   Bin 0 -> 67241 bytes
 media/js/jwysiwyg/jquery.wysiwyg.js                |  2538 ++++
 media/js/jwysiwyg/jquery.wysiwyg.modal.css         |    62 +
 media/js/jwysiwyg/jquery.wysiwyg.no-alpha.gif      |   Bin 0 -> 5124 bytes
 media/js/jwysiwyg/jquery.wysiwyg.old-school.css    |    63 +
 media/js/jwysiwyg/jquery/jquery-1.3.2.js           |  4376 ++++++
 media/js/jwysiwyg/lib/jquery1.5.js                 |  8176 ++++++++++
 media/js/jwysiwyg/plugins/fileManager/icon.png     |   Bin 0 -> 413 bytes
 .../plugins/fileManager/images/application.png     |   Bin 0 -> 464 bytes
 .../jwysiwyg/plugins/fileManager/images/code.png   |   Bin 0 -> 603 bytes
 .../js/jwysiwyg/plugins/fileManager/images/css.png |   Bin 0 -> 618 bytes
 .../js/jwysiwyg/plugins/fileManager/images/db.png  |   Bin 0 -> 579 bytes
 .../plugins/fileManager/images/directory.png       |   Bin 0 -> 537 bytes
 .../js/jwysiwyg/plugins/fileManager/images/doc.png |   Bin 0 -> 651 bytes
 .../jwysiwyg/plugins/fileManager/images/file.png   |   Bin 0 -> 294 bytes
 .../jwysiwyg/plugins/fileManager/images/film.png   |   Bin 0 -> 653 bytes
 .../jwysiwyg/plugins/fileManager/images/flash.png  |   Bin 0 -> 582 bytes
 .../plugins/fileManager/images/folder_open.png     |   Bin 0 -> 583 bytes
 .../jwysiwyg/plugins/fileManager/images/html.png   |   Bin 0 -> 734 bytes
 .../jwysiwyg/plugins/fileManager/images/java.png   |   Bin 0 -> 633 bytes
 .../jwysiwyg/plugins/fileManager/images/linux.png  |   Bin 0 -> 668 bytes
 .../jwysiwyg/plugins/fileManager/images/mkdir.png  |   Bin 0 -> 668 bytes
 .../jwysiwyg/plugins/fileManager/images/music.png  |   Bin 0 -> 385 bytes
 .../js/jwysiwyg/plugins/fileManager/images/pdf.png |   Bin 0 -> 591 bytes
 .../js/jwysiwyg/plugins/fileManager/images/php.png |   Bin 0 -> 538 bytes
 .../plugins/fileManager/images/picture.png         |   Bin 0 -> 606 bytes
 .../js/jwysiwyg/plugins/fileManager/images/ppt.png |   Bin 0 -> 588 bytes
 .../plugins/fileManager/images/prev-directory.png  |   Bin 0 -> 404 bytes
 .../js/jwysiwyg/plugins/fileManager/images/psd.png |   Bin 0 -> 856 bytes
 .../jwysiwyg/plugins/fileManager/images/remove.png |   Bin 0 -> 715 bytes
 .../jwysiwyg/plugins/fileManager/images/rename.png |   Bin 0 -> 273 bytes
 .../jwysiwyg/plugins/fileManager/images/ruby.png   |   Bin 0 -> 626 bytes
 .../jwysiwyg/plugins/fileManager/images/script.png |   Bin 0 -> 859 bytes
 .../js/jwysiwyg/plugins/fileManager/images/txt.png |   Bin 0 -> 342 bytes
 .../jwysiwyg/plugins/fileManager/images/upload.png |   Bin 0 -> 658 bytes
 .../js/jwysiwyg/plugins/fileManager/images/xls.png |   Bin 0 -> 663 bytes
 .../js/jwysiwyg/plugins/fileManager/images/zip.png |   Bin 0 -> 386 bytes
 .../plugins/fileManager/wysiwyg.fileManager.css    |   113 +
 media/js/jwysiwyg/plugins/wysiwyg.autoload.js      |    61 +
 media/js/jwysiwyg/plugins/wysiwyg.fileManager.js   |   574 +
 media/js/jwysiwyg/plugins/wysiwyg.fullscreen.js    |   141 +
 media/js/jwysiwyg/plugins/wysiwyg.i18n.js          |    99 +
 media/js/jwysiwyg/plugins/wysiwyg.rmFormat.js      |   357 +
 media/js/jwysiwyg/quirks/placeholder.mozilla.js    |   102 +
 .../jwysiwyg/quirks/toolbarHighlight.interval.js   |    28 +
 media/js/jwysiwyg/quirks/toolbarHighlight.keyup.js |    27 +
 media/js/login.js                                  |    42 +
 media/js/openid/jquery-1.2.6.min.js                |    32 +
 media/js/openid/openid-jquery-en.js                |   101 +
 media/js/openid/openid-jquery-ru.js                |    74 +
 media/js/openid/openid-jquery-uk.js                |    74 +
 media/js/openid/openid-jquery.js                   |   202 +
 media/js/picbox.js                                 |    22 +
 media/js/plugins.js                                |    63 +
 media/js/protochart/ProtoChart.js                  |  2653 ++++
 media/js/protochart/excanvas-compressed.js         |    19 +
 media/js/protochart/excanvas.js                    |   785 +
 media/js/protochart/prototype.js                   |  4221 ++++++
 media/js/raphael-ushahidi-impact.js                |   137 +
 media/js/raphael.js                                |     7 +
 media/js/selectToUISlider.jQuery.js                |   241 +
 media/js/tinymce/jquery.tinymce.js                 |     1 +
 media/js/tinymce/langs/en.js                       |   154 +
 media/js/tinymce/license.txt                       |   504 +
 media/js/tinymce/plugins/advhr/css/advhr.css       |     5 +
 media/js/tinymce/plugins/advhr/editor_plugin.js    |     1 +
 .../js/tinymce/plugins/advhr/editor_plugin_src.js  |    54 +
 media/js/tinymce/plugins/advhr/js/rule.js          |    43 +
 media/js/tinymce/plugins/advhr/langs/en_dlg.js     |     5 +
 media/js/tinymce/plugins/advhr/rule.htm            |    62 +
 media/js/tinymce/plugins/advimage/css/advimage.css |    13 +
 media/js/tinymce/plugins/advimage/editor_plugin.js |     1 +
 .../tinymce/plugins/advimage/editor_plugin_src.js  |    47 +
 media/js/tinymce/plugins/advimage/image.htm        |   237 +
 media/js/tinymce/plugins/advimage/img/sample.gif   |   Bin 0 -> 1624 bytes
 media/js/tinymce/plugins/advimage/js/image.js      |   443 +
 media/js/tinymce/plugins/advimage/langs/en_dlg.js  |    43 +
 media/js/tinymce/plugins/advlink/css/advlink.css   |     8 +
 media/js/tinymce/plugins/advlink/editor_plugin.js  |     1 +
 .../tinymce/plugins/advlink/editor_plugin_src.js   |    58 +
 media/js/tinymce/plugins/advlink/js/advlink.js     |   528 +
 media/js/tinymce/plugins/advlink/langs/en_dlg.js   |    52 +
 media/js/tinymce/plugins/advlink/link.htm          |   338 +
 .../js/tinymce/plugins/autoresize/editor_plugin.js |     1 +
 .../plugins/autoresize/editor_plugin_src.js        |   114 +
 media/js/tinymce/plugins/autosave/editor_plugin.js |     1 +
 .../tinymce/plugins/autosave/editor_plugin_src.js  |    51 +
 media/js/tinymce/plugins/bbcode/editor_plugin.js   |     1 +
 .../js/tinymce/plugins/bbcode/editor_plugin_src.js |   117 +
 .../tinymce/plugins/contextmenu/editor_plugin.js   |     1 +
 .../plugins/contextmenu/editor_plugin_src.js       |    95 +
 .../plugins/directionality/editor_plugin.js        |     1 +
 .../plugins/directionality/editor_plugin_src.js    |    79 +
 media/js/tinymce/plugins/emotions/editor_plugin.js |     1 +
 .../tinymce/plugins/emotions/editor_plugin_src.js  |    40 +
 media/js/tinymce/plugins/emotions/emotions.htm     |    40 +
 .../tinymce/plugins/emotions/img/smiley-cool.gif   |   Bin 0 -> 354 bytes
 .../js/tinymce/plugins/emotions/img/smiley-cry.gif |   Bin 0 -> 329 bytes
 .../plugins/emotions/img/smiley-embarassed.gif     |   Bin 0 -> 331 bytes
 .../plugins/emotions/img/smiley-foot-in-mouth.gif  |   Bin 0 -> 344 bytes
 .../tinymce/plugins/emotions/img/smiley-frown.gif  |   Bin 0 -> 340 bytes
 .../plugins/emotions/img/smiley-innocent.gif       |   Bin 0 -> 336 bytes
 .../tinymce/plugins/emotions/img/smiley-kiss.gif   |   Bin 0 -> 338 bytes
 .../plugins/emotions/img/smiley-laughing.gif       |   Bin 0 -> 344 bytes
 .../plugins/emotions/img/smiley-money-mouth.gif    |   Bin 0 -> 321 bytes
 .../tinymce/plugins/emotions/img/smiley-sealed.gif |   Bin 0 -> 325 bytes
 .../tinymce/plugins/emotions/img/smiley-smile.gif  |   Bin 0 -> 345 bytes
 .../plugins/emotions/img/smiley-surprised.gif      |   Bin 0 -> 342 bytes
 .../plugins/emotions/img/smiley-tongue-out.gif     |   Bin 0 -> 328 bytes
 .../plugins/emotions/img/smiley-undecided.gif      |   Bin 0 -> 337 bytes
 .../tinymce/plugins/emotions/img/smiley-wink.gif   |   Bin 0 -> 351 bytes
 .../tinymce/plugins/emotions/img/smiley-yell.gif   |   Bin 0 -> 336 bytes
 media/js/tinymce/plugins/emotions/js/emotions.js   |    22 +
 media/js/tinymce/plugins/emotions/langs/en_dlg.js  |    20 +
 media/js/tinymce/plugins/example/dialog.htm        |    27 +
 media/js/tinymce/plugins/example/editor_plugin.js  |     1 +
 .../tinymce/plugins/example/editor_plugin_src.js   |    81 +
 media/js/tinymce/plugins/example/img/example.gif   |   Bin 0 -> 87 bytes
 media/js/tinymce/plugins/example/js/dialog.js      |    19 +
 media/js/tinymce/plugins/example/langs/en.js       |     3 +
 media/js/tinymce/plugins/example/langs/en_dlg.js   |     3 +
 media/js/tinymce/plugins/fullpage/css/fullpage.css |   182 +
 media/js/tinymce/plugins/fullpage/editor_plugin.js |     1 +
 .../tinymce/plugins/fullpage/editor_plugin_src.js  |   146 +
 media/js/tinymce/plugins/fullpage/fullpage.htm     |   576 +
 media/js/tinymce/plugins/fullpage/js/fullpage.js   |   461 +
 media/js/tinymce/plugins/fullpage/langs/en_dlg.js  |    85 +
 .../js/tinymce/plugins/fullscreen/editor_plugin.js |     1 +
 .../plugins/fullscreen/editor_plugin_src.js        |   145 +
 media/js/tinymce/plugins/fullscreen/fullscreen.htm |   110 +
 media/js/tinymce/plugins/iespell/editor_plugin.js  |     1 +
 .../tinymce/plugins/iespell/editor_plugin_src.js   |    51 +
 .../tinymce/plugins/inlinepopups/editor_plugin.js  |     1 +
 .../plugins/inlinepopups/editor_plugin_src.js      |   632 +
 .../inlinepopups/skins/clearlooks2/img/alert.gif   |   Bin 0 -> 818 bytes
 .../inlinepopups/skins/clearlooks2/img/button.gif  |   Bin 0 -> 280 bytes
 .../inlinepopups/skins/clearlooks2/img/buttons.gif |   Bin 0 -> 1195 bytes
 .../inlinepopups/skins/clearlooks2/img/confirm.gif |   Bin 0 -> 915 bytes
 .../inlinepopups/skins/clearlooks2/img/corners.gif |   Bin 0 -> 911 bytes
 .../skins/clearlooks2/img/horizontal.gif           |   Bin 0 -> 769 bytes
 .../skins/clearlooks2/img/vertical.gif             |   Bin 0 -> 92 bytes
 .../inlinepopups/skins/clearlooks2/window.css      |    90 +
 media/js/tinymce/plugins/inlinepopups/template.htm |   387 +
 .../plugins/insertdatetime/editor_plugin.js        |     1 +
 .../plugins/insertdatetime/editor_plugin_src.js    |    80 +
 media/js/tinymce/plugins/layer/editor_plugin.js    |     1 +
 .../js/tinymce/plugins/layer/editor_plugin_src.js  |   209 +
 media/js/tinymce/plugins/media/css/content.css     |     6 +
 media/js/tinymce/plugins/media/css/media.css       |    16 +
 media/js/tinymce/plugins/media/editor_plugin.js    |     1 +
 .../js/tinymce/plugins/media/editor_plugin_src.js  |   411 +
 media/js/tinymce/plugins/media/img/flash.gif       |   Bin 0 -> 241 bytes
 media/js/tinymce/plugins/media/img/flv_player.swf  |   Bin 0 -> 11668 bytes
 media/js/tinymce/plugins/media/img/quicktime.gif   |   Bin 0 -> 303 bytes
 media/js/tinymce/plugins/media/img/realmedia.gif   |   Bin 0 -> 439 bytes
 media/js/tinymce/plugins/media/img/shockwave.gif   |   Bin 0 -> 387 bytes
 media/js/tinymce/plugins/media/img/trans.gif       |   Bin 0 -> 43 bytes
 .../js/tinymce/plugins/media/img/windowsmedia.gif  |   Bin 0 -> 415 bytes
 media/js/tinymce/plugins/media/js/embed.js         |    73 +
 media/js/tinymce/plugins/media/js/media.js         |   630 +
 media/js/tinymce/plugins/media/langs/en_dlg.js     |   103 +
 media/js/tinymce/plugins/media/media.htm           |   822 +
 .../tinymce/plugins/nonbreaking/editor_plugin.js   |     1 +
 .../plugins/nonbreaking/editor_plugin_src.js       |    50 +
 .../tinymce/plugins/noneditable/editor_plugin.js   |     1 +
 .../plugins/noneditable/editor_plugin_src.js       |    87 +
 media/js/tinymce/plugins/pagebreak/css/content.css |     1 +
 .../js/tinymce/plugins/pagebreak/editor_plugin.js  |     1 +
 .../tinymce/plugins/pagebreak/editor_plugin_src.js |    74 +
 .../js/tinymce/plugins/pagebreak/img/pagebreak.gif |   Bin 0 -> 325 bytes
 media/js/tinymce/plugins/pagebreak/img/trans.gif   |   Bin 0 -> 43 bytes
 media/js/tinymce/plugins/paste/editor_plugin.js    |     1 +
 .../js/tinymce/plugins/paste/editor_plugin_src.js  |   531 +
 media/js/tinymce/plugins/paste/js/pastetext.js     |    36 +
 media/js/tinymce/plugins/paste/js/pasteword.js     |    51 +
 media/js/tinymce/plugins/paste/langs/en_dlg.js     |     5 +
 media/js/tinymce/plugins/paste/pastetext.htm       |    33 +
 media/js/tinymce/plugins/paste/pasteword.htm       |    27 +
 media/js/tinymce/plugins/preview/editor_plugin.js  |     1 +
 .../tinymce/plugins/preview/editor_plugin_src.js   |    50 +
 media/js/tinymce/plugins/preview/example.html      |    28 +
 media/js/tinymce/plugins/preview/jscripts/embed.js |    73 +
 media/js/tinymce/plugins/preview/preview.html      |    17 +
 media/js/tinymce/plugins/print/editor_plugin.js    |     1 +
 .../js/tinymce/plugins/print/editor_plugin_src.js  |    31 +
 media/js/tinymce/plugins/safari/blank.htm          |     1 +
 media/js/tinymce/plugins/safari/editor_plugin.js   |     1 +
 .../js/tinymce/plugins/safari/editor_plugin_src.js |   438 +
 media/js/tinymce/plugins/save/editor_plugin.js     |     1 +
 media/js/tinymce/plugins/save/editor_plugin_src.js |    98 +
 .../plugins/searchreplace/css/searchreplace.css    |     6 +
 .../tinymce/plugins/searchreplace/editor_plugin.js |     1 +
 .../plugins/searchreplace/editor_plugin_src.js     |    54 +
 .../plugins/searchreplace/js/searchreplace.js      |   126 +
 .../tinymce/plugins/searchreplace/langs/en_dlg.js  |    16 +
 .../plugins/searchreplace/searchreplace.htm        |   104 +
 .../tinymce/plugins/spellchecker/css/content.css   |     1 +
 .../tinymce/plugins/spellchecker/editor_plugin.js  |     1 +
 .../plugins/spellchecker/editor_plugin_src.js      |   338 +
 .../js/tinymce/plugins/spellchecker/img/wline.gif  |   Bin 0 -> 46 bytes
 media/js/tinymce/plugins/style/css/props.css       |    13 +
 media/js/tinymce/plugins/style/editor_plugin.js    |     1 +
 .../js/tinymce/plugins/style/editor_plugin_src.js  |    52 +
 media/js/tinymce/plugins/style/js/props.js         |   641 +
 media/js/tinymce/plugins/style/langs/en_dlg.js     |    63 +
 media/js/tinymce/plugins/style/props.htm           |   730 +
 media/js/tinymce/plugins/tabfocus/editor_plugin.js |     1 +
 .../tinymce/plugins/tabfocus/editor_plugin_src.js  |   109 +
 media/js/tinymce/plugins/table/cell.htm            |   183 +
 media/js/tinymce/plugins/table/css/cell.css        |    17 +
 media/js/tinymce/plugins/table/css/row.css         |    25 +
 media/js/tinymce/plugins/table/css/table.css       |    13 +
 media/js/tinymce/plugins/table/editor_plugin.js    |     1 +
 .../js/tinymce/plugins/table/editor_plugin_src.js  |  1202 ++
 media/js/tinymce/plugins/table/js/cell.js          |   269 +
 media/js/tinymce/plugins/table/js/merge_cells.js   |    29 +
 media/js/tinymce/plugins/table/js/row.js           |   212 +
 media/js/tinymce/plugins/table/js/table.js         |   440 +
 media/js/tinymce/plugins/table/langs/en_dlg.js     |    74 +
 media/js/tinymce/plugins/table/merge_cells.htm     |    37 +
 media/js/tinymce/plugins/table/row.htm             |   160 +
 media/js/tinymce/plugins/table/table.htm           |   192 +
 media/js/tinymce/plugins/template/blank.htm        |    12 +
 media/js/tinymce/plugins/template/css/template.css |    23 +
 media/js/tinymce/plugins/template/editor_plugin.js |     1 +
 .../tinymce/plugins/template/editor_plugin_src.js  |   156 +
 media/js/tinymce/plugins/template/js/template.js   |   106 +
 media/js/tinymce/plugins/template/langs/en_dlg.js  |    15 +
 media/js/tinymce/plugins/template/template.htm     |    38 +
 .../tinymce/plugins/visualchars/editor_plugin.js   |     1 +
 .../plugins/visualchars/editor_plugin_src.js       |    73 +
 .../js/tinymce/plugins/wordcount/editor_plugin.js  |     1 +
 .../tinymce/plugins/wordcount/editor_plugin_src.js |    95 +
 media/js/tinymce/plugins/xhtmlxtras/abbr.htm       |   148 +
 media/js/tinymce/plugins/xhtmlxtras/acronym.htm    |   148 +
 media/js/tinymce/plugins/xhtmlxtras/attributes.htm |   153 +
 media/js/tinymce/plugins/xhtmlxtras/cite.htm       |   148 +
 .../tinymce/plugins/xhtmlxtras/css/attributes.css  |    11 +
 media/js/tinymce/plugins/xhtmlxtras/css/popup.css  |     9 +
 media/js/tinymce/plugins/xhtmlxtras/del.htm        |   169 +
 .../js/tinymce/plugins/xhtmlxtras/editor_plugin.js |     1 +
 .../plugins/xhtmlxtras/editor_plugin_src.js        |   136 +
 media/js/tinymce/plugins/xhtmlxtras/ins.htm        |   169 +
 media/js/tinymce/plugins/xhtmlxtras/js/abbr.js     |    25 +
 media/js/tinymce/plugins/xhtmlxtras/js/acronym.js  |    25 +
 .../js/tinymce/plugins/xhtmlxtras/js/attributes.js |   123 +
 media/js/tinymce/plugins/xhtmlxtras/js/cite.js     |    25 +
 media/js/tinymce/plugins/xhtmlxtras/js/del.js      |    60 +
 .../plugins/xhtmlxtras/js/element_common.js        |   231 +
 media/js/tinymce/plugins/xhtmlxtras/js/ins.js      |    59 +
 .../js/tinymce/plugins/xhtmlxtras/langs/en_dlg.js  |    32 +
 media/js/tinymce/themes/advanced/about.htm         |    56 +
 media/js/tinymce/themes/advanced/anchor.htm        |    31 +
 media/js/tinymce/themes/advanced/charmap.htm       |    53 +
 media/js/tinymce/themes/advanced/color_picker.htm  |    75 +
 .../js/tinymce/themes/advanced/editor_template.js  |     1 +
 .../tinymce/themes/advanced/editor_template_src.js |  1153 ++
 media/js/tinymce/themes/advanced/image.htm         |    85 +
 .../js/tinymce/themes/advanced/img/colorpicker.jpg |   Bin 0 -> 3189 bytes
 media/js/tinymce/themes/advanced/img/icons.gif     |   Bin 0 -> 11505 bytes
 media/js/tinymce/themes/advanced/js/about.js       |    72 +
 media/js/tinymce/themes/advanced/js/anchor.js      |    37 +
 media/js/tinymce/themes/advanced/js/charmap.js     |   325 +
 .../js/tinymce/themes/advanced/js/color_picker.js  |   253 +
 media/js/tinymce/themes/advanced/js/image.js       |   245 +
 media/js/tinymce/themes/advanced/js/link.js        |   156 +
 .../js/tinymce/themes/advanced/js/source_editor.js |    62 +
 media/js/tinymce/themes/advanced/langs/en.js       |    62 +
 media/js/tinymce/themes/advanced/langs/en_dlg.js   |    51 +
 media/js/tinymce/themes/advanced/link.htm          |    63 +
 .../themes/advanced/skins/default/content.css      |    32 +
 .../themes/advanced/skins/default/dialog.css       |   116 +
 .../themes/advanced/skins/default/img/buttons.png  |   Bin 0 -> 3274 bytes
 .../themes/advanced/skins/default/img/items.gif    |   Bin 0 -> 70 bytes
 .../advanced/skins/default/img/menu_arrow.gif      |   Bin 0 -> 68 bytes
 .../advanced/skins/default/img/menu_check.gif      |   Bin 0 -> 70 bytes
 .../themes/advanced/skins/default/img/progress.gif |   Bin 0 -> 1787 bytes
 .../themes/advanced/skins/default/img/tabs.gif     |   Bin 0 -> 1326 bytes
 .../tinymce/themes/advanced/skins/default/ui.css   |   214 +
 .../tinymce/themes/advanced/skins/o2k7/content.css |    32 +
 .../tinymce/themes/advanced/skins/o2k7/dialog.css  |   115 +
 .../themes/advanced/skins/o2k7/img/button_bg.png   |   Bin 0 -> 5859 bytes
 .../advanced/skins/o2k7/img/button_bg_black.png    |   Bin 0 -> 3736 bytes
 .../advanced/skins/o2k7/img/button_bg_silver.png   |   Bin 0 -> 5358 bytes
 media/js/tinymce/themes/advanced/skins/o2k7/ui.css |   215 +
 .../themes/advanced/skins/o2k7/ui_black.css        |     8 +
 .../themes/advanced/skins/o2k7/ui_silver.css       |     5 +
 media/js/tinymce/themes/advanced/source_editor.htm |    31 +
 media/js/tinymce/themes/simple/editor_template.js  |     1 +
 .../tinymce/themes/simple/editor_template_src.js   |    85 +
 media/js/tinymce/themes/simple/img/icons.gif       |   Bin 0 -> 1440 bytes
 media/js/tinymce/themes/simple/langs/en.js         |    11 +
 .../themes/simple/skins/default/content.css        |    25 +
 .../js/tinymce/themes/simple/skins/default/ui.css  |    32 +
 .../tinymce/themes/simple/skins/o2k7/content.css   |    17 +
 .../themes/simple/skins/o2k7/img/button_bg.png     |   Bin 0 -> 5102 bytes
 media/js/tinymce/themes/simple/skins/o2k7/ui.css   |    35 +
 media/js/tinymce/tiny_mce.js                       |     1 +
 media/js/tinymce/tiny_mce_popup.js                 |     5 +
 media/js/tinymce/tiny_mce_src.js                   | 12428 +++++++++++++++
 media/js/tinymce/utils/editable_selects.js         |    69 +
 media/js/tinymce/utils/form_utils.js               |   199 +
 media/js/tinymce/utils/mctabs.js                   |    76 +
 media/js/tinymce/utils/validate.js                 |   219 +
 media/js/ushahidi.js                               |  1193 ++
 media/uploads/.gitignore                           |     0
 modules/auth/config/auth.php                       |    55 +
 modules/auth/libraries/Auth.php                    |   262 +
 modules/auth/libraries/drivers/Auth.php            |   142 +
 modules/auth/libraries/drivers/Auth/File.php       |    73 +
 modules/auth/libraries/drivers/Auth/ORM.php        |   542 +
 modules/auth/models/auth_role.php                  |    37 +
 modules/auth/models/auth_user.php                  |   155 +
 modules/auth/models/auth_user_token.php            |   102 +
 modules/auth/models/role.php                       |     7 +
 modules/auth/models/user.php                       |     7 +
 modules/auth/models/user_token.php                 |     7 +
 modules/csrf/helpers/MY_form.php                   |    68 +
 modules/csrf/helpers/csrf.php                      |    61 +
 modules/csrf/i18n/en_US/csrf.php                   |    13 +
 .../controllers/admin/clickatell_settings.php      |   156 +
 plugins/clickatell/controllers/clickatell.php      |    67 +
 plugins/clickatell/hooks/clickatell.php            |    36 +
 plugins/clickatell/libraries/Clickatell_API.php    |   389 +
 .../clickatell/libraries/Clickatell_Install.php    |    51 +
 .../libraries/Clickatell_Sms_Provider.php          |    44 +
 plugins/clickatell/models/clickatell.php           |    21 +
 plugins/clickatell/readme.txt                      |    19 +
 .../views/clickatell/admin/clickatell_settings.php |    55 +
 .../clickatell/admin/clickatell_settings_js.php    |     9 +
 .../controllers/admin/frontlinesms_settings.php    |    46 +
 plugins/frontlinesms/controllers/frontlinesms.php  |    52 +
 plugins/frontlinesms/hooks/frontlinesms.php        |    36 +
 .../libraries/Frontlinesms_Install.php             |    48 +
 plugins/frontlinesms/models/frontlinesms.php       |    21 +
 plugins/frontlinesms/readme.txt                    |    19 +
 .../frontlinesms/admin/frontlinesms_settings.php   |    31 +
 .../sharing/controllers/admin/manage/sharing.php   |   210 +
 plugins/sharing/controllers/json/share.php         |   236 +
 .../sharing/controllers/scheduler/s_sharing.php    |   125 +
 plugins/sharing/helpers/sharing_helper.php         |    29 +
 plugins/sharing/hooks/sharing.php                  |    81 +
 plugins/sharing/i18n/en_US/sharing.php             |    20 +
 plugins/sharing/libraries/sharing_install.php      |    94 +
 plugins/sharing/models/sharing.php                 |    23 +
 plugins/sharing/models/sharing_incident.php        |    23 +
 plugins/sharing/readme.txt                         |    17 +
 .../sharing/views/admin/manage/sharing/main.php    |   183 +
 .../views/admin/manage/sharing/sharing_js.php      |    40 +
 plugins/sharing/views/js/sharing_bar_js.php        |    40 +
 plugins/sharing/views/sharing/sharing_bar.php      |    15 +
 .../smssync/controllers/admin/smssync_settings.php |   102 +
 plugins/smssync/controllers/smssync.php            |   167 +
 plugins/smssync/hooks/smssync.php                  |    36 +
 plugins/smssync/libraries/Smssync_Sms_Provider.php |    20 +
 plugins/smssync/libraries/smssync_install.php      |    63 +
 plugins/smssync/models/smssync_message.php         |    20 +
 plugins/smssync/models/smssync_settings.php        |    20 +
 plugins/smssync/readme.txt                         |    22 +
 plugins/smssync/views/images/smssync.png           |   Bin 0 -> 1017 bytes
 .../views/smssync/admin/smssync_settings.php       |    41 +
 sql/upgrade.sql                                    |   288 +
 sql/upgrade100-101.sql                             |     5 +
 sql/upgrade101-102.sql                             |     5 +
 sql/upgrade102-103.sql                             |     9 +
 sql/upgrade103-104.sql                             |    21 +
 sql/upgrade104-105.sql                             |    14 +
 sql/upgrade105-106.sql                             |     9 +
 sql/upgrade106-107.sql                             |     5 +
 sql/upgrade107-108.sql                             |     5 +
 sql/upgrade108-109.sql                             |     5 +
 sql/upgrade109-110.sql                             |    11 +
 sql/upgrade11-12.sql                               |     3 +
 sql/upgrade110-111.sql                             |     5 +
 sql/upgrade111-112.sql                             |    17 +
 sql/upgrade112-113.sql                             |     5 +
 sql/upgrade113-114.sql                             |     5 +
 sql/upgrade114-115.sql                             |     7 +
 sql/upgrade115-116.sql                             |     5 +
 sql/upgrade116-117.sql                             |     5 +
 sql/upgrade117-118.sql                             |     5 +
 sql/upgrade118-119.sql                             |     5 +
 sql/upgrade12-13.sql                               |     3 +
 sql/upgrade13-14.sql                               |     6 +
 sql/upgrade14-15.sql                               |    13 +
 sql/upgrade15-16.sql                               |     4 +
 sql/upgrade16-17.sql                               |     3 +
 sql/upgrade17-18.sql                               |     7 +
 sql/upgrade18-19.sql                               |     5 +
 sql/upgrade19-20.sql                               |     4 +
 sql/upgrade20-21.sql                               |    15 +
 sql/upgrade21-22.sql                               |    26 +
 sql/upgrade22-23.sql                               |    46 +
 sql/upgrade23-24.sql                               |     8 +
 sql/upgrade24-25.sql                               |    27 +
 sql/upgrade25-26.sql                               |     4 +
 sql/upgrade26-27.sql                               |    34 +
 sql/upgrade27-28.sql                               |     3 +
 sql/upgrade28-29.sql                               |    29 +
 sql/upgrade29-30.sql                               |    14 +
 sql/upgrade30-31.sql                               |    13 +
 sql/upgrade31-32.sql                               |     3 +
 sql/upgrade32-33.sql                               |     4 +
 sql/upgrade33-34.sql                               |     8 +
 sql/upgrade34-35.sql                               |     3 +
 sql/upgrade35-36.sql                               |    15 +
 sql/upgrade36-37.sql                               |     3 +
 sql/upgrade37-38.sql                               |     3 +
 sql/upgrade38-39.sql                               |     4 +
 sql/upgrade39-40.sql                               |    83 +
 sql/upgrade40-41.sql                               |    13 +
 sql/upgrade41-42.sql                               |     4 +
 sql/upgrade42-43.sql                               |     8 +
 sql/upgrade43-44.sql                               |    14 +
 sql/upgrade44-45.sql                               |     4 +
 sql/upgrade45-46.sql                               |     3 +
 sql/upgrade46-47.sql                               |    32 +
 sql/upgrade47-48.sql                               |     6 +
 sql/upgrade48-49.sql                               |     4 +
 sql/upgrade49-50.sql                               |     3 +
 sql/upgrade50-51.sql                               |    13 +
 sql/upgrade51-52.sql                               |    17 +
 sql/upgrade52-53.sql                               |     2 +
 sql/upgrade53-54.sql                               |     8 +
 sql/upgrade54-55.sql                               |    61 +
 sql/upgrade55-56.sql                               |     3 +
 sql/upgrade56-57.sql                               |     3 +
 sql/upgrade57-58.sql                               |    16 +
 sql/upgrade58-59.sql                               |     3 +
 sql/upgrade59-60.sql                               |     4 +
 sql/upgrade60-61.sql                               |    11 +
 sql/upgrade61-62.sql                               |     8 +
 sql/upgrade62-63.sql                               |    18 +
 sql/upgrade63-64.sql                               |     4 +
 sql/upgrade64-65.sql                               |     3 +
 sql/upgrade65-66.sql                               |     3 +
 sql/upgrade66-67.sql                               |     4 +
 sql/upgrade67-68.sql                               |     6 +
 sql/upgrade68-69.sql                               |     5 +
 sql/upgrade69-70.sql                               |     5 +
 sql/upgrade70-71.sql                               |     7 +
 sql/upgrade71-72.sql                               |    10 +
 sql/upgrade72-73.sql                               |    38 +
 sql/upgrade73-74.sql                               |     9 +
 sql/upgrade74-75.sql                               |    11 +
 sql/upgrade75-76.sql                               |     7 +
 sql/upgrade76-77.sql                               |     2 +
 sql/upgrade77-78.sql                               |     4 +
 sql/upgrade78-79.sql                               |     2 +
 sql/upgrade79-80.sql                               |   189 +
 sql/upgrade80-81.sql                               |    53 +
 sql/upgrade81-82.sql                               |     3 +
 sql/upgrade82-83.sql                               |     5 +
 sql/upgrade83-84.sql                               |     5 +
 sql/upgrade84-85.sql                               |     3 +
 sql/upgrade85-86.sql                               |     2 +
 sql/upgrade86-87.sql                               |     4 +
 sql/upgrade87-88.sql                               |     3 +
 sql/upgrade88-89.sql                               |     4 +
 sql/upgrade89-90.sql                               |     2 +
 sql/upgrade90-91.sql                               |     2 +
 sql/upgrade91-92.sql                               |   145 +
 sql/upgrade92-93.sql                               |    94 +
 sql/upgrade93-94.sql                               |     5 +
 sql/upgrade94-95.sql                               |     5 +
 sql/upgrade95-96.sql                               |     5 +
 sql/upgrade96-97.sql                               |     5 +
 sql/upgrade97-98.sql                               |    11 +
 sql/upgrade98-99.sql                               |     5 +
 sql/upgrade99-100.sql                              |     9 +
 sql/ushahidi.sql                                   |  1484 ++
 system/config/cache.php                            |    32 +
 system/config/cache_memcache.php                   |    20 +
 system/config/cache_sqlite.php                     |    11 +
 system/config/cache_xcache.php                     |    12 +
 system/config/captcha.php                          |    29 +
 system/config/cookie.php                           |    32 +
 system/config/credit_cards.php                     |    60 +
 system/config/email.php                            |    22 +
 system/config/encryption.php                       |    31 +
 system/config/http.php                             |    19 +
 system/config/image.php                            |    13 +
 system/config/inflector.php                        |    58 +
 system/config/locale.php                           |    16 +
 system/config/mimes.php                            |   224 +
 system/config/pagination.php                       |    25 +
 system/config/profiler.php                         |     8 +
 system/config/routes.php                           |     7 +
 system/config/session.php                          |    47 +
 system/config/sql_types.php                        |    49 +
 system/config/upload.php                           |    17 +
 system/config/user_agents.php                      |   109 +
 system/config/view.php                             |    17 +
 system/controllers/captcha.php                     |    23 +
 system/controllers/template.php                    |    54 +
 system/core/Benchmark.php                          |   165 +
 system/core/Bootstrap.php                          |    61 +
 system/core/Event.php                              |   232 +
 system/core/Kohana.php                             |  1745 +++
 system/core/utf8.php                               |   743 +
 system/core/utf8/from_unicode.php                  |    68 +
 system/core/utf8/ltrim.php                         |    22 +
 system/core/utf8/ord.php                           |    88 +
 system/core/utf8/rtrim.php                         |    22 +
 system/core/utf8/str_ireplace.php                  |    70 +
 system/core/utf8/str_pad.php                       |    54 +
 system/core/utf8/str_split.php                     |    33 +
 system/core/utf8/strcasecmp.php                    |    19 +
 system/core/utf8/strcspn.php                       |    30 +
 system/core/utf8/stristr.php                       |    28 +
 system/core/utf8/strlen.php                        |    21 +
 system/core/utf8/strpos.php                        |    30 +
 system/core/utf8/strrev.php                        |    18 +
 system/core/utf8/strrpos.php                       |    30 +
 system/core/utf8/strspn.php                        |    30 +
 system/core/utf8/strtolower.php                    |    84 +
 system/core/utf8/strtoupper.php                    |    84 +
 system/core/utf8/substr.php                        |    75 +
 system/core/utf8/substr_replace.php                |    22 +
 system/core/utf8/to_unicode.php                    |   141 +
 system/core/utf8/transliterate_to_ascii.php        |    77 +
 system/core/utf8/trim.php                          |    17 +
 system/core/utf8/ucfirst.php                       |    18 +
 system/core/utf8/ucwords.php                       |    26 +
 system/fonts/DejaVuSerif.ttf                       |   Bin 0 -> 305632 bytes
 system/fonts/LICENSE                               |    99 +
 system/helpers/arr.php                             |   316 +
 system/helpers/cookie.php                          |    84 +
 system/helpers/date.php                            |   399 +
 system/helpers/download.php                        |   105 +
 system/helpers/email.php                           |   187 +
 system/helpers/expires.php                         |   110 +
 system/helpers/feed.php                            |   116 +
 system/helpers/file.php                            |   186 +
 system/helpers/form.php                            |   544 +
 system/helpers/format.php                          |    66 +
 system/helpers/html.php                            |   428 +
 system/helpers/inflector.php                       |   193 +
 system/helpers/num.php                             |    26 +
 system/helpers/remote.php                          |    71 +
 system/helpers/request.php                         |   239 +
 system/helpers/security.php                        |    47 +
 system/helpers/text.php                            |   420 +
 system/helpers/upload.php                          |   162 +
 system/helpers/url.php                             |   249 +
 system/helpers/valid.php                           |   377 +
 system/i18n/en_US/cache.php                        |    10 +
 system/i18n/en_US/calendar.php                     |    59 +
 system/i18n/en_US/captcha.php                      |    33 +
 system/i18n/en_US/core.php                         |    34 +
 system/i18n/en_US/encrypt.php                      |     8 +
 system/i18n/en_US/errors.php                       |    16 +
 system/i18n/en_US/event.php                        |     7 +
 system/i18n/en_US/image.php                        |    33 +
 system/i18n/en_US/orm.php                          |     3 +
 system/i18n/en_US/pagination.php                   |    15 +
 system/i18n/en_US/profiler.php                     |    15 +
 system/i18n/en_US/session.php                      |     6 +
 system/i18n/en_US/swift.php                        |     6 +
 system/i18n/en_US/upload.php                       |     6 +
 system/i18n/en_US/validation.php                   |    41 +
 system/libraries/Cache.php                         |   228 +
 system/libraries/Calendar.php                      |   362 +
 system/libraries/Calendar_Event.php                |   297 +
 system/libraries/Captcha.php                       |   279 +
 system/libraries/Controller.php                    |    86 +
 system/libraries/Database.php                      |  1342 ++
 system/libraries/Encrypt.php                       |   164 +
 system/libraries/Event_Observer.php                |    70 +
 system/libraries/Event_Subject.php                 |    67 +
 system/libraries/Image.php                         |   431 +
 system/libraries/Input.php                         |   454 +
 system/libraries/Model.php                         |    31 +
 system/libraries/ORM.php                           |  1450 ++
 system/libraries/ORM_Iterator.php                  |   228 +
 system/libraries/ORM_Tree.php                      |    76 +
 system/libraries/ORM_Versioned.php                 |   143 +
 system/libraries/Pagination.php                    |   236 +
 system/libraries/Profiler.php                      |   270 +
 system/libraries/Profiler_Table.php                |    69 +
 system/libraries/Router.php                        |   307 +
 system/libraries/Session.php                       |   458 +
 system/libraries/Tagcloud.php                      |   130 +
 system/libraries/URI.php                           |   279 +
 system/libraries/Validation.php                    |   826 +
 system/libraries/View.php                          |   303 +
 system/libraries/drivers/Cache.php                 |    40 +
 system/libraries/drivers/Cache/Apc.php             |    53 +
 system/libraries/drivers/Cache/Eaccelerator.php    |    53 +
 system/libraries/drivers/Cache/File.php            |   245 +
 system/libraries/drivers/Cache/Memcache.php        |    78 +
 system/libraries/drivers/Cache/Sqlite.php          |   228 +
 system/libraries/drivers/Cache/Xcache.php          |   116 +
 system/libraries/drivers/Captcha.php               |   228 +
 system/libraries/drivers/Captcha/Alpha.php         |    92 +
 system/libraries/drivers/Captcha/Basic.php         |    81 +
 system/libraries/drivers/Captcha/Black.php         |    72 +
 system/libraries/drivers/Captcha/Math.php          |    61 +
 system/libraries/drivers/Captcha/Riddle.php        |    47 +
 system/libraries/drivers/Captcha/Word.php          |    37 +
 system/libraries/drivers/Database.php              |   636 +
 system/libraries/drivers/Database/Mssql.php        |   458 +
 system/libraries/drivers/Database/Mysql.php        |   492 +
 system/libraries/drivers/Database/Mysqli.php       |   372 +
 system/libraries/drivers/Database/Pdosqlite.php    |   473 +
 system/libraries/drivers/Database/Pgsql.php        |   552 +
 system/libraries/drivers/Image.php                 |   149 +
 system/libraries/drivers/Image/GD.php              |   379 +
 system/libraries/drivers/Image/GraphicsMagick.php  |   211 +
 system/libraries/drivers/Image/ImageMagick.php     |   212 +
 system/libraries/drivers/Session.php               |    70 +
 system/libraries/drivers/Session/Cache.php         |   105 +
 system/libraries/drivers/Session/Cookie.php        |    80 +
 system/libraries/drivers/Session/Database.php      |   166 +
 system/vendor/Markdown.php                         |  2789 ++++
 system/vendor/swift/EasySwift.php                  |   949 ++
 system/vendor/swift/Swift.php                      |   489 +
 system/vendor/swift/Swift/Address.php              |   107 +
 system/vendor/swift/Swift/AddressContainer.php     |     8 +
 system/vendor/swift/Swift/Authenticator.php        |    33 +
 .../swift/Swift/Authenticator/@PopB4Smtp.php       |    86 +
 .../vendor/swift/Swift/Authenticator/CRAMMD5.php   |    73 +
 system/vendor/swift/Swift/Authenticator/LOGIN.php  |    49 +
 system/vendor/swift/Swift/Authenticator/PLAIN.php  |    50 +
 .../Authenticator/PopB4Smtp/Pop3Connection.php     |   176 +
 system/vendor/swift/Swift/BadResponseException.php |    22 +
 system/vendor/swift/Swift/BatchMailer.php          |   229 +
 system/vendor/swift/Swift/Cache.php                |    55 +
 system/vendor/swift/Swift/Cache/Disk.php           |   130 +
 .../vendor/swift/Swift/Cache/JointOutputStream.php |    74 +
 system/vendor/swift/Swift/Cache/Memory.php         |    78 +
 system/vendor/swift/Swift/Cache/OutputStream.php   |    60 +
 system/vendor/swift/Swift/CacheFactory.php         |    47 +
 system/vendor/swift/Swift/ClassLoader.php          |    38 +
 system/vendor/swift/Swift/Connection.php           |    81 +
 system/vendor/swift/Swift/Connection/Multi.php     |   161 +
 .../vendor/swift/Swift/Connection/NativeMail.php   |   136 +
 system/vendor/swift/Swift/Connection/Rotator.php   |   194 +
 system/vendor/swift/Swift/Connection/SMTP.php      |   452 +
 system/vendor/swift/Swift/Connection/Sendmail.php  |   352 +
 system/vendor/swift/Swift/ConnectionBase.php       |   102 +
 system/vendor/swift/Swift/ConnectionException.php  |    22 +
 system/vendor/swift/Swift/Events.php               |    41 +
 .../swift/Swift/Events/BeforeCommandListener.php   |    25 +
 .../swift/Swift/Events/BeforeSendListener.php      |    25 +
 system/vendor/swift/Swift/Events/CommandEvent.php  |    74 +
 .../vendor/swift/Swift/Events/CommandListener.php  |    25 +
 system/vendor/swift/Swift/Events/ConnectEvent.php  |    42 +
 .../vendor/swift/Swift/Events/ConnectListener.php  |    25 +
 .../vendor/swift/Swift/Events/DisconnectEvent.php  |    42 +
 .../swift/Swift/Events/DisconnectListener.php      |    25 +
 system/vendor/swift/Swift/Events/Listener.php      |    17 +
 .../vendor/swift/Swift/Events/ListenerMapper.php   |    47 +
 system/vendor/swift/Swift/Events/ResponseEvent.php |    71 +
 .../vendor/swift/Swift/Events/ResponseListener.php |    25 +
 system/vendor/swift/Swift/Events/SendEvent.php     |   116 +
 system/vendor/swift/Swift/Events/SendListener.php  |    24 +
 system/vendor/swift/Swift/Exception.php            |    36 +
 system/vendor/swift/Swift/File.php                 |   215 +
 system/vendor/swift/Swift/FileException.php        |    22 +
 system/vendor/swift/Swift/Iterator.php             |    44 +
 system/vendor/swift/Swift/Iterator/Array.php       |   111 +
 system/vendor/swift/Swift/Iterator/MySQLResult.php |   121 +
 system/vendor/swift/Swift/Log.php                  |   152 +
 system/vendor/swift/Swift/Log/DefaultLog.php       |    58 +
 system/vendor/swift/Swift/LogContainer.php         |    47 +
 system/vendor/swift/Swift/Message.php              |   797 +
 system/vendor/swift/Swift/Message/Attachment.php   |   161 +
 system/vendor/swift/Swift/Message/EmbeddedFile.php |    77 +
 system/vendor/swift/Swift/Message/Encoder.php      |   455 +
 system/vendor/swift/Swift/Message/Headers.php      |   573 +
 system/vendor/swift/Swift/Message/Image.php        |    55 +
 system/vendor/swift/Swift/Message/Mime.php         |   500 +
 .../vendor/swift/Swift/Message/MimeException.php   |    22 +
 system/vendor/swift/Swift/Message/Part.php         |   134 +
 system/vendor/swift/Swift/Plugin/AntiFlood.php     |   105 +
 .../vendor/swift/Swift/Plugin/BandwidthMonitor.php |   107 +
 .../swift/Swift/Plugin/ConnectionRotator.php       |   113 +
 system/vendor/swift/Swift/Plugin/Decorator.php     |   259 +
 .../swift/Swift/Plugin/Decorator/Replacements.php  |    77 +
 .../Swift/Plugin/EasySwiftResponseTracker.php      |    46 +
 system/vendor/swift/Swift/Plugin/FileEmbedder.php  |   431 +
 system/vendor/swift/Swift/Plugin/MailSend.php      |   173 +
 system/vendor/swift/Swift/Plugin/Throttler.php     |   168 +
 .../vendor/swift/Swift/Plugin/VerboseSending.php   |    88 +
 .../Swift/Plugin/VerboseSending/AbstractView.php   |    26 +
 .../Swift/Plugin/VerboseSending/DefaultView.php    |    50 +
 system/vendor/swift/Swift/RecipientList.php        |   234 +
 system/views/kohana/template.php                   |    36 +
 system/views/kohana_calendar.php                   |    52 +
 system/views/kohana_error_disabled.php             |    17 +
 system/views/kohana_error_page.php                 |    28 +
 system/views/kohana_errors.css                     |    21 +
 system/views/kohana_profiler.php                   |    37 +
 system/views/kohana_profiler_table.css             |    53 +
 system/views/kohana_profiler_table.php             |    25 +
 system/views/pagination/classic.php                |    39 +
 system/views/pagination/digg.php                   |    83 +
 system/views/pagination/extended.php               |    27 +
 system/views/pagination/punbb.php                  |    37 +
 tests/phpunit/.gitignore                           |     8 +
 tests/phpunit/bootstrap.php                        |   116 +
 tests/phpunit/classes/helpers/Addon_Test.php       |    65 +
 tests/phpunit/classes/helpers/Alerts_Test.php      |   127 +
 tests/phpunit/classes/helpers/Customforms_Test.php |   154 +
 tests/phpunit/classes/helpers/Download_Test.php    |  1151 ++
 tests/phpunit/classes/helpers/Geocode_Test.php     |    47 +
 tests/phpunit/classes/helpers/Reports_Test.php     |   157 +
 .../classes/hooks/Hook_Https_Check_Test.php        |    29 +
 .../MY_Admin_Category_Api_Object_Test.php          |   126 +
 .../libraries/MY_Admin_Reports_Api_Object_Test.php |   258 +
 tests/phpunit/classes/libraries/MY_Auth_Test.php   |   108 +
 .../libraries/MY_Countries_Api_Object_Test.php     |   145 +
 .../libraries/MY_Incidents_Api_Object_Test.php     |   397 +
 .../libraries/MY_Locations_Api_Object_Test.php     |    73 +
 .../classes/libraries/Requirements_Test.php        |   320 +
 tests/phpunit/classes/libraries/Themes_Test.php    |    28 +
 tests/phpunit/classes/libraries/WKT_Test.php       |    38 +
 tests/phpunit/classes/models/Alert_Model_Test.php  |   177 +
 .../phpunit/classes/models/Category_Model_Test.php |   129 +
 .../phpunit/classes/models/Country_Model_Test.php  |    29 +
 tests/phpunit/classes/models/Feed_Model_Test.php   |    84 +
 .../classes/models/Form_Field_Model_Test.php       |    86 +
 .../phpunit/classes/models/Incident_Model_Test.php |   100 +
 tests/phpunit/classes/models/User_Model_Test.php   |    85 +
 tests/phpunit/data/RequirementsTest_a.css          |     1 +
 tests/phpunit/data/RequirementsTest_a.js           |     1 +
 tests/phpunit/data/RequirementsTest_b.css          |     1 +
 tests/phpunit/data/RequirementsTest_b.js           |     1 +
 tests/phpunit/data/RequirementsTest_c.css          |     1 +
 tests/phpunit/data/RequirementsTest_c.js           |     1 +
 tests/phpunit/data/alert.xml                       |     2 +
 tests/phpunit/data/form_field.xml                  |     1 +
 tests/phpunit/phpunit.xml.template                 |    15 +
 tests/phpunit/testbootstrap.php                    |    55 +
 tests/selenium/admin/admin_blocks_hide_test.html   |    27 +
 tests/selenium/admin/admin_blocks_list_test.html   |    27 +
 tests/selenium/admin/admin_blocks_show_test.html   |    27 +
 .../admin/admin_categories_add_failed_test.html    |    48 +
 .../selenium/admin/admin_categories_add_test.html  |    51 +
 .../admin/admin_categories_delete_test.html        |    37 +
 .../selenium/admin/admin_categories_hide_test.html |    32 +
 .../selenium/admin/admin_categories_list_test.html |    27 +
 .../selenium/admin/admin_categories_show_test.html |    33 +
 .../admin/admin_feed_item_delete_test.html         |    32 +
 .../selenium/admin/admin_feed_items_list_test.html |    28 +
 .../admin/admin_feeds_add_failed_test.html         |    37 +
 tests/selenium/admin/admin_feeds_add_test.html     |    52 +
 tests/selenium/admin/admin_feeds_delete_test.html  |    37 +
 tests/selenium/admin/admin_feeds_hide_test.html    |    32 +
 tests/selenium/admin/admin_feeds_show_test.html    |    32 +
 .../admin/admin_feeds_view_items_test.html         |    32 +
 .../admin/admin_forms_add_failed_test.html         |    42 +
 tests/selenium/admin/admin_forms_add_test.html     |    48 +
 tests/selenium/admin/admin_forms_list_test.html    |    27 +
 .../selenium/admin/admin_forms_show_hide_test.html |    38 +
 .../admin/admin_layers_add_failed_test.html        |    44 +
 tests/selenium/admin/admin_layers_add_test.html    |    58 +
 tests/selenium/admin/admin_layers_delete_test.html |    39 +
 tests/selenium/admin/admin_layers_hide_test.html   |    39 +
 tests/selenium/admin/admin_layers_show_test.html   |    40 +
 tests/selenium/admin/admin_login_invalid_test.html |    52 +
 tests/selenium/admin/admin_login_valid_test.html   |    51 +
 .../admin/admin_pages_add_failed_test.html         |    42 +
 tests/selenium/admin/admin_pages_add_test.html     |    42 +
 tests/selenium/admin/admin_pages_delete_test.html  |    37 +
 tests/selenium/admin/admin_pages_hide_test.html    |    37 +
 tests/selenium/admin/admin_pages_list_test.html    |    28 +
 tests/selenium/admin/admin_pages_show_test.html    |    37 +
 .../admin_schedule_activate_deactivate_test.html   |    37 +
 tests/selenium/admin/admin_schedule_edit_test.html |    63 +
 .../admin/admin_sharing_add_failed_test.html       |    42 +
 tests/selenium/admin/admin_sharing_add_test.html   |    57 +
 .../selenium/admin/admin_sharing_delete_test.html  |    37 +
 tests/selenium/admin/admin_sharing_hide_test.html  |    38 +
 tests/selenium/admin/admin_sharing_show_test.html  |    37 +
 tests/selenium/admin_suite.html                    |    54 +
 .../selenium/alerts/alert_marker_present_test.html |    27 +
 tests/selenium/alerts/category_select_test.html    |    87 +
 tests/selenium/alerts/click_map_test.html          |   122 +
 tests/selenium/alerts/map_loaded_test.html         |    27 +
 tests/selenium/alerts/open_alerts_page_test.html   |    27 +
 .../alerts/radius_polygon_present_test.html        |    27 +
 tests/selenium/alerts/radius_slider_test.html      |    82 +
 tests/selenium/alerts_suite.html                   |    20 +
 .../main/main_click_all_categories_test.html       |    32 +
 tests/selenium/main/main_click_filters_test.html   |   102 +
 tests/selenium/main/main_map_loaded_test.html      |    27 +
 .../main/main_map_reports_layer_loaded_test.html   |    27 +
 tests/selenium/main/main_timeline_loaded_test.html |    32 +
 tests/selenium/main_suite.html                     |    18 +
 themes/bueno/WooThemes - Bueno.webloc              |     8 +
 themes/bueno/css/_default.css                      |   240 +
 themes/bueno/css/color-options/blue.css            |    33 +
 themes/bueno/css/color-options/brown.css           |    29 +
 themes/bueno/css/color-options/green.css           |    29 +
 themes/bueno/css/color-options/grey.css            |    30 +
 themes/bueno/css/color-options/purple.css          |    29 +
 themes/bueno/css/color-options/red.css             |    29 +
 themes/bueno/images/bodytile.jpg                   |   Bin 0 -> 35272 bytes
 themes/bueno/images/bullet.png                     |   Bin 0 -> 155 bytes
 themes/bueno/images/bullet_hover.png               |   Bin 0 -> 155 bytes
 .../bueno/images/color-options/blue/bodytile.jpg   |   Bin 0 -> 63637 bytes
 themes/bueno/images/color-options/blue/bullet.png  |   Bin 0 -> 155 bytes
 .../images/color-options/blue/bullet_hover.png     |   Bin 0 -> 155 bytes
 themes/bueno/images/color-options/blue/date.png    |   Bin 0 -> 1071 bytes
 themes/bueno/images/color-options/blue/ico-rss.png |   Bin 0 -> 395 bytes
 .../bueno/images/color-options/brown/bodytile.jpg  |   Bin 0 -> 64573 bytes
 themes/bueno/images/color-options/brown/bullet.png |   Bin 0 -> 155 bytes
 .../images/color-options/brown/bullet_hover.png    |   Bin 0 -> 155 bytes
 themes/bueno/images/color-options/brown/date.png   |   Bin 0 -> 1071 bytes
 .../bueno/images/color-options/brown/ico-rss.png   |   Bin 0 -> 406 bytes
 .../bueno/images/color-options/green/bodytile.jpg  |   Bin 0 -> 60371 bytes
 themes/bueno/images/color-options/green/bullet.png |   Bin 0 -> 155 bytes
 .../images/color-options/green/bullet_hover.png    |   Bin 0 -> 155 bytes
 themes/bueno/images/color-options/green/date.png   |   Bin 0 -> 1072 bytes
 .../bueno/images/color-options/green/ico-rss.png   |   Bin 0 -> 404 bytes
 .../bueno/images/color-options/grey/bodytile.jpg   |   Bin 0 -> 56744 bytes
 themes/bueno/images/color-options/grey/bullet.png  |   Bin 0 -> 155 bytes
 .../images/color-options/grey/bullet_hover.png     |   Bin 0 -> 155 bytes
 themes/bueno/images/color-options/grey/date.png    |   Bin 0 -> 1080 bytes
 themes/bueno/images/color-options/grey/ico-rss.png |   Bin 0 -> 390 bytes
 .../bueno/images/color-options/purple/bodytile.jpg |   Bin 0 -> 80260 bytes
 .../bueno/images/color-options/purple/bullet.png   |   Bin 0 -> 155 bytes
 .../images/color-options/purple/bullet_hover.png   |   Bin 0 -> 155 bytes
 themes/bueno/images/color-options/purple/date.png  |   Bin 0 -> 1073 bytes
 .../bueno/images/color-options/purple/ico-rss.png  |   Bin 0 -> 401 bytes
 themes/bueno/images/color-options/red/bodytile.jpg |   Bin 0 -> 79068 bytes
 themes/bueno/images/color-options/red/bullet.png   |   Bin 0 -> 155 bytes
 .../images/color-options/red/bullet_hover.png      |   Bin 0 -> 156 bytes
 themes/bueno/images/color-options/red/date.png     |   Bin 0 -> 1074 bytes
 themes/bueno/images/color-options/red/ico-rss.png  |   Bin 0 -> 402 bytes
 themes/bueno/images/date.png                       |   Bin 0 -> 1035 bytes
 themes/bueno/images/ico-rss.png                    |   Bin 0 -> 399 bytes
 themes/bueno/images/woothemes.png                  |   Bin 0 -> 2570 bytes
 themes/bueno/license.txt                           |   281 +
 themes/bueno/readme.txt                            |    10 +
 themes/bueno/screenshot.png                        |   Bin 0 -> 52126 bytes
 themes/bueno/views/footer.php                      |    83 +
 themes/bueno/views/header.php                      |    95 +
 themes/default/css/accordion.css                   |    48 +
 themes/default/css/base.css                        |   389 +
 themes/default/css/ie6hacks.css                    |    33 +
 themes/default/css/ie7hacks.css                    |    35 +
 themes/default/css/iehacks.css                     |    41 +
 themes/default/css/slider.css                      |   107 +
 themes/default/css/style.css                       |  2136 +++
 .../default/images/bg_filter-accordion-title.png   |   Bin 0 -> 180 bytes
 themes/default/images/cat-filters-bg.jpg           |   Bin 0 -> 350 bytes
 themes/default/images/category_1_1305234038.png    |   Bin 0 -> 1102 bytes
 .../default/images/category_1_1305234038_16x16.png |   Bin 0 -> 461 bytes
 themes/default/images/category_5_1305234050.png    |   Bin 0 -> 495 bytes
 .../default/images/category_5_1305234050_16x16.png |   Bin 0 -> 258 bytes
 themes/default/images/filters-bg.jpg               |   Bin 0 -> 353 bytes
 themes/default/images/green-btn-l.gif              |   Bin 0 -> 72 bytes
 themes/default/images/green-btn-r.gif              |   Bin 0 -> 72 bytes
 themes/default/images/icon_sprite-2.png            |   Bin 0 -> 1892 bytes
 themes/default/images/icons/application-form.png   |   Bin 0 -> 454 bytes
 themes/default/images/icons/cross-button.png       |   Bin 0 -> 588 bytes
 themes/default/images/icons/facebook.png           |   Bin 0 -> 554 bytes
 themes/default/images/icons/film.png               |   Bin 0 -> 654 bytes
 themes/default/images/icons/films.png              |   Bin 0 -> 664 bytes
 themes/default/images/icons/foursquare.png         |   Bin 0 -> 836 bytes
 themes/default/images/icons/image.png              |   Bin 0 -> 562 bytes
 themes/default/images/icons/images.png             |   Bin 0 -> 671 bytes
 themes/default/images/icons/linkedin.png           |   Bin 0 -> 631 bytes
 themes/default/images/icons/mail.png               |   Bin 0 -> 505 bytes
 themes/default/images/icons/meetup.png             |   Bin 0 -> 538 bytes
 themes/default/images/icons/mobile-phone.png       |   Bin 0 -> 515 bytes
 themes/default/images/icons/newspaper.png          |   Bin 0 -> 581 bytes
 themes/default/images/icons/newspapers.png         |   Bin 0 -> 631 bytes
 themes/default/images/icons/posterous.png          |   Bin 0 -> 659 bytes
 themes/default/images/icons/reddit.png             |   Bin 0 -> 694 bytes
 themes/default/images/icons/social-rss.png         |   Bin 0 -> 747 bytes
 themes/default/images/icons/stumbleupon.png        |   Bin 0 -> 794 bytes
 themes/default/images/icons/technorati.png         |   Bin 0 -> 738 bytes
 themes/default/images/icons/tick-button.png        |   Bin 0 -> 605 bytes
 themes/default/images/icons/tumblr.png             |   Bin 0 -> 642 bytes
 themes/default/images/icons/twitter.png            |   Bin 0 -> 676 bytes
 themes/default/images/icons/vimeo.png              |   Bin 0 -> 705 bytes
 themes/default/images/icons/yelp.png               |   Bin 0 -> 774 bytes
 themes/default/images/icons/youtube.png            |   Bin 0 -> 705 bytes
 themes/default/images/loading-animation_18x18.gif  |   Bin 0 -> 7386 bytes
 themes/default/images/page-bg.gif                  |   Bin 0 -> 51 bytes
 themes/default/images/page-bg.jpg                  |   Bin 0 -> 708 bytes
 themes/default/images/pause.gif                    |   Bin 0 -> 56 bytes
 themes/default/images/search-button.jpg            |   Bin 0 -> 462 bytes
 themes/default/images/share.jpg                    |   Bin 0 -> 2192 bytes
 themes/default/images/submit-incident.png          |   Bin 0 -> 350 bytes
 themes/default/images/tooltip-arrow.png            |   Bin 0 -> 225 bytes
 themes/default/readme.txt                          |     8 +
 themes/default/screenshot.png                      |   Bin 0 -> 32183 bytes
 themes/default/views/alerts/alerts_js.php          |   144 +
 themes/default/views/alerts/confirm.php            |    86 +
 themes/default/views/alerts/main.php               |    85 +
 themes/default/views/alerts/radius.php             |    43 +
 themes/default/views/alerts/unsubscribe.php        |    29 +
 themes/default/views/alerts/verify.php             |    46 +
 themes/default/views/blocks/main_news.php          |    42 +
 themes/default/views/blocks/main_reports.php       |    39 +
 themes/default/views/contact.php                   |    71 +
 themes/default/views/feed/atom.php                 |    40 +
 themes/default/views/feed/feeds.php                |    43 +
 themes/default/views/feed/rss2.php                 |    36 +
 themes/default/views/footer.php                    |    84 +
 themes/default/views/header.php                    |    96 +
 themes/default/views/header_nav.php                |    71 +
 themes/default/views/kohana_error_page.php         |    79 +
 themes/default/views/login/login_js.php            |    69 +
 themes/default/views/login/main.php                |   218 +
 themes/default/views/main/layout.php               |   275 +
 themes/default/views/main/main_js.php              |   362 +
 themes/default/views/main/map.php                  |    11 +
 themes/default/views/main/timeline.php             |    20 +
 themes/default/views/page.php                      |    11 +
 .../default/views/pagination/front-end-reports.php |    97 +
 themes/default/views/profile/main.php              |    12 +
 themes/default/views/profile/user.php              |    58 +
 themes/default/views/reports/comments.php          |    21 +
 themes/default/views/reports/comments_form.php     |    68 +
 themes/default/views/reports/detail.php            |   225 +
 .../default/views/reports/detail_custom_forms.php  |    64 +
 themes/default/views/reports/list.php              |   177 +
 themes/default/views/reports/main.php              |   226 +
 themes/default/views/reports/reports_js.php        |   943 ++
 themes/default/views/reports/stats.php             |    14 +
 themes/default/views/reports/submit.php            |   337 +
 .../default/views/reports/submit_custom_forms.php  |   336 +
 themes/default/views/reports/submit_edit_js.php    |   951 ++
 themes/default/views/reports/submit_thanks.php     |    18 +
 themes/default/views/reports/view_js.php           |   249 +
 themes/default/views/search.php                    |    13 +
 themes/polaroid/css/_default.css                   |   269 +
 themes/polaroid/css/colorbox.css                   |    78 +
 themes/polaroid/hooks/register_polaroid_block.php  |    58 +
 themes/polaroid/images/bg_body.png                 |   Bin 0 -> 144 bytes
 themes/polaroid/images/bg_body_alt.png             |   Bin 0 -> 148 bytes
 themes/polaroid/images/bg_polaroid-pic.png         |   Bin 0 -> 2973 bytes
 themes/polaroid/images/colorbox/border1.png        |   Bin 0 -> 1057 bytes
 themes/polaroid/images/colorbox/border2.png        |   Bin 0 -> 170 bytes
 .../images/colorbox/ie6/borderBottomCenter.png     |   Bin 0 -> 153 bytes
 .../images/colorbox/ie6/borderBottomLeft.png       |   Bin 0 -> 473 bytes
 .../images/colorbox/ie6/borderBottomRight.png      |   Bin 0 -> 470 bytes
 .../images/colorbox/ie6/borderMiddleLeft.png       |   Bin 0 -> 148 bytes
 .../images/colorbox/ie6/borderMiddleRight.png      |   Bin 0 -> 139 bytes
 .../images/colorbox/ie6/borderTopCenter.png        |   Bin 0 -> 153 bytes
 .../polaroid/images/colorbox/ie6/borderTopLeft.png |   Bin 0 -> 359 bytes
 .../images/colorbox/ie6/borderTopRight.png         |   Bin 0 -> 465 bytes
 themes/polaroid/images/colorbox/loading.gif        |   Bin 0 -> 9427 bytes
 themes/polaroid/js/_jquery.colorbox-min.js         |     4 +
 themes/polaroid/js/_jquery.isotope.min.js          |    13 +
 themes/polaroid/js/initialize.js                   |    39 +
 themes/polaroid/readme.txt                         |    24 +
 themes/polaroid/screenshot.png                     |   Bin 0 -> 56257 bytes
 themes/polaroid/views/blocks/polaroid_reports.php  |   114 +
 themes/polaroid/views/footer.php                   |    83 +
 themes/polaroid/views/header.php                   |    95 +
 themes/polaroid/views/main/layout.php              |     9 +
 themes/polaroid/views/main/main_js.php             |     0
 themes/polaroid/views/reports/detail.php           |   222 +
 themes/terra/css/terra.css                         |    89 +
 themes/terra/images/terra.gif                      |   Bin 0 -> 214 bytes
 themes/terra/readme.txt                            |     6 +
 themes/terra/screenshot.png                        |   Bin 0 -> 33526 bytes
 themes/unicorn/css/_default.css                    |   313 +
 .../unicorn/images/bg_category-filter-controls.png |   Bin 0 -> 1186 bytes
 themes/unicorn/images/bg_solid-lines-for-IE.png    |   Bin 0 -> 172 bytes
 themes/unicorn/images/bg_top-cap.png               |   Bin 0 -> 525 bytes
 themes/unicorn/images/bg_transparent-lines.png     |   Bin 0 -> 183 bytes
 themes/unicorn/images/drag_edit_all.png            |   Bin 0 -> 660 bytes
 themes/unicorn/readme.txt                          |    17 +
 themes/unicorn/screenshot.png                      |   Bin 0 -> 59645 bytes
 themes/unicorn/views/footer.php                    |    84 +
 themes/unicorn/views/header.php                    |   108 +
 themes/unicorn/views/main/layout.php               |   273 +
 2109 files changed, 265313 insertions(+), 70 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a262a4a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+.*swp
+.htaccess
+*~
+*.DS_Store
+application/logs/*
+application/cache/*
+media/uploads/*
+application/config/auth.php
+application/config/config.php
+application/config/database.php
+application/config/encryption.php
+tests/phpunit/phpunit.xml
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..d0a354e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "application/i18n"]
+	path = application/i18n
+	url = git://github.com/ushahidi/Ushahidi-Localizations.git
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 0000000..2cfe104
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,30 @@
+# Turn on URL rewriting only when mod rewrite is turn on
+
+<IfModule mod_rewrite.c>
+	RewriteEngine On
+
+	# Installation directory
+	RewriteBase /
+
+	# Protect application and system files from being viewed
+	RewriteRule ^(application|modules|system|tests|sql) - [F,L]
+
+	# Allow any files or directories that exist to be displayed directly
+	RewriteCond %{REQUEST_FILENAME} !-f
+	RewriteCond %{REQUEST_FILENAME} !-d
+
+	# Rewrite all other URLs to index.php/URL
+	RewriteRule .* index.php?kohana_uri=$0 [L,QSA]
+</IfModule>
+
+# Protect the htaccess from being viewed
+<Files .htaccess>
+	order allow,deny
+	deny from all
+</Files>
+
+# Don't show directory listings for URLs which map to a directory.
+Options -Indexes
+
+#Follow symlinks
+Options +FollowSymlinks
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100755
index 0000000..e386107
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,437 @@
+Ushahidi 2.7.4 (Bug fix release) - 05/08/2014
+-------------------------------------
+
+### Major Fixes
+
+* Upgraded Openlayers to version 2.13.1 (https://github.com/ushahidi/Ushahidi_Web/pull/1391) 
+* Stop checking what characters are in passowrd (https://github.com/ushahidi/Ushahidi_Web/pull/1373) 
+* Added option for admins to change the maximum file size for attaching photos to reports (https://github.com/ushahidi/Ushahidi_Web/pull/1369)
+* Delete cache after changing theme so changes reflect immediately (https://github.com/ushahidi/Ushahidi_Web/pull/1368)
+
+Ushahidi 2.7.3 (Bug fix release) - 23/04/2014
+-------------------------------------
+
+### Major Fixes
+
+* Timeline upgrade (https://github.com/ushahidi/Ushahidi_Web/pull/1341) 
+* Fixed issue with dual alert sign up (https://github.com/ushahidi/Ushahidi_Web/pull/1339) 
+* Updated sms helper and scheduler to add url in sms (https://github.com/ushahidi/Ushahidi_Web/pull/1339)
+* Check category translation on csv import to avoid category duplication (https://github.com/ushahidi/Ushahidi_Web/pull/1345)
+* Fixed issue with the reverse geocoder (https://github.com/ushahidi/Ushahidi_Web/pull/1336)
+* Fixed broken video embeds in reports when viewed over HTTPS (https://github.com/ushahidi/Ushahidi_Web/pull/1328)
+* Added South Sudan and Kosovo to the country list (https://github.com/ushahidi/Ushahidi_Web/pull/1324) 
+* Fixed issue with feed category 
+* Replaced feedback email after submit report with site email
+
+Ushahidi 2.7.2 (Bug fix release) - 28/01/2014
+-------------------------------------
+
+### Major Fixes
+
+* Tiding up conflicting logic on email alerts(https://github.com/ushahidi/Ushahidi_Web/issues/943) 
+* Escaping db password (https://github.com/ushahidi/Ushahidi_Web/issues/1265) 
+* Added Feed category (https://github.com/ushahidi/Ushahidi_Web/issues/1291) 
+* Fix incosistency in custom fields position(https://github.com/ushahidi/Ushahidi_Web/issues/1296) 
+* Handle bad file encodings when importing CSV files (https://github.com/ushahidi/Ushahidi_Web/issues/1140) 
+* Adding Nominatin as a geocoder option (https://github.com/ushahidi/Ushahidi_Web/issues/794) 
+* Fixed reports date picker 
+* Added reverse geocoder 
+* Moved header_nav.php to themes/default/views/ to allow theme override 
+* Cleaned up alerts scheduler 
+* Removed ci_cumulus theme 
+* Removed checkins 
+* Added Delete All Reports for Admin
+* Fixed timeline js timing out
+* Lots of code clean up, error handling and coding standards.
+
+Ushahidi 2.7.1 (Bug fix release) - 04/09/2013
+-------------------------------------
+
+### Major Fixes
+
+* Ushahidi should not upsize uploaded images, only downsize them[#1132](https://github.com/ushahidi/Ushahidi_Web/issues/1132)
+* Tagline running under report button[#1040](https://github.com/ushahidi/Ushahidi_Web/issues/1040)
+* Long, Lat order on mouse hover[#1092](https://github.com/ushahidi/Ushahidi_Web/issues/1092)
+* Bulk deletions coupled with single report deletion[#1093](https://github.com/ushahidi/Ushahidi_Web/issues/1093)
+* Unexpected Function error[#1123](https://github.com/ushahidi/Ushahidi_Web/issues/1123)
+* ‘Undefined’ on hover[#1067](https://github.com/ushahidi/Ushahidi_Web/issues/1067)
+* Wrong Content Type for RSS Feeds[#991](https://github.com/ushahidi/Ushahidi_Web/issues/991)
+* Twitter Credential Error[#1109](https://github.com/ushahidi/Ushahidi_Web/issues/1109)
+* Validation error on custom fields dropdown[#1113](https://github.com/ushahidi/Ushahidi_Web/issues/1113)
+* CSV Uploader[#1181](https://github.com/ushahidi/Ushahidi_Web/issues/1181)
+* Error with reports view page[#1176,1166,1178](https://github.com/ushahidi/Ushahidi_Web/issues/1176,https://github.com/ushahidi/Ushahidi_Web/issues/1166,https://github.com/ushahidi/Ushahidi_Web/issues/1178)
+* Login page issues[#1161](https://github.com/ushahidi/Ushahidi_Web/pull/1161)
+* Issues on Crowdmap[#1158](https://github.com/ushahidi/Ushahidi_Web/issues/1158)
+* Database table prefix installation[#1154](https://github.com/ushahidi/Ushahidi_Web/issues/1154)
+* Upload images .jpeg fails[#1136](https://github.com/ushahidi/Ushahidi_Web/issues/1136)
+* Database Error[#1149](https://github.com/ushahidi/Ushahidi_Web/issues/1149)
+* Category impact charts not loading[#1147](https://github.com/ushahidi/Ushahidi_Web/issues/1147)
+* Fatal error in stats page[#1134](https://github.com/ushahidi/Ushahidi_Web/pull/1134)
+* Twitter scheduler bugs[#1133](https://github.com/ushahidi/Ushahidi_Web/pull/1133)
+* CSV downloaded form platform throws errors on attempt to upload[#1140](https://github.com/ushahidi/Ushahidi_Web/issues/1140)
+* Default report time on reports always wrong[#1130](https://github.com/ushahidi/Ushahidi_Web/pull/1130)
+* Date picker issues in default theme[#1126](https://github.com/ushahidi/Ushahidi_Web/pull/1126)
+* Make nav helper more friendly and filterable for plugins and themes[#1125](https://github.com/ushahidi/Ushahidi_Web/pull/1125)
+* Cannot enable clean URLs from admin[#1124](https://github.com/ushahidi/Ushahidi_Web/pull/1124)
+* Unable to create new users, database error[#1122](https://github.com/ushahidi/Ushahidi_Web/pull/1122)
+* Incident location not on incident pages[#1121](https://github.com/ushahidi/Ushahidi_Web/issues/1122)
+* Table name escaping creates SQL error when there’s a table prefix[#1118](https://github.com/ushahidi/Ushahidi_Web/pull/1118)
+* Error adding report to a photo (Crowdmap Classic)[#1099](https://github.com/ushahidi/Ushahidi_Web/pull/1099)
+
+
+Ushahidi 2.7 (Bamako) - 02/05/2013
+-------------------------------------
+
+### Major changes
+
+* Use OAuth to grab twitter feeds
+* Better XSS protection
+	- Add HTMLPurifier library for proper HTML sanitization
+	- Add function to html helper: html::escape() html::strip_tags() html::clean()
+	  These should be used instead of htmlentities, string_tags or other built in HTML cleaning functions
+* Theming changes
+	- Use CDN for theme files too [#904](https://github.com/ushahidi/Ushahidi_Web/issues/904)
+	- Add theme inheritance and css/js overriding
+		* This still default to including the default theme
+		* Allows themes to specify CSS/JS files to include through readme.txt
+		* Allow themes to override CSS/JS from parent theme by include a file of the same name
+	- Split out themes/default/css/style.css
+	- Handle all CSS / JS includes through 1 library: Requirements
+		* This enables us to combine and compress these files
+		* We're adding CSSMin and JSMin to compress files
+		* A bunch of new options in application/config/requirements.php
+	- Add support for RTL css files through Requirements library.
+		* All CSS files can be replaced by a file of the same name with the -rtl suffix.
+	- Further documentation here: https://wiki.ushahidi.com/display/WIKI/Managing+CSS+and+JS+in+Ushahidi
+* Reworking reports upload and download
+	- Adding support for upload/download of reports via XML format
+	- Adding Form_id to downloaded CSV, allowing for import of reports/field responses matched with their respective forms [#792](https://github.com/ushahidi/Ushahidi_Web/issues/792).
+		* Custom fields within different forms but with the same name shall be differentiated by the form_id appended to column names
+* New hooks and events
+	- Added hook for getting the incident object from the member's report controller [#891](https://github.com/ushahidi/Ushahidi_Web/issues/891)
+	- Add new event to change members main tabs [#882](https://github.com/ushahidi/Ushahidi_Web/issues/882)
+	- Add event to allow adding extra variables to a view [#550](https://github.com/ushahidi/Ushahidi_Web/issues/550)
+	- Add report_save hook to incidents model [#913](https://github.com/ushahidi/Ushahidi_Web/issues/913)
+
+### Other changes and fixes
+
+* Removing hard coded HTTP requests
+	- Add config.external_site_protocol setting to control if external requests use HTTP
+* Ushahidi.js / Other mapping improvements
+	- Restore Openlayers TMS support so cloudmade works again [#911](https://github.com/ushahidi/Ushahidi_Web/issues/911)
+	- Fix broken map on /reports/view/XXX pages
+	- Improve handling of marker selection in Ushahidi.js [#780](https://github.com/ushahidi/Ushahidi_Web/issues/780)
+	- Fix handling for layer urls with query parameters
+	- Make map helper handle TMS layers
+	- Extend timeline by day to up to 6 month [#964](https://github.com/ushahidi/Ushahidi_Web/issues/964)
+	- Make main map filter by start and end date on first load [#964](https://github.com/ushahidi/Ushahidi_Web/issues/964)
+	- Set Google maps language based on current locale
+	- Fix json/cluster when some reports have no location [#907](https://github.com/ushahidi/Ushahidi_Web/issues/907)
+	- Improve JSON controller for easier extension [#853](https://github.com/ushahidi/Ushahidi_Web/issues/853)
+	- Build cities list from OSM instead of Geonames [#979](https://github.com/ushahidi/Ushahidi_Web/issues/979)
+* Custom forms fixes
+	- Fix form field visibility/submission permissions [#744](https://github.com/ushahidi/Ushahidi_Web/issues/744)
+	- Fix custom form fields with large list of select options [#906](https://github.com/ushahidi/Ushahidi_Web/issues/906)
+	- Fix custom form fields permissions [#695](https://github.com/ushahidi/Ushahidi_Web/issues/695)
+	- Don't assume all users have roles that are pushed in customforms helper.
+	- Removing index in form_field table for those upgrading [#922](https://github.com/ushahidi/Ushahidi_Web/issues/922).
+* API fixes
+	- Comments API fixes [#918](https://github.com/ushahidi/Ushahidi_Web/issues/918)
+	- Fix fatal errors in KML api
+	- Fix for API authentication on installations that use CrowdmapID
+	- Fix incidents API returning spam comments. Closes [#1002](https://github.com/ushahidi/Ushahidi_Web/issues/1002)
+	- Make api?task=reports able to submit reports too [#988](https://github.com/ushahidi/Ushahidi_Web/issues/988)
+	- Allow HTTP Basic Auth for authentication anywhere, not just API. Particularly useful for private deployments
+	- Only reset session for non-ajax API requests [#791](https://github.com/ushahidi/Ushahidi_Web/issues/791)
+	- Added support for custom fields in Ushahidi API
+* Scheduler
+	- Optimize cleanup scheduler to only load image media type
+	- Scheduler: Add locking mechanism base on using mysql
+	- Scheduler: Check and increase max_execution_time if its too low
+* Optimizations
+	* Adding indexes based on @jetherton's blog post to speed up sql queries
+	* Optimize User_Model::has_permission() to only load roles once
+	* Load feed items for feed block with the feed data in 1 query 
+* Fixing and improving date fitlers:
+	- Allow date filters with only 'from' or 'to' value, not both
+	- Fix /reports date filter: make 'All Time' filter work [#91](https://github.com/ushahidi/Ushahidi_Web/issues/91)
+	- Make fetch_incidents() date search from beginning till end of day [#220](https://github.com/ushahidi/Ushahidi_Web/issues/220)
+* Other miscellaneous changes
+	- Allow deleting multiple feed items [#981](https://github.com/ushahidi/Ushahidi_Web/issues/981)
+	- Fix redirect to addons/plugins when clean urls are off. Closes [#1061](https://github.com/ushahidi/Ushahidi_Web/issues/1061)
+	- Fix unicorn theme with man nav items. Closes [#952](https://github.com/ushahidi/Ushahidi_Web/issues/952)
+	- Clarify what facebook settings are for. Closes [#1059](https://github.com/ushahidi/Ushahidi_Web/issues/1059)
+	- Site banner setting: accept jpeg and add error message. Closes [#579](https://github.com/ushahidi/Ushahidi_Web/issues/579)
+	- Fix incident rating: get total incident rating, not single rating entry
+	- Delete form responses when deleting an incident.
+	- Correct OSM attribution. Closes [#1029](https://github.com/ushahidi/Ushahidi_Web/issues/1029)
+	- Fix public listing: Pass lat,lon of map center to public listing form.
+	- Fix lat/lon checks on reports/edit form
+	- Fix more info form when member logs in [#300](https://github.com/ushahidi/Ushahidi_Web/issues/300)
+	- Fix [#993](https://github.com/ushahidi/Ushahidi_Web/issues/993) undefined variable when resetting password.
+	- Fix missing table prefix when listing messages by reporter [#992](https://github.com/ushahidi/Ushahidi_Web/issues/992)
+	- Rewrote a large portion of the CrowdmapID authentication driver to resolve character encoding issues and improve error handling.
+	- Fix Category_Model::get_categories with prefixes in the database [#994](https://github.com/ushahidi/Ushahidi_Web/issues/994)
+	- Support thumbnails for Videos
+	- Better handling of youtube URL without v= first [#982](https://github.com/ushahidi/Ushahidi_Web/issues/982)
+	- Better handling of missing settings in hooks/2_settings.php [#963](https://github.com/ushahidi/Ushahidi_Web/issues/963)
+	- More information link on /reports [#935](https://github.com/ushahidi/Ushahidi_Web/issues/935)
+	- Add config option to enable the profiler everywhere
+	- Fix data switcher on /reports so it sits above the map
+	- Make blocks::render() handle missing block classes gracefully [#916](https://github.com/ushahidi/Ushahidi_Web/issues/916)
+	- Add extra class to custom field ```<tr>``` and check to show empty fields [#914](https://github.com/ushahidi/Ushahidi_Web/issues/914)
+	- Fix error in reports::verify_approve() if no authenticated user [#912](https://github.com/ushahidi/Ushahidi_Web/issues/912)
+	- Add admin reports search form [#220](https://github.com/ushahidi/Ushahidi_Web/issues/220)
+	- Fix html escaping with UTF8 characters [#908](https://github.com/ushahidi/Ushahidi_Web/issues/908)
+	- Make Settings_Model::save_setting() work when inserting new records too
+	- Fix errors when signing up for mobile alerts [#895](https://github.com/ushahidi/Ushahidi_Web/issues/895)
+	- Fix category::form_tree() not closing ```<li>``` and ```<ul>``` tags. [#905](https://github.com/ushahidi/Ushahidi_Web/issues/905)
+	- Fixing bug: Editing a pre-existing incident as a member creates a duplicate incident [#897](https://github.com/ushahidi/Ushahidi_Web/issues/897)
+	- Don't append country name to locations in /admin/reports. Fixes [#880](https://github.com/ushahidi/Ushahidi_Web/issues/880)
+	- Add new lines between phone number [#879](https://github.com/ushahidi/Ushahidi_Web/issues/879).
+
+Ushahidi 2.6.1 - Security Fix Release, 20-11-2012
+-------------------------------------
+
+* Vulnerability: Forgotten password challenge guessable. 
+
+Ushahidi 2.6 (Tripoli), 23-10-2012
+-------------------------------------
+* Improved way of handling translations
+	- Ushahidi translations are now managed through Transifex  - Ushahidi
+	  translations are now managed through Transifex 
+	- The Ushahidi-Localizations repo is now included in core through a
+	  submodule. Rungit submodule update --init when you next pull from git or
+	  git clone --recursive git://github.com/ushahidi/Ushahidi_Web.git to
+	  clone and include submodules
+	- All localizations will be shipped with core releases
+	- In addition , support for category translations have been added to the
+	  categories API
+	- For more on Localization, check out the wiki
+	  -> https://wiki.ushahidi.com/display/WIKI/Localization
+* Support for JSONP  
+	- We have added JSONP(http://en.wikipedia.org/wiki/JSONP) support to the API to allow cross-domain interaction.
+* Zooming Controls - With an upgrade to openlayers 2.12 , several cool things have been added
+	- kinetic dragging: http://openlayers.org/dev/examples/kinetic.html
+	- animated panning:http://openlayers.org/dev/examples/animated_panning.html
+	- zoom transitions: http://openlayers.org/dev/examples/transition.html / http://openlayers.org/dev/examples/google-v3.html
+* Better handling of reports layer
+	- Better handling of reports layer
+	- Old layer not removed until new one loaded.
+	- Fade between old/new zoom layer
+	- No reload on zoom unless clustering
+* Actions: The actions feature has had some more work done i.e
+	- Added UI for editing and action
+	- Added UI for deleting an action
+	- Added actions trigger for geotagged feed items
+* Refactor JSON controller for easier extension: Increases code reuse and add the following events
+	- ushahidi_filter.json_replace_markers
+	- ushahidi_filter.json_replace_markers
+	- ushahidi_filter.json_features
+* More events in main view
+	- ushahidi_action.main_sidebar_pre_filters
+	- ushahidi_action.main_sidebar_post_filters
+* Add events to tweak get_incident / get_neighbouring_incidents SQL
+	- ushahidi_filter.get_neighbouring_incidents_sql
+	- ushahidi_filter.get_incidents_sql
+* Rework Upload and Download Feature
+	- Upload of custom form fields
+	- Case insensitivity issues fixed - Previously, csv content in lower case
+	  would not be recognised and would cause upload failure, forcing users to
+	  adhere to uppercase content for particular fields. Users can now upload
+	  content regardless of what case the csv content is in.
+	- Added support for upload/download of Incident reporters
+	- Compatibility issues with CSV files generated by non UNIX systems
+
+
+Ushahidi 2.5 (Cairo), 01-08-2012
+-------------------------------------
+
+* Mapping: 
+	- The mapping code has been refactored and decoupled from the timeline. 
+	- There are now events triggered for actions such as zoom changes, changine layer, etc. , which can be used to extend mapping function
+	- The timeline can now be turned on or off.
+* Themes: 
+	- The views have been namespaced on a per-controller basis.
+	- Views for the front-end (including JS) have all been moved to the themes folder
+* Settings table:
+	- The structure for the settings table has been modified so that data are stored as key/value pairs.
+* Installer:
+	- The installer has been reworked in order to work with the new settings table structure
+	- Additionally, we now perform the installation check in index.php
+* Configuration files:
+	- The following files are no longer in the repository: config.php, auth.php and encryption.php.
+		In their place are templates (.template.php files) which are used by the installer to generate the respective config files.
+* Roles and Permission:
+	- User permissions have been refactored into a separate table form roles. This now allows plugins to add their own permissions.
+* Alerts
+	- Allow admin to view, search and delete user alerts in the system
+* Security fixes thanks to OWASP Portland
+	- Multiple SQL injections  (Thanks: Timothy D. Morgan, Kees Cook, postmodern)
+	- Missing authentication on comments, reports and email API calls  (Thanks: Kees Cook, Dennison Williams)
+	- Admin user hijacking through the installer  (Thanks: Wil Clouser)
+	- Stored XSS on member profile pages  (Thanks: Amy K. Farrell)
+	- User data exposed in comment API
+* Further details on migrating to 2.5 are available on the wiki
+  -> https://wiki.ushahidi.com/display/WIKI/Migrating+to+Ushahidi+2.5
+
+
+Ushahidi 2.4.1 - Upgrader fix release, 01-08-2012
+-------------------------------------
+
+* Add support for removing old files during upgrade (#716)
+* Clear cache after new files copied over
+* Fix for DB upgrades 
+
+
+Ushahidi 2.4 - Bug fix release, 30-05-2012
+-------------------------------------
+
+* Fix A 500 error that was being thrown by the Scheduler (#387)
+* Category icons now showing up on the map (#390)
+* Fix map styling on the Actions/Triggers module (#435)
+* Security fixes
+	 - Cross Site Scripting (XSS)
+* More on the bugs fixed can be found here:
+	-> https://docs.google.com/a/ushahidi.com/spreadsheet/ccc?key=0AizezfooB3k9dC1GX3RsNl9ocnQ3U1lmTUtSbFAwMlE#gid=7
+
+
+
+Ushahidi 2.3.2 - Bug fix release, 08-05-2012
+-------------------------------------
+
+* Security fix for Admin API - prevent member role from accessing the admin API. (SA-WEB-2012-005)
+* Fix Google maps base layers - base layer type were being incorrectly escaped
+* Fix session driver change from 2.3.1 - Set session driver back to cookie as other drivers are buggy
+
+Ushahidi 2.3.1 - Security fix release, 30-04-2012
+-------------------------------------
+
+* Security fix for session storage - sessions from any deployment were valid on any other deployment (SA-WEB-2012-004)
+
+Ushahidi 2.3 - (Juba) Bug fix release, 24-04-2012
+--------------------------
+* Cleaned up database Schema
+* Few improvements on the installer 
+	– This includes the introduction of an admin login email configuration.
+	- Hiding of admin password once installation is complete.
+* Added HTML editing and more attributes to the page editor. 
+* Security fixes
+    - Cross Site Request Forgery (CSRF)
+    - Cross Site Scripting (XSS)
+
+Ushahidi 2.2.1 - bug fix release, 04-04-2012
+--------------------------
+* Map JS unification (#278)
+* Make Scheduler JS options (#331)
+* Update child categories of parent category that was reassigned to id 999 updated to match the correct parent_id 999 and not 5 (#322) 
+* Zoom level is never set on Get Alerts map (#358)
+* Security fixes
+    - JSON controller returned non-approved reports
+    - JSON controller exposed on private deployment
+    - Markers controller exposed on private deployment
+    - json/single/ID SQL injection vulnerability
+    - Download reports (admin) could inadvertently return non-approved reports
+
+Ushahidi 2.2 (Juba), 13-03-2012
+---------------------------------
+* Riverid Integration
+    - RiverID is an authentication and identity management system that provides users with a secure central sign-on facility.
+    - With RiverID, your are able to access all the Ushahidi products using just one username and password. This includes all the Crowdmaps you've set up and, when launched, your SwiftRiver streams.
+    - This eliminates the need for multiple passwords per person per Crowdmap deployment. Though, you are free to use as many different passwords as you choose.
+* Base Map defaulted to OSM
+    - The default map version has been set to OpenStreetMap (OSM). There is a drop-down to select your map. You can select to use a variety of maps including Google.
+* Badges
+	- Badges are a great way for Ushahidi deployers to award their users.
+	- Developers can also find badge image resources to include in their projects.
+	- These are badge images in a variety of categories which can be used in Ushahidi or Crowdmap deployments or other services.	
+* Automated Actions
+    - The admin panel now allows you to set up automated actions on your deployment.
+	- You are now able to set a chain of events into motion when certain conditions that you specify are hit.
+* Alerts
+    - Subscription of Alerts via SMS.
+    - Unsubscribing from mobile alerts.
+* Security
+    - Plugged SQL and HTML injection vulnerabilites
+* Plugins
+    - Sharing feature moved into a plugin
+	- New plugins, adsense and Viddler
+* Public Listing
+    - Plublic listing of deployment which increases your deployment's discoverability.
+* Features changed on core
+    - Changed the Pages editor from Tinymce to JWYSIWYG
+* Themes
+	- More themes to choose from.
+
+
+Ushahidi 2.1 (Tunis), 09-08-2011
+---------------------------------
+* Usability
+    - A new multi-faceted reports listing page that provides the user with the ability
+    to filter reports using a variety of options such as categories, verification status, report submission channel
+    - Centralized the validation and saving of reports. This is now handled by the reports helper
+    - Streamlined the list of parameters for filtering reports on the frontend
+    - Implemented category sorting in a drag and drop fashion
+    - A new blocks feature to allow the user to toggle the display of certain sections on the front-end
+    - Enhanced the custom forms feature (courtesy of the Konpa Group) to better augment data collection via the reports submission page
+    - Ability to add geometries (polygons) to a report
+    - Option to get a taller/wider map in the report viewing page
+* Checkins (Experimental)
+    - Checkins system
+* Facebook Module
+* Member Module
+    - New member module that allows people to log in to a deployment via OpenID or a username/password
+    pair specific to the deployment
+* Performance
+    - Refactored the json controller to fetch all the reports via a single query instead of using multiple
+    queries
+    - Switched to straight SQL instead of ORM for fetching the reports
+* Security
+    - Plugged SQL injection vulnerabilites
+* Testing
+    - Added test framework (PHPUnit for unit tests and Selenium for functional tests) and tests
+* JavaScript
+    - Switched from GML to Vector layers in timline.js for both the reports and KML layers on the main map
+* Features removed from the core
+    - Removed Laconica from the list of message services. No longer supported
+
+Ushahidi 2.0 (Luanda), 22-11-2010
+---------------------------------
+* Usability
+    - Improved reports listing to convey different sets of information quickly and in a practical way
+* Themes
+    - Themes can now be created without having to tamper with the code.
+* API
+    - Refactored the API controller and implemented the API system in a modular fashion. 
+      API tasks/features are now availed by way of libraries
+* Plugins
+    - New plugin architecture based on Kohana Events
+    - Implemented FrontlineSMS and Clickatell SMS services as plugins and added them to the list of stock plugins
+    - SMSSync plugin for the SMSSync Android application
+* Scheduler
+    - Ability to schedule tasks in cron like fashion
+* Upgrader
+    - An upgrader to automatically update the platform the latest release
+    
+Ushahidi 1.2 (After Haiti in Jan 2010)
+--------------------------------------
+* Usability
+    - A collapsible category tree on the submit report page
+* Clustering
+    - Clustering of reports is now on the server side. It was previously being done on the client side via JavaScript
+* Custom Forms
+    - A custom forms feature to collect additional information in addition to that in the "Submit Report" page
+* Report Edit Logs
+    - Edit logs for a report are now available so a user of the admin console can see the series of changes/revisions/edits 
+      that have been made to a report
+
+Ushahidi 1.0 (Mogadishu) 10-12-2009
+-----------------------------------
+* A web installer is now bundled with the platform to ease the process of deploying/installing the platform
+* New CSS based design that is easier to skin
+* Admin email notifications on emails and comments
+* Site statistics and analytics
+* Feature to add custom pages and tabs
+* Auto-geotagging of news feeds
+* Ability to create reports from news feeds
+* Feature to add KML/KMZ overlays on the map
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..893f50b
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,17 @@
+Ushahidi is an open source project. We encourage you to pitch in. [Join the mailing list](http://list.ushahidi.com) and dive into the [wiki](https://wiki.ushahidi.com)!
+
+* If you want to submit a bug report please make sure to follow our [reporting guidelines](https://wiki.ushahidi.com/display/WIKI/Report+a+bug).
+
+* If you want to submit a patch, please read the checkout the contribution guidelines:
+  * [Ushahidi Developer Guide](https://wiki.ushahidi.com/display/WIKI/Ushahidi+Developer+Guide)
+  * [Coding Style Guide](https://wiki.ushahidi.com/display/WIKI/Coding+Style+Guide)
+  * [Localization best practice](https://wiki.ushahidi.com/display/WIKI/Localization+-+dev+best+practices)
+
+* If you have a change or new feature in mind, you can [submit it on github](https://github.com/ushahidi/Ushahidi_Web/issues/new) or [add it to the wishlist for 3.0](https://wiki.ushahidi.com/display/WIKI/Ushahidi+3.x+Wishlist).
+
+*We only accept bug reports, feature request and pull requests in GitHub*.
+
+* If you have a question about how to use Ushahidi, please [ask on the Ushahidi Forums](http://forums.ushahidi.com).
+
+Thanks!
+Ushahidi Team
diff --git a/License.txt b/License.txt
new file mode 100644
index 0000000..fb09a89
--- /dev/null
+++ b/License.txt
@@ -0,0 +1,200 @@
+		   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ 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.
+
+----------------------------------------------
+
+Kohana License Agreement
+
+This license is a legal agreement between you and the Kohana Software
+Foundation for the use of Kohana Framework (the "Software"). By obtaining the
+Software you agree to comply with the terms and conditions of this license.
+
+Copyright (c) 2007-2008 Kohana Team
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,this
+list of conditions and the following disclaimer.
+* Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+* Neither the name of the Kohana nor the names of its contributors may be
+used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/README.markdown b/README.markdown
new file mode 100644
index 0000000..94b9a1d
--- /dev/null
+++ b/README.markdown
@@ -0,0 +1,161 @@
+Ushahidi Platform
+=================
+The Ushahidi Platform is an open source web application for information collection, vizualisation and interactive 
+mapping. It allows people to collect and share their own stories using various mediums such
+as SMS, Web Forms, Email or Twitter. For more information about the platform and use cases (case studies) visit: http://www.ushahidi.com
+
+
+System Requirements
+-------------------
+To install the platform on your computer/server, the target system must meet the following requirements:
+
+* PHP version 5.2.3 or greater (5.3 or greater is recommended)
+* MySQL version 5.0 or greater
+* An HTTP Server. Kohana, which Ushahidi is built on, is known to work with the following web servers:
+    - Apache 1.3+
+    - Apache 2.0+
+    - lighttpd
+    - nginx
+    - Microsoft Internet Information Server (MS IIS)
+* Unicode support in the operating system
+
+
+Required Extensions
+-------------------
+The follwing is a list of PHP extensions that must be installed on your server in order for Ushahidi to run properly:
+
+* PCRE (http://php.net/pcre) must be compiled with –enable-utf8 and –enable-unicode-properties for UTF-8 functions to work properly.
+* iconv (http://php.net/iconv) is required for UTF-8 transliteration.
+* mcrypt (http://php.net/mcrypt) is required for encryption.
+* SPL (http://php.net/spl) is required for several core libraries.
+* mbstring (http://php.net/mbstring) which speeds up Kohana's UTF-8 functions.
+* cURL (http://php.net/curl) which is used to access remote sites.
+* MySQL (http://php.net/mysql) is required for database access.
+* IMAP (http://php.net/imap) is required for email functionality.
+* GD (http://php.net/gd) is required for image processing
+
+__NOTE: Need to figure out what extensions you already have installed on your server? Here are instructions to do just that: http://jontangerine.com/silo/php/phpinfo/__
+
+
+Optional Server Requirements
+----------------------------
+To use Ushahidi's "Clean URLS" feature on an Apache Web Server, you will need the mod_rewrite module
+and the ability to use local `.htaccess` files. 
+
+###Installing mod_rewrite
+
+#####Debian/Ubuntu flavours of Linux
+    
+    sudo a2enmod rewrite
+
+#####CentOS, OS X and Windows
+
+Make sure the following line is __NOT__ commented in your `httpd.conf`
+
+    LoadModule rewrite_module
+
+
+###Additional Configuration
+To check if local `.htaccess` files are allowed, verify that the "AllowOverride" directive in your Apache config 
+(for the web server directory in which you have installed Ushahidi) has been set to "All" i.e.:
+
+    <Directory [your-document-root-directory]>
+        ...
+        AllowOverride All
+        ...
+    </Directory>
+
+__NOTE:__ 
+
+* Clean URLs means that the URLs of your deployment will not have the 'index.php' prefix
+* You __MUST__ restart your Apache web server after making the changes outlined above
+
+
+Installation
+------------
+* ####Download and extract Ushahidi
+    You can obtain the official release of the software from [the download site](http://download.ushahidi.com). 
+    Alternatively, you can find downloads for the current and previous releases on the [Wiki](https://wiki.ushahidi.com/display/WIKI/Ushahidi+Platform+Downloads)
+    
+    To unzip/extract the archive on a typical Unix/Linux command line:
+    
+        tar -xvf Ushahidi_Web-xxxx.tar.gz
+    
+    or in the case of a zip file:
+
+        unzip Ushahidi_Web-xxxx.zip
+
+    This will create a new directory Ushahidi_Web-xxxx containing all the Ushahidi platform files and directories - Move the contents of this directory
+    into a directory within your webserver's document root or your public HTML directory.
+
+    #####Getting the latest develop code (CAUTION: only do this if you know what you're doing) 
+
+    clone the latest code from github
+
+        git clone --recursive git://github.com/ushahidi/Ushahidi_Web.git
+
+    We add the recursive flag so that git will clone the submodules too      
+
+* ####Ensure the following directories are writable (i.e. have their permission values set to 777)
+    - application/config
+    - application/cache
+    - application/logs
+    - media/uploads
+    - .htaccess
+    
+    On Unix/Linux, you can change the permissions as follows:
+
+        cd path-to-webserver-document-root-directory
+        chmod -R 777 application/config
+        chmod -R 777 application/cache
+        chmod -R 777 application/logs
+        chmod -R 777 media/uploads
+        chmod 777 .htaccess
+        
+    __NOTE: The process of configuring file permissions is different for various operating systems. Here are some helpful links about permissions for the Windows (http://support.microsoft.com/kb/308419) and Unix (http://www.washington.edu/computing/unix/permissions.html) operating systems.__
+
+* ####Create the Ushahidi database
+    Ushahidi stores all its information in a database. You must therefore create this database in order to install Ushahidi. This is done as follows:
+    
+        mysqladmin -u 'username' -p create 'databasename'
+    
+    MySQL will prompt for the password for the <username> database password and then create the initial database files. Next, you must log in and set the 
+    database access rights:
+    
+        mysql -u 'username' -p
+    
+    Again, you will be prompted for the 'username' database password. At the MySQL prompt, enter the following command:
+    
+        GRANT SELECT, INSERT, DELETE, UPDATE, CREATE, DROP, ALTER, INDEX, LOCK TABLES on database.* 
+        TO 'username'@'localhost' IDENTIFIED BY 'password';
+    
+    Where:
+    - 'databasename' is the name of your database
+    - 'username at localhost' is the name of your MySQL account
+    - 'password' is the password required for that username
+
+    __NOTE: Your account must have all the privileges listed above in order to run Ushahidi on your webserver.__
+
+* ####Ensure PHP error_reporting level is compatable
+    As of PHP-5.4 Ushahidi doesn't work with the error_reporting level E_STRICT.  Ensure this level is excluded from the error_reporting configuration.
+
+* ####Run the install script
+    To run the install script, point your browser to the base url of your website: (e.g. http://www.example.com).
+    
+    You will be guided through a series of screens to set up the database and site settings depending on the installation method you choose (Basic or Advanced)
+
+* ####Clean up
+    ##### Delete the installer
+    Leaving the installer files in your installation is a security risk.
+    Now you've installed successfully, **Delete the entire installer directory**
+
+    ##### Remove write permissions from config files
+
+        cd path-to-webserver-document-root-directory
+        chmod -R 755 application/config
+        chmod 644 application/config/*
+        chmod 644 .htaccess
+
+Additional Information
+----------------------
+For further references and documentation, head over to our wiki (http://wiki.ushahidi.com). Also, we encourage you to drop by our forums (https://wiki.ushahidi.com/display/forums/Ushahidi+Forums) if you have any additional questions or concerns.
diff --git a/application/cache/.gitignore b/application/cache/.gitignore
new file mode 100755
index 0000000..e69de29
diff --git a/application/config/actions.php b/application/config/actions.php
new file mode 100644
index 0000000..6170dde
--- /dev/null
+++ b/application/config/actions.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+/**
+ * The Ushahidi Actions Configuration
+ *
+ * This file contains logic config settings for admin configurable events that can take place
+ * when certain hooks are fired.
+ *
+ * Actions can be manually found throughout the code by searching for "ushahidi_action.*"
+ * and seeing where the hook is called.
+ *
+ */
+
+// ----- TRIGGERS & QUALIFIERS -----
+
+// A list of hooks that are worthy of having a user set up a custom trigger for it
+
+$config['trigger_options'] = array
+(
+	// Name of the action (ie: ushahidi_action.report_add) with a human readable name
+	'report_add' => 'Report Added',
+	'message_twitter_add' => 'Geotagged Twitter Messages',
+	'feed_item_add' => 'Geotagged Feed Item'
+);
+
+// This is a list of the advanced option areas for the qualifiers
+
+$config['advanced_option_areas'] = array('user','location','keyword','category','on_specific_count',
+										'between_times','days_of_the_week','specific_days','from','feed_id');
+
+// This shows which advanced options are relevant to the triggers
+
+$config['trigger_advanced_options'] = array(
+	'report_add' => array('user','location','keyword','category','on_specific_count','between_times','days_of_the_week','specific_days'),
+	'message_twitter_add' => array('location','keyword','between_times','days_of_the_week','specific_days','from'),
+	'feed_item_add' => array('location','keyword','between_times','days_of_the_week','specific_days','feed_id')
+);
+
+// ----- RESPONSES -----
+
+// Responses
+
+$config['response_options'] = array(
+	'email' => 'Email',
+	'approve_report' => 'Approve Report',
+	'log_it' => 'Log it',
+	'assign_badge' => 'Assign Badge',
+	'create_report' => 'Create Report'
+);
+
+// This is a list of the advanced option areas for the qualifiers
+
+$config['response_advanced_option_areas'] = array('email_send_address','email_subject','email_body','add_category','verify','approve','badge','report_title');
+
+// Andanced response options
+
+$config['response_advanced_options'] = array(
+	'email' => array('email_send_address','email_subject','email_body'),
+	'approve_report' => array('add_category','verify'),
+	'log_it' => array(),
+	'assign_badge' => array('badge'),
+	'create_report' => array('report_title','add_category','verify','approve')
+);
+
+// Allowed responses for trigger
+
+$config['trigger_allowed_responses'] = array(
+	'report_add' => array('email','approve_report','log_it','assign_badge'),
+	'message_twitter_add' => array('log_it','create_report'),
+	'feed_item_add' => array('log_it','create_report')
+);
diff --git a/application/config/api.php b/application/config/api.php
new file mode 100644
index 0000000..631ed14
--- /dev/null
+++ b/application/config/api.php
@@ -0,0 +1,64 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+/**
+ * The Ushahidi API configuration
+ *
+ * This file contains entries for routing API tasks whose libraries files cannot be inferred from the
+ * the name of the API task. API task handlers are first looked up in this file before the API service
+ * attempts to determine the name of the implementing library from the API task name
+ *
+ */
+ $config = array
+ (
+     // Version
+     "version" => array("System", "get_version_number"),
+
+     // MHI Enabled
+     "mhienabled" => array("System", "get_mhi_enabled"),
+     
+     // SSL Enabled
+     "httpsenabled" => array("System", "get_https_enabled"),
+     
+     // Map center
+     "mapcenter" => array("Private_Func", "map_center"),
+     
+     // Statistics
+     "statistics" => array("Private_Func", "statistics"),
+
+     "sms" => array("Private_Func", "sms"),
+ 
+     "country" => "Countries", 
+     "location" => "Locations",
+     "3dkml" => "Kml",
+     
+     // Geographic midpoint
+     "geographicmidpoint" => array("Incidents", "get_geographic_midpoint"),
+     
+     // Incident count
+     "incidentcount" => array("Incidents", "get_incident_count"),
+     
+     // Media tagging
+     "tagnews" => "Tag_Media",
+     "tagvideo" => "Tag_Media",
+     "tagphoto" => "Tag_Media",
+ 
+     // Admin report functions 
+     "reports" => "Admin_Reports",
+                         	
+     // Category Action 
+     "category" => array("Admin_Category","category_action"),
+
+     // Comment Action
+     "comments" => "Comments",
+     
+     // Twitter action
+     "twitteraction" => array( "Twitter", "twitter_action"),
+     
+     // Email action
+     "emailaction" => array( "Email", "email_action"),
+     
+     // SMS action
+     "smsaction" => array( "Sms", "sms_action"),
+
+     // Swiftriver action
+     "swiftriver" => "Swiftriver_Report",
+ );
diff --git a/application/config/auth.template.php b/application/config/auth.template.php
new file mode 100644
index 0000000..c099b97
--- /dev/null
+++ b/application/config/auth.template.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Auth library configuration. By default, Auth will use the controller
+ * database connection. If Database is not loaded, it will use use the default
+ * database group.
+ *
+ * In order to log a user in, a user must have the `login` role. You may create
+ * and assign any other role to your users.
+ */
+
+/**
+ * Driver to use for authentication. By default, file and ORM are available.
+ */
+$config['driver'] = 'ORM';
+
+/**
+ * Type of hash to use for passwords. Any algorithm supported by the hash function
+ * can be used here. Note that the length of your password is determined by the
+ * hash type + the number of salt characters.
+ * Note: This is unrelated to the hash settings in the Openid library.
+ * @see http://php.net/hash
+ * @see http://php.net/hash_algos
+ */
+$config['hash_method'] = 'sha1';
+
+/**
+ * Defines the hash offsets to insert the salt at. The password hash length
+ * will be increased by the total number of offsets.
+ * CHANGE ME: THIS SHOULD BE UNIQUE TO YOUR DEPLOYMENT
+ */
+$config['salt_pattern'] = '3, 5, 6, 10, 24, 26, 35, 36, 37, 40';
+
+/**
+ * Set the auto-login (remember me) cookie lifetime, in seconds. The default
+ * lifetime is two weeks.
+ */
+$config['lifetime'] = 1209600;
+
+/**
+ * Set the session key that will be used to store the current user.
+ */
+$config['session_key'] = 'auth_user';
+
+/**
+ * Usernames (keys) and hashed passwords (values) used by the File driver.
+ */
+$config['users'] = array
+(
+	// 'admin' => '4ccd0e25c2a7ffefd4b92ecbbd4781752920145f826a881073',
+);
+
+/**
+ * Allowed Password Length. 127 is the upper limit.
+ * ie. '8,127' says passwords must be between 8 and 127 characters long
+ */
+$config['password_length'] = '1,127';
\ No newline at end of file
diff --git a/application/config/benchmark.php b/application/config/benchmark.php
new file mode 100644
index 0000000..5a74ed5
--- /dev/null
+++ b/application/config/benchmark.php
@@ -0,0 +1,39 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Boolean TRUE/FALSE to turn on the saving of benchmarked figures
+ * to a database
+ */
+$config['enable'] = FALSE;
+
+/**
+ * Connection details for the database where we want to save benchmark
+ * figures. This can be the database for your site but we reccomend
+ * using a different database (maybe on another server) since it can
+ * use a lot of resources depending on how many times your deployment
+ * is accessed. Please see the schema for this table at the end of the file.
+ */
+$config['db'] = array
+(
+	'user'     => 'username_here',
+	'pass'     => 'password_here',
+	'host'     => 'localhost',
+	'port'     => FALSE,
+	'database' => 'database_name_here',
+	'table_prefix'  => ''
+);
+
+/**
+ * Schema for benchmark table data:
+ 
+ CREATE TABLE `benchmark` (
+	`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+	`timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
+	`name` VARCHAR( 100 ) NOT NULL ,
+	`time` DECIMAL(5,4) NOT NULL ,
+	`memory` INT NOT NULL
+	) ENGINE = MyISAM;
+	
+ *
+ */
+ 
+?>
\ No newline at end of file
diff --git a/application/config/cache.php b/application/config/cache.php
new file mode 100644
index 0000000..52a00d9
--- /dev/null
+++ b/application/config/cache.php
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* CACHE CONFIGURATION
+*/
+
+/**
+ * Enable or disable file caching. This makes pages display faster
+ * but can take a large amount of storage space on larger sites
+ */
+$config['cache_pages'] = FALSE;
+
+if (@!is_writable(APPPATH.'cache'))
+{
+	$config["cache_pages"] = FALSE;
+}
+
+/**
+ * CONFIGURATION
+ * 'file' driver can be substituted for:
+ *  -> Memcache - Memcache is very high performance, but prevents cache tags from being used.
+ *  -> APC - Alternative Php Cache
+ *  -> Eaccelerator
+ *  -> Xcache
+ */
+$config['default'] = array(
+	'driver' => 'file',
+	'params' => APPPATH.'cache',
+	'lifetime' => 1800,
+	'requests' => -1
+);
+
+?>
\ No newline at end of file
diff --git a/application/config/captcha.php b/application/config/captcha.php
new file mode 100755
index 0000000..663bcb4
--- /dev/null
+++ b/application/config/captcha.php
@@ -0,0 +1,28 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha configuration is defined in groups which allows you to easily switch
+ * between different Captcha settings for different forms on your website.
+ * Note: all groups inherit and overwrite the default group.
+ *
+ * Group Options:
+ *  style      - Captcha type, e.g. basic, alpha, word, math, riddle
+ *  width      - Width of the Captcha image
+ *  height     - Height of the Captcha image
+ *  complexity - Difficulty level (0-10), usage depends on chosen style
+ *  background - Path to background image file
+ *  fontpath   - Path to font folder
+ *  fonts      - Font files
+ *  promote    - Valid response count threshold to promote user (FALSE to disable)
+ */
+/*$config['default'] = array
+(
+	'style'      => 'math',
+	'width'      => 150,
+	'height'     => 50,
+	'complexity' => 4,
+	'background' => '',
+	'fontpath'   => SYSPATH.'fonts/',
+	'fonts'      => array('DejaVuSerif.ttf'),
+	'promote'    => FALSE,
+);*/
+$config['default']['style'] = 'math';
diff --git a/application/config/cdn.php b/application/config/cdn.php
new file mode 100644
index 0000000..5526e0b
--- /dev/null
+++ b/application/config/cdn.php
@@ -0,0 +1,59 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* CDN CONFIGURATION
+*/
+
+/**
+ * Content Distribution Network (CDN) Configuration
+ * Use a content distribution network to serve up
+ * static CSS, JS, and IMG files
+ */
+
+$config['cdn_css'] = "";
+$config['cdn_js'] = "";
+$config['cdn_img'] = "";
+
+/**
+ * Dynamic content CDN details
+ *
+ * Are you turning on dynamic uploaded file CDN support on
+ * an active deployment? You may need to set 'cdn_gradual_upgrade'
+ * to true so your server will upload a file at a random interval.
+ * You may also choose to write your own script to do everything
+ * at once but that could lead to significant downtime.
+ */
+
+// Set to true to store dynamic uploaded files on a CDN
+$config['cdn_store_dynamic_content'] = false;
+
+// Currently the only option here is "cloudfiles" from Rackspace
+$config['cdn_provider'] = "cloudfiles";
+
+// Details for your CDN account
+$config['cdn_username'] = "";
+$config['cdn_api_key'] = "";
+$config['cdn_container'] = "";
+
+// An integer value that corresponds to the number of visits before
+//   a file gets passed on to the CDN if there are files that are left
+//   to be uploaded to the CDN. This is important to turn off once the
+//   the files are finished uploading to the CDN. For example, setting
+//   this to 1 will upload a file every time someone visits the site
+//   and setting it to 2 will be every other visitor and setting it to
+//   10 will be one in every 10 visitors. Your deployment will continue
+//   to operate normally with new files being pushed to the CDN and the
+//   old ones updating over time with this method.
+//
+//   Set to false if you don't want to do this or if all of your files
+//   have been moved to the CDN.
+//
+//   WARNING: Setting this means files will be deleted as they are
+//   uploaded to the CDN. Please be smart and backup your deployment.
+//
+$config['cdn_gradual_upgrade'] = false;
+
+// Avoid XSS problems with jwysiwyg in CDN environments.
+$config['cdn_ignore_jwysiwyg'] = true;
+
+?>
diff --git a/application/config/config.template.php b/application/config/config.template.php
new file mode 100644
index 0000000..fc66c6e
--- /dev/null
+++ b/application/config/config.template.php
@@ -0,0 +1,194 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Base path of the web site. If this includes a domain, eg: localhost/ushahidi/
+ * then a full URL will be used, eg: http://localhost/ushahidi/. If it only includes
+ * the path, and a site_protocol is specified, the domain will be auto-detected.
+ */
+$config['site_domain'] = '/';
+
+/**
+ * Force a default protocol to be used by the site. If no site_protocol is
+ * specified, then the current protocol is used, or when possible, only an
+ * absolute path (with no protocol/domain) is used.
+ */
+$config['site_protocol'] = 'http';
+
+/**
+ * Name of the front controller for this application. Default: index.php
+ *
+ * This can be removed by using URL rewriting.
+ */
+$config['index_page'] = 'index.php';
+
+/**
+ * Whether or not you want to have the auto upgrader enabled.
+ * TRUE will ping Ushahidi.com for the latest version.
+ * FALSE will require you to check manually and do upgrades by hand.
+ */
+$config['enable_auto_upgrader'] = TRUE;
+
+/**
+ * The admin panel shows a warning if you upgrade your deployment
+ * code but not the database. Setting this to false disabled that
+ * warning.
+ */
+$config['enable_ver_sync_warning'] = TRUE;
+
+/**
+ * The admin panel shows a warning if you haven't changed your 
+ * encryption key. Set this to false to disable
+ */
+$config['enable_security_info'] = TRUE;
+
+/**
+ * Include Google Analytics (if set) on admin panel
+ */
+$config['google_analytics_in_admin'] = TRUE;
+
+/**
+ * Fake file extension that will be added to all generated URLs. Example: .html
+ */
+$config['url_suffix'] = '';
+
+/**
+ * Length of time of the internal cache in seconds. 0 or FALSE means no caching.
+ * The internal cache stores file paths and config entries across requests and
+ * can give significant speed improvements at the expense of delayed updating.
+ */
+$config['internal_cache'] = TRUE;
+
+/**
+ * Enable or disable gzip output compression. This can dramatically decrease
+ * server bandwidth usage, at the cost of slightly higher CPU usage. Set to
+ * the compression level (1-9) that you want to use, or FALSE to disable.
+ *
+ * Do not enable this option if you are using output compression in php.ini!
+ */
+$config['output_compression'] = TRUE;
+
+/**
+ * Enable or disable global XSS filtering of GET, POST, and SERVER data. This
+ * option also accepts a string to specify a specific XSS filtering tool.
+ */
+$config['global_xss_filtering'] = 'htmlpurifier';
+
+/**
+ * Enable or disable hooks. Setting this option to TRUE will enable
+ * all hooks. By using an array of hook filenames, you can control
+ * which hooks are enabled. Setting this option to FALSE disables hooks.
+ */
+$config['enable_hooks'] = TRUE;
+
+/**
+ * Log thresholds:
+ *  0 - Disable logging
+ *  1 - Errors and exceptions
+ *  2 - Warnings
+ *  3 - Notices
+ *  4 - Debugging
+ */
+$config['log_threshold'] = 1;
+
+/**
+ * Message logging directory.
+ */
+$config['log_directory'] = APPPATH.'logs';
+
+if ( ! @is_writable($config["log_directory"]))
+{
+	$config["log_threshold"] = 0;
+}
+
+/**
+ * The scheduler removes old logs. Set to false to disable or an int for the
+ * number of days to keep old logs.
+ */
+$config['log_cleanup_days_old'] = 7;
+
+/**
+ * Enable or disable displaying of Kohana error pages. This will not affect
+ * logging. Turning this off will disable ALL error pages.
+ */
+$config['display_errors'] = TRUE;
+
+/**
+ * Enable or disable statistics in the final output. Stats are replaced via
+ * specific strings, such as {execution_time}.
+ *
+ * @see http://docs.kohanaphp.com/general/configuration
+ */
+$config['render_stats'] = TRUE;
+
+/**
+ * Enable profiler
+ */
+$config['enable_profiler'] = FALSE;
+
+/**
+ * Turn MHI on or off. This is an advanced feature that will drastically alter
+ * the way your instance works. Please read documentation before proceeding.
+ *
+ * @see [A URL not yet created]
+ */
+$config['enable_mhi'] = FALSE;
+
+/**
+ * Allow members to sign in with OpenID providers, excluding RiverID
+ */
+$config['allow_openid'] = FALSE;
+
+/**
+ * Filename prefixed used to determine extensions. For example, an
+ * extension to the Controller class would be named MY_Controller.php.
+ */
+$config['extension_prefix'] = 'MY_';
+
+/**
+ * Check if we should launch the installer or not
+ */
+$config['installer_check'] = TRUE;
+
+/**
+ * Output scheduler JS in footer
+ */
+$config['output_scheduler_js'] = TRUE;
+
+/**
+ * Protocol to use for loading external requests
+ * 
+ * This is used for requests from PHP to external APIs that offer
+ * both http and https. Normally this should default to 'https'
+ * but some countries/firewalls block https requests so its a setting.
+ */
+$config['external_site_protocol'] = 'https';
+
+/**
+ * Allowed HTML tags in report description and other large text fields
+ * 
+ * Formated is based on http://htmlpurifier.org/live/configdoc/plain.html#HTML.Allowed
+ */
+$config['allowed_html'] = "a[href|title],p,img[src|alt],br,b,u,strong,em,i,iframe[width|height|frameborder|src]";
+
+/**
+ * Allowed iframe URLs in report description and other large text fields
+ * 
+ * Formated is based on http://htmlpurifier.org/live/configdoc/plain.html#URI.SafeIframeRegexp
+ */
+$config['safe_iframe_regexp'] = '%^//(www.youtube.com/embed/|player.vimeo.com/video/|w.soundcloud.com/player!)%';
+
+/**
+ * Additional resource paths, or "modules". Each path can either be absolute
+ * or relative to the docroot. Modules can include any resource that can exist
+ * in your application directory, configuration files, controllers, views, etc.
+ */
+$config['modules'] = array
+(
+	MODPATH.'auth',         // Authentication
+	MODPATH.'csrf',         // CSRF Handling
+	// MODPATH.'forge',     // Form generation
+	// MODPATH.'formation', // Form generation
+	// MODPATH.'kodoc',     // Self-generating documentation
+	// MODPATH.'media',     // Media caching and compression
+	// MODPATH.'archive',   // Archive utility
+	// MODPATH.'unit_test', // Unit testing
+);
\ No newline at end of file
diff --git a/application/config/cookie.php b/application/config/cookie.php
new file mode 100644
index 0000000..3f248a0
--- /dev/null
+++ b/application/config/cookie.php
@@ -0,0 +1,32 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Domain, to restrict the cookie to a specific website domain. For security,
+ * you are encouraged to set this option. An empty setting allows the cookie
+ * to be read by any website domain.
+ */
+$config['domain'] = '';
+
+/**
+ * Restrict cookies to a specific path, typically the installation directory.
+ */
+$config['path'] = '/';
+
+/**
+ * Lifetime of the cookie. A setting of 0 makes the cookie active until the
+ * users browser is closed or the cookie is deleted.
+ */
+$config['expire'] = 0;
+
+/**
+ * Enable this option to only allow the cookie to be read when using the a
+ * secure protocol.
+ */
+$config['secure'] = FALSE;
+
+/**
+ * Enable this option to disable the cookie cannot be accessed through client side scripts (ie. JS). This option is only available in PHP 5.2 and above.
+ * Prevent potentially XSS attacks in supported browsers - https://www.owasp.org/index.php/HttpOnly
+ */
+$config['httponly'] = TRUE;
\ No newline at end of file
diff --git a/application/config/database.template.php b/application/config/database.template.php
new file mode 100644
index 0000000..5193c5c
--- /dev/null
+++ b/application/config/database.template.php
@@ -0,0 +1,32 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Sample database configuration file for Ushahidi.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - https://github.com/ushahidi/Ushahidi_Web
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+$config['default'] = array(
+	'benchmark' => TRUE,
+	'persistent' => FALSE,
+	'connection' => array(
+		'type' => 'mysqli',
+		'user' => 'username',
+		'pass' => 'password',
+		'host' => 'localhost',
+		'port' => FALSE,
+		'socket' => FALSE,
+		'database' => 'db'
+	),
+	'character_set' => 'utf8',
+	'table_prefix' => '',
+	'object' => TRUE,
+	'cache' => FALSE,
+	'escape' => TRUE
+);
diff --git a/application/config/email.php b/application/config/email.php
new file mode 100644
index 0000000..b2fcc84
--- /dev/null
+++ b/application/config/email.php
@@ -0,0 +1,25 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * SwiftMailer driver, used with the email helper.
+ *
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/nativemail
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/sendmail
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/smtp
+ *
+ * Valid drivers are: native, sendmail, smtp
+ */
+$config['driver'] = 'native';
+
+/**
+ * To use secure connections with SMTP, set "port" to 465 instead of 25.
+ * To enable TLS, set "encryption" to "tls".
+ *
+ * Driver options:
+ * @param   null    native: no options
+ * @param   string  sendmail: executable path, with -bs or equivalent attached
+ * @param   array   smtp: hostname, (username), (password), (port), (auth), (encryption)
+ */
+$config['options'] = NULL;
+
+
+$config['max_imap_messages'] = 10;
\ No newline at end of file
diff --git a/application/config/encryption.template.php b/application/config/encryption.template.php
new file mode 100644
index 0000000..703ed3e
--- /dev/null
+++ b/application/config/encryption.template.php
@@ -0,0 +1,29 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Encrypt
+ *
+ * Encrypt configuration is defined in groups which allows you to easily switch
+ * between different encryption settings for different uses.
+ * Note: all groups inherit and overwrite the default group.
+ *
+ * Group Options:
+ *  key    - Encryption key used to do encryption and decryption. The default option
+ *           should never be used for a production website.
+ *
+ *           For best security, your encryption key should be at least 16 characters
+ *           long and contain letters, numbers, and symbols.
+ *           @note Do not use a hash as your key. This significantly lowers encryption entropy.
+ *
+ *  mode   - MCrypt encryption mode. By default, MCRYPT_MODE_NOFB is used. This mode
+ *           offers initialization vector support, is suited to short strings, and
+ *           produces the shortest encrypted output.
+ *           @see http://php.net/mcrypt
+ *
+ *  cipher - MCrypt encryption cipher. By default, the MCRYPT_RIJNDAEL_128 cipher is used.
+ *           This is also known as 128-bit AES.
+ *           @see http://php.net/mcrypt
+ */
+// CHANGE ME: THIS SHOULD BE UNIQUE TO YOUR DEPLOYMENT
+$config['default']['key'] = 'USHAHIDI-INSECURE';
+$config['default']['mode'] = MCRYPT_MODE_NOFB;
+$config['default']['cipher'] = MCRYPT_RIJNDAEL_128;
diff --git a/application/config/globalcode.php b/application/config/globalcode.php
new file mode 100644
index 0000000..13aa650
--- /dev/null
+++ b/application/config/globalcode.php
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* GLOBAL CODE SNIPPETS
+*
+* These settings will run HTML, CSS or JavaScript on every page in
+* specific areas (mostly defined by the variable name)
+*
+*/
+
+/**
+* Head Tag
+*
+* This code snippet is inserted near the top of the <head> tag.
+* More specifically, it's inserted at the top of the "header_block"
+* variable called in the header.php view and admin views
+*
+*/
+
+$config['head'] = "";
+
+/**
+* Footer Tag
+*
+* This code snippet is inserted near the bottom of the page.
+* More specifically, it's inserted at the top of the "footer_block"
+* variable called in the footer.php view and admin views
+*
+*/
+
+$config['foot'] = "";
+
+?>
\ No newline at end of file
diff --git a/application/config/image.php b/application/config/image.php
new file mode 100644
index 0000000..35075f5
--- /dev/null
+++ b/application/config/image.php
@@ -0,0 +1,7 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+$config['driver'] = 'GD';
+$config['params'] = array
+(
+'directory' => '/usr/local/bin'
+);
\ No newline at end of file
diff --git a/application/config/locale.php b/application/config/locale.php
new file mode 100644
index 0000000..b692c00
--- /dev/null
+++ b/application/config/locale.php
@@ -0,0 +1,29 @@
+<?php defined('SYSPATH') or die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Default language locale name(s).
+ * First item must be a valid i18n directory name, subsequent items are alternative locales
+ * for OS's that don't support the first (e.g. Windows). The first valid locale in the array will be used.
+ * @see http://php.net/setlocale
+ */
+$config['language'] = array('en_US', 'English_United States');
+
+/**
+ * All Available Languages
+ * Activate new languages by adding them to this array
+ */
+$config['all_languages'] = array ( 'en_US'=>'English (US)', 'fr_FR'=>'Français' );
+
+/**
+ * Locale timezone. Defaults to use the server timezone.
+ * @see http://php.net/timezones
+ */
+$config['timezone'] = '';
+
+/**
+ * Force text-direction to be either LTR or RTL
+ * Possible values: 'rtl' 'ltr' or FALSE
+ * @see Ush_Locale::is_rtl_language()
+ */
+$config['force_text_direction'] = FALSE;
diff --git a/application/config/map.php b/application/config/map.php
new file mode 100644
index 0000000..2fed878
--- /dev/null
+++ b/application/config/map.php
@@ -0,0 +1,88 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* MAP CONFIGURATION
+*/
+
+/**
+ * Set single marker radius
+ * Values from 1 to 10
+ * Default: 4
+ */
+$config['marker_radius'] = "4";
+
+
+/**
+ * Set marker opacity.
+ * Values from 1 (very transparent) to 10 (Opaque)
+ * Default: 8
+ */
+$config['marker_opacity'] = "8";
+
+
+/**
+ * Set marker stroke width
+ * Each marker circle can have a line around it
+ * Values from 0 to 5
+ * Default: 2
+ */
+$config['marker_stroke_width'] = "2";
+
+
+/**
+ * Set marker stroke opacity.
+ * Values from 1 (very transparent) to 10 (Opaque)
+ * Default: 9
+ */
+$config['marker_stroke_opacity'] = "3";
+
+
+/**
+ * Set list of map layers to use
+ * Values ???
+ * Default: array()
+ */
+$config['layers'] = array();
+
+/**
+ * Set number of zoom levels.
+ * If maxZoomLevel - minZoomLevel > numZoomLevels then numZoomLevels has priority
+ */
+
+$config['numZoomLevels'] = "21";
+
+
+/**
+ * Set minimum zoom level, as defined by the provider. (0-21)
+ * http://code.google.com/apis/maps/documentation/staticmaps/#Zoomlevels
+ */
+
+$config['minZoomLevel'] = "0";
+
+
+/**
+ * Set maximum zoom level, as defined by the provider. (0-21)
+ * http://code.google.com/apis/maps/documentation/staticmaps/#Zoomlevels
+ */
+
+$config['maxZoomLevel'] = "21";
+
+/**
+ * Set maximum extents for the map. This will limit the area on the map
+ * that users can access.
+ * Default values allow the user to see the whole world
+ */
+
+$config['lonFrom'] = "-180";
+$config['latFrom'] = "-85";
+$config['lonTo'] = "180";
+$config['latTo'] = "85";
+
+
+
+/**
+ * Determines which geocoding engine to use
+ * Options are google or nominatim
+ */
+
+$config['geocode'] = "nominatim";
diff --git a/application/config/openlayers.ushahidi.cfg b/application/config/openlayers.ushahidi.cfg
new file mode 100644
index 0000000..0e1d7dd
--- /dev/null
+++ b/application/config/openlayers.ushahidi.cfg
@@ -0,0 +1,51 @@
+[first]
+
+[last]
+
+[include]
+OpenLayers/Ajax.js
+OpenLayers/Bounds.js
+OpenLayers/Control/Attribution.js
+OpenLayers/Control/DragFeature.js
+OpenLayers/Control/EditingToolbar.js
+OpenLayers/Control/LayerSwitcher.js
+OpenLayers/Control/MousePosition.js
+OpenLayers/Control/Navigation.js
+OpenLayers/Control/PanZoomBar.js
+OpenLayers/Control/Scale.js
+OpenLayers/Control/ScaleLine.js
+OpenLayers/Control/SelectFeature.js
+OpenLayers/Control/Zoom.js
+OpenLayers/Feature/Vector.js
+OpenLayers/Filter/Comparison.js
+OpenLayers/Format/GeoJSON.js
+OpenLayers/Format/KML.js
+OpenLayers/Format/WKT.js
+OpenLayers/Geometry/LinearRing.js
+OpenLayers/Geometry/Point.js
+OpenLayers/Geometry/Polygon.js
+OpenLayers/Kinetic.js
+OpenLayers/Layer/Bing.js
+OpenLayers/Layer/Google.js
+OpenLayers/Layer/Google/v3.js
+OpenLayers/Layer/Markers.js
+OpenLayers/Layer/OSM.js
+OpenLayers/Layer/TMS.js
+OpenLayers/Layer/Vector.js
+OpenLayers/Layer/WMS.js
+OpenLayers/Layer/XYZ.js
+OpenLayers/Map.js
+OpenLayers/Marker.js
+OpenLayers/Popup/FramedCloud.js
+OpenLayers/Projection.js
+OpenLayers/Protocol/HTTP.js
+OpenLayers/Renderer.js
+OpenLayers/Renderer/Canvas.js
+OpenLayers/Renderer/SVG.js
+OpenLayers/Renderer/VML.js
+OpenLayers/Size.js
+OpenLayers/StyleMap.js
+OpenLayers/Strategy/Fixed.js
+
+[exclude]
+
diff --git a/application/config/plugins.php b/application/config/plugins.php
new file mode 100644
index 0000000..180bef0
--- /dev/null
+++ b/application/config/plugins.php
@@ -0,0 +1,14 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+/**
+ * Plugins Configuration
+ *
+ * This file contains configuration options for plugins
+ *
+ */
+
+// This setting will hide plugins from the plugin list in the admin panel.
+//   Must be an array of the dir names of the plugins you want to hide.
+//   i.e. 'smssync','frontlinesms', etc...
+$config['hide_from_list'] = array();
+
+// TODO: Add other config options like "force enable", "force disable", etc.
\ No newline at end of file
diff --git a/application/config/requirements.php b/application/config/requirements.php
new file mode 100644
index 0000000..7a7bd8f
--- /dev/null
+++ b/application/config/requirements.php
@@ -0,0 +1,41 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* REQUIREMENTS CONFIGURATION
+*/
+
+/**
+ * Do we want requirements to suffix modification time onto file names
+ * */
+$config['suffix_requirements'] = TRUE;
+
+/**
+ * Enable combining of css/javascript files.
+ **/
+$config['combined_files_enabled'] = FALSE;
+
+/**
+ * Using the JSMin library to minify any
+ * javascript file passed to {@link combine_files()}.
+ **/
+$config['combine_js_with_jsmin'] = TRUE;
+
+/**
+ * Using the CSSMin library to minify any
+ * css file passed to {@link combine_files()}.
+ **/
+$config['combine_css_with_cssmin'] = TRUE;
+
+/**
+ * Enable auto uploading combined css/js to CDN
+ **/
+$config['cdn_store_combined_files'] = TRUE;
+
+/**
+ * Put all javascript includes at the bottom of the template
+ * before the closing <body> tag instead of the <head> tag.
+ * This means script downloads won't block other HTTP-requests,
+ * which can be a performance improvement.
+ * @see Requirements_Backend::$write_js_to_body for details
+ **/
+$config['write_js_to_body'] = FALSE;
diff --git a/application/config/riverid.php b/application/config/riverid.php
new file mode 100644
index 0000000..01b36b0
--- /dev/null
+++ b/application/config/riverid.php
@@ -0,0 +1,46 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* RIVERID CONFIGURATION
+*/
+
+/**
+ * Enable RiverID, bool
+ * If this is set to true, you must define an API endpoint
+ */
+
+$config['enable'] = false;
+
+/**
+ * RiverID Server HTTP API endpoint
+ * ie https://crowdmapid.com/api (no trailing slash!)
+ */
+
+$config['endpoint'] = '';
+
+/**
+ * RiverID API Key
+ * A registered API key is necessary to communicate with an endpoint.
+ */
+
+$config['api_key'] = '';
+
+/**
+ * RiverID user exemption list
+ * Performs authentication locally instead of on the defined RiverID server
+ *
+ * Array of integer userids to be exempted from being authenticated
+ *   through the RiverID server. You may want to consider doing
+ *   this with the main administrator account (usually ID 1).
+ */
+$config['exempt'] = array();
+
+/**
+ * Cache lifetime in seconds.
+ * We cache some variables like server name, version and URL. This is
+ * the life of that cache. These variables rarely change.
+ */
+
+$config['cache_lifetime'] = 86400; // Default: 1 Day = 86400 Seconds
+
+?>
\ No newline at end of file
diff --git a/application/config/routes.php b/application/config/routes.php
new file mode 100644
index 0000000..83eff52
--- /dev/null
+++ b/application/config/routes.php
@@ -0,0 +1,12 @@
+<?php defined('SYSPATH') or die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Sets the default route to "welcome"
+ */
+
+$config['_default'] = 'main';
+$config['feed/atom'] = 'feed/index/atom';
+
+// Action::config - Config Routes
+Event::run('ushahidi_action.config_routes', $config);
\ No newline at end of file
diff --git a/application/config/session.php b/application/config/session.php
new file mode 100644
index 0000000..f1c9f03
--- /dev/null
+++ b/application/config/session.php
@@ -0,0 +1,45 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * @package Session
+ *
+ * Session driver name.
+ */
+$config['driver'] = 'database';
+
+/**
+ * Session storage parameter, used by drivers.
+ */
+//$config['storage'] = '';
+
+/**
+ * Session name.
+ * It must contain only alphanumeric characters and underscores. At least one letter must be present.
+ */
+$config['name'] = 'ushahidi';
+
+/**
+ * Session parameters to validate: user_agent, ip_address, expiration.
+ */
+$config['validate'] = array('user_agent', 'expiration');
+
+/**
+ * Enable or disable session encryption.
+ */
+$config['encryption'] = TRUE;
+
+/**
+ * Session lifetime. Number of seconds that each session will last.
+ * A value of 0 will keep the session active until the browser is closed (with a limit of 24h).
+ */
+//$config['expiration'] = 7200;
+
+/**
+ * Number of page loads before the session id is regenerated.
+ * A value of 0 will disable automatic session id regeneration.
+ */
+$config['regenerate'] = 0;
+
+/**
+ * Percentage probability that the gc (garbage collection) routine is started.
+ */
+//$config['gc_probability'] = 2;
diff --git a/application/config/settings.php b/application/config/settings.php
new file mode 100644
index 0000000..fa0e017
--- /dev/null
+++ b/application/config/settings.php
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* SITE CONFIGURATIONS
+*/
+
+// Find the subdomain
+$subdomain = '';
+if(substr_count($_SERVER["HTTP_HOST"],'.') > 1) $subdomain = substr($_SERVER["HTTP_HOST"],0,strpos($_SERVER["HTTP_HOST"],'.'));
+
+$config = array
+(
+	'site_name' => 'Ushahidi',
+	'site_email' => '',
+	'default_map' => '',
+	'api_google' => '',
+	'api_yahoo' => '',
+	'default_city' => '',
+	'default_country' => '',
+	'default_lat' => '',
+	'default_lon' => '',
+	'default_zoom' => '',
+	'items_per_page' => '5',
+	'items_per_page_admin' => '20',
+	'items_per_api_request' => '20',
+	'api_url' => '',
+	'api_url_all' => '',
+	'subdomain' => $subdomain,
+	'title_delimiter' => ' | ',
+	'alert_days' => 0, // HT: No of days of alert to be sent
+	'timeline_graph' => 'line', // HT: Timeline graph type
+	'timeline_point_label' => false // HT: Timeline graph point label
+);
diff --git a/application/config/upload.php b/application/config/upload.php
new file mode 100644
index 0000000..3a7aa14
--- /dev/null
+++ b/application/config/upload.php
@@ -0,0 +1,9 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+$config['directory'] = DOCROOT.'media/uploads';
+$config['relative_directory'] = 'media/uploads';
+$config['create_directories'] = TRUE;
+$conif['remove_spaces'] = TRUE;
+
+// Action::config - Config Upload
+Event::run('ushahidi_action.config_upload', $config);
\ No newline at end of file
diff --git a/application/config/version.php b/application/config/version.php
new file mode 100755
index 0000000..568aab9
--- /dev/null
+++ b/application/config/version.php
@@ -0,0 +1,12 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * The Ushahidi Engine version
+ * Make sure to update the ushahidi_version in the settings table too!
+ */
+$config['ushahidi_version'] = "2.7.4";
+
+/**
+ * The Ushahidi Engine DB revision number
+ * Increments when changes are made to the Ushahidi DB schema.
+ */
+$config['ushahidi_db_version'] = "117";
diff --git a/application/controllers/admin.php b/application/controllers/admin.php
new file mode 100644
index 0000000..c415159
--- /dev/null
+++ b/application/controllers/admin.php
@@ -0,0 +1,240 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This main controller for the Admin section
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * Admin_Controller
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Admin_Controller extends Template_Controller {
+	/**
+	 * Automatically display the views
+	 * @var bool
+	 */
+	public $auto_render = TRUE;
+
+	/**
+	 * Path to the parent view for the pages in the admin console
+	 * @var string
+	 */
+	public $template = 'admin/layout';
+
+	/**
+	 * Cache instance
+	 * @var Cache
+	 */
+	protected $cache;
+
+	/**
+	 * Whether authentication is required
+	 * @var bool
+	 */
+	protected $auth_required = FALSE;
+
+	/**
+	 * ORM reference for the currently logged in user
+	 * @var object
+	 */
+	protected $user;
+
+	/**
+	 * Configured table prefix in the database config file
+	 * @var string
+	 */
+	protected $table_prefix;
+
+	/**
+	 * Release name of the platform
+	 * @var string
+	 */
+	protected $release;
+
+	/**
+	 * No. of items to display per page - to be used for paginating lists
+	 * @var int
+	 */
+	protected $items_per_page;
+
+	/**
+	 * Auth instance for the admin controllers
+	 * @var Auth
+	 */
+	protected $auth;
+
+
+	public function __construct()
+	{
+		parent::__construct();
+
+		// Load cache
+		$this->cache = new Cache;
+
+		// Load session
+		$this->session = new Session;
+
+		// Load database
+		$this->db = new Database();
+
+		$this->session = Session::instance();
+
+		$this->auth = Auth::instance();
+
+		// Themes Helper
+		$this->themes = new Themes();
+		$this->themes->admin = TRUE;
+
+		// Admin is not logged in, or this is a member (not admin)
+		if ( ! $this->auth->logged_in('login'))
+		{
+			url::redirect('login');
+		}
+
+		// Check if user has the right to see the admin panel
+		if( ! $this->auth->admin_access())
+		{
+			// This user isn't allowed in the admin panel
+			url::redirect('/');
+		}
+
+		// Get the authenticated user
+		$this->user = $this->auth->get_user();
+
+		// Set Table Prefix
+		$this->table_prefix = Kohana::config('database.default.table_prefix');
+
+		// Get the no. of items to display setting
+		$this->items_per_page = (int) Kohana::config('settings.items_per_page_admin');
+
+		$this->template->admin_name = $this->user->name;
+
+		// Retrieve Default Settings
+		$this->template->site_name = Kohana::config('settings.site_name');
+		$this->template->mapstraction = Kohana::config('settings.mapstraction');
+		$this->themes->api_url = Kohana::config('settings.api_url');
+
+		// Javascript Header
+		$this->themes->map_enabled = FALSE;
+		$this->themes->datepicker_enabled = FALSE;
+		$this->themes->flot_enabled = FALSE;
+		$this->themes->treeview_enabled = FALSE;
+		$this->themes->protochart_enabled = FALSE;
+		$this->themes->colorpicker_enabled = FALSE;
+		$this->themes->editor_enabled = FALSE;
+		$this->themes->tablerowsort_enabled = FALSE;
+		$this->themes->json2_enabled = FALSE;
+		$this->themes->hovertip_enabled = TRUE;
+		$this->themes->slider_enabled = TRUE;
+		$this->themes->js = '';
+		$this->template->form_error = FALSE;
+
+		// Initialize some variables for raphael impact charts
+		$this->themes->raphael_enabled = FALSE;
+		$this->themes->impact_json = '';
+
+		// Generate main tab navigation list.
+		$this->template->main_tabs = admin::main_tabs();
+
+		// Generate sub navigation list (in default layout, sits on right side).
+		$this->template->main_right_tabs = admin::main_right_tabs($this->user);
+
+		$this->template->this_page = "";
+
+		// Header Nav
+		$header_nav = new View('header_nav');
+		$this->template->header_nav = $header_nav;
+		$this->template->header_nav->loggedin_user = $this->user;
+		$this->template->header_nav->loggedin_role = $this->user->dashboard();
+		$this->template->header_nav->site_name = Kohana::config('settings.site_name');
+
+		// Language switcher
+		$this->template->languages = $this->themes->languages();
+		
+		Event::add('ushahidi_filter.view_pre_render.admin_layout', array($this, '_pre_render'));
+	}
+
+	public function index()
+	{
+		// Send them to the right page
+		if (Kohana::config('config.enable_mhi') == TRUE && Kohana::config('settings.subdomain') == '')
+		{
+			url::redirect('admin/mhi');
+		}
+		else
+		{
+			url::redirect('admin/dashboard');
+		}
+	}
+
+
+    /**
+     * Checks version sequence parts
+     *
+     * @param string release_version - The version released.
+     * @param string version_ushahidi - The version of ushahidi installed.
+     *
+     * @return boolean
+     */
+	private function _new_or_not($release_version=NULL, $version_ushahidi=NULL)
+	{
+		if ($release_version AND $version_ushahidi)
+		{
+			// Split version numbers xx.xx.xx
+			$remote_version = explode(".", $release_version);
+			$local_version = explode(".", $version_ushahidi);
+
+			// Check first part .. if its the same, move on to next part
+			if (isset($remote_version[0]) AND isset($local_version[0])
+				AND (int) $remote_version[0] > (int) $local_version[0])
+			{
+				return TRUE;
+			}
+
+			// Check second part .. if its the same, move on to next part
+			if (isset($remote_version[1]) AND isset($local_version[1])
+				AND (int) $remote_version[1] > (int) $local_version[1])
+			{
+				return TRUE;
+			}
+
+			// Check third part
+			if (isset($remote_version[2]) AND (int) $remote_version[2] > 0)
+			{
+				if ( ! isset($local_version[2]))
+				{
+					return TRUE;
+				}
+				elseif( (int) $remote_version[2] > (int) $local_version[2] )
+				{
+					return TRUE;
+				}
+			}
+		}
+
+		return TRUE;
+	}
+	
+	/**
+	 * Trigger themes->admin_requirements() at the last minute
+	 * 
+	 * This is in case features are enabled/disabled
+	 */
+	public function _pre_render()
+	{
+		$this->themes->requirements();
+		$this->themes->plugin_requirements();
+		$this->template->header_block = $this->themes->header_block();
+		$this->template->footer_block = $this->themes->footer_block();
+	}
+
+
+} // End Admin
+
diff --git a/application/controllers/admin/addons.php b/application/controllers/admin/addons.php
new file mode 100644
index 0000000..3071005
--- /dev/null
+++ b/application/controllers/admin/addons.php
@@ -0,0 +1,35 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Addon Manager
+ * Install new Plugins & Themes
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Addons_Controller extends Admin_Controller {
+    
+    function __construct()
+    {
+        parent::__construct();
+        $this->template->this_page = 'addons';
+
+        // If this is not a super-user account, redirect to dashboard
+        if(!$this->auth->logged_in('admin') && !$this->auth->logged_in('superadmin'))
+        {
+             url::redirect('admin/dashboard');
+        }
+    }
+    
+    public function index()
+    {
+        url::redirect('admin/addons/plugins/');
+    }
+}
diff --git a/application/controllers/admin/addons/plugins.php b/application/controllers/admin/addons/plugins.php
new file mode 100644
index 0000000..ee5e551
--- /dev/null
+++ b/application/controllers/admin/addons/plugins.php
@@ -0,0 +1,204 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Addon Manager
+ * Install new Plugins & Themes
+ * Credits To Jeremy Bush at Zombor.net (http://www.zombor.net/)
+ *
+ * Portions of this code:
+ * Copyright (c) 2008-2009, Argentum Team
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Plugins_Controller extends Admin_Controller {
+
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'addons';
+
+		// If this is not a super-user account, redirect to dashboard
+		if (!$this->auth->logged_in('admin') && !$this->auth->logged_in('superadmin'))
+		{
+			url::redirect('admin/dashboard');
+		}
+	}
+
+	public function index()
+	{
+		$this->template->content = new View('admin/addons/plugins');
+		$this->template->content->title = 'Addons';
+
+		if (isset($_GET['status']) && !empty($_GET['status']))
+		{
+			$status = $_GET['status'];
+
+			if (strtolower($status) == 'a')
+			{
+				$filter = 'plugin_active = 1';
+			}
+			elseif (strtolower($status) == 'i')
+			{
+				$filter = 'plugin_active = 0';
+			}
+			else
+			{
+				$status = "0";
+				$filter = '1=1';
+			}
+		}
+		else
+		{
+			$status = "0";
+			$filter = '1=1';
+		}
+
+		// Add the hidden plugins to the list of plugins to filter out
+		if(count(Kohana::config('plugins.hide_from_list')) != 0) {
+			$hide_from_list = array_map(array(Database::instance(), 'escape'), Kohana::config('plugins.hide_from_list'));
+			$filter .= ' AND plugin_name NOT IN ('.implode(",", $hide_from_list).')';
+		}
+
+		$plugins = plugin::resync_plugins();
+
+		// check, has the form been submitted?
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			//  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			$post->add_rules('plugin_id.*','required','numeric');
+
+			if ($post->validate())
+			{
+				if ($post->action == 'a')
+				{
+					// Activate Action
+					foreach(array_unique($post->plugin_id) as $item)
+					{
+						$plugin = ORM::factory('plugin', $item);
+						// Make sure we run the installer if it hasnt been installed yet.
+						// Then mark it as installed
+						if ($plugin->loaded AND $plugin->plugin_name)
+						{
+							Kohana::config_set('core.modules', array_merge(Kohana::config('core.modules'), array(PLUGINPATH . $plugin->plugin_name)));
+
+							// Name of the class (First letter of class should be capitalized)
+							$class = ucfirst($plugin->plugin_name) . '_Install';
+
+							// Find the Library File
+							$path = plugin::find_install($plugin->plugin_name);
+							if ($path)
+							{
+								include $path;
+
+								// Run the installer
+								$install = new $class;
+								$install->run_install();
+							}
+
+							// Mark as Active and Mark as Installed
+							$plugin->plugin_active = 1;
+							$plugin->plugin_installed = 1;
+							$plugin->save();
+						}
+					}
+				}
+				elseif ($post->action == 'i')
+				{
+					// Deactivate Action
+					foreach ($post->plugin_id as $item)
+					{
+						$plugin = ORM::factory('plugin', $item);
+						if ($plugin->loaded)
+						{
+							$plugin->plugin_active = 0;
+							$plugin->save();
+						}
+					}
+				}
+				elseif ($post->action == 'd')
+				{
+					// Delete Action
+					foreach ($post->plugin_id as $item)
+					{
+						$plugin = ORM::factory('plugin', $item);
+						if ($plugin->loaded AND $plugin->plugin_name)
+						{
+							Kohana::config_set('core.modules', array_merge(Kohana::config('core.modules'), array(PLUGINPATH . $plugin->plugin_name)));
+
+							// Name of the class (First letter of class should be capitalized)
+							$class = ucfirst($plugin->plugin_name) . '_Install';
+
+							// Find the Library File
+							$path = plugin::find_install($plugin->plugin_name);
+
+							if ($path)
+							{
+								include $path;
+
+								// Run the uninstaller
+								$install = new $class;
+								$install->uninstall();
+							}
+
+							// Mark as InActive and Mark as UnInstalled
+							$plugin->plugin_active = 0;
+							$plugin->plugin_installed = 0;
+							$plugin->save();
+						}
+					}
+				}
+			}
+			else
+			{
+				$form_error = TRUE;
+			}
+		}
+
+		$plugins = ORM::factory('plugin')
+			->where($filter)
+			->orderby('plugin_name', 'ASC')
+			->find_all();
+		$this->template->content->plugins = $plugins;
+		$this->template->content->total_items = $plugins->count();
+
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+
+		// Status Tab
+		$this->template->content->status = $status;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/addons/addons_js');
+	}
+
+}
diff --git a/application/controllers/admin/addons/themes.php b/application/controllers/admin/addons/themes.php
new file mode 100644
index 0000000..644151e
--- /dev/null
+++ b/application/controllers/admin/addons/themes.php
@@ -0,0 +1,101 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Addon Manager
+ * Install new Plugins & Themes
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Themes_Controller extends Admin_Controller {
+
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'addons';
+
+		// If this is not a super-user account, redirect to dashboard
+		if (!$this->auth->logged_in('admin') && !$this->auth->logged_in('superadmin'))
+		{
+			url::redirect('admin/dashboard');
+		}
+	}
+
+	function index()
+	{
+		$this->template->content = new View('admin/addons/themes');
+		$this->template->content->title = 'Addons';
+
+		// setup and initialize form field names
+		$form = array('site_style' => '');
+		//  Copy the form as errors, so the errors will be stored with keys
+		//  corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('site_style', 'length[1,50]');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// Yes! everything is valid
+				Settings_Model::save_setting('site_style', $post->site_style);
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			$site_style = Settings_Model::get_setting('site_style');
+			// Retrieve Current Settings
+			$form = array('site_style' => (! empty($site_style)) ? $site_style : 'default');
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$themes = addon::get_addons('theme');	
+		$this->template->content->themes = $themes;
+		//delete cache to make sure theme is reloaded after change
+  		$this->cache->delete_all();
+	}
+
+}
diff --git a/application/controllers/admin/comments.php b/application/controllers/admin/comments.php
new file mode 100644
index 0000000..6e760aa
--- /dev/null
+++ b/application/controllers/admin/comments.php
@@ -0,0 +1,192 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Comments Controller.
+ * This controller will take care of viewing and editing comments in the Admin section.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Comments_Controller extends Admin_Controller {
+	
+	public function __construct()
+    {
+        parent::__construct();
+    
+        $this->template->this_page = 'reports';
+        
+        // If user doesn't have access, redirect to dashboard
+        if ( ! $this->auth->has_permission("reports_comments"))
+        {
+            url::redirect(url::site().'admin/dashboard');
+        }
+    }
+    
+    
+    /**
+    * Lists the reports.
+    * @param int $page
+    */
+    function index($page = 1)
+    {
+        $this->template->content = new View('admin/comments/main');
+        $this->template->content->title = Kohana::lang('ui_admin.comments');
+        
+        
+        if (!empty($_GET['status']))
+        {
+            $status = $_GET['status'];
+            
+            if (strtolower($status) == 'a')
+            {
+                $filter = 'comment_active = 1 AND comment_spam = 0';
+            }
+            elseif (strtolower($status) == 'p')
+            {
+                $filter = 'comment_active = 0 AND comment_spam = 0';
+            }
+            elseif (strtolower($status) == 's')
+            {
+                $filter = 'comment_spam = 1';
+            }
+            else
+            {
+                $status = "0";
+                $filter = 'comment_spam = 0';
+            }
+        }
+        else
+        {
+            $status = "0";
+            $filter = 'comment_spam = 0';
+        }
+        
+        
+        // check, has the form been submitted?
+        $form_error = FALSE;
+        $form_saved = FALSE;
+        $form_action = "";
+        if ($_POST)
+        {
+            $post = Validation::factory($_POST);
+            
+             //  Add some filters
+            $post->pre_filter('trim', TRUE);
+
+            // Add some rules, the input field, followed by a list of checks, carried out in order
+            $post->add_rules('action','required', 'alpha', 'length[1,1]');
+            $post->add_rules('comment_id.*','required','numeric');
+            
+            if ($post->validate())
+            {
+                if ($post->action == 'a')       
+                { // Approve Action
+                    foreach($post->comment_id as $item)
+                    {
+                        $update = new Comment_Model($item);
+                        if ($update->loaded == true) {
+                            $update->comment_active = '1';
+                            $update->comment_spam = '0';
+                            $update->save();
+                        }
+                    }
+                    $form_action = utf8::strtoupper(Kohana::lang('ui_admin.approved'));
+                }
+                elseif ($post->action == 'u')   
+                { // Unapprove Action
+                    foreach($post->comment_id as $item)
+                    {
+                        $update = new Comment_Model($item);
+                        if ($update->loaded == true) {
+                            $update->comment_active = '0';
+                            $update->save();
+                        }
+                    }
+                    $form_action = utf8::strtoupper(Kohana::lang('ui_admin.unapproved'));
+                }
+                elseif ($post->action == 's')   
+                { // Spam Action
+                    foreach($post->comment_id as $item)
+                    {
+                        $update = new Comment_Model($item);
+                        if ($update->loaded == true) {
+                            $update->comment_spam = '1';
+                            $update->comment_active = '0';
+                            $update->save();
+                        }
+                    }
+                    $form_action = utf8::strtoupper(Kohana::lang('ui_admin.marked_as_spam'));
+                }
+                elseif ($post->action == 'n')   
+                { // Spam Action
+                    foreach($post->comment_id as $item)
+                    {
+                        $update = new Comment_Model($item);
+                        if ($update->loaded == true) {
+                            $update->comment_spam = '0';
+                            $update->comment_active = '1';
+                            $update->save();
+                        }
+                    }
+                    $form_action = utf8::strtoupper(Kohana::lang('ui_admin.marked_as_not_spam'));
+                }
+                elseif ($post->action == 'd')   // Delete Action
+                {
+                    foreach($post->comment_id as $item)
+                    {
+                        $update = new Comment_Model($item);
+                        if ($update->loaded == true)
+                        {
+                            $update->delete();
+                        }                   
+                    }
+                    $form_action = Kohana::lang('ui_admin.deleted');
+                }
+                elseif ($post->action == 'x')   // Delete All Spam Action
+                {
+                    ORM::factory('comment')->where('comment_spam','1')->delete_all();
+                    $form_action = Kohana::lang('ui_admin.deleted');
+                }
+                $form_saved = TRUE;
+            }
+            else
+            {
+                $form_error = TRUE;
+            }
+            
+        }
+        
+        
+        // Pagination
+        $pagination = new Pagination(array(
+            'query_string'    => 'page',
+            'items_per_page' => $this->items_per_page,
+            'total_items'    => ORM::factory('comment')->where($filter)->count_all()
+        ));
+
+        $comments = ORM::factory('comment')->where($filter)->orderby('comment_date', 'desc')->find_all($this->items_per_page, $pagination->sql_offset);
+        
+        $this->template->content->comments = $comments;
+        $this->template->content->pagination = $pagination;
+        $this->template->content->form_error = $form_error;
+        $this->template->content->form_saved = $form_saved;
+        $this->template->content->form_action = $form_action;
+        
+        // Total Reports
+        $this->template->content->total_items = $pagination->total_items;
+        
+        // Status Tab
+        $this->template->content->status = $status;
+        
+        // Javascript Header
+        $this->themes->js = new View('admin/comments/comments_js');        
+    }
+    
+}
diff --git a/application/controllers/admin/dashboard.php b/application/controllers/admin/dashboard.php
new file mode 100644
index 0000000..dcda5cc
--- /dev/null
+++ b/application/controllers/admin/dashboard.php
@@ -0,0 +1,135 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used for the main Admin panel
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Dashboard_Controller extends Admin_Controller
+{
+	public function __construct()
+	{
+		parent::__construct();
+	}
+
+	public function index()
+	{
+		
+		// Don't show auto-upgrader when disabled.
+		if (Kohana::config('config.enable_auto_upgrader') AND Kohana::config('version.ushahidi_db_version') > Kohana::config('settings.db_version'))
+		{
+			url::redirect('admin/upgrade/database');
+		}
+		
+		$this->template->content = new View('admin/dashboard/main');
+		$this->template->content->title = Kohana::lang('ui_admin.dashboard');
+		$this->template->this_page = 'dashboard';
+
+		// Retrieve Dashboard Count...
+
+		// Total Reports
+		$this->template->content->reports_total = ORM::factory('incident')->count_all();
+
+		// Total Unapproved Reports
+		$this->template->content->reports_unapproved = ORM::factory('incident')->where('incident_active', '0')->count_all();
+
+		// Total Unverified Reports
+		$this->template->content->reports_unverified = ORM::factory('incident')->where('incident_verified', '0')->count_all();
+
+		// Total Categories
+		$this->template->content->categories = ORM::factory('category')->count_all();
+
+		// Total Locations
+		$this->template->content->locations = ORM::factory('location')->count_all();
+
+		// Total Incoming Media
+		$this->template->content->incoming_media = ORM::factory('feed_item')->count_all();
+
+		// Messages By Service
+		$total_message_count = 0;
+		$message_services = array();
+		$services = ORM::factory('service')->find_all();
+
+		foreach ($services as $service)
+		{
+			$message_count = ORM::factory('message')
+							->join('reporter','message.reporter_id','reporter.id')
+							->where('service_id', $service->id)
+							->where('message_type', '1')
+							->count_all();
+
+			$message_services[] = array(
+				'id'    => $service->id,
+				'name'  => $service->service_name,
+				'count' => $message_count
+			);
+
+			$total_message_count += $message_count;
+		}
+
+		$this->template->content->message_services = $message_services;
+
+		// Total Messages
+		$this->template->content->message_count = $total_message_count;
+
+
+		// Get reports for display
+		$incidents = ORM::factory('incident')
+					    ->limit(5)
+					    ->orderby('incident_dateadd', 'desc')
+					    ->find_all();
+
+		$this->template->content->incidents = $incidents;
+
+		// Get Incoming Media (We'll Use NewsFeeds for now)
+		$this->template->content->feeds = ORM::factory('feed_item')
+											->limit('3')
+											->orderby('item_date', 'desc')
+											->find_all();
+
+		// Javascript Header
+		$this->themes->protochart_enabled = TRUE;
+		$this->themes->js = new View('admin/stats/stats_js');
+
+		$this->template->content->failure = '';
+
+		// Build dashboard chart
+
+		// Set the date range (how many days in the past from today?)
+		// Default to all time if not set
+		$range = (!empty($_GET['range']))
+			? $_GET['range']
+			: 0;
+		
+		$incident_data = Incident_Model::get_number_reports_by_date($range);
+		$data = array('Reports'=>$incident_data);
+		$options = array('xaxis'=>array('mode'=>'"time"'));
+		
+		$this->template->content->report_chart = protochart::chart('report_chart', $data, $options, 
+		    array('Reports'=>'CC0000'), 410, 310);
+		
+		// Render version sync checks if enabled
+		$this->template->content->version_sync = NULL;
+		if (Kohana::config('config.enable_ver_sync_warning') == TRUE)
+		{
+			$this->template->content->version_sync = View::factory('admin/version_sync');
+		}
+		
+		// Render security checks if enabled
+		$this->template->content->security_info = NULL;
+		if (Kohana::config('config.enable_security_info') == TRUE)
+		{
+			$this->template->content->security_info = View::factory('admin/security_info');
+		}
+		
+	}
+}
+?>
diff --git a/application/controllers/admin/jwysiwyg/filemanager.php b/application/controllers/admin/jwysiwyg/filemanager.php
new file mode 100644
index 0000000..98b30be
--- /dev/null
+++ b/application/controllers/admin/jwysiwyg/filemanager.php
@@ -0,0 +1,72 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used to handle jwysiwyg file manager 
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class FileManager_Controller extends Admin_Controller {
+	
+	public function index()
+	{
+		define('DEBUG', true);
+		
+		// an array of file extensions to accept
+		$accepted_extensions = array(
+			"png", "jpg", "gif"
+		);
+		
+		// http://your-web-site.domain/base/url
+		$base_url = url::base() . Kohana::config('upload.relative_directory', TRUE)."jwysiwyg";
+		
+		// the root path of the upload directory on the server
+		$uploads_dir = Kohana::config('upload.directory', TRUE)."jwysiwyg";
+		
+		// the root path that the files are available from the webserver
+		$uploads_access_dir = Kohana::config('upload.directory', TRUE)."jwysiwyg";
+		
+		if (!file_exists($uploads_access_dir)) {
+			mkdir($uploads_access_dir, 0775);
+		}
+		
+		if (DEBUG) {
+			if (!file_exists($uploads_access_dir)) {
+				$error = 'Folder "' . $uploads_access_dir . '" doesn\'t exists.';
+		
+				header('Content-type: text/html; charset=UTF-8');
+				print('{"error":"config.php: ' . htmlentities($error) . '","success":false}');
+				exit();
+			}
+		}
+		
+		$capabilities = array(
+			"move" => false,
+			"rename" => true,
+			"remove" => true,
+			"mkdir" => false,
+			"upload" => true
+		);
+		
+		
+		if (extension_loaded('mbstring')) {
+			mb_internal_encoding('UTF-8');
+			mb_regex_encoding('UTF-8');
+		}
+		
+		require_once Kohana::find_file('libraries/jwysiwyg', 'common', TRUE);
+		require_once Kohana::find_file('libraries/jwysiwyg', 'handlers', TRUE);
+		
+		ResponseRouter::getInstance()->run();
+		
+	}
+	
+}
+	
\ No newline at end of file
diff --git a/application/controllers/admin/manage.php b/application/controllers/admin/manage.php
new file mode 100644
index 0000000..c005f52
--- /dev/null
+++ b/application/controllers/admin/manage.php
@@ -0,0 +1,1075 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used to add/ remove categories, forms, pages,
+ * news feeds, layers and managing the scheduler and public listings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+/**
+ * Feed type value for news
+ */
+define('FEED_TYPE_TEXT', 'text');
+
+/**
+ * Feed type value for videos
+ */
+define('FEED_TYPE_VIDEO', 'video');
+
+/**
+ * Feed type value for photos
+ */
+define('FEED_TYPE_PHOTO', 'photo');
+
+class Manage_Controller extends Admin_Controller
+{
+
+	public function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'manage';
+
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("manage"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+
+	/**
+	 * Add Edit Categories
+	 */
+	public function index()
+	{
+		$this->template->content = new View('admin/manage/categories/main');
+		$this->template->content->title = Kohana::lang('ui_admin.categories');
+
+		// Locale (Language) Array
+		$locales = ush_locale::get_i18n();
+
+		// Setup and initialize form field names
+		$form = array(
+			'action' => '',
+			'category_id' => '',
+			'parent_id' => '',
+			'category_title' => '',
+			'category_description' => '',
+			'category_color' => '',
+			'category_image' => '',
+			'category_image_thumb' => '',
+			'form_auth_token' => ''
+		);
+
+		// Add the different language form keys for fields
+		foreach ($locales as $lang_key => $lang_name)
+		{
+			$form['category_title_'.$lang_key] = '';
+			$form['category_description_'.$lang_key] = '';
+		}
+
+		// Copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		$parents_array = array();
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{			
+			// Fetch the post data
+			$post_data = array_merge($_POST, $_FILES);
+			
+			// Extract category-specific  information
+			$category_data = arr::extract($post_data, 'parent_id',
+				'category_title', 'category_description', 'category_color');
+			
+			// Extract category image and category languages for independent validation
+			$secondary_data = arr::extract($post_data, 'category_image',
+				'category_title_lang','category_description_lang', 'action');
+			
+			// Setup validation for the secondary data
+			$post = Validation::factory($secondary_data)
+						->pre_filter('trim', TRUE);
+
+			// Add validation for the add/edit action
+			if ($post->action == 'a')
+			{
+				$post->add_rules('category_image', 'upload::valid',
+					'upload::type[gif,jpg,png]', 'upload::size[50K]');
+
+				// Add the different language form keys for fields
+				foreach ($locales as $lang_key => $lang_name)
+				{
+					$post->add_rules('category_title_lang['.$lang_key.']','length[3,80]');
+				}
+			}
+			
+			// Category instance for the operation
+			$category = (! empty($_POST['category_id']) AND Category_Model::is_valid_category($_POST['category_id']))
+				? new Category_Model($_POST['category_id'])
+				: new Category_Model();
+			
+			
+			// Check the specified action
+			if ($post->action == 'a')
+			{
+				// Test to see if things passed the rule checks
+				if ($category->validate($category_data) AND $post->validate(FALSE))
+				{
+					// Get Category position for a new category
+					if (empty($_POST['category_id']))
+					{
+						$cat_count = ORM::factory('category')->count_all();
+						$category->category_position = $cat_count;
+					}
+					
+					// Save the category
+					$category->save();
+					
+					// Get the category localization
+					$languages = ($category->loaded) ? Category_Lang_Model::category_langs($category->id) : FALSE;
+					
+					$category_lang = (isset($languages[$category->id])) ? $languages[$category->id] : FALSE;
+					
+					// Save localizations
+					foreach ($post->category_title_lang as $lang_key => $localized_category_name)
+					{
+						// Skip language if same as category locale
+						if ($lang_key == $category->locale) continue;
+						// Skip lang if fields are blank
+						if ($localized_category_name == '' AND $post->category_description_lang[$lang_key] == '') continue;
+						
+						$cl = (isset($category_lang[$lang_key]['id']))
+							? ORM::factory('category_lang',$category_lang[$lang_key]['id'])
+							: ORM::factory('category_lang');
+						
+ 						$cl->category_title = $localized_category_name;
+ 						$cl->category_description = $post->category_description_lang[$lang_key];
+ 						$cl->locale = $lang_key;
+ 						$cl->category_id = $category->id;
+						$cl->save();
+					}
+					
+					// Upload Image/Icon
+					$filename = upload::save('category_image');
+					if ($filename)
+					{
+						$new_filename = "category_".$category->id."_".time();
+						
+						// Name the files for the DB
+						$cat_img_file = $new_filename.".png";
+						$cat_img_thumb_file = $new_filename."_16x16.png";
+
+						// Resize Image to 32px if greater
+						Image::factory($filename)->resize(32,32,Image::HEIGHT)
+							->save(Kohana::config('upload.directory', TRUE) . $cat_img_file);
+						// Create a 16x16 version too
+						Image::factory($filename)->resize(16,16,Image::HEIGHT)
+							->save(Kohana::config('upload.directory', TRUE) . $cat_img_thumb_file);
+							
+						// Okay, now we have these three different files on the server, now check to see
+						//   if we should be dropping them on the CDN
+						
+						if (Kohana::config("cdn.cdn_store_dynamic_content"))
+						{
+							$cat_img_file = cdn::upload($cat_img_file);
+							$cat_img_thumb_file = cdn::upload($cat_img_thumb_file);
+							
+							// We no longer need the files we created on the server. Remove them.
+							$local_directory = rtrim(Kohana::config('upload.directory', TRUE), '/').'/';
+							unlink($local_directory.$new_filename.".png");
+							unlink($local_directory.$new_filename."_16x16.png");
+						}
+
+						// Remove the temporary file
+						unlink($filename);
+
+						// Delete Old Image
+						$category_old_image = $category->category_image;
+						if ( ! empty($category_old_image))
+						{
+							if (file_exists(Kohana::config('upload.directory', TRUE).$category_old_image))
+							{
+								unlink(Kohana::config('upload.directory', TRUE).$category_old_image);
+							}
+							elseif (Kohana::config("cdn.cdn_store_dynamic_content") AND valid::url($category_old_image))
+							{
+								cdn::delete($category_old_image);
+							}
+						}
+
+						// Save
+						$category->category_image = $cat_img_file;
+						$category->category_image_thumb = $cat_img_thumb_file;
+						$category->save();
+						Event::run('ushahidi_action.category_save', $post);
+					}
+
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.added_edited'));
+
+					// Empty $form array
+					array_fill_keys($form, '');
+				}
+				else
+				{
+					// Validation failed
+
+					// Repopulate the form fields
+					$form = arr::overwrite($form, array_merge($category_data->as_array(), $post->as_array()));
+
+					// populate the error fields, if any
+					$errors = arr::overwrite($errors, 
+						array_merge($category_data->errors('category'), $post->errors('category')));
+					$form_error = TRUE;
+				}
+				
+			}
+			elseif ($post->action == 'd' AND $post->validate())
+			{
+				// Delete action
+				if ($category->loaded)
+				{
+					// Delete category - except if it is trusted
+					if (! $category->category_trusted)
+					{
+						$category->delete();
+						// Note: deleting related models is handled by Category_Model::delete()
+					}
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+			}
+			elseif ($post->action == 'v' AND $post->validate())
+			{ 
+				// Show/Hide Action
+				if ($category->loaded)
+				{
+					// Check for all subcategories tied to this category 
+					$children = ORM::factory('category')
+								->where('parent_id', $category->id)
+								->find_all();
+					
+					// Then show/hide subcategories based on status of parent category
+					foreach ($children as $child)
+					{
+						$sub_cat = new Category_Model($child->id);
+						$sub_cat->category_visible = ($category->category_visible == 1)? 0: 1;
+						$sub_cat->save();
+					}
+					
+					// Change status of the Parent Category
+					$category->category_visible = ($category->category_visible == 1)? 0 : 1;
+					$category->save();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+			}
+			elseif ($post->action == 'i' AND $post->validate())
+			{ 
+				// Delete Image/Icon Action
+				if ($category->loaded)
+				{
+					$category_image = $category->category_image;
+					$category_image_thumb = $category->category_image_thumb;
+
+					// Delete the main image
+					if
+					( 
+						! empty($category_image) AND
+						file_exists(Kohana::config('upload.directory', TRUE).$category_image)
+					)
+					{
+						unlink(Kohana::config('upload.directory', TRUE) . $category_image);
+					}
+
+					// Delete the thumb
+					if
+					(
+						! empty($category_image_thumb) AND
+						file_exists(Kohana::config('upload.directory', TRUE).$category_image_thumb)
+					)
+					{
+						unlink(Kohana::config('upload.directory', TRUE) . $category_image_thumb);
+					}
+
+					$category->category_image = NULL;
+					$category->category_image_thumb = NULL;
+					$category->save();
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+			}
+		}
+
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string' => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items' => ORM::factory('category')
+			->where('parent_id','0')
+			->count_all()
+		));
+
+		$categories = ORM::factory('category')
+						->with('category_lang')
+						->where('parent_id','0')
+						->orwhere('parent_id NOT IN (SELECT id from `'.Kohana::config('database.default.table_prefix').'category`)') // Find orphaned categories.. slightly horrible SQL
+						->find_all($this->items_per_page, $pagination->sql_offset);
+
+		$parents_array = ORM::factory('category')
+							->where('parent_id','0')
+							->where('category_trusted != 1')
+							->select_list('id', 'category_title');
+
+		// add none to the list
+		$parents_array[0] = "--- Top Level Category ---";
+
+		// Put "--- Top Level Category ---" at the top of the list
+		ksort($parents_array);
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->categories = $categories;
+
+		$this->template->content->parents_array = $parents_array;
+
+		// Javascript Header
+		$this->themes->colorpicker_enabled = TRUE;
+		$this->themes->tablerowsort_enabled = TRUE;
+		$this->themes->js = new View('admin/manage/categories/categories_js');
+		$this->template->form_error = $form_error;
+
+		$this->template->content->locale_array = $locales;
+		$this->themes->js->locale_array = $locales;
+	}
+	
+	/**
+	 * Sorts categories
+	 */
+	public function category_sort()
+	{
+		$this->auto_render = FALSE;
+		$this->template = "";
+		
+		if ($_POST)
+		{
+			if (isset($_POST['categories'])
+				AND ! empty($_POST['categories'])
+				)
+			{
+				$categories = array_map('trim', explode(',', $_POST['categories']));
+				$i = 0;
+				$parent_id = 0;
+				foreach ($categories as $category_id)
+				{
+					if ($category_id)
+					{
+						$category = ORM::factory('category', $category_id);
+						if ($category->loaded)
+						{
+							if ($i == 0 AND $category->parent_id != 0)
+							{ // ERROR!!!!!!!! WHY ARE YOU TRYING TO PLACE A SUBCATEGORY ABOVE A CATEGORY???
+								echo "ERROR";
+								return;
+							}
+							
+							if ($category->parent_id == 0)
+							{
+								// Set Parent ID
+								$parent_id = $category->id;
+							}
+							else
+							{
+								if ($parent_id)
+								{
+									$category->parent_id = $parent_id;
+								}
+							}							
+							
+							$category->category_position = $i;
+							$category->save();
+						}
+					}
+					
+					$i++;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Manage Public Listing for External Applications
+	 */
+	public function publiclisting()
+	{
+		$this->template->content = new View('admin/manage/publiclisting');
+
+		$this->template->content->encoded_stat_id = base64_encode(Settings_Model::get_setting('stat_id'));
+		$this->template->content->encoded_stat_key = base64_encode(Settings_Model::get_setting('stat_key'));
+		$this->template->content->lat = Settings_Model::get_setting('default_lat');
+		$this->template->content->lon = Settings_Model::get_setting('default_lon');
+	}
+
+
+	/**
+	 * Add Edit Pages
+	 */
+	public function pages()
+	{
+		$this->template->content = new View('admin/manage/pages/main');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'action' => '',
+			'page_id' => '',
+			'page_title' => '',
+			'page_tab' => '',
+			'page_description' => ''
+		);
+		
+		// Copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			$page = (isset($_POST['page_id']) AND Page_Model::is_valid_page($_POST['page_id']))
+				? ORM::factory('page', $_POST['page_id'])
+				: new Page_Model();
+				
+			
+			$post = array_merge($_POST, $_FILES);
+			$post = array_merge($post, array("id"=>$page->id));
+			Event::run('ushahidi_action.page_submit', $post);
+			
+			// Check for the specified action
+			if ($_POST['action'] == 'a')
+			{
+				// Manually extract the data to be validated from $_POST
+				$data = arr::extract($_POST, 'page_id', 'page_title', 'page_description', 'page_tab');
+				
+				if ($page->validate($data))
+				{
+					$page->save();
+					Event::run('ushahidi_action.page_edit', $page);
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.added_edited'));
+					array_fill_keys($form, '');
+				}
+				else
+				{
+					// Repopulate the form fields
+					$form = arr::overwrite($form, $data->as_array());
+
+					// Populate the error fields, if any
+					$errors = arr::overwrite($errors, $data->errors('page'));
+					$form_error = TRUE;
+				}
+			}
+			elseif ($_POST['action'] == 'd')
+			{ 
+				// Delete action
+				if ($page->loaded)
+				{
+					$page->delete();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+
+			}
+			elseif ($_POST['action'] == 'v')
+			{ 
+				// Show/Hide Action
+				if ($page->loaded)
+				{
+					$page->page_active = ($page->page_active == 1)? 0 : 1;
+					$page->save();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+			}
+		}
+
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string' => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items' => ORM::factory('page')->count_all()
+		));
+
+		$pages = ORM::factory('page')
+					->orderby('page_title', 'asc')
+					->find_all($this->items_per_page, $pagination->sql_offset);
+
+		$this->template->content->form = $form;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->pages = $pages;
+		$this->template->content->errors = $errors;
+
+		// Javascript Header
+		$this->themes->editor_enabled = TRUE;
+		$this->themes->js = new View('admin/manage/pages/pages_js');
+	}
+
+
+	/**
+	 * Add Edit News Feeds
+	 */
+	public function feeds()
+	{
+		$this->template->content = new View('admin/manage/feeds/main');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'action' => '',
+			'feed_id' => '',
+			'feed_name' => '',
+			'feed_url' => '',
+			'feed_active' => ''
+		);
+		//	copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+
+		if( $_POST )
+		{
+			// Feed_Model instance
+			$feed = (isset($_POST['feed_id']) AND Feed_Model::is_valid_feed($_POST['feed_id']))
+						? new Feed_Model($_POST['feed_id'])
+						: new Feed_Model();
+			
+			if ($_POST['action'] == 'a')		// Add Action
+			{
+				// Manually extract the data to be validated
+				$data = arr::extract($_POST, 'feed_name', 'feed_url');
+				
+				// Test validation
+				if ($feed->validate($data))
+				{
+					$feed->save();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.added_edited'));
+				}
+				else
+				{
+					// Repopulate the form fields
+					$form = arr::overwrite($form, $data->as_array());
+
+					// Populate the error fields, if any
+					$errors = arr::overwrite($errors, $data->errors('feeds'));
+					$form_error = TRUE;
+				}
+			}
+			elseif ($_POST['action'] == 'd')
+			{
+				// Delete Action
+				if ($feed->loaded == TRUE)
+				{
+					ORM::factory('feed_item')->where('feed_id', $feed->id)->delete_all();
+					$feed->delete();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+			}
+			elseif($_POST['action'] == 'v')
+			{
+				// Active/Inactive Action
+				if ($feed->loaded == TRUE)
+				{
+					$feed->feed_active =  ($feed->feed_active == 1) ? 0 : 1;
+					$feed->save();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+			}
+			elseif ($_POST['action'] == 'r')
+			{
+				$this->_parse_feed();
+			}
+		}
+
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string' => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items'	 => ORM::factory('feed')->count_all()
+		));
+
+		$feeds = ORM::factory('feed')
+					->orderby('feed_name', 'asc')
+					->find_all($this->items_per_page, $pagination->sql_offset);
+
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->feeds = $feeds;
+		$this->template->content->errors = $errors;
+
+		// Javascript Header
+		$this->themes->colorpicker_enabled = TRUE;
+		$this->themes->js = new View('admin/manage/feeds/feeds_js');
+	}
+
+	/**
+	 * View/Edit News Feed Items
+	 */
+	public function feeds_items()
+	{
+		$this->template->content = new View('admin/manage/feeds/items');
+		
+		// Check if the last segment of the URI is numeric and grab it
+		$feed_id = is_numeric($this->uri->last_segment())
+					? $this->uri->last_segment()
+					: "";
+		
+		// SQL filter
+		$filter = (isset($feed_id)  AND !empty($feed_id))
+					? " feed_id = '" . $feed_id . "' "
+					: " 1=1";
+
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		
+		// Check for form submission
+		if ( $_POST )
+		{
+			$post = Validation::factory($_POST);
+
+			 //	 Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			if( $post->validate() )
+			{
+				$item_id = $this->input->post('item_id');
+				if (!is_array($item_id)) $item_id = array($item_id);
+
+				ORM::factory('feed_item')->in('id', $item_id)->delete_all($item_id);
+
+				$form_saved = TRUE;
+				$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+			}
+		}
+
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string' => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items' => ORM::factory('feed_item')
+							->where($filter)
+							->count_all()
+		));
+
+		$feed_items = ORM::factory('feed_item')
+						->where($filter)
+						->orderby('item_date','desc')
+						->find_all($this->items_per_page, $pagination->sql_offset);
+
+		$this->template->content->feed_items = $feed_items;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+
+		// Total Reports
+		$this->template->content->total_items = $pagination->total_items;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/manage/feeds/items_js');
+	}
+
+	/**
+	 * Add Edit Layers (KML, KMZ, GeoRSS)
+	 */
+	public function layers()
+	{
+		$this->template->content = new View('admin/manage/layers/main');
+		$this->template->content->title = Kohana::lang('ui_admin.layers');
+
+		// Setup and initialize form field names
+		$form = array(
+			'action' => '',
+			'layer_id' => '',
+			'layer_name' => '',
+			'layer_url'	=> '',
+			'layer_file' => '',
+			'layer_color' => ''
+		);
+
+		// Copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		$parents_array = array();
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Fetch the submitted data
+			$post_data = array_merge($_POST, $_FILES);
+			
+			// Layer instance for the actions
+			$layer = (isset($post_data['layer_id']) AND Layer_Model::is_valid_layer($post_data['layer_id']))
+						? new Layer_Model($post_data['layer_id'])
+						: new Layer_Model();
+						
+			// Check for action
+			if ($post_data['action'] == 'a')
+			{
+				// Manually extract the primary layer data
+				$layer_data = arr::extract($post_data, 'layer_name', 'layer_color', 'layer_url', 'layer_file_old');
+				
+				// Grab the layer file to be uploaded
+				$layer_data['layer_file'] = isset($post_data['layer_file']['name'])? $post_data['layer_file']['name'] : NULL;
+				
+				// Extract the layer file for upload validation
+				$other_data = arr::extract($post_data, 'layer_file');
+				
+				// Set up validation for the layer file
+				$post = Validation::factory($other_data)
+						->pre_filter('trim', TRUE)
+						->add_rules('layer_file', 'upload::valid','upload::type[kml,kmz]');
+				
+				// Test to see if validation has passed
+				if ($layer->validate($layer_data) AND $post->validate(FALSE))
+				{
+					// Success! SAVE
+					$layer->save();
+					
+					$path_info = upload::save("layer_file");
+					if ($path_info)
+					{
+						$path_parts = pathinfo($path_info);
+						$file_name = $path_parts['filename'];
+						$file_ext = $path_parts['extension'];
+						$layer_file = $file_name.".".$file_ext;
+						$layer_url = '';
+
+						if (strtolower($file_ext) == "kmz")
+						{ 
+							// This is a KMZ Zip Archive, so extract
+							$archive = new Pclzip($path_info);
+							if (TRUE == ($archive_files = $archive->extract(PCLZIP_OPT_EXTRACT_AS_STRING)))
+							{
+								foreach ($archive_files as $file)
+								{
+									$ext_file_name = $file['filename'];
+									$archive_file_parts = pathinfo($ext_file_name);
+									//because there can be more than one file in a KMZ
+									if
+									(
+										$archive_file_parts['extension'] == 'kml' AND
+										$ext_file_name AND
+										$archive->extract(PCLZIP_OPT_PATH, Kohana::config('upload.directory')) == TRUE
+									)
+									{ 
+										// Okay, so we have an extracted KML - Rename it and delete KMZ file
+										rename($path_parts['dirname']."/".$ext_file_name, 
+											$path_parts['dirname']."/".$file_name.".kml");
+			
+										$file_ext = "kml";
+										unlink($path_info);
+										$layer_file = $file_name.".".$file_ext;
+									}
+									
+								}
+							}
+
+							
+						}
+
+						
+						// Upload the KML to the CDN server if configured
+						if (Kohana::config("cdn.cdn_store_dynamic_content"))
+						{
+							// Upload the file to the CDN
+							$layer_url = cdn::upload($layer_file);
+
+							// We no longer need the files we created on the server. Remove them.
+							$local_directory = rtrim(Kohana::config('upload.directory', TRUE), '/').'/';
+							unlink($local_directory.$layer_file);
+
+							// We no longer need to store the file name for the local file since it's gone
+							$layer_file = '';
+						}
+
+						// Set the final variables for the DB
+						$layer->layer_url = $layer_url;
+						$layer->layer_file = $layer_file;
+						$layer->save();
+					}
+					
+					$form_saved = TRUE;
+					array_fill_keys($form, '');
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.added_edited'));
+				}
+				else
+				{
+					// Validation failed
+
+					// Repopulate the form fields
+					$form = arr::overwrite($form, array_merge($layer_data->as_array(), $post->as_array()));
+
+					// Ropulate the error fields, if any
+					$errors = arr::overwrite($errors, array_merge($layer_data->errors('layer'), $post->errors('layer')));
+					$form_error = TRUE;
+				}
+				
+			}
+			elseif ($post_data['action'] == 'd')
+			{
+				// Delete action
+				if ($layer->loaded)
+				{
+					// Delete KMZ file if any
+					$layer_file = $layer->layer_file;
+					if ( ! empty($layer_file) AND file_exists(Kohana::config('upload.directory', TRUE).$layer_file))
+					{
+						unlink(Kohana::config('upload.directory', TRUE) . $layer_file);
+					}
+
+					$layer->delete();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+			}
+			elseif ($post_data['action'] == 'v')
+			{
+				// Show/Hide Action
+				if ($layer->loaded == TRUE)
+				{
+					$layer->layer_visible =  ($layer->layer_visible == 1)? 0 : 1;
+					$layer->save();
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+			}
+			elseif ($post_data['action'] == 'i')
+			{
+				// Delete KML/KMZ action
+				if ($layer->loaded == TRUE)
+				{
+					$layer_file = $layer->layer_file;
+					if ( ! empty($layer_file) AND file_exists(Kohana::config('upload.directory', TRUE).$layer_file))
+					{
+						unlink(Kohana::config('upload.directory', TRUE) . $layer_file);
+					}
+
+					$layer->layer_file = null;
+					$layer->save();
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+			}
+		}
+
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string' => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items' => ORM::factory('layer')->count_all()
+		));
+
+		$layers = ORM::factory('layer')
+					->orderby('layer_name', 'asc')
+					->find_all($this->items_per_page, $pagination->sql_offset);
+
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->layers = $layers;
+
+		// Javascript Header
+		$this->themes->colorpicker_enabled = TRUE;
+		$this->themes->js = new View('admin/manage/layers/layers_js');
+	}
+
+	/**
+	 * get the feed type of the feed item.
+	 */
+	private function _get_feed_type( $feed_item )
+	{
+		@$enclosures = $feed_item->get_enclosures();
+		if($enclosures and ($enclosures[0]->medium == 'video' || strstr($enclosures[0]->type,'video')))
+		{
+			return FEED_TYPE_VIDEO;
+		}
+		if($enclosures and strstr($enclosures[0]->type,'image') || $enclosures[0]->medium == 'image')
+		{
+			return FEED_TYPE_PHOTO;
+		}
+
+		$categories = $feed_item->get_categories();
+		if(!$categories || empty($categories))
+		{
+			return FEED_TYPE_TEXT;
+		}
+		// go through categories for the label having Report Type
+		foreach($categories as $key => $category)
+		{
+			if ( ! empty($category->label))
+			{
+				$matched = strstr($category->label,'Report type');
+				if ( ! empty($matched))
+				{
+					$split_array = split(':', $category->label);
+					return trim($split_array[1]);
+				}
+			}
+		}
+		return FEED_TYPE_TEXT;
+	}
+
+	/**
+	 * parse feed and send feed items to database
+	 */
+	private function _parse_feed()
+	{
+		// Max number of feeds to keep
+		$max_feeds = 100;
+
+		// Today's Date
+		$today = strtotime('now');
+
+		// Get All Feeds From DB
+		$feeds = ORM::factory('feed')->find_all();
+		foreach ($feeds as $feed)
+		{
+			$last_update = $feed->feed_update;
+
+			// Has it been more than 24 hours since the last update?
+			// Since its a manual refresh, we don't need to set a time
+			if ( ((int)$today - (int)$last_update) > 0	)	// 86400 = 24 hours
+			{
+				// Parse Feed URL using Feed Helper
+				$feed_data = feed::simplepie( $feed->feed_url );
+
+				foreach ($feed_data->get_items(0,50) as $feed_data_item)
+				{
+					$title = $feed_data_item->get_title();
+					$link = $feed_data_item->get_link();
+					$description = $feed_data_item->get_description();
+					$date = $feed_data_item->get_date();
+					$latitude = $feed_data_item->get_latitude();
+					$longitude = $feed_data_item->get_longitude();
+
+					// Make Sure Title is Set (Atleast)
+					if (isset($title) AND !empty($title ))
+					{
+						// We need to check for duplicates!!!
+						// Maybe combination of Title + Date? (Kinda Heavy on the Server :-( )
+						$dupe_count = ORM::factory('feed_item')->where('item_title',$title)->where('item_date',date("Y-m-d H:i:s",strtotime($date)))->count_all();
+
+						if ($dupe_count == 0)
+						{
+							// Does this feed have a location??
+							$location_id = 0;
+							// STEP 1: SAVE LOCATION
+							if ($latitude AND $longitude)
+							{
+								$location = new Location_Model();
+								$location->location_name = Kohana::lang('ui_admin.unknown');
+								$location->latitude = $latitude;
+								$location->longitude = $longitude;
+								$location->location_date = date("Y-m-d H:i:s",time());
+								$location->save();
+								$location_id = $location->id;
+							}
+
+							$newitem = new Feed_Item_Model();
+							$newitem->feed_id = $feed->id;
+							$newitem->location_id = $location_id;
+							$newitem->item_title = $title;
+
+							if (isset($description) AND !empty($description))
+							{
+								$newitem->item_description = $description;
+							}
+							if (isset($link) AND !empty($link))
+							{
+								$newitem->item_link = $link;
+							}
+							if (isset($date) AND !empty($date))
+							{
+								$newitem->item_date = date("Y-m-d H:i:s",strtotime($date));
+							}
+							// Set todays date
+							else
+							{
+								$newitem->item_date = date("Y-m-d H:i:s",time());
+							}
+
+							if (isset($feed_type) AND ! empty($feed_type))
+							{
+								$newitem->feed_type = $feed_type;
+							}
+
+							$newitem->save();
+
+							// Action::feed_item_add - Feed Item Received!
+							Event::run('ushahidi_action.feed_item_add', $newitem);
+						}
+					}
+				}
+
+				// Get Feed Item Count
+				$feed_count = ORM::factory('feed_item')->where('feed_id', $feed->id)->count_all();
+				if ($feed_count > $max_feeds)
+				{
+					// Excess Feeds
+					$feed_excess = $feed_count - $max_feeds;
+
+					// Delete Excess Feeds
+					foreach (ORM::factory('feed_item')
+										->where('feed_id', $feed->id)
+										->orderby('id', 'ASC')
+										->limit($feed_excess)
+										->find_all() as $del_feed)
+					{
+						$del_feed->delete($del_feed->id);
+					}
+				}
+
+				// Set feed update date
+				$feed->feed_update = strtotime('now');
+				$feed->save();
+			}
+		}
+	}
+}
diff --git a/application/controllers/admin/manage/actions.php b/application/controllers/admin/manage/actions.php
new file mode 100644
index 0000000..7a75558
--- /dev/null
+++ b/application/controllers/admin/manage/actions.php
@@ -0,0 +1,381 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Actions Triggers Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Actions_Controller extends Admin_Controller
+{
+
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'actions';
+
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("manage"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+
+
+	function index()
+	{
+		$this->template->content = new View('admin/manage/actions/main');
+		$this->template->content->title = Kohana::lang('ui_admin.actions');
+
+		$this->themes->map_enabled = TRUE;
+		$this->themes->treeview_enabled = TRUE;
+
+		$this->themes->js = new View('admin/manage/actions/actions_js');
+		$this->themes->js->default_map = Kohana::config('settings.default_map');
+		$this->themes->js->default_zoom = Kohana::config('settings.default_zoom');
+		$this->themes->js->latitude = Kohana::config('settings.default_lat');
+		$this->themes->js->longitude = Kohana::config('settings.default_lon');
+
+		// TODO: Figure out what to do with this
+		$this->themes->js->incident_zoom = array();
+		$this->themes->js->geometries = array();
+
+		$trigger_options = $this->_trigger_options();
+		$response_options = $this->_response_options();
+		$trigger_advanced_options = $this->_trigger_advanced_options();
+		$advanced_option_areas = $this->_advanced_option_areas();
+		$response_advanced_options = $this->_response_advanced_options();
+		$response_advanced_option_areas = $this->_response_advanced_option_areas();
+		$trigger_allowed_responses = $this->_trigger_allowed_responses();
+
+		// Setup and initialize form field names
+		$form = array(
+			'geometry' => '',
+			'action_trigger' => '',
+			'action_user' => '',
+			'action_location_specific' => '',
+			'action_keywords' => '',
+			'action_category' => array(),
+			'action_on_specific_count' => '',
+			'action_on_specific_count_collective' => '',
+			'action_days_of_the_week' => array(),
+			'action_specific_days' => array(),
+			'action_between_times_hour_1' => '',
+			'action_between_times_hour_2' => '',
+			'action_between_times_minute_1' => '',
+			'action_between_times_minute_2' => '',
+			'action_response' => '',
+			'action_email_send_address' => '',
+			'action_email_send_address_specific' => '',
+			'action_email_subject' => '',
+			'action_email_body' => '',
+			'action_add_category' => array(),
+			'action_verify' => '',
+			'action_badge' => ''
+			);
+			
+		// Process form submission
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			// Trim all of the fields to get rid of errant spaces
+			$post->pre_filter('trim', TRUE);
+			
+			$expected_qualifier_fields = $trigger_advanced_options[$post['action_trigger']];
+			$expected_response_fields = $response_advanced_options[$post['action_response']];
+			$expected_fileds = array_merge($expected_qualifier_fields,$expected_response_fields);
+
+			// Since our form is dynamic, we need to set validation dynamically
+			foreach($expected_fileds as $field)
+			{
+				$this->_form_field_rules($field,$post);
+			}
+
+			if( $post->validate() )
+			{
+				$qualifiers = array();
+				foreach($expected_qualifier_fields as $field){
+					$form_field = 'action_'.$field;
+
+					// 1. Standard field population
+					if( isset($post->$form_field) )
+					{
+						$qualifiers[$field] = $post->$form_field;
+					}
+
+					// 2. Check additional field population
+
+					// Populate additional geometry field
+					if($field == 'location' && $post->$form_field == 'specific')
+					{
+						// Add geometry if this is a specific location
+						$qualifiers['geometry'] = $post->geometry;
+					}
+
+					// Populate additional specific count collective boolean
+					if($field == 'on_specific_count')
+					{
+						// Grab if we are counting everyone or just the individual users themselves
+						$qualifiers['on_specific_count_collective'] = $post->action_on_specific_count_collective;
+					}
+
+					// Change the specific_days field to an array of timestamps
+					if($field == 'specific_days')
+					{
+						// Grab if we are counting everyone or just the individual users themselves
+						$qualifiers['specific_days'] = explode(',',$qualifiers['specific_days']);
+						foreach($qualifiers['specific_days'] as $key => $specific_day){
+							$qualifiers['specific_days'][$key] = strtotime($specific_day);
+						}
+						if($qualifiers['specific_days'][0] == false) {
+							// Just get rid of it if we aren't using it
+							unset($qualifiers['specific_days']);
+						}
+					}
+
+					// Grab dropdowns for between_times
+					if($field == 'between_times')
+					{
+						// Do everything for between times here
+
+						if($post->action_between_times_hour_1 != 0 OR $post->action_between_times_minute_1 != 0
+							OR $post->action_between_times_hour_2 != 0 OR $post->action_between_times_minute_2 != 0)
+						{
+							// We aren't all zeroed out so the user is not ignoring between_times. Now we need
+							//   to calculate seconds into the day for each and put the lower count in the first
+							//   variable and the higher in the second so the check in the hook doesn't have to
+							//   do so much work. Also, set between_times to true so the hook knows to check it.
+
+							$qualifiers['between_times'] = 1;
+
+							$time1 = ((int)$post->action_between_times_hour_1 * 3600) + ((int)$post->action_between_times_minute_1 * 60);
+							$time2 = ((int)$post->action_between_times_hour_2 * 3600) + ((int)$post->action_between_times_minute_2 * 60);
+
+							if($time1 < $time2){
+								$qualifiers['between_times_1'] = $time1;
+								$qualifiers['between_times_2'] = $time2;
+							}else{
+								$qualifiers['between_times_1'] = $time2;
+								$qualifiers['between_times_2'] = $time1;
+							}
+
+						}else{
+							// Between_times is being ignored, set it that way here
+							$qualifiers['between_times'] = 0;
+						}
+
+					}
+
+				}
+
+				$qualifiers = serialize($qualifiers);
+
+				$response_vars = array();
+				foreach($expected_response_fields as $field){
+					$form_field = 'action_'.$field;
+					if( isset($post->$form_field) )
+					{
+						$r_var = $post->$form_field;
+
+						if($field == 'email_send_address' AND $post->$form_field == '1'){
+							// Then set as the specific email address so we know where to send it
+							$r_var = $post->action_email_send_address_specific;
+						}
+
+						// This is the array we're building to pass on the data we need
+						//  to perform the response when qualifiers are all passed
+						$response_vars[$field] = $r_var;
+					}
+				}
+
+				$response_vars = serialize($response_vars);
+
+				$action = ORM::factory('actions', $post->id);
+				$action->action = $post->action_trigger;
+				$action->qualifiers = $qualifiers;
+				$action->response = $post->action_response;
+				$action->response_vars = $response_vars;
+				$action->active = 1;
+				$action->save();
+
+			}else{
+				// TODO: Proper Validation
+				$errors = $post->errors();
+				foreach ($errors as $key => $val)
+				{
+					echo $key.' failed rule '.$val.'<br />';
+				}
+			}
+
+		}
+
+	    // Copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		$sharing_id = "";
+
+		// Defined actions by the user that already exist in the system
+		$this->template->content->actions = $this->_get_actions();
+		$this->template->content->total_items = $this->template->content->actions->count();
+
+		$this->template->content->trigger_options = $trigger_options;
+		$this->template->content->response_options = $response_options;
+
+		$this->template->content->trigger_advanced_options = $trigger_advanced_options;
+		$this->template->content->advanced_option_areas = $advanced_option_areas;
+		$this->template->content->response_advanced_options = $response_advanced_options;
+		$this->template->content->response_advanced_option_areas = $response_advanced_option_areas;
+		$this->template->content->trigger_allowed_responses = $trigger_allowed_responses;
+
+		// Build user options list
+		$this->template->content->user_options = $this->_user_options();
+
+		// Grab badges for dropdown
+		$this->template->content->badges = Badge_Model::badge_names();
+
+		// Grab feeds for dropdown
+		$this->template->content->feeds = ORM::factory('feed')->find_all()->select_list('id','feed_name');
+
+		// Timezone
+		$this->template->content->site_timezone = Kohana::config('settings.site_timezone');
+
+		// Days of the week
+		$this->template->content->days = array('mon' => Kohana::lang('datetime.monday.full'),
+												'tue' => Kohana::lang('datetime.tuesday.full'),
+												'wed' => Kohana::lang('datetime.wednesday.full'),
+												'thu' => Kohana::lang('datetime.thursday.full'),
+												'fri' => Kohana::lang('datetime.friday.full'),
+												'sat' => Kohana::lang('datetime.saturday.full'),
+												'sun' => Kohana::lang('datetime.sunday.full'));
+
+		$this->template->content->form = $form;
+		$this->template->content->form_error = $form_error;
+        $this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->errors = $errors;
+
+		// Enable date picker
+		$this->themes->datepicker_enabled = TRUE;
+	}
+
+	function changestate(){
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			// Trim all of the fields to get rid of errant spaces
+			$post->pre_filter('trim', TRUE);
+			$post->add_rules('action_id','required', 'digit');
+			$post->add_rules('action_switch_to','required');
+
+			if( $post->validate())
+			{
+				if ($post->action_switch_to == 'de')
+				{
+					ORM::factory('actions',$post->action_id)->delete();
+				}
+				else
+				{
+					$active = (int)($post->action_switch_to);
+					
+					$action = ORM::factory('actions',$post->action_id);
+					$action->active = $active;
+					$action->save();
+				}
+			}
+		}
+
+		// This controller doesn't display anything so send the user back
+
+		url::redirect(url::site().'admin/manage/actions');
+
+	}
+
+	public function _form_field_rules($field,&$post){
+		switch ($field) {
+			case 'user':
+				$post->add_rules('action_user','required', 'digit');
+				break;
+			case 'location':
+				$post->add_rules('action_location','required');
+
+				if($post->action_location == 'specific')
+				{
+					$post->add_rules('geometry','required');
+				}
+				break;
+			case 'email_send_address':
+				$post->add_rules('action_email_send_address','required','digit');
+				break;
+			case 'on_specific_count':
+				$post->add_rules('action_on_specific_count', 'digit');
+				break;
+			default:
+				return false;
+		}
+	}
+
+	public function _advanced_option_areas()
+	{
+		return Kohana::config('actions.advanced_option_areas');
+	}
+
+	public function _trigger_advanced_options()
+	{
+		return Kohana::config('actions.trigger_advanced_options');
+	}
+
+	public function _response_advanced_option_areas()
+	{
+		return Kohana::config('actions.response_advanced_option_areas');
+	}
+
+	public function _response_advanced_options()
+	{
+		return Kohana::config('actions.response_advanced_options');
+	}
+
+	public function _trigger_options()
+	{
+		$trigger_options = array('0'=>Kohana::lang('ui_admin.please_select'));
+		return array_merge($trigger_options,Kohana::config('actions.trigger_options'));
+	}
+
+	public function _trigger_allowed_responses(){
+		return Kohana::config('actions.trigger_allowed_responses');
+	}
+
+	public function _response_options()
+	{
+		return Kohana::config('actions.response_options');
+	}
+
+	public function _user_options()
+	{
+		$users = ORM::factory('user')
+					->orderby('name', 'asc')
+					->find_all();
+		$user_options = array('0'=>'Anyone');
+		foreach($users as $user)
+		{
+			$user_options[$user->id] = $user->name;
+		}
+		return $user_options;
+	}
+
+	public function _get_actions()
+	{
+		return $this->db->from('actions')->get();
+	}
+
+}
diff --git a/application/controllers/admin/manage/alerts.php b/application/controllers/admin/manage/alerts.php
new file mode 100644
index 0000000..ab8dea4
--- /dev/null
+++ b/application/controllers/admin/manage/alerts.php
@@ -0,0 +1,166 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used to view/remove Alerts
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Alerts_Controller extends Admin_Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'manage';
+		
+		// If user doesn't have access, redirect to dashboard
+		if ( ! admin::permissions($this->user, "manage"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+
+	/**
+	* Lists all alerts in the system
+	* @return void
+    */
+	public function index()
+	{
+		$this->template->content = new View('admin/manage/alerts/main');
+		$this->template->content->title = Kohana::lang('ui_admin.alerts');
+
+		// Is this an SMS or Email Filter?
+		if (!empty($_GET['type']))
+		{
+			$type = $_GET['type'];
+
+			if ($type == '1')
+			{ // SMS
+				$filter = 'alert_type=1';
+			}
+			elseif ($type == '2')
+			{ // EMAIL
+				$filter = 'alert_type=2';
+			}
+			else
+			{ // ALL
+				$filter = '1=1';
+			}
+		}
+		else
+		{
+			$type = "0";
+			$filter = '1=1';
+		}
+
+		// Are we using an Alert Keyword?
+		if (isset($_GET['ak']) AND !empty($_GET['ak']))
+		{
+			$table_prefix = Kohana::config('database.default.table_prefix');
+
+			//	Brute force input sanitization
+			// Phase 1 - Strip the search string of all non-word characters
+			$keyword = $_GET['ak'];
+			$keyword_raw = preg_replace('#/\w+/#', '', $keyword);
+
+			// Strip any HTML tags that may have been missed in Phase 1
+			$keyword_raw = strip_tags($keyword_raw);
+
+			// Phase 3 - Invoke Kohana's XSS cleaning mechanism just incase an outlier wasn't caught
+			// in the first 2 steps
+			$keyword_raw = $this->input->xss_clean($keyword_raw);
+			$keyword_raw = $this->db->escape_str($keyword_raw);
+
+			$filter .= " AND ".$table_prefix."alert_recipient LIKE '%".$keyword_raw."%'";
+		}
+		else
+		{
+			$keyword = '';
+		}
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'action' => ''
+		);
+		//	copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+
+		if ( $_POST )
+		{
+			$post = Validation::factory($_POST);
+
+			 //	 Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			if ($post->action =='d')
+			{
+				$post->add_rules('alert_id.*','required','numeric');
+			}
+			
+			if ($post->validate())
+			{
+				// Delete Alert
+				if ($post->action =='d')
+				{
+
+					foreach ($post->alert_id as $item)
+					{
+						$update = new Alert_Model($item);
+						if ($update->loaded)
+						{
+							$update->delete();
+						}
+					}
+
+					$form_saved = TRUE;
+					$form_action = strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+			}
+			else
+			{
+				$errors = arr::overwrite($errors, $post->errors('alerts'));
+				$form_error = TRUE;
+			}
+		}
+		
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string'   => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items'    => ORM::factory('alert')
+				->where($filter)
+			    ->count_all()
+			));
+
+		$alerts = ORM::factory('alert')
+			->where($filter)
+		    ->find_all($this->items_per_page, $pagination->sql_offset);
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->alerts = $alerts;
+		$this->template->content->type = $type;
+		$this->template->content->keyword = $keyword;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/manage/alerts/alerts_js');
+	}
+}
\ No newline at end of file
diff --git a/application/controllers/admin/manage/badges.php b/application/controllers/admin/manage/badges.php
new file mode 100644
index 0000000..79b6ebc
--- /dev/null
+++ b/application/controllers/admin/manage/badges.php
@@ -0,0 +1,231 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Badges Controller
+ * Add and assign badges
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Badges_Controller extends Admin_Controller
+{
+
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'settings';
+
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("manage"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+
+	function index()
+	{
+		$this->template->content = new View('admin/manage/badges/main');
+		$this->template->content->title = Kohana::lang('ui_main.badges');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'id' => '',
+			'name' => '',
+			'description' => ''
+		);
+		//	copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+
+		if ( $_POST )
+		{
+
+			$post = Validation::factory($_POST);
+			
+			 //	 Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action', 'required', 'alpha', 'length[1,1]');
+			$post->add_rules('name', 'standard_text', 'length[1,250]');
+			$post->add_rules('description', 'standard_text');
+			
+			// Add some additional rules for adding a new badge
+			if (isset($_POST['action']) AND $_POST['action'] == 'a')
+			{
+				// Users can select a badge or upload one. See what they are doing.
+				if ($_FILES['image']['error'] == 0)
+				{
+					// Uploading an image, if uploaded, it overrules a selection
+					$uploading_custom_badge = true;
+					$post->add_rules('image', 'upload::valid', 'upload::type[gif,jpg,png]', 'upload::size[100K]');
+				}
+				else
+				{
+					// Selecting one
+					$uploading_custom_badge = false;
+					$post->add_rules('selected_badge', 'required');
+				}
+			}
+
+			if ($post->validate())
+			{
+				// ADD
+				if ($post->action == 'a')
+				{
+					// Step 1. Save badge name and description
+
+					$badge = new Badge_Model();
+					$badge->name = $post->name;
+					$badge->description = $post->description;
+					$badge->save();
+
+					// Step 2. Save badge image
+					
+					if ($uploading_custom_badge)
+					{
+						$filename = upload::save('image');
+					}
+					else
+					{
+						// We already have this on the filesystem! Use that one.
+						$bp_path = MEDIAPATH.'img/badge_packs/';
+						$selected_badge = base64_decode(str_ireplace('badge_','',$post->selected_badge));
+						$filename = $bp_path.$selected_badge;
+					}
+					
+					if ($filename)
+					{
+						$new_filename = "badge_".$badge->id."_".time();
+						$file_type = strrev(substr(strrev($filename),0,4));
+
+						// Large size
+						$l_name = $new_filename.$file_type;
+						Image::factory($filename)->save(Kohana::config('upload.directory', TRUE).$l_name);
+
+						// Medium size
+						$m_name = $new_filename.'_m'.$file_type;
+						Image::factory($filename)->resize(80,80,Image::HEIGHT)
+							->save(Kohana::config('upload.directory', TRUE).$m_name);
+
+						// Thumbnail
+						$t_name = $new_filename.'_t'.$file_type;
+						Image::factory($filename)->resize(60,60,Image::HEIGHT)
+							->save(Kohana::config('upload.directory', TRUE).$t_name);
+						
+						// Name the files for the DB
+						$media_link = $l_name;
+						$media_medium = $m_name;
+						$media_thumb = $t_name;
+						
+						// Okay, now we have these three different files on the server, now check to see
+						//   if we should be dropping them on the CDN
+						
+						if(Kohana::config("cdn.cdn_store_dynamic_content"))
+						{
+							$cdn = new cdn;
+							$media_link = $cdn->upload($media_link);
+							$media_medium = $cdn->upload($media_medium);
+							$media_thumb = $cdn->upload($media_thumb);
+							
+							// We no longer need the files we created on the server. Remove them.
+							$local_directory = rtrim(Kohana::config('upload.directory', TRUE), '/').'/';
+							unlink($local_directory.$new_filename.$file_type);
+							unlink($local_directory.$new_filename.'_m'.$file_type);
+							unlink($local_directory.$new_filename.'_t'.$file_type);
+						}
+						
+						// Only perform this operation if it's not coming from a badge pack,
+						//   otherwise we would lose badges every time we selected them!
+						if ($uploading_custom_badge)
+						{
+							// Remove the temporary file
+							unlink($filename);
+						}
+
+						// Delete old badge image
+						ORM::factory('media')->where(array('badge_id' => $badge->id))->delete_all();
+
+						// Save new badge image
+						$media = new Media_Model();
+						$media->badge_id = $badge->id;
+						$media->media_type = 1; // Image
+						$media->media_link = $media_link;
+						$media->media_medium = $media_medium;
+						$media->media_thumb = $media_thumb;
+						$media->media_date = date("Y-m-d H:i:s",time());
+						$media->save();
+					}
+				}
+
+				// ASSIGN USER
+				if ($post->action == 'b')
+				{
+					$badge_user = new Badge_User_Model();
+					$badge_user->badge_id = $post->badge_id;
+					$badge_user->user_id = $post->assign_user;
+					$badge_user->save();
+				}
+
+				// REVOKE USER
+				if ($post->action == 'r')
+				{
+					ORM::factory('badge_user')->where(array('badge_id' => (int)$post->badge_id, 'user_id' => (int)$post->revoke_user))->delete_all();
+				}
+
+				// DELETE
+				elseif ($post->action =='d')
+				{
+					// Remove from badge table
+					ORM::factory('badge')->delete((int)$post->badge_id);
+
+					// Remove from media
+					ORM::factory('media')->where(array('badge_id' => (int)$post->badge_id))->delete_all();
+
+					// Remove from assignment
+					ORM::factory('badge_user')->where(array('badge_id' => (int)$post->badge_id))->delete_all();
+				}
+			}
+			else
+			{
+				$errors = arr::overwrite($errors, $post->errors('badges'));
+				$form_error = TRUE;
+			}
+		}
+		
+		// Badge Pack stuff
+		$this->template->content->badge_packs = badges::get_packs();
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+
+		// Get badges
+		$this->template->content->badges = Badge_Model::badges();
+		$this->template->content->total_items = count($this->template->content->badges);
+
+		// Get all users for dropdowns
+		$users_result = ORM::factory('user')->orderby('name', 'asc')->find_all();
+		$users = array();
+		foreach($users_result as $user)
+		{
+			$users[$user->id] = $user->username;
+		}
+		$this->template->content->users = $users;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/manage/badges/badges_js');
+	}
+}
diff --git a/application/controllers/admin/manage/blocks.php b/application/controllers/admin/manage/blocks.php
new file mode 100644
index 0000000..5724b98
--- /dev/null
+++ b/application/controllers/admin/manage/blocks.php
@@ -0,0 +1,151 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Blocks Controller
+ * Add/Edit Ushahidi Instance Shares
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Blocks_Controller extends Admin_Controller
+{
+	private $_registered_blocks;
+
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'settings';
+		
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("manage"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+		
+		$this->_registered_blocks = Kohana::config("settings.blocks");
+	}
+	
+	function index()
+	{
+		$this->template->content = new View('admin/manage/blocks/main');
+		$this->template->content->title = Kohana::lang('ui_admin.blocks');
+		
+		// Get Registered Blocks 
+		if ( ! is_array($this->_registered_blocks) )
+		{
+			$this->_registered_blocks = array();
+		}
+		
+		// Get Active Blocks
+		$active_blocks = Settings_Model::get_setting('blocks');
+		$active_blocks = array_filter(explode("|", $active_blocks));
+		
+		// setup and initialize form field names
+		$form = array
+		(
+			'action' => '',
+			'block' => ''
+		);
+		//	copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+
+		if ( $_POST )
+		{
+			$post = Validation::factory($_POST);
+
+			 //	 Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			$post->add_rules('block','required', 'alpha_dash');
+			if ( ! array_key_exists($post->block, $this->_registered_blocks))
+			{
+				$post->add_error('block','exists');
+			}
+			
+			if ($post->validate())
+			{
+				// Activate a block
+				if ($post->action == 'a')
+				{
+					array_push($active_blocks, $post->block);
+					Settings_Model::save_setting('blocks', implode("|", $active_blocks));
+				}
+				
+				// Deactivate a block
+				elseif ($post->action =='d')
+				{
+					$active_blocks = array_diff($active_blocks, array($post->block));
+					Settings_Model::save_setting('blocks', implode("|", $active_blocks));
+				}
+			}
+			else
+			{
+				$errors = arr::overwrite($errors, $post->errors('blocks'));
+				$form_error = TRUE;
+			}
+		}
+		
+		// Sort the Blocks
+		$sorted_blocks = blocks::sort($active_blocks, array_keys($this->_registered_blocks));
+		
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->total_items = count($this->_registered_blocks);
+		$this->template->content->registered_blocks = $this->_registered_blocks;
+		$this->template->content->active_blocks = $active_blocks;
+		$this->template->content->sorted_blocks = $sorted_blocks;
+		
+		// Javascript Header
+		$this->themes->tablerowsort_enabled = TRUE;
+		$this->themes->js = new View('admin/manage/blocks/blocks_js');
+	}
+	
+	public function sort()
+	{
+		$this->auto_render = FALSE;
+		$this->template = "";
+		
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+			$post->add_rules('blocks','required');
+			
+			if ($post->validate())
+			{
+				$active_blocks = Settings_Model::get_setting('blocks');
+				$active_blocks = array_filter(explode("|", $active_blocks));
+				
+				$blocks = array_map('trim', explode(',', $_POST['blocks']));
+				$block_check = array();
+				foreach ($blocks as $block)
+				{
+					if ( in_array($block, $active_blocks) )
+					{
+						$block_check[] = $block;
+					}
+				}
+				
+				Settings_Model::save_setting('blocks', implode("|", $block_check));
+				echo 'success';
+				return;
+			}
+		}
+		echo 'failure';
+		return;
+	}
+}
diff --git a/application/controllers/admin/manage/forms.php b/application/controllers/admin/manage/forms.php
new file mode 100755
index 0000000..7650a27
--- /dev/null
+++ b/application/controllers/admin/manage/forms.php
@@ -0,0 +1,1013 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used to add/ remove Custom Forms
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Forms_Controller extends Admin_Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'manage';
+		
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("manage"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+	
+	
+	/**
+	* Lists the forms.
+    */
+	public function index()
+	{
+		$this->template->content = new View('admin/manage/forms/main');
+		
+		// Setup and initialize form field names
+		$form = array
+	    (
+			'action' => '',
+	        'form_id' => '',
+			'form_title' => '',
+	        'form_description' => '',
+	        'form_active' => '',
+			'field_type' => ''
+	    );
+	
+		// Copy the form as errors, so the errors will be stored with keys 
+		// corresponding to the form field names
+	    $errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		$form_id = "";
+		
+		if( $_POST ) 
+		{
+			$post = Validation::factory( $_POST );
+			
+			 //  Add some filters
+	        $post->pre_filter('trim', TRUE);
+	
+			if ($post->action == 'a')		// Add Action
+			{
+				// Add some rules, the input field, followed by a list of checks, carried out in order
+				$post->add_rules('form_title','required', 'length[1,1000]');
+				$post->add_rules('form_description','required');
+				
+				// Ensure that you don't have forms with duplicate names
+				$same_form = ORM::factory('form')
+						->where('form_title', $_POST['form_title'])
+						->count_all();
+				
+				if ($same_form > 0)
+				{
+					$post->add_error('form_title', 'exists');
+				}
+			}
+			elseif ($post->action == 'd')
+			{
+				if ($_POST['form_id'] == 1)
+				{ 
+					// Default Form Cannot Be Deleted
+					$post->add_error('form_id','default');
+				}
+			}
+			
+			if( $post->validate() )
+			{
+				$form_id = $post->form_id;
+				
+				$custom_form = new Form_Model($form_id);
+				if ($post->action == 'd')
+				{ 
+					// Delete Action
+					$custom_form->delete( $form_id );
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+				elseif ($post->action == 'h')
+				{ 
+					// Active/Inactive Action
+					if ($custom_form->loaded)
+					{
+						// @todo Doesn't make sense, find out what the logic for this is
+						// Customary values for active and inactive are 1 and 0 respectively
+						$custom_form->form_active = ($custom_form->form_active == 1)? 0: 1;
+						$custom_form->save();
+						$form_saved = TRUE;
+						$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+					}
+				}
+				else
+				{
+					// Save Action
+					$custom_form->form_title = $post->form_title;
+					$custom_form->form_description = $post->form_description;
+					$custom_form->save();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.created_edited'));
+				}
+				
+				// Empty $form array
+				array_fill_keys($form, '');
+				
+			}
+			else
+			{
+				// Repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// Populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('form'));
+				$form_error = TRUE;
+			}
+		}
+		
+        // Pagination
+        $pagination = new Pagination(array(
+                            'query_string' => 'page',
+                            'items_per_page' => $this->items_per_page,
+                            'total_items' => ORM::factory('form')->count_all()
+                        ));
+
+		$forms = ORM::factory('form')
+				->orderby('id', 'asc')
+				->find_all($this->items_per_page, $pagination->sql_offset);
+				
+		// Form Field Types
+		$form_field_types = array
+		(
+			'' => Kohana::lang('ui_admin.select_field_type'),
+			1 => Kohana::lang('ui_admin.text_field'),
+			2 => Kohana::lang('ui_admin.free_text_field'),
+			3 => Kohana::lang('ui_admin.date_field'),
+			5 => Kohana::lang('ui_admin.radio_field'),
+			6 => Kohana::lang('ui_admin.checkbox_field'),
+			7 => Kohana::lang('ui_admin.dropdown_field'),
+			8 => Kohana::lang('ui_admin.divider_start_field'),
+			9 => Kohana::lang('ui_admin.divider_end_field'),
+			// 4 => 'Add Attachments'
+		);
+		
+		$this->template->content->form = $form;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->forms = $forms;
+		$this->template->content->form_field_types = $form_field_types;
+		$this->template->content->errors = $errors;
+
+        // Javascript Header
+        $this->themes->js = new View('admin/manage/forms/forms_js');
+		$this->themes->js->form_id = $form_id;
+		$this->template->form_error = $form_error;
+	}
+
+	
+	/**
+	* Generates Form Field Entry Form (Add/Edit) via Ajax Request
+    */
+	public function selector()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		// Seelctor ID
+		$selector_id = (isset($_POST['selector_id']))? intval($_POST['selector_id']) : -1;
+		
+		// Form ID
+		$form_id = (isset($_POST['form_id']))? intval($_POST['form_id']) : 0;
+		
+		// Field ID
+		$field_id = (isset($_POST['field_id']))? intval($_POST['field_id']) : 0;
+		
+		$selector_content = "";
+		
+		if ($selector_id >= 0 AND $form_id > 0)
+		{
+			switch ($selector_id) {
+			    case 0:
+			        $selector_content = Kohana::lang('ui_main.select_item');
+			        break;
+			    case 1:
+			        $selector_content = $this->_get_selector_text($form_id, $field_id);
+			        break;
+			    case 2:
+			        $selector_content = $this->_get_selector_textarea($form_id, $field_id);
+			        break;
+				case 3:
+		        	$selector_content = $this->_get_selector_date($form_id, $field_id);
+		        	break;
+				case 4:
+		        	$selector_content = $this->_get_selector_multi($form_id, $field_id, $selector_id);
+		        	break;
+				case 5:
+		        	$selector_content = $this->_get_selector_multi($form_id, $field_id, $selector_id);
+		        	break;
+				case 6:
+		        	$selector_content = $this->_get_selector_multi($form_id, $field_id, $selector_id);
+		        	break;
+				case 7:
+		        	$selector_content = $this->_get_selector_multi($form_id, $field_id, $selector_id);
+		        	break;
+				case 8:
+		        	$selector_content = $this->_get_selector_div($form_id, $field_id, "start");
+		        	break;
+				case 9:
+		        	$selector_content = $this->_get_selector_div($form_id, $field_id, "end");
+		        	break;
+			}
+		}
+		echo json_encode(array("status"=>"success", "message"=>$selector_content));
+	}
+	
+	
+	/**
+	 * Create/Edit & Save New Form Field
+	 */
+	public function field_add()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		// setup and initialize form field names
+		$form = array
+	    (
+			'field_type' => '',
+			'field_name' => '',
+	        'field_default' => '',
+	        'field_required' => '',
+			'field_width' => '',
+			'field_height' => ''
+	    );
+		//  copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+	    $errors = $form;
+		
+		$field_add_status = "";
+		$field_add_response = "";
+		
+		if ($_POST) 
+		{
+			// @todo Manually extract the data to be validated
+			$form_field_data = arr::extract($_POST, 'form_id', 'field_id', 'field_type', 'field_name', 'field_default', 'field_required', 
+				'field_width', 'field_height', 'field_isdate', 'field_ispublic_visible', 'field_ispublic_submit');
+			
+			// Sanitize the default value (if provided)
+			$form_field_data['field_default'] = $this->input->xss_clean($form_field_data['field_default']);
+			
+			// Form_Field_Model instance
+			$form_field = Form_Field_Model::is_valid_form_field($_POST['field_id'])
+				? ORM::factory('form_field', $_POST['field_id'])
+				: new Form_Field_Model();
+
+			
+			// Validate the form field data
+			if ($form_field->validate($form_field_data))
+			{
+				// Validation succeeded, proceed...
+				
+				// Check for new form field entry
+				$new_field = $form_field->loaded;
+				
+				// Save the new/modified form field entry
+				$form_field->save();
+				
+				// Get the form field id
+				$field_id = $form_field->id;
+				
+				// Save optional values
+				if (isset($_POST['field_options']))
+				{
+					foreach ($_POST['field_options'] as $name => $value)
+					{
+						$option_exists = ORM::factory('form_field_option')->where('form_field_id',$field_id)->where('option_name',$name)->find();
+						
+						$option_entry = ($option_exists->loaded == TRUE)
+							? ORM::factory('form_field_option', $option_exists->id)
+							: new Form_Field_Option_Model();
+						
+						$option_entry->form_field_id = $field_id;
+						$option_entry->option_name = $name;
+						$option_entry->option_value = $value;
+						$option_entry->save();
+					}
+				}
+
+				// If a new field, calculate the field position
+				if (empty($new_field))
+				{
+					// Calculate the field position
+					$field_position = ORM::factory('form_field')
+										->where(array('form_id' => $form_field->form_id, 'id != ' => $field_id))
+										->count_all() + 1;
+					
+					$form_field->field_position = $field_position;
+					$form_field->save();
+				}
+
+				$field_add_status = "success";
+				$field_add_response = rawurlencode(customforms::get_current_fields($form_field->form_id, $this->user));
+				
+			}
+			else
+			{
+				// Repopulate the form fields
+	            $form = arr::overwrite($form, $form_field_data->as_array());
+
+               // Populate the error fields, if any
+                $errors = arr::overwrite($errors, $form_field_data->errors('form'));
+                
+				// populate the response to this post request
+				$field_add_status = "error";
+				$field_add_response  = "";
+				$field_add_response .= "<ul>";
+				
+				foreach ($errors as $error_item => $error_description)
+				{
+					$field_add_response .= (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+				}
+				$field_add_response .= "</ul>";
+			}
+		}
+		
+		echo json_encode(array("status"=>$field_add_status, "response"=>$field_add_response));
+	}
+	
+	
+	/**
+	* Delete Form Field
+    */
+	public function field_delete()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		// Get the form id
+		$form_id = (isset($_POST['form_id']))? intval($_POST['form_id']) : 0;
+		
+		// Get the field id
+		$field_id = (isset($_POST['field_id']))? intval($_POST['field_id']) : 0;
+		
+		// To hold the return content
+		$return_content = "";
+		
+		if ($field_id > 0 AND $form_id > 0)
+		{
+			$form_field = ORM::factory('form_field', $field_id);
+			if ($form_field->loaded)
+			{
+				$form_field->delete();
+			}
+			$return_content = customforms::get_current_fields($form_id,$this->user);
+		}
+		
+		echo json_encode(array("status"=>"success", "response"=>$return_content));
+	}
+	
+	
+	/**
+	* Move Form Field Up or Down
+	* Positioning in layout
+    */
+	public function field_move()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		// Get the form id
+		$form_id = (isset($_POST['form_id']) AND intval($_POST['form_id']) > 0)
+			? intval($_POST['form_id'])
+			: 0;
+		
+		// Get the field id
+		$field_id = (isset($_POST['field_id']) AND intval($_POST['field_id']) > 0)
+			? intval($_POST['field_id'])
+			: 0;
+		
+		// Field position
+		$field_position = (isset($_POST['field_position']))? $_POST['field_position'] : "";
+		
+		$return_content = "";
+		
+		if ($field_position == 'u' OR $field_position == 'd')
+		{
+			
+			// Load This Field
+			$field = ORM::factory('form_field', $field_id);
+			if ($field->loaded == TRUE)
+			{
+				// Get the total number of fields for the form
+				$total_fields = ORM::factory('form_field')->where('form_id', $field->form_id)->count_all();
+
+				// Get current position
+				$current_position = $field->field_position;
+				
+				if ($field_position == 'u' AND $current_position > 1)
+				{
+					// Move down the fields whose position value is greater
+					// than that of the selected field 
+					$sql = "UPDATE `".$this->table_prefix."form_field` SET field_position = ? WHERE field_position = ?";
+					$this->db->query($sql, $current_position, $current_position-1);
+
+					// Move the selected field upwards
+					$field->field_position = $current_position - 1;
+					$field->save();
+				}
+				elseif ($field_position == 'd' AND $current_position != $total_fields)
+				{ 
+					// Move all other form fields upwards
+					$sql = "UPDATE `".$this->table_prefix."form_field` SET field_position = ? WHERE field_position = ?";
+					$this->db->query($sql,  $current_position, $current_position + 1);
+					
+					// Move the selected field downwards - increase its field position in the database
+					$field->field_position = $current_position + 1;
+					$field->save();
+				}
+			}
+			
+		}
+		
+		$return_content = customforms::get_current_fields($form_id,$this->user);
+		echo json_encode(array("status"=>"success", "response"=>$return_content));
+	}
+
+	/**
+	* Generate Public Visible / Submit Dropdown Boxes
+    * @param int $field_ispublic_submit If this can be submitted by anyone
+    * @param int $field_ispublic_submit If answers this can be viewed by anyone
+	*/
+	private function _get_public_state($field_ispublic_submit, $field_ispublic_visible)
+	{
+		$visibility_selection = array('0' => Kohana::lang('ui_admin.anyone_role'));
+		
+		$roles = ORM::factory('role')->orderby('access_level','asc')->find_all();
+		foreach($roles as $role)
+		{
+			$visibility_selection[$role->id] = ucfirst($role->name);
+		}
+
+		$html ="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.ispublic_submit').":</strong><br />";
+		if (isset($field_ispublic_submit))
+			$html .=  form::dropdown('field_ispublic_submit',$visibility_selection,$field_ispublic_submit);
+		else
+			$html .=  form::dropdown('field_ispublic_submit',$visibility_selection,'0');
+
+		$html .="</div>";
+		
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.ispublic_visible').":</strong><br />";
+		if (isset($field_ispublic_visible))
+			$html .=  form::dropdown('field_ispublic_visible',$visibility_selection,$field_ispublic_visible);
+		else
+			$html .=  form::dropdown('field_ispublic_visible',$visibility_selection,'0');
+		$html .="</div>";
+
+		return $html;
+	}
+
+	/**
+	* Generate Div Fields
+    * @param int $form_id The id no. of the form
+    * @param int $field_id The id no. of the field
+	* @param string $type "start" for start of a div "end" for the end
+    */
+	private function _get_selector_div($form_id = 0, $field_id = 0, $type = "")
+	{
+		// Grab the last field_id to be incremented and amended to blank_div
+		$form_field_id = ORM::factory('form_field')
+					->orderby('id','desc')
+					->limit(1)
+					->find();
+		$increment = $form_field_id->id + 1;
+		
+		if (intval($field_id) > 0)
+		{
+			$field = ORM::factory('form_field', $field_id);
+			if ($field->loaded == true)
+			{
+				$field_name = $field->field_name;
+				$field_default = $field->field_default;
+				$field_required = $field->field_required;
+				$field_width = $field->field_width;
+				$field_height = $field->field_height;
+				$field_maxlength = $field->field_maxlength;
+				$field_isdate = $field->field_isdate;
+				$field_ispublic_visible = $field->field_ispublic_visible;
+				$field_ispublic_submit = $field->field_ispublic_submit;
+			}
+		}
+		else
+		{
+			$field_id = "";
+			$field_name = "";
+			$field_default = "";
+			$field_required = "0";
+			$field_width = "";
+			$field_height = "";
+			$field_maxlength = "";
+			$field_isdate = "0";
+			$field_ispublic_visible = "0";
+			$field_ispublic_submit = "0";
+		}
+		
+		$html = "";
+		$html .="<input type=\"hidden\" name=\"form_id\" id=\"form_id\" value=\"".$form_id."\">";
+		$html .="<input type=\"hidden\" name=\"field_id\" id=\"field_id\" value=\"".$field_id."\">";
+		$html .="<div id=\"form_result_".$form_id."\" class=\"forms_fields_result\"></div>";
+		if($type == "start")
+		{
+			$html .="<div class=\"forms_item\">"; 
+			$html .="	<strong>".Kohana::lang('ui_admin.field_name').":</strong><br />"; 
+			$html .= 	form::input('field_name', $field_name, ' class="text"');
+			$html .="</div>"; 
+			$html .="<div class=\"forms_item\">"; 
+			$html .="	<strong>".Kohana::lang('ui_admin.divider_class').":</strong><br />"; 
+			$html .= 	form::input('field_default', $field_default, ' class="text"');
+			$html .="</div>"; 
+		}else{
+			$html .="<input type=\"hidden\" name=\"field_name\" id=\"field_name\" value=\"BLANKDIV-".$increment."\">";
+			$html .="<input type=\"hidden\" name=\"field_default\" id=\"field_default\" value=\"BLANKDIV-".$increment."\">";
+		}
+		$html .="<input type=\"hidden\" name=\"field_required\" id=\"field_required\" value=\"FALSE\">";
+		$html .= $this->_get_public_state($field_ispublic_submit, $field_ispublic_visible);
+	
+		// toggle options
+		$toggle_default = '0';
+		$toggle_check = ORM::factory('form_field_option')->where('form_field_id',$field_id)->where('option_name','field_toggle')->find();
+		if($toggle_check->loaded == TRUE)
+			$toggle_default = $toggle_check->option_value;
+
+		$toggle_options = array(
+			'0' => Kohana::lang('ui_admin.field_toggle_no'),
+			'1' => Kohana::lang('ui_admin.field_toggle_yes_open'),
+			'2' => Kohana::lang('ui_admin.field_toggle_yes_close')
+		);
+		$html .="<div class=\"forms_item\">"; 
+		$html .="<strong>" . Kohana::lang('ui_admin.field_toggle') . ":</strong><br />";
+		$html .= form::dropdown('field_options[field_toggle]',$toggle_options,$toggle_default);
+		$html .="</div>";
+
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .="<div class=\"forms_item\">";
+		$html .="	<div id=\"form_loading_".$form_id."\" class=\"forms_fields_loading\"></div>";
+		$html .="<input type=\"submit\" class=\"save-rep-btn\" value=\"".Kohana::lang('ui_main.save')."\" />";
+		$html .="</div>";
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .=$this->_get_selector_js($form_id);
+		
+		return $html;
+	}
+
+	/**
+	* Generate Text Field Entry Form
+    * @param int $form_id The id no. of the form
+    * @param int $field_id The id no. of the field
+    */
+	private function _get_selector_text($form_id = 0, $field_id = 0)
+	{
+		if (intval($field_id) > 0)
+		{
+			$field = ORM::factory('form_field', $field_id);
+			if ($field->loaded == true)
+			{
+				$field_name = $field->field_name;
+				$field_default = $field->field_default;
+				$field_required = $field->field_required;
+				$field_width = $field->field_width;
+				$field_height = $field->field_height;
+				$field_maxlength = $field->field_maxlength;
+				$field_isdate = $field->field_isdate;
+				$field_ispublic_visible = $field->field_ispublic_visible;
+				$field_ispublic_submit = $field->field_ispublic_submit;
+			}
+		}
+		else
+		{
+			$field_id = "";
+			$field_name = "";
+			$field_default = "";
+			$field_required = "0";
+			$field_width = "";
+			$field_height = "";
+			$field_maxlength = "";
+			$field_isdate = "0";
+			$field_ispublic_visible = "0";
+			$field_ispublic_submit = "0";
+		}
+		
+		$html = "";
+		$html .="<input type=\"hidden\" name=\"form_id\" id=\"form_id\" value=\"".$form_id."\">";
+		$html .="<input type=\"hidden\" name=\"field_id\" id=\"field_id\" value=\"".$field_id."\">";
+		$html .="<div id=\"form_result_".$form_id."\" class=\"forms_fields_result\"></div>";
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.field_name').":</strong><br />"; 
+		$html .= 	form::input('field_name', $field_name, ' class="text"');
+		$html .="</div>"; 
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.field_default').":</strong><br />"; 
+		$html .= 	form::input('field_default', $field_default, ' class="text"');
+		$html .="</div>"; 
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.required').":</strong><br />";
+		if ($field_required != 1)
+		{
+			$html .= 	Kohana::lang('ui_admin.yes')." " . form::radio('field_required', '1', FALSE) . "  ";
+			$html .= 	Kohana::lang('ui_admin.no')." " . form::radio('field_required', '0', TRUE);
+		}
+		else
+		{
+			$html .= 	Kohana::lang('ui_admin.yes')." " . form::radio('field_required', '1', TRUE) . "  ";
+			$html .= 	Kohana::lang('ui_admin.no')." " . form::radio('field_required', '0', FALSE);
+		}
+		$html .="</div>";
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.field_maxlength').":</strong><br />"; 
+		$html .= 	form::input('field_maxlength', $field_maxlength, ' class="text short"');
+		$html .="</div>";
+		
+		// is_public additions by george
+		$html .= $this->_get_public_state($field_ispublic_submit, $field_ispublic_visible);
+		
+		//datatype options
+		$datatype_default = '0';
+		$datatype_check = ORM::factory('form_field_option')->where('form_field_id',$field_id)->where('option_name','field_datatype')->find();
+		if($datatype_check->loaded == TRUE)
+			$datatype_default = $datatype_check->option_value;
+
+		$datatype_options = array(
+			'text' => Kohana::lang('ui_admin.field_datatype_text'),
+			'numeric' => Kohana::lang('ui_admin.field_datatype_numeric'),
+			'email' => Kohana::lang('ui_admin.header_email'),
+			'phonenumber' => Kohana::lang('ui_admin.phone') . " #" 
+		);
+		$html .="<div class=\"forms_item\">"; 
+		$html .="<strong>" . Kohana::lang('ui_admin.field_datatype') . ":</strong><br />";
+		$html .= form::dropdown('field_options[field_datatype]',$datatype_options, $datatype_default);
+		$html .="</div>";	
+		//hidden options
+		$hidden_default = '0';
+		$hidden_check = ORM::factory('form_field_option')->where('form_field_id',$field_id)->where('option_name','field_hidden')->find();
+		if($hidden_check->loaded == TRUE)
+			$hidden_default = $hidden_check->option_value;
+
+		$hidden_options = array(
+			'0' => Kohana::lang('ui_main.no'),
+			'1' => Kohana::lang('ui_main.yes')
+		);
+		$html .="<div class=\"forms_item\">"; 
+		$html .="<strong>" . Kohana::lang('ui_admin.field_hidden') . ":</strong><br />";
+		$html .= form::dropdown('field_options[field_hidden]',$hidden_options, $hidden_default);
+		$html .="</div>";
+
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .="<div class=\"forms_item\">";
+		$html .="	<div id=\"form_loading_".$form_id."\" class=\"forms_fields_loading\"></div>";
+		$html .="<input type=\"submit\" class=\"save-rep-btn\" value=\"".Kohana::lang('ui_main.save')."\" />";
+		$html .="</div>";
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .=$this->_get_selector_js($form_id);
+		
+		return $html;
+	}
+	
+	
+	/**
+	* Generate TextArea Field Entry Form
+    * @param int $form_id The id no. of the form
+    * @param int $field_id The id no. of the field
+    */
+	private function _get_selector_textarea($form_id = 0, $field_id = 0)
+	{
+		if (intval($field_id) > 0)
+		{
+			$field = ORM::factory('form_field', $field_id);
+			if ($field->loaded == true)
+			{
+				$field_name = $field->field_name;
+				$field_default = $field->field_default;
+				$field_required = $field->field_required;
+				$field_width = $field->field_width;
+				$field_height = $field->field_height;
+				$field_maxlength = $field->field_maxlength;
+				$field_isdate = $field->field_isdate;
+				$field_ispublic_visible = $field->field_ispublic_visible;
+				$field_ispublic_submit = $field->field_ispublic_submit;
+			}
+		}
+		else
+		{
+			$field_id = "";
+			$field_name = "";
+			$field_default = "";
+			$field_required = "0";
+			$field_width = "";
+			$field_height = "";
+			$field_maxlength = "";
+			$field_isdate = "0";
+			$field_ispublic_visible = "0";
+			$field_ispublic_submit = "0";
+		}
+		
+		$html = "";
+		$html .="<input type=\"hidden\" name=\"form_id\" id=\"form_id\" value=\"".$form_id."\">";
+		$html .="<input type=\"hidden\" name=\"field_id\" id=\"field_id\" value=\"".$field_id."\">";
+		$html .="<input type=\"hidden\" name=\"field_isdate\" id=\"field_id\" value=\"0\">";
+		$html .="<input type=\"hidden\" name=\"field_ispublic_visible\" id=\"field_id\" value=\"0\">";
+		$html .="<input type=\"hidden\" name=\"field_ispublic_submit\" id=\"field_id\" value=\"0\">";
+		$html .="<div id=\"form_result_".$form_id."\" class=\"forms_fields_result\"></div>";
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.field_name').":</strong><br />"; 
+		$html .= 	form::input('field_name', $field_name, ' class="text"');
+		$html .="</div>"; 
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.field_default').":</strong><br />"; 
+		$html .= 	form::textarea('field_default', $field_default, ' class="text" style="width:438px;"');
+		$html .="</div>"; 
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.required').":</strong><br />"; 
+		if ($field_required != 1)
+		{
+			$html .= 	Kohana::lang('ui_admin.yes')." " . form::radio('field_required', '1', FALSE) . "  ";
+			$html .= 	Kohana::lang('ui_admin.no')." " . form::radio('field_required', '0', TRUE);
+		}
+		else
+		{
+			$html .= 	Kohana::lang('ui_admin.yes')." " . form::radio('field_required', '1', TRUE) . "  ";
+			$html .= 	Kohana::lang('ui_admin.no')." " . form::radio('field_required', '0', FALSE);
+		}
+		$html .="</div>";
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.field_height').":</strong><br />"; 
+		$html .= 	form::input('field_height', $field_height, ' class="text short"');
+		$html .="</div>";
+		
+		// is_public additions by george
+		$html .= $this->_get_public_state($field_ispublic_submit, $field_ispublic_visible);
+		
+		//datatype options
+		$datatype_default = '0';
+		$datatype_check = ORM::factory('form_field_option')->where('form_field_id',$field_id)->where('option_name','field_datatype')->find();
+		if($datatype_check->loaded == TRUE)
+			$datatype_default = $datatype_check->option_value;
+
+		$datatype_options = array(
+			'text' => Kohana::lang('ui_admin.field_datatype_text'),
+			'markup' => Kohana::lang('ui_admin.field_datatype_markup'),
+			// 'javascript' => Kohana::lang('ui_admin.field_datatype_javascript')
+		);
+		$html .="<div class=\"forms_item\">"; 
+		$html .="<strong>" . Kohana::lang('ui_admin.field_datatype') . ":</strong><br />";
+		$html .= form::dropdown('field_options[field_datatype]',$datatype_options, $datatype_default);
+		$html .="</div>";
+
+/*		Not sure this makes sense in the context of text areas
+		//hidden options
+		$hidden_default = '0';
+		$hidden_check = ORM::factory('form_field_option')->where('form_field_id',$field_id)->where('option_name','field_hidden')->find();
+		if($hidden_check->loaded == TRUE)
+			$hidden_default = $hidden_check->option_value;
+
+		$hidden_options = array(
+			'0' => Kohana::lang('ui_main.no'),
+			'1' => Kohana::lang('ui_main.yes')
+		);
+		$html .="<div class=\"forms_item\">"; 
+		$html .="<strong>" . Kohana::lang('ui_admin.field_hidden') . ":</strong><br />";
+		$html .= form::dropdown('field_options[field_hidden]',$hidden_options, $hidden_default);
+		$html .="</div>";
+*/
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .="<div class=\"forms_item\">";
+		$html .="	<div id=\"form_loading_".$form_id."\" class=\"forms_fields_loading\"></div>";
+		$html .="<input type=\"submit\" class=\"save-rep-btn\" value=\"".Kohana::lang('ui_main.save')."\" />";
+		$html .="</div>";
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .=$this->_get_selector_js($form_id);
+		
+		return $html;
+	}
+		
+	/**
+	* Generate Date Field Entry Form
+    * @param int $form_id The id no. of the form
+    * @param int $field_id The id no. of the field
+    */
+	private function _get_selector_date($form_id = 0, $field_id = 0)
+	{
+		if (intval($field_id) > 0)
+		{
+			$field = ORM::factory('form_field', $field_id);
+			if ($field->loaded == TRUE)
+			{
+				$field_name = $field->field_name;
+				$field_default = $field->field_default;
+				$field_required = $field->field_required;
+				$field_width = $field->field_width;
+				$field_height = $field->field_height;
+				$field_maxlength = $field->field_maxlength;
+				$field_isdate = $field->field_isdate;
+				$field_ispublic_visible = $field->field_ispublic_visible;
+				$field_ispublic_submit = $field->field_ispublic_submit;
+			}
+		}
+		else
+		{
+			$field_id = "";
+			$field_name = "";
+			$field_default = "";
+			$field_required = "0";
+			$field_width = "";
+			$field_height = "";
+			$field_maxlength = "";
+			$field_isdate = "0";
+			$field_ispublic_visible = "0";
+			$field_ispublic_submit = "0";
+		}
+		
+		$html = "";
+		$html .="<input type=\"hidden\" name=\"form_id\" id=\"form_id\" value=\"".$form_id."\">";
+		$html .="<input type=\"hidden\" name=\"field_id\" id=\"field_id\" value=\"".$field_id."\">";
+		$html .="<div id=\"form_result_".$form_id."\" class=\"forms_fields_result\"></div>";
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.field_name').":</strong><br />"; 
+		$html .= 	form::input('field_name', $field_name, ' class="text"');
+		$html .="</div>"; 
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.field_default').":</strong><br />"; 
+		$html .= 	form::input('field_default', $field_default, ' class="text"');
+		$html .="</div>"; 
+		$html .="<div class=\"forms_item\">"; 
+		$html .="	<strong>".Kohana::lang('ui_admin.required').":</strong><br />";
+		if ($field_required != 1)
+		{
+			$html .= 	Kohana::lang('ui_admin.yes')." " . form::radio('field_required', '1', FALSE) . "  ";
+			$html .= 	Kohana::lang('ui_admin.no')." " . form::radio('field_required', '0', TRUE);
+		}
+		else
+		{
+			$html .= 	Kohana::lang('ui_admin.yes')." " . form::radio('field_required', '1', TRUE) . "  ";
+			$html .= 	Kohana::lang('ui_admin.no')." " . form::radio('field_required', '0', FALSE);
+		}
+		$html .="</div>";
+		
+		// is_public additions by george
+		$html .= $this->_get_public_state($field_ispublic_submit, $field_ispublic_visible);
+		
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .="<div class=\"forms_item\">";
+		$html .="	<div id=\"form_loading_".$form_id."\" class=\"forms_fields_loading\"></div>";
+		$html .="<input type=\"submit\" class=\"save-rep-btn\" value=\"".Kohana::lang('ui_main.save')."\" />";
+		$html .="</div>";
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .=$this->_get_selector_js($form_id);
+		
+		return $html;	
+	}
+
+	/**
+	* Generate Multi-Selector Field Entry Form (radio, dropdown, checkbox)
+    * @param int $form_id The id no. of the form
+    * @param int $field_id The id no. of the field
+    * @param int $type 5=radio, 6=checkbox, 7=dropdown
+    */
+	private function _get_selector_multi($form_id = 0, $field_id = 0, $type="")
+	{
+		if (intval($field_id) > 0)
+		{
+			$field = ORM::factory('form_field', $field_id);
+			if ($field->loaded == true)
+			{
+				$field_name = $field->field_name;
+				$field_default = $field->field_default;
+				$field_required = $field->field_required;
+				$field_ispublic_visible = $field->field_ispublic_visible;
+				$field_ispublic_submit = $field->field_ispublic_submit;
+			}
+		}
+		else
+		{
+			$field_id = "";
+			$field_name = "";
+			$field_default = "";
+			$field_required = "0";
+			$field_ispublic_visible = "0";
+			$field_ispublic_submit = "0";
+		}
+		
+		// Prompt/label for the values
+		$values_prompt = (intval($type) == 7)? Kohana::lang('ui_admin.dropdown_choices') : Kohana::lang('ui_admin.field_choices');
+		
+		// Tooltip display value
+		$tooltip = (intval($type) == 7) ? Kohana::lang('tooltips.dropdown_choices'): (intval($type) == 5)? Kohana::lang('tooltips.radio_choices'): Kohana::lang('tooltips.default_value');
+		
+		$html = "<input type=\"hidden\" name=\"form_id\" id=\"form_id\" value=\"".$form_id."\">"
+			. "<input type=\"hidden\" name=\"field_id\" id=\"field_id\" value=\"".$field_id."\">"
+			. "<input type=\"hidden\" name=\"field_ispublic_visible\" id=\"field_id\" value=\"0\">"
+			. "<input type=\"hidden\" name=\"field_ispublic_submit\" id=\"field_id\" value=\"0\">"
+			. "<div id=\"form_result_".$form_id."\" class=\"forms_fields_result\"></div>"
+			. "<div class=\"forms_item\">"
+			. "		<strong>".Kohana::lang('ui_admin.field_name').":</strong><br />" 
+			.		form::input('field_name', $field_name, ' class="text"')
+			. "</div>" 
+			. "<div class=\"forms_item\">" 
+			. "		<strong>".$values_prompt.":<a href=\"#\" class=\"tooltip\""
+			. "		title=\"".$tooltip."\"></a><br />" 
+			.		form::textarea('field_default', $field_default, ' class="text"')
+			. "</div>" 
+			. "<div class=\"forms_item\">" 
+			. "		<strong>".Kohana::lang('ui_admin.required').":</strong><br />"; 
+		
+		if ($field_required != 1)
+		{
+			$html .= 	Kohana::lang('ui_admin.yes')." " . form::radio('field_required', '1', FALSE) . "  ";
+			$html .= 	Kohana::lang('ui_admin.no')." " . form::radio('field_required', '0', TRUE);
+		}
+		else
+		{
+			$html .= 	Kohana::lang('ui_admin.yes')." " . form::radio('field_required', '1', TRUE) . "  ";
+			$html .= 	Kohana::lang('ui_admin.no')." " . form::radio('field_required', '0', FALSE);
+		}
+		$html .="</div>";
+		$html .= $this->_get_public_state($field_ispublic_submit, $field_ispublic_visible);
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .="<div class=\"forms_item\">";
+		$html .="	<div id=\"form_loading_".$form_id."\" class=\"forms_fields_loading\"></div>";
+		$html .="<input type=\"submit\" class=\"save-rep-btn\" value=\"".Kohana::lang('ui_main.save')."\" />";
+		$html .="</div>";
+		$html .="<div style=\"clear:both;\"></div>";
+		$html .=$this->_get_selector_js($form_id);
+		
+		return $html;
+	}
+
+	/** 
+	* Custom callback for testing the field_options array
+	*
+	* @param  Validation  $array   Validation object
+	* @param  string      $field   name of field being validated
+	* @param int	$field_type 	What type of field this is
+	*/
+	private function _options_validation( Validation $array, $options)
+	{
+		error_log('inside options validation');
+		return;
+	}
+
+	/**
+	* Generate Field Entry Form Javascript
+	* For Ajax Requests
+    * @param int $form_id The id no. of the form
+    */
+	private function _get_selector_js($form_id = 0)
+	{
+		$html = "";
+		$html .="<script type=\"text/javascript\" charset=\"utf-8\">";
+		$html .="$(document).ready(function(){";
+		$html .="i=0;";
+		$html .="var options = { ";
+		$html .="    dataType:   'json',";
+		$html .="    beforeSubmit:    function() { ";
+		$html .="        $('#form_loading_".$form_id."').html('<img src=\"".url::base()."media/img/loading_g.gif\">');";
+		$html .="    }, ";
+		$html .="    success:    function(data) { ";
+		$html .="        $('#form_loading_".$form_id."').html(''); ";
+		$html .="        if(data.status != 'success'){";
+		$html .="        	$('#form_result_".$form_id."').removeClass(\"forms_fields_result\").addClass(\"forms_fields_result_error\");";
+		$html .="        	$('#form_result_".$form_id."').html(data.response);";
+		$html .="        	$('#form_result_".$form_id."').show(); ";
+		$html .="        } else { ";
+		$html .="        	$('#form_result_".$form_id."').removeClass(\"forms_fields_result_error\").addClass(\"forms_fields_result\");";
+		$html .="        	$('#formadd_".$form_id."').hide(300);";
+		$html .="        	$('#form_fields_".$form_id."').hide();";
+		$html .="        	$('#form_fields_current_".$form_id."').html('');";
+		$html .="        	$('#form_fields_current_".$form_id."').html(decodeURIComponent(data.response));";
+		$html .="        	$('#form_fields_current_".$form_id."').effect(\"highlight\", {}, 2000);";
+		$html .="        };";
+		$html .="    } ";
+		$html .="};";
+		$html .="$(\"#form_field_".$form_id."\").ajaxForm(options);";
+		$html .="$(\"#form_field_".$form_id."\").submit(function() {";
+		$html .="return false;";
+		$html .="});";		
+		$html .="});";
+		$html .="</script>";
+		
+		return $html;
+	}
+}
diff --git a/application/controllers/admin/manage/scheduler.php b/application/controllers/admin/manage/scheduler.php
new file mode 100644
index 0000000..cc437f9
--- /dev/null
+++ b/application/controllers/admin/manage/scheduler.php
@@ -0,0 +1,225 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Scheduler
+ * This controller is used to manage the scheduled tasks
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Scheduler_Controller extends Admin_Controller
+{
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'manage';
+
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("manage"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+
+	function index()
+	{
+		$this->template->content = new View('admin/manage/scheduler/main');
+
+		// Check if we should be running the scheduler and then do it
+		if (isset($_GET['run_scheduler'])){
+			// Get all active scheduled items
+			foreach (ORM::factory('scheduler')
+				->where('scheduler_active','1')
+				->find_all() as $scheduler)
+			{
+				$s_controller = $scheduler->scheduler_controller;
+				try {
+					$dispatch = Dispatch::controller($s_controller, "scheduler/");
+					if ($dispatch instanceof Dispatch) $run = $dispatch->method('index', '');
+				}
+				catch (Exception $e)
+				{
+					$run = FALSE;
+				}
+				
+				if ($run !== FALSE)
+				{
+					// Set last time of last execution
+					$schedule_time = time();
+					$scheduler->scheduler_last = $schedule_time;
+					$scheduler->save();
+
+					// Record Action to Log
+					$scheduler_log = new Scheduler_Log_Model();
+					$scheduler_log->scheduler_id = $scheduler->id;
+					$scheduler_log->scheduler_status = "200";
+					$scheduler_log->scheduler_date = $schedule_time;
+					$scheduler_log->save();
+				}
+			}
+		}
+
+		// setup and initialize form field names
+		$form = array(
+			'action' => '',
+			'schedule_id'	  => '',
+			'scheduler_weekday'	  => '',
+			'scheduler_day'	  => '',
+			'scheduler_hour'	  => '',
+			'scheduler_minute'	  => '',
+			'scheduler_active'  => ''
+		);
+		//  copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+
+		if ($_POST)
+		{
+			//print_r($_POST);
+			$post = Validation::factory( $_POST );
+
+			 //  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			if ($post->action == 'a')		// Add Action
+			{
+				// Add some rules, the input field, followed by a list of checks, carried out in order
+				$post->add_rules('scheduler_weekday','required', 'between[1,7]');
+				$post->add_rules('scheduler_day','required', 'between[-1,31]');
+				$post->add_rules('scheduler_hour','required', 'between[-1,23]');
+				$post->add_rules('scheduler_minute','required', 'between[-1,59]');
+			}
+
+			if ( $post->validate() )
+			{
+				$scheduler_id = $post->scheduler_id;
+
+				$scheduler = new Scheduler_Model($scheduler_id);
+				if ($post->action == 'v')
+				{ // Active/Inactive Action
+					if ($scheduler->loaded == TRUE)
+					{
+						$scheduler->scheduler_active = ($scheduler->scheduler_active == 1) ? 0 : 1;
+						
+						$scheduler->save();
+						$form_saved = TRUE;
+						$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+					}
+				}
+				else
+				{ // SAVE Schedule
+					$scheduler->scheduler_weekday = $post->scheduler_weekday;
+					$scheduler->scheduler_day = $post->scheduler_day;
+					$scheduler->scheduler_hour = $post->scheduler_hour;
+					$scheduler->scheduler_minute = $post->scheduler_minute;
+					$scheduler->save();
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.edited'));
+				}
+
+			} else {
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+               // populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('scheduler'));
+				$form_error = TRUE;
+			}
+		}
+
+        // Pagination
+		$pagination = new Pagination(array(
+			'query_string' => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items'	=> ORM::factory('scheduler')->count_all()
+			));
+
+		$schedules = ORM::factory('scheduler')
+			->orderby('scheduler_name', 'asc')
+			->find_all($this->items_per_page, $pagination->sql_offset);
+
+		$this->template->content->weekday_array = array(
+			"-1"=>"ALL",
+			"0"=>"Sunday",
+			"1"=>"Monday",
+			"2"=>"Tuesday",
+			"3"=>"Wednesday",
+			"4"=>"Thursday",
+			"5"=>"Friday",
+			"6"=>"Saturday",
+			);
+
+		for ($i=0; $i <= 31 ; $i++)
+		{
+			$day_array = $i;
+		}
+		$this->template->content->day_array = $day_array;
+
+		$day_array = array();
+		$day_array[-1] = "ALL";
+		for ($i=1; $i <= 31 ; $i++)
+		{
+			$day_array[] = $i;
+		}
+		$this->template->content->day_array = $day_array;
+
+		$hour_array = array();
+		$hour_array[-1] = "ALL";
+		for ($i=0; $i <= 23 ; $i++)
+		{
+			$hour_array[] = $i;
+		}
+		$this->template->content->hour_array = $hour_array;
+
+		$minute_array = array();
+		$minute_array[-1] = "ALL";
+		for ($i=0; $i <= 59 ; $i++)
+		{
+			$minute_array[] = $i;
+		}
+		$this->template->content->minute_array = $minute_array;
+
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->schedules = $schedules;
+		$this->template->content->errors = $errors;
+
+        // Javascript Header
+		$this->themes->js = new View('admin/manage/scheduler/scheduler_js');
+	}
+
+
+	public function log()
+	{
+		$this->template->content = new View('admin/manage/scheduler/log');
+
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string'   => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items'	=> ORM::factory('scheduler_log')
+				->count_all()
+		));
+
+
+		$scheduler_logs = ORM::factory('scheduler_log')
+			->orderby('scheduler_date','desc')
+			->find_all($this->items_per_page, $pagination->sql_offset);
+
+		$this->template->content->scheduler_logs = $scheduler_logs;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+	}
+}
diff --git a/application/controllers/admin/messages.php b/application/controllers/admin/messages.php
new file mode 100755
index 0000000..fafd5ec
--- /dev/null
+++ b/application/controllers/admin/messages.php
@@ -0,0 +1,407 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Messages Controller.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Messages_Controller extends Admin_Controller {
+	
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->template->this_page = 'messages';
+
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("messages"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+
+	/**
+	 * Lists the messages.
+	 * @param int $service_id
+	 */
+	public function index($service_id = 1)
+	{
+		// If a table prefix is specified
+		$db_config = Kohana::config('database.default');
+		$table_prefix = $db_config['table_prefix'];
+
+		$this->template->content = new View('admin/messages/main');
+
+		// Get Title
+		$service = ORM::factory('service', $service_id);
+		$this->template->content->title = $service->service_name;
+
+		// Display Reply to Option?
+		$this->template->content->reply_to = TRUE;
+		if ( ! Kohana::config("settings.sms_provider"))
+		{
+			// Hide Reply to option
+			$this->template->content->reply_to = FALSE;
+		}
+
+		// Is this an Inbox or Outbox Filter?
+		if (!empty($_GET['type']))
+		{
+			$type = $_GET['type'];
+
+			if ($type == '2')
+			{ 
+				// OUTBOX
+				$filter = 'message.message_type = 2';
+			}
+			else
+			{
+				// INBOX
+				$type = "1";
+				$filter = 'message.message_type = 1';
+			}
+		}
+		else
+		{
+			$type = "1";
+			$filter = 'message.message_type = 1';
+		}
+        
+		// Do we have a reporter ID?
+		if (isset($_GET['rid']) AND !empty($_GET['rid']))
+		{
+			$filter .= ' AND '.$table_prefix.'message.reporter_id=\''.intval($_GET['rid']).'\'';
+		}
+        
+		// ALL / Trusted / Spam
+		$level = '0';
+		if (isset($_GET['level']) AND !empty($_GET['level']))
+		{
+			$level = $_GET['level'];
+			if ($level == 4)
+			{
+				$filter .= " AND ( ".$table_prefix."reporter.level_id = '4' OR "
+				    . $table_prefix."reporter.level_id = '5' ) "
+				    . "AND ( ".$table_prefix."message.message_level != '99' ) ";
+			}
+			elseif ($level == 2)
+			{
+				$filter .= " AND ( ".$table_prefix."message.message_level = '99' ) ";
+			}
+		}
+
+		// Check, has the form been submitted?
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+        
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+			$post = Validation::factory($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			$post->add_rules('message_id.*','required','numeric');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{   
+				if( $post->action == 'd' )              // Delete Action
+				{
+					foreach($post->message_id as $item)
+					{
+						// Delete Message
+						$message = ORM::factory('message')->find($item);
+						$message->message_type = 3; // Tag As Deleted/Trash
+						$message->save();
+					}
+
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+				elseif ($post->action == 'n')
+				{
+					// Not Spam
+					foreach($post->message_id as $item)
+					{
+						// Update Message Level
+						$message = ORM::factory('message')->find($item);
+						if ($message->loaded)
+						{
+							$message->message_level = '1';
+							$message->save();
+						}
+					}
+
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+				elseif ($post->action == 's')
+				{
+					// Spam
+					foreach ($post->message_id as $item)
+					{
+						// Update Message Level
+						$message = ORM::factory('message')->find($item);
+						if ($message->loaded)
+						{
+							$message->message_level = '99';
+							$message->save();
+						}
+					}
+
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+			}
+			// No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('message'));
+				$form_error = TRUE;
+			}
+		}       
+        
+		// Pagination
+		$pagination = new Pagination(array(
+		'query_string'   => 'page',
+		'items_per_page' => $this->items_per_page,
+		'total_items'    => ORM::factory('message')
+		    ->join('reporter','message.reporter_id','reporter.id')
+		    ->where($filter)
+		    ->where('service_id', $service_id)
+		    ->count_all()
+		));
+
+		$messages = ORM::factory('message')
+		    ->join('reporter','message.reporter_id','reporter.id')
+		    ->where('service_id', $service_id)
+		    ->where($filter)
+		    ->orderby('message_date','desc')
+		    ->find_all($this->items_per_page, $pagination->sql_offset);
+            
+		// Get Message Count
+		// ALL
+		$this->template->content->count_all = ORM::factory('message')
+		    ->join('reporter','message.reporter_id','reporter.id')
+		    ->where('service_id', $service_id)
+		    ->where('message_type', 1)
+		    ->count_all();
+            
+		// Trusted
+		$this->template->content->count_trusted = ORM::factory('message')
+		    ->join('reporter','message.reporter_id','reporter.id')
+		    ->where('service_id', $service_id)
+		    ->where('message_type', 1)
+		    ->where("message.message_level != '99' AND ( ".$table_prefix."reporter.level_id = '4' OR ".$table_prefix."reporter.level_id = '5' )")
+		    ->count_all();
+        
+		// Spam
+		$this->template->content->count_spam = ORM::factory('message')
+		    ->join('reporter','message.reporter_id','reporter.id')
+		    ->where('service_id', $service_id)
+		    ->where('message_type', 1)
+		    ->where("message.message_level = '99'")
+		    ->count_all();
+
+		//Reporters
+		$this->template->content->count_reporters = ORM::factory('reporter')
+		    ->where('service_id', $service_id)
+		    ->count_all();
+
+		$this->template->content->messages = $messages;
+		$this->template->content->service_id = $service_id;
+		$this->template->content->services = ORM::factory('service')->find_all();
+		$this->template->content->pagination = $pagination;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+        
+		$levels = ORM::factory('level')->orderby('level_weight')->find_all();
+		$this->template->content->levels = $levels;
+
+		// Total Reports
+		$this->template->content->total_items = $pagination->total_items;
+
+		// Message Type Tab - Inbox/Outbox
+		$this->template->content->type = $type;
+		$this->template->content->level = $level;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/messages/messages_js');
+	}
+
+	/**
+	 * Send A New Message Using Default SMS Provider
+	 */
+	public function send()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+
+		// Setup and initialize form field names
+		$form = array(
+			'to_id' => '',
+			'message' => ''
+		);
+
+		//  Copy the form as errors, so the errors will be stored with keys
+		//  corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('to_id', 'required', 'numeric');
+			$post->add_rules('message', 'required', 'length[1,160]');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// Yes! everything is valid
+				$reply_to = ORM::factory('message', $post->to_id);
+
+				if ($reply_to->loaded == true)
+				{
+					// Yes! Replyto Exists
+					// This is the message we're replying to
+
+					$sms_to = $reply_to->message_from;
+					//checks if the number is encrypted
+					if (preg_match("/([a-zA-Z])(\D)/", $sms_to))
+					{
+						$this->decrypter = new Encrypt;
+						$sms_to = $this->decrypter->decode($sms_to);
+					}
+					else
+					{
+						$sms_to = $sms_to;
+					}
+
+					// Load Users Settings
+					$settings = Settings_Model::get_array();
+					if ( !empty($settings))
+					{
+						// Get SMS Numbers
+						if ( ! empty($settings['sms_no1']))
+						{
+							$sms_from = $settings['sms_no1'];
+						}
+						elseif ( ! empty($settings['sms_no2']))
+						{
+							$sms_from = $settings['sms_no2'];
+						}
+						elseif ( ! empty($settings['sms_no3']))
+						{
+							$sms_from = $settings['sms_no3'];
+						}
+						else
+						{
+							// User needs to set up an SMS number
+							$sms_from = "000";
+						}
+
+						// Send Message
+						$response = sms::send($sms_to, $sms_from, $post->message);
+
+						// Message Went Through??
+						if ($response === TRUE)
+						{
+							$message = ORM::factory('message');
+							$message->parent_id = $post->to_id;  // The parent message
+							$message->message_from = $sms_from;
+							$message->message_to = $sms_to;
+							$message->message = $post->message;
+							$message->message_type = 2;          // This is an outgoing message
+							$message->reporter_id = $reply_to->reporter_id;
+							$message->message_date = date("Y-m-d H:i:s",time());
+							$message->save();
+
+							echo json_encode(array(
+								"status" => "sent", 
+								"message" => Kohana::lang('ui_admin.message_sent')
+							));
+						}                        
+						else
+						{
+							// Message Failed 
+							echo json_encode(array(
+								"status" => "error", 
+								"message" => Kohana::lang('ui_admin.error_msg')." - " . $response
+							));
+						}
+					}
+					else
+					{
+						echo json_encode(array(
+							"status" => "error",
+							"message" => Kohana::lang('ui_admin.error_msg').Kohana::lang('ui_admin.check_sms_settings')
+						));
+					}
+				}
+				else
+				{
+					// Send_To Mobile Number Doesn't Exist
+					echo json_encode(array(
+						"status" => "error", 
+						"message" => Kohana::lang('ui_admin.error_msg').Kohana::lang('ui_admin.check_number')
+					));
+				}
+			}
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// Populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('messages'));
+
+				echo json_encode(array(
+					"status" => "error",
+					"message" => Kohana::lang('ui_admin.error_msg').Kohana::lang('ui_admin.check_message_valid')
+				));
+			}
+		}
+	}
+
+	/**
+	 * Setup simplepie
+	 * @param string $raw_data
+	 */
+	private function _setup_simplepie($raw_data)
+	{
+		$data = new SimplePie();
+		$data->set_raw_data( $raw_data );
+		$data->enable_cache(false);
+		$data->enable_order_by_date(true);
+		$data->init();
+		$data->handle_content_type();
+		return $data;
+	}
+
+}
diff --git a/application/controllers/admin/messages/reporters.php b/application/controllers/admin/messages/reporters.php
new file mode 100644
index 0000000..81a3ef4
--- /dev/null
+++ b/application/controllers/admin/messages/reporters.php
@@ -0,0 +1,227 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Reporters Controller
+ * Add/Edit Ushahidi Reporters
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Reporters_Controller extends Admin_Controller
+{
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'messages';
+		
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("messages_reporters"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+	
+	public function index($service_id = 1)
+	{
+		$this->template->content = new View('admin/reporters/main');
+		$this->template->content->title = Kohana::lang('ui_admin.reporters');
+		
+		// setup and initialize form field names
+		$form = array
+		(
+			'reporter_id' => '',
+			'level_id' => '',
+			'service_name' => '',
+			'service_account' => '',
+			'location_id' => '',
+			'location_name' => '',
+			'latitude' => '',
+			'longitude' => ''
+		);
+		//  copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+			$post = Validation::factory($_POST);
+
+			    //  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			$post->add_rules('reporter_id.*','required','numeric');
+			
+			if ($post->action == 'l')
+			{
+				$post->add_rules('level_id','required','numeric');
+			}
+			elseif ($post->action == 'a')
+			{
+				$post->add_rules('level_id','required','numeric');
+				// If any location data is provided, require all location parameters
+				if ($post->latitude OR $post->longitude OR $post->location_name)
+				{
+					$post->add_rules('latitude','required','between[-90,90]');		// Validate for maximum and minimum latitude values
+					$post->add_rules('longitude','required','between[-180,180]');	// Validate for maximum and minimum longitude values
+					$post->add_rules('location_name','required', 'length[3,200]');
+				}
+			}
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{	
+				if( $post->action == 'd' )				// Delete Action
+				{
+					foreach($post->reporter_id as $item)
+					{
+						// Delete Reporters Messages
+						ORM::factory('message')
+							->where('reporter_id', $item)
+							->delete_all();
+					
+						// Delete Reporter
+						$reporter = ORM::factory('reporter')->find($item);
+						$reporter->delete( $item );
+					}
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+				elseif( $post->action == 'l' )			// Modify Level Action
+				{
+					foreach($post->reporter_id as $item)
+					{
+						// Update Reporter Level
+						$reporter = ORM::factory('reporter')->find($item);
+						if ($reporter->loaded)
+						{
+							$reporter->level_id = $post->level_id;
+							$reporter->save();
+						}
+					}
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+				else if( $post->action == 'a' ) 		// Save Action
+				{
+					foreach($post->reporter_id as $item)
+					{
+						$reporter = ORM::factory('reporter')->find($item);
+					
+						// SAVE Reporter only if loaded
+						if ($reporter->loaded)
+						{
+							$reporter->level_id = $post->level_id;
+
+							// SAVE Location if available
+							if ($post->latitude AND $post->longitude)
+							{
+								$location = new Location_Model($post->location_id);
+								$location->location_name = $post->location_name;
+								$location->latitude = $post->latitude;
+								$location->longitude = $post->longitude;
+								$location->location_date = date("Y-m-d H:i:s",time());
+								$location->save();
+							
+								$reporter->location_id = $location->id;
+							}
+						
+							$reporter->save();
+
+							$form_saved = TRUE;
+							$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+						}
+					}
+				}
+			}
+			// No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+				
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('reporters'));
+				$form_error = TRUE;
+			}
+		}
+
+		// Start building query
+		$filter = '1=1 ';
+		
+		// Default search type to service id
+		$search_type = ( isset($_GET['s']) ) ? intval($_GET['s']) : intval($service_id);
+		if ($search_type > 0)
+		{
+			$filter .= 'AND service_id = '.intval($search_type).' ';
+		}
+		
+		// Get Search Keywords (If Any)
+		$keyword = '';
+		if (isset($_GET['k']) AND !empty($_GET['k']))
+		{
+			$keyword = $_GET['k'];
+			$filter .= 'AND service_account LIKE \'%'.Database::instance()->escape_str($_GET['k']).'%\' ';
+		}
+
+		// Pagination
+		$pagination = new Pagination(array(
+		                    'query_string' => 'page',
+		                    'items_per_page' => $this->items_per_page,
+		                    'total_items' => ORM::factory('reporter')
+								->where($filter)
+								->count_all()
+		                ));
+
+		$reporters = ORM::factory('reporter')
+						->where($filter)
+		                ->orderby('service_account', 'asc')
+		                ->find_all($this->items_per_page,  $pagination->sql_offset);
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->reporters = $reporters;
+		$this->template->content->service_id = $service_id;
+		$this->template->content->search_type = $search_type;
+		$search_type_array = Service_Model::get_array();
+		$search_type_array[0] = "All";
+		asort($search_type_array);
+		$this->template->content->search_type_array = $search_type_array;
+		$this->template->content->keyword = $keyword;
+		
+		$levels = ORM::factory('level')->orderby('level_weight')->find_all();
+		$this->template->content->levels = $levels;
+
+		// Level and Service Arrays
+		$this->template->content->level_array = Level_Model::get_array();
+		$this->template->content->service_array = Service_Model::get_array();
+		
+		// Javascript Header
+        $this->themes->map_enabled = TRUE;
+        $this->themes->js = new View('admin/reporters/reporters_js');
+		$this->themes->js->default_map = Kohana::config('settings.default_map');
+		$this->themes->js->default_zoom = Kohana::config('settings.default_zoom');
+		$this->themes->js->latitude = Kohana::config('settings.default_lat');
+		$this->themes->js->longitude = Kohana::config('settings.default_lon');
+		$this->themes->js->form_error = $form_error;
+	}
+}
diff --git a/application/controllers/admin/profile.php b/application/controllers/admin/profile.php
new file mode 100644
index 0000000..a1da1bd
--- /dev/null
+++ b/application/controllers/admin/profile.php
@@ -0,0 +1,173 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * "My Profile" - allows admin to configure their settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Profile_Controller extends Admin_Controller
+{
+    protected $user_id;
+
+    function __construct()
+    {
+        parent::__construct();
+        $this->template->this_page = 'profile';
+
+        $this->user_id = $_SESSION['auth_user']->id;
+    }
+
+    public function index()
+    {
+        $this->template->content = new View('admin/profile');
+
+        // setup and initialize form field names
+        $form = array
+        (
+            'current_password'  => '',
+            'new_password'  => '',
+            'password_again'  => '',
+            'name'      => '',
+            'email'     => '',
+            'notify'    => ''
+        );
+
+        //  Copy the form as errors, so the errors will be stored with keys
+        //  corresponding to the form field names
+        $errors = $form;
+        $form_error = FALSE;
+        $form_saved = FALSE;
+
+        // check, has the form been submitted, if so, setup validation
+        if ($_POST)
+        {
+			$post = Validation::factory($_POST);
+
+			 //  Add some filters
+			$post->pre_filter('trim', TRUE);
+			$post->add_rules('name','required','length[3,100]');
+			$post->add_rules('email','required','email','length[4,64]');
+			$post->add_rules('current_password','required');
+			$post->add_callbacks('email',array($this,'email_exists_chk'));
+			$post->add_callbacks('current_password',array($this,'current_pw_valid_chk'));
+
+            // If Password field is not blank
+            if ( ! empty($post->new_password))
+            {
+				$post->add_rules('new_password','required','length['.Kohana::config('auth.password_length').']','matches[password_again]');
+            }
+		//for plugins that'd like to know what the user has to say about their profile
+		Event::run('ushahidi_action.profile_add_admin', $post);
+			if ($post->validate())
+			{
+				$user = ORM::factory('user',$this->user_id);
+				if ($user->loaded)
+				{
+					$user->name = $post->name;
+					$user->email = $post->email;
+					$user->notify = $post->notify;
+					if ($post->new_password != '')
+                    {
+                        $user->password = $post->new_password;
+                    }
+					$user->save();
+
+					Event::run('ushahidi_action.profile_edit', $user);
+
+
+	                // We also need to update the RiverID server with the new password if
+	                //    we are using RiverID and a password is being passed
+	                if (kohana::config('riverid.enable') == TRUE
+	                	AND ! empty($user->riverid)
+	                	AND $post->new_password != '')
+					{
+						$riverid = new RiverID;
+						$riverid->email = $user->email;
+						$riverid->password = $post->current_password;
+						$riverid->new_password = $post->new_password;
+						if ($riverid->changepassword() == FALSE)
+						{
+							// TODO: Something went wrong. Tell the user.
+						}
+					}
+				}
+
+                $form_saved = TRUE;
+
+                // Repopulate the form fields
+                $form = arr::overwrite($form, $post->as_array());
+                $form['new_password'] = "";
+                $form['password_again'] = "";
+            }
+            else
+            {
+                // repopulate the form fields
+                $form = arr::overwrite($form, $post->as_array());
+
+                // populate the error fields, if any
+                $errors = arr::overwrite($errors, $post->errors('auth'));
+                $form_error = TRUE;
+            }
+        }
+        else
+        {
+            $user = ORM::factory('user',$this->user_id);
+            $form['username'] = $user->email;
+            $form['name'] = $user->name;
+            $form['email'] = $user->email;
+            $form['notify'] = $user->notify;
+        }
+
+        $this->template->content->form = $form;
+        $this->template->content->errors = $errors;
+        $this->template->content->form_error = $form_error;
+        $this->template->content->form_saved = $form_saved;
+        $this->template->content->yesno_array = array('1'=>utf8::strtoupper(Kohana::lang('ui_main.yes')),'0'=>utf8::strtoupper(Kohana::lang('ui_main.no')));
+
+        // Javascript Header
+    }
+
+
+    /**
+     * Checks if email address is associated with an account.
+     * @param Validation $post $_POST variable with validation rules
+     */
+    public function email_exists_chk( Validation $post )
+    {
+		if (array_key_exists('email',$post->errors()))
+			return;
+
+		$users = ORM::factory('user')
+		    ->where('id <> '.$this->user_id);
+
+		if ($users->email_exists( $post->email ))
+		{
+			$post->add_error('email','exists');
+		}
+	}
+
+	/**
+	 * Checks if current password being passed is correct
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function current_pw_valid_chk( Validation $post )
+	{
+		if (array_key_exists('current_password',$post->errors()))
+			return;
+
+		$user = ORM::factory('user',$this->user_id);
+
+		if ( ! User_Model::check_password($user->email,$post->current_password) )
+		{
+			$post->add_error('current_password','incorrect');
+		}
+	}
+}
diff --git a/application/controllers/admin/reports.php b/application/controllers/admin/reports.php
new file mode 100755
index 0000000..4cdc1c5
--- /dev/null
+++ b/application/controllers/admin/reports.php
@@ -0,0 +1,1528 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Reports Controller.
+ * This controller will take care of adding and editing reports in the Admin section.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Reports_Controller extends Admin_Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->template->this_page = 'reports';
+		$this->params = array('all_reports' => TRUE);
+	}
+
+
+	/**
+	 * Lists the reports.
+	 *
+	 * @param int $page
+	 */
+	public function index($page = 1)
+	{
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("reports_view"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+
+		$this->template->content = new View('admin/reports/main');
+		$this->template->content->title = Kohana::lang('ui_admin.reports');
+
+		// Database table prefix
+		$table_prefix = Kohana::config('database.default.table_prefix');
+
+		// Hook into the event for the reports::fetch_incidents() method
+		Event::add('ushahidi_filter.fetch_incidents_set_params', array($this,'_add_incident_filters'));
+
+
+		$status = "0";
+
+		if ( !empty($_GET['status']))
+		{
+			$status = $_GET['status'];
+
+			if (strtolower($status) == 'a')
+			{
+				array_push($this->params, 'i.incident_active = 0');
+			}
+			elseif (strtolower($status) == 'v')
+			{
+				array_push($this->params, 'i.incident_verified = 0');
+			}
+			elseif (strtolower($status) == 'o')
+			{
+				array_push($this->params, '(ic.category_id IS NULL)');
+			}
+			elseif (strtolower($status) != 'search')
+			{
+				$status = "0";
+			}
+		}
+
+		// Get Search Keywords (If Any)
+		if (isset($_GET['k']))
+		{
+			//	Brute force input sanitization
+			// Phase 1 - Strip the search string of all non-word characters
+			$keyword_raw = (isset($_GET['k']))? preg_replace('#/\w+/#', '', $_GET['k']) : "";
+
+			// Strip any HTML tags that may have been missed in Phase 1
+			$keyword_raw = strip_tags($keyword_raw);
+
+			// Phase 3 - Invoke Kohana's XSS cleaning mechanism just incase an outlier wasn't caught
+			// in the first 2 steps
+			$keyword_raw = $this->input->xss_clean($keyword_raw);
+
+			$filter = " (".$this->_get_searchstring($keyword_raw).")";
+
+			array_push($this->params, $filter);
+		}
+		else
+		{
+			$keyword_raw = "";
+		}
+		
+		$this->template->content->search_form = $this->_search_form();
+		$this->template->content->search_form->keywords = $keyword_raw;
+		
+		// Handler sort/order fields
+		$order_field = 'date'; $sort = 'DESC';
+		if (isset($_GET['order']))
+		{
+			$order_field = html::escape($_GET['order']);
+		}
+		if (isset($_GET['sort']))
+		{
+			$sort = (strtoupper($_GET['sort']) == 'ASC') ? 'ASC' : 'DESC';
+		}
+
+		// Check, has the form been submitted?
+		$form_error = FALSE;
+		$errors = array();
+		$form_saved = FALSE;
+		$form_action = "";
+
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			 //	Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks,
+			// carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			$post->add_rules('incident_id.*','required','numeric');
+			
+			if (in_array($post->action, array('a','u')) AND ! Auth::instance()->has_permission('reports_approve'))
+			{
+				$post->add_error('action','permission');
+			}
+			
+			if ($post->action == 'v' AND ! Auth::instance()->has_permission('reports_verify'))
+			{
+				$post->add_error('action','permission');
+			}
+			
+			if ($post->action == 'd' AND ! Auth::instance()->has_permission('reports_edit'))
+			{
+				$post->add_error('action','permission');
+			}
+			
+			if ($post->action == 'a')
+			{
+				// sanitize the incident_ids
+				$post->incident_id = array_map('intval', $post->incident_id);
+				
+				// Query to check if this report is uncategorized i.e categoryless
+				$query = "SELECT i.* FROM ".$table_prefix."incident i "
+				    . "LEFT JOIN ".$table_prefix."incident_category ic ON i.id=ic.incident_id "
+				    . "LEFT JOIN ".$table_prefix."category c ON c.id = ic.category_id "
+				    . "WHERE c.id IS NULL "
+				    . "AND i.id IN :incidentids";
+
+				$result = Database::instance()->query($query, array(':incidentids' => $post->incident_id));
+
+				// We enly approve the report IF it's categorized
+				// throw an error if any incidents aren't categorized
+				foreach ($result as $incident)
+				{
+					$post->add_error('incident_id', 'categories_required', $incident->incident_title);
+				}
+			}
+
+			if ($post->validate())
+			{
+				// Approve Action
+				if ($post->action == 'a')
+				{
+					foreach($post->incident_id as $item)
+					{
+						$update = new Incident_Model($item);
+						if ($update->loaded == TRUE)
+						{
+							$update->incident_active = '1';
+
+							// Tag this as a report that needs to be sent out as an alert
+							if ($update->incident_alert_status != '2')
+							{
+								// 2 = report that has had an alert sent
+								$update->incident_alert_status = '1';
+							}
+							$update->save();
+
+							// Record 'Verified By' Action
+							reports::verify_approve($update);
+
+							// Action::report_approve - Approve a Report
+							Event::run('ushahidi_action.report_approve', $update);
+						}
+						$form_action = utf8::strtoupper(Kohana::lang('ui_admin.approved'));
+					}
+
+				}
+				
+				// Unapprove Action
+				elseif ($post->action == 'u')
+				{
+					foreach ($post->incident_id as $item)
+					{
+						$update = new Incident_Model($item);
+						if ($update->loaded == TRUE)
+						{
+							$update->incident_active = '0';
+
+							// If Alert hasn't been sent yet, disable it
+							if ($update->incident_alert_status == '1')
+							{
+								$update->incident_alert_status = '0';
+							}
+
+							$update->save();
+
+							// Record 'Verified By' Action
+							reports::verify_approve($update);
+
+							// Action::report_unapprove - Unapprove a Report
+							Event::run('ushahidi_action.report_unapprove', $update);
+						}
+					}
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.unapproved'));
+				}
+				
+				// Verify Action
+				elseif ($post->action == 'v')
+				{
+					foreach ($post->incident_id as $item)
+					{
+						$update = new Incident_Model($item);
+						$verify = new Verify_Model();
+						if ($update->loaded == TRUE)
+						{
+							if ($update->incident_verified == '1')
+							{
+								$update->incident_verified = '0';
+								$verify->verified_status = '0';
+							}
+							else
+							{
+								$update->incident_verified = '1';
+								$verify->verified_status = '2';
+							}
+							$update->save();
+
+							// Record 'Verified By' Action
+							reports::verify_approve($update);
+						}
+					}
+
+					// Set the form action
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.verified_unverified'));
+				}
+
+				// Delete Action
+				elseif ($post->action == 'd')
+				{
+					foreach ($post->incident_id as $item)
+					{
+						$update = new Incident_Model($item);
+						if ($update->loaded)
+						{
+							$update->delete();
+						}
+					}
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+				$form_saved = TRUE;
+			}
+			else
+			{
+				// Repopulate the form fields
+				//$form = arr::overwrite($form, $post->as_array());
+
+				// Populate the error fields, if any
+				$errors = $post->errors('reports');
+				$form_error = TRUE;
+			}
+		}
+
+		// Fetch all incidents
+		$incidents = reports::fetch_incidents(TRUE, Kohana::config('settings.items_per_page_admin'));
+
+		Event::run('ushahidi_filter.filter_incidents',$incidents);
+
+		$this->template->content->countries = Country_Model::get_countries_list();
+		$this->template->content->incidents = $incidents;
+		$this->template->content->pagination = reports::$pagination;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+
+		// Total Reports
+		$this->template->content->total_items = reports::$pagination->total_items;
+
+		// Status Tab
+		$this->template->content->status = $status;
+		$this->template->content->order_field = $order_field;
+		$this->template->content->sort = $sort;
+		
+		$this->themes->map_enabled = TRUE;
+		$this->themes->json2_enabled = TRUE;
+		$this->themes->treeview_enabled = TRUE;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/reports/reports_js');
+	}
+
+	/**
+	 * Edit a report
+	 * @param bool|int $id The id no. of the report
+	 * @param bool|string $saved
+	 */
+	public function edit($id = FALSE, $saved = FALSE)
+	{
+		$db = new Database();
+
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("reports_edit"))
+		{
+			url::redirect('admin/dashboard');
+		}
+
+		$this->template->content = new View('admin/reports/edit');
+		$this->template->content->title = Kohana::lang('ui_admin.create_report');
+
+		// Setup and initialize form field names
+		$form = array(
+			'location_id' => '',
+			'form_id' => '',
+			'locale' => '',
+			'incident_title' => '',
+			'incident_description' => '',
+			'incident_date' => '',
+			'incident_hour' => '',
+			'incident_minute' => '',
+			'incident_ampm' => '',
+			'latitude' => '',
+			'longitude' => '',
+			'geometry' => array(),
+			'location_name' => '',
+			'country_id' => '',
+			'country_name' =>'',
+			'incident_category' => array(),
+			'incident_news' => array(),
+			'incident_video' => array(),
+			'incident_photo' => array(),
+			'person_first' => '',
+			'person_last' => '',
+			'person_email' => '',
+			'custom_field' => array(),
+			'incident_active' => '',
+			'incident_verified' => '',
+			'incident_zoom' => ''
+		);
+
+		// Copy the form as errors, so the errors will be stored with keys
+		// corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = ($saved == 'saved');
+
+		// Initialize Default Values
+		$form['locale'] = Kohana::config('locale.language');
+		//$form['latitude'] = Kohana::config('settings.default_lat');
+		//$form['longitude'] = Kohana::config('settings.default_lon');
+		$form['incident_date'] = date("m/d/Y",time());
+		$form['incident_hour'] = date('h');
+		$form['incident_minute'] = date('i');
+		$form['incident_ampm'] = date('a');
+		$form['country_id'] = Kohana::config('settings.default_country');
+
+		// get the form ID if relevant, kind of a hack
+		// to just hit the database like this for one
+		// tiny bit of info then throw away the DB model object,
+		// but seems to be what everyone else does, so
+		// why should I care. Just know that when your Ush system crashes
+		// because you have 1000 concurrent users you'll need to do this
+		// correctly. Etherton.
+		$form['form_id'] = 1;
+		$form_id = $form['form_id'];
+		if ($id AND Incident_Model::is_valid_incident($id, FALSE))
+		{
+			$form_id = ORM::factory('incident', $id)->form_id;
+		}
+		
+		// Initialize custom field array
+		$form['custom_field'] = customforms::get_custom_form_fields($id,$form_id,TRUE);
+
+		// Locale (Language) Array
+		$this->template->content->locale_array = Kohana::config('locale.all_languages');
+
+		// Create Categories
+		$this->template->content->new_categories_form = $this->_new_categories_form_arr();
+
+		// Time formatting
+		$this->template->content->hour_array = $this->_hour_array();
+		$this->template->content->minute_array = $this->_minute_array();
+		$this->template->content->ampm_array = $this->_ampm_array();
+
+		$this->template->content->stroke_width_array = $this->_stroke_width_array();
+
+		// Get Countries
+		$countries = array();
+		foreach (ORM::factory('country')->orderby('country')->find_all() as $country)
+		{
+			// Create a list of all countries
+			$this_country = $country->country;
+			if (strlen($this_country) > 35)
+			{
+				$this_country = substr($this_country, 0, 35) . "...";
+			}
+			$countries[$country->id] = $this_country;
+		}
+
+		// Initialize Default Value for Hidden Field Country Name, 
+		// just incase Reverse Geo coding yields no result
+		$form['country_name'] = $countries[$form['country_id']];
+		$this->template->content->countries = $countries;
+
+		// GET custom forms
+		$forms = array();
+		foreach (customforms::get_custom_forms(FALSE) as $custom_forms)
+		{
+			$forms[$custom_forms->id] = $custom_forms->form_title;
+		}
+		$this->template->content->forms = $forms;
+
+		// Get the incident media
+		$incident_media =  Incident_Model::is_valid_incident($id, FALSE)
+			? ORM::factory('incident', $id)->media
+			: FALSE;
+
+		$this->template->content->incident_media = $incident_media;
+
+		// Are we creating this report from SMS/Email/Twitter?
+		// If so retrieve message
+		if (isset($_GET['mid']) AND intval($_GET['mid']) > 0)
+		{
+			$message_id = intval($_GET['mid']);
+			$service_id = "";
+			$message = ORM::factory('message', $message_id);
+
+			if ($message->loaded AND $message->message_type == 1)
+			{
+				$service_id = $message->reporter->service_id;
+
+				// Has a report already been created for this Message?
+				if ($message->incident_id != 0) {
+
+					// Redirect to report
+					url::redirect('admin/reports/edit/'. $message->incident_id);
+				}
+
+				$this->template->content->show_messages = TRUE;
+				$incident_description = $message->message;
+				if ( ! empty($message->message_detail))
+				{
+					$form['incident_title'] = $message->message;
+					$incident_description = $message->message_detail;
+				}
+
+				$form['incident_description'] = $incident_description;
+				$form['incident_date'] = date('m/d/Y', strtotime($message->message_date));
+				$form['incident_hour'] = date('h', strtotime($message->message_date));
+				$form['incident_minute'] = date('i', strtotime($message->message_date));
+				$form['incident_ampm'] = date('a', strtotime($message->message_date));
+				$form['person_first'] = $message->reporter->reporter_first;
+				$form['person_last'] = $message->reporter->reporter_last;
+
+				// Does the message itself have a location?
+				if ($message->latitude != NULL AND $message->longitude != NULL)
+				{
+					$form['latitude'] = $message->latitude;
+					$form['longitude'] = $message->longitude;
+				}
+				
+				// As a fallback, does the sender of this message have a location?
+				elseif ($message->reporter->location->loaded)
+				{
+					$form['location_id'] = $message->reporter->location->id;
+					$form['latitude'] = $message->reporter->location->latitude;
+					$form['longitude'] = $message->reporter->location->longitude;
+					$form['location_name'] = $message->reporter->location->location_name;
+				}
+
+				// Events to manipulate an already known location
+				Event::run('ushahidi_action.location_from',$message_from = $message->message_from);
+				
+				// Filter location name
+				Event::run('ushahidi_filter.location_name',$form['location_name']);
+				
+				// Filter //location find
+				Event::run('ushahidi_filter.location_find',$form['location_find']);
+
+
+				// Retrieve Last 5 Messages From this account
+				$this->template->content->all_messages = ORM::factory('message')
+					->where('reporter_id', $message->reporter_id)
+					->orderby('message_date', 'desc')
+					->limit(5)
+					->find_all();
+			}
+			else
+			{
+				$message_id = "";
+				$this->template->content->show_messages = FALSE;
+			}
+		}
+		else
+		{
+			$this->template->content->show_messages = FALSE;
+		}
+
+		// Are we creating this report from a Newsfeed?
+		if ( isset($_GET['fid']) AND intval($_GET['fid']) > 0 )
+		{
+			$feed_item_id = intval($_GET['fid']);
+			$feed_item = ORM::factory('feed_item', $feed_item_id);
+
+			if ($feed_item->loaded)
+			{
+				// Has a report already been created for this Feed item?
+				if ($feed_item->incident_id != 0)
+				{
+					// Redirect to report
+					url::redirect('admin/reports/edit/'. $feed_item->incident_id);
+				}
+
+				$form['incident_title'] = $feed_item->item_title;
+				$form['incident_description'] = $feed_item->item_description;
+				$form['incident_date'] = date('m/d/Y', strtotime($feed_item->item_date));
+				$form['incident_hour'] = date('h', strtotime($feed_item->item_date));
+				$form['incident_minute'] = date('i', strtotime($feed_item->item_date));
+				$form['incident_ampm'] = date('a', strtotime($feed_item->item_date));
+
+				// News Link
+				$form['incident_news'][0] = $feed_item->item_link;
+
+				// Does this newsfeed have a geolocation?
+				if ($feed_item->location_id)
+				{
+					$form['location_id'] = $feed_item->location_id;
+					$form['latitude'] = $feed_item->location->latitude;
+					$form['longitude'] = $feed_item->location->longitude;
+					$form['location_name'] = $feed_item->location->location_name;
+				}
+				// HT: new code
+				$feed_item_categories = ORM::factory('feed_item_category')->where('feed_item_id', $feed_item->id)->select_list('id', 'category_id');
+				if ($feed_item_categories)
+				{
+					foreach($feed_item_categories as $feed_item_category) {
+						$form['incident_category'][] = $feed_item_category;
+					}
+				}
+				// HT: end of new code
+			}
+			else
+			{
+				$feed_item_id = "";
+			}
+		}
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite 
+			// $_POST fields with our own things
+			$post = array_merge($_POST, $_FILES);
+
+			// Check if the service id exists
+			if (isset($service_id) AND intval($service_id) > 0)
+			{
+				$post = array_merge($post, array('service_id' => $service_id));
+			}
+
+			// Check if the incident id is valid an add it to the post data
+			if (Incident_Model::is_valid_incident($id, FALSE))
+			{
+				$post = array_merge($post, array('incident_id' => $id));
+			}
+
+			/**
+			 * NOTES - E.Kala July 27, 2011
+			 *
+			 * Previously, the $post parameter for this event was a Validation
+			 * object. Now it's an array (i.e. the raw data without any validation rules applied to them).
+			 * As such, all plugins making use of this event shall have to be updated
+			 */
+
+			// Action::report_submit_admin - Report Posted
+			Event::run('ushahidi_action.report_submit_admin', $post);
+
+			// Validate
+			if (reports::validate($post))
+			{
+				// Yes! everything is valid
+				$location_id = $post->location_id;
+
+				// STEP 1: SAVE LOCATION
+				$location = new Location_Model($location_id);
+				reports::save_location($post, $location);
+
+				// STEP 2: SAVE INCIDENT
+				$incident = new Incident_Model($id);
+				reports::save_report($post, $incident, $location->id);
+
+				// STEP 2b: Record Approval/Verification Action
+				reports::verify_approve($incident);
+
+				// STEP 2c: SAVE INCIDENT GEOMETRIES
+				reports::save_report_geometry($post, $incident);
+
+				// STEP 3: SAVE CATEGORIES
+				reports::save_category($post, $incident);
+
+				// STEP 4: SAVE MEDIA
+				reports::save_media($post, $incident);
+
+				// STEP 5: SAVE PERSONAL INFORMATION
+				reports::save_personal_info($post, $incident);
+
+				// STEP 6a: SAVE LINK TO REPORTER MESSAGE
+				// We're creating a report from a message with this option
+				if (isset($message_id) AND intval($message_id) > 0)
+				{
+					$savemessage = ORM::factory('message', $message_id);
+					if ($savemessage->loaded)
+					{
+						$savemessage->incident_id = $incident->id;
+						$savemessage->save();
+
+						// Does Message Have Attachments?
+						// Add Attachments
+						$attachments = ORM::factory("media")
+							->where("message_id", $savemessage->id)
+							->find_all();
+						foreach ($attachments AS $attachment)
+						{
+							$attachment->incident_id = $incident->id;
+							$attachment->save();
+						}
+					}
+				}
+
+				// STEP 6b: SAVE LINK TO NEWS FEED
+				// We're creating a report from a newsfeed with this option
+				if (isset($feed_item_id) AND intval($feed_item_id) > 0)
+				{
+					$savefeed = ORM::factory('feed_item', $feed_item_id);
+					if ($savefeed->loaded)
+					{
+						$savefeed->incident_id = $incident->id;
+						$savefeed->location_id = $location->id;
+						$savefeed->save();
+					}
+				}
+
+				// STEP 7: SAVE CUSTOM FORM FIELDS
+				reports::save_custom_fields($post, $incident);
+
+				// Action::report_edit - Edited a Report
+				Event::run('ushahidi_action.report_edit', $incident);
+
+				// SAVE AND CLOSE?
+				switch ($post->save)
+				{
+					case 1:
+					case 'dontclose':
+						// Save but don't close
+						url::redirect('admin/reports/edit/'. $incident->id .'/saved');
+						break;
+					case 'addnew':
+						// Save and add new
+						url::redirect('admin/reports/edit/0/saved');
+						break;
+					default:
+						// Save and close
+						url::redirect('admin/reports/');
+				}
+			}
+			
+			// No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// Repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// Populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('report'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			if (Incident_Model::is_valid_incident($id, FALSE))
+			{
+				// Retrieve Current Incident
+				$incident = ORM::factory('incident', $id);
+				if ($incident->loaded == TRUE)
+				{
+					// Retrieve Categories
+					$incident_category = array();
+					foreach($incident->incident_category as $category)
+					{
+						$incident_category[] = $category->category_id;
+					}
+
+					// Retrieve Media
+					$incident_news = array();
+					$incident_video = array();
+					$incident_photo = array();
+					foreach($incident->media as $media)
+					{
+						if ($media->media_type == 4)
+						{
+							$incident_news[] = $media->media_link;
+						}
+						elseif ($media->media_type == 2)
+						{
+							$incident_video[] = $media->media_link;
+						}
+						elseif ($media->media_type == 1)
+						{
+							$incident_photo[] = $media->media_link;
+						}
+					}
+
+					// Get Geometries via SQL query as ORM can't handle Spatial Data
+					$sql = "SELECT AsText(geometry) as geometry, geometry_label,
+						geometry_comment, geometry_color, geometry_strokewidth
+						FROM ".Kohana::config('database.default.table_prefix')."geometry
+						WHERE incident_id = ?";
+					$query = $db->query($sql, $id);
+					foreach ( $query as $item )
+					{
+						$geometry = array(
+								"geometry" => $item->geometry,
+								"label" => $item->geometry_label,
+								"comment" => $item->geometry_comment,
+								"color" => $item->geometry_color,
+								"strokewidth" => $item->geometry_strokewidth
+							);
+						$form['geometry'][] = json_encode($geometry);
+					}
+
+					// Combine Everything
+					$incident_arr = array(
+						'location_id' => $incident->location->id,
+						'form_id' => $incident->form_id,
+						'locale' => $incident->locale,
+						'incident_title' => $incident->incident_title,
+						'incident_description' => $incident->incident_description,
+						'incident_date' => date('m/d/Y', strtotime($incident->incident_date)),
+						'incident_hour' => date('h', strtotime($incident->incident_date)),
+						'incident_minute' => date('i', strtotime($incident->incident_date)),
+						'incident_ampm' => date('a', strtotime($incident->incident_date)),
+						'latitude' => $incident->location->latitude,
+						'longitude' => $incident->location->longitude,
+						'location_name' => $incident->location->location_name,
+						'country_id' => $incident->location->country_id,
+						'incident_category' => $incident_category,
+						'incident_news' => $incident_news,
+						'incident_video' => $incident_video,
+						'incident_photo' => $incident_photo,
+						'person_first' => $incident->incident_person->person_first,
+						'person_last' => $incident->incident_person->person_last,
+						'person_email' => $incident->incident_person->person_email,
+						'custom_field' => customforms::get_custom_form_fields($id, $incident->form_id, TRUE, 'submit'),
+						'incident_active' => $incident->incident_active,
+						'incident_verified' => $incident->incident_verified,
+						'incident_zoom' => $incident->incident_zoom
+					);
+
+					// Merge To Form Array For Display
+					$form = arr::overwrite($form, $incident_arr);
+				}
+				else
+				{
+					// Redirect
+					url::redirect('admin/reports/');
+				}
+
+			}
+		}
+
+		$this->template->content->id = $id;
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+
+		// Retrieve Custom Form Fields Structure
+		$this->template->content->custom_forms = new View('reports/submit_custom_forms');
+		$disp_custom_fields = customforms::get_custom_form_fields($id, $form['form_id'], FALSE, "view");
+		$custom_field_mismatch = customforms::get_edit_mismatch($form['form_id']);
+		// Quick hack to make sure view-only fields have data set
+		foreach ($custom_field_mismatch as $id => $field)
+		{
+			$form['custom_field'][$id] = $disp_custom_fields[$id]['field_response'];
+		}
+		
+		$this->template->content->custom_forms->disp_custom_fields = $disp_custom_fields;
+		$this->template->content->custom_forms->custom_field_mismatch = $custom_field_mismatch;
+		$this->template->content->custom_forms->form = $form;
+		
+
+		// Retrieve Previous & Next Records
+		$previous = ORM::factory('incident')->where('id < ', $id)->orderby('id','desc')->find();
+		$previous_url = $previous->loaded
+		    ? url::site('admin/reports/edit/'.$previous->id)
+		    : url::site('admin/reports/');
+		$next = ORM::factory('incident')->where('id > ', $id)->orderby('id','desc')->find();
+		$next_url = $next->loaded
+		    ? url::site('admin/reports/edit/'.$next->id)
+		    : url::site('admin/reports/');
+		$this->template->content->previous_url = $previous_url;
+		$this->template->content->next_url = $next_url;
+
+		// Javascript Header
+		$this->themes->map_enabled = TRUE;
+		$this->themes->colorpicker_enabled = TRUE;
+		$this->themes->treeview_enabled = TRUE;
+		$this->themes->json2_enabled = TRUE;
+
+		$this->themes->js = new View('reports/submit_edit_js');
+		$this->themes->js->edit_mode = TRUE;
+		$this->themes->js->default_map = Kohana::config('settings.default_map');
+		$this->themes->js->default_zoom = Kohana::config('settings.default_zoom');
+
+		if ( ! $form['latitude'] OR !$form['latitude'])
+		{
+			$this->themes->js->latitude = Kohana::config('settings.default_lat');
+			$this->themes->js->longitude = Kohana::config('settings.default_lon');
+		}
+		else
+		{
+			$this->themes->js->latitude = $form['latitude'];
+			$this->themes->js->longitude = $form['longitude'];
+		}
+
+		$this->themes->js->incident_zoom = $form['incident_zoom'];
+		$this->themes->js->geometries = $form['geometry'];
+
+		// Inline Javascript
+		$this->template->content->date_picker_js = $this->_date_picker_js();
+		$this->template->content->color_picker_js = $this->_color_picker_js();
+		$this->template->content->new_category_toggle_js = $this->_new_category_toggle_js();
+
+		// Pack Javascript
+		$myPacker = new javascriptpacker($this->themes->js , 'Normal', FALSE, FALSE);
+		$this->themes->js = $myPacker->pack();
+	}
+
+
+	/**
+	 * Download Reports in CSV format
+	 */
+	public function download()
+	{
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("reports_download"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+
+		$this->template->content = new View('admin/reports/download');
+		$this->template->content->title = Kohana::lang('ui_admin.download_reports');
+
+		$errors = $form = array(
+			'format' =>'',
+			'data_active'   => array(),
+			'data_verified'   => array(),
+			'data_include' => array(),
+			'from_date'	   => '',
+			'to_date'	   => '',
+			'form_auth_token'=> ''
+		);
+
+		// Default to all selected
+		$form['data_active'] = array(0,1);
+		$form['data_verified'] = array(0,1);
+		$form['data_include'] = array(1,2,3,4,5,6,7);
+		
+		$form_error = FALSE;
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+			$post = array_merge($_POST, $_FILES);
+
+			// Test to see if things passed the rule checks
+			if (download::validate($post))
+			{
+				// Set filter
+				$filter = '( ';
+				
+				// Report Type Filter
+				$show_active = FALSE;
+				$show_inactive = FALSE;
+				$show_verified = FALSE;
+				$show_not_verified = FALSE;
+				
+				if (in_array(1, $post->data_active))
+				{
+					$show_active = TRUE;
+				}
+
+				if (in_array(0, $post->data_active))
+				{
+					$show_inactive = TRUE;
+				}
+
+				if (in_array(1, $post->data_verified))
+				{
+					$show_verified = TRUE;
+				}
+
+				if (in_array(0, $post->data_verified))
+				{
+					$show_not_verified = TRUE;
+				}
+				
+				// Handle active or not active
+				if ($show_active && !$show_inactive)
+				{
+					$filter .= ' incident_active = 1 ';
+				}
+				elseif (!$show_active && $show_inactive)
+				{
+					$filter .= '  incident_active = 0 ';
+				}
+				elseif ($show_active && $show_inactive)
+				{
+					$filter .= ' (incident_active = 1 OR incident_active = 0) ';
+				}
+				
+				// Neither active nor inactive selected: select nothing
+				elseif (!$show_active && !$show_inactive)
+				{
+					// Equivalent to 1 = 0
+					$filter .= ' (incident_active = 0 AND incident_active = 1) ';
+				}
+				
+				$filter .= ' AND ';
+				
+				// Handle verified
+				if($show_verified && !$show_not_verified)
+				{				
+					$filter .= ' incident_verified = 1 ';
+				}
+				elseif (!$show_verified && $show_not_verified)
+				{				
+					$filter .= ' incident_verified = 0 ';
+				}
+				elseif ($show_verified && $show_not_verified)
+				{				
+					$filter .= ' (incident_verified = 0 OR incident_verified = 1) ';
+				}
+				elseif (!$show_verified && !$show_not_verified)
+				{				
+					$filter .= ' (incident_verified = 0 AND incident_verified = 1) ';
+				}
+				
+				$filter .= ') ';
+
+				// Report Date Filter
+				if ( ! empty($post->from_date))
+				{
+					$filter .= " AND incident_date >= '" . date("Y-m-d H:i:s",strtotime($post->from_date)) . "' ";
+				}
+				if (  !empty($post->to_date))
+				{
+					$filter .= " AND incident_date <= '" . date("Y-m-d H:i:s",strtotime($post->to_date)) . "' ";
+				}
+
+				// Retrieve reports
+				$incidents = ORM::factory('incident')->where($filter)->orderby('incident_dateadd', 'desc')->find_all();
+
+				// Retrieve categories
+				$categories = Category_Model::get_categories(FALSE, FALSE, FALSE);
+
+				// Retrieve Forms
+				$forms = ORM::Factory('form')->find_all();
+
+				// Retrieve Custom forms
+				$custom_forms = customforms::get_custom_form_fields();
+
+				// If CSV format is selected
+				if($post->format == 'csv')
+					{
+					$report_csv = download::download_csv($post, $incidents, $custom_forms);
+
+				// Output to browser
+				header("Content-type: text/x-csv");
+				header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
+				header("Content-Disposition: attachment; filename=" . time() . ".csv");
+				header("Content-Length: " . strlen($report_csv));
+				echo $report_csv;
+				exit;
+					
+			}
+
+				// If XML format is selected
+				if($post->format == 'xml')
+				{ 
+					header('Content-type: text/xml; charset=UTF-8');
+					header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
+					header("Content-Disposition: attachment; filename=" . time() . ".xml");	
+					$content = download::download_xml($post, $incidents, $categories, $forms);
+					echo $content;
+					exit;
+				}
+			}
+
+			// No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// Repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// Populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('report'));
+				$form_error = TRUE;
+			}
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/reports/download_js');
+		$this->themes->js->calendar_img = url::base() . "media/img/icon-calendar.gif";
+	}
+
+	public function upload()
+	{
+		$form = array(
+			'uploadfile' => '',
+		);
+		
+		$errors = array();
+		$notices = array();
+		
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("reports_upload"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+
+		if ($_SERVER['REQUEST_METHOD'] == 'GET') {
+			$this->template->content = new View('admin/reports/upload');
+			$this->template->content->title = 'Upload Reports';
+			$this->template->content->form_error = FALSE;
+		}
+
+		if ($_SERVER['REQUEST_METHOD']=='POST')
+		{
+			$post = array_merge($_POST, $_FILES);
+
+			// Set up validation
+			$post = Validation::factory($post)
+					->add_rules('uploadfile', 'upload::valid', 'upload::required', 'upload::type[xml,csv]', 'upload::size[3M]');
+					
+			if($post->validate(TRUE))
+					{
+				// Establish if file to be uploaded is .xml or .csv format
+				$fileinfo = pathinfo($post['uploadfile']['name']);
+				$extension = $fileinfo['extension'];
+				$allowable_extensions = array ('csv', 'xml');
+
+				if (file_exists($_FILES['uploadfile']['tmp_name']))
+						{
+					// If file type uploaded is CSV or XML
+					if (in_array($extension, $allowable_extensions))
+					{
+						// Pick the corresponding import library
+						$importer = $extension == 'csv' ? new CSVImporter : new XMLImporter;
+						if($importer->import($_FILES['uploadfile']['tmp_name']))
+						{
+							$this->template->content = new View('admin/reports/upload_success');
+							$this->template->content->title = 'Upload Reports';
+							$this->template->content->rowcount = $importer->totalreports;
+							$this->template->content->imported = $importer->importedreports;
+							$this->template->content->notices = $importer->notices;
+						}
+						
+						else
+						{
+							$errors = $importer->errors;
+						}
+					}
+					
+					else
+					{
+						$errors[] = Kohana::lang('reports.uploadfile.type');
+					}
+				}
+
+				// File doesn't exist
+				else
+				{
+					$errors[] = Kohana::lang('ui_admin.file_not_found_upload');
+				}
+			}
+
+			else
+			{
+				foreach($post->errors('reports') as $error)
+				{
+					$errors[] = $error;
+			}
+
+			}
+			
+			if (count($errors))
+			{
+				$this->template->content = new View('admin/reports/upload');
+				$this->template->content->title = Kohana::lang('ui_admin.upload_reports');
+				$this->template->content->errors = $errors;
+				$this->template->content->form_error = 1;
+			}
+		}
+	}
+
+	/**
+	* Save newly added dynamic categories
+	*/
+	public function save_category()
+	{
+		$this->auto_render = FALSE;
+		$this->template = "";
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+			// HT: New code for category save with parent
+			$post = arr::extract($_POST, 'parent_id',
+					'category_title', 'category_description', 'category_color');
+
+			// Category instance for the operation
+			$category = new Category_Model();
+			if ($category->validate($post)) {
+				$category->save();
+				$form_saved = TRUE;
+
+				echo json_encode(array("status"=>"saved", "id"=>$category->id));
+			}
+			// HT: End of code for category save with parent
+			/*
+			$post = Validation::factory($_POST);
+
+			 //	Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('category_title','required', 'length[3,200]');
+			$post->add_rules('category_description','required');
+			$post->add_rules('category_color','required', 'length[6,6]');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// SAVE Category
+				$category = new Category_Model();
+				$category->category_title = $post->category_title;
+				$category->category_description = $post->category_description;
+				$category->category_color = $post->category_color;
+				$category->save();
+				$form_saved = TRUE;
+
+				echo json_encode(array("status"=>"saved", "id"=>$category->id));
+			}*/
+			else
+			{
+				echo json_encode(array("status"=>"error"));
+			}
+		}
+		else
+		{
+			echo json_encode(array("status"=>"error"));
+		}
+	}
+
+	/** Deletes all reports from the database **/
+	public function deleteall() {
+
+		// Only superadmins should be able to do this...
+		if ( ! $this->auth->has_permission("delete_all_reports"))
+		{
+			url::redirect(url::site() . 'admin/dashboard');
+		}
+
+		if (isset($_POST["confirm_delete_all"]) && $_POST["confirm_delete_all"] == 1)
+		{
+			$table_prefix = Kohana::config('database.default.table_prefix');
+
+			Database::instance()->query("UPDATE `" . $table_prefix . "message` SET `incident_id` = 0;");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "media`");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "location`");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "comment`");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "rating`");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "form_response`");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "incident_person`");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "incident_lang`");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "incident_category`");
+			Database::instance()->query("TRUNCATE TABLE `" . $table_prefix . "incident`");
+		}
+
+
+		$this->template->content = new View('admin/reports/delete_all');
+		$this->template->content->report_count = Incident_Model::get_total_reports();
+		$this->themes->js = new View('admin/reports/delete_all_js');
+
+
+	}
+
+	/* private functions */
+
+	// Dynamic categories form fields
+	private function _new_categories_form_arr()
+	{
+		// HT: Parent category list
+		$parents_array = ORM::factory('category')
+		->where('parent_id','0')
+		->where('category_trusted != 1')
+		->select_list('id', 'category_title');
+
+		// add none to the list
+		$parents_array[0] = "--- Top Level Category ---";
+
+		// Put "--- Top Level Category ---" at the top of the list
+		ksort($parents_array);
+		// HT: End of Parent category list
+
+		return array(
+			'category_name' => '',
+			'category_description' => '',
+			'category_color' => '',
+			'parent_id' => 0, // HT: new category parent
+			'category_parent_array' => $parents_array, // HT: new category parent
+		);
+	}
+
+	// Time functions
+	private function _hour_array()
+	{
+		for ($i=1; $i <= 12 ; $i++)
+		{
+			// Add Leading Zero
+			$hour_array[sprintf("%02d", $i)] = sprintf("%02d", $i);
+		}
+		return $hour_array;
+	}
+
+	private function _minute_array()
+	{
+		for ($j=0; $j <= 59 ; $j++)
+		{
+			// Add Leading Zero
+			$minute_array[sprintf("%02d", $j)] = sprintf("%02d", $j);
+		}
+		return $minute_array;
+	}
+
+	private function _ampm_array()
+	{
+		return $ampm_array = array('pm'=>Kohana::lang('ui_admin.pm'),'am'=>Kohana::lang('ui_admin.am'));
+	}
+
+	private function _stroke_width_array()
+	{
+		for ($i = 0.5; $i <= 8 ; $i += 0.5)
+		{
+			$stroke_width_array["$i"] = $i;
+		}
+		return $stroke_width_array;
+	}
+
+	// Javascript functions
+	private function _color_picker_js()
+	{
+		 return "<script type=\"text/javascript\">
+					$(document).ready(function() {
+					$('#category_color').ColorPicker({
+							onSubmit: function(hsb, hex, rgb) {
+								$('#category_color').val(hex);
+							},
+							onChange: function(hsb, hex, rgb) {
+								$('#category_color').val(hex);
+							},
+							onBeforeShow: function () {
+								$(this).ColorPickerSetColor(this.value);
+							}
+						})
+					.bind('keyup', function(){
+						$(this).ColorPickerSetColor(this.value);
+					});
+					});
+				</script>";
+	}
+
+	private function _date_picker_js()
+	{
+		return "<script type=\"text/javascript\">
+				$(document).ready(function() {
+				$(\"#incident_date\").datepicker({
+				showOn: \"both\",
+				buttonImage: \"" . url::base() . "media/img/icon-calendar.gif\",
+				buttonImageOnly: true
+				});
+				});
+			</script>";
+	}
+
+
+	private function _new_category_toggle_js()
+	{
+		return "<script type=\"text/javascript\">
+				$(document).ready(function() {
+					$('a#category_toggle').click(function() {console.log('toggle');
+						$('#category_add').toggle(400);
+						return false;
+					});
+				});
+			</script>";
+	}
+
+
+	/**
+	 * Checks if translation for this report & locale exists
+	 * @param Validation $post $_POST variable with validation rules
+	 * @param int $iid The unique incident_id of the original report
+	 */
+	public function translate_exists_chk(Validation $post)
+	{
+		// If add->rules validation found any errors, get me out of here!
+		if (array_key_exists('locale', $post->errors()))
+			return;
+
+		$iid = (isset($_GET['iid']) AND intval($_GTE['iid'] > 0))? intval($_GET['iid']) : 0;
+
+		// Load translation
+		$translate = ORM::factory('incident_lang')
+						->where('incident_id',$iid)
+						->where('locale',$post->locale)
+						->find();
+
+		if ($translate->loaded)
+		{
+			$post->add_error( 'locale', 'exists');
+		}
+		else
+		{
+			// Not found
+			return;
+		}
+	}
+
+	/**
+	 * Creates a SQL string from search keywords
+	 */
+	private function _get_searchstring($keyword_raw)
+	{
+		$or = '';
+		$where_string = '';
+
+		/**
+		 * NOTES: 2011-11-17 - John Etherton <john at ethertontech.com> I'm pretty sure this needs to be
+		 * internationalized, seems rather biased towards English.
+		 * */
+		// Stop words that we won't search for
+		// Add words as needed!!
+		$stop_words = array('the', 'and', 'a', 'to', 'of', 'in', 'i', 'is', 'that', 'it',
+		'on', 'you', 'this', 'for', 'but', 'with', 'are', 'have', 'be',
+		'at', 'or', 'as', 'was', 'so', 'if', 'out', 'not');
+
+		$keywords = explode(' ', $keyword_raw);
+
+		if (is_array($keywords) AND !empty($keywords))
+		{
+			array_change_key_case($keywords, CASE_LOWER);
+			$i = 0;
+
+			foreach ($keywords as $value)
+			{
+				if (!in_array($value,$stop_words) AND !empty($value))
+				{
+					$chunk = $this->db->escape_str($value);
+					if ($i > 0)
+					{
+						$or = ' OR ';
+					}
+					$where_string = $where_string
+									.$or
+									."incident_title LIKE '%$chunk%' OR incident_description LIKE '%$chunk%'  OR location_name LIKE '%$chunk%'";
+					$i++;
+				}
+			}
+		}
+
+		// Return
+		return (!empty($where_string)) ? $where_string :  "1=1";
+	}
+
+	/**
+	 * Adds extra filter parameters to the reports::fetch_incidents()
+	 * method. This way we can add 'all_reports=>TRUE and other filters
+	 * that don't come standard since we are on the backend.
+	 * Works by simply adding in SQL conditions to the params
+	 * array of the reports::fetch_incidents() method
+	 * @return none
+	 */
+	public function _add_incident_filters()
+	{
+		$params = Event::$data;
+		$params = array_merge($params, $this->params);
+		Event::$data = $params;
+	}
+	
+	private function _search_form()
+	{
+		$search_form = View::factory('admin/reports/search_form');
+		
+		// Handling location filter
+		$location_filter = isset($_GET['location_filter']);
+		if (! $location_filter)
+		{
+			if ( isset($_GET['start_loc']) )
+			{
+				unset($_GET['start_loc']);
+			}
+			if ( isset($_GET['alert_radius']) )
+			{
+				unset($_GET['alert_radius']);
+			}
+		}
+		else
+		{
+			$_GET['radius'] = $_GET['alert_radius'];
+		}
+		$search_form->location_filter = $location_filter;
+		$search_form->start_loc = isset($_GET['start_loc']) ? $_GET['start_loc'] : array(0,0);
+		
+		// Get the date of the oldest report
+		if (! empty($_GET['from']))
+		{
+			$date_from = strtotime($_GET['from']);
+		}
+		else
+		{
+			$date_from = Incident_Model::get_oldest_report_timestamp();
+		}
+
+		// Get the date of the latest report
+		if (! empty($_GET['to']))
+		{
+			$date_to = strtotime($_GET['to']);
+		}
+		else
+		{
+			$date_to = Incident_Model::get_latest_report_timestamp();
+		}
+		
+		$search_form->date_from = $date_from;
+		$search_form->date_to = $date_to;
+		
+		// Categories
+		if (! isset($_GET['c']) OR ! is_array($_GET['c']))
+		{
+			$_GET['c'] = isset($_GET['c']) ? array($_GET['c']) : array();
+		}
+		$search_form->categories = $_GET['c'];
+		
+		// Media
+		if (! isset($_GET['m']) OR ! is_array($_GET['m']))
+		{
+			$_GET['m'] = isset($_GET['m']) ? array($_GET['m']) : array();
+		}
+		$search_form->media = $_GET['m'];
+		
+		// Mode
+		if (! isset($_GET['mode']) OR ! is_array($_GET['mode']))
+		{
+			$_GET['mode'] = isset($_GET['mode']) ? array($_GET['mode']) : array();
+		}
+		$search_form->mode = $_GET['mode'];
+		
+		// Approved
+		$search_form->approved = $_GET['a'] = isset($_GET['a']) ? $_GET['a'] : 'all';
+		if ($_GET['a'] == 'all') unset($_GET['a']);
+		// Verified
+		$search_form->verified = $_GET['v'] = isset($_GET['v']) ? $_GET['v'] : 'all';
+		if ($_GET['v'] == 'all') unset($_GET['v']);
+		
+		// Load the alert radius view
+		$alert_radius_view = new View('alerts/radius');
+		$alert_radius_view->show_usage_info = FALSE;
+		$alert_radius_view->enable_find_location = TRUE;
+		$alert_radius_view->css_class = "map_holder_reports";
+		$search_form->alert_radius_view = $alert_radius_view;
+		
+		return $search_form;
+	}
+	
+	/**
+	 * Function used by the photo delete button
+	 * in /admin/reports/edit/N
+	 * @param $id is the DB id of the image to delete
+	 **/
+	public function deletePhoto($id = 0)
+	{
+		$this->auto_render = false;
+		$this->template = null;
+		if($id)
+		{
+			Media_Model::delete_photo($id);
+		}
+	}
+
+
+
+}
diff --git a/application/controllers/admin/settings.php b/application/controllers/admin/settings.php
new file mode 100755
index 0000000..84fae0e
--- /dev/null
+++ b/application/controllers/admin/settings.php
@@ -0,0 +1,1320 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used to manage user settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Settings_Controller extends Admin_Controller {
+
+	/**
+	 * @var Cache
+	 */
+	protected $cache;
+
+	public function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'settings';
+
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("settings"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+
+		$this->cache = Cache::instance();
+	}
+
+	/**
+	 * Site Settings
+	 */
+	public function site()
+	{
+		$this->template->content = new View('admin/settings/site');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+		$this->themes->js = new View('admin/settings/site_js');
+
+		// setup and initialize form field names
+		$form = array(
+			'site_name' => '',
+			'site_tagline' => '',
+			'banner_image' => '',
+			'delete_banner_image' => '',
+			'site_email' => '',
+			'alerts_email' =>  '',
+			'site_language' => '',
+			'site_timezone' => '',
+			'site_message' => '',
+			'site_copyright_statement' => '',
+			'site_submit_report_message' => '',
+			'site_contact_page' => '',
+			'items_per_page' => '',
+			'items_per_page_admin' => '',
+			'blocks_per_row' => '',
+			'allow_alerts' => '',
+			'allow_reports' => '',
+			'allow_comments' => '',
+			'max_upload_size' => '',
+			'allow_feed' => '',
+			'allow_feed_category' => '',
+			'feed_geolocation_user' => '',
+			'allow_stat_sharing' => '',
+			'cache_pages' => '',
+			'cache_pages_lifetime' => '',
+			'private_deployment' => '',
+			'manually_approve_users' => '',
+			'require_email_confirmation' => '',
+			'google_analytics' => '',
+			'api_akismet' => '',
+			'alert_days' => 0, // HT: No of days of alert to be sent
+		);
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('site_name', 'required', 'length[3,250]');
+			$post->add_rules('site_tagline', 'length[3,250]');
+			$post->add_rules('site_email', 'email', 'length[4,100]');
+			//$post->add_rules('alerts_email','required', 'email', 'length[4,100]');
+			//$post->add_rules('site_message', 'standard_text');
+			$post->add_rules('site_copyright_statement', 'length[4,600]');
+			$post->add_rules('site_language','required', 'length[2, 5]');
+			//$post->add_rules('site_timezone','required', 'between[10,50]');
+			$post->add_rules('site_contact_page','required','between[0,1]');
+			$post->add_rules('items_per_page','required','between[5,50]');
+			$post->add_rules('items_per_page_admin','required','between[5,50]');
+			$post->add_rules('blocks_per_row','required','numeric');
+			$post->add_rules('allow_alerts','required','between[0,1]');
+			$post->add_rules('allow_reports','required','between[0,1]');
+			$post->add_rules('max_upload_size','length[0,50]', 'alpha_numeric');
+			$post->add_rules('allow_comments','required','between[0,2]');
+			$post->add_rules('allow_feed','required','between[0,1]');
+			$post->add_rules('allow_feed_category','required','between[0,1]');
+			$post->add_rules('feed_geolocation_user','length[0,100]');
+			$post->add_rules('allow_stat_sharing','required','between[0,1]');
+			$post->add_rules('cache_pages','required','between[0,1]');
+			$post->add_rules('cache_pages_lifetime','required','in_array[60,300,600,900,1800]');
+			$post->add_rules('private_deployment','required','between[0,1]');
+			$post->add_rules('manually_approve_users','required','between[0,1]');
+			$post->add_rules('require_email_confirmation','required','between[0,1]');
+			$post->add_rules('google_analytics','length[0,20]');
+			$post->add_rules('api_akismet','length[0,100]', 'alpha_numeric');
+			$post->add_rules('alert_days', 'numeric'); // HT: No of days of alert to be sent
+
+			// Add rules for file upload
+			$files = Validation::factory($_FILES);
+			$files->add_rules('banner_image', 'upload::valid', 'upload::type[gif,jpg,jpeg,png]', 'upload::size[250K]');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate() AND $files->validate(FALSE))
+			{
+				// Yes! everything is valid
+				Settings_Model::save_all($post);
+
+				// Deal with banner image now
+
+				// Check if deleting or updating a new image (or doing nothing)
+				if (isset($post->delete_banner_image) AND $post->delete_banner_image == 1)
+				{
+					// Delete old badge image
+					ORM::factory('media')->delete(Settings_Model::get_setting('site_banner_id'));
+
+					// Remove from DB table
+					Settings_Model::save_setting('site_banner_id', NULL);
+				}
+				else
+				{
+					// We aren't deleting, so try to upload if we are uploading an image
+					$filename = upload::save('banner_image');
+					if ($filename)
+					{
+						$new_filename = "banner_".time();
+						$file_type = strrev(substr(strrev($filename),0,4));
+
+						// Large size
+						$l_name = $new_filename.$file_type;
+						Image::factory($filename)->save(Kohana::config('upload.directory', TRUE).$l_name);
+
+						// Medium size
+						$m_name = $new_filename."_m".$file_type;
+						Image::factory($filename)->resize(80,80,Image::HEIGHT)
+							->save(Kohana::config('upload.directory', TRUE).$m_name);
+
+						// Thumbnail
+						$t_name = $new_filename."_t".$file_type;
+						Image::factory($filename)->resize(60,60,Image::HEIGHT)
+							->save(Kohana::config('upload.directory', TRUE).$t_name);
+
+						// Name the files for the DB
+						$media_link = $l_name;
+						$media_medium = $m_name;
+						$media_thumb = $t_name;
+
+						// Okay, now we have these three different files on the server, now check to see
+						//   if we should be dropping them on the CDN
+
+						if (Kohana::config("cdn.cdn_store_dynamic_content"))
+						{
+							$media_link = cdn::upload($media_link);
+							$media_medium = cdn::upload($media_medium);
+							$media_thumb = cdn::upload($media_thumb);
+
+							// We no longer need the files we created on the server. Remove them.
+							$local_directory = rtrim(Kohana::config('upload.directory', TRUE), '/').'/';
+							unlink($local_directory.$l_name);
+							unlink($local_directory.$m_name);
+							unlink($local_directory.$t_name);
+						}
+
+						// Remove the temporary file
+						unlink($filename);
+
+						// Save banner image in the media table
+						$media = new Media_Model();
+						$media->media_type = 1; // Image
+						$media->media_link = $media_link;
+						$media->media_medium = $media_medium;
+						$media->media_thumb = $media_thumb;
+						$media->media_date = date("Y-m-d H:i:s",time());
+						$media->save();
+
+						// Save new banner image in settings
+						Settings_Model::save_setting('site_banner_id', $media->id);
+					}
+				}
+
+				// Delete Settings Cache
+				$this->cache->delete('settings');
+				$this->cache->delete_tag('settings');
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// Action::site_settings_modified - Site settings have changed
+				Event::run('ushahidi_action.site_settings_modified');
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				if (is_array($files->errors()) AND count($files->errors()) > 0)
+				{
+					// Error with file upload
+					$errors = arr::overwrite($errors, $files->errors('settings'));
+				}
+				else
+				{
+					// Error with other form filed
+					$errors = arr::overwrite($errors, $post->errors('settings'));
+				}
+
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			$settings = Settings_Model::get_array();
+			$settings['alert_days'] = (isset($settings['alert_days'])) ? $settings['alert_days'] : 0; // HT: might not be in database so calling manually retrun NULL if not exist
+
+			$form = array(
+				'site_name' => $settings['site_name'],
+				'site_tagline' => $settings['site_tagline'],
+				'site_banner_id' => $settings['site_banner_id'],
+				'site_email' => $settings['site_email'],
+				'alerts_email' => $settings['alerts_email'],
+				'site_message' => $settings['site_message'],
+				'site_copyright_statement' => $settings['site_copyright_statement'],
+				'site_submit_report_message' => $settings['site_submit_report_message'],
+				'site_language' => $settings['site_language'],
+				'site_timezone' => $settings['site_timezone'],
+				'site_contact_page' => $settings['site_contact_page'],
+				'items_per_page' => $settings['items_per_page'],
+				'items_per_page_admin' => $settings['items_per_page_admin'],
+				'blocks_per_row' => $settings['blocks_per_row'],
+				'allow_alerts' => $settings['allow_alerts'],
+				'allow_reports' => $settings['allow_reports'],
+				'allow_comments' => $settings['allow_comments'],
+				'max_upload_size' => $settings['max_upload_size'],
+				'allow_feed' => $settings['allow_feed'],
+				'allow_feed_category' => $settings['allow_feed_category'],
+				'feed_geolocation_user' => isset($settings['feed_geolocation_user']) ? $settings['feed_geolocation_user'] : null,
+				'allow_stat_sharing' => $settings['allow_stat_sharing'],
+				'cache_pages' => $settings['cache_pages'],
+				'cache_pages_lifetime' => $settings['cache_pages_lifetime'],
+				'private_deployment' => $settings['private_deployment'],
+				'manually_approve_users' => $settings['manually_approve_users'],
+				'require_email_confirmation' => $settings['require_email_confirmation'],
+				'google_analytics' => $settings['google_analytics'],
+				'api_akismet' => $settings['api_akismet'],
+				'alert_days' => $settings['alert_days'] // HT: No of days of alert to be sent
+			);
+		}
+
+		// Get banner image
+		if (($site_banner_id = Settings_Model::get_setting('site_banner_id')) !== NULL)
+		{
+			$banner = ORM::factory('media')->find($site_banner_id);
+			$this->template->content->banner = url::convert_uploaded_to_abs($banner->media_link);
+			$this->template->content->banner_m = url::convert_uploaded_to_abs($banner->media_medium);
+			$this->template->content->banner_t = url::convert_uploaded_to_abs($banner->media_thumb);
+		}
+		else
+		{
+			$this->template->content->banner = NULL;
+			$this->template->content->banner_m = NULL;
+			$this->template->content->banner_t = NULL;
+		}
+
+		$this->themes->colorpicker_enabled = TRUE;
+		$this->themes->slider_enabled = TRUE;
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->items_per_page_array = array('5'=>'5 Items','10'=>'10 Items','20'=>'20 Items','30'=>'30 Items','50'=>'50 Items');
+		$blocks_per_row_array = array();
+		for ($i=1; $i <= 21; $i++)
+		{
+			$blocks_per_row_array[$i] = $i;
+		}
+		$this->template->content->blocks_per_row_array = $blocks_per_row_array;
+		$this->template->content->yesno_array = array(
+			'1'=>utf8::strtoupper(Kohana::lang('ui_main.yes')),
+			'0'=>utf8::strtoupper(Kohana::lang('ui_main.no')));
+
+		$this->template->content->comments_array = array(
+			'1'=>utf8::strtoupper(Kohana::lang('ui_main.yes')." - ".Kohana::lang('ui_admin.approve_auto')),
+			'2'=>utf8::strtoupper(Kohana::lang('ui_main.yes')." - ".Kohana::lang('ui_admin.approve_manual')),
+			'0'=>utf8::strtoupper(Kohana::lang('ui_main.no')));
+
+		$this->template->content->cache_pages_lifetime_array = array(
+			'60'=>'1 '.Kohana::lang('ui_admin.minute'),
+			'300'=>'5 '.Kohana::lang('ui_admin.minutes'),
+			'600'=>'10 '.Kohana::lang('ui_admin.minutes'),
+			'900'=>'15 '.Kohana::lang('ui_admin.minutes'),
+			'1800'=>'30 '.Kohana::lang('ui_admin.minutes'));
+
+		//Generate all timezones
+		$site_timezone_array = array();
+		$site_timezone_array[0] = Kohana::lang('ui_admin.server_time');
+		foreach (timezone_identifiers_list() as $timezone)
+		{
+			$site_timezone_array[$timezone] = $timezone;
+		}
+		$this->template->content->site_timezone_array = $site_timezone_array;
+
+
+		// Generate Available Locales
+		$locales = ush_locale::get_i18n(TRUE);
+		$this->template->content->locales_array = $locales;
+		$this->cache->set('locales', $locales, array('locales'), 604800);
+	}
+
+	/**
+	* Map Settings
+	*/
+	public function index($saved = false)
+	{
+		// Display all maps
+		$this->themes->api_url = Kohana::config('settings.api_url_all');
+
+		// Current Default Country
+		$current_country = Kohana::config('settings.default_country');
+
+		$this->template->content = new View('admin/settings/main');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+
+		// setup and initialize form field names
+		$form = array(
+			'default_map' => '',
+			'api_google' => '',
+			'api_live' => '',
+			'default_country' => '',
+			'multi_country' => '',
+			'default_lat' => '',
+			'default_lon' => '',
+			'default_zoom' => '',
+			'default_map_all' => '',
+			'allow_clustering' => '',
+			'default_map_all_icon' => '',
+			'default_map_all_icon_id' => '',
+			'delete_default_map_all_icon' => '',
+			'enable_timeline' => '',
+			'timeline_graph' => '', // HT: To choose graph type line or bar
+			'timeline_point_label' => '' // HT: Timeline graph point label
+		);
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = ($saved == 'saved');
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = Validation::factory($_POST)
+			    ->pre_filter('trim', TRUE)
+			    ->add_rules('default_country', 'required', 'numeric', 'length[1,4]')
+			    ->add_rules('multi_country', 'numeric', 'length[1,1]')
+			    ->add_rules('default_map', 'required', 'length[0,100]')
+			    ->add_rules('default_zoom','required','between[0,21]')		// Validate for maximum and minimum zoom values
+			    ->add_rules('default_lat','required','between[-85,85]')		// Validate for maximum and minimum latitude values
+			    ->add_rules('default_lon','required','between[-180,180]')		// Validate for maximum and minimum longitude values
+			    ->add_rules('allow_clustering','required','between[0,1]')
+			    ->add_rules('default_map_all','required', 'alpha_numeric', 'length[6,6]')
+			    ->add_rules('api_google', 'length[0,200]')
+			    ->add_rules('api_live', 'length[0,200]')
+				->add_rules('enable_timeline', 'numeric', 'length[1,1]')
+				->add_rules('timeline_graph', 'in_array[line, bar]') // HT: Timeline graph type
+				->add_rules('timeline_point_label', 'numeric', 'length[1,1]'); // HT: Timeline graph type
+
+			// Add rules for file upload
+			$files = Validation::factory($_FILES);
+			$files->add_rules('default_map_all_icon', 'upload::valid', 'upload::type[gif,jpg,png]', 'upload::size[250K]');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate() AND $files->validate(FALSE))
+			{
+				// HT: Default timeline_graph if not in database
+				if(!Settings_Model::get_setting('timeline_graph'))
+					Settings_Model::save_setting('timeline_graph', $post->timeline_graph);
+				if(!Settings_Model::get_setting('timeline_point_label'))
+					Settings_Model::save_setting('timeline_point_label', $post->timeline_point_label);
+
+				// Save all the settings
+				Settings_Model::save_all($post);
+
+				// E.Kala 20th April 2012
+				// Ghetto workaround prevent resetting og Bing Maps API Key
+				// Soon to be addressed conclusively
+				if (isset($post['api_live']) AND ! empty($post['api_live']))
+				{
+					Settings_Model::save_setting('api_live', $post->api_live);
+				}
+
+
+				// Deal with default category icon now
+
+				// Check if deleting or updating a new image (or doing nothing)
+				if( isset($post->delete_default_map_all_icon) AND $post->delete_default_map_all_icon == 1)
+				{
+					// Delete old badge image
+					ORM::factory('media')->delete(Settings_Model::get_setting('default_map_all_icon_id'));
+
+					// Remove from DB table
+					Settings_Model::save_setting('default_map_all_icon_id', NULL);
+
+				}
+				else
+				{
+					// We aren't deleting, so try to upload if we are uploading an image
+					$filename = upload::save('default_map_all_icon');
+					if ($filename)
+					{
+						$new_filename = "default_map_all_".time();
+						$file_type = strrev(substr(strrev($filename),0,4));
+
+						// Large size
+						$l_name = $new_filename.$file_type;
+						Image::factory($filename)->save(Kohana::config('upload.directory', TRUE).$l_name);
+
+						// Medium size
+						$m_name = $new_filename."_m".$file_type;
+						Image::factory($filename)->resize(32,32,Image::HEIGHT)
+							->save(Kohana::config('upload.directory', TRUE).$m_name);
+
+						// Thumbnail
+						$t_name = $new_filename."_t".$file_type;
+						Image::factory($filename)->resize(16,16,Image::HEIGHT)
+							->save(Kohana::config('upload.directory', TRUE).$t_name);
+
+						// Name the files for the DB
+						$media_link = $l_name;
+						$media_medium = $m_name;
+						$media_thumb = $t_name;
+
+						// Okay, now we have these three different files on the server, now check to see
+						//   if we should be dropping them on the CDN
+
+						if (Kohana::config("cdn.cdn_store_dynamic_content"))
+						{
+							$media_link = cdn::upload($media_link);
+							$media_medium = cdn::upload($media_medium);
+							$media_thumb = cdn::upload($media_thumb);
+
+							// We no longer need the files we created on the server. Remove them.
+							$local_directory = rtrim(Kohana::config('upload.directory', TRUE), '/').'/';
+							unlink($local_directory.$l_name);
+							unlink($local_directory.$m_name);
+							unlink($local_directory.$t_name);
+						}
+
+						// Remove the temporary file
+						unlink($filename);
+
+						// Save image in the media table
+						$media = new Media_Model();
+						$media->media_type = 1; // Image
+						$media->media_link = $media_link;
+						$media->media_medium = $media_medium;
+						$media->media_thumb = $media_thumb;
+						$media->media_date = date("Y-m-d H:i:s",time());
+						$media->save();
+
+						// Save new image in settings
+						Settings_Model::save_setting('default_map_all_icon_id', $media->id);
+					}
+				}
+
+
+				// Delete Settings Cache
+				$this->cache->delete('settings');
+				$this->cache->delete_tag('settings');
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// Action::map_settings_modified - Map settings have changed
+				Event::run('ushahidi_action.map_settings_modified');
+
+				// Redirect to reload everything over again
+				url::redirect('admin/settings/index/saved');
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			// Retrieve Current Settings
+			$settings = Settings_Model::get_settings(array_keys($form));
+			$settings['timeline_graph'] = (isset($settings['timeline_graph'])) ? $settings['timeline_graph'] : 'line'; // HT: might not be in database so calling manually retrun NULL if not exist
+			$settings['timeline_point_label'] = (isset($settings['timeline_point_label'])) ? $settings['timeline_point_label'] : false; // HT: might not be in database so calling manually retrun NULL if not exist
+
+			$form = array(
+				'default_map' => $settings['default_map'],
+				'api_google' => $settings['api_google'],
+				'api_live' => $settings['api_live'],
+				'default_country' => $settings['default_country'],
+				'multi_country' => $settings['multi_country'],
+				'default_lat' => $settings['default_lat'],
+				'default_lon' => $settings['default_lon'],
+				'default_zoom' => $settings['default_zoom'],
+				'allow_clustering' => $settings['allow_clustering'],
+				'default_map_all' => $settings['default_map_all'],
+				'default_map_all_icon_id' => $settings['default_map_all_icon_id'],
+				'enable_timeline' => $settings['enable_timeline'],
+				'timeline_graph' => $settings['timeline_graph'], // HT: Timeline graph type line or bar
+				'timeline_point_label' => $settings['timeline_point_label'] // HT: Timeline graph point label
+			);
+		}
+
+		// Get default category image
+		if (($default_map_all_icon_id = Settings_Model::get_setting('default_map_all_icon_id')) !== NULL)
+		{
+			$icon = ORM::factory('media')->find($default_map_all_icon_id);
+			$this->template->content->default_map_all_icon = url::convert_uploaded_to_abs($icon->media_link);
+			$this->template->content->default_map_all_icon_m = url::convert_uploaded_to_abs($icon->media_medium);
+			$this->template->content->default_map_all_icon_t = url::convert_uploaded_to_abs($icon->media_thumb);
+		}
+		else
+		{
+			$this->template->content->default_map_all_icon = NULL;
+			$this->template->content->default_map_all_icon_m = NULL;
+			$this->template->content->default_map_all_icon_t = NULL;
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+
+		// Get Countries
+		$countries = array();
+		foreach (ORM::factory('country')->orderby('country')->find_all() as $country)
+		{
+			// Create a list of all categories
+			$this_country = $country->country;
+			if (strlen($this_country) > 35)
+			{
+				$this_country = substr($this_country, 0, 30) . "...";
+			}
+			$countries[$country->id] = $this_country;
+		}
+		$this->template->content->countries = $countries;
+
+		// Zoom Array for Slider
+		$default_zoom_array = array();
+
+		for ($i=Kohana::config('map.minZoomLevel'); $i<Kohana::config('map.minZoomLevel')+Kohana::config('map.numZoomLevels') ; $i++)
+		{
+			$default_zoom_array[$i] = $i;
+		}
+		$this->template->content->default_zoom_array = $default_zoom_array;
+
+		// Get Map API Providers
+		$layers = map::base();
+		$map_array = array();
+		foreach ($layers as $layer)
+		{
+			$map_array[$layer->name] = $layer->title;
+		}
+		$this->template->content->map_array = $map_array;
+
+		$this->template->content->yesno_array = array(
+			'1'=>utf8::strtoupper(Kohana::lang('ui_main.yes')),
+			'0'=>utf8::strtoupper(Kohana::lang('ui_main.no')));
+
+		// Javascript Header
+		$this->themes->map_enabled = TRUE;
+		$this->themes->colorpicker_enabled = TRUE;
+		$this->themes->js = new View('admin/settings/settings_js');
+		$this->themes->js->default_map = $form['default_map'];
+		$this->themes->js->default_zoom = $form['default_zoom'];
+		$this->themes->js->default_lat = $form['default_lat'];
+		$this->themes->js->default_lon = $form['default_lon'];
+		$this->themes->js->all_maps_json = $this->_generate_settings_map_js();
+	}
+
+
+	/**
+	 * Handles SMS Settings
+	 */
+	public function sms()
+	{
+		$this->template->content = new View('admin/settings/sms');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+
+		// setup and initialize form field names
+		$form = array(
+			'sms_provider' => '',
+			'sms_no1' => '',
+			'sms_no2' => '',
+			'sms_no3' => '',
+			'sms_alert_url' => '' // HT: new setting for url on sms
+		);
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('sms_provider', 'length[1,100]');
+			$post->add_rules('sms_no1', 'numeric', 'length[1,30]');
+			$post->add_rules('sms_no2', 'numeric', 'length[1,30]');
+			$post->add_rules('sms_no3', 'numeric', 'length[1,30]');
+			$post->add_rules('sms_alert_url','required','between[0,1]'); // HT: new setting for url on sms
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				if(!Settings_Model::get_setting('sms_alert_url'))
+					Settings_Model::save_setting('sms_alert_url', $post->sms_alert_url);
+				// Yes! everything is valid
+				Settings_Model::save_all($post);
+
+				// Delete Settings Cache
+				$this->cache->delete('settings');
+				$this->cache->delete_tag('settings');
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			$settings = Settings_Model::get_settings(array_keys($form))
+			;
+			$settings['sms_alert_url'] = (isset($settings['sms_alert_url'])) ? $settings['sms_alert_url'] : 0; // HT: might not be in database so calling manually return NULL if not exist
+			// Retrieve Current Settings
+			$form = array(
+				'sms_provider' => $settings['sms_provider'],
+				'sms_no1' => $settings['sms_no1'],
+				'sms_no2' => $settings['sms_no2'],
+				'sms_no3' => $settings['sms_no3'],
+				'sms_alert_url' => $settings['sms_alert_url'] // HT: new setting for url on sms
+			);
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->alert_url_array = array('1'=>Kohana::lang('ui_admin.yes'),'0'=>Kohana::lang('ui_admin.no'));
+
+		$this->template->content->sms_provider_array = array_merge(
+			array("" => "-- Select One --"),
+			plugin::get_sms_providers()
+			);
+	}
+
+
+	/**
+	 * Email Settings
+	 */
+	public function email()
+	{
+		$this->template->content = new View('admin/settings/email');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+
+		// setup and initialize form field names
+		$form = array(
+			'email_username' => '',
+			'email_password' => '',
+			'email_port' => '',
+			'email_host' => '',
+			'email_servertype' => '',
+			'email_ssl' => ''
+		);
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('email_username', 'required', 'length[3,50]');
+			$post->add_rules('email_password', 'length[3,100]');
+			$post->add_rules('email_port', 'numeric[1,100]','length[1,20]');
+			$post->add_rules('email_host','required', 'length[3,100]');
+			$post->add_rules('email_servertype','required','length[3,100]');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// Yes! everything is valid
+				Settings_Model::save_all($post);
+
+				// Delete Settings Cache
+				$this->cache->delete('settings');
+				$this->cache->delete_tag('settings');
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			// Retrieve Current Settings
+			$settings = Settings_Model::get_settings(array_keys($form));
+
+			$form = array(
+				'email_username' => $settings['email_username'],
+				'email_password' => $settings['email_password'],
+				'email_port' => $settings['email_port'],
+				'email_host' => $settings['email_host'],
+				'email_servertype' => $settings['email_servertype'],
+				'email_ssl' => $settings['email_ssl']
+			);
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->email_ssl_array = array('1'=>Kohana::lang('ui_admin.yes'),'0'=>Kohana::lang('ui_admin.no'));
+
+		// Javascript Header
+		$this->themes->js = new View('admin/settings/email_js');
+	}
+
+		/**
+	 * Clean URLs settings
+	 */
+	public function cleanurl() {
+
+		// We cannot allow cleanurl settings to be changed if MHI is enabled since it modifies a file in the config folder
+		if (Kohana::config('config.enable_mhi') == TRUE)
+		{
+			throw new Kohana_User_Exception('Access Error', "Please contact the administrator in order to use this feature.");
+		}
+
+		$this->template->content = new View('admin/settings/cleanurl');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'enable_clean_url' => '',
+		);
+
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('enable_clean_url','required','between[0,1]');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// Yes! everything is valid
+
+				// Delete Settings Cache
+				$this->cache->delete('settings');
+				$this->cache->delete_tag('settings');
+
+				$this->_configure_index_page($post->enable_clean_url);
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+
+		}
+		else
+		{
+
+			$yes_or_no = $this->_check_clean_url_on_ushahidi() == TRUE ? 1 : 0;
+			// initialize form
+			$form = array
+			(
+				'enable_clean_url' => $yes_or_no,
+			);
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->yesno_array = array('1'=>utf8::strtoupper(Kohana::lang('ui_main.yes')),'0'=>utf8::strtoupper(Kohana::lang('ui_main.no')));
+		$this->template->content->is_clean_url_enabled = $this->_check_for_clean_url();
+
+	}
+
+	/**
+	 * HTTPS settings
+	 */
+	public function https()
+	{
+		// We cannot allow cleanurl settings to be changed if MHI is enabled since it modifies a file in the config folder
+		if (Kohana::config('config.enable_mhi') == TRUE)
+		{
+			throw new Kohana_User_Exception('Access Error', "Please contact the administrator in order to use this feature.");
+		}
+
+		$this->template->content = new View('admin/settings/https');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'enable_https' => '',
+		);
+
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('enable_https','required','between[0,1]');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// Yes! everything is valid
+
+				// Delete Settings Cache
+				$this->cache->delete('settings');
+				$this->cache->delete_tag('settings');
+
+				$this->_configure_https_mode($post->enable_https);
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+
+		}
+		else
+		{
+
+			$yes_or_no = $this->_is_https_enabled() == TRUE ? 1 : 0;
+			// initialize form
+			$form = array
+			(
+				'enable_https' => $yes_or_no,
+			);
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->yesno_array = array('1'=>utf8::strtoupper(Kohana::lang('ui_main.yes')),'0'=>utf8::strtoupper(Kohana::lang('ui_main.no')));
+		$this->template->content->is_https_capable = $this->_is_https_capable();
+	}
+
+	/**
+	 * Retrieves cities listing using GeoNames Service
+	 *
+	 * @param int $country_id The id of the country to retrieve cities for
+	 * Returns a JSON response
+	 */
+	public function update_cities($country_id = 0)
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+
+		// Get country ISO code from DB
+		$country = ORM::factory('country', (int) $country_id);
+
+		// No. of cities fetched
+		$entries = 0;
+
+		// Default payload to be returned to client as a JSON object
+		$status_response = array(
+			"status" => "error",
+
+			// Default response message
+			"response" => sprintf("%d %s %s", $entries, Kohana::lang('ui_admin.cities_loaded'),
+			                        Kohana::lang('ui_admin.country_not_found'))
+		);
+
+		if ($country->loaded)
+		{
+			$base_url = "http://overpass-api.de/api/";
+
+			// Get the cities within the country info
+			// Limited to 1000 items to avoid query time out
+			/* This URL runs an Overpass API request using OverpassQL similar to:
+				[out:json][timeout:300];
+				area["admin_level"="2"]["name:en"~"^Germany"];
+				(
+				  node["place"="city"](area);
+				);
+				out body 1000;
+			*/
+			$cities_url = $base_url . "interpreter?data=" .
+			urlencode(
+				'[out:json][timeout:300];area["admin_level"="2"]["name:en"~"^'.$country->country.'"];(node["place"="city"](area););out body 1000;'
+			);
+
+			// Fetch the cities
+			$cities_client = new HttpClient($cities_url, 300);
+			if (($response = $cities_client->execute()) !== FALSE)
+			{
+				// Decode the JSON responce
+				$response = json_decode($response);
+
+				$cities = isset($response->elements)
+				    ? $response->elements
+				    : array();
+
+				// Only proceed if cities are returned
+				if (count($cities) > 0)
+				{
+					// Set the city count for the country
+					$country->cities = count($cities);
+					$country->save();
+
+					// Delete all the cities for the current country
+					ORM::factory('city')
+					    ->where('country_id', $country->id)
+					    ->delete_all();
+
+					// Manually construct the query (DB library can't do bulk inserts)
+					$query = sprintf("INSERT INTO %scity (`country_id`, `city`, `city_lat`, `city_lon`) VALUES ", $this->table_prefix);
+
+					$values = array();
+					// Create a database expression and use that to sanitize values
+					$values_expr = new Database_Expression("(:countryid, :city, :lat, :lon)");
+
+					// Add the freshly fetched cities
+					foreach ($cities as $city)
+					{
+						// Skip nameless nodes or nodes with lat/lon
+						if (!isset($city->tags->name) OR ! $city->lat OR ! $city->lon) continue;
+
+						$values_expr->param(':countryid', $country->id);
+						$values_expr->param(':city', $city->tags->name);
+						$values_expr->param(':lat', $city->lat);
+						$values_expr->param(':lon', $city->lon);
+						$values[] = $values_expr->compile();
+					}
+
+					$query .= implode(",", $values);
+
+					// Batch insert
+					Database::instance()->query($query);
+
+					$entries = count($cities);
+				}
+
+				// Set the response payload
+				$status_response['status'] = "success";
+				$status_response['response'] = sprintf("%d %s", $entries,
+				    Kohana::lang('ui_admin.cities_loaded'));
+			}
+			else
+			{
+				// Geonames timeout
+				$status_response['response'] = Kohana::lang('ui_admin.timeout');
+			}
+		}
+
+		echo json_encode($status_response);
+	}
+
+
+	/**
+	 * Check if clean url can be enabled on the server so
+	 * Ushahidi can cough it.
+	 *
+	 * @return boolean
+	 */
+
+	private function _check_for_clean_url() {
+
+		$url = url::base() .'reports/';
+
+		$request = new HttpClient($url);
+		$return_code = $request->get_http_response_code();
+
+		return ($return_code == 404)? FALSE : TRUE;
+	}
+
+	/**
+	 * Removes / Adds index.php from / to index page variable in application/config.config.php file
+	 *
+	 * @param $yes_or_no
+	 */
+	private function _configure_index_page( $yes_or_no ) {
+
+		$config_file = @file('application/config/config.php');
+		$handle = @fopen('application/config/config.php', 'w');
+
+		if(is_array($config_file) )
+		{
+			foreach ($config_file as $line_number => $line)
+			{
+				if ($yes_or_no == 1)
+				{
+					if( strpos(" ".$line,"\$config['index_page'] = 'index.php';") != 0 )
+					{
+						fwrite($handle, str_replace("index.php","",$line ));
+
+						// Set the 'index_page' property in the configuration
+						Kohana::config_set('core.index_page', '');
+					}
+					else
+					{
+						fwrite($handle, $line);
+					}
+
+				}
+				else
+				{
+					if( strpos(" ".$line,"\$config['index_page'] = '';") != 0 )
+					{
+						fwrite($handle, str_replace("''","'index.php'",$line ));
+
+						// Set the 'index_page' property in the configuration
+						Kohana::config_set('core.index_page', 'index.php');
+					}
+					else
+					{
+						fwrite($handle, $line);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Check if clean URL is enabled on Ushahidi
+	 */
+	private function _check_clean_url_on_ushahidi()
+	{
+		$config_file = @file_get_contents('application/config/config.php');
+
+		return (strpos( $config_file,"\$config['index_page'] = 'index.php';") != 0 )
+			? FALSE
+			: TRUE;
+	}
+
+	private function _generate_settings_map_js()
+	{
+		$map_layers = array();
+		$layers = map::base();
+
+		foreach ($layers as $layer)
+		{
+			$map_layers[$layer->name] = array();
+			$map_layers[$layer->name]['title'] = $layer->title;
+			$map_layers[$layer->name]['openlayers'] = $layer->openlayers;
+			if (isset($layer->api_signup))
+			{
+				$map_layers[$layer->name]['api_signup'] = $layer->api_signup;
+			}
+			else
+			{
+				$map_layers[$layer->name]['api_signup'] = "";
+			}
+		}
+
+		return json_encode($map_layers);
+	}
+
+	/**
+	 * Check if SSL is currently enabled on the instance
+	 */
+	private function _is_https_enabled()
+	{
+		$config_file = @file_get_contents('application/config/config.php');
+
+		return (strpos( $config_file,"\$config['site_protocol'] = 'http';") != 0 )
+			? FALSE
+			: TRUE;
+	}
+
+	/**
+	 * Check if the Webserver is HTTPS capable
+	 */
+	private function _is_https_capable()
+	{
+		// Get the current site protocol
+		$protocol = Kohana::config('core.site_protocol');
+
+		// Build an SSL URL
+		$url = ($protocol == 'https')? url::base() : str_replace('http://', 'https://', url::base());
+
+		$url .= 'index.php';
+
+		// Initialize cURL
+		$ch = curl_init();
+
+		// Set cURL options
+		curl_setopt($ch, CURLOPT_URL, $url);
+
+		// Disable following any "Location:" sent as part of the HTTP header
+		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
+
+		// Return the output of curl_exec() as a string instead of outputting it directly
+		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
+
+		// Suppress header information from the output
+		curl_setopt($ch, CURLOPT_HEADER, FALSE);
+
+		// Allow connection to HTTPS
+		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+
+		// Perform cURL session
+		curl_exec($ch);
+
+		// Get the cURL error number
+		$error_no = curl_errno($ch);
+
+		// Get the return code
+		$http_return_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+
+		// Close the cURL handle
+		curl_close($ch);
+
+		// Check if the cURL session succeeded
+		return (($error_no > 0 AND $error_no != 60) OR $http_return_code == 404)
+			? FALSE
+			: TRUE;
+	}
+
+	/**
+	 * Configures the HTTPS mode for the Ushahidi instance
+	 *
+	 * @param int $yes_or_no
+	 */
+	private function _configure_https_mode($yes_or_no)
+	{
+		$config_file = @file('application/config/config.php');
+		$handle = @fopen('application/config/config.php', 'w');
+
+		if(is_array($config_file) AND $handle)
+		{
+			foreach ($config_file as $line_number => $line)
+			{
+				if ($yes_or_no == 1)
+				{
+					if( strpos(" ".$line,"\$config['site_protocol'] = 'http';") != 0 )
+					{
+						fwrite($handle, str_replace("http", "https", $line ));
+
+						// Enable HTTPS on the config
+						Kohana::config_set('core.site_protocol', 'https');
+					}
+					else
+					{
+						fwrite($handle, $line);
+					}
+				}
+				else
+				{
+					if( strpos(" ".$line,"\$config['site_protocol'] = 'https';") != 0 )
+					{
+						fwrite($handle, str_replace("https", "http", $line ));
+
+						// Enable HTTP on the config
+						Kohana::config_set('core.site_protocol', 'http');
+					}
+					else
+					{
+						fwrite($handle, $line);
+					}
+				}
+			}
+		}
+
+	}
+}
diff --git a/application/controllers/admin/settings/api.php b/application/controllers/admin/settings/api.php
new file mode 100644
index 0000000..e089742
--- /dev/null
+++ b/application/controllers/admin/settings/api.php
@@ -0,0 +1,354 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+
+/**
+ * This controller is used to manage API logging
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Api_Controller extends Admin_Controller {
+    
+    public function __construct()
+    {
+        parent::__construct();
+        
+        $this->template->this_page = 'settings';
+        if ( ! $this->auth->has_permission("manage"))
+        {
+            url::redirect(url::site().'admin/dashboard');
+        }
+    }
+    
+    /**
+     * API Logging settings
+     */
+    public function index()
+    {
+        $this->template->content = new View('admin/settings/api/main');
+
+        // Set up and initialize form field names
+        $form = array
+        (
+            'api_default_record_limit' => '',
+            'api_max_record_limit' => '',
+            'api_max_requests_per_ip_address' => '',
+            'api_max_requests_quota_basis' => ''
+        );
+
+        // Copy the form as errors, so the errors will be stored with keys
+        // corresponding to the form field names
+        $errors = $form;
+        $form_error = FALSE;
+        $form_saved = FALSE;
+
+        // Check if the form has been submitted, if so setup validation
+        if ($_POST)
+        {
+            $post = new Validation($_POST);
+
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+
+            // Add validation rules
+            // All values must be positive values; no (-ve) values are allowed
+            $post->add_rules('api_default_record_limit', 'required', 'numeric', 'length[1,20]')
+                 ->add_rules('api_max_record_limit', 'numeric', 'length[0,20]')
+                 ->add_rules('api_max_requests_per_ip_address', 'depends_on[api_max_requests_quota_basis]', 'numeric', 'length[0,20]')
+                 ->add_rules('api_max_requests_quota_basis', 'depends_on[api_max_requests_per_ip_address]', 'numeric', 'between[0,1]');
+
+            // Test to see if rule checks have beens satisfied
+            if ($post->validate() AND $post->action == 's')
+            {
+                // Check if the maximum record limit is less than the default
+                if (isset($post->api_max_record_limit) AND strlen($post->api_max_record_limit > 0))
+                {
+                    if ((int) $post->api_default_record_limit > (int) $post->api_max_record_limit)
+                    {
+                        $errors[] = Kohana::lang('ui_admin.api_invalid_max_record_limit');
+                        $form_error = TRUE;
+                    }
+                }
+                
+                // Proceed with saving if there's no form error
+                if ( ! $form_error)
+                {
+                    // Everything is valid
+                    $api_settings = new Api_Settings_Model(1);
+                    
+                    $api_settings->default_record_limit = ((int) $post->api_default_record_limit > 0)
+                        ? $post->api_default_record_limit
+                        : (int) Kohana::config('settings.items_per_api_request');
+                        
+                    $api_settings->max_record_limit = $post->api_max_record_limit;
+                    $api_settings->max_requests_per_ip_address = $post->api_max_requests_per_ip_address;
+                    
+                    // Only set the quota basis if the max. no of API requests per IP has been specified
+                    $api_settings->max_requests_quota_basis = ((int) $post->api_max_requests_per_ip_address > 0)
+                        ? $post->api_max_requests_quota_basis
+                        : NULL;
+                    
+                    $api_settings->modification_date = date("Y-m-d H:i:s", time());
+                    $api_settings->save();
+                
+                    $form_saved = TRUE;
+                
+                    // Repopulate the form fields
+                    $form = arr::overwrite($form, $post->as_array());
+                }
+            }
+            // There are validation errors
+            else
+            {
+                // Re-populate the form fields
+                $form = arr::overwrite($form, $post->as_array());
+                
+                // Populate the error fields if any
+                $errors = arr::overwrite($errors, $post->errors('api_settings'));
+                $form_error = TRUE;
+            }
+        }
+        else
+        {
+            // Retrieve current settings
+            $api_settings = ORM::factory('api_settings', 1);
+            
+            $form = array
+            (
+                'api_default_record_limit' => ((int) $api_settings->default_record_limit > 0)
+                    ? $api_settings->default_record_limit 
+                    : Kohana::config('settings.items_per_api_request'),
+                'api_max_record_limit' => $api_settings->max_record_limit,
+                'api_max_requests_per_ip_address' => $api_settings->max_requests_per_ip_address,
+                'api_max_requests_quota_basis' => $api_settings->max_requests_quota_basis
+            );
+        }
+
+        // Set the form data
+        $this->template->content->form = $form;
+        
+        // Set the form errors
+        $this->template->content->errors = $errors;
+        
+        // Set the status of the form
+        $this->template->content->form_error = $form_error;
+        $this->template->content->form_saved = $form_saved;
+        
+        // API request quota options (per day, month)
+        $this->template->content->max_requests_quota_array = array(
+            '' => '-- Select --',
+            '0' => Kohana::lang('ui_main.day'), 
+            '1' => Kohana::lang('ui_main.month')
+        );
+        
+        // Javascript header
+        $this->themes->js = new View('admin/settings/api/api_js');
+    }
+    
+    /**
+     * Displays the API logs
+     */
+    public function log()
+    {
+        $this->template->content = new View('admin/settings/api/logs');
+        $this->template->content->this_page='apilogs';
+        $this->template->content->title = Kohana::lang('ui_main.api_logs');
+        
+        $form_error = FALSE;
+        $form_saved = FALSE;
+        $form_action = "";
+        
+        // Check if the form has been submitted
+        if ($_POST)
+        {
+            $post = Validation::factory($_POST);
+            
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+            
+            // Add some rules
+            $post->add_rules('action', 'required', 'alpha', 'length[1,1]');
+            $post->add_rules('api_log_id.*', 'required', 'numeric');
+            
+            // Validate the submitted data against the validation rules
+            if ($post->validate())
+            {
+                if ($post->action == 'd') // Delete action
+                {
+                    foreach ($post->api_log_id as $item)
+                    {
+                        $update = new Api_Log_Model($item);
+                        if ($update->loaded == true)
+                        {
+                            $update->delete();
+                        }
+                    }
+                    $form_action = "DELETED";
+                }
+                elseif ($post->action == 'x') // Delete all logs action
+                {
+                    ORM::factory('api_log')->delete_all();
+                    $form_action = "DELETED";
+                }
+                elseif ($post->action == 'b')
+                {
+                    foreach ($post->api_log_id as $item)
+                    {
+                        $log_item = new Api_Log_Model($item);
+                        if ($log_item->loaded == true)
+                        {
+                            // Get the IP Address associated with the specified api_log id
+                            $log_ip_address = $log_item->api_ipaddress;
+                            
+                            // Check if the IP address has already been banned
+                            $banned_count = ORM::factory('api_banned')
+                                    ->where('banned_ipaddress', $log_ip_address)
+                                    ->count_all();
+                            
+                            if ($banned_count == 0)
+                            {
+                                // Add the IP to the list of banned addresses                            
+                                $api_banned = new Api_Banned_Model();
+                                $api_banned->banned_ipaddress = $log_ip_address;
+                                $api_banned->banned_date = date('Y-m-d H:i:s', time());
+                                $api_banned->save();
+                            }
+                        }
+                    }
+                    $form_action = "BANNED";
+                }
+                $form_saved = TRUE;
+            }
+            else
+            {
+                $form_error = TRUE;
+            }
+        }
+        // END form submission check
+
+        // Set up pagination
+        $pagination = new Pagination(array(
+            'query_string' => 'page',
+            'items_per_page' => (int)Kohana::config('settings.items_per_page_admin'),
+            'total_items' => ORM::factory('api_log')->count_all()
+        ));
+        
+        // Fetch the api logs and page them
+        $api_logs = $this->db->query('
+                SELECT al.id, al.api_task, ab.id AS ban_id, al.api_parameters, al.api_records, al.api_ipaddress, al.api_date 
+                FROM '.$this->table_prefix.'api_log al
+                LEFT JOIN '.$this->table_prefix.'api_banned AS ab ON (ab.banned_ipaddress = al.api_ipaddress)
+                ORDER BY al.api_date DESC
+                LIMIT ?, ?', $pagination->sql_offset, $this->items_per_page
+            );
+        
+        /*    
+        $api_logs = ORM::factory('api_log')
+                        ->orderby('api_date', 'asc')
+                        ->find_all($this->items_per_page, $pagination->sql_offset);
+        */
+        
+        // Set the total no. of items
+        $this->template->content->total_items = ORM::factory('api_log')->count_all();
+        
+        // Set the form action
+        $this->template->content->form_action = $form_action;
+        
+        $this->template->content->form_error = $form_error;
+        $this->template->content->form_saved = $form_saved;
+        $this->template->content->api_logs = $api_logs;
+        $this->template->content->pagination = $pagination;
+        
+        // Javascript header
+        $this->themes->js = new View('admin/settings/api/logs_js');
+    }
+    
+    /**
+     * Displays the list of IP addresses that have been banned from access the API
+     */
+    public function banned()
+    {
+        $this->template->content = new View('admin/settings/api/banned');
+        $this->template->content->this_page = 'apibanned';
+        $this->template->content->title = Kohana::lang('ui_main.api_banned');
+        
+        $form_error = FALSE;
+        $form_saved = FALSE;
+        $form_action = "";
+        
+        // Check if the form has been submitted
+        if ($_POST)
+        {
+            $post = Validation::factory($_POST);
+            
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+            
+            // Add some validation rules
+            $post->add_rules('action', 'required', 'alpha', 'length[1,1]');
+            $post->add_rules('api_banned_id.*', 'required', 'numeric');
+            
+            // Validate the submitted data against the validatieon rules
+            if ($post->validate())
+            {
+                if ($post->action == 'd') // Uban action
+                {
+                    foreach ($post->api_banned_id as $item)
+                    {
+                        $update = new Api_Banned_Model($item);
+                        if ($update->loaded == true)
+                        {
+                            $update->delete();
+                        }
+                    }
+                    $form_action = "UNBANNED";
+                }
+                elseif ($post->action == 'x') // Unban all IP addresses
+                {
+                    ORM::factory('api_banned')->delete_all();
+                    $form_action = "UNBANNED";
+                }
+                $form_saved = TRUE;
+            }
+            else // Validation failed
+            {
+                $form_error = TRUE;
+            }
+        }
+        // END form submission check
+
+        // Set up pagination
+        $pagination = new Pagination(array(
+            'query_string' => 'page',
+            'items_per_page' => $this->items_per_page,
+            'total_items' => ORM::factory('api_banned')->count_all()
+        ));
+        
+        // Fetch all the IP addresses banned from accessing the API
+        $api_bans = ORM::factory('api_banned')
+                        ->orderby('banned_date', 'desc')
+                        ->find_all($this->items_per_page, $pagination->sql_offset);
+        
+        
+        // Set the total no. of items
+        $this->template->content->total_items = ORM::factory('api_banned')->count_all();
+        
+        // Set the form action
+        $this->template->content->form_action = $form_action;
+        
+        $this->template->content->form_error = $form_error;
+        $this->template->content->form_saved = $form_saved;
+        $this->template->content->api_bans = $api_bans;
+        $this->template->content->pagination = $pagination;
+        
+        // Javascript header
+        $this->themes->js = new View('admin/settings/api/banned_js');
+    }
+}
diff --git a/application/controllers/admin/settings/externalapps.php b/application/controllers/admin/settings/externalapps.php
new file mode 100644
index 0000000..d8fd315
--- /dev/null
+++ b/application/controllers/admin/settings/externalapps.php
@@ -0,0 +1,105 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+
+/**
+ * This controller is used to manage External Application Settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Externalapps_Controller extends Admin_Controller {
+
+	function index()
+	{
+		$this->template->content = new View('admin/settings/externalapps/main');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'id' => '',
+			'name' => '',
+			'url' => ''
+		);
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('name', 'length[1,255]');
+			$post->add_rules('url', 'length[1,255]');
+			
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// TODO: Save External App Here
+				
+				if ($post->action == 'a')
+				{
+					$app = new Externalapp_Model();
+					$app->id = $post->id;
+					$app->name = $post->name;
+					$app->url = $post->url;
+					$app->save();
+				}
+				elseif ($post->action == 'd')
+				{
+					$app = new Externalapp_Model($post->id);
+					$app->delete();
+				}
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+		}
+		
+		// Grab all of the external apps from the database
+		$this->template->content->externalapps = ORM::factory('externalapp')->find_all();
+		$this->template->content->total_items = count($this->template->content->externalapps);
+		
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		
+		// Javascript Header
+		$this->themes->js = new View('admin/settings/externalapps/externalapps_js');
+	}
+	
+}
diff --git a/application/controllers/admin/settings/facebook.php b/application/controllers/admin/settings/facebook.php
new file mode 100644
index 0000000..e9334d3
--- /dev/null
+++ b/application/controllers/admin/settings/facebook.php
@@ -0,0 +1,100 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+
+/**
+ * This controller is used to manage Facebook Settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Facebook_Controller extends Admin_Controller {
+
+	/**
+	 * Handles SMS Settings
+	 */
+	function index()
+	{
+		$this->template->content = new View('admin/settings/facebook/main');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'facebook_appid' => '',
+			'facebook_appsecret' => ''
+		);
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('facebook_appid', 'length[1,150]');
+			$post->add_rules('facebook_appsecret', 'length[1,150]');
+			
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// Yes! everything is valid
+				Settings_Model::save_setting('facebook_appid', $post->facebook_appid);
+				Settings_Model::save_setting('facebook_appsecret', $post->facebook_appsecret);
+
+				// Delete Settings Cache
+				$this->cache->delete('settings');
+				$this->cache->delete_tag('settings');
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			$form = array
+			(
+				'facebook_appid' => Settings_Model::get_setting('facebook_appid'),
+				'facebook_appsecret' => Settings_Model::get_setting('facebook_appsecret')
+			);
+		}
+		
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+	}
+	
+}
diff --git a/application/controllers/admin/settings/test_email.php b/application/controllers/admin/settings/test_email.php
new file mode 100644
index 0000000..440e5eb
--- /dev/null
+++ b/application/controllers/admin/settings/test_email.php
@@ -0,0 +1,66 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Test Email Controller
+ * Tests for IMAP Library
+ * Tests pulling email via pop3 or imap
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Test_Email_Controller extends Admin_Controller {
+
+	function __construct()
+	{
+		parent::__construct();
+	}
+
+	public function index()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+
+		// First is IMAP PHP Library Installed?
+		if (extension_loaded('imap'))
+		{
+			// If SSL Enabled
+			$ssl = Kohana::config('settings.email_ssl') == true ? "/ssl" : "";
+
+			// Do not validate certificates (TLS/SSL server)
+			//$novalidate = strtolower(Kohana::config('settings.email_servertype')) == "imap" ? "/novalidate-cert" : "";
+			$novalidate = "/novalidate-cert";
+
+			// If POP3 Disable TLS
+			$notls = strtolower(Kohana::config('settings.email_servertype')) == "pop3" ? "/notls" : "";
+
+			$service = "{".Kohana::config('settings.email_host').":"
+				.Kohana::config('settings.email_port')."/"
+				.Kohana::config('settings.email_servertype')
+				.$notls.$ssl.$novalidate."}";
+
+			// Connected!
+			if (@imap_open($service, Kohana::config('settings.email_username')
+				,Kohana::config('settings.email_password'), 0, 1))
+			{
+				echo json_encode(array("status"=>"success", "message"=>Kohana::lang('ui_main.success')));
+			}
+			// Connection Failed!
+			else
+			{
+				echo json_encode(array("status"=>"error", "message"=>Kohana::lang('ui_main.error')." - ".imap_last_error()));
+			}
+		}
+		// IMAP Not installed
+		else
+		{
+			echo json_encode(array("status"=>"error", "message"=>Kohana::lang('ui_main.error')." - ".Kohana::lang('ui_admin.error_imap')));
+		}
+	}
+}
diff --git a/application/controllers/admin/settings/test_sms.php b/application/controllers/admin/settings/test_sms.php
new file mode 100644
index 0000000..e69de29
diff --git a/application/controllers/admin/settings/test_twitter.php b/application/controllers/admin/settings/test_twitter.php
new file mode 100644
index 0000000..a512c32
--- /dev/null
+++ b/application/controllers/admin/settings/test_twitter.php
@@ -0,0 +1,52 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Test Twitter Controller
+ * Tests pulling tweets via twitteroaith
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Test_Twitter_Controller extends Admin_Controller {
+
+	function __construct()
+	{
+		parent::__construct();	
+	}
+
+	public function index()
+	{
+		$this->template = "";
+		$this->auto_render =FALSE;
+		
+		// grab the necessary keys consumer key, secret, token, token secret
+		$consumer_key = Settings_Model::get_setting('twitter_api_key');
+		$consumer_secret = Settings_Model::get_setting('twitter_api_key_secret');
+		$oauth_token = Settings_Model::get_setting('twitter_token');
+		$oauth_token_secret =Settings_Model::get_setting('twitter_token_secret');
+		$_SESSION['access_token'] = array('oauth_token'=> $oauth_token,'oauth_token_secret' => $oauth_token_secret);
+		$access_token = $_SESSION['access_token'];
+
+		$connection = new Twitter_Oauth($consumer_key,$consumer_secret,$access_token['oauth_token'],$access_token['oauth_token_secret']);
+		$connection->decode_json = FALSE;
+		$connection->get('account/verify_credentials');
+		if ($connection->http_code == 200) 
+		{
+			echo json_encode(array("status"=>"success", "message"=>Kohana::lang('ui_main.success')));
+		}
+		else
+		{
+			echo json_encode(array("status"=>"error","message"=>Kohana::lang('ui_main.error')." - ".Kohana::lang('ui_admin.error_twitter')));
+		}
+				
+	}
+}
+
+?>
diff --git a/application/controllers/admin/settings/twitter.php b/application/controllers/admin/settings/twitter.php
new file mode 100644
index 0000000..903128e
--- /dev/null
+++ b/application/controllers/admin/settings/twitter.php
@@ -0,0 +1,116 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+
+/**
+ * This controller is used to manage Twitter Settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Twitter_Controller extends Admin_Controller {
+
+	/**
+	 * Handles twitter Settings
+	 */
+	function index()
+	{
+		$this->template->content = new View('admin/settings/twitter/main');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'twitter_api_key' => '',
+			'twitter_api_key_secret' => '',
+			'twitter_token' => '',
+			'twitter_token_secret' => '',
+			'twitter_hashtags' => ''
+		);
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST
+			// fields with our own things
+			$post = new Validation($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			$post->add_rules('twitter_api_key','required', 'length[1,150]');
+			$post->add_rules('twitter_api_key_secret','required', 'length[1,150]');
+			$post->add_rules('twitter_token','required', 'length[1,150]');
+			$post->add_rules('twitter_token_secret','required', 'length[1,150]');
+			$post->add_rules('twitter_hashtags', 'length[1,150]');
+			
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				// Yes! everything is valid
+				Settings_Model::save_setting('twitter_api_key', $post->twitter_api_key);
+				Settings_Model::save_setting('twitter_api_key_secret', $post->twitter_api_key_secret);
+				Settings_Model::save_setting('twitter_token', $post->twitter_token);
+				Settings_Model::save_setting('twitter_token_secret', $post->twitter_token_secret);
+				Settings_Model::save_setting('twitter_hashtags', $post->twitter_hashtags);
+
+				// Delete Settings Cache
+				$this->cache->delete('settings');
+				$this->cache->delete_tag('settings');
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+			}
+
+			// No! We have validation errors, we need to show the form again,
+			// with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('settings'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			$form = array
+			(
+				'twitter_api_key' => Settings_Model::get_setting('twitter_api_key'),
+				'twitter_api_key_secret' => Settings_Model::get_setting('twitter_api_key_secret'),
+				'twitter_token' => Settings_Model::get_setting('twitter_token'),
+				'twitter_token_secret' => Settings_Model::get_setting('twitter_token_secret'),
+				'twitter_hashtags' => Settings_Model::get_setting('twitter_hashtags')
+			);
+		}
+		
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/settings/twitter/twitter_js');
+
+	}
+	
+}
diff --git a/application/controllers/admin/stats.php b/application/controllers/admin/stats.php
new file mode 100644
index 0000000..e0b76c1
--- /dev/null
+++ b/application/controllers/admin/stats.php
@@ -0,0 +1,546 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used to manage users
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Stats_Controller extends Admin_Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'stats';
+
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("stats"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+    }
+
+	public function index()
+	{   
+		$this->template->content = new View('admin/stats/hits');
+		$this->template->content->title = Kohana::lang('ui_admin.statistics');
+
+		// Retrieve Current Settings
+		$stat_id = Settings_Model::get_setting('stat_id');
+
+		if ($stat_id === NULL OR $stat_id == 0)
+		{
+			$sitename = Settings_Model::get_setting('site_name');
+			$url = url::base();
+			$this->template->content->stat_id = Stats_Model::create_site( $sitename, $url );
+		}
+
+		// Show the hits page since stats are already set up
+		$this->hits();
+	}
+	
+	/**
+	 * Report statistics
+	 */
+	public function reports()
+	{
+		$this->template->content = new View('admin/stats/reports');
+		$this->template->content->title = Kohana::lang('ui_admin.statistics');
+
+		// Javascript Header
+		$this->themes->protochart_enabled = TRUE;
+		$this->themes->js = new View('admin/stats/stats_js');
+
+		$this->template->content->failure = '';
+
+		// Set the date range (how many days in the past from today?)
+		$range = 10000;
+		if (isset($_GET['range']))
+		{
+			$range = $this->input->xss_clean($_GET['range']);
+			$range = (intval($range) > 0)? intval($range) : 10000;
+		}
+
+		$this->template->content->range = $range;
+
+		// Get an arbitrary date range
+		$dp1 = (isset($_GET['dp1'])) ? $_GET['dp1'] : null;
+		$dp2 = (isset($_GET['dp2'])) ? $_GET['dp2'] : null;
+
+		// Report Data
+		$data = Stats_Model::get_report_stats(false,false,$range,$dp1,$dp2);
+
+		$reports_chart = new protochart;
+
+		// This makes the chart a delicious pie chart
+		$options = array(
+			'pies'=>array('show'=>'true')
+		);
+
+		// Grab category data
+		$cats = Category_Model::categories();
+
+		$this->template->content->category_data = $cats;
+
+		$report_data = array();
+		$colors = array();
+		$reports_per_cat = array();
+		
+		foreach ($data['category_counts'] as $category_id => $count)
+		{
+			// Verify if the array key $category_id exists before attempting to fetch
+			if (array_key_exists($category_id, $cats))
+			{
+				$category_name = $cats[$category_id]['category_title'];
+				$report_data[$category_name] = $count;
+            
+				$colors[$category_name] = (isset($cats[$category_id]['category_color']))
+					? $cats[$category_id]['category_color']
+					: 'FFFFFF';
+
+				foreach ($count as $c)
+				{             
+					// Count up the total number of reports per category
+					if ( ! isset($reports_per_cat[$category_id]))
+					{
+                        $reports_per_cat[$category_id] = 0;
+                    }
+
+					$reports_per_cat[$category_id] += $c;
+				}
+			}
+		}
+		asort($reports_per_cat, SORT_NUMERIC);
+		$reports_per_cat = array_reverse($reports_per_cat, TRUE);
+		
+		$this->template->content->num_categories = $data['total_categories'];
+		$this->template->content->reports_per_cat = $reports_per_cat;
+
+		$this->template->content->reports_chart = $reports_chart->chart('reports',$report_data,$options,$colors,350,350);
+
+		$this->template->content->verified = 0;
+		$this->template->content->unverified = 0;
+		$this->template->content->approved = 0;
+		$this->template->content->unapproved = 0;
+
+		$report_status_chart = new protochart;
+		$report_staus_data = array();
+        
+        foreach ($data['verified_counts'] as $ver_or_un => $arr)
+        {
+            if ( ! isset($report_staus_data[$ver_or_un][0]))
+            {
+                $report_staus_data[$ver_or_un][0] = 0;
+            }
+                
+            if ( ! isset($this->template->content->$ver_or_un))
+            {
+                $this->template->content->$ver_or_un = 0;
+            }
+            
+            foreach($arr as $count)
+            {
+                $report_staus_data[$ver_or_un][0] += $count;
+                $this->template->content->$ver_or_un += $count;
+            }
+        }
+        
+        $colors = array('verified'=>'0E7800','unverified'=>'FFCF00');
+        $this->template->content->report_status_chart_ver = $report_status_chart->chart('report_status_ver',$report_staus_data,$options,$colors,150,150);
+        
+        $report_staus_data = array();
+        
+		foreach ($data['approved_counts'] as $app_or_un => $arr)
+		{
+            if ( ! isset($report_staus_data[$app_or_un][0]))
+            {
+                $report_staus_data[$app_or_un][0] = 0;
+            }
+            
+            if ( ! isset($this->template->content->$app_or_un))
+            {
+                $this->template->content->$app_or_un = 0;
+            }
+            
+            foreach($arr as $count)
+            {
+                $report_staus_data[$app_or_un][0] += $count;
+                $this->template->content->$app_or_un += $count;
+            }
+        }
+        
+        $this->template->content->num_reports = $data['total_reports'];
+        
+        $colors = array('approved'=>'0E7800','unapproved'=>'FFCF00');
+        $this->template->content->report_status_chart_app = $report_status_chart->chart('report_status_app',$report_staus_data,$options,$colors,150,150);
+        
+        // Set the date
+        $this->template->content->dp1 = date('Y-m-d',$data['earliest_report_time']);
+        $this->template->content->dp2 = date('Y-m-d',$data['latest_report_time']);
+    }
+    
+    public function impact()
+    {
+        $this->template->content = new View('admin/stats/impact');
+        $this->template->content->title = Kohana::lang('ui_admin.statistics');
+        
+        // Javascript Header
+        $this->themes->raphael_enabled = TRUE;
+        $this->themes->js = new View('admin/stats/stats_js');
+        
+        $this->template->content->failure = '';
+        
+        // Set the date range (how many days in the past from today?)       
+        $range = (isset($_GET['range']) AND is_int($_GET['range'])) 
+            ? $_GET['range']
+            : 10000; // Get all reports so go back far into the past
+        
+        $this->template->content->range = $range;
+        
+        // Get an arbitrary date range
+        $dp1 = (isset($_GET['dp1'])) ? $_GET['dp1'] : null;
+        
+        $dp2 = (isset($_GET['dp2'])) ? $_GET['dp2'] : null;
+        
+        // Report Data
+        $data = Stats_Model::get_report_stats(false,true,$range,$dp1,$dp2);
+        
+        // If we failed to get hit data, fail.
+        if ( ! isset($data['category_counts']))
+        {
+            $this->template->content->num_reports = 0;
+            $this->template->content->num_categories = 0;
+            $this->template->impact_json = '';
+
+            $this->template->content->dp1 = $dp1;
+            $this->template->content->dp2 = $dp2;
+
+            return false;
+        }
+        
+        $json = array();
+        $use_log = '';
+        $json['buckets'] = array();
+        $cat_report_count = array();
+        $category_counter = array();
+        
+        foreach($data['category_counts'] as $timestamp => $count_array)
+        {
+            $line = array();
+            // If this number is greater than 0, we'll show the line
+            $display_test = 0;
+            foreach($count_array as $category_id => $count)
+            {
+                $category_counter[$category_id] = 1;
+                
+                // We aren't allowing 0s
+                if($count > 0)
+                {
+                    $line[] = array($category_id, $count);
+                    
+                    $display_test += $count;
+                    
+                    // If we see a count over 50 (picked this arbitrarily), then switch to log format
+                    if($count > 50) $use_log = 1;
+                    
+                    // Count the number of reports so we have something useful to show in the legend
+                    if ( ! isset($cat_report_count[$category_id])) $cat_report_count[$category_id] = 0;
+                    $cat_report_count[$category_id] += $count;
+                }
+            }
+            if ($display_test > 0)
+            {
+                $json['buckets'][] = array(
+                  'd' => $timestamp,
+                  'i' => $line
+                );
+            }
+        }
+        
+        $this->template->content->num_reports = $data['total_reports'];
+        $this->template->content->num_categories = $data['total_categories'];
+        
+        $json['use_log'] = $use_log;
+        $json['categories'] = array();
+        
+        // Grab category data
+        $cats = Category_Model::categories();
+        
+        foreach ($cats as $category_id => $cat_array)
+        {
+            $report_count = 0;
+            if (isset($cat_report_count[$category_id]))
+            { 
+                $report_count = $cat_report_count[$category_id];
+            }
+            
+            $json['categories'][$category_id] = array(
+              "name" => $cat_array['category_title'],
+              "fill" => '#'.$cat_array['category_color'],
+              "reports" => $report_count
+            );
+        }
+
+        $this->themes->impact_json = json_encode($json);
+
+        // Set the date
+        $this->template->content->dp1 = date('Y-m-d',$data['earliest_report_time']);
+        $this->template->content->dp2 = date('Y-m-d',$data['latest_report_time']);
+        
+    }
+    
+	public function hits()
+    {
+        $this->template->content = new View('admin/stats/hits');
+        $this->template->content->title = Kohana::lang('ui_admin.statistics');
+        
+        // Javascript Header
+        $this->themes->protochart_enabled = TRUE;
+        $this->themes->js = new View('admin/stats/stats_js');
+        
+        $this->template->content->failure = '';
+        
+        // Set the date range (how many days in the past from today?)
+        $range = (isset($_GET['range'])) ? $_GET['range'] : 30;
+        $this->template->content->range = $range;
+        
+        // Get an arbitrary date range
+        $dp1 = (isset($_GET['dp1'])) ? $_GET['dp1']: null;
+        $dp2 = (isset($_GET['dp2'])) ? $_GET['dp2']: null;
+        
+        // Hit Data
+        $data = Stats_Model::get_hit_stats($range,$dp1,$dp2);
+        
+        $this->template->content->uniques = 0;
+        $this->template->content->visits = 0;
+        $this->template->content->pageviews = 0;
+        $this->template->content->active_tab = 'uniques';
+        
+        // Lazy tab switcher (not using javascript, just refreshing the page)
+        if (isset($_GET['active_tab']))
+        {
+            $this->template->content->active_tab = $_GET['active_tab'];
+        }
+        
+        // If we failed to get hit data, fail.
+        if ( ! $data)
+        {
+            $this->template->content->traffic_chart = Kohana::lang('ui_admin.chart_display_error');
+            $this->template->content->raw_data = array();
+            $this->template->content->dp1 = $dp1;
+            $this->template->content->dp2 = $dp2;
+            $this->template->content->failure = 'Stat Collection Failed! Either your stat_id or stat_key in the settings table in the database are incorrect or our stat server is down. Try back in a bit to see if the server is up and running. If you are really in a pinch, you can always modify stat_id (set to null) and stat_key (set to 0) in the settings table of your database to get your stats back up and running. Keep in mind you will lose access to your stats currently on the stats server.';
+            return false;
+        }
+        
+        $counts = array();
+        foreach ($data as $label => $data_array)
+        {
+            if ( ! isset($this->template->content->$label))
+            {
+                $this->template->content->$label = 0;
+            }
+            
+            foreach ($data_array as $timestamp => $count)
+            {
+                $this->template->content->$label += $count;
+            }
+        }
+        
+        $traffic_chart = new protochart;
+        $options = array(
+            'xaxis'=>array('mode'=>'"time"'),
+            'legend'=>array('show'=>'true')
+            );
+        $this->template->content->traffic_chart = $traffic_chart->chart('traffic',$data,$options,null,884,300);
+        $this->template->content->raw_data = $data;
+        
+        
+        // Set the date
+        reset($data['visits']);
+        $this->template->content->dp1 = date('Y-m-d',(key($data['visits'])/1000));
+        end($data['visits']);
+        $this->template->content->dp2 = date('Y-m-d',(key($data['visits'])/1000));
+    }
+    
+    function country()
+    {
+        $this->template->content = new View('admin/stats/country');
+        $this->template->content->title = Kohana::lang('ui_admin.statistics');
+        
+        // Javascript Header
+        $this->themes->js = new View('admin/stats/stats_js');
+        
+        $this->template->content->failure = '';
+        
+        // Set the date range (how many days in the past from today?)
+        $range = (isset($_GET['range'])) ? $_GET['range'] : 30;
+        
+        $this->template->content->range = $range;
+        
+        // Get an arbitrary date range
+        $dp1 = (isset($_GET['dp1'])) ? $_GET['dp1'] : null;
+        $dp2 = (isset($_GET['dp2'])) ? $_GET['dp2'] : null;
+        
+        $countries = Stats_Model::get_hit_countries($range,$dp1,$dp2);
+        
+        // If we failed to get country data, fail.
+        if(!$countries) {
+            $this->template->content->countries = array();
+            $this->template->content->num_countries = 0;
+            $this->template->content->dp1 = $dp1;
+            $this->template->content->dp2 = $dp2;
+            $this->template->content->visitor_map = '';
+            $this->template->content->uniques = 0;
+            $this->template->content->visits = 0;
+            $this->template->content->pageviews = 0;
+            $this->template->content->active_tab = 'uniques';
+            $this->template->content->failure = Kohana::lang('ui_admin.stats_collection_error_short');
+            return false;
+        }
+        
+        //Set up country map and totals
+        $country_total = array();
+        $countries_reformatted = array();
+        foreach($countries as $country)
+        {
+            foreach($country as $code => $arr)
+            {
+                if ( ! isset($country_total[$code])) $country_total[$code] = 0;
+                $country_total[$code] += $arr['uniques'];
+                
+                $name = $arr['label'];
+                if ( ! isset($countries_reformatted[$name]))
+                {
+                    $countries_reformatted[$name] = array();
+                }
+                
+                if ( ! isset($countries_reformatted[$name]['count']))
+                {
+                    $countries_reformatted[$name]['count'] = 0;
+                }
+                $countries_reformatted[$name]['count'] += $arr['uniques'];
+                $countries_reformatted[$name]['icon'] = $arr['logo'];
+            }
+        }
+        
+        arsort($countries_reformatted);
+        
+        $this->template->content->countries = $countries_reformatted;
+        
+        $this->template->content->num_countries = count($countries_reformatted);
+        
+        arsort($country_total);
+        
+        $codes = '';
+        $values = '';
+        $i = 0;
+        foreach($country_total as $code => $uniques)
+        {
+            if($i == 0) $highest = $uniques;
+            if($i != 0) $values .= ',';
+            $values .= ($uniques / $highest) * 100;
+            $codes .= strtoupper($code);
+            $i++;
+        }
+        
+        $this->template->content->visitor_map = Kohana::config('core.site_protocol')."://chart.googleapis.com/chart?chs=440x220&chf=bg,s,ffffff&cht=t&chtm=world&chco=cccccc,A07B7B,a20000&chld=".$codes."&chd=t:".$values;
+        
+        // Hit Data
+        $data = Stats_Model::get_hit_stats($range,$dp1,$dp2);
+        
+        $this->template->content->uniques = 0;
+        $this->template->content->visits = 0;
+        $this->template->content->pageviews = 0;
+        $this->template->content->active_tab = 'uniques';
+        
+        // Lazy tab switcher (not using javascript)
+        if (isset($_GET['active_tab']))
+        {
+            $this->template->content->active_tab = $_GET['active_tab'];
+        }
+        
+        // If we failed to get hit data, fail.
+        if ( ! $data)
+        {
+            $this->template->content->dp1 = $dp1;
+            $this->template->content->dp2 = $dp2;
+
+            return false;
+        }
+        
+        $counts = array();
+        foreach ($data as $label => $data_array)
+        {
+            if ( ! isset($this->template->content->$label))
+            {
+                $this->template->content->$label = 0;
+            }
+            
+            foreach ($data_array as $timestamp => $count)
+            {
+                $this->template->content->$label += $count;
+            }
+        }
+        
+        // Set the date
+        reset($data['visits']);
+        $this->template->content->dp1 = date('Y-m-d',(key($data['visits'])/1000));
+        end($data['visits']);
+        $this->template->content->dp2 = date('Y-m-d',(key($data['visits'])/1000));
+    }
+    
+    function punchcard()
+	{
+		$this->template->content = new View('admin/stats/punchcard');
+		$this->template->content->title = Kohana::lang('ui_admin.statistics');
+
+		$incident_dates = Incident_Model::get_incident_dates();
+
+		// Initialize the array. Zeroing everything out now to keep us from having to loop it
+
+		$data = array('sun'=>array(0=>0,1=>0,2=>0,3=>0,4=>0,5=>0,6=>0,7=>0,8=>0,9=>0,10=>0,
+								11=>0,12=>0,13=>0,14=>0,15=>0,16=>0,17=>0,18=>0,19=>0,
+								20=>0,21=>0,22=>0,23=>0),
+					  'mon'=>array(0=>0,1=>0,2=>0,3=>0,4=>0,5=>0,6=>0,7=>0,8=>0,9=>0,10=>0,
+								11=>0,12=>0,13=>0,14=>0,15=>0,16=>0,17=>0,18=>0,19=>0,
+								20=>0,21=>0,22=>0,23=>0),
+					  'tue'=>array(0=>0,1=>0,2=>0,3=>0,4=>0,5=>0,6=>0,7=>0,8=>0,9=>0,10=>0,
+								11=>0,12=>0,13=>0,14=>0,15=>0,16=>0,17=>0,18=>0,19=>0,
+								20=>0,21=>0,22=>0,23=>0),
+					  'wed'=>array(0=>0,1=>0,2=>0,3=>0,4=>0,5=>0,6=>0,7=>0,8=>0,9=>0,10=>0,
+								11=>0,12=>0,13=>0,14=>0,15=>0,16=>0,17=>0,18=>0,19=>0,
+								20=>0,21=>0,22=>0,23=>0),
+					  'thu'=>array(0=>0,1=>0,2=>0,3=>0,4=>0,5=>0,6=>0,7=>0,8=>0,9=>0,10=>0,
+								11=>0,12=>0,13=>0,14=>0,15=>0,16=>0,17=>0,18=>0,19=>0,
+								20=>0,21=>0,22=>0,23=>0),
+					  'fri'=>array(0=>0,1=>0,2=>0,3=>0,4=>0,5=>0,6=>0,7=>0,8=>0,9=>0,10=>0,
+					  			11=>0,12=>0,13=>0,14=>0,15=>0,16=>0,17=>0,18=>0,19=>0,
+					  			20=>0,21=>0,22=>0,23=>0),
+					  'sat'=>array(0=>0,1=>0,2=>0,3=>0,4=>0,5=>0,6=>0,7=>0,8=>0,9=>0,10=>0,
+					  			11=>0,12=>0,13=>0,14=>0,15=>0,16=>0,17=>0,18=>0,19=>0,
+					  			20=>0,21=>0,22=>0,23=>0));
+
+		$highest_value = 0;
+		foreach($incident_dates as $datetime)
+		{
+			$t = strtotime($datetime);
+			$dow = strtolower(date('D',$t));
+			$hour = date('G',$t);
+			$data[$dow][$hour] += 1;
+			if($data[$dow][$hour] > $highest_value)
+			{
+				$highest_value = $data[$dow][$hour];
+			}
+		}
+		$this->template->content->chart_url = Kohana::config('core.site_protocol').'://chart.googleapis.com/chart?chs=905x300&chds=-1,24,-1,7,0,'.$highest_value.'&chf=bg,s,efefef&chd=t:0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,0,1 [...]
+
+	}
+}
diff --git a/application/controllers/admin/upgrade.php b/application/controllers/admin/upgrade.php
new file mode 100644
index 0000000..6c960a4
--- /dev/null
+++ b/application/controllers/admin/upgrade.php
@@ -0,0 +1,736 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Messages Controller.
+ * View SMS Messages Received Via FrontlineSMS
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source .ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Upgrade_Controller extends Admin_Controller {
+
+	protected $db;
+	protected $upgrade;
+	protected $release;
+
+	function __construct()
+	{
+		parent::__construct();
+		
+		$this->db = new Database();
+		
+		$this->template->this_page = 'upgrade';
+		$this->upgrade = new Upgrade;
+		$this->release = $this->upgrade->_fetch_core_release();
+		
+		$release_version = $this->_get_release_version();
+		
+		// Don't show auto-upgrader when disabled.
+		if (Kohana::config('config.enable_auto_upgrader') == FALSE)
+		{
+			die(Kohana::lang('ui_main.disabled'));
+		}
+	}
+
+	/**
+	 * Upgrade page.
+	 *
+	 */
+	public function index()
+	{
+		$this->template->content = new View('admin/upgrade/upgrade');
+
+		$form_action = "";
+		
+		$this->template->content->title = Kohana::lang('ui_admin.upgrade_ushahidi');
+
+		//$this->template->content->db_version =  Kohana::config('
+		  //	  settings.db_version');
+		$this->template->content->db_version = Kohana::config('settings.db_version');
+
+		 //Setup and initialize form fields names
+		$form = array 
+		(
+			'chk_db_backup_box' => ''
+		);
+
+		//check if form has been submitted
+		if ( $_POST )
+		{
+			// For sanity sake, validate the data received from users.
+			$post = Validation::factory(array_merge($_POST,$_FILES));
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+			
+			$post->add_rules('chk_db_backup_box', 'between[0,1]');
+			
+			if ($post->validate())
+			{
+				$this->upgrade->logger("STARTED UPGRADE\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+				$this->template->content = new View('admin/upgrade/upgrade_status');
+				$this->themes->js = new View('admin/upgrade/upgrade_status_js');
+				$this->themes->js->backup = $post->chk_db_backup_box;
+				$this->template->content->title = Kohana::lang('ui_admin.upgrade_ushahidi_status');
+				
+				$this->session->set('ftp_server', $post->ftp_server);
+				$this->session->set('ftp_user_name', $post->ftp_user_name);
+				$this->session->set('ftp_user_pass', $post->ftp_user_pass);
+				
+				Settings_Model::save_setting('ftp_server', $post->ftp_server);
+				Settings_Model::save_setting('ftp_user_name', $post->ftp_user_name);
+				
+				// Log file location
+				$this->themes->js->log_file = url::site(). "admin/upgrade/logfile?f=".$this->session->get('upgrade_session').".txt";
+			}
+			 // No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				$this->themes->js = new View('admin/upgrade/upgrade_js');
+				
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = $post->errors('upgrade');
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			$this->themes->js = new View('admin/upgrade/upgrade_js');
+		}
+		
+		$this->template->content->ftp_server = Settings_Model::get_setting('ftp_server');
+		$this->template->content->ftp_user_name = Settings_Model::get_setting('ftp_user_name');
+		
+		$this->template->content->form_action = $form_action;
+		$this->template->content->current_version = Kohana::config('settings.ushahidi_version');
+		$this->template->content->current_db_version = Kohana::config('settings.db_version');
+		$this->template->content->environment = $this->_environment();
+		$this->template->content->release_version = (is_object($this->release) == true) ? $this->release->version : "";
+		$this->template->content->release_db_version = (is_object($this->release) == true) ? $this->release->version_db : "";
+		$this->template->content->changelogs = (is_object($this->release) == true) ? $this->release->changelog : array();
+		$this->template->content->download = (is_object($this->release) == true) ? $this->release->download : "";
+		$this->template->content->critical = (is_object($this->release) == true) ? $this->release->critical : "";
+	}
+		
+	public function status($step = 0) 
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		$url = $this->release->download;
+		$working_dir = Kohana::config('upload.relative_directory')."/upgrade/";
+		$zip_file = Kohana::config('upload.relative_directory')."/upgrade/ushahidi.zip";	
+		
+		if ($step == 0)
+		{
+			$this->upgrade->logger("Downloading latest version of ushahidi...");
+			echo json_encode(array("status"=>"success", "message"=> Kohana::lang('upgrade.download')));
+		}
+		
+		if ($step == 1)
+		{
+			// Create the Directory if it doesn't exist
+			if ( ! file_exists(DOCROOT."media/uploads/upgrade"))
+			{
+				mkdir(DOCROOT."media/uploads/upgrade");
+				chmod(DOCROOT."media/uploads/upgrade",0777);
+				$this->upgrade->logger("Creating Directory - ".DOCROOT."media/uploads/upgrade");
+			}
+			
+			$latest_ushahidi = $this->upgrade->download_ushahidi($url);
+
+			//download was successful
+			if ($this->upgrade->success)
+			{
+				$this->upgrade->write_to_file($latest_ushahidi, $zip_file);
+				$this->upgrade->logger("Successfully Downloaded. Unpacking ".$zip_file);
+				echo json_encode(array("status"=>"success", "message"=> Kohana::lang('upgrade.successfully_downloaded')));
+			}
+			else
+			{
+				$this->upgrade->logger("** Failed downloading.\n\n");
+				echo json_encode(array("status"=>"error", "message"=> Kohana::lang('upgrade.failed_downloading')));
+			}
+		}
+		
+		if ($step == 2)
+		{
+			//extract compressed file
+			$this->upgrade->unzip_ushahidi($zip_file, $working_dir);
+			
+			//extraction was successful
+			if ($this->upgrade->success)
+			{
+				$this->upgrade->logger("Successfully Unpacked. Copying files...");
+				echo json_encode(array("status"=>"success", "message"=>Kohana::lang('upgrade.successfully_unpacked')));
+			}
+			else
+			{
+				$this->upgrade->logger("** Failed unpacking.\n\n");
+				echo json_encode(array("status"=>"error", "message"=>Kohana::lang('upgrade.failed_unpacking')));
+			}
+		}
+
+
+		if ($step == 3)
+		{
+			//copy files
+			$this->upgrade->ftp_recursively($working_dir."ushahidi/",DOCROOT);
+			$this->upgrade->remove_old($working_dir.'ushahidi/upgrader_removed_files.txt', DOCROOT);
+			
+			// Clear out caches before new request
+			Cache::instance()->delete_all();
+			Kohana::cache_save('configuration', NULL, Kohana::config('core.internal_cache'));
+			Kohana::cache_save('language', NULL, Kohana::config('core.internal_cache'));
+			Kohana::cache_save('find_file_paths', NULL, Kohana::config('core.internal_cache'));
+			Event::clear('system.shutdown', array('Kohana', 'internal_cache_save'));
+			
+			//copying was successful
+			if ($this->upgrade->success)
+			{
+				$this->upgrade->logger("Successfully Copied. Upgrading Database...");
+				echo json_encode(array("status"=>"success", "message"=>Kohana::lang('upgrade.successfully_copied')));
+			}
+			else
+			{
+				$this->upgrade->logger("** Failed copying files.\n\n");
+				echo json_encode(array("status"=>"error", "message"=>Kohana::lang('upgrade.failed_copying')));
+			}
+		}
+		
+		
+		// Database BACKUP + UPGRADE
+		if ($step == 4)
+		{
+			// backup database.
+			// is gzip enabled ?
+			$gzip = Kohana::config('config.output_compression');
+			$error = $this->_do_db_backup( $gzip );
+			
+			if (empty($error))
+			{
+				if (file_exists(DOCROOT."sql/"))
+				{
+					$this->_process_db_upgrade(DOCROOT."sql/");
+				}
+				$this->upgrade->logger("Database backup and upgrade successful.");
+				echo json_encode(array("status"=>"success", "message"=>Kohana::lang('upgrade.backup_success')));
+			}
+			else
+			{
+				$this->upgrade->logger("** Failed backing up database.\n\n");
+				echo json_encode(array("status"=>"error", "message"=>Kohana::lang('upgrade.backup_failed')));
+			}
+		}
+		
+		
+		// Database UPGRADE ONLY
+		if ($step == 5)
+		{
+			if (file_exists(DOCROOT."sql/"))
+			{
+				//upgrade tables
+				$this->_process_db_upgrade(DOCROOT."sql/");
+				$this->upgrade->logger("Database upgrade successful.");
+				echo json_encode(array("status"=>"success", "message"=>Kohana::lang('upgrade.dbupgrade_success')));
+			}
+			else
+			{
+				$this->upgrade->logger("Database upgrade successful.");
+				echo json_encode(array("status"=>"success", "message"=>Kohana::lang('upgrade.dbupgrade_success')));
+			}
+		}
+		
+		// Delete downloaded files
+		if ($step == 6)
+		{
+			$this->upgrade->logger("Deleting downloaded files...");
+			echo json_encode(array("status"=>"success", "message"=>Kohana::lang('upgrade.deleting_files')));
+		}
+		
+		if ($step == 7)
+		{
+			$this->upgrade->remove_recursively($working_dir);
+			$this->upgrade->logger("UPGRADE SUCCESSFUL");
+			echo json_encode(array(
+					"status"=>"success",
+					"message"=> Kohana::lang('upgrade.upgrade_success', array( url::site("admin/upgrade/logfile?f=".$this->session->get('upgrade_session').".txt") ))
+			));
+			
+			$this->session->delete('upgrade_session');
+		}
+	}
+	
+	public function logfile() 
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		if (isset($_GET['f']) AND ! empty($_GET['f']))
+		{
+			$log_file = DOCROOT."application/logs/upgrade_".$_GET['f'];
+			$log_file_ext = strrev(substr(strrev($log_file),0,3));
+			if (file_exists($log_file) AND $log_file_ext == "txt")
+			{
+				$contents = file_get_contents($log_file);
+				$contents = nl2br($contents);
+				echo $contents;
+			}
+        }	
+	}
+	
+	public function check_current_version()
+	{
+		//This is an AJAX call, so none of this fancy templating, just render the data
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		// Disable profiler
+		if (isset($this->profiler))
+		{
+			$this->profiler->disable();
+		}
+		
+		$view = View::factory('admin/current_version');
+		
+		$upgrade = new Upgrade;
+		
+		//fetch latest release of ushahidi
+		$this->release = $upgrade->_fetch_core_release();
+		
+		if(!empty($this->release) )
+		{
+				$view->version = $this->_get_release_version();
+				$view->critical = $this->release->critical;
+		}
+		$view->render(TRUE);
+	}
+	
+	/**
+	 * UI for running database upgrades after manual code update
+	 **/
+	public function database()
+	{
+		$this->template->content = new View('admin/upgrade/upgrade_database');
+		$this->template->content->errors = array();
+		$this->template->content->form_error = FALSE;
+		$this->template->content->form_saved = FALSE;
+		
+		//check if form has been submitted
+		if ( $_POST )
+		{
+			// For sanity sake, validate the data received from users.
+			$post = Validation::factory($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+			
+			$post->add_rules('chk_db_backup_box', 'between[0,1]');
+			
+			if ($post->validate())
+			{
+				$this->upgrade->logger("STARTED DB UPGRADE\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
+				
+				$working_dir = DOCROOT;
+				
+				// Database BACKUP + UPGRADE
+				if ($post->chk_db_backup_box == 1)
+				{
+					// backup database.
+					// is gzip enabled ?
+					$gzip = Kohana::config('config.output_compression');
+					$error = $this->_do_db_backup( $gzip );
+					
+					if (empty($error))
+					{
+						if (file_exists($working_dir."sql"))
+						{
+							$this->_process_db_upgrade($working_dir."sql/");
+						}
+						$this->upgrade->logger("Database backup and upgrade successful.");
+						$this->template->content->message = Kohana::lang('upgrade.backup_success');
+						$this->template->content->form_saved = TRUE;
+					}
+					else
+					{
+						$this->upgrade->logger("** Failed backing up database.\n\n");
+						$this->template->content->errors = array(Kohana::lang('upgrade.backup_failed'));
+						$this->template->content->form_error = TRUE;
+					}
+				}
+				else
+				// Database UPGRADE ONLY
+				{
+					if (file_exists($working_dir."sql"))
+					{
+						//upgrade tables
+						$this->_process_db_upgrade($working_dir."sql/");
+					}
+					$this->upgrade->logger("Database upgrade successful.");
+					$this->template->content->message = Kohana::lang('upgrade.dbupgrade_success');
+					$this->template->content->form_saved = TRUE;
+				}
+			}
+			 // No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// populate the error fields, if any
+				$this->template->content->errors = $post->errors('upgrade');
+				$this->template->content->form_error = TRUE;
+			}
+		}
+		
+		$this->template->content->current_version = Kohana::config('settings.ushahidi_version');
+		$this->template->content->current_db_version = Kohana::config('settings.db_version');
+		$this->template->content->environment = $this->_environment();
+		
+	}
+	
+	/**
+	 * Execute SQL statement to upgrade the necessary tables.
+	 *
+	 * @param string - upgrade_sql - upgrade sql file
+	 */
+	private function _execute_upgrade_script($upgrade_sql) 
+	{
+		$upgrade_schema = @file_get_contents($upgrade_sql);
+
+		// If a table prefix is specified, add it to sql
+		$db_config = Kohana::config('database.default');
+		$table_prefix = $db_config['table_prefix'];
+		
+		if ($table_prefix)
+		{
+			$find = array(
+				'CREATE TABLE IF NOT EXISTS `',
+				'INSERT INTO `',
+				'INSERT IGNORE INTO `',
+				'ALTER TABLE `',
+				'UPDATE `',
+				'FROM `',
+				'LOCK TABLES `',
+				'DROP TABLE IF EXISTS `',
+				'RENAME TABLE `',
+				' TO `',
+				 // Potentially problematic. We use this to catch CREATE TABLE X LIKE Y, but could catch SELECT * WHERE X LIKE Y;
+				' LIKE `',
+			);
+			
+			$replace = array(
+				'CREATE TABLE IF NOT EXISTS `'.$table_prefix,
+				'INSERT INTO `'.$table_prefix,
+				'INSERT IGNORE INTO `'.$table_prefix,
+				'ALTER TABLE `'.$table_prefix,
+				'UPDATE `'.$table_prefix,
+				'FROM `'.$table_prefix,
+				'LOCK TABLES `'.$table_prefix,
+				'DROP TABLE IF EXISTS `'.$table_prefix,
+				'RENAME TABLE `'.$table_prefix,
+				' TO `'.$table_prefix,
+				' LIKE `'.$table_prefix,
+			);
+			
+			$upgrade_schema = str_replace($find, $replace, $upgrade_schema);
+		}
+
+		// Split by ; to get the sql statement for creating individual tables.
+		$queries = explode( ';',$upgrade_schema );
+		
+		// get the database object.
+	
+		foreach ($queries as $query)
+		{
+			// Trim whitespace and make sure we're not running an empty query (for example from the new line after the last query.)
+			$query = utf8::trim($query);
+			if (!empty($query))
+			{
+				$result = $this->db->query($query);
+			}
+		}
+			
+		// Delete cache
+		$cache = Cache::instance();
+		$cache->delete(Kohana::config('settings.subdomain').'_settings');
+		
+	}
+	
+	/**
+	 * Get the available sql update scripts from the 
+	 * sql folder then upgrade necessary tables.
+	 */
+	private function _process_db_upgrade($dir_path)
+	{
+		ini_set('max_execution_time', 300);
+		
+		$file = $dir_path . $this->_get_next_db_upgrade();
+		$this->upgrade->logger("Looking for update file: ".$file);
+		while ( file_exists($file) AND is_file($file) )
+		{
+			$this->upgrade->logger("Database imported ".$file);
+			$this->_execute_upgrade_script($file);
+			
+			// Get the next file
+			$file = $dir_path . $this->_get_next_db_upgrade();
+			$this->upgrade->logger("Looking for update file: ".$file);
+		}
+		return;
+	}
+	
+	/**
+	 * Gets the file name for the next db upgrade script
+	 * 
+	 * @return the db version.
+	 */
+	private function _get_next_db_upgrade()
+	{
+		// get the db version from the settings
+		try
+		{
+			$query = Database::instance()->query('SELECT `value` FROM '.Kohana::config('database.default.table_prefix').'settings WHERE `key` = \'db_version\' LIMIT 1')->current();
+			$version_in_db = $query->value;
+		}
+		catch (Exception $e)
+		{
+			$query = Database::instance()->query('SELECT `db_version` FROM '.Kohana::config('database.default.table_prefix').'settings LIMIT 1')->current();
+			$version_in_db = $query->db_version;
+		}
+		
+		// Just in case we get a DB fail.
+		if ($version_in_db == NULL)
+		{
+			return FALSE;
+		}
+		
+		// Special case for really old Ushahidi version
+		if ($version_in_db < 11)
+		{
+			return 'upgrade.sql';
+		}
+		
+		// Update DB
+		$db_version = $version_in_db;
+
+		$upgrade_to = $db_version + 1;
+		
+		return 'upgrade'.$db_version.'-'.$upgrade_to.'.sql';
+		
+	}
+	
+	/**
+	 * See if mysqldump exist, then detect its installed path.
+	 *
+	 * Most of the code here were borrowed from 
+	 * @return (array) $paths - include mysql and mysqldump application's path.
+	 */
+	private function _detect_mysql()
+	{
+		
+		$paths = array('mysql' => '', 'mysqldump' => '');
+		
+		//check for platform
+		if (substr(PHP_OS,0,3) == 'WIN')
+		{
+			$result = mysql_query("SHOW VARIABLES LIKE 'basedir'");
+			
+			$mysql_install = mysql_fetch_array($result);
+
+			if (is_array($mysql_install) AND sizeof($mysql_install)>0 )
+			{
+				$install_path = str_replace('\\', '/', $mysql_install[0]->Value);
+				$paths['mysql'] = $install_path.'bin/mysql.exe';
+				$paths['mysqldump'] = $install_path.'bin/mysqldump.exe';
+			}
+			else
+			{
+				$paths['mysql'] = 'mysql.exe';
+				$paths['mysqldump'] = 'mysqldump.exe';
+			}
+		}
+		else
+		{
+			if (function_exists('exec'))
+			{
+				$paths['mysql'] = @exec('which mysql');
+				$paths['mysqldump'] = @exec('which mysqldump');
+				
+				if ( ! $paths['mysql'])
+				{
+					$paths['mysql'] = 'mysql';
+				}
+				
+				if ( ! $paths['mysqldump'])
+				{
+					$paths['mysqldump'] = 'mysqldump';
+				}
+			}
+			else
+			{
+				$paths['mysql'] = 'mysql';
+				$paths['mysqldump'] = 'mysqldump';
+			}
+				
+			return $paths; 
+		}
+		
+	}
+
+	/**
+	 * Backup database
+	 *
+	 * @param boolean - gzip - set to false by default 
+	 * 
+	 * @return void or error message
+	 */
+	private function _do_db_backup( $gzip=FALSE ) 
+	{
+		$mysql_path = $this->_detect_mysql();
+		
+		$database = Kohana::config('database');
+		
+		$error = '';
+		$backup = array();
+		$backup += $mysql_path;
+		$backup['user'] = $database['default']['connection']['user'];
+		$backup['password'] = $database['default']['connection']['pass'];
+		$backup['host'] = $database['default']['connection']['host'];
+		$backup['database'] = $database['default']['connection']['database'];
+		$backup['date'] = time();
+		$backup['filepath'] = preg_replace('/\//', '/', Kohana::config('upload.relative_directory'));
+		$backup['filename'] = $backup['filepath'].'/backup_'.$this->session->get('upgrade_session').'.sql';
+		
+		if ($gzip)
+		{
+			$backup['filename'] = $backup['filename'].'.gz';
+			$command = $mysql_path['mysqldump'].' --host="'.$backup['host'].'" --user="'.$backup['user'].'" --password="'.$backup['password'].'" --add-drop-table --skip-lock-tables '.$backup['database'].' | gzip > '.$backup['filename'];
+		} 
+		else
+		{
+			$backup['filename'] = $backup['filename'];
+			$command = $mysql_path['mysqldump'].' --host="'.$backup['host'].'" --user="'.$backup['user'].'" --password="'.$backup['password'].'" --add-drop-table --skip-lock-tables '.$backup['database'].' > '.$backup['filename'];
+		}
+		
+		$this->upgrade->logger("Backing up database to ".DOCROOT."media/uploads/".$backup['filename']);
+		
+		//Execute mysqldump command
+		if (substr(PHP_OS, 0, 3) == 'WIN')
+		{
+			
+			$writable_dir = $backup['filepath'];
+			$tmpnam = $writable_dir.'/backup_script.sql';
+			$fp = fopen($tmpnam, 'w');
+			fwrite($fp, $command);
+			fclose($fp);
+			system($tmpnam.' > NUL', $error);
+			unlink($tmpnam);
+		}
+		else
+		{
+			passthru($command, $error);
+		}
+		
+		return $error;
+	}
+	
+	/**
+	 * Get the operating environment Ushahidi is on.
+	 *
+	 * @return string
+	 */
+	private function _environment()
+	{
+		$environment = "";
+		$environment .= str_replace("/", " ", preg_replace("/ .*$/", "", $_SERVER["SERVER_SOFTWARE"]));
+		$environment .= ", PHP ".phpversion();
+		
+		return $environment;
+	}
+
+	/**
+	 * Fetches the latest ushahidi release version number
+	 *
+	 * @return int or string
+	 */
+	private function _get_release_version()
+	{
+		if (is_object($this->release))
+		{
+			$release_version = $this->release->version;
+		
+			$version_ushahidi = Kohana::config('settings.ushahidi_version');
+			
+			if ($this->_new_or_not($release_version,$version_ushahidi))
+			{
+				return $release_version;
+			} 
+			else 
+			{
+				return "";
+			}
+		}
+		else
+		{
+			return "";
+		}
+	}
+	
+	
+	
+	/**
+	 * Checks version sequence parts
+	 *
+	 * @param string release_version - The version released.
+	 * @param string version_ushahidi - The version of ushahidi installed.
+	 *
+	 * @return boolean
+	 */
+	private function _new_or_not($release_version=NULL,
+			$version_ushahidi=NULL )
+	{
+		if ($release_version AND $version_ushahidi)
+        {
+            
+			// Split version numbers xx.xx.xx
+			$remote_version = explode(".", $release_version);
+			$local_version = explode(".", $version_ushahidi);
+
+			// Check first part .. if its the same, move on to next part
+			if (isset($remote_version[0]) AND isset($local_version[0])
+				AND (int) $remote_version[0] > (int) $local_version[0])
+			{
+				return true;
+			}
+
+			// Check second part .. if its the same, move on to next part
+			if (isset($remote_version[1]) AND isset($local_version[1])
+				AND (int) $remote_version[1] > (int) $local_version[1])
+			{
+				return true;
+			}
+
+			// Check third part
+			if (isset($remote_version[2]) AND (int) $remote_version[2] > 0)
+			{
+				if ( ! isset($local_version[2]))
+				{
+					return true;
+				}
+				elseif( (int) $remote_version[2] > (int) $local_version[2] )
+				{
+					return true;
+				}
+			}
+		}
+
+		return false;
+	}
+}
diff --git a/application/controllers/admin/users.php b/application/controllers/admin/users.php
new file mode 100755
index 0000000..9077d5c
--- /dev/null
+++ b/application/controllers/admin/users.php
@@ -0,0 +1,446 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used to manage users
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Users_Controller extends Admin_Controller {
+
+	private $display_roles = FALSE;
+
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->template->this_page = 'users';
+
+		// If user doesn't have access, redirect to dashboard
+		if (!$this->auth->has_permission("users"))
+		{
+			url::redirect(url::site() . 'admin/dashboard');
+		}
+
+		$this->display_roles = $this->auth->has_permission('manage_roles');
+	}
+
+	public function index()
+	{
+		$this->template->content = new View('admin/users/main');
+		$this->themes->js = new View('admin/users/users_js');
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			$post = Validation::factory(array_merge($_POST, $_FILES));
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// As far as I know, the only time we submit a form here is to delete a user
+
+			if ($post->action == 'd')
+			{
+				// We don't want to delete the first user
+
+				if ($post->user_id_action != 1)
+				{
+					// Delete the user
+
+					$user = ORM::factory('user', $post->user_id_action)->delete();
+
+				}
+
+				$form_saved = TRUE;
+				$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+			}
+		}
+
+		// Pagination
+		$pagination = new Pagination( array('query_string' => 'page', 'items_per_page' => (int)Kohana::config('settings.items_per_page_admin'), 'total_items' => ORM::factory('user')->count_all()));
+
+		$users_query = ORM::factory('user')
+						->orderby('name', 'asc');
+
+		$superadmin_role = ORM::factory('role','superadmin');
+
+		// If users is NOT a superadmin, exclude superadmin users
+		if (! $this->auth->get_user()->has( $superadmin_role ) )
+		{
+			// !!WARNING: UNESCAPED QUERY - DO NOT INSERT VARIABLES!!
+			$users_query
+				->where(
+					"`{$this->table_prefix}users`.`id` NOT IN (
+						SELECT `ru`.`user_id` FROM `{$this->table_prefix}roles` r
+						INNER JOIN `{$this->table_prefix}roles_users` ru ON `ru`.`role_id` = `r`.`id`
+						WHERE `r`.`name` = 'superadmin'
+					)"
+				);
+		}
+
+		$users = $users_query->find_all((int)Kohana::config('settings.items_per_page_admin'), $pagination->sql_offset);
+
+		// Set the flag for displaying the roles link
+		$this->template->content->display_roles = $this->display_roles;
+
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->users = $users;
+	}
+
+	/**
+	 * Edit a user
+	 * @param bool|int $user_id The id no. of the user
+	 * @param bool|string $saved
+	 */
+	public function edit($user_id = FALSE, $saved = FALSE)
+	{
+		$this->template->content = new View('admin/users/edit');
+
+		if ($user_id)
+		{
+			$user_exists = ORM::factory('user')->find($user_id);
+
+			if ( ! $user_exists->loaded OR
+					($user_exists->has( ORM::factory('role','superadmin') ) && !$this->auth->get_user()->has( ORM::factory('role','superadmin') ) )
+				)
+			{
+				// Redirect
+				url::redirect(url::site() . 'admin/users/');
+			}
+		}
+
+		// Setup and initialize form field names
+		$form = array('username' => '', 'name' => '', 'email' => '', 'password' => '', 'notify' => '', 'role' => '');
+
+		$this->template->content->user_id = $user_id;
+
+		if ($user_id == FALSE)
+		{
+			// Tack this on when adding a new user
+			$form['password'] = '';
+			$form['password_again'] = '';
+		}
+
+		// Copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		$user = "";
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Get the submitted data
+			$post = $_POST;
+
+			// Add the user_id to the $_POST data
+			$user_id = ($user_id) ? $user_id : NULL;
+			$post = array_merge($post, array('user_id' => $user_id));
+
+			if (User_Model::custom_validate($post))
+			{
+				$user = ORM::factory('user', $user_id);
+				$user->name = $post->name;
+				$user->email = $post->email;
+				$user->notify = $post->notify;
+				if ($user_id == NULL)
+				{
+					$user->password = $post->password;
+				}
+
+				// We can only set a new password if we are using the standard ORM method,
+				//    otherwise it won't actually change the password used for authentication
+				if (isset($post->new_password) AND Kohana::config('riverid.enable') == FALSE AND strlen($post->new_password) > 0)
+				{
+					$user->password = $post->new_password;
+				}
+
+				// Existing User??
+				if ($user->loaded)
+				{
+					// Prevent modification of the main admin account username or role
+					if ($user->id != 1)
+					{
+						$user->username = $post->username;
+
+						// Flag if user was previously unapproved
+						$previously_unapproved = FALSE;
+						if (count($user->roles) == 0) $previously_unapproved = TRUE;
+
+						// Remove Old Roles
+						foreach ($user->roles as $role)
+						{
+							$user->remove($role);
+						}
+
+						// Add New Roles
+						if ($post->role != 'none')
+						{
+							$user->add(ORM::factory('role', 'login'));
+							$user->add(ORM::factory('role', $post->role));
+							
+							if ($previously_unapproved)
+							{
+								// Send approved email
+								$this->_send_email_approved($user);
+							}
+						}
+					}
+				}
+				// New User
+				else
+				{
+					$user->username = $post->username;
+
+					// Add New Roles
+					if ($post->role != 'none')
+					{
+						$user->add(ORM::factory('role', 'login'));
+						$user->add(ORM::factory('role', $post->role));
+					}
+				}
+				$user->save();
+
+				//Event for adding user admin details
+				Event::run('ushahidi_action.users_add_admin', $post);
+
+				Event::run('ushahidi_action.user_edit', $user);
+
+				// Redirect
+				url::redirect(url::site() . 'admin/users/');
+			}
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('auth'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			if ($user_id)
+			{
+				// Retrieve Current Incident
+				$user = ORM::factory('user', $user_id);
+				if ($user->loaded)
+				{
+					// Some users don't have roles so we have this "none" role
+					$role = 'none';
+					foreach ($user->roles as $user_role)
+					{
+						$role = $user_role->name;
+					}
+
+					$form = array('user_id' => $user->id, 'username' => $user->username, 'name' => $user->name, 'email' => $user->email, 'notify' => $user->notify, 'role' => $role);
+				}
+			}
+		}
+
+		$roles = ORM::factory('role')->where('id != 1')->orderby('name', 'asc')->find_all();
+
+		foreach ($roles as $role)
+		{
+			$role_array[$role->name] = utf8::strtoupper($role->name);
+		}
+
+		// Add one additional role for users with no role
+		$role_array['none'] = utf8::strtoupper(Kohana::lang('ui_main.none'));
+
+		$this->template->content->id = $user_id;
+		$this->template->content->display_roles = $this->display_roles;
+		$this->template->content->user = $user;
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->yesno_array = array('1' => utf8::strtoupper(Kohana::lang('ui_main.yes')), '0' => utf8::strtoupper(Kohana::lang('ui_main.no')));
+		$this->template->content->role_array = $role_array;
+	}
+
+	public function roles()
+	{
+		$this->template->content = new View('admin/users/roles');
+		
+		$permissions = ORM::factory('permission')->find_all()->select_list('id','name');
+
+		$form = array('role_id' => '', 'action' => '', 'name' => '', 'description' => '', 'access_level' => '', 'permissions' => '');
+		foreach($permissions as $permission)
+		{
+			$form[$permission] = '';
+		}
+		
+		//copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			//  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			if ($post->action == 'a')// Add / Edit Action
+			{
+				$post->add_rules('name', 'required', 'length[3,30]', 'alpha_numeric');
+				$post->add_rules('description', 'required', 'length[3,100]');
+				$post->add_rules('access_level', 'required', 'between[0,100]', 'numeric');
+				$post->add_rules('permissions[]', 'numeric');
+
+				if ($post->role_id == "3" || $post->role_id == "1" || $post->role_id == "4")
+				{
+					$post->add_error('name', 'nomodify');
+				}
+
+				// Unique Role Name
+				$post->role_id == '' ? $post->add_callbacks('name', array($this, 'role_exists_chk')) : '';
+			}
+
+			if ($post->validate())
+			{
+				$role = ORM::factory('role', $post->role_id);
+				if ($post->action == 'a')// Add/Edit Action
+				{
+					// Remove non-existant permissions
+					$perm_ids = array_keys($permissions);
+					foreach ($post->permissions as $k => $perm)
+					{
+						if (! in_array($perm, $perm_ids))
+						{
+							unset($post->permissions[$k]);
+						}
+					}
+
+					$role->name = $post->name;
+					$role->description = $post->description;
+					$role->access_level = $post->access_level;
+					$role->permissions = array_unique($post->permissions);
+					$role->save();
+
+					$form_saved = TRUE;
+					$form_action = strtoupper(Kohana::lang('ui_admin.added_edited'));
+				}
+				elseif ($post->action == 'd')// Delete Action
+				{
+					if ($post->role_id != 1 AND $post->role_id != 2 AND $post->role_id != 3)
+					{
+						// Delete the role
+						$role->delete();
+					}
+
+					$form_saved = TRUE;
+					$form_action = strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+			}
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('roles'));
+				$form_error = TRUE;
+			}
+		}
+
+		$roles = ORM::factory('role')->where('id != 1')->orderby('access_level', 'desc')->find_all();
+
+		$this->template->content->display_roles = $this->display_roles;
+		$this->template->content->roles = $roles;
+		$this->template->content->permissions = $permissions;
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->themes->js = new View('admin/users/roles_js');
+	}
+
+	/**
+	 * Checks if username already exists.
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function username_exists_chk(Validation $post)
+	{
+		$users = ORM::factory('user');
+		// If add->rules validation found any errors, get me out of here!
+		if (array_key_exists('username', $post->errors()))
+			return;
+
+		if ($users->username_exists($post->username))
+			$post->add_error('username', 'exists');
+	}
+
+	/**
+	 * Checks if email address is associated with an account.
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function email_exists_chk(Validation $post)
+	{
+		$users = ORM::factory('user');
+		if (array_key_exists('email', $post->errors()))
+			return;
+
+		if ($users->email_exists($post->email))
+			$post->add_error('email', 'exists');
+	}
+
+	/**
+	 * Checks if role already exists.
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function role_exists_chk(Validation $post)
+	{
+		$roles = ORM::factory('role')->where('name', $post->name)->find();
+
+		// If add->rules validation found any errors, get me out of here!
+		if (array_key_exists('name', $post->errors()))
+			return;
+
+		if ($roles->loaded)
+		{
+			$post->add_error('name', 'exists');
+		}
+	}
+
+	/**
+	 * Sends an email for admin approval
+	 */
+	private function _send_email_approved($user)
+	{
+		// Check if we require users to go through this process
+		if (! Kohana::config('settings.manually_approve_users'))
+		{
+			return FALSE;
+		}
+
+		$url = url::site('login');
+
+		$to = $user->email;
+		$from = array(Kohana::config('settings.site_email'), Kohana::config('settings.site_name'));
+		$subject = Kohana::config('settings.site_name').' '.Kohana::lang('ui_main.login_signup_approval_subject');
+		$message = Kohana::lang('ui_main.login_signup_approval_message',
+			array(Kohana::config('settings.site_name'), $url));
+
+		email::send($to, $from, $subject, $message, FALSE);
+
+		return TRUE;
+	}
+
+}
diff --git a/application/controllers/alerts.php b/application/controllers/alerts.php
new file mode 100644
index 0000000..b51f2a3
--- /dev/null
+++ b/application/controllers/alerts.php
@@ -0,0 +1,320 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller handles requests for SMS/ Email alerts
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Alerts_Controller extends Main_Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+	}
+
+    public function index()
+    {
+
+		// First, are we allowed to subscribe for alerts via web?
+		if ( ! Kohana::config('settings.allow_alerts'))
+		{
+			url::redirect(url::site().'main');
+		}
+
+		$this->template->header->this_page = $this->themes->this_page = 'alerts';
+		$this->template->content = new View('alerts/main');
+
+		// Load the alert radius map view
+		$alert_radius_view = new View('alerts/radius');
+		$alert_radius_view->show_usage_info = TRUE;
+		$alert_radius_view->enable_find_location = TRUE;
+
+		$this->template->content->alert_radius_view = $alert_radius_view;
+
+
+		// Display Mobile Option?
+		$this->template->content->show_mobile = TRUE;
+
+		if ( ! Kohana::config("settings.sms_provider"))
+		{
+			// Hide Mobile
+			$this->template->content->show_mobile = FALSE;
+		}
+
+		// Retrieve default country, latitude, longitude
+		$default_country = Kohana::config('settings.default_country');
+
+		// Retrieve Country Cities
+		$this->template->content->cities = $this->_get_cities($default_country);
+		
+		// Populate this for backwards compat
+		$this->template->content->categories = array();
+
+		// Setup and initialize form field names
+		$form = array (
+			'alert_mobile' => '',
+			'alert_mobile_yes' => '',
+			'alert_email' => '',
+			'alert_email_yes' => '',
+			'alert_lat' => '',
+			'alert_lon' => '',
+			'alert_radius' => '',
+			'alert_country' => '',
+			'alert_confirmed' => ''
+		);
+
+		if ($this->user)
+		{
+			$form['alert_email'] = $this->user->email;
+		}
+
+		// Get Countries
+		$countries = array();
+		foreach (ORM::factory('country')->orderby('country')->find_all() as $country)
+		{
+			// Create a list of all countries
+			$this_country = $country->country;
+			if (strlen($this_country) > 35)
+			{
+				$this_country = substr($this_country, 0, 35) . "...";
+			}
+			$countries[$country->id] = $this_country;
+		}
+
+		//Initialize default value for Alert confirmed hidden value
+
+		$this->template->content->countries = $countries;
+
+		// Copy the form as errors, so the errors will be stored with keys
+		// corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// If there is a post and $_POST is not empty
+		if ($post = $this->input->post())
+		{
+			$alert_orm = new Alert_Model();
+			// HT: created new model and post for mobile alert
+			$alert_orm1 = new Alert_Model();
+			$post1 = $this->input->post();
+			if ($alert_orm->validate($post))
+			{
+				// Yes! everything is valid
+				// Save alert and send out confirmation code
+
+				if ( ! empty($post->alert_mobile))
+				{
+					// HT: setting value of post1 to alert_orm1
+					$alert_orm1->validate($post1);
+					alert::_send_mobile_alert($post1, $alert_orm1);
+					$this->session->set('alert_mobile', $post->alert_mobile);
+				}
+
+				if ( ! empty($post->alert_email))
+				{
+					alert::_send_email_alert($post, $alert_orm);
+					$this->session->set('alert_email', $post->alert_email);
+				}
+
+				url::redirect('alerts/confirm');
+            }
+            // No! We have validation errors, we need to show the form again, with the errors
+            else
+            {
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('alerts'));
+				
+				if (array_key_exists('alert_recipient', $post->errors('alerts')))
+				{
+					$errors = array_merge($errors, $post->errors('alerts'));
+				}
+				
+				$form_error = TRUE;
+            }
+        }
+        else
+        {
+			$form['alert_lat'] = Kohana::config('settings.default_lat');
+			$form['alert_lon'] = Kohana::config('settings.default_lon');
+			$form['alert_radius'] = 20;
+			$form['alert_category'] = array();
+        }
+
+		$this->template->content->form_error = $form_error;
+		// Initialize Default Value for Hidden Field Country Name, just incase Reverse Geo coding yields no result
+		$form['alert_country'] = $countries[$default_country];
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_saved = $form_saved;
+
+
+		// Javascript Header
+		$this->themes->map_enabled = TRUE;
+		$this->themes->js = new View('alerts/alerts_js');
+		$this->themes->treeview_enabled = TRUE;
+		$this->themes->slider_enabled = TRUE;
+		$this->themes->js->latitude = $form['alert_lat'];
+		$this->themes->js->longitude = $form['alert_lon'];
+
+    }
+
+
+	/**
+	 * Alerts Confirmation Page
+	 */
+	public function confirm()
+	{
+		$this->template->header->this_page = 'alerts';
+		$this->template->content = new View('alerts/confirm');
+
+		$this->template->content->alert_mobile = (isset($_SESSION['alert_mobile']) AND ! empty($_SESSION['alert_mobile']))
+			? $_SESSION['alert_mobile']
+			: "";
+
+		$this->template->content->alert_email = (isset($_SESSION['alert_email']) AND ! empty($_SESSION['alert_email']))
+			? $_SESSION['alert_email']
+			: "";
+
+		// Display Mobile Option?
+		$this->template->content->show_mobile = TRUE;
+
+		if (empty($_SESSION['alert_mobile']))
+		{
+			// Hide Mobile
+			$this->template->content->show_mobile = FALSE;
+		}
+	}
+
+
+	/**
+	 * Verifies a previously sent alert confirmation code
+	 */
+	public function verify()
+	{
+		// Define error codes for this view.
+		define("ER_CODE_VERIFIED", 0);
+		define("ER_CODE_NOT_FOUND", 1);
+		define("ER_CODE_ALREADY_VERIFIED", 3);
+
+		$code = (isset($_GET['c']) AND !empty($_GET['c'])) ? $_GET['c'] : "";
+
+		$email = (isset($_GET['e']) AND !empty($_GET['e'])) ? $_GET['e'] : "";
+		// HT: Mobile verification by url
+		$mobile = (isset($_GET['m']) AND !empty($_GET['m'])) ? $_GET['m'] : "";
+
+		// INITIALIZE the content's section of the view
+		$this->template->content = new View('alerts/verify');
+		$this->template->header->this_page = 'alerts';
+
+		$filter = " ";
+		$missing_info = FALSE;
+
+		if ($_POST AND isset($_POST['alert_code']) AND ! empty($_POST['alert_code']))
+		{
+			if (isset($_POST['alert_mobile']) AND ! empty($_POST['alert_mobile']))
+			{
+				$filter = "alert.alert_type=1 AND alert_code='".Database::instance()->escape_str(utf8::strtoupper($_POST['alert_code']))."' AND alert_recipient='".Database::instance()->escape_str($_POST['alert_mobile'])."' ";
+			}
+			elseif (isset($_POST['alert_email']) AND ! empty($_POST['alert_email']))
+			{
+				$filter = "alert.alert_type=2 AND alert_code='".Database::instance()->escape_str($_POST['alert_code'])."' AND alert_recipient='".Database::instance()->escape_str($_POST['alert_email'])."' ";
+			}
+			else
+			{
+				$missing_info = TRUE;
+			}
+		}
+		else
+		{
+			//if (empty($code) OR empty($email))
+			if (empty($code) OR (empty($email) AND empty($mobile)))
+			{
+				$missing_info = TRUE;
+			}
+			else
+			{
+				if(! empty($email)) // HT: condition to check email alert
+					$filter = "alert.alert_type=2 AND alert_code='".Database::instance()->escape_str($code)."' AND alert_recipient='".Database::instance()->escape_str($email)."' ";
+				elseif(! empty($mobile)) // HT: condition to check mobile alert
+				$filter = "alert.alert_type=1 AND alert_code='".Database::instance()->escape_str(utf8::strtoupper($code))."' AND alert_recipient='".Database::instance()->escape_str($mobile)."' ";
+			}
+		}
+
+		if ( ! $missing_info)
+		{
+			$alert_check = ORM::factory('alert')
+								->where($filter)
+								->find();
+
+			// IF there was no result
+			if ( ! $alert_check->loaded)
+			{
+				$this->template->content->errno = ER_CODE_NOT_FOUND;
+			}
+			elseif ($alert_check->alert_confirmed)
+			{
+				$this->template->content->errno = ER_CODE_ALREADY_VERIFIED;
+			}
+			else
+			{
+				// SET the alert as confirmed, and save it
+				$alert_check->set('alert_confirmed', 1)->save();
+				$this->template->content->errno = ER_CODE_VERIFIED;
+			}
+		}
+		else
+		{
+			$this->template->content->errno = ER_CODE_NOT_FOUND;
+		}
+
+	} // END function verify
+
+
+	/**
+	 * Unsubscribes alertee using alertee's confirmation code
+	 *
+	 * @param string $code
+	 */
+	public function unsubscribe($code = NULL)
+	{
+		$this->template->content = new View('alerts/unsubscribe');
+		$this->template->header->this_page = 'alerts';
+		$this->template->content->unsubscribed = FALSE;
+
+		// XXX Might need to validate $code as well
+		if ($code != NULL)
+		{
+			Alert_Model::unsubscribe($code);
+			$this->template->content->unsubscribed = TRUE;
+		}
+
+    }
+
+	/**
+	 * Retrieves Previously Cached Geonames Cities
+	 */
+	private function _get_cities()
+	{
+		$cities = ORM::factory('city')->orderby('city', 'asc')->find_all();
+		$city_select = array('' => Kohana::lang('ui_main.alerts_select_city'));
+
+		foreach ($cities as $city)
+		{
+			$city_select[$city->city_lon.",".$city->city_lat] = $city->city;
+		}
+		return $city_select;
+	}
+
+}
diff --git a/application/controllers/api.php b/application/controllers/api.php
new file mode 100755
index 0000000..c0b67a5
--- /dev/null
+++ b/application/controllers/api.php
@@ -0,0 +1,74 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller handles API requests.
+ *
+ * @version 29 - Henry Addo 2010-11-09
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * Api_Controller
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Api_Controller extends Controller {
+	
+	/**
+	 * Starting point
+	 */
+	public function index()
+	{
+		// Disables CSRF validation for API requests
+		Validation::$is_api_request = TRUE;
+
+		// Reset session for API requests - since they don't get CSRF checked
+		// AJAX requests are ok - they skip CSRF anyway.
+		if (! request::is_ajax())
+		{
+			// Reset the session - API should be stateless
+			$_SESSION = array();
+			// Especially reset auth
+			Session::instance()->set(Kohana::config('auth.session_key'), null);
+			
+			// Re-authenticate
+			$this->auth->http_auth_login();
+		}
+
+		// Instantiate the API service
+		$api_service = new Api_Service();
+
+		// Run the service
+		$api_service->run_service();
+
+		// Avoid caching
+		header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
+		header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
+
+		$resp = '';
+		
+		if ($api_service->get_response_type() == 'jsonp')
+		{
+			header("Content-type: application/json; charset=utf-8");
+			$resp = $_GET['callback'].'('.$api_service->get_response().')';
+		}
+		elseif ($api_service->get_response_type() == 'xml')
+		{
+			header("Content-type: text/xml");    
+			$resp = $api_service->get_response();
+		}
+		else
+		{
+			header("Content-type: application/json; charset=utf-8");
+			$resp =  $api_service->get_response();
+		}
+
+		print $resp;
+
+	}
+}
diff --git a/application/controllers/contact.php b/application/controllers/contact.php
new file mode 100644
index 0000000..30fb4f4
--- /dev/null
+++ b/application/controllers/contact.php
@@ -0,0 +1,113 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Contact Us Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Contact_Controller extends Main_Controller {
+	function __construct()
+	{
+		parent::__construct();
+	}
+
+	public function index()
+	{
+		// If contact page disabled, or site_email not set then return 404
+		if (! Kohana::config('settings.site_contact_page') OR Kohana::config('settings.site_email') == "")
+		{
+			throw new Kohana_404_Exception();
+		}
+
+		$this->template->header->this_page = 'contact';
+		$this->template->content = new View('contact');
+
+		$this->template->header->page_title .= Kohana::lang('ui_main.contact') . Kohana::config('settings.title_delimiter');
+
+		// Setup and initialize form field names
+		$form = array('contact_name' => '', 'contact_email' => '', 'contact_phone' => '', 'contact_subject' => '', 'contact_message' => '', 'captcha' => '');
+
+		// Copy the form as errors, so the errors will be stored with keys
+		// corresponding to the form field names
+		$captcha = Captcha::factory();
+		$errors = $form;
+		$form_error = FALSE;
+		$form_sent = FALSE;
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+			$post = Validation::factory($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('contact_name', 'required', 'length[3,100]');
+			$post->add_rules('contact_email', 'required', 'email', 'length[4,100]');
+			$post->add_rules('contact_subject', 'required', 'length[3,100]');
+			$post->add_rules('contact_message', 'required');
+			$post->add_rules('captcha', 'required', 'Captcha::valid');
+
+			// Test to see if things passed the rule checks
+			// Skip CSRF check since we have a CAPTCHA already
+			if ($post->validate(FALSE))
+			{
+				// Yes! everything is valid - Send email
+				$site_email = Kohana::config('settings.site_email');
+				$message = Kohana::lang('ui_admin.sender') . ": " . $post->contact_name . "\n";
+				$message .= Kohana::lang('ui_admin.email') . ": " . $post->contact_email . "\n";
+				$message .= Kohana::lang('ui_admin.phone') . ": " . $post->contact_phone . "\n\n";
+				$message .= Kohana::lang('ui_admin.message') . ": \n" . $post->contact_message . "\n\n\n";
+				$message .= "~~~~~~~~~~~~~~~~~~~~~~\n";
+				$message .= Kohana::lang('ui_admin.sent_from_website') . url::base();
+
+				// Send Admin Message
+				try
+				{
+					email::send($site_email, $post->contact_email, $post->contact_subject, $message, FALSE);
+	
+					$form_sent = TRUE;
+				}
+				catch (Exception $e)
+				{
+					// repopulate the form fields
+					$form = arr::overwrite($form, $post->as_array());
+
+					// Manually add an error message for the email send failure.
+					$errors['email_send'] = Kohana::lang('contact.email_send.failed');
+
+					// populate the error fields, if any
+					$errors = arr::merge($errors, $post->errors('contact'));
+					$form_error = TRUE;
+				}
+			}
+			// No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('contact'));
+				$form_error = TRUE;
+			}
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_sent = $form_sent;
+		$this->template->content->captcha = $captcha;
+	}
+
+}
diff --git a/application/controllers/error.php b/application/controllers/error.php
new file mode 100644
index 0000000..7cfdc51
--- /dev/null
+++ b/application/controllers/error.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Custom 404 Error Page Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Error_Controller extends Controller {
+	/**
+	 * Render Custom 404 Error Page
+	 */
+	public function error_404()
+	{
+		Header("HTTP/1.0 404 Not Found");
+
+		$this->layout = new View('error');
+		$this->layout->title = Kohana::lang('ui_admin.page_not_found');
+		if (Kohana::config('settings.site_contact_page') AND Kohana::config('settings.site_email') != "")
+		{
+			$this->layout->content = Kohana::lang('ui_admin.page_not_found_message_with_contact', array(url::site('contact'), url::site('contact')));
+		}
+		else
+		{
+			$this->layout->content = Kohana::lang('ui_admin.page_not_found_message');
+		}
+		$this->layout->render(true);
+	}
+
+}
diff --git a/application/controllers/feed.php b/application/controllers/feed.php
new file mode 100644
index 0000000..4c13422
--- /dev/null
+++ b/application/controllers/feed.php
@@ -0,0 +1,117 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Feed Controller
+ * ATOM/RSS2 Generator
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - https://github.com/ushahidi/Ushahidi_Web
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Feed_Controller extends Controller {
+	
+	public function __construct()
+	{
+		parent::__construct();
+	}
+
+	public function index($feedtype = 'rss2')
+	{
+		if ( ! Kohana::config('settings.allow_feed'))
+		{
+			throw new Kohana_404_Exception();
+		}
+		
+		if ($feedtype != 'atom' AND $feedtype != 'rss2')
+		{
+			throw new Kohana_404_Exception();
+		}
+
+		// How Many Items Should We Retrieve?
+		$limit = ( isset($_GET['l']) AND !empty($_GET['l']) AND (int) $_GET['l'] <= 200)
+			? (int) $_GET['l'] : 20;
+
+		// Start at which page?
+		$page = ( isset($_GET['p']) AND ! empty($_GET['p']) AND (int) $_GET['p'] >= 1 )
+			? (int) $_GET['p'] 
+			: 1;
+			
+		$page_position = ($page == 1) ? 0 : ( $page * $limit ) ; // Query position
+
+		$site_url = url::base();
+
+		// Cache the Feed with subdomain in the cache name if mhi is set
+
+		$subdomain = '';
+		if (substr_count($_SERVER["HTTP_HOST"],'.') > 1 AND Kohana::config('config.enable_mhi') == TRUE)
+		{
+			$subdomain = substr($_SERVER["HTTP_HOST"],0,strpos($_SERVER["HTTP_HOST"],'.'));
+		}
+
+		$cache = Cache::instance();
+		$feed_items = $cache->get($subdomain.'_feed_'.$limit.'_'.$page);
+		
+		if ($feed_items == NULL)
+		{ // Cache is Empty so Re-Cache
+			$incidents = ORM::factory('incident')
+							->where('incident_active', '1')
+							->orderby('incident_date', 'desc')
+							->limit($limit, $page_position)->find_all();
+			$items = array();
+
+			foreach ($incidents as $incident)
+			{
+				$categories = Array();
+				foreach ($incident->category AS $category)
+				{
+					$categories[] = (string)$category->category_title;
+				}
+			  
+			  
+				$item = array();
+				$item['id'] = $incident->id;
+				$item['title'] = $incident->incident_title;
+				$item['link'] = $site_url.'reports/view/'.$incident->id;
+				$item['description'] = $incident->incident_description;
+				$item['date'] = $incident->incident_date;
+				$item['categories'] = $categories;
+				
+				if
+				(
+					$incident->location_id != 0 AND
+					$incident->location->longitude AND
+					$incident->location->latitude
+				)
+				{
+					$item['point'] = array(
+						$incident->location->latitude,
+					    $incident->location->longitude
+					);
+					$items[] = $item;
+				}
+			}
+
+			$cache->set($subdomain.'_feed_'.$limit.'_'.$page, $items, array('feed'), 3600); // 1 Hour
+			$feed_items = $items;
+		}
+
+		$feedpath = $feedtype == 'atom' ? 'feed/atom/' : 'feed/';
+
+		header('Content-Type: application/' . ($feedtype == 'atom' ? 'atom' : 'rss') . '+xml; charset=utf-8');
+		$view = new View('feed/'.$feedtype);
+		$view->feed_title = Kohana::config('settings.site_name');
+		$view->site_url = $site_url;
+		$view->georss = 1; // this adds georss namespace in the feed
+		$view->feed_url = $site_url.$feedpath;
+		$view->feed_date = gmdate("D, d M Y H:i:s T", time());
+		$view->feed_description = Kohana::lang('ui_admin.incident_feed').' '.Kohana::config('settings.site_name');
+		$view->items = $feed_items;
+		$view->render(TRUE);
+	}
+}
diff --git a/application/controllers/feeds.php b/application/controllers/feeds.php
new file mode 100644
index 0000000..de44c0b
--- /dev/null
+++ b/application/controllers/feeds.php
@@ -0,0 +1,81 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * This controller lists the available feed items
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - https://github.com/ushahidi/Ushahidi_Web
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Feeds_Controller extends Main_Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+	}
+
+	/**
+	 * Displays all feeds.
+	 */
+	public function index()
+	{
+		$this->template->header->this_page = Kohana::lang('ui_admin.feeds');
+		$this->template->content = new View('feed/feeds');
+
+		// Pagination
+		$pagination = new Pagination(array(
+					  'query_string' => 'page',
+					  'items_per_page' => (int) Kohana::config('settings.items_per_page'),
+					  'total_items' => ORM::factory('feed_item')
+									   ->count_all()
+					  ));
+
+		$feeds = ORM::factory('feed_item')
+					 ->orderby('item_date', 'desc')
+					 ->find_all( (int) Kohana::config('settings.items_per_page'),
+								 $pagination->sql_offset);
+
+		$this->template->content->feeds = $feeds;
+
+		//Set default as not showing pagination. Will change below if necessary.
+		$this->template->content->pagination = '';
+
+		// Pagination and Total Num of Report Stats
+		$plural = ($pagination->total_items == 1)? '' : 's';
+
+		if ($pagination->total_items > 0)
+		{
+			$current_page = ($pagination->sql_offset/ (int) Kohana::config('settings.items_per_page')) + 1;
+			$total_pages = ceil($pagination->total_items/ (int) Kohana::config('settings.items_per_page'));
+
+			if ($total_pages > 1)
+			{
+				// Paginate results
+				$pagination_stats = Kohana::lang('ui_admin.showing_page').' '
+				    . $current_page.' '
+				    . Kohana::lang('ui_admin.of')
+				    . ' '.$total_pages.' '
+				    . Kohana::lang('ui_admin.pages');
+				
+				$this->template->content->pagination_stats = $pagination_stats;
+
+				$this->template->content->pagination = $pagination;
+			}
+			else
+			{
+				// No pagination
+				$this->template->content->pagination_stats = $pagination->total_items.' '.Kohana::lang('ui_admin.feeds');
+			}
+		}
+		else
+		{
+			$this->template->content->pagination_stats = $pagination->total_items.' '.Kohana::lang('ui_admin.feeds');
+		}
+	}
+}
diff --git a/application/controllers/json.php b/application/controllers/json.php
new file mode 100644
index 0000000..165489f
--- /dev/null
+++ b/application/controllers/json.php
@@ -0,0 +1,877 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Json Controller
+ * Generates Map GeoJSON File
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Json_Controller extends Template_Controller {
+
+	/**
+	 * Disable automatic rendering
+	 * @var bool
+	 */
+	public $auto_render = FALSE;
+
+	/**
+	 * Template for this controller
+	 * @var string
+	 */
+	public $template = '';
+
+	/**
+	 * Database table prefix
+	 * @var string
+	 */
+	protected $table_prefix;
+
+	/**
+	 * Geometry data
+	 * @var array
+	 */
+	private static $geometry_data = array();
+
+
+	public function __construct()
+	{
+		parent::__construct();
+		
+		// Disable profiler
+		if (isset($this->profiler))
+		{
+			$this->profiler->disable();
+		}
+
+		// Set Table Prefix
+		$this->table_prefix = Kohana::config('database.default.table_prefix');
+
+		// Cacheable JSON Controller
+		$this->is_cachable = TRUE;
+	}
+
+
+	/**
+	 * Generate JSON in NON-CLUSTER mode
+	 */
+	public function index()
+	{
+		$this->geojson('markers');
+	}
+
+	/**
+	 * Generate JSON in CLUSTER mode
+	 */
+	public function cluster()
+	{
+		$this->geojson('clusters');
+	}
+	
+	/**
+	 * Generate geojson
+	 * 
+	 * @param string $type type of geojson to generate. Valid options are: 'clusters' and 'markers'
+	 **/
+	protected function geojson($type)
+	{
+		$color = Kohana::config('settings.default_map_all');
+		$icon = "";
+		$markers = FALSE;
+		
+		if (Kohana::config('settings.default_map_all_icon_id'))
+		{
+			$icon_object = ORM::factory('media')->find(Kohana::config('settings.default_map_all_icon_id'));
+			$icon = url::convert_uploaded_to_abs($icon_object->media_medium);
+		}
+		
+		// Category ID
+		$category_id = (isset($_GET['c']) AND intval($_GET['c']) > 0) ? intval($_GET['c']) : 0;
+		// Get the category colour
+		if (Category_Model::is_valid_category($category_id))
+		{
+			// Get the color & icon
+			$cat = ORM::factory('category', $category_id);
+			$color = $cat->category_color;
+			$icon = "";
+			if ($cat->category_image)
+			{
+				$icon = url::convert_uploaded_to_abs($cat->category_image);
+			}
+		}
+		
+		$params = array('color' => $color, 'icon' => $icon);
+		Event::run('ushahidi_filter.json_alter_params', $params);
+		$color = $params['color'];
+		$icon = $params['icon'];
+
+		// Run event ushahidi_filter.json_replace_markers
+		// This allows a plugin to completely replace $markers
+		// If markers are added at this point we don't bother fetching incidents at all
+		Event::run('ushahidi_filter.json_replace_markers', $markers);
+
+		// Fetch the incidents
+		if (! $markers)
+		{
+			$markers = (isset($_GET['page']) AND intval($_GET['page']) > 0)
+			    ? reports::fetch_incidents(TRUE)
+			    : reports::fetch_incidents();
+		}
+		
+		// Run event ushahidi_filter.json_alter_markers
+		// This allows a plugin to alter $markers
+		// Plugins can add or remove markers as needed
+		Event::run('ushahidi_filter.json_alter_markers', $markers);
+		
+		// Get geojson features array
+		$function = "{$type}_geojson";
+		$json_features = $this->$function($markers, $category_id, $color, $icon);
+		
+		$this->render_geojson($json_features);
+	}
+
+	/**
+	 * Render geojson features array to geojson and output
+	 * 
+	 * @param array $json_features geojson features array
+	 **/
+	protected function render_geojson($json_features)
+	{
+		// Run event ushahidi_filter.json_features
+		// Allow plugins to alter json array before its rendered
+		Event::run('ushahidi_filter.json_features', $json_features);
+		
+		$json = json_encode(array(
+			"type" => "FeatureCollection",
+			"features" => $json_features
+		));
+		
+		header('Content-type: application/json; charset=utf-8');
+		echo $json;
+	}
+
+	/**
+	 * Generate GEOJSON from incidents
+	 * 
+	 * @param ORM_Iterator|Database_Result|array $incidents collection of incidents
+	 * @param int $category_id
+	 * @param string $color
+	 * @param string $icon
+	 * @return array $json_features geojson features array
+	 **/
+	protected function markers_geojson($incidents, $category_id, $color, $icon, $include_geometries = TRUE)
+	{
+		$json_features = array();
+		
+		// Extra params for markers only
+		// Get the incidentid (to be added as first marker)
+		$first_incident_id = (isset($_GET['i']) AND intval($_GET['i']) > 0)? intval($_GET['i']) : 0;
+		
+		$media_type = (isset($_GET['m']) AND intval($_GET['m']) > 0)? intval($_GET['m']) : 0;
+		
+		foreach ($incidents as $marker)
+		{
+			// Handle both reports::fetch_incidents() response and actual ORM objects
+			$marker->id = isset($marker->incident_id) ? $marker->incident_id : $marker->id;
+			if (isset($marker->latitude) AND isset($marker->longitude))
+			{
+				$latitude = $marker->latitude;
+				$longitude = $marker->longitude;
+			}
+			elseif (isset($marker->location) AND isset($marker->location->latitude) AND isset($marker->location->longitude))
+			{
+				$latitude = $marker->location->latitude;
+				$longitude = $marker->location->longitude;
+			}
+			else
+			{
+				// No location - skip this report
+				continue;
+			}
+			
+			// Get thumbnail
+			$thumb = "";
+			$media = ORM::factory('incident', $marker->id)->media;
+			if ($media->count())
+			{
+				foreach ($media as $photo)
+				{
+					if ($photo->media_thumb)
+					{
+						// Get the first thumb
+						$thumb = url::convert_uploaded_to_abs($photo->media_thumb);
+						break;
+					}
+				}
+			}
+
+			// Get URL from object, fallback to Incident_Model::get() if object doesn't have url or url()
+			if (method_exists($marker, 'url'))
+			{
+				$link = $marker->url();
+			}
+			elseif (isset($marker->url))
+			{
+				$link = $marker->url;
+			}
+			else
+			{
+				$link = Incident_Model::get_url($marker);
+			}
+			$item_name = $this->get_title($marker->incident_title, $link);
+
+			$json_item = array();
+			$json_item['type'] = 'Feature';
+			$json_item['properties'] = array(
+				'id' => $marker->id,
+				'name' => $item_name,
+				'link' => $link,
+				'category' => array($category_id),
+				'color' => $color,
+				'icon' => $icon,
+				'thumb' => $thumb,
+				'timestamp' => strtotime($marker->incident_date),
+				'count' => 1,
+				'class' => get_class($marker),
+				'title'  => $marker->incident_title
+			);
+			$json_item['geometry'] = array(
+				'type' => 'Point',
+				'coordinates' => array($longitude, $latitude)
+			);
+
+			if ($marker->id == $first_incident_id)
+			{
+				array_unshift($json_features, $json_item);
+			}
+			else
+			{
+				array_push($json_features, $json_item);
+			}
+			
+			// Get Incident Geometries
+			if ($include_geometries)
+			{
+				$geometry = $this->get_geometry($marker->id, $marker->incident_title, $marker->incident_date, $link);
+				if (count($geometry))
+				{
+					foreach ($geometry as $g)
+					{
+						array_push($json_features, $g);
+					}
+				}
+			}
+		}
+		
+		Event::run('ushahidi_filter.json_index_features', $json_features);
+		
+		return $json_features;
+	}
+
+	/**
+	 * Generate clustered GEOJSON from incidents
+	 * 
+	 * @param ORM_Iterator|Database_Result|array $incidents collection of incidents
+	 * @param int $category_id
+	 * @param string $color
+	 * @param string $icon
+	 * @return array $json_features geojson features array
+	 **/
+	protected function clusters_geojson($incidents, $category_id, $color, $icon)
+	{
+		$json_features = array();
+		
+		// Extra params for clustering
+		// Start date
+		$start_date = (isset($_GET['s']) AND intval($_GET['s']) > 0) ? intval($_GET['s']) : NULL;
+		
+		// End date
+		$end_date = (isset($_GET['e']) AND intval($_GET['e']) > 0) ? intval($_GET['e']) : NULL;
+		
+		// Get Zoom Level
+		$zoomLevel = (isset($_GET['z']) AND !empty($_GET['z'])) ? (int) $_GET['z'] : 8;
+		$distance = (10000000 >> $zoomLevel) / 100000;
+		
+		// Get markers array
+		if ($incidents instanceof ORM_Iterator)
+		{
+			$markers = $incidents->as_array();
+		}
+		elseif ($incidents instanceof Database_Result)
+		{
+			$markers = $incidents->result_array();
+		}
+		else
+		{
+			$markers = $incidents;
+		}
+
+		$clusters = array();	// Clustered
+		$singles = array();	// Non Clustered
+
+		// Loop until all markers have been compared
+		while (count($markers))
+		{
+			$marker	 = array_pop($markers);
+			$cluster = array();
+			
+			// Handle both reports::fetch_incidents() response and actual ORM objects
+			$marker->id = isset($marker->incident_id) ? $marker->incident_id : $marker->id;
+			if (isset($marker->latitude) AND isset($marker->longitude))
+			{
+				$marker_latitude = $marker->latitude;
+				$marker_longitude = $marker->longitude;
+			}
+			elseif (isset($marker->location) AND isset($marker->location->latitude) AND isset($marker->location->longitude))
+			{
+				$marker_latitude = $marker->location->latitude;
+				$marker_longitude = $marker->location->longitude;
+			}
+			else
+			{
+				// No location - skip this report
+				continue;
+			}
+
+			// Compare marker against all remaining markers.
+			foreach ($markers as $key => $target)
+			{
+				// Handle both reports::fetch_incidents() response and actual ORM objects
+				if (isset($target->latitude) AND isset($target->longitude))
+				{
+					$target_latitude = $target->latitude;
+					$target_longitude = $target->longitude;
+				}
+				elseif (isset($target->location) AND isset($target->location->latitude) AND isset($target->location->longitude))
+				{
+					$target_latitude = $target->location->latitude;
+					$target_longitude = $target->location->longitude;
+				}
+				else
+				{
+					// No location - skip this report
+					continue;
+				}
+				
+				// This function returns the distance between two markers, at a defined zoom level.
+				// $pixels = $this->_pixelDistance($marker['latitude'], $marker['longitude'],
+				// $target['latitude'], $target['longitude'], $zoomLevel);
+
+				$pixels = abs($marker_longitude - $target_longitude) +
+					abs($marker_latitude - $target_latitude);
+					
+				// If two markers are closer than defined distance, remove compareMarker from array and add to cluster.
+				if ($pixels < $distance)
+				{
+					unset($markers[$key]);
+					$cluster[] = $target;
+				}
+			}
+
+			// If a marker was added to cluster, also add the marker we were comparing to.
+			if (count($cluster) > 0)
+			{
+				$cluster[] = $marker;
+				$clusters[] = $cluster;
+			}
+			else
+			{
+				$singles[] = $marker;
+			}
+		}
+
+		// Create Json
+		foreach ($clusters as $cluster)
+		{
+			// Calculate cluster center
+			$bounds = $this->calculate_center($cluster);
+			$cluster_center = array_values($bounds['center']);
+			$southwest = $bounds['sw']['longitude'].','.$bounds['sw']['latitude'];
+			$northeast = $bounds['ne']['longitude'].','.$bounds['ne']['latitude'];
+
+			// Number of Items in Cluster
+			$cluster_count = count($cluster);
+			
+			// Get the time filter
+			$time_filter = ( ! empty($start_date) AND ! empty($end_date))
+				? "&s=".$start_date."&e=".$end_date
+				: "";
+			
+			// Build query string for title link, passing through any GET params
+			// This allows plugins to extend more easily
+			$query = http_build_query(array_merge(
+				array(
+					'sw' => $southwest,
+					'ne' => $northeast
+				),
+				$_GET
+			));
+			
+			// Build out the JSON string
+			$link = url::site("reports/index/?$query");
+			$item_name = $this->get_title(Kohana::lang('ui_main.reports_count', $cluster_count), $link);
+			
+			$json_item = array();
+			$json_item['type'] = 'Feature';
+			$json_item['properties'] = array(
+				'name' => $item_name,
+				'link' => $link,
+				'category' => array($category_id),
+				'color' => $color,
+				'icon' => $icon,
+				'thumb' => '',
+				'timestamp' => 0,
+				'count' => $cluster_count,
+			);
+			$json_item['geometry'] = array(
+				'type' => 'Point',
+				'coordinates' => $cluster_center
+			);
+
+			array_push($json_features, $json_item);
+		}
+
+		// Pass single points to standard markers json
+		$json_features = array_merge($json_features, $this->markers_geojson($singles, $category_id, $color, $icon, FALSE));
+		
+		// 
+		// E.Kala July 27, 2011
+		// @todo Parking this geometry business for review
+		// 
+		/*
+		//Get Incident Geometries
+		$geometry = $this->_get_geometry($marker->incident_id, $marker->incident_title, $marker->incident_date);
+		if (count($geometry))
+		{
+			foreach ($geometry as $g)
+			{
+				array_push($json_features, $g);
+			}
+		}
+		*/
+		
+		Event::run('ushahidi_filter.json_cluster_features', $json_features);
+		
+		return $json_features;
+	}
+
+	/**
+	 * Retrieve Single Marker (and its neighbours)
+	 * 
+	 * @param int $incident_id
+	 */
+	public function single($incident_id = 0)
+	{
+		$json_features = array();
+
+		$incident_id = intval($incident_id);
+
+		// Check if incident valid/approved
+		if ( ! Incident_Model::is_valid_incident($incident_id, TRUE) )
+		{
+			throw new Kohana_404_Exception();
+		}
+
+		// Load the incident
+		// @todo avoid the double load here
+		$marker = ORM::factory('incident')->where('incident.incident_active', 1)->with('location')->find($incident_id);
+		if ( ! $marker->loaded )
+		{
+			throw new Kohana_404_Exception();
+		}
+		
+		// Get geojson features for main incident (including geometry) 
+		$json_features = $this->markers_geojson(array($marker), 0, null, null, TRUE);
+
+		// Get the neigbouring incidents & their json (without geometries)
+		$neighbours = Incident_Model::get_neighbouring_incidents($incident_id, FALSE, 20, 100);
+		if ($neighbours)
+		{
+			$json_features = array_merge($json_features, $this->markers_geojson($neighbours, 0, null, null, FALSE));
+		}
+
+		Event::run('ushahidi_filter.json_single_features', $json_features);
+
+		$this->render_geojson($json_features);
+	}
+
+	/**
+	 * Retrieve timeline JSON
+	 */
+	public function timeline($category_id = 0)
+	{
+		$category_id = (isset($_GET["c"]) AND ! empty($_GET["c"])) ? (int) $_GET["c"] : (int) $category_id; // HT: set category from url param is 'c' set
+
+		$this->auto_render = FALSE;
+		$db = new Database();
+
+		$interval = (isset($_GET["i"]) AND ! empty($_GET["i"]))
+		    ? $_GET["i"]
+		    : "month";
+
+		// Get Category Info
+		$category_title = "All Categories";
+		$category_color = "#990000";
+		if ($category_id > 0)
+		{
+			$category = ORM::factory("category", $category_id);
+			if ($category->loaded)
+			{
+				$category_title = $category->category_title;
+				$category_color = "#".$category->category_color;
+			}
+		}
+
+		// Change select / group by expression based on interval
+		// Not a great way to do this but can't think of a better option
+		// Default values: month
+		$select_date_text = "DATE_FORMAT(i.incident_date, '%Y-%m-01')";
+		$groupby_date_text = "DATE_FORMAT(i.incident_date, '%Y%m')";
+		if ($interval == 'day')
+		{
+			$select_date_text = "DATE_FORMAT(i.incident_date, '%Y-%m-%d')";
+			$groupby_date_text = "DATE_FORMAT(i.incident_date, '%Y%m%d')";
+		}
+		elseif ($interval == 'hour')
+		{
+			$select_date_text = "DATE_FORMAT(i.incident_date, '%Y-%m-%d %H:%M')";
+			$groupby_date_text = "DATE_FORMAT(i.incident_date, '%Y%m%d%H')";
+		}
+		elseif ($interval == 'week')
+		{
+			$select_date_text = "STR_TO_DATE(CONCAT(CAST(YEARWEEK(i.incident_date) AS CHAR), ' Sunday'), '%X%V %W')";
+			$groupby_date_text = "YEARWEEK(i.incident_date)";
+		}
+
+		$graph_data = array();
+		$graph_data[0] = array();
+		$graph_data[0]['label'] = $category_title;
+		$graph_data[0]['color'] = $category_color;
+		$graph_data[0]['data'] = array();
+
+		// Gather allowed ids if we are looking at a specific category
+		$incident_id_in = '';
+		$params = array();
+		if ($category_id != 0)
+		{
+			$query = 'SELECT ic.incident_id AS id '
+			    . 'FROM '.$this->table_prefix.'incident_category ic '
+			    . 'INNER JOIN '.$this->table_prefix.'category c ON (ic.category_id = c.id) '
+			    . 'WHERE (c.id = :cid OR c.parent_id = :cid)';
+			
+			$params[':cid'] = $category_id;
+			$incident_id_in .= " AND i.id IN ( $query ) ";
+		}
+		
+		// Apply start and end date filters
+		if (isset($_GET['s']) AND isset($_GET['e']))
+		{
+			$query = 'SELECT filtered_incidents.id FROM '.$this->table_prefix.'incident AS filtered_incidents '
+			    . 'WHERE filtered_incidents.incident_date >= :datestart '
+			    . 'AND filtered_incidents.incident_date <= :dateend ';
+
+			// Cast timestamps to int to avoid php error - they'll be sanitized again by db_query
+			$params[':datestart'] = date("Y-m-d H:i:s", (int)$_GET['s']);
+			$params[':dateend'] = date('Y-m-d H:i:s', (int)$_GET['e']);
+			$incident_id_in .= " AND i.id IN ( $query ) ";
+		}
+
+		// Apply media type filters
+		if (isset($_GET['m']) AND intval($_GET['m']) > 0)
+		{
+			$query = "SELECT incident_id AS id FROM ".$this->table_prefix."media "
+			    . "WHERE media_type = :mtype ";
+
+			$params[':mtype'] = $_GET['m'];
+			$incident_id_in .= " AND i.id IN ( $query ) ";
+		}
+
+		// Fetch the timeline data
+		$query = 'SELECT UNIX_TIMESTAMP('.$select_date_text.') AS time, COUNT(i.id) AS number '
+		    . 'FROM '.$this->table_prefix.'incident AS i '
+		    . 'WHERE i.incident_active = 1 '.$incident_id_in.' '
+		    . 'GROUP BY '.$groupby_date_text;
+
+		foreach ($db->query($query, $params) as $items)
+		{
+			array_push($graph_data[0]['data'], array($items->time * 1000, $items->number));
+		}
+		
+		// If no data fake a flat line graph
+		// This is so jqplot still plots something
+		if (count($graph_data[0]['data']) == 0)
+		{
+			array_push($graph_data[0]['data'], array((int)$_GET['s'] * 1000, 0));
+			array_push($graph_data[0]['data'], array((int)$_GET['e'] * 1000, 0));
+		}
+		// HT: If only one point append start and end with 0 unless start or end has value
+		elseif (count($graph_data[0]['data']) == 1) { 
+			$start = $end = false;
+			if($graph_data[0]['data'][0][0] == (int)$_GET['s']) $start = true;
+			if($graph_data[0]['data'][0][0] == (int)$_GET['e']) $end = true;
+			if(!$start) array_unshift($graph_data[0]['data'], array((int)$_GET['s'] * 1000, 0));
+			if(!$end) array_push($graph_data[0]['data'], array((int)$_GET['e'] * 1000, 0));
+		}
+
+		// Debug: push the query back in json
+		//$graph_data['query'] = $db->last_query();
+
+		header('Content-type: application/json; charset=utf-8');
+		echo json_encode($graph_data);
+	}
+	
+
+	/**
+	 * Read in new layer KML via HttpClient
+	 * @param int $layer_id - ID of the new KML Layer
+	 */
+	public function layer($layer_id = 0)
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+
+		$layer = ORM::factory('layer')
+			->where('layer_visible', 1)
+			->find($layer_id);
+
+		if ($layer->loaded)
+		{
+			$layer_url = $layer->layer_url;
+			$layer_file = $layer->layer_file;
+
+			if ($layer_url != '')
+			{
+				// Pull from a URL
+				$layer_link = $layer_url;
+			}
+			else
+			{
+				// Pull from an uploaded file
+				$layer_link = Kohana::config('upload.directory').'/'.$layer_file;
+			}
+
+			$layer_request = new HttpClient($layer_link);
+			$content = $layer_request->execute();
+
+			if ($content === FALSE) 
+			{
+				throw new Kohana_Exception($layer_request->get_error_msg());
+			}
+			else
+			{
+				echo $content;
+			}
+		}
+		else
+		{
+			throw new Kohana_404_Exception();
+		}
+	}
+
+	/**
+	 * Get Geometry JSON
+	 * @param int $incident_id
+	 * @param string $incident_title
+	 * @param int $incident_date
+	 * @param string $incident_link
+	 * @return array $geometry
+	 */
+	protected function get_geometry($incident_id, $incident_title, $incident_date, $incident_link)
+	{
+		$geometry = array();
+		if ($incident_id)
+		{
+			$geom_data = $this->_get_geometry_data_for_incident($incident_id);
+			$wkt = new Wkt();
+
+			foreach ( $geom_data as $item )
+			{
+				$geom = $wkt->read($item->geometry);
+				$geom_array = $geom->getGeoInterface();
+
+				$title = ($item->geometry_label) ? $item->geometry_label : $incident_title;
+				$item_name = $this->get_title($title, $incident_link);
+					
+				$fillcolor = ($item->geometry_color) ? $item->geometry_color : "ffcc66";
+					
+				$strokecolor = ($item->geometry_color) ? $item->geometry_color : "CC0000";
+					
+				$strokewidth = ($item->geometry_strokewidth) ? $item->geometry_strokewidth : "3";
+
+				$json_item = array();
+				$json_item['type'] = 'Feature';
+				$json_item['properties'] = array(
+					'id' => $incident_id,
+					'feature_id' => $item->id,
+					'name' => $item_name,
+					'description' => $item->geometry_comment,
+					'color' => $fillcolor,
+					'icon' => '',
+					'strokecolor' => $strokecolor,
+					'strokewidth' => $strokewidth,
+					'link' => $incident_link,
+					'category' => array(0),
+					'timestamp' => strtotime($incident_date),
+				);
+				$json_item['geometry'] = $geom_array;
+
+				$geometry[] = $json_item;
+			}
+		}
+
+		return $geometry;
+	}
+
+
+	/**
+	 * Get geometry records from the database and cache 'em.
+	 *
+	 * They're heavily read from, no point going back to the db constantly to
+	 * get them.
+	 * @param int $incident_id - Incident to get geometry for
+	 * @return array
+	 */
+	public function _get_geometry_data_for_incident($incident_id) {
+		if (self::$geometry_data) {
+			return isset(self::$geometry_data[$incident_id]) ? self::$geometry_data[$incident_id] : array();
+		}
+
+		$db = new Database();
+		// Get Incident Geometries via SQL query as ORM can't handle Spatial Data
+		$sql = "SELECT id, incident_id, AsText(geometry) as geometry, geometry_label, 
+			geometry_comment, geometry_color, geometry_strokewidth FROM ".$this->table_prefix."geometry";
+		$query = $db->query($sql);
+
+		foreach ( $query as $item )
+		{
+			self::$geometry_data[$item->incident_id][] = $item;
+		}
+
+		return isset(self::$geometry_data[$incident_id]) ? self::$geometry_data[$incident_id] : array();
+	}
+
+
+	/**
+	 * Convert Longitude to Cartesian (Pixels) value
+	 * @param double $lon - Longitude
+	 * @return int
+	 */
+	private function _lonToX($lon)
+	{
+		return round(OFFSET + RADIUS * $lon * pi() / 180);
+	}
+
+	/**
+	 * Convert Latitude to Cartesian (Pixels) value
+	 * @param double $lat - Latitude
+	 * @return int
+	 */
+	private function _latToY($lat)
+	{
+		return round(OFFSET - RADIUS *
+					log((1 + sin($lat * pi() / 180)) /
+					(1 - sin($lat * pi() / 180))) / 2);
+	}
+
+	/**
+	 * Calculate distance using Cartesian (pixel) coordinates
+	 * @param int $lat1 - Latitude for point 1
+	 * @param int $lon1 - Longitude for point 1
+	 * @param int $lon2 - Latitude for point 2
+	 * @param int $lon2 - Longitude for point 2
+	 * @return int
+	 */
+	private function _pixelDistance($lat1, $lon1, $lat2, $lon2, $zoom)
+	{
+		$x1 = $this->_lonToX($lon1);
+		$y1 = $this->_latToY($lat1);
+
+		$x2 = $this->_lonToX($lon2);
+		$y2 = $this->_latToY($lat2);
+
+		return sqrt(pow(($x1-$x2),2) + pow(($y1-$y2),2)) >> (21 - $zoom);
+	}
+
+	/**
+	 * Calculate the center of a cluster of markers
+	 * @param array $cluster
+	 * @return array - (center, southwest bound, northeast bound)
+	 */
+	protected function calculate_center($cluster)
+	{
+		// Calculate average lat and lon of clustered items
+		$south = 90;
+		$west = 180;
+		$north = -90;
+		$east = -180;
+
+		$lat_sum = $lon_sum = 0;
+		foreach ($cluster as $marker)
+		{
+			// Normalising data
+			if (is_array($marker))
+			{
+				$marker = (object) $marker;
+			}
+
+			// Handle both reports::fetch_incidents() response and actual ORM objects
+			$latitude = isset($marker->latitude) ? $marker->latitude : $marker->location->latitude;
+			$longitude = isset($marker->longitude) ? $marker->longitude : $marker->location->longitude;
+			
+			if ($latitude < $south)
+			{
+				$south = $latitude;
+			}
+
+			if ($longitude < $west)
+			{
+				$west = $longitude;
+			}
+
+			if ($latitude > $north)
+			{
+				$north = $latitude;
+			}
+
+			if ($longitude > $east)
+			{
+				$east = $longitude;
+			}
+
+			$lat_sum += $latitude;
+			$lon_sum += $longitude;
+		}
+		$lat_avg = $lat_sum / count($cluster);
+		$lon_avg = $lon_sum / count($cluster);
+
+		$center = array('longitude' => $lon_avg, 'latitude' => $lat_avg);
+		$sw = array('longitude' => $west,'latitude' => $south);
+		$ne = array('longitude' => $east,'latitude' => $north);
+
+		return array(
+			"center"=>$center,
+			"sw"=>$sw,
+			"ne"=>$ne
+		);
+	}
+	
+	/**
+	 * Get encoded title linked to url
+	 * @param string $title - Item title
+	 * @param string $url - URL to link to
+	 * @return string
+	 */
+	protected function get_title($title, $url)
+	{
+		$item_name = "<a href='$url'>".$title."</a>";
+		$item_name = str_replace(array(chr(10),chr(13)), ' ', $item_name);
+		return $item_name;
+	}
+}
diff --git a/application/controllers/login.php b/application/controllers/login.php
new file mode 100644
index 0000000..c9f6085
--- /dev/null
+++ b/application/controllers/login.php
@@ -0,0 +1,1043 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller handles login requests.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Login_Controller extends Template_Controller {
+
+	public $auto_render = TRUE;
+	// Session Object
+	protected $session;
+
+	// Main template
+	public $template = 'login/main';
+
+
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->session = new Session();
+	}
+
+	public function index($user_id = 0)
+	{
+		// Set messages to display on the login page for the user
+		$message = FALSE;
+		$message_class = 'login_error';
+
+		$auth = Auth::instance();
+
+		// If already logged in redirect to user account page
+
+		$insufficient_role = FALSE;
+
+		if ($auth->logged_in())
+		{
+			// Redirect users to the relevant dashboard
+			if ($auth->logged_in('login'))
+			{
+				url::redirect($auth->get_user()->dashboard());
+			}
+
+			$insufficient_role = TRUE;
+			$message_class = 'login_error';
+			$message = Kohana::lang('ui_main.insufficient_role');
+		}
+
+		// setup and initialize form field names
+		$form = array(
+			'action'	=> '',
+			'username'	=> '',
+			'password'	=> '',
+			'password_again'  => '',
+			'name'		=> '',
+			'email'		=> '',
+			'resetemail' => '',
+			'confirmation_email' => '',
+		);
+		//	copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$openid_error = FALSE;
+		$success = FALSE;
+		$change_pw_success = FALSE;
+		$new_confirm_email_form = FALSE;
+
+		$action = (isset($_POST["action"])) ? $_POST["action"] : "";
+
+		// Override success variable if change_pw_success GET var is set
+		if (isset($_GET["change_pw_success"]))
+		{
+			$change_pw_success = TRUE;
+			$message_class = 'login_success';
+			$message = Kohana::lang('ui_main.password_changed_successfully');
+		}
+
+		// Show send new confirm email form
+		if (isset($_GET["new_confirm_email"]))
+		{
+			$new_confirm_email_form = TRUE;
+			$message_class = 'login_error';
+			$message = Kohana::lang('ui_main.must_confirm_email_address');
+		}
+
+		// Show send new confirm email form
+		if (isset($_GET["confirmation_failure"]))
+		{
+			$new_confirm_email_form = TRUE;
+			$message_class = 'login_error';
+			$message = Kohana::lang('ui_main.confirm_email_failed');
+		}
+
+		// Show that confirming the email address was a success
+		if (isset($_GET["confirmation_success"]))
+		{
+			// Check if approval is still required
+			if (Kohana::config('settings.manually_approve_users'))
+			{
+				$message_class = 'login_success';
+				$message = Kohana::lang('ui_main.confirm_email_successful_and_approval');
+			}
+			else
+			{
+				$message_class = 'login_success';
+				$message = Kohana::lang('ui_main.confirm_email_successful');
+			}
+		}
+
+		// Is this a password reset request? We need to show the password reset form if it is
+		if (isset($_GET["reset"]))
+		{
+			$this->template->token = $this->uri->segment(4);
+			$this->template->changeid = $this->uri->segment(3);
+		}
+
+		// Regular Form Post for Signin
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST AND isset($_POST["action"]) AND $_POST["action"] == "signin")
+		{
+
+			// START: Signin Process
+
+			$post = Validation::factory($_POST);
+			$post->pre_filter('trim');
+			$post->add_rules('username', 'required');
+			$post->add_rules('password', 'required');
+
+			if ($post->validate(FALSE))
+			{
+				// Sanitize $_POST data removing all inputs without rules
+				$postdata_array = $post->safe_array();
+
+				// Flip this flag to flase to skip the login
+				$valid_login = TRUE;
+
+				// Load the user
+				$user = ORM::factory('user', $postdata_array['username']);
+
+				$remember = (isset($post->remember)) ? TRUE : FALSE;
+
+				// Allow a login with username or email address, but we need to figure out which is
+				// which so we can pass the appropriate variable on login. Mostly used for RiverID
+
+				$email = $postdata_array['username'];
+				if (valid::email($email) == FALSE)
+				{
+					// Invalid Email, we need to grab it from the user account instead
+
+					$email = $user->email;
+					if (valid::email($email) == FALSE AND kohana::config('riverid.enable') == TRUE)
+					{
+						// We don't have any valid email for this user.
+						// Only skip login if we are authenticating with RiverID.
+						$valid_login = FALSE;
+					}
+				}
+
+				// Auth Login requires catching exceptions to properly show errors
+				try
+				{
+					$login = $auth->login($user, $postdata_array['password'], $remember, $email);
+
+					// Attempt a login
+					if ($login AND $valid_login )
+					{
+						// Action::user_login - User Logged In
+						Event::run('ushahidi_action.user_login',$user);
+
+						// Exists Redirect to Dashboard
+						url::redirect($user->dashboard());
+					}
+					else
+					{
+						// If user isn't confirmed, redirect to resend confirmation page
+						if (Kohana::config('settings.require_email_confirmation') AND ORM::factory('user', $user)->confirmed == 0)
+						{
+							url::redirect("login?new_confirm_email");
+						}
+						
+						// If user isn't approved, show not approved error
+						elseif (Kohana::config('settings.manually_approve_users')
+							AND ! ORM::factory('user', $user)->has(ORM::factory('role', 'login'))
+							)
+						{
+							$post->add_error('username', 'approval error');
+						}
+						
+						else
+						{
+							// Generic Error if exception not passed
+							$post->add_error('password', 'login error');
+						}
+					}
+				}
+				catch (Exception $e)
+				{
+					$error_message = $e->getMessage();
+
+					// We use a "custom" message because of RiverID.
+					$post->add_error('password', $error_message);
+				}
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				// We need to already have created an error message file, for Kohana to use
+				// Pass the error message file name to the errors() method
+				$errors = arr::merge($errors, $post->errors('auth'));
+				$form_error = TRUE;
+
+			}
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				// We need to already have created an error message file, for Kohana to use
+				// Pass the error message file name to the errors() method
+				$errors = arr::merge($errors, $post->errors('auth'));
+				$form_error = TRUE;
+			}
+
+			// END: Signin Process
+
+		}
+		elseif ($_POST AND isset($_POST["action"]) AND $_POST["action"] == "new")
+		{
+
+			// START: New User Process
+
+			$post = Validation::factory($_POST);
+
+			//	Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			$post->add_rules('password','required', 'length['.kohana::config('auth.password_length').']');
+			$post->add_rules('name','required','length[3,100]');
+			$post->add_rules('email','required','email','length[4,64]');
+			$post->add_callbacks('username', array($this,'username_exists_chk'));
+			$post->add_callbacks('email', array($this,'email_exists_chk'));
+
+			// If Password field is not blank
+			if ( ! empty($post->password))
+			{
+				$post->add_rules('password','required','length['.kohana::config('auth.password_length').']'
+					,'matches[password_again]');			
+			}
+			//pass the post object to any plugins that care to know.
+			Event::run('ushahidi_action.users_add_login_form', $post);
+			if ($post->validate())
+			{
+
+				$riverid_id = false;
+				if (kohana::config('riverid.enable') == true)
+				{
+					$riverid = new RiverID;
+					$riverid->email = $post->email;
+					$riverid->password = $post->password;
+					$riverid->register();
+					$riverid_id = $riverid->user_id;
+				}
+
+				$user = User_Model::create_user($post->email,$post->password,$riverid_id,$post->name);
+				//pass the new user on to any plugins that care to know
+				Event::run('ushahidi_action.user_edit', $user); 
+				// Send Confirmation email
+				$email_sent = $this->_send_email_confirmation($user);
+
+				if ($email_sent)
+				{
+					$message_class = 'login_success';
+					$message = Kohana::lang('ui_main.login_confirmation_sent');
+				}
+				elseif ($this->_send_email_admin_approval($user))
+				{
+					$message_class = 'login_success';
+					$message = Kohana::lang('ui_main.login_approval_required');
+				}
+				else
+				{
+					$message_class = 'login_success';
+					$message = Kohana::lang('ui_main.login_account_creation_successful');
+				}
+
+				$success = TRUE;
+				$action = "";
+			}
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('auth'));
+				$form_error = TRUE;
+			}
+
+			// END: New User Process
+
+		}
+		elseif ($_POST AND isset($_POST["action"]) AND $_POST["action"] == "forgot")
+		{
+
+			// START: Forgot Password Process
+
+			$post = Validation::factory($_POST);
+
+			//	Add some filters
+			$post->pre_filter('trim', TRUE);
+			$post->add_callbacks('resetemail', array($this,'email_exists_chk'));
+
+			if ($post->validate())
+			{
+				$user = ORM::factory('user',$post->resetemail);
+
+				// Existing User??
+				if ($user->loaded)
+				{
+					$email_sent = FALSE;
+					// Determine which reset method to use. The options are to use the RiverID server
+					//  or to use the normal method which just resets the password locally.
+					if (Kohana::config('riverid.enable') == TRUE AND ! empty($user->riverid))
+					{
+						// Reset on RiverID Server
+
+						$secret_link = url::site('login/index/'.$user->id.'/%token%?reset');
+						$message = $this->_email_resetlink_message($user->name, $secret_link);
+
+						$riverid = new RiverID;
+						$riverid->email = $post->resetemail;
+						$email_sent = $riverid->requestpassword($message);
+					}
+					else
+					{
+						// Reset locally
+						$secret = $user->forgot_password_token();
+						$secret_link = url::site('login/index/'.$user->id.'/'.urlencode($secret).'?reset');
+						$email_sent = $this->_email_resetlink($post->resetemail, $user->name, $secret_link);
+					}
+
+					if ($email_sent == TRUE)
+					{
+						$message_class = 'login_success';
+						$message = Kohana::lang('ui_main.login_confirmation_sent');
+					}
+					else
+					{
+						$message_class = 'login_error';
+						$message = Kohana::lang('ui_main.unable_send_email');
+					}
+
+					$success = TRUE;
+					$action = "";
+				}
+			}
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('auth'));
+				$form_error = TRUE;
+			}
+
+			// END: Forgot Password Process
+
+		}
+		elseif ($_POST AND isset($_POST["action"]) AND $_POST["action"] == "changepass")
+		{
+
+			// START: Password Change Process
+
+			$post = Validation::factory($_POST);
+
+			//	Add some filters
+			$post->pre_filter('trim', TRUE);
+			$post->add_rules('token','required');
+			$post->add_rules('changeid','required');
+			$post->add_rules('password','required','length['.Kohana::config('auth.password_length').']');
+			$post->add_rules('password','required','length['.Kohana::config('auth.password_length').']','matches[password_again]');
+
+			if ($post->validate())
+			{
+				$success = $this->_new_password($post->changeid, $post->password, $post->token);
+
+				if ($success == TRUE)
+				{
+					// We don't need to see this page anymore if we were successful. We want to go
+					//   to the login form and let the user know that they were successful at
+					//   changing their password
+
+					url::redirect("login?change_pw_success");
+					exit();
+				}
+				
+				$post->add_error('token', 'invalid');
+				
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('auth'));
+				$form_error = TRUE;
+			}
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('auth'));
+				$form_error = TRUE;
+			}
+
+			// END: Password Change Process
+
+		}
+		elseif ($_POST AND isset($_POST["action"]) AND $_POST["action"] == "resend_confirmation")
+		{
+			// START: Confirmation Email Resend Process
+
+			$post = Validation::factory($_POST);
+
+			//	Add some filters
+			$post->pre_filter('trim', TRUE);
+			$post->add_callbacks('confirmation_email', array($this,'email_exists_chk'));
+
+			if ($post->validate())
+			{
+				$user = ORM::factory('user',$post->confirmation_email);
+
+				if ($user->loaded)
+				{
+					// Send Confirmation email
+					$email_sent = $this->_send_email_confirmation($user);
+
+					if ($email_sent)
+					{
+						$message_class = 'login_success';
+						$message = Kohana::lang('ui_main.login_confirmation_sent');
+						$success = TRUE;
+					}
+					else
+					{
+						$message_class = 'login_error';
+						$message = Kohana::lang('ui_main.unable_send_email');
+						$success = FALSE;
+					}
+				}
+				else
+				{
+					// ERROR: User doesn't exist
+					$message_class = 'login_error';
+					$message = Kohana::lang('ui_main.login_email_doesnt_exist');
+					$success = FALSE;
+				}
+			}
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('auth'));
+				$form_error = TRUE;
+			}
+		}
+
+		// Only if we allow OpenID, should we even try this
+		if (Kohana::config('config.allow_openid') == TRUE)
+		{
+
+			// START: OpenID Shenanigans
+
+			// OpenID Post
+			try
+			{
+				$openid = new OpenID;
+
+				// Retrieve the Name (if available) and Email
+				$openid->required = array("namePerson", "contact/email");
+
+				if( ! $openid->mode)
+				{
+					if(isset($_POST["openid_identifier"]))
+					{
+						$openid->identity = $_POST["openid_identifier"];
+						header("Location: " . $openid->authUrl());
+					}
+				}
+				elseif ($openid->mode == "cancel")
+				{
+					$openid_error = TRUE;
+					$message_class = 'login_error';
+					$message = "You have canceled authentication!";
+				}
+				else
+				{
+					if ($openid->validate())
+					{
+						// Does User Exist?
+						$openid_user = ORM::factory("openid")
+							->where("openid", $openid->identity)
+							->find();
+
+						if ($openid_user->loaded AND $openid_user->user)
+						{
+							// First log all other sessions out
+							$auth->logout();
+
+							// Initiate Ushahidi side login + AutoLogin
+							$auth->force_login($openid_user->user->username);
+
+							// Exists Redirect to Dashboard
+							url::redirect($user->dashboard());
+						}
+						else
+						{
+							// Does this openid have the required email??
+							$new_openid = $openid->getAttributes();
+							if ( ! isset($new_openid["contact/email"]) OR
+								empty($new_openid["contact/email"]))
+							{
+								$openid_error = TRUE;
+								$message_class = 'login_error';
+								$message = $openid->identity . " has not been logged in. No Email Address Found.";
+							}
+							else
+							{
+								// Create new User and save OpenID
+								$user = ORM::factory("user");
+
+								// But first... does this email address already exist
+								// in the system?
+								if ($user->email_exists($new_openid["contact/email"]))
+								{
+									$openid_error = TRUE;
+									$message_class = 'login_error';
+									$message = $new_openid["contact/email"] . " is already registered in our system.";
+								}
+								else
+								{
+									$username = "user".time(); // Random User Name from TimeStamp - can be changed later
+									$password = text::random("alnum", 16); // Create Random Strong Password
+
+									// Name Available?
+									$user->name = (isset($new_openid["namePerson"]) AND ! empty($new_openid["namePerson"]))
+										? $new_openid["namePerson"]
+										: $username;
+									$user->username = $username;
+									$user->password = $password;
+									$user->email = $new_openid["contact/email"];
+
+									// Add New Roles
+									$user->add(ORM::factory('role', 'login'));
+									$user->add(ORM::factory('role', 'member'));
+
+									$user->save();
+
+									// Save OpenID and Association
+									$openid_user->user_id = $user->id;
+									$openid_user->openid = $openid->identity;
+									$openid_user->openid_email = $new_openid["contact/email"];
+									$openid_user->openid_server = $openid->server;
+									$openid_user->openid_date = date("Y-m-d H:i:s");
+									$openid_user->save();
+
+									// Initiate Ushahidi side login + AutoLogin
+									$auth->login($username, $password, TRUE);
+
+									// Redirect to Dashboard
+									url::redirect($user->dashboard());
+								}
+							}
+						}
+					}
+					else
+					{
+						$openid_error = TRUE;
+						$message_class = 'login_error';
+						$message = $openid->identity . "has not been logged in.";
+					}
+				}
+			}
+			catch (ErrorException $e)
+			{
+				$openid_error = TRUE;
+				$message_class = 'login_error';
+				$message = $e->getMessage();
+			}
+
+			// END: OpenID Shenanigans
+
+		}
+
+		// Set the little badge under the form informing users that their logins are being managed
+		//   by an external service.
+		$this->template->riverid_information = '';
+		if (kohana::config('riverid.enable') == TRUE)
+		{
+			$riverid = new RiverID;
+			$this->template->riverid_information = Kohana::lang('ui_main.riverid_information',$riverid->name);
+			$this->template->riverid_url = $riverid->url;
+		}
+
+		$this->template->errors = $errors;
+		$this->template->success = $success;
+		$this->template->change_pw_success = $change_pw_success;
+		$this->template->form = $form;
+		$this->template->form_error = $form_error;
+		$this->template->new_confirm_email_form = $new_confirm_email_form;
+
+		// Message to user
+		$this->template->message_class = $message_class;
+		$this->template->message = $message;
+
+		// This just means the user isn't a member or an admin, so they have nowhere to go, but they are logged in.
+		$this->template->insufficient_role = $insufficient_role;
+
+		$this->template->site_name = Kohana::config('settings.site_name');
+		$this->template->site_tagline = Kohana::config('settings.site_tagline');
+
+		// Javascript Header
+		$this->template->js = new View('login/login_js');
+		$this->template->js->action = $action;
+
+		// Header Nav
+		$header_nav = new View('header_nav');
+		$this->template->header_nav = $header_nav;
+		$this->template->header_nav->loggedin_user = FALSE;
+		if ( isset(Auth::instance()->get_user()->id) )
+		{
+			// Load User
+			$this->template->header_nav->loggedin_role = Auth::instance()->get_user()->dashboard();
+			$this->template->header_nav->loggedin_user = Auth::instance()->get_user();
+		}
+		$this->template->header_nav->site_name = Kohana::config('settings.site_name');
+	}
+
+	/**
+	 * Confirms user registration
+	 */
+	public function verify()
+	{
+		$auth = Auth::instance();
+
+		$code = (isset($_GET['c']) AND ! empty($_GET['c'])) ? $_GET['c'] : "";
+		$email = (isset($_GET['e']) AND ! empty($_GET['e'])) ? $_GET['e'] : "";
+
+		$user = ORM::factory("user")
+			->where("code", $code)
+			->where("email", $email)
+			->where("confirmed != 1")
+			->find();
+
+		if ($user->loaded)
+		{
+			$user->confirmed = 1;
+
+			// Give the user the appropriate roles if the admin doesn't need to verify accounts
+			//   and if they don't already have role assigned.
+			if (Kohana::config('settings.manually_approve_users') == 0
+				AND ! $user->has(ORM::factory('role', 'login')))
+			{
+				$user->add(ORM::factory('role', 'login'));
+				$user->add(ORM::factory('role', 'member'));
+			}
+			else
+			{
+				// Try sending and email to the admin for approval
+				$this->_send_email_admin_approval($user);
+			}
+
+			$user->save();
+
+			// Log all other sessions out so they can log in nicely on the login page
+			$auth->logout();
+
+			// Redirect to login
+			url::redirect("login?confirmation_success");
+		}
+		else
+		{
+			// Redirect to Login which will log themin if they are already logged in
+			url::redirect("login?confirmation_failure");
+		}
+
+
+	}
+
+	/**
+	 * Facebook connect function
+	 */
+	public function facebook()
+	{
+		$auth = Auth::instance();
+
+		$this->template = "";
+		$this->auto_render = FALSE;
+
+		$settings = ORM::factory("settings")->find(1);
+
+		$appid = $settings->facebook_appid;
+		$appsecret = $settings->facebook_appsecret;
+		$next_url = url::site()."members/login/facebook";
+		$cancel_url = url::site()."members/login";
+
+		// Create our Application instance.
+		$facebook = new Facebook(array(
+			'appId'  => $appid,
+			'secret' => $appsecret,
+			'cookie' => true
+		));
+
+		// Get User ID
+		$fb_user = $facebook->getUser();
+		if ($fb_user)
+		{
+			try
+			{
+		    	// Proceed knowing you have a logged in user who's authenticated.
+				$new_openid = $facebook->api('/me');
+
+				// Does User Exist?
+				$openid_user = ORM::factory("openid")
+					->where("openid", "facebook_".$new_openid["id"])
+					->find();
+
+				if ($openid_user->loaded AND $openid_user->user)
+				{
+					// First log all other sessions out
+					$auth->logout();
+
+					// Initiate Ushahidi side login + AutoLogin
+					$auth->force_login($openid_user->user->username);
+
+					// Exists Redirect to Dashboard
+					url::redirect($auth->get_user()->dashboard());
+				}
+				else
+				{
+					// Does this login have the required email??
+					if ( ! isset($new_openid["email"]) OR empty($new_openid["email"]))
+					{
+						$openid_error = "User has not been logged in. No Email Address Found.";
+
+						// Redirect back to login
+						url::redirect("login");
+					}
+					else
+					{
+						// Create new User and save OpenID
+						$user = ORM::factory("user");
+
+						// But first... does this email address already exist
+						// in the system?
+						if ($user->email_exists($new_openid["email"]))
+						{
+							$openid_error = $new_openid["email"] . " is already registered in our system.";
+
+							// Redirect back to login
+							url::redirect("login");
+						}
+						else
+						{
+							$username = "user".time(); // Random User Name from TimeStamp - can be changed later
+							$password = text::random("alnum", 16); // Create Random Strong Password
+
+							// Name Available?
+							$user->name = (isset($new_openid["name"]) AND ! empty($new_openid["name"]))
+								? $new_openid["name"]
+								: $username;
+							$user->username = $username;
+							$user->password = $password;
+							$user->email = $new_openid["email"];
+
+							// Add New Roles
+							$user->add(ORM::factory('role', 'login'));
+							$user->add(ORM::factory('role', 'member'));
+
+							$user->save();
+
+							// Save OpenID and Association
+							$openid_user->user_id = $user->id;
+							$openid_user->openid = "facebook_".$new_openid["id"];
+							$openid_user->openid_email = $new_openid["email"];
+							$openid_user->openid_server = Kohana::config('config.external_site_protocol').'://www.facebook.com';
+							$openid_user->openid_date = date("Y-m-d H:i:s");
+							$openid_user->save();
+
+							// Initiate Ushahidi side login + AutoLogin
+							$auth->login($username, $password, TRUE);
+
+							// Redirect to Dashboard
+							url::redirect($auth->get_user()->dashboard());
+						}
+					}
+				}
+			}
+			catch (FacebookApiException $e)
+			{
+				error_log($e);
+				$user = null;
+			}
+		}
+		else
+		{
+			$login_url = $facebook->getLoginUrl(
+				array(
+					'canvas' => 1,
+					'fbconnect' => 0,
+					'scope' => "email,publish_stream",
+					'next' => $next_url,
+					'cancel' => $cancel_url
+				)
+			);
+
+			url::redirect($login_url);
+		}
+	}
+
+	/**
+	 * Checks if username already exists.
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function username_exists_chk(Validation $post)
+	{
+		$users = ORM::factory('user');
+		// If add->rules validation found any errors, get me out of here!
+		if (array_key_exists('username', $post->errors()))
+			return;
+
+		if ($users->username_exists($post->username))
+			$post->add_error( 'username', 'exists');
+	}
+
+	/**
+	 * Checks if email address is associated with an account.
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function email_exists_chk( Validation $post )
+	{
+		$users = ORM::factory('user');
+		if ($post->action == "new")
+		{
+			if (array_key_exists('email',$post->errors()))
+				return;
+
+			if ($users->email_exists( $post->email ) )
+				$post->add_error('email','exists');
+		}
+		elseif($post->action == "forgot")
+		{
+			if (array_key_exists('resetemail',$post->errors()))
+				return;
+
+			if ( ! $users->email_exists( $post->resetemail ) )
+				$post->add_error('resetemail','invalid');
+		}
+	}
+
+	/**
+	 * Create New password upon user request.
+	 */
+	private function _new_password($user_id = 0, $password, $token)
+	{
+		$auth = Auth::instance();
+		$user = ORM::factory('user',$user_id);
+		if ($user->loaded == true)
+		{
+			// Determine Method (RiverID or standard)
+
+			if (kohana::config('riverid.enable') == TRUE AND ! empty($user->riverid))
+			{
+				// Use RiverID
+
+				// We don't really have to save the password locally but if a deployer
+				//   ever wants to switch back locally, it's nice to have the pw there
+				$user->password = $password;
+				$user->save();
+
+				// Relay the password change back to the RiverID server
+				$riverid = new RiverID;
+				$riverid->email = $user->email;
+				$riverid->token = $token;
+				$riverid->new_password = $password;
+				if ($riverid->setpassword() == FALSE)
+				{
+					return FALSE;
+				}
+
+			}
+			else
+			{
+				// Use Standard
+				if($user->check_forgot_password_token($token))
+				{
+					$user->password = $password;
+					$user->save();
+				}
+				else
+				{
+					return FALSE;
+				}
+			}
+
+			return TRUE;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Sends an email confirmation
+	 */
+	private function _send_email_confirmation($user)
+	{
+		$settings = Kohana::config('settings');
+
+		// Check if we require users to go through this process
+		if ($settings['require_email_confirmation'] == 0)
+		{
+			return FALSE;
+		}
+
+		$email = $user->email;
+		$code = text::random('alnum', 20);
+		$user->code = $code;
+		$user->save();
+
+		$url = url::site()."login/verify/?c=".urlencode($code)."&e=".urlencode($email);
+
+		$to = $email;
+		$from = array($settings['site_email'], $settings['site_name']);
+		$subject = $settings['site_name'].' '.Kohana::lang('ui_main.login_signup_confirmation_subject');
+		$message = Kohana::lang('ui_main.login_signup_confirmation_message',
+			array($settings['site_name'], $url));
+
+		email::send($to, $from, $subject, $message, FALSE);
+
+		return TRUE;
+	}
+
+	/**
+	 * Sends an email for admin approval
+	 */
+	private function _send_email_admin_approval($user)
+	{
+		// Check if we require users to go through this process
+		if (! Kohana::config('settings.manually_approve_users'))
+		{
+			return FALSE;
+		}
+
+		$url = url::site('admin/users/edit/'.$user->id);
+
+		$admins = ORM::factory('User')
+			->join('roles_users', 'roles_users.user_id', 'users.id', 'INNER')
+			->join('roles', 'roles_users.role_id', 'roles.id', 'INNER')
+			->where("(roles.name = 'superadmin' OR roles.name = 'admin')")
+			->where("users.notify", 1)
+			->find_all();
+		
+		$emails = $admins->select_list('id', 'email');
+		
+		//$to = $emails;
+		$from = array(Kohana::config('settings.site_email'), Kohana::config('settings.site_name'));
+		$subject = Kohana::config('settings.site_name').' '.Kohana::lang('ui_main.login_signup_admin_approval_subject');
+		$message = Kohana::lang('ui_main.login_signup_admin_approval_message',
+			array(Kohana::config('settings.site_name'), $url));
+
+		foreach ($emails as $id => $email)
+		{
+			if ( ! email::send($email, $from, $subject, $message, FALSE))
+			{
+				Kohana::log('error', "email to $email could not be sent");
+			}
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Email reset link to the user.
+	 *
+	 * @param the email address of the user requesting a password reset.
+	 * @param the username of the user requesting a password reset.
+	 * @param the new generated password.
+	 *
+	 * @return void.
+	 */
+	private function _email_resetlink( $email, $name, $secret_url )
+	{
+		$to = $email;
+		$from = array(Kohana::config('settings.site_email'), Kohana::config('settings.site_name'));
+		$subject = Kohana::lang('ui_admin.password_reset_subject');
+		$message = $this->_email_resetlink_message($name, $secret_url);
+
+		try {
+			$recipients = email::send( $to, $from, $subject, $message, FALSE );
+		}
+		catch (Exception $e)
+		{
+			Kohana::log('alert', Swift_LogContainer::getLog()->dump(true));
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Generate the email message body that goes out to the user when a password is reset
+	 *
+	 * @param the username of the user requesting a password reset.
+	 * @param the new generated password.
+	 *
+	 * @return void.
+	 */
+	private function _email_resetlink_message( $name, $secret_url )
+	{
+		$message = Kohana::lang('ui_admin.password_reset_message_line_1').' '.$name.",\n";
+		$message .= Kohana::lang('ui_admin.password_reset_message_line_2').' '.$name.". ";
+		$message .= Kohana::lang('ui_admin.password_reset_message_line_3')."\n\n";
+		$message .= $secret_url."\n\n";
+
+		return $message;
+
+	}
+
+}
diff --git a/application/controllers/logout.php b/application/controllers/logout.php
new file mode 100644
index 0000000..e1505cd
--- /dev/null
+++ b/application/controllers/logout.php
@@ -0,0 +1,43 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller handles login requests.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Logout_Controller extends Controller {
+	
+	protected $destination = 'login';
+
+    public function __construct()
+    {
+        parent::__construct();
+    }
+	
+    public function index()
+    {
+		$auth = new Auth;
+		$auth->logout(TRUE);
+
+		url::redirect($this->destination);	
+	}
+	
+	public function front()
+    {
+    	/**
+	     * If the login page is for the frontend, hit this function first.
+	     */
+		
+		$this->destination = '/';
+		$this->index();
+		
+    }
+}
diff --git a/application/controllers/main.php b/application/controllers/main.php
new file mode 100644
index 0000000..4427f4f
--- /dev/null
+++ b/application/controllers/main.php
@@ -0,0 +1,477 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This is the controller for the main site.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class Main_Controller extends Template_Controller {
+	/**
+	 * Automatically render the views loaded in this controller
+	 * @var bool
+	 */
+	public $auto_render = TRUE;
+
+	/**
+	 * Name of the template view
+	 * @var string
+	 */
+	public $template = 'layout';
+
+	/**
+	 * Cache object - to be used for caching content
+	 * @var Cache
+	 */
+	protected $cache;
+
+	/**
+	 * Whether the current controller is cacheable - defaults to FALSE
+	 * @var bool
+	 */
+	public $is_cachable = FALSE;
+
+	/**
+	 * Session object
+	 * @var Session
+	 */
+	protected $session;
+
+	/**
+	 * Prefix for the database tables
+	 * @var string
+	 */
+	protected $table_prefix;
+
+	/**
+	 * Themes helper library object
+	 * @var Themes
+	 */
+	protected $themes;
+
+	public function __construct()
+	{
+		parent::__construct();
+
+		// Load Session
+		$this->session = Session::instance();
+
+		// Load cache
+		$this->cache = new Cache;
+
+        // Load Header & Footer
+		$this->template->header  = new View('header');
+		$this->template->footer  = new View('footer');
+
+		// Themes Helper
+		$this->themes = new Themes();
+		$this->themes->requirements();
+		$this->themes->frontend = TRUE;
+		$this->themes->api_url = Kohana::config('settings.api_url');
+		$this->template->header->submit_btn = $this->themes->submit_btn();
+		$this->template->header->languages = $this->themes->languages();
+		$this->template->header->search = $this->themes->search();
+
+		// Set Table Prefix
+		$this->table_prefix = Kohana::config('database.default.table_prefix');
+
+		// Retrieve Default Settings
+		$site_name = Kohana::config('settings.site_name');
+
+		// Get banner image and pass to the header
+		if (Kohana::config('settings.site_banner_id') != NULL)
+		{
+			$banner = ORM::factory('media')->find(Kohana::config('settings.site_banner_id'));
+			$this->template->set_global('banner', url::convert_uploaded_to_abs($banner->media_link));
+		}
+		else
+		{
+			$this->template->set_global('banner',NULL);
+		}
+
+		// Prevent Site Name From Breaking up if its too long
+		// by reducing the size of the font
+		$site_name_style = (strlen($site_name) > 20) ? " style=\"font-size:21px;\"" : "";
+
+		$this->template->header->private_deployment = Kohana::config('settings.private_deployment');
+
+		$this->template->set_global('site_name', $site_name);
+		$this->template->set_global('site_name_style', $site_name_style);
+		$this->template->set_global('site_tagline', Kohana::config('settings.site_tagline'));
+
+		// page_title is a special variable that will be overridden by other controllers to
+		//    change the title bar contents
+		$this->template->header->page_title = '';
+
+		//pass the URI to the header so we can dynamically add css classes to the "body" tag
+		$this->template->header->uri_segments = Router::$segments;
+
+		$this->template->header->this_page = "";
+
+		// add copyright info
+		$this->template->footer->site_copyright_statement = '';
+		$site_copyright_statement = trim(Kohana::config('settings.site_copyright_statement'));
+		if ($site_copyright_statement != '')
+		{
+			$this->template->footer->site_copyright_statement = $site_copyright_statement;
+		}
+
+		// Display news feeds?
+		$this->template->header->allow_feed = Kohana::config('settings.allow_feed');
+
+		// Header Nav
+		$header_nav = new View('header_nav');
+		$this->template->header->header_nav = $header_nav;
+		$this->template->header->header_nav->loggedin_user = FALSE;
+		if ( isset(Auth::instance()->get_user()->id) )
+		{
+			// Load User
+			$this->template->header->header_nav->loggedin_role = Auth::instance()->get_user()->dashboard();
+			$this->template->header->header_nav->loggedin_user = Auth::instance()->get_user();
+		}
+		$this->template->header->header_nav->site_name = Kohana::config('settings.site_name');
+		
+		Event::add('ushahidi_filter.view_pre_render.layout', array($this, '_pre_render'));
+	}
+
+	/**
+	 * Get Trusted Category Count
+	 */
+	public function get_trusted_category_count($id)
+	{
+		$trusted = ORM::factory("incident")
+						->join("incident_category","incident.id","incident_category.incident_id")
+						->where("category_id",$id);
+		return $trusted;
+	}
+
+    public function index()
+    {
+        $this->template->header->this_page = 'home';
+        $this->template->content = new View('main/layout');
+
+		// Cacheable Main Controller
+		$this->is_cachable = TRUE;
+
+		// Map and Slider Blocks
+		$div_map = new View('main/map');
+		$div_timeline = new View('main/timeline');
+
+		// Filter::map_main - Modify Main Map Block
+		Event::run('ushahidi_filter.map_main', $div_map);
+
+		// Filter::map_timeline - Modify Main Map Block
+		Event::run('ushahidi_filter.map_timeline', $div_timeline);
+
+		$this->template->content->div_map = $div_map;
+		$this->template->content->div_timeline = $div_timeline;
+
+		// Check if there is a site message
+		$this->template->content->site_message = '';
+		$site_message = trim(Kohana::config('settings.site_message'));
+		if($site_message != '')
+		{
+			// Send the site message to both the header and the main content body
+			//   so a theme can utilize it in either spot.
+			$this->template->content->site_message = $site_message;
+			$this->template->header->site_message = $site_message;
+		}
+
+		// Get locale
+		$l = Kohana::config('locale.language.0');
+
+        // Get all active top level categories
+		$parent_categories = array();
+		$all_parents = ORM::factory('category')
+		    ->where('category_visible', '1')
+		    ->where('parent_id', '0')
+		    ->find_all();
+
+		foreach ($all_parents as $category)
+		{
+			// Get The Children
+			$children = array();
+			foreach ($category->children as $child)
+			{
+				$child_visible = $child->category_visible;
+				if ($child_visible)
+				{
+					// Check for localization of child category
+					$display_title = Category_Lang_Model::category_title($child->id,$l);
+
+					$ca_img = ($child->category_image != NULL)
+					    ? url::convert_uploaded_to_abs($child->category_image)
+					    : NULL;
+					
+					$children[$child->id] = array(
+						$display_title,
+						$child->category_color,
+						$ca_img
+					);
+				}
+			}
+
+			// Check for localization of parent category
+			$display_title = Category_Lang_Model::category_title($category->id,$l);
+
+			// Put it all together
+			$ca_img = ($category->category_image != NULL)
+			    ? url::convert_uploaded_to_abs($category->category_image)
+			    : NULL;
+
+			$parent_categories[$category->id] = array(
+				$display_title,
+				$category->category_color,
+				$ca_img,
+				$children
+			);
+		}
+		$this->template->content->categories = $parent_categories;
+
+		// Get all active Layers (KMZ/KML)
+		$layers = array();
+		$config_layers = Kohana::config('map.layers'); // use config/map layers if set
+		if ($config_layers == $layers) {
+			foreach (ORM::factory('layer')
+					  ->where('layer_visible', 1)
+					  ->find_all() as $layer)
+			{
+				$layers[$layer->id] = array($layer->layer_name, $layer->layer_color,
+					$layer->layer_url, $layer->layer_file);
+			}
+		}
+		else
+		{
+			$layers = $config_layers;
+		}
+		$this->template->content->layers = $layers;
+
+		// Get Default Color
+		$this->template->content->default_map_all = Kohana::config('settings.default_map_all');
+
+		// Get default icon
+		$this->template->content->default_map_all_icon = '';
+		if (Kohana::config('settings.default_map_all_icon_id'))
+		{
+			$icon_object = ORM::factory('media')->find(Kohana::config('settings.default_map_all_icon_id'));
+			$this->template->content->default_map_all_icon = Kohana::config('upload.relative_directory')."/".$icon_object->media_medium;
+		}
+
+		// Get Twitter Hashtags
+		$this->template->content->twitter_hashtag_array = array_filter(array_map('trim',
+			explode(',', Kohana::config('settings.twitter_hashtags'))));
+
+		// Get Report-To-Email
+		$this->template->content->report_email = Kohana::config('settings.site_email');
+
+		// Get SMS Numbers
+		$phone_array = array();
+		$sms_no1 = Kohana::config('settings.sms_no1');
+		$sms_no2 = Kohana::config('settings.sms_no2');
+		$sms_no3 = Kohana::config('settings.sms_no3');
+		if ( ! empty($sms_no1))
+		{
+			$phone_array[] = $sms_no1;
+		}
+		if ( ! empty($sms_no2))
+		{
+			$phone_array[] = $sms_no2;
+		}
+		if ( ! empty($sms_no3))
+		{
+			$phone_array[] = $sms_no3;
+		}
+		$this->template->content->phone_array = $phone_array;
+
+		// Get external apps
+		$external_apps = array();
+		// Catch errors, in case we have an old db
+		try {
+			$external_apps = ORM::factory('externalapp')->find_all();
+		}
+		catch(Exception $e) {}
+		$this->template->content->external_apps = $external_apps;
+
+        // Get The START, END and Incident Dates
+		$intervalDate = ""; // HT: manual intervalDate
+        $startDate = "";
+		$endDate = "";
+		$display_startDate = 0;
+		$display_endDate = 0;
+
+		$db = new Database();
+		
+        // Next, Get the Range of Years
+		$query = $db->query('SELECT DATE_FORMAT(incident_date, \'%Y-%c\') AS dates '
+		    . 'FROM '.$this->table_prefix.'incident '
+		    . 'WHERE incident_active = 1 '
+		    . 'GROUP BY DATE_FORMAT(incident_date, \'%Y-%c\') '
+		    . 'ORDER BY incident_date');
+
+		$first_year = date('Y');
+		$last_year = date('Y');
+		$first_month = 1;
+		$last_month = 12;
+		$i = 0;
+
+		foreach ($query as $data)
+		{
+			$date = explode('-',$data->dates);
+
+			$year = $date[0];
+			$month = $date[1];
+
+			// Set first year
+			if ($i == 0)
+			{
+				$first_year = $year;
+				$first_month = $month;
+			}
+
+			// Set last dates
+			$last_year = $year;
+			$last_month = $month;
+
+			$i++;
+		}
+
+		$show_year = $first_year;
+		$selected_start_flag = TRUE;
+
+		while ($show_year <= $last_year)
+		{
+			$startDate .= "<optgroup label=\"".$show_year."\">";
+
+			$s_m = 1;
+			if ($show_year == $first_year)
+			{
+				// If we are showing the first year, the starting month may not be January
+				$s_m = $first_month;
+			}
+
+			$l_m = 12;
+			if ($show_year == $last_year)
+			{
+				// If we are showing the last year, the ending month may not be December
+				$l_m = $last_month;
+			}
+
+			for ( $i=$s_m; $i <= $l_m; $i++ )
+			{
+				if ($i < 10 )
+				{
+					// All months need to be two digits
+					$i = "0".$i;
+				}
+				$startDate .= "<option value=\"".strtotime($show_year."-".$i."-01")."\"";
+				if($selected_start_flag == TRUE)
+				{
+					$display_startDate = strtotime($show_year."-".$i."-01");
+					$startDate .= " selected=\"selected\" ";
+					$selected_start_flag = FALSE;
+				}
+				$startDate .= ">".date('M', mktime(0,0,0,$i,1))." ".$show_year."</option>";
+			}
+			$startDate .= "</optgroup>";
+
+			$endDate .= "<optgroup label=\"".$show_year."\">";
+			
+			for ( $i=$s_m; $i <= $l_m; $i++ )
+			{
+				if ( $i < 10 )
+				{
+					// All months need to be two digits
+					$i = "0".$i;
+				}
+				$endDate .= "<option value=\"".strtotime($show_year."-".$i."-".date('t', mktime(0,0,0,$i,1))." 23:59:59")."\"";
+
+                if($i == $l_m AND $show_year == $last_year)
+				{
+					$display_endDate = strtotime($show_year."-".$i."-".date('t', mktime(0,0,0,$i,1))." 23:59:59");
+					$endDate .= " selected=\"selected\" ";
+				}
+				$endDate .= ">".date('M', mktime(0,0,0,$i,1))." ".$show_year."</option>";
+			}
+			
+			$endDate .= "</optgroup>";
+
+			// Show next year
+			$show_year++;
+		}
+
+		//HT: Manaul time interval form input
+		$intervals = array('' =>'Auto', 'hour' => 'Hourly', 'day' => 'Daily', 'week' => 'Weekly', 'month' => 'Monthly');
+		foreach($intervals as $val => $label) {
+			$intervalDate .= "<option value=\"".$val."\"";
+			$intervalDate .= ">".Kohana::lang('ui_main.'.$label)."</option>";
+		}
+		// HT: End of time interval form input
+
+		Event::run('ushahidi_filter.active_startDate', $display_startDate);
+		Event::run('ushahidi_filter.active_endDate', $display_endDate);
+		Event::run('ushahidi_filter.startDate', $startDate);
+		Event::run('ushahidi_filter.endDate', $endDate);
+
+		$this->template->content->div_timeline->startDate = $startDate;
+		$this->template->content->div_timeline->endDate = $endDate;
+		$this->template->content->div_timeline->intervalDate = $intervalDate; // HT: manual interval time selection options
+
+		// Javascript Header
+		$this->themes->map_enabled = TRUE;
+		$this->themes->slider_enabled = TRUE;
+		
+		if (Kohana::config('settings.enable_timeline'))
+		{
+			$this->themes->timeline_enabled = TRUE;
+		}
+
+		// Map Settings
+		$marker_radius = Kohana::config('map.marker_radius');
+		$marker_opacity = Kohana::config('map.marker_opacity');
+		$marker_stroke_width = Kohana::config('map.marker_stroke_width');
+		$marker_stroke_opacity = Kohana::config('map.marker_stroke_opacity');
+
+		$this->themes->js = new View('main/main_js');
+
+		$this->themes->js->marker_radius = ($marker_radius >=1 AND $marker_radius <= 10 )
+		    ? $marker_radius
+		    : 5;
+
+		$this->themes->js->marker_opacity = ($marker_opacity >=1 AND $marker_opacity <= 10 )
+		    ? $marker_opacity * 0.1
+		    : 0.9;
+
+		$this->themes->js->marker_stroke_width = ($marker_stroke_width >=1 AND $marker_stroke_width <= 5)
+		    ? $marker_stroke_width
+		    : 2;
+
+		$this->themes->js->marker_stroke_opacity = ($marker_stroke_opacity >=1 AND $marker_stroke_opacity <= 10)
+		    ? $marker_stroke_opacity * 0.1
+		    : 0.9;
+
+
+		$this->themes->js->active_startDate = $display_startDate;
+		$this->themes->js->active_endDate = $display_endDate;
+
+		$this->themes->js->blocks_per_row = Kohana::config('settings.blocks_per_row');
+	}
+	
+	/**
+	 * Trigger themes->requirements() at the last minute
+	 * 
+	 * This is in case features are enabled/disabled
+	 */
+	public function _pre_render()
+	{
+		$this->themes->requirements();
+		$this->themes->plugin_requirements();
+		$this->template->header->header_block = $this->themes->header_block();
+		$this->template->footer->footer_block = $this->themes->footer_block();
+	}
+
+} // End Main
diff --git a/application/controllers/media.php b/application/controllers/media.php
new file mode 100644
index 0000000..dc0bfde
--- /dev/null
+++ b/application/controllers/media.php
@@ -0,0 +1,142 @@
+<?php
+/**
+ * Media Controller
+ * A controller used to serve css, images and js files from the media directory.
+ *
+ * This class also compresses js and css files with gzip compression (if the browser supports it) and correctly handles
+ * ETag/Last-Modified headers to prevent sending unmodified files to the client.
+ *
+ * //// GZIP Compression has been disabled in this controller and is handled
+ * by settings in the config.php file where it can be enabled/disabled ////
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+*/
+
+class Media_Controller extends Controller {
+
+    // Javascript URI
+    public function js()
+    {
+        $this->_send();
+    }
+
+    // CSS URI
+    public function css()
+    {
+        $this->_send();
+    }
+
+    // Image URI
+    public function img()
+    {
+        $this->_send();
+    }
+
+    // Method retrieves file data via file_get_contents
+    public function _send()
+    {
+        $gzip = false;  // Enable/Disable GZip Compression
+
+        $segments = $this->uri->segment_array();    // URI Segments
+        $file = array_pop($segments);
+        $file_path = implode("/", $segments);
+
+        $pos = strrpos($file, '.');
+        if ($pos === false)
+        {
+            $ext = '';
+        }
+        else
+        {
+            $ext = substr($file,$pos+1);
+            $file = substr($file,0,$pos);
+        }
+
+        $file = $file_path."/".$file.".".$ext;
+        if ( ! file_exists($file)) {
+
+            // If the file doesn't exist, just pass an empty file.
+            $file = false;
+            $file_data = '';
+
+        }
+        else
+        {
+
+            $mtime = filemtime($file);
+            $file_data = file_get_contents($file);
+
+        }
+
+        if ($ext == "css")
+        { // Compress CSS data
+            $file_data = $this->_css_compress($file_data);
+        }
+
+        // HTTP Headers
+        $expiry_time = 613200;  // 1 Week
+        $mime = ($ext == 'css') ? 'text/css' : 'application/javascript';
+        header('Content-type: '.$mime);
+        header('Cache-Control: must-revalidate');
+        header('Expires: '.gmdate("D, d M Y H:i:s", time() + $expiry_time).' GMT');
+        if (isset($mtime))
+        {
+          header('ETag: '.$mtime);
+        }
+        header("Last-Modified: ".gmdate("D, d M Y H:i:s", $mtime)." GMT");
+
+        $oldetag = isset($_SERVER['HTTP_IF_NONE_MATCH'])?trim($_SERVER['HTTP_IF_NONE_MATCH']):'';
+        $oldmtime = isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])?$_SERVER['HTTP_IF_MODIFIED_SINCE']:'';
+        $accencoding = isset($_SERVER['HTTP_ACCEPT_ENCODING'])?$_SERVER['HTTP_ACCEPT_ENCODING']:'';
+
+        if (($oldmtime AND strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $oldmtime) OR $oldetag == $mtime)
+        {
+            header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
+        }
+        else
+        {
+            if (strpos($accencoding, 'gzip') !== false AND $gzip)
+            {
+                header('Content-Encoding: gzip');
+                echo gzencode($file_data);
+            }
+            else echo $file_data;
+        }
+    }
+
+    private function _css_compress($data)
+    {
+        // Remove comments
+        $data = preg_replace('~/\*[^*]*\*+([^/][^*]*\*+)*/~', '', $data);
+
+        // Replace all whitespace by single spaces
+        $data = preg_replace('~\s+~', ' ', $data);
+
+        // Remove needless whitespace
+        $data = preg_replace('~ *+([{}+>:;,]) *~', '$1', trim($data));
+
+        // Remove ; that closes last property of each declaration
+        $data = str_replace(';}', '}', $data);
+
+        // Remove empty CSS declarations
+        $data = preg_replace('~[^{}]++\{\}~', '', $data);
+
+        return $data;
+    }
+
+    private function _js_compress($data)
+    {
+        $packer = new JavaScriptPacker($data, $this->pack_js);
+        return $packer->pack();
+    }
+
+}
+?>
diff --git a/application/controllers/members.php b/application/controllers/members.php
new file mode 100644
index 0000000..ab7382e
--- /dev/null
+++ b/application/controllers/members.php
@@ -0,0 +1,131 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This main controller for the Members section 
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Members_Controller extends Template_Controller
+{
+	public $auto_render = TRUE;
+
+	// Main template
+	public $template = 'members/layout';
+
+	// Cache instance
+	protected $cache;
+
+	// Enable auth
+	protected $auth_required = FALSE;
+
+	// Table Prefix
+	protected $table_prefix;
+    
+    protected $release;
+
+	public function __construct()
+	{
+		parent::__construct();	
+
+		// Load cache
+		$this->cache = new Cache;
+
+		// Load session
+		$this->session = new Session;
+
+		// Load database
+		$this->db = new Database();
+
+		$this->session = Session::instance();
+
+		if ( ! $this->auth->logged_in('login'))
+		{
+			url::redirect('login');
+		}
+
+		// Check if user has the right to see the user dashboard
+		if( ! $this->auth->has_permission('member_ui'))
+		{
+			// This user isn't allowed in the admin panel
+			url::redirect('/');
+		}
+
+		// Themes Helper
+		$this->themes = new Themes();
+		$this->themes->admin = TRUE;
+
+		// Set Table Prefix
+		$this->table_prefix = Kohana::config('database.default.table_prefix');
+
+		$this->template->admin_name = $this->user->name;
+		
+		// Retrieve Default Settings
+		$this->template->site_name = Kohana::config('settings.site_name');
+		$this->themes->api_url = Kohana::config('settings.api_url');
+
+		// Javascript Header
+		$this->themes->map_enabled = FALSE;
+		$this->themes->flot_enabled = FALSE;
+		$this->themes->treeview_enabled = FALSE;
+		$this->themes->protochart_enabled = FALSE;
+		$this->themes->colorpicker_enabled = FALSE;
+		$this->themes->editor_enabled = FALSE;
+		$this->themes->tablerowsort_enabled = FALSE;
+		$this->themes->autocomplete_enabled = FALSE;
+		$this->themes->json2_enabled = FALSE;
+		$this->themes->js = '';
+		
+		$this->template->form_error = FALSE;
+
+		// Initialize some variables for raphael impact charts
+		$this->themes->raphael_enabled = FALSE;
+		$this->themes->impact_json = '';
+
+		// Generate main tab navigation list.
+		$this->template->main_tabs = members::main_tabs();
+
+		$this->template->this_page = "";
+
+		// Header Nav
+		$header_nav = new View('header_nav');
+		$this->template->header_nav = $header_nav;
+		$this->template->header_nav->loggedin_user = FALSE;
+		if ( isset(Auth::instance()->get_user()->id) )
+		{
+			// Load User
+			$this->template->header_nav->loggedin_role = Auth::instance()->get_user()->dashboard();
+			$this->template->header_nav->loggedin_user = Auth::instance()->get_user();
+		}
+		$this->template->header_nav->site_name = Kohana::config('settings.site_name');
+		
+		Event::add('ushahidi_filter.view_pre_render.members_layout', array($this, '_pre_render'));
+	}
+
+	public function index()
+	{
+		url::redirect('members/dashboard');
+	}
+	
+	/**
+	 * Trigger themes->admin_requirements() at the last minute
+	 * 
+	 * This is in case features are enabled/disabled
+	 */
+	public function _pre_render()
+	{
+		$this->themes->requirements();
+		$this->themes->plugin_requirements();
+		$this->template->header_block = $this->themes->header_block();
+		$this->template->footer_block = $this->themes->footer_block();
+	}
+
+} // End Admin
+
diff --git a/application/controllers/members/alerts.php b/application/controllers/members/alerts.php
new file mode 100644
index 0000000..cf61be0
--- /dev/null
+++ b/application/controllers/members/alerts.php
@@ -0,0 +1,132 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Alerts Controller.
+ * This controller will take care of adding and editing reports in the Member section.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Members
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Alerts_Controller extends Members_Controller {
+	
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->template->this_page = 'alerts';
+	}
+	
+	/**
+	 * Lists all the alerts
+	 */
+	public function index()
+	{
+		$this->template->content = new View('members/alerts');
+		$this->template->content->title = Kohana::lang('ui_admin.my_alerts');
+		
+		// Is this an Inbox or Outbox Filter?
+		if (!empty($_GET['type']))
+		{
+			$type = $_GET['type'];
+
+			if ($type == '1')
+			{ // SMS
+				$filter = 'alert_type=1';
+			}
+			elseif ($type == '2')
+			{ // EMAIL
+				$filter = 'alert_type=2';
+			}
+			else
+			{ // ALL
+				$filter = '1=1';
+			}
+		}
+		else
+		{
+			$type = "0";
+			$filter = '1=1';
+		}
+		
+		// check, has the form been submitted?
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			 //	 Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			$post->add_rules('alert_id.*','required','numeric');
+
+			if ($post->validate())
+			{
+				if ($post->action == 'd')	//Delete Action
+				{
+					foreach($post->alert_id as $item)
+					{
+						$update = ORM::factory('alert')
+							->where('user_id', $this->user->id)
+							->find($item);
+						if ($update->loaded)
+						{
+							$alert_id = $update->id;
+							$update->delete();
+
+							// Delete Media
+							ORM::factory('alert_category')->where('alert_id',$alert_id)->delete_all();
+						}
+					}
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+				$form_saved = TRUE;
+			}
+			else
+			{
+				$form_error = TRUE;
+			}
+		}
+		
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string'	 => 'page',
+			'items_per_page' => (int) Kohana::config('settings.items_per_page_admin'),
+			'total_items'	 => ORM::factory('alert')
+				->where("user_id", $this->user->id)
+				->where($filter)
+				->count_all()
+			));
+
+		$alerts = ORM::factory('alert')
+			->where("user_id", $this->user->id)
+			->where($filter)
+			->orderby('id', 'asc')
+			->find_all((int) Kohana::config('settings.items_per_page_admin'), $pagination->sql_offset);
+			
+		$this->template->content->alerts = $alerts;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->type = $type;
+
+		// Total Messages
+		$this->template->content->total_items = $pagination->total_items;
+		
+		// Javascript Header
+		$this->themes->js = new View('members/alerts_js');
+	}	
+}
diff --git a/application/controllers/members/dashboard.php b/application/controllers/members/dashboard.php
new file mode 100644
index 0000000..18b2f6b
--- /dev/null
+++ b/application/controllers/members/dashboard.php
@@ -0,0 +1,120 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller is used for the main Admin panel
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Members
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Dashboard_Controller extends Members_Controller {
+
+	function __construct()
+	{
+		parent::__construct();
+	}
+
+	function index()
+	{
+		$this->template->content = new View('members/dashboard');
+		$this->template->content->title = Kohana::lang('ui_admin.dashboard');
+		$this->template->this_page = 'dashboard';
+
+		// User
+		$this->template->content->user = $this->user;
+
+		// User Reputation Score
+		$this->template->content->reputation = reputation::calculate($this->user->id);
+
+		// Get Badges
+		$this->template->content->badges = Badge_Model::users_badges($this->user->id);
+
+		// Retrieve Dashboard Counts...
+		// Total Reports
+		$this->template->content->reports_total = ORM::factory('incident')
+			->where("user_id", $this->user->id)
+			->count_all();
+
+		// Total Unapproved Reports
+		$this->template->content->reports_unapproved = ORM::factory('incident')
+			->where('incident_active', '0')
+			->where("user_id", $this->user->id)
+			->count_all();
+
+		// Total Alerts
+		$this->template->content->alerts = ORM::factory('alert')
+			->where("user_id", $this->user->id)
+			->count_all();
+
+		// Total Votes
+		$this->template->content->votes = ORM::factory('rating')
+			->where("user_id", $this->user->id)
+			->count_all();
+
+		// Total Votes Positive
+		$this->template->content->votes_up = ORM::factory('rating')
+			->where("user_id", $this->user->id)
+			->where("rating", "1")
+			->count_all();
+
+		// Total Votes Negative
+		$this->template->content->votes_down = ORM::factory('rating')
+			->where("user_id", $this->user->id)
+			->where("rating", "-1")
+			->count_all();
+
+		// Get reports for display
+		$this->template->content->incidents = ORM::factory('incident')
+				->where("user_id", $this->user->id)
+				->limit(5)
+				->orderby('incident_dateadd', 'desc')
+				->find_all();
+
+		// To support the "welcome" or "not enough info on user" form
+		if($this->user->public_profile == 1)
+		{
+			$this->template->content->profile_public = TRUE;
+			$this->template->content->profile_private = FALSE;
+		}else{
+			$this->template->content->profile_public = FALSE;
+			$this->template->content->profile_private = TRUE;
+		}
+
+		$this->template->content->hidden_welcome_fields = array
+		(
+			'email' => $this->user->email,
+			'notify' => $this->user->notify,
+			'color' => $this->user->color,
+			'password' => '', // Don't set a new password from here
+			'needinfo' => $this->user->needinfo // After we save this form once, we don't need to show it again
+		);
+
+		// Javascript Header
+		$this->themes->protochart_enabled = TRUE;
+		$this->themes->js = new View('admin/stats/stats_js');
+
+		$this->template->content->failure = '';
+
+		// Build dashboard chart
+
+		// Set the date range (how many days in the past from today?)
+		// Default to one year if invalid or not set
+		$range = (!empty($_GET['range']))
+			? $_GET['range']
+			: 365;
+
+		// Phase 3 - Invoke Kohana's XSS cleaning mechanism just incase an outlier wasn't caught
+		$range = $this->input->xss_clean($range);
+		$incident_data = Incident_Model::get_number_reports_by_date($range, $this->user->id);
+		$data = array('Reports'=>$incident_data);
+		$options = array('xaxis'=>array('mode'=>'"time"'));
+		$this->template->content->report_chart = protochart::chart('report_chart',$data,$options,array('Reports'=>'CC0000'),410,310);
+	}
+}
+?>
diff --git a/application/controllers/members/login.php b/application/controllers/members/login.php
new file mode 100644
index 0000000..f7ad98d
--- /dev/null
+++ b/application/controllers/members/login.php
@@ -0,0 +1,51 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller handles login requests.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Members
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Login_Controller extends Template_Controller {
+	
+	public $auto_render = TRUE;
+	
+	// Session Object
+	protected $session;
+	
+	// Main template
+	public $template = 'login';
+	
+
+	public function __construct()
+	{
+		parent::__construct();
+		
+		// REDIRECT TO NEW LOGIN CONTROLLER
+		
+		url::redirect("login");
+	}
+	
+	public function index($user_id = 0)
+	{
+	
+		// REDIRECT TO NEW LOGIN CONTROLLER
+		
+		url::redirect("login");
+	}
+	
+	public function verify()
+	{
+		
+		// REDIRECT TO NEW LOGIN CONTROLLER
+		
+		url::redirect("login");
+	}
+}
diff --git a/application/controllers/members/private.php b/application/controllers/members/private.php
new file mode 100644
index 0000000..4682c49
--- /dev/null
+++ b/application/controllers/members/private.php
@@ -0,0 +1,310 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Private Messages Controller.
+ * This controller will take care of adding and editing reports in the Member section.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Members
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Private_Controller extends Members_Controller {
+	function __construct()
+	{
+		parent::__construct();
+
+		$this->template->this_page = 'private';
+	}
+
+
+	/**
+	 * Lists the private messages.
+	 * @param int $page
+	 */
+	public function index($page = 1)
+	{
+		$this->template->content = new View('members/private');
+		$this->template->content->title = Kohana::lang('ui_admin.private_messages');
+		
+		// Is this an Inbox or Outbox Filter?
+		if (!empty($_GET['type']))
+		{
+			$type = $_GET['type'];
+
+			if ($type == '2')
+			{ // OUTBOX
+				$filter = 'from_user_id';
+			}
+			else
+			{ // INBOX
+				$type = "1";
+				$filter = 'user_id';
+			}
+		}
+		else
+		{
+			$type = "1";
+			$filter = 'user_id';
+		}
+
+		// check, has the form been submitted?
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+			$post = Validation::factory($_POST);
+
+				//	Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			$post->add_rules('message_id.*','required','numeric');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{	
+				if( $post->action == 'd' )				// Delete Action
+				{
+					foreach($post->message_id as $item)
+					{
+						// Delete Message
+						$message = ORM::factory('private_message')
+							->where("user_id", $this->user->id)
+							->find($item);
+						$message->delete();
+					}
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+				elseif( $post->action == 'r' )			// Mark As Read
+				{
+					foreach($post->message_id as $item)
+					{
+						// Update Message Level
+						$message = ORM::factory('private_message')
+							->where("user_id", $this->user->id)
+							->find($item);
+						if ($message->loaded)
+						{
+							$message->private_message_new = '0';
+							$message->save();
+						}
+					}
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.modified'));
+				}
+			}
+			// No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('private_message'));
+				$form_error = TRUE;
+			}
+		}
+		
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string'	 => 'page',
+			'items_per_page' => (int) Kohana::config('settings.items_per_page_admin'),
+			'total_items'	 => ORM::factory('private_message')
+				->where($filter, $this->user->id)
+				->count_all()
+			));
+
+		$messages = ORM::factory('private_message')
+			->where($filter, $this->user->id)
+			->orderby('private_message_date', 'desc')
+			->find_all((int) Kohana::config('settings.items_per_page_admin'), $pagination->sql_offset);
+			
+		$this->template->content->messages = $messages;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->type = $type;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->user_id = $this->user->id;
+
+		// Total Messages
+		$this->template->content->total_items = $pagination->total_items;
+		
+		// Javascript Header
+		$this->themes->js = new View('members/private_js');
+	}
+	
+	/**
+	 * Send a new private message
+	 */
+	public function send()
+	{
+		$this->template->content = new View('members/private_send');
+		$this->template->content->title = Kohana::lang('ui_admin.private_messages');
+		
+		// setup and initialize form field names
+		$form = array
+		(
+			'parent_id'  => '',
+			'private_to'  => '',
+			'private_subject'  => '',
+			'private_message'  => ''
+		);
+		
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		
+		$form['private_to'] = (isset($_GET['to']) AND ! empty($_GET['to'])) ? html::specialchars($_GET['to']) : "";
+		$form['parent_id'] = (isset($_GET['p']) AND ! empty($_GET['p'])) ? html::specialchars($_GET['p']) : "";
+		
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+			
+			 //	 Add some filters
+			$post->pre_filter('trim', TRUE);
+			$post->add_rules('parent_id','numeric');
+			$post->add_rules('private_to','required');
+			if ( ! empty($_POST['private_to']))
+			{
+				$to_array = array_filter( explode(",", trim($_POST['private_to'])) );
+				foreach ($to_array as $name)
+				{
+					$this->_user_name_chk($name, $post);
+				}
+			}
+			$post->add_rules('private_subject','required','length[3,150]');
+			$post->add_rules('private_message','required');
+			
+			if ($post->validate())
+			{
+				$to_array = array_filter( explode(",", $post->private_to) );
+				foreach ($to_array as $name)
+				{
+					$account = ORM::factory('user')
+						->where("name", $name)
+						->where("id !=".$this->user->id)
+						->find();
+
+					if ($account->loaded)
+					{
+						$message = ORM::factory('private_message');
+						$message->parent_id = $post->parent_id;
+						$message->user_id = $account->id;
+						$message->from_user_id = $this->user->id;
+						$message->private_subject = $post->private_subject;
+						$message->private_message = $post->private_message;
+						$message->private_message_date = date("Y-m-d H:i:s",time());
+						$message->save();
+						
+						// Email Private Message
+						$to = $account->email;
+						$from = array();
+							$settings = kohana::config('settings');
+							$from[] = $settings['site_email'];
+							$from[] = $settings['site_name'];
+						$subject = "[".Kohana::config('settings.site_name')."] - ".$post->private_subject;
+						$body = Kohana::lang('notifications.member_new_message.message').
+							"\n\n~~~~~~~~~~~~~~~~~~~~~~~~~\n".
+							$post->private_message.
+							"\n\n".Kohana::lang('notifications.member_new_message.footer').
+							"\n ".url::site('members/');
+							
+						if ( ! email::send($to,
+							$from,
+							$subject,
+							$body,
+							FALSE))
+						{
+							Kohana::log('error', "email to $to could not be sent");
+						}
+					}
+				}
+				
+				$form_saved = TRUE;
+			}
+			else 
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('private_message'));
+				$form_error = TRUE;
+			}
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		
+		// Javascript Header
+		$this->themes->autocomplete_enabled = TRUE;
+		$this->themes->js = new View('members/private_send_js');
+	}
+	
+	/**
+	 * Retrieve User by UserName or Real Name
+	 */ 
+	public function get_user()
+	{
+		$db = Database::instance();
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		$name = (isset($_GET['q'])) ? utf8::strtolower('%'.str_replace(array('%','_'), array('|%','|_'), $_GET['q']).'%') : "";
+		
+		if ($name)
+		{
+			$users = $db->query("SELECT * from users where id != :id AND LOWER(name) LIKE :name ESCAPE '|'",
+				array(':id' => $this->user->id, ':name' => $name));
+			
+			foreach ($users as $user)
+			{
+				echo "$user->name\n";
+			}
+		}
+		else
+		{
+			return;
+		}
+	}
+	
+	/**
+	 * Checks if user_id is associated with an account.
+	 * @param Validation $post $_POST variable with validation rules 
+	 */
+	private function _user_name_chk( $name, $post )
+	{
+		$account = ORM::factory('user')
+			->where("name", $name)
+			->orwhere("username", $name)
+			->orwhere("email", $name)
+			->where("id !=".$this->user->id)
+			->find();
+
+		if ( ! $account->loaded)
+		{
+			$post->add_error('private_to','exists');
+		}
+	}
+}
diff --git a/application/controllers/members/profile.php b/application/controllers/members/profile.php
new file mode 100644
index 0000000..625ef95
--- /dev/null
+++ b/application/controllers/members/profile.php
@@ -0,0 +1,233 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * "My Profile" - allows member to configure their settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Members
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Profile_Controller extends Members_Controller
+{
+	protected $user_id;
+
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'profile';
+
+		$this->user_id = $this->user->id;
+	}
+
+	/**
+	 * Display the Member Profile
+	 */
+	public function index()
+	{
+		$this->template->content = new View('members/profile');
+
+		// setup and initialize form field names
+		$form = array
+		(
+			'current_password' => '',
+			'username' => '',
+			'new_password' => '',
+			'password_again' => '',
+			'name' => '',
+			'email' => '',
+			'notify' => '',
+			'public_profile' => '',
+			'color' => ''
+		);
+
+		//	Copy the form as errors, so the errors will be stored with keys
+		//	corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			 //	 Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			$post->add_rules('username','required','alpha_numeric');
+			$post->add_rules('name','required','length[3,100]');
+			$post->add_rules('email','required','email','length[4,64]');
+			$post->add_rules('current_password','required');
+
+			$post->add_callbacks('email',array($this,'email_exists_chk'));
+			$post->add_callbacks('username',array($this,'username_exists_chk'));
+			$post->add_callbacks('current_password',array($this,'current_pw_valid_chk'));
+
+			// If Password field is not blank
+			if ( ! empty($post->new_password))
+			{
+				$post->add_rules('new_password','required','length['.kohana::config('auth.password_length').']','matches[password_again]');	
+			}
+			//for plugins that want to know what the user had to say about things
+			Event::run('ushahidi_action.profile_post_member', $post);
+			if ($post->validate())
+			{
+
+				// Needinfo is set to 1 if we need more information on a user
+				//   Set to 0 if the user is filling out the form, it means
+				//   they have had an opportunity to provide extra details.
+				$needinfo = 0;
+				if ( ! empty($post->needinfo))
+				{
+					$needinfo = $post->needinfo;
+				}
+
+				$username = '';
+				if (isset($post->username))
+				{
+					$username = $post->username;
+				}
+
+				$user = ORM::factory('user',$this->user_id);
+				if ($user->loaded)
+				{
+					$user->username = $username;
+					$user->name = $post->name;
+					$user->email = $post->email;
+					$user->notify = $post->notify;
+					$user->public_profile = $post->public_profile;
+					$user->color = $post->color;
+					$user->needinfo = $needinfo;
+					if (! empty($post->new_password))
+					{
+						$user->password = $post->new_password;
+					}
+					$user->save();
+					//for plugins that want to know how the user now stands
+					Event::run('ushahidi_action.profile_edit_member', $user);
+
+					// We also need to update the RiverID server with the new password if
+	                //    we are using RiverID and a password is being passed
+	                if (kohana::config('riverid.enable') == TRUE
+	                	AND ! empty($user->riverid)
+	                	AND $post->new_password != '')
+					{
+						$riverid = new RiverID;
+						$riverid->email = $user->email;
+						$riverid->password = $post->current_password;
+						$riverid->new_password = $post->new_password;
+						if ($riverid->changepassword() == FALSE)
+						{
+							// TODO: Something went wrong. Tell the user.
+						}
+					}
+				}
+
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+				$form['new_password'] = "";
+				$form['password_again'] = "";
+			}
+			else
+			{
+				// repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('auth'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			$user = ORM::factory('user',$this->user_id);
+			$form['username'] = $user->username;
+			$form['name'] = $user->name;
+			$form['email'] = $user->email;
+			$form['notify'] = $user->notify;
+			$form['public_profile'] = $user->public_profile;
+			$form['color'] = $user->color;
+		}
+
+		// If $user was never set above, we need to grab it now.
+		if ( ! isset($user))
+		{
+			$user = ORM::factory('user',$this->user_id);
+		}
+
+		if($user->public_profile == 1)
+		{
+			$this->template->content->profile_public = TRUE;
+			$this->template->content->profile_private = FALSE;
+		}else{
+			$this->template->content->profile_public = FALSE;
+			$this->template->content->profile_private = TRUE;
+		}
+
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->yesno_array = array('1'=>utf8::strtoupper(Kohana::lang('ui_main.yes')),'0'=>utf8::strtoupper(Kohana::lang('ui_main.no')));
+
+		// Javascript Header
+		$this->themes->colorpicker_enabled = TRUE;
+	}
+
+	/**
+	 * Checks if email address is associated with an account.
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function email_exists_chk( Validation $post )
+	{
+		if (array_key_exists('email',$post->errors()))
+			return;
+
+		$users = ORM::factory('user')
+			->where('id <> '.$this->user_id);
+
+		if ($users->email_exists( $post->email ) )
+			$post->add_error('email','exists');
+	}
+
+	/**
+	 * Checks if username is associated with an account.
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function username_exists_chk( Validation $post )
+	{
+		if (array_key_exists('username',$post->errors()))
+			return;
+
+		$users = ORM::factory('user')
+			->where('id <> '.$this->user_id);
+
+		if ($users->username_exists( $post->username ) )
+			$post->add_error('username','exists');
+	}
+
+	/**
+	 * Checks if current password being passed is correct
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function current_pw_valid_chk( Validation $post )
+	{
+		if (array_key_exists('current_password',$post->errors()))
+			return;
+
+		$user = User_Model::get_user_by_id($this->user_id);
+
+		if ( ! User_Model::check_password($user->email,$post->current_password) )
+		{
+			$post->add_error('current_password','incorrect');
+		}
+	}
+}
diff --git a/application/controllers/members/reports.php b/application/controllers/members/reports.php
new file mode 100755
index 0000000..f7f4cbd
--- /dev/null
+++ b/application/controllers/members/reports.php
@@ -0,0 +1,744 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Reports Controller.
+ * This controller will take care of adding and editing reports in the Member section.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Members
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Reports_Controller extends Members_Controller {
+	
+	public function __construct()
+	{
+		parent::__construct();
+
+		$this->template->this_page = 'reports';
+	}
+
+
+	/**
+	* Lists the reports.
+	* @param int $page
+	*/
+	public function index($page = 1)
+	{
+		$this->template->content = new View('members/reports');
+		$this->template->content->title = Kohana::lang('ui_admin.reports');
+
+
+		if ( ! empty($_GET['status']))
+		{
+			$status = $_GET['status'];
+
+			if (strtolower($status) == 'a')
+			{
+				$filter = 'incident_active = 0';
+			}
+			elseif (strtolower($status) == 'v')
+			{
+				$filter = 'incident_verified = 0';
+			}
+			else
+			{
+				$status = "0";
+				$filter = '1=1';
+			}
+		}
+		else
+		{
+			$status = "0";
+			$filter = "1=1";
+		}
+
+		// Get Search Keywords (If Any)
+		if (isset($_GET['k']))
+		{
+			// Brute force input sanitization
+			// Phase 1 - Strip the search string of all non-word characters 
+			$keyword_raw = preg_replace('/[^\w+]\w*/', '', $_GET['k']);
+			
+			// Strip any HTML tags that may have been missed in Phase 1
+			$keyword_raw = strip_tags($keyword_raw);
+			
+			// Phase 3 - Invoke Kohana's XSS cleaning mechanism just incase an outlier wasn't caught
+			// in the first 2 steps
+			$keyword_raw = $this->input->xss_clean($keyword_raw);
+			
+			$filter .= " AND (".$this->_get_searchstring($keyword_raw).")";
+		}
+		else
+		{
+			$keyword_raw = "";
+		}
+
+		// Check, has the form been submitted?
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		
+		if ($_POST)
+		{
+			// Setup validation
+			$post = Validation::factory($_POST);
+
+			 //	 Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('action','required', 'alpha', 'length[1,1]');
+			$post->add_rules('incident_id.*','required','numeric');
+
+			if ($post->validate())
+			{
+				// Delete Action
+				if ($post->action == 'd')	
+				{
+					foreach ($post->incident_id as $item)
+					{
+						$update = ORM::factory('incident')
+							->where('user_id', $this->user->id)
+							->find($item);
+						if ($update->loaded == true)
+						{
+							$incident_id = $update->id;
+							$location_id = $update->location_id;
+							$update->delete();
+						}
+					}
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+				$form_saved = TRUE;
+			}
+			else
+			{
+				$form_error = TRUE;
+			}
+
+		}
+
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string'	 => 'page',
+			'items_per_page' => intval(Kohana::config('settings.items_per_page_admin')),
+			'total_items'	 => ORM::factory('incident')
+				->join('location', 'incident.location_id', 'location.id','INNER')
+				->where($filter)
+				->where('user_id', $this->user->id)
+				->count_all()
+			));
+
+		$incidents = ORM::factory('incident')
+			->join('location', 'incident.location_id', 'location.id','INNER')
+			->where($filter)
+			->where('user_id', $this->user->id)
+			->orderby('incident_dateadd', 'desc')
+			->find_all((int) Kohana::config('settings.items_per_page_admin'), $pagination->sql_offset);
+
+		$location_ids = array();
+		$country_ids = array();
+		foreach ($incidents as $incident)
+		{
+			$location_ids[] = $incident->location_id;
+		}
+		
+		// Check if location_ids is not empty
+		if (count($location_ids ) > 0 ) 
+		{
+			$locations_result = ORM::factory('location')->in('id',implode(',',$location_ids))->find_all();
+			$locations = array();
+			foreach ($locations_result as $loc)
+			{
+				$locations[$loc->id] = $loc->location_name;
+				$country_ids[$loc->id]['country_id'] = $loc->country_id;
+			}
+		}
+		else
+		{
+			$locations = array();
+		}
+
+		$this->template->content->locations = $locations;
+		$this->template->content->country_ids = $country_ids;
+
+		// GET countries
+		$countries = array();
+		foreach (ORM::factory('country')->orderby('country')->find_all() as $country)
+		{
+			// Create a list of all categories
+			$this_country = $country->country;
+			if (strlen($this_country) > 35)
+			{
+				$this_country = substr($this_country, 0, 35) . "...";
+			}
+			$countries[$country->id] = $this_country;
+		}
+
+		$this->template->content->countries = $countries;
+		$this->template->content->incidents = $incidents;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+
+		// Total Reports
+		$this->template->content->total_items = $pagination->total_items;
+
+		// Status Tab
+		$this->template->content->status = $status;
+
+		// Javascript Header
+		$this->themes->js = new View('admin/reports/reports_js');
+	}
+
+
+	/**
+	* Edit a report
+	* @param bool|int $id The id no. of the report
+	* @param bool|string $saved
+	*/
+	public function edit($id = FALSE, $saved = FALSE)
+	{
+		$db = new Database();
+
+		$this->template->content = new View('members/reports_edit');
+		$this->template->content->title = Kohana::lang('ui_admin.create_report');
+
+		// Setup and initialize form field names
+		$form = array(
+			'location_id' => '',
+			'form_id' => '1',
+			'locale' => '',
+			'incident_title' => '',
+			'incident_description' => '',
+			'incident_date' => '',
+			'incident_hour' => '',
+			'incident_minute' => '',
+			'incident_ampm' => '',
+			'latitude' => '',
+			'longitude' => '',
+			'geometry' => array(),
+			'location_name' => '',
+			'country_id' => '',
+			'country_name' => '',
+			'incident_category' => array(),
+			'incident_news' => array(),
+			'incident_video' => array(),
+			'incident_photo' => array(),
+			'person_first' => '',
+			'person_last' => '',
+			'person_email' => '',
+			'custom_field' => array(),
+			'incident_zoom' => '',
+			'incident_source'=> '',
+			'incident_information' => ''
+		);
+
+		// Copy the form as errors, so the errors will be stored with keys 
+		// corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = ($saved == 'saved');
+
+		// Initialize Default Values
+		$form['locale'] = Kohana::config('locale.language');
+		//$form['latitude'] = Kohana::config('settings.default_lat');
+		//$form['longitude'] = Kohana::config('settings.default_lon');
+		$form['country_id'] = Kohana::config('settings.default_country');
+		$form['incident_date'] = date("m/d/Y",time());
+		$form['incident_hour'] = date('h');
+		$form['incident_minute'] = date('i');
+		$form['incident_ampm'] = date('a');
+		
+		// Initialize custom field array
+		$form_id = $form['form_id'];
+		$form['custom_field'] = customforms::get_custom_form_fields($id, $form_id, TRUE);
+
+		// Locale (Language) Array
+		$this->template->content->locale_array = Kohana::config('locale.all_languages');
+
+		// Time formatting
+		$this->template->content->hour_array = $this->_hour_array();
+		$this->template->content->minute_array = $this->_minute_array();
+		$this->template->content->ampm_array = $this->_ampm_array();
+		
+		$this->template->content->stroke_width_array = $this->_stroke_width_array();
+
+		// Get Countries
+		$countries = array();
+		foreach (ORM::factory('country')->orderby('country')->find_all() as $country)
+		{
+			// Create a list of all categories
+			$this_country = $country->country;
+			if (strlen($this_country) > 35)
+			{
+				$this_country = substr($this_country, 0, 35) . "...";
+			}
+			$countries[$country->id] = $this_country;
+		}
+		$this->template->content->countries = $countries;
+		
+		// Initialize Default Value for Hidden Field Country Name, just incase Reverse Geo coding yields no result
+		$form['country_name'] = $countries[$form['country_id']];
+
+		//GET custom forms
+		$forms = array();
+		foreach (ORM::factory('form')->where('form_active',1)->find_all() as $custom_forms)
+		{
+			$forms[$custom_forms->id] = $custom_forms->form_title;
+		}
+		
+		$this->template->content->forms = $forms;
+
+		// Retrieve thumbnail photos (if edit);
+		//XXX: fix _get_thumbnails
+		$this->template->content->incident = $this->_get_thumbnails($id);
+	
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+			$post = array_merge($_POST,$_FILES);
+			
+			if (reports::validate($post))
+			{
+				// STEP 1: SAVE LOCATION
+				$location = new Location_Model();
+				reports::save_location($post, $location);
+
+				// STEP 2: SAVE INCIDENT
+				$incident = new Incident_Model($id);
+				reports::save_report($post, $incident, $location->id);
+
+				// STEP 2b: SAVE INCIDENT GEOMETRIES
+				reports::save_report_geometry($post, $incident);
+				
+				// STEP 3: SAVE CATEGORIES
+				reports::save_category($post, $incident);
+
+				// STEP 4: SAVE MEDIA
+				reports::save_media($post, $incident);
+
+				// STEP 5: SAVE CUSTOM FORM FIELDS
+				reports::save_custom_fields($post, $incident);
+
+				// STEP 6: SAVE PERSONAL INFORMATION
+				reports::save_personal_info($post, $incident);
+
+				// Action::report_add / report_submit_members - Added a New Report
+				Event::run('ushahidi_action.report_submit_members', $post);
+				Event::run('ushahidi_action.report_edit', $incident);
+
+				// SAVE AND CLOSE?
+				if ($post->save == 1)
+				{
+					// Save but don't close
+					url::redirect('members/reports/edit/'. $incident->id .'/saved');
+				}
+				else
+				{
+					// Save and close
+					url::redirect('members/reports/');
+				}
+			}
+
+			// No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// Repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// Populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('report'));
+				$form_error = TRUE;
+			}
+		}
+		else
+		{
+			if ($id)
+			{
+				// Retrieve Current Incident
+				$incident = ORM::factory('incident')
+					->where('user_id', $this->user->id)
+					->find($id);
+				if ($incident->loaded == true)
+				{
+					// Retrieve Categories
+					$incident_category = array();
+					foreach($incident->incident_category as $category)
+					{
+						$incident_category[] = $category->category_id;
+					}
+
+					// Retrieve Media
+					$incident_news = array();
+					$incident_video = array();
+					$incident_photo = array();
+					foreach($incident->media as $media)
+					{
+						if ($media->media_type == 4)
+						{
+							$incident_news[] = $media->media_link;
+						}
+						elseif ($media->media_type == 2)
+						{
+							$incident_video[] = $media->media_link;
+						}
+						elseif ($media->media_type == 1)
+						{
+							$incident_photo[] = $media->media_link;
+						}
+					}
+					
+					// Get Geometries via SQL query as ORM can't handle Spatial Data
+					$sql = "SELECT AsText(geometry) as geometry, geometry_label, 
+						geometry_comment, geometry_color, geometry_strokewidth 
+						FROM ".Kohana::config('database.default.table_prefix')."geometry 
+						WHERE incident_id = ?";
+					$query = $db->query($sql, $id);
+					foreach ( $query as $item )
+					{
+						$geometry = array(
+							"geometry" => $item->geometry,
+							"label" => $item->geometry_label,
+							"comment" => $item->geometry_comment,
+							"color" => $item->geometry_color,
+							"strokewidth" => $item->geometry_strokewidth
+						);
+						$form['geometry'][] = json_encode($geometry);
+					}
+					
+					// Combine Everything
+					$incident_arr = array(
+						'location_id' => $incident->location->id,
+						'form_id' => $incident->form_id,
+						'locale' => $incident->locale,
+						'incident_title' => $incident->incident_title,
+						'incident_description' => $incident->incident_description,
+						'incident_date' => date('m/d/Y', strtotime($incident->incident_date)),
+						'incident_hour' => date('h', strtotime($incident->incident_date)),
+						'incident_minute' => date('i', strtotime($incident->incident_date)),
+						'incident_ampm' => date('a', strtotime($incident->incident_date)),
+						'latitude' => $incident->location->latitude,
+						'longitude' => $incident->location->longitude,
+						'location_name' => $incident->location->location_name,
+						'country_id' => $incident->location->country_id,
+						'incident_category' => $incident_category,
+						'incident_news' => $incident_news,
+						'incident_video' => $incident_video,
+						'incident_photo' => $incident_photo,
+						'person_first' => $incident->incident_person->person_first,
+						'person_last' => $incident->incident_person->person_last,
+						'person_email' => $incident->incident_person->person_email,
+						'incident_source' => '',
+						'incident_information' => '',
+						'custom_field' => customforms::get_custom_form_fields($id, $incident->form_id, TRUE),
+						'incident_zoom' => $incident->incident_zoom
+					);
+
+					// Merge To Form Array For Display
+					$form = arr::overwrite($form, $incident_arr);
+				}
+				else
+				{
+					// Redirect
+					url::redirect('members/reports/');
+				}
+
+			}
+		}
+
+		$this->template->content->id = $id;
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+
+		// Retrieve Custom Form Fields Structure
+		$this->template->content->custom_forms = new View('reports/submit_custom_forms');
+		$disp_custom_fields = customforms::get_custom_form_fields($id, $form['form_id'], FALSE, "view");
+		$custom_field_mismatch = customforms::get_edit_mismatch($form['form_id']);
+		// Quick hack to make sure view-only fields have data set
+		foreach ($custom_field_mismatch as $id => $field)
+		{
+			$form['custom_field'][$id] = $disp_custom_fields[$id]['field_response'];
+		}
+		$this->template->content->custom_forms->disp_custom_fields = $disp_custom_fields;
+		$this->template->content->custom_forms->custom_field_mismatch = $custom_field_mismatch;
+		$this->template->content->custom_forms->form = $form;
+
+		// Retrieve Previous & Next Records
+		$previous = ORM::factory('incident')->where('id < ', $id)->orderby('id','desc')->find();
+		$previous_url = $previous->loaded
+		    ? url::site('members/reports/edit/'.$previous->id)
+		    : url::site().'members/reports/';
+		$next = ORM::factory('incident')->where('id > ', $id)->orderby('id','desc')->find();
+
+		$next_url = $next->loaded
+		    ? url::site('members/reports/edit/'.$next->id)
+		    : url::site('members/reports/');
+		$this->template->content->previous_url = $previous_url;
+		$this->template->content->next_url = $next_url;
+
+		// Javascript Header
+		$this->themes->map_enabled = TRUE;
+		$this->themes->colorpicker_enabled = TRUE;
+		$this->themes->treeview_enabled = TRUE;
+		$this->themes->json2_enabled = TRUE;
+		
+		$this->themes->js = new View('reports/submit_edit_js');
+		$this->themes->js->edit_mode = FALSE;
+		$this->themes->js->default_map = Kohana::config('settings.default_map');
+		$this->themes->js->default_zoom = Kohana::config('settings.default_zoom');
+
+		if ( ! $form['latitude'] OR ! $form['latitude'])
+		{
+			$this->themes->js->latitude = Kohana::config('settings.default_lat');
+			$this->themes->js->longitude = Kohana::config('settings.default_lon');
+		}
+		else
+		{
+			$this->themes->js->latitude = $form['latitude'];
+			$this->themes->js->longitude = $form['longitude'];
+		}
+		
+		$this->themes->js->incident_zoom = $form['incident_zoom'];
+		$this->themes->js->geometries = $form['geometry'];
+
+		// Inline Javascript
+		$this->template->content->date_picker_js = $this->_date_picker_js();
+		$this->template->content->color_picker_js = $this->_color_picker_js();
+		
+		// Pack Javascript
+		$myPacker = new javascriptpacker($this->themes->js , 'Normal', FALSE, FALSE);
+		$this->themes->js = $myPacker->pack();
+	}
+
+	/* private functions */
+
+	// Return thumbnail photos
+	//XXX: This needs to be fixed, it's probably ok to return an empty iterable instead of "0"
+	private function _get_thumbnails( $id )
+	{
+		$incident = ORM::factory('incident', $id);
+
+		if ($id)
+		{
+			$incident = ORM::factory('incident', $id);
+			return $incident;
+		}
+		return "0";
+	}
+
+	// Time functions
+	private function _hour_array()
+	{
+		for ($i=1; $i <= 12 ; $i++)
+		{
+			$hour_array[sprintf("%02d", $i)] = sprintf("%02d", $i);		// Add Leading Zero
+		}
+		return $hour_array;
+	}
+
+	private function _minute_array()
+	{
+		for ($j=0; $j <= 59 ; $j++)
+		{
+			$minute_array[sprintf("%02d", $j)] = sprintf("%02d", $j);	// Add Leading Zero
+		}
+		return $minute_array;
+	}
+
+	private function _ampm_array()
+	{
+		return $ampm_array = array('pm'=>Kohana::lang('ui_admin.pm'),'am'=>Kohana::lang('ui_admin.am'));
+	}
+	
+	private function _stroke_width_array()
+	{
+		for ($i = 0.5; $i <= 8 ; $i += 0.5)
+		{
+			$stroke_width_array["$i"] = $i;
+		}	
+		return $stroke_width_array;
+	}
+
+	// Javascript functions
+	private function _color_picker_js()
+	{
+	 return "<script type=\"text/javascript\">
+				$(document).ready(function() {
+				$('#category_color').ColorPicker({
+						onSubmit: function(hsb, hex, rgb) {
+							$('#category_color').val(hex);
+						},
+						onChange: function(hsb, hex, rgb) {
+							$('#category_color').val(hex);
+						},
+						onBeforeShow: function () {
+							$(this).ColorPickerSetColor(this.value);
+						}
+					})
+				.bind('keyup', function(){
+					$(this).ColorPickerSetColor(this.value);
+				});
+				});
+			</script>";
+	}
+
+	private function _date_picker_js()
+	{
+		return "<script type=\"text/javascript\">
+				$(document).ready(function() {
+				$(\"#incident_date\").datepicker({
+				showOn: \"both\",
+				buttonImage: \"" . url::base() . "media/img/icon-calendar.gif\",
+				buttonImageOnly: true
+				});
+				});
+			</script>";
+	}
+
+
+	private function _new_category_toggle_js()
+	{
+		return "<script type=\"text/javascript\">
+				$(document).ready(function() {
+				$('a#category_toggle').click(function() {
+				$('#category_add').toggle(400);
+				return false;
+				});
+				});
+			</script>";
+	}
+
+
+	/**
+	 * Ajax call to update Incident Reporting Form
+	 */
+	public function switch_form()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+
+		isset($_POST['form_id']) ? $form_id = $_POST['form_id'] : $form_id = "1";
+		isset($_POST['incident_id']) ? $incident_id = $_POST['incident_id'] : $incident_id = "";
+
+		$html = "";
+		$fields_array = array();
+		$custom_form = ORM::factory('form', $form_id)->orderby('field_position','asc');
+
+		foreach ($custom_form->form_field as $custom_formfield)
+		{
+			$fields_array[$custom_formfield->id] = array(
+				'field_id' => $custom_formfield->id,
+				'field_name' => $custom_formfield->field_name,
+				'field_type' => $custom_formfield->field_type,
+				'field_required' => $custom_formfield->field_required,
+				'field_maxlength' => $custom_formfield->field_maxlength,
+				'field_height' => $custom_formfield->field_height,
+				'field_width' => $custom_formfield->field_width,
+				'field_isdate' => $custom_formfield->field_isdate,
+				'field_response' => ''
+				);
+
+			// Load Data, if Any
+			foreach ($custom_formfield->form_response as $form_response)
+			{
+				if ($form_response->incident_id = $incident_id)
+				{
+					$fields_array[$custom_formfield->id]['field_response'] = $form_response->form_response;
+				}
+			}
+		}
+
+		foreach ($fields_array as $field_property)
+		{
+			$html .= "<div class=\"row\">";
+			$html .= "<h4>" . $field_property['field_name'] . "</h4>";
+			if ($field_property['field_type'] == 1)
+			{ // Text Field
+				// Is this a date field?
+				if ($field_property['field_isdate'] == 1)
+				{
+					$html .= form::input('custom_field['.$field_property['field_id'].']', $field_property['field_response'],
+						' id="custom_field_'.$field_property['field_id'].'" class="text"');
+					$html .= "<script type=\"text/javascript\">
+							$(document).ready(function() {
+							$(\"#custom_field_".$field_property['field_id']."\").datepicker({
+							showOn: \"both\",
+							buttonImage: \"" . url::base() . "media/img/icon-calendar.gif\",
+							buttonImageOnly: true
+							});
+							});
+						</script>";
+				}
+				else
+				{
+					$html .= form::input('custom_field['.$field_property['field_id'].']', $field_property['field_response'],
+						' id="custom_field_'.$field_property['field_id'].'" class="text custom_text"');
+				}
+			}
+			elseif ($field_property['field_type'] == 2)
+			{ // TextArea Field
+				$html .= form::textarea('custom_field['.$field_property['field_id'].']',
+					$field_property['field_response'], ' class="custom_text" rows="3"');
+			}
+			$html .= "</div>";
+		}
+		echo json_encode(array("status"=>"success", "response"=>$html));
+	}
+
+	/**
+	 * Creates a SQL string from search keywords
+	 */
+	private function _get_searchstring($keyword_raw)
+	{
+		$or = '';
+		$where_string = '';
+
+		// Stop words that we won't search for
+		// Add words as needed!!
+		$stop_words = array('the', 'and', 'a', 'to', 'of', 'in', 'i', 'is', 'that', 'it',
+		'on', 'you', 'this', 'for', 'but', 'with', 'are', 'have', 'be',
+		'at', 'or', 'as', 'was', 'so', 'if', 'out', 'not');
+
+		$keywords = explode(' ', $keyword_raw);
+		
+		if (is_array($keywords) && !empty($keywords))
+		{
+			array_change_key_case($keywords, CASE_LOWER);
+			$i = 0;
+			foreach($keywords as $value)
+			{
+				if (!in_array($value,$stop_words) && !empty($value))
+				{
+					$chunk = mysql_real_escape_string($value);
+					if ($i > 0) {
+						$or = ' OR ';
+					}
+					$where_string = $where_string.$or."incident_title LIKE '%$chunk%' OR incident_description LIKE '%"
+					.$chunk."%' OR location_name LIKE '%$chunk%'";
+					$i++;
+				}
+			}
+		}
+
+		if ($where_string)
+		{
+			return $where_string;
+		}
+		else
+		{
+			return "1=1";
+		}
+	}
+}
diff --git a/application/controllers/page.php b/application/controllers/page.php
new file mode 100644
index 0000000..86cbb96
--- /dev/null
+++ b/application/controllers/page.php
@@ -0,0 +1,56 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Pages controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Page_Controller extends Main_Controller {
+
+	function __construct()
+	{
+		parent::__construct();
+	}
+
+	public function index($page_id = 1)
+	{
+		$this->template->header->this_page = "page_".$page_id;
+		$this->template->content = new View('page');
+
+		if ( ! $page_id)
+		{
+			url::redirect('main');
+		}
+
+		$page = ORM::factory('page',$page_id)->find($page_id);
+		if ($page->loaded)
+		{
+
+			$page_title = $page->page_title;
+			$page_description = $page->page_description;
+			// Filter::page_title - Modify Page Title
+			Event::run('ushahidi_filter.page_title', $page_title);
+			// Filter::page_description - Modify Page Description
+			Event::run('ushahidi_filter.page_description', $page_description);
+
+			$this->template->content->page_title = $page_title;
+			$this->template->content->page_description = $page_description;
+			$this->template->content->page_id = $page->id;
+		}
+		else
+		{
+			url::redirect('main');
+		}
+
+		$this->template->header->page_title .= $page_title.Kohana::config('settings.title_delimiter');
+	}
+
+}
diff --git a/application/controllers/profile.php b/application/controllers/profile.php
new file mode 100644
index 0000000..697e31f
--- /dev/null
+++ b/application/controllers/profile.php
@@ -0,0 +1,106 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * This controller is used to view user profiles
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Profile_Controller extends Main_Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+	}
+
+	/**
+	 * Displays the default "profile" page
+	 */
+	public function index()
+	{
+		// Cacheable Controller
+		$this->is_cachable = TRUE;
+
+		$this->template->header->this_page = 'profile';
+		$this->template->content = new View('profile/main');
+
+		$this->template->content->users = User_Model::get_public_users();
+
+		$this->template->header->page_title .= Kohana::lang('ui_main.browse_profiles').Kohana::config('settings.title_delimiter');
+	}
+
+	/**
+	 * Displays a profile page for a user
+	 */
+	public function user()
+	{
+		// Cacheable Controller
+		$this->is_cachable = TRUE;
+
+		$this->template->header->this_page = 'profile';
+
+		// Check if we are looking for a user. Argument must be set to continue.
+		if( ! isset(Router::$arguments[0]))
+		{
+			url::redirect('profile');
+		}
+
+		$username = Router::$arguments[0];
+
+		// We won't allow profiles to be public if the username is an email address
+		if (valid::email($username))
+		{
+			url::redirect('profile');
+		}
+
+		$user = User_Model::get_user_by_username($username);
+
+		// We only want to show public profiles here
+		if($user->public_profile == 1)
+		{
+			$this->template->content = new View('profile/user');
+
+			$this->template->content->user = $user;
+
+			// User Reputation Score
+			$this->template->content->reputation = reputation::calculate($user->id);
+
+			// All users reports
+			$this->template->content->reports = ORM::factory('incident')
+				->where(array('user_id' => $user->id, 'incident_active' => 1))
+				->with('incident:location')
+				->find_all();
+
+			// Get Badges
+			$this->template->content->badges = Badge_Model::users_badges($user->id);
+
+			// Logged in user id (false if not logged in)
+			$logged_in_id = FALSE;
+			if(isset(Auth::instance()->get_user()->id))
+			{
+				$logged_in_id = Auth::instance()->get_user()->id;
+			}
+			$this->template->content->logged_in_id = $logged_in_id;
+
+			// Is this the logged in user?
+			$logged_in_user = FALSE;
+			if($logged_in_id == $user->id){
+				$logged_in_user = TRUE;
+			}
+			$this->template->content->logged_in_user = $logged_in_user;
+		}else{
+			// this is a private profile so get out of here
+			url::redirect('profile');
+		}
+
+		$this->template->header->page_title .= $user->name.Kohana::config('settings.title_delimiter');
+	}
+
+} // End Profile
diff --git a/application/controllers/reports.php b/application/controllers/reports.php
new file mode 100755
index 0000000..2a31e6a
--- /dev/null
+++ b/application/controllers/reports.php
@@ -0,0 +1,982 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * This controller is used to list/ view and edit reports
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Reports_Controller extends Main_Controller {
+	
+	/**
+	 * Whether an admin console user is logged in
+	 * @var bool
+	 */
+	var $logged_in;
+
+	public function __construct()
+	{
+		parent::__construct();
+
+		// Is the Admin Logged In?
+		$this->logged_in = Auth::instance()->logged_in();
+	}
+
+	/**
+	 * Displays all reports.
+	 */
+	public function index()
+	{
+		// Cacheable Controller
+		$this->is_cachable = TRUE;
+
+		$this->template->header->this_page = 'reports';
+		$this->template->content = new View('reports/main');
+		$this->themes->js = new View('reports/reports_js');
+
+		$this->template->header->page_title .= Kohana::lang('ui_main.reports').Kohana::config('settings.title_delimiter');
+
+		// Store any exisitng URL parameters
+		$this->themes->js->url_params = json_encode($_GET);
+
+		// Enable the map
+		$this->themes->map_enabled = TRUE;
+
+		// Set the latitude and longitude
+		$this->themes->js->latitude = Kohana::config('settings.default_lat');
+		$this->themes->js->longitude = Kohana::config('settings.default_lon');
+		$this->themes->js->default_map = Kohana::config('settings.default_map');
+		$this->themes->js->default_zoom = Kohana::config('settings.default_zoom');
+
+		// Get Default Color
+		$this->themes->js->default_map_all = $this->template->content->default_map_all = Kohana::config('settings.default_map_all');
+		
+		// Get default icon
+		$this->themes->js->default_map_all_icon = $this->template->content->default_map_all_icon = '';
+		if (Kohana::config('settings.default_map_all_icon_id'))
+		{
+			$icon_object = ORM::factory('media')->find(Kohana::config('settings.default_map_all_icon_id'));
+			$this->themes->js->default_map_all_icon = $this->template->content->default_map_all_icon = Kohana::config('upload.relative_directory')."/".$icon_object->media_thumb;
+		}
+
+		// Load the alert radius view
+		$alert_radius_view = new View('alerts/radius');
+		$alert_radius_view->show_usage_info = FALSE;
+		$alert_radius_view->enable_find_location = FALSE;
+		$alert_radius_view->css_class = "rb_location-radius";
+
+		$this->template->content->alert_radius_view = $alert_radius_view;
+
+		// Get locale
+		$l = Kohana::config('locale.language.0');
+
+		// Get the report listing view
+		$report_listing_view = $this->_get_report_listing_view($l);
+
+		// Set the view
+		$this->template->content->report_listing_view = $report_listing_view;
+
+		// Load the category
+		$category_id = (isset($_GET['c']) AND intval($_GET['c']) > 0)? intval($_GET['c']) : 0;
+		$category = ORM::factory('category', $category_id);
+
+		if ($category->loaded)
+		{
+			// Set the category title
+			$this->template->content->category_title = Category_Lang_Model::category_title($category_id,$l);
+		}
+		else
+		{
+			$this->template->content->category_title = "";
+		}
+
+		// Collect report stats
+		$this->template->content->report_stats = new View('reports/stats');
+		
+		// Total Reports
+		$total_reports = Incident_Model::get_total_reports(TRUE);
+
+		// Get the date of the oldest report
+		if (isset($_GET['s']) AND !empty($_GET['s']) AND intval($_GET['s']) > 0)
+		{
+			$oldest_timestamp =  intval($_GET['s']);
+		}
+		else
+		{
+			$oldest_timestamp = Incident_Model::get_oldest_report_timestamp();
+		}
+
+		// Get the date of the latest report
+		if (isset($_GET['e']) AND !empty($_GET['e']) AND intval($_GET['e']) > 0)
+		{
+			$latest_timestamp = intval($_GET['e']);
+		}
+		else
+		{
+			$latest_timestamp = Incident_Model::get_latest_report_timestamp();
+		}
+
+		// Round the number of days up to the nearest full day
+		$days_since = ceil((time() - $oldest_timestamp) / 86400);
+		$avg_reports_per_day = ($days_since < 1)? $total_reports : round(($total_reports / $days_since),2);
+
+		// Percent Verified
+		$total_verified = Incident_Model::get_total_reports_by_verified(TRUE);
+		$percent_verified = ($total_reports == 0) ? '-' : round((($total_verified / $total_reports) * 100),2).'%';
+
+		// Category tree view
+		$this->template->content->category_tree_view = category::get_category_tree_view();
+
+		// Additional view content
+		$this->template->content->custom_forms_filter = new View('reports/submit_custom_forms');
+		$this->template->content->custom_forms_filter->disp_custom_fields = customforms::get_custom_form_fields();
+		$this->template->content->custom_forms_filter->search_form = TRUE;
+		$this->template->content->oldest_timestamp = $oldest_timestamp;
+		$this->template->content->latest_timestamp = $latest_timestamp;
+		$this->template->content->report_stats->total_reports = $total_reports;
+		$this->template->content->report_stats->avg_reports_per_day = $avg_reports_per_day;
+		$this->template->content->report_stats->percent_verified = $percent_verified;
+		$this->template->content->services = Service_Model::get_array();
+	}
+
+	/**
+	 * Helper method to load the report listing view
+	 */
+	private function _get_report_listing_view($locale = '')
+	{
+		// Check if the local is empty
+		if (empty($locale))
+		{
+			$locale = Kohana::config('locale.language.0');
+		}
+
+		// Load the report listing view
+		$report_listing = new View('reports/list');
+
+		// Fetch all incidents
+		$incidents = reports::fetch_incidents(TRUE);
+
+		// Pagination
+		$pagination = reports::$pagination;
+
+		// For compatibility with older custom themes:
+		// Generate array of category titles with their proper localizations using an array
+		// DO NOT use this in new code, call Category_Lang_Model::category_title() directly
+		foreach(Category_Model::categories() as $category)
+		{
+			$localized_categories[$category['category_title']] = Category_Lang_Model::category_title($category['category_id']);
+		}
+
+		// Set the view content
+		$report_listing->incidents = $incidents;
+		$report_listing->localized_categories = $localized_categories;
+
+		//Set default as not showing pagination. Will change below if necessary.
+		$report_listing->pagination = "";
+
+		// Pagination and Total Num of Report Stats
+		$plural = ($pagination->total_items == 1)? "" : "s";
+
+		// Set the next and previous page numbers
+		$report_listing->next_page = $pagination->next_page;
+		$report_listing->previous_page = $pagination->previous_page;
+
+		if ($pagination->total_items > 0)
+		{
+			$current_page = ($pagination->sql_offset / $pagination->items_per_page) + 1;
+			$total_pages = ceil($pagination->total_items / $pagination->items_per_page);
+
+			if ($total_pages >= 1)
+			{
+				$report_listing->pagination = $pagination;
+
+				// Show the total of report
+				// @todo This is only specific to the frontend reports theme
+				$report_listing->stats_breadcrumb = $pagination->current_first_item.'-'
+											. $pagination->current_last_item.' of '.$pagination->total_items.' '
+											. Kohana::lang('ui_main.reports');
+			}
+			else
+			{ 
+				// If we don't want to show pagination
+				$report_listing->stats_breadcrumb = $pagination->total_items.' '.Kohana::lang('ui_admin.reports');
+			}
+		}
+		else
+		{
+			$report_listing->stats_breadcrumb = '('.$pagination->total_items.' report'.$plural.')';
+		}
+
+		// Return
+		return $report_listing;
+	}
+
+	public function fetch_reports()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+		
+		$report_listing_view = $this->_get_report_listing_view();
+		print $report_listing_view;
+	}
+
+	/**
+	 * Submits a new report.
+	 */
+	public function submit($id = FALSE, $saved = FALSE)
+	{
+		$db = new Database();
+
+		// First, are we allowed to submit new reports?
+		if ( ! Kohana::config('settings.allow_reports'))
+		{
+			url::redirect(url::site().'main');
+		}
+
+		$this->template->header->this_page = 'reports_submit';
+		$this->template->content = new View('reports/submit');
+
+		$this->template->header->page_title .= Kohana::lang('ui_main.reports_submit_new')
+											   .Kohana::config('settings.title_delimiter');
+
+		//Retrieve API URL
+		$this->template->api_url = Kohana::config('settings.api_url');
+
+		// Setup and initialize form field names
+		$form = array(
+			'incident_title' => '',
+			'incident_description' => '',
+			'incident_date' => '',
+			'incident_hour' => '',
+			'incident_minute' => '',
+			'incident_ampm' => '',
+			'latitude' => '',
+			'longitude' => '',
+			'geometry' => array(),
+			'location_name' => '',
+			'country_id' => '',
+			'country_name'=>'',
+			'incident_category' => array(),
+			'incident_news' => array(),
+			'incident_video' => array(),
+			'incident_photo' => array(),
+			'incident_zoom' => '',
+			'person_first' => '',
+			'person_last' => '',
+			'person_email' => '',
+			'form_id'	  => '',
+			'custom_field' => array()
+		);
+
+		// Copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+		$errors = $form;
+		$form_error = FALSE;
+		$form_saved = ($saved == 'saved');
+
+		// Initialize Default Values
+		$form['incident_date'] = date("m/d/Y",time());
+		$form['incident_hour'] = date('h');
+		$form['incident_minute'] = date('i');
+		$form['incident_ampm'] = date('a');
+		$form['country_id'] = Kohana::config('settings.default_country');
+
+		// Initialize Default Value for Hidden Field Country Name, just incase Reverse Geo coding yields no result
+		$country_name = ORM::factory('country',$form['country_id']);
+		$form['country_name'] = $country_name->country;
+
+		// Initialize custom field array
+		$form['form_id'] = 1;
+		$form_id = $form['form_id'];
+		$form['custom_field'] = customforms::get_custom_form_fields($id,$form_id,true);
+
+		// GET custom forms
+		$forms = array();
+		foreach (customforms::get_custom_forms() as $custom_forms)
+		{
+			$forms[$custom_forms->id] = $custom_forms->form_title;
+		}
+		$this->template->content->forms = $forms;
+
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+			$post = array_merge($_POST, $_FILES);
+			
+			// Adding event for endtime plugin to hook into
+			Event::run('ushahidi_action.report_posted_frontend', $post);
+
+			// Test to see if things passed the rule checks
+			if (reports::validate($post))
+			{
+
+				// STEP 1: SAVE LOCATION
+				$location = new Location_Model();
+				reports::save_location($post, $location);
+
+				// STEP 2: SAVE INCIDENT
+				$incident = new Incident_Model();
+				reports::save_report($post, $incident, $location->id);
+
+				// STEP 2b: SAVE INCIDENT GEOMETRIES
+				reports::save_report_geometry($post, $incident);
+
+				// STEP 3: SAVE CATEGORIES
+				reports::save_category($post, $incident);
+
+				// STEP 4: SAVE MEDIA
+				reports::save_media($post, $incident);
+
+				// STEP 5: SAVE CUSTOM FORM FIELDS
+				reports::save_custom_fields($post, $incident);
+
+				// STEP 6: SAVE PERSONAL INFORMATION
+				reports::save_personal_info($post, $incident);
+
+				// Run events
+				Event::run('ushahidi_action.report_submit', $post);
+				Event::run('ushahidi_action.report_add', $incident);
+
+				url::redirect('reports/thanks');
+			}
+
+			// No! We have validation errors, we need to show the form again, with the errors
+			else
+			{
+				// Repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// Populate the error fields, if any
+				$errors = arr::merge($errors, $post->errors('report'));
+				$form_error = TRUE;
+			}
+		}
+
+		// Retrieve Country Cities
+		$default_country = Kohana::config('settings.default_country');
+		$this->template->content->cities = $this->_get_cities($default_country);
+		$this->template->content->multi_country = Kohana::config('settings.multi_country');
+
+		$this->template->content->id = $id;
+		$this->template->content->form = $form;
+		$this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		
+		 // Populate this for backwards compat
+		$this->template->content->categories = array();
+
+		// Pass timezone
+		$this->template->content->site_timezone = Kohana::config('settings.site_timezone');
+
+		// Pass the submit report message
+		$this->template->content->site_submit_report_message = Kohana::config('settings.site_submit_report_message');
+
+		// Retrieve Custom Form Fields Structure
+		$this->template->content->custom_forms = new View('reports/submit_custom_forms');
+		$disp_custom_fields = customforms::get_custom_form_fields($id, $form_id, FALSE);
+		$this->template->content->disp_custom_fields = $disp_custom_fields;
+		$this->template->content->stroke_width_array = $this->_stroke_width_array();
+		$this->template->content->custom_forms->disp_custom_fields = $disp_custom_fields;
+		$this->template->content->custom_forms->form = $form;
+
+		// Javascript Header
+		$this->themes->map_enabled = TRUE;
+		$this->themes->treeview_enabled = TRUE;
+		$this->themes->colorpicker_enabled = TRUE;
+
+		$this->themes->js = new View('reports/submit_edit_js');
+		$this->themes->js->edit_mode = FALSE;
+		$this->themes->js->incident_zoom = FALSE;
+		$this->themes->js->default_map = Kohana::config('settings.default_map');
+		$this->themes->js->default_zoom = Kohana::config('settings.default_zoom');
+		if ( ! $form['latitude'] OR ! $form['latitude'])
+		{
+			$this->themes->js->latitude = Kohana::config('settings.default_lat');
+			$this->themes->js->longitude = Kohana::config('settings.default_lon');
+		}
+		else
+		{
+			$this->themes->js->latitude = $form['latitude'];
+			$this->themes->js->longitude = $form['longitude'];
+		}
+		$this->themes->js->geometries = $form['geometry'];
+
+	}
+
+	 /**
+	 * Displays a report.
+	 * @param boolean $id If id is supplied, a report with that id will be
+	 * retrieved.
+	 */
+	public function view($id = FALSE)
+	{
+		$this->template->header->this_page = 'reports';
+		$this->template->content = new View('reports/detail');
+
+		// Load Akismet API Key (Spam Blocker)
+		$api_akismet = Kohana::config('settings.api_akismet');
+
+		// Sanitize the report id before proceeding
+		$id = intval($id);
+
+		if ($id > 0 AND Incident_Model::is_valid_incident($id,TRUE))
+		{
+			$incident = ORM::factory('incident')
+				->where('id',$id)
+				->where('incident_active',1)
+				->find();
+				
+			// Not Found
+			if ( ! $incident->loaded) 
+			{
+				url::redirect('reports/view/');
+			}
+
+			// Comment Post?
+			// Setup and initialize form field names
+
+			$form = array(
+				'comment_author' => '',
+				'comment_description' => '',
+				'comment_email' => '',
+				'comment_ip' => '',
+				'captcha' => ''
+			);
+
+			$captcha = Captcha::factory();
+			$errors = $form;
+			$form_error = FALSE;
+
+			// Check, has the form been submitted, if so, setup validation
+
+			if ($_POST AND Kohana::config('settings.allow_comments') )
+			{
+				// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+				$post = Validation::factory($_POST);
+
+				// Add some filters
+				$post->pre_filter('trim', TRUE);
+
+				// Add some rules, the input field, followed by a list of checks, carried out in order
+				if ( ! $this->user)
+				{
+					$post->add_rules('comment_author', 'required', 'length[3,100]');
+					$post->add_rules('comment_email', 'required','email', 'length[4,100]');
+				}
+				$post->add_rules('comment_description', 'required');
+				$post->add_rules('captcha', 'required', 'Captcha::valid');
+
+				// Test to see if things passed the rule checks
+				if ($post->validate())
+				{
+					// Yes! everything is valid
+					if ($api_akismet != "")
+					{
+						// Run Akismet Spam Checker
+						$akismet = new Akismet();
+
+						// Comment data
+						$comment = array(
+							'website' => "",
+							'body' => $post->comment_description,
+							'user_ip' => $_SERVER['REMOTE_ADDR']
+						);
+
+						if ($this->user)
+						{
+							$comment['author'] = $this->user->name;
+							$comment['email'] = $this->user->email;
+						}
+						else
+						{
+							$comment['author'] = $post->comment_author;
+							$comment['email'] = $post->comment_email;
+						}
+
+						$config = array(
+							'blog_url' => url::site(),
+							'api_key' => $api_akismet,
+							'comment' => $comment
+						);
+
+						$akismet->init($config);
+
+						if ($akismet->errors_exist())
+						{
+							if ($akismet->is_error('AKISMET_INVALID_KEY'))
+							{
+								// throw new Kohana_Exception('akismet.api_key');
+							}
+							elseif ($akismet->is_error('AKISMET_RESPONSE_FAILED'))
+							{
+								// throw new Kohana_Exception('akismet.server_failed');
+							}
+							elseif ($akismet->is_error('AKISMET_SERVER_NOT_FOUND'))
+							{
+								// throw new Kohana_Exception('akismet.server_not_found');
+							}
+
+							$comment_spam = 0;
+						}
+						else
+						{
+							$comment_spam = ($akismet->is_spam()) ? 1 : 0;
+						}
+					}
+					else
+					{
+						// No API Key!!
+						$comment_spam = 0;
+					}
+
+					$comment = new Comment_Model();
+					$comment->incident_id = $id;
+					if ($this->user)
+					{
+						$comment->user_id = $this->user->id;
+						$comment->comment_author = $this->user->name;
+						$comment->comment_email = $this->user->email;
+					}
+					else
+					{
+						$comment->comment_author = $post->comment_author;
+						$comment->comment_email = $post->comment_email;
+					}
+					$comment->comment_description = $post->comment_description;
+					$comment->comment_ip = $_SERVER['REMOTE_ADDR'];
+					$comment->comment_date = date("Y-m-d H:i:s",time());
+
+					// Activate comment for now
+					if ($comment_spam == 1)
+					{
+						$comment->comment_spam = 1;
+						$comment->comment_active = 0;
+					}
+					else
+					{
+						$comment->comment_spam = 0;
+						$comment->comment_active = (Kohana::config('settings.allow_comments') == 1)? 1 : 0;
+					}
+					$comment->save();
+
+					// Event::comment_add - Added a New Comment
+					Event::run('ushahidi_action.comment_add', $comment);
+
+					// Notify Admin Of New Comment
+					// HT: Ensure only valid comments not spam are sent out
+					// as notifications to the administrator
+					if ($comment_spam == 0) { 
+						$send = notifications::notify_admins(
+							"[".Kohana::config('settings.site_name')."] ".
+								Kohana::lang('notifications.admin_new_comment.subject'),
+								Kohana::lang('notifications.admin_new_comment.message')
+								."\n\n'".utf8::strtoupper($incident->incident_title)."'"
+								."\n".url::site('reports/view/'.$id)
+							);
+					}
+					// Redirect
+					url::redirect('reports/view/'.$id);
+				
+				}
+				else
+				{
+					// No! We have validation errors, we need to show the form again, with the errors
+					// Repopulate the form fields
+					$form = arr::overwrite($form, $post->as_array());
+
+					// Populate the error fields, if any
+					$errors = arr::overwrite($errors, $post->errors('comments'));
+					$form_error = TRUE;
+				}
+			}
+
+			// Filters
+			$incident_title = $incident->incident_title;
+			$incident_description = $incident->incident_description;
+			Event::run('ushahidi_filter.report_title', $incident_title);
+			Event::run('ushahidi_filter.report_description', $incident_description);
+
+			$this->template->header->page_title .= $incident_title.Kohana::config('settings.title_delimiter');
+
+			// Add Features
+			$this->template->content->features_count = $incident->geometry->count();
+			$this->template->content->features = $incident->geometry;
+			$this->template->content->incident_id = $incident->id;
+			$this->template->content->incident_title = $incident_title;
+			$this->template->content->incident_description = $incident_description;
+			$this->template->content->incident_location = $incident->location->location_name;
+			$this->template->content->incident_latitude = $incident->location->latitude;
+			$this->template->content->incident_longitude = $incident->location->longitude;
+			$this->template->content->incident_date = date('M j Y', strtotime($incident->incident_date));
+			$this->template->content->incident_time = date('H:i', strtotime($incident->incident_date));
+			$this->template->content->incident_category = $incident->incident_category;
+
+			// Incident rating
+			$this->template->content->incident_rating = $this->_get_rating($incident->id, 'original');
+
+			// Retrieve Media
+			$incident_news = array();
+			$incident_video = array();
+			$incident_photo = array();
+
+			foreach ($incident->media as $media)
+			{
+				if ($media->media_type == 4)
+				{
+					$incident_news[] = $media->media_link;
+				}
+				elseif ($media->media_type == 2)
+				{
+					$incident_video[] = $media->media_link;
+				}
+				elseif ($media->media_type == 1)
+				{
+					$incident_photo[] = array(
+						'large' => url::convert_uploaded_to_abs($media->media_link),
+						'thumb' => url::convert_uploaded_to_abs($media->media_thumb)
+						);
+				}
+			}
+
+			$this->template->content->incident_verified = $incident->incident_verified;
+
+			// Retrieve Comments (Additional Information)
+			$this->template->content->comments = "";
+			if (Kohana::config('settings.allow_comments'))
+			{
+				$this->template->content->comments = new View('reports/comments');
+				$incident_comments = array();
+				if ($id)
+				{
+					$incident_comments = Incident_Model::get_comments($id);
+				}
+				$this->template->content->comments->incident_comments = $incident_comments;
+			}
+		}
+		else
+		{
+			url::redirect('main');
+		}
+
+		// Add Neighbors
+		$this->template->content->incident_neighbors = Incident_Model::get_neighbouring_incidents($id, TRUE, 0, 5);
+
+		// News Source links
+		$this->template->content->incident_news = $incident_news;
+
+
+		// Video links
+		$this->template->content->incident_videos = $incident_video;
+
+		// Images
+		$this->template->content->incident_photos = $incident_photo;
+
+		// Create object of the video embed class
+		$video_embed = new VideoEmbed();
+		$this->template->content->videos_embed = $video_embed;
+
+		// Javascript Header
+		$this->themes->map_enabled = TRUE;
+		$this->themes->photoslider_enabled = TRUE;
+		$this->themes->validator_enabled = TRUE;
+		$this->themes->js = new View('reports/view_js');
+		$this->themes->js->incident_id = $incident->id;
+		$this->themes->js->default_map = Kohana::config('settings.default_map');
+		$this->themes->js->default_zoom = Kohana::config('settings.default_zoom');
+		$this->themes->js->latitude = $incident->location->latitude;
+		$this->themes->js->longitude = $incident->location->longitude;
+		$this->themes->js->incident_zoom = $incident->incident_zoom;
+		$this->themes->js->incident_photos = $incident_photo;
+
+		// Initialize custom field array
+		$this->template->content->custom_forms = new View('reports/detail_custom_forms');
+		$form_field_names = customforms::get_custom_form_fields($id, $incident->form_id, FALSE, "view");
+		$this->template->content->custom_forms->form_field_names = $form_field_names;
+
+		// Are we allowed to submit comments?
+		$this->template->content->comments_form = "";
+		if (Kohana::config('settings.allow_comments'))
+		{
+			$this->template->content->comments_form = new View('reports/comments_form');
+			$this->template->content->comments_form->user = $this->user;
+			$this->template->content->comments_form->form = $form;
+			$this->template->content->comments_form->form_field_names = $form_field_names;
+			$this->template->content->comments_form->captcha = $captcha;
+			$this->template->content->comments_form->errors = $errors;
+			$this->template->content->comments_form->form_error = $form_error;
+		}
+
+		// If the Admin is Logged in - Allow for an edit link
+		$this->template->content->logged_in = $this->logged_in;
+	}
+
+	/**
+	 * Report Thanks Page
+	 */
+	public function thanks()
+	{
+		$this->template->header->this_page = 'reports_submit';
+		$this->template->content = new View('reports/submit_thanks');
+		// Get Site Email
+		$this->template->content->report_email = Kohana::config('settings.site_email');
+	}
+
+	/**
+	 * Report Rating.
+	 * @param boolean $id If id is supplied, a rating will be applied to selected report
+	 */
+	public function rating($id = false)
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+
+		if (!$id)
+		{
+			echo json_encode(array("status"=>"error", "message"=>"ERROR!"));
+		}
+		else
+		{
+			if (!empty($_POST['action']) AND !empty($_POST['type']))
+			{
+				$action = $_POST['action'];
+				$type = $_POST['type'];
+
+				// Is this an ADD(+1) or SUBTRACT(-1)?
+				if ($action == 'add')
+				{
+					$action = 1;
+				}
+				elseif ($action == 'subtract')
+				{
+					$action = -1;
+				}
+				else
+				{
+					$action = 0;
+				}
+
+				if (!empty($action) AND ($type == 'original' OR $type == 'comment'))
+				{
+					// Has this User or IP Address rated this post before?
+					if ($this->user)
+					{
+						$filter = array("user_id" => $this->user->id);
+					}
+					else
+					{
+						$filter = array("rating_ip" => $_SERVER['REMOTE_ADDR']);
+					}
+
+					if ($type == 'original')
+					{
+						$previous = ORM::factory('rating')
+							->where('incident_id',$id)
+							->where($filter)
+							->find();
+					}
+					elseif ($type == 'comment')
+					{
+						$previous = ORM::factory('rating')
+							->where('comment_id',$id)
+							->where($filter)
+							->find();
+					}
+
+					// If previous exits... update previous vote
+					$rating = new Rating_Model($previous->id);
+
+					// Are we rating the original post or the comments?
+					if ($type == 'original')
+					{
+						$rating->incident_id = $id;
+					}
+					elseif ($type == 'comment')
+					{
+						$rating->comment_id = $id;
+					}
+
+					// Is there a user?
+					if ($this->user)
+					{
+						$rating->user_id = $this->user->id;
+
+						// User can't rate their own stuff
+						if ($type == 'original')
+						{
+							if ($rating->incident->user_id == $this->user->id)
+							{
+								echo json_encode(array("status"=>"error", "message"=>"Can't rate your own Reports!"));
+								exit;
+							}
+						}
+						elseif ($type == 'comment')
+						{
+							if ($rating->comment->user_id == $this->user->id)
+							{
+								echo json_encode(array("status"=>"error", "message"=>"Can't rate your own Comments!"));
+								exit;
+							}
+						}
+					}
+
+					$rating->rating = $action;
+					$rating->rating_ip = $_SERVER['REMOTE_ADDR'];
+					$rating->rating_date = date("Y-m-d H:i:s",time());
+					$rating->save();
+
+					// Get total rating and send back to json
+					$total_rating = $this->_get_rating($id, $type);
+
+					echo json_encode(array("status"=>"saved", "message"=>"SAVED!", "rating"=>$total_rating));
+				}
+				else
+				{
+					echo json_encode(array("status"=>"error", "message"=>"Nothing To Do!"));
+				}
+			}
+			else
+			{
+				echo json_encode(array("status"=>"error", "message"=>"Nothing To Do!"));
+			}
+		}
+	}
+
+	public function geocode()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+
+		if (isset($_POST['address']) AND ! empty($_POST['address']))
+		{
+			$geocode_result = map::geocode($_POST['address']);
+			if ($geocode_result)
+			{
+				echo json_encode(array_merge(
+					$geocode_result, 
+					array('status' => 'success')
+				));
+			}
+			else
+			{
+				echo json_encode(array(
+					'status' => 'error',
+					'message' =>'ERROR!'
+				));
+			}
+		}
+		else
+		{
+			echo json_encode(array(
+				'status' => 'error',
+				'message' => 'ERROR!'
+			));
+		}
+	}
+
+	/**
+	 * Retrieves Cities
+	 * @param int $country_id Id of the country whose cities are to be fetched
+	 * @return array
+	 */
+	private function _get_cities($country_id)
+	{
+		// Get the cities
+		$cities = (Kohana::config('settings.multi_country'))
+		    ? City_Model::get_all()
+		    : ORM::factory('country', $country_id)->get_cities();
+
+		$city_select = array('' => Kohana::lang('ui_main.reports_select_city'));
+
+		foreach ($cities as $city)
+		{
+			$city_select[$city->city_lon.",".$city->city_lat] = $city->city;
+		}
+
+		return $city_select;
+	}
+
+	/**
+	 * Retrieves Total Rating For Specific Post
+	 * Also Updates The Incident & Comment Tables (Ratings Column)
+	 */
+	private function _get_rating($id = FALSE, $type = NULL)
+	{
+		if (empty($id))
+			return 0;
+		
+		$total_rating = 0;
+		$result = FALSE;
+		
+		if ($type == 'original')
+		{
+			$result = $this->db->query('SELECT SUM(rating) as total_rating FROM '.$this->table_prefix.'rating WHERE incident_id = ?', $id);
+		}
+		elseif ($type == 'comment')
+		{
+			$result = $this->db->query('SELECT SUM(rating) as total_rating FROM '.$this->table_prefix.'rating WHERE comment_id = ?', $id);
+		}
+		
+		if ($result->count() == 0 OR $result->current()->total_rating == NULL) return 0;
+		
+		$total_rating = $result->current()->total_rating;
+		
+		return $total_rating;
+	}
+
+	/**
+	 * Validates a numeric array. All items contained in the array must be numbers or numeric strings
+	 *
+	 * @param array $nuemric_array Array to be verified
+	 */
+	private function _is_numeric_array($numeric_array=array())
+	{
+		if (count($numeric_array) == 0)
+			return FALSE;
+		else
+		{
+			foreach ($numeric_array as $item)
+			{
+				if (! is_numeric($item))
+					return FALSE;
+			}
+
+			return TRUE;
+		}
+	}
+
+	/**
+	 * Array with Geometry Stroke Widths
+    */
+	private function _stroke_width_array()
+	{
+		for ($i = 0.5; $i <= 8 ; $i += 0.5)
+		{
+			$stroke_width_array["$i"] = $i;
+		}
+
+		return $stroke_width_array;
+	}
+
+	/**
+	 * Ajax call to update Incident Reporting Form
+	 */
+	public function switch_form()
+	{
+		$this->template = "";
+		$this->auto_render = FALSE;
+		isset($_POST['form_id']) ? $form_id = $_POST['form_id'] : $form_id = "1";
+		isset($_POST['incident_id']) ? $incident_id = $_POST['incident_id'] : $incident_id = "";
+		
+		$form_fields = customforms::switcheroo($incident_id,$form_id);
+		echo json_encode(array("status"=>"success", "response"=>$form_fields));
+	}
+
+}
diff --git a/application/controllers/riverid.php b/application/controllers/riverid.php
new file mode 100644
index 0000000..d9d6115
--- /dev/null
+++ b/application/controllers/riverid.php
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This controller handles RiverID
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Riverid_Controller extends Template_Controller {
+	
+	public $auto_render = TRUE;
+	
+	// Session Object
+	protected $session;
+	
+	// Main template
+	public $template = 'riverid';
+	
+	// RiverID Library Object
+	public $riverid;
+	
+
+	public function __construct()
+	{
+		parent::__construct();
+		
+		$this->session = new Session();
+		
+		// Fire up the RiverID Object
+		$this->riverid = new RiverID;
+		
+		// Set riverid vars
+		$this->riverid->email = @$_GET['email'];
+		
+		header('Content-type: application/json');
+		
+	}
+	
+	public function index()
+	{
+		// We don't have anything here, maybe return some kind of status code
+		//   that says we aren't passing variables
+	}
+	
+	public function registered()
+	{	
+		$this->template->json = $this->riverid->registered();
+	}
+	
+	
+}
diff --git a/application/controllers/scheduler.php b/application/controllers/scheduler.php
new file mode 100644
index 0000000..7bdebb2
--- /dev/null
+++ b/application/controllers/scheduler.php
@@ -0,0 +1,225 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Scheduler Controller (FAUX Cron)
+ * Generates 1x1 pixel image while executing scheduled tasks
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Scheduler_Controller extends Controller {
+	public function __construct()
+	{
+		parent::__construct();
+		// Increase max execution time
+		if (ini_get('max_execution_time') < 180)
+		{
+			ini_set('max_execution_time', 180);
+		}
+
+		// Set time limit only if we're not on safe_mode and set_time_limit is enabled
+		$safe_mode_enabled = ini_get('safe_mode');
+		$disabled_functions = ini_get('disable_functions');
+
+		if (empty($safe_mode_enabled) && strstr($disabled_functions, "set_time_limit") === false)
+		{
+			set_time_limit(180);
+		}
+	}
+
+	public function index()
+	{
+		// Debug
+		$debug = "";
+		
+		
+		// @todo abstract most of this into a library, especially locking
+		
+		// Ensure settings entry for `scheduler_lock` exists
+		Database::instance()->query(
+			"INSERT IGNORE INTO `".Kohana::config('database.default.table_prefix')."settings`
+			(`key`, `value`) VALUES ('scheduler_lock', 0)");
+		
+		// Now try and update the scheduler_lock
+		$result = Database::instance()->query(
+			"UPDATE `".Kohana::config('database.default.table_prefix')."settings`
+			SET `value` = UNIX_TIMESTAMP() + 180
+			WHERE `value` < UNIX_TIMESTAMP() AND `key` = 'scheduler_lock';");
+		
+		// If no entries were changed, scheduler is already running
+		if ($result->count() <= 0)
+		{
+			Kohana::log('info', 'Could not acquire scheduler lock');
+			if (isset($_GET['debug']) AND $_GET['debug'] == 1)
+			{
+				echo 'Could not acquire scheduler lock';
+			}
+			return;
+		}
+
+		// Get all active scheduled items
+		foreach (ORM::factory('scheduler')
+		->where('scheduler_active','1')
+		->find_all() as $scheduler)
+		{
+			$scheduler_id = $scheduler->id;
+			$scheduler_last = $scheduler->scheduler_last;
+			// Next run time
+			$scheduler_weekday = $scheduler->scheduler_weekday;
+			// Day of the week
+			$scheduler_day = $scheduler->scheduler_day;
+			// Day of the month
+			$scheduler_hour = $scheduler->scheduler_hour;
+			// Hour
+			$scheduler_minute = $scheduler->scheduler_minute;
+			// Minute
+
+			// Controller that performs action
+			$scheduler_controller = $scheduler->scheduler_controller;
+
+			if ($scheduler_day <= -1)
+			{
+				// Ran every day?
+				$scheduler_day = "*";
+			}
+
+			if ($scheduler_weekday <= -1)
+			{
+				// Ran every day?
+				$scheduler_weekday = "*";
+			}
+
+			if ($scheduler_hour <= -1)
+			{
+				// Ran every hour?
+				$scheduler_hour = "*";
+			}
+
+			if ($scheduler_minute <= -1)
+			{
+				// Ran every minute?
+				$scheduler_minute = "*";
+			}
+
+			$scheduler_cron = $scheduler_minute . " " . $scheduler_hour . " " . $scheduler_day . " * " . $scheduler_weekday;
+
+			//Start new cron parser instance
+			$cron = new CronParser();
+
+			if (!$cron->calcLastRan($scheduler_cron))
+			{
+				echo "Error parsing CRON";
+			}
+
+			$lastRan = $cron->getLastRan();
+			//Array (0=minute, 1=hour, 2=dayOfMonth, 3=month, 4=week, 5=year)
+			$cronRan = mktime($lastRan[1], $lastRan[0], 0, $lastRan[3], $lastRan[2], $lastRan[5]);
+
+			if (isset($_GET['debug']) AND $_GET['debug'] == 1)
+			{
+				$debug .= "~~~~~~~~~~~~~~~~~~~~~~~~~~~" . "<BR />~~~~~~~~~~~~~~~~~~~~~~~~~~~" . "<BR />RUNNING: " . $scheduler->scheduler_name . "<BR />~~~~~~~~~~~~~~~~~~~~~~~~~~~" . "<BR /> LAST RUN: " . date("r", $scheduler_last) . "<BR /> LAST DUE AT: " . date('r', $cron->getLastRanUnix()) . "<BR /> SCHEDULE: <a href=\"http://en.wikipedia.org/wiki/Cron\" target=\"_blank\">" . $scheduler_cron . "</a>";
+			}
+
+			if ($scheduler_controller AND (!($scheduler_last > $cronRan) OR $scheduler_last == 0))
+			{
+				$run = FALSE;
+
+				// Catch errors from missing scheduler or other bugs
+				try {
+					$dispatch = Dispatch::controller($scheduler_controller, "scheduler/");
+
+					if ($dispatch instanceof Dispatch && method_exists($dispatch,'method'))
+					{
+						$run = $dispatch->method('index', '');
+					}
+				}
+				catch (Exception $e)
+				{
+					// Nada.
+				}
+
+				// @todo allow tasks to save state between runs. 
+
+				if ($run !== FALSE)
+				{
+					// Set last time of last execution
+					$schedule_time = time();
+					$scheduler->scheduler_last = $schedule_time;
+					$scheduler->save();
+
+					// Record Action to Log
+					$scheduler_log = new Scheduler_Log_Model();
+					$scheduler_log->scheduler_id = $scheduler_id;
+					$scheduler_log->scheduler_status = "200";
+					$scheduler_log->scheduler_date = $schedule_time;
+					$scheduler_log->save();
+
+					if (isset($_GET['debug']) AND $_GET['debug'] == 1)
+					{
+						$debug .= "<BR /> STATUS: {{ EXECUTED }}";
+					}
+				}
+				else
+				{
+					if (isset($_GET['debug']) AND $_GET['debug'] == 1)
+					{
+						$debug .= "<BR /> STATUS: {{ SCHEDULER NOT FOUND! }}";
+					}
+				}
+
+			}
+			else
+			{
+				if (isset($_GET['debug']) AND $_GET['debug'] == 1)
+				{
+					$debug .= "<BR /> STATUS: {{ NOT RUN }}";
+				}
+			}
+			if (isset($_GET['debug']) AND $_GET['debug'] == 1)
+			{
+				//$debug .= "<BR /><BR />CRON DEBUG:<BR />".nl2br($cron->getDebug());
+				$debug .= "<BR />~~~~~~~~~~~~~~~~~~~~~~~~~~~<BR />~~~~~~~~~~~~~~~~~~~~~~~~~~~<BR /><BR /><BR />";
+			}
+		}
+
+		if (Kohana::config('cdn.cdn_gradual_upgrade') != FALSE)
+		{
+			cdn::gradual_upgrade();
+		}
+
+		// If DEBUG is TRUE echo DEBUG info instead of transparent GIF
+		if (isset($_GET['debug']) AND $_GET['debug'] == 1)
+		{
+			echo $debug;
+			if (isset($this->profiler))
+			{
+				echo $this->profiler->render(TRUE);
+		}
+		}
+		else
+		{
+			// Transparent GIF
+			Header("Content-type: image/gif");
+			Header("Expires: Wed, 11 Nov 1998 11:11:11 GMT");
+			Header("Cache-Control: no-cache");
+			Header("Cache-Control: must-revalidate");
+			Header("Content-Length: 49");
+			echo pack('H*', '47494638396101000100910000000000ffffffff' . 'ffff00000021f90405140002002c000000000100' . '01000002025401003b');
+		}
+		
+		// Release lock
+		$result = Database::instance()->query(
+			"UPDATE `".Kohana::config('database.default.table_prefix')."settings`
+			SET `value` = 0
+			WHERE `key` = 'scheduler_lock';");
+		
+	}
+
+}
diff --git a/application/controllers/scheduler/s_alerts.php b/application/controllers/scheduler/s_alerts.php
new file mode 100644
index 0000000..ed68ee4
--- /dev/null
+++ b/application/controllers/scheduler/s_alerts.php
@@ -0,0 +1,300 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Alerts Scheduler Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Scheduler
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+*/
+
+class S_Alerts_Controller extends Controller {
+	
+	public $table_prefix = '';
+	
+	// Cache instance
+	protected $cache;
+	
+	function __construct()
+	{
+		parent::__construct();
+
+		// Load cache
+		$this->cache = new Cache;
+		
+		// *************************************
+		// ** SAFEGUARD DUPLICATE SEND-OUTS **
+		// Create A 15 Minute SEND LOCK
+		// This lock is released at the end of execution
+		// Or expires automatically
+		$alerts_lock = $this->cache->get(Kohana::config('settings.subdomain')."_alerts_lock");
+		if ( ! $alerts_lock)
+		{
+			// Lock doesn't exist
+			$timestamp = time();
+			$this->cache->set(Kohana::config('settings.subdomain')."_alerts_lock", $timestamp, array("alerts"), 900);
+		}
+		else
+		{
+			// Lock Exists - End
+			exit("Other process is running - waiting 15 minutes!");
+		}
+		// *************************************
+	}
+	
+	function __destruct()
+	{
+		$this->cache->delete(Kohana::config('settings.subdomain')."_alerts_lock");
+	}
+	
+	public function index() 
+	{
+		$settings = kohana::config('settings');
+		$site_name = $settings['site_name'];
+		$alerts_email = ($settings['alerts_email']) ? $settings['alerts_email']
+			: $settings['site_email'];
+		$unsubscribe_message = Kohana::lang('alerts.unsubscribe')
+								.url::site().'alerts/unsubscribe/';
+
+				$database_settings = kohana::config('database'); //around line 33
+				$this->table_prefix = $database_settings['default']['table_prefix']; //around line 34
+
+		$settings = NULL;
+		$sms_from = NULL;
+
+		$db = new Database();
+		
+		/* Find All Alerts with the following parameters
+		- incident_active = 1 -- An approved incident
+		- incident_alert_status = 1 -- Incident has been tagged for sending
+		
+		Incident Alert Statuses
+		- 0, Incident has not been tagged for sending. Ensures old incidents are not sent out as alerts
+		- 1, Incident has been tagged for sending by updating it with 'approved' or 'verified'
+		- 2, Incident has been tagged as sent. No need to resend again
+		*/
+		// HT: New Code
+		// Fixes an issue with one report being sent out as an alert more than ones
+		// becoming spam to users
+		$incident_query = "SELECT i.id, incident_title,
+				incident_description, incident_verified,
+				l.latitude, l.longitude FROM ".$this->table_prefix."incident AS i INNER JOIN ".$this->table_prefix."location AS l ON i.location_id = l.id
+				WHERE i.incident_active=1 AND i.incident_alert_status = 1 ";
+		/** HT: Code for alert days limitation
+		 * @int alert_days = 0 : All alerts
+		 * @int alert_days = 1 : TODAY
+		 * @int alert_days > 1 : alert_days - 1 days before
+		 */
+		if($alert_days = $settings['alert_days'])
+		{
+			$incident_query .= "AND DATE(i.incident_date) >= DATE_SUB( CURDATE(), INTERVAL ".($alert_days-1)." DAY )";
+		}
+		// End of New Code		
+		
+		$incidents = $db->query($incident_query);
+		
+		foreach ($incidents as $incident)
+		{
+			// ** Pre-Formatting Message ** //
+			// Convert HTML to Text
+			$incident_description = $incident->incident_description;
+			$incident_url = url::site().'reports/view/'.$incident->id;
+			$incident_description = html::clean($incident_description);
+
+			// EMAIL MESSAGE
+			$email_message = $incident_description . "\n\n" . $incident_url;
+
+			// SMS MESSAGE
+			$sms_message = $incident_description;
+			// Remove line breaks
+			$sms_message = str_replace("\n", " ", $sms_message);
+			// Shorten to text message size
+			if(Kohana::config("settings.sms_alert_url"))
+			{
+				$sms_message = text::limit_chars($sms_message, 100, "..."); // HT: Decreased sms lenght of sms to add incident_url
+				$sms_message .= " ".$incident_url; // HT: Added incident_url to sms
+			}
+			else
+			{
+				$sms_message = text::limit_chars($sms_message, 150, "...");
+			}
+			
+			
+			
+			$latitude = (double) $incident->latitude;
+			$longitude = (double) $incident->longitude;
+
+			// Find all the catecories including parents
+			$category_ids = $this->_find_categories($incident->id);
+
+				
+			// HT: New Code
+			$alert_sent = ORM::factory('alert_sent')->where('incident_id', $incident->id)->select_list('id', 'alert_id');
+			$alertObj = ORM::factory('alert')->where('alert_confirmed','1');
+			
+			if(!empty($alert_sent)) {
+				$alertObj->notin('id', $alert_sent);
+			}
+			$alertees = $alertObj->find_all();
+			// End of new code
+			
+			foreach ($alertees as $alertee)
+			{
+				// HT: check same alert_receipent multi subscription does not get multiple alert
+				if($this->_multi_subscribe($alertee, $incident->id)) {
+					continue;
+				}
+				// Check the categories
+				if (!$this->_check_categories($alertee, $category_ids)) {
+				  continue;
+				}
+
+				$alert_radius = (int) $alertee->alert_radius;
+				$alert_type = (int) $alertee->alert_type;
+				$latitude2 = (double) $alertee->alert_lat;
+				$longitude2 = (double) $alertee->alert_lon;
+				
+				$distance = (string) new Distance($latitude, $longitude, $latitude2, $longitude2);
+				
+				// If the calculated distance between the incident and the alert fits...
+				if ($distance <= $alert_radius)
+				{
+					if ($alert_type == 1) // SMS alertee
+					{
+						// Get SMS Numbers
+						if (Kohana::config("settings.sms_no3"))
+							$sms_from = Kohana::config("settings.sms_no3");
+						elseif (Kohana::config("settings.sms_no2"))
+							$sms_from = Kohana::config("settings.sms_no2");
+						elseif (Kohana::config("settings.sms_no1"))
+							$sms_from = Kohana::config("settings.sms_no1");
+						else
+							$sms_from = "12053705050";		// Admin needs to set up an SMS number	
+						
+						
+						
+						if ($response = sms::send($alertee->alert_recipient, $sms_from, $sms_message) === true)
+						{
+							$alert = ORM::factory('alert_sent');
+							$alert->alert_id = $alertee->id;
+							$alert->incident_id = $incident->id;
+							$alert->alert_date = date("Y-m-d H:i:s");
+							$alert->save();
+						}
+						else
+						{
+							// The gateway couldn't send for some reason
+							// in future we'll keep a record of this
+						}
+					}
+
+					elseif ($alert_type == 2) // Email alertee
+					{
+						$to = $alertee->alert_recipient;
+						$from = array();
+							$from[] = $alerts_email;
+							$from[] = $site_name;
+						$subject = "[$site_name] ".$incident->incident_title;
+						$message = text::auto_p($email_message
+									. "\n\n".$unsubscribe_message
+									. $alertee->alert_code . "\n");
+
+						//if (email::send($to, $from, $subject, $message, FALSE) == 1)
+						if (email::send($to, $from, $subject, $message, TRUE) == 1) // HT: New Code
+						{
+							$alert = ORM::factory('alert_sent');
+							$alert->alert_id = $alertee->id;
+							$alert->incident_id = $incident->id;
+							$alert->alert_date = date("Y-m-d H:i:s");
+							$alert->save();
+						}
+					}
+				}
+			} // End For Each Loop
+			
+
+			// Update Incident - All Alerts Have Been Sent!
+			$update_incident = ORM::factory('incident', $incident->id);
+			if ($update_incident->loaded)
+			{
+				$update_incident->incident_alert_status = 2;
+				$update_incident->save();
+			}
+		}
+	}
+
+	private function _find_categories($incident_id) {
+	  $ret = array();
+	  $incident_categories = ORM::factory('incident_category')
+	    ->where('incident_id', $incident_id)
+	    ->find_all();
+
+	  foreach ($incident_categories as $ic) {
+	    $category = ORM::factory('category')
+	      ->where('id', $ic->category_id)
+	      ->find();
+	    $this->_add_category($ret, $category);
+	  }
+
+	  return $ret;
+	}
+
+	private function _add_category(array & $ids, Category_Model $category) {
+	  if ($category == null) {
+	    return;
+	  }
+
+	  $id = (string)$category->id;
+
+	  if (!array_key_exists($id, $ids)) {
+	    $ids[$id] = 1;
+	  }
+
+	  if ($category->parent_id != 0) {
+	    $parent = ORM::factory('category')
+	      ->where('id', $category->parent_id)
+	      ->find();
+
+	    $this->_add_category($ids, $parent);
+	  }
+	}
+
+	private function _check_categories(Alert_Model $alertee, array $category_ids) {
+	  $ret = false;
+
+	  $alert_categories = ORM::factory('alert_category')
+	    ->where('alert_id', $alertee->id)
+	    ->find_all();
+
+	  if (count($alert_categories) == 0) {
+	    $ret = true;
+	  }
+	  else {
+	    foreach ($alert_categories as $ac) {
+	      if (array_key_exists((string)$ac->category_id, $category_ids)) {
+		$ret = true;
+	      }
+	    }
+	  }
+
+	  return $ret;
+	}
+	
+	/**
+	 * HT: Function to verify that alert is not sent to same alert_receipent being subscribed multiple time
+	 * @param Alert_Model $alertee
+	 * @param integer $incident_id
+	 * @return boolean
+	 */
+	private function _multi_subscribe(Alert_Model $alertee, $incident_id) {
+		$multi_subscribe_ids = ORM::factory('alert')->where('alert_confirmed','1')->where('alert_recipient', $alertee->alert_recipient)->select_list('id', 'id');
+		$subscription_alert = ORM::factory('alert_sent')->where('incident_id', $incident_id)->in('alert_id', $multi_subscribe_ids)->find();
+		return ((boolean) $subscription_alert->id);
+	}
+
+}
diff --git a/application/controllers/scheduler/s_cleanup.php b/application/controllers/scheduler/s_cleanup.php
new file mode 100644
index 0000000..cc14ac7
--- /dev/null
+++ b/application/controllers/scheduler/s_cleanup.php
@@ -0,0 +1,157 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Cleaup Scheduler Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Scheduler
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+*/
+
+class S_Cleanup_Controller extends Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+	}
+
+	/**
+	 * Call the various cleanup functions;
+	 */
+	public function index()
+	{
+
+		$this->remove_orphan_images();
+		$this->remove_old_logs();
+
+		return TRUE;
+	}
+
+	/**
+	 * Deletes old log files older than # days defined in config
+	 */
+	public function remove_old_logs()
+	{
+		$days_old = Kohana::config('config.log_cleanup_days_old');
+
+		// First check if we should even be doing this
+		if ($days_old == FALSE OR ! is_int($days_old))
+		{
+			return FALSE;
+		}
+
+		$dir = Kohana::log_directory();
+
+		if (is_dir($dir)
+			AND is_writable($dir)
+			AND $dh = opendir($dir))
+		{
+			$oldest_allowed = date('Y-m-d',mktime(0, 0, 0, date("m"), date("d")-$days_old, date("Y")));
+
+			while ( ($file = readdir($dh)) !== false )
+			{
+				// If it's a hidden or system file, skip it
+				if ($file{0} == '.')
+				{
+					continue;
+				}
+
+				// Strip off the file extension so we can just evaluate the date
+				$date = str_ireplace('.log'.EXT, '', $file);
+
+				if ($date <= $oldest_allowed)
+				{
+					// This file needs to be deleted.
+					unlink($dir.$file);
+				}
+			}
+			closedir($dh);
+		}
+
+		//$filename = $dir.date('Y-m-d').'.log'.EXT;
+		//var_dump($filename);
+	}
+
+	/**
+	 * This function helps cleanup orphaned images in the upload directory
+	 * Orphans are usually a result of new reports that are never completed
+	 */
+	public function remove_orphan_images()
+	{
+
+		// open the images directory and create it if it's not there.
+		if( ! is_dir(Kohana::config('upload.relative_directory')))
+		{
+			mkdir(Kohana::config('upload.relative_directory'), 0755);
+		}
+
+		$dhandle = opendir(Kohana::config('upload.relative_directory'));
+
+		// define an array to hold the files
+		$files = array();
+
+		// Image Extensions
+		$img_extensions = array(".jpg", ".gif", ".png");
+
+		if ($dhandle)
+		{
+			// Get all the media files from the database so we can check if the file isn't orphaned
+
+			$images = ORM::factory("media")->where('media_type', 1)->find_all();
+
+			// Turn this into an array that we can easily check against
+
+			$image_list = array();
+			foreach($images as $image)
+			{
+				if($image->media_link != NULL) $image_list[] = $image->media_link;
+				if($image->media_medium != NULL) $image_list[] = $image->media_medium;
+				if($image->media_thumb != NULL) $image_list[] = $image->media_thumb;
+			}
+
+			// Get all the categroy image files from the database to add to the list
+
+			$category_images = ORM::factory("category")->find_all();
+			foreach($category_images as $image)
+			{
+				if($image->category_image != NULL) $image_list[] = $image->category_image;
+				if($image->category_image_thumb != NULL) $image_list[] = $image->category_image_thumb;
+			}
+
+			// loop through all of the files
+			while (false !== ($fname = readdir($dhandle)))
+			{
+				// if the file is not this file, and does not start with a '.' or '..',
+				// then store it for later display
+				if (($fname != '.') && ($fname != '..') AND
+					($fname != basename($_SERVER['PHP_SELF'])))
+				{
+					// Get all the files in this directory
+					if ( ! is_dir( "./$fname" ))
+					{
+						// Grab the extension of the file
+						$extension = strtolower( substr($fname, strrpos ($fname, '.')) );
+						if ( in_array($extension, $img_extensions) )
+						{
+							if( ! in_array($fname, $image_list))
+							{
+								// This is an orphan... so delete it
+								$orphan = Kohana::config('upload.relative_directory')."/".$fname;
+								//echo '-- '.$orphan.'<br/><br/>';
+								@unlink($orphan);
+							}
+						}
+					}
+				}
+			}
+
+			// close the directory
+			closedir($dhandle);
+		}
+	}
+}
diff --git a/application/controllers/scheduler/s_email.php b/application/controllers/scheduler/s_email.php
new file mode 100644
index 0000000..b37d553
--- /dev/null
+++ b/application/controllers/scheduler/s_email.php
@@ -0,0 +1,212 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * EMAIL Scheduler Controller (IMAP/POP3)
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Scheduler
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+*/
+
+class S_Email_Controller extends Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+	}
+
+	public function index()
+	{
+		if (extension_loaded('imap'))
+		{
+			$email_username = Kohana::config('settings.email_username');
+			$email_password = Kohana::config('settings.email_password');
+			$email_host = Kohana::config('settings.email_host');
+			$email_port = Kohana::config('settings.email_port');
+			$email_servertype = Kohana::config('settings.email_servertype');
+
+			if ( ! empty($email_username)
+			AND ! empty($email_password)
+			AND ! empty($email_host)
+			AND ! empty($email_port)
+			AND ! empty($email_servertype) )
+			{
+				$check_email = new Imap;
+
+				$messages = $check_email->get_messages();
+
+				// Close Connection
+				$check_email->close();
+
+				// Add Messages
+				$this->add_email($messages);
+			}
+			else
+			{
+				echo "Email is not configured.<BR /><BR />";
+			}
+		}
+		else
+		{
+			echo "You Do Not Have the IMAP PHP Library installed. Email will not be retrieved.<BR/ ><BR/ >";
+		}
+	}
+
+
+	/**
+	* Adds email to the database and saves the sender as a new
+	* Reporter if they don't already exist
+	* @param string $messages
+	*/
+	private function add_email($messages)
+	{
+		$service = ORM::factory('service')
+			->where('service_name', 'Email')
+			->find();
+
+		if ( ! $service->loaded)
+		{
+			return;
+		}
+
+		if (empty($messages) OR ! is_array($messages))
+		{
+			return;
+		}
+
+		foreach($messages as $message) {
+			$reporter = ORM::factory('reporter')
+				->where('service_id', $service->id)
+				->where('service_account', $message['email'])
+				->find();
+
+			if (!$reporter->loaded == true)
+			{
+				// Add new reporter
+				$names = explode(' ', $message['from'], 2);
+				$last_name = '';
+				if (count($names) == 2) {
+					$last_name = $names[1];
+				}
+
+				// get default reporter level (Untrusted)
+				$level = ORM::factory('level')
+					->where('level_weight', 0)
+					->find();
+
+				$reporter->service_id		= $service->id;
+				$reporter->level_id			= $level->id;
+				$reporter->service_account	= $message['email'];
+				$reporter->reporter_first	= $names[0];
+				$reporter->reporter_last	= $last_name;
+				$reporter->reporter_email	= $message['email'];
+				$reporter->reporter_phone	= null;
+				$reporter->reporter_ip		= null;
+				$reporter->reporter_date	= date('Y-m-d');
+				$reporter->save();
+			}
+
+			if ($reporter->level_id > 1 &&
+				count(ORM::factory('message')
+					->where('service_messageid', $message['message_id'])
+					->find_all()) == 0 )
+			{
+				// Save Email as Message
+				$email = new Message_Model();
+				$email->parent_id = 0;
+				$email->incident_id = 0;
+				$email->user_id = 0;
+				$email->reporter_id = $reporter->id;
+				$email->message_from = $message['from'];
+				$email->message_to = null;
+				$email->message = $message['subject'];
+				$email->message_detail = $message['body'];
+				$email->message_type = 1; // Inbox
+				$email->message_date = $message['date'];
+				$email->service_messageid = $message['message_id'];
+				$email->save();
+
+				// Attachments?
+				foreach ($message['attachments'] as $attachments)
+				{
+					foreach ($attachments as $attachment)
+					{
+						$media = new Media_Model();
+						$media->location_id = 0;
+						$media->incident_id = 0;
+						$media->message_id = $email->id;
+						$media->media_type = 1; // Images
+						$media->media_link = $attachment[0];
+						$media->media_medium = $attachment[1];
+						$media->media_thumb = $attachment[2];
+						$media->media_date = date("Y-m-d H:i:s",time());
+						$media->save();
+					}
+				}
+
+
+				// Auto-Create A Report if Reporter is Trusted
+				$reporter_weight = $reporter->level->level_weight;
+				$reporter_location = $reporter->location;
+				if ($reporter_weight > 0 AND $reporter_location)
+				{
+					// Create Incident
+					$incident = new Incident_Model();
+					$incident->location_id = $reporter_location->id;
+					$incident->incident_title = $message['subject'];
+					$incident->incident_description = $message['body'];
+					$incident->incident_date = $message['date'];
+					$incident->incident_dateadd = date("Y-m-d H:i:s",time());
+					$incident->incident_active = 1;
+					if ($reporter_weight == 2)
+					{
+						$incident->incident_verified = 1;
+					}
+					$incident->save();
+
+					// Update Message with Incident ID
+					$email->incident_id = $incident->id;
+					$email->save();
+
+					// Save Incident Category
+					$trusted_categories = ORM::factory("category")
+						->where("category_trusted", 1)
+						->find();
+					if ($trusted_categories->loaded)
+					{
+						$incident_category = new Incident_Category_Model();
+						$incident_category->incident_id = $incident->id;
+						$incident_category->category_id = $trusted_categories->id;
+						$incident_category->save();
+					}
+
+					// Add Attachments
+					$attachments = ORM::factory("media")
+						->where("message_id", $email->id)
+						->find_all();
+					foreach ($attachments AS $attachment)
+					{
+						$attachment->incident_id = $incident->id;
+						$attachment->save();
+					}
+				}
+
+
+				// Notify Admin Of New Email Message
+				$send = notifications::notify_admins(
+					"[".Kohana::config('settings.site_name')."] ".
+						Kohana::lang('notifications.admin_new_email.subject'),
+					Kohana::lang('notifications.admin_new_email.message')
+					);
+
+				// Action::message_email_add - Email Received!
+				Event::run('ushahidi_action.message_email_add', $email);
+			}
+		}
+	}
+}
diff --git a/application/controllers/scheduler/s_feeds.php b/application/controllers/scheduler/s_feeds.php
new file mode 100644
index 0000000..d749b0c
--- /dev/null
+++ b/application/controllers/scheduler/s_feeds.php
@@ -0,0 +1,147 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Feeds Scheduler Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Scheduler
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+*/
+
+class S_Feeds_Controller extends Controller {
+	
+	public function __construct()
+    {
+        parent::__construct();
+	}
+	
+	/**
+	 * parse feed and send feed items to database
+	 */
+	public function index()
+	{
+		// Max number of feeds to keep
+		$max_feeds = 100;
+		
+		// Today's Date
+		$today = strtotime('now');
+		
+		// Get All Feeds From DB
+		$feeds = ORM::factory('feed')->find_all();
+		foreach ($feeds as $feed)
+		{
+			$last_update = $feed->feed_update;
+			
+			// Parse Feed URL using Feed Helper
+			$feed_data = feed::simplepie( $feed->feed_url );
+
+			foreach($feed_data->get_items(0,50) as $feed_data_item)
+			{
+				$title = $feed_data_item->get_title();
+				$link = $feed_data_item->get_link();
+				$description = $feed_data_item->get_description();
+				$date = $feed_data_item->get_date();
+				$latitude = $feed_data_item->get_latitude();
+				$longitude = $feed_data_item->get_longitude();
+				$categories = $feed_data_item->get_categories(); // HT: new code
+				$category_ids = new stdClass(); // HT: new code
+				
+				// Make Sure Title is Set (Atleast)
+				if (isset($title) && !empty($title ))
+				{	
+					// We need to check for duplicates!!!
+					// Maybe combination of Title + Date? (Kinda Heavy on the Server :-( )
+					$dupe_count = ORM::factory('feed_item')->where('item_title',$title)->where('item_date',date("Y-m-d H:i:s",strtotime($date)))->count_all();
+
+					if ($dupe_count == 0)
+					{
+						// Does this feed have a location??
+						$location_id = 0;
+						// STEP 1: SAVE LOCATION
+						if ($latitude AND $longitude)
+						{
+							$location = new Location_Model();
+							$location->location_name = "Unknown";
+							$location->latitude = $latitude;
+							$location->longitude = $longitude;
+							$location->location_date = date("Y-m-d H:i:s",time());
+							$location->save();
+							$location_id = $location->id;
+						}
+						
+						$newitem = new Feed_Item_Model();
+						$newitem->feed_id = $feed->id;
+						$newitem->location_id = $location_id;
+						$newitem->item_title = $title;
+						if (isset($description) AND !empty($description))
+						{
+							$newitem->item_description = $description;
+						}
+						if (isset($link) AND !empty($link))
+						{
+							$newitem->item_link = $link;
+						}
+						if (isset($date) AND !empty($date))
+						{
+							$newitem->item_date = date("Y-m-d H:i:s",strtotime($date));
+						}
+						// Set todays date
+						else
+						{
+							$newitem->item_date = date("Y-m-d H:i:s",time());
+						}
+						// HT: new code
+							if(!empty($categories)) {
+								foreach($categories as $category) {
+									$categoryData = ORM::factory('category')->where('category_title', $category->term)->find();
+									if($categoryData->loaded == TRUE) {
+										$category_ids->feed_item_category[$categoryData->id] = $categoryData->id;
+									}
+									elseif (Kohana::config('settings.allow_feed_category'))
+									{
+										$newcategory = new Category_Model();
+										$newcategory->category_title = $category->term;
+										$newcategory->parent_id = 0;
+										$newcategory->category_description = $category->term;
+										$newcategory->category_color = '000000';
+										$newcategory->category_visible = 0;
+										$newcategory->save();
+										$category_ids->feed_item_category[$newcategory->id] = $newcategory->id;
+									}
+								}	
+							}
+						// HT: End of new code
+						
+						$newitem->save();
+
+						// HT: New code
+						if(!empty($category_ids->feed_item_category)) {
+							feed::save_category($category_ids, $newitem);
+						}
+						// HT: End of New code
+						
+						// Action::feed_item_add - Feed Item Received!
+						Event::run('ushahidi_action.feed_item_add', $newitem);
+					}
+				}
+			}
+			
+			// Get Feed Item Count
+			$feed_count = ORM::factory('feed_item')->where('feed_id', $feed->id)->count_all();
+			if ($feed_count > $max_feeds) 
+			{
+				// Excess Feeds
+				$feed_excess = $feed_count - $max_feeds;
+			}
+
+			// Set feed update date
+			$feed->feed_update = strtotime('now');
+			$feed->save();
+		}
+	}
+}
diff --git a/application/controllers/scheduler/s_twitter.php b/application/controllers/scheduler/s_twitter.php
new file mode 100644
index 0000000..3dd62b0
--- /dev/null
+++ b/application/controllers/scheduler/s_twitter.php
@@ -0,0 +1,229 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Twitter Scheduler Controller
+ *
+ * This utilizes twitterouath by abrahama -> https://github.com/abraham/twitteroauth
+
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Scheduler
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+*/
+
+class S_Twitter_Controller extends Controller {
+
+	// Cache instance
+	protected $cache;
+
+	public function __construct()
+	{
+		parent::__construct();
+
+		// Load cache
+		$this->cache = new Cache;
+
+		//Load session
+		$this->session = new Session;
+	}
+
+	public function index()
+	{
+
+		// Grab all the twitter credentials - tokens and keys
+		$consumer_key = Settings_Model::get_setting('twitter_api_key');
+		$consumer_secret = Settings_Model::get_setting('twitter_api_key_secret');
+		$oauth_token = Settings_Model::get_setting('twitter_token');
+		$oauth_token_secret = Settings_Model::get_setting('twitter_token_secret');
+
+		$_SESSION['access_token'] = array(
+		  'oauth_token'=> $oauth_token,
+			'oauth_token_secret' => $oauth_token_secret
+			);
+
+		/* Get user access tokens out of the session. */
+		$access_token = $_SESSION['access_token'];
+
+		/* Create a TwitterOauth object with consumer/user tokens. */
+		$connection = new Twitter_Oauth($consumer_key, $consumer_secret, $access_token['oauth_token'], $access_token['oauth_token_secret']);
+		$connection->decode_json = FALSE;
+
+		// Retrieve Last Stored Twitter ID
+		$last_tweet_id = "";
+		$tweets = ORM::factory('message')
+			->with('reporter')
+			->where('service_id', '3')
+			->orderby('service_messageid','desc')
+			->find();
+		if ($tweets->loaded == true)
+		{
+			$last_tweet_id = "&since_id=" . $tweets->service_messageid;
+		}
+		
+		// Perform Hashtag Search
+		$twitter_hashtags = Settings_Model::get_setting('twitter_hashtags');
+		$hashtags = explode(',',$twitter_hashtags);
+		foreach($hashtags as $hashtag){
+			if (!empty($hashtag))
+			{
+				$page = 1;
+				$have_results = TRUE; //just starting us off as true, although there may be no results
+				while($have_results == TRUE AND $page <= 2)
+				{ //This loop is for pagination of twitter results
+					$hashtag = rawurlencode(trim($hashtag));
+					$twitter_url = $connection->get('search/tweets',array('count' => 100, 'q' => $hashtag));
+					$have_results = $this->add_hash_tweets($twitter_url);
+					$page++;
+				}
+			}
+		}
+	}
+
+	/**
+	* Adds hash tweets in JSON format to the database and saves the sender as a new
+	* Reporter if they don't already exist
+	* @param string $data - Twitter JSON results
+	*/
+	private function add_hash_tweets($data)
+	{
+
+		$services = new Service_Model();
+		$service = $services->where('service_name', 'Twitter')->find();
+
+		$tweet_results = json_decode($data);
+		foreach($tweet_results->statuses as $tweet)
+		{
+			$reporter = ORM::factory('reporter')
+				->where('service_id', $service->id)
+				->where('service_account', $tweet->user->screen_name)
+				->find();
+
+			if (!$reporter->loaded)
+			{
+				// get default reporter level (Untrusted)
+				$level = ORM::factory('level')
+					->where('level_weight', 0)
+					->find();
+
+				$reporter->service_id	   = $service->id;
+				$reporter->level_id			= $level->id;
+				$reporter->service_account	= $tweet->user->screen_name;
+				$reporter->reporter_first	= null;
+				$reporter->reporter_last	= null;
+				$reporter->reporter_email	= null;
+				$reporter->reporter_phone	= null;
+				$reporter->reporter_ip	  = null;
+				$reporter->reporter_date	= date('Y-m-d');
+				$reporter->save();
+			}
+
+			if ($reporter->level_id > 1 &&
+				count(ORM::factory("message")
+					->where("service_messageid = '".$tweet->{'id_str'}."'")
+					->find_all()) == 0)
+			{
+
+				// Grab geo data if it exists from the tweet
+				$tweet_lat = null;
+				$tweet_lon = null;
+				if ($tweet->{'coordinates'} != null)
+				{
+					$tweet_lat = $tweet->{'coordinates'}->coordinates[0];
+					$tweet_lon = $tweet->{'coordinates'}->coordinates[1];
+				}
+
+				// Save Tweet as Message
+				$message = new Message_Model();
+				$message->parent_id = 0;
+				$message->incident_id = 0;
+				$message->user_id = 0;
+				$message->reporter_id = $reporter->id;
+				$message->message_from = $tweet->user->screen_name;
+				$message->message_to = null;
+				$message->message = $tweet->{'text'};
+				$message->message_type = 1; // Inbox
+				$tweet_date = date("Y-m-d H:i:s",strtotime($tweet->{'created_at'}));
+				$message->message_date = $tweet_date;
+				$message->service_messageid = $tweet->{'id_str'};
+				$message->latitude = $tweet_lat;
+				$message->longitude = $tweet_lon;
+				$message->save();
+
+				// Action::message_twitter_add - Twitter Message Received!
+				Event::run('ushahidi_action.message_twitter_add', $message);
+
+				// Auto-Create A Report if Reporter is Trusted
+				$reporter_weight = $reporter->level->level_weight;
+				$reporter_location = $reporter->location;
+				if ($reporter_weight > 0 AND $reporter_location)
+				{
+					$incident_title = text::limit_chars($message->message, 50, "...", false);
+
+					// Create Incident
+					$incident = new Incident_Model();
+					$incident->location_id = $reporter_location->id;
+					$incident->incident_title = $incident_title;
+					$incident->incident_description = $message->message;
+					$incident->incident_date = $tweet_date;
+					$incident->incident_dateadd = date("Y-m-d H:i:s",time());
+					$incident->incident_active = 1;
+					$incident->incident_mode = 4;
+					if ($reporter_weight == 2)
+					{
+						$incident->incident_verified = 1;
+					}
+					$incident->save();
+
+					// Update Message with Incident ID
+					$message->incident_id = $incident->id;
+					$message->save();
+
+					// Save Incident Category
+					$trusted_categories = ORM::factory("category")
+						->where("category_trusted", 1)
+						->find();
+					if ($trusted_categories->loaded)
+					{
+						$incident_category = new Incident_Category_Model();
+						$incident_category->incident_id = $incident->id;
+						$incident_category->category_id = $trusted_categories->id;
+						$incident_category->save();
+					}
+				}
+			}
+		}
+
+		$this->_unlock();
+		return true;
+	}
+
+	private function _lock()
+	{
+		// *************************************
+		// Create A 5 Minute RETRIEVE LOCK
+		// This lock is released at the end of execution
+		// Or expires automatically
+		$twitter_lock = $this->cache->get(Kohana::config('settings.subdomain')."_twitter_lock");
+		if ( ! $twitter_lock)
+		{
+			// Lock doesn't exist
+			$timestamp = time();
+			$this->cache->set(Kohana::config('settings.subdomain')."_twitter_lock", $timestamp, array("twitter"), 300);
+			return false;
+		}
+		else
+		{
+			// Lock Exists - End
+			return true;
+		}
+	}
+
+	private function _unlock()
+	{
+		$this->cache->delete(Kohana::config('settings.subdomain')."_twitter_lock");
+	}
+}
diff --git a/application/controllers/search.php b/application/controllers/search.php
new file mode 100644
index 0000000..5b4a0f9
--- /dev/null
+++ b/application/controllers/search.php
@@ -0,0 +1,220 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Search controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Search_Controller extends Main_Controller {
+
+	public function __construct()
+	{
+		parent::__construct();
+	}
+	
+	/**
+	 * Build a search query with relevancy
+	 * Stop word control included
+	 */
+	public function index($page = 1)
+	{
+		$this->template->content = new View('search');
+
+		$search_query = "";
+		$keyword_string = "";
+		$where_string = "";
+		$plus = "";
+		$or = "";
+		$search_info = "";
+		$html = "";
+		$pagination = "";
+
+		// Stop words that we won't search for
+		// Add words as needed!!
+		$stop_words = array('the', 'and', 'a', 'to', 'of', 'in', 'i', 'is', 'that', 'it',
+			'on', 'you', 'this', 'for', 'but', 'with', 'are', 'have', 'be',
+			'at', 'or', 'as', 'was', 'so', 'if', 'out', 'not'
+		);
+
+		if ($_GET)
+		{
+			/**
+			 * NOTES: 15/10/2010 - Emmanuel Kala <emmanuel at ushahidi.com>
+			 *
+			 * The search string undergoes a 3-phase sanitization process. This is not optimal
+			 * but it works for now. The Kohana provided XSS cleaning mechanism does not expel
+			 * content contained in between HTML tags this the "bruteforce" input sanitization.
+			 *
+			 * However, XSS is attempted using Javascript tags, Kohana's routing mechanism strips
+			 * the "<script>" tags from the URL variables and passes inline text as part of the URL
+			 * variable - This has to be fixed
+			 */
+
+			// Phase 1 - Fetch the search string and perform initial sanitization
+			$keyword_raw = (isset($_GET['k']))? preg_replace('#/\w+/#', '', $_GET['k']) : "";
+
+			// Phase 2 - Strip the search string of any HTML and PHP tags that may be present for additional safety
+			$keyword_raw = strip_tags($keyword_raw);
+
+			// Phase 3 - Apply Kohana's XSS cleaning mechanism
+			$keyword_raw = $this->input->xss_clean($keyword_raw);
+		}
+		else
+		{
+			$keyword_raw = "";
+		}
+
+		// Database instance
+		$db = new Database();
+
+		$keywords = explode(' ', $keyword_raw);
+		if (is_array($keywords) AND ! empty($keywords)) 
+		{
+			array_change_key_case($keywords, CASE_LOWER);
+			$i = 0;
+
+			foreach($keywords as $value)
+			{
+				if ( ! in_array($value,$stop_words) AND ! empty($value))
+				{
+					// Escape the string for query safety
+					$chunk = $db->escape_str($value);
+
+					if ($i > 0)
+					{
+						$plus = ' + ';
+						$or = ' OR ';
+					}
+
+					// Give relevancy weighting
+					// Title weight = 2
+					// Description weight = 1
+					$keyword_string = $keyword_string.$plus."(CASE WHEN incident_title LIKE '%$chunk%' THEN 2 ELSE 0 END) + "
+										. "(CASE WHEN incident_description LIKE '%$chunk%' THEN 1 ELSE 0 END) ";
+
+					$where_string = $where_string.$or."(incident_title LIKE '%$chunk%' OR incident_description LIKE '%$chunk%')";
+					$i++;
+				}
+			}
+
+			if ( ! empty($keyword_string) AND !empty($where_string))
+			{
+				// Limit the result set to only those reports that have been approved
+				$where_string = '(' . $where_string . ') AND incident_active = 1';
+				$search_query = "SELECT *, (".$keyword_string.") AS relevance FROM "
+								. $this->table_prefix."incident "
+								. "WHERE ".$where_string." "
+								. "ORDER BY relevance DESC LIMIT ?, ?";
+			}
+		}
+		
+		if ( ! empty($search_query))
+		{
+			// Pagination
+			$pagination = new Pagination(array(
+				'query_string' => 'page',
+				'items_per_page' => (int) Kohana::config('settings.items_per_page'),
+				'total_items' => ORM::factory('incident')->where($where_string)->count_all()
+			));
+
+			$query = $db->query($search_query, $pagination->sql_offset, (int)Kohana::config('settings.items_per_page'));
+
+			// Results Bar
+			if ($pagination->total_items != 0)
+			{
+				$search_info .= "<div class=\"search_info\">"
+							. Kohana::lang('ui_admin.showing_results')
+							. ' '. ( $pagination->sql_offset + 1 )
+							. ' '.Kohana::lang('ui_admin.to')
+							. ' '.( (int) Kohana::config('settings.items_per_page') + $pagination->sql_offset )
+							. ' '.Kohana::lang('ui_admin.of').' '. $pagination->total_items
+							. ' '.Kohana::lang('ui_admin.searching_for').' <strong>'. $keyword_raw . "</strong>"
+							. "</div>";
+			}
+			else
+			{
+				$search_info .= "<div class=\"search_info\">0 ".Kohana::lang('ui_admin.results')."</div>";
+
+				$html .= "<div class=\"search_result\">";
+				$html .= "<h3>".Kohana::lang('ui_admin.your_search_for')."<strong> ".$keyword_raw."</strong> ".Kohana::lang('ui_admin.match_no_documents')."</h3>";
+				$html .= "</div>";
+
+				$pagination = "";
+			}
+
+			foreach ($query as $search)
+			{
+				$incident_id = $search->id;
+				$incident_title = strip_tags($search->incident_title);
+				$highlight_title = "";
+				$incident_title_arr = explode(' ', $incident_title);
+
+				foreach($incident_title_arr as $value)
+				{
+					if (in_array(strtolower($value),$keywords) AND !in_array(strtolower($value),$stop_words))
+					{
+						$highlight_title .= "<span class=\"search_highlight\">" . $value . "</span> ";
+					}
+					else
+					{
+						$highlight_title .= $value . " ";
+					}
+				}
+
+				// Remove any markup, otherwise trimming below will mess things up
+				$incident_description = strip_tags($search->incident_description);
+
+				// Trim to 180 characters without cutting words
+				if ((strlen($incident_description) > 180) AND (strlen($incident_description) > 1))
+				{
+					$whitespaceposition = strpos($incident_description," ",175)-1;
+					$incident_description = substr($incident_description, 0, $whitespaceposition);
+				}
+
+				$highlight_description = "";
+				$incident_description_arr = explode(' ', $incident_description);
+
+				foreach($incident_description_arr as $value)
+				{
+					if (in_array(strtolower($value),$keywords) && !in_array(strtolower($value),$stop_words))
+					{
+						$highlight_description .= "<span class=\"search_highlight\">" . $value . "</span> ";
+					}
+					else
+					{
+						$highlight_description .= $value . " ";
+					}
+				}
+
+				$incident_date = date('D M j Y g:i:s a', strtotime($search->incident_date));
+
+				$html .= "<div class=\"search_result\">";
+				$html .= "<h3><a href=\"" . url::site( "reports/view/" . $incident_id) . "\">" . $highlight_title . "</a></h3>";
+				$html .= $highlight_description . " ...";
+				$html .= "<div class=\"search_date\">" . $incident_date . " | ".Kohana::lang('ui_admin.relevance').": <strong>+" . $search->relevance . "</strong></div>";
+				$html .= "</div>";
+			}
+		}
+		else
+		{
+			// Results Bar
+			$search_info .= "<div class=\"search_info\">0 ".Kohana::lang('ui_admin.results')."</div>";
+
+			$html .= "<div class=\"search_result\">";
+			$html .= "<h3>".Kohana::lang('ui_admin.your_search_for')."<strong>".$keyword_raw."</strong> ".Kohana::lang('ui_admin.match_no_documents')."</h3>";
+			$html .= "</div>";
+		}
+
+		$html .= $pagination;
+
+		$this->template->content->search_info = $search_info;
+		$this->template->content->search_results = $html;
+    }
+}
diff --git a/application/controllers/swatch.php b/application/controllers/swatch.php
new file mode 100644
index 0000000..ce7d199
--- /dev/null
+++ b/application/controllers/swatch.php
@@ -0,0 +1,111 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Swatch Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+*/
+
+class Swatch_Controller extends Controller
+{
+    function index()
+    {
+        // Image Color
+        if (!isset($_GET['c'])) {
+            $main_color    = "990000";
+        }
+        else
+        {
+            $main_color     = $_GET['c'];
+        }
+    
+        
+        // Image Width
+        if (!isset($_GET['w']) || !is_numeric($_GET['w'])) {
+            $width = "16";
+        }
+        else
+        {
+            $width    = $_GET['w'];
+        }
+    
+        // Image Height
+        if (!isset($_GET['h']) || !is_numeric($_GET['h'])) {
+            $height = "16";
+        }
+        else
+        {
+            $height = $_GET['h'];
+        }
+    
+        // Image Border Color
+        if (!isset($_GET['b'])) {
+            $brdr_color = "990000";
+        }
+        else
+        {
+            $brdr_color = $_GET['b'];
+        }
+        
+        // Image Type (Circle or Rectangle?)
+        if (!isset($_GET['t']) || ($_GET['t'] != "rec" && $_GET['t'] != "cir") ) {
+            $image_type = "rec";
+        }
+        else
+        {
+            $image_type    = $_GET['t'];
+        }
+
+        $mc_red    =    hexdec(substr($main_color, 0, 2));
+        $mc_green  =    hexdec(substr($main_color, 2, 2));
+        $mc_blue   =    hexdec(substr($main_color, 4, 2));
+
+        $bc_red    =    hexdec(substr($brdr_color, 0, 2));
+        $bc_green  =    hexdec(substr($brdr_color, 2, 2));
+        $bc_blue   =    hexdec(substr($brdr_color, 4, 2));
+        
+        
+        if ($image_type == 'rec')
+        {
+            $image         = imagecreate( $width, $height );
+            $main_color    = imagecolorallocate( $image, $mc_red, $mc_green, $mc_blue );
+            $brdr_color    = imagecolorallocate( $image, $bc_red, $bc_green, $bc_blue );
+            
+            imagefill( $image, 0, 0, $brdr_color);
+            imagefilledrectangle( $image, 1, 1, ($width-2), ($height-2), $main_color);
+        }
+        else
+        { // Use imagecolorallocatealpha to set transparency level
+            
+            $a = $width;
+            $b = $a*4;
+            $c = $b/2;
+            $d = $b;
+            $e = $d-(2*8);
+            
+            $image         = imagecreate( $a, $a );
+            $image2        = imagecreate( ($b), ($b) );
+            $main_color    = imagecolorallocatealpha( $image2, $mc_red, $mc_green, $mc_blue, 20 );
+            $brdr_color    = imagecolorallocatealpha( $image2, $bc_red, $bc_green, $bc_blue, 0 );
+            $bkg_color     = imagecolorallocatealpha($image2, 0, 0, 0, 127);
+            
+            imagefill($image2, 0, 0, $bkg_color);
+            imagefilledellipse( $image2, $c, $c, $d, $d, $brdr_color);
+            imagefilledellipse( $image2, $c, $c, $e, $e, $main_color);
+            
+            imagecopyresampled($image,$image2,0,0,0,0,$a,$a,$b,$b);
+        }
+        
+        
+        header("Content-type: image/png");
+        imagepng($image);
+        imagedestroy($image);
+    }
+}
diff --git a/application/helpers/MY_file.php b/application/helpers/MY_file.php
new file mode 100644
index 0000000..cc0603c
--- /dev/null
+++ b/application/helpers/MY_file.php
@@ -0,0 +1,42 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * File helper class.
+ * Extends built-in helper class
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     File Helper
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class file extends file_Core {
+
+	/**
+	 * Case Insensitive file_exists
+	 *
+	 * @param   string - Path to file
+	 * @param   string - real path to file
+	 * @return  bool
+	 */
+	public static function file_exists_i($file, &$real_path = null)
+	{
+		$path = pathinfo($file);
+		$dir = $path['dirname'] != '' ? $path['dirname'] : '.';
+		$path_array = glob($path['dirname'] . '/*');
+		$path_array = (is_array($path_array)) ? $path_array : array();
+		
+		$key = array_search(strtolower($file), array_map('strtolower', $path_array));
+		if ($key !== FALSE)
+		{
+			$real_path = $path_array[$key];
+			return TRUE;
+		}
+		
+		return FALSE;
+	}
+
+}
diff --git a/application/helpers/MY_html.php b/application/helpers/MY_html.php
new file mode 100644
index 0000000..4b22400
--- /dev/null
+++ b/application/helpers/MY_html.php
@@ -0,0 +1,111 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * HTML helper class.
+ *
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     File Helper
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class html extends html_Core {
+	
+	/**
+	 * Helper function for easy use of HTMLPurifier
+	 * 
+	 * @param string $input
+	 * @return string
+	 */
+	public function clean($input)
+	{
+		require_once APPPATH.'libraries/htmlpurifier/HTMLPurifier.auto.php';
+
+		$config = HTMLPurifier_Config::createDefault();
+		// Defaults to UTF-8
+		// $config->set('Core.Encoding', 'UTF-8');
+		// $config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
+		$config->set('Core.EnableIDNA', TRUE);
+		$config->set('Cache.SerializerPath', APPPATH.'cache');
+		$config->set('HTML.Allowed', Kohana::config('config.allowed_html', FALSE, TRUE));
+		// Allow some basic iframes
+		$config->set('HTML.SafeIframe', true);
+		$config->set('URI.SafeIframeRegexp', 
+			Kohana::config('config.safe_iframe_regexp', FALSE, TRUE)
+		);
+		$config->set('Filter.YouTube', true);
+		$purifier = new HTMLPurifier($config);
+		$clean_html = $purifier->purify($input);
+
+		return $clean_html;
+	}
+	
+	/**
+	 * Helper function to clean and escape plaintext before display
+	 * 
+	 * This should be used to strip tags and then escape html entities, etc.
+	 * 
+	 * @param string $input
+	 * @param bool $encode Encode html entities?
+	 * @return string
+	 */
+	public function strip_tags($input, $encode = TRUE)
+	{
+		require_once APPPATH.'libraries/htmlpurifier/HTMLPurifier.auto.php';
+
+		$config = HTMLPurifier_Config::createDefault();
+		// Defaults to UTF-8
+		// $config->set('Core.Encoding', 'UTF-8');
+		// $config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
+		$config->set('Core.EnableIDNA', TRUE);
+		$config->set('Cache.SerializerPath', APPPATH.'cache');
+		$config->set('HTML.Allowed', "");
+		
+		$purifier = new HTMLPurifier($config);
+		$clean_html = $purifier->purify($input);
+
+		return $encode ? self::escape($clean_html) : $clean_html;
+	}
+	
+	/**
+	 * Get info message about allowed html tags
+	 * 
+	 * @return string
+	 **/
+	public function allowed_html()
+	{
+		require_once APPPATH.'libraries/htmlpurifier/HTMLPurifier.auto.php';
+		
+		$def = new HTMLPurifier_HTMLDefinition();
+		list($el, $attr) = $def->parseTinyMCEAllowedList(Kohana::config('config.allowed_html', FALSE, TRUE));
+		$iframes = explode('|', str_replace(array('%^http://(',')%'), '', Kohana::config('config.safe_iframe_regexp', FALSE, TRUE)));
+		
+		$output = "";
+		$output .= Kohana::lang('ui_main.allowed_tags', implode(', ', array_keys($el)));
+		$output .= "<br/> ";
+		$output .= Kohana::lang('ui_main.allowed_iframes', implode(', ', $iframes));
+		return $output;
+	}
+	
+	/**
+	 * Helper function to escape plaintext before display
+	 * 
+	 * This should be used to escape html entities, etc.
+	 * 
+	 * @param string $input
+	 * @param bool $double_encode
+	 * @return string
+	 */
+	public function escape($input, $double_encode = FALSE)
+	{
+		// Ensure we have valid correctly encoded string..
+		// http://stackoverflow.com/questions/1412239/why-call-mb-convert-encoding-to-sanitize-text
+		$input = mb_convert_encoding($input, "UTF-8", "UTF-8");
+		// why are we using html entities? this -> http://stackoverflow.com/a/110576/992171
+		return htmlentities($input, ENT_QUOTES, 'UTF-8', $double_encode);
+	}
+	
+}
+	
\ No newline at end of file
diff --git a/application/helpers/MY_remote.php b/application/helpers/MY_remote.php
new file mode 100644
index 0000000..998fab3
--- /dev/null
+++ b/application/helpers/MY_remote.php
@@ -0,0 +1,236 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Remote url helper, extends generic remote_Core
+ *
+ * @package    Core
+ * @author     zextra <zextra at gmail.com>
+ * @license    http://kohanaphp.com/license.html
+ */
+class remote extends remote_Core {
+
+	/**
+	 * Shorthand method to GET data from remote url
+	 *
+	 * @param string $url
+	 * @param array $headers
+	 * @return HTTP_Response
+	 */
+	public static function get($url, $headers = array())
+	{
+		return self::request('GET', $url, $headers);
+	}
+
+	/**
+	 * Shorthand method to POST data to remote url
+	 *
+	 * @param string $url
+	 * @param array $data
+	 * @param array $headers
+	 * @return HTTP_Response
+	 */
+	public static function post($url, $data = array(), $headers = array())
+	{
+		return self::request('POST', $url, $headers, $data);
+	}
+
+	/**
+	 * Method that allows sending any kind of HTTP request to remote url
+	 *
+	 * @param string $method
+	 * @param string $url
+	 * @param array $headers
+	 * @param array $data
+	 * @return HTTP_Response
+	 */
+	public static function request($method, $url, $headers = array(), $data = array())
+	{
+		$valid_methods = array('POST', 'GET', 'PUT', 'DELETE');
+
+		$method = utf8::strtoupper($method);
+
+		if (!valid::url($url, 'http'))
+			return FALSE;
+
+		if (!in_array($method, $valid_methods))
+			return FALSE;
+
+		// Get the hostname and path
+		$url = parse_url($url);
+
+		if (empty($url['path']))
+		{
+			// Request the root document
+			$url['path'] = '/';
+		}
+
+		// Open a remote connection
+		$remote = fsockopen($url['host'], 80, $errno, $errstr, 5);
+
+		if (!is_resource($remote))
+			return FALSE;
+
+		// Set CRLF
+		$CRLF = "\r\n";
+
+		$path = $url['path'];
+
+		if ($method == 'GET' AND !empty($url['query']))
+			$path .= '?' . $url['query'];
+
+		$headers_default = array('Host' => $url['host'], 'Connection' => 'close', 'User-Agent' => 'Ushahidi Scheduler (+http://ushahidi.com/)', );
+
+		$body_content = '';
+
+		if ($method != 'GET')
+		{
+			$headers_default['Content-Type'] = 'application/x-www-form-urlencoded';
+			if (count($data) > 0)
+			{
+				$body_content = http_build_query($data);
+			}
+			$headers_default['Content-Length'] = strlen($body_content);
+		}
+
+		$headers = array_merge($headers_default, $headers);
+
+		// Send request
+		$request = $method . ' ' . $path . ' HTTP/1.0' . $CRLF;
+
+		foreach ($headers as $key => $value)
+		{
+			$request .= $key . ': ' . $value . $CRLF;
+		}
+
+		// Send one more CRLF to terminate the headers
+		$request .= $CRLF;
+
+		if ($body_content)
+		{
+			$request .= $body_content . $CRLF;
+		}
+
+		fwrite($remote, $request);
+
+		$response = '';
+
+		while (!feof($remote))
+		{
+			// Get 1K from buffer
+			$response .= fread($remote, 1024);
+		}
+
+		// Close the connection
+		fclose($remote);
+
+		return new HTTP_Response($response, $method);
+	}
+
+}
+
+/**
+ * Very simple class that handles raw response received from remote host
+ *
+ * @package Core
+ */
+class HTTP_Response {
+	/**
+	 * HTTP method
+	 *
+	 * @var string
+	 */
+	protected $method;
+
+	/**
+	 * HTTP status code
+	 *
+	 * @var integer
+	 */
+	protected $status;
+
+	/**
+	 * Complete request, as received from remote host
+	 *
+	 * @var string
+	 */
+	protected $response;
+
+	/**
+	 * Headers received from remote host
+	 *
+	 * @var string
+	 */
+	protected $headers;
+
+	/**
+	 * Body received from remote host
+	 *
+	 * @var string
+	 */
+	protected $body;
+
+	public function __construct($full_response, $method)
+	{
+		$this->method = $method;
+		$this->response = $full_response;
+		$this->parse();
+	}
+
+	/**
+	 * Splits $this->response into header and body, also extract status code
+	 *
+	 * @return void
+	 */
+	protected function parse()
+	{
+		// split response by newlines and detect first empty line (between header and body)
+		$lines = explode("\n", $this->response);
+
+		$headers = array();
+
+		foreach ($lines as $line)
+		{
+			// each time we take one line, we will remove that line, until we find empty one
+			$headers[] = array_shift($lines);
+
+			if ($line !== '' AND preg_match('#^HTTP/1\.[01] (\d{3})#', $line, $matches))
+			{
+				// Response code found
+				$this->status = (int)$matches[1];
+			}
+
+			if ($line === "\r" or $line === "")
+			{
+				break;
+			}
+		}
+
+		$this->headers = trim(implode("\n", $headers));
+		$this->body = implode("\n", $lines);
+	}
+
+	/**
+	 * Returns only body() if object is stringified
+	 *
+	 * @return unknown
+	 */
+	public function __toString()
+	{
+		return (string)$this->body();
+	}
+
+	/**
+	 * Basic getter for class members
+	 *
+	 * @param string $method
+	 * @param array $args
+	 * @return mixed
+	 */
+	public function __call($method, array $args)
+	{
+		if (isset($this->$method) AND count($args) == 0)
+		{
+			return $this->$method;
+		}
+	}
+
+}
diff --git a/application/helpers/MY_url.php b/application/helpers/MY_url.php
new file mode 100644
index 0000000..d60f8a7
--- /dev/null
+++ b/application/helpers/MY_url.php
@@ -0,0 +1,59 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URL helper class
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class url extends url_Core {
+
+	/**
+	 * Returns the base URL of the CDN or the standard base URL if no CDN is configured.
+	 *
+	 * @param   boolean  type of cdn url. Acceptable values are img, css, js
+	 * @return  string
+	 */
+	public static function file_loc($type)
+	{
+		// Pull CDN URL from the cdn settings if it's set. Otherwise use the base URL by default
+		switch($type){
+			case 'img':
+				$url = (Kohana::config("cdn.cdn_img")) ? Kohana::config("cdn.cdn_img") : url::base();
+				break;
+			case 'css':
+				$url = (Kohana::config("cdn.cdn_css")) ? Kohana::config("cdn.cdn_css") : url::base();
+				break;
+			case 'js':
+				$url = (Kohana::config("cdn.cdn_js")) ? Kohana::config("cdn.cdn_js") : url::base();
+				break;
+			default:
+				$url = url::base();
+		}
+
+		// Force a slash on the end of the URL
+		return rtrim($url, '/').'/';
+	}
+
+	/**
+	 * Converts a file location to an absolute URL or returns the absolute URL if absolute URL
+	 * is passed. This function is for uploaded files since it uses the configured upload dir
+	 *
+	 * @param   string  file location or full URL
+	 * @return  string
+	 */
+	public static function convert_uploaded_to_abs($file)
+	{
+		if(valid::url($file) == true){
+			return $file;
+		}
+
+		return url::base().Kohana::config('upload.relative_directory').'/'.$file;
+	}
+
+} // End url
\ No newline at end of file
diff --git a/application/helpers/addon.php b/application/helpers/addon.php
new file mode 100644
index 0000000..5fbf3c4
--- /dev/null
+++ b/application/helpers/addon.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Addons helper
+ * 
+ * @package    Ushahidi
+ * @subpackage Helpers
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class addon_Core {
+	
+	/**
+	 * Find addons in theme/plugin dir
+	 * Optionally return the meta data too
+	 * 
+	 * @param   string addon type
+	 * @param   boolean include meta data
+	 * @return  array
+	 */
+	public static function get_addons($type, $include_meta = TRUE)
+	{
+		$addons = array();
+
+		$base_path = ($type == 'plugin') ? PLUGINPATH : THEMEPATH;
+
+		//var_dump(glob($base_path.'*', GLOB_ONLYDIR));
+		// Set default values for addon_type
+		if ($type == 'plugin')
+		{
+			$defaults = array(
+				"name" => "",
+				"description" => "",
+				"website" => "",
+				"author" => "",
+				"version" => "",
+			);
+		}
+		else
+		{
+			$defaults = array(
+				'Theme Name' => "",
+				'Description' => "",
+				'Demo' => "",
+				'Version' => "",
+				'Author' => "",
+				'Author Email' => "",
+				'Template_Dir' => "",
+				'Base Theme' => 'default',
+				'CSS' => '',
+				'JS' => '',
+			);
+		}
+
+		// Files ushahidi/themes directory and one subdir down
+		$pattern = $base_path . '*/readme.txt';
+		foreach (glob($pattern) as $file)
+		{
+			$addon = str_replace(array('/readme.txt',$base_path),'',$file);
+			
+			$addons[$addon] = array();
+			if ($include_meta)
+			{
+				$addons[$addon] = self::meta_data($addon, $type, $defaults);
+			}
+		}
+
+		ksort($addons);
+
+		return $addons;
+	}
+	
+
+	/**
+	 * Load addon Information from readme.txt file
+	 *
+	 * @param   string addon name
+	 * @param   string addon type
+	 * @param   array  default meta data
+	 * @return  array
+	 */
+	public static function meta_data($addon, $type, $defaults = array())
+	{
+		$base = ($type == 'plugin') ? PLUGINPATH : THEMEPATH;
+		// Determine if readme.txt (Case Insensitive) exists
+		$file = $base.$addon."/readme.txt";
+		if ( file::file_exists_i($file, $file) )
+		{
+			$fp = fopen( $file, 'r' );
+			
+			// Pull only the first 8kiB of the file in.
+			$file_data = fread( $fp, 8192 );
+			fclose( $fp );
+
+			preg_match_all( '/^(.*):(.*)$/mU', $file_data, $matches, PREG_PATTERN_ORDER);
+			$meta_data = array_combine(array_map('trim',$matches[1]), array_map('trim',$matches[2]));
+
+			foreach (array('png', 'gif', 'jpg', 'jpeg') as $ext)
+			{
+				if (file_exists($base . $addon . "/screenshot.$ext"))
+				{
+					$meta_data['Screenshot'] = "screenshot.$ext";
+					break;
+				}
+			}
+			
+			return arr::merge($defaults, $meta_data);
+		}
+		return false;
+	}
+}
\ No newline at end of file
diff --git a/application/helpers/admin.php b/application/helpers/admin.php
new file mode 100755
index 0000000..c608003
--- /dev/null
+++ b/application/helpers/admin.php
@@ -0,0 +1,304 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Admin helper class.
+ *
+ * @package	   Admin
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+class admin_Core {
+
+	/**
+	 * Generate Main Tab Menus
+	 */
+	public static function main_tabs()
+	{
+		// Change tabs for MHI
+		if (Kohana::config('config.enable_mhi') == TRUE AND Kohana::config('settings.subdomain') == '')
+		{
+			// Start from scratch on admin tabs since most are irrelevant
+
+			return array(
+				'mhi' => Kohana::lang('ui_admin.mhi'),
+				'stats' => Kohana::lang('ui_admin.stats'),
+				'manage/pages' => Kohana::lang('ui_main.pages')
+			);
+		}
+		else
+		{
+			$tabs = array();
+			$tabs['dashboard'] = Kohana::lang('ui_admin.dashboard');
+			$tabs['reports'] = Kohana::lang('ui_admin.reports');
+			$tabs['messages'] = Kohana::lang('ui_admin.messages');
+			$tabs['stats'] = Kohana::lang('ui_admin.stats');
+			$tabs['addons'] = Kohana::lang('ui_admin.addons');
+			Event::run('ushahidi_action.nav_admin_main_top', $tabs);
+			return $tabs;
+		}
+	}
+
+
+	/**
+	 * Generate Main Tab Menus (RIGHT SIDE)
+	 */
+	public static function main_right_tabs($user = FALSE)
+	{
+		$main_right_tabs = array();
+
+		// Change tabs for MHI
+		if (Kohana::config('config.enable_mhi') == TRUE AND Kohana::config('settings.subdomain') == '')
+		{
+			$main_right_tabs = array(
+				'users' => Kohana::lang('ui_admin.users'),
+				'mhi/settings' => Kohana::lang('ui_admin.settings')
+			);
+		}
+		else
+		{
+			// Build the tabs array depending on the role permissions for each section
+			if ($user)
+			{
+				// Check permissions for settings panel
+				$main_right_tabs = (Auth::instance()->has_permission('settings', $user))
+					? arr::merge($main_right_tabs, array('settings/site' => Kohana::lang('ui_admin.settings')))
+					: $main_right_tabs;
+
+				// Check permissions for the manage panel
+				$main_right_tabs = (Auth::instance()->has_permission('manage', $user))
+					? arr::merge($main_right_tabs, array('manage' => Kohana::lang('ui_admin.manage')))
+					: $main_right_tabs;
+
+				// Check permissions for users panel
+				$main_right_tabs = (Auth::instance()->has_permission('users', $user))
+					? arr::merge($main_right_tabs, array('users' => Kohana::lang('ui_admin.users')))
+					: $main_right_tabs;
+			}
+		}
+
+		return $main_right_tabs;
+	}
+
+	/**
+	 * Generate MHI Sub Tab Menus
+	 * @param string $this_sub_page
+	 * @return string $menu
+	 */
+	public static function mhi_subtabs($this_sub_page = FALSE)
+	{
+		$menu = "";
+
+		$menu .= ($this_sub_page == "deployments") ? "Deployments" : "<a href=\"".url::site('admin/mhi')."\">Deployments</a>";
+
+		$menu .= ($this_sub_page == "activity") ? "Activity Stream" : "<a href=\"".url::site('admin/mhi/activity')."\">Activity Stream</a>";
+
+		$menu .= ($this_sub_page == "updatelist") ? "Update List" : "<a href=\"".url::site('admin/mhi/updatelist')."\">Update List</a>";
+
+		echo $menu;
+	}
+
+	/**
+	 * Generate Report Sub Tab Menus
+	 * @param string $this_sub_page
+	 * @return string $menu
+	 */
+	public static function reports_subtabs($this_sub_page = FALSE)
+	{
+		$menu = "";
+
+		$menu .= ($this_sub_page == "view") ? Kohana::lang('ui_main.view_reports') : "<a href=\"".url::site("admin/reports")."\">".Kohana::lang('ui_main.view_reports')."</a>";
+
+		$menu .= ($this_sub_page == "edit") ? Kohana::lang('ui_main.create_report') : "<a href=\"".url::site("admin/reports/edit")."\">".Kohana::lang('ui_main.create_report')."</a>";
+
+		$menu .= ($this_sub_page == "comments") ? Kohana::lang('ui_main.comments') : "<a href=\"".url::site('admin/comments')."\">".Kohana::lang('ui_main.comments')."</a>";
+
+		$menu .= ($this_sub_page == "download") ? Kohana::lang('ui_main.download_reports') : "<a href=\"".url::site("admin/reports/download")."\">".Kohana::lang('ui_main.download_reports')."</a>";
+
+		$menu .= ($this_sub_page == "upload") ? Kohana::lang('ui_main.upload_reports') : "<a href=\"".url::site("admin/reports/upload")."\">".Kohana::lang('ui_main.upload_reports')."</a>";
+
+		//only super admins have access to this
+		if (Auth::instance()->has_permission("delete_all_reports"))
+		{
+			$menu .= ($this_sub_page == "deleteall") ? Kohana::lang('ui_admin.delete_all') : "<a href=\"".url::site('admin/reports/deleteall')."\">".Kohana::lang('ui_admin.delete_all')."</a>";
+		}
+
+		echo $menu;
+
+		// Action::nav_admin_reports - Add items to the admin reports navigation tabs
+		Event::run('ushahidi_action.nav_admin_reports', $this_sub_page);
+	}
+
+
+	/**
+	 * Generate Messages Sub Tab Menus
+	 * @param int $service_id
+	 * @return string $menu
+	 */
+	public static function messages_subtabs($service_id = FALSE)
+	{
+		$menu = "";
+		foreach (ORM::factory('service')->find_all() as $service)
+		{
+			if ($service->id == $service_id)
+			{
+				$menu .= $service->service_name;
+			}
+			else
+			{
+				$menu .= "<a href=\"" . url::site() . "admin/messages/index/".$service->id."\">".$service->service_name."</a>";
+			}
+		}
+
+		echo $menu;
+
+		// Action::nav_admin_messages - Add items to the admin messages navigation tabs
+		Event::run('ushahidi_action.nav_admin_messages', $service_id);
+	}
+
+
+	/**
+	 * Generate Settings Sub Tab Menus
+	 * @param string $this_sub_page
+	 * @return string $menu
+	 */
+	public static function settings_subtabs($this_sub_page = FALSE)
+	{
+		$menu = "";
+
+		$menu .= ($this_sub_page == "site") ? Kohana::lang('ui_main.site') : "<a href=\"".url::site()."admin/settings/site\">".Kohana::lang('ui_main.site')."</a>";
+
+		$menu .= ($this_sub_page == "map") ? Kohana::lang('ui_main.map') : "<a href=\"".url::site()."admin/settings\">".Kohana::lang('ui_main.map')."</a>";
+
+		$menu .= ($this_sub_page == "sms") ? Kohana::lang('ui_main.sms') : "<a href=\"".url::site()."admin/settings/sms\">".Kohana::lang('ui_main.sms')."</a>";
+
+		$menu .= ($this_sub_page == "email") ? Kohana::lang('ui_main.email') : "<a href=\"".url::site()."admin/settings/email\">".Kohana::lang('ui_main.email')."</a>";
+
+		// We cannot allow cleanurl settings to be changed if MHI is enabled since it modifies a file in the config folder
+		if (Kohana::config('config.enable_mhi') == FALSE)
+		{
+			$menu .= ($this_sub_page == "cleanurl") ? Kohana::lang('ui_main.cleanurl'):	 "<a href=\"".url::site() ."admin/settings/cleanurl\">".Kohana::lang('ui_main.cleanurl')."</a>";
+
+			// SSL subtab
+			$menu .= ($this_sub_page == "https") ? Kohana::lang('ui_main.https'):  "<a href=\"".url::site() ."admin/settings/https\">".Kohana::lang('ui_main.https')."</a>";
+		}
+
+		$menu .= ($this_sub_page == "api") ? Kohana::lang('ui_main.api') : "<a href=\"".url::site()."admin/settings/api\">".Kohana::lang('ui_main.api')."</a>";
+
+		$menu .= ($this_sub_page == "facebook") ? "Facebook" : "<a href=\"".url::site()."admin/settings/facebook\">".Kohana::lang('ui_main.facebook')."</a>";
+
+		$menu .= ($this_sub_page == "twitter") ? "Twitter" : "<a href=\"".url::site()."admin/settings/twitter\">".Kohana::lang('ui_main.twitter')."</a>";
+
+		$menu .= ($this_sub_page == "externalapps") ? Kohana::lang('ui_main.external_apps') : "<a href=\"".url::site()."admin/settings/externalapps\">".Kohana::lang('ui_main.external_apps')."</a>";
+
+		echo $menu;
+
+		// Action::nav_admin_settings - Add items to the admin settings navigation tabs
+		Event::run('ushahidi_action.nav_admin_settings', $this_sub_page);
+	}
+
+
+	/**
+	 * Generate SMS Sub Tab Menus
+	 * @param string $this_sub_page
+	 * @return string $menu
+	 */
+	public static function settings_sms_subtabs($this_sub_page = FALSE)
+	{
+		$menu = "";
+		$menu .= ($this_sub_page == "sms") ? Kohana::lang('ui_main.sms') : "<a href=\"".url::site('admin/settings/sms')."\">".Kohana::lang('settings.sms.option_1')."</a>";
+		$menu .= ($this_sub_page == "smsglobal") ? Kohana::lang('ui_main.sms') : "<a href=\"".url::site('admin/settings/smsglobal')."\">".Kohana::lang('settings.sms.option_2')."</a>";
+
+		echo $menu;
+
+		// Action::nav_admin_settings_sms - Add items to the settings sms  navigation tabs
+		Event::run('ushahidi_action.sub_nav_admin_settings_sms', $this_sub_page);
+	}
+
+
+
+
+	/**
+	 * Generate Manage Sub Tab Menus
+	 * @param string $this_sub_page
+	 * @return string $menu
+	 */
+	public static function manage_subtabs($this_sub_page = FALSE)
+	{
+		$menu = "";
+
+		$menu .= ($this_sub_page == "categories") ? Kohana::lang('ui_main.categories') : "<a href=\"".url::site()."admin/manage\">".Kohana::lang('ui_main.categories')."</a>";
+
+		$menu .= ($this_sub_page == "blocks") ? Kohana::lang('ui_admin.blocks') : "<a href=\"".url::site()."admin/manage/blocks\">".Kohana::lang('ui_admin.blocks')."</a>";
+
+		$menu .= ($this_sub_page == "forms") ? Kohana::lang('ui_main.forms') : "<a href=\"".url::site()."admin/manage/forms\">".Kohana::lang('ui_main.forms')."</a>";
+
+		$menu .= ($this_sub_page == "pages") ? Kohana::lang('ui_main.pages') : "<a href=\"".url::site()."admin/manage/pages\">".Kohana::lang('ui_main.pages')."</a>";
+
+		$menu .= ($this_sub_page == "feeds") ? Kohana::lang('ui_main.news_feeds') : "<a href=\"".url::site()."admin/manage/feeds\">".Kohana::lang('ui_main.news_feeds')."</a>";
+
+		$menu .= ($this_sub_page == "layers") ? Kohana::lang('ui_main.layers') : "<a href=\"".url::site()."admin/manage/layers\">".Kohana::lang('ui_main.layers')."</a>";
+
+		$menu .= ($this_sub_page == "scheduler") ? Kohana::lang('ui_main.scheduler') : "<a href=\"".url::site()."admin/manage/scheduler\">".Kohana::lang('ui_main.scheduler')."</a>";
+
+		$menu .= ($this_sub_page == "publiclisting") ? Kohana::lang('ui_admin.public_listing') : "<a href=\"".url::site()."admin/manage/publiclisting\">".Kohana::lang('ui_admin.public_listing')."</a>";
+
+		$menu .= ($this_sub_page == "actions") ? Kohana::lang('ui_admin.actions') : "<a href=\"".url::site()."admin/manage/actions\">".Kohana::lang('ui_admin.actions')."</a>";
+
+		$menu .= ($this_sub_page == "badges") ? Kohana::lang('ui_main.badges') : "<a href=\"".url::site()."admin/manage/badges\">".Kohana::lang('ui_main.badges')."</a>";
+
+		$menu .= ($this_sub_page == "alerts") ? Kohana::lang('ui_admin.alerts') : "<a href=\"".url::site()."admin/manage/alerts\">".Kohana::lang('ui_admin.alerts')."</a>";
+
+		echo $menu;
+
+		// Action::nav_admin_manage - Add items to the admin manage navigation tabs
+		Event::run('ushahidi_action.nav_admin_manage', $this_sub_page);
+	}
+
+
+	/**
+	 * Generate User Sub Tab Menus
+	 * @param string $this_sub_page
+	 * @param boolean $display_roles
+	 * @return string $menu
+	 */
+	public static function user_subtabs($this_sub_page = FALSE, $display_roles = FALSE)
+	{
+		$menu = "";
+
+		$menu .= ($this_sub_page == "users") ? Kohana::lang('ui_admin.manage_users') : "<a href=\"".url::site()."admin/users/\">".Kohana::lang('ui_admin.manage_users')."</a>";
+
+		$menu .= ($this_sub_page == "users_edit") ? Kohana::lang('ui_admin.manage_users_edit') : "<a href=\"".url::site()."admin/users/edit/\">".Kohana::lang('ui_admin.manage_users_edit')."</a>";
+
+		// Only display the link for roles where $display_roles = TRUE
+		if ($display_roles)
+		{
+			$menu .= ($this_sub_page == "roles") ? Kohana::lang('ui_admin.manage_roles') : "<a
+			href=\"".url::site()."admin/users/roles/\">".Kohana::lang('ui_admin.manage_roles')."</a>";
+		}
+
+		echo $menu;
+
+		// Action::nav_admin_users - Add items to the admin manage navigation tabs
+		Event::run('ushahidi_action.nav_admin_users', $this_sub_page);
+	}
+	
+	/**
+	 * Legacy permissions check
+	 * @deprecated Use Auth::has_permission() instead.
+	 */
+	public function permissions($user = FALSE, $permission = FALSE)
+	{
+		Kohana::log('alert', 'admin::permissions() in deprecated and replaced by Auth::has_permission()');
+		return Auth::instance()->has_permission($permission, $user);
+	}
+	
+	/**
+	 * Legacy admin access check
+	 * @deprecated Use Auth::admin_access() instead.
+	 */
+	public function admin_access($user = FALSE)
+	{
+		Kohana::log('alert', 'admin::admin_access() in deprecated and replaced by Auth::admin_access()');
+		return Auth::instance()->admin_access($user);
+	}
+}
diff --git a/application/helpers/alert.php b/application/helpers/alert.php
new file mode 100644
index 0000000..41b1c07
--- /dev/null
+++ b/application/helpers/alert.php
@@ -0,0 +1,279 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Alerts helper class
+ *
+ * @package     Ushahidi
+ * @category    Helpers
+ * @author      Ushahidi Team
+ * @copyright   (c) 2008 Ushahidi Team
+ * @license     http://www.ushahidi.com/license.html
+ */
+
+class alert_Core {
+
+	const MOBILE_ALERT = 1;
+	const EMAIL_ALERT = 2;
+
+	/**
+	 * Sends an alert to a mobile phone
+	 *
+	 * @param Validation_Core $post
+	 * @param Alert_Model $alert
+	 * @return bool
+	 */
+	public static function _send_mobile_alert($post, $alert)
+	{
+		if ( ! $post instanceof Validation_Core AND !$alert instanceof Alert_Model)
+		{
+			throw new Kohana_Exception('Invalid parameter types');
+		}
+
+		// Should be 8 distinct characters
+		$alert_code = text::random('distinct', 8);
+		// HT: Mobile alert for link
+		$alert_mobile = $post->alert_mobile;
+		$sms_from = self::_sms_from();
+
+		$message = Kohana::lang('ui_admin.confirmation_code').$alert_code
+			.'.'.Kohana::lang('ui_admin.not_case_sensitive');
+		// HT: verify link for mobile
+		$message .= ' '.Kohana::lang('alerts.confirm_request').url::site().'alerts/verify?c='.$alert_code."&m=".$alert_mobile;
+	
+		if (sms::send($post->alert_mobile, $sms_from, $message) === true)
+		{
+			$alert->alert_type = self::MOBILE_ALERT;
+			$alert->alert_recipient = $post->alert_mobile;
+			$alert->alert_code = $alert_code;
+			if (isset($_SESSION['auth_user']))
+			{
+				$alert->user_id = $_SESSION['auth_user']->id;
+			}
+			$alert->save();
+
+			self::_add_categories($alert, $post);
+
+			return TRUE;
+		}
+
+		return FALSE;
+    }
+
+	/**
+	 * Sends an email alert
+	 *
+	 * @param Validation_Core $post
+	 * @param Alert_Model $alert
+	 * @return bool 
+	 */
+	public static function _send_email_alert($post, $alert)
+	{
+		if ( ! $post instanceof Validation_Core AND !$alert instanceof Alert_Model)
+		{
+			throw new Kohana_Exception('Invalid parameter types');
+		}
+
+		// Email Alerts, Confirmation Code
+		$alert_email = $post->alert_email;
+		$alert_code = text::random('alnum', 20);
+
+		$settings = kohana::config('settings');
+
+		$to = $alert_email;
+		$from = array();
+		
+		$from[] = ($settings['alerts_email']) 
+			? $settings['alerts_email']
+			: $settings['site_email'];
+		
+		$from[] = $settings['site_name'];
+		$subject = $settings['site_name']." ".Kohana::lang('alerts.verification_email_subject');
+		
+
+		$message = Kohana::lang('ui_admin.confirmation_code').$alert_code."<br><br>";
+		if(!empty($post->alert_category))
+		{
+			$message .= Kohana::lang('alerts.alerts_subscribed')."\n";
+			foreach ($post->alert_category as $item)
+			{
+				$category = ORM::factory('category')
+								->where('id',$item)
+								->find();
+
+				if($category->loaded)
+				{
+
+					$message .= "<ul><li>".$category->category_title ."</li></ul>";
+				}
+			}
+			
+		}
+
+		$message .= Kohana::lang('alerts.confirm_request').url::site().'alerts/verify?c='.$alert_code."&e=".$alert_email;
+
+		if (email::send($to, $from, $subject, $message, TRUE) == 1)
+		{
+			$alert->alert_type = self::EMAIL_ALERT;
+			$alert->alert_recipient = $alert_email;
+			$alert->alert_code = $alert_code;
+			if (isset($_SESSION['auth_user']))
+			{
+				$alert->user_id = $_SESSION['auth_user']->id;
+			}
+			$alert->save();
+
+			self::_add_categories($alert, $post);
+
+			return TRUE;
+		}
+
+		return FALSE;
+	}   
+
+
+	/**
+	 * This handles sms alerts subscription via phone
+	 *
+	 * @param string $message_from Subscriber MSISDN (mobile phone number)
+	 * @param string $message_description Message content
+	 * @return bool
+	 */
+	public static function mobile_alerts_register($message_from, $message_description)
+	{
+		// Preliminary validation
+		if (empty($message_from) OR empty($message_description))
+		{
+			// Log the error
+			Kohana::log('info', 'Insufficient data to proceed with subscription via mobile phone');
+			
+			// Return
+			return FALSE;
+		}
+
+		//Get the message details (location, category, distance)
+		$message_details = explode(" ",$message_description);
+		$message = $message_details[1].",".Kohana::config('settings.default_country');
+		$geocoder = map::geocode($message);
+			
+		// Generate alert code
+		$alert_code = text::random('distinct', 8);
+
+		// POST variable with items to save
+		$post = array(
+			'alert_type'=> self::MOBILE_ALERT,
+			'alert_mobile'=>$message_from,
+			'alert_code'=>$alert_code,
+			'alert_lon'=> isset($geocoder['lon']) ? $geocoder['lon'] : FALSE,
+			'alert_lat'=> isset($geocoder['lat']) ? $geocoder['lat'] : FALSE,
+			'alert_radius'=>'20',
+			'alert_confirmed'=>'1'
+		);
+
+		// Create ORM object for the alert and validate
+		$alert_orm = new Alert_Model();
+		if ($alert_orm->validate($post))
+		{
+			return self::_send_mobile_alert($post, $alert_orm);
+		}
+
+		return FALSE;
+
+	}
+
+	/**
+	 * This handles unsubscription from alerts via the mobile phone
+	 * 
+	 * @param string $message_from Phone number of subscriber
+	 * @param string $message_description Message content
+	 * @return bool
+	 */
+	public static function mobile_alerts_unsubscribe($message_from, $message_description)
+	{
+		// Validate parameters
+		
+		if (empty($message_from) OR empty($message_description))
+		{
+			// Log the error
+			Kohana::log('info', 'Cannot unsubscribe from alerts via the mobile phone - insufficient data');
+			
+			// Return
+			return FALSE;
+		}
+
+		$sms_from = self::_sms_from();
+
+		$site_name = $settings->site_name;
+		$message = Kohana::lang('ui_admin.unsubscribe_message').' ' .$site_name;
+
+		if (sms::send($message_from, $sms_from, $message) === true)
+		{
+			// Fetch all alerts with the specified code
+			$alerts = ORM::factory('alert')
+					->where('alert_recipient', $message_from)
+					->find_all();
+		
+			foreach ($alerts as $alert)
+			{
+				// Delete all alert categories with the specified phone number
+				ORM::factory('alert_category')
+					->where('alert_id', $alert->id)
+					->delete_all();
+
+				$alert->delete();
+			}
+			return TRUE;
+		}
+		return FALSE;	
+	}
+
+
+	/* This handles saving alert categories that a subscriber has subscribed for 
+	 *
+	 * @param $Alert_Model $alert 
+	*/
+
+	private static function _add_categories(Alert_Model $alert, $post)
+	{
+		if (isset($post->alert_category))
+		{
+			foreach ($post->alert_category as $item)
+			{
+				$category = ORM::factory('category')->find($item);
+				
+				if($category->loaded)
+				{
+					$alert_category = new Alert_Category_Model();
+					$alert_category->alert_id = $alert->id;
+					$alert_category->category_id = $category->id;
+					$alert_category->save();
+				}
+			}
+		}
+	}
+	
+	private static function _sms_from()
+	{
+		
+		$settings = Kohana::config('settings');
+		
+		// Get SMS Numbers
+		if ( ! empty($settings['sms_no3'])) 
+		{
+			$sms_from = $settings['sms_no3'];
+		}
+		elseif ( ! empty($settings['sms_no2'])) 
+		{
+			$sms_from = $settings['sms_no2'];
+		}
+		elseif ( ! empty($settings['sms_no1'])) 
+		{
+			$sms_from = $settings['sms_no1'];
+		}
+		else
+		{
+			$sms_from = "000";// User needs to set up an SMS number
+		}
+		
+		return $sms_from;
+	}
+
+}
diff --git a/application/helpers/badges.php b/application/helpers/badges.php
new file mode 100644
index 0000000..1fdb49e
--- /dev/null
+++ b/application/helpers/badges.php
@@ -0,0 +1,56 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Badges helper class.
+ *
+ * @package    Admin
+ * @author     Ushahidi Team
+ * @copyright  (c) 2011 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class badges_Core {
+
+	/**
+	 * Get all of the predefined images in badge packs
+	 *
+	 * @return array 	ie. Array('packname'=>Array('badge1.jpg','badge2.png','badge3.jpg'))
+	 */
+	public static function get_packs()
+	{	
+		$packs = array();
+
+		$allowed_badge_extensions = array('png', 'gif', 'jpg', 'jpeg');
+
+		$bp_path = MEDIAPATH.'img/badge_packs/';
+
+		$directories = scandir($bp_path);
+
+		foreach($directories as $directory)
+		{
+			// Don't examine anything starting with a dot (ie .DS_Store, .., .)
+			if ($directory{0} == '.')
+				continue;
+
+			$packname = $directory;
+			$pack_dir = $bp_path.$directory;
+
+			// Only try to look in the directory if it's actually a directory
+			if (is_dir($pack_dir))
+			{
+				foreach(scandir($pack_dir) as $filename)
+				{
+					if ($filename{0} == '.')
+						continue;
+
+					// We only want to consider images.
+					if ( in_array(pathinfo($filename, PATHINFO_EXTENSION),$allowed_badge_extensions) )
+					{
+						$packs[$packname][] = $filename;
+					}
+				}
+			}
+		}
+
+		return $packs;
+	}
+
+}
\ No newline at end of file
diff --git a/application/helpers/blocks.php b/application/helpers/blocks.php
new file mode 100644
index 0000000..871b308
--- /dev/null
+++ b/application/helpers/blocks.php
@@ -0,0 +1,121 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Blocks helper class.
+ *
+ * @package    Admin
+ * @author     Ushahidi Team
+ * @copyright  (c) 2011 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class blocks_Core {
+	
+	/**
+	 * Open A Block
+	 *
+	 * @return string
+	 */
+	public static function open($id = NULL)
+	{
+		if ($id)
+		{
+			echo "<li id=\"block-".$id."\"><div class=\"content-block\">";
+		}
+		else
+		{
+		  echo "<li><div class=\"content-block\">";
+		}
+		
+	}
+	
+	/**
+	 * Close A Block
+	 *
+	 * @return string
+	 */
+	public static function close()
+	{
+		echo "</div></li>";
+	}
+	
+	/**
+	 * Block Title
+	 *
+	 * @return string
+	 */
+	public static function title($title = NULL)
+	{
+		if ($title)
+		{
+			echo "<h5>$title</h5>";
+		}
+	}
+	
+	/**
+	 * Register A Block
+	 *
+	 * @param array $block an array with classname, name and description
+	 */
+	public static function register($block = array())
+	{
+		// global variable that contains all the blocks
+		$blocks = Kohana::config("settings.blocks");
+		if ( ! is_array($blocks) )
+		{
+			$blocks = array();
+		}
+		
+		if ( is_array($block) AND 
+			array_key_exists("classname", $block) AND 
+			array_key_exists("name", $block) AND 
+			array_key_exists("description", $block) )
+		{
+			if ( ! array_key_exists($block["classname"], $blocks))
+			{
+				$blocks[$block["classname"]] = array(
+					"name" => $block["name"],
+					"description" => $block["description"]
+				);
+			}
+		}
+		asort($blocks);
+		Kohana::config_set("settings.blocks", $blocks);
+	}
+	
+	/**
+	 * Render all the active blocks
+	 *
+	 * @return string block html
+	 */	
+	public static function render()
+	{
+		// Get Active Blocks
+		$active_blocks = Settings_Model::get_setting('blocks');
+		$active_blocks = array_filter(explode("|", $active_blocks));
+		foreach ($active_blocks as $block)
+		{
+			if (class_exists($block))
+			{
+				$block = new $block();
+				$block->block();
+			}
+		}
+	}
+	
+	/**
+	 * Sort Active and Non-Active Blocks
+	 * 
+	 * @param array $active array of active blocks
+	 * @param array $registered array of all blocks
+	 * @return array merged and sorted array of active and inactive blocks
+	 */
+	public static function sort($active = array(), $registered = array())
+	{
+		// Remove Empty Keys
+		$active = array_filter($active);
+		$registered = array_filter($registered);
+		
+		$sorted_array = array();
+		$sorted_array = array_intersect($active, $registered);
+		return array_merge($sorted_array, array_diff($registered, $sorted_array));
+	}
+}
\ No newline at end of file
diff --git a/application/helpers/category.php b/application/helpers/category.php
new file mode 100644
index 0000000..2cd72f5
--- /dev/null
+++ b/application/helpers/category.php
@@ -0,0 +1,277 @@
+<?php
+/**
+ * Category helper. Displays categories on the front-end.
+ *
+ * @package    Category
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class category_Core {
+
+	/**
+	 * Displays a single category checkbox.
+	 */
+	private static function display_category_checkbox($category, $selected_categories, $form_field, $enable_parents = FALSE)
+	{
+		$html = '';
+		
+		$cid = $category['category_id'];
+
+		// Category is selected.
+		$category_checked = in_array($cid, $selected_categories);
+
+		$disabled = "";
+		if (!$enable_parents AND count($category['children']) > 0)
+		{
+			$disabled = " disabled=\"disabled\"";	
+		}
+
+		$html .= "<label>";
+		$html .= form::checkbox($form_field.'[]', $cid, $category_checked, ' class="check-box"'.$disabled);
+		$html .= $category['category_title'];
+		$html .= "</label>";
+
+		return $html;
+	}
+
+	/**
+	 * Display category tree with input checkboxes.
+	 * @deprecated replaced by form_tree()
+	 **/
+	public static function tree($categories, $hide_children, array $selected_categories = array(), $form_field, $columns = 1, $enable_parents = FALSE)
+	{
+		Kohana::log('alert', 'category::tree() in deprecated and replaced by category::form_tree()');
+		return self::form_tree($form_field, $selected_categories, $columns, $enable_parents, ! $hide_children);
+	}
+
+	/**
+	 * Display category tree with input checkboxes for forms
+	 * 
+	 * @param string $form_field form field name
+	 * @param array $selected_categories Categories that should be already selected
+	 * @param int $columns number of columns to display
+	 * @param bool $enable_parents Can parent categoires be select
+	 * @param bool $show_hidden Show hidden categories
+	 */
+	public static function form_tree($form_field, array $selected_categories = array(), $columns = 1, $enable_parents = FALSE, $show_hidden = FALSE)
+	{
+		$category_data = self::get_category_tree_data(FALSE, $show_hidden);
+		
+		$html = '';
+
+		// Validate columns
+		$columns = (int) $columns;
+		if ($columns == 0)
+		{
+			$columns = 1;
+		}
+
+		$categories_total = count($category_data);
+
+		// Format categories for column display.
+		// Column number
+		$this_col = 1;
+
+		// Maximum number of elements per column
+		$maxper_col = round($categories_total / $columns);
+		
+		// start the first column
+		$html .= "\n".'<ul class="category-column category-column-'.$this_col.'" id="category-column-'.$this_col.'">'."\n";
+
+		$i = 1;  // Element Count
+		foreach ($category_data as $category)
+		{
+
+			// Display parent category.
+			$html .= "\n\t".'<li title="'.$category['category_description'].'">';
+			$html .= "\n\t\t".category::display_category_checkbox($category, $selected_categories, $form_field, $enable_parents)."\n";
+			
+			// Display child categories.
+			if (count($category['children']) > 0)
+			{
+				$html .= "\t\t<ul>";
+				foreach ($category['children'] as $child)
+				{
+					$html .= "\n\t\t\t".'<li title="'.$child['category_description'].'">'."\n";
+					$html .= category::display_category_checkbox($child, $selected_categories, $form_field, $enable_parents);
+					$html .= "\n\t\t\t".'</li>'."\r\n";
+				}
+				$html .= "\t\t".'</ul>'."\r\n";
+			}
+			
+			$html .= "\t</li>\n";
+
+			// If this is the last element of a column, close the UL
+			if ( (($i % $maxper_col) == 0 AND $i > 0) OR $i == $categories_total)
+			{
+				$html .= "</ul>\n";
+				$this_col++;
+				if($i < $categories_total)
+				{
+					$html .= '<ul class="category-column category-column-'.$this_col.'" id="category-column-'.$this_col.'">';
+				}
+			}
+			
+			$i++;
+			
+		}
+
+		return $html;
+	}
+	
+	/**
+	 * Generates a category tree view - recursively iterates
+	 *
+	 * @return string
+	 */
+	public static function get_category_tree_view()
+	{
+		$category_data = self::get_category_tree_data(TRUE);
+		
+		// Generate and return the HTML
+		return self::_generate_treeview_html($category_data);
+	}
+	
+	/**
+	 * Get categories as an tree array
+	 * @param bool Get category count?
+	 * @param bool Include hidden categories
+	 * @return array
+	 **/
+	public static function get_category_tree_data($count = FALSE, $include_hidden = FALSE)
+	{
+		
+		// To hold the category data
+		$category_data = array();
+		
+		// Database table prefix
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		// Database instance
+		$db = new Database();
+		
+		// Fetch the other categories
+		if ($count)
+		{
+			$sql = "SELECT c.id, c.parent_id, c.category_title, c.category_color, c.category_image, c.category_image_thumb, COUNT(i.id) report_count "
+				. "FROM ".$table_prefix."category c "
+				. "LEFT JOIN ".$table_prefix."category c_parent ON (c.parent_id = c_parent.id) "
+				. "LEFT JOIN ".$table_prefix."incident_category ic ON (ic.category_id = c.id) "
+				. "LEFT JOIN ".$table_prefix."incident i ON (ic.incident_id = i.id AND i.incident_active = 1 ) "
+				. "WHERE 1=1 "
+				. (!$include_hidden ? "AND c.category_visible = 1 " : "")
+				. (!$include_hidden ? "AND (c_parent.category_visible = 1 OR c.parent_id = 0)" : "") // Parent must be visible, or must be top level
+				. "GROUP BY c.id "
+				. "ORDER BY c.category_position ASC";
+		}
+		else
+		{
+			$sql = "SELECT c.id, c.parent_id, c.category_title, c.category_color, c.category_image, c.category_image_thumb "
+				. "FROM ".$table_prefix."category c "
+				. "LEFT JOIN ".$table_prefix."category c_parent ON (c.parent_id = c_parent.id) "
+				. "WHERE 1=1 "
+				. (!$include_hidden ? "AND c.category_visible = 1 " : "")
+				. (!$include_hidden ? "AND (c_parent.category_visible = 1 OR c.parent_id = 0)" : "") // Parent must be visible, or must be top level
+				. "ORDER BY c.category_position ASC";
+		}
+		
+		// Create nested array - all in one pass
+		foreach ($db->query($sql) as $category)
+		{
+			// If we didn't fetch report_count set fake value
+			if (!$count)
+			{
+			$category->report_count = 0;
+			}
+			
+			// If this is a parent category, just add it to the array
+			if ($category->parent_id == 0)
+			{
+				// save children and report_count if already been created.
+				$children = isset($category_data[$category->id]['children']) ? $category_data[$category->id]['children'] : array();
+				$report_count = isset($category_data[$category->id]['report_count']) ? $category_data[$category->id]['report_count'] : 0;
+				
+				$category_data[$category->id] = array(
+					'category_id' => $category->id,
+					'category_title' => html::escape(Category_Lang_Model::category_title($category->id)),
+					'category_description' => html::escape(Category_Lang_Model::category_description($category->id)),
+					'category_color' => $category->category_color,
+					'category_image' => $category->category_image,
+					'children' => $children,
+					'category_image_thumb' => $category->category_image_thumb,
+					'parent_id' => $category->parent_id,
+					'report_count' => $category->report_count + $report_count
+				);
+			}
+			// If this is a child, add it underneath its parent category
+			else
+			{
+				// If we haven't processed the parent yet, add placeholder parent category
+				if (! array_key_exists($category->parent_id, $category_data))
+				{
+					$category_data[$category->parent_id] = array(
+						'category_id' => $category->parent_id,
+						'category_title' => '',
+						'category_description' => '',
+						'parent_id' => 0,
+						'category_color' => '',
+						'category_image' => '',
+						'category_image_thumb' => '',
+						'children' => array(),
+						'report_count' => 0
+					);
+				}
+				
+				// Add children
+				$category_data[$category->parent_id]['children'][$category->id] = array(
+					'category_id' => $category->id,
+					'category_title' => html::escape(Category_Lang_Model::category_title($category->id)),
+					'category_description' => html::escape(Category_Lang_Model::category_description($category->id)),
+					'parent_id' => $category->parent_id,
+					'category_color' => $category->category_color,
+					'category_image' => $category->category_image,
+					'category_image_thumb' => $category->category_image_thumb,
+					'report_count' => $category->report_count,
+					'children' => array()
+				);
+				// Add to parent report count too
+				$category_data[$category->parent_id]['report_count'] += $category->report_count;
+			}
+		}
+
+		return $category_data;
+	}
+	
+	/**
+	 * Traverses an array containing category data and returns a tree view
+	 *
+	 * @param array $category_data
+	 * @return string
+	 */
+	private static function _generate_treeview_html($category_data)
+	{
+		// To hold the treeview HTMl
+		$tree_html = "";
+		
+		foreach ($category_data as $id => $category)
+		{
+			// Determine the category class
+			$category_class = ($category['parent_id'] > 0)? " class=\"report-listing-category-child\"" : "";
+			
+			$category_image = $category['category_image_thumb'] ? html::image(array('src'=> url::convert_uploaded_to_abs($category['category_image_thumb']), 'style'=>'float:left;padding-right:5px;')) : NULL;
+			
+			$tree_html .= "<li".$category_class.">"
+							. "<a href=\"#\" class=\"cat_selected\" id=\"filter_link_cat_".$id."\" title=\"{$category['category_description']}\">"
+							. "<span class=\"item-swatch\" style=\"background-color: #".$category['category_color']."\">$category_image</span>"
+							. "<span class=\"item-title\">".html::strip_tags($category['category_title'])."</span>"
+							. "<span class=\"item-count\">".$category['report_count']."</span>"
+							. "</a></li>";
+							
+			$tree_html .= self::_generate_treeview_html($category['children']);
+		}
+		
+		// Return
+		return $tree_html;
+	}
+}
diff --git a/application/helpers/cdn.php b/application/helpers/cdn.php
new file mode 100644
index 0000000..fe61489
--- /dev/null
+++ b/application/helpers/cdn.php
@@ -0,0 +1,246 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * CDN helper class to pass data to the proper provier library
+ *
+ * @package    Ushahidi
+ * @category   Helpers
+ * @author     Ushahidi Team
+ * @copyright  (c) 2011 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class cdn_Core {
+
+	/**
+	 * Reference to the CDN provider library
+	 * @var mixed
+	 */
+	private static $cdn = NULL;
+
+	/**
+	 * Connect to the right CDN provider library
+	 */
+	private static function connection()
+	{
+		if (self::$cdn == NULL)
+		{
+			try
+			{
+				if (Kohana::config("cdn.cdn_provider") == 'cloudfiles')
+				{
+					// Okay, configured properly to use a supported provider.
+					self::$cdn = new Cloudfiles;
+				}
+				else
+				{
+					return FALSE;
+				}
+			}
+			catch (Exception $e)
+			{
+				Kohana::log('error', Kohana::config("settings.subdomain")." CDN ERROR: ".$e->getMessage());
+				return FALSE;
+			}
+		}
+	}
+
+	/**
+	 * Uploads a file to the CDN
+	 *
+	 * @param string $filename Name of the file to be uploaded
+	 * @return string
+	 */
+	public static function upload($filename, $appendUploadDir = TRUE)
+	{
+		try
+		{
+			self::connection();
+
+			// Upload to the CDN and return new filename
+			return self::$cdn->upload($filename, $appendUploadDir);
+		}
+		catch (Exception $e)
+		{
+			Kohana::log('error', Kohana::config("settings.subdomain")." CDN ERROR: ".$e->getMessage());
+			return FALSE;
+		}
+	}
+
+
+	/**
+	 * Deletes an item from the CDN
+	 *
+	 * @param string $url URI for the item to delete
+	 */
+	public static function delete($url)
+	{
+		try
+		{
+			self::connection();
+			return self::$cdn->delete($url);
+		}
+		catch (Exception $e)
+		{
+			Kohana::log('error', Kohana::config("settings.subdomain")." CDN ERROR: ".$e->getMessage());
+			return FALSE;
+		}
+	}
+
+
+	/**
+	 * Upgrades files over time based on users hitting the site
+	 *
+	 * @return bool TRUE if the upgrade is successful, FALSE otherwise
+	 */
+	public static function gradual_upgrade()
+	{
+		if (Kohana::config('cdn.cdn_store_dynamic_content') == FALSE)
+		{
+			return FALSE;
+		}
+
+		try
+		{
+			self::connection();
+
+			if ( ! self::$cdn)
+				throw new Kohana_Exception('CDN provider not specified');
+
+			// Get the table prefix
+			$table_prefix = Kohana::config('database.default.table_prefix');
+
+			// Select at random since admin may not want every user to initiate a CDN upload
+			if (rand(1, intval(Kohana::config('cdn.cdn_gradual_upgrade'))) == 1)
+			{
+				$query = 'SELECT id, media_link, media_medium, media_thumb '
+						. 'FROM '.$table_prefix.'media '
+						. 'WHERE media_type = 1 AND media_link NOT LIKE \'http%\' LIMIT 1';
+
+				// Database instance for fetch and update operations
+				$db = Database::instance();
+
+				$media_links = $db->query($query);
+				$uploaded_flag = false;
+				foreach ($media_links as $row)
+				{
+					// Upload files to the CDN
+					$new_large = self::$cdn->upload($row->media_link);
+					$new_medium = self::$cdn->upload($row->media_medium);
+					$new_thumb = self::$cdn->upload($row->media_thumb);
+
+					// Update the entry for the media file in the DB
+					$db->update('media',
+						// Columns to update and their new values
+						array(
+							'media_link' => $new_large,
+							'media_medium'=>$new_medium,
+							'media_thumb'=>$new_thumb
+						),
+						// WHERE clause to update on
+						array('id' => $row->id)
+					);
+
+					// Remove old files
+					$local_directory = Kohana::config('upload.directory', TRUE);
+					$local_directory = rtrim($local_directory, '/').'/';
+					unlink($local_directory.$row->media_link);
+					unlink($local_directory.$row->media_medium);
+					unlink($local_directory.$row->media_thumb);
+
+					$uploaded_flag = true;
+				}
+
+				// If we didn't have any more user uploaded images to upload, move on to category images
+				if($uploaded_flag == false)
+				{
+					$query = 'SELECT id, category_image, category_image_thumb '
+							. 'FROM '.$table_prefix.'category '
+							. 'WHERE category_image NOT LIKE \'http%\' LIMIT 1';
+
+					// Fetch
+					$category_images = $db->query($query);
+					foreach ($category_images as $row)
+					{
+						// Upload files to the CDN
+						$new_category_image = self::$cdn->upload($row->category_image);
+						$new_category_image_thumb = self::$cdn->upload($row->category_image_thumb);
+
+						// Update the entry for the media file in the DB
+						$db->update('category',
+							// Columns to be updated
+							array(
+								'category_image' => $new_category_image,
+								'category_image_thumb' => $new_category_image_thumb
+							),
+							// WHERE clause to update on
+							array('id' => $row->id)
+						);
+
+						// Remove old files
+						$local_directory = Kohana::config('upload.directory', TRUE);
+						$local_directory = rtrim($local_directory, '/').'/';
+						unlink($local_directory.$row['category_image']);
+						unlink($local_directory.$row['category_image_thumb']);
+
+						$uploaded_flag = true;
+					}
+				}
+
+				// If we are done with category images, move on to KML layers
+				if($uploaded_flag == false)
+				{
+					// Grab a KML file we have locally that isn't linking to an external URL
+					$query = 'SELECT id, layer_file '
+							. 'FROM '.$table_prefix.'layer '
+							. 'WHERE layer_url = \'\' LIMIT 1';
+					$layers = $db->query($query);
+					foreach ($layers as $row)
+					{
+						$layer_file = $row->layer_file;
+
+						// Upload the file to the CDN
+						$layer_url = cdn::upload($layer_file);
+
+						// We no longer need the files we created on the server. Remove them.
+						$local_directory = rtrim(Kohana::config('upload.directory', TRUE), '/').'/';
+						unlink($local_directory.$layer_file);
+
+						$layer = new Layer_Model($row->id);
+						$layer->layer_url = $layer_url;
+						$layer->layer_file = '';
+						$layer->save();
+					}
+
+				}
+
+				// Garbage collection
+				unset ($db, $media_links, $category_images);
+
+				return TRUE;
+			}
+		}
+		catch (Exception $e)
+		{
+			Kohana::log('error', Kohana::config("settings.subdomain")." CDN ERROR: ".$e->getMessage());
+
+			return FALSE;
+		}
+
+		return FALSE;
+	}
+
+
+	/**
+	 * Javascript call to the upgrade
+	 *
+	 * @return string
+	 */
+	public static function cdn_gradual_upgrade_js()
+	{
+		return '<script async type="text/javascript">
+					$.ajax({
+						type: "GET",
+						url: "'.url::site().'main/cdn_gradual_upgrade"
+					});
+				</script>';
+	}
+}
\ No newline at end of file
diff --git a/application/helpers/customforms.php b/application/helpers/customforms.php
new file mode 100644
index 0000000..0c88f01
--- /dev/null
+++ b/application/helpers/customforms.php
@@ -0,0 +1,446 @@
+<?php
+/**
+ * Custom Forms Helper
+ * Functions to pull in the custom form fields and display them
+ *
+ * @package    Custom Forms
+ * @author     The Konpa Group - http://konpagroup.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class customforms_Core {
+
+	/**
+	 * Retrieve Custom Forms
+	 * @param bool $active_only Whether or not to limit to active forms only
+	 * @return ORM_Iterator
+	 */
+	public static function get_custom_forms($active_only = TRUE)
+	{
+		$custom_forms = ORM::factory('form');
+		if ($active_only)
+		{
+			$custom_forms->where('form_active',1);
+		}
+		return $custom_forms->find_all();
+	}
+
+	/**
+	 * Retrieve Custom Form Fields
+	 * @param bool|int $incident_id The unique incident_id of the original report
+	 * @param int $form_id The unique form_id. If none selected, retrieve custom form fields from ALL custom forms
+	 * @param bool $data_only Whether or not to include just data
+	 * @param string $action If this is being used to grab fields for submit or view of data
+	 */
+	public static function get_custom_form_fields($incident_id = FALSE, $form_id = NULL, $data_only = FALSE, $action = "submit")
+	{
+		$fields_array = array();
+
+		// If we have a form id but its invalid, return empty
+		if( ! empty($form_id) AND ! Form_Model::is_valid_form($form_id))
+			return $fields_array;
+
+		// Database table prefix
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		// Get field we'll check permissions against
+		$ispublic_field = ($action == "view") ? 'field_ispublic_visible' : 'field_ispublic_submit';
+
+		// NOTE will probably need to add a user_level variable for non-web based requests
+		$user_level = self::get_user_max_auth();
+		
+		// Check if incident is valid
+		// Have to do this early since we can't build 2 ORM queries at once.
+		$valid_incident = Incident_Model::is_valid_incident($incident_id, FALSE);
+
+		// Check if the provided incident exists, then fill in the data
+		if ($valid_incident)
+		{
+			$sql = "SELECT ff.*, fr.form_response
+			FROM `{$table_prefix}form_field` ff
+			LEFT JOIN `{$table_prefix}roles` r ON (r.id = {$ispublic_field})
+			LEFT JOIN
+				`{$table_prefix}form_response` fr ON (
+					fr.form_field_id = ff.id AND
+					fr.incident_id = :incident_id
+				)
+			WHERE (access_level <= :user_level OR access_level IS NULL) "
+			. ( ! empty($form_id) ? "AND form_id = :form_id " : '')
+			. "ORDER BY field_position ASC";
+		}
+		else
+		{
+			$sql = "SELECT ff.*
+			FROM `{$table_prefix}form_field` ff
+			LEFT JOIN `{$table_prefix}roles` r ON (r.id = {$ispublic_field})
+			WHERE (access_level <= :user_level OR access_level IS NULL) "
+			. ( ! empty($form_id) ? "AND form_id = :form_id " : '')
+			. "ORDER BY field_position ASC";
+		}
+
+		$form_fields = Database::instance()->query($sql, array(
+			':form_id' => $form_id,
+			':user_level' => $user_level,
+			':incident_id' => $incident_id
+		));
+
+		foreach ($form_fields as $custom_formfield)
+		{
+			if ($data_only)
+			{
+				// Return Data Only
+				$fields_array[$custom_formfield->id] = isset($custom_formfield->form_response) ? $custom_formfield->form_response : '';
+			}
+			else
+			{
+				// Return Field Structure
+				$fields_array[$custom_formfield->id] = array(
+					'field_id' => $custom_formfield->id,
+					'form_id' => $custom_formfield->form_id,
+					'field_name' => $custom_formfield->field_name,
+					'field_type' => $custom_formfield->field_type,
+					'field_default' => $custom_formfield->field_default,
+					'field_required' => $custom_formfield->field_required,
+					'field_maxlength' => $custom_formfield->field_maxlength,
+					'field_height' => $custom_formfield->field_height,
+					'field_width' => $custom_formfield->field_width,
+					'field_isdate' => $custom_formfield->field_isdate,
+					'field_ispublic_visible' => $custom_formfield->field_ispublic_visible,
+					'field_ispublic_submit' => $custom_formfield->field_ispublic_submit,
+					'field_response' => isset($custom_formfield->form_response) ? $custom_formfield->form_response : '',
+				);
+			}
+		}
+
+		// Garbage collection
+		unset ($form_fields);
+
+		// Return
+		return $fields_array;
+	}
+
+	/**
+	 * Returns the user's maximum role id number
+	 *
+	 * @param array $user the current user object
+	 * @return int
+	 */
+	public static function get_user_max_auth(){
+		if( ! isset($_SESSION['auth_user']))
+			return 0;
+
+		$user = new User_Model($_SESSION['auth_user']->id);
+
+		if ($user->loaded == TRUE)
+		{
+			$r = array();
+			foreach($user->roles as $role)
+			{
+				array_push($r,$role->access_level);
+			}
+
+			if (count($r) == 0)
+			{
+				// There are no roles so clearly they have no authorization
+				return 0;
+			}
+
+			return max($r);
+		}
+		return 0;
+	}
+
+	/**
+	 * Validate Custom Form Fields
+	 * @param Validation $post Validation object from form post
+	 * XXX This whole function is being done backwards
+	 * Need to pull the list of custom form fields first
+	 * Then look through them to see if they're set, not the other way around.
+	 */
+	public static function validate_custom_form_fields(&$post)
+	{
+		$custom_fields = array();
+
+		if (!isset($post->custom_field))
+			return;
+
+		/* XXX Checkboxes hackery
+			 Checkboxes are submitted in the post as custom_field[field_id-boxnum]
+			 This foreach loop consolidates them into one variable separated by commas.
+			 If no checkboxes are selected then the custom_field[] for that variable is not sent
+			 To get around that the view sets a hidden custom_field[field_id-BLANKHACK] field that
+			 ensures the checkbox custom_field is there to be tested.
+		*/
+		foreach ($post->custom_field as $field_id => $field_response)
+		{
+			$split = explode("-", $field_id);
+			if (isset($split[1]))
+			{
+				// The view sets a hidden field for blankhack
+				if ($split[1] == 'BLANKHACK')
+				{
+					if(!isset($custom_fields[$split[0]]))
+					{
+						// then no checkboxes were checked
+						$custom_fields[$split[0]] = '';
+					}
+					// E.Kala - Removed the else {} block; either way continue is still invoked
+					continue;
+				}
+
+				if (isset($custom_fields[$split[0]]))
+				{
+					$custom_fields[$split[0]] .= ",$field_response";
+				}
+				else
+				{
+					$custom_fields[$split[0]] = $field_response;
+				}
+			}
+			else
+			{
+				$custom_fields[$split[0]] = $field_response;
+			}
+		}
+
+		$post->custom_field = $custom_fields;
+		// Kohana::log('debug', Kohana::debug($custom_fields));
+
+		foreach ($post->custom_field  as $field_id => $field_response)
+		{
+
+			$field_param = ORM::factory('form_field',$field_id);
+			$custom_name = $field_param->field_name;
+
+			// Validate that this custom field already exists
+			if ( ! $field_param->loaded)
+			{
+				// Populate the error field
+				//$errors[$field_id] = "The $custom_name field does not exist";
+				$post->add_error('custom_field', 'not_exist', array($field_id));
+				return;
+			}
+
+			$max_auth = self::get_user_max_auth();
+			$required_role = ORM::factory('role', $field_param->field_ispublic_submit);
+			if (($required_role->loaded ? $required_role->access_level : 0) > $max_auth)
+			{
+				// Populate the error field
+				$post->add_error('custom_field', 'permission', array($custom_name));
+				return;
+			}
+
+			// Validate that the field is required
+			if ( $field_param->field_required == 1 AND $field_response == "")
+			{
+				$post->add_error('custom_field', 'required', array($custom_name));
+				return;
+			}
+
+			// Grab the custom field options for this field
+			$field_options = self::get_custom_field_options($field_id);
+
+			// Validate Custom fields for text boxes
+			if ($field_param->field_type == 1 AND isset($field_options) AND $field_response != '')
+			{
+				if (isset($field_options['field_datatype']))
+				{
+					if ($field_options['field_datatype'] == 'email' AND !valid::email($field_response))
+					{
+						$post->add_error('custom_field', 'email', array($custom_name));
+					}
+
+					if ($field_options['field_datatype'] == 'phonenumber' AND !valid::phone($field_response))
+					{
+						$post->add_error('custom_field', 'phone', array($custom_name));
+					}
+
+					if ($field_options['field_datatype'] == 'numeric' AND !valid::numeric($field_response))
+					{
+						$post->add_error('custom_field', 'numeric', array($custom_name));
+					}
+				}
+			}
+
+			// Validate for date
+			if ($field_param->field_type == 3 AND $field_response != "")
+			{
+				$field_default = $field_param->field_default;
+				if ( ! valid::date_mmddyyyy($field_response))
+				{
+					$post->add_error('custom_field', 'date_mmddyyyy', array($custom_name));
+				}
+			}
+
+			// Validate multi-value boxes only have acceptable values
+			if ($field_param->field_type >= 5 AND $field_param->field_type <=7)
+			{
+				$defaults = explode('::',$field_param->field_default);
+				$options = array();
+				if (preg_match("/[0-9]+-[0-9]+/",$defaults[0]) AND count($defaults) == 1)
+				{
+					$dashsplit = explode('-',$defaults[0]);
+					$start = $dashsplit[0];
+					$end = $dashsplit[1];
+					for($i = $start; $i <= $end; $i++)
+					{
+						array_push($options,$i);
+					}
+				}
+				else
+				{
+					$options = array_map('trim',explode(',',$defaults[0]));
+				}
+
+				$responses = explode(',',$field_response);
+				foreach ($responses as $response)
+				{
+					if ( ! in_array($response, $options) AND $response != '')
+					{
+						$post->add_error('custom_field', 'values', array($custom_name));
+						//$errors[$field_id] = "The $custom_name field does not include $response as an option";
+					}
+				}
+			}
+
+			// Validate that a required checkbox is checked
+			if ($field_param->field_type == 6 AND $field_response == 'BLANKHACK' AND $field_param->field_required == 1)
+			{
+				$post->add_error('custom_field', 'required', array($custom_name));
+			}
+		}
+
+		return;
+	}
+
+	/**
+	 * Generate list of currently created Form Fields for the admin interface
+	 * @param int $form_id The id no. of the form
+	 * @return string
+	 */
+    public static function get_current_fields($form_id = 0)
+    {
+		$form_fields = form::open(NULL, array('method' => 'get'));
+		$form = array();
+		$form['custom_field'] = self::get_custom_form_fields('',$form_id, true);
+		$form['id'] = $form_id;
+		$custom_forms = new View('reports/submit_custom_forms');
+		$disp_custom_fields = self::get_custom_form_fields('', $form_id,false);
+		$custom_forms->disp_custom_fields = $disp_custom_fields;
+		$custom_forms->form = $form;
+		$custom_forms->editor = true;
+		$form_fields.= $custom_forms->render();
+		$form_fields .= form::close();
+
+		return $form_fields;
+	}
+
+	/**
+	 * Generates the html that's passed back in the json switch_Action form switcher
+	 * @param int $incident_id The Incident Id
+	 * @param int $form_id Form Id
+	 * @param int $public_visible If this form should be publicly visible
+	 * @param int $pubilc_submit If this form is allowed to be submitted by anyone on the internets.
+	 * @return string
+	 */
+	public static function switcheroo($incident_id = '', $form_id = '')
+	{
+		$form_fields = '';
+
+		$fields_array = self::get_custom_form_fields($incident_id, $form_id, TRUE);
+
+		$form = array();
+		$form['custom_field'] = self::get_custom_form_fields($incident_id,$form_id, TRUE);
+		$form['id'] = $form_id;
+		$custom_forms = new View('reports/submit_custom_forms');
+		$disp_custom_fields = self::get_custom_form_fields($incident_id,$form_id, FALSE);
+		$custom_forms->disp_custom_fields = $disp_custom_fields;
+		$custom_forms->form = $form;
+		$form_fields.= $custom_forms->render();
+
+		return $form_fields;
+	}
+
+	/**
+	 * Generates an array of fields that an admin can see but can't edit
+	 *
+	 * @param int $form_id The form id
+	 * @return array
+	 */
+	public static function get_edit_mismatch($form_id = 0)
+	{
+		$table_prefix = Kohana::config('database.default.table_prefix');
+
+		$user_level = self::get_user_max_auth();
+		
+		$db = Database::instance();
+		$custom_formfields = $db->query(
+			"SELECT `form_field`.`id`
+			FROM `{$table_prefix}form_field` AS `form_field`
+			LEFT JOIN `{$table_prefix}roles` AS `roles_submit` ON (`form_field`.`field_ispublic_submit` = `roles_submit`.`id`)
+			LEFT JOIN `{$table_prefix}roles` AS `roles_view` ON (`form_field`.`field_ispublic_visible` = `roles_view`.`id`)
+			WHERE `form_id` = :form_id 
+			AND `roles_submit`.`access_level` > :user_level 
+			AND (`roles_view`.`access_level` <= :user_level OR `roles_view`.`access_level` IS NULL )
+			ORDER BY `field_position` ASC",
+			array(
+				':user_level' => $user_level,
+				':form_id' => $form_id
+			)
+		);
+		
+		$mismatches = array();
+		foreach ($custom_formfields as $custom_formfield)
+		{
+			$mismatches[$custom_formfield->id] = 1;
+		}
+		return $mismatches;
+	}
+
+	/**
+	 * Checks if a field type has multiple values
+	 *
+	 * @param array $field
+	 * @return bool
+	 */
+	public static function field_is_multi_value($field){
+		$is_multi = FALSE;
+
+		switch ($field["field_type"])
+		{
+			case 5: //Radio
+				$is_multi = TRUE;
+			break;
+
+			case 6: // Checkbox
+				$is_multi = TRUE;
+			break;
+
+			case 7: // Dropdown
+				$is_multi = TRUE;
+			break;
+
+			default:
+			$is_multi = FALSE;
+		}
+		return $is_multi;
+	}
+
+	/**
+	 * Returns the form field options associated with this form field
+	 *
+	 * @param int $field_id The Field Id
+	 * @return array
+	 */
+	public static function get_custom_field_options($field_id)
+	{
+		//XXX should be able to use the model for this, right?
+		$field_options = array();
+		$field_option_query = ORM::factory('form_field_option')->where('form_field_id',$field_id)->find_all();
+		foreach($field_option_query as $option)
+		{
+			$field_options[$option->option_name] = $option->option_value;
+		}
+
+		return $field_options;
+	}
+}
diff --git a/application/helpers/download.php b/application/helpers/download.php
new file mode 100644
index 0000000..4a51ff9
--- /dev/null
+++ b/application/helpers/download.php
@@ -0,0 +1,704 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Reports Download helper class.
+ *
+ * @package	   Admin
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2012 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+class download_Core {
+	/**
+	 * Validation of form fields
+	 *
+	 * @param array $post Values to be validated
+	 */
+	public static function validate(array & $post)
+	{
+		// Exception handling
+		if ( ! isset($post) OR ! is_array($post))
+			return FALSE;
+		
+		// Create validation object
+		$post = Validation::factory($post)
+				->pre_filter('trim', TRUE)
+				->add_rules('format','required')
+				->add_rules('data_active.*','required','numeric','between[0,1]')
+				->add_rules('data_verified.*','required','numeric','between[0,1]')
+				->add_rules('data_include.*','numeric','between[1,7]')
+				->add_rules('from_date','date_mmddyyyy')
+				->add_rules('to_date','date_mmddyyyy');
+				
+		// Validate the report dates
+			// TO Date not greater than FROM Date?
+		if (!empty($post->from_date) AND !empty($post->to_date)
+			AND strtotime($post->from_date) > strtotime($post->to_date))
+		{
+			$post->add_error('to_date','range_greater');
+		}
+			
+		// Make sure valid format is passed
+		if ($post->format !='csv' AND $post->format !='xml')
+		{
+			$post->add_error('format','valid');
+		}	
+				
+		// Return
+		return $post->validate();
+	}
+	
+	/**
+	 * Download Reports in CSV format
+	 * @param Validation $post Validation object with the download criteria 
+	 * @param array $incidents Reports to be downloaded
+	 * @param array $custom_forms Custom form field structure and values
+	 * @return string $report_csv CSV content
+	 */
+	public static function download_csv($post, $incidents, $custom_forms)
+	{
+		$fiveMBs = 5 * 1024 * 1024;
+		$fp = fopen("php://temp/maxmemory:$fiveMBs", 'r+');
+		
+		// Column Titles
+		$csv_headers = array("#","FORM #","INCIDENT TITLE","INCIDENT DATE");
+		$item_map = array(
+		    1 => 'LOCATION',
+		    2 => 'DESCRIPTION',
+		    3 => 'CATEGORY',
+		    4 => 'LATITUDE',
+		    5 => 'LONGITUDE',
+		    7 => array('FIRST NAME','LAST NAME','EMAIL')
+		);
+
+		// Ensure column order is always the same
+		sort($post->data_include);
+
+		foreach($post->data_include as $item)
+		{
+			if ( (int)$item == 6)
+			{
+				foreach($custom_forms as $field_name)
+				{
+					$csv_headers[] = $field_name['field_name']."-".$field_name['form_id'];
+				}
+
+			}
+			elseif (is_array($item_map[$item]))
+			{
+				foreach ($item_map[$item] as $i)
+				{
+					$csv_headers[] = $i;
+				}
+			}
+			elseif ( array_key_exists($item, $item_map))
+			{
+				$csv_headers[] = $item_map[$item];
+			}
+		}
+
+		$csv_headers[] = 'APPROVED';
+		$csv_headers[] = 'VERIFIED';
+
+		// Incase a plugin would like to add some custom fields
+		Event::run('ushahidi_filter.report_download_csv_header', $csv_headers);
+
+		fwrite($fp, self::arrayToCsv($csv_headers));
+
+		foreach ($incidents as $incident)
+		{
+			$csv_line = array();
+			$csv_line[] = $incident->id;
+			$csv_line[] = $incident->form_id;
+			$csv_line[] = $incident->incident_title;
+			$csv_line[] = $incident->incident_date;
+
+			foreach($post->data_include as $item)
+			{
+				switch ($item)
+				{
+					case 1:
+					$csv_line[] = $incident->location->location_name;
+					break;
+
+					case 2:
+					$csv_line[] = $incident->incident_description;
+					break;
+
+					case 3:
+					$cat_titles = array();
+					foreach($incident->incident_category as $category)
+					{
+						if ($category->category->category_title)
+						{
+							$cat_titles[] = $category->category->category_title;
+						}
+					}
+					$csv_line[] = implode(',',$cat_titles);
+					break;
+
+					case 4:
+					$csv_line[] = $incident->location->latitude;
+					break;
+
+					case 5:
+					$csv_line[] = $incident->location->longitude;
+					break;
+
+					case 6:
+					$incident_id = $incident->id;
+					$custom_fields = customforms::get_custom_form_fields($incident_id, NULL, FALSE);
+					if ( ! empty($custom_fields))
+					{
+						foreach($custom_fields as $custom_field)
+						{
+							$csv_line[] = $custom_field['field_response'];
+						}
+					}
+					else
+					{
+						foreach ($custom_forms as $custom)
+						{
+							$csv_line[] = '';
+						}
+					}
+					break;
+
+					case 7:
+					$incident_person = $incident->incident_person;
+					if($incident_person->loaded)
+					{
+						$csv_line[] = $incident_person->person_first;
+						$csv_line[] = $incident_person->person_last;
+						$csv_line[] = $incident_person->person_email;
+					}
+					else
+					{
+						$csv_line[] = '';
+						$csv_line[] = '';
+						$csv_line[] = '';
+					}
+					break;
+				}
+			}
+
+			$csv_line[] = $incident->incident_active ? 'YES' : 'NO';
+			$csv_line[] = $incident->incident_verified ? 'YES' : 'NO';
+			
+
+			// Incase a plugin would like to add some custom data for an incident
+			$event_data = array("csv_line" => $csv_line, "incident" => $incident);
+			Event::run('ushahidi_filter.report_download_csv_incident', $event_data);
+			$csv_line = $event_data['csv_line'];
+			
+			// Add line to CSV
+			fwrite($fp, self::arrayToCsv($csv_line));
+		}
+		
+		rewind($fp);
+		$csv_text = stream_get_contents($fp);
+		fclose($fp);
+		return $csv_text;
+	}
+	
+	/**
+	 * Download Reports in XML format
+	 * @param Validation $post Validation object with the download criteria 
+	 * @param array $incidents reports to be downloaded
+	 * @param array $categories deployment categories
+	 * @param array $custom_forms Custom form field structure and values
+	 */
+	public static function download_xml($post, $incidents, $categories, $custom_forms)
+	{
+		// Adding XML Content
+		$writer = new XMLWriter;
+		$writer->openMemory();
+		$writer->startDocument('1.0', 'UTF-8');
+		$writer->setIndent(TRUE);
+		
+		/* Category Element/Attribute maps */
+		// Category map
+		$category_map = array(
+			'attributes' => array(
+				'color' => 'category_color',
+				'visible' => 'category_visible',
+				'trusted' => 'category_trusted'
+			),
+			'elements' => array(
+				'title' => 'category_title',
+				'description' => 'category_description'
+			)
+		);
+
+		// Array of category elements
+		$category_elements = array('color', 'visible', 'trusted', 'title', 'description','parent');
+		
+		/* Category translation Element/Attribute maps */
+		// Translation map
+		$translation_map = array(
+			'attributes' => array(
+				'locale' => 'locale',
+			),
+			'elements' => array(
+				'translation_title' => 'category_title',
+				'translation_description' => 'category_description'
+			)
+		);
+		
+		// Array of translation elements
+		$translation_elements = array('locale', 'translation_title', 'translation_description');
+		
+		/* Form element/attribute maps */
+		// Forms map
+		$form_map = array(
+					'attributes' => array(
+						'active' => 'form_active'
+						),
+					'elements' => array(
+						'title' => 'form_title',
+						'description' => 'form_description'
+						)
+					);
+					
+		// Forms element
+		$form_elements = array('active', 'title', 'description');
+		
+		/* Custom fields element/attribute maps */	
+		// Field elements
+		$form_field_elements = array('type', 'required', 'visible_by', 'submit_by', 'datatype', 'hidden', 'name', 'default');
+		
+		/* Reports element/attribute maps */
+		// Report map
+		$report_map = array(
+						'attributes' => array(
+							'id' => 'id',
+							'approved' => 'incident_active',
+							'verified' => 'incident_verified',
+							'mode' => 'incident_mode',
+						),
+						'elements' => array(
+							'title' => 'incident_title',
+							'date' => 'incident_date',
+							'dateadd' => 'incident_dateadd',
+						)
+					);
+					
+		// Report elements
+		$report_elements = array('id', 'approved', 'verified', 'form_name', 'mode', 'title', 'date', 'dateadd');
+		
+		// Location Map
+		$location_map = array(
+			'attributes' => array(),
+			'elements' => array(
+				'name' => 'location_name',
+				'longitude' => 'longitude',
+				'latitude' => 'latitude'		
+			)
+		);
+		
+		// Location elements
+		$location_elements = array('name', 'longitude', 'latitude');
+		
+		// Media Map
+		$media_map = array(
+						'attributes' => array(
+							'type' => 'media_type',
+							'active' => 'media_active',
+							'date' => 'media_date'
+						),
+						'elements' => array()
+					);
+		
+		// Media elements
+		$media_elements = array('type', 'active', 'date');
+		
+		// Personal info map
+		$person_map = array(
+						'attributes' => array(),
+						'elements' => array(
+							'first_name' => 'person_first',
+							'last_name' => 'person_last',
+							'email' => 'person_email'	
+						)
+					);
+	
+		// Personal info elements
+		$person_elements = array('first_name', 'last_name', 'email');
+		
+		// Incident Category map
+		$incident_category_map = array(
+									'attributes' => array(),
+									'elements' => array(
+										'category' => 'category_title',
+									)
+			
+								);
+					
+		// Incident Category elements 
+		$incident_category_elements = array('category');
+
+		// Ensure column order is always the same
+		sort($post->data_include);
+
+		/* Start Import Tag*/
+		$writer->startElement('UshahidiReports');
+		foreach ($post->data_include as $item)
+		{
+			switch($item)
+			{
+				case 3:
+				/* Start Categories element */
+				$writer->startElement('categories');
+				if (count($categories) > 0)
+				{
+					foreach ($categories as $category)
+					{
+						// Begin individual category tag	
+						$writer->startElement('category');	
+						
+						// Generate category element map
+						$category_element_map = xml::generate_element_attribute_map($category, $category_map);
+						
+						if ($category->parent_id > 0)
+						{
+							// Category's parent
+							$parent = ORM::factory('category', $category->parent_id);
+						
+							// If parent category exists
+							if ($parent->loaded)
+							{
+								// Add to array of category_element_map for purposes of generating tags
+								$category_element_map['elements']['parent'] = $parent->category_title;
+							}
+						}
+						
+						// Generate individual category tags						
+						xml::generate_tags($writer, $category_element_map, $category_elements);
+
+						// Category Translation
+						$translations = ORM::factory('category_lang')->where('category_id', $category->id)->find_all();
+				
+						// If translations exist
+						if (count($translations) > 0)
+						{
+							$writer->startElement('translations');
+							foreach ($translations as $translation)
+							{
+								// Begin individual translation element
+								$writer->startElement('translation');
+								
+								// Generate translation element map
+								$translation_element_map = xml::generate_element_attribute_map($translation, $translation_map); 
+
+								// Generate translation tags
+								xml::generate_tags($writer, $translation_element_map, $translation_elements);
+								
+								// End individual category translation tag
+								$writer->endElement();
+							}
+							$writer->endElement();
+						}	
+						$writer->endElement();
+					}	
+				}
+		
+				// If there are no categories
+				else
+				{
+					$writer->text('There are no categories on this deployment.');
+				}
+		
+				/* Close Categories Element */
+				$writer->endElement();
+				break;
+				
+				case 6:
+				/* Start Customforms Element */
+				$writer->startElement('custom_forms');
+			
+				// If we have custom forms
+				if (count($custom_forms) > 0)
+				{
+					foreach ($custom_forms as $form)
+					{	
+						// Custom Form element
+						$writer->startElement('form');
+						
+						// Generate form elements map
+						$form_element_map = xml::generate_element_attribute_map($form, $form_map);
+						
+						// Generate form element tags
+						xml::generate_tags($writer, $form_element_map, $form_elements);
+					
+						// Get custom fields associated with this form
+						$form_fields = customforms::get_custom_form_fields(FALSE,$form->id,FALSE);
+						foreach ($form_fields as $field)
+						{
+							// Make sure this custom form field belongs to the current form
+							if ($field['form_id'] == $form->id)
+							{
+								// Custom Form Fields
+								$writer->startElement('field');
+							
+								$form_field_map = array(
+													'attributes' => array(
+														'type' => $field['field_type'],
+														'required' => $field['field_required'],
+														'visible_by' => $field['field_ispublic_visible'],
+														'submit_by' => $field['field_ispublic_submit']
+														),
+													'elements' => array()
+													);
+								
+								/* Get custom form field options */
+								$options = ORM::factory('form_field_option')->where('form_field_id',$field['field_id'])->find_all();
+								foreach ($options as $option)
+								{
+									if ($option->option_name == 'field_datatype')
+									{
+										// Data type i.e Free, Numeric, Email, Phone?
+										$form_field_map['attributes']['datatype'] = $option->option_value;
+									}
+									if ($option->option_name == 'field_hidden')
+									{
+										// Hidden Field?
+										$form_field_map['attributes']['hidden'] = $option->option_value;
+									}
+								}
+							
+								// Field name
+								$form_field_map['elements']['name'] = $field['field_name'];
+
+								// Default Value
+								if ($field['field_default'] != '')
+								{
+									$form_field_map['elements']['default'] = $field['field_default'];
+								}
+	
+								// Generate custom fields tags
+								xml::generate_tags($writer, $form_field_map, $form_field_elements);
+	
+								// Close Custom form field element	
+								$writer->endElement();
+							}
+						} 
+				
+						// Close Custom Form Element
+						$writer->endElement();	
+					}	
+				}
+		
+				// We have no Custom forms
+				else
+				{
+					$writer->text('There are no custom forms on this deployment.');
+				}
+		
+				/* End Custom Forms Element */
+				$writer->endElement();
+				break;
+			}
+		}
+			
+		/* Start Reports Element*/
+		$writer->startElement('reports');
+		
+			
+		// If we have reports on this deployment
+		if (count($incidents) > 0)
+		{	
+			foreach ($incidents as $incident)
+			{
+				// Start Individual report
+				$writer->startElement('report');
+								
+				// Generate report map
+				$report_element_map = xml::generate_element_attribute_map($incident, $report_map);
+				
+				// Grab Default form title
+				$default_form = ORM::factory('form', 1);
+				
+				// Form this incident belongs to?
+				$form_name = $incident->form->loaded ? $incident->form->form_title : $default_form->form_title;
+				
+				// Add it to report element map
+				$report_element_map['attributes']['form_name'] = $form_name;
+				
+				// Generate report tags
+				xml::generate_tags($writer, $report_element_map, $report_elements);
+								
+				foreach($post->data_include as $item)
+				{
+					switch($item)
+					{	
+						// Report Description
+						case 2:
+						$writer->startElement('description');
+							$writer->text($incident->incident_description);
+						$writer->endElement();
+						break;
+						
+						// Report Location
+						case 1:
+						$writer->startElement('location');
+						
+						// Generate location map
+						$location_map_element = xml::generate_element_attribute_map($incident->location, $location_map);
+						
+						// Generate location tags
+						xml::generate_tags($writer, $location_map_element, $location_elements);
+						
+						// Close location tag
+						$writer->endElement();
+						break;
+
+						case 7:
+						
+						// Report Personal information
+						$incident_person = $incident->incident_person;
+						if ($incident_person->loaded)
+						{
+							$writer->startElement('personal_info');
+							
+							// Generate incident person element map
+							$person_element_map = xml::generate_element_attribute_map($incident_person, $person_map);
+							
+							// Generate incident person element tags
+							xml::generate_tags($writer, $person_element_map, $person_elements);
+							
+							// Close personal info tag	
+							$writer->endElement();
+						}
+						break;
+
+						case 3:
+						
+						// Report Category
+						$writer->startElement('report_categories');
+						foreach($incident->incident_category as $category)
+						{
+							// Generate Incident Category Element Map
+							$incident_category_element_map = xml::generate_element_attribute_map($category->category, $incident_category_map);
+							
+							// Generate Incident Category Tags
+							xml::generate_tags($writer, $incident_category_element_map, $incident_category_elements);
+						}
+						$writer->endElement();
+						break;
+
+						case 6:
+						
+						// Report Fields
+						$customresponses = customforms::get_custom_form_fields($incident->id,$incident->form_id,FALSE);
+						if ( ! empty($customresponses))
+						{
+							$writer->startElement('custom_fields');
+							foreach($customresponses as $customresponse)
+							{
+								// If we don't have an empty form response
+								if ($customresponse['field_response'] != '')
+								{
+									$writer->startElement('field');
+										$writer->startAttribute('name');
+											$writer->text($customresponse['field_name']);
+										$writer->endAttribute();
+										$writer->text($customresponse['field_response']);
+									$writer->endElement();
+								}
+							}
+							$writer->endElement();
+						}
+						break;
+					}
+				}
+				
+				// Report Media
+				$reportmedia = $incident->media;
+						
+				if (count($reportmedia) > 0)
+				{
+					$writer->startElement('media');
+					foreach ($reportmedia as $media)
+					{
+						// Videos and news links only
+						if ($media->media_type == 2 OR $media->media_type == 4)
+						{
+							$writer->startElement('item');
+									
+							// Generate media elements map 
+							$media_element_map = xml::generate_element_attribute_map($media, $media_map);
+									
+							// Generate media elements
+							xml::generate_tags($writer, $media_element_map, $media_elements);
+									
+							$writer->endAttribute();
+							$writer->text($media->media_link);
+							
+							// Close item tag
+							$writer->endElement();
+						}
+					}
+					$writer->endElement();
+				}
+				
+				// Close individual report	
+				$writer->endElement();
+			}
+		}
+		else
+		{
+			$writer->text('There are no reports on this deployment.');
+		}
+		
+		/* Close reports Element */	
+		$writer->endElement();
+
+		/* Close import tag */
+		$writer->endElement();
+
+		// Close the document
+		$writer->endDocument();
+
+		// Print
+		return $writer->outputMemory(TRUE);
+		
+	}
+
+	/**
+	 * Formats a line (passed as a fields  array) as CSV and returns the CSV as a string.
+	 * 
+	 * @param array $fields            Array of fields ie. array('Field 3','Field 2', 'Field 3')
+	 * @param String $delimiter        Field delimiter, defaults to comma ','
+	 * @param String $enclosure        Enclosure character, defaults to double-quote '"'
+	 * @param Boolean $encloseAll      Enclose all fields, even if they don't have whitespace or characters to escape
+	 * @param Boolean $nullToMysqlNull Convert null values to MySQL type String 'NULL'
+	 * @return String CSV formatted string
+	 */
+	public static function arrayToCsv( array &$fields, $delimiter = ',', $enclosure = '"', $encloseAll = TRUE, $nullToMysqlNull = FALSE )
+	{
+		$delimiter_esc = preg_quote($delimiter, '/');
+		$enclosure_esc = preg_quote($enclosure, '/');
+		
+		$output = array();
+		foreach ( $fields as $field )
+		{
+			if ($field === null && $nullToMysqlNull)
+			{
+				$output[] = 'NULL';
+				continue;
+			}
+			
+			// Enclose fields containing $delimiter, $enclosure or whitespace
+			if ( $encloseAll || preg_match( "/(?:${delimiter_esc}|${enclosure_esc}|\s)/", $field ) )
+			{
+				$output[] = $enclosure . str_replace($enclosure, $enclosure . $enclosure, $field) . $enclosure;
+			}
+			else
+			{
+				$output[] = $field;
+			}
+		}
+		
+		return implode( $delimiter, $output ) . "\r\n";
+	}
+}
+?>
\ No newline at end of file
diff --git a/application/helpers/feed.php b/application/helpers/feed.php
new file mode 100644
index 0000000..3d3e20a
--- /dev/null
+++ b/application/helpers/feed.php
@@ -0,0 +1,67 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Feed helper class.
+ * Common functions for handling news feeds
+ *
+ * $Id: valid.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Ushahidi
+ * @category   Helpers
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class feed_Core {
+
+	public static function simplepie( $feed_url = NULL )
+	{
+		if ( ! $feed_url)
+			return false;
+
+		$data = new SimplePie();
+
+		//*******************************
+		// Convert To GeoRSS feed
+		// To Disable Uncomment these 3 lines
+		//*******************************
+
+		$geocoder = new Geocoder();
+		$georss_feed = $geocoder->geocode_feed($feed_url);
+
+		if ($georss_feed == false OR empty($georss_feed))
+		{
+			// Our RSS feed pull failed, so let's grab the original RSS feed
+			$data->set_feed_url($feed_url);
+		}else{
+			// Converting our feed to GeoRSS was successful, use that data
+			$data->set_raw_data( $georss_feed );
+		}
+
+		// Uncomment Below to disable geocoding
+		//$data->set_feed_url( $feed_url );
+		//*******************************
+
+		$data->enable_cache(false);
+		$data->enable_order_by_date(true);
+		$data->init();
+		$data->handle_content_type();
+
+		return $data;
+	}
+	
+	// HT: New function to save category of feed
+	public static function save_category($post, $feed_item)
+	{
+		// Delete Previous Entries
+		ORM::factory('feed_item_category')->where('feed_item_id', $feed_item->id)->delete_all();
+	
+		foreach ($post->feed_item_category as $item)
+		{
+			$feed_item_category = new Feed_Item_Category_Model();
+			$feed_item_category->feed_item_id = $feed_item->id;
+			$feed_item_category->category_id = $item;
+			$feed_item_category->save();
+		}
+	}
+	
+}
diff --git a/application/helpers/geocode.php b/application/helpers/geocode.php
new file mode 100644
index 0000000..a46378e
--- /dev/null
+++ b/application/helpers/geocode.php
@@ -0,0 +1,302 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Geocode helper class
+ *
+ * Portions of this class credited to: zzolo, phayes, tmcw, brynbellomy, bdragon
+ *
+ * @package    Geocode
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+
+
+class geocode_Core {
+
+	/**
+	 * Geocoding function. Will receive an address and call service configured on map.geocode config
+	 *
+	 * @param 	string 				Address
+	 * @return 	string[]|boolean	Location information [country, country_id, location_name, latitude, longitude], FALSE if unsucessful
+	 *
+	 */
+	static public function geocode($address = NULL) {
+		$service = Kohana::config('map.geocode');
+
+		if ($address)
+		{
+			if (! method_exists('geocode_Core', $service)) {
+				throw new Kohana_Exception("'" . $service . "' is not a valid geocode service");
+				return FALSE;
+			}
+
+			return self::$service($address);
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Geocoding function that uses nominatin engine.
+	 *
+	 * @param 	string 				Address
+	 * @return 	string[]|boolean	Location information [country, country_id, location_name, latitude, longitude], FALSE if unsucessful
+	 *
+	 */
+	static public function nominatim($address) {
+		$payload = FALSE;
+		$result = FALSE;
+
+		$params = array(
+				"format"			=> "json",
+				"addressdetails"	=> 1,
+				"accept-language"	=> Settings_Model::get('site_language'),
+				"q"					=> $address,
+				"zoom"				=> 200
+			);
+
+		$url = "http://nominatim.openstreetmap.org/search/?" . http_build_query($params);
+
+		$url_request = new HttpClient($url);
+
+		if ($result = $url_request->execute())
+		{
+			$payload = json_decode($result);
+		}
+		else
+		{
+			Kohana::log('error', "Geocode - Nominatin\n" . $url_request->get_error_msg());
+		}
+
+		// TODO: Nomaninatins documentation on error returning is poor - this could be improved to have meaningful error messages
+		if (!$payload || count($payload) == 0)
+		{
+			return FALSE;
+		}
+
+		$result = array_shift($payload);
+
+		$country_name = isset($result->address->country) ? $result->address->country : $result->display_name;
+
+		$country = self::getCountryId($country_name);
+
+		// if we can't find the country by name, try finding it by code
+		if ($country == 0 && isset($result->address->country_code))
+		{
+			$country = self::getCountryIdByCode($result->address->country_code);
+		}
+
+		$geocodes = array(
+			'country' 			=> $country_name,
+			'country_id' 		=> $country,
+			'location_name' 	=> $result->display_name,
+			'latitude' 			=> $result->lat,
+			'longitude' 		=> $result->lon
+		);
+
+		return $geocodes;
+	}
+
+	/**
+	 * Geocoding function that uses google engine.
+	 *
+	 * @param 	string 				Address
+	 * @return 	string[]|boolean	Location information [country, country_id, location_name, latitude, longitude], FALSE if unsucessful
+	 *
+	 */
+	static public function google($address) {
+		$payload = FALSE;
+
+		$url = Kohana::config('config.external_site_protocol').'://maps.google.com/maps/api/geocode/json?sensor=false&address='.rawurlencode($address);
+		$result = FALSE;
+
+		$url_request = new HttpClient($url);
+
+		if ($result = $url_request->execute())
+		{
+			$payload = json_decode($result);
+		}
+		else
+		{
+			Kohana::log('error', "Geocode - Google\n" . $url_request->get_error_msg());
+		}
+
+		// Verify that the request succeeded
+		if (! isset($payload->status)) return FALSE;
+		if ($payload->status != 'OK')
+		{
+			if ($payload->status != 'ZERO_RESULTS')
+			{
+				// logs anything different from OK or ZERO_RESULTS
+				Kohana::log('error', "Geocode - Google: " . $payload->status);
+			}
+
+			return FALSE;
+		}
+
+		// Convert the Geocoder's results to an array
+		$all_components = json_decode(json_encode($payload->results), TRUE);
+
+		$result = array_pop($all_components);
+		$location = $result['geometry']['location'];
+
+		// Find the country
+		$address_components = $result['address_components'];
+		$country_name = NULL;
+		foreach ($address_components as $component)
+		{
+			if (in_array('country', $component['types']))
+			{
+				$country_name  = $component['long_name'];
+				break;
+			}
+		}
+
+		// If no country has been found, use the formatted address
+		if (empty($country_name))
+		{
+			$country_name = $result['formatted_address'];
+		}
+
+		$geocodes = array(
+			'country' 			=> $country_name,
+			'country_id' 		=> self::getCountryId($country_name),
+			'location_name' 	=> $result['formatted_address'],
+			'latitude' 			=> $location['lat'],
+			'longitude' 		=> $location['lng']
+		);
+
+		return $geocodes;
+	}
+
+	/**
+	 * Finds country on deployment database by name
+	 * @param 	string 	Country Name
+	 * @return 	int 	Country Id if exists, 0 if not
+	 *
+	 */
+	static function getCountryId($country_name) {
+		// Grab country_id
+		$country = Country_Model::get_country_by_name($country_name);
+		return ( ! empty($country) AND $country->loaded)? $country->id : 0;
+	}
+
+	/**
+	 * Finds country on deployment database by code
+	 * @param 	string 	Country Name
+	 * @return 	int 	Country Id if exists, 0 if not
+	 *
+	 */
+	static function getCountryIdByCode($country_code) {
+		// Grab country_id
+		$country = Country_Model::get_country_by_code($country_code);
+		return ( ! empty($country) AND $country->loaded)? $country->id : 0;
+	}
+
+
+	/**
+	 * Reverse Geocode a point
+	 *
+	 * @author
+	 * @param   double  $latitude
+	 * @param   double  $longitude
+	 * @return  string  closest approximation of the point as a display name
+	 */
+	static function reverseGeocode($latitude, $longitude) {
+		$service = Kohana::config('map.geocode');
+        
+		if ($latitude && $longitude)
+		{
+			$function = "reverse" . ucfirst($service);
+
+			if (! method_exists('geocode_Core', $function)) {
+				throw new Kohana_Exception("'" . $service . "' is not a valid geocode service");
+				return FALSE;
+			}
+
+			return self::$function($latitude, $longitude);
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Reverse Geocode a point using Nominatin
+	 *
+	 * @author
+	 * @param   double  $latitude
+	 * @param   double  $longitude
+	 * @return  string  closest approximation of the point as a display name
+	 */
+	static function reverseNominatim($lat, $lng) {
+		if ($lat && $lng)
+		{
+			$url = 'http://nominatim.openstreetmap.org/reverse?format=json&lat=' . $lat . '&lon=' . $lng;
+
+			$request = new HttpClient($url);
+			if ( ! $json = $request->execute()) {
+				Kohana::log('error', "Geocode - reverseNominatin\n" . $url_request->get_error_msg());
+
+				return FALSE;
+			}
+
+			$location = json_decode($json, FALSE);
+            
+			return $location->display_name;
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Reverse Geocode a point using Google Geocode
+	 *
+	 * @author
+	 * @param   double  $latitude
+	 * @param   double  $longitude
+	 * @return  string  closest approximation of the point as a display name
+	 */
+	static function reverseGoogle($lat, $lng) {
+		if ($lat && $lng)
+		{
+			$url = Kohana::config('config.external_site_protocol') . '://maps.googleapis.com/maps/api/geocode/json?sensor=false&latlng=' . $lat . "," . $lng;
+
+			$request = new HttpClient($url);
+			if ( ! $json = $request->execute()) {
+				Kohana::log('error', "Geocode - reverseGoogle\n" . $url . "\n" . $request->get_error_msg());
+
+				return FALSE;
+			}
+
+			$location = json_decode($json);
+
+			if ($location->status != 'OK')
+			{
+				// logs anything different from OK
+				Kohana::log('error', "Geocode - reverseGoogle: " . $location->status . " - " . $location->error_message);
+
+				return FALSE;
+			}
+
+			if (count($location->results) == 0)
+			{
+				return FALSE;
+			}
+
+			return $location->results[0]->formatted_address;
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+
+
+}
\ No newline at end of file
diff --git a/application/helpers/javascriptpacker.php b/application/helpers/javascriptpacker.php
new file mode 100644
index 0000000..c4edb48
--- /dev/null
+++ b/application/helpers/javascriptpacker.php
@@ -0,0 +1,740 @@
+<?php
+/* 9 April 2008. version 1.1
+ * 
+ * This is the php version of the Dean Edwards JavaScript's Packer,
+ * Based on :
+ * 
+ * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards
+ * a multi-pattern parser.
+ * KNOWN BUG: erroneous behavior when using escapeChar with a replacement
+ * value that is a function
+ * 
+ * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards
+ * 
+ * License: http://creativecommons.org/licenses/LGPL/2.1/
+ * 
+ * Ported to PHP by Nicolas Martin.
+ * 
+ * ----------------------------------------------------------------------
+ * changelog:
+ * 1.1 : correct a bug, '\0' packed then unpacked becomes '\'.
+ * ----------------------------------------------------------------------
+ * 
+ * examples of usage :
+ * $myPacker = new JavaScriptPacker($script, 62, true, false);
+ * $packed = $myPacker->pack();
+ * 
+ * or
+ * 
+ * $myPacker = new JavaScriptPacker($script, 'Normal', true, false);
+ * $packed = $myPacker->pack();
+ * 
+ * or (default values)
+ * 
+ * $myPacker = new JavaScriptPacker($script);
+ * $packed = $myPacker->pack();
+ * 
+ * 
+ * params of the constructor :
+ * $script:       the JavaScript to pack, string.
+ * $encoding:     level of encoding, int or string :
+ *                0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'.
+ *                default: 62.
+ * $fastDecode:   include the fast decoder in the packed result, boolean.
+ *                default : true.
+ * $specialChars: if you are flagged your private and local variables
+ *                in the script, boolean.
+ *                default: false.
+ * 
+ * The pack() method return the compressed JavasScript, as a string.
+ * 
+ * see http://dean.edwards.name/packer/usage/ for more information.
+ * 
+ * Notes :
+ * # need PHP 5 . Tested with PHP 5.1.2, 5.1.3, 5.1.4, 5.2.3
+ * 
+ * # The packed result may be different than with the Dean Edwards
+ *   version, but with the same length. The reason is that the PHP
+ *   function usort to sort array don't necessarily preserve the
+ *   original order of two equal member. The Javascript sort function
+ *   in fact preserve this order (but that's not require by the
+ *   ECMAScript standard). So the encoded keywords order can be
+ *   different in the two results.
+ * 
+ * # Be careful with the 'High ASCII' Level encoding if you use
+ *   UTF-8 in your files... 
+ */
+
+
+class JavaScriptPacker_Core {
+	// constants
+	const IGNORE = '$1';
+
+	// validate parameters
+	private $_script = '';
+	private $_encoding = 62;
+	private $_fastDecode = true;
+	private $_specialChars = false;
+	
+	private $LITERAL_ENCODING = array(
+		'None' => 0,
+		'Numeric' => 10,
+		'Normal' => 62,
+		'High ASCII' => 95
+	);
+	
+	public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false)
+	{
+		$this->_script = $_script . "\n";
+		if (array_key_exists($_encoding, $this->LITERAL_ENCODING))
+			$_encoding = $this->LITERAL_ENCODING[$_encoding];
+		$this->_encoding = min((int)$_encoding, 95);
+		$this->_fastDecode = $_fastDecode;	
+		$this->_specialChars = $_specialChars;
+	}
+	
+	public function pack() {
+		$this->_addParser('_basicCompression');
+		if ($this->_specialChars)
+			$this->_addParser('_encodeSpecialChars');
+		if ($this->_encoding)
+			$this->_addParser('_encodeKeywords');
+		
+		// go!
+		return $this->_pack($this->_script);
+	}
+	
+	// apply all parsing routines
+	private function _pack($script) {
+		for ($i = 0; isset($this->_parsers[$i]); $i++) {
+			$script = call_user_func(array(&$this,$this->_parsers[$i]), $script);
+		}
+		return $script;
+	}
+	
+	// keep a list of parsing functions, they'll be executed all at once
+	private $_parsers = array();
+	private function _addParser($parser) {
+		$this->_parsers[] = $parser;
+	}
+	
+	// zero encoding - just removal of white space and comments
+	private function _basicCompression($script) {
+		$parser = new ParseMaster();
+		// make safe
+		$parser->escapeChar = '\\';
+		// protect strings
+		$parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE);
+		$parser->add('/"[^"\\n\\r]*"/', self::IGNORE);
+		// remove comments
+		$parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ');
+		$parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
+		// protect regular expressions
+		$parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
+		$parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE);
+		// remove: ;;; doSomething();
+		if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
+		// remove redundant semi-colons
+		$parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops
+		$parser->add('/;+\\s*([};])/', '$2');
+		// apply the above
+		$script = $parser->exec($script);
+
+		// remove white-space
+		$parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
+		$parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
+		$parser->add('/\\s+/', '');
+		// done
+		return $parser->exec($script);
+	}
+	
+	private function _encodeSpecialChars($script) {
+		$parser = new ParseMaster();
+		// replace: $name -> n, $$name -> na
+		$parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
+					 array('fn' => '_replace_name')
+		);
+		// replace: _name -> _0, double-underscore (__name) is ignored
+		$regexp = '/\\b_[A-Za-z\\d]\\w*/';
+		// build the word list
+		$keywords = $this->_analyze($script, $regexp, '_encodePrivate');
+		// quick ref
+		$encoded = $keywords['encoded'];
+		
+		$parser->add($regexp,
+			array(
+				'fn' => '_replace_encoded',
+				'data' => $encoded
+			)
+		);
+		return $parser->exec($script);
+	}
+	
+	private function _encodeKeywords($script) {
+		// escape high-ascii values already in the script (i.e. in strings)
+		if ($this->_encoding > 62)
+			$script = $this->_escape95($script);
+		// create the parser
+		$parser = new ParseMaster();
+		$encode = $this->_getEncoder($this->_encoding);
+		// for high-ascii, don't encode single character low-ascii
+		$regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
+		// build the word list
+		$keywords = $this->_analyze($script, $regexp, $encode);
+		$encoded = $keywords['encoded'];
+		
+		// encode
+		$parser->add($regexp,
+			array(
+				'fn' => '_replace_encoded',
+				'data' => $encoded
+			)
+		);
+		if (empty($script)) return $script;
+		else {
+			//$res = $parser->exec($script);
+			//$res = $this->_bootStrap($res, $keywords);
+			//return $res;
+			return $this->_bootStrap($parser->exec($script), $keywords);
+		}
+	}
+	
+	private function _analyze($script, $regexp, $encode) {
+		// analyse
+		// retreive all words in the script
+		$all = array();
+		preg_match_all($regexp, $script, $all);
+		$_sorted = array(); // list of words sorted by frequency
+		$_encoded = array(); // dictionary of word->encoding
+		$_protected = array(); // instances of "protected" words
+		$all = $all[0]; // simulate the javascript comportement of global match
+		if (!empty($all)) {
+			$unsorted = array(); // same list, not sorted
+			$protected = array(); // "protected" words (dictionary of word->"word")
+			$value = array(); // dictionary of charCode->encoding (eg. 256->ff)
+			$this->_count = array(); // word->count
+			$i = count($all); $j = 0; //$word = null;
+			// count the occurrences - used for sorting later
+			do {
+				--$i;
+				$word = '$' . $all[$i];
+				if (!isset($this->_count[$word])) {
+					$this->_count[$word] = 0;
+					$unsorted[$j] = $word;
+					// make a dictionary of all of the protected words in this script
+					//  these are words that might be mistaken for encoding
+					//if (is_string($encode) && method_exists($this, $encode))
+					$values[$j] = call_user_func(array(&$this, $encode), $j);
+					$protected['$' . $values[$j]] = $j++;
+				}
+				// increment the word counter
+				$this->_count[$word]++;
+			} while ($i > 0);
+			// prepare to sort the word list, first we must protect
+			//  words that are also used as codes. we assign them a code
+			//  equivalent to the word itself.
+			// e.g. if "do" falls within our encoding range
+			//      then we store keywords["do"] = "do";
+			// this avoids problems when decoding
+			$i = count($unsorted);
+			do {
+				$word = $unsorted[--$i];
+				if (isset($protected[$word]) /*!= null*/) {
+					$_sorted[$protected[$word]] = substr($word, 1);
+					$_protected[$protected[$word]] = true;
+					$this->_count[$word] = 0;
+				}
+			} while ($i);
+			
+			// sort the words by frequency
+			// Note: the javascript and php version of sort can be different :
+			// in php manual, usort :
+			// " If two members compare as equal,
+			// their order in the sorted array is undefined."
+			// so the final packed script is different of the Dean's javascript version
+			// but equivalent.
+			// the ECMAscript standard does not guarantee this behaviour,
+			// and thus not all browsers (e.g. Mozilla versions dating back to at
+			// least 2003) respect this. 
+			usort($unsorted, array(&$this, '_sortWords'));
+			$j = 0;
+			// because there are "protected" words in the list
+			//  we must add the sorted words around them
+			do {
+				if (!isset($_sorted[$i]))
+					$_sorted[$i] = substr($unsorted[$j++], 1);
+				$_encoded[$_sorted[$i]] = $values[$i];
+			} while (++$i < count($unsorted));
+		}
+		return array(
+			'sorted'  => $_sorted,
+			'encoded' => $_encoded,
+			'protected' => $_protected);
+	}
+	
+	private $_count = array();
+	private function _sortWords($match1, $match2) {
+		return $this->_count[$match2] - $this->_count[$match1];
+	}
+	
+	// build the boot function used for loading and decoding
+	private function _bootStrap($packed, $keywords) {
+		$ENCODE = $this->_safeRegExp('$encode\\($count\\)');
+
+		// $packed: the packed script
+		$packed = "'" . $this->_escape($packed) . "'";
+
+		// $ascii: base for encoding
+		$ascii = min(count($keywords['sorted']), $this->_encoding);
+		if ($ascii == 0) $ascii = 1;
+
+		// $count: number of words contained in the script
+		$count = count($keywords['sorted']);
+
+		// $keywords: list of words contained in the script
+		foreach ($keywords['protected'] as $i=>$value) {
+			$keywords['sorted'][$i] = '';
+		}
+		// convert from a string to an array
+		ksort($keywords['sorted']);
+		$keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')";
+
+		$encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
+		$encode = $this->_getJSFunction($encode);
+		$encode = preg_replace('/_encoding/','$ascii', $encode);
+		$encode = preg_replace('/arguments\\.callee/','$encode', $encode);
+		$inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : '');
+
+		// $decode: code snippet to speed up decoding
+		if ($this->_fastDecode) {
+			// create the decoder
+			$decode = $this->_getJSFunction('_decodeBody');
+			if ($this->_encoding > 62)
+				$decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
+			// perform the encoding inline for lower ascii values
+			elseif ($ascii < 36)
+				$decode = preg_replace($ENCODE, $inline, $decode);
+			// special case: when $count==0 there are no keywords. I want to keep
+			//  the basic shape of the unpacking funcion so i'll frig the code...
+			if ($count == 0)
+				$decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
+		}
+
+		// boot function
+		$unpack = $this->_getJSFunction('_unpack');
+		if ($this->_fastDecode) {
+			// insert the decoder
+			$this->buffer = $decode;
+			$unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1);
+		}
+		$unpack = preg_replace('/"/', "'", $unpack);
+		if ($this->_encoding > 62) { // high-ascii
+			// get rid of the word-boundaries for regexp matches
+			$unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
+		}
+		if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
+			// insert the encode function
+			$this->buffer = $encode;
+			$unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1);
+		} else {
+			// perform the encoding inline
+			$unpack = preg_replace($ENCODE, $inline, $unpack);
+		}
+		// pack the boot function too
+		$unpackPacker = new JavaScriptPacker($unpack, 0, false, true);
+		$unpack = $unpackPacker->pack();
+		
+		// arguments
+		$params = array($packed, $ascii, $count, $keywords);
+		if ($this->_fastDecode) {
+			$params[] = 0;
+			$params[] = '{}';
+		}
+		$params = implode(',', $params);
+		
+		// the whole thing
+		return 'eval(' . $unpack . '(' . $params . "))\n";
+	}
+	
+	private $buffer;
+	private function _insertFastDecode($match) {
+		return '{' . $this->buffer . ';';
+	}
+	private function _insertFastEncode($match) {
+		return '{$encode=' . $this->buffer . ';';
+	}
+	
+	// mmm.. ..which one do i need ??
+	private function _getEncoder($ascii) {
+		return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
+		       '_encode95' : '_encode62' : '_encode36' : '_encode10';
+	}
+	
+	// zero encoding
+	// characters: 0123456789
+	private function _encode10($charCode) {
+		return $charCode;
+	}
+	
+	// inherent base36 support
+	// characters: 0123456789abcdefghijklmnopqrstuvwxyz
+	private function _encode36($charCode) {
+		return base_convert($charCode, 10, 36);
+	}
+	
+	// hitch a ride on base36 and add the upper case alpha characters
+	// characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+	private function _encode62($charCode) {
+		$res = '';
+		if ($charCode >= $this->_encoding) {
+			$res = $this->_encode62((int)($charCode / $this->_encoding));
+		}
+		$charCode = $charCode % $this->_encoding;
+		
+		if ($charCode > 35)
+			return $res . chr($charCode + 29);
+		else
+			return $res . base_convert($charCode, 10, 36);
+	}
+	
+	// use high-ascii values
+	// characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
+	private function _encode95($charCode) {
+		$res = '';
+		if ($charCode >= $this->_encoding)
+			$res = $this->_encode95($charCode / $this->_encoding);
+		
+		return $res . chr(($charCode % $this->_encoding) + 161);
+	}
+	
+	private function _safeRegExp($string) {
+		return '/'.preg_replace('/\$/', '\\\$', $string).'/';
+	}
+	
+	private function _encodePrivate($charCode) {
+		return "_" . $charCode;
+	}
+	
+	// protect characters used by the parser
+	private function _escape($script) {
+		return preg_replace('/([\\\\\'])/', '\\\$1', $script);
+	}
+	
+	// protect high-ascii characters already in the script
+	private function _escape95($script) {
+		return preg_replace_callback(
+			'/[\\xa1-\\xff]/',
+			array(&$this, '_escape95Bis'),
+			$script
+		);
+	}
+	private function _escape95Bis($match) {
+		return '\x'.((string)dechex(ord($match)));
+	}
+	
+	
+	private function _getJSFunction($aName) {
+		if (defined('self::JSFUNCTION'.$aName))
+			return constant('self::JSFUNCTION'.$aName);
+		else 
+			return '';
+	}
+	
+	// JavaScript Functions used.
+	// Note : In Dean's version, these functions are converted
+	// with 'String(aFunctionName);'.
+	// This internal conversion complete the original code, ex :
+	// 'while (aBool) anAction();' is converted to
+	// 'while (aBool) { anAction(); }'.
+	// The JavaScript functions below are corrected.
+	
+	// unpacking function - this is the boot strap function
+	//  data extracted from this packing routine is passed to
+	//  this function when decoded in the target
+	// NOTE ! : without the ';' final.
+	const JSFUNCTION_unpack =
+
+'function($packed, $ascii, $count, $keywords, $encode, $decode) {
+    while ($count--) {
+        if ($keywords[$count]) {
+            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
+        }
+    }
+    return $packed;
+}';
+/*
+'function($packed, $ascii, $count, $keywords, $encode, $decode) {
+    while ($count--)
+        if ($keywords[$count])
+            $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
+    return $packed;
+}';
+*/
+	
+	// code-snippet inserted into the unpacker to speed up decoding
+	const JSFUNCTION_decodeBody =
+//_decode = function() {
+// does the browser support String.replace where the
+//  replacement value is a function?
+
+'    if (!\'\'.replace(/^/, String)) {
+        // decode all the values we need
+        while ($count--) {
+            $decode[$encode($count)] = $keywords[$count] || $encode($count);
+        }
+        // global replacement function
+        $keywords = [function ($encoded) {return $decode[$encoded]}];
+        // generic match
+        $encode = function () {return \'\\\\w+\'};
+        // reset the loop counter -  we are now doing a global replace
+        $count = 1;
+    }
+';
+//};
+/*
+'	if (!\'\'.replace(/^/, String)) {
+        // decode all the values we need
+        while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
+        // global replacement function
+        $keywords = [function ($encoded) {return $decode[$encoded]}];
+        // generic match
+        $encode = function () {return\'\\\\w+\'};
+        // reset the loop counter -  we are now doing a global replace
+        $count = 1;
+    }';
+*/
+	
+	 // zero encoding
+	 // characters: 0123456789
+	 const JSFUNCTION_encode10 =
+'function($charCode) {
+    return $charCode;
+}';//;';
+	
+	 // inherent base36 support
+	 // characters: 0123456789abcdefghijklmnopqrstuvwxyz
+	 const JSFUNCTION_encode36 =
+'function($charCode) {
+    return $charCode.toString(36);
+}';//;';
+	
+	// hitch a ride on base36 and add the upper case alpha characters
+	// characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+	const JSFUNCTION_encode62 =
+'function($charCode) {
+    return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
+    (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
+}';
+	
+	// use high-ascii values
+	// characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
+	const JSFUNCTION_encode95 =
+'function($charCode) {
+    return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
+        String.fromCharCode($charCode % _encoding + 161);
+}'; 
+	
+}
+
+
+class ParseMaster {
+	public $ignoreCase = false;
+	public $escapeChar = '';
+	
+	// constants
+	const EXPRESSION = 0;
+	const REPLACEMENT = 1;
+	const LENGTH = 2;
+	
+	// used to determine nesting levels
+	private $GROUPS = '/\\(/';//g
+	private $SUB_REPLACE = '/\\$\\d/';
+	private $INDEXED = '/^\\$\\d+$/';
+	private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
+	private $ESCAPE = '/\\\./';//g
+	private $QUOTE = '/\'/';
+	private $DELETED = '/\\x01[^\\x01]*\\x01/';//g
+	
+	public function add($expression, $replacement = '') {
+		// count the number of sub-expressions
+		//  - add one because each pattern is itself a sub-expression
+		$length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out);
+		
+		// treat only strings $replacement
+		if (is_string($replacement)) {
+			// does the pattern deal with sub-expressions?
+			if (preg_match($this->SUB_REPLACE, $replacement)) {
+				// a simple lookup? (e.g. "$2")
+				if (preg_match($this->INDEXED, $replacement)) {
+					// store the index (used for fast retrieval of matched strings)
+					$replacement = (int)(substr($replacement, 1)) - 1;
+				} else { // a complicated lookup (e.g. "Hello $2 $1")
+					// build a function to do the lookup
+					$quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
+					         ? '"' : "'";
+					$replacement = array(
+						'fn' => '_backReferences',
+						'data' => array(
+							'replacement' => $replacement,
+							'length' => $length,
+							'quote' => $quote
+						)
+					);
+				}
+			}
+		}
+		// pass the modified arguments
+		if (!empty($expression)) $this->_add($expression, $replacement, $length);
+		else $this->_add('/^$/', $replacement, $length);
+	}
+	
+	public function exec($string) {
+		// execute the global replacement
+		$this->_escaped = array();
+		
+		// simulate the _patterns.toSTring of Dean
+		$regexp = '/';
+		foreach ($this->_patterns as $reg) {
+			$regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|';
+		}
+		$regexp = substr($regexp, 0, -1) . '/';
+		$regexp .= ($this->ignoreCase) ? 'i' : '';
+		
+		$string = $this->_escape($string, $this->escapeChar);
+		$string = preg_replace_callback(
+			$regexp,
+			array(
+				&$this,
+				'_replacement'
+			),
+			$string
+		);
+		$string = $this->_unescape($string, $this->escapeChar);
+		
+		return preg_replace($this->DELETED, '', $string);
+	}
+		
+	public function reset() {
+		// clear the patterns collection so that this object may be re-used
+		$this->_patterns = array();
+	}
+
+	// private
+	private $_escaped = array();  // escaped characters
+	private $_patterns = array(); // patterns stored by index
+	
+	// create and add a new pattern to the patterns collection
+	private function _add() {
+		$arguments = func_get_args();
+		$this->_patterns[] = $arguments;
+	}
+	
+	// this is the global replace function (it's quite complicated)
+	private function _replacement($arguments) {
+		if (empty($arguments)) return '';
+		
+		$i = 1; $j = 0;
+		// loop through the patterns
+		while (isset($this->_patterns[$j])) {
+			$pattern = $this->_patterns[$j++];
+			// do we have a result?
+			if (isset($arguments[$i]) && ($arguments[$i] != '')) {
+				$replacement = $pattern[self::REPLACEMENT];
+				
+				if (is_array($replacement) && isset($replacement['fn'])) {
+					
+					if (isset($replacement['data'])) $this->buffer = $replacement['data'];
+					return call_user_func(array(&$this, $replacement['fn']), $arguments, $i);
+					
+				} elseif (is_int($replacement)) {
+					return $arguments[$replacement + $i];
+				
+				}
+				$delete = ($this->escapeChar == '' ||
+				           strpos($arguments[$i], $this->escapeChar) === false)
+				        ? '' : "\x01" . $arguments[$i] . "\x01";
+				return $delete . $replacement;
+			
+			// skip over references to sub-expressions
+			} else {
+				$i += $pattern[self::LENGTH];
+			}
+		}
+	}
+	
+	private function _backReferences($match, $offset) {
+		$replacement = $this->buffer['replacement'];
+		$quote = $this->buffer['quote'];
+		$i = $this->buffer['length'];
+		while ($i) {
+			$replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
+		}
+		return $replacement;
+	}
+	
+	private function _replace_name($match, $offset){
+		$length = strlen($match[$offset + 2]);
+		$start = $length - max($length - strlen($match[$offset + 3]), 0);
+		return substr($match[$offset + 1], $start, $length) . $match[$offset + 4];
+	}
+	
+	private function _replace_encoded($match, $offset) {
+		return $this->buffer[$match[$offset]];
+	}
+	
+	
+	// php : we cannot pass additional data to preg_replace_callback,
+	// and we cannot use &$this in create_function, so let's go to lower level
+	private $buffer;
+	
+	// encode escaped characters
+	private function _escape($string, $escapeChar) {
+		if ($escapeChar) {
+			$this->buffer = $escapeChar;
+			return preg_replace_callback(
+				'/\\' . $escapeChar . '(.)' .'/',
+				array(&$this, '_escapeBis'),
+				$string
+			);
+			
+		} else {
+			return $string;
+		}
+	}
+	private function _escapeBis($match) {
+		$this->_escaped[] = $match[1];
+		return $this->buffer;
+	}
+	
+	// decode escaped characters
+	private function _unescape($string, $escapeChar) {
+		if ($escapeChar) {
+			$regexp = '/'.'\\'.$escapeChar.'/';
+			$this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0);
+			return preg_replace_callback
+			(
+				$regexp,
+				array(&$this, '_unescapeBis'),
+				$string
+			);
+			
+		} else {
+			return $string;
+		}
+	}
+	private function _unescapeBis() {
+		if (isset($this->_escaped[$this->buffer['i']])
+			&& $this->_escaped[$this->buffer['i']] != '')
+		{
+			 $temp = $this->_escaped[$this->buffer['i']];
+		} else {
+			$temp = '';
+		}
+		$this->buffer['i']++;
+		return $this->buffer['escapeChar'] . $temp;
+	}
+	
+	private function _internalEscape($string) {
+		return preg_replace($this->ESCAPE, '', $string);
+	}
+}
\ No newline at end of file
diff --git a/application/helpers/map.php b/application/helpers/map.php
new file mode 100644
index 0000000..f5499ee
--- /dev/null
+++ b/application/helpers/map.php
@@ -0,0 +1,518 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Map helper class
+ *
+ * Portions of this class credited to: zzolo, phayes, tmcw, brynbellomy, bdragon
+ *
+ * @package    Map
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+
+
+class map_Core {
+
+	/**
+	 * Generate the Javascript for Each Layer
+	 * .. new OpenLayers.Layer.XXXX
+	 * if $all is set to TRUE all maps are rendered
+	 * **caveat is that each mapping api js must be loaded**
+	 *
+	 * @param   bool  $all
+	 * @return  string $js
+	 */
+	public static function layers_js($all = FALSE)
+	{
+		// Javascript
+		$js = "";
+
+		// Get All Layers
+		$layers = map::base();
+
+		// Next get the default base layer
+		$default_map = Kohana::config('settings.default_map');
+
+		if ( ! isset($layers[$default_map]))
+		{ // Map Layer Doesn't Exist - default to google
+			$default_map = "osm_mapnik";
+		}
+
+		// Get OpenLayers type
+		$openlayers_type = $layers[$default_map]->openlayers;
+
+		// To store options for the bing maps
+		foreach ($layers as $layer)
+		{
+			if ($layer->active)
+			{
+
+				if ($all == TRUE OR $layer->openlayers == $openlayers_type)
+				{
+					//++ Bing doesn't have the first argument
+					if ($layer->openlayers == "Bing")
+					{
+						// Options for the Bing layer constructor
+						$bing_options = "{\n"
+						    . "\t name: \"".$layer->data['name']."\",\n"
+						    . "\t type: \"".$layer->data['type']."\",\n"
+						    . "\t key: \"".$layer->data['key']."\"\n"
+						    . "}";
+						
+						$js .= "var ".$layer->name." = new OpenLayers.Layer.".$layer->openlayers."($bing_options);\n\n";
+					}
+					// Allow layers to specify a custom set of OpenLayers options
+					// this should allow plugins to add OpenLayers Layer types we haven't considered here
+					// See http://dev.openlayers.org/docs/files/OpenLayers/Layer-js.html for other layer types
+					elseif (isset($layer->openlayers_options) AND $layer->openlayers_options != null)
+					{
+						$js .= "var ".$layer->name." = new OpenLayers.Layer.{$layer->openlayers}({$layer->openlayers_options});\n\n";
+					}
+					// Finally construct JS for the majority of layers
+					else
+					{
+						$js .= "var ".$layer->name." = new OpenLayers.Layer.".$layer->openlayers."(\"".$layer->title."\", ";
+
+						if ($layer->openlayers == 'XYZ' || $layer->openlayers == 'WMS' || $layer->openlayers == 'TMS')
+						{
+							if (isset($layer->data['url']))
+							{
+								$js .= '"'.$layer->data['url'].'", ';
+							}
+						}
+
+						// Extra parameter used by WMS - key/value pairs representing the GetMap query string
+						if ($layer->openlayers == 'WMS' AND isset($layer->wms_params))
+						{
+							// Add some unnescessary params so that json_encode creates an object not an array.
+							if (!isset($layer->wms_params['styles'])) $layer->wms_params['styles'] = '';
+							if (!isset($layer->wms_params['layers'])) $layer->wms_params['layers'] = '';
+							
+							$js .= json_encode($layer->wms_params);
+							$js .= ', ';
+						}
+	
+						$js .= "{ \n";
+
+						$params = $layer->data;
+						if (isset($params['url'])) unset($params['url']);
+						if (isset($params['baselayer'])) unset($params['baselayer']);
+						$params['sphericalMercator'] = true;
+
+						// Special handling for layer type - don't quote the value as it should be a js variable
+						if (isset($params['type']) AND $params['type'] != '')
+						{
+							$js .= " type: ".$params['type'].",\n";
+							unset($params['type']);
+						}
+
+						foreach ($params as $key => $value)
+						{
+							if ( ! empty($value))
+							{
+								$js .= " ".$key.": ".json_encode($value).",\n";
+							}
+						}
+
+						$js .= " maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34)});\n\n";
+					}
+					
+				}
+			}
+		}
+
+		// Hack on XYZ / Esri Attribution layer here since XYZ doesn't support images in attribution
+		if (stripos($default_map,'esri_') !== FALSE AND $all == FALSE)
+		{
+			// We have two div ids that we use for maps, map and divMap. We need a more permanent
+			//   solution to cover any div name.
+			// $js .= "if ( $(\"#map\").length > 0 ) { divName = \"map\" }else{ divName= \"divMap\" }"
+			// 	. "var esriAttributionDiv = document.createElement('div');"
+			//     . "$(esriAttributionDiv).html('"
+			// 	. "<img src=\"http://www.arcgis.com/home/images/map/logo-sm.png\" style=\"float:right;\"/>"
+			// 	. "<small style=\"position: absolute; bottom: -10px;\">"
+			// 	. "Sources: Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, "
+			// 	. "GeoBase, IGN, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), "
+			// 	. "and the GIS User Community"
+			// 	. "</small>');\n"
+			//     . "$(esriAttributionDiv).css({\n"
+			//     . "\t'position': 'absolute',\n"
+			//     . "\t'z-index': 10000,\n"
+			//     . "\t'margin': '-40px 0 0 85px',\n"
+			//     . "\t'right': $('div#'+divName).offset().right + 10,\n"
+			//     . "\t'width': $('div#'+divName).width() - 90,\n"
+			//     . "});\n"
+			// 	. "$(esriAttributionDiv).appendTo($('div#'+divName));";
+		}
+		
+		Event::run('ushahidi_filter.map_layers_js', $js);
+
+		return $js;
+	}
+
+	/**
+	 * Generate the Map Array.
+	 * These are the maps that show up in the Layer Switcher
+	 * if $all is set to TRUE all maps are rendered
+	 * **caveat is that each mapping api js must be loaded **
+	 *
+	 * @param   bool  $all
+	 * @return  string $js
+	 */
+	public static function layers_array($all = FALSE)
+	{
+		// Javascript
+		$js = "[";
+
+		// Get All Layers
+		$layers = map::base();
+
+		// Next get the default base layer
+		$default_map = Kohana::config('settings.default_map');
+
+		if ( ! isset($layers[$default_map]))
+		{
+			// Map Layer Doesn't Exist - default to google
+			$default_map = "google_normal";
+		}
+
+		// Get openlayers type
+		$openlayers_type = $layers[$default_map]->openlayers;
+		$js .= $default_map;
+		foreach ($layers as $layer)
+		{
+			if ($layer->name != $default_map AND $layer->active)
+			{
+				if ($all == TRUE OR $layer->openlayers == $openlayers_type)
+				{
+					$js .= ",".$layer->name;
+				}
+			}
+		}
+		$js .= "]";
+
+		return $js;
+	}
+
+	/**
+	 * Generate the Map Base Layer Object
+	 * If a layer name is passed, it will return only the object
+	 * for that layer
+	 *
+	 * @author
+	 * @param   string  $layer_name
+	 * @return  string $js
+	 */
+	public static function base($layer_name = NULL)
+	{
+		$layers = array();
+
+		// Esri Topo
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'esri_topo';
+		$layer->openlayers = "XYZ";
+		$layer->title = 'Esri World Topo Map';
+		$layer->description = 'This world topographic map (aka "the community basemap") includes boundaries, cities, water features, physiographic features, parks, landmarks, transportation, and buildings.';
+		$layer->api_url = '';
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'attribution' => '',
+			'url' => Kohana::config('core.site_protocol').'://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/${z}/${y}/${x}',
+			'type' => '',
+			'transitionEffect' => 'resize',
+		);
+		$layers[$layer->name] = $layer;
+
+		// Esri Street Map
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'esri_street';
+		$layer->openlayers = "XYZ";
+		$layer->title = 'Esri Street Map';
+		$layer->description = 'This map service presents highway-level data for the world and street-level data for North America, Europe, Southern Africa, parts of Asia, and more.';
+		$layer->api_url = '';
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'attribution' => '',
+			'url' => Kohana::config('core.site_protocol').'://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/${z}/${y}/${x}',
+			'type' => '',
+			'transitionEffect' => 'resize',
+		);
+		$layers[$layer->name] = $layer;
+
+		// Esri Imagery
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'esri_imagery';
+		$layer->openlayers = "XYZ";
+		$layer->title = 'Esri Imagery Map';
+		$layer->description = 'This map service presents satellite imagery for the world and high-resolution imagery for the United States, Great Britain, and hundreds of cities around the world.';
+		$layer->api_url = '';
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'attribution' => '',
+			'url' => Kohana::config('core.site_protocol').'://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/${z}/${y}/${x}',
+			'type' => '',
+			'transitionEffect' => 'resize',
+		);
+		$layers[$layer->name] = $layer;
+
+		// Esri National Geographic
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'esri_natgeo';
+		$layer->openlayers = "XYZ";
+		$layer->title = 'Esri National Geographic Map';
+		$layer->description = 'This map is designed to be used as a general reference map for informational and educational purposes as well as a basemap by GIS professionals and other users for creating web maps and web mapping applications.';
+		$layer->api_url = '';
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'attribution' => '',
+			'url' => Kohana::config('core.site_protocol').'://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/${z}/${y}/${x}',
+			'type' => '',
+			'transitionEffect' => 'resize',
+		);
+		$layers[$layer->name] = $layer;
+
+		// GOOGLE Satellite
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'google_satellite';
+		$layer->openlayers = "Google";
+		$layer->title = 'Google Maps Satellite';
+		$layer->description = 'Google Maps Satellite Imagery.';
+		$layer->api_url = 'https://maps.google.com/maps/api/js?v=3.7&sensor=false&language='.Kohana::config('locale.language.0');
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'type' => 'google.maps.MapTypeId.SATELLITE',
+			'animationEnabled' => TRUE,
+		);
+		$layers[$layer->name] = $layer;
+
+		// GOOGLE Hybrid
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'google_hybrid';
+		$layer->openlayers = "Google";
+		$layer->title = 'Google Maps Hybrid';
+		$layer->description = 'Google Maps with roads and terrain.';
+		$layer->api_url = 'https://maps.google.com/maps/api/js?v=3.7&sensor=false&language='.Kohana::config('locale.language.0');
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'type' => 'google.maps.MapTypeId.HYBRID',
+			'animationEnabled' => TRUE,
+		);
+		$layers[$layer->name] = $layer;
+
+		// GOOGLE Normal
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'google_normal';
+		$layer->openlayers = "Google";
+		$layer->title = 'Google Maps Normal';
+		$layer->description = 'Standard Google Maps Roads';
+		$layer->api_url = 'https://maps.google.com/maps/api/js?v=3.7&sensor=false&language='.Kohana::config('locale.language.0');
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'type' => '',
+			'animationEnabled' => TRUE,
+		);
+		$layers[$layer->name] = $layer;
+
+		// GOOGLE Physical
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'google_physical';
+		$layer->openlayers = "Google";
+		$layer->title = 'Google Maps Physical';
+		$layer->description = 'Google Maps Hillshades';
+		$layer->api_url = 'https://maps.google.com/maps/api/js?v=3.7&sensor=false&language='.Kohana::config('locale.language.0');
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'type' => 'google.maps.MapTypeId.TERRAIN',
+			'animationEnabled' => TRUE,
+		);
+		$layers[$layer->name] = $layer;
+
+		// BING Road
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'bing_road';
+		$layer->openlayers = "Bing";
+		$layer->title = 'Bing-Road';
+		$layer->description = 'Bing Road Maps';
+		$layer->api_signup = Kohana::config('core.site_protocol').'://www.bingmapsportal.com/';
+		$layer->api_url = '';
+		$layer->data = array(
+			'name' => 'Bing-Road',
+			'baselayer' => TRUE,
+			'key' => Kohana::config('settings.api_live'),
+			'type' => 'Road',
+		);
+		$layers[$layer->name] = $layer;
+
+		// BING Hybrid
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'bing_hybrid';
+		$layer->openlayers = "Bing";
+		$layer->title = 'Bing-Hybrid';
+		$layer->description = 'Bing hybrid of streets and satellite tiles.';
+		$layer->api_signup = Kohana::config('core.site_protocol').'://www.bingmapsportal.com/';
+		$layer->api_url = '';
+		$layer->data = array(
+			'name' => 'Bing-Hybrid',
+			'baselayer' => TRUE,
+			'key' => Kohana::config('settings.api_live'),
+			'type' => 'AerialWithLabels',
+		);
+		$layers[$layer->name] = $layer;
+
+		// BING Aerial
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'bing_satellite';
+		$layer->openlayers = "Bing";
+		$layer->title = 'Bing-Satellite';
+		$layer->description = 'Bing Satellite Tiles';
+		$layer->api_signup = Kohana::config('core.site_protocol').'://www.bingmapsportal.com/';
+		$layer->api_url = '';
+		$layer->data = array(
+			'name' => 'Bing-Satellite',
+			'baselayer' => TRUE,
+			'key' => Kohana::config('settings.api_live'),
+			'type' => 'Aerial',
+		);
+		$layers[$layer->name] = $layer;
+
+		// OpenStreetMap Mapnik
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'osm_mapnik';
+		$layer->openlayers = "OSM.Mapnik";
+		$layer->title = 'OSM Mapnik';
+		$layer->description = 'The main OpenStreetMap map';
+		$layer->api_url = Kohana::config('core.site_protocol').'://www.openstreetmap.org/openlayers/OpenStreetMap.js';
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'attribution' => '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>',
+			'url' => 'http://tile.openstreetmap.org/${z}/${x}/${y}.png',
+			'type' => '',
+			'transitionEffect' => 'resize',
+		);
+		$layers[$layer->name] = $layer;
+
+		// OpenStreetMap Cycling Map
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'osm_cycle';
+		$layer->openlayers = "OSM.CycleMap";
+		$layer->title = 'OSM Cycling Map';
+		$layer->description = 'OpenStreetMap with highlighted bike lanes';
+		$layer->api_url = Kohana::config('core.site_protocol').'://www.openstreetmap.org/openlayers/OpenStreetMap.js';
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'attribution' => '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>',
+			'url' => 'http://tile.openstreetmap.org/cycle/${z}/${x}/${y}.png',
+			'type' => '',
+			'transitionEffect' => 'resize',
+		);
+		$layers[$layer->name] = $layer;
+
+		// OpenStreetMap Transport
+		$layer = new stdClass();
+		$layer->active = TRUE;
+		$layer->name = 'osm_TransportMap';
+		$layer->openlayers = "OSM.TransportMap";
+		$layer->title = 'OSM Transport Map';
+		$layer->description = 'TransportMap';
+		$layer->api_url = Kohana::config('core.site_protocol').'://www.openstreetmap.org/openlayers/OpenStreetMap.js';
+		$layer->data = array(
+			'baselayer' => TRUE,
+			'attribution' => '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>',
+			'url' => 'http://tile.openstreetmap.org/transport/${z}/${x}/${y}.png',
+			'type' => '',
+			'transitionEffect' => 'resize',
+		);
+		$layers[$layer->name] = $layer;
+
+		// Add Custom Layers
+		// Filter::map_base_layers
+		Event::run('ushahidi_filter.map_base_layers', $layers);
+
+		if ($layer_name)
+		{
+			if (isset($layers[$layer_name]))
+			{
+				return $layers[$layer_name];
+			}
+			else
+			{
+				return FALSE;
+			}
+		}
+		else
+		{
+			return $layers;
+		}
+	}
+
+	/**
+	 * GeoCode An Address
+	 *
+	 * @author
+	 * @param   string  $address
+	 * @return  array $geocodes - lat/lon
+	 */
+	public static function geocode($address = NULL)
+	{
+		return geocode::geocode($address);
+	}
+
+	/**
+	 * Reverse Geocode a point
+	 *
+	 * @author
+	 * @param   double  $latitude
+	 * @param   double  $longitude
+	 * @return  string  closest approximation of the point as a display name
+	 */
+	public static function reverse_geocode($latitude,$longitude)
+	{
+		return geocode::reverseGeocode($latitude, $longitude);
+	}
+
+	/**
+	 * Calculate distances between two points
+	 *
+	 * @param   double	 point 1 latitude
+	 * @param   double   point 1 longitude
+	 * @param   double	 point 2 latitude
+	 * @param   double   point 2 longitude
+	 * @param   string   unit (m, k, n)
+	 */
+	public static function distance($lat1, $lon1, $lat2, $lon2, $unit = "k")
+	{
+		$theta = $lon1 - $lon2;
+		$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) +  cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
+		$dist = acos($dist);
+		$dist = rad2deg($dist);
+		$miles = $dist * 60 * 1.1515;
+		$unit = strtoupper($unit);
+
+		if ($unit == "K")
+		{
+			return ($miles * 1.609344);
+		}
+		else if ($unit == "N")
+		{
+			return ($miles * 0.8684);
+		}
+		else
+		{
+			return $miles;
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/application/helpers/members.php b/application/helpers/members.php
new file mode 100644
index 0000000..49d952e
--- /dev/null
+++ b/application/helpers/members.php
@@ -0,0 +1,131 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Admin helper class.
+ *
+ * @package    Admin
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class members_Core {
+
+	/**
+	 * Generate Main Tab Menus
+	 * @return array array of all the main tabs
+	 */
+	public static function main_tabs()
+	{
+		$tabs =  array(
+			'dashboard' => Kohana::lang('ui_admin.dashboard'),
+			'reports' => Kohana::lang('ui_admin.my_reports'),
+			'alerts' => Kohana::lang('ui_admin.my_alerts'),
+			'private' => Kohana::lang('ui_admin.private_messages')
+		);
+		
+		Event::run('ushahidi_action.nav_members_main_top', $tabs);
+		
+		return $tabs;
+	}
+	
+	/**
+     * Generate Report Sub Tab Menus
+     * @param string $this_sub_page
+     * @return string $menu
+     */
+    public static function reports_subtabs($this_sub_page = FALSE)
+    {
+        $menu = "";
+
+        $menu .= ($this_sub_page == "view")
+			? Kohana::lang('ui_main.view_reports')
+			: "<a href=\"".url::site()."members/reports\">".Kohana::lang('ui_main.view_reports')."</a>";
+
+        $menu .= ($this_sub_page == "edit")
+			? Kohana::lang('ui_main.create_report')
+			: "<a href=\"".url::site()."members/reports/edit\">".Kohana::lang('ui_main.create_report')."</a>";
+
+        echo $menu;
+        
+        // Action::nav_admin_reports - Add items to the admin reports navigation tabs
+        Event::run('ushahidi_action.nav_members_reports', $this_sub_page);
+    }
+
+
+	/**
+     * Generate Private Messages Sub Tab Menus
+     * @param string $this_sub_page
+     * @return string $menu
+     */
+    public static function private_subtabs($this_sub_page = FALSE)
+    {
+        $menu = "";
+
+        $menu .= ($this_sub_page == "view")
+			? Kohana::lang('ui_admin.view_private') 
+			: "<a href=\"".url::site()."members/private\">".Kohana::lang('ui_admin.view_private')."</a>";
+
+        $menu .= ($this_sub_page == "new")
+			? Kohana::lang('ui_admin.new_private') 
+			: "<a href=\"".url::site()."members/private/send\">".Kohana::lang('ui_admin.new_private')."</a>";
+
+        echo $menu;
+        
+        // Action::nav_members_private - Add items to the members private messages navigation tabs
+        Event::run('ushahidi_action.nav_members_private', $this_sub_page);
+    }
+
+
+	/**
+     * Generate Alerts Sub Tab Menus
+     * @param string $this_sub_page
+     * @return string $menu
+     */
+    public static function alerts_subtabs($this_sub_page = FALSE)
+    {
+        $menu = "";
+
+        $menu .= ($this_sub_page == "view")
+			? Kohana::lang('ui_admin.my_alerts') 
+			: "<a href=\"".url::site()."members/alerts\">".Kohana::lang('ui_admin.my_alerts')."</a>";
+
+			// $menu .= ($this_sub_page == "edit")
+			// 	? Kohana::lang('ui_admin.new_alert') 
+			// 	: "<a href=\"".url::site()."members/alerts/edit\">".Kohana::lang('ui_admin.new_alert')."</a>";
+
+        echo $menu;
+        
+        // Action::nav_members_alerts - Add items to the members alerts navigation tabs
+        Event::run('ushahidi_action.nav_members_alerts', $this_sub_page);
+    }
+	
+	
+	/**
+	 * Get either a Gravatar URL or complete image tag for a specified email address.
+	 *
+	 * @param string $email The email address
+	 * @param string $s Size in pixels, defaults to 80px [ 1 - 512 ]
+	 * @param string $d Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
+	 * @param string $r Maximum rating (inclusive) [ g | pg | r | x ]
+	 * @param boole $img True to return a complete IMG tag False for just the URL
+	 * @param array $atts Optional, additional key/value attributes to include in the IMG tag
+	 * @return String containing either just a URL or a complete image tag
+	 * @source http://gravatar.com/site/implement/images/php/
+	 */
+	public function gravatar($email, $s = 80, $d = 'mm', $r = 'g', $img = FALSE, $atts = array())
+	{
+		$url = Kohana::config('core.site_protocol').'://secure.gravatar.com/avatar/'
+			. md5(strtolower(trim( $email)))
+			. "?s=$s&d=$d&r=$r";
+			
+		if ($img)
+		{
+			$url = '<img src="' . $url . '"';
+			foreach ($atts as $key => $val)
+			{
+				$url .= ' ' . $key . '="' . $val . '"';
+			}
+			$url .= ' />';
+		}
+		return $url;
+	}
+}
\ No newline at end of file
diff --git a/application/helpers/nav.php b/application/helpers/nav.php
new file mode 100644
index 0000000..0ba0668
--- /dev/null
+++ b/application/helpers/nav.php
@@ -0,0 +1,117 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Front-End Nav helper class.
+ *
+ * @package    Nav
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class nav_Core {
+	
+	/**
+	 * Generate Main Tabs
+     * @param string $this_page
+     * @param array $dontshow
+     */
+	public static function main_tabs($this_page = FALSE, $dontshow = FALSE)
+	{
+		$menu_items = array();
+
+		if( ! is_array($dontshow))
+		{
+			// Set $dontshow as an array to prevent errors
+			$dontshow = array();
+		}
+		
+		// Home
+		if( ! in_array('home',$dontshow))
+		{
+			$menu_items[] = array( 
+				'page' => 'home',
+				'url' => url::site('main'),
+				'name' => Kohana::lang('ui_main.home')
+			);
+		}
+
+		// Reports List
+		if( ! in_array('reports',$dontshow))
+		{
+			$menu_items[] = array( 
+				'page' => 'reports',
+				'url' => url::site('reports'),
+				'name' => Kohana::lang('ui_main.reports')
+			);
+		 }
+		
+		// Reports Submit
+		if( ! in_array('reports_submit',$dontshow))
+		{
+			if (Kohana::config('settings.allow_reports'))
+			{
+				$menu_items[] = array( 
+					'page' => 'reports_submit',
+					'url' => url::site('reports/submit'),
+					'name' => Kohana::lang('ui_main.submit')
+				);
+			}
+		}
+		
+		// Alerts
+		if(! in_array('alerts',$dontshow))
+		{
+			if(Kohana::config('settings.allow_alerts'))
+			{
+				$menu_items[] = array( 
+					'page' => 'alerts',
+					'url' => url::site('alerts'),
+					'name' => Kohana::lang('ui_main.alerts')
+				);
+			}
+		}
+		
+		// Contacts
+		if( ! in_array('contact',$dontshow))
+		{
+			if (Kohana::config('settings.site_contact_page') AND Kohana::config('settings.site_email') != "")
+			{
+				$menu_items[] = array( 
+					'page' => 'contact',
+					'url' => url::site('contact'),
+					'name' => Kohana::lang('ui_main.contact')
+				);	
+			}
+		}
+		
+		// Custom Pages
+		
+		if( ! in_array('pages',$dontshow))
+		{
+			$pages = ORM::factory('page')->where('page_active', '1')->find_all();
+			foreach ($pages as $page)
+			{
+				if( ! in_array('page/'.$page->id,$dontshow))
+				{
+					$menu_items[] = array( 
+						'page' => 'page_'.$page->id,
+						'url' => url::site('page/index/'.$page->id),
+						'name' => $page->page_tab
+					);
+				}
+			}
+		}
+
+		Event::run('ushahidi_filter.nav_main_tabs', $menu_items);
+
+		foreach( $menu_items as $item )
+		{
+			$active = ($this_page == $item['page']) ? ' class="active"' : '';
+			echo '<li><a href="'.$item['url'].'"'.$active.'>'.$item['name'].'</a></li>';
+		}
+
+		// Action::nav_admin_reports - Add items to the admin reports navigation tabs
+		Event::run('ushahidi_action.nav_main_top', $this_page);
+	}
+	
+	
+}
diff --git a/application/helpers/notifications.php b/application/helpers/notifications.php
new file mode 100644
index 0000000..80aa186
--- /dev/null
+++ b/application/helpers/notifications.php
@@ -0,0 +1,52 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class notifications_Core 
+{
+	
+	public function notify_admins($subject = NULL, $message = NULL)	
+	{
+		
+		// Don't show the exceptions for this operation to the user. Log them
+		// instead
+		try
+		{
+			if ($subject && $message)
+			{
+				$settings = kohana::config('settings');
+				$from = array();
+					$from[] = $settings['site_email'];
+					$from[] = $settings['site_name'];
+				$users = ORM::factory('user')->where('notify', 1)->find_all();
+
+				foreach($users as $user) 
+				{
+					if ($user->has(ORM::factory('role', 'admin')))
+					{
+						$address = $user->email;
+						
+						$message .= "\n\n\n\n~~~~~~~~~~~~\n".Kohana::lang('notifications.admin_footer')
+							."\n".url::base()
+							."\n\n".Kohana::lang('notifications.admin_login_url')
+							."\n".url::base()."admin";
+												
+						if ( ! email::send($address, $from, $subject, $message, FALSE))
+						{
+							Kohana::log('error', "email to $address could not be sent");
+						}
+					}
+				}
+			}
+			else
+			{
+				Kohana::log('error', "email to $address could not be sent
+				 - Missing Subject or Message");
+			}
+		}
+		catch (Exception $e)
+		{
+			Kohana::log('error', "An exception occured ".$e->__toString());
+
+		}
+
+	}
+}
diff --git a/application/helpers/plugin.php b/application/helpers/plugin.php
new file mode 100644
index 0000000..fba18c0
--- /dev/null
+++ b/application/helpers/plugin.php
@@ -0,0 +1,252 @@
+<?php
+/**
+ * Plugins helper
+ * 
+ * @package    Ushahidi
+ * @subpackage Helpers
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class plugin_Core {
+	
+	/**
+	 * @var array
+	 */
+	protected static $javascripts = array();
+	
+	/**
+	 * @var array
+	 */
+	protected static $stylesheets = array();
+	
+	/**
+	 * @var array
+	 */
+	protected static $sms_providers = array();
+	
+	/**
+	 * Adds an array of javascript items to the list of javascript sources
+	 *
+	 * @param array $javascripts
+	 */
+	public static function add_javascript($javascripts = array())
+	{
+		if ( ! is_array($javascripts))
+			$javascripts = array($javascripts);
+
+		foreach ($javascripts as $key => $javascript)
+		{
+			self::$javascripts[] = $javascript;
+		}
+	}
+	
+	/**
+	 * Removes a list of javascript items from the list of javascript
+	 * sources
+	 *
+	 * @param array $javascripts
+	 */
+	public static function remove_javascript($javascripts = array())
+	{
+		foreach (self::$javascripts as $key => $javascript)
+		{
+			if (in_array($javascript, $javascripts))
+				unset(self::$javascripts[$key]);
+		}
+	}
+	
+	/**
+	 * Adds a list of stylesheet items to the list of stylesheets for the
+	 * plugin
+	 *
+	 * @param array $stylesheets
+	 */
+	public static function add_stylesheet($stylesheets = array())
+	{
+		if ( ! is_array($stylesheets))
+			$stylesheets = array($stylesheets);
+
+		foreach ($stylesheets as $key => $stylesheet)
+		{
+			self::$stylesheets[] = $stylesheet;
+		}
+	}
+	
+	/**
+	 * Adds an SMS provider to the list of available SMS providers
+	 *
+	 * @param array $sms_providers
+	 */
+	public static function add_sms_provider($sms_providers = array())
+	{
+		if ( ! is_array($sms_providers))
+			$sms_providers = array($sms_providers => $sms_providers);
+
+		foreach ($sms_providers as $key => $sms_provider)
+		{
+			self::$sms_providers[$key] = $sms_provider;
+		}
+	}
+	
+	/**
+	 * Rettuns the list of SMS providers
+	 *
+	 * @return array
+	 */
+	public static function get_sms_providers()
+	{
+		return self::$sms_providers;
+	}
+	
+	/**
+	 * Adds a the stylesheet/javascript to the header of the view file
+	 *
+	 * @param string $type
+	 */
+	public static function render($type)
+	{
+		$files = $type.'s';
+		
+		$html = '';
+
+		foreach (self::$$files as $key => $file)
+		{
+			switch ($type)
+			{
+				case 'stylesheet':
+					if (substr_compare($file, '.css', -3, 3, FALSE) !== 0)
+					{
+						// Add the javascript suffix
+						$file .= '.css';
+					}
+					$html .= '<link rel="stylesheet" type="text/css" href="'.url::base()."plugins/".$file.'" />';
+					break;
+				case 'javascript':
+					if (substr_compare($file, '.js', -3, 3, FALSE) !== 0)
+					{
+						// Add the javascript suffix
+						$file .= '.js';
+					}
+					$html .= '<script type="text/javascript" src="'.url::base()."plugins/".$file.'"></script>';
+					break;
+			}
+		}
+		
+		return $html;
+	}
+		
+	/**
+	 * Adds a the stylesheet/javascript to the header of the view file
+	 *
+	 * @param string $type
+	 */
+	public static function get_requirements($type)
+	{
+		$files = $type.'s';
+		
+		$output = array();
+
+		foreach (self::$$files as $key => $file)
+		{
+			switch ($type)
+			{
+				case 'stylesheet':
+					if (substr_compare($file, '.css', -3, 3, FALSE) !== 0)
+					{
+						// Add the javascript suffix
+						$file .= '.css';
+					}
+					$output[] = "plugins/".$file;
+					break;
+				case 'javascript':
+					if (substr_compare($file, '.js', -3, 3, FALSE) !== 0)
+					{
+						// Add the javascript suffix
+						$file .= '.js';
+					}
+					$output[] = "plugins/".$file;
+					break;
+			}
+		}
+		
+		return $output;
+	}
+	
+	/**
+	 * Find plugin install file
+	 * Using this function because someone somewhere will name this file wrong!!!
+	 * @param string $plugin 
+	 */
+	public static function find_install($plugin, $type = 'plugin')
+	{
+		$base = ($type == 'plugin') ? PLUGINPATH : THEMEPATH;
+		// Determine if readme.txt (Case Insensitive) exists
+		$file = $base."{$plugin}/libraries/{$plugin}_install.php";
+		$real_path = null;
+		if ( file::file_exists_i($file, $real_path) )
+		{
+			return $real_path;
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+	
+	/**
+	 * Discover Plugin Settings Controller
+	 *
+	 * @param   string plugin name
+	 * @return  mixed Plugin settings page on success, FALSE otherwise
+	 */
+	public static function find_settings($plugin)
+	{
+		// Determine if settings controller exists
+		$file = "admin/{$plugin}_settings";
+		$file2 = "admin/settings/{$plugin}";
+		if ($path = Kohana::find_file('controllers', $file))
+		{
+			return $file;
+		}
+		elseif ($path = Kohana::find_file('controllers', $file2))
+		{
+			return $file2;
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+	
+	/**
+	 * Resync plugins in codebase with database
+	 */
+	public static function resync_plugins()
+	{
+		$plugins = addon::get_addons('plugin', FALSE);
+
+		// Get all plugins from the db
+		$plugins_db = ORM::factory('plugin')->select_list('id','plugin_name');
+
+		// Sync the folder with the database
+		foreach ($plugins as $dir => $plugin)
+		{
+			if ( ! in_array($dir, $plugins_db))
+			{
+				$plugin = ORM::factory('plugin');
+				$plugin->plugin_name = $dir;
+				$plugin->save();
+			}
+		}
+
+		// Remove any plugins not found in the plugins folder and not previously installed from the database
+		foreach (ORM::factory('plugin')->where('plugin_installed', 0)->find_all() as $plugin)
+		{
+			if ( ! array_key_exists($plugin->plugin_name, $plugins) )
+			{
+				$plugin->delete();
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/application/helpers/pointinpoly.php b/application/helpers/pointinpoly.php
new file mode 100644
index 0000000..47ab005
--- /dev/null
+++ b/application/helpers/pointinpoly.php
@@ -0,0 +1,72 @@
+<?php
+
+// Class modified from code found on http://www.assemblysys.com/dataServices/php_pointinpolygon.php on August 4, 2011
+
+class pointinpoly {
+    
+    var $pointOnVertex = true; // Check if the point sits exactly on one of the vertices
+
+	function pointinpoly(){
+	
+	}
+	
+	function pointInPolygon($point, $polygon, $pointOnVertex = true) {
+        $this->pointOnVertex = $pointOnVertex;
+        
+        // Transform string coordinates into arrays with x and y values
+        $point = $this->pointStringToCoordinates($point);
+        $vertices = array(); 
+        foreach ($polygon as $vertex) {
+            $vertices[] = $this->pointStringToCoordinates($vertex); 
+        }
+        
+        // Check if the point sits exactly on a vertex
+        if ($this->pointOnVertex == true and $this->pointOnVertex($point, $vertices) == true) {
+            return true;
+        }
+        
+        // Check if the point is inside the polygon or on the boundary
+        $intersections = 0; 
+        $vertices_count = count($vertices);
+    
+        for ($i=1; $i < $vertices_count; $i++) {
+            $vertex1 = $vertices[$i-1]; 
+            $vertex2 = $vertices[$i];
+            if ($vertex1['y'] == $vertex2['y'] and $vertex1['y'] == $point['y'] and $point['x'] > min($vertex1['x'], $vertex2['x']) and $point['x'] < max($vertex1['x'], $vertex2['x'])) { // Check if point is on an horizontal polygon boundary
+                return true;
+            }
+            if ($point['y'] > min($vertex1['y'], $vertex2['y']) and $point['y'] <= max($vertex1['y'], $vertex2['y']) and $point['x'] <= max($vertex1['x'], $vertex2['x']) and $vertex1['y'] != $vertex2['y']) { 
+                $xinters = ($point['y'] - $vertex1['y']) * ($vertex2['x'] - $vertex1['x']) / ($vertex2['y'] - $vertex1['y']) + $vertex1['x']; 
+                if ($xinters == $point['x']) { // Check if point is on the polygon boundary (other than horizontal)
+                    return true;
+                }
+                if ($vertex1['x'] == $vertex2['x'] || $point['x'] <= $xinters) {
+                    $intersections++; 
+                }
+            } 
+        } 
+        // If the number of edges we passed through is even, then it's in the polygon. 
+        if ($intersections % 2 != 0) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    function pointOnVertex($point, $vertices) {
+        foreach($vertices as $vertex) {
+            if ($point == $vertex) {
+                return true;
+            }
+        }
+    
+    }
+
+    function pointStringToCoordinates($pointString) {
+        $coordinates = explode(" ", $pointString);
+        return array("x" => $coordinates[0], "y" => $coordinates[1]);
+    }
+
+}
+
+?>
\ No newline at end of file
diff --git a/application/helpers/protochart.php b/application/helpers/protochart.php
new file mode 100644
index 0000000..56082dd
--- /dev/null
+++ b/application/helpers/protochart.php
@@ -0,0 +1,104 @@
+<?php
+
+class protochart {
+	
+	function protochart() {
+	
+	}
+
+	/*
+	 * The chart function creates a nice protochart
+	 * 
+	 * name: The name of the div element for the chart. Should be unique from all other charts
+	 * data: Multi-dimensional array. Ex: array('label1'=>array(1,2,3,4,5),'label2'=>array(3,2,4,2))
+	 * options: Multi-dimensional array. Ex: array('bars'=>array('show'=>'true'))
+	 *          See protochart site for more details related to options: http://www.deensoft.com/lab/protochart/
+	 *          Eaxample bar graph options: array('bars'=>array('show'=>'true'));
+	 * custom_color: array with label as key and a RRGGBB code (ex: FF0000) as a value.
+	 * width: width of the chart in pixels
+	 * height: height of the chart in pixels
+	 *
+	 */
+	public function chart($name='chart',$data,$options_array=null,$custom_color=null,$width=400,$height=300) {
+		
+		// Set default options
+		if($options_array === null) {
+			$options_array = array('xaxis'=>array('tickSize'=>'1'));
+		}
+		
+		// Compile options
+		$options = '';
+		$first = 1;
+		foreach($options_array as $modifying => $opts){
+			$options .= $modifying.': {';
+			$first_ = 1;
+			foreach($opts as $key => $val){
+				$options .= "$key: $val";
+				if($first_ < count($opts))
+					$options .= ",";
+					
+				$first_++;
+			}
+			$options .= '}';
+			
+			if($first < count($options_array))
+				$options .= ",";
+			
+			$first++;
+		}
+		
+		$name = 'protochart_'.$name;
+		
+		$html = '<script type="text/javascript" charset="utf-8">
+			Event.observe(window, \'load\', function() {
+				';
+				
+		// Compile data
+		$labels = array();
+		$i = 0;
+		foreach($data as $label => $data_array){
+			$html .= "data$i = new Array(";
+			$labels[$i] = $label;
+			$show_comma = false;
+			if(is_array($data_array)){
+				foreach($data_array as $pos => $value){
+					if($show_comma){
+						$html .= ",";
+					}
+					$html .= "[$pos,$value]";
+					$show_comma = true;
+				}
+			}
+			$html .= ");
+			";
+			$i++;
+		}
+		
+		$html .= "new Proto.Chart($('$name'),[";
+		
+		$first = 1;
+		foreach($labels as $i => $label_name){
+			
+			// Apply custom colors, otherwise use defaults.
+			$color = '';
+			if(isset($custom_color[$label_name])) $color = "color:\"#".$custom_color[$label_name]."\",";
+			
+			$html .= "{label: \"$label_name\", $color data: data$i}";
+			if($first < count($labels))
+				$html .= ",";
+		}
+		
+		$html .= "],{
+				$options
+				});		
+			});
+		</script>
+		<div id=\"$name\" style=\"width:".$width."px;height:".$height."px\"></div>";
+		
+		return $html;
+	}
+
+}
+
+
+?>
\ No newline at end of file
diff --git a/application/helpers/reports.php b/application/helpers/reports.php
new file mode 100644
index 0000000..256b009
--- /dev/null
+++ b/application/helpers/reports.php
@@ -0,0 +1,1079 @@
+<?php 
+/**
+ * Reports Helper class.
+ *
+ * This class holds functions used for new report submission from both the backend and frontend.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @category   Helpers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class reports_Core {
+	
+	/**
+	 * Maintains the list of parameters used for fetching incidents
+	 * in the fetch_incidents method
+	 * @var array
+	 */
+	public static $params = array();
+	
+	/**
+	 * Pagination object user in fetch_incidents method
+	 * @var Pagination
+	 */
+	public static $pagination = array();
+	
+			
+	/**
+	 * Validation of form fields
+	 *
+	 * @param array $post Values to be validated
+	 */
+	public static function validate(array & $post)
+	{
+
+		// Exception handling
+		if ( ! isset($post) OR ! is_array($post))
+			return FALSE;
+		
+		// Create validation object
+		$post = Validation::factory($post)
+				->pre_filter('trim', TRUE)
+				->add_rules('incident_title','required', 'length[3,200]')
+				->add_rules('incident_description','required')
+				->add_rules('incident_date','required','date_mmddyyyy')
+				->add_rules('incident_hour','required','between[1,12]')
+				->add_rules('incident_minute','required','between[0,59]')
+				->add_rules('incident_ampm','required');
+			
+		if (isset($post->incident_ampm) AND $post->incident_ampm != "am" AND $post->incident_ampm != "pm")
+		{
+			$post->add_error('incident_ampm','values');
+		}
+			
+		// Validate for maximum and minimum latitude values
+		$post->add_rules('latitude','required','between[-90,90]');
+		
+		// Validate for maximum and minimum longitude values		
+		$post->add_rules('longitude','required','between[-180,180]');	
+		$post->add_rules('location_name','required', 'length[3,200]');
+
+		//XXX: Hack to validate for no checkboxes checked
+		if ( ! isset($post->incident_category))
+		{
+			$post->incident_category = "";
+			$post->add_error('incident_category','required');
+		}
+		else
+		{
+			$post->add_rules('incident_category.*','required','numeric');
+		}
+
+		// Validate only the fields that are filled in
+		if ( ! empty($post->incident_news))
+		{
+			foreach ($post->incident_news as $key => $url) 
+			{
+				if ( ! empty($url) AND ! valid::url($url))
+				{
+					$post->add_error('incident_news','url');
+				}
+			}
+		}
+
+		// Validate only the fields that are filled in
+		if ( ! empty($post->incident_video))
+		{
+			foreach ($post->incident_video as $key => $url) 
+			{
+				if (!empty($url) AND ! valid::url($url))
+				{
+					$post->add_error('incident_video','url');
+				}
+			}
+		}
+		
+		// If deployment is a single country deployment, check that the location mapped is in the default country
+		if ( ! Kohana::config('settings.multi_country') AND isset($post->country_name))
+		{
+			$country = Country_Model::get_country_by_name($post->country_name);
+			if ($country AND $country->id != Kohana::config('settings.default_country'))
+			{
+				$post->add_error('country_name','single_country', array(ORM::factory('country', Kohana::config('settings.default_country'))->country) );
+			}
+		}
+		
+		// Validate photo uploads
+		$max_upload_size = Kohana::config('settings.max_upload_size');
+		$post->add_rules('incident_photo', 'upload::valid', 'upload::type[gif,jpg,png,jpeg]', "upload::size[".$max_upload_size."M]");
+
+
+		// Validate Personal Information
+		if ( ! empty($post->person_first))
+		{
+			$post->add_rules('person_first', 'length[2,100]');
+		}
+		else
+		{
+			$post->person_first = '';
+		}
+
+		if ( ! empty($post->person_last))
+		{
+			$post->add_rules('person_last', 'length[2,100]');
+		}
+		else
+		{
+			$post->person_last = '';
+		}
+
+		if ( ! empty($post->person_email))
+		{
+			$post->add_rules('person_email', 'email', 'length[3,100]');
+		}
+		else
+		{
+			$post->person_email = '';
+		}
+		
+		$post->add_rules('location_id','numeric');
+		$post->add_rules('incident_active', 'between[0,1]');
+		$post->add_rules('incident_verified', 'between[0,1]');
+		$post->add_rules('incident_zoom', 'numeric');
+
+		// Custom form fields validation
+		customforms::validate_custom_form_fields($post);
+		//> END custom form fields validation
+
+		// Return
+		return $post->validate();
+	}
+	
+	/**
+	 * Function to save report location
+	 * 
+	 * @param Validation $post
+	 * @param Location_Model $location Instance of the location model
+	 */
+	public static function save_location($post, $location)
+	{
+		// Load the country
+		$country = isset($post->country_name)
+			? Country_Model::get_country_by_name($post->country_name)
+			: new Country_Model(Kohana::config('settings.default_country'));
+			
+		// Fetch the country id
+		$country_id = ( ! empty($country) AND $country->loaded)? $country->id : 0;
+		
+		// Assign country_id retrieved
+		$post->country_id = $country_id;
+		$location->location_name = $post->location_name;
+		$location->latitude = $post->latitude;
+		$location->longitude = $post->longitude;
+		$location->country_id = $country_id;
+		$location->location_date = date("Y-m-d H:i:s",time());
+		$location->save();
+		
+		// Garbage collection
+		unset ($country, $country_id);
+	}
+	
+	/**
+	 * Saves an incident
+	 *
+	 * @param Validation $post Validation object with the data to be saved
+	 * @param Incident_Model $incident Incident_Model instance to be modified
+	 * @param Location_Model $location_model Location to be attached to the incident
+	 * @param int $id ID no. of the report
+	 *
+	 */
+	public static function save_report($post, $incident, $location_id)
+	{
+		// Exception handling
+		if ( ! $post instanceof Validation_Core AND  ! $incident instanceof Incident_Model)
+		{
+			// Throw exception
+			throw new Kohana_Exception('Invalid parameter types');
+		}
+		
+		// Verify that the location id exists
+		if ( ! Location_Model::is_valid_location($location_id))
+		{
+			throw new Kohana_Exception(sprintf('Invalid location id specified: ', $location_id));
+		}
+		
+		// Is this new or edit?
+		if ($incident->loaded)	
+		{
+			// Edit
+			$incident->incident_datemodify = date("Y-m-d H:i:s",time());
+		}
+		else
+		{
+			// New
+			$incident->incident_dateadd = date("Y-m-d H:i:s",time());
+		}
+		
+		$incident->location_id = $location_id;
+		//$incident->locale = $post->locale;
+		if (isset($post->form_id))
+		{
+			$incident->form_id = $post->form_id;
+		}
+		
+		// Check if the user id has been specified
+		if ( ! $incident->loaded AND Auth::instance()->get_user() instanceof User_Model)
+		{
+			$incident->user_id = Auth::instance()->get_user()->id;
+		}
+		
+		$incident->incident_title = $post->incident_title;
+		$incident->incident_description = $post->incident_description;
+
+		$incident_date=explode("/",$post->incident_date);
+		// Where the $_POST['date'] is a value posted by form in mm/dd/yyyy format
+		$incident_date=$incident_date[2]."-".$incident_date[0]."-".$incident_date[1];
+
+		$incident_time = $post->incident_hour . ":" . $post->incident_minute . ":00 " . $post->incident_ampm;
+		$incident->incident_date = date( "Y-m-d H:i:s", strtotime($incident_date . " " . $incident_time) );
+				
+		
+		// Is this an Email, SMS, Twitter submitted report?
+		if ( ! empty($post->service_id))
+		{
+			// SMS
+			if ($post->service_id == 1)
+			{
+				$incident->incident_mode = 2;
+			}
+			// Email
+			elseif ($post->service_id == 2)
+			{
+				$incident->incident_mode = 3;
+			}
+			// Twitter
+			elseif ($post->service_id == 3)
+			{
+				$incident->incident_mode = 4;
+			}
+			else
+			{
+				// Default to Web Form
+				$incident->incident_mode = 1;
+			}
+		}
+		
+		// Approval Status: Only set if user has permission
+		if (isset($post->incident_active) AND Auth::instance()->has_permission('reports_approve'))
+		{
+			$incident->incident_active = $post->incident_active;
+		}
+		// Verification status:  Only set if user has permission
+		if (isset($post->incident_verified) AND Auth::instance()->has_permission('reports_verify'))
+		{
+			$incident->incident_verified = $post->incident_verified;
+		}
+		
+		// Incident zoom
+		if ( ! empty($post->incident_zoom))
+		{
+			$incident->incident_zoom = intval($post->incident_zoom);
+		}
+		// Tag this as a report that needs to be sent out as an alert
+		if ($incident->incident_active == 1 AND $incident->incident_alert_status != 2)
+		{ 
+			// 2 = report that has had an alert sent
+			$incident->incident_alert_status = '1';
+		}
+		
+		// Remove alert if report is unactivated and alert hasn't yet been sent
+		if ($incident->incident_active == 0 AND $incident->incident_alert_status == 1)
+		{
+			$incident->incident_alert_status = '0';
+		}
+		
+		// Save the incident
+		$incident->save();
+	}
+	
+	/**
+	 * Function to record the verification/approval actions
+	 *
+	 * @param mixed $incident
+	 */
+	public static function verify_approve($incident)
+	{
+		// @todo Exception handling
+		
+		$verify = new Verify_Model();
+		$verify->incident_id = $incident->id;
+		
+		// Record 'Verified By' Action
+		$verify->user_id = 0;
+		if (Auth::instance()->get_user() instanceof User_Model)
+		{
+			$verify->user_id = Auth::instance()->get_user()->id;
+		}
+		$verify->verified_date = date("Y-m-d H:i:s",time());
+		
+		if ($incident->incident_active == 1)
+		{
+			$verify->verified_status = '1';
+		}
+		elseif ($incident->incident_verified == 1)
+		{
+			$verify->verified_status = '2';
+		}
+		elseif ($incident->incident_active == 1 AND $incident->incident_verified == 1)
+		{
+			$verify->verified_status = '3';
+		}
+		else
+		{
+			$verify->verified_status = '0';
+		}
+		
+		// Save
+		$verify->save();
+	} 
+	
+	/**
+	 * Function that saves incident geometries
+	 *
+	 * @param Incident_Model $incident
+	 * @param mixed $incident
+	 *
+	 */
+	public static function save_report_geometry($post, $incident)
+	{
+		// Delete all current geometry
+		ORM::factory('geometry')->where('incident_id',$incident->id)->delete_all();
+		
+		if (isset($post->geometry)) 
+		{
+			// Database object
+			$db = new Database();
+			
+			// SQL for creating the incident geometry
+			$sql = "INSERT INTO ".Kohana::config('database.default.table_prefix')."geometry "
+				. "(incident_id, geometry, geometry_label, geometry_comment, geometry_color, geometry_strokewidth) "
+				. "VALUES(%d, GeomFromText('%s'), '%s', '%s', '%s', %s)";
+				
+			foreach($post->geometry as $item)
+			{
+				if ( ! empty($item))
+				{
+					//Decode JSON
+					$item = json_decode($item);
+					//++ TODO - validate geometry
+					$geometry = (isset($item->geometry)) ? $db->escape_str($item->geometry) : "";
+					$label = (isset($item->label)) ? $db->escape_str(substr($item->label, 0, 150)) : "";
+					$comment = (isset($item->comment)) ? $db->escape_str(substr($item->comment, 0, 255)) : "";
+					$color = (isset($item->color)) ? $db->escape_str(substr($item->color, 0, 6)) : "";
+					$strokewidth = (isset($item->strokewidth) AND (float) $item->strokewidth) ? (float) $item->strokewidth : "2.5";
+					if ($geometry)
+					{
+						// 	Format the SQL string
+						$sql = "INSERT INTO ".Kohana::config('database.default.table_prefix')."geometry "
+							. "(incident_id, geometry, geometry_label, geometry_comment, geometry_color, geometry_strokewidth)"
+							. "VALUES(".$incident->id.", GeomFromText('".$geometry."'), '".$label."', '".$comment."', '".$color."', ".$strokewidth.")";
+						Kohana::log('debug', $sql);
+						// Execute the query
+						$db->query($sql);
+					}
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Function to save incident categories
+	 *
+	 * @param mixed $post
+	 * @param mixed $incident_model
+	 */
+	public static function save_category($post, $incident)
+	{
+		// Delete Previous Entries
+		ORM::factory('incident_category')->where('incident_id', $incident->id)->delete_all();
+		
+		foreach ($post->incident_category as $item)
+		{
+			$incident_category = new Incident_Category_Model();
+			$incident_category->incident_id = $incident->id;
+			$incident_category->category_id = $item;
+			$incident_category->save();
+		}
+	}
+	
+	/**
+	 * Function to save news, photos and videos
+	 *
+	 * @param mixed $location_model
+	 * @param mixed $post
+	 *
+	 */
+	public static function save_media($post, $incident)
+	{
+		$upload_dir = Kohana::config('upload.directory', TRUE);
+
+		// Delete Previous Entries
+		ORM::factory('media')->where('incident_id',$incident->id)->where('media_type <> 1')->delete_all();
+
+		// a. News
+		if (isset($post->incident_news))
+		{
+			foreach ($post->incident_news as $item)
+			{
+				if ( ! empty($item))
+				{
+					$news = new Media_Model();
+					$news->location_id = $incident->location_id;
+					$news->incident_id = $incident->id;
+					$news->media_type = 4;		// News
+					$news->media_link = $item;
+					$news->media_date = date("Y-m-d H:i:s",time());
+					$news->save();
+				}
+			}
+		}
+
+		// b. Video
+		if (isset($post->incident_video))
+		{
+			$videoembed = new VideoEmbed();
+			foreach ($post->incident_video as $k => $video_link)
+			{
+				if ( ! empty($video_link))
+				{
+					$video_thumb = $videoembed->thumbnail($video_link);
+					$new_filename = $incident->id.'_v'.$k.'_'.time();
+					$file_type = substr($video_thumb, strrpos($video_thumb, '.'));
+
+					$media_thumb = NULL;
+					$media_medium = NULL;
+
+					// Make sure file has an image extension
+					if ($video_thumb AND in_array($file_type, array('.gif','.jpg','.png','.jpeg')))
+					{
+						// Name the files for the DB
+						$media_link = $new_filename.$file_type;
+						$media_medium = $new_filename.'_m'.$file_type;
+						$media_thumb = $new_filename.'_t'.$file_type;
+						
+						try {
+							if ($data = file_get_contents($video_thumb))
+							{
+								file_put_contents($upload_dir.$media_link, $data);
+							}
+						}
+						catch (Exception $e)
+						{
+
+						}
+						
+						// IMAGE SIZES: 800X600, 400X300, 89X59
+						// Catch any errors from corrupt image files
+						try
+						{
+							$image = Image::factory($upload_dir.$media_link);
+							
+							// Medium size
+							if( $image->height > 300 )
+							{
+								Image::factory($upload_dir.$media_link)->resize(400,300,Image::HEIGHT)
+									->save($upload_dir.$media_medium);
+							}
+							else
+							{
+								// Cannot reuse the original image as it is deleted a bit further down
+								$image->save($upload_dir.$media_medium);
+							}
+							
+							// Thumbnail
+							if( $image->height > 59 )
+							{
+								Image::factory($upload_dir.$media_link)->resize(89,59,Image::HEIGHT)
+									->save($upload_dir.$media_thumb);
+							}
+							else
+							{
+								// Reuse the medium image when it is small enough
+								$media_thumb = $media_medium;
+							}
+						}
+						catch (Exception $e)
+						{
+							// Do nothing. Too late to throw errors
+							// Set links to NULL
+							$media_medium = NULL;
+							$media_thumb = NULL;
+						}
+						
+						// Okay, now we have these three different files on the server, now check to see
+						//   if we should be dropping them on the CDN
+
+						$local_directory = rtrim($upload_dir, '/').'/';
+						if ($media_medium AND $media_thumb AND Kohana::config("cdn.cdn_store_dynamic_content"))
+						{
+							$cdn_media_medium = cdn::upload($media_medium);
+							$cdn_media_thumb = cdn::upload($media_thumb);
+
+							// We no longer need the files we created on the server. Remove them.
+
+							if (file_exists($local_directory.$media_medium))
+							{
+								unlink($local_directory.$media_medium);
+							}
+
+							if (file_exists($local_directory.$media_thumb))
+							{
+								unlink($local_directory.$media_thumb);
+							}
+
+							$media_medium = $cdn_media_medium;
+							$media_thumb = $cdn_media_thumb;
+						}
+
+						if (file_exists($local_directory.$media_link)) {
+							// Remove original image
+							unlink($upload_dir.$media_link);
+						}
+					}
+					
+					$video = new Media_Model();
+					$video->location_id = $incident->location_id;
+					$video->incident_id = $incident->id;
+					$video->media_type = 2;		// Video
+					$video->media_link = $video_link;
+					$video->media_thumb = $media_thumb;
+					$video->media_medium = $media_medium;
+					$video->media_date = date("Y-m-d H:i:s",time());
+					$video->save();
+				}
+			}
+		}
+
+		// c. Photos
+		if ( ! empty($post->incident_photo))
+		{
+			$filenames = upload::save('incident_photo');
+			$i = 1;
+
+			foreach ($filenames as $filename)
+			{
+				$new_filename = $incident->id.'_'.$i.'_'.time();
+
+				//$file_type = substr($filename,-4);
+				$file_type =".".substr(strrchr($filename, '.'), 1); // replaces the commented line above to take care of images with .jpeg extension.
+				
+				// Name the files for the DB
+				$media_link = $new_filename.$file_type;
+				$media_medium = $new_filename.'_m'.$file_type;
+				$media_thumb = $new_filename.'_t'.$file_type;
+
+				// IMAGE SIZES: 800X600, 400X300, 89X59
+				// Catch any errors from corrupt image files
+				try
+				{
+					$image = Image::factory($filename);
+					// Large size
+					if( $image->width > 800 || $image->height > 600 )
+					{
+						Image::factory($filename)->resize(800,600,Image::AUTO)
+							->save($upload_dir.$media_link);
+					}
+					else
+					{
+						$image->save($upload_dir.$media_link);
+					}
+
+					// Medium size
+					if( $image->height > 300 )
+					{
+						Image::factory($filename)->resize(400,300,Image::HEIGHT)
+							->save($upload_dir.$media_medium);
+					}
+					else
+					{
+						// Reuse the large image when it is small enough
+						$media_medium = $media_link;
+					}
+
+					// Thumbnail
+					if( $image->height > 59 )
+					{
+						Image::factory($filename)->resize(89,59,Image::HEIGHT)
+							->save($upload_dir.$media_thumb);
+					}
+					else
+					{
+						// Reuse the medium image when it is small enough
+						$media_thumb = $media_medium;
+					}
+				}
+				catch (Kohana_Exception $e)
+				{
+					// Do nothing. Too late to throw errors
+					$media_link = NULL;
+					$media_medium = NULL;
+					$media_thumb = NULL;
+				}
+					
+				// Okay, now we have these three different files on the server, now check to see
+				//   if we should be dropping them on the CDN
+				
+				if (Kohana::config("cdn.cdn_store_dynamic_content"))
+				{
+					$cdn_media_link = cdn::upload($media_link);
+					$cdn_media_medium = cdn::upload($media_medium);
+					$cdn_media_thumb = cdn::upload($media_thumb);
+					
+					// We no longer need the files we created on the server. Remove them.
+					$local_directory = rtrim($upload_dir, '/').'/';
+					if (file_exists($local_directory.$media_link))
+					{
+						unlink($local_directory.$media_link);
+					}
+ 
+					if (file_exists($local_directory.$media_medium))
+					{
+						unlink($local_directory.$media_medium);
+					}
+ 
+					if (file_exists($local_directory.$media_thumb))
+					{
+						unlink($local_directory.$media_thumb);
+					}
+					
+					$media_link = $cdn_media_link;
+					$media_medium = $cdn_media_medium;
+					$media_thumb = $cdn_media_thumb;
+				}
+
+				// Remove the temporary file
+				unlink($filename);
+
+				// Save to DB
+				$photo = new Media_Model();
+				$photo->location_id = $incident->location_id;
+				$photo->incident_id = $incident->id;
+				$photo->media_type = 1; // Images
+				$photo->media_link = $media_link;
+				$photo->media_medium = $media_medium;
+				$photo->media_thumb = $media_thumb;
+				$photo->media_date = date("Y-m-d H:i:s",time());
+				$photo->save();
+				$i++;
+			}
+		}
+	}
+	
+	/**
+	 * Function to save custom field values
+	 *
+	 * @param mixed $incident_model
+	 * @param mixed $post
+	 *
+	 */
+	public static function save_custom_fields($post, $incident)
+	{
+		if (isset($post->custom_field))
+		{
+			foreach($post->custom_field as $key => $value)
+			{
+				$form_response = ORM::factory('form_response')
+								->where('form_field_id', $key)
+								->where('incident_id', $incident->id)
+								->find();
+													 
+				if ($form_response->loaded == true)
+				{
+					$form_response->form_field_id = $key;
+					$form_response->form_response = $value;
+					$form_response->save();
+				}
+				else
+				{
+					$form_response = new Form_Response_Model();
+					$form_response->form_field_id = $key;
+					$form_response->incident_id = $incident->id;
+					$form_response->form_response = $value;
+					$form_response->save();
+				}
+			}
+		}	
+	}
+	
+	/**
+	 * Function to save personal information
+	 *
+	 * @param mixed $incident_model
+	 * @param mixed $post
+	 *
+	 */
+	public static function save_personal_info($post, $incident)
+	{
+		// Delete Previous Entries
+		ORM::factory('incident_person')->where('incident_id',$incident->id)->delete_all();
+		
+		$person = new Incident_Person_Model();
+		$person->incident_id = $incident->id;
+		$person->person_first = $post->person_first;
+		$person->person_last = $post->person_last;
+		$person->person_email = $post->person_email;
+		$person->person_date = date("Y-m-d H:i:s",time());
+		$person->save();		
+	}
+	
+	/**
+	 * Helper function to fetch and optionally paginate the list of 
+	 * incidents/reports via the Incident Model using one or all of the 
+	 * following URL parameters
+	 *	- category
+	 *	- location bounds
+	 *	- incident mode
+	 *	- media
+	 *	- location radius
+	 *
+	 * @param bool $paginate Optionally paginate the incidents - Default is FALSE
+	 * @param int $items_per_page No. of items to show per page
+	 * @return Database_Result
+	 */
+	public static function fetch_incidents($paginate = FALSE, $items_per_page = 0)
+	{
+		// Reset the paramters
+		self::$params = array();
+		
+		// Initialize the category id
+		$category_id = 0;
+		
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		// Fetch the URL data into a local variable
+		$url_data = $_GET;
+		
+		// Split selected parameters on ","
+		// For simplicity, always turn them into arrays even theres just one value
+		$exclude_params = array('c', 'v', 'm', 'mode', 'sw', 'ne', 'start_loc');
+		foreach ($url_data as $key => $value)
+		{
+			if (in_array($key, $exclude_params) AND ! is_array($value))
+			{
+				$url_data[$key] = explode(",", $value);
+			}
+		}
+		
+		//> BEGIN PARAMETER FETCH
+		// 
+		// Check for the category parameter
+		// 
+		if (isset($url_data['c']) AND is_array($url_data['c']))
+		{
+			// Sanitize each of the category ids
+			$category_ids = array();
+			foreach ($url_data['c'] as $c_id)
+			{
+				if (intval($c_id) > 0)
+				{
+					$category_ids[] = intval($c_id);
+				}
+			}
+			
+			// Check if there are any category ids
+			if (count($category_ids) > 0)
+			{
+				$category_ids = implode(",", $category_ids);
+			
+				array_push(self::$params,
+					'(c.id IN ('.$category_ids.') OR c.parent_id IN ('.$category_ids.'))',
+					'c.category_visible = 1'
+				);
+			}
+		}
+		
+		// 
+		// Incident modes
+		// 
+		if (isset($url_data['mode']) AND is_array($url_data['mode']))
+		{
+			$incident_modes = array();
+			
+			// Sanitize the modes
+			foreach ($url_data['mode'] as $mode)
+			{
+				if (intval($mode) > 0)
+				{
+					$incident_modes[] = intval($mode);
+				}
+			}
+			
+			// Check if any modes exist and add them to the parameter list
+			if (count($incident_modes) > 0)
+			{
+				array_push(self::$params, 
+					'i.incident_mode IN ('.implode(",", $incident_modes).')'
+				);
+			}
+		}
+		
+		// 
+		// Location bounds parameters
+		// 
+		if (isset($url_data['sw']) AND isset($url_data['ne']))
+		{
+			$southwest = $url_data['sw'];
+			$northeast = $url_data['ne'];
+			
+			if ( count($southwest) == 2 AND count($northeast) == 2 )
+			{
+				$lon_min = (float) $southwest[0];
+				$lon_max = (float) $northeast[0];
+				$lat_min = (float) $southwest[1];
+				$lat_max = (float) $northeast[1];
+			
+				// Add the location conditions to the parameter list
+				array_push(self::$params, 
+					'l.latitude >= '.$lat_min,
+					'l.latitude <= '.$lat_max,
+					'l.longitude >= '.$lon_min,
+					'l.longitude <= '.$lon_max
+				);
+			}
+		}
+		
+		// 
+		// Location bounds - based on start location and radius
+		// 
+		if (isset($url_data['radius']) AND isset($url_data['start_loc']))
+		{
+			//if $url_data['start_loc'] is just comma delimited strings, then make it into an array
+			if (intval($url_data['radius']) > 0 AND is_array($url_data['start_loc']))
+			{
+				$bounds = $url_data['start_loc'];
+				if (count($bounds) == 2 AND is_numeric($bounds[0]) AND is_numeric($bounds[1]))
+				{
+					self::$params['radius'] = array(
+						'distance' => intval($url_data['radius']),
+						'latitude' => $bounds[0],
+						'longitude' => $bounds[1]
+					);
+				}
+			}
+		}
+		
+		// 
+		// Check for incident date range parameters
+		// 
+		if (!empty($url_data['from']))
+		{
+			// Add hours/mins/seconds so we still get reports if from and to are the same day
+			$date_from = date('Y-m-d 00:00:00', strtotime($url_data['from']));
+			
+			array_push(self::$params, 
+				'i.incident_date >= "'.$date_from.'"'
+			);
+		}
+		if (!empty($url_data['to']))
+		{
+			// Add hours/mins/seconds so we still get reports if from and to are the same day
+			$date_to = date('Y-m-d 23:59:59', strtotime($url_data['to']));
+			
+			array_push(self::$params, 
+				'i.incident_date <= "'.$date_to.'"'
+			);
+		}
+		
+		// Additional checks for date parameters specified in timestamp format
+		// This only affects those submitted from the main page
+		
+		// Start Date
+		if (isset($_GET['s']) AND intval($_GET['s']) > 0)
+		{
+			$start_date = intval($_GET['s']);
+			array_push(self::$params, 
+				'i.incident_date >= "'.date("Y-m-d H:i:s", $start_date).'"'
+			);
+		}
+
+		// End Date
+		if (isset($_GET['e']) AND intval($_GET['e']))
+		{
+			$end_date = intval($_GET['e']);
+			array_push(self::$params, 
+				'i.incident_date <= "'.date("Y-m-d H:i:s", $end_date).'"'
+			);
+		}
+		
+		// 
+		// Check for media type parameter
+		// 
+		if (isset($url_data['m']) AND is_array($url_data['m']))
+		{
+			// An array of media filters has been specified
+			// Validate the media types
+			$media_types = array();
+			foreach ($url_data['m'] as $media_type)
+			{
+				if (intval($media_type) > 0)
+				{
+					$media_types[] = intval($media_type);
+				}
+			}
+			if (count($media_types) > 0)
+			{
+				array_push(self::$params, 
+					'i.id IN (SELECT DISTINCT incident_id FROM '
+						.$table_prefix.'media WHERE media_type IN ('.implode(",", $media_types).'))'
+				);
+			}
+			
+		}
+		
+		// 
+		// Check if the verification status has been specified
+		// 
+		if (isset($url_data['v']) AND is_array($url_data['v']))
+		{
+			$verified_status = array();
+			foreach ($url_data['v'] as $verified)
+			{
+				if (intval($verified) >= 0)
+				{
+					$verified_status[] = intval($verified);
+				}
+			}
+			
+			if (count($verified_status) > 0)
+			{
+				array_push(self::$params, 
+					'i.incident_verified IN ('.implode(",", $verified_status).')'
+				);
+			}
+		}
+		
+		//
+		// Check if they're filtering over custom form fields
+		//
+		if (isset($url_data['cff']) AND is_array($url_data['cff']))
+		{
+			$where_text = "";
+			$i = 0;
+			foreach ($url_data['cff'] as $field)
+			{			
+				$field_id = $field[0];
+				if (intval($field_id) < 1)
+					continue;
+
+				$field_value = $field[1];
+				if (is_array($field_value))
+				{
+					$field_value = implode(",", $field_value);
+				}
+								
+				$i++;
+				if ($i > 1)
+				{
+					$where_text .= " OR ";
+				}
+				
+				$where_text .= "(form_field_id = ".intval($field_id)
+					. " AND form_response = '".Database::instance()->escape_str(trim($field_value))."')";
+			}
+			
+			// Make sure there was some valid input in there
+			if ($i > 0)
+			{
+				// Get the valid IDs - faster in a separate query as opposed
+				// to a subquery within the main query
+				$db = new Database();
+
+				$rows = $db->query('SELECT DISTINCT incident_id FROM '
+				    .$table_prefix.'form_response WHERE '.$where_text);
+				
+				$incident_ids = '';
+				foreach ($rows as $row)
+				{
+					if ($incident_ids != '')
+					{
+							$incident_ids .= ',';
+					}
+
+					$incident_ids .= $row->incident_id;
+				}
+				//make sure there are IDs found
+				if ($incident_ids != '')
+				{
+					array_push(self::$params, 'i.id IN ('.$incident_ids.')');
+				}
+				else
+				{
+					array_push(self::$params, 'i.id IN (0)');
+				}
+			}
+			
+		} // End of handling cff
+		
+		// In case a plugin or something wants to get in on the parameter fetching fun
+		Event::run('ushahidi_filter.fetch_incidents_set_params', self::$params);
+		
+		//> END PARAMETER FETCH
+
+		// Check for order and sort params
+		$order_field = NULL; $sort = NULL;
+		$order_options = array(
+			'title' => 'i.incident_title',
+			'date' => 'i.incident_date',
+			'id' => 'i.id'
+		);
+		if (isset($url_data['order']) AND isset($order_options[$url_data['order']]))
+		{
+			$order_field = $order_options[$url_data['order']];
+		}
+		if (isset($url_data['sort']))
+		{
+			$sort = (strtoupper($url_data['sort']) == 'ASC') ? 'ASC' : 'DESC';
+		}
+		
+		if ($paginate)
+		{
+			// Fetch incident count
+			$incident_count = Incident_Model::get_incidents(self::$params, false, $order_field, $sort, TRUE);
+			
+			// Set up pagination
+			$page_limit = (intval($items_per_page) > 0)
+			    ? $items_per_page 
+			    : intval(Kohana::config('settings.items_per_page'));
+					
+			$total_items = $incident_count->current()
+					? $incident_count->current()->report_count
+					: 0;
+			
+			$pagination = new Pagination(array(
+					'style' => 'front-end-reports',
+					'query_string' => 'page',
+					'items_per_page' => $page_limit,
+					'total_items' => $total_items
+				));
+			
+			Event::run('ushahidi_filter.pagination',$pagination);
+			
+			self::$pagination = $pagination;
+			
+			// Return paginated results
+			return Incident_Model::get_incidents(self::$params, self::$pagination, $order_field, $sort);
+		}
+		else
+		{
+			// Return
+			return Incident_Model::get_incidents(self::$params, false, $order_field, $sort);;
+		}
+	}	
+}
+?>
diff --git a/application/helpers/reputation.php b/application/helpers/reputation.php
new file mode 100644
index 0000000..62fd6e4
--- /dev/null
+++ b/application/helpers/reputation.php
@@ -0,0 +1,101 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Reputation Score helper class
+ * 
+ *
+ * @package	   Reputation
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+
+class reputation_Core {
+	
+	/**
+	 * Calculate Total Reputation Score for User
+	 * @param int User ID
+	 * @return int reputation score
+	 */
+	public static function calculate($user_id)	
+	{
+		$subdomain = '';
+		if (substr_count($_SERVER["HTTP_HOST"],'.') > 1 AND Kohana::config('config.enable_mhi') == TRUE)
+		{
+			$subdomain = substr($_SERVER["HTTP_HOST"],0,strpos($_SERVER["HTTP_HOST"],'.'));
+		}
+		
+		$cache = Cache::instance();
+		// This is kind of a heavy query, so we'll use a 10 minute cache for $totals
+		$total = $cache->get($subdomain.'_reputation');
+
+		if ($total == NULL)
+		{ // Cache is Empty so Re-Cache
+			$total = 0;
+			$upvoted_reports = 10;
+			$approved_reports = 15;
+			$verified_reports = 20;
+			$upvoted_comments = 5;
+			$downvoted_reports = 2;
+			$downvoted_comments = 1;
+
+			// Get Reports Approved Verified
+			$reports = ORM::factory("incident")
+						->where("user_id", $user_id)
+						->find_all();
+						
+			foreach ($reports as $report)
+			{
+				if ($report->incident_active)
+				{
+					$total += $approved_reports;
+				}
+
+				if ($report->incident_verified)
+				{
+					$total += $verified_reports;
+				}
+			}
+
+			// Get Totals on [My] Reports that have been voted on
+			$ratings = ORM::factory("rating")
+						->join("incident", "incident.id", "rating.incident_id")
+						->join("users", "users.id", "incident.user_id")
+						->where("users.id", $user_id)
+						->find_all();
+			foreach ($ratings as $rating)
+			{
+				if ($rating->rating > 0)
+				{ // Upvote
+					$total += ( $rating->rating * $upvoted_reports );
+				}
+				elseif ($rating->rating < 0)
+				{ // Downvote
+					$total += ( $rating->rating * $downvoted_reports );
+				}
+			}
+
+			// Get Totals on [My] Comments that have been voted on
+			$ratings = ORM::factory("rating")
+				->join("comment", "comment.id", "rating.comment_id")
+				->join("users", "users.id", "comment.user_id")
+				->where("users.id", $user_id)
+				->find_all();
+				
+			foreach ($ratings as $rating)
+			{
+				if ($rating->rating > 0)
+				{ // Upvote
+					$total += ( $rating->rating * $upvoted_comments );
+				}
+				elseif ($rating->rating < 0)
+				{ // Downvote
+					$total += ( $rating->rating * $downvoted_comments );
+				}
+			}
+
+			$cache->set($subdomain.'_reputation', $total, array('reputation'), 600); // 10 Minutes
+		}
+
+		return $total;
+	}
+}
\ No newline at end of file
diff --git a/application/helpers/sms.php b/application/helpers/sms.php
new file mode 100644
index 0000000..ea4b1fd
--- /dev/null
+++ b/application/helpers/sms.php
@@ -0,0 +1,177 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * SMS helper class
+ *
+ * @package     Ushahidi
+ * @category    Helpers
+ * @author      Ushahidi Team
+ * @copyright   (c) 2008 Ushahidi Team
+ * @license     http://www.ushahidi.com/license.html
+ */
+
+class sms_Core {
+
+	/**
+	 * Send The SMS Message Using Default Provider
+	 *
+	 * @param to mixed	The destination address.
+	 * @param from mixed  The source/sender address
+	 * @param text mixed  The text content of the message
+	 * @return mixed/bool (returns TRUE if sent FALSE or other text for fail)
+	 */
+	public static function send($to = NULL, $from = NULL, $message = NULL)	
+	{
+		if ( ! $to OR ! $message)
+			return "Missing Recipients and/or Message";
+		
+		// 1. Do we have an SMS Provider?
+		$provider = Kohana::config("settings.sms_provider");
+		if ($provider)
+		{
+			// 2. Does the plugin exist, and if so, is it active?
+			$plugin = ORM::factory("plugin")
+				->where("plugin_name", $provider)
+				->where("plugin_active", 1)
+				->find();
+			
+			// Plugin loaded
+			if ($plugin->loaded)
+			{
+				// 3. Does this plugin have the SMS Library in place?
+				// SMS libaries should be suffixed with "_Sms_Provider"
+				$class = ucfirst($provider).'_Sms_Provider';
+				
+				if (Kohana::find_file('libraries', $class))
+				{
+					$provider  = new $class;
+					
+					// Sanity check - Ensure all SMS providers are sub-classes of Sms_Provider_Core
+					if ( ! $provider instanceof Sms_Provider_Core)
+					{
+						throw new Kohana_Exception('All SMS Provider libraries must be be sub-classes of Sms_Provider_Core');
+					}
+					
+					// Proceed
+					$response = $provider->send($to, $from, $message);
+					
+					// Return
+					return $response;
+				}
+			}
+		}
+		
+		return "No SMS Sending Provider In System";
+	}
+	
+	/**
+	 * Send The SMS Message Using Default Provider
+	 * @param from mixed  The source/sender address
+	 * @param message mixed  The text content of the message
+	 * @param to mixed  Optional... 'which number the message was sent to'
+	 */
+	public static function add($from = NULL, $message = NULL, $to = NULL)
+	{
+		$from = preg_replace("#[^0-9]#", "", $from);
+		$to = preg_replace("#[^0-9]#", "", $to);
+		
+		if ( ! $from OR ! $message)
+			return "Missing Sender and/or Message";
+		
+		//Filters to allow modification of the values from the SMS gateway
+		Event::run('ushahidi_filter.message_sms_from',$from);
+		Event::run('ushahidi_filter.message_sms', $message);
+
+		$services = new Service_Model();
+		$service = $services->where('service_name', 'SMS')->find();
+
+		if ( ! $service) 
+			return FALSE;
+
+		$reporter = ORM::factory('reporter')
+		    ->where('service_id', $service->id)
+		    ->where('service_account', $from)
+		    ->find();
+
+		if ( ! $reporter->loaded == TRUE)
+		{
+			// get default reporter level (Untrusted)
+			$level = ORM::factory('level')
+			    ->where('level_weight', 0)
+			    ->find();
+			
+			$reporter->service_id = $service->id;
+			$reporter->level_id = $level->id;
+			$reporter->service_account = $from;
+			$reporter->reporter_first = NULL;
+			$reporter->reporter_last = NULL;
+			$reporter->reporter_email = NULL;
+			$reporter->reporter_phone = NULL;
+			$reporter->reporter_ip = NULL;
+			$reporter->reporter_date = date('Y-m-d');
+			$reporter->save();
+		}
+		
+		// Save Message
+		$sms = new Message_Model();
+		$sms->parent_id = 0;
+		$sms->incident_id = 0;
+		$sms->user_id = 0;
+		$sms->reporter_id = $reporter->id;
+		$sms->message_from = $from;
+		$sms->message_to = $to;
+		$sms->message = $message;
+		$sms->message_type = 1; // Inbox
+		$sms->message_date = date("Y-m-d H:i:s",time());
+		$sms->service_messageid = NULL;
+		$sms->save();
+		
+		// Notify Admin Of New Email Message
+		$send = notifications::notify_admins(
+			"[".Kohana::config('settings.site_name')."] ".
+				Kohana::lang('notifications.admin_new_sms.subject'),
+			Kohana::lang('notifications.admin_new_sms.message')
+			);
+
+		// Action::message_sms_add - SMS Received!
+		Event::run('ushahidi_action.message_sms_add', $sms);
+		
+		// Auto-Create A Report if Reporter is Trusted
+		$reporter_weight = $reporter->level->level_weight;
+		$reporter_location = $reporter->location;
+		if ($reporter_weight > 0 AND $reporter_location)
+		{
+			$incident_title = text::limit_chars($message, 50, "...", false);
+			// Create Incident
+			$incident = new Incident_Model();
+			$incident->location_id = $reporter_location->id;
+			$incident->incident_title = $incident_title;
+			$incident->incident_description = $message;
+			$incident->incident_date = $sms->message_date;
+			$incident->incident_dateadd = date("Y-m-d H:i:s",time());
+			$incident->incident_active = 1;
+			if ($reporter_weight == 2)
+			{
+				$incident->incident_verified = 1;
+			}
+			$incident->save();
+			
+			// Update Message with Incident ID
+			$sms->incident_id = $incident->id;
+			$sms->save();
+			
+			// Save Incident Category
+			$trusted_categories = ORM::factory("category")
+				->where("category_trusted", 1)
+				->find();
+			if ($trusted_categories->loaded)
+			{
+				$incident_category = new Incident_Category_Model();
+				$incident_category->incident_id = $incident->id;
+				$incident_category->category_id = $trusted_categories->id;
+				$incident_category->save();
+			}
+		}
+		
+		return TRUE;
+	}
+}
diff --git a/application/helpers/strptime.php b/application/helpers/strptime.php
new file mode 100644
index 0000000..c590a63
--- /dev/null
+++ b/application/helpers/strptime.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Parse a time/date generated with strftime().
+ *
+ * This function is the same as the original one defined by PHP (Linux/Unix only),
+ *  but now you can use it on Windows too.
+ *  Limitation : Only this format can be parsed %S, %M, %H, %d, %m, %Y
+ *
+ * @author Lionel SAURON
+ * @version 1.0
+ * @public
+ *
+ * @param $sDate(string)    The string to parse (e.g. returned from strftime()).
+ * @param $sFormat(string)  The format used in date  (e.g. the same as used in strftime()).
+ * @return (array)          Returns an array with the <code>$sDate</code> parsed, or <code>false</code> on error.
+ */
+
+if(function_exists("strptime") == false){
+	function strptime($sDate, $sFormat){
+		$aResult = array(
+			'tm_sec'   => 0,
+			'tm_min'   => 0,
+			'tm_hour'  => 0,
+			'tm_mday'  => 1,
+			'tm_mon'   => 0,
+			'tm_year'  => 0,
+			'tm_wday'  => 0,
+			'tm_yday'  => 0,
+			'unparsed' => $sDate,
+		);
+		
+		while($sFormat != ""){
+			// ===== Search a %x element, Check the static string before the %x =====
+			$nIdxFound = strpos($sFormat, '%');
+			if($nIdxFound === false){
+				// There is no more format. Check the last static string.
+				$aResult['unparsed'] = ($sFormat == $sDate) ? "" : $sDate;
+				break;
+			}
+		}
+
+		// ===== Create the other value of the result array =====
+		$nParsedDateTimestamp = mktime($aResult['tm_hour'], $aResult['tm_min'], $aResult['tm_sec'],
+								$aResult['tm_mon'] + 1, $aResult['tm_mday'], $aResult['tm_year'] + 1900);
+		
+		// Before PHP 5.1 return -1 when error
+		if(($nParsedDateTimestamp === false)
+		||($nParsedDateTimestamp === -1)) return false;
+		
+		$aResult['tm_wday'] = (int) strftime("%w", $nParsedDateTimestamp); // Days since Sunday (0-6)
+		$aResult['tm_yday'] = (strftime("%j", $nParsedDateTimestamp) - 1); // Days since January 1 (0-365)
+		
+		return $aResult;
+	} // END of function
+   
+} // END if(function_exists("strptime") == false)
+?>
\ No newline at end of file
diff --git a/application/helpers/testutils.php b/application/helpers/testutils.php
new file mode 100644
index 0000000..3b384c1
--- /dev/null
+++ b/application/helpers/testutils.php
@@ -0,0 +1,38 @@
+<?php defined('SYSPATH') or die('No direct script access');
+/**
+ * Helper library for the unit tests
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+// Table prefix for the tables
+define('TABLE_PREFIX', Kohana::config('database.default.table_prefix'));
+
+class testutils_Core {	
+	
+	/**
+	 * Gets a random value from the id column of the specified database table
+	 *
+	 * @param string $table_name Database table name from which to fetch the id
+	 * @return int
+	 */
+	public static function get_random_id($table_name, $where = '')
+	{
+		// Database instance for the query
+		$db = new Database();
+		
+		// Fetch all values from the ID column of the table
+		$result = $db->query('SELECT id FROM '.TABLE_PREFIX.$table_name.' '.$where)->as_array();
+		
+		// Get a random id
+		return (count($result) > 0) ? $result[array_rand($result)]->id : count($result);
+	}
+}
+?>
\ No newline at end of file
diff --git a/application/helpers/upload.php b/application/helpers/upload.php
new file mode 100644
index 0000000..f3ea3a8
--- /dev/null
+++ b/application/helpers/upload.php
@@ -0,0 +1,318 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Upload helper class for working with the global $_FILES
+ * array and Validation library.
+ *
+ * $Id: upload.php 3264 2008-09-23 19:03:14Z David Kobia $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class upload_Core {
+
+	/**
+	 * Save an uploaded file to a new location.
+	 *
+	 * @param   mixed    name of $_FILE input or array of upload data
+	 * @param   string   new filename
+	 * @param   string   new directory
+	 * @param   integer  chmod mask
+	 * @return  string   full path to new file
+	 */
+	public static function save($file, $filename = NULL, $directory = NULL, $chmod = 0644)
+	{
+		// Load file data from FILES if not passed as array
+		
+              $file = is_array($file) ? $file : $_FILES[$file];
+
+		if ($filename === NULL)
+		{
+			// Use the default filename, with a timestamp pre-pended
+			$filename = time().(is_array($file['name']) ? $file['name'][0] : $file['name']);
+		}
+
+		if (Kohana::config('upload.remove_spaces') === TRUE)
+		{
+			// Remove spaces from the filename
+			$filename = preg_replace('/\s+/', '_', $filename);
+		}
+
+		if ($directory === NULL)
+		{
+			// Use the pre-configured upload directory
+			$directory = Kohana::config('upload.directory', TRUE);
+		}
+
+		// Make sure the directory ends with a slash
+		$directory = rtrim($directory, '/').'/';
+
+		if ( ! is_dir($directory) AND Kohana::config('upload.create_directories') === TRUE)
+		{
+			// Create the upload directory
+			mkdir($directory, 0777, TRUE);
+		}
+
+		if ( ! is_writable($directory))
+			throw new Kohana_Exception('upload.not_writable', $directory);
+		
+		// loop through if tmp_name returns an array
+		if( is_array( $file['tmp_name'] ) ) {
+			$i = 0;
+			$filenames = array();
+			foreach( $file['tmp_name'] as $tmp_name ) { 
+				if (is_uploaded_file($tmp_name ) AND 
+				move_uploaded_file($tmp_name, $filename = 
+					$directory.$file['name'][$i] ) ) 
+				{		
+					if ($chmod !== FALSE)
+					{
+						// Set permissions on filename
+						chmod( $filename, $chmod );
+					}
+					
+					// Add $filename to $filenames array
+					$filenames[] = $filename;
+				}
+				$i++;
+			}
+			
+			// Return new file path array
+			return $filenames;
+		}
+		else
+		{
+			if (is_uploaded_file($file['tmp_name']) AND move_uploaded_file($file['tmp_name'], $filename = $directory.$filename))
+			{
+				if ($chmod !== FALSE)
+				{
+					// Set permissions on filename
+					chmod($filename, $chmod);
+				}
+
+				// Return new file path
+				return $filename;
+			}
+		}
+
+		return FALSE;
+	}
+
+	/* Validation Rules */
+
+	/**
+	 * Tests if input data is valid file type, even if no upload is present.
+	 *
+	 * @param   array  $_FILES item
+	 * @return  bool
+	 */
+	public static function valid($file)
+	{
+		if (is_array($file))
+		{
+			// Is this a multi-upload array?
+			if (is_array($file['name']))
+			{
+                            $filesCount = count($file['name'])-1;
+				for ($i=0; $i <= $filesCount ; $i++) 
+				{ 
+					if (isset($file['error'][$i])
+						AND isset($file['name'][$i])
+						AND isset($file['type'][$i])
+						AND isset($file['tmp_name'][$i])
+						AND isset($file['size'][$i]))
+					{
+                                            
+						if($filesCount == $i)
+                                                    return true;
+					}
+					else
+					{
+						return false;
+					}
+				}
+			}
+			// No - this is a single upload
+			else
+			{
+				return (isset($file['error'])
+					AND isset($file['name'])
+					AND isset($file['type'])
+					AND isset($file['tmp_name'])
+					AND isset($file['size']));
+			}
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Tests if input data has valid upload data.
+	 *
+	 * @param   array    $_FILES item
+	 * @return  bool
+	 */
+	public static function required(array $file)
+	{
+		if (is_array($file['name']))
+		{
+			for ($i=0; $i <= count($file['name']) ; $i++) 
+			{ 
+				if (isset($file['tmp_name'][$i])
+					AND isset($file['error'][$i])
+					AND is_uploaded_file($file['tmp_name'][$i])
+					AND (int) $file['error'][$i] === UPLOAD_ERR_OK)
+				{
+					return true;
+				}
+				else
+				{
+					return false;
+				}
+			}
+		}
+		// This is a single upload
+		else
+		{
+			return (isset($file['tmp_name'])
+				AND isset($file['error'])
+				AND is_uploaded_file($file['tmp_name'])
+				AND (int) $file['error'] === UPLOAD_ERR_OK);
+		}
+	}
+
+	/**
+	 * Validation rule to test if an uploaded file is allowed by extension.
+	 *
+	 * @param   array    $_FILES item
+	 * @param   array    allowed file extensions
+	 * @return  bool
+	 */
+	public static function type(array $file, array $allowed_types)
+	{ 
+		if (is_array($file['name']))
+		{
+                        $filesCount =  count($file['name'])-1;
+			for ($i=0; $i <= $filesCount ; $i++) 
+			{ 
+				if ((int) $file['error'][$i] !== UPLOAD_ERR_OK)
+				{
+					return TRUE;
+				}
+				
+				// Get the default extension of the file
+                                $extension = strtolower(pathinfo($file['name'][$i], PATHINFO_EXTENSION));
+                                
+				// Get the mime types for the extension
+				$mime_types = Kohana::config('mimes.'.$extension);
+
+				// Make sure there is an extension, that the extension is allowed, and that mime types exist
+				if ( ! empty($extension) AND in_array($extension, $allowed_types) AND is_array($mime_types))
+				{
+                                      if($filesCount == $i)
+					return TRUE;
+				}
+				else
+				{
+					return false;
+				}
+			}
+		}
+		// This is a single upload
+		else
+		{
+			if ((int) $file['error'] !== UPLOAD_ERR_OK)
+				return TRUE;
+
+			// Get the default extension of the file
+                        $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));			
+
+			// Get the mime types for the extension
+			$mime_types = Kohana::config('mimes.'.$extension);
+
+			// Make sure there is an extension, that the extension is allowed, and that mime types exist
+			return ( ! empty($extension) AND in_array($extension, $allowed_types) AND is_array($mime_types));
+		}
+	}
+
+	/**
+	 * Validation rule to test if an uploaded file is allowed by file size.
+	 * File sizes are defined as: SB, where S is the size (1, 15, 300, etc) and
+	 * B is the byte modifier: (B)ytes, (K)ilobytes, (M)egabytes, (G)igabytes.
+	 * Eg: to limit the size to 1MB or less, you would use "1M".
+	 *
+	 * @param   array    $_FILES item
+	 * @param   array    maximum file size
+	 * @return  bool
+	 */
+	public static function size(array $file, array $size)
+	{
+		if (is_array($file['name']))
+		{
+                    // Only one size is allowed
+                    $size = strtoupper($size[0]);
+
+                    /*if ( ! preg_match('/[0-9]++[BKMG]/', $size))
+                    {
+                            return FALSE;
+                    }*/
+
+                    // Make the size into a power of 1024
+                    switch (substr($size, -1))
+                    {
+                            case 'G': $size = intval($size) * pow(1024, 3); break;
+                            case 'M': $size = intval($size) * pow(1024, 2); break;
+                            case 'K': $size = intval($size) * pow(1024, 1); break;
+                            default:  $size = intval($size);                break;
+                    }
+                    $filesCount = count($file['name'])-1;
+			for ($i=0; $i <= $filesCount ; $i++) 
+			{
+				if ((int) $file['error'][$i] !== UPLOAD_ERR_OK)
+				{
+					return TRUE;
+				}
+				
+				// Test that the file is under or equal to the max size
+				if ($file['size'][$i] <= $size)
+				{
+                                    
+                                     if($filesCount == $i)
+					return true;
+				}
+				else
+				{
+					return false;
+				}
+			}
+		}
+		// This is a single upload
+		else
+		{
+			if ((int) $file['error'] !== UPLOAD_ERR_OK)
+				return TRUE;
+
+			// Only one size is allowed
+			$size = strtoupper($size[0]);
+
+			if ( ! preg_match('/[0-9]++[BKMG]/', $size))
+				return FALSE;
+
+			// Make the size into a power of 1024
+			switch (substr($size, -1))
+			{
+				case 'G': $size = intval($size) * pow(1024, 3); break;
+				case 'M': $size = intval($size) * pow(1024, 2); break;
+				case 'K': $size = intval($size) * pow(1024, 1); break;
+				default:  $size = intval($size);                break;
+			}
+
+			// Test that the file is under or equal to the max size
+			return ($file['size'] <= $size);
+		}
+	}
+
+} // End upload
diff --git a/application/helpers/ush_locale.php b/application/helpers/ush_locale.php
new file mode 100644
index 0000000..6f55188
--- /dev/null
+++ b/application/helpers/ush_locale.php
@@ -0,0 +1,565 @@
+<?php
+/**
+ * Locale helper
+ *
+ * @package    Ush_Locale
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class ush_locale_Core
+{
+	/**
+	 * @param   string	 ISO-639 language code
+	 */
+	public static function language($iso639)
+	{
+		$iso_array = array (
+			"aa" => "Afaraf",
+			"ab" => "Аҧсуа",
+			"ae" => "Avesta",
+			"af" => "Afrikaans",
+			"ak" => "Akan",
+			"am" => "አማርኛ",
+			"an" => "Aragonés",
+			"ar" => "العربية",
+			"as" => "অসমীয়া",
+			"av" => "авар мацӀ, магӀарул мацӀ",
+			"ay" => "Aymar aru",
+			"az" => "Azərbaycan dili",
+			"ba" => "башҡорт теле",
+			"be" => "Беларуская",
+			"bg" => "Български",
+			"bh" => "भोजपुरी",
+			"bi" => "Bislama",
+			"bm" => "Bamanankan",
+			"bn" => "বাংলা",
+			"bo" => "བོད་ཡིག",
+			"br" => "Brezhoneg",
+			"bs" => "Bosanski",
+			"ca" => "Català",
+			"ce" => "нохчийн мотт",
+			"ch" => "Chamoru",
+			"co" => "corsu, lingua corsa",
+			"cr" => "ᓀᐦᐃᔭᐍᐏᐣ",
+			"cs" => "česky, čeština",
+			"cu" => "ѩзыкъ словѣньскъ",
+			"cv" => "чӑваш чӗлхи",
+			"cy" => "Cymraeg",
+			"da" => "Dansk",
+			"de" => "Deutsch",
+			"dr" => "دری",
+			"dv" => "ދިވެހި",
+			"dz" => "རྫོང་ཁ",
+			"ee" => "Eʋegbe",
+			"el" => "Ελληνικά",
+			"en" => "English",
+			"eo" => "Esperanto",
+			"es" => "Español",
+			"et" => "Eesti",
+			"eu" => "Euskara",
+			"fa" => "فارسی",
+			"ff" => "Fulfulde, Pulaar, Pular",
+			"fi" => "Suomi",
+			"fj" => "Vosa Vakaviti",
+			"fo" => "Føroyskt",
+			"fr" => "Français",
+			"fy" => "Frysk",
+			"ga" => "Gaeilge",
+			"gd" => "Gàidhlig",
+			"gl" => "Galego",
+			"gn" => "Avañe'ẽ",
+			"gu" => "ગુજરાતી",
+			"gv" => "Gaelg, Gailck",
+			"ha" => "Hausa, هَوُسَ",
+			"he" => "עברית",
+			"hi" => "हिन्दी, हिंदी",
+			"ho" => "Hiri Motu",
+			"hr" => "hrvatski",
+			"ht" => "Kreyòl ayisyen",
+			"hu" => "Magyar",
+			"hy" => "Հայերեն",
+			"hz" => "Otjiherero",
+			"ia" => "Interlingua",
+			"id" => "Bahasa Indonesia",
+			"ie" => "Interlingue",
+			"ig" => "Igbo",
+			"ii" => "ꆇꉙ",
+			"ik" => "Iñupiaq, Iñupiatun",
+			"io" => "Ido",
+			"is" => "Íslenska",
+			"it" => "Italiano",
+			"iu" => "ᐃᓄᒃᑎᑐᑦ",
+			"ja" => "日本語 (にほんご/にっぽんご)",
+			"jv" => "Basa Jawa",
+			"ka" => "ქართული",
+			"kg" => "KiKongo",
+			"ki" => "Gĩkũyũ",
+			"kj" => "Kuanyama",
+			"kk" => "Қазақ тілі",
+			"kl" => "kalaallisut, kalaallit oqaasii",
+			"km" => "ភាសាខ្មែរ",
+			"kn" => "ಕನ್ನಡ",
+			"ko" => "한국어 (韓國語), 조선말 (朝鮮語)",
+			"kr" => "Kanuri",
+			"ks" => "कश्मीरी, كشميري‎",
+			"ku" => "Kurdî, كوردی‎",
+			"kv" => "Коми",
+			"kw" => "Kernewek",
+			"ky" => "кыргыз тили",
+			"la" => "Latine",
+			"lb" => "Lëtzebuergesch",
+			"lg" => "Luganda",
+			"li" => "Limburgs",
+			"ln" => "Lingála",
+			"lo" => "ພາສາລາວ",
+			"lt" => "Lietuvių",
+			"lu" => "Luba-Katanga",
+			"lv" => "Latviešu",
+			"mg" => "Malagasy fiteny",
+			"mh" => "Kajin M̧ajeļ",
+			"mi" => "te reo Māori",
+			"mk" => "Македонски",
+			"ml" => "മലയാളം",
+			"mn" => "Монгол",
+			"mr" => "मराठी",
+			"ms" => "bahasa Melayu, بهاس ملايو‎",
+			"mt" => "Malti",
+			"my" => "ဗမာစာ",
+			"na" => "Ekakairũ Naoero",
+			"nb" => "Norsk bokmål",
+			"nd" => "isiNdebele",
+			"ne" => "नेपाली",
+			"ng" => "Owambo",
+			"nl" => "Nederlands",
+			"nn" => "Norsk nynorsk",
+			"no" => "Norsk",
+			"nr" => "isiNdebele",
+			"nv" => "Diné bizaad, Dinékʼehǰí",
+			"ny" => "chiCheŵa, chinyanja",
+			"oc" => "Occitan",
+			"oj" => "ᐊᓂᔑᓈᐯᒧᐎᓐ",
+			"om" => "Afaan Oromoo",
+			"or" => "ଓଡ଼ିଆ",
+			"os" => "Ирон æвзаг",
+			"pa" => "ਪੰਜਾਬੀ, پنجابی‎",
+			"pi" => "पाऴि",
+			"pl" => "Polski",
+			"ps" => "پښتو",
+			"pt" => "Português",
+			"qu" => "Runa Simi, Kichwa",
+			"rm" => "rumantsch grischun",
+			"rn" => "kiRundi",
+			"ro" => "română",
+			"ru" => "Русский",
+			"rw" => "Ikinyarwanda",
+			"sa" => "संस्कृतम्",
+			"sc" => "sardu",
+			"sd" => "सिन्धी, سنڌي، سندھی‎",
+			"se" => "Davvisámegiella",
+			"sg" => "yângâ tî sängö",
+			"si" => "සිංහල",
+			"sk" => "Slovenčina",
+			"sl" => "Slovenščina",
+			"sm" => "gagana fa'a Samoa",
+			"sn" => "chiShona",
+			"so" => "Soomaaliga, af Soomaali",
+			"sq" => "Shqip",
+			"sr" => "Српски",
+			"ss" => "SiSwati",
+			"st" => "Sesotho",
+			"su" => "Basa Sunda",
+			"sv" => "Svenska",
+			"sw" => "Kiswahili",
+			"ta" => "தமிழ்",
+			"te" => "తెలుగు",
+			"tg" => "тоҷикӣ, toğikī, تاجیکی‎",
+			"th" => "ไทย",
+			"ti" => "ትግርኛ",
+			"tk" => "Türkmen, Түркмен",
+			"tl" => "Wikang Tagalog, ᜏᜒᜃᜅ᜔ ᜆᜄᜎᜓᜄ᜔",
+			"tn" => "Setswana",
+			"to" => "faka Tonga",
+			"tq" => "tlhIngan Hol (Klingon)",
+			"tr" => "Türkçe",
+			"ts" => "Xitsonga",
+			"tt" => "татарча, tatarça, تاتارچا‎",
+			"tw" => "Twi",
+			"ty" => "Reo Mā`ohi",
+			"ug" => "Uyƣurqə, ئۇيغۇرچە‎",
+			"uk" => "Українська",
+			"ur" => "اردو",
+			"uz" => "O'zbek, Ўзбек, أۇزبېك‎",
+			"ve" => "Tshivenḓa",
+			"vi" => "Tiếng Việt",
+			"vo" => "Volapük",
+			"wa" => "Walon",
+			"wo" => "Wollof",
+			"xh" => "isiXhosa",
+			"yi" => "ייִדיש",
+			"yo" => "Yorùbá",
+			"za" => "Saɯ cueŋƅ, Saw cuengh",
+			"zh" => "中文 (Zhōngwén), 汉语, 漢語",
+			"zu" => "isiZulu",
+		);
+
+		if (array_key_exists($iso639, $iso_array))
+		{
+			return $iso_array[$iso639];
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Is a language written Right-to-left
+	 * @param $locale
+	 */
+	public static function is_rtl_language($locale = NULL)
+	{
+		// Check for text direction override
+		if (Kohana::config('locale.force_text_direction') == 'rtl') return TRUE;
+		if (Kohana::config('locale.force_text_direction') == 'ltr') return FALSE;
+
+		// Check for special translation string 'ui_main.text_direction'
+		if (Kohana::lang('core.text_direction', $locale) == 'rtl') return TRUE;
+
+		return FALSE;
+	}
+
+	/**
+	 * @param   string	 ISO-3166 country code
+	 */
+	public static function country($iso3166)
+	{
+		$iso_array = array(
+			"AD" => "Andorra",
+			"AE" => "United Arab Emirates",
+			"AF" => "Afghanistan",
+			"AG" => "Antigua and Barbuda",
+			"AI" => "Anguilla",
+			"AL" => "Albania",
+			"AM" => "Armenia",
+			"AN" => "Netherlands Antilles",
+			"AO" => "Angola",
+			"AQ" => "Antarctica",
+			"AR" => "Argentina",
+			"AS" => "American Samoa",
+			"AT" => "Austria",
+			"AU" => "Australia",
+			"AW" => "Aruba",
+			"AX" => "Aland Islands",
+			"AZ" => "Azerbaijan",
+			"BA" => "Bosnia and Herzegovina",
+			"BB" => "Barbados",
+			"BD" => "Bangladesh",
+			"BE" => "Belgium",
+			"BF" => "Burkina Faso",
+			"BG" => "Bulgaria",
+			"BH" => "Bahrain",
+			"BI" => "Burundi",
+			"BJ" => "Benin",
+			"BM" => "Bermuda",
+			"BN" => "Brunei Darussalam",
+			"BO" => "Bolivia",
+			"BR" => "Brazil",
+			"BS" => "Bahamas",
+			"BT" => "Bhutan",
+			"BV" => "Bouvet Island",
+			"BW" => "Botswana",
+			"BY" => "Belarus",
+			"BZ" => "Belize",
+			"CA" => "Canada",
+			"CC" => "Cocos (Keeling) Islands",
+			"CD" => "Congo, The Democratic Republic of the",
+			"CF" => "Central African Republic",
+			"CG" => "Congo",
+			"CH" => "Switzerland",
+			"CI" => "Cote d'Ivoire",
+			"CK" => "Cook Islands",
+			"CL" => "Chile",
+			"CM" => "Cameroon",
+			"CN" => "China",
+			"CO" => "Colombia",
+			"CR" => "Costa Rica",
+			"CU" => "Cuba",
+			"CV" => "Cape Verde",
+			"CX" => "Christmas Island",
+			"CY" => "Cyprus",
+			"CZ" => "Czech Republic",
+			"DE" => "Germany",
+			"DJ" => "Djibouti",
+			"DK" => "Denmark",
+			"DM" => "Dominica",
+			"DO" => "Dominican Republic",
+			"DZ" => "Algeria",
+			"EC" => "Ecuador",
+			"EE" => "Estonia",
+			"EG" => "Egypt",
+			"EH" => "Western Sahara",
+			"ER" => "Eritrea",
+			"ES" => "Spain",
+			"ET" => "Ethiopia",
+			"FI" => "Finland",
+			"FJ" => "Fiji",
+			"FK" => "Falkland Islands (Malvinas)",
+			"FM" => "Micronesia, Federated States of",
+			"FO" => "Faroe Islands",
+			"FR" => "France",
+			"GA" => "Gabon",
+			"GB" => "United Kingdom",
+			"GD" => "Grenada",
+			"GE" => "Georgia",
+			"GF" => "French Guiana",
+			"GG" => "Guernsey",
+			"GH" => "Ghana",
+			"GI" => "Gibraltar",
+			"GL" => "Greenland",
+			"GM" => "Gambia",
+			"GN" => "Guinea",
+			"GP" => "Guadeloupe",
+			"GQ" => "Equatorial Guinea",
+			"GR" => "Greece",
+			"GS" => "South Georgia and the South Sandwich Islands",
+			"GT" => "Guatemala",
+			"GU" => "Guam",
+			"GW" => "Guinea-Bissau",
+			"GY" => "Guyana",
+			"HK" => "Hong Kong",
+			"HM" => "Heard Island and McDonald Islands",
+			"HN" => "Honduras",
+			"HR" => "Croatia",
+			"HT" => "Haiti",
+			"HU" => "Hungary",
+			"ID" => "Indonesia",
+			"IE" => "Ireland",
+			"IL" => "Israel",
+			"IM" => "Isle of Man",
+			"IN" => "India",
+			"IO" => "British Indian Ocean Territory",
+			"IQ" => "Iraq",
+			"IR" => "Iran, Islamic Republic of",
+			"IS" => "Iceland",
+			"IT" => "Italy",
+			"JE" => "Jersey",
+			"JM" => "Jamaica",
+			"JO" => "Jordan",
+			"JP" => "Japan",
+			"KE" => "Kenya",
+			"KG" => "Kyrgyzstan",
+			"KH" => "Cambodia",
+			"KI" => "Kiribati",
+			"KM" => "Comoros",
+			"KN" => "Saint Kitts and Nevis",
+			"KP" => "Korea, Democratic People's Republic of",
+			"KR" => "Korea, Republic of",
+			"KW" => "Kuwait",
+			"KY" => "Cayman Islands",
+			"KZ" => "Kazakhstan",
+			"LA" => "Lao People's Democratic Republic",
+			"LB" => "Lebanon",
+			"LC" => "Saint Lucia",
+			"LI" => "Liechtenstein",
+			"LK" => "Sri Lanka",
+			"LR" => "Liberia",
+			"LS" => "Lesotho",
+			"LT" => "Lithuania",
+			"LU" => "Luxembourg",
+			"LV" => "Latvia",
+			"LY" => "Libyan Arab Jamahiriya",
+			"MA" => "Morocco",
+			"MC" => "Monaco",
+			"MD" => "Moldova, Republic of",
+			"ME" => "Montenegro",
+			"MG" => "Madagascar",
+			"MH" => "Marshall Islands",
+			"MK" => "Macedonia",
+			"ML" => "Mali",
+			"MM" => "Myanmar",
+			"MN" => "Mongolia",
+			"MO" => "Macao",
+			"MP" => "Northern Mariana Islands",
+			"MQ" => "Martinique",
+			"MR" => "Mauritania",
+			"MS" => "Montserrat",
+			"MT" => "Malta",
+			"MU" => "Mauritius",
+			"MV" => "Maldives",
+			"MW" => "Malawi",
+			"MX" => "Mexico",
+			"MY" => "Malaysia",
+			"MZ" => "Mozambique",
+			"NA" => "Namibia",
+			"NC" => "New Caledonia",
+			"NE" => "Niger",
+			"NF" => "Norfolk Island",
+			"NG" => "Nigeria",
+			"NI" => "Nicaragua",
+			"NL" => "Netherlands",
+			"NO" => "Norway",
+			"NP" => "Nepal",
+			"NR" => "Nauru",
+			"NU" => "Niue",
+			"NZ" => "New Zealand",
+			"OM" => "Oman",
+			"PA" => "Panama",
+			"PE" => "Peru",
+			"PF" => "French Polynesia",
+			"PG" => "Papua New Guinea",
+			"PH" => "Philippines",
+			"PK" => "Pakistan",
+			"PL" => "Poland",
+			"PM" => "Saint Pierre and Miquelon",
+			"PN" => "Pitcairn",
+			"PR" => "Puerto Rico",
+			"PS" => "Palestinian Territory",
+			"PT" => "Portugal",
+			"PW" => "Palau",
+			"PY" => "Paraguay",
+			"QA" => "Qatar",
+			"RE" => "Reunion",
+			"RO" => "Romania",
+			"RS" => "Serbia",
+			"RU" => "Russian Federation",
+			"RW" => "Rwanda",
+			"SA" => "Saudi Arabia",
+			"SB" => "Solomon Islands",
+			"SC" => "Seychelles",
+			"SD" => "Sudan",
+			"SE" => "Sweden",
+			"SG" => "Singapore",
+			"SH" => "Saint Helena",
+			"SI" => "Slovenia",
+			"SJ" => "Svalbard and Jan Mayen",
+			"SK" => "Slovakia",
+			"SL" => "Sierra Leone",
+			"SM" => "San Marino",
+			"SN" => "Senegal",
+			"SO" => "Somalia",
+			"SR" => "Suriname",
+			"ST" => "Sao Tome and Principe",
+			"SV" => "El Salvador",
+			"SY" => "Syrian Arab Republic",
+			"SZ" => "Swaziland",
+			"TC" => "Turks and Caicos Islands",
+			"TD" => "Chad",
+			"TF" => "French Southern Territories",
+			"TG" => "Togo",
+			"TH" => "Thailand",
+			"TJ" => "Tajikistan",
+			"TK" => "Tokelau",
+			"TL" => "Timor-Leste",
+			"TM" => "Turkmenistan",
+			"TN" => "Tunisia",
+			"TO" => "Tonga",
+			"TR" => "Turkey",
+			"TT" => "Trinidad and Tobago",
+			"TV" => "Tuvalu",
+			"TW" => "Taiwan",
+			"TZ" => "Tanzania, United Republic of",
+			"UA" => "Ukraine",
+			"UG" => "Uganda",
+			"UM" => "United States Minor Outlying Islands",
+			"US" => "United States",
+			"UY" => "Uruguay",
+			"UZ" => "Uzbekistan",
+			"VA" => "Holy See (Vatican City State)",
+			"VC" => "Saint Vincent and the Grenadines",
+			"VE" => "Venezuela",
+			"VG" => "Virgin Islands, British",
+			"VI" => "Virgin Islands, U.S.",
+			"VN" => "Vietnam",
+			"VU" => "Vanuatu",
+			"WF" => "Wallis and Futuna",
+			"WS" => "Samoa",
+			"YE" => "Yemen",
+			"YT" => "Mayotte",
+			"ZA" => "South Africa",
+			"ZM" => "Zambia",
+			"ZW" => "Zimbabwe"
+		);
+
+		if (array_key_exists($iso3166, $iso_array))
+		{
+			return $iso_array[$iso3166];
+		}
+		else
+		{
+			return Kohana::lang('ui_admin.unknown');
+		}
+	}
+
+	/**
+	 * checks the i18n folder to see what folders we have available
+	 * @param boolean Force reloading locale cache
+	 */
+	public static function get_i18n($refresh = FALSE)
+	{
+		// If we had cached locales return those
+		if (! $refresh)
+		{
+			$locales = Cache::instance()->get('locales');
+			if ( $locales )
+			{
+				return $locales;
+			}
+		}
+
+		$locales = array();
+
+		// i18n path
+		$i18n_path = APPPATH.'i18n/';
+
+		// i18n folder
+		$i18n_folder = @ opendir($i18n_path);
+
+		if ( !$i18n_folder )
+			return false;
+
+		while ( ($i18n_dir = readdir($i18n_folder)) !== false )
+		{
+			if ( is_dir($i18n_path.$i18n_dir) && is_readable($i18n_path.$i18n_dir) )
+			{
+				// Strip out .  and .. and any other stuff
+				if ( $i18n_dir{0} == '.' || $i18n_dir == '..'
+				 	|| $i18n_dir ==  '.DS_Store' || $i18n_dir == '.git')
+					continue;
+
+				$locale = explode("_", $i18n_dir);
+				if ( count($locale) < 2 AND ! ush_locale::language($locale[0]))
+					continue;
+
+				$locales[$i18n_dir] = ush_locale::language($locale[0]) ? ush_locale::language($locale[0]) : $locale[0];
+				$locales[$i18n_dir] .= isset($locale[1]) ? " (".$locale[1].")" : "";
+			}
+		}
+
+		if ( is_dir( $i18n_dir ) )
+			@closedir( $i18n_dir );
+
+		Cache::instance()->set('locales', $locales, array('locales'), 604800);
+
+		return $locales;
+	}
+	
+	/**
+	 * Detect language from GET param, session or settings.
+	 */
+	public static function detect_language()
+	{
+		// Locale form submitted?
+		if (isset($_GET['l']) && !empty($_GET['l']))
+		{
+			Session::instance()->set('locale', $_GET['l']);
+		}
+
+		// Has a locale session been set?
+		if (Session::instance()->get('locale',FALSE))
+		{
+			// Change current locale
+			Kohana::config_set('locale.language', $_SESSION['locale']);
+		}
+	}
+}
diff --git a/application/helpers/valid.php b/application/helpers/valid.php
new file mode 100644
index 0000000..86b7f9c
--- /dev/null
+++ b/application/helpers/valid.php
@@ -0,0 +1,400 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Validation helper class.
+ *
+ * $Id: valid.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class valid_Core {
+
+	/**
+	 * Validate email, commonly used characters only
+	 *
+	 * @param   string   email address
+	 * @return  boolean
+	 */
+	public static function email($email)
+	{
+		return (bool) preg_match('/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d++)?$/iD', (string) $email);
+	}
+
+	/**
+	 * Validate the domain of an email address by checking if the domain has a
+	 * valid MX record.
+	 *
+	 * @param   string   email address
+	 * @return  boolean
+	 */
+	public static function email_domain($email)
+	{
+		// If we can't prove the domain is invalid, consider it valid
+		// Note: checkdnsrr() is not implemented on Windows platforms
+		if ( ! function_exists('checkdnsrr'))
+			return TRUE;
+
+		// Check if the email domain has a valid MX record
+		return (bool) checkdnsrr(preg_replace('/^[^@]+@/', '', $email), 'MX');
+	}
+
+	/**
+	 * Validate email, RFC compliant version
+	 * Note: This function is LESS strict than valid_email. Choose carefully.
+	 *
+	 * @see  Originally by Cal Henderson, modified to fit Kohana syntax standards:
+	 * @see  http://www.iamcal.com/publish/articles/php/parsing_email/
+	 * @see  http://www.w3.org/Protocols/rfc822/
+	 *
+	 * @param   string   email address
+	 * @return  boolean
+	 */
+	public static function email_rfc($email)
+	{
+		$qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
+		$dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
+		$atom  = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
+		$pair  = '\\x5c[\\x00-\\x7f]';
+
+		$domain_literal = "\\x5b($dtext|$pair)*\\x5d";
+		$quoted_string  = "\\x22($qtext|$pair)*\\x22";
+		$sub_domain     = "($atom|$domain_literal)";
+		$word           = "($atom|$quoted_string)";
+		$domain         = "$sub_domain(\\x2e$sub_domain)*";
+		$local_part     = "$word(\\x2e$word)*";
+		$addr_spec      = "$local_part\\x40$domain";
+
+		return (bool) preg_match('/^'.$addr_spec.'$/D', (string) $email);
+	}
+
+	/**
+	 * Validate URL
+	 *
+	 * @param   string   URL
+	 * @param   bool     allow idna/unicode urls
+	 * @return  boolean
+	 */
+	public static function url($url, $idna = TRUE)
+	{
+		if ($idna) $url = IDNA_convert::singleton()->encode($url);
+		
+		return $url AND (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
+	}
+
+	/**
+	 * Validate IP
+	 *
+	 * @param   string   IP address
+	 * @param   boolean  allow IPv6 addresses
+	 * @param   boolean  allow private IP networks
+	 * @return  boolean
+	 */
+	public static function ip($ip, $ipv6 = FALSE, $allow_private = TRUE)
+	{
+		// By default do not allow private and reserved range IPs
+		$flags = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE;
+		if ($allow_private === TRUE)
+			$flags =  FILTER_FLAG_NO_RES_RANGE;
+
+		if ($ipv6 === TRUE)
+			return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags);
+
+		return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags | FILTER_FLAG_IPV4);
+	}
+
+	/**
+	 * Validates a credit card number using the Luhn (mod10) formula.
+	 * @see http://en.wikipedia.org/wiki/Luhn_algorithm
+	 *
+	 * @param   integer       credit card number
+	 * @param   string|array  card type, or an array of card types
+	 * @return  boolean
+	 */
+	public static function credit_card($number, $type = NULL)
+	{
+		// Remove all non-digit characters from the number
+		if (($number = preg_replace('/\D+/', '', $number)) === '')
+			return FALSE;
+
+		if ($type == NULL)
+		{
+			// Use the default type
+			$type = 'default';
+		}
+		elseif (is_array($type))
+		{
+			foreach ($type as $t)
+			{
+				// Test each type for validity
+				if (valid::credit_card($number, $t))
+					return TRUE;
+			}
+
+			return FALSE;
+		}
+
+		$cards = Kohana::config('credit_cards');
+
+		// Check card type
+		$type = strtolower($type);
+
+		if ( ! isset($cards[$type]))
+			return FALSE;
+
+		// Check card number length
+		$length = strlen($number);
+
+		// Validate the card length by the card type
+		if ( ! in_array($length, preg_split('/\D+/', $cards[$type]['length'])))
+			return FALSE;
+
+		// Check card number prefix
+		if ( ! preg_match('/^'.$cards[$type]['prefix'].'/', $number))
+			return FALSE;
+
+		// No Luhn check required
+		if ($cards[$type]['luhn'] == FALSE)
+			return TRUE;
+
+		// Checksum of the card number
+		$checksum = 0;
+
+		for ($i = $length - 1; $i >= 0; $i -= 2)
+		{
+			// Add up every 2nd digit, starting from the right
+			$checksum += substr($number, $i, 1);
+		}
+
+		for ($i = $length - 2; $i >= 0; $i -= 2)
+		{
+			// Add up every 2nd digit doubled, starting from the right
+			$double = substr($number, $i, 1) * 2;
+
+			// Subtract 9 from the double where value is greater than 10
+			$checksum += ($double >= 10) ? $double - 9 : $double;
+		}
+
+		// If the checksum is a multiple of 10, the number is valid
+		return ($checksum % 10 === 0);
+	}
+
+	/**
+	 * Checks if a phone number is valid.
+	 *
+	 * @param   string   phone number to check
+	 * @return  boolean
+	 */
+	public static function phone($number, $lengths = NULL)
+	{
+		if ( ! is_array($lengths))
+		{
+			$lengths = array(7,10,11);
+		}
+
+		// Remove all non-digit characters from the number
+		$number = preg_replace('/\D+/', '', $number);
+
+		// Check if the number is within range
+		return in_array(strlen($number), $lengths);
+	}
+
+	/**
+	 * Tests if a string is a valid date string.
+	 * 
+	 * @param   string   date to check
+	 * @return  boolean
+	 */
+	public function date($str)
+	{
+		return (strtotime($str) !== FALSE);
+	}
+
+	/**
+	 * Checks whether a string consists of alphabetical characters only.
+	 *
+	 * @param   string   input string
+	 * @param   boolean  trigger UTF-8 compatibility
+	 * @return  boolean
+	 */
+	public static function alpha($str, $utf8 = FALSE)
+	{
+		return ($utf8 === TRUE)
+			? (bool) preg_match('/^\pL++$/uD', (string) $str)
+			: ctype_alpha((string) $str);
+	}
+
+	/**
+	 * Checks whether a string consists of alphabetical characters and numbers only.
+	 *
+	 * @param   string   input string
+	 * @param   boolean  trigger UTF-8 compatibility
+	 * @return  boolean
+	 */
+	public static function alpha_numeric($str, $utf8 = FALSE)
+	{
+		return ($utf8 === TRUE)
+			? (bool) preg_match('/^[\pL\pN]++$/uD', (string) $str)
+			: ctype_alnum((string) $str);
+	}
+
+	/**
+	 * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
+	 *
+	 * @param   string   input string
+	 * @param   boolean  trigger UTF-8 compatibility
+	 * @return  boolean
+	 */
+	public static function alpha_dash($str, $utf8 = FALSE)
+	{
+		return ($utf8 === TRUE)
+			? (bool) preg_match('/^[-\pL\pN_]++$/uD', (string) $str)
+			: (bool) preg_match('/^[-a-z0-9_]++$/iD', (string) $str);
+	}
+
+	/**
+	 * Checks whether a string consists of digits only (no dots or dashes).
+	 *
+	 * @param   string   input string
+	 * @param   boolean  trigger UTF-8 compatibility
+	 * @return  boolean
+	 */
+	public static function digit($str, $utf8 = FALSE)
+	{
+		return ($utf8 === TRUE)
+			? (bool) preg_match('/^\pN++$/uD', (string) $str)
+			: ctype_digit((string) $str);
+	}
+
+	/**
+	 * Checks whether a string is a valid number (negative and decimal numbers allowed).
+	 *
+	 * @see Uses locale conversion to allow decimal point to be locale specific.
+	 * @see http://www.php.net/manual/en/function.localeconv.php
+	 * 
+	 * @param   string   input string
+	 * @return  boolean
+	 */
+	public static function numeric($str)
+	{
+		// Use localeconv to set the decimal_point value: Usually a comma or period.
+		$locale = localeconv();
+		return (preg_match('/^[-0-9'.$locale['decimal_point'].']++$/D', (string) $str));
+	}
+
+	/**
+	 * Checks whether a string is a valid text. Letters, numbers, whitespace,
+	 * dashes, periods, and underscores are allowed.
+	 *
+	 * @param   string   text to check
+	 * @return  boolean
+	 */
+	public static function standard_text($str)
+	{
+		// pL matches letters
+		// pN matches numbers
+		// pZ matches whitespace
+		// pPc matches underscores
+		// pPd matches dashes
+		// pPo matches normal puncuation
+		return (bool) preg_match('/^[\pL\pN\pZ\p{Pc}\p{Pd}\p{Po}]++$/uD', (string) $str);
+	}
+
+	/**
+	 * Checks if a string is a proper decimal format. The format array can be
+	 * used to specify a decimal length, or a number and decimal length, eg:
+	 * array(2) would force the number to have 2 decimal places, array(4,2)
+	 * would force the number to have 4 digits and 2 decimal places.
+	 *
+	 * @param   string   input string
+	 * @param   array    decimal format: y or x,y
+	 * @return  boolean
+	 */
+	public static function decimal($str, $format = NULL)
+	{
+		// Create the pattern
+		$pattern = '/^[0-9]%s\.[0-9]%s$/';
+
+		if ( ! empty($format))
+		{
+			if (count($format) > 1)
+			{
+				// Use the format for number and decimal length
+				$pattern = sprintf($pattern, '{'.$format[0].'}', '{'.$format[1].'}');
+			}
+			elseif (count($format) > 0)
+			{
+				// Use the format as decimal length
+				$pattern = sprintf($pattern, '+', '{'.$format[0].'}');
+			}
+		}
+		else
+		{
+			// No format
+			$pattern = sprintf($pattern, '+', '+');
+		}
+
+		return (bool) preg_match($pattern, (string) $str);
+	}
+	
+	/**
+	 * Checks whether a string is a valid number (negative and decimal numbers allowed) and between a specified range.
+	 *
+	 * @param   string   input string
+	 * @param   float (MIN/MAX)
+	 * @return  boolean
+	 */
+	public static function between($str, $min_max = array(0,0))
+	{
+		$set_min = $min_max[0];
+		$set_max = $min_max[1];
+		$str = (float) $str;
+		return (is_numeric($str) AND preg_match('/^[-0-9.]++$/D', (string) $str) AND ($str <= $set_max AND $str >= $set_min) );
+	}
+	
+	/**
+	 * Checks whether a string is a valid date (mm/dd/yyyy).
+	 *
+	 * @param   string   input string
+	 * @return  boolean
+	 */
+	public static function date_mmddyyyy($str)
+	{
+		$str = str_replace(' ', '-', $str);
+		$str = str_replace('/', '-', $str);
+		$str = str_replace('--', '-', $str);
+		preg_match('/^(\d{2})-(\d{2})-(\d{4})$/', $str, $xadBits);
+		if (is_array($xadBits) && count($xadBits) > 3)
+		{
+			return checkdate($xadBits[1], $xadBits[2], $xadBits[3]);
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Checks whether a string is a valid date (dd/mm/yyyy).
+	 *
+	 * @param   string   input string
+	 * @return  boolean
+	 */
+	public static function date_ddmmyyyy($str)
+	{
+		$str = str_replace(' ', '-', $str);
+		$str = str_replace('/', '-', $str);
+		$str = str_replace('--', '-', $str);
+		preg_match('/^(\d{2})-(\d{2})-(\d{4})$/', $str, $xadBits);
+		if (is_array($xadBits) && count($xadBits) > 3)
+		{
+			return checkdate($xadBits[2], $xadBits[1], $xadBits[3]);
+		}
+		else
+		{
+			return false;
+		}
+	}	
+
+} // End valid
\ No newline at end of file
diff --git a/application/helpers/xml.php b/application/helpers/xml.php
new file mode 100644
index 0000000..87b04d4
--- /dev/null
+++ b/application/helpers/xml.php
@@ -0,0 +1,124 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * XML Import/Export helper class.
+ *
+ * @package	   Admin
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2012 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+class xml_Core{
+	
+	/**
+	 * Given an XMLWriter instance, an associative array map and 
+	 * array of attribute/element tags to match with the array map,
+	 * generate element/attribute tags
+	 * @param XMLWriter object $writer
+	 * @param Associative array map $object_map
+	 * @param array $elements
+	 *
+	 */
+	public static function generate_tags( $writer, $object_map, $elements)
+	{
+		foreach ($elements as $element)
+		{	
+			// For Attributes
+			if (array_key_exists($element, $object_map['attributes']))
+			{
+				$writer->startAttribute($element);
+					$writer->text($object_map['attributes'][$element]);
+			}
+
+			// For elements
+			elseif (array_key_exists($element, $object_map['elements']))
+			{
+				$writer->startElement($element);
+					$writer->text($object_map['elements'][$element]);
+				$writer->endElement();
+			}
+		}	
+	}
+	
+	/**
+	 * Given an ORM object and an associative array, generates and returns
+	 * an associative array with the corresponding values from the ORM object
+	 * e.g given ORM Object Incident with id = 1 and title = 'Test Incident'
+	 * Pass associative array $map = array(
+	 *									'attributes' => array('id' => 'id'),
+	 *									'elements' => array('title' => 'incident_title')
+	 *									)
+	 *
+	 * Associative array $returned = array(
+	 *									'attributes' => array('id' => 1),
+	 *									'elements' => array('title' => 'Test Incident')
+	 * 									)
+	 *
+	 * @param object $object
+	 * @param array $map 
+	 * @return array
+	 */
+	public static function generate_element_attribute_map($object, $map)
+	{
+		$output_map = array(
+			'elements' => array(),
+			'attributes' => array()
+		);
+		
+		// For each item in 'attributes' in the map, get the value from the (orm) object
+		foreach ($map['attributes'] as $attribute => $column)
+		{
+			$output_map['attributes'][$attribute] = $object->$column;
+		}
+		
+		// For each element in 'elements', get corresponding value from orm object
+		foreach ($map['elements'] as $element => $column)
+		{
+			$output_map['elements'][$element] = $object->$column;
+		}
+		
+		return $output_map;
+	}
+	
+	/**
+	 * Get node values from DOMNodeList element /attribute, and sanitize
+	 * @param DOMNodeList Object $node
+	 * @param string Element within DOMNodelist object $tag_name
+	 * @param boolean $element Set to FALSE if getting an attribute value
+	 * @return mixed String if the node value exists, FALSE otherwise
+	 */	
+	public function get_node_text($node, $tag_name, $element = TRUE)
+	{
+		$node_value = NULL;
+		try
+		{
+			// This is an element
+			if ($element)
+			{
+				if ($node->getElementsByTagName($tag_name)->length > 0)
+				{
+					$element = $node->getElementsByTagName($tag_name)->item(0);
+					$node_value = trim($element->nodeValue);
+				}
+				else
+				{
+					return FALSE;
+				}
+			}
+			
+			// This is an attribute
+			else
+			{
+				$attribute = $node->getAttribute($tag_name);
+				$node_value = trim($attribute);
+			}
+		}
+		catch (Exception $e)
+		{
+			Kohana::log("xml_upload_error", $e->getMessage());
+			return FALSE;
+		}
+		
+		return ! empty($node_value) ? $node_value : FALSE;		
+	}
+}
+?>
\ No newline at end of file
diff --git a/application/hooks/1_maintenance.php b/application/hooks/1_maintenance.php
new file mode 100644
index 0000000..6ef7a7f
--- /dev/null
+++ b/application/hooks/1_maintenance.php
@@ -0,0 +1,84 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Check for maintenance mode
+*   In order to put your site in maintenance mode using this method, add at least one
+*   row to the `maintenance` table. If you don't want to allow any IP addresses, simply
+*   add a random string to the allowed_ip field. A better method for putting your
+*   site into maintenance mode is to simply change the filename of your maintenance_off.php
+*   to maintenance.php and all users will be told the site is undergoing maintenance.
+*/
+
+// running at the command line, fake some server values
+if (php_sapi_name() == 'cli')
+{
+	$_SERVER['SERVER_PROTOCOL'] = "HTTP/1.1";
+	$_SERVER['HTTP_HOST'] = 'localhost';
+	$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+	$_SERVER['REQUEST_URI'] = Router::$current_uri;
+}
+
+// Grab the IP address in case we need to use it for maintenance mode
+$ip_address = FALSE;
+if ( ! empty($_SERVER['HTTP_CLIENT_IP']))
+{
+	$ip_address = $_SERVER['HTTP_CLIENT_IP'];
+}
+elseif ( ! empty($_SERVER['HTTP_X_FORWARDED_FOR']))
+{
+	$ip_address  = $_SERVER['HTTP_X_FORWARDED_FOR'];
+}
+else
+{
+	$ip_address = $_SERVER['REMOTE_ADDR'];
+}
+
+$maintenance = FALSE;
+try {
+	$db = Database::instance();
+	$maintenance_ips = $db->query("SELECT `allowed_ip` FROM `".Kohana::config('database.default.table_prefix')."maintenance`;");
+	foreach ($maintenance_ips as $row)
+	{
+		// Assume we will be in maintenance mode now
+		$maintenance = TRUE;
+
+		// Check if we should be allowed to bypass maintenance
+		if ($ip_address == $row->allowed_ip)
+		{
+			$maintenance = FALSE;
+			// Since we already matched an IP, no need to keep looping
+			break;
+		}
+	}
+}
+catch (Exception $e)
+{}
+
+// If we are in maintenance mode and didn't match the IP, show maintenance message
+if ($maintenance == TRUE)
+{
+	// Find out maintenance file and output or output a simple message
+
+	header("Status: 503 Service Temporarily Unavailable");
+
+	if(file_exists('maintenance_off.php'))
+	{
+		// maintenance_off.php is our default maintenance message file
+		$contents = file_get_contents('maintenance_off.php');
+	}
+	elseif(file_exists('maintenance.php'))
+	{
+		// maintenance.php shouldn't exist if we've gotten this far but check anyway
+		$contents = file_get_contents('maintenance.php');
+	}
+	else
+	{
+		$contents = Kohana::lang('maintenance.message');
+	}
+
+	// Kill the script and tell the user the site is down for maintenance
+	die($contents);
+
+}
+
+?>
diff --git a/application/hooks/2_settings.php b/application/hooks/2_settings.php
new file mode 100644
index 0000000..4c49a54
--- /dev/null
+++ b/application/hooks/2_settings.php
@@ -0,0 +1,90 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Default Settings From Database
+*/
+
+// Retrieve Cached Settings
+
+$cache = Cache::instance();
+$subdomain = Kohana::config('settings.subdomain');
+$settings = $cache->get($subdomain.'_settings');
+if ( ! $settings OR ! is_array($settings))
+{ // Cache is Empty so Re-Cache
+	$settings = Settings_Model::get_array();
+	$cache->set($subdomain.'_settings', $settings, array('settings'), 60); // 1 Day
+}
+
+// Set Site Language
+Kohana::config_set('locale.language', $settings['site_language']);
+ush_locale::detect_language();
+
+// Copy everything into kohana config settings.XYZ
+foreach($settings as $key => $setting)
+{
+	Kohana::config_set('settings.'.$key, $setting);
+}
+
+// Set Site Timezone
+if (function_exists('date_default_timezone_set'))
+{
+	$timezone = isset($settings['site_timezone']) ? $settings['site_timezone'] : null;
+	// Set default timezone, due to increased validation of date settings
+	// which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+
+	date_default_timezone_set(empty($timezone) ? date_default_timezone_get() : $timezone);
+	Kohana::config_set('settings.site_timezone', $timezone);
+}
+
+// Cache Settings
+$cache_pages = (isset($settings['cache_pages']) AND $settings['cache_pages']) ? TRUE : FALSE;
+Kohana::config_set('cache.cache_pages', $cache_pages);
+Kohana::config_set('cache.default.lifetime', isset($settings['cache_pages_lifetime']) ? $settings['cache_pages_lifetime'] : 1800);
+
+$default_map = isset($settings['default_map']) ? $settings['default_map'] : 'osm_mapnik';
+$map_layer = map::base($default_map);
+if (! empty($map_layer->api_url))
+{
+	Kohana::config_set('settings.api_url', $map_layer->api_url);
+}
+
+// And in case you want to display all maps on one page...
+$api_url_all = array();
+foreach (map::base() as $layer)
+{
+	if (empty($layer->api_url)) continue;
+	// Add to array, use url as key to avoid dupes
+	$api_url_all[$layer->api_url] = $layer->api_url;
+}
+Kohana::config_set('settings.api_url_all', $api_url_all);
+
+// Additional Mime Types (KMZ/KML)
+Kohana::config_set('mimes.kml', array('text/xml'));
+Kohana::config_set('mimes.kmz', array('text/xml'));
+
+// Set 'settings.forgot_password_key' if not set already
+if ( ! Kohana::config('settings.forgot_password_secret'))
+{
+	$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+[]{};:,.?`~';
+	$key = text::random($pool, 64);
+	Settings_Model::save_setting('forgot_password_secret', $key);
+	Kohana::config_set('settings.forgot_password_secret', $key);
+	$cache->delete($subdomain.'_settings');
+}
+
+// Set dfault value for external site protocol
+if ( ! Kohana::config('config.external_site_protocol'))
+{
+	Kohana::config_set('config.external_site_protocol', 'https');
+}
+
+// Default for allowed_html in case upgraders don't add it to config.php
+if ( ! Kohana::config('config.allowed_html') )
+{
+  Kohana::config_set('config.allowed_html', 'a[href|title],p,img[src|alt],br,b,u,strong,em,i'); 
+}
+// Default for allowed iframe regexp, in case upgraders don't add it to config.php
+if ( ! Kohana::config('config.allowed_iframe_regexp') )
+{
+  Kohana::config_set('config.allowed_iframe_regexp', '%^http://(www.youtube.com/embed/|player.vimeo.com/video/|w.soundcloud.com/player)%'); 
+}
+
diff --git a/application/hooks/actions.php b/application/hooks/actions.php
new file mode 100644
index 0000000..2b3d68e
--- /dev/null
+++ b/application/hooks/actions.php
@@ -0,0 +1,899 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Actions Hook
+ * This file processes all of the administrator defined actions
+ * in the actions section of the admin panel.
+ * The "actioner" class waits for events to be fired
+ * and then performs user defined actions
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Browser Hoook
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class actioner {
+
+	protected $db;
+
+	protected $data;
+
+	protected $qualifiers;
+
+	protected $action_id;
+
+	/**
+	 * Registers the main event add method
+	 */
+	public function __construct()
+	{
+		$this->db = new Database();
+
+		// Hook into routing
+		Event::add('system.pre_controller', array($this, 'add'));
+	}
+
+	/**
+	 * Adds all the events to the main Ushahidi application
+	 */
+	public function add()
+	{
+		Event::add('ushahidi_action.report_add', array($this, '_report_add'));
+		Event::add('ushahidi_action.message_twitter_add', array($this, '_message_twitter_add'));
+		Event::add('ushahidi_action.feed_item_add', array($this, '_feed_item_add'));
+	}
+
+	// START ACTION TRIGGER FUNCTIONS
+
+	/**
+	 * Routes twitter messages to the message add action trigger
+	 */
+	public function _message_twitter_add()
+	{
+		$this->_message_add('message_twitter_add');
+	}
+
+	/**
+	 * Perform actions for added messages
+	 */
+	public function _message_add($message_action)
+	{
+		$this->data = Event::$data;
+
+		// Our vars
+		/*
+		$this->data->message; // tweet
+		$this->data->message_date; // tweet datetime
+		$this->data->latitude;
+		$this->data->longitude;
+		*/
+
+		// Grab all action triggers that involve this fired action
+		$actions = $this->_get_actions($message_action);
+
+		// Get out of here as fast as possible if there are no actions.
+		if($actions->count() <= 0) return false;
+
+		foreach ($actions as $action)
+		{
+			// Collect variables for this action
+			$this->action_id = $action->action_id;
+			$trigger = $action->action;
+			$this->qualifiers = unserialize($action->qualifiers);
+			$response = $action->response;
+			$response_vars = unserialize($action->response_vars);
+
+			// If geometry isn't set because we didn't have any geometry to pass, set it as false
+			//   to prevent errors when we need to call the variable later in the script.
+			if( ! isset($this->qualifiers['geometry'])) $this->qualifiers['geometry'] = FALSE;
+
+			// Check if we qualify for performing the response
+
+			// --- Check Location
+
+			// We can only consider location if location is set
+			if ( isset($this->data->latitude) AND $this->data->latitude != NULL
+				AND isset($this->data->longitude) AND $this->data->longitude != NULL)
+			{
+				$point = array('lat' => $this->data->latitude, 'lon' => $this->data->longitude);
+			}
+			else
+			{
+				$point = array('lat'=>FALSE,'lon'=>FALSE);
+			}
+
+			if( ! $this->__check_location($this->qualifiers['location'],$this->qualifiers['geometry'],$point))
+			{
+				// Not the right location
+				continue;
+			}
+			
+			// --- Check author against message from
+
+			if( ! $this->__check_from($this->qualifiers['from'],$this->data->message_from))
+			{
+				// Not the right keyword
+				continue;
+			}
+
+			// --- Check Keywords
+			//     against subject and body. If both fail, then this action doesn't qualify
+
+			if( ! $this->__check_keywords($this->qualifiers['keyword'],$this->data->message) AND
+				! $this->__check_keywords($this->qualifiers['keyword'],$this->data->message_detail)
+				)
+			{
+				// Not the right keyword
+				continue;
+			}
+
+			// --- Check Between Times
+			if( ! $this->__check_between_times(strtotime($this->data->message_date)))
+			{
+				// Not the right time
+				continue;
+			}
+
+			// --- Check Specific Days
+			if( ! $this->__check_specific_days(strtotime($this->data->message_date)))
+			{
+				// Not the right day
+				continue;
+			}
+
+			// --- Begin Response
+
+			// Record that the magic happened
+			$this->__record_log($this->action_id,$this->data->user_id);
+
+			// Qapla! Begin response phase since we passed all of the qualifier tests
+			$this->__perform_response($response,$response_vars);
+		}
+	}
+
+	/**
+	 * Perform actions for added messages
+	 */
+	public function _feed_item_add()
+	{
+		$this->data = Event::$data;
+
+		// Our vars
+		/*
+		$this->data->item_title;
+		$this->data->item_description;
+		$this->data->item_date;
+		$this->data->item_link;
+		$this->data->location->latitude;
+		$this->data->location->longitude;
+		*/
+
+		// Grab all action triggers that involve this fired action
+		$actions = $this->_get_actions('feed_item_add');
+
+		// Get out of here as fast as possible if there are no actions.
+		if($actions->count() <= 0) return false;
+
+		foreach ($actions as $action)
+		{
+			// Collect variables for this action
+			$this->action_id = $action->action_id;
+			$trigger = $action->action;
+			$this->qualifiers = unserialize($action->qualifiers);
+			$response = $action->response;
+			$response_vars = unserialize($action->response_vars);
+
+			// If geometry isn't set because we didn't have any geometry to pass, set it as false
+			//   to prevent errors when we need to call the variable later in the script.
+			if( ! isset($this->qualifiers['geometry'])) $this->qualifiers['geometry'] = FALSE;
+
+			// Check if we qualify for performing the response
+
+			// --- Check Location
+
+			// We can only consider location if location is set
+			if ( isset($this->data->latitude) AND $this->data->latitude != NULL
+				AND isset($this->data->longitude) AND $this->data->longitude != NULL)
+			{
+				$point = array('lat' => $this->data->location->latitude, 'lon' => $this->data->location->longitude);
+			}
+			else
+			{
+				$point = array('lat'=>FALSE,'lon'=>FALSE);
+			}
+
+			if( ! $this->__check_location($this->qualifiers['location'],$this->qualifiers['geometry'],$point))
+			{
+				// Not the right location
+				continue;
+			}
+
+
+			if( ! $this->__check_feed_id($this->qualifiers['feed_id'],$this->data->feed_id))
+			{
+				// Not the right feed
+				continue;
+			}
+			
+			// --- Check Keywords
+			//     against subject and body. If both fail, then this action doesn't qualify
+
+			if( ! $this->__check_keywords($this->qualifiers['keyword'],$this->data->item_description) AND
+				! $this->__check_keywords($this->qualifiers['keyword'],$this->data->item_title)
+				)
+			{
+				// Not the right keyword
+				continue;
+			}
+
+			// --- Check Between Times
+			if( ! $this->__check_between_times(strtotime($this->data->item_date)))
+			{
+				// Not the right time
+				continue;
+			}
+
+			// --- Check Specific Days
+			if( ! $this->__check_specific_days(strtotime($this->data->item_date)))
+			{
+				// Not the right day
+				continue;
+			}
+
+			// --- Begin Response
+
+			// Record that the magic happened
+			$this->__record_log($this->action_id, 0);
+
+			// Qapla! Begin response phase since we passed all of the qualifier tests
+			$this->__perform_response($response, $response_vars);
+		}
+	}
+
+
+	/**
+	 * Perform actions for report_add
+	 */
+	public function _report_add()
+	{
+		$this->data = Event::$data;
+
+		// Grab all action triggers that involve this fired action
+		$actions = $this->_get_actions('report_add');
+
+		// Get out of here as fast as possible if there are no actions.
+		if($actions->count() <= 0) return false;
+
+		foreach ($actions as $action)
+		{
+			// Collect variables for this action
+			$this->action_id = $action->action_id;
+			$trigger = $action->action;
+			$this->qualifiers = unserialize($action->qualifiers);
+			$response = $action->response;
+			$response_vars = unserialize($action->response_vars);
+
+			// If geometry isn't set because we didn't have any geometry to pass, set it as false
+			//   to prevent errors when we need to call the variable later in the script.
+			if( ! isset($this->qualifiers['geometry'])) $this->qualifiers['geometry'] = FALSE;
+
+			// Check if we qualify for performing the response
+
+			// --- Check User
+
+			// If it's not for everyone and the user submitting isn't the user specified, then
+			//   move on to the next action
+			if( ! $this->__check_user($this->qualifiers['user'],$this->data->user_id)){
+				// Not the correct user
+				continue;
+			}
+
+			// --- Check Category
+			if( isset($this->qualifiers['category'])
+				AND ! $this->__check_category($this->qualifiers['category']) ){
+				// Not in the correct category
+				continue;
+			}
+
+			// --- Check Location
+			if( ! $this->__check_location($this->qualifiers['location'],$this->qualifiers['geometry']))
+			{
+				// Not the right location
+				continue;
+			}
+
+			// --- Check Keywords
+			//     against subject and body. If both fail, then this action doesn't qualify
+
+			if( ! $this->__check_keywords($this->qualifiers['keyword'],$this->data->incident_title)
+				AND ! $this->__check_keywords($this->qualifiers['keyword'],$this->data->incident_description))
+			{
+				// Not the right keyword
+				continue;
+			}
+
+			// --- Check Between Times
+			if( ! $this->__check_between_times(strtotime($this->data->incident_date)))
+			{
+				// Not the right time
+				continue;
+			}
+
+			// --- Check Specific Days
+			if( ! $this->__check_specific_days(strtotime($this->data->incident_date)))
+			{
+				// Not the right day
+				continue;
+			}
+
+			// --- Begin Response
+
+			// Record that the magic happened
+			$this->__record_log($this->action_id,$this->data->user_id);
+
+			// Qapla! Begin response phase since we passed all of the qualifier tests
+			$this->__perform_response($response,$response_vars);
+		}
+	}
+
+	// START QUALIFIER CHECK FUNCTIONS
+
+	// Checks if user is global and matches the data passed for userid
+	public function __check_user($user,$user_check_against)
+	{
+		if(($user != 0) AND ($user != $user_check_against)){
+			return false;
+		}
+		return true;
+	}
+
+	// Checks if feed is global and matches the data passed for feedid
+	public function __check_feed_id($feeds, $feed_check_against)
+	{
+		// Return true if no feeds selected
+		if ($feeds == 0 || count($feeds) == 0) return TRUE;
+		
+		// Make sure feeds is an array
+		if (! is_array($feeds)) $feeds = array($feeds);
+		
+		foreach ($feeds as $feed_id)
+		{
+			if($feed_id == $feed_check_against)
+			{
+				// Feed Match!
+				return TRUE;
+			}
+		}
+
+		// Never matched a category
+		return FALSE;
+	}
+
+	// Checks if the data has any categories in the qualifier set of categories
+	public function __check_category($categories)
+	{
+		$data_id = $this->data->id;
+
+		$report_categories = $this->db->from('incident_category')->where(array('incident_id' => $data_id))->get();
+
+		// This data doesn't seem to have any category
+		if($report_categories->count() <= 0) return FALSE;
+
+		foreach ($report_categories as $report_category)
+		{
+			if(in_array($report_category->category_id,$categories))
+			{
+				// Category Match!
+				return TRUE;
+			}
+		}
+
+		// Never matched a category
+		return FALSE;
+	}
+
+	/**
+	 * Checks if the item is on one of the qualified days
+	 *   Accepts a timestamp for $time
+	 */
+	public function __check_specific_days($time)
+	{
+
+		if( ! isset($this->qualifiers['specific_days'])
+			OR ! is_array($this->qualifiers['specific_days']))
+		{
+			// We aren't checking this so pass the test
+			return TRUE;
+		}
+
+		$time = date('Y-m-d',$time);
+		foreach($this->qualifiers['specific_days'] as $day){
+			// Check if the dates match up
+			$day = date('Y-m-d',$day);
+			if($time == $day)
+			{
+				// Found it
+				return TRUE;
+			}
+		}
+
+		// Never matched
+		return FALSE;
+	}
+
+	/**
+	 * Checks if the item is on qualified days of the week
+	 */
+	public function __check_days_of_the_week($time)
+	{
+
+		if( ! isset($this->qualifiers['days_of_the_week'])
+			OR ! is_array($this->qualifiers['days_of_the_week']))
+		{
+			// We aren't checking days_of_the_week so pass the test
+			return TRUE;
+		}
+
+		$days_of_the_week = $this->qualifiers['days_of_the_week'];
+
+		// Make sure everything is lowercase
+		array_walk($days_of_the_week,'strtolower');
+
+		$day = strtolower(date('D'),$time);
+
+		if(in_array($day,$days_of_the_week))
+		{
+			// Found it
+			return TRUE;
+		}
+
+		// Never matched
+		return FALSE;
+	}
+
+	/**
+	 * Checks if the item is between two times, set as the number of seconds from the start of the day
+	 *   The variable being passed, time, is a full timestamp, not the number of seconds from the start
+	 *   of the day.
+	 */
+	public function __check_between_times($time)
+	{
+
+		if( ! isset($this->qualifiers['between_times']) OR $this->qualifiers['between_times'] != 1)
+		{
+			// We aren't checking between_times so pass the test
+			return TRUE;
+		}
+
+		// Convert time to seconds from the start of the day
+		$time_at_beginning_of_today = mktime(0,0,0,date('n'),date('j'),date('Y'));
+
+		$seconds_from_start_of_day = $time - $time_at_beginning_of_today;
+
+		if($this->qualifiers['between_times_1'] <= $seconds_from_start_of_day
+			AND $this->qualifiers['between_times_2'] >= $seconds_from_start_of_day)
+		{
+			return TRUE;
+		}
+
+		// Never matched
+		return FALSE;
+	}
+
+
+	/**
+	 * Takes a location as "lon lat" and checks if it is inside a polygon which
+	 *   is passed as an array like array("lon lat","lon lat","lon lat","lon lat",. . .);
+	 *   As far as I know, the polygon can be as complex as you like.
+	 */
+	public function __check_location($location,$m_geometry,$point=FALSE)
+	{
+
+		if($location == 'specific')
+		{
+			// So location now needs to be in a specific spot. Gotta crunch the numbers to see if the
+			//   lat/lon of this report falls inside the user defined polygons
+
+			$pointLocation = new pointinpoly();
+
+			foreach($m_geometry as $geometry)
+			{
+				// Set the polygon of the fence
+
+				$geometry = str_ireplace('{"geometry":"POLYGON((','',$geometry);
+				$geometry = str_ireplace('))"}','',$geometry);
+				$polygon = explode(',',(string)$geometry);
+
+				// If no point array passed, fall back to a location_id
+
+				if ( ! isset($point['lat']) OR ! isset($point['lon']) )
+				{
+					$location = ORM::factory('location',$this->data->location_id);
+					$point = $location->longitude.' '.$location->latitude;
+				}
+				else
+				{
+					// As a sanity check, fail this if there is no lat/lon but it's still being passed
+					if ($point['lat'] == FALSE OR $point['lon'] == FALSE)
+					{
+						// Get out, we shouldn't even be considering this data for a location search
+						//   so fail it.
+						return false;
+					}
+
+					// Convert the array to a string the pointInPolygon function will understand
+					$point = $point['lon'].' '.$point['lat'];
+				}
+
+				if($pointLocation->pointInPolygon($point, $polygon)){
+					// It's inside the fence!
+					return true;
+				}
+			}
+
+			// It's not inside the fence. Sorry, bro.
+			return false;
+
+		}
+
+		return true;
+	}
+
+	/**
+	 * Takes a CSV list of keywords and checks each of them against a string
+	 */
+	public function __check_keywords($keywords,$string)
+	{
+		if($keywords != '')
+		{
+			// Okay, keywords were defined so lets check to see if the keywords match
+			$exploded_kw = explode(',',$keywords);
+			foreach($exploded_kw as $kw){
+				// if we found it, get out of the function
+				if(stripos($string,$kw) !== FALSE) {
+					return TRUE;
+				}
+			}
+		}else{
+			// If no keywords were set, then you can simply pass this test
+			return TRUE;
+		}
+	}
+
+	/**
+	 * Takes a CSV list of twitter usernames and checks each of them against a string
+	 */
+	public function __check_from($from,$string)
+	{
+		if($from != '')
+		{
+			// Okay, from was defined so lets check to see if the authors match
+			$exploded_author = explode(',',$from);
+			foreach($exploded_author as $author) {
+				// if we found it, get out of the function
+				if(strtolower($string) == strtolower($author)) {
+					return TRUE;
+				}
+			}
+			return FALSE;
+		} else {
+			// If no author was set, then you can simply pass this test
+			return TRUE;
+		}
+		return FALSE;
+	}
+
+	// START COUNT / QUALIFIER / RESPONSE FUNCTIONS
+
+	/**
+	 * Returns TRUE if this is the specific count or if we aren't counting
+	 */
+	public function __pre_response_on_specific_count()
+	{
+		if( ! isset($this->qualifiers['on_specific_count'])
+			OR $this->qualifiers['on_specific_count'] == ''
+			OR $this->qualifiers['on_specific_count'] == 0)
+		{
+			// We arent checking
+			return TRUE;
+		}
+
+		// Count is the specific count we need to hit
+		$count = $this->qualifiers['on_specific_count'];
+
+		// Collective determines if we look up count by user_id or by action_id
+		$collective = FALSE;
+		if(isset($this->qualifiers['on_specific_count_collective'])
+			AND $this->qualifiers['on_specific_count_collective'] == 1)
+		{
+			$collective = TRUE;
+		}
+
+		// Look up count
+		if($collective)
+		{
+			// Search by action_id
+			$check_count = $this->db->where(array('action_id' => $this->action_id))->count_records('actions_log');
+		}else{
+			// Search by user_id
+			$check_count = $this->db->where(array('action_id' => $this->action_id, 'user_id' => $this->data->user_id))->count_records('actions_log');
+		}
+
+		// Count matches
+		if($check_count == $count) return TRUE;
+
+		// We never matched the counts
+		return FALSE;
+	}
+
+	// START RESPONSE FUNCTIONS
+
+	/**
+	 * Routes the response to the appropriate response function for processing
+	 */
+	public function __perform_response($response,$response_vars)
+	{
+		// Go through the list of count qualifiers to see if we should be performing a response or not
+
+		// on_specific_count
+		if( ! $this->__pre_response_on_specific_count()) return FALSE;
+		//if( ! $this->__pre_response_on_specific_count()) return FALSE;
+		// etc ...
+
+		// Route and perform the response
+		switch ($response) {
+			case 'email':
+				return $this->__response_email($response_vars);
+			case 'approve_report':
+				return $this->__response_approve_report($response_vars);
+			case 'log_it':
+				// This response is special in that it does nothing and allows
+				//   a line to be written to the action log
+				return TRUE;
+			case 'assign_badge':
+				return $this->__response_assign_badge($response_vars);
+			case 'create_report':
+				return $this->__response_create_report($response_vars);
+			default:
+				return FALSE;
+		}
+
+		return FALSE;
+
+	}
+
+	/**
+	 * Saves this action to the log, which is used as a qualifier in some cases
+	 */
+	public function __record_log($action_id,$user_id)
+	{
+		$actions_log = new Actions_Log_Model();
+		$actions_log->action_id = $action_id;
+		$actions_log->user_id = $user_id;
+		$actions_log->time = time();
+		$actions_log->save();
+
+		return TRUE;
+	}
+
+	/**
+	 * Approve a report and assign it to one or more categories
+	 */
+	public function __response_approve_report($vars)
+	{
+		$incident_id = $this->data->id;
+
+		$categories = array();
+		if( isset($vars['add_category']))
+		{
+			$categories = $vars['add_category'];
+		}
+
+		$verify = 0;
+		if( isset($vars['verify']))
+		{
+			$verify = (int)$vars['verify'];
+		}
+
+		foreach($categories as $category_id)
+		{
+			// Assign Category
+			Incident_Category_Model::assign_category_to_incident($incident_id,$category_id);
+		}
+
+		// Approve Report
+		Incident_Model::set_approve($incident_id,1);
+
+		// Set Verification
+		Incident_Model::set_verification($incident_id,$verify);
+
+		return TRUE;
+	}
+
+	/**
+	 * Create a report and assign it to one or more categories and set verification
+	 */
+	public function __response_create_report($vars)
+	{
+		$categories = array();
+		if( isset($vars['add_category']))
+		{
+			$categories = $vars['add_category'];
+		}
+
+		$verify = 0;
+		if( isset($vars['verify']))
+		{
+			$verify = (int)$vars['verify'];
+		}
+
+		$approve = 0;
+		if( isset($vars['approve']))
+		{
+			$approve = (int)$vars['approve'];
+		}
+
+		// Grab the location_id or create one if we can
+		$location_id = 0;
+		if ( isset($this->data->location_id))
+		{
+			$location_id = $this->data->location_id;
+		}
+		elseif ( isset($this->data->latitude) AND isset($this->data->longitude))
+		{
+			$location_name = map::reverse_geocode($this->data->latitude,$this->data->longitude);
+
+			// In case our location name is too long, chop off the end
+			$location_name = substr_replace($location_name, '', 250);
+
+			$location_data = (object) array('location_name' => $location_name,
+									'latitude' => $this->data->latitude,
+									'longitude' => $this->data->longitude);
+			$location = new Location_Model();
+			reports::save_location($location_data, $location);
+			$location_id = $location->id;
+		}
+
+		// We can only create reports if we have location.
+		if ($location_id == FALSE OR $location_id == 0)
+		{
+			return false;
+		}
+		
+		// Build title
+		
+		
+		// Build title & description
+		// If this is a message
+		if (isset($this->data->message))
+		{
+			$incident_title = $this->data->message;
+			$incident_description = $this->data->message;
+			$incident_date = $this->data->message_date;
+			// If we're got more message detail, make that the description
+			if ( ! empty($message->message_detail))
+			{
+				$incident_description = $this->data->message_detail;
+			}
+		}
+		// If this is a feed item
+		elseif (isset($this->data->item_title))
+		{
+			$incident_title = html::strip_tags(html_entity_decode(html_entity_decode($this->data->item_title, ENT_QUOTES)));
+			$incident_description = html::clean(html_entity_decode($this->data->item_description, ENT_QUOTES));
+			$incident_date = $this->data->item_date;
+		}
+		
+		
+		// Override title from action options
+		if (! empty($vars['report_title']))
+		{
+			$incident_title = $vars['report_title'];
+		}
+
+		// Save Incident
+		$incident = new Incident_Model();
+		$incident->location_id = $location_id;
+		$incident->incident_title = $incident_title;
+		$incident->incident_description = $incident_description;
+		$incident->incident_date = $incident_date;
+		$incident->incident_active = $approve;
+		$incident->incident_verified = $verify;
+		$incident->incident_dateadd = date("Y-m-d H:i:s",time());
+		$incident->save();
+
+		// Conflicted.. do I run report add here? Potential to create a mess with action triggers?
+		//Event::run('ushahidi_action.report_add', $incident);
+		
+		// Save media
+		if (isset($this->data->item_title))
+		{
+			$news = new Media_Model();
+			$news->location_id = $incident->location_id;
+			$news->incident_id = $incident->id;
+			$news->media_type = 4; // News
+			$news->media_link = $this->data->item_link;
+			$news->media_date = $this->data->item_date;
+			$news->save();
+		}
+
+		$incident_id = $incident->id;
+
+		foreach($categories as $category_id)
+		{
+			// Assign Category
+			Incident_Category_Model::assign_category_to_incident($incident_id,$category_id);
+		}
+
+		// Link message with incident?
+		if ( isset($this->data->message) AND isset($this->data->id))
+		{
+			$message = new Message_Model($this->data->id);
+			$message->incident_id = $incident_id;
+			$message->save();
+		}
+		// Link feed item with incident
+		elseif ( isset($this->data->item_title) AND isset($this->data->id))
+		{
+			$item = new Feed_Item_Model($this->data->id);
+			$item->incident_id = $incident_id;
+			$item->save();
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Assigns a badge to the triggering user
+	 */
+	public function __response_assign_badge($vars)
+	{
+		$count = ORM::factory('badge_user')->where(array('badge_id' => (int)$vars['badge'], 'user_id' => (int)$this->data->user_id))->count_all();
+		if($count == 0)
+		{
+			$badge_user = new Badge_User_Model();
+			$badge_user->badge_id = $vars['badge']; // badge id
+			$badge_user->user_id = $this->data->user_id;
+			$badge_user->save();
+		}
+		return TRUE;
+	}
+
+	/**
+	 * Shoots an email to the defined person
+	 */
+	public function __response_email($vars)
+	{
+		$settings = kohana::config('settings');
+
+		if($vars['email_send_address'] == '0')
+		{
+			// If our send address is 0, then it means we need to send the email to
+			//   the triggering user
+			$to = User_Model::get_email($this->data->user_id);
+		}else{
+			$to = $vars['email_send_address'];
+		}
+
+		$from = array($settings['site_email'], $settings['site_name']);
+		$subject = $vars['email_subject'];
+		$message = $vars['email_body'];
+
+		return email::send($to, $from, $subject, $message, FALSE);
+	}
+
+	/**
+	 *
+	 */
+	public function _get_actions($activity)
+	{
+		return $this->db->from('actions')->where(array('action' => $activity, 'active' => 1))->get();
+	}
+}
+
+new actioner;
diff --git a/application/hooks/browser.php b/application/hooks/browser.php
new file mode 100644
index 0000000..568ffa4
--- /dev/null
+++ b/application/hooks/browser.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Browser Checker Hook
+ * Determines the capabilities of the users browser
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Browser Hoook
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+// Detect Gzip
+$gz = "";
+if (isset($_SERVER['HTTP_ACCEPT_ENCODING']))
+{
+	$gz  = strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== false ? 'gz.' : '';
+}
+Kohana::config_set('settings.gz', $gz);
\ No newline at end of file
diff --git a/application/hooks/error.php b/application/hooks/error.php
new file mode 100644
index 0000000..a475bba
--- /dev/null
+++ b/application/hooks/error.php
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Custom Error Pages
+*/
+
+Event::clear('system.404', array('Kohana', 'show_404'));
+
+Event::add('system.404', 'error_404');
+
+function error_404() {
+	$controller = new Error_Controller();
+	$controller->error_404();
+	die();
+}
diff --git a/application/hooks/https_check.php b/application/hooks/https_check.php
new file mode 100644
index 0000000..db5210f
--- /dev/null
+++ b/application/hooks/https_check.php
@@ -0,0 +1,59 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * HTTPS Check Hook
+ *
+ * This hook checks if HTTPS has been enabled and whether the Webserver is HTTPS capable
+ * If the sanity check fails, $config['site_protocol'] is set back to 'http'
+ * and a redirect is performed so as to re-load the URL using the newly set protocol
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     HTTPS Check Hook
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class https_check {
+
+	private $https_enabled;   // Flag to denote whether HTTPS is enabled/disabled
+
+	/**
+	 * Constructor
+	 */
+	public function __construct()
+	{
+		$this->https_enabled = Kohana::config('core.site_protocol');
+
+		// Hook into routing
+		Event::add_after('system.routing', array('Router', 'setup'), array($this, 'rewrite_url'));
+	}
+
+	/**
+	 * Rewrites the URL depending on whether HTTPS is enabled/disabled
+	 *
+	 * NOTES: - Emmanuel Kala, 18th Feb 2011
+	 * This may bring issues with accessing the API (querying or posting) via mobile and/or external applications
+	 * as they may not support querying information via HTTPS
+	 *
+	 */
+	public function rewrite_url()
+	{
+		if ($this->https_enabled == 'HTTPS')
+		{
+			$is_https_request = (array_key_exists('HTTPS', $_SERVER) AND $_SERVER['HTTPS'] == 'on')
+				? TRUE
+				: FALSE;
+
+			if (($this->https_enabled AND ! $is_https_request) OR ( ! $this->https_enabled AND $is_https_request))
+			{
+				url::redirect(url::base().url::current().Router::$query_string);
+			}
+		}
+	}
+}
+
+new https_check();
\ No newline at end of file
diff --git a/application/hooks/page_cache.php b/application/hooks/page_cache.php
new file mode 100644
index 0000000..69507c2
--- /dev/null
+++ b/application/hooks/page_cache.php
@@ -0,0 +1,76 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Page_Cache Hook - Caches entire pages to cache directory
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Page_Cache Hook
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class hook_page_cache
+{
+	private $cache;
+
+	private $subdomain;
+
+	private $gzip = "";
+
+	public function __construct()
+	{
+		$this->cache = new Cache;
+
+		// Gzip compression
+		$this->gzip = Kohana::config('settings.gz');
+
+		// Subdomain (MHI)?
+		$subdomain = Kohana::config('settings.subdomain');
+		if ( ! empty($subdomain))
+		{
+			$this->subdomain = $subdomain."_";
+		}
+
+		// If this is a POST, disable cache
+		if (empty($_POST))
+		{
+			Event::add_before( 'system.routing',
+				array('Router', 'setup'), array($this, 'load_cache') );
+		}
+	}
+
+	public function load_cache()
+	{
+		if ($cache = $this->cache->get($this->subdomain.'page_'.$this->gzip.'_'.$_SERVER['REQUEST_URI']))
+		{
+			Kohana::render($cache);
+			exit;
+		}
+		else
+		{
+			Event::add('system.display', array($this, 'save_cache'));
+		}
+	}
+
+	public function save_cache()
+	{
+		// If controller is cachable - cache
+		// If this is an error page - DO NOT cache
+		if ( ! empty(Kohana::$instance->is_cachable)
+		 	AND Kohana::$instance->is_cachable == true
+		 	AND Kohana::$has_error == false )
+		{
+			$this->cache->set($this->subdomain.'page_'.$this->gzip.'_'.$_SERVER['REQUEST_URI'], Event::$data);
+		}
+	}
+}
+
+if (Kohana::config('cache.cache_pages'))
+{
+	$hook = new hook_page_cache;
+	unset($hook);
+}
\ No newline at end of file
diff --git a/application/hooks/register_default_blocks.php b/application/hooks/register_default_blocks.php
new file mode 100644
index 0000000..3fe225c
--- /dev/null
+++ b/application/hooks/register_default_blocks.php
@@ -0,0 +1,62 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+class reports_block {
+
+	public function __construct()
+	{
+		$block = array(
+			"classname" => "reports_block",
+			"name" => "Reports",
+			"description" => "List the 10 latest reports in the system"
+		);
+
+		blocks::register($block);
+	}
+
+	public function block()
+	{
+		$content = new View('blocks/main_reports');
+
+		// Get Reports
+		$content->incidents = ORM::factory('incident')
+			->with('location')
+			->where('incident_active', '1')
+			->limit('10')
+			->orderby('incident_date', 'desc')
+			->find_all();
+
+		echo $content;
+	}
+}
+
+new reports_block;
+
+
+class news_block {
+
+	public function __construct()
+	{
+		$block = array(
+			"classname" => "news_block",
+			"name" => "Main Stream News",
+			"description" => "List the 10 latest news items from available news feeds"
+		);
+
+		blocks::register($block);
+	}
+
+	public function block()
+	{
+		$content = new View('blocks/main_news');
+		// Get RSS News Feeds
+		$content->feeds = ORM::factory('feed_item')
+			->with('feed')
+			->limit('10')
+			->orderby('item_date', 'desc')
+			->find_all();
+
+		echo $content;
+	}
+}
+
+new news_block;
\ No newline at end of file
diff --git a/application/hooks/register_plugins.php b/application/hooks/register_plugins.php
new file mode 100644
index 0000000..da566f0
--- /dev/null
+++ b/application/hooks/register_plugins.php
@@ -0,0 +1,87 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Register Plugins Hook
+ * Credits To Jeremy Bush at Zombor.net (http://www.zombor.net/)
+ *
+ * Portions of this code:
+ * Copyright (c) 2008-2009, Argentum Team
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Register Plugins Hook
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class register_plugins {
+	/**
+	 * Adds the register method to load after the find_uri Router method.
+	 */
+	public function __construct()
+	{
+		// Hook into routing
+		if (Kohana::config('config.installer_check') == FALSE OR file_exists(DOCROOT."application/config/database.php"))
+		{
+			Event::add_after('system.routing', array('Router', 'find_uri'), array($this, 'register'));
+		}
+
+		// Set Table Prefix
+		$this->table_prefix = Kohana::config('database.default.table_prefix');
+	}
+
+	/**
+	 * Loads all ushahidi plugins
+	 */
+	public function register()
+	{
+		$db = Database::instance();
+		$plugins = array();
+		// Get the list of plugins from the db
+		foreach ($db->getwhere('plugin', array(
+				'plugin_active' => 1,
+				'plugin_installed' => 1)) as $plugin)
+		{
+			$plugins[$plugin->plugin_name] = PLUGINPATH.$plugin->plugin_name;
+		}
+
+		// Now set the plugins
+		Kohana::config_set('core.modules', array_merge(Kohana::config('core.modules'), $plugins));
+
+		// We need to manually include the hook file for each plugin,
+		// because the additional plugins aren't loaded until after the application hooks are loaded.
+		foreach ($plugins as $key => $plugin)
+		{
+			if (file_exists($plugin.'/hooks'))
+			{
+				$d = dir($plugin.'/hooks'); // Load all the hooks
+				while (($entry = $d->read()) !== FALSE)
+				{
+					if ($entry[0] != '.')
+					{
+						// $plugin_base Variable gives plugin hook access to the base location of the plugin
+						$plugin_base = url::base()."plugins/".$key."/";
+						include $plugin.'/hooks/'.$entry;
+					}
+				}
+			}
+		}
+	}
+}
+
+new register_plugins;
\ No newline at end of file
diff --git a/application/hooks/register_themes.php b/application/hooks/register_themes.php
new file mode 100755
index 0000000..f2373ac
--- /dev/null
+++ b/application/hooks/register_themes.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Register Themes Hook
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Register Themes Hook
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class register_themes {
+	
+	protected $themes = array();
+	protected $loaded_themes = array();
+	
+	protected $theme_js = array();
+	protected $theme_css = array();
+	
+	/**
+	 * Adds the register method to load after system.ready
+	 */
+	public function __construct()
+	{
+		// Hook into routing
+		if (Kohana::config('config.installer_check') == FALSE OR file_exists(DOCROOT."application/config/database.php"))
+		{
+			Event::add('system.ready', array('Themes', 'register_theme'));
+		}
+	}
+}
+
+new register_themes;
\ No newline at end of file
diff --git a/application/libraries/Akismet.php b/application/libraries/Akismet.php
new file mode 100644
index 0000000..d00a23e
--- /dev/null
+++ b/application/libraries/Akismet.php
@@ -0,0 +1,230 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Akismet Library
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Elliot Haughin http://www.haughin.com/code/akismet/
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @port	   Pawel Golonko <pgolonko at gmail.com>
+ * @port	   Ushahidi Team <team at ushahidi.com> 
+ * @module     Akismet Library
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+define("AKISMET_SERVER_NOT_FOUND",	0);
+define("AKISMET_RESPONSE_FAILED",	1);
+define("AKISMET_INVALID_KEY",		2); 
+
+class Akismet_Core {
+	var $akismet_version = '1.1';
+	var $akismet_server = 'rest.akismet.com';
+	var $api_port = 80;
+	
+	var $ignore = array(
+		'HTTP_COOKIE',
+		'HTTP_X_FORWARDED_FOR',
+		'HTTP_X_FORWARDED_HOST',
+		'HTTP_MAX_FORWARDS',
+		'HTTP_X_FORWARDED_SERVER',
+		'REDIRECT_STATUS',
+		'SERVER_PORT',
+		'PATH',
+		'DOCUMENT_ROOT',
+		'SERVER_ADMIN',
+		'QUERY_STRING',
+		'PHP_SELF',
+		'argv'
+	);
+	
+	var $api_key;
+	var $blog_url;
+	var $errors = array();
+	var $comment = array();
+
+	function __construct() {
+		
+	}
+	
+	function init($config) {
+		foreach ($config as $key => $value) {
+			$this->$key = $value;
+		}
+		
+		$this->set_comment($this->comment);
+
+		$this->_connect();
+		
+		if($this->errors_exist()) {
+			$this->errors = array_merge($this->errors, $this->get_errors());
+		}
+		
+		// Check if the API key is valid
+		if(!$this->_is_valid_api_key($this->api_key)) {
+			$this->set_error('AKISMET_INVALID_KEY', Kohana::lang('libraries.askimet_invalid_apikey'));
+		}
+	}
+	
+	// Connect to the Akismet server and store that connection in the instance variable $con
+	function _connect() {
+		if(!($this->con = @fsockopen($this->akismet_server, $this->api_port))) {
+			$this->set_error('AKISMET_SERVER_NOT_FOUND', Kohana::lang('libraries.akismet_cannot_connect'));
+		}
+	}
+	
+	// Close the connection to the Akismet server
+	function _disconnect()
+	{
+		@fclose($this->con);
+	}
+	
+	function get_response($request, $path, $type = "post", $response_length = 1160) {
+		$this->_connect();
+		
+		if($this->con && !$this->is_error('AKISMET_SERVER_NOT_FOUND')) {
+			$request  = 
+					strToUpper($type)." /{$this->akismet_version}/$path HTTP/1.0\r\n" .
+					"Host: ".((!empty($this->api_key)) ? $this->api_key."." : null)."{$this->akismet_server}\r\n" .
+					"Content-Type: application/x-www-form-urlencoded; charset=utf-8\r\n" .
+					"Content-Length: ".strlen($request)."\r\n" .
+					"User-Agent: Akismet CodeIgniter Library\r\n" .
+					"\r\n" .
+					$request
+				;
+			$response = "";
+	
+			@fwrite($this->con, $request);
+	
+			while(!feof($this->con)) {
+				$response .= @fgets($this->con, $response_length);
+			}
+	
+			$response = explode("\r\n\r\n", $response, 2);
+			return $response[1];
+		}
+		else {
+			$this->set_error('AKISMET_RESPONSE_FAILED', Kohana::lang('libraries.akismet_cannot_retrieve'));
+		}
+		
+		$this->_disconnect();
+	}
+	
+	function set_error($name, $message) {
+		$this->errors[$name] = $message;
+	}
+	
+	function get_error($name) {
+		if($this->is_error($name)) {
+			return $this->errors[$name];
+		}
+		else {
+			return false;
+		}
+	}
+	 
+	function get_errors() {
+		return (array)$this->errors;
+	}
+	
+	function is_error($name) {
+		return isset($this->errors[$name]);
+	}
+	
+	function errors_exist() {
+		return (count($this->errors) > 0);
+	}
+	
+	function is_spam() {
+		$response = $this->get_response($this->_get_query_string(), 'comment-check');		
+		return ($response == "true");
+	}
+	
+	function submit_spam() {
+		$this->get_response($this->_get_querystring(), 'submit-spam');
+	}
+	
+	
+	function submit_ham() {
+		$this->get_response($this->_get_query_string(), 'submit-ham');
+	}
+	
+	function set_comment($comment) {
+		$this->comment = $comment;
+		
+		if(!empty($comment))
+		{
+			$this->_format_comment_array();
+			$this->_fill_comment_values();
+		}
+	}
+	
+	function get_comment() {
+		return $this->comment;
+	}
+	
+	function _is_valid_api_key($key) {
+		$key_check = $this->get_response("key=".$this->api_key."&blog=".$this->blog_url, 'verify-key');
+		return ($key_check == "valid");
+	}
+	
+	function _format_comment_array() {			
+		$format = array(
+				'type' => 'comment_type',
+				'author' => 'comment_author',
+				'email' => 'comment_author_email',
+				'website' => 'comment_author_url',
+				'body' => 'comment_content'
+			);
+	
+		foreach($format as $short => $long) {
+			if(isset($this->comment[$short])) {
+				$this->comment[$long] = $this->comment[$short];
+				unset($this->comment[$short]);
+			}
+		}
+	}
+	
+	/**
+	 * Fill any values not provided by the developer with available values.
+	 *
+	 * @return	void
+	 */
+	
+	function _fill_comment_values() {
+		if(!isset($this->comment['user_ip'])) {
+			$this->comment['user_ip'] = ($_SERVER['REMOTE_ADDR'] != getenv('SERVER_ADDR')) ? $_SERVER['REMOTE_ADDR'] : getenv('HTTP_X_FORWARDED_FOR');
+		}
+		
+		if(!isset($this->comment['user_agent'])) {
+			$this->comment['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
+		}
+		
+		if(!isset($this->comment['referrer']) && !empty($_SERVER['HTTP_REFERER'])) {
+			$this->comment['referrer'] = $_SERVER['HTTP_REFERER'];
+		}
+		
+		if(!isset($this->comment['blog'])) {
+			$this->comment['blog'] = $this->blog_url;
+		}
+	}
+	
+	
+	function _get_query_string() {
+		foreach($_SERVER as $key => $value) {
+			if(!in_array($key, $this->ignore)) {
+				($key == 'REMOTE_ADDR') ? $this->comment[$key] = $this->comment['user_ip'] : $this->comment[$key] = $value;
+			}
+		}
+
+		$query_string = '';
+
+		foreach($this->comment as $key => $data) {
+			$query_string .= $key . '=' . urlencode(stripslashes($data)) . '&';
+		}
+
+		return $query_string;
+	}
+} // End Akismet
diff --git a/application/libraries/Api_Service.php b/application/libraries/Api_Service.php
new file mode 100755
index 0000000..bf37a0a
--- /dev/null
+++ b/application/libraries/Api_Service.php
@@ -0,0 +1,607 @@
+<?php defined('SYSPATH') or die('No direct script access allowed');
+/**
+ * Api_Service
+ *
+ * This class runs the API service. It abstracts the details of handling of the API
+ * requests from the API controller. All task switching and routing is handled by
+ * this class.
+ *
+ * The API routing works through inversion of control (IoC). The name of the library
+ * that services the api request is inferred from the name of the task. Not all API
+ * requests have their own libraries. As such, this service makes use of a
+ * "task routing table". This table is a key=>value array with the key being the
+ * name of the api task and the value the name of the implementing library
+ * or associative array of the class name and callback method to service the request
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+// Suffix for all API library names
+define('API_LIBRARY_SUFFIX', '_Api_Object');
+
+final class Api_Service {
+	/**
+	 * The API request parameters
+	 * @var array
+	 */
+	private $request = array();
+
+	/**
+	 * Response to be returned to the calling controller
+	 * @var string
+	 */
+	private $response;
+
+	/**
+	 * Format in which the response is returned to the client - defaults to JSON
+	 * @var string
+	 */
+	private $response_type;
+
+	/**
+	 * API library object to handle the requested task
+	 * @var Api_Object
+	 */
+	private $api_object;
+
+	/**
+	 * Name of the API task to be routed
+	 * @var string
+	 */
+	private $task_name;
+
+	/**
+	 * IP Address of the client making the API request
+	 * @var string
+	 */
+	private $request_ip_address;
+
+	/**
+	 * API request parameters
+	 * @var array
+	 */
+	private $api_parameters;
+
+	/**
+	 * Api_Log_Model object
+	 * @var Api_Log_Model
+	 */
+	private $api_logger;
+
+	/**
+	 * Database object
+	 * @var Database
+	 */
+	private $db;
+
+	public function __construct()
+	{
+		// Set the request data
+		$this->request = ($_SERVER['REQUEST_METHOD'] == 'POST')
+			? $_POST
+			: $_GET;
+
+		// Load the API configuration file
+		Kohana::config_load('api');
+
+		// Get the IP Address of the client submitting the API request
+
+		// Check if the IP is from a shared internet connection
+		if ( ! empty($_SERVER['HTTP_CLIENT_IP']))
+		{
+			$this->request_ip_address = $_SERVER['HTTP_CLIENT_IP'];
+		}
+		// Check if the IP address is passed from a proxy server such as Nginx
+		elseif ( ! empty($_SERVER['HTTP_X_FORWARDED_FOR']))
+		{
+			$this->request_ip_address  = $_SERVER['HTTP_X_FORWARDED_FOR'];
+		}
+		else
+		{
+			$this->request_ip_address = $_SERVER['REMOTE_ADDR'];
+		}
+
+		// Unpack the URL parameters
+		$this->api_parameters = serialize(array_keys($this->request));
+
+		// Instantiate the database
+		$this->db = new Database();
+		
+		// Get auth instance
+		$this->auth = Auth::instance();
+	}
+
+	/**
+	 * Runs the API service
+	 */
+	public function run_service()
+	{
+		// Check the request is allowed
+		if ($this->_is_api_request_allowed())
+		{
+			// Route the API task
+			$this->_route_api_task();
+		}
+		else
+		{
+			// Set the response to "ACCESS DENIED"
+			$this->set_response($this->get_error_msg(006));
+
+			// Terminate execution
+			return;
+		}
+	}
+
+	/**
+	 * Gets the API request parameters as an array
+	 *
+	 * @return array
+	 */
+	public function get_request()
+	{
+		return $this->request;
+	}
+
+	/**
+	 * Sets the response type
+	 *
+	 * @param $response_type New value for $this->response_type
+	 */
+	public function set_response_type($response_type)
+	{
+		$this->response_type = $response_type;
+	}
+
+	/**
+	 * Returns the response type
+	 *
+	 * @return string
+	 */
+	public function get_response_type()
+	{
+		return $this->response_type;
+	}
+
+	/**
+	 * Sets the response data
+	 *
+	 * @param mixed $response_data
+	 */
+	public function set_response($response_data)
+	{
+		$this->response = (is_array($response_data))
+			? json_encode($response_data)
+			: $response_data;
+	}
+
+	/**
+	 * Gets the response data
+	 *
+	 * @return string The response to the API request
+	 */
+	public function get_response()
+	{
+		return $this->response;
+	}
+
+	/**
+	 * Gets the name of the task being handled by the API service
+	 *
+	 * @return string
+	 */
+	public function get_task_name()
+	{
+		return $this->task_name;
+	}
+
+	/**
+	 * Log user in.
+	 * This method is mainly used for admin tasks performed via the API
+	 *
+	 * @param bool $admin require admin access?
+	 * @param bool $member require member access?
+	 * @return mixed user_id, FALSE if authentication fails
+	 */
+	public function _login($admin = FALSE, $member = FALSE)
+	{
+		// Actual HTTP Auth login is now handled by base controller and Auth->http_auth_login()
+		
+		// Is user previously authenticated?
+		if ($this->auth->logged_in())
+		{
+			// Check if admin privileges are required
+			if ($admin == FALSE OR $this->auth->has_permission('admin_ui'))
+			{
+				return $this->auth->get_user()->id;
+			}
+			// Check if member perms required, assume admins also have member perms
+			else if ($member == FALSE OR $this->auth->has_permission('member_ui') OR $this->auth->has_permission('admin_ui'))
+			{
+				return $this->auth->get_user()->id;
+			}
+			else
+			{
+				return FALSE;
+			}
+		}
+		else
+		{
+			// Get username and password
+			if ($admin OR $member)
+			{
+				$this->auth->http_auth_prompt_login();
+			}
+
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Routes the API task requests to their respective API libraries
+	 *
+	 * The name of the library is inferred from the name of the task. If the
+	 * library is not found, a lookup is done in the task routing table. If the
+	 * lookup fails, the API task request returns a "not found"(404) error
+	 */
+	private function _route_api_task()
+    {
+
+		// Make sure we have a task to work with
+		if ( ! $this->verify_array_index($this->request, 'task'))
+		{
+			$this->set_response($this->get_error_msg(001, 'task'));
+
+			// Log the failed attempt
+			$this->api_logger = new Api_Log_Model();
+
+			// Set the log data
+			$this->api_logger->api_task = 'None';
+			$this->api_logger->api_parameters  = strlen($this->api_parameters > 0)
+				? $this->api_parameters
+				: serialize('None Specified');
+			$this->api_logger->api_records = 0;
+			$this->api_logger->api_ipaddress = $this->request_ip_address;
+			$this->api_logger->api_date = date('Y-m-d H:i:s', time());
+
+			// Save the log
+			$this->api_logger->save();
+
+			return;
+		}
+		else
+		{
+			$this->task_name = ucfirst($this->request['task']);
+		}
+
+		// Load the base API library
+		require_once Kohana::find_file('libraries/api', 'Api_Object');
+
+		// Get the task handler (from the api config file) for the requested task
+		$task_handler = $this->_get_task_handler(strtolower($this->task_name));
+
+		$task_library_found = FALSE;
+
+		// Check if handler has been set
+		if (isset($task_handler))
+		{
+			// Check if the handler is an array
+			if (is_array($task_handler))
+			{
+				// Load the library for the specified class
+				$this->_init_api_library($task_handler[0]);
+
+				// Execute the callback function
+				call_user_func(array($this->api_object, $task_handler[1]));
+			}
+			else
+			{
+				// Load the library specified in $task_handler
+				$this->_init_api_library($task_handler);
+
+				// Perform the requested task
+				$this->api_object->perform_task();
+			}
+
+			// Set the response data
+			$this->response = $this->api_object->get_response_data();
+
+			$task_library_found = TRUE;
+		}
+		else // Task handler not found in routing table therefore look the implementing library
+		{
+			// All library file names *must* be suffixed with the value specified in API_LIBRARY_SUFFIX
+			$library_file_name = $this->task_name.API_LIBRARY_SUFFIX;
+
+			if (Kohana::find_file('libraries/api',
+				Kohana::config('config.extension_prefix').$library_file_name)) // Library file exists
+			{
+				// Initialize the API library
+				$this->_init_api_library($this->task_name);
+
+				// Perform the requested task
+				$this->api_object->perform_task();
+
+				// Set the response data
+				$this->response = $this->api_object->get_response_data();
+
+				$task_library_found = TRUE;
+			}
+			else
+			{   // Library not found
+				$this->response = json_encode(array(
+					"error" => $this->get_error_msg(999)
+				));
+
+				// Log the unsuccessful API request
+				$this->_log_api_request($task_library_found);
+
+				return;
+			}
+		}
+
+		// Log successful API request
+		$this->_log_api_request($task_library_found);
+
+		// Discard the API object from memory
+		if (isset($this->api_object))
+		{
+			unset($this->api_object);
+		}
+	}
+
+	/**
+	 * Initializes the API library to be used to service the API task
+	 *
+	 * The name of API library file containing the class implementation is
+	 * constructed/inferred from the @param $class_name
+	 *
+	 * @param string $base_name Name of the implementing class
+	 */
+	private function _init_api_library($base_name)
+	{
+		// Generate the name of the class
+		$class_name = $base_name.API_LIBRARY_SUFFIX;
+
+		// Check if the implementing library exists
+		if ( ! Kohana::find_file('libraries/api',
+			Kohana::config('config.extension_prefix').$class_name))
+		{
+			throw new Kohana_Exception('libraries.api_library_not_found',
+				Kohana::config('config.extension_prefix').$class_name.'.php', $class_name);
+		}
+
+		// Include the implementing API library file
+		require_once Kohana::find_file('libraries/api', Kohana::config('config.extension_prefix').$class_name);
+
+		// Temporary instance for type checking
+		$temp_api_object = new $class_name($this);
+
+		// Check if the implementing library is an instance of Api_Object_Core
+		// NOTE: All API libraries *MUST* be subclasses of Api_Object_Core
+		if ( ! $temp_api_object instanceof Api_Object_Core)
+			throw new Kohana_Exception('libraries.invalid_api_library', $class_name, 'Api_Object_Core');
+
+		// Discard the old copy
+		unset($this->temp_api_object);
+
+		// Instaniate a fresh copy of the API library
+		$this->api_object = new $class_name($this);
+	}
+
+	/**
+	 * Makes sure the appropriate key is there in a given
+	 * array (POST or GET) and that it is set
+	 *
+	 * @param arrray $arr - The given array.
+	 * @param string $index  The array key index to lookup
+	 * @return bool
+	 */
+	public function verify_array_index(array & $arr, $index)
+	{
+		return (isset($arr[$index]) AND array_key_exists($index, $arr));
+	}
+
+	/**
+	 * Displays Error codes with their corresponding messages.
+	 * returns an array error - array("code" => "CODE",
+	 * "message" => "MESSAGE") based on the given code
+	 *
+	 * @param string $errcode  - The error code to be displayed.
+	 * @param string $param - The missing parameter.
+	 * @param string $message - The error message to be displayed.
+	 * @return array
+	 */
+	public function get_error_msg($errcode, $param = '', $message='')
+	{
+		switch ($errcode)
+		{
+			case 0:
+				return array(
+					"code" => "0",
+					"message" => Kohana::lang('ui_admin.no_error')
+				);
+
+			case 001:
+				return array(
+					"code" => "001",
+					"message" => Kohana::lang('ui_admin.missing_parameter')." - $param."
+				);
+
+			case 002:
+				return array(
+					"code" => "002",
+					"message" => Kohana::lang('ui_admin.invalid_parameter')
+				);
+
+			case 003:
+				return array("code" => "003", "message" => $message);
+
+			case 004:
+				return array(
+					"code" => "004",
+					"message" => Kohana::lang('ui_admin.post_method_not_used')
+				);
+
+			case 005:
+				return array(
+					"code" => "005",
+					"message" => Kohana::lang('ui_admin.access_denied_credentials')
+				);
+
+			case 006:
+				return array(
+					"code" => "006",
+					"message" => Kohana::lang('ui_admin.access_denied_others')
+				);
+
+			case 007:
+				return array(
+					"code" => "007",
+					"message" => Kohana::lang('ui_admin.no_data')
+				);
+
+			case 010:
+				return array(
+					"code" => "010",
+					"message" => Kohana::lang('ui_admin.disabled')
+				);
+
+			case 011:
+				return array(
+					"code" => "011",
+					"message" => Kohana::lang('ui_admin.unknown_failure')
+				);
+			case 012:
+				return array(
+					"code" => "012",
+					"message" => Kohana::lang('ui_admin.unknown_id')
+				);
+			default:
+				return array(
+					"code" => "999",
+					"message" => Kohana::lang('ui_admin.not_found')
+				);
+		}
+	}
+
+	/**
+	 * Looks up the api config file for the library that handles the task
+	 * specified in @param $task. The api config file is the API task routing
+	 * table
+	 *
+	 * @param string $task - Task to be looked up in the routing table
+	 * @return mixed
+	 */
+	private function _get_task_handler($task)
+	{
+		$task_handler = Kohana::config('api.'.$task);
+
+		return (isset($task_handler))
+			? $task_handler
+			: NULL;
+	}
+
+	/**
+	 * Logs API requests
+	 * If @param task_library_found == FALSE the no. of records returned is 0
+	 *
+	 * @param bool $task_library_found
+	 */
+	private function _log_api_request($task_library_found)
+	{
+		// Log the API request
+		$this->api_logger = new Api_Log_Model();
+
+		$this->api_logger->api_task = strtolower($this->task_name);
+		$this->api_logger->api_parameters = $this->api_parameters;
+		$this->api_logger->api_ipaddress = $this->request_ip_address;
+
+		$this->api_logger->api_records = ($task_library_found)? $this->api_object->get_record_count() : 0;
+
+		$this->api_logger->api_date = date('Y-m-d H:i:s', time());
+		$this->api_logger->save();
+	}
+
+	/**
+	 * Checks if the API request is allowed. The function first checks if the request IP
+	 * address has been banned then proceeds to check if the IP has exceeded the quota
+	 * for the day/month
+	 *
+	 * @return boolean
+	 */
+	private function _is_api_request_allowed()
+	{
+		// STEP 1: Check to see if site is private
+		if(Kohana::config('settings.private_deployment'))
+		{
+			if ( ! $this->_login())
+			{
+				// @todo better error message
+				return FALSE;
+			}
+		}
+
+		// STEP 2: Check if the IP has been banned
+		$banned_count = ORM::factory('api_banned')
+						->where('banned_ipaddress', $this->request_ip_address)
+						->count_all();
+
+		if ($banned_count > 0)
+			return FALSE;
+
+		// STEP 3: Check if the IP address has exceeded the request quota
+
+		// Get the API settings
+		$api_settings = new Api_Settings_Model(1);
+
+		// Check if an API request quota has been set
+		if ( ! isset ($api_settings->max_requests_per_ip_address))
+			return TRUE;
+
+		// Get the API request quota
+		$request_quota = $api_settings->max_requests_per_ip_address;
+
+		// Get the quota basis
+		$quota_basis = (isset($api_settings->max_requests_quota_basis))
+			? $api_settings->max_requests_quota_basis
+			: NULL;
+
+		$num_api_requests = -1; // Will hold the number of API requests for the specified IP
+
+		// Database table prefix
+		$table_prefix = Kohana::config('database.default.table_prefix');
+
+		// Template query
+		$template_query = "SELECT COUNT(*) AS record_count ";
+		$template_query .= "FROM ".$table_prefix."api_log ";
+		$template_query .= "WHERE DATE_FORMAT(api_date, '%s') = '%s' ";
+		$template_query .= "AND api_ipaddress = '".$this->request_ip_address."'";
+
+		// Get the number of api requests logged depending on the quota basis
+		switch ($quota_basis)
+		{
+			// Per day quota
+			case 0:
+				$items = $this->db->query(sprintf($template_query, '%Y-%m-%d', date('Y-m-d', time())));
+				$num_api_requests = (int)$items[0]->record_count;
+			break;
+
+			// Per month quota
+			case 1:
+				$items = $this->db->query(sprintf($template_query, '%Y-%m', date('Y-m', time())));
+				$num_api_requests = (int)$items[0]->record_count;
+			break;
+		}
+
+		// Return value
+		return ($num_api_requests >= $request_quota)? FALSE : TRUE;
+	}
+}
+?>
diff --git a/application/libraries/CSSmin.php b/application/libraries/CSSmin.php
new file mode 100644
index 0000000..6741eee
--- /dev/null
+++ b/application/libraries/CSSmin.php
@@ -0,0 +1,741 @@
+<?php
+
+/*!
+ * cssmin.php rev 91c5ea5
+ * Author: Tubal Martin - http://blog.margenn.com/
+ * Repo: https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port
+ *
+ * This is a PHP port of the Javascript port of the CSS minification tool
+ * distributed with YUICompressor, itself a port of the cssmin utility by
+ * Isaac Schlueter - http://foohack.com/
+ * Permission is hereby granted to use the PHP version under the same
+ * conditions as the YUICompressor.
+ */
+
+/*!
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+
+class CSSmin
+{
+    const NL = '___YUICSSMIN_PRESERVED_NL___';
+    const TOKEN = '___YUICSSMIN_PRESERVED_TOKEN_';
+    const COMMENT = '___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_';
+    const CLASSCOLON = '___YUICSSMIN_PSEUDOCLASSCOLON___';
+
+    private $comments;
+    private $preserved_tokens;
+    private $memory_limit;
+    private $max_execution_time;
+    private $pcre_backtrack_limit;
+    private $pcre_recursion_limit;
+    private $raise_php_limits;
+
+    /**
+     * Minify CSS
+     *
+     * @uses __construct()
+     * @uses min()
+     * @param string $js Javascript to be minified
+     * @return string
+     */
+    public static function go($css) {
+      $cssmin = new CSSMin();
+      return $cssmin->run($css);
+    }
+
+    /**
+     * @param bool|int $raise_php_limits
+     * If true, PHP settings will be raised if needed
+     */
+    public function __construct($raise_php_limits = TRUE)
+    {
+        // Set suggested PHP limits
+        $this->memory_limit = 128 * 1048576; // 128MB in bytes
+        $this->max_execution_time = 60; // 1 min
+        $this->pcre_backtrack_limit = 1000 * 1000;
+        $this->pcre_recursion_limit =  500 * 1000;
+
+        $this->raise_php_limits = (bool) $raise_php_limits;
+    }
+
+    /**
+     * Minify a string of CSS
+     * @param string $css
+     * @param int|bool $linebreak_pos
+     * @return string
+     */
+    public function run($css = '', $linebreak_pos = FALSE)
+    {
+        if (empty($css)) {
+            return '';
+        }
+
+        if ($this->raise_php_limits) {
+            $this->do_raise_php_limits();
+        }
+
+        $this->comments = array();
+        $this->preserved_tokens = array();
+
+        $start_index = 0;
+        $length = strlen($css);
+
+        $css = $this->extract_data_urls($css);
+
+        // collect all comment blocks...
+        while (($start_index = $this->index_of($css, '/*', $start_index)) >= 0) {
+            $end_index = $this->index_of($css, '*/', $start_index + 2);
+            if ($end_index < 0) {
+                $end_index = $length;
+            }
+            $comment_found = $this->str_slice($css, $start_index + 2, $end_index);
+            $this->comments[] = $comment_found;
+            $comment_preserve_string = self::COMMENT . (count($this->comments) - 1) . '___';
+            $css = $this->str_slice($css, 0, $start_index + 2) . $comment_preserve_string . $this->str_slice($css, $end_index);
+            // Set correct start_index: Fixes issue #2528130
+            $start_index = $end_index + 2 + strlen($comment_preserve_string) - strlen($comment_found);
+        }
+
+        // preserve strings so their content doesn't get accidentally minified
+        $css = preg_replace_callback('/(?:"(?:[^\\\\"]|\\\\.|\\\\)*")|'."(?:'(?:[^\\\\']|\\\\.|\\\\)*')/S", array($this, 'replace_string'), $css);
+
+        // Let's divide css code in chunks of 25.000 chars aprox.
+        // Reason: PHP's PCRE functions like preg_replace have a "backtrack limit"
+        // of 100.000 chars by default (php < 5.3.7) so if we're dealing with really
+        // long strings and a (sub)pattern matches a number of chars greater than
+        // the backtrack limit number (i.e. /(.*)/s) PCRE functions may fail silently
+        // returning NULL and $css would be empty.
+        $charset = '';
+        $charset_regexp = '/@charset [^;]+;/i';
+        $css_chunks = array();
+        $css_chunk_length = 25000; // aprox size, not exact
+        $start_index = 0;
+        $i = $css_chunk_length; // save initial iterations
+        $l = strlen($css);
+
+
+        // if the number of characters is 25000 or less, do not chunk
+        if ($l <= $css_chunk_length) {
+            $css_chunks[] = $css;
+        } else {
+            // chunk css code securely
+            while ($i < $l) {
+                $i += 50; // save iterations. 500 checks for a closing curly brace }
+                if ($l - $start_index <= $css_chunk_length || $i >= $l) {
+                    $css_chunks[] = $this->str_slice($css, $start_index);
+                    break;
+                }
+                if ($css[$i - 1] === '}' && $i - $start_index > $css_chunk_length) {
+                    // If there are two ending curly braces }} separated or not by spaces,
+                    // join them in the same chunk (i.e. @media blocks)
+                    $next_chunk = substr($css, $i);
+                    if (preg_match('/^\s*\}/', $next_chunk)) {
+                        $i = $i + $this->index_of($next_chunk, '}') + 1;
+                    }
+
+                    $css_chunks[] = $this->str_slice($css, $start_index, $i);
+                    $start_index = $i;
+                }
+            }
+        }
+
+        // Minify each chunk
+        for ($i = 0, $n = count($css_chunks); $i < $n; $i++) {
+            $css_chunks[$i] = $this->minify($css_chunks[$i], $linebreak_pos);
+            // Keep the first @charset at-rule found
+            if (empty($charset) && preg_match($charset_regexp, $css_chunks[$i], $matches)) {
+                $charset = $matches[0];
+            }
+            // Delete all @charset at-rules
+            $css_chunks[$i] = preg_replace($charset_regexp, '', $css_chunks[$i]);
+        }
+
+        // Update the first chunk and push the charset to the top of the file.
+        $css_chunks[0] = $charset . $css_chunks[0];
+
+        return implode('', $css_chunks);
+    }
+
+    /**
+     * Sets the memory limit for this script
+     * @param int|string $limit
+     */
+    public function set_memory_limit($limit)
+    {
+        $this->memory_limit = $this->normalize_int($limit);
+    }
+
+    /**
+     * Sets the maximum execution time for this script
+     * @param int|string $seconds
+     */
+    public function set_max_execution_time($seconds)
+    {
+        $this->max_execution_time = (int) $seconds;
+    }
+
+    /**
+     * Sets the PCRE backtrack limit for this script
+     * @param int $limit
+     */
+    public function set_pcre_backtrack_limit($limit)
+    {
+        $this->pcre_backtrack_limit = (int) $limit;
+    }
+
+    /**
+     * Sets the PCRE recursion limit for this script
+     * @param int $limit
+     */
+    public function set_pcre_recursion_limit($limit)
+    {
+        $this->pcre_recursion_limit = (int) $limit;
+    }
+
+    /**
+     * Try to configure PHP to use at least the suggested minimum settings
+     */
+    private function do_raise_php_limits()
+    {
+        $php_limits = array(
+            'memory_limit' => $this->memory_limit,
+            'max_execution_time' => $this->max_execution_time,
+            'pcre.backtrack_limit' => $this->pcre_backtrack_limit,
+            'pcre.recursion_limit' =>  $this->pcre_recursion_limit
+        );
+
+        // If current settings are higher respect them.
+        foreach ($php_limits as $name => $suggested) {
+            $current = $this->normalize_int(ini_get($name));
+            // memory_limit exception: allow -1 for "no memory limit".
+            if ($current > -1 && ($suggested == -1 || $current < $suggested)) {
+                ini_set($name, $suggested);
+            }
+        }
+    }
+
+    /**
+     * Does bulk of the minification
+     * @param string $css
+     * @param int|bool $linebreak_pos
+     * @return string
+     */
+    private function minify($css, $linebreak_pos)
+    {
+        // strings are safe, now wrestle the comments
+        for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
+
+            $token = $this->comments[$i];
+            $placeholder = '/' . self::COMMENT . $i . '___/';
+
+            // ! in the first position of the comment means preserve
+            // so push to the preserved tokens keeping the !
+            if (substr($token, 0, 1) === '!') {
+                $this->preserved_tokens[] = $token;
+                $token_tring = self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
+                $css = preg_replace($placeholder, $token_tring, $css, 1);
+                // Preserve new lines for /*! important comments
+                $css = preg_replace('/\s*[\n\r\f]+\s*(\/\*'. $token_tring .')/S', self::NL.'$1', $css);
+                $css = preg_replace('/('. $token_tring .'\*\/)\s*[\n\r\f]+\s*/S', '$1'.self::NL, $css);
+                continue;
+            }
+
+            // \ in the last position looks like hack for Mac/IE5
+            // shorten that to /*\*/ and the next one to /**/
+            if (substr($token, (strlen($token) - 1), 1) === '\\') {
+                $this->preserved_tokens[] = '\\';
+                $css = preg_replace($placeholder,  self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
+                $i = $i + 1; // attn: advancing the loop
+                $this->preserved_tokens[] = '';
+                $css = preg_replace('/' . self::COMMENT . $i . '___/',  self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
+                continue;
+            }
+
+            // keep empty comments after child selectors (IE7 hack)
+            // e.g. html >/**/ body
+            if (strlen($token) === 0) {
+                $start_index = $this->index_of($css, $this->str_slice($placeholder, 1, -1));
+                if ($start_index > 2) {
+                    if (substr($css, $start_index - 3, 1) === '>') {
+                        $this->preserved_tokens[] = '';
+                        $css = preg_replace($placeholder,  self::TOKEN . (count($this->preserved_tokens) - 1) . '___', $css, 1);
+                    }
+                }
+            }
+
+            // in all other cases kill the comment
+            $css = preg_replace('/\/\*' . $this->str_slice($placeholder, 1, -1) . '\*\//', '', $css, 1);
+        }
+
+
+        // Normalize all whitespace strings to single spaces. Easier to work with that way.
+        $css = preg_replace('/\s+/', ' ', $css);
+
+        // Shorten & preserve calculations calc(...) since spaces are important
+        $css = preg_replace_callback('/calc(\((?:[^\(\)]+|(?1))*\))/i', array($this, 'replace_calc'), $css);
+
+        // Replace positive sign from numbers preceded by : or a white-space before the leading space is removed
+        // +1.2em to 1.2em, +.8px to .8px, +2% to 2%
+        $css = preg_replace('/((?<!\\\\)\:|\s)\+(\.?\d+)/S', '$1$2', $css);
+
+        // Remove leading zeros from integer and float numbers preceded by : or a white-space
+        // 000.6 to .6, -0.8 to -.8, 0050 to 50, -01.05 to -1.05
+        $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)0+(\.?\d+)/S', '$1$2$3', $css);
+
+        // Remove trailing zeros from float numbers preceded by : or a white-space
+        // -6.0100em to -6.01em, .0100 to .01, 1.200px to 1.2px
+        $css = preg_replace('/((?<!\\\\)\:|\s)(\-?)(\d?\.\d+?)0+([^\d])/S', '$1$2$3$4', $css);
+
+        // Remove trailing .0 -> -9.0 to -9
+        $css = preg_replace('/((?<!\\\\)\:|\s)(\-?\d+)\.0([^\d])/S', '$1$2$3', $css);
+
+        // Replace 0 length numbers with 0
+        $css = preg_replace('/((?<!\\\\)\:|\s)\-?\.?0+([^\d])/S', '${1}0$2', $css);
+
+        // Remove the spaces before the things that should not have spaces before them.
+        // But, be careful not to turn "p :link {...}" into "p:link{...}"
+        // Swap out any pseudo-class colons with the token, and then swap back.
+        $css = preg_replace_callback('/(?:^|\})(?:(?:[^\{\:])+\:)+(?:[^\{]*\{)/', array($this, 'replace_colon'), $css);
+        $css = preg_replace('/\s+([\!\{\}\;\:\>\+\(\)\]\~\=,])/', '$1', $css);
+        $css = preg_replace('/' . self::CLASSCOLON . '/', ':', $css);
+
+        // retain space for special IE6 cases
+        $css = preg_replace('/\:first\-(line|letter)(\{|,)/i', ':first-$1 $2', $css);
+
+        // no space after the end of a preserved comment
+        $css = preg_replace('/\*\/ /', '*/', $css);
+
+        // Put the space back in some cases, to support stuff like
+        // @media screen and (-webkit-min-device-pixel-ratio:0){
+        $css = preg_replace('/\band\(/i', 'and (', $css);
+
+        // Remove the spaces after the things that should not have spaces after them.
+        $css = preg_replace('/([\!\{\}\:;\>\+\(\[\~\=,])\s+/S', '$1', $css);
+
+        // remove unnecessary semicolons
+        $css = preg_replace('/;+\}/', '}', $css);
+
+        // Fix for issue: #2528146
+        // Restore semicolon if the last property is prefixed with a `*` (lte IE7 hack)
+        // to avoid issues on Symbian S60 3.x browsers.
+        $css = preg_replace('/(\*[a-z0-9\-]+\s*\:[^;\}]+)(\})/', '$1;$2', $css);
+
+        // Replace 0 length units 0(px,em,%) with 0.
+        $css = preg_replace('/((?<!\\\\)\:|\s)\-?0(?:em|ex|ch|rem|vw|vh|vm|vmin|cm|mm|in|px|pt|pc|%)/iS', '${1}0', $css);
+
+        // Replace 0 0; or 0 0 0; or 0 0 0 0; with 0.
+        $css = preg_replace('/\:0(?: 0){1,3}(;|\})/', ':0$1', $css);
+
+        // Fix for issue: #2528142
+        // Replace text-shadow:0; with text-shadow:0 0 0;
+        $css = preg_replace('/(text-shadow\:0)(;|\})/ie', "strtolower('$1 0 0$2')", $css);
+
+        // Replace background-position:0; with background-position:0 0;
+        // same for transform-origin
+        $css = preg_replace('/(background\-position|(?:webkit|moz|o|ms|)\-?transform\-origin)\:0(;|\})/ieS', "strtolower('$1:0 0$2')", $css);
+
+        // Shorten colors from rgb(51,102,153) to #336699, rgb(100%,0%,0%) to #ff0000 (sRGB color space)
+        // Shorten colors from hsl(0, 100%, 50%) to #ff0000 (sRGB color space)
+        // This makes it more likely that it'll get further compressed in the next step.
+        $css = preg_replace_callback('/rgb\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'rgb_to_hex'), $css);
+        $css = preg_replace_callback('/hsl\s*\(\s*([0-9,\s\-\.\%]+)\s*\)(.{1})/i', array($this, 'hsl_to_hex'), $css);
+
+        // Shorten colors from #AABBCC to #ABC or short color name.
+        $css = $this->compress_hex_colors($css);
+
+        // border: none to border:0, outline: none to outline:0
+        $css = preg_replace('/(border\-?(?:top|right|bottom|left|)|outline)\:none(;|\})/ieS', "strtolower('$1:0$2')", $css);
+
+        // shorter opacity IE filter
+        $css = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $css);
+
+        // Remove empty rules.
+        $css = preg_replace('/[^\};\{\/]+\{\}/S', '', $css);
+
+        // Some source control tools don't like it when files containing lines longer
+        // than, say 8000 characters, are checked in. The linebreak option is used in
+        // that case to split long lines after a specific column.
+        if ($linebreak_pos !== FALSE && (int) $linebreak_pos >= 0) {
+            $linebreak_pos = (int) $linebreak_pos;
+            $start_index = $i = 0;
+            while ($i < strlen($css)) {
+                $i++;
+                if ($css[$i - 1] === '}' && $i - $start_index > $linebreak_pos) {
+                    $css = $this->str_slice($css, 0, $i) . "\n" . $this->str_slice($css, $i);
+                    $start_index = $i;
+                }
+            }
+        }
+
+        // Replace multiple semi-colons in a row by a single one
+        // See SF bug #1980989
+        $css = preg_replace('/;;+/', ';', $css);
+
+        // Restore new lines for /*! important comments
+        $css = preg_replace('/'. self::NL .'/', "\n", $css);
+
+        // restore preserved comments and strings
+        for ($i = 0, $max = count($this->preserved_tokens); $i < $max; $i++) {
+            $css = preg_replace('/' . self::TOKEN . $i . '___/', $this->preserved_tokens[$i], $css, 1);
+        }
+
+        // Trim the final string (for any leading or trailing white spaces)
+        return trim($css);
+    }
+
+    /**
+     * Utility method to replace all data urls with tokens before we start
+     * compressing, to avoid performance issues running some of the subsequent
+     * regexes against large strings chunks.
+     *
+     * @param string $css
+     * @return string
+     */
+    private function extract_data_urls($css)
+    {
+        // Leave data urls alone to increase parse performance.
+        $max_index = strlen($css) - 1;
+        $append_index = $index = $last_index = $offset = 0;
+        $sb = array();
+        $pattern = '/url\(\s*(["\']?)data\:/i';
+
+        // Since we need to account for non-base64 data urls, we need to handle
+        // ' and ) being part of the data string. Hence switching to indexOf,
+        // to determine whether or not we have matching string terminators and
+        // handling sb appends directly, instead of using matcher.append* methods.
+
+        while (preg_match($pattern, $css, $m, 0, $offset)) {
+            $index = $this->index_of($css, $m[0], $offset);
+            $last_index = $index + strlen($m[0]);
+            $start_index = $index + 4; // "url(".length()
+            $end_index = $last_index - 1;
+            $terminator = $m[1]; // ', " or empty (not quoted)
+            $found_terminator = FALSE;
+
+            if (strlen($terminator) === 0) {
+                $terminator = ')';
+            }
+
+            while ($found_terminator === FALSE && $end_index+1 <= $max_index) {
+                $end_index = $this->index_of($css, $terminator, $end_index + 1);
+
+                // endIndex == 0 doesn't really apply here
+                if ($end_index > 0 && substr($css, $end_index - 1, 1) !== '\\') {
+                    $found_terminator = TRUE;
+                    if (')' != $terminator) {
+                        $end_index = $this->index_of($css, ')', $end_index);
+                    }
+                }
+            }
+
+            // Enough searching, start moving stuff over to the buffer
+            $sb[] = $this->substring($css, $append_index, $index);
+
+            if ($found_terminator) {
+                $token = $this->substring($css, $start_index, $end_index);
+                $token = preg_replace('/\s+/', '', $token);
+                $this->preserved_tokens[] = $token;
+
+                $preserver = 'url(' . self::TOKEN . (count($this->preserved_tokens) - 1) . '___)';
+                $sb[] = $preserver;
+
+                $append_index = $end_index + 1;
+            } else {
+                // No end terminator found, re-add the whole match. Should we throw/warn here?
+                $sb[] = $this->substring($css, $index, $last_index);
+                $append_index = $last_index;
+            }
+
+            $offset = $last_index;
+        }
+
+        $sb[] = $this->substring($css, $append_index);
+
+        return implode('', $sb);
+    }
+
+    /**
+     * Utility method to compress hex color values of the form #AABBCC to #ABC or short color name.
+     *
+     * DOES NOT compress CSS ID selectors which match the above pattern (which would break things).
+     * e.g. #AddressForm { ... }
+     *
+     * DOES NOT compress IE filters, which have hex color values (which would break things).
+     * e.g. filter: chroma(color="#FFFFFF");
+     *
+     * DOES NOT compress invalid hex values.
+     * e.g. background-color: #aabbccdd
+     *
+     * @param string $css
+     * @return string
+     */
+    private function compress_hex_colors($css)
+    {
+        // Look for hex colors inside { ... } (to avoid IDs) and which don't have a =, or a " in front of them (to avoid filters)
+        $pattern = '/(\=\s*?["\']?)?#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])(\}|[^0-9a-f{][^{]*?\})/iS';
+        $_index = $index = $last_index = $offset = 0;
+        $sb = array();
+        // See: http://ajaxmin.codeplex.com/wikipage?title=CSS%20Colors
+        $short_safe = array(
+            '#808080' => 'gray',
+            '#008000' => 'green',
+            '#800000' => 'maroon',
+            '#000080' => 'navy',
+            '#808000' => 'olive',
+            '#800080' => 'purple',
+            '#c0c0c0' => 'silver',
+            '#008080' => 'teal',
+            '#f00' => 'red'
+        );
+
+        while (preg_match($pattern, $css, $m, 0, $offset)) {
+            $index = $this->index_of($css, $m[0], $offset);
+            $last_index = $index + strlen($m[0]);
+            $is_filter = (bool) $m[1];
+
+            $sb[] = $this->substring($css, $_index, $index);
+
+            if ($is_filter) {
+                // Restore, maintain case, otherwise filter will break
+                $sb[] = $m[1] . '#' . $m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7];
+            } else {
+                if (strtolower($m[2]) == strtolower($m[3]) &&
+                    strtolower($m[4]) == strtolower($m[5]) &&
+                    strtolower($m[6]) == strtolower($m[7])) {
+                    // Compress.
+                    $hex = '#' . strtolower($m[3] . $m[5] . $m[7]);
+                } else {
+                    // Non compressible color, restore but lower case.
+                    $hex = '#' . strtolower($m[2] . $m[3] . $m[4] . $m[5] . $m[6] . $m[7]);
+                }
+                // replace Hex colors to short safe color names
+                $sb[] = array_key_exists($hex, $short_safe) ? $short_safe[$hex] : $hex;
+            }
+
+            $_index = $offset = $last_index - strlen($m[8]);
+        }
+
+        $sb[] = $this->substring($css, $_index);
+
+        return implode('', $sb);
+    }
+
+    /* CALLBACKS
+     * ---------------------------------------------------------------------------------------------
+     */
+
+    private function replace_string($matches)
+    {
+        $match = $matches[0];
+        $quote = substr($match, 0, 1);
+        // Must use addcslashes in PHP to avoid parsing of backslashes
+        $match = addcslashes($this->str_slice($match, 1, -1), '\\');
+
+        // maybe the string contains a comment-like substring?
+        // one, maybe more? put'em back then
+        if (($pos = $this->index_of($match, self::COMMENT)) >= 0) {
+            for ($i = 0, $max = count($this->comments); $i < $max; $i++) {
+                $match = preg_replace('/' . self::COMMENT . $i . '___/', $this->comments[$i], $match, 1);
+            }
+        }
+
+        // minify alpha opacity in filter strings
+        $match = preg_replace('/progid\:DXImageTransform\.Microsoft\.Alpha\(Opacity\=/i', 'alpha(opacity=', $match);
+
+        $this->preserved_tokens[] = $match;
+        return $quote . self::TOKEN . (count($this->preserved_tokens) - 1) . '___' . $quote;
+    }
+
+    private function replace_colon($matches)
+    {
+        return preg_replace('/\:/', self::CLASSCOLON, $matches[0]);
+    }
+
+    private function replace_calc($matches)
+    {
+        $this->preserved_tokens[] = preg_replace('/\s?([\*\/\(\),])\s?/', '$1', $matches[0]);
+        return self::TOKEN . (count($this->preserved_tokens) - 1) . '___';
+    }
+
+    private function rgb_to_hex($matches)
+    {
+        // Support for percentage values rgb(100%, 0%, 45%);
+        if ($this->index_of($matches[1], '%') >= 0){
+            $rgbcolors = explode(',', str_replace('%', '', $matches[1]));
+            for ($i = 0; $i < count($rgbcolors); $i++) {
+                $rgbcolors[$i] = $this->round_number(floatval($rgbcolors[$i]) * 2.55);
+            }
+        } else {
+            $rgbcolors = explode(',', $matches[1]);
+        }
+
+        // Values outside the sRGB color space should be clipped (0-255)
+        for ($i = 0; $i < count($rgbcolors); $i++) {
+            $rgbcolors[$i] = $this->clamp_number(intval($rgbcolors[$i], 10), 0, 255);
+            $rgbcolors[$i] = sprintf("%02x", $rgbcolors[$i]);
+        }
+
+        // Fix for issue #2528093
+        if (!preg_match('/[\s\,\);\}]/', $matches[2])){
+            $matches[2] = ' ' . $matches[2];
+        }
+
+        return '#' . implode('', $rgbcolors) . $matches[2];
+    }
+
+    private function hsl_to_hex($matches)
+    {
+        $values = explode(',', str_replace('%', '', $matches[1]));
+        $h = floatval($values[0]);
+        $s = floatval($values[1]);
+        $l = floatval($values[2]);
+
+        // Wrap and clamp, then fraction!
+        $h = ((($h % 360) + 360) % 360) / 360;
+        $s = $this->clamp_number($s, 0, 100) / 100;
+        $l = $this->clamp_number($l, 0, 100) / 100;
+
+        if ($s == 0) {
+            $r = $g = $b = $this->round_number(255 * $l);
+        } else {
+            $v2 = $l < 0.5 ? $l * (1 + $s) : ($l + $s) - ($s * $l);
+            $v1 = (2 * $l) - $v2;
+            $r = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h + (1/3)));
+            $g = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h));
+            $b = $this->round_number(255 * $this->hue_to_rgb($v1, $v2, $h - (1/3)));
+        }
+
+        return $this->rgb_to_hex(array('', $r.','.$g.','.$b, $matches[2]));
+    }
+
+    /* HELPERS
+     * ---------------------------------------------------------------------------------------------
+     */
+
+    private function hue_to_rgb($v1, $v2, $vh)
+    {
+        $vh = $vh < 0 ? $vh + 1 : ($vh > 1 ? $vh - 1 : $vh);
+        if ($vh * 6 < 1) return $v1 + ($v2 - $v1) * 6 * $vh;
+        if ($vh * 2 < 1) return $v2;
+        if ($vh * 3 < 2) return $v1 + ($v2 - $v1) * ((2/3) - $vh) * 6;
+        return $v1;
+    }
+
+    private function round_number($n)
+    {
+        return intval(floor(floatval($n) + 0.5), 10);
+    }
+
+    private function clamp_number($n, $min, $max)
+    {
+        return min(max($n, $min), $max);
+    }
+
+    /**
+     * PHP port of Javascript's "indexOf" function for strings only
+     * Author: Tubal Martin http://blog.margenn.com
+     *
+     * @param string $haystack
+     * @param string $needle
+     * @param int    $offset index (optional)
+     * @return int
+     */
+    private function index_of($haystack, $needle, $offset = 0)
+    {
+        $index = strpos($haystack, $needle, $offset);
+
+        return ($index !== FALSE) ? $index : -1;
+    }
+
+    /**
+     * PHP port of Javascript's "substring" function
+     * Author: Tubal Martin http://blog.margenn.com
+     * Tests: http://margenn.com/tubal/substring/
+     *
+     * @param string   $str
+     * @param int      $from index
+     * @param int|bool $to index (optional)
+     * @return string
+     */
+    private function substring($str, $from = 0, $to = FALSE)
+    {
+        if ($to !== FALSE) {
+            if ($from == $to || ($from <= 0 && $to < 0)) {
+                return '';
+            }
+
+            if ($from > $to) {
+                $from_copy = $from;
+                $from = $to;
+                $to = $from_copy;
+            }
+        }
+
+        if ($from < 0) {
+            $from = 0;
+        }
+
+        $substring = ($to === FALSE) ? substr($str, $from) : substr($str, $from, $to - $from);
+        return ($substring === FALSE) ? '' : $substring;
+    }
+
+    /**
+     * PHP port of Javascript's "slice" function for strings only
+     * Author: Tubal Martin http://blog.margenn.com
+     * Tests: http://margenn.com/tubal/str_slice/
+     *
+     * @param string   $str
+     * @param int      $start index
+     * @param int|bool $end index (optional)
+     * @return string
+     */
+    private function str_slice($str, $start = 0, $end = FALSE)
+    {
+        if ($end !== FALSE && ($start < 0 || $end <= 0)) {
+            $max = strlen($str);
+
+            if ($start < 0) {
+                if (($start = $max + $start) < 0) {
+                    return '';
+                }
+            }
+
+            if ($end < 0) {
+                if (($end = $max + $end) < 0) {
+                    return '';
+                }
+            }
+
+            if ($end <= $start) {
+                return '';
+            }
+        }
+
+        $slice = ($end === FALSE) ? substr($str, $start) : substr($str, $start, $end - $start);
+        return ($slice === FALSE) ? '' : $slice;
+    }
+
+    /**
+     * Convert strings like "64M" or "30" to int values
+     * @param mixed $size
+     * @return int
+     */
+    private function normalize_int($size)
+    {
+        if (is_string($size)) {
+            switch (substr($size, -1)) {
+                case 'M': case 'm': return $size * 1048576;
+                case 'K': case 'k': return $size * 1024;
+                case 'G': case 'g': return $size * 1073741824;
+            }
+        }
+
+        return (int) $size;
+    }
+}
\ No newline at end of file
diff --git a/application/libraries/CSVImporter.php b/application/libraries/CSVImporter.php
new file mode 100644
index 0000000..ed3e219
--- /dev/null
+++ b/application/libraries/CSVImporter.php
@@ -0,0 +1,434 @@
+<?php
+/**
+ * CSV Report Importer Library
+ *
+ * Imports reports within CSV file referenced by filehandle.
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ *
+ */
+class CSVImporter {	
+	/**
+	 * Notices to be passed on successful data import
+	 * @var array
+	 */
+	public $notices = array();
+	
+	/**
+	 * Errors to be passed on failed data import
+	 * @var array
+	 */
+	public $errors = array();
+	
+	/**
+	 * Total number of reports within CSV file
+	 * @var int
+	 */		
+	public $totalreports = 0;
+	
+	/**
+	 * Total number of reports successfully imported
+	 * @var int
+	 */
+	public $importedreports = 0;
+	
+	/**
+	 * Categories successfully imported
+	 * @var array
+	 */
+	private $categories_added = array();
+		
+	/**
+	 * Reports successfully imported
+	 * @var array
+	 */
+	private $incidents_added = array();
+	
+	/**
+	 * Incident persons successfully imported
+	 * @var array
+	 */
+	private $incident_persons_added = array();
+	
+	/**
+	 * Custom form field responses successfully imported
+	 * @var array
+	 */
+	private $incident_responses_added = array();
+	
+	/**
+	 * Incident locations successfully imported
+	 * @var array
+	 */
+	private $locations_added = array();
+	
+	/**
+	 * Incident categories successfully imported
+	 * @var array
+	 */
+	private $incident_categories_added = array();
+	
+	/**
+	 * Function to import CSV file referenced by the file handle
+	 * @param string $filehandle
+	 * @return bool 
+	 */
+	function import($file) 
+	{
+		// Get contents of CSV file
+		$data = file_get_contents($file);
+
+		// Normalize new lines, replace ANY unicode new line with \n (should cover Mac OS9, Unix, Windows, etc)
+		$replacedata = preg_replace('/\R/u', "\n", mb_convert_encoding($data, 'UTF-8'));
+
+		// Check for preg error, and fall back to original data
+		if (preg_last_error() !== PREG_NO_ERROR) {
+			$replacedata = $data;
+		}
+
+		// Replace file content
+		file_put_contents($file, $replacedata);
+		
+		if($filehandle = fopen($_FILES['uploadfile']['tmp_name'], 'r'))
+		{
+			$csvtable = new Csvtable($filehandle);
+			// Set the required columns of the CSV file
+			$requiredcolumns = array('INCIDENT TITLE','INCIDENT DATE');
+			foreach ($requiredcolumns as $requiredcolumn)
+			{
+				// If the CSV file is missing any required column, return an error
+				if (!$csvtable->hasColumn($requiredcolumn))
+				{
+					$this->errors[] = Kohana::lang('import.csv.required_column').'"'.$requiredcolumn.'"';
+				}
+			}
+		
+			if (count($this->errors))
+			{
+				return false;
+			}
+		
+			// So we can assign category id to incidents, based on category title
+			$this->existing_categories = ORM::factory('category')->select_list('category_title','id'); 
+			//Since we capitalize the category names from the CSV file, we should also capitlize the 
+			//category titles here so we get case insensative behavior. For some reason users don't
+			//always captilize the cateogry names as they enter them in
+			$temp_cat = array();
+			foreach($this->existing_categories as $title => $id)
+			{
+				$temp_cat[utf8::strtoupper($title)] = $id;
+
+				// Add translated titles too
+				$langs = Category_Lang_Model::category_langs($id);
+				if (isset($langs[$id]))
+				{
+					foreach($langs[$id] as $l)
+					{
+						$temp_cat[utf8::strtoupper($l['category_title'])] = $id;
+					}
+				}
+			}
+			$this->existing_categories = $temp_cat;
+		
+			// So we can check if incident already exists in database
+			$this->incident_ids = ORM::factory('incident')->select_list('id','id'); 
+			$this->time = date("Y-m-d H:i:s",time());
+			$rows = $csvtable->getRows();
+			$this->totalreports = count($rows);
+			$this->rownumber = 0;
+	 	
+			// Loop through CSV rows
+		 	foreach($rows as $row)
+		 	{
+				$this->rownumber++;
+				if (isset($row['#']) AND isset($this->incident_ids[$row['#']]))
+				{
+					$this->notices[] = Kohana::lang('import.incident_exists').$row['#'];
+				}
+				else
+				{
+					if ($this->import_report($row))
+					{
+						$this->importedreports++;
+					}
+					else
+					{
+						$this->rollback();
+						return false;
+					}
+				}
+			} 
+		}
+		else
+		{
+			$this->errors[] = Kohana::lang('ui_admin.file_open_error');
+		}
+		
+		// If we have errors, return FALSE, else TRUE
+		return count($this->errors) === 0;
+	}
+	
+	/**
+	 * Function to undo import of reports
+	 */
+	function rollback()
+	{
+		if (count($this->incidents_added)) ORM::factory('incident')->delete_all($this->incidents_added);
+		if (count($this->categories_added)) ORM::factory('category')->delete_all($this->categories_added);
+		if (count($this->locations_added)) ORM::factory('location')->delete_all($this->locations_added);
+		if (count($this->incident_categories_added)) ORM::factory('incident_category')->delete_all($this->incident_categories_added);
+		if (count($this->incident_persons_added)) ORM::factory('incident_person')->delete_all($this->incident_persons_added);
+		if (count($this->incident_responses_added)) ORM::factory('form_response')->delete_all($this->incident_responses_added);
+	}
+	
+	/**
+	 * Function to import a report form a row in the CSV file
+	 * @param array $row
+	 * @return bool
+	 */
+	function import_report($row)
+	{
+		// If the date is not in proper date format
+		if (!strtotime($row['INCIDENT DATE']))
+		{
+			$this->errors[] = Kohana::lang('import.incident_date').($this->rownumber+1).': '.$row['INCIDENT DATE'];
+		}
+		// If a value of Yes or No is NOT set for approval status for the imported row
+		if (isset($row["APPROVED"]) AND !in_array(utf8::strtoupper($row["APPROVED"]),array('NO','YES')))
+		{
+			$this->errors[] = Kohana::lang('import.csv.approved').($this->rownumber+1);
+		}
+		// If a value of Yes or No is NOT set for verified status for the imported row 
+		if (isset($row["VERIFIED"]) AND !in_array(utf8::strtoupper($row["VERIFIED"]),array('NO','YES'))) 
+		{
+			$this->errors[] = Kohana::lang('import.csv.verified').($this->rownumber+1);
+		}
+		if (count($this->errors)) 
+		{
+			return false;
+		}
+		
+		// STEP 1: SAVE LOCATION
+		if (isset($row['LOCATION']))
+		{
+			$location = new Location_Model();
+			$location->location_name = isset($row['LOCATION']) ? $row['LOCATION'] : '';
+			
+			// For Geocoding purposes
+			$location_geocoded = map::geocode($location->location_name);
+			
+			// If we have LATITUDE and LONGITUDE use those
+			if ( isset($row['LATITUDE']) AND isset($row['LONGITUDE']) ) 
+			{
+				$location->latitude = isset($row['LATITUDE']) ? $row['LATITUDE'] : 0;
+				$location->longitude = isset($row['LONGITUDE']) ? $row['LONGITUDE'] : 0;
+			} 
+			
+			// Otherwise, get geocoded lat/lon values
+			else
+			{
+				$location->latitude = $location_geocoded ? $location_geocoded['latitude'] : 0;
+				$location->longitude = $location_geocoded ? $location_geocoded['longitude'] : 0;
+			}
+			$location->country_id = $location_geocoded ? $location_geocoded['country_id'] : 0;
+			$location->location_date = $this->time;
+			$location->save();
+			$this->locations_added[] = $location->id;
+		}
+		
+		// STEP 2: SAVE INCIDENT
+		$incident = new Incident_Model();
+		$incident->location_id = isset($row['LOCATION']) ? $location->id : 0;
+		$incident->user_id = 0;
+		$incident->form_id = (isset($row['FORM #']) AND Form_Model::is_valid_form($row['FORM #'])) ? $row['FORM #'] : 1;
+		$incident->incident_title = $row['INCIDENT TITLE'];
+		$incident->incident_description = isset($row['DESCRIPTION']) ? $row['DESCRIPTION'] : '';
+		$incident->incident_date = date("Y-m-d H:i:s",strtotime($row['INCIDENT DATE']));
+		$incident->incident_dateadd = $this->time;
+		$incident->incident_active = (isset($row['APPROVED']) AND utf8::strtoupper($row['APPROVED']) == 'YES') ? 1 : 0;
+		$incident->incident_verified = (isset($row['VERIFIED']) AND utf8::strtoupper($row['VERIFIED']) == 'YES') ? 1 :0;
+		$incident->save();
+		$this->incidents_added[] = $incident->id;
+		
+		// STEP 3: Save Personal Information
+		if(isset($row['FIRST NAME']) OR isset($row['LAST NAME']) OR isset($row['EMAIL']))
+		{
+			$person = new Incident_Person_Model();
+			$person->incident_id = $incident->id;
+			$person->person_first = isset($row['FIRST NAME']) ? $row['FIRST NAME'] : '';
+			$person->person_last = isset($row['LAST NAME']) ? $row['LAST NAME'] : '';
+			$person->person_email = (isset($row['EMAIL']) AND valid::email($row['EMAIL']))? $row['EMAIL'] : '';
+			$person->person_date = date("Y-m-d H:i:s",time());
+			
+			// Make sure that you're not importing an empty record i.e at least one field has been recorded
+			// If all fields are empty i.e you have an empty record, don't save
+			if(!empty($person->person_first) OR !empty($person->person_last) OR !empty($person->person_email))
+			{
+				$person->save();
+								
+				// Add to array of incident persons added
+				$this->incident_persons_added[] = $person->id;
+			}
+			
+		}
+		// STEP 4: SAVE CATEGORIES
+		// If CATEGORY column exists
+		if (isset($row['CATEGORY']))
+		{
+			$categorynames = explode(',',trim($row['CATEGORY']));
+			
+			// Trim whitespace from array values
+			$categorynames = array_map('trim',$categorynames);
+			
+			// Get rid of duplicate category entries in a row
+			$categories = array_unique(array_map('strtolower', $categorynames));
+		
+			// Add categories to incident
+			foreach ($categories as $categoryname)
+			{
+				// Convert the first string character of the category name to Uppercase
+				$categoryname = utf8::ucfirst($categoryname);
+				
+				// For purposes of adding an entry into the incident_category table
+				$incident_category = new Incident_Category_Model();
+				$incident_category->incident_id = $incident->id; 
+				
+				// If category name exists, add entry in incident_category table
+				if($categoryname != '')
+				{
+					// Check if the category exists (made sure to convert to uppercase for comparison)
+					if (!isset($this->existing_categories[utf8::strtoupper($categoryname)]))
+					{
+						$this->notices[] = Kohana::lang('import.new_category').$categoryname;
+						$category = new Category_Model;
+						$category->category_title = $categoryname;
+	
+						// We'll just use black for now. Maybe something random?
+						$category->category_color = '000000'; 
+						
+						// because all current categories are of type '5'
+						$category->category_visible = 1;
+						$category->category_description = $categoryname;
+						$category->category_position = count($this->existing_categories);
+						$category->save();
+						$this->categories_added[] = $category->id;
+						// Now category_id is known: This time, and for the rest of the import.
+						$this->existing_categories[utf8::strtoupper($categoryname)] = $category->id; 
+					}
+					$incident_category->category_id = $this->existing_categories[utf8::strtoupper($categoryname)];
+					$incident_category->save();
+					$this->incident_categories_added[] = $incident_category->id;
+				}	
+			} 
+		}
+		
+		// STEP 5: Save Custom form fields responses
+		// Check for form_id
+		$form_id = (isset($row['FORM #']) AND Form_Model::is_valid_form($row['FORM #'])) ? $row['FORM #'] : 1;
+		
+		// Get custom form fields for this particular form
+		$custom_titles = customforms::get_custom_form_fields('',$form_id,false);
+		
+		// Do custom form fields exist on this deployment?
+		if (!empty($custom_titles))
+		{
+			foreach($custom_titles as $field_name)
+			{
+				// Check if the column exists in the CSV
+				$rowname = utf8::strtoupper($field_name['field_name']);
+				if(isset($row[$rowname.'-'.$form_id]))
+				{		
+					$response = $row[$rowname.'-'.$form_id];
+						
+					// Grab field_id and field_type
+					$field_id = $field_name['field_id'];
+					$field_type = $field_name['field_type'];
+					
+					// Initialize form response model
+					$form_response = new Form_Response_Model();
+					$form_response->incident_id = $incident->id;
+					$form_response->form_field_id = $field_id;
+						
+					// If form response exists
+					if($response != '')
+					{
+						/* Handling case sensitivity issues with custom form field upload */ 
+						// Check if the field is a radio button, checkbox OR dropdown field
+						if ($field_type == '5' OR $field_type == '6' OR $field_type =='7')
+						{
+							// Get field option values
+							$field_values = $field_name['field_default'];
+							
+							// Split field options into individual values
+							$options = explode(",", $field_values);
+							
+							// Since radio button and dropdown fields take single responses
+							if ($field_type == '5' OR $field_type == '7')
+							{
+								foreach ($options as $option)
+								{
+									// Carry out a case insensitive comparison between individual field options and csv response
+									// If there's a match, store field option value from the db
+									if (strcasecmp($option, $response) == 0)
+									{
+										$form_response->form_response = $option;
+									}
+								}	
+							}
+							
+							// For checkboxes, which accomodate multiple responses
+							if ($field_type == '6')
+							{
+								// Split user responses into single values
+								$csvresponses = explode(",", $response);
+								$values = array();
+								foreach ($options as $option)
+								{
+									foreach ($csvresponses as $csvresponse)
+									{
+										// Carry out a case insensitive comparison between individual field options and csv response
+										// If there's a match
+										if(strcasecmp($option, $csvresponse) == 0)
+										{
+											// Store field option value from the db
+											$values[] = $option;
+										}
+									}	
+								}
+								
+								// Concatenate checkbox values into a string, separated by a comma
+								$form_response->form_response = implode(",", $values);	
+							}	
+						}
+						
+						// For all other form fields apart from the three mentioned above
+						else
+						{
+							$form_response->form_response = $response;
+						}
+						
+						// If form_response is provided based on conditions set above, Save the form response
+						if ($form_response->form_response != '')
+						{
+							$form_response->save();
+							
+							// Add to array of field responses added
+							$this->incident_responses_added[] = $form_response->id;
+						}
+					}
+				}	
+			}	
+		}
+		 
+	return true;
+	}
+}
+
+?>
diff --git a/application/libraries/Cloudfiles.php b/application/libraries/Cloudfiles.php
new file mode 100644
index 0000000..b2f5992
--- /dev/null
+++ b/application/libraries/Cloudfiles.php
@@ -0,0 +1,159 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cloudfiles
+ *
+ * Base class for all Cloud Files API Bindings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+define("PHP_CF_VERSION", "1.7.10");
+define("USER_AGENT", sprintf("PHP-CloudFiles/%s", PHP_CF_VERSION));
+define("MAX_HEADER_NAME_LEN", 128);
+define("MAX_HEADER_VALUE_LEN", 256);
+define("ACCOUNT_CONTAINER_COUNT", "X-Account-Container-Count");
+define("ACCOUNT_BYTES_USED", "X-Account-Bytes-Used");
+define("CONTAINER_OBJ_COUNT", "X-Container-Object-Count");
+define("CONTAINER_BYTES_USED", "X-Container-Bytes-Used");
+define("MANIFEST_HEADER", "X-Object-Manifest");
+define("METADATA_HEADER_PREFIX", "X-Object-Meta-");
+define("CONTENT_HEADER_PREFIX", "Content-");
+define("ACCESS_CONTROL_HEADER_PREFIX", "Access-Control-");
+define("ORIGIN_HEADER", "Origin");
+define("CDN_URI", "X-CDN-URI");
+define("CDN_SSL_URI", "X-CDN-SSL-URI");
+define("CDN_STREAMING_URI", "X-CDN-Streaming-URI");
+define("CDN_ENABLED", "X-CDN-Enabled");
+define("CDN_LOG_RETENTION", "X-Log-Retention");
+define("CDN_ACL_USER_AGENT", "X-User-Agent-ACL");
+define("CDN_ACL_REFERRER", "X-Referrer-ACL");
+define("CDN_TTL", "X-TTL");
+define("CDNM_URL", "X-CDN-Management-Url");
+define("STORAGE_URL", "X-Storage-Url");
+define("AUTH_TOKEN", "X-Auth-Token");
+define("AUTH_USER_HEADER", "X-Auth-User");
+define("AUTH_KEY_HEADER", "X-Auth-Key");
+define("AUTH_USER_HEADER_LEGACY", "X-Storage-User");
+define("AUTH_KEY_HEADER_LEGACY", "X-Storage-Pass");
+define("AUTH_TOKEN_LEGACY", "X-Storage-Token");
+define("CDN_EMAIL", "X-Purge-Email");
+define("DESTINATION", "Destination");
+define("ETAG_HEADER", "ETag");
+define("LAST_MODIFIED_HEADER", "Last-Modified");
+define("CONTENT_TYPE_HEADER", "Content-Type");
+define("CONTENT_LENGTH_HEADER", "Content-Length");
+define("USER_AGENT_HEADER", "User-Agent");
+
+define("DEFAULT_CF_API_VERSION", 1);
+define("MAX_CONTAINER_NAME_LEN", 256);
+define("MAX_OBJECT_NAME_LEN", 1024);
+define("MAX_OBJECT_SIZE", 5*1024*1024*1024+1);
+define("US_AUTHURL", "https://auth.api.rackspacecloud.com");
+define("UK_AUTHURL", "https://lon.auth.api.rackspacecloud.com");
+
+class Cloudfiles {
+
+	// Cloud Files Username, defined in cdn config file
+	protected $username;
+
+	// Rackspace API Key, defined in cdn config file
+	protected $api_key;
+
+	// Name of containter you want to store your files in, defined in cdn config file
+	protected $container;
+
+	// Connection used throughout the class
+	protected $conn;
+
+	public function __construct()
+	{
+		$this->username = Kohana::config("cdn.cdn_username");
+		$this->api_key = Kohana::config("cdn.cdn_api_key");
+		$this->container = Kohana::config("cdn.cdn_container");
+	}
+
+	public function authenticate()
+	{
+		if($this->conn) return;
+
+		// Include CF libraries
+		require_once Kohana::find_file('libraries/cloudfiles', 'CF_Authentication');
+		require_once Kohana::find_file('libraries/cloudfiles', 'CF_Connection');
+		require_once Kohana::find_file('libraries/cloudfiles', 'CF_Container');
+		require_once Kohana::find_file('libraries/cloudfiles', 'CF_Http');
+		require_once Kohana::find_file('libraries/cloudfiles', 'CF_Object');
+
+		$auth = new CF_Authentication($this->username, $this->api_key);
+		$auth->authenticate();
+		$this->conn = new CF_Connection($auth);
+	}
+
+	// $file must be the absolute path to the file
+	public function upload($filename, $appendUploadDir = TRUE)
+	{
+		$this->authenticate();
+
+		$local_directory = Kohana::config('upload.directory', TRUE);
+		$local_directory = rtrim($local_directory, '/').'/';
+
+		$fullpath = $appendUploadDir ? $local_directory.$filename : DOCROOT.$filename;
+
+		// Put this in a special directory based on subdomain if subdomain is set
+		$dir = $this->_special_dir();
+
+		// Set container
+		$container = $this->conn->create_container($this->container);
+
+		// This creates a "fake" directory structure on the Cloud Files system
+		$container->create_paths($dir.$filename);
+
+		// Get the object ready for loading
+		$file = $container->create_object($dir.$filename);
+
+		// Finally, upload the file
+		$file->load_from_filename($fullpath);
+
+		$uri = $container->make_public();
+
+		// Return the file path URL
+		return (Kohana::config('config.external_site_protocol') == 'https') ? $file->public_ssl_uri() : $file->public_uri();
+	}
+
+	public function delete($url)
+	{
+		$this->authenticate();
+
+		// Get the container that has the object
+		$container = $this->conn->get_container($this->container);
+
+		// Figure out object name based on the URL
+		$url = str_ireplace('http://','',$url);
+		$url = str_ireplace('https://','',$url);
+		$url = explode('/',$url);
+		unset($url[0]);
+		$object = implode('/',$url);
+
+		// Do the deed
+		$container->delete_object($object);
+	}
+
+	public function _special_dir()
+	{
+		$dir = '';
+		if(Kohana::config('settings.subdomain') != '') {
+			$dir = Kohana::config('settings.subdomain');
+			// Make sure there's a slash on the end
+			$dir = rtrim($dir, '/').'/';
+		}
+		return $dir;
+	}
+}
+
+?>
diff --git a/application/libraries/CronParser.php b/application/libraries/CronParser.php
new file mode 100644
index 0000000..62eba74
--- /dev/null
+++ b/application/libraries/CronParser.php
@@ -0,0 +1,604 @@
+<?php /* $Id: CronParser.php,v 1.7 2005/09/12 01:04:05 ns Exp $ */
+
+/**####################################################################################################**\
+   Version: V1.01
+   Release Date: 12 Sep 2005
+   Licence: GPL
+   By: Nikol S
+   Please send bug reports to ns at eyo.com.au
+\**####################################################################################################**/
+
+/* This class is based on the concept in the CronParser class written by Mick Sear http://www.ecreate.co.uk
+ * The following functions are direct copies from or based on the original class:
+ * getLastRan(), getDebug(), debug(), expand_ranges()
+ *
+ * Who can use this class?
+ * This class is idea for people who can not use the traditional Unix cron through shell.
+ * One way of using is embedding the calling script in a web page which is often visited.
+ * The script will work out the last due time, by comparing with run log timestamp. The scrip
+ * will envoke any scripts needed to run, be it deleting older table records, or updating prices.
+ * It can parse the same cron string used by Unix.
+ */
+
+/* Usage example:
+
+$cron_str0 = "0,12,30-51 3,21-23,10 1-25 9-12,1 0,3-7";
+require_once("CronParser.php");
+$cron = new CronParser();
+$cron->calcLastRan($cron_str0);
+// $cron->getLastRanUnix() returns an Unix timestamp
+echo "Cron '$cron_str0' last due at: " . date('r', $cron->getLastRanUnix()) . "<p>";
+// $cron->getLastRan() returns last due time in an array
+print_r($cron->getLastRan());
+echo "Debug:<br>" . nl2br($cron->getDebug());
+
+$cron_str1 = "3 12 * * *";
+if ($cron->calcLastRan($cron_str1))
+{
+   echo "<p>Cron '$cron_str1' last due at: " . date('r', $cron->getLastRanUnix()) . "<p>";
+   print_r($cron->getLastRan());
+}
+else
+{
+   echo "Error parsing";
+}
+echo "Debug:<br>" . nl2br($cron->getDebug());
+
+ *#######################################################################################################
+ */
+
+class CronParser
+{
+
+ 	var $bits = Array(); //exploded String like 0 1 * * *
+ 	var $now = Array();	//Array of cron-style entries for time()
+ 	var $lastRan; 		//Timestamp of last ran time.
+ 	var $taken;
+ 	var $debug;
+	var $year;
+	var $month;
+	var $day;
+	var $hour;
+	var $minute;
+	var $minutes_arr = array();	//minutes array based on cron string
+	var $hours_arr = array();	//hours array based on cron string
+	var $months_arr = array();	//months array based on cron string
+
+	function getLastRan()
+	{
+		return explode(",", strftime("%M,%H,%d,%m,%w,%Y", $this->lastRan)); //Get the values for now in a format we can use
+	}
+
+	function getLastRanUnix()
+	{
+		return $this->lastRan;
+	}
+
+	function getDebug()
+	{
+ 		return $this->debug;
+	}
+
+	function debug($str)
+	{
+		if (is_array($str))
+		{
+			$this->debug .= "\nArray: ";
+			foreach($str as $k=>$v)
+			{
+				$this->debug .= "$k=>$v, ";
+			}
+
+		}
+		else
+		{
+			$this->debug .= "\n$str";
+		}
+		//echo nl2br($this->debug);
+	}
+
+	/**
+	 * Assumes that value is not *, and creates an array of valid numbers that
+	 * the string represents.  Returns an array.
+	 */
+	function expand_ranges($str)
+	{
+		if (strstr($str,  ","))
+		{
+			$arParts = explode(',', $str);
+			foreach ($arParts AS $part)
+			{
+				if (strstr($part, '-'))
+				{
+					$arRange = explode('-', $part);
+					for ($i = $arRange[0]; $i <= $arRange[1]; $i++)
+					{
+						$ret[] = $i;
+					}
+				}
+				else
+				{
+					$ret[] = $part;
+				}
+			}
+		}
+		elseif (strstr($str,  '-'))
+		{
+			$arRange = explode('-', $str);
+			for ($i = $arRange[0]; $i <= $arRange[1]; $i++)
+			{
+				$ret[] = $i;
+			}
+		}
+		else
+		{
+			$ret[] = $str;
+		}
+		$ret = array_unique($ret);
+		sort($ret);
+		return $ret;
+	}
+
+	function daysinmonth($month, $year)
+	{
+		return date('t', mktime(0, 0, 0, $month, 1, $year));
+	}
+
+	/**
+	 *  Calculate the last due time before this moment
+	 */
+	function calcLastRan($string)
+	{
+
+ 		$tstart = microtime();
+		$this->debug = "";
+		$this->lastRan = 0;
+		$this->year = NULL;
+		$this->month = NULL;
+		$this->day = NULL;
+		$this->hour = NULL;
+		$this->minute = NULL;
+		$this->hours_arr = array();
+		$this->minutes_arr = array();
+		$this->months_arr = array();
+
+		$string = preg_replace('/[\s]{2,}/', ' ', $string);
+
+		if (preg_match('/[^-,* \\d]/', $string) !== 0)
+		{
+			$this->debug("Cron String contains invalid character");
+			return false;
+		}
+
+		$this->debug("<b>Working on cron schedule: $string</b>");
+ 		$this->bits = @explode(" ", $string);
+
+		if (count($this->bits) != 5)
+		{
+			$this->debug("Cron string is invalid. Too many or too little sections after explode");
+			return false;
+		}
+
+		//put the current time into an array
+		$t = strftime("%M,%H,%d,%m,%w,%Y", time());
+		$this->now = explode(",", $t);
+
+		$this->year = $this->now[5];
+
+		$arMonths = $this->_getMonthsArray();
+
+		do
+		{
+			$this->month = array_pop($arMonths);
+		}
+		while ($this->month > $this->now[3]);
+
+		if ($this->month === NULL)
+		{
+			$this->year = $this->year - 1;
+			$this->debug("Not due within this year. So checking the previous year " . $this->year);
+			$arMonths = $this->_getMonthsArray();
+			$this->_prevMonth($arMonths);
+		}
+		elseif ($this->month == $this->now[3]) //now Sep, month = array(7,9,12)
+		{
+			$this->debug("Cron is due this month, getting days array.");
+			$arDays = $this->_getDaysArray($this->month, $this->year);
+
+			do
+			{
+				$this->day = array_pop($arDays);
+			}
+			while ($this->day > $this->now[2]);
+
+			if ($this->day === NULL)
+			{
+				$this->debug("Smallest day is even greater than today");
+				$this->_prevMonth($arMonths);
+			}
+			elseif ($this->day == $this->now[2])
+			{
+				$this->debug("Due to run today");
+				$arHours = $this->_getHoursArray();
+
+				do
+				{
+					$this->hour = array_pop($arHours);
+				}
+				while ($this->hour > $this->now[1]);
+
+				if ($this->hour === NULL) // now =2, arHours = array(3,5,7)
+				{
+					$this->debug("Not due this hour and some earlier hours, so go for previous day");
+					$this->_prevDay($arDays, $arMonths);
+				}
+				elseif ($this->hour < $this->now[1]) //now =2, arHours = array(1,3,5)
+				{
+					$this->minute = $this->_getLastMinute();
+				}
+				else // now =2, arHours = array(1,2,5)
+				{
+					$this->debug("Due this hour");
+					$arMinutes = $this->_getMinutesArray();
+					do
+					{
+						$this->minute = array_pop($arMinutes);
+					}
+					while ($this->minute > $this->now[0]);
+
+					if ($this->minute === NULL)
+					{
+						$this->debug("Not due this minute, so go for previous hour.");
+						$this->_prevHour($arHours, $arDays, $arMonths);
+					}
+					else
+					{
+						$this->debug("Due this very minute or some earlier minutes before this moment within this hour.");
+					}
+				}
+			}
+			else
+			{
+				$this->debug("Cron was due on " . $this->day . " of this month");
+				$this->hour = $this->_getLastHour();
+				$this->minute = $this->_getLastMinute();
+			}
+		}
+		else //now Sep, arrMonths=array(7, 10)
+		{
+			$this->debug("Cron was due before this month. Previous month is: " . $this->year . '-' . $this->month);
+			$this->day = $this->_getLastDay($this->month, $this->year);
+			if ($this->day === NULL)
+			{
+				//No scheduled date within this month. So we will try the previous month in the month array
+				$this->_prevMonth($arMonths);
+			}
+			else
+			{
+				$this->hour = $this->_getLastHour();
+				$this->minute = $this->_getLastMinute();
+			}
+		}
+
+		$tend = microtime();
+		$this->taken = $tend - $tstart;
+		$this->debug("Parsing $string taken " . $this->taken . " seconds");
+
+		//if the last due is beyond 1970
+		if ($this->minute === NULL)
+		{
+			$this->debug("Error calculating last due time");
+			return false;
+		}
+		else
+		{
+			$this->debug("LAST DUE: " . $this->hour . ":" . $this->minute . " on " . $this->day . "/" . $this->month . "/" . $this->year);
+			$this->lastRan = mktime($this->hour, $this->minute, 0, $this->month, $this->day, $this->year);
+			return true;
+		}
+	}
+
+	//get the due time before current month
+	function _prevMonth($arMonths)
+	{
+		$this->month = array_pop($arMonths);
+		if ($this->month === NULL)
+		{
+			$this->year = $this->year -1;
+			if ($this->year <= 1970)
+			{
+				$this->debug("Can not calculate last due time. At least not before 1970..");
+			}
+			else
+			{
+				$this->debug("Have to go for previous year " . $this->year);
+				$arMonths = $this->_getMonthsArray();
+				$this->_prevMonth($arMonths);
+			}
+		}
+		else
+		{
+			$this->debug("Getting the last day for previous month: " . $this->year . '-' . $this->month);
+			$this->day = $this->_getLastDay($this->month, $this->year);
+
+			if ($this->day === NULL)
+			{
+				//no available date schedule in this month
+				$this->_prevMonth($arMonths);
+			}
+			else
+			{
+				$this->hour = $this->_getLastHour();
+				$this->minute = $this->_getLastMinute();
+			}
+		}
+
+	}
+
+	//get the due time before current day
+	function _prevDay($arDays, $arMonths)
+	{
+		$this->debug("Go for the previous day");
+		$this->day = array_pop($arDays);
+		if ($this->day === NULL)
+		{
+			$this->debug("Have to go for previous month");
+			$this->_prevMonth($arMonths);
+		}
+		else
+		{
+			$this->hour = $this->_getLastHour();
+			$this->minute = $this->_getLastMinute();
+		}
+	}
+
+	//get the due time before current hour
+	function _prevHour($arHours, $arDays, $arMonths)
+	{
+		$this->debug("Going for previous hour");
+		$this->hour = array_pop($arHours);
+		if ($this->hour === NULL)
+		{
+			$this->debug("Have to go for previous day");
+			$this->_prevDay($arDays, $arMonths);
+		}
+		else
+		{
+			$this->minute = $this->_getLastMinute();
+		}
+	}
+
+	//not used at the moment
+	function _getLastMonth()
+	{
+		$months = $this->_getMonthsArray();
+		$month = array_pop($months);
+
+		return $month;
+	}
+
+	function _getLastDay($month, $year)
+	{
+		//put the available days for that month into an array
+		$days = $this->_getDaysArray($month, $year);
+		$day = array_pop($days);
+
+		return $day;
+	}
+
+	function _getLastHour()
+	{
+		$hours = $this->_getHoursArray();
+		$hour = array_pop($hours);
+
+		return $hour;
+	}
+
+	function _getLastMinute()
+	{
+		$minutes = $this->_getMinutesArray();
+		$minute = array_pop($minutes);
+
+		return $minute;
+	}
+
+	//remove the out of range array elements. $arr should be sorted already and does not contain duplicates
+	function _sanitize ($arr, $low, $high)
+	{
+		$count = count($arr);
+		for ($i = 0; $i <= ($count - 1); $i++)
+		{
+			if ($arr[$i] < $low)
+			{
+				$this->debug("Remove out of range element. {$arr[$i]} is outside $low - $high");
+				unset($arr[$i]);
+			}
+			else
+			{
+				break;
+			}
+		}
+
+		for ($i = ($count - 1); $i >= 0; $i--)
+		{
+			if ($arr[$i] > $high)
+			{
+				$this->debug("Remove out of range element. {$arr[$i]} is outside $low - $high");
+				unset ($arr[$i]);
+			}
+			else
+			{
+				break;
+			}
+		}
+
+		//re-assign keys
+		sort($arr);
+		return $arr;
+	}
+
+	//given a month/year, list all the days within that month fell into the week days list.
+	function _getDaysArray($month, $year = 0)
+	{
+		if ($year == 0)
+		{
+			$year = $this->year;
+		}
+
+		$days = array();
+
+		//return everyday of the month if both bit[2] and bit[4] are '*'
+		if ($this->bits[2] == '*' AND $this->bits[4] == '*')
+		{
+			$days = $this->getDays($month, $year);
+		}
+		else
+		{
+			//create an array for the weekdays
+			if ($this->bits[4] == '*')
+			{
+				for ($i = 0; $i <= 6; $i++)
+				{
+					$arWeekdays[] = $i;
+				}
+			}
+			else
+			{
+				$arWeekdays = $this->expand_ranges($this->bits[4]);
+				$arWeekdays = $this->_sanitize($arWeekdays, 0, 7);
+
+				//map 7 to 0, both represents Sunday. Array is sorted already!
+				if (in_array(7, $arWeekdays))
+				{
+					if (in_array(0, $arWeekdays))
+					{
+						array_pop($arWeekdays);
+					}
+					else
+					{
+						$tmp[] = 0;
+						array_pop($arWeekdays);
+						$arWeekdays = array_merge($tmp, $arWeekdays);
+					}
+				}
+			}
+			$this->debug("Array for the weekdays");
+			$this->debug($arWeekdays);
+
+			if ($this->bits[2] == '*')
+			{
+				$daysmonth = $this->getDays($month, $year);
+			}
+			else
+			{
+				$daysmonth = $this->expand_ranges($this->bits[2]);
+				// so that we do not end up with 31 of Feb
+				$daysinmonth = $this->daysinmonth($month, $year);
+				$daysmonth = $this->_sanitize($daysmonth, 1, $daysinmonth);
+			}
+
+			//Now match these days with weekdays
+			foreach ($daysmonth AS $day)
+			{
+				$wkday = date('w', mktime(0, 0, 0, $month, $day, $year));
+				if (in_array($wkday, $arWeekdays))
+				{
+					$days[] = $day;
+				}
+			}
+		}
+		$this->debug("Days array matching weekdays for $year-$month");
+		$this->debug($days);
+		return $days;
+	}
+
+	//given a month/year, return an array containing all the days in that month
+	function getDays($month, $year)
+	{
+		$daysinmonth = $this->daysinmonth($month, $year);
+		$this->debug("Number of days in $year-$month : $daysinmonth");
+		$days = array();
+		for ($i = 1; $i <= $daysinmonth; $i++)
+		{
+			$days[] = $i;
+		}
+		return $days;
+	}
+
+	function _getHoursArray()
+	{
+		if (empty($this->hours_arr))
+		{
+			$hours = array();
+
+			if ($this->bits[1] == '*')
+			{
+				for ($i = 0; $i <= 23; $i++)
+				{
+					$hours[] = $i;
+				}
+			}
+			else
+			{
+				$hours = $this->expand_ranges($this->bits[1]);
+				$hours = $this->_sanitize($hours, 0, 23);
+			}
+
+			$this->debug("Hour array");
+			$this->debug($hours);
+			$this->hours_arr = $hours;
+		}
+		return $this->hours_arr;
+	}
+
+	function _getMinutesArray()
+	{
+		if (empty($this->minutes_arr))
+		{
+			$minutes = array();
+
+			if ($this->bits[0] == '*')
+			{
+				for ($i = 0; $i <= 60; $i++)
+				{
+					$minutes[] = $i;
+				}
+			}
+			else
+			{
+				$minutes = $this->expand_ranges($this->bits[0]);
+				$minutes = $this->_sanitize($minutes, 0, 59);
+			}
+			$this->debug("Minutes array");
+			$this->debug($minutes);
+			$this->minutes_arr = $minutes;
+		}
+		return $this->minutes_arr;
+	}
+
+	function _getMonthsArray()
+	{
+		if (empty($this->months_arr))
+		{
+			$months = array();
+			if ($this->bits[3] == '*')
+			{
+				for ($i = 1; $i <= 12; $i++)
+				{
+					$months[] = $i;
+				}
+			}
+			else
+			{
+				$months = $this->expand_ranges($this->bits[3]);
+				$months = $this->_sanitize($months, 1, 12);
+			}
+			$this->debug("Months array");
+			$this->debug($months);
+			$this->months_arr = $months;
+		}
+		return $this->months_arr;
+	}
+
+}
+?>
\ No newline at end of file
diff --git a/application/libraries/Csvtable.php b/application/libraries/Csvtable.php
new file mode 100644
index 0000000..b61424a
--- /dev/null
+++ b/application/libraries/Csvtable.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * CSV Table
+ *
+ * Simple class to load arbitrary CSV files as an array of associative arrays.
+ * Uses first line of the file as column names.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ *
+ */
+class Csvtable_Core {
+	/**
+	 * Column names
+	 * @var array
+	 */
+	private $colnames = array();
+
+	function __construct($filehandle)
+	{
+		$this->filehandle = $filehandle;
+		// 1000 chars is max line length
+		if(($fields = fgetcsv($filehandle, 1000)) !== FALSE)
+		{
+			$colnum = 0;
+			foreach($fields as $field)
+			{
+				$this->colnames[utf8::strtoupper($field)] = $colnum;
+				$colnum++;
+			}
+		}
+	}
+
+	/**
+	 * Function to check if the CSV File has a column in the name given
+	 * @param string $name Name of column to be checked
+	 * @return bool
+	 */
+	function hasColumn($name)
+	{
+		return isset($this->colnames[$name]);
+	}
+
+	/**
+	 * Function to get rows in the CSV files
+	 * @return array
+	 */
+	function getRows()
+	{
+		$rows = array();
+		while (($fields = fgetcsv($this->filehandle, 4000, ",")) !== FALSE)
+		{
+			foreach ($this->colnames as $colname => $colnum)
+			{
+				$row[$colname] = isset($fields[$colnum]) ? $fields[$colnum] : '';
+			}
+			$rows[] = $row;
+		}
+		return $rows;
+	}
+}
+?>
\ No newline at end of file
diff --git a/application/libraries/Database_Expression.php b/application/libraries/Database_Expression.php
new file mode 100644
index 0000000..52e114b
--- /dev/null
+++ b/application/libraries/Database_Expression.php
@@ -0,0 +1,141 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Database Expression library ported from KO3
+ * 
+ * Database expressions can be used to add unescaped SQL fragments to a
+ * [Database_Query_Builder] object.
+ *
+ * For example, you can use an expression to generate a column alias:
+ *
+ *     // SELECT CONCAT(first_name, last_name) AS full_name
+ *     $query = DB::select(array(DB::expr('CONCAT(first_name, last_name)'), 'full_name')));
+ *
+ * More examples are available on the [Query Builder](database/query/builder#database-expressions) page
+ * 
+ * @package    Kohana/Database
+ * @category   Base
+ * @author     Kohana Team
+ * @copyright  (c) 2009 Kohana Team
+ * @license    http://kohanaphp.com/license
+ */
+class Database_Expression_Core {
+
+	// Unquoted parameters
+	protected $_parameters;
+
+	// Raw expression string
+	protected $_value;
+
+	/**
+	 * Sets the expression string.
+	 *
+	 *     $expression = new Database_Expression('COUNT(users.id)');
+	 *
+	 * @param   string  $value      raw SQL expression string
+	 * @param   array   $parameters unquoted parameter values
+	 * @return  void
+	 */
+	public function __construct($value, $parameters = array())
+	{
+		// Set the expression string
+		$this->_value = $value;
+		$this->_parameters = $parameters;
+	}
+
+	/**
+	 * Bind a variable to a parameter.
+	 *
+	 * @param   string  $param  parameter key to replace
+	 * @param   mixed   $var    variable to use
+	 * @return  $this
+	 */
+	public function bind($param, & $var)
+	{
+		$this->_parameters[$param] =& $var;
+
+		return $this;
+	}
+
+	/**
+	 * Set the value of a parameter.
+	 *
+	 * @param   string  $param  parameter key to replace
+	 * @param   mixed   $value  value to use
+	 * @return  $this
+	 */
+	public function param($param, $value)
+	{
+		$this->_parameters[$param] = $value;
+
+		return $this;
+	}
+
+	/**
+	 * Add multiple parameter values.
+	 *
+	 * @param   array   $params list of parameter values
+	 * @return  $this
+	 */
+	public function parameters(array $params)
+	{
+		$this->_parameters = $params + $this->_parameters;
+
+		return $this;
+	}
+
+	/**
+	 * Get the expression value as a string.
+	 *
+	 *     $sql = $expression->value();
+	 *
+	 * @return  string
+	 */
+	public function value()
+	{
+		return (string) $this->_value;
+	}
+
+	/**
+	 * Return the value of the expression as a string.
+	 *
+	 *     echo $expression;
+	 *
+	 * @return  string
+	 * @uses    Database_Expression::value
+	 */
+	public function __toString()
+	{
+		return $this->value();
+	}
+
+	/**
+	 * Compile the SQL expression and return it. Replaces any parameters with
+	 * their given values.
+	 *
+	 * @param   mixed    Database instance or name of instance
+	 * @return  string
+	 */
+	public function compile($db = NULL)
+	{
+		// Get the database instance
+		if ( ! is_object($db))
+		{
+			// Get the database instance
+			$db = Database::instance($db);
+		}
+
+		$value = $this->value();
+
+		if ( ! empty($this->_parameters))
+		{
+			// Quote all of the parameter values
+			$params = array_map(array($db, 'escape'), $this->_parameters);
+
+			// Replace the values in the expression
+			$value = strtr($value, $params);
+		}
+
+		return $value;
+	}
+
+} // End Database_Expression
diff --git a/application/libraries/Dispatch.php b/application/libraries/Dispatch.php
new file mode 100644
index 0000000..bfa78e0
--- /dev/null
+++ b/application/libraries/Dispatch.php
@@ -0,0 +1,163 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Dispatch Library
+ * Run other controllers
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Maarten Van Vliet (dlib) http://code.google.com/p/kohana-mptt/
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @port	   David Kobia <david at ushahidi.com> 
+ * @module     Dispatch Library
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Dispatch_Core {
+
+	/**
+	 * Directory in which the controller file is located
+	 * @var string
+	 */
+	protected $directory;
+
+	/**
+	 * Name of the controller
+	 * @var string
+	 */
+	protected $controller;
+	
+	public static function controller($controller, $directory)
+	{
+		$controller_file=strtolower($controller);
+		
+		// Set controller class name
+		$controller = ucfirst($controller).'_Controller';
+		
+		if(!class_exists($controller, FALSE))
+		{					
+			// If the file doesn't exist, just return
+			if (($filepath = Kohana::find_file('controllers/'.$directory, $controller_file)) === FALSE)
+					return FALSE;
+					
+			// Include the Controller file
+			require_once $filepath;
+		}
+
+		// Run system.pre_controller
+		Event::run('dispatch.pre_controller');
+		
+		// Initialize the controller
+		$controller = new $controller;
+
+		// Run system.post_controller_constructor
+		Event::run('dispatch.post_controller_constructor');				
+								
+		return new Dispatch($controller);
+	}
+
+	public function __construct(Controller $controller, $directory = NULL)
+	{
+		$this->controller=$controller;
+	}
+
+	public function __get($key)
+	{
+		return ($key=='controller')
+		    ? $this->$key
+		    : $this->controller->$key;
+	}
+
+	public function __set($key,$value)
+	{
+		$this->controller->$key=$value;
+	}
+
+	public function __toString()
+	{
+		return $this->render();
+	}
+
+	public function render()
+	{
+		return (string) $this->controller;
+	}
+
+	public function __call($name,$arguments=null)
+	{
+		if (method_exists($this->controller,$name))
+		{
+			return $this->method($name,$arguments);
+		}
+
+		return FALSE;
+	}
+
+	public function method($method,$arguments=null)
+	{
+		if ( ! method_exists($this->controller,$method))
+			return FALSE;
+				
+		if (method_exists($this->controller,'_remap'))
+		{
+			// Make the arguments routed
+			$arguments = array($method, $arguments);
+
+			// The method becomes part of the arguments
+			array_unshift($arguments, $method);
+
+			// Set the method to _remap
+			$method = '_remap';
+		}		
+						
+		ob_start();
+		
+		if (is_string($arguments))
+		{
+			$arguments=array($arguments);
+		}
+				
+		switch (count($arguments))
+		{
+			case 1:
+				$result=$this->controller->$method($arguments[0]);
+			break;
+
+			case 2:
+				$result=$this->controller->$method($arguments[0], $arguments[1]);
+			break;
+
+			case 3:
+				$result=$this->controller->$method($arguments[0],
+					$arguments[1], $arguments[2]);
+			break;
+
+			case 4:
+				$result=$this->controller->$method($arguments[0],
+					$arguments[1], $arguments[2], $arguments[3]);
+			break;
+
+			default:
+				// Resort to using call_user_func_array for many segments
+				$result=call_user_func_array(
+				    array($this->controller, $method),
+				    $arguments
+				);
+			break;
+		}				
+
+		// Run system.post_controller
+		Event::run('dispatch.post_controller');
+
+		if ($result!=NULL)
+		{
+			$result=ob_get_contents();
+			
+			ob_end_clean();			
+		}
+
+		return $result;
+	}
+}
diff --git a/application/libraries/Distance.php b/application/libraries/Distance.php
new file mode 100644
index 0000000..e0bbc5f
--- /dev/null
+++ b/application/libraries/Distance.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Distance Calculator library.
+ *
+ * Calculates the distance between two points given latitude/longitude
+ * co-ordinates of both. Returns KMs or Miles
+ * 
+ * @package    Ushahidi
+ * @category   Libraries
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class Distance_Core
+{
+	private $dist;
+	
+	/**
+	 * Create a  Distance object
+	 *
+	 * @param	string	latitude of first point
+	 * @param	string	longitude of first point
+	 * @param	string	latitude2 of second point
+	 * @param	string	longitude2 of second point
+	 * @param	bool	True if the distance is in Kms, otherwise returns Miles
+	 */
+	public function __construct($latitude = 0, $longitude = 0, $latitude2 = 0, $longitude2 = 0,$in_kms = TRUE)
+	{
+		$EARTH_RADIUS_MILES = 3963;	// Miles
+		$miles2kms = 1.609;
+		$dist = 0;
+
+		// Convert degrees to radians
+		$latitude = $latitude * M_PI / 180;
+		$longitude = $longitude * M_PI / 180;
+		$latitude2 = $latitude2 * M_PI / 180;
+		$longitude2 = $longitude2 * M_PI / 180;
+
+		if ($latitude != $latitude2 || $longitude != $longitude2) 
+		{
+			// The two points are not the same
+			$dist = 
+				sin($latitude) * sin($latitude2)
+				+ cos($latitude) * cos($latitude2)
+				* cos($longitude2 - $longitude);
+			
+			// Safety check
+			if ($dist > 0)
+			{
+				$sqrt = sqrt(1 - $dist * $dist);
+				if($sqrt > 0)
+				{
+					$dist = $EARTH_RADIUS_MILES * (-1 * atan($dist / $sqrt) + M_PI / 2);
+				}
+			}
+		}
+		
+		if ($in_kms)
+		{
+			$dist = $dist * $miles2kms;
+		}
+		
+		$this->dist = round($dist,2);
+	}
+	
+	
+	public function __get($name)
+	{
+		return $this->$name;
+    }
+
+	public function __toString()
+	{
+		return (string) $this->dist;
+	}
+
+}
\ No newline at end of file
diff --git a/application/libraries/Facebook.php b/application/libraries/Facebook.php
new file mode 100644
index 0000000..9c98ce1
--- /dev/null
+++ b/application/libraries/Facebook.php
@@ -0,0 +1,1157 @@
+<?php
+/**
+ * Copyright 2011 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+if (!function_exists('curl_init')) {
+  throw new Exception('Facebook needs the CURL PHP extension.');
+}
+if (!function_exists('json_decode')) {
+  throw new Exception('Facebook needs the JSON PHP extension.');
+}
+
+/**
+ * Extends the BaseFacebook class with the intent of using
+ * PHP sessions to store user ids and access tokens.
+ */
+class Facebook_Core extends BaseFacebook
+{
+  /**
+   * Identical to the parent constructor, except that
+   * we start a PHP session to store the user ID and
+   * access token if during the course of execution
+   * we discover them.
+   *
+   * @param Array $config the application configuration.
+   * @see BaseFacebook::__construct in facebook.php
+   */
+  public function __construct($config) {
+    if (!session_id()) {
+      session_start();
+    }
+    parent::__construct($config);
+  }
+
+  protected static $kSupportedKeys =
+    array('state', 'code', 'access_token', 'user_id');
+
+  /**
+   * Provides the implementations of the inherited abstract
+   * methods.  The implementation uses PHP sessions to maintain
+   * a store for authorization codes, user ids, CSRF states, and
+   * access tokens.
+   */
+  protected function setPersistentData($key, $value) {
+    if (!in_array($key, self::$kSupportedKeys)) {
+      self::errorLog('Unsupported key passed to setPersistentData.');
+      return;
+    }
+
+    $session_var_name = $this->constructSessionVariableName($key);
+    $_SESSION[$session_var_name] = $value;
+  }
+
+  protected function getPersistentData($key, $default = false) {
+    if (!in_array($key, self::$kSupportedKeys)) {
+      self::errorLog('Unsupported key passed to getPersistentData.');
+      return $default;
+    }
+
+    $session_var_name = $this->constructSessionVariableName($key);
+    return isset($_SESSION[$session_var_name]) ?
+      $_SESSION[$session_var_name] : $default;
+  }
+
+  protected function clearPersistentData($key) {
+    if (!in_array($key, self::$kSupportedKeys)) {
+      self::errorLog('Unsupported key passed to clearPersistentData.');
+      return;
+    }
+
+    $session_var_name = $this->constructSessionVariableName($key);
+    unset($_SESSION[$session_var_name]);
+  }
+
+  protected function clearAllPersistentData() {
+    foreach (self::$kSupportedKeys as $key) {
+      $this->clearPersistentData($key);
+    }
+  }
+
+  protected function constructSessionVariableName($key) {
+    return implode('_', array('fb',
+                              $this->getAppId(),
+                              $key));
+  }
+}
+
+
+/**
+ * Thrown when an API call returns an exception.
+ *
+ * @author Naitik Shah <naitik at facebook.com>
+ */
+class FacebookApiException extends Exception
+{
+  /**
+   * The result from the API server that represents the exception information.
+   */
+  protected $result;
+
+  /**
+   * Make a new API Exception with the given result.
+   *
+   * @param array $result The result from the API server
+   */
+  public function __construct($result) {
+    $this->result = $result;
+
+    $code = isset($result['error_code']) ? $result['error_code'] : 0;
+
+    if (isset($result['error_description'])) {
+      // OAuth 2.0 Draft 10 style
+      $msg = $result['error_description'];
+    } else if (isset($result['error']) && is_array($result['error'])) {
+      // OAuth 2.0 Draft 00 style
+      $msg = $result['error']['message'];
+    } else if (isset($result['error_msg'])) {
+      // Rest server style
+      $msg = $result['error_msg'];
+    } else {
+      $msg = 'Unknown Error. Check getResult()';
+    }
+
+    parent::__construct($msg, $code);
+  }
+
+  /**
+   * Return the associated result object returned by the API server.
+   *
+   * @return array The result from the API server
+   */
+  public function getResult() {
+    return $this->result;
+  }
+
+  /**
+   * Returns the associated type for the error. This will default to
+   * 'Exception' when a type is not available.
+   *
+   * @return string
+   */
+  public function getType() {
+    if (isset($this->result['error'])) {
+      $error = $this->result['error'];
+      if (is_string($error)) {
+        // OAuth 2.0 Draft 10 style
+        return $error;
+      } else if (is_array($error)) {
+        // OAuth 2.0 Draft 00 style
+        if (isset($error['type'])) {
+          return $error['type'];
+        }
+      }
+    }
+
+    return 'Exception';
+  }
+
+  /**
+   * To make debugging easier.
+   *
+   * @return string The string representation of the error
+   */
+  public function __toString() {
+    $str = $this->getType() . ': ';
+    if ($this->code != 0) {
+      $str .= $this->code . ': ';
+    }
+    return $str . $this->message;
+  }
+}
+
+/**
+ * Provides access to the Facebook Platform.  This class provides
+ * a majority of the functionality needed, but the class is abstract
+ * because it is designed to be sub-classed.  The subclass must
+ * implement the three abstract methods listed at the bottom of
+ * the file.
+ *
+ * @author Naitik Shah <naitik at facebook.com>
+ */
+abstract class BaseFacebook
+{
+  /**
+   * Version.
+   */
+  const VERSION = '3.0.1';
+
+  /**
+   * Default options for curl.
+   */
+  public static $CURL_OPTS = array(
+    CURLOPT_CONNECTTIMEOUT => 10,
+    CURLOPT_RETURNTRANSFER => true,
+    CURLOPT_TIMEOUT        => 60,
+    CURLOPT_USERAGENT      => 'facebook-php-3.0',
+  );
+
+  /**
+   * List of query parameters that get automatically dropped when rebuilding
+   * the current URL.
+   */
+  protected static $DROP_QUERY_PARAMS = array(
+    'code',
+    'state',
+    'signed_request',
+  );
+
+  /**
+   * Maps aliases to Facebook domains.
+   */
+  public static $DOMAIN_MAP = array(
+    'api'       => 'https://api.facebook.com/',
+    'api_video' => 'https://api-video.facebook.com/',
+    'api_read'  => 'https://api-read.facebook.com/',
+    'graph'     => 'https://graph.facebook.com/',
+    'www'       => 'https://www.facebook.com/',
+  );
+
+  /**
+   * The Application ID.
+   *
+   * @var string
+   */
+  protected $appId;
+
+  /**
+   * The Application API Secret.
+   *
+   * @var string
+   */
+  protected $apiSecret;
+
+  /**
+   * The ID of the Facebook user, or 0 if the user is logged out.
+   *
+   * @var integer
+   */
+  protected $user;
+
+  /**
+   * The data from the signed_request token.
+   */
+  protected $signedRequest;
+
+  /**
+   * A CSRF state variable to assist in the defense against CSRF attacks.
+   */
+  protected $state;
+
+  /**
+   * The OAuth access token received in exchange for a valid authorization
+   * code.  null means the access token has yet to be determined.
+   *
+   * @var string
+   */
+  protected $accessToken = null;
+
+  /**
+   * Indicates if the CURL based @ syntax for file uploads is enabled.
+   *
+   * @var boolean
+   */
+  protected $fileUploadSupport = false;
+
+  /**
+   * Initialize a Facebook Application.
+   *
+   * The configuration:
+   * - appId: the application ID
+   * - secret: the application secret
+   * - fileUpload: (optional) boolean indicating if file uploads are enabled
+   *
+   * @param array $config The application configuration
+   */
+  public function __construct($config) {
+    $this->setAppId($config['appId']);
+    $this->setApiSecret($config['secret']);
+    if (isset($config['fileUpload'])) {
+      $this->setFileUploadSupport($config['fileUpload']);
+    }
+
+    $state = $this->getPersistentData('state');
+    if (!empty($state)) {
+      $this->state = $this->getPersistentData('state');
+    }
+  }
+
+  /**
+   * Set the Application ID.
+   *
+   * @param string $appId The Application ID
+   * @return BaseFacebook
+   */
+  public function setAppId($appId) {
+    $this->appId = $appId;
+    return $this;
+  }
+
+  /**
+   * Get the Application ID.
+   *
+   * @return string the Application ID
+   */
+  public function getAppId() {
+    return $this->appId;
+  }
+
+  /**
+   * Set the API Secret.
+   *
+   * @param string $apiSecret The API Secret
+   * @return BaseFacebook
+   */
+  public function setApiSecret($apiSecret) {
+    $this->apiSecret = $apiSecret;
+    return $this;
+  }
+
+  /**
+   * Get the API Secret.
+   *
+   * @return string the API Secret
+   */
+  public function getApiSecret() {
+    return $this->apiSecret;
+  }
+
+  /**
+   * Set the file upload support status.
+   *
+   * @param boolean $fileUploadSupport The file upload support status.
+   * @return BaseFacebook
+   */
+  public function setFileUploadSupport($fileUploadSupport) {
+    $this->fileUploadSupport = $fileUploadSupport;
+    return $this;
+  }
+
+  /**
+   * Get the file upload support status.
+   *
+   * @return boolean true if and only if the server supports file upload.
+   */
+  public function useFileUploadSupport() {
+    return $this->fileUploadSupport;
+  }
+
+  /**
+   * Sets the access token for api calls.  Use this if you get
+   * your access token by other means and just want the SDK
+   * to use it.
+   *
+   * @param string $access_token an access token.
+   * @return BaseFacebook
+   */
+  public function setAccessToken($access_token) {
+    $this->accessToken = $access_token;
+    return $this;
+  }
+
+  /**
+   * Determines the access token that should be used for API calls.
+   * The first time this is called, $this->accessToken is set equal
+   * to either a valid user access token, or it's set to the application
+   * access token if a valid user access token wasn't available.  Subsequent
+   * calls return whatever the first call returned.
+   *
+   * @return string The access token
+   */
+  public function getAccessToken() {
+    if ($this->accessToken !== null) {
+      // we've done this already and cached it.  Just return.
+      return $this->accessToken;
+    }
+
+    // first establish access token to be the application
+    // access token, in case we navigate to the /oauth/access_token
+    // endpoint, where SOME access token is required.
+    $this->setAccessToken($this->getApplicationAccessToken());
+    if ($user_access_token = $this->getUserAccessToken()) {
+      $this->setAccessToken($user_access_token);
+    }
+
+    return $this->accessToken;
+  }
+
+  /**
+   * Determines and returns the user access token, first using
+   * the signed request if present, and then falling back on
+   * the authorization code if present.  The intent is to
+   * return a valid user access token, or false if one is determined
+   * to not be available.
+   *
+   * @return string A valid user access token, or false if one
+   *                could not be determined.
+   */
+  protected function getUserAccessToken() {
+    // first, consider a signed request if it's supplied.
+    // if there is a signed request, then it alone determines
+    // the access token.
+    $signed_request = $this->getSignedRequest();
+    if ($signed_request) {
+      if (array_key_exists('oauth_token', $signed_request)) {
+        $access_token = $signed_request['oauth_token'];
+        $this->setPersistentData('access_token', $access_token);
+        return $access_token;
+      }
+
+      // signed request states there's no access token, so anything
+      // stored should be cleared.
+      $this->clearAllPersistentData();
+      return false; // respect the signed request's data, even
+                    // if there's an authorization code or something else
+    }
+
+    $code = $this->getCode();
+    if ($code && $code != $this->getPersistentData('code')) {
+      $access_token = $this->getAccessTokenFromCode($code);
+      if ($access_token) {
+        $this->setPersistentData('code', $code);
+        $this->setPersistentData('access_token', $access_token);
+        return $access_token;
+      }
+
+      // code was bogus, so everything based on it should be invalidated.
+      $this->clearAllPersistentData();
+      return false;
+    }
+
+    // as a fallback, just return whatever is in the persistent
+    // store, knowing nothing explicit (signed request, authorization
+    // code, etc.) was present to shadow it (or we saw a code in $_REQUEST,
+    // but it's the same as what's in the persistent store)
+    return $this->getPersistentData('access_token');
+  }
+
+  /**
+   * Get the data from a signed_request token.
+   *
+   * @return string The base domain
+   */
+  public function getSignedRequest() {
+    if (!$this->signedRequest) {
+      if (isset($_REQUEST['signed_request'])) {
+        $this->signedRequest = $this->parseSignedRequest(
+          $_REQUEST['signed_request']);
+      }
+    }
+    return $this->signedRequest;
+  }
+
+  /**
+   * Get the UID of the connected user, or 0
+   * if the Facebook user is not connected.
+   *
+   * @return string the UID if available.
+   */
+  public function getUser() {
+    if ($this->user !== null) {
+      // we've already determined this and cached the value.
+      return $this->user;
+    }
+
+    return $this->user = $this->getUserFromAvailableData();
+  }
+
+  /**
+   * Determines the connected user by first examining any signed
+   * requests, then considering an authorization code, and then
+   * falling back to any persistent store storing the user.
+   *
+   * @return integer The id of the connected Facebook user,
+   *                 or 0 if no such user exists.
+   */
+  protected function getUserFromAvailableData() {
+    // if a signed request is supplied, then it solely determines
+    // who the user is.
+    $signed_request = $this->getSignedRequest();
+    if ($signed_request) {
+      if (array_key_exists('user_id', $signed_request)) {
+        $user = $signed_request['user_id'];
+        $this->setPersistentData('user_id', $signed_request['user_id']);
+        return $user;
+      }
+
+      // if the signed request didn't present a user id, then invalidate
+      // all entries in any persistent store.
+      $this->clearAllPersistentData();
+      return 0;
+    }
+
+    $user = $this->getPersistentData('user_id', $default = 0);
+    $persisted_access_token = $this->getPersistentData('access_token');
+
+    // use access_token to fetch user id if we have a user access_token, or if
+    // the cached access token has changed.
+    $access_token = $this->getAccessToken();
+    if ($access_token &&
+        $access_token != $this->getApplicationAccessToken() &&
+        !($user && $persisted_access_token == $access_token)) {
+      $user = $this->getUserFromAccessToken();
+      if ($user) {
+        $this->setPersistentData('user_id', $user);
+      } else {
+        $this->clearAllPersistentData();
+      }
+    }
+
+    return $user;
+  }
+
+  /**
+   * Get a Login URL for use with redirects. By default, full page redirect is
+   * assumed. If you are using the generated URL with a window.open() call in
+   * JavaScript, you can pass in display=popup as part of the $params.
+   *
+   * The parameters:
+   * - redirect_uri: the url to go to after a successful login
+   * - scope: comma separated list of requested extended perms
+   *
+   * @param array $params Provide custom parameters
+   * @return string The URL for the login flow
+   */
+  public function getLoginUrl($params=array()) {
+    $this->establishCSRFTokenState();
+    $currentUrl = $this->getCurrentUrl();
+    return $this->getUrl(
+      'www',
+      'dialog/oauth',
+      array_merge(array(
+                    'client_id' => $this->getAppId(),
+                    'redirect_uri' => $currentUrl, // possibly overwritten
+                    'state' => $this->state),
+                  $params));
+  }
+
+  /**
+   * Get a Logout URL suitable for use with redirects.
+   *
+   * The parameters:
+   * - next: the url to go to after a successful logout
+   *
+   * @param array $params Provide custom parameters
+   * @return string The URL for the logout flow
+   */
+  public function getLogoutUrl($params=array()) {
+    return $this->getUrl(
+      'www',
+      'logout.php',
+      array_merge(array(
+        'next' => $this->getCurrentUrl(),
+        'access_token' => $this->getAccessToken(),
+      ), $params)
+    );
+  }
+
+  /**
+   * Get a login status URL to fetch the status from Facebook.
+   *
+   * The parameters:
+   * - ok_session: the URL to go to if a session is found
+   * - no_session: the URL to go to if the user is not connected
+   * - no_user: the URL to go to if the user is not signed into facebook
+   *
+   * @param array $params Provide custom parameters
+   * @return string The URL for the logout flow
+   */
+  public function getLoginStatusUrl($params=array()) {
+    return $this->getUrl(
+      'www',
+      'extern/login_status.php',
+      array_merge(array(
+        'api_key' => $this->getAppId(),
+        'no_session' => $this->getCurrentUrl(),
+        'no_user' => $this->getCurrentUrl(),
+        'ok_session' => $this->getCurrentUrl(),
+        'session_version' => 3,
+      ), $params)
+    );
+  }
+
+  /**
+   * Make an API call.
+   *
+   * @return mixed The decoded response
+   */
+  public function api(/* polymorphic */) {
+    $args = func_get_args();
+    if (is_array($args[0])) {
+      return $this->_restserver($args[0]);
+    } else {
+      return call_user_func_array(array($this, '_graph'), $args);
+    }
+  }
+
+  /**
+   * Get the authorization code from the query parameters, if it exists,
+   * and otherwise return false to signal no authorization code was
+   * discoverable.
+   *
+   * @return mixed The authorization code, or false if the authorization
+   *               code could not be determined.
+   */
+  protected function getCode() {
+    if (isset($_REQUEST['code'])) {
+      if ($this->state !== null &&
+          isset($_REQUEST['state']) &&
+          $this->state === $_REQUEST['state']) {
+
+        // CSRF state has done its job, so clear it
+        $this->state = null;
+        $this->clearPersistentData('state');
+        return $_REQUEST['code'];
+      } else {
+        self::errorLog('CSRF state token does not match one provided.');
+        return false;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Retrieves the UID with the understanding that
+   * $this->accessToken has already been set and is
+   * seemingly legitimate.  It relies on Facebook's Graph API
+   * to retrieve user information and then extract
+   * the user ID.
+   *
+   * @return integer Returns the UID of the Facebook user, or 0
+   *                 if the Facebook user could not be determined.
+   */
+  protected function getUserFromAccessToken() {
+    try {
+      $user_info = $this->api('/me');
+      return $user_info['id'];
+    } catch (FacebookApiException $e) {
+      return 0;
+    }
+  }
+
+  /**
+   * Returns the access token that should be used for logged out
+   * users when no authorization code is available.
+   *
+   * @return string The application access token, useful for gathering
+   *                public information about users and applications.
+   */
+  protected function getApplicationAccessToken() {
+    return $this->appId.'|'.$this->apiSecret;
+  }
+
+  /**
+   * Lays down a CSRF state token for this process.
+   *
+   * @return void
+   */
+  protected function establishCSRFTokenState() {
+    if ($this->state === null) {
+      $this->state = md5(uniqid(mt_rand(), true));
+      $this->setPersistentData('state', $this->state);
+    }
+  }
+
+  /**
+   * Retrieves an access token for the given authorization code
+   * (previously generated from www.facebook.com on behalf of
+   * a specific user).  The authorization code is sent to graph.facebook.com
+   * and a legitimate access token is generated provided the access token
+   * and the user for which it was generated all match, and the user is
+   * either logged in to Facebook or has granted an offline access permission.
+   *
+   * @param string $code An authorization code.
+   * @return mixed An access token exchanged for the authorization code, or
+   *               false if an access token could not be generated.
+   */
+  protected function getAccessTokenFromCode($code) {
+    if (empty($code)) {
+      return false;
+    }
+
+    try {
+      // need to circumvent json_decode by calling _oauthRequest
+      // directly, since response isn't JSON format.
+      $access_token_response =
+        $this->_oauthRequest(
+          $this->getUrl('graph', '/oauth/access_token'),
+          $params = array('client_id' => $this->getAppId(),
+                          'client_secret' => $this->getApiSecret(),
+                          'redirect_uri' => $this->getCurrentUrl(),
+                          'code' => $code));
+    } catch (FacebookApiException $e) {
+      // most likely that user very recently revoked authorization.
+      // In any event, we don't have an access token, so say so.
+      return false;
+    }
+
+    if (empty($access_token_response)) {
+      return false;
+    }
+
+    $response_params = array();
+    parse_str($access_token_response, $response_params);
+    if (!isset($response_params['access_token'])) {
+      return false;
+    }
+
+    return $response_params['access_token'];
+  }
+
+  /**
+   * Invoke the old restserver.php endpoint.
+   *
+   * @param array $params Method call object
+   *
+   * @return mixed The decoded response object
+   * @throws FacebookApiException
+   */
+  protected function _restserver($params) {
+    // generic application level parameters
+    $params['api_key'] = $this->getAppId();
+    $params['format'] = 'json-strings';
+
+    $result = json_decode($this->_oauthRequest(
+      $this->getApiUrl($params['method']),
+      $params
+    ), true);
+
+    // results are returned, errors are thrown
+    if (is_array($result) && isset($result['error_code'])) {
+      throw new FacebookApiException($result);
+    }
+
+    return $result;
+  }
+
+  /**
+   * Invoke the Graph API.
+   *
+   * @param string $path The path (required)
+   * @param string $method The http method (default 'GET')
+   * @param array $params The query/post data
+   *
+   * @return mixed The decoded response object
+   * @throws FacebookApiException
+   */
+  protected function _graph($path, $method = 'GET', $params = array()) {
+    if (is_array($method) && empty($params)) {
+      $params = $method;
+      $method = 'GET';
+    }
+    $params['method'] = $method; // method override as we always do a POST
+
+    $result = json_decode($this->_oauthRequest(
+      $this->getUrl('graph', $path),
+      $params
+    ), true);
+
+    // results are returned, errors are thrown
+    if (is_array($result) && isset($result['error'])) {
+      $this->throwAPIException($result);
+    }
+
+    return $result;
+  }
+
+  /**
+   * Make a OAuth Request.
+   *
+   * @param string $url The path (required)
+   * @param array $params The query/post data
+   *
+   * @return string The decoded response object
+   * @throws FacebookApiException
+   */
+  protected function _oauthRequest($url, $params) {
+    if (!isset($params['access_token'])) {
+      $params['access_token'] = $this->getAccessToken();
+    }
+
+    // json_encode all params values that are not strings
+    foreach ($params as $key => $value) {
+      if (!is_string($value)) {
+        $params[$key] = json_encode($value);
+      }
+    }
+
+    return $this->makeRequest($url, $params);
+  }
+
+  /**
+   * Makes an HTTP request. This method can be overridden by subclasses if
+   * developers want to do fancier things or use something other than curl to
+   * make the request.
+   *
+   * @param string $url The URL to make the request to
+   * @param array $params The parameters to use for the POST body
+   * @param CurlHandler $ch Initialized curl handle
+   *
+   * @return string The response text
+   */
+  protected function makeRequest($url, $params, $ch=null) {
+    if (!$ch) {
+      $ch = curl_init();
+    }
+
+    $opts = self::$CURL_OPTS;
+    if ($this->useFileUploadSupport()) {
+      $opts[CURLOPT_POSTFIELDS] = $params;
+    } else {
+      $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&');
+    }
+    $opts[CURLOPT_URL] = $url;
+
+    // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait
+    // for 2 seconds if the server does not support this header.
+    if (isset($opts[CURLOPT_HTTPHEADER])) {
+      $existing_headers = $opts[CURLOPT_HTTPHEADER];
+      $existing_headers[] = 'Expect:';
+      $opts[CURLOPT_HTTPHEADER] = $existing_headers;
+    } else {
+      $opts[CURLOPT_HTTPHEADER] = array('Expect:');
+    }
+
+    curl_setopt_array($ch, $opts);
+    $result = curl_exec($ch);
+
+    if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT
+      self::errorLog('Invalid or no certificate authority found, '.
+                     'using bundled information');
+      curl_setopt($ch, CURLOPT_CAINFO,
+                  dirname(__FILE__) . '/fb_ca_chain_bundle.crt');
+      $result = curl_exec($ch);
+    }
+
+    if ($result === false) {
+      $e = new FacebookApiException(array(
+        'error_code' => curl_errno($ch),
+        'error' => array(
+        'message' => curl_error($ch),
+        'type' => 'CurlException',
+        ),
+      ));
+      curl_close($ch);
+      throw $e;
+    }
+    curl_close($ch);
+    return $result;
+  }
+
+  /**
+   * Parses a signed_request and validates the signature.
+   *
+   * @param string $signed_request A signed token
+   * @return array The payload inside it or null if the sig is wrong
+   */
+  protected function parseSignedRequest($signed_request) {
+    list($encoded_sig, $payload) = explode('.', $signed_request, 2);
+
+    // decode the data
+    $sig = self::base64UrlDecode($encoded_sig);
+    $data = json_decode(self::base64UrlDecode($payload), true);
+
+    if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') {
+      self::errorLog('Unknown algorithm. Expected HMAC-SHA256');
+      return null;
+    }
+
+    // check sig
+    $expected_sig = hash_hmac('sha256', $payload,
+                              $this->getApiSecret(), $raw = true);
+    if ($sig !== $expected_sig) {
+      self::errorLog('Bad Signed JSON signature!');
+      return null;
+    }
+
+    return $data;
+  }
+
+  /**
+   * Build the URL for api given parameters.
+   *
+   * @param $method String the method name.
+   * @return string The URL for the given parameters
+   */
+  protected function getApiUrl($method) {
+    static $READ_ONLY_CALLS =
+      array('admin.getallocation' => 1,
+            'admin.getappproperties' => 1,
+            'admin.getbannedusers' => 1,
+            'admin.getlivestreamvialink' => 1,
+            'admin.getmetrics' => 1,
+            'admin.getrestrictioninfo' => 1,
+            'application.getpublicinfo' => 1,
+            'auth.getapppublickey' => 1,
+            'auth.getsession' => 1,
+            'auth.getsignedpublicsessiondata' => 1,
+            'comments.get' => 1,
+            'connect.getunconnectedfriendscount' => 1,
+            'dashboard.getactivity' => 1,
+            'dashboard.getcount' => 1,
+            'dashboard.getglobalnews' => 1,
+            'dashboard.getnews' => 1,
+            'dashboard.multigetcount' => 1,
+            'dashboard.multigetnews' => 1,
+            'data.getcookies' => 1,
+            'events.get' => 1,
+            'events.getmembers' => 1,
+            'fbml.getcustomtags' => 1,
+            'feed.getappfriendstories' => 1,
+            'feed.getregisteredtemplatebundlebyid' => 1,
+            'feed.getregisteredtemplatebundles' => 1,
+            'fql.multiquery' => 1,
+            'fql.query' => 1,
+            'friends.arefriends' => 1,
+            'friends.get' => 1,
+            'friends.getappusers' => 1,
+            'friends.getlists' => 1,
+            'friends.getmutualfriends' => 1,
+            'gifts.get' => 1,
+            'groups.get' => 1,
+            'groups.getmembers' => 1,
+            'intl.gettranslations' => 1,
+            'links.get' => 1,
+            'notes.get' => 1,
+            'notifications.get' => 1,
+            'pages.getinfo' => 1,
+            'pages.isadmin' => 1,
+            'pages.isappadded' => 1,
+            'pages.isfan' => 1,
+            'permissions.checkavailableapiaccess' => 1,
+            'permissions.checkgrantedapiaccess' => 1,
+            'photos.get' => 1,
+            'photos.getalbums' => 1,
+            'photos.gettags' => 1,
+            'profile.getinfo' => 1,
+            'profile.getinfooptions' => 1,
+            'stream.get' => 1,
+            'stream.getcomments' => 1,
+            'stream.getfilters' => 1,
+            'users.getinfo' => 1,
+            'users.getloggedinuser' => 1,
+            'users.getstandardinfo' => 1,
+            'users.hasapppermission' => 1,
+            'users.isappuser' => 1,
+            'users.isverified' => 1,
+            'video.getuploadlimits' => 1);
+    $name = 'api';
+    if (isset($READ_ONLY_CALLS[strtolower($method)])) {
+      $name = 'api_read';
+    } else if (strtolower($method) == 'video.upload') {
+      $name = 'api_video';
+    }
+    return self::getUrl($name, 'restserver.php');
+  }
+
+  /**
+   * Build the URL for given domain alias, path and parameters.
+   *
+   * @param $name string The name of the domain
+   * @param $path string Optional path (without a leading slash)
+   * @param $params array Optional query parameters
+   *
+   * @return string The URL for the given parameters
+   */
+  protected function getUrl($name, $path='', $params=array()) {
+    $url = self::$DOMAIN_MAP[$name];
+    if ($path) {
+      if ($path[0] === '/') {
+        $path = substr($path, 1);
+      }
+      $url .= $path;
+    }
+    if ($params) {
+      $url .= '?' . http_build_query($params, null, '&');
+    }
+
+    return $url;
+  }
+
+  /**
+   * Returns the Current URL, stripping it of known FB parameters that should
+   * not persist.
+   *
+   * @return string The current URL
+   */
+  protected function getCurrentUrl() {
+    $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'
+      ? 'https://'
+      : 'http://';
+    $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
+    $parts = parse_url($currentUrl);
+
+    $query = '';
+    if (!empty($parts['query'])) {
+      // drop known fb params
+      $params = explode('&', $parts['query']);
+      $retained_params = array();
+      foreach ($params as $param) {
+        if ($this->shouldRetainParam($param)) {
+          $retained_params[] = $param;
+        }
+      }
+
+      if (!empty($retained_params)) {
+        $query = '?'.implode($retained_params, '&');
+      }
+    }
+
+    // use port if non default
+    $port =
+      isset($parts['port']) &&
+      (($protocol === 'http://' && $parts['port'] !== 80) ||
+       ($protocol === 'https://' && $parts['port'] !== 443))
+      ? ':' . $parts['port'] : '';
+
+    // rebuild
+    return $protocol . $parts['host'] . $port . $parts['path'] . $query;
+  }
+
+  /**
+   * Returns true if and only if the key or key/value pair should
+   * be retained as part of the query string.  This amounts to
+   * a brute-force search of the very small list of Facebook-specific
+   * params that should be stripped out.
+   *
+   * @param string $param A key or key/value pair within a URL's query (e.g.
+   *                     'foo=a', 'foo=', or 'foo'.
+   *
+   * @return boolean
+   */
+  protected function shouldRetainParam($param) {
+    foreach (self::$DROP_QUERY_PARAMS as $drop_query_param) {
+      if (strpos($param, $drop_query_param.'=') === 0) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Analyzes the supplied result to see if it was thrown
+   * because the access token is no longer valid.  If that is
+   * the case, then the persistent store is cleared.
+   *
+   * @param $result array A record storing the error message returned
+   *                      by a failed API call.
+   */
+  protected function throwAPIException($result) {
+    $e = new FacebookApiException($result);
+    switch ($e->getType()) {
+      // OAuth 2.0 Draft 00 style
+      case 'OAuthException':
+        // OAuth 2.0 Draft 10 style
+      case 'invalid_token':
+        $message = $e->getMessage();
+      if ((strpos($message, 'Error validating access token') !== false) ||
+          (strpos($message, 'Invalid OAuth access token') !== false)) {
+        $this->setAccessToken(null);
+        $this->user = 0;
+        $this->clearAllPersistentData();
+      }
+    }
+
+    throw $e;
+  }
+
+
+  /**
+   * Prints to the error log if you aren't in command line mode.
+   *
+   * @param string $msg Log message
+   */
+  protected static function errorLog($msg) {
+    // disable error log if we are running in a CLI environment
+    // @codeCoverageIgnoreStart
+    if (php_sapi_name() != 'cli') {
+      error_log($msg);
+    }
+    // uncomment this if you want to see the errors on the page
+    // print 'error_log: '.$msg."\n";
+    // @codeCoverageIgnoreEnd
+  }
+
+  /**
+   * Base64 encoding that doesn't need to be urlencode()ed.
+   * Exactly the same as base64_encode except it uses
+   *   - instead of +
+   *   _ instead of /
+   *
+   * @param string $input base64UrlEncoded string
+   * @return string
+   */
+  protected static function base64UrlDecode($input) {
+    return base64_decode(strtr($input, '-_', '+/'));
+  }
+
+  /**
+   * Each of the following four methods should be overridden in
+   * a concrete subclass, as they are in the provided Facebook class.
+   * The Facebook class uses PHP sessions to provide a primitive
+   * persistent store, but another subclass--one that you implement--
+   * might use a database, memcache, or an in-memory cache.
+   *
+   * @see Facebook
+   */
+
+  /**
+   * Stores the given ($key, $value) pair, so that future calls to
+   * getPersistentData($key) return $value. This call may be in another request.
+   *
+   * @param string $key
+   * @param array $value
+   *
+   * @return void
+   */
+  abstract protected function setPersistentData($key, $value);
+
+  /**
+   * Get the data for $key, persisted by BaseFacebook::setPersistentData()
+   *
+   * @param string $key The key of the data to retrieve
+   * @param boolean $default The default value to return if $key is not found
+   *
+   * @return mixed
+   */
+  abstract protected function getPersistentData($key, $default = false);
+
+  /**
+   * Clear the data with $key from the persistent storage
+   *
+   * @param string $key
+   * @return void
+   */
+  abstract protected function clearPersistentData($key);
+
+  /**
+   * Clear all data from the persistent storage
+   *
+   * @return void
+   */
+  abstract protected function clearAllPersistentData();
+}
diff --git a/application/libraries/Geocoder.php b/application/libraries/Geocoder.php
new file mode 100644
index 0000000..a3cb6ab
--- /dev/null
+++ b/application/libraries/Geocoder.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * GeoCoder Library
+ * Uses a variety of methods to geocode locations and feeds
+ *
+ * @package    GeoCoder
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+
+define("GEOCODER_GOOGLE", "maps.google.com");
+define("GEOCODER_GEONAMES", "ws.geonames.org");
+
+class Geocoder_Core {
+
+	/**
+	 * Google Location GeoCoding
+	 *
+	 * Reuses map::geocode() rather than reimplementing.
+	 * Only really keeping this for backwards compat
+	 *
+	 * @param   string location / address
+	 * @return  array (longitude, latitude)
+	 */
+	function geocode_location ($address = NULL)
+	{
+		$result = map::geocode($address);
+		if ($result)
+		{
+			return array($result['longitude'], $result['latitude'], $result['country_id']);
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+
+	/**
+	 * Geonames Feeds GeoCoding (RSS to GEORSS)
+	 * Due to limitations, this returns only 20 items
+	 *
+	 * @param   string location / address
+	 * @return  string raw georss data
+	 */
+	function geocode_feed ($feed_url = NULL)
+	{
+		$base_url = "http://" . GEOCODER_GEONAMES . "/rssToGeoRSS?";
+
+		// Only requests service if we have an user
+		$geocode_username = Settings_Model::get_setting('feed_geolocation_user');
+
+		if ($feed_url && !empty($geocode_username))
+		{
+			// First check to make sure geonames webservice is running
+			$geonames_status = @remote::status( $base_url );
+
+			if ($geonames_status == "200")
+			{ // Successful
+				$request_url = $base_url . "&feedUrl=" . urlencode($feed_url) . "&username=" . $geocode_username;
+			}
+			else
+			{ // Down perhaps?? Use direct feed
+				$request_url = $feed_url;
+			}
+
+			$request = new HttpClient($request_url);
+
+			if ( ! ($georss = $request->execute($request_url)))
+			{
+				// If the request failed, something may be wrong with the GEOCODER_GEONAMES service
+				return false;
+			}
+			//$georss = utf8_encode($georss);
+
+			// Lez verify this we got a good reply from the geocoder before proceeding
+			$data = new SimplePie();
+			$data->set_raw_data( $georss );
+			$data->init();
+			$data->handle_content_type();
+
+			// Feed no good - get outta here!
+			if ($data->error()) {
+				Kohana::log('error', $data->error() . $request_url);
+
+				return false;
+			}
+
+			return trim($georss);
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+}
diff --git a/application/libraries/Html2Text.php b/application/libraries/Html2Text.php
new file mode 100644
index 0000000..1f0becc
--- /dev/null
+++ b/application/libraries/Html2Text.php
@@ -0,0 +1,487 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/*************************************************************************
+ *                                                                       *
+ * class.html2text.inc                                                   *
+ *                                                                       *
+ *************************************************************************
+ *                                                                       *
+ * Converts HTML to formatted plain text                                 *
+ *                                                                       *
+ * Copyright (c) 2005-2007 Jon Abernathy <jon at chuggnutt.com>             *
+ * All rights reserved.                                                  *
+ *                                                                       *
+ * This script is free software; you can redistribute it and/or modify   *
+ * it under the terms of the GNU General Public License as published by  *
+ * the Free Software Foundation; either version 2 of the License, or     *
+ * (at your option) any later version.                                   *
+ *                                                                       *
+ * The GNU General Public License can be found at                        *
+ * http://www.gnu.org/copyleft/gpl.html.                                 *
+ *                                                                       *
+ * This script is distributed in the hope that it will be useful,        *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
+ * GNU General Public License for more details.                          *
+ *                                                                       *
+ * Author(s): Jon Abernathy <jon at chuggnutt.com>                          *
+ *                                                                       *
+ * Last modified: 08/08/07                                               *
+ *                                                                       *
+ *************************************************************************/
+
+
+/**
+ *  Takes HTML and converts it to formatted, plain text.
+ *
+ *  Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
+ *  correcting an error in the regexp search array. Fixed 7/30/03.
+ *
+ *  Updated set_html() function's file reading mechanism, 9/25/03.
+ *
+ *  Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
+ *  several more HTML entity codes to the $search and $replace arrays.
+ *  Updated 11/7/03.
+ *
+ *  Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
+ *  suggesting the addition of $allowed_tags and its supporting function
+ *  (which I slightly modified). Updated 3/12/04.
+ *
+ *  Thanks to Justin Dearing for pointing out that a replacement for the
+ *  <TH> tag was missing, and suggesting an appropriate fix.
+ *  Updated 8/25/04.
+ *
+ *  Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
+ *  display/formatting bug in the _build_link_list() function: email
+ *  readers would show the left bracket and number ("[1") as part of the
+ *  rendered email address.
+ *  Updated 12/16/04.
+ *
+ *  Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
+ *  to handle relative links, which I hadn't considered. I modified his
+ *  code a bit to handle normal HTTP links and MAILTO links. Also for
+ *  suggesting three additional HTML entity codes to search for.
+ *  Updated 03/02/05.
+ *
+ *  Thanks to Jacob Chandler for pointing out another link condition
+ *  for the _build_link_list() function: "https".
+ *  Updated 04/06/05.
+ *
+ *  Thanks to Marc Bertrand (http://www.dresdensky.com/) for
+ *  suggesting a revision to the word wrapping functionality; if you
+ *  specify a $width of 0 or less, word wrapping will be ignored.
+ *  Updated 11/02/06.
+ *
+ *  *** Big housecleaning updates below:
+ *
+ *  Thanks to Colin Brown (http://www.sparkdriver.co.uk/) for
+ *  suggesting the fix to handle </li> and blank lines (whitespace).
+ *  Christian Basedau (http://www.movetheweb.de/) also suggested the
+ *  blank lines fix.
+ *
+ *  Special thanks to Marcus Bointon (http://www.synchromedia.co.uk/),
+ *  Christian Basedau, Norbert Laposa (http://ln5.co.uk/),
+ *  Bas van de Weijer, and Marijn van Butselaar
+ *  for pointing out my glaring error in the <th> handling. Marcus also
+ *  supplied a host of fixes.
+ *
+ *  Thanks to Jeffrey Silverman (http://www.newtnotes.com/) for pointing
+ *  out that extra spaces should be compressed--a problem addressed with
+ *  Marcus Bointon's fixes but that I had not yet incorporated.
+ *
+ *	Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
+ *  suggesting a valuable fix with <a> tag handling.
+ *
+ *  Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
+ *  including the <a> tag handling that Daniel Schledermann pointed
+ *  out but that I had not yet incorporated. I haven't (yet)
+ *  incorporated all of Wojciech's changes, though I may at some
+ *  future time.
+ *
+ *  *** End of the housecleaning updates. Updated 08/08/07.
+ *
+ *  @author Jon Abernathy <jon at chuggnutt.com>
+ *  @version 1.0.0
+ *  @since PHP 4.0.2
+ */
+class Html2Text
+{
+
+    /**
+     *  Contains the HTML content to convert.
+     *
+     *  @var string $html
+     *  @access public
+     */
+    var $html;
+
+    /**
+     *  Contains the converted, formatted text.
+     *
+     *  @var string $text
+     *  @access public
+     */
+    var $text;
+
+    /**
+     *  Maximum width of the formatted text, in columns.
+     *
+     *  Set this value to 0 (or less) to ignore word wrapping
+     *  and not constrain text to a fixed-width column.
+     *
+     *  @var integer $width
+     *  @access public
+     */
+    var $width = 70;
+
+    /**
+     *  List of preg* regular expression patterns to search for,
+     *  used in conjunction with $replace.
+     *
+     *  @var array $search
+     *  @access public
+     *  @see $replace
+     */
+    var $search = array(
+        "/\r/",                                  // Non-legal carriage return
+        "/[\n\t]+/",                             // Newlines and tabs
+        '/[ ]{2,}/',                             // Runs of spaces, pre-handling
+        '/<script[^>]*>.*?<\/script>/i',         // <script>s -- which strip_tags supposedly has problems with
+        '/<style[^>]*>.*?<\/style>/i',           // <style>s -- which strip_tags supposedly has problems with
+        //'/<!-- .* -->/',                         // Comments -- which strip_tags might have problem a with
+        '/<h[123][^>]*>(.*?)<\/h[123]>/ie',      // H1 - H3
+        '/<h[456][^>]*>(.*?)<\/h[456]>/ie',      // H4 - H6
+        '/<p[^>]*>/i',                           // <P>
+        '/<br[^>]*>/i',                          // <br>
+        '/<b[^>]*>(.*?)<\/b>/ie',                // <b>
+        '/<strong[^>]*>(.*?)<\/strong>/ie',      // <strong>
+        '/<i[^>]*>(.*?)<\/i>/i',                 // <i>
+        '/<em[^>]*>(.*?)<\/em>/i',               // <em>
+        '/(<ul[^>]*>|<\/ul>)/i',                 // <ul> and </ul>
+        '/(<ol[^>]*>|<\/ol>)/i',                 // <ol> and </ol>
+        '/<li[^>]*>(.*?)<\/li>/i',               // <li> and </li>
+        '/<li[^>]*>/i',                          // <li>
+        '/<a [^>]*href="([^"]+)"[^>]*>(.*?)<\/a>/ie',
+                                                 // <a href="">
+        '/<hr[^>]*>/i',                          // <hr>
+        '/(<table[^>]*>|<\/table>)/i',           // <table> and </table>
+        '/(<tr[^>]*>|<\/tr>)/i',                 // <tr> and </tr>
+        '/<td[^>]*>(.*?)<\/td>/i',               // <td> and </td>
+        '/<th[^>]*>(.*?)<\/th>/ie',              // <th> and </th>
+        '/&(nbsp|#160);/i',                      // Non-breaking space
+        '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
+		                                         // Double quotes
+        '/&(apos|rsquo|lsquo|#8216|#8217);/i',   // Single quotes
+        '/>/i',                               // Greater-than
+        '/</i',                               // Less-than
+        '/&(amp|#38);/i',                        // Ampersand
+        '/&(copy|#169);/i',                      // Copyright
+        '/&(trade|#8482|#153);/i',               // Trademark
+        '/&(reg|#174);/i',                       // Registered
+        '/&(mdash|#151|#8212);/i',               // mdash
+        '/&(ndash|minus|#8211|#8722);/i',        // ndash
+        '/&(bull|#149|#8226);/i',                // Bullet
+        '/&(pound|#163);/i',                     // Pound sign
+        '/&(euro|#8364);/i',                     // Euro sign
+        '/&[^&;]+;/i',                           // Unknown/unhandled entities
+        '/[ ]{2,}/'                              // Runs of spaces, post-handling
+    );
+
+    /**
+     *  List of pattern replacements corresponding to patterns searched.
+     *
+     *  @var array $replace
+     *  @access public
+     *  @see $search
+     */
+    var $replace = array(
+        '',                                     // Non-legal carriage return
+        ' ',                                    // Newlines and tabs
+        ' ',                                    // Runs of spaces, pre-handling
+        '',                                     // <script>s -- which strip_tags supposedly has problems with
+        '',                                     // <style>s -- which strip_tags supposedly has problems with
+        //'',                                     // Comments -- which strip_tags might have problem a with
+        "strtoupper(\"\n\n\\1\n\n\")",          // H1 - H3
+        "ucwords(\"\n\n\\1\n\n\")",             // H4 - H6
+        "\n\n\t",                               // <P>
+        "\n",                                   // <br>
+        'strtoupper("\\1")',                    // <b>
+        'strtoupper("\\1")',                    // <strong>
+        '_\\1_',                                // <i>
+        '_\\1_',                                // <em>
+        "\n\n",                                 // <ul> and </ul>
+        "\n\n",                                 // <ol> and </ol>
+        "\t* \\1\n",                            // <li> and </li>
+        "\n\t* ",                               // <li>
+        '$this->_build_link_list("\\1", "\\2")',
+                                                // <a href="">
+        "\n-------------------------\n",        // <hr>
+        "\n\n",                                 // <table> and </table>
+        "\n",                                   // <tr> and </tr>
+        "\t\t\\1\n",                            // <td> and </td>
+        "strtoupper(\"\t\t\\1\n\")",            // <th> and </th>
+        ' ',                                    // Non-breaking space
+        '"',                                    // Double quotes
+        "'",                                    // Single quotes
+        '>',
+        '<',
+        '&',
+        '(c)',
+        '(tm)',
+        '(R)',
+        '--',
+        '-',
+        '*',
+        '£',
+        'EUR',                                  // Euro sign. Ä ?
+        '',                                     // Unknown/unhandled entities
+        ' '                                     // Runs of spaces, post-handling
+    );
+
+    /**
+     *  Contains a list of HTML tags to allow in the resulting text.
+     *
+     *  @var string $allowed_tags
+     *  @access public
+     *  @see set_allowed_tags()
+     */
+    var $allowed_tags = '';
+
+    /**
+     *  Contains the base URL that relative links should resolve to.
+     *
+     *  @var string $url
+     *  @access public
+     */
+    var $url;
+
+    /**
+     *  Indicates whether content in the $html variable has been converted yet.
+     *
+     *  @var boolean $_converted
+     *  @access private
+     *  @see $html, $text
+     */
+    var $_converted = false;
+
+    /**
+     *  Contains URL addresses from links to be rendered in plain text.
+     *
+     *  @var string $_link_list
+     *  @access private
+     *  @see _build_link_list()
+     */
+    var $_link_list = '';
+    
+    /**
+     *  Number of valid links detected in the text, used for plain text
+     *  display (rendered similar to footnotes).
+     *
+     *  @var integer $_link_count
+     *  @access private
+     *  @see _build_link_list()
+     */
+    var $_link_count = 0;
+
+    /**
+     *  Constructor.
+     *
+     *  If the HTML source string (or file) is supplied, the class
+     *  will instantiate with that source propagated, all that has
+     *  to be done it to call get_text().
+     *
+     *  @param string $source HTML content
+     *  @param boolean $from_file Indicates $source is a file to pull content from
+     *  @access public
+     *  @return void
+     */
+    function html2text( $source = '', $from_file = false )
+    {
+        if ( !empty($source) ) {
+            $this->set_html($source, $from_file);
+        }
+        $this->set_base_url();
+    }
+
+    /**
+     *  Loads source HTML into memory, either from $source string or a file.
+     *
+     *  @param string $source HTML content
+     *  @param boolean $from_file Indicates $source is a file to pull content from
+     *  @access public
+     *  @return void
+     */
+    function set_html( $source, $from_file = false )
+    {
+        $this->html = $source;
+
+        if ( $from_file && file_exists($source) ) {
+            $fp = fopen($source, 'r');
+            $this->html = fread($fp, filesize($source));
+            fclose($fp);
+        }
+
+        $this->_converted = false;
+    }
+
+    /**
+     *  Returns the text, converted from HTML.
+     *
+     *  @access public
+     *  @return string
+     */
+    function get_text()
+    {
+        if ( !$this->_converted ) {
+            $this->_convert();
+        }
+
+        return $this->text;
+    }
+
+    /**
+     *  Prints the text, converted from HTML.
+     *
+     *  @access public
+     *  @return void
+     */
+    function print_text()
+    {
+        print $this->get_text();
+    }
+
+    /**
+     *  Alias to print_text(), operates identically.
+     *
+     *  @access public
+     *  @return void
+     *  @see print_text()
+     */
+    function p()
+    {
+        print $this->get_text();
+    }
+
+    /**
+     *  Sets the allowed HTML tags to pass through to the resulting text.
+     *
+     *  Tags should be in the form "<p>", with no corresponding closing tag.
+     *
+     *  @access public
+     *  @return void
+     */
+    function set_allowed_tags( $allowed_tags = '' )
+    {
+        if ( !empty($allowed_tags) ) {
+            $this->allowed_tags = $allowed_tags;
+        }
+    }
+
+    /**
+     *  Sets a base URL to handle relative links.
+     *
+     *  @access public
+     *  @return void
+     */
+    function set_base_url( $url = '' )
+    {
+        if ( empty($url) ) {
+        	if ( !empty($_SERVER['HTTP_HOST']) ) {
+	            $this->url = Kohana::config('core.site_protocol').'://' . $_SERVER['HTTP_HOST'];
+        	} else {
+	            $this->url = '';
+	        }
+        } else {
+            // Strip any trailing slashes for consistency (relative
+            // URLs may already start with a slash like "/file.html")
+            if ( substr($url, -1) == '/' ) {
+                $url = substr($url, 0, -1);
+            }
+            $this->url = $url;
+        }
+    }
+
+    /**
+     *  Workhorse function that does actual conversion.
+     *
+     *  First performs custom tag replacement specified by $search and
+     *  $replace arrays. Then strips any remaining HTML tags, reduces whitespace
+     *  and newlines to a readable format, and word wraps the text to
+     *  $width characters.
+     *
+     *  @access private
+     *  @return void
+     */
+    function _convert()
+    {
+        // Variables used for building the link list
+        $this->_link_count = 0;
+        $this->_link_list = '';
+
+        $text = trim(stripslashes($this->html));
+
+        // Run our defined search-and-replace
+        $text = preg_replace($this->search, $this->replace, $text);
+
+        // Strip any other HTML tags
+        $text = strip_tags($text, $this->allowed_tags);
+
+        // Bring down number of empty lines to 2 max
+        $text = preg_replace("/\n\s+\n/", "\n\n", $text);
+        $text = preg_replace("/[\n]{3,}/", "\n\n", $text);
+
+        // Add link list
+        if ( !empty($this->_link_list) ) {
+            $text .= "\n\nLinks:\n------\n" . $this->_link_list;
+        }
+
+        // Wrap the text to a readable format
+        // for PHP versions >= 4.0.2. Default width is 75
+        // If width is 0 or less, don't wrap the text.
+        if ( $this->width > 0 ) {
+        	$text = wordwrap($text, $this->width);
+        }
+
+        $this->text = $text;
+
+        $this->_converted = true;
+    }
+
+    /**
+     *  Helper function called by preg_replace() on link replacement.
+     *
+     *  Maintains an internal list of links to be displayed at the end of the
+     *  text, with numeric indices to the original point in the text they
+     *  appeared. Also makes an effort at identifying and handling absolute
+     *  and relative links.
+     *
+     *  @param string $link URL of the link
+     *  @param string $display Part of the text to associate number with
+     *  @access private
+     *  @return string
+     */
+    function _build_link_list( $link, $display )
+    {
+		if ( substr($link, 0, 7) == 'http://' || substr($link, 0, 8) == 'https://' ||
+             substr($link, 0, 7) == 'mailto:' ) {
+            $this->_link_count++;
+            $this->_link_list .= "[" . $this->_link_count . "] $link\n";
+            $additional = ' [' . $this->_link_count . ']';
+		} elseif ( substr($link, 0, 11) == 'javascript:' ) {
+			// Don't count the link; ignore it
+			$additional = '';
+		// what about href="#anchor" ?
+        } else {
+            $this->_link_count++;
+            $this->_link_list .= "[" . $this->_link_count . "] " . $this->url;
+            if ( substr($link, 0, 1) != '/' ) {
+                $this->_link_list .= '/';
+            }
+            $this->_link_list .= "$link\n";
+            $additional = ' [' . $this->_link_count . ']';
+        }
+
+        return $display . $additional;
+    }
+
+}
\ No newline at end of file
diff --git a/application/libraries/HttpClient.php b/application/libraries/HttpClient.php
new file mode 100644
index 0000000..bdd6e18
--- /dev/null
+++ b/application/libraries/HttpClient.php
@@ -0,0 +1,218 @@
+<?php
+/**
+ * HTTP client Implementation based on php-curl
+ *
+ * @author Henry Addo <henry at addhen.org>
+ * @version 1.0
+ * @package HttpClient
+ * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class HttpClient_Core 
+{
+
+	/**
+	 * Curl handler
+	 *
+	 * @access private
+	 * @var resource
+	 */
+	private $ch;
+	/**
+	 * Ability to turn debugging info for useful info when
+	 * debugging
+	 *
+	 * @access private
+	 * @var string
+	 */
+	private $debug;
+
+	/**
+	 * Holds error messages if error occurs
+	 *
+	 * @access private
+	 * @var string
+	 */
+	private $error_msg;
+
+	/**
+	 * The Ushahidi URL
+	 *
+	 * @access private
+	 * @var string
+	 */
+	private $url;
+
+	/**
+	 * Timeout to do a request that is taking forever.
+	 *
+	 * @access private
+	 * @var int
+	 */
+	private $timeout;
+
+	/**
+	 * Holds response code in case someone is interested in it
+	 *
+	 * @access private
+	 * @var int
+	 */
+	private $response_code;
+
+
+	public function __construct($url, $timeout=20)
+	{
+		$this->url = $url;
+		$this->timeout = $timeout;
+		$this->init_curl();
+	}
+
+	/**
+	 * Initialize a curl session
+	 *
+	 * @access private
+	 */
+	private function init_curl()
+	{
+        // Make sure cURL is installed
+        if ( ! function_exists('curl_exec'))
+        {
+            throw new Kohana_Exception('HttpClient - cURL_not_installed');
+            return FALSE;
+        }
+
+		//initial curl handle
+		$this->ch = curl_init();
+		// set curl's various options
+
+		//set error in case http return code bigger than 300
+		curl_setopt($this->ch, CURLOPT_FAILONERROR, TRUE);
+
+		// allow redirects just incase a user wants that
+		curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, TRUE);
+
+		// use gzip if possible for performance
+		curl_setopt($this->ch, CURLOPT_ENCODING , 'gzip, deflate');
+
+		// do not veryfy ssl for
+		// as well for being able to access pages with non valid cert
+		curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, FALSE);
+		curl_setopt($this->ch, CURLOPT_SSL_VERIFYHOST,  2);
+	}
+
+	/**
+	 * Set client's user agent
+	 *
+	 * @access private
+	 * @param string useragent
+	 */
+	public function set_useragent($useragent)
+	{
+		curl_setopt($this->ch, CURLOPT_USERAGENT, $useragent);
+	}
+
+	/**
+	 * Get http response code
+	 *
+	 * @access private
+	 * @return int
+	 */
+	public function get_http_response_code()
+	{
+		return $this->response_code;
+	}
+
+	/**
+	 * Set error message that might show up
+	 *
+	 * @access protected
+	 * @param string error_msg - The error message
+	 */
+	public function get_error_msg()
+	{
+		return $this->error_msg;
+	}
+
+	/**
+	 * Return last error message and error number
+	 *
+	 * @access private
+	 * @return string - Error msg
+	 */
+	public function set_error_msg()
+	{
+		$err = "Error number: " . curl_errno($this->ch) ."\n";
+		$err .= "Error message: "  .curl_error($this->ch) ."\n";
+		$this->error_msg .= $err;
+
+		return $this->error_msg;
+	}
+
+	/**
+	 * Close curl session and free resource
+	 * Usually no need to call this function directly
+	 * in case you do you have to call init() to recreate curl
+	 *
+	 * @access private
+	 */
+	private function close()
+	{
+		//close curl session and free up resources
+		curl_close($this->ch);
+	}
+
+	/**
+	 * Setup ip interface to curl and timeouts for curl. Shows all debugging and error info
+	 * if there are any
+	 *
+	 * @access private
+	 *
+	 * @param string qry_string
+	 * @param string ip address to bind (default null)
+	 * @param int timeout in sec for complete curl operation (default 10)
+	 *
+	 */
+	private function prepare_curl()
+	{
+
+		//set various curl options first
+		curl_setopt($this->ch, CURLOPT_URL, $this->url);
+
+		// return into a variable rather than displaying it
+		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, TRUE);
+
+		//set curl function timeout to $timeout
+		curl_setopt($this->ch, CURLOPT_TIMEOUT, $this->timeout);
+	}
+
+	/**
+	 * Fetch data from target URL return data returned from url or false if error occured
+	 *
+	 * @access protected
+	 *
+	 * @param string getdata - The query data to pass to the url
+	 * @param string ip address to bind (default null)
+	 * @param int timeout in sec for complete curl operation (default 5)
+	 * @return string|boolean Returns HTTP response body or FALSE on error
+	 */
+	public function execute()
+	{
+		$this->prepare_curl();
+		//set method to get
+
+		//and finally send curl request
+		$result = curl_exec($this->ch);
+		$this->response_code = curl_getinfo($this->ch, CURLINFO_HTTP_CODE);
+
+		if (curl_errno($this->ch))
+		{
+			$this->set_error_msg();
+			$this->close();
+
+			return FALSE;
+		}
+
+		$this->close();
+		return $result;
+	}
+}
diff --git a/application/libraries/IDNA_convert.php b/application/libraries/IDNA_convert.php
new file mode 100755
index 0000000..517f5b4
--- /dev/null
+++ b/application/libraries/IDNA_convert.php
@@ -0,0 +1,1606 @@
+<?php
+// {{{ license
+
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
+//
+// +----------------------------------------------------------------------+
+// | 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.                                                                 |
+// +----------------------------------------------------------------------+
+//
+
+// }}}
+
+/**
+ * Encode/decode Internationalized Domain Names.
+ *
+ * The class allows to convert internationalized domain names
+ * (see RFC 3490 for details) as they can be used with various registries worldwide
+ * to be translated between their original (localized) form and their encoded form
+ * as it will be used in the DNS (Domain Name System).
+ *
+ * The class provides two public methods, encode() and decode(), which do exactly
+ * what you would expect them to do. You are allowed to use complete domain names,
+ * simple strings and complete email addresses as well. That means, that you might
+ * use any of the following notations:
+ *
+ * - www.nörgler.com
+ * - xn--nrgler-wxa
+ * - xn--brse-5qa.xn--knrz-1ra.info
+ *
+ * Unicode input might be given as either UTF-8 string, UCS-4 string or UCS-4 array.
+ * Unicode output is available in the same formats.
+ * You can select your preferred format via {@link set_paramter()}.
+ *
+ * ACE input and output is always expected to be ASCII.
+ *
+ * @author  Matthias Sommerfeld <mso at phlylabs.de>
+ * @copyright 2004-2011 phlyLabs Berlin, http://phlylabs.de
+ * @version 0.8.0 2011-03-11
+ * @link http://phlymail.com/en/downloads/idna-convert.html
+ */
+class IDNA_convert
+{
+    // NP See below
+
+    // Internal settings, do not mess with them
+    protected $_punycode_prefix = 'xn--';
+    protected $_invalid_ucs = 0x80000000;
+    protected $_max_ucs = 0x10FFFF;
+    protected $_base = 36;
+    protected $_tmin = 1;
+    protected $_tmax = 26;
+    protected $_skew = 38;
+    protected $_damp = 700;
+    protected $_initial_bias = 72;
+    protected $_initial_n = 0x80;
+    protected $_sbase = 0xAC00;
+    protected $_lbase = 0x1100;
+    protected $_vbase = 0x1161;
+    protected $_tbase = 0x11A7;
+    protected $_lcount = 19;
+    protected $_vcount = 21;
+    protected $_tcount = 28;
+    protected $_ncount = 588;   // _vcount * _tcount
+    protected $_scount = 11172; // _lcount * _tcount * _vcount
+    protected $_error = false;
+
+    protected static $_mb_string_overload = null;
+
+    // See {@link set_paramter()} for details of how to change the following
+    // settings from within your script / application
+    protected $_api_encoding = 'utf8';   // Default input charset is UTF-8
+    protected $_allow_overlong = false;  // Overlong UTF-8 encodings are forbidden
+    protected $_strict_mode = false;     // Behave strict or not
+    protected $_idn_version = 2003;      // Can be either 2003 (old, default) or 2008
+
+    /**
+     * the constructor
+     *
+     * @param array $options
+     * @return boolean
+     * @since 0.5.2
+     */
+    public function __construct($options = false)
+    {
+        $this->slast = $this->_sbase + $this->_lcount * $this->_vcount * $this->_tcount;
+        // If parameters are given, pass these to the respective method
+        if (is_array($options)) {
+            $this->set_parameter($options);
+        }
+
+        // populate mbstring overloading cache if not set
+        if (self::$_mb_string_overload === null) {
+            self::$_mb_string_overload = (extension_loaded('mbstring')
+                && (ini_get('mbstring.func_overload') & 0x02) === 0x02);
+        }
+    }
+
+    /**
+     * Sets a new option value. Available options and values:
+     * [encoding - Use either UTF-8, UCS4 as array or UCS4 as string as input ('utf8' for UTF-8,
+     *         'ucs4_string' and 'ucs4_array' respectively for UCS4); The output is always UTF-8]
+     * [overlong - Unicode does not allow unnecessarily long encodings of chars,
+     *             to allow this, set this parameter to true, else to false;
+     *             default is false.]
+     * [strict - true: strict mode, good for registration purposes - Causes errors
+     *           on failures; false: loose mode, ideal for "wildlife" applications
+     *           by silently ignoring errors and returning the original input instead
+     *
+     * @param    mixed     Parameter to set (string: single parameter; array of Parameter => Value pairs)
+     * @param    string    Value to use (if parameter 1 is a string)
+     * @return   boolean   true on success, false otherwise
+     */
+    public function set_parameter($option, $value = false)
+    {
+        if (!is_array($option)) {
+            $option = array($option => $value);
+        }
+        foreach ($option as $k => $v) {
+            switch ($k) {
+            case 'encoding':
+                switch ($v) {
+                case 'utf8':
+                case 'ucs4_string':
+                case 'ucs4_array':
+                    $this->_api_encoding = $v;
+                    break;
+                default:
+                    $this->_error('Set Parameter: Unknown parameter '.$v.' for option '.$k);
+                    return false;
+                }
+                break;
+            case 'overlong':
+                $this->_allow_overlong = ($v) ? true : false;
+                break;
+            case 'strict':
+                $this->_strict_mode = ($v) ? true : false;
+                break;
+            case 'idn_version':
+                if (in_array($v, array('2003', '2008'))) {
+                    $this->_idn_version = $v;
+                } else {
+                    $this->_error('Set Parameter: Unknown parameter '.$v.' for option '.$k);
+                }
+                break;
+            case 'encode_german_sz': // Deprecated
+                if (!$v) {
+                    self::$NP['replacemaps'][0xDF] = array(0x73, 0x73);
+                } else {
+                    unset(self::$NP['replacemaps'][0xDF]);
+                }
+                break;
+            default:
+                $this->_error('Set Parameter: Unknown option '.$k);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Decode a given ACE domain name
+     * @param    string   Domain name (ACE string)
+     * [@param    string   Desired output encoding, see {@link set_parameter}]
+     * @return   string   Decoded Domain name (UTF-8 or UCS-4)
+     */
+    public function decode($input, $one_time_encoding = false)
+    {
+        // Optionally set
+        if ($one_time_encoding) {
+            switch ($one_time_encoding) {
+            case 'utf8':
+            case 'ucs4_string':
+            case 'ucs4_array':
+                break;
+            default:
+                $this->_error('Unknown encoding '.$one_time_encoding);
+                return false;
+            }
+        }
+        // Make sure to drop any newline characters around
+        $input = trim($input);
+
+        // Negotiate input and try to determine, whether it is a plain string,
+        // an email address or something like a complete URL
+        if (strpos($input, '@')) { // Maybe it is an email address
+            // No no in strict mode
+            if ($this->_strict_mode) {
+                $this->_error('Only simple domain name parts can be handled in strict mode');
+                return false;
+            }
+            list ($email_pref, $input) = explode('@', $input, 2);
+            $arr = explode('.', $input);
+            foreach ($arr as $k => $v) {
+                if (preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $v)) {
+                    $conv = $this->_decode($v);
+                    if ($conv) $arr[$k] = $conv;
+                }
+            }
+            $input = join('.', $arr);
+            $arr = explode('.', $email_pref);
+            foreach ($arr as $k => $v) {
+                if (preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $v)) {
+                    $conv = $this->_decode($v);
+                    if ($conv) $arr[$k] = $conv;
+                }
+            }
+            $email_pref = join('.', $arr);
+            $return = $email_pref . '@' . $input;
+        } elseif (preg_match('![:\./]!', $input)) { // Or a complete domain name (with or without paths / parameters)
+            // No no in strict mode
+            if ($this->_strict_mode) {
+                $this->_error('Only simple domain name parts can be handled in strict mode');
+                return false;
+            }
+            $parsed = parse_url($input);
+            if (isset($parsed['host'])) {
+                $arr = explode('.', $parsed['host']);
+                foreach ($arr as $k => $v) {
+                    $conv = $this->_decode($v);
+                    if ($conv) $arr[$k] = $conv;
+                }
+                $parsed['host'] = join('.', $arr);
+                $return =
+                        (empty($parsed['scheme']) ? '' : $parsed['scheme'].(strtolower($parsed['scheme']) == 'mailto' ? ':' : '://'))
+                        .(empty($parsed['user']) ? '' : $parsed['user'].(empty($parsed['pass']) ? '' : ':'.$parsed['pass']).'@')
+                        .$parsed['host']
+                        .(empty($parsed['port']) ? '' : ':'.$parsed['port'])
+                        .(empty($parsed['path']) ? '' : $parsed['path'])
+                        .(empty($parsed['query']) ? '' : '?'.$parsed['query'])
+                        .(empty($parsed['fragment']) ? '' : '#'.$parsed['fragment']);
+            } else { // parse_url seems to have failed, try without it
+                $arr = explode('.', $input);
+                foreach ($arr as $k => $v) {
+                    $conv = $this->_decode($v);
+                    $arr[$k] = ($conv) ? $conv : $v;
+                }
+                $return = join('.', $arr);
+            }
+        } else { // Otherwise we consider it being a pure domain name string
+            $return = $this->_decode($input);
+            if (!$return) $return = $input;
+        }
+        // The output is UTF-8 by default, other output formats need conversion here
+        // If one time encoding is given, use this, else the objects property
+        switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) {
+        case 'utf8':
+            return $return;
+            break;
+        case 'ucs4_string':
+           return $this->_ucs4_to_ucs4_string($this->_utf8_to_ucs4($return));
+           break;
+        case 'ucs4_array':
+            return $this->_utf8_to_ucs4($return);
+            break;
+        default:
+            $this->_error('Unsupported output format');
+            return false;
+        }
+    }
+
+    /**
+     * Encode a given UTF-8 domain name
+     * @param    string   Domain name (UTF-8 or UCS-4)
+     * [@param    string   Desired input encoding, see {@link set_parameter}]
+     * @return   string   Encoded Domain name (ACE string)
+     */
+    public function encode($decoded, $one_time_encoding = false)
+    {
+        // Forcing conversion of input to UCS4 array
+        // If one time encoding is given, use this, else the objects property
+        switch ($one_time_encoding ? $one_time_encoding : $this->_api_encoding) {
+        case 'utf8':
+            $decoded = $this->_utf8_to_ucs4($decoded);
+            break;
+        case 'ucs4_string':
+           $decoded = $this->_ucs4_string_to_ucs4($decoded);
+        case 'ucs4_array':
+           break;
+        default:
+            $this->_error('Unsupported input format: '.($one_time_encoding ? $one_time_encoding : $this->_api_encoding));
+            return false;
+        }
+
+        // No input, no output, what else did you expect?
+        if (empty($decoded)) return '';
+
+        // Anchors for iteration
+        $last_begin = 0;
+        // Output string
+        $output = '';
+        foreach ($decoded as $k => $v) {
+            // Make sure to use just the plain dot
+            switch($v) {
+            case 0x3002:
+            case 0xFF0E:
+            case 0xFF61:
+                $decoded[$k] = 0x2E;
+                // Right, no break here, the above are converted to dots anyway
+            // Stumbling across an anchoring character
+            case 0x2E:
+            case 0x2F:
+            case 0x3A:
+            case 0x3F:
+            case 0x40:
+                // Neither email addresses nor URLs allowed in strict mode
+                if ($this->_strict_mode) {
+                   $this->_error('Neither email addresses nor URLs are allowed in strict mode.');
+                   return false;
+                } else {
+                    // Skip first char
+                    if ($k) {
+                        $encoded = '';
+                        $encoded = $this->_encode(array_slice($decoded, $last_begin, (($k)-$last_begin)));
+                        if ($encoded) {
+                            $output .= $encoded;
+                        } else {
+                            $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($k)-$last_begin)));
+                        }
+                        $output .= chr($decoded[$k]);
+                    }
+                    $last_begin = $k + 1;
+                }
+            }
+        }
+        // Catch the rest of the string
+        if ($last_begin) {
+            $inp_len = sizeof($decoded);
+            $encoded = '';
+            $encoded = $this->_encode(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
+            if ($encoded) {
+                $output .= $encoded;
+            } else {
+                $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
+            }
+            return $output;
+        } else {
+            if ($output = $this->_encode($decoded)) {
+                return $output;
+            } else {
+                return $this->_ucs4_to_utf8($decoded);
+            }
+        }
+    }
+
+    /**
+     * Removes a weakness of encode(), which cannot properly handle URIs but instead encodes their
+     * path or query components, too.
+     * @param string  $uri  Expects the URI as a UTF-8 (or ASCII) string
+     * @return  string  The URI encoded to Punycode, everything but the host component is left alone
+     * @since 0.6.4
+     */
+    public function encode_uri($uri)
+    {
+        $parsed = parse_url($uri);
+        if (!isset($parsed['host'])) {
+            $this->_error('The given string does not look like a URI');
+            return false;
+        }
+        $arr = explode('.', $parsed['host']);
+        foreach ($arr as $k => $v) {
+            $conv = $this->encode($v, 'utf8');
+            if ($conv) $arr[$k] = $conv;
+        }
+        $parsed['host'] = join('.', $arr);
+        $return =
+                (empty($parsed['scheme']) ? '' : $parsed['scheme'].(strtolower($parsed['scheme']) == 'mailto' ? ':' : '://'))
+                .(empty($parsed['user']) ? '' : $parsed['user'].(empty($parsed['pass']) ? '' : ':'.$parsed['pass']).'@')
+                .$parsed['host']
+                .(empty($parsed['port']) ? '' : ':'.$parsed['port'])
+                .(empty($parsed['path']) ? '' : $parsed['path'])
+                .(empty($parsed['query']) ? '' : '?'.$parsed['query'])
+                .(empty($parsed['fragment']) ? '' : '#'.$parsed['fragment']);
+        return $return;
+    }
+
+    /**
+     * Use this method to get the last error ocurred
+     * @param    void
+     * @return   string   The last error, that occured
+     */
+    public function get_last_error()
+    {
+        return $this->_error;
+    }
+
+    /**
+     * The actual decoding algorithm
+     * @param string
+     * @return mixed
+     */
+    protected function _decode($encoded)
+    {
+        $decoded = array();
+        // find the Punycode prefix
+        if (!preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $encoded)) {
+            $this->_error('This is not a punycode string');
+            return false;
+        }
+        $encode_test = preg_replace('!^'.preg_quote($this->_punycode_prefix, '!').'!', '', $encoded);
+        // If nothing left after removing the prefix, it is hopeless
+        if (!$encode_test) {
+            $this->_error('The given encoded string was empty');
+            return false;
+        }
+        // Find last occurence of the delimiter
+        $delim_pos = strrpos($encoded, '-');
+        if ($delim_pos > self::byteLength($this->_punycode_prefix)) {
+            for ($k = self::byteLength($this->_punycode_prefix); $k < $delim_pos; ++$k) {
+                $decoded[] = ord($encoded{$k});
+            }
+        }
+        $deco_len = count($decoded);
+        $enco_len = self::byteLength($encoded);
+
+        // Wandering through the strings; init
+        $is_first = true;
+        $bias = $this->_initial_bias;
+        $idx = 0;
+        $char = $this->_initial_n;
+
+        for ($enco_idx = ($delim_pos) ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) {
+            for ($old_idx = $idx, $w = 1, $k = $this->_base; 1 ; $k += $this->_base) {
+                $digit = $this->_decode_digit($encoded{$enco_idx++});
+                $idx += $digit * $w;
+                $t = ($k <= $bias) ? $this->_tmin :
+                        (($k >= $bias + $this->_tmax) ? $this->_tmax : ($k - $bias));
+                if ($digit < $t) break;
+                $w = (int) ($w * ($this->_base - $t));
+            }
+            $bias = $this->_adapt($idx - $old_idx, $deco_len + 1, $is_first);
+            $is_first = false;
+            $char += (int) ($idx / ($deco_len + 1));
+            $idx %= ($deco_len + 1);
+            if ($deco_len > 0) {
+                // Make room for the decoded char
+                for ($i = $deco_len; $i > $idx; $i--) $decoded[$i] = $decoded[($i - 1)];
+            }
+            $decoded[$idx++] = $char;
+        }
+        return $this->_ucs4_to_utf8($decoded);
+    }
+
+    /**
+     * The actual encoding algorithm
+     * @param  string
+     * @return mixed
+     */
+    protected function _encode($decoded)
+    {
+        // We cannot encode a domain name containing the Punycode prefix
+        $extract = self::byteLength($this->_punycode_prefix);
+        $check_pref = $this->_utf8_to_ucs4($this->_punycode_prefix);
+        $check_deco = array_slice($decoded, 0, $extract);
+
+        if ($check_pref == $check_deco) {
+            $this->_error('This is already a punycode string');
+            return false;
+        }
+        // We will not try to encode strings consisting of basic code points only
+        $encodable = false;
+        foreach ($decoded as $k => $v) {
+            if ($v > 0x7a) {
+                $encodable = true;
+                break;
+            }
+        }
+        if (!$encodable) {
+            $this->_error('The given string does not contain encodable chars');
+            return false;
+        }
+        // Do NAMEPREP
+        $decoded = $this->_nameprep($decoded);
+        if (!$decoded || !is_array($decoded)) return false; // NAMEPREP failed
+        $deco_len  = count($decoded);
+        if (!$deco_len) return false; // Empty array
+        $codecount = 0; // How many chars have been consumed
+        $encoded = '';
+        // Copy all basic code points to output
+        for ($i = 0; $i < $deco_len; ++$i) {
+            $test = $decoded[$i];
+            // Will match [-0-9a-zA-Z]
+            if ((0x2F < $test && $test < 0x40) || (0x40 < $test && $test < 0x5B)
+                    || (0x60 < $test && $test <= 0x7B) || (0x2D == $test)) {
+                $encoded .= chr($decoded[$i]);
+                $codecount++;
+            }
+        }
+        if ($codecount == $deco_len) return $encoded; // All codepoints were basic ones
+
+        // Start with the prefix; copy it to output
+        $encoded = $this->_punycode_prefix.$encoded;
+        // If we have basic code points in output, add an hyphen to the end
+        if ($codecount) $encoded .= '-';
+        // Now find and encode all non-basic code points
+        $is_first = true;
+        $cur_code = $this->_initial_n;
+        $bias = $this->_initial_bias;
+        $delta = 0;
+        while ($codecount < $deco_len) {
+            // Find the smallest code point >= the current code point and
+            // remember the last ouccrence of it in the input
+            for ($i = 0, $next_code = $this->_max_ucs; $i < $deco_len; $i++) {
+                if ($decoded[$i] >= $cur_code && $decoded[$i] <= $next_code) {
+                    $next_code = $decoded[$i];
+                }
+            }
+            $delta += ($next_code - $cur_code) * ($codecount + 1);
+            $cur_code = $next_code;
+
+            // Scan input again and encode all characters whose code point is $cur_code
+            for ($i = 0; $i < $deco_len; $i++) {
+                if ($decoded[$i] < $cur_code) {
+                    $delta++;
+                } elseif ($decoded[$i] == $cur_code) {
+                    for ($q = $delta, $k = $this->_base; 1; $k += $this->_base) {
+                        $t = ($k <= $bias) ? $this->_tmin :
+                                (($k >= $bias + $this->_tmax) ? $this->_tmax : $k - $bias);
+                        if ($q < $t) break;
+                        $encoded .= $this->_encode_digit(intval($t + (($q - $t) % ($this->_base - $t)))); //v0.4.5 Changed from ceil() to intval()
+                        $q = (int) (($q - $t) / ($this->_base - $t));
+                    }
+                    $encoded .= $this->_encode_digit($q);
+                    $bias = $this->_adapt($delta, $codecount+1, $is_first);
+                    $codecount++;
+                    $delta = 0;
+                    $is_first = false;
+                }
+            }
+            $delta++;
+            $cur_code++;
+        }
+        return $encoded;
+    }
+
+    /**
+     * Adapt the bias according to the current code point and position
+     * @param int $delta
+     * @param int $npoints
+     * @param int $is_first
+     * @return int
+     */
+    protected function _adapt($delta, $npoints, $is_first)
+    {
+        $delta = intval($is_first ? ($delta / $this->_damp) : ($delta / 2));
+        $delta += intval($delta / $npoints);
+        for ($k = 0; $delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2; $k += $this->_base) {
+            $delta = intval($delta / ($this->_base - $this->_tmin));
+        }
+        return intval($k + ($this->_base - $this->_tmin + 1) * $delta / ($delta + $this->_skew));
+    }
+
+    /**
+     * Encoding a certain digit
+     * @param    int $d
+     * @return string
+     */
+    protected function _encode_digit($d)
+    {
+        return chr($d + 22 + 75 * ($d < 26));
+    }
+
+    /**
+     * Decode a certain digit
+     * @param    int $cp
+     * @return int
+     */
+    protected function _decode_digit($cp)
+    {
+        $cp = ord($cp);
+        return ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $this->_base));
+    }
+
+    /**
+     * Internal error handling method
+     * @param  string $error
+     */
+    protected function _error($error = '')
+    {
+        $this->_error = $error;
+    }
+
+    /**
+     * Do Nameprep according to RFC3491 and RFC3454
+     * @param    array    Unicode Characters
+     * @return   string   Unicode Characters, Nameprep'd
+     */
+    protected function _nameprep($input)
+    {
+        $output = array();
+        $error = false;
+        //
+        // Mapping
+        // Walking through the input array, performing the required steps on each of
+        // the input chars and putting the result into the output array
+        // While mapping required chars we apply the cannonical ordering
+        foreach ($input as $v) {
+            // Map to nothing == skip that code point
+            if (in_array($v, self::$NP['map_nothing'])) continue;
+            // Try to find prohibited input
+            if (in_array($v, self::$NP['prohibit']) || in_array($v, self::$NP['general_prohibited'])) {
+                $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v));
+                return false;
+            }
+            foreach (self::$NP['prohibit_ranges'] as $range) {
+                if ($range[0] <= $v && $v <= $range[1]) {
+                    $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v));
+                    return false;
+                }
+            }
+
+            if (0xAC00 <= $v && $v <= 0xD7AF) {
+                // Hangul syllable decomposition
+                foreach ($this->_hangul_decompose($v) as $out) {
+                    $output[] = (int) $out;
+                }
+            } elseif (($this->_idn_version == '2003') && isset(self::$NP['replacemaps'][$v])) {
+                // There's a decomposition mapping for that code point
+                // Decompositions only in version 2003 (original) of IDNA
+                foreach ($this->_apply_cannonical_ordering(self::$NP['replacemaps'][$v]) as $out) {
+                    $output[] = (int) $out;
+                }
+            } else {
+                $output[] = (int) $v;
+            }
+        }
+        // Before applying any Combining, try to rearrange any Hangul syllables
+        $output = $this->_hangul_compose($output);
+        //
+        // Combine code points
+        //
+        $last_class = 0;
+        $last_starter = 0;
+        $out_len = count($output);
+        for ($i = 0; $i < $out_len; ++$i) {
+            $class = $this->_get_combining_class($output[$i]);
+            if ((!$last_class || $last_class > $class) && $class) {
+                // Try to match
+                $seq_len = $i - $last_starter;
+                $out = $this->_combine(array_slice($output, $last_starter, $seq_len));
+                // On match: Replace the last starter with the composed character and remove
+                // the now redundant non-starter(s)
+                if ($out) {
+                    $output[$last_starter] = $out;
+                    if (count($out) != $seq_len) {
+                        for ($j = $i+1; $j < $out_len; ++$j) $output[$j-1] = $output[$j];
+                        unset($output[$out_len]);
+                    }
+                    // Rewind the for loop by one, since there can be more possible compositions
+                    $i--;
+                    $out_len--;
+                    $last_class = ($i == $last_starter) ? 0 : $this->_get_combining_class($output[$i-1]);
+                    continue;
+                }
+            }
+            // The current class is 0
+            if (!$class) $last_starter = $i;
+            $last_class = $class;
+        }
+        return $output;
+    }
+
+    /**
+     * Decomposes a Hangul syllable
+     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul
+     * @param    integer  32bit UCS4 code point
+     * @return   array    Either Hangul Syllable decomposed or original 32bit value as one value array
+     */
+    protected function _hangul_decompose($char)
+    {
+        $sindex = (int) $char - $this->_sbase;
+        if ($sindex < 0 || $sindex >= $this->_scount) return array($char);
+        $result = array();
+        $result[] = (int) $this->_lbase + $sindex / $this->_ncount;
+        $result[] = (int) $this->_vbase + ($sindex % $this->_ncount) / $this->_tcount;
+        $T = intval($this->_tbase + $sindex % $this->_tcount);
+        if ($T != $this->_tbase) $result[] = $T;
+        return $result;
+    }
+    /**
+     * Ccomposes a Hangul syllable
+     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul
+     * @param    array    Decomposed UCS4 sequence
+     * @return   array    UCS4 sequence with syllables composed
+     */
+    protected function _hangul_compose($input)
+    {
+        $inp_len = count($input);
+        if (!$inp_len) return array();
+        $result = array();
+        $last = (int) $input[0];
+        $result[] = $last; // copy first char from input to output
+
+        for ($i = 1; $i < $inp_len; ++$i) {
+            $char = (int) $input[$i];
+            $sindex = $last - $this->_sbase;
+            $lindex = $last - $this->_lbase;
+            $vindex = $char - $this->_vbase;
+            $tindex = $char - $this->_tbase;
+            // Find out, whether two current characters are LV and T
+            if (0 <= $sindex && $sindex < $this->_scount && ($sindex % $this->_tcount == 0)
+                    && 0 <= $tindex && $tindex <= $this->_tcount) {
+                // create syllable of form LVT
+                $last += $tindex;
+                $result[(count($result) - 1)] = $last; // reset last
+                continue; // discard char
+            }
+            // Find out, whether two current characters form L and V
+            if (0 <= $lindex && $lindex < $this->_lcount && 0 <= $vindex && $vindex < $this->_vcount) {
+                // create syllable of form LV
+                $last = (int) $this->_sbase + ($lindex * $this->_vcount + $vindex) * $this->_tcount;
+                $result[(count($result) - 1)] = $last; // reset last
+                continue; // discard char
+            }
+            // if neither case was true, just add the character
+            $last = $char;
+            $result[] = $char;
+        }
+        return $result;
+    }
+
+    /**
+     * Returns the combining class of a certain wide char
+     * @param    integer    Wide char to check (32bit integer)
+     * @return   integer    Combining class if found, else 0
+     */
+    protected function _get_combining_class($char)
+    {
+        return isset(self::$NP['norm_combcls'][$char]) ? self::$NP['norm_combcls'][$char] : 0;
+    }
+
+    /**
+     * Applies the cannonical ordering of a decomposed UCS4 sequence
+     * @param    array      Decomposed UCS4 sequence
+     * @return   array      Ordered USC4 sequence
+     */
+    protected function _apply_cannonical_ordering($input)
+    {
+        $swap = true;
+        $size = count($input);
+        while ($swap) {
+            $swap = false;
+            $last = $this->_get_combining_class(intval($input[0]));
+            for ($i = 0; $i < $size-1; ++$i) {
+                $next = $this->_get_combining_class(intval($input[$i+1]));
+                if ($next != 0 && $last > $next) {
+                    // Move item leftward until it fits
+                    for ($j = $i + 1; $j > 0; --$j) {
+                        if ($this->_get_combining_class(intval($input[$j-1])) <= $next) break;
+                        $t = intval($input[$j]);
+                        $input[$j] = intval($input[$j-1]);
+                        $input[$j-1] = $t;
+                        $swap = true;
+                    }
+                    // Reentering the loop looking at the old character again
+                    $next = $last;
+                }
+                $last = $next;
+            }
+        }
+        return $input;
+    }
+
+    /**
+     * Do composition of a sequence of starter and non-starter
+     * @param    array      UCS4 Decomposed sequence
+     * @return   array      Ordered USC4 sequence
+     */
+    protected function _combine($input)
+    {
+        $inp_len = count($input);
+        foreach (self::$NP['replacemaps'] as $np_src => $np_target) {
+            if ($np_target[0] != $input[0]) continue;
+            if (count($np_target) != $inp_len) continue;
+            $hit = false;
+            foreach ($input as $k2 => $v2) {
+                if ($v2 == $np_target[$k2]) {
+                    $hit = true;
+                } else {
+                    $hit = false;
+                    break;
+                }
+            }
+            if ($hit) return $np_src;
+        }
+        return false;
+    }
+
+    /**
+     * This converts an UTF-8 encoded string to its UCS-4 representation
+     * By talking about UCS-4 "strings" we mean arrays of 32bit integers representing
+     * each of the "chars". This is due to PHP not being able to handle strings with
+     * bit depth different from 8. This apllies to the reverse method _ucs4_to_utf8(), too.
+     * The following UTF-8 encodings are supported:
+     * bytes bits  representation
+     * 1        7  0xxxxxxx
+     * 2       11  110xxxxx 10xxxxxx
+     * 3       16  1110xxxx 10xxxxxx 10xxxxxx
+     * 4       21  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+     * 5       26  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+     * 6       31  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+     * Each x represents a bit that can be used to store character data.
+     * The five and six byte sequences are part of Annex D of ISO/IEC 10646-1:2000
+     * @param string $input
+     * @return string
+     */
+    protected function _utf8_to_ucs4($input)
+    {
+        $output = array();
+        $out_len = 0;
+        $inp_len = self::byteLength($input);
+        $mode = 'next';
+        $test = 'none';
+        for ($k = 0; $k < $inp_len; ++$k) {
+            $v = ord($input{$k}); // Extract byte from input string
+            if ($v < 128) { // We found an ASCII char - put into stirng as is
+                $output[$out_len] = $v;
+                ++$out_len;
+                if ('add' == $mode) {
+                    $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
+                    return false;
+                }
+                continue;
+            }
+            if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char
+                $start_byte = $v;
+                $mode = 'add';
+                $test = 'range';
+                if ($v >> 5 == 6) { // &110xxxxx 10xxxxx
+                    $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left
+                    $v = ($v - 192) << 6;
+                } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx
+                    $next_byte = 1;
+                    $v = ($v - 224) << 12;
+                } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+                    $next_byte = 2;
+                    $v = ($v - 240) << 18;
+                } elseif ($v >> 2 == 62) { // &111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+                    $next_byte = 3;
+                    $v = ($v - 248) << 24;
+                } elseif ($v >> 1 == 126) { // &1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+                    $next_byte = 4;
+                    $v = ($v - 252) << 30;
+                } else {
+                    $this->_error('This might be UTF-8, but I don\'t understand it at byte '.$k);
+                    return false;
+                }
+                if ('add' == $mode) {
+                    $output[$out_len] = (int) $v;
+                    ++$out_len;
+                    continue;
+                }
+            }
+            if ('add' == $mode) {
+                if (!$this->_allow_overlong && $test == 'range') {
+                    $test = 'none';
+                    if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) {
+                        $this->_error('Bogus UTF-8 character detected (out of legal range) at byte '.$k);
+                        return false;
+                    }
+                }
+                if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx
+                    $v = ($v - 128) << ($next_byte * 6);
+                    $output[($out_len - 1)] += $v;
+                    --$next_byte;
+                } else {
+                    $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
+                    return false;
+                }
+                if ($next_byte < 0) {
+                    $mode = 'next';
+                }
+            }
+        } // for
+        return $output;
+    }
+
+    /**
+     * Convert UCS-4 string into UTF-8 string
+     * See _utf8_to_ucs4() for details
+     * @param string  $input
+     * @return string
+     */
+    protected function _ucs4_to_utf8($input)
+    {
+        $output = '';
+        foreach ($input as $k => $v) {
+            if ($v < 128) { // 7bit are transferred literally
+                $output .= chr($v);
+            } elseif ($v < (1 << 11)) { // 2 bytes
+                $output .= chr(192+($v >> 6)).chr(128+($v & 63));
+            } elseif ($v < (1 << 16)) { // 3 bytes
+                $output .= chr(224+($v >> 12)).chr(128+(($v >> 6) & 63)).chr(128+($v & 63));
+            } elseif ($v < (1 << 21)) { // 4 bytes
+                $output .= chr(240+($v >> 18)).chr(128+(($v >> 12) & 63)).chr(128+(($v >> 6) & 63)).chr(128+($v & 63));
+            } elseif (self::$safe_mode) {
+                $output .= self::$safe_char;
+            } else {
+                $this->_error('Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k);
+                return false;
+            }
+        }
+        return $output;
+    }
+
+    /**
+     * Convert UCS-4 array into UCS-4 string
+     *
+     * @param array $input
+     * @return string
+     */
+    protected function _ucs4_to_ucs4_string($input)
+    {
+        $output = '';
+        // Take array values and split output to 4 bytes per value
+        // The bit mask is 255, which reads &11111111
+        foreach ($input as $v) {
+            $output .= chr(($v >> 24) & 255).chr(($v >> 16) & 255).chr(($v >> 8) & 255).chr($v & 255);
+        }
+        return $output;
+    }
+
+    /**
+     * Convert UCS-4 strin into UCS-4 garray
+     *
+     * @param  string $input
+     * @return array
+     */
+    protected function _ucs4_string_to_ucs4($input)
+    {
+        $output = array();
+        $inp_len = self::byteLength($input);
+        // Input length must be dividable by 4
+        if ($inp_len % 4) {
+            $this->_error('Input UCS4 string is broken');
+            return false;
+        }
+        // Empty input - return empty output
+        if (!$inp_len) return $output;
+        for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) {
+            // Increment output position every 4 input bytes
+            if (!($i % 4)) {
+                $out_len++;
+                $output[$out_len] = 0;
+            }
+            $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) );
+        }
+        return $output;
+    }
+
+    /**
+     * Gets the length of a string in bytes even if mbstring function
+     * overloading is turned on
+     *
+     * @param string $string the string for which to get the length.
+     * @return integer the length of the string in bytes.
+     */
+    protected static function byteLength($string)
+    {
+        if (self::$_mb_string_overload) {
+            return mb_strlen($string, '8bit');
+        }
+        return strlen((binary) $string);
+    }
+
+    /**
+     * Attempts to return a concrete IDNA instance.
+     *
+     * @param array $params Set of paramaters
+     * @return idna_convert
+     * @access public
+     */
+    public static function getInstance($params = array())
+    {
+        return new idna_convert($params);
+    }
+
+    /**
+     * Attempts to return a concrete IDNA instance for either php4 or php5,
+     * only creating a new instance if no IDNA instance with the same
+     * parameters currently exists.
+     *
+     * @param array $params Set of paramaters
+     *
+     * @return object idna_convert
+     * @access public
+     */
+    public static function singleton($params = array())
+    {
+        static $instances;
+        if (!isset($instances)) {
+            $instances = array();
+        }
+        $signature = serialize($params);
+        if (!isset($instances[$signature])) {
+            $instances[$signature] = self::getInstance($params);
+        }
+        return $instances[$signature];
+    }
+
+    /**
+     * Holds all relevant mapping tables
+     * See RFC3454 for details
+     *
+     * @private array
+     * @since 0.5.2
+     */
+    protected static $NP = array
+            ('map_nothing' => array(0xAD, 0x34F, 0x1806, 0x180B, 0x180C, 0x180D, 0x200B, 0x200C
+                    ,0x200D, 0x2060, 0xFE00, 0xFE01, 0xFE02, 0xFE03, 0xFE04, 0xFE05, 0xFE06, 0xFE07
+                    ,0xFE08, 0xFE09, 0xFE0A, 0xFE0B, 0xFE0C, 0xFE0D, 0xFE0E, 0xFE0F, 0xFEFF
+                    )
+            ,'general_prohibited' => array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
+                    ,20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 ,33, 34, 35, 36, 37, 38, 39, 40, 41, 42
+                    ,43, 44, 47, 59, 60, 61, 62, 63, 64, 91, 92, 93, 94, 95, 96, 123, 124, 125, 126, 127, 0x3002
+                    )
+            ,'prohibit' => array(0xA0, 0x340, 0x341, 0x6DD, 0x70F, 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003
+                    ,0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x200B, 0x200C, 0x200D, 0x200E, 0x200F
+                    ,0x2028, 0x2029, 0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 0x202F, 0x205F, 0x206A, 0x206B, 0x206C
+                    ,0x206D, 0x206E, 0x206F, 0x3000, 0xFEFF, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF
+                    ,0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE
+                    ,0x6FFFF, 0x7FFFE, 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF, 0xBFFFE, 0xBFFFF
+                    ,0xCFFFE, 0xCFFFF, 0xDFFFE, 0xDFFFF, 0xE0001, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE, 0x10FFFF
+                    )
+            ,'prohibit_ranges' => array(array(0x80, 0x9F), array(0x2060, 0x206F), array(0x1D173, 0x1D17A)
+                    ,array(0xE000, 0xF8FF) ,array(0xF0000, 0xFFFFD), array(0x100000, 0x10FFFD)
+                    ,array(0xFDD0, 0xFDEF), array(0xD800, 0xDFFF), array(0x2FF0, 0x2FFB), array(0xE0020, 0xE007F)
+                    )
+            ,'replacemaps' => array(0x41 => array(0x61), 0x42 => array(0x62), 0x43 => array(0x63)
+                    ,0x44 => array(0x64), 0x45 => array(0x65), 0x46 => array(0x66), 0x47 => array(0x67)
+                    ,0x48 => array(0x68), 0x49 => array(0x69), 0x4A => array(0x6A), 0x4B => array(0x6B)
+                    ,0x4C => array(0x6C), 0x4D => array(0x6D), 0x4E => array(0x6E), 0x4F => array(0x6F)
+                    ,0x50 => array(0x70), 0x51 => array(0x71), 0x52 => array(0x72), 0x53 => array(0x73)
+                    ,0x54 => array(0x74), 0x55 => array(0x75), 0x56 => array(0x76), 0x57 => array(0x77)
+                    ,0x58 => array(0x78), 0x59 => array(0x79), 0x5A => array(0x7A), 0xB5 => array(0x3BC)
+                    ,0xC0 => array(0xE0), 0xC1 => array(0xE1), 0xC2 => array(0xE2), 0xC3 => array(0xE3)
+                    ,0xC4 => array(0xE4), 0xC5 => array(0xE5), 0xC6 => array(0xE6), 0xC7 => array(0xE7)
+                    ,0xC8 => array(0xE8), 0xC9 => array(0xE9), 0xCA => array(0xEA), 0xCB => array(0xEB)
+                    ,0xCC => array(0xEC), 0xCD => array(0xED), 0xCE => array(0xEE), 0xCF => array(0xEF)
+                    ,0xD0 => array(0xF0), 0xD1 => array(0xF1), 0xD2 => array(0xF2), 0xD3 => array(0xF3)
+                    ,0xD4 => array(0xF4), 0xD5 => array(0xF5), 0xD6 => array(0xF6), 0xD8 => array(0xF8)
+                    ,0xD9 => array(0xF9), 0xDA => array(0xFA), 0xDB => array(0xFB), 0xDC => array(0xFC)
+                    ,0xDD => array(0xFD), 0xDE => array(0xFE), 0xDF => array(0x73, 0x73)
+                    ,0x100 => array(0x101), 0x102 => array(0x103), 0x104 => array(0x105)
+                    ,0x106 => array(0x107), 0x108 => array(0x109), 0x10A => array(0x10B)
+                    ,0x10C => array(0x10D), 0x10E => array(0x10F), 0x110 => array(0x111)
+                    ,0x112 => array(0x113), 0x114 => array(0x115), 0x116 => array(0x117)
+                    ,0x118 => array(0x119), 0x11A => array(0x11B), 0x11C => array(0x11D)
+                    ,0x11E => array(0x11F), 0x120 => array(0x121), 0x122 => array(0x123)
+                    ,0x124 => array(0x125), 0x126 => array(0x127), 0x128 => array(0x129)
+                    ,0x12A => array(0x12B), 0x12C => array(0x12D), 0x12E => array(0x12F)
+                    ,0x130 => array(0x69, 0x307), 0x132 => array(0x133), 0x134 => array(0x135)
+                    ,0x136 => array(0x137), 0x139 => array(0x13A), 0x13B => array(0x13C)
+                    ,0x13D => array(0x13E), 0x13F => array(0x140), 0x141 => array(0x142)
+                    ,0x143 => array(0x144), 0x145 => array(0x146), 0x147 => array(0x148)
+                    ,0x149 => array(0x2BC, 0x6E), 0x14A => array(0x14B), 0x14C => array(0x14D)
+                    ,0x14E => array(0x14F), 0x150 => array(0x151), 0x152 => array(0x153)
+                    ,0x154 => array(0x155), 0x156 => array(0x157), 0x158 => array(0x159)
+                    ,0x15A => array(0x15B), 0x15C => array(0x15D), 0x15E => array(0x15F)
+                    ,0x160 => array(0x161), 0x162 => array(0x163), 0x164 => array(0x165)
+                    ,0x166 => array(0x167), 0x168 => array(0x169), 0x16A => array(0x16B)
+                    ,0x16C => array(0x16D), 0x16E => array(0x16F), 0x170 => array(0x171)
+                    ,0x172 => array(0x173), 0x174 => array(0x175), 0x176 => array(0x177)
+                    ,0x178 => array(0xFF), 0x179 => array(0x17A), 0x17B => array(0x17C)
+                    ,0x17D => array(0x17E), 0x17F => array(0x73), 0x181 => array(0x253)
+                    ,0x182 => array(0x183), 0x184 => array(0x185), 0x186 => array(0x254)
+                    ,0x187 => array(0x188), 0x189 => array(0x256), 0x18A => array(0x257)
+                    ,0x18B => array(0x18C), 0x18E => array(0x1DD), 0x18F => array(0x259)
+                    ,0x190 => array(0x25B), 0x191 => array(0x192), 0x193 => array(0x260)
+                    ,0x194 => array(0x263), 0x196 => array(0x269), 0x197 => array(0x268)
+                    ,0x198 => array(0x199), 0x19C => array(0x26F), 0x19D => array(0x272)
+                    ,0x19F => array(0x275), 0x1A0 => array(0x1A1), 0x1A2 => array(0x1A3)
+                    ,0x1A4 => array(0x1A5), 0x1A6 => array(0x280), 0x1A7 => array(0x1A8)
+                    ,0x1A9 => array(0x283), 0x1AC => array(0x1AD), 0x1AE => array(0x288)
+                    ,0x1AF => array(0x1B0), 0x1B1 => array(0x28A), 0x1B2 => array(0x28B)
+                    ,0x1B3 => array(0x1B4), 0x1B5 => array(0x1B6), 0x1B7 => array(0x292)
+                    ,0x1B8 => array(0x1B9), 0x1BC => array(0x1BD), 0x1C4 => array(0x1C6)
+                    ,0x1C5 => array(0x1C6), 0x1C7 => array(0x1C9), 0x1C8 => array(0x1C9)
+                    ,0x1CA => array(0x1CC), 0x1CB => array(0x1CC), 0x1CD => array(0x1CE)
+                    ,0x1CF => array(0x1D0), 0x1D1   => array(0x1D2), 0x1D3   => array(0x1D4)
+                    ,0x1D5   => array(0x1D6), 0x1D7   => array(0x1D8), 0x1D9   => array(0x1DA)
+                    ,0x1DB   => array(0x1DC), 0x1DE   => array(0x1DF), 0x1E0   => array(0x1E1)
+                    ,0x1E2   => array(0x1E3), 0x1E4   => array(0x1E5), 0x1E6   => array(0x1E7)
+                    ,0x1E8   => array(0x1E9), 0x1EA   => array(0x1EB), 0x1EC   => array(0x1ED)
+                    ,0x1EE   => array(0x1EF), 0x1F0   => array(0x6A, 0x30C), 0x1F1   => array(0x1F3)
+                    ,0x1F2   => array(0x1F3), 0x1F4   => array(0x1F5), 0x1F6   => array(0x195)
+                    ,0x1F7   => array(0x1BF), 0x1F8   => array(0x1F9), 0x1FA   => array(0x1FB)
+                    ,0x1FC   => array(0x1FD), 0x1FE   => array(0x1FF), 0x200   => array(0x201)
+                    ,0x202   => array(0x203), 0x204   => array(0x205), 0x206   => array(0x207)
+                    ,0x208   => array(0x209), 0x20A   => array(0x20B), 0x20C   => array(0x20D)
+                    ,0x20E   => array(0x20F), 0x210   => array(0x211), 0x212   => array(0x213)
+                    ,0x214   => array(0x215), 0x216   => array(0x217), 0x218   => array(0x219)
+                    ,0x21A   => array(0x21B), 0x21C   => array(0x21D), 0x21E   => array(0x21F)
+                    ,0x220   => array(0x19E), 0x222   => array(0x223), 0x224   => array(0x225)
+                    ,0x226   => array(0x227), 0x228   => array(0x229), 0x22A   => array(0x22B)
+                    ,0x22C   => array(0x22D), 0x22E   => array(0x22F), 0x230   => array(0x231)
+                    ,0x232   => array(0x233), 0x345   => array(0x3B9), 0x37A   => array(0x20, 0x3B9)
+                    ,0x386   => array(0x3AC), 0x388   => array(0x3AD), 0x389   => array(0x3AE)
+                    ,0x38A   => array(0x3AF), 0x38C   => array(0x3CC), 0x38E   => array(0x3CD)
+                    ,0x38F   => array(0x3CE), 0x390   => array(0x3B9, 0x308, 0x301)
+                    ,0x391   => array(0x3B1), 0x392   => array(0x3B2), 0x393   => array(0x3B3)
+                    ,0x394   => array(0x3B4), 0x395   => array(0x3B5), 0x396   => array(0x3B6)
+                    ,0x397   => array(0x3B7), 0x398   => array(0x3B8), 0x399   => array(0x3B9)
+                    ,0x39A   => array(0x3BA), 0x39B   => array(0x3BB), 0x39C   => array(0x3BC)
+                    ,0x39D   => array(0x3BD), 0x39E   => array(0x3BE), 0x39F   => array(0x3BF)
+                    ,0x3A0   => array(0x3C0), 0x3A1   => array(0x3C1), 0x3A3   => array(0x3C3)
+                    ,0x3A4   => array(0x3C4), 0x3A5   => array(0x3C5), 0x3A6   => array(0x3C6)
+                    ,0x3A7   => array(0x3C7), 0x3A8   => array(0x3C8), 0x3A9   => array(0x3C9)
+                    ,0x3AA   => array(0x3CA), 0x3AB   => array(0x3CB), 0x3B0   => array(0x3C5, 0x308, 0x301)
+                    ,0x3C2   => array(0x3C3), 0x3D0   => array(0x3B2), 0x3D1   => array(0x3B8)
+                    ,0x3D2   => array(0x3C5), 0x3D3   => array(0x3CD), 0x3D4   => array(0x3CB)
+                    ,0x3D5   => array(0x3C6), 0x3D6   => array(0x3C0), 0x3D8   => array(0x3D9)
+                    ,0x3DA   => array(0x3DB), 0x3DC   => array(0x3DD), 0x3DE   => array(0x3DF)
+                    ,0x3E0   => array(0x3E1), 0x3E2   => array(0x3E3), 0x3E4   => array(0x3E5)
+                    ,0x3E6   => array(0x3E7), 0x3E8   => array(0x3E9), 0x3EA   => array(0x3EB)
+                    ,0x3EC   => array(0x3ED), 0x3EE   => array(0x3EF), 0x3F0   => array(0x3BA)
+                    ,0x3F1   => array(0x3C1), 0x3F2   => array(0x3C3), 0x3F4   => array(0x3B8)
+                    ,0x3F5   => array(0x3B5), 0x400   => array(0x450), 0x401   => array(0x451)
+                    ,0x402   => array(0x452), 0x403   => array(0x453), 0x404   => array(0x454)
+                    ,0x405   => array(0x455), 0x406   => array(0x456), 0x407   => array(0x457)
+                    ,0x408   => array(0x458), 0x409   => array(0x459), 0x40A   => array(0x45A)
+                    ,0x40B   => array(0x45B), 0x40C   => array(0x45C), 0x40D   => array(0x45D)
+                    ,0x40E   => array(0x45E), 0x40F   => array(0x45F), 0x410   => array(0x430)
+                    ,0x411   => array(0x431), 0x412   => array(0x432), 0x413   => array(0x433)
+                    ,0x414   => array(0x434), 0x415   => array(0x435), 0x416   => array(0x436)
+                    ,0x417   => array(0x437), 0x418   => array(0x438), 0x419   => array(0x439)
+                    ,0x41A   => array(0x43A), 0x41B   => array(0x43B), 0x41C   => array(0x43C)
+                    ,0x41D   => array(0x43D), 0x41E   => array(0x43E), 0x41F   => array(0x43F)
+                    ,0x420   => array(0x440), 0x421   => array(0x441), 0x422   => array(0x442)
+                    ,0x423   => array(0x443), 0x424   => array(0x444), 0x425   => array(0x445)
+                    ,0x426   => array(0x446), 0x427   => array(0x447), 0x428   => array(0x448)
+                    ,0x429   => array(0x449), 0x42A   => array(0x44A), 0x42B   => array(0x44B)
+                    ,0x42C   => array(0x44C), 0x42D   => array(0x44D), 0x42E   => array(0x44E)
+                    ,0x42F   => array(0x44F), 0x460   => array(0x461), 0x462   => array(0x463)
+                    ,0x464   => array(0x465), 0x466   => array(0x467), 0x468   => array(0x469)
+                    ,0x46A   => array(0x46B), 0x46C   => array(0x46D), 0x46E   => array(0x46F)
+                    ,0x470   => array(0x471), 0x472   => array(0x473), 0x474   => array(0x475)
+                    ,0x476   => array(0x477), 0x478   => array(0x479), 0x47A   => array(0x47B)
+                    ,0x47C   => array(0x47D), 0x47E   => array(0x47F), 0x480   => array(0x481)
+                    ,0x48A   => array(0x48B), 0x48C   => array(0x48D), 0x48E   => array(0x48F)
+                    ,0x490   => array(0x491), 0x492   => array(0x493), 0x494   => array(0x495)
+                    ,0x496   => array(0x497), 0x498   => array(0x499), 0x49A   => array(0x49B)
+                    ,0x49C   => array(0x49D), 0x49E   => array(0x49F), 0x4A0   => array(0x4A1)
+                    ,0x4A2   => array(0x4A3), 0x4A4   => array(0x4A5), 0x4A6   => array(0x4A7)
+                    ,0x4A8   => array(0x4A9), 0x4AA   => array(0x4AB), 0x4AC   => array(0x4AD)
+                    ,0x4AE   => array(0x4AF), 0x4B0   => array(0x4B1), 0x4B2   => array(0x4B3)
+                    ,0x4B4   => array(0x4B5), 0x4B6   => array(0x4B7), 0x4B8   => array(0x4B9)
+                    ,0x4BA   => array(0x4BB), 0x4BC   => array(0x4BD), 0x4BE   => array(0x4BF)
+                    ,0x4C1   => array(0x4C2), 0x4C3   => array(0x4C4), 0x4C5   => array(0x4C6)
+                    ,0x4C7   => array(0x4C8), 0x4C9   => array(0x4CA), 0x4CB   => array(0x4CC)
+                    ,0x4CD   => array(0x4CE), 0x4D0   => array(0x4D1), 0x4D2   => array(0x4D3)
+                    ,0x4D4   => array(0x4D5), 0x4D6   => array(0x4D7), 0x4D8   => array(0x4D9)
+                    ,0x4DA   => array(0x4DB), 0x4DC   => array(0x4DD), 0x4DE   => array(0x4DF)
+                    ,0x4E0   => array(0x4E1), 0x4E2   => array(0x4E3), 0x4E4   => array(0x4E5)
+                    ,0x4E6   => array(0x4E7), 0x4E8   => array(0x4E9), 0x4EA   => array(0x4EB)
+                    ,0x4EC   => array(0x4ED), 0x4EE   => array(0x4EF), 0x4F0   => array(0x4F1)
+                    ,0x4F2   => array(0x4F3), 0x4F4   => array(0x4F5), 0x4F8   => array(0x4F9)
+                    ,0x500   => array(0x501), 0x502   => array(0x503), 0x504   => array(0x505)
+                    ,0x506   => array(0x507), 0x508   => array(0x509), 0x50A   => array(0x50B)
+                    ,0x50C   => array(0x50D), 0x50E   => array(0x50F), 0x531   => array(0x561)
+                    ,0x532   => array(0x562), 0x533   => array(0x563), 0x534   => array(0x564)
+                    ,0x535   => array(0x565), 0x536   => array(0x566), 0x537   => array(0x567)
+                    ,0x538   => array(0x568), 0x539   => array(0x569), 0x53A   => array(0x56A)
+                    ,0x53B   => array(0x56B), 0x53C   => array(0x56C), 0x53D   => array(0x56D)
+                    ,0x53E   => array(0x56E), 0x53F   => array(0x56F), 0x540   => array(0x570)
+                    ,0x541   => array(0x571), 0x542   => array(0x572), 0x543   => array(0x573)
+                    ,0x544   => array(0x574), 0x545   => array(0x575), 0x546   => array(0x576)
+                    ,0x547   => array(0x577), 0x548   => array(0x578), 0x549   => array(0x579)
+                    ,0x54A   => array(0x57A), 0x54B   => array(0x57B), 0x54C   => array(0x57C)
+                    ,0x54D   => array(0x57D), 0x54E   => array(0x57E), 0x54F   => array(0x57F)
+                    ,0x550   => array(0x580), 0x551   => array(0x581), 0x552   => array(0x582)
+                    ,0x553   => array(0x583), 0x554   => array(0x584), 0x555   => array(0x585)
+                    ,0x556 => array(0x586), 0x587 => array(0x565, 0x582), 0xE33 => array(0xE4D, 0xE32)
+                    ,0x1E00  => array(0x1E01), 0x1E02  => array(0x1E03), 0x1E04  => array(0x1E05)
+                    ,0x1E06  => array(0x1E07), 0x1E08  => array(0x1E09), 0x1E0A  => array(0x1E0B)
+                    ,0x1E0C  => array(0x1E0D), 0x1E0E  => array(0x1E0F), 0x1E10  => array(0x1E11)
+                    ,0x1E12  => array(0x1E13), 0x1E14  => array(0x1E15), 0x1E16  => array(0x1E17)
+                    ,0x1E18  => array(0x1E19), 0x1E1A  => array(0x1E1B), 0x1E1C  => array(0x1E1D)
+                    ,0x1E1E  => array(0x1E1F), 0x1E20  => array(0x1E21), 0x1E22  => array(0x1E23)
+                    ,0x1E24  => array(0x1E25), 0x1E26  => array(0x1E27), 0x1E28  => array(0x1E29)
+                    ,0x1E2A  => array(0x1E2B), 0x1E2C  => array(0x1E2D), 0x1E2E  => array(0x1E2F)
+                    ,0x1E30  => array(0x1E31), 0x1E32  => array(0x1E33), 0x1E34  => array(0x1E35)
+                    ,0x1E36  => array(0x1E37), 0x1E38  => array(0x1E39), 0x1E3A  => array(0x1E3B)
+                    ,0x1E3C  => array(0x1E3D), 0x1E3E  => array(0x1E3F), 0x1E40  => array(0x1E41)
+                    ,0x1E42  => array(0x1E43), 0x1E44  => array(0x1E45), 0x1E46  => array(0x1E47)
+                    ,0x1E48  => array(0x1E49), 0x1E4A  => array(0x1E4B), 0x1E4C  => array(0x1E4D)
+                    ,0x1E4E  => array(0x1E4F), 0x1E50  => array(0x1E51), 0x1E52  => array(0x1E53)
+                    ,0x1E54  => array(0x1E55), 0x1E56  => array(0x1E57), 0x1E58  => array(0x1E59)
+                    ,0x1E5A  => array(0x1E5B), 0x1E5C  => array(0x1E5D), 0x1E5E  => array(0x1E5F)
+                    ,0x1E60  => array(0x1E61), 0x1E62  => array(0x1E63), 0x1E64  => array(0x1E65)
+                    ,0x1E66  => array(0x1E67), 0x1E68  => array(0x1E69), 0x1E6A  => array(0x1E6B)
+                    ,0x1E6C  => array(0x1E6D), 0x1E6E  => array(0x1E6F), 0x1E70  => array(0x1E71)
+                    ,0x1E72  => array(0x1E73), 0x1E74  => array(0x1E75), 0x1E76  => array(0x1E77)
+                    ,0x1E78  => array(0x1E79), 0x1E7A  => array(0x1E7B), 0x1E7C  => array(0x1E7D)
+                    ,0x1E7E  => array(0x1E7F), 0x1E80  => array(0x1E81), 0x1E82  => array(0x1E83)
+                    ,0x1E84  => array(0x1E85), 0x1E86  => array(0x1E87), 0x1E88  => array(0x1E89)
+                    ,0x1E8A  => array(0x1E8B), 0x1E8C  => array(0x1E8D), 0x1E8E  => array(0x1E8F)
+                    ,0x1E90  => array(0x1E91), 0x1E92  => array(0x1E93), 0x1E94  => array(0x1E95)
+                    ,0x1E96  => array(0x68, 0x331), 0x1E97  => array(0x74, 0x308), 0x1E98  => array(0x77, 0x30A)
+                    ,0x1E99  => array(0x79, 0x30A), 0x1E9A  => array(0x61, 0x2BE), 0x1E9B  => array(0x1E61)
+                    ,0x1EA0  => array(0x1EA1), 0x1EA2  => array(0x1EA3), 0x1EA4  => array(0x1EA5)
+                    ,0x1EA6  => array(0x1EA7), 0x1EA8  => array(0x1EA9), 0x1EAA  => array(0x1EAB)
+                    ,0x1EAC  => array(0x1EAD), 0x1EAE  => array(0x1EAF), 0x1EB0  => array(0x1EB1)
+                    ,0x1EB2  => array(0x1EB3), 0x1EB4  => array(0x1EB5), 0x1EB6  => array(0x1EB7)
+                    ,0x1EB8  => array(0x1EB9), 0x1EBA  => array(0x1EBB), 0x1EBC  => array(0x1EBD)
+                    ,0x1EBE  => array(0x1EBF), 0x1EC0  => array(0x1EC1), 0x1EC2  => array(0x1EC3)
+                    ,0x1EC4  => array(0x1EC5), 0x1EC6  => array(0x1EC7), 0x1EC8  => array(0x1EC9)
+                    ,0x1ECA  => array(0x1ECB), 0x1ECC  => array(0x1ECD), 0x1ECE  => array(0x1ECF)
+                    ,0x1ED0  => array(0x1ED1), 0x1ED2  => array(0x1ED3), 0x1ED4  => array(0x1ED5)
+                    ,0x1ED6  => array(0x1ED7), 0x1ED8  => array(0x1ED9), 0x1EDA  => array(0x1EDB)
+                    ,0x1EDC  => array(0x1EDD), 0x1EDE  => array(0x1EDF), 0x1EE0  => array(0x1EE1)
+                    ,0x1EE2  => array(0x1EE3), 0x1EE4  => array(0x1EE5), 0x1EE6  => array(0x1EE7)
+                    ,0x1EE8  => array(0x1EE9), 0x1EEA  => array(0x1EEB), 0x1EEC  => array(0x1EED)
+                    ,0x1EEE  => array(0x1EEF), 0x1EF0  => array(0x1EF1), 0x1EF2  => array(0x1EF3)
+                    ,0x1EF4  => array(0x1EF5), 0x1EF6  => array(0x1EF7), 0x1EF8  => array(0x1EF9)
+                    ,0x1F08  => array(0x1F00), 0x1F09  => array(0x1F01), 0x1F0A  => array(0x1F02)
+                    ,0x1F0B  => array(0x1F03), 0x1F0C  => array(0x1F04), 0x1F0D  => array(0x1F05)
+                    ,0x1F0E  => array(0x1F06), 0x1F0F  => array(0x1F07), 0x1F18  => array(0x1F10)
+                    ,0x1F19  => array(0x1F11), 0x1F1A  => array(0x1F12), 0x1F1B  => array(0x1F13)
+                    ,0x1F1C  => array(0x1F14), 0x1F1D  => array(0x1F15), 0x1F28  => array(0x1F20)
+                    ,0x1F29  => array(0x1F21), 0x1F2A  => array(0x1F22), 0x1F2B  => array(0x1F23)
+                    ,0x1F2C  => array(0x1F24), 0x1F2D  => array(0x1F25), 0x1F2E  => array(0x1F26)
+                    ,0x1F2F  => array(0x1F27), 0x1F38  => array(0x1F30), 0x1F39  => array(0x1F31)
+                    ,0x1F3A  => array(0x1F32), 0x1F3B  => array(0x1F33), 0x1F3C  => array(0x1F34)
+                    ,0x1F3D  => array(0x1F35), 0x1F3E  => array(0x1F36), 0x1F3F  => array(0x1F37)
+                    ,0x1F48  => array(0x1F40), 0x1F49  => array(0x1F41), 0x1F4A  => array(0x1F42)
+                    ,0x1F4B  => array(0x1F43), 0x1F4C  => array(0x1F44), 0x1F4D  => array(0x1F45)
+                    ,0x1F50  => array(0x3C5, 0x313), 0x1F52  => array(0x3C5, 0x313, 0x300)
+                    ,0x1F54  => array(0x3C5, 0x313, 0x301), 0x1F56  => array(0x3C5, 0x313, 0x342)
+                    ,0x1F59  => array(0x1F51), 0x1F5B  => array(0x1F53), 0x1F5D  => array(0x1F55)
+                    ,0x1F5F  => array(0x1F57), 0x1F68  => array(0x1F60), 0x1F69  => array(0x1F61)
+                    ,0x1F6A  => array(0x1F62), 0x1F6B  => array(0x1F63), 0x1F6C  => array(0x1F64)
+                    ,0x1F6D  => array(0x1F65), 0x1F6E  => array(0x1F66), 0x1F6F  => array(0x1F67)
+                    ,0x1F80  => array(0x1F00, 0x3B9), 0x1F81  => array(0x1F01, 0x3B9)
+                    ,0x1F82  => array(0x1F02, 0x3B9), 0x1F83  => array(0x1F03, 0x3B9)
+                    ,0x1F84  => array(0x1F04, 0x3B9), 0x1F85  => array(0x1F05, 0x3B9)
+                    ,0x1F86  => array(0x1F06, 0x3B9), 0x1F87  => array(0x1F07, 0x3B9)
+                    ,0x1F88  => array(0x1F00, 0x3B9), 0x1F89  => array(0x1F01, 0x3B9)
+                    ,0x1F8A  => array(0x1F02, 0x3B9), 0x1F8B  => array(0x1F03, 0x3B9)
+                    ,0x1F8C  => array(0x1F04, 0x3B9), 0x1F8D  => array(0x1F05, 0x3B9)
+                    ,0x1F8E  => array(0x1F06, 0x3B9), 0x1F8F  => array(0x1F07, 0x3B9)
+                    ,0x1F90  => array(0x1F20, 0x3B9), 0x1F91  => array(0x1F21, 0x3B9)
+                    ,0x1F92  => array(0x1F22, 0x3B9), 0x1F93  => array(0x1F23, 0x3B9)
+                    ,0x1F94  => array(0x1F24, 0x3B9), 0x1F95  => array(0x1F25, 0x3B9)
+                    ,0x1F96  => array(0x1F26, 0x3B9), 0x1F97  => array(0x1F27, 0x3B9)
+                    ,0x1F98  => array(0x1F20, 0x3B9), 0x1F99  => array(0x1F21, 0x3B9)
+                    ,0x1F9A  => array(0x1F22, 0x3B9), 0x1F9B  => array(0x1F23, 0x3B9)
+                    ,0x1F9C  => array(0x1F24, 0x3B9), 0x1F9D  => array(0x1F25, 0x3B9)
+                    ,0x1F9E  => array(0x1F26, 0x3B9), 0x1F9F  => array(0x1F27, 0x3B9)
+                    ,0x1FA0  => array(0x1F60, 0x3B9), 0x1FA1  => array(0x1F61, 0x3B9)
+                    ,0x1FA2  => array(0x1F62, 0x3B9), 0x1FA3  => array(0x1F63, 0x3B9)
+                    ,0x1FA4  => array(0x1F64, 0x3B9), 0x1FA5  => array(0x1F65, 0x3B9)
+                    ,0x1FA6  => array(0x1F66, 0x3B9), 0x1FA7  => array(0x1F67, 0x3B9)
+                    ,0x1FA8  => array(0x1F60, 0x3B9), 0x1FA9  => array(0x1F61, 0x3B9)
+                    ,0x1FAA  => array(0x1F62, 0x3B9), 0x1FAB  => array(0x1F63, 0x3B9)
+                    ,0x1FAC  => array(0x1F64, 0x3B9), 0x1FAD  => array(0x1F65, 0x3B9)
+                    ,0x1FAE  => array(0x1F66, 0x3B9), 0x1FAF  => array(0x1F67, 0x3B9)
+                    ,0x1FB2  => array(0x1F70, 0x3B9), 0x1FB3  => array(0x3B1, 0x3B9)
+                    ,0x1FB4  => array(0x3AC, 0x3B9), 0x1FB6  => array(0x3B1, 0x342)
+                    ,0x1FB7  => array(0x3B1, 0x342, 0x3B9), 0x1FB8  => array(0x1FB0)
+                    ,0x1FB9  => array(0x1FB1), 0x1FBA  => array(0x1F70), 0x1FBB  => array(0x1F71)
+                    ,0x1FBC  => array(0x3B1, 0x3B9), 0x1FBE  => array(0x3B9)
+                    ,0x1FC2  => array(0x1F74, 0x3B9), 0x1FC3  => array(0x3B7, 0x3B9)
+                    ,0x1FC4  => array(0x3AE, 0x3B9), 0x1FC6  => array(0x3B7, 0x342)
+                    ,0x1FC7  => array(0x3B7, 0x342, 0x3B9), 0x1FC8  => array(0x1F72)
+                    ,0x1FC9  => array(0x1F73), 0x1FCA  => array(0x1F74), 0x1FCB  => array(0x1F75)
+                    ,0x1FCC  => array(0x3B7, 0x3B9), 0x1FD2  => array(0x3B9, 0x308, 0x300)
+                    ,0x1FD3  => array(0x3B9, 0x308, 0x301), 0x1FD6  => array(0x3B9, 0x342)
+                    ,0x1FD7  => array(0x3B9, 0x308, 0x342), 0x1FD8  => array(0x1FD0)
+                    ,0x1FD9  => array(0x1FD1), 0x1FDA  => array(0x1F76)
+                    ,0x1FDB  => array(0x1F77), 0x1FE2  => array(0x3C5, 0x308, 0x300)
+                    ,0x1FE3  => array(0x3C5, 0x308, 0x301), 0x1FE4  => array(0x3C1, 0x313)
+                    ,0x1FE6  => array(0x3C5, 0x342), 0x1FE7  => array(0x3C5, 0x308, 0x342)
+                    ,0x1FE8  => array(0x1FE0), 0x1FE9  => array(0x1FE1)
+                    ,0x1FEA  => array(0x1F7A), 0x1FEB  => array(0x1F7B)
+                    ,0x1FEC  => array(0x1FE5), 0x1FF2  => array(0x1F7C, 0x3B9)
+                    ,0x1FF3  => array(0x3C9, 0x3B9), 0x1FF4  => array(0x3CE, 0x3B9)
+                    ,0x1FF6  => array(0x3C9, 0x342), 0x1FF7  => array(0x3C9, 0x342, 0x3B9)
+                    ,0x1FF8  => array(0x1F78), 0x1FF9  => array(0x1F79), 0x1FFA  => array(0x1F7C)
+                    ,0x1FFB  => array(0x1F7D), 0x1FFC  => array(0x3C9, 0x3B9)
+                    ,0x20A8  => array(0x72, 0x73), 0x2102  => array(0x63), 0x2103  => array(0xB0, 0x63)
+                    ,0x2107  => array(0x25B), 0x2109  => array(0xB0, 0x66), 0x210B  => array(0x68)
+                    ,0x210C  => array(0x68), 0x210D  => array(0x68), 0x2110  => array(0x69)
+                    ,0x2111  => array(0x69), 0x2112  => array(0x6C), 0x2115  => array(0x6E)
+                    ,0x2116  => array(0x6E, 0x6F), 0x2119  => array(0x70), 0x211A  => array(0x71)
+                    ,0x211B  => array(0x72), 0x211C  => array(0x72), 0x211D  => array(0x72)
+                    ,0x2120  => array(0x73, 0x6D), 0x2121  => array(0x74, 0x65, 0x6C)
+                    ,0x2122  => array(0x74, 0x6D), 0x2124  => array(0x7A), 0x2126  => array(0x3C9)
+                    ,0x2128  => array(0x7A), 0x212A  => array(0x6B), 0x212B  => array(0xE5)
+                    ,0x212C  => array(0x62), 0x212D  => array(0x63), 0x2130  => array(0x65)
+                    ,0x2131  => array(0x66), 0x2133  => array(0x6D), 0x213E  => array(0x3B3)
+                    ,0x213F  => array(0x3C0), 0x2145  => array(0x64) ,0x2160  => array(0x2170)
+                    ,0x2161  => array(0x2171), 0x2162  => array(0x2172), 0x2163  => array(0x2173)
+                    ,0x2164  => array(0x2174), 0x2165  => array(0x2175), 0x2166  => array(0x2176)
+                    ,0x2167  => array(0x2177), 0x2168  => array(0x2178), 0x2169  => array(0x2179)
+                    ,0x216A  => array(0x217A), 0x216B  => array(0x217B), 0x216C  => array(0x217C)
+                    ,0x216D  => array(0x217D), 0x216E  => array(0x217E), 0x216F  => array(0x217F)
+                    ,0x24B6  => array(0x24D0), 0x24B7  => array(0x24D1), 0x24B8  => array(0x24D2)
+                    ,0x24B9  => array(0x24D3), 0x24BA  => array(0x24D4), 0x24BB  => array(0x24D5)
+                    ,0x24BC  => array(0x24D6), 0x24BD  => array(0x24D7), 0x24BE  => array(0x24D8)
+                    ,0x24BF  => array(0x24D9), 0x24C0  => array(0x24DA), 0x24C1  => array(0x24DB)
+                    ,0x24C2  => array(0x24DC), 0x24C3  => array(0x24DD), 0x24C4  => array(0x24DE)
+                    ,0x24C5  => array(0x24DF), 0x24C6  => array(0x24E0), 0x24C7  => array(0x24E1)
+                    ,0x24C8  => array(0x24E2), 0x24C9  => array(0x24E3), 0x24CA  => array(0x24E4)
+                    ,0x24CB  => array(0x24E5), 0x24CC  => array(0x24E6), 0x24CD  => array(0x24E7)
+                    ,0x24CE  => array(0x24E8), 0x24CF  => array(0x24E9), 0x3371  => array(0x68, 0x70, 0x61)
+                    ,0x3373  => array(0x61, 0x75), 0x3375  => array(0x6F, 0x76)
+                    ,0x3380  => array(0x70, 0x61), 0x3381  => array(0x6E, 0x61)
+                    ,0x3382  => array(0x3BC, 0x61), 0x3383  => array(0x6D, 0x61)
+                    ,0x3384  => array(0x6B, 0x61), 0x3385  => array(0x6B, 0x62)
+                    ,0x3386  => array(0x6D, 0x62), 0x3387  => array(0x67, 0x62)
+                    ,0x338A  => array(0x70, 0x66), 0x338B  => array(0x6E, 0x66)
+                    ,0x338C  => array(0x3BC, 0x66), 0x3390  => array(0x68, 0x7A)
+                    ,0x3391  => array(0x6B, 0x68, 0x7A), 0x3392  => array(0x6D, 0x68, 0x7A)
+                    ,0x3393  => array(0x67, 0x68, 0x7A), 0x3394  => array(0x74, 0x68, 0x7A)
+                    ,0x33A9  => array(0x70, 0x61), 0x33AA  => array(0x6B, 0x70, 0x61)
+                    ,0x33AB  => array(0x6D, 0x70, 0x61), 0x33AC  => array(0x67, 0x70, 0x61)
+                    ,0x33B4  => array(0x70, 0x76), 0x33B5  => array(0x6E, 0x76)
+                    ,0x33B6  => array(0x3BC, 0x76), 0x33B7  => array(0x6D, 0x76)
+                    ,0x33B8  => array(0x6B, 0x76), 0x33B9  => array(0x6D, 0x76)
+                    ,0x33BA  => array(0x70, 0x77), 0x33BB  => array(0x6E, 0x77)
+                    ,0x33BC  => array(0x3BC, 0x77), 0x33BD  => array(0x6D, 0x77)
+                    ,0x33BE  => array(0x6B, 0x77), 0x33BF  => array(0x6D, 0x77)
+                    ,0x33C0  => array(0x6B, 0x3C9), 0x33C1  => array(0x6D, 0x3C9) /*
+                    ,0x33C2  => array(0x61, 0x2E, 0x6D, 0x2E) */
+                    ,0x33C3  => array(0x62, 0x71), 0x33C6  => array(0x63, 0x2215, 0x6B, 0x67)
+                    ,0x33C7  => array(0x63, 0x6F, 0x2E), 0x33C8  => array(0x64, 0x62)
+                    ,0x33C9  => array(0x67, 0x79), 0x33CB  => array(0x68, 0x70)
+                    ,0x33CD  => array(0x6B, 0x6B), 0x33CE  => array(0x6B, 0x6D)
+                    ,0x33D7  => array(0x70, 0x68), 0x33D9  => array(0x70, 0x70, 0x6D)
+                    ,0x33DA  => array(0x70, 0x72), 0x33DC  => array(0x73, 0x76)
+                    ,0x33DD  => array(0x77, 0x62), 0xFB00  => array(0x66, 0x66)
+                    ,0xFB01  => array(0x66, 0x69), 0xFB02  => array(0x66, 0x6C)
+                    ,0xFB03  => array(0x66, 0x66, 0x69), 0xFB04  => array(0x66, 0x66, 0x6C)
+                    ,0xFB05  => array(0x73, 0x74), 0xFB06  => array(0x73, 0x74)
+                    ,0xFB13  => array(0x574, 0x576), 0xFB14  => array(0x574, 0x565)
+                    ,0xFB15  => array(0x574, 0x56B), 0xFB16  => array(0x57E, 0x576)
+                    ,0xFB17  => array(0x574, 0x56D), 0xFF21  => array(0xFF41)
+                    ,0xFF22  => array(0xFF42), 0xFF23  => array(0xFF43), 0xFF24  => array(0xFF44)
+                    ,0xFF25  => array(0xFF45), 0xFF26  => array(0xFF46), 0xFF27  => array(0xFF47)
+                    ,0xFF28  => array(0xFF48), 0xFF29  => array(0xFF49), 0xFF2A  => array(0xFF4A)
+                    ,0xFF2B  => array(0xFF4B), 0xFF2C  => array(0xFF4C), 0xFF2D  => array(0xFF4D)
+                    ,0xFF2E  => array(0xFF4E), 0xFF2F  => array(0xFF4F), 0xFF30  => array(0xFF50)
+                    ,0xFF31  => array(0xFF51), 0xFF32  => array(0xFF52), 0xFF33  => array(0xFF53)
+                    ,0xFF34  => array(0xFF54), 0xFF35  => array(0xFF55), 0xFF36  => array(0xFF56)
+                    ,0xFF37  => array(0xFF57), 0xFF38  => array(0xFF58), 0xFF39  => array(0xFF59)
+                    ,0xFF3A  => array(0xFF5A), 0x10400 => array(0x10428), 0x10401 => array(0x10429)
+                    ,0x10402 => array(0x1042A), 0x10403 => array(0x1042B), 0x10404 => array(0x1042C)
+                    ,0x10405 => array(0x1042D), 0x10406 => array(0x1042E), 0x10407 => array(0x1042F)
+                    ,0x10408 => array(0x10430), 0x10409 => array(0x10431), 0x1040A => array(0x10432)
+                    ,0x1040B => array(0x10433), 0x1040C => array(0x10434), 0x1040D => array(0x10435)
+                    ,0x1040E => array(0x10436), 0x1040F => array(0x10437), 0x10410 => array(0x10438)
+                    ,0x10411 => array(0x10439), 0x10412 => array(0x1043A), 0x10413 => array(0x1043B)
+                    ,0x10414 => array(0x1043C), 0x10415 => array(0x1043D), 0x10416 => array(0x1043E)
+                    ,0x10417 => array(0x1043F), 0x10418 => array(0x10440), 0x10419 => array(0x10441)
+                    ,0x1041A => array(0x10442), 0x1041B => array(0x10443), 0x1041C => array(0x10444)
+                    ,0x1041D => array(0x10445), 0x1041E => array(0x10446), 0x1041F => array(0x10447)
+                    ,0x10420 => array(0x10448), 0x10421 => array(0x10449), 0x10422 => array(0x1044A)
+                    ,0x10423 => array(0x1044B), 0x10424 => array(0x1044C), 0x10425 => array(0x1044D)
+                    ,0x1D400 => array(0x61), 0x1D401 => array(0x62), 0x1D402 => array(0x63)
+                    ,0x1D403 => array(0x64), 0x1D404 => array(0x65), 0x1D405 => array(0x66)
+                    ,0x1D406 => array(0x67), 0x1D407 => array(0x68), 0x1D408 => array(0x69)
+                    ,0x1D409 => array(0x6A), 0x1D40A => array(0x6B), 0x1D40B => array(0x6C)
+                    ,0x1D40C => array(0x6D), 0x1D40D => array(0x6E), 0x1D40E => array(0x6F)
+                    ,0x1D40F => array(0x70), 0x1D410 => array(0x71), 0x1D411 => array(0x72)
+                    ,0x1D412 => array(0x73), 0x1D413 => array(0x74), 0x1D414 => array(0x75)
+                    ,0x1D415 => array(0x76), 0x1D416 => array(0x77), 0x1D417 => array(0x78)
+                    ,0x1D418 => array(0x79), 0x1D419 => array(0x7A), 0x1D434 => array(0x61)
+                    ,0x1D435 => array(0x62), 0x1D436 => array(0x63), 0x1D437 => array(0x64)
+                    ,0x1D438 => array(0x65), 0x1D439 => array(0x66), 0x1D43A => array(0x67)
+                    ,0x1D43B => array(0x68), 0x1D43C => array(0x69), 0x1D43D => array(0x6A)
+                    ,0x1D43E => array(0x6B), 0x1D43F => array(0x6C), 0x1D440 => array(0x6D)
+                    ,0x1D441 => array(0x6E), 0x1D442 => array(0x6F), 0x1D443 => array(0x70)
+                    ,0x1D444 => array(0x71), 0x1D445 => array(0x72), 0x1D446 => array(0x73)
+                    ,0x1D447 => array(0x74), 0x1D448 => array(0x75), 0x1D449 => array(0x76)
+                    ,0x1D44A => array(0x77), 0x1D44B => array(0x78), 0x1D44C => array(0x79)
+                    ,0x1D44D => array(0x7A), 0x1D468 => array(0x61), 0x1D469 => array(0x62)
+                    ,0x1D46A => array(0x63), 0x1D46B => array(0x64), 0x1D46C => array(0x65)
+                    ,0x1D46D => array(0x66), 0x1D46E => array(0x67), 0x1D46F => array(0x68)
+                    ,0x1D470 => array(0x69), 0x1D471 => array(0x6A), 0x1D472 => array(0x6B)
+                    ,0x1D473 => array(0x6C), 0x1D474 => array(0x6D), 0x1D475 => array(0x6E)
+                    ,0x1D476 => array(0x6F), 0x1D477 => array(0x70), 0x1D478 => array(0x71)
+                    ,0x1D479 => array(0x72), 0x1D47A => array(0x73), 0x1D47B => array(0x74)
+                    ,0x1D47C => array(0x75), 0x1D47D => array(0x76), 0x1D47E => array(0x77)
+                    ,0x1D47F => array(0x78), 0x1D480 => array(0x79), 0x1D481 => array(0x7A)
+                    ,0x1D49C => array(0x61), 0x1D49E => array(0x63), 0x1D49F => array(0x64)
+                    ,0x1D4A2 => array(0x67), 0x1D4A5 => array(0x6A), 0x1D4A6 => array(0x6B)
+                    ,0x1D4A9 => array(0x6E), 0x1D4AA => array(0x6F), 0x1D4AB => array(0x70)
+                    ,0x1D4AC => array(0x71), 0x1D4AE => array(0x73), 0x1D4AF => array(0x74)
+                    ,0x1D4B0 => array(0x75), 0x1D4B1 => array(0x76), 0x1D4B2 => array(0x77)
+                    ,0x1D4B3 => array(0x78), 0x1D4B4 => array(0x79), 0x1D4B5 => array(0x7A)
+                    ,0x1D4D0 => array(0x61), 0x1D4D1 => array(0x62), 0x1D4D2 => array(0x63)
+                    ,0x1D4D3 => array(0x64), 0x1D4D4 => array(0x65), 0x1D4D5 => array(0x66)
+                    ,0x1D4D6 => array(0x67), 0x1D4D7 => array(0x68), 0x1D4D8 => array(0x69)
+                    ,0x1D4D9 => array(0x6A), 0x1D4DA => array(0x6B), 0x1D4DB => array(0x6C)
+                    ,0x1D4DC => array(0x6D), 0x1D4DD => array(0x6E), 0x1D4DE => array(0x6F)
+                    ,0x1D4DF => array(0x70), 0x1D4E0 => array(0x71), 0x1D4E1 => array(0x72)
+                    ,0x1D4E2 => array(0x73), 0x1D4E3 => array(0x74), 0x1D4E4 => array(0x75)
+                    ,0x1D4E5 => array(0x76), 0x1D4E6 => array(0x77), 0x1D4E7 => array(0x78)
+                    ,0x1D4E8 => array(0x79), 0x1D4E9 => array(0x7A), 0x1D504 => array(0x61)
+                    ,0x1D505 => array(0x62), 0x1D507 => array(0x64), 0x1D508 => array(0x65)
+                    ,0x1D509 => array(0x66), 0x1D50A => array(0x67), 0x1D50D => array(0x6A)
+                    ,0x1D50E => array(0x6B), 0x1D50F => array(0x6C), 0x1D510 => array(0x6D)
+                    ,0x1D511 => array(0x6E), 0x1D512 => array(0x6F), 0x1D513 => array(0x70)
+                    ,0x1D514 => array(0x71), 0x1D516 => array(0x73), 0x1D517 => array(0x74)
+                    ,0x1D518 => array(0x75), 0x1D519 => array(0x76), 0x1D51A => array(0x77)
+                    ,0x1D51B => array(0x78), 0x1D51C => array(0x79), 0x1D538 => array(0x61)
+                    ,0x1D539 => array(0x62), 0x1D53B => array(0x64), 0x1D53C => array(0x65)
+                    ,0x1D53D => array(0x66), 0x1D53E => array(0x67), 0x1D540 => array(0x69)
+                    ,0x1D541 => array(0x6A), 0x1D542 => array(0x6B), 0x1D543 => array(0x6C)
+                    ,0x1D544 => array(0x6D), 0x1D546 => array(0x6F), 0x1D54A => array(0x73)
+                    ,0x1D54B => array(0x74), 0x1D54C => array(0x75), 0x1D54D => array(0x76)
+                    ,0x1D54E => array(0x77), 0x1D54F => array(0x78), 0x1D550 => array(0x79)
+                    ,0x1D56C => array(0x61), 0x1D56D => array(0x62), 0x1D56E => array(0x63)
+                    ,0x1D56F => array(0x64), 0x1D570 => array(0x65), 0x1D571 => array(0x66)
+                    ,0x1D572 => array(0x67), 0x1D573 => array(0x68), 0x1D574 => array(0x69)
+                    ,0x1D575 => array(0x6A), 0x1D576 => array(0x6B), 0x1D577 => array(0x6C)
+                    ,0x1D578 => array(0x6D), 0x1D579 => array(0x6E), 0x1D57A => array(0x6F)
+                    ,0x1D57B => array(0x70), 0x1D57C => array(0x71), 0x1D57D => array(0x72)
+                    ,0x1D57E => array(0x73), 0x1D57F => array(0x74), 0x1D580 => array(0x75)
+                    ,0x1D581 => array(0x76), 0x1D582 => array(0x77), 0x1D583 => array(0x78)
+                    ,0x1D584 => array(0x79), 0x1D585 => array(0x7A), 0x1D5A0 => array(0x61)
+                    ,0x1D5A1 => array(0x62), 0x1D5A2 => array(0x63), 0x1D5A3 => array(0x64)
+                    ,0x1D5A4 => array(0x65), 0x1D5A5 => array(0x66), 0x1D5A6 => array(0x67)
+                    ,0x1D5A7 => array(0x68), 0x1D5A8 => array(0x69), 0x1D5A9 => array(0x6A)
+                    ,0x1D5AA => array(0x6B), 0x1D5AB => array(0x6C), 0x1D5AC => array(0x6D)
+                    ,0x1D5AD => array(0x6E), 0x1D5AE => array(0x6F), 0x1D5AF => array(0x70)
+                    ,0x1D5B0 => array(0x71), 0x1D5B1 => array(0x72), 0x1D5B2 => array(0x73)
+                    ,0x1D5B3 => array(0x74), 0x1D5B4 => array(0x75), 0x1D5B5 => array(0x76)
+                    ,0x1D5B6 => array(0x77), 0x1D5B7 => array(0x78), 0x1D5B8 => array(0x79)
+                    ,0x1D5B9 => array(0x7A), 0x1D5D4 => array(0x61), 0x1D5D5 => array(0x62)
+                    ,0x1D5D6 => array(0x63), 0x1D5D7 => array(0x64), 0x1D5D8 => array(0x65)
+                    ,0x1D5D9 => array(0x66), 0x1D5DA => array(0x67), 0x1D5DB => array(0x68)
+                    ,0x1D5DC => array(0x69), 0x1D5DD => array(0x6A), 0x1D5DE => array(0x6B)
+                    ,0x1D5DF => array(0x6C), 0x1D5E0 => array(0x6D), 0x1D5E1 => array(0x6E)
+                    ,0x1D5E2 => array(0x6F), 0x1D5E3 => array(0x70), 0x1D5E4 => array(0x71)
+                    ,0x1D5E5 => array(0x72), 0x1D5E6 => array(0x73), 0x1D5E7 => array(0x74)
+                    ,0x1D5E8 => array(0x75), 0x1D5E9 => array(0x76), 0x1D5EA => array(0x77)
+                    ,0x1D5EB => array(0x78), 0x1D5EC => array(0x79), 0x1D5ED => array(0x7A)
+                    ,0x1D608 => array(0x61), 0x1D609 => array(0x62) ,0x1D60A => array(0x63)
+                    ,0x1D60B => array(0x64), 0x1D60C => array(0x65), 0x1D60D => array(0x66)
+                    ,0x1D60E => array(0x67), 0x1D60F => array(0x68), 0x1D610 => array(0x69)
+                    ,0x1D611 => array(0x6A), 0x1D612 => array(0x6B), 0x1D613 => array(0x6C)
+                    ,0x1D614 => array(0x6D), 0x1D615 => array(0x6E), 0x1D616 => array(0x6F)
+                    ,0x1D617 => array(0x70), 0x1D618 => array(0x71), 0x1D619 => array(0x72)
+                    ,0x1D61A => array(0x73), 0x1D61B => array(0x74), 0x1D61C => array(0x75)
+                    ,0x1D61D => array(0x76), 0x1D61E => array(0x77), 0x1D61F => array(0x78)
+                    ,0x1D620 => array(0x79), 0x1D621 => array(0x7A), 0x1D63C => array(0x61)
+                    ,0x1D63D => array(0x62), 0x1D63E => array(0x63), 0x1D63F => array(0x64)
+                    ,0x1D640 => array(0x65), 0x1D641 => array(0x66), 0x1D642 => array(0x67)
+                    ,0x1D643 => array(0x68), 0x1D644 => array(0x69), 0x1D645 => array(0x6A)
+                    ,0x1D646 => array(0x6B), 0x1D647 => array(0x6C), 0x1D648 => array(0x6D)
+                    ,0x1D649 => array(0x6E), 0x1D64A => array(0x6F), 0x1D64B => array(0x70)
+                    ,0x1D64C => array(0x71), 0x1D64D => array(0x72), 0x1D64E => array(0x73)
+                    ,0x1D64F => array(0x74), 0x1D650 => array(0x75), 0x1D651 => array(0x76)
+                    ,0x1D652 => array(0x77), 0x1D653 => array(0x78), 0x1D654 => array(0x79)
+                    ,0x1D655 => array(0x7A), 0x1D670 => array(0x61), 0x1D671 => array(0x62)
+                    ,0x1D672 => array(0x63), 0x1D673 => array(0x64), 0x1D674 => array(0x65)
+                    ,0x1D675 => array(0x66), 0x1D676 => array(0x67), 0x1D677 => array(0x68)
+                    ,0x1D678 => array(0x69), 0x1D679 => array(0x6A), 0x1D67A => array(0x6B)
+                    ,0x1D67B => array(0x6C), 0x1D67C => array(0x6D), 0x1D67D => array(0x6E)
+                    ,0x1D67E => array(0x6F), 0x1D67F => array(0x70), 0x1D680 => array(0x71)
+                    ,0x1D681 => array(0x72), 0x1D682 => array(0x73), 0x1D683 => array(0x74)
+                    ,0x1D684 => array(0x75), 0x1D685 => array(0x76), 0x1D686 => array(0x77)
+                    ,0x1D687 => array(0x78), 0x1D688 => array(0x79), 0x1D689 => array(0x7A)
+                    ,0x1D6A8 => array(0x3B1), 0x1D6A9 => array(0x3B2), 0x1D6AA => array(0x3B3)
+                    ,0x1D6AB => array(0x3B4), 0x1D6AC => array(0x3B5), 0x1D6AD => array(0x3B6)
+                    ,0x1D6AE => array(0x3B7), 0x1D6AF => array(0x3B8), 0x1D6B0 => array(0x3B9)
+                    ,0x1D6B1 => array(0x3BA), 0x1D6B2 => array(0x3BB), 0x1D6B3 => array(0x3BC)
+                    ,0x1D6B4 => array(0x3BD), 0x1D6B5 => array(0x3BE), 0x1D6B6 => array(0x3BF)
+                    ,0x1D6B7 => array(0x3C0), 0x1D6B8 => array(0x3C1), 0x1D6B9 => array(0x3B8)
+                    ,0x1D6BA => array(0x3C3), 0x1D6BB => array(0x3C4), 0x1D6BC => array(0x3C5)
+                    ,0x1D6BD => array(0x3C6), 0x1D6BE => array(0x3C7), 0x1D6BF => array(0x3C8)
+                    ,0x1D6C0 => array(0x3C9), 0x1D6D3 => array(0x3C3), 0x1D6E2 => array(0x3B1)
+                    ,0x1D6E3 => array(0x3B2), 0x1D6E4 => array(0x3B3), 0x1D6E5 => array(0x3B4)
+                    ,0x1D6E6 => array(0x3B5), 0x1D6E7 => array(0x3B6), 0x1D6E8 => array(0x3B7)
+                    ,0x1D6E9 => array(0x3B8), 0x1D6EA => array(0x3B9), 0x1D6EB => array(0x3BA)
+                    ,0x1D6EC => array(0x3BB), 0x1D6ED => array(0x3BC), 0x1D6EE => array(0x3BD)
+                    ,0x1D6EF => array(0x3BE), 0x1D6F0 => array(0x3BF), 0x1D6F1 => array(0x3C0)
+                    ,0x1D6F2 => array(0x3C1), 0x1D6F3 => array(0x3B8) ,0x1D6F4 => array(0x3C3)
+                    ,0x1D6F5 => array(0x3C4), 0x1D6F6 => array(0x3C5), 0x1D6F7 => array(0x3C6)
+                    ,0x1D6F8 => array(0x3C7), 0x1D6F9 => array(0x3C8) ,0x1D6FA => array(0x3C9)
+                    ,0x1D70D => array(0x3C3), 0x1D71C => array(0x3B1), 0x1D71D => array(0x3B2)
+                    ,0x1D71E => array(0x3B3), 0x1D71F => array(0x3B4), 0x1D720 => array(0x3B5)
+                    ,0x1D721 => array(0x3B6), 0x1D722 => array(0x3B7), 0x1D723 => array(0x3B8)
+                    ,0x1D724 => array(0x3B9), 0x1D725 => array(0x3BA), 0x1D726 => array(0x3BB)
+                    ,0x1D727 => array(0x3BC), 0x1D728 => array(0x3BD), 0x1D729 => array(0x3BE)
+                    ,0x1D72A => array(0x3BF), 0x1D72B => array(0x3C0), 0x1D72C => array(0x3C1)
+                    ,0x1D72D => array(0x3B8), 0x1D72E => array(0x3C3), 0x1D72F => array(0x3C4)
+                    ,0x1D730 => array(0x3C5), 0x1D731 => array(0x3C6), 0x1D732 => array(0x3C7)
+                    ,0x1D733 => array(0x3C8), 0x1D734 => array(0x3C9), 0x1D747 => array(0x3C3)
+                    ,0x1D756 => array(0x3B1), 0x1D757 => array(0x3B2), 0x1D758 => array(0x3B3)
+                    ,0x1D759 => array(0x3B4), 0x1D75A => array(0x3B5), 0x1D75B => array(0x3B6)
+                    ,0x1D75C => array(0x3B7), 0x1D75D => array(0x3B8), 0x1D75E => array(0x3B9)
+                    ,0x1D75F => array(0x3BA), 0x1D760 => array(0x3BB), 0x1D761 => array(0x3BC)
+                    ,0x1D762 => array(0x3BD), 0x1D763 => array(0x3BE), 0x1D764 => array(0x3BF)
+                    ,0x1D765 => array(0x3C0), 0x1D766 => array(0x3C1), 0x1D767 => array(0x3B8)
+                    ,0x1D768 => array(0x3C3), 0x1D769 => array(0x3C4), 0x1D76A => array(0x3C5)
+                    ,0x1D76B => array(0x3C6), 0x1D76C => array(0x3C7), 0x1D76D => array(0x3C8)
+                    ,0x1D76E => array(0x3C9), 0x1D781 => array(0x3C3), 0x1D790 => array(0x3B1)
+                    ,0x1D791 => array(0x3B2), 0x1D792 => array(0x3B3), 0x1D793 => array(0x3B4)
+                    ,0x1D794 => array(0x3B5), 0x1D795 => array(0x3B6), 0x1D796 => array(0x3B7)
+                    ,0x1D797 => array(0x3B8), 0x1D798 => array(0x3B9), 0x1D799 => array(0x3BA)
+                    ,0x1D79A => array(0x3BB), 0x1D79B => array(0x3BC), 0x1D79C => array(0x3BD)
+                    ,0x1D79D => array(0x3BE), 0x1D79E => array(0x3BF), 0x1D79F => array(0x3C0)
+                    ,0x1D7A0 => array(0x3C1), 0x1D7A1 => array(0x3B8), 0x1D7A2 => array(0x3C3)
+                    ,0x1D7A3 => array(0x3C4), 0x1D7A4 => array(0x3C5), 0x1D7A5 => array(0x3C6)
+                    ,0x1D7A6 => array(0x3C7), 0x1D7A7 => array(0x3C8), 0x1D7A8 => array(0x3C9)
+                    ,0x1D7BB => array(0x3C3), 0x3F9   => array(0x3C3), 0x1D2C  => array(0x61)
+                    ,0x1D2D  => array(0xE6), 0x1D2E  => array(0x62), 0x1D30  => array(0x64)
+                    ,0x1D31  => array(0x65), 0x1D32  => array(0x1DD), 0x1D33  => array(0x67)
+                    ,0x1D34  => array(0x68), 0x1D35  => array(0x69), 0x1D36  => array(0x6A)
+                    ,0x1D37  => array(0x6B), 0x1D38  => array(0x6C), 0x1D39  => array(0x6D)
+                    ,0x1D3A  => array(0x6E), 0x1D3C  => array(0x6F), 0x1D3D  => array(0x223)
+                    ,0x1D3E  => array(0x70), 0x1D3F  => array(0x72), 0x1D40  => array(0x74)
+                    ,0x1D41  => array(0x75), 0x1D42  => array(0x77), 0x213B  => array(0x66, 0x61, 0x78)
+                    ,0x3250  => array(0x70, 0x74, 0x65), 0x32CC  => array(0x68, 0x67)
+                    ,0x32CE  => array(0x65, 0x76), 0x32CF  => array(0x6C, 0x74, 0x64)
+                    ,0x337A  => array(0x69, 0x75), 0x33DE  => array(0x76, 0x2215, 0x6D)
+                    ,0x33DF  => array(0x61, 0x2215, 0x6D)
+                    )
+            ,'norm_combcls' => array(0x334   => 1,   0x335   => 1,   0x336   => 1,   0x337   => 1
+                    ,0x338   => 1,   0x93C   => 7,   0x9BC   => 7,   0xA3C   => 7,   0xABC   => 7
+                    ,0xB3C   => 7,   0xCBC   => 7,   0x1037  => 7,   0x3099  => 8,   0x309A  => 8
+                    ,0x94D   => 9,   0x9CD   => 9,   0xA4D   => 9,   0xACD   => 9,   0xB4D   => 9
+                    ,0xBCD   => 9,   0xC4D   => 9,   0xCCD   => 9,   0xD4D   => 9,   0xDCA   => 9
+                    ,0xE3A   => 9,   0xF84   => 9,   0x1039  => 9,   0x1714  => 9,   0x1734  => 9
+                    ,0x17D2  => 9,   0x5B0   => 10,  0x5B1   => 11,  0x5B2   => 12,  0x5B3   => 13
+                    ,0x5B4   => 14,  0x5B5   => 15,  0x5B6   => 16,  0x5B7   => 17,  0x5B8   => 18
+                    ,0x5B9   => 19,  0x5BB   => 20,  0x5Bc   => 21,  0x5BD   => 22,  0x5BF   => 23
+                    ,0x5C1   => 24,  0x5C2   => 25,  0xFB1E  => 26,  0x64B   => 27,  0x64C   => 28
+                    ,0x64D   => 29,  0x64E   => 30,  0x64F   => 31,  0x650   => 32,  0x651   => 33
+                    ,0x652   => 34,  0x670   => 35,  0x711   => 36,  0xC55   => 84,  0xC56   => 91
+                    ,0xE38   => 103, 0xE39   => 103, 0xE48   => 107, 0xE49   => 107, 0xE4A   => 107
+                    ,0xE4B   => 107, 0xEB8   => 118, 0xEB9   => 118, 0xEC8   => 122, 0xEC9   => 122
+                    ,0xECA   => 122, 0xECB   => 122, 0xF71   => 129, 0xF72   => 130, 0xF7A   => 130
+                    ,0xF7B   => 130, 0xF7C   => 130, 0xF7D   => 130, 0xF80   => 130, 0xF74   => 132
+                    ,0x321   => 202, 0x322   => 202, 0x327   => 202, 0x328   => 202, 0x31B   => 216
+                    ,0xF39   => 216, 0x1D165 => 216, 0x1D166 => 216, 0x1D16E => 216, 0x1D16F => 216
+                    ,0x1D170 => 216, 0x1D171 => 216, 0x1D172 => 216, 0x302A  => 218, 0x316   => 220
+                    ,0x317   => 220, 0x318   => 220, 0x319   => 220, 0x31C   => 220, 0x31D   => 220
+                    ,0x31E   => 220, 0x31F   => 220, 0x320   => 220, 0x323   => 220, 0x324   => 220
+                    ,0x325   => 220, 0x326   => 220, 0x329   => 220, 0x32A   => 220, 0x32B   => 220
+                    ,0x32C   => 220, 0x32D   => 220, 0x32E   => 220, 0x32F   => 220, 0x330   => 220
+                    ,0x331   => 220, 0x332   => 220, 0x333   => 220, 0x339   => 220, 0x33A   => 220
+                    ,0x33B   => 220, 0x33C   => 220, 0x347   => 220, 0x348   => 220, 0x349   => 220
+                    ,0x34D   => 220, 0x34E   => 220, 0x353   => 220, 0x354   => 220, 0x355   => 220
+                    ,0x356   => 220, 0x591   => 220, 0x596   => 220, 0x59B   => 220, 0x5A3   => 220
+                    ,0x5A4   => 220, 0x5A5   => 220, 0x5A6   => 220, 0x5A7   => 220, 0x5AA   => 220
+                    ,0x655   => 220, 0x656   => 220, 0x6E3   => 220, 0x6EA   => 220, 0x6ED   => 220
+                    ,0x731   => 220, 0x734   => 220, 0x737   => 220, 0x738   => 220, 0x739   => 220
+                    ,0x73B   => 220, 0x73C   => 220, 0x73E   => 220, 0x742   => 220, 0x744   => 220
+                    ,0x746   => 220, 0x748   => 220, 0x952   => 220, 0xF18   => 220, 0xF19   => 220
+                    ,0xF35   => 220, 0xF37   => 220, 0xFC6   => 220, 0x193B  => 220, 0x20E8  => 220
+                    ,0x1D17B => 220, 0x1D17C => 220, 0x1D17D => 220, 0x1D17E => 220, 0x1D17F => 220
+                    ,0x1D180 => 220, 0x1D181 => 220, 0x1D182 => 220, 0x1D18A => 220, 0x1D18B => 220
+                    ,0x59A   => 222, 0x5AD   => 222, 0x1929  => 222, 0x302D  => 222, 0x302E  => 224
+                    ,0x302F  => 224, 0x1D16D => 226, 0x5AE   => 228, 0x18A9  => 228, 0x302B  => 228
+                    ,0x300   => 230, 0x301   => 230, 0x302   => 230, 0x303   => 230, 0x304   => 230
+                    ,0x305   => 230, 0x306   => 230, 0x307   => 230, 0x308   => 230, 0x309   => 230
+                    ,0x30A   => 230, 0x30B   => 230, 0x30C   => 230, 0x30D   => 230, 0x30E   => 230
+                    ,0x30F   => 230, 0x310   => 230, 0x311   => 230, 0x312   => 230, 0x313   => 230
+                    ,0x314   => 230, 0x33D   => 230, 0x33E   => 230, 0x33F   => 230, 0x340   => 230
+                    ,0x341   => 230, 0x342   => 230, 0x343   => 230, 0x344   => 230, 0x346   => 230
+                    ,0x34A   => 230, 0x34B   => 230, 0x34C   => 230, 0x350   => 230, 0x351   => 230
+                    ,0x352   => 230, 0x357   => 230, 0x363   => 230, 0x364   => 230, 0x365   => 230
+                    ,0x366   => 230, 0x367   => 230, 0x368   => 230, 0x369   => 230, 0x36A   => 230
+                    ,0x36B   => 230, 0x36C   => 230, 0x36D   => 230, 0x36E   => 230, 0x36F   => 230
+                    ,0x483   => 230, 0x484   => 230, 0x485   => 230, 0x486   => 230, 0x592   => 230
+                    ,0x593   => 230, 0x594   => 230, 0x595   => 230, 0x597   => 230, 0x598   => 230
+                    ,0x599   => 230, 0x59C   => 230, 0x59D   => 230, 0x59E   => 230, 0x59F   => 230
+                    ,0x5A0   => 230, 0x5A1   => 230, 0x5A8   => 230, 0x5A9   => 230, 0x5AB   => 230
+                    ,0x5AC   => 230, 0x5AF   => 230, 0x5C4   => 230, 0x610   => 230, 0x611   => 230
+                    ,0x612   => 230, 0x613   => 230, 0x614   => 230, 0x615   => 230, 0x653   => 230
+                    ,0x654   => 230, 0x657   => 230, 0x658   => 230, 0x6D6   => 230, 0x6D7   => 230
+                    ,0x6D8   => 230, 0x6D9   => 230, 0x6DA   => 230, 0x6DB   => 230, 0x6DC   => 230
+                    ,0x6DF   => 230, 0x6E0   => 230, 0x6E1   => 230, 0x6E2   => 230, 0x6E4   => 230
+                    ,0x6E7   => 230, 0x6E8   => 230, 0x6EB   => 230, 0x6EC   => 230, 0x730   => 230
+                    ,0x732   => 230, 0x733   => 230, 0x735   => 230, 0x736   => 230, 0x73A   => 230
+                    ,0x73D   => 230, 0x73F   => 230, 0x740   => 230, 0x741   => 230, 0x743   => 230
+                    ,0x745   => 230, 0x747   => 230, 0x749   => 230, 0x74A   => 230, 0x951   => 230
+                    ,0x953   => 230, 0x954   => 230, 0xF82   => 230, 0xF83   => 230, 0xF86   => 230
+                    ,0xF87   => 230, 0x170D  => 230, 0x193A  => 230, 0x20D0  => 230, 0x20D1  => 230
+                    ,0x20D4  => 230, 0x20D5  => 230, 0x20D6  => 230, 0x20D7  => 230, 0x20DB  => 230
+                    ,0x20DC  => 230, 0x20E1  => 230, 0x20E7  => 230, 0x20E9  => 230, 0xFE20  => 230
+                    ,0xFE21  => 230, 0xFE22  => 230, 0xFE23  => 230, 0x1D185 => 230, 0x1D186 => 230
+                    ,0x1D187 => 230, 0x1D189 => 230, 0x1D188 => 230, 0x1D1AA => 230, 0x1D1AB => 230
+                    ,0x1D1AC => 230, 0x1D1AD => 230, 0x315   => 232, 0x31A   => 232, 0x302C  => 232
+                    ,0x35F   => 233, 0x362   => 233, 0x35D   => 234, 0x35E   => 234, 0x360   => 234
+                    ,0x361   => 234, 0x345   => 240
+                    )
+            );
+}
+?>
\ No newline at end of file
diff --git a/application/libraries/Imap.php b/application/libraries/Imap.php
new file mode 100644
index 0000000..86b33d3
--- /dev/null
+++ b/application/libraries/Imap.php
@@ -0,0 +1,475 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Imap library. Wrapper to read email using IMAP/POP3.
+ * @package    Imap
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2009 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class Imap_Core {
+
+	private $imap_stream;
+
+	/**
+	 * Opens an IMAP stream
+	 */
+	public function __construct()
+	{
+		// Set Imap Timeouts
+		imap_timeout(IMAP_OPENTIMEOUT,90);
+		imap_timeout(IMAP_READTIMEOUT,90);
+
+
+		// If SSL Enabled
+		$ssl = Kohana::config('settings.email_ssl') == true ? "/ssl" : "";
+
+		// Do not validate certificates (TLS/SSL server)
+		//$novalidate = strtolower(Kohana::config('settings.email_servertype')) == "imap" ? "/novalidate-cert" : "";
+		$novalidate = "/novalidate-cert";
+
+		// If POP3 Disable TLS
+		$notls = strtolower(Kohana::config('settings.email_servertype')) == "pop3" ? "/notls" : "";
+
+		/*
+		More Info about above options at:
+		http://php.net/manual/en/function.imap-open.php
+		*/
+
+		$service = "{".Kohana::config('settings.email_host').":"
+			.Kohana::config('settings.email_port')."/"
+			.Kohana::config('settings.email_servertype')
+			.$notls.$ssl.$novalidate."}";
+
+		// Check if the host name is valid, if not, set imap_stream as false and return false
+		if(count(dns_get_record("".Kohana::config('settings.email_host')."")) == 0)
+		{
+			$this->imap_stream = false;
+			return false;
+		}
+
+		if ( $imap_stream = @imap_open($service, Kohana::config('settings.email_username')
+			,Kohana::config('settings.email_password')))
+		{
+
+			$this->imap_stream = $imap_stream;
+
+		} else {
+			// We don't usually want to break the entire scheduler process if email settings are off
+			//   so lets return false instead of halting the entire script with a Kohana Exception.
+
+			$this->imap_stream = false;
+			return false;
+
+			//throw new Kohana_Exception('imap.imap_stream_not_opened', $throwing_error);
+		}
+	}
+
+	/**
+	 * Get messages according to a search criteria
+	 *
+	 * @param	string	search criteria (RFC2060, sec. 6.4.4). Set to "UNSEEN" by default
+	 *					NB: Search criteria only affects IMAP mailboxes.
+	 * @param	string	date format. Set to "Y-m-d H:i:s" by default
+	 * @return	mixed	array containing messages
+	 */
+	public function get_messages($search_criteria="UNSEEN",
+								 $date_format="Y-m-d H:i:s")
+	{
+		global $htmlmsg,$plainmsg,$attachments;
+		
+		// If our imap connection failed earlier, return no messages
+
+		if($this->imap_stream == false)
+		{
+			return array();
+		}
+
+		// Use imap_search() to find the 'UNSEEN' messages.
+		// This is more efficient than previous code using imap_num_msg()
+		$new_msgs = imap_search($this->imap_stream, 'UNSEEN');
+		$max_imap_messages = Kohana::config('email.max_imap_messages');
+
+		if ($new_msgs == null)
+		{
+			return array();
+		}
+
+		// Check to see if the number of messages we want to sort through is greater than
+		//   the number of messages we want to allow. If there are too many messages, it
+		//   can fail and that's no good.
+		$msg_to_pull = sizeof($new_msgs);
+		
+		// This check has had problems in the past
+		if($msg_to_pull > $max_imap_messages)
+		{
+			$msg_to_pull = $max_imap_messages;
+		}
+
+		$messages = array();
+
+		for ($msgidx = 0; $msgidx < $msg_to_pull; $msgidx++)
+		{
+			$msgno = $new_msgs[$msgidx];
+			$header = imap_headerinfo($this->imap_stream, $msgno);
+
+			if( ! isset($header->message_id) OR ! isset($header->udate))
+			{
+				continue;
+			}
+			
+			// Skip messages that aren't new/unseen
+			// not sure we need this check now we use imap_search to pull only UNSEEN
+			if ($header->Unseen != 'U' AND $header->Recent != 'N')
+			{
+				continue;
+			}
+
+			$message_id = $header->message_id;
+			$date = date($date_format, $header->udate);
+
+			if (isset($header->from))
+			{
+				$from = $header->from;
+			}else{
+				$from = FALSE;
+			}
+
+			$fromname = "";
+			$fromaddress = "";
+			$subject = "";
+			$body = "";
+			$attachments = "";
+
+			if ($from != FALSE)
+			{
+				foreach ($from as $id => $object)
+				{
+					if (isset($object->personal))
+					{
+						$fromname = $object->personal;
+					}
+
+					if (isset($object->mailbox) AND isset($object->host))
+					{
+						$fromaddress = $object->mailbox."@".$object->host;
+					}
+
+					if ($fromname == "")
+					{
+						// In case from object doesn't have Name
+						$fromname = $fromaddress;
+					}
+				}
+			}
+
+			if (isset($header->subject))
+			{
+				$subject = $this->_mime_decode($header->subject);
+			}
+
+			// Fetch Body
+			$this->_getmsg($this->imap_stream, $msgno);
+			
+			if ($htmlmsg)
+			{
+				// Convert HTML to Text
+				$html2text = new Html2Text($htmlmsg);
+				$htmlmsg = $html2text->get_text();
+			}
+			$body = ($plainmsg) ? $plainmsg : $htmlmsg;
+			
+			// Fetch Attachments
+			$attachments = $this->_extract_attachments($this->imap_stream, $msgno);
+
+			// This isn't the perfect solution but windows-1256 encoding doesn't work with mb_detect_encoding()
+			//   so if it doesn't return an encoding, lets assume it's arabic. (sucks)
+			if(mb_detect_encoding($body, 'auto', TRUE) == '')
+			{
+				$body = iconv("windows-1256", "UTF-8", $body);
+			}
+
+			// Convert to valid UTF8
+			$detected_encoding = mb_detect_encoding($body, "auto");
+			if($detected_encoding == 'ASCII') $detected_encoding = 'iso-8859-1';
+			$body = mb_convert_encoding($body, $detected_encoding, 'UTF-8');
+			$body = html::escape($body);
+			$subject = html::strip_tags($subject);
+
+			array_push($messages, array('message_id' => $message_id,
+										'date' => $date,
+										'from' => $fromname,
+										'email' => $fromaddress,
+										'subject' => $subject,
+										'body' => $body,
+										'attachments' => $attachments));
+
+			// Mark Message As Read
+			imap_setflag_full($this->imap_stream, $msgno, "\\Seen");
+		}
+
+		return $messages;
+	}
+
+	/**
+	 * Delete a message
+	 * @param	int	message number
+	 */
+	public function delete_message($msg_no)
+	{
+		imap_delete($this->imap_stream, $msg_no);
+	}
+
+	/**
+	 * Closes an IMAP stream
+	 */
+	public function close()
+	{
+		// Dump imap errors to avoid 'Mailbox is empty' errors 
+		$error = imap_errors();
+		@imap_close($this->imap_stream);
+	}
+
+	private function _mime_decode($str)
+	{
+		$elements = imap_mime_header_decode($str);
+		$text = "";
+
+		foreach ($elements as $element)
+		{
+			
+			// Make sure Arabic characters can be passed through as UTF-8
+			
+			if(strtoupper($element->charset) == 'WINDOWS-1256'){
+				$element->text = iconv("windows-1256", "UTF-8", $element->text);
+			}
+			
+			$text.= $element->text;
+		}
+
+		return $text;
+	}
+	
+	/**
+	 * Extract Attachments from Email
+	 */
+	private function _extract_attachments($connection, $message_number) {
+
+		$attachments = array();
+		$structure = imap_fetchstructure($connection, $message_number);
+
+		if(isset($structure->parts) && count($structure->parts)) {
+
+			for($i = 0; $i < count($structure->parts); $i++) {
+
+				$attachments[$i] = array(
+					'is_attachment' => false,
+					'file_name' => '',
+					'name' => '',
+					'type' => 0,
+					'subtype' => '',
+					'attachment' => ''
+				);
+
+				// Use the type and subtype to resolve the attachments.
+				// Previously used of the file name extension but found that different phone models (when sending MMS to email),
+				// different carriers (when sending MMS to email), and different email clients
+				// do not reliably add extensions or even provide a sane filename.
+				// However, they all set the content type correctly and PHP was able to identify the mime type as image/*.
+				
+				$attachments[$i]['type'] = $structure->parts[$i]->type;
+				$attachments[$i]['subtype'] = $structure->parts[$i]->subtype;
+
+				if($structure->parts[$i]->ifdparameters) {
+					foreach($structure->parts[$i]->dparameters as $object) {
+						if(strtolower($object->attribute) == 'filename') {
+							$attachments[$i]['is_attachment'] = true;
+							$attachments[$i]['file_name'] = $object->value;
+						}
+					}
+				}
+
+				if($structure->parts[$i]->ifparameters) {
+					foreach($structure->parts[$i]->parameters as $object) {
+						if(strtolower($object->attribute) == 'name') {
+							$attachments[$i]['is_attachment'] = true;
+							$attachments[$i]['name'] = $object->value;
+						}
+					}
+				}
+
+				if($attachments[$i]['is_attachment']) {
+					$attachments[$i]['attachment'] = imap_fetchbody($connection, $message_number, $i+1);
+					if($structure->parts[$i]->encoding == 3) { // 3 = BASE64
+						$attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']);
+					}
+					elseif($structure->parts[$i]->encoding == 4) { // 4 = QUOTED-PRINTABLE
+						$attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']);
+					}
+				}
+
+			}
+
+		}
+		
+		$valid_attachments = array();
+		foreach ($attachments as $attachment)
+		{
+			$file_content = $attachment['attachment'];
+			
+			// Don't accept images smaller that 12.5k
+			// When MMS is sent to an email address, sometimes the source
+			// carrier wraps the message and the image into html with some 
+			// embedded GIFs. This tries to filter them out
+			if (strlen($file_content) < 12500) {
+				continue;
+			}
+			
+			$file_type = $attachment['type'];
+			$file_extension = $attachment['subtype'];
+			
+			if ($file_extension == 'JPEG')
+			{
+				$file_extension = '.JPG';
+			}
+			else
+			{
+				$file_extension = '.' . $file_extension;
+			}
+			
+			$new_file_name = time()."_".$this->_random_string(10); // Included rand so that files don't overwrite each other
+			$valid_attachments[] = $this->_save_attachments($file_type, $new_file_name, $file_content, $file_extension);
+		}
+
+		// Remove Nulls
+		return array_filter($valid_attachments);
+	}
+	
+	
+	/**
+	 * Save Attachments to Upload Folder
+	 * Right now we only accept gif, png and jpg files
+	 */	
+	private function _save_attachments($file_type,$file_name,$file_content,$file_extension)
+	{
+	  // $file_type == 5 is image, == 6 is video...  see:
+	  // http://us.php.net/manual/en/function.imap-fetchstructure.php
+	  if ($file_type == 5)
+		{
+			$attachments = array();
+			$file = Kohana::config("upload.directory")."/".$file_name.$file_extension;
+			$fp = fopen($file, "w");
+			fwrite($fp, $file_content);
+			fclose($fp);
+			
+			// IMAGE SIZES: 800X600, 400X300, 89X59
+			
+			// Large size
+			Image::factory($file)->resize(800,600,Image::AUTO)
+				->save(Kohana::config('upload.directory', TRUE).$file_name.$file_extension);
+
+			// Medium size
+			Image::factory($file)->resize(400,300,Image::HEIGHT)
+				->save(Kohana::config('upload.directory', TRUE).$file_name."_m".$file_extension);
+			
+			// Thumbnail
+			Image::factory($file)->resize(89,59,Image::HEIGHT)
+				->save(Kohana::config('upload.directory', TRUE).$file_name."_t".$file_extension);
+				
+			$attachments[] = array(
+					$file_name.$file_extension,
+					$file_name."_m".$file_extension,
+					$file_name."_t".$file_extension
+				);
+			return $attachments;
+		}
+		else
+		{
+			return null;
+		}
+	}
+	
+	// Random Character String
+	private function _random_string($length)
+	{
+		$random = "";
+		srand((double)microtime()*1000000);
+		$char_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+		$char_list .= "abcdefghijklmnopqrstuvwxyz";
+		$char_list .= "1234567890";
+		// Add the special characters to $char_list if needed
+
+		for($i = 0; $i < $length; $i++)
+		{
+			$random .= substr($char_list,(rand()%(strlen($char_list))), 1);
+		}
+		
+		return $random;
+	}
+	
+	private function _getmsg($mbox,$mid)
+	{
+		// input $mbox = IMAP stream, $mid = message id
+		// output all the following:
+		global $htmlmsg,$plainmsg,$attachments;
+		// the message may in $htmlmsg, $plainmsg, or both
+		$htmlmsg = $plainmsg = '';
+		$attachments = array();
+
+		// HEADER
+		$h = imap_header($mbox,$mid);
+		// add code here to get date, from, to, cc, subject...
+
+		// BODY
+		$s = imap_fetchstructure($mbox,$mid);
+		if (@!$s->parts)	 // not multipart
+			$this->_getpart($mbox,$mid,$s,0);  // no part-number, so pass 0
+		else {	// multipart: iterate through each part
+			foreach ($s->parts as $partno0=>$p)
+				$this->_getpart($mbox,$mid,$p,$partno0+1);
+		}
+	}
+
+	private function _getpart($mbox,$mid,$p,$partno)
+	{
+		// $partno = '1', '2', '2.1', '2.1.3', etc if multipart, 0 if not multipart
+		global $htmlmsg,$plainmsg,$attachments;
+
+		// DECODE DATA
+		$data = ($partno)?
+			imap_fetchbody($mbox,$mid,$partno):	 // multipart
+			imap_body($mbox,$mid);	// not multipart
+		// Any part may be encoded, even plain text messages, so check everything.
+		if ($p->encoding==4)
+			$data = quoted_printable_decode($data);
+		elseif ($p->encoding==3)
+			$data = base64_decode($data);
+		// no need to decode 7-bit, 8-bit, or binary
+
+		// TEXT
+		if ($p->type==0 && $data) {
+			// Messages may be split in different parts because of inline attachments,
+			// so append parts together with blank row.
+			if (strtolower($p->subtype)=='plain')
+				$plainmsg .= trim($data) ."\n\n";
+			else
+				$htmlmsg .= $data ."<br><br>";
+		}
+
+		// EMBEDDED MESSAGE
+		// Many bounce notifications embed the original message as type 2,
+		// but AOL uses type 1 (multipart), which is not handled here.
+		// There are no PHP functions to parse embedded messages,
+		// so this just appends the raw source to the main message.
+		elseif ($p->type==2 && $data)
+		{
+			$plainmsg .= trim($data) ."\n\n";
+		}
+
+		// SUBPART RECURSION
+		if (isset($p->parts))
+		{
+			foreach ($p->parts as $partno0=>$p2)
+				$this->_getpart($mbox,$mid,$p2,$partno.'.'.($partno0+1));  // 1.2, 1.2.1, etc.
+		}
+	}
+} // End Imap
diff --git a/application/libraries/JSMin.php b/application/libraries/JSMin.php
new file mode 100644
index 0000000..ee1b442
--- /dev/null
+++ b/application/libraries/JSMin.php
@@ -0,0 +1,386 @@
+<?php
+/**
+ * jsmin.php - PHP implementation of Douglas Crockford's JSMin.
+ *
+ * This is pretty much a direct port of jsmin.c to PHP with just a few
+ * PHP-specific performance tweaks. Also, whereas jsmin.c reads from stdin and
+ * outputs to stdout, this library accepts a string as input and returns another
+ * string as output.
+ *
+ * PHP 5 or higher is required.
+ *
+ * Permission is hereby granted to use this version of the library under the
+ * same terms as jsmin.c, which has the following license:
+ *
+ * --
+ * Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of
+ * this software and associated documentation files (the "Software"), to deal in
+ * the Software without restriction, including without limitation the rights to
+ * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is furnished to do
+ * so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * The Software shall be used for Good, not Evil.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ * --
+ *
+ * @package JSMin
+ * @author Ryan Grove <ryan at wonko.com>
+ * @copyright 2002 Douglas Crockford <douglas at crockford.com> (jsmin.c)
+ * @copyright 2008 Ryan Grove <ryan at wonko.com> (PHP port)
+ * @copyright 2012 Adam Goforth <aag at adamgoforth.com> (Updates)
+ * @license http://opensource.org/licenses/mit-license.php MIT License
+ * @version 1.1.2 (2012-05-01)
+ * @link https://github.com/rgrove/jsmin-php
+ */
+
+class JSMin {
+  const ORD_LF            = 10;
+  const ORD_SPACE         = 32;
+  const ACTION_KEEP_A     = 1;
+  const ACTION_DELETE_A   = 2;
+  const ACTION_DELETE_A_B = 3;
+
+  protected $a           = '';
+  protected $b           = '';
+  protected $input       = '';
+  protected $inputIndex  = 0;
+  protected $inputLength = 0;
+  protected $lookAhead   = null;
+  protected $output      = '';
+
+  // -- Public Static Methods --------------------------------------------------
+
+  /**
+   * Minify Javascript
+   *
+   * @uses __construct()
+   * @uses min()
+   * @param string $js Javascript to be minified
+   * @return string
+   */
+  public static function minify($js) {
+    $jsmin = new JSMin($js);
+    return $jsmin->min();
+  }
+
+  // -- Public Instance Methods ------------------------------------------------
+
+  /**
+   * Constructor
+   *
+   * @param string $input Javascript to be minified
+   */
+  public function __construct($input) {
+    $this->input       = str_replace("\r\n", "\n", $input);
+    $this->inputLength = strlen($this->input);
+  }
+
+  // -- Protected Instance Methods ---------------------------------------------
+
+  /**
+   * Action -- do something! What to do is determined by the $command argument.
+   *
+   * action treats a string as a single character. Wow!
+   * action recognizes a regular expression if it is preceded by ( or , or =.
+   *
+   * @uses next()
+   * @uses get()
+   * @throws JSMinException If parser errors are found:
+   *         - Unterminated string literal
+   *         - Unterminated regular expression set in regex literal
+   *         - Unterminated regular expression literal
+   * @param int $command One of class constants:
+   *      ACTION_KEEP_A      Output A. Copy B to A. Get the next B.
+   *      ACTION_DELETE_A    Copy B to A. Get the next B. (Delete A).
+   *      ACTION_DELETE_A_B  Get the next B. (Delete B).
+  */
+  protected function action($command) {
+    switch($command) {
+      case self::ACTION_KEEP_A:
+        $this->output .= $this->a;
+
+      case self::ACTION_DELETE_A:
+        $this->a = $this->b;
+
+        if ($this->a === "'" || $this->a === '"') {
+          for (;;) {
+            $this->output .= $this->a;
+            $this->a       = $this->get();
+
+            if ($this->a === $this->b) {
+              break;
+            }
+
+            if (ord($this->a) <= self::ORD_LF) {
+              throw new JSMinException('Unterminated string literal.');
+            }
+
+            if ($this->a === '\\') {
+              $this->output .= $this->a;
+              $this->a       = $this->get();
+            }
+          }
+        }
+
+      case self::ACTION_DELETE_A_B:
+        $this->b = $this->next();
+
+        if ($this->b === '/' && (
+            $this->a === '(' || $this->a === ',' || $this->a === '=' ||
+            $this->a === ':' || $this->a === '[' || $this->a === '!' ||
+            $this->a === '&' || $this->a === '|' || $this->a === '?' ||
+            $this->a === '{' || $this->a === '}' || $this->a === ';' ||
+            $this->a === "\n" )) {
+
+          $this->output .= $this->a . $this->b;
+
+          for (;;) {
+            $this->a = $this->get();
+
+            if ($this->a === '[') {
+              /*
+                inside a regex [...] set, which MAY contain a '/' itself. Example: mootools Form.Validator near line 460:
+                  return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value'));
+              */
+              for (;;) {
+                $this->output .= $this->a;
+                $this->a = $this->get();
+
+                if ($this->a === ']') {
+                    break;
+                } elseif ($this->a === '\\') {
+                  $this->output .= $this->a;
+                  $this->a       = $this->get();
+                } elseif (ord($this->a) <= self::ORD_LF) {
+                  throw new JSMinException('Unterminated regular expression set in regex literal.');
+                }
+              }
+            } elseif ($this->a === '/') {
+              break;
+            } elseif ($this->a === '\\') {
+              $this->output .= $this->a;
+              $this->a       = $this->get();
+            } elseif (ord($this->a) <= self::ORD_LF) {
+              throw new JSMinException('Unterminated regular expression literal.');
+            }
+
+            $this->output .= $this->a;
+          }
+
+          $this->b = $this->next();
+        }
+    }
+  }
+
+  /**
+   * Get next char. Convert ctrl char to space.
+   *
+   * @return string|null
+   */
+  protected function get() {
+    $c = $this->lookAhead;
+    $this->lookAhead = null;
+
+    if ($c === null) {
+      if ($this->inputIndex < $this->inputLength) {
+        $c = substr($this->input, $this->inputIndex, 1);
+        $this->inputIndex += 1;
+      } else {
+        $c = null;
+      }
+    }
+
+    if ($c === "\r") {
+      return "\n";
+    }
+
+    if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) {
+      return $c;
+    }
+
+    return ' ';
+  }
+
+  /**
+   * Is $c a letter, digit, underscore, dollar sign, or non-ASCII character.
+   *
+   * @return bool
+   */
+  protected function isAlphaNum($c) {
+    return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1;
+  }
+
+  /**
+   * Perform minification, return result
+   *
+   * @uses action()
+   * @uses isAlphaNum()
+   * @uses get()
+   * @uses peek()
+   * @return string
+   */
+  protected function min() {
+    if (0 == strncmp($this->peek(), "\xef", 1)) {
+        $this->get();
+        $this->get();
+        $this->get();
+    } 
+
+    $this->a = "\n";
+    $this->action(self::ACTION_DELETE_A_B);
+
+    while ($this->a !== null) {
+      switch ($this->a) {
+        case ' ':
+          if ($this->isAlphaNum($this->b)) {
+            $this->action(self::ACTION_KEEP_A);
+          } else {
+            $this->action(self::ACTION_DELETE_A);
+          }
+          break;
+
+        case "\n":
+          switch ($this->b) {
+            case '{':
+            case '[':
+            case '(':
+            case '+':
+            case '-':
+            case '!':
+            case '~':
+              $this->action(self::ACTION_KEEP_A);
+              break;
+
+            case ' ':
+              $this->action(self::ACTION_DELETE_A_B);
+              break;
+
+            default:
+              if ($this->isAlphaNum($this->b)) {
+                $this->action(self::ACTION_KEEP_A);
+              }
+              else {
+                $this->action(self::ACTION_DELETE_A);
+              }
+          }
+          break;
+
+        default:
+          switch ($this->b) {
+            case ' ':
+              if ($this->isAlphaNum($this->a)) {
+                $this->action(self::ACTION_KEEP_A);
+                break;
+              }
+
+              $this->action(self::ACTION_DELETE_A_B);
+              break;
+
+            case "\n":
+              switch ($this->a) {
+                case '}':
+                case ']':
+                case ')':
+                case '+':
+                case '-':
+                case '"':
+                case "'":
+                  $this->action(self::ACTION_KEEP_A);
+                  break;
+
+                default:
+                  if ($this->isAlphaNum($this->a)) {
+                    $this->action(self::ACTION_KEEP_A);
+                  }
+                  else {
+                    $this->action(self::ACTION_DELETE_A_B);
+                  }
+              }
+              break;
+
+            default:
+              $this->action(self::ACTION_KEEP_A);
+              break;
+          }
+      }
+    }
+
+    return $this->output;
+  }
+
+  /**
+   * Get the next character, skipping over comments. peek() is used to see
+   *  if a '/' is followed by a '/' or '*'.
+   *
+   * @uses get()
+   * @uses peek()
+   * @throws JSMinException On unterminated comment.
+   * @return string
+   */
+  protected function next() {
+    $c = $this->get();
+
+    if ($c === '/') {
+      switch($this->peek()) {
+        case '/':
+          for (;;) {
+            $c = $this->get();
+
+            if (ord($c) <= self::ORD_LF) {
+              return $c;
+            }
+          }
+
+        case '*':
+          $this->get();
+
+          for (;;) {
+            switch($this->get()) {
+              case '*':
+                if ($this->peek() === '/') {
+                  $this->get();
+                  return ' ';
+                }
+                break;
+
+              case null:
+                throw new JSMinException('Unterminated comment.');
+            }
+          }
+
+        default:
+          return $c;
+      }
+    }
+
+    return $c;
+  }
+
+  /**
+   * Get next char. If is ctrl character, translate to a space or newline.
+   *
+   * @uses get()
+   * @return string|null
+   */
+  protected function peek() {
+    $this->lookAhead = $this->get();
+    return $this->lookAhead;
+  }
+}
+
+// -- Exceptions ---------------------------------------------------------------
+class JSMinException extends Exception {}
+?>
diff --git a/application/libraries/MY_Auth.php b/application/libraries/MY_Auth.php
new file mode 100644
index 0000000..7ee29d1
--- /dev/null
+++ b/application/libraries/MY_Auth.php
@@ -0,0 +1,96 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Custom extensions to the User authorization library. 
+ *
+ * @package	   Auth
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+class Auth extends Auth_Core {
+
+	/**
+	 * Check if user has specified permission
+	 * @param $user User_Model
+	 * @param $permission String permission name
+	 **/
+	public function has_permission($permission = FALSE, $user = FALSE)
+	{
+		// Get current user if none passed
+		if (!$user)
+		{
+			$user = $this->get_user();
+		}
+		
+		if ($user AND $user instanceof User_Model AND $permission)
+		{
+			return $user->has_permission($permission);
+		}
+		
+		return FALSE;
+	}
+
+	/**
+	 * Check if user has admin_access
+	 * 
+	 * @param object $user
+	 * @return bool TRUE if has any permission to access anything. FALSE if not (essentially login only level)
+	 */
+	public function admin_access($user = FALSE)
+	{
+		return $this->has_permission('admin_ui', $user);
+	}
+
+	/**
+	 * Attempt to log user in via HTTP BASIC AUTH
+	 *
+ 	 * @return bool
+ 	 */
+	public function http_auth_login() {
+
+		// Get username and password
+		if (isset($_SERVER['PHP_AUTH_USER']) AND isset($_SERVER['PHP_AUTH_PW']))
+		{
+			$username = $_SERVER['PHP_AUTH_USER'];
+			$password = $_SERVER['PHP_AUTH_PW'];
+
+			$email = FALSE;
+			
+			if(Kohana::config('riverid.enable') == TRUE AND filter_var($username, FILTER_VALIDATE_EMAIL))
+			{
+				$email = $username;
+			}
+
+			try
+			{
+				if ($this->login($username, $password, FALSE, $email))
+				{
+					return TRUE;
+				}
+			}
+			catch (Exception $e)
+			{
+				return FALSE;
+			}
+
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Sends an HTTP AUTH prompt.
+	 *
+	 * @return void
+	 */
+	public function http_auth_prompt_login()
+	{
+		header('HTTP/1.0 401 Unauthorized');
+		// Avoid popping login box for ajax requests
+		if (!request::is_ajax())
+		{
+			header('WWW-Authenticate: Basic realm="Ushahidi API"');
+		}
+	}
+
+}
diff --git a/application/libraries/MY_Controller.php b/application/libraries/MY_Controller.php
new file mode 100644
index 0000000..cb33aa5
--- /dev/null
+++ b/application/libraries/MY_Controller.php
@@ -0,0 +1,89 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Base Controller
+ * Enforces basic access control, ie. for private deployments
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+abstract class Controller extends Controller_Core {
+
+	/**
+	 * ORM reference for the currently logged in user
+	 * @var object
+	 */
+	protected $user;
+
+	/**
+	 * Reference to Auth object 
+	 * @var object
+	 */
+	protected $auth;
+
+	/**
+	 * Reference to Database object 
+	 * @var object
+	 */
+	protected $db;
+	
+	public function __construct()
+	{
+		parent::__construct();
+		
+		// Load profiler
+		if (Kohana::config('config.enable_profiler'))
+		{
+			$this->profiler = new Profiler;
+		}
+		
+		$this->auth = Auth::instance();
+		
+		$this->db = Database::instance();
+
+		// Are we logged in? if not, do we have an auto-login cookie?
+		if (! $this->auth->logged_in()) {
+			// Try to login with 'remember me' token
+			if (! $this->auth->auto_login())
+			{
+				// Login user in via HTTP AUTH
+				$this->auth->http_auth_login();
+			}
+		}
+
+		// Get session information
+		$this->user = Auth::instance()->get_user();
+
+		// Check private deployment access
+		$controller_whitelist = array(
+			'login',
+			'riverid',
+			'api',
+			// Whitelist all known SMS plugins
+			// @todo add hook for plugins to add themselves
+			'frontlinesms',
+			'smssync',
+			'nexmo'
+		);
+
+		if (Kohana::config('settings.private_deployment'))
+		{
+			if (!$this->auth->logged_in('login') AND ! in_array(Router::$controller, $controller_whitelist))
+			{
+				// Redirect to login form
+				url::redirect('login');
+			}
+		}
+		
+		// Set default content-type header
+		header('Content-type: text/html; charset=UTF-8');
+	}
+}
diff --git a/application/libraries/MY_Database.php b/application/libraries/MY_Database.php
new file mode 100755
index 0000000..faafbbb
--- /dev/null
+++ b/application/libraries/MY_Database.php
@@ -0,0 +1,43 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Custom extendsion to Database library
+ *
+ * Backports Kohana 3 style named query binding
+ *
+ * @package	   Ushahidi
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+class Database extends Database_Core {
+
+	/**
+	 * Combine a SQL statement with the bind values. Used for safe queries.
+	 * 
+	 * If $binds is an indexed array use KO3 style named binding
+	 * otherwise fallback to Kohana core
+	 *
+	 * @param   string  query to bind to the values
+	 * @param   array   array of values to bind to the query
+	 * @return  string
+	 */
+	public function compile_binds($sql, $binds)
+	{
+		$isIndexed = array_values($binds) === $binds;
+		// if we have an associative array, use named bindings ala KO3
+		if (! $isIndexed)
+		{
+			// Escape all of the values
+			$values = array_map(array($this->driver, 'escape'), $binds);
+	
+			// Replace the values in the SQL
+			$sql = strtr($sql, $values);
+			
+			return $sql;
+		}
+		else
+		{
+			return parent::compile_binds($sql, $binds);
+		}
+	}
+} // End Database Class
diff --git a/application/libraries/MY_View.php b/application/libraries/MY_View.php
new file mode 100644
index 0000000..8465712
--- /dev/null
+++ b/application/libraries/MY_View.php
@@ -0,0 +1,49 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Ushahidi View class
+ * Adds extra hooks to views
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class View extends View_Core
+{
+	// Save name for use in hook
+	protected $name = FALSE;
+	
+	public function __construct($name = NULL, $data = NULL, $type = NULL)
+	{
+		$this->name = $name;
+		
+		parent::__construct($name, $data, $type);
+	}
+	
+	/**
+	 * Renders a view.
+	 * 
+	 * Add an additional filter to modify view data
+	 *
+	 * @param   boolean   set to TRUE to echo the output instead of returning it
+	 * @param   callback  special renderer to pass the output through
+	 * @return  string    if print is FALSE
+	 * @return  void      if print is TRUE
+	 */
+	public function render($print = FALSE, $renderer = FALSE)
+	{
+		// Run view_pre_render filter to allow plugins/themes to add extra data to a view
+		Event::run('ushahidi_filter.view_pre_render', $this->kohana_local_data);
+		// View specific hook pre render hook ie. ushahidi_filter.view_pre_render.reports_main
+		Event::run('ushahidi_filter.view_pre_render.'.str_replace('/','_',$this->name), $this->kohana_local_data);
+		
+		return parent::render($print, $renderer);
+	}
+}
diff --git a/application/libraries/Minify_CSS_UriRewriter.php b/application/libraries/Minify_CSS_UriRewriter.php
new file mode 100644
index 0000000..d960e0e
--- /dev/null
+++ b/application/libraries/Minify_CSS_UriRewriter.php
@@ -0,0 +1,313 @@
+<?php
+/**
+ * Class Minify_CSS_UriRewriter  
+ * @package Minify
+ * @copyright 2008 Ryan Grove, Stephen Clay. All rights reserved.
+ * @license http://opensource.org/licenses/bsd-license.php  New BSD License
+ * @link http://code.google.com/p/minify/
+ */
+
+/**
+ * Rewrite file-relative URIs as root-relative in CSS files
+ *
+ * @package Minify
+ * @author Stephen Clay <steve at mrclay.org>
+ */
+class Minify_CSS_UriRewriter {
+    
+    /**
+     * rewrite() and rewriteRelative() append debugging information here
+     *
+     * @var string
+     */
+    public static $debugText = '';
+    
+    /**
+     * In CSS content, rewrite file relative URIs as root relative
+     * 
+     * @param string $css
+     * 
+     * @param string $currentDir The directory of the current CSS file.
+     * 
+     * @param string $docRoot The document root of the web site in which 
+     * the CSS file resides (default = $_SERVER['DOCUMENT_ROOT']).
+     * 
+     * @param array $symlinks (default = array()) If the CSS file is stored in 
+     * a symlink-ed directory, provide an array of link paths to
+     * target paths, where the link paths are within the document root. Because 
+     * paths need to be normalized for this to work, use "//" to substitute 
+     * the doc root in the link paths (the array keys). E.g.:
+     * <code>
+     * array('//symlink' => '/real/target/path') // unix
+     * array('//static' => 'D:\\staticStorage')  // Windows
+     * </code>
+     * 
+     * @return string
+     */
+    public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array()) 
+    {
+        self::$_docRoot = self::_realpath(
+            $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
+        );
+        self::$_currentDir = self::_realpath($currentDir);
+        self::$_symlinks = array();
+        
+        // normalize symlinks
+        foreach ($symlinks as $link => $target) {
+            $link = ($link === '//')
+                ? self::$_docRoot
+                : str_replace('//', self::$_docRoot . '/', $link);
+            $link = strtr($link, '/', DIRECTORY_SEPARATOR);
+            self::$_symlinks[$link] = self::_realpath($target);
+        }
+        
+        self::$debugText .= "docRoot    : " . self::$_docRoot . "\n"
+                          . "currentDir : " . self::$_currentDir . "\n";
+        if (self::$_symlinks) {
+            self::$debugText .= "symlinks : " . var_export(self::$_symlinks, 1) . "\n";
+        }
+        self::$debugText .= "\n";
+        
+        $css = self::_trimUrls($css);
+        
+        // rewrite
+        $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
+            ,array(self::$className, '_processUriCB'), $css);
+        $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+            ,array(self::$className, '_processUriCB'), $css);
+
+        return $css;
+    }
+    
+    /**
+     * In CSS content, prepend a path to relative URIs
+     * 
+     * @param string $css
+     * 
+     * @param string $path The path to prepend.
+     * 
+     * @return string
+     */
+    public static function prepend($css, $path)
+    {
+        self::$_prependPath = $path;
+        
+        $css = self::_trimUrls($css);
+        
+        // append
+        $css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
+            ,array(self::$className, '_processUriCB'), $css);
+        $css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+            ,array(self::$className, '_processUriCB'), $css);
+
+        self::$_prependPath = null;
+        return $css;
+    }
+    
+    /**
+     * Get a root relative URI from a file relative URI
+     *
+     * <code>
+     * Minify_CSS_UriRewriter::rewriteRelative(
+     *       '../img/hello.gif'
+     *     , '/home/user/www/css'  // path of CSS file
+     *     , '/home/user/www'      // doc root
+     * );
+     * // returns '/img/hello.gif'
+     * 
+     * // example where static files are stored in a symlinked directory
+     * Minify_CSS_UriRewriter::rewriteRelative(
+     *       'hello.gif'
+     *     , '/var/staticFiles/theme'
+     *     , '/home/user/www'
+     *     , array('/home/user/www/static' => '/var/staticFiles')
+     * );
+     * // returns '/static/theme/hello.gif'
+     * </code>
+     * 
+     * @param string $uri file relative URI
+     * 
+     * @param string $realCurrentDir realpath of the current file's directory.
+     * 
+     * @param string $realDocRoot realpath of the site document root.
+     * 
+     * @param array $symlinks (default = array()) If the file is stored in 
+     * a symlink-ed directory, provide an array of link paths to
+     * real target paths, where the link paths "appear" to be within the document 
+     * root. E.g.:
+     * <code>
+     * array('/home/foo/www/not/real/path' => '/real/target/path') // unix
+     * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path')  // Windows
+     * </code>
+     * 
+     * @return string
+     */
+    public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array())
+    {
+        // prepend path with current dir separator (OS-independent)
+        $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)  
+            . DIRECTORY_SEPARATOR . strtr($uri, '/', DIRECTORY_SEPARATOR);
+        
+        self::$debugText .= "file-relative URI  : {$uri}\n"
+                          . "path prepended     : {$path}\n";
+        
+        // "unresolve" a symlink back to doc root
+        foreach ($symlinks as $link => $target) {
+            if (0 === strpos($path, $target)) {
+                // replace $target with $link
+                $path = $link . substr($path, strlen($target));
+                
+                self::$debugText .= "symlink unresolved : {$path}\n";
+                
+                break;
+            }
+        }
+        // strip doc root
+        $path = substr($path, strlen($realDocRoot));
+        
+        self::$debugText .= "docroot stripped   : {$path}\n";
+        
+        // fix to root-relative URI
+        $uri = strtr($path, '/\\', '//');
+        $uri = self::removeDots($uri);
+      
+        self::$debugText .= "traversals removed : {$uri}\n\n";
+        
+        return $uri;
+    }
+
+    /**
+     * Remove instances of "./" and "../" where possible from a root-relative URI
+     *
+     * @param string $uri
+     *
+     * @return string
+     */
+    public static function removeDots($uri)
+    {
+        $uri = str_replace('/./', '/', $uri);
+        // inspired by patch from Oleg Cherniy
+        do {
+            $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
+        } while ($changed);
+        return $uri;
+    }
+    
+    /**
+     * Defines which class to call as part of callbacks, change this
+     * if you extend Minify_CSS_UriRewriter
+     *
+     * @var string
+     */
+    protected static $className = 'Minify_CSS_UriRewriter';
+
+    /**
+     * Get realpath with any trailing slash removed. If realpath() fails,
+     * just remove the trailing slash.
+     * 
+     * @param string $path
+     * 
+     * @return mixed path with no trailing slash
+     */
+    protected static function _realpath($path)
+    {
+        $realPath = realpath($path);
+        if ($realPath !== false) {
+            $path = $realPath;
+        }
+        return rtrim($path, '/\\');
+    }
+
+    /**
+     * Directory of this stylesheet
+     *
+     * @var string
+     */
+    private static $_currentDir = '';
+
+    /**
+     * DOC_ROOT
+     *
+     * @var string
+     */
+    private static $_docRoot = '';
+
+    /**
+     * directory replacements to map symlink targets back to their
+     * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
+     *
+     * @var array
+     */
+    private static $_symlinks = array();
+
+    /**
+     * Path to prepend
+     *
+     * @var string
+     */
+    private static $_prependPath = null;
+
+    /**
+     * @param string $css
+     *
+     * @return string
+     */
+    private static function _trimUrls($css)
+    {
+        return preg_replace('/
+            url\\(      # url(
+            \\s*
+            ([^\\)]+?)  # 1 = URI (assuming does not contain ")")
+            \\s*
+            \\)         # )
+        /x', 'url($1)', $css);
+    }
+
+    /**
+     * @param array $m
+     *
+     * @return string
+     */
+    private static function _processUriCB($m)
+    {
+        // $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
+        $isImport = ($m[0][0] === '@');
+        // determine URI and the quote character (if any)
+        if ($isImport) {
+            $quoteChar = $m[1];
+            $uri = $m[2];
+        } else {
+            // $m[1] is either quoted or not
+            $quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
+                ? $m[1][0]
+                : '';
+            $uri = ($quoteChar === '')
+                ? $m[1]
+                : substr($m[1], 1, strlen($m[1]) - 2);
+        }
+        // analyze URI
+        if ('/' !== $uri[0]                  // root-relative
+            && false === strpos($uri, '//')  // protocol (non-data)
+            && 0 !== strpos($uri, 'data:')   // data protocol
+        ) {
+            // URI is file-relative: rewrite depending on options
+            if (self::$_prependPath === null) {
+                $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
+            } else {
+                $uri = self::$_prependPath . $uri;
+                if ($uri[0] === '/') {
+                    $root = '';
+                    $rootRelative = $uri;
+                    $uri = $root . self::removeDots($rootRelative);
+                } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
+                    $root = $m[1];
+                    $rootRelative = substr($uri, strlen($root));
+                    $uri = $root . self::removeDots($rootRelative);
+                }
+            }
+        }
+        return $isImport
+            ? "@import {$quoteChar}{$uri}{$quoteChar}"
+            : "url({$quoteChar}{$uri}{$quoteChar})";
+    }
+}
diff --git a/application/libraries/OAuth.php b/application/libraries/OAuth.php
new file mode 100644
index 0000000..e763041
--- /dev/null
+++ b/application/libraries/OAuth.php
@@ -0,0 +1,872 @@
+<?php
+// vim: foldmethod=marker
+
+/* Generic exception class
+ */
+class OAuthException extends Exception {
+  // pass
+}
+
+class OAuthConsumer {
+  public $key;
+  public $secret;
+
+  function __construct($key, $secret, $callback_url=NULL) {
+    $this->key = $key;
+    $this->secret = $secret;
+    $this->callback_url = $callback_url;
+  }
+
+  function __toString() {
+    return "OAuthConsumer[key=$this->key,secret=$this->secret]";
+  }
+}
+
+class OAuthToken {
+  // access tokens and request tokens
+  public $key;
+  public $secret;
+
+  /**
+   * key = the token
+   * secret = the token secret
+   */
+  function __construct($key, $secret) {
+    $this->key = $key;
+    $this->secret = $secret;
+  }
+
+  /**
+   * generates the basic string serialization of a token that a server
+   * would respond to request_token and access_token calls with
+   */
+  function to_string() {
+    return "oauth_token=" .
+           OAuthUtil::urlencode_rfc3986($this->key) .
+           "&oauth_token_secret=" .
+           OAuthUtil::urlencode_rfc3986($this->secret);
+  }
+
+  function __toString() {
+    return $this->to_string();
+  }
+}
+
+/**
+ * A class for implementing a Signature Method
+ * See section 9 ("Signing Requests") in the spec
+ */
+abstract class OAuthSignatureMethod {
+  /**
+   * Needs to return the name of the Signature Method (ie HMAC-SHA1)
+   * @return string
+   */
+  abstract public function get_name();
+
+  /**
+   * Build up the signature
+   * NOTE: The output of this function MUST NOT be urlencoded.
+   * the encoding is handled in OAuthRequest when the final
+   * request is serialized
+   * @param OAuthRequest $request
+   * @param OAuthConsumer $consumer
+   * @param OAuthToken $token
+   * @return string
+   */
+  abstract public function build_signature($request, $consumer, $token);
+
+  /**
+   * Verifies that a given signature is correct
+   * @param OAuthRequest $request
+   * @param OAuthConsumer $consumer
+   * @param OAuthToken $token
+   * @param string $signature
+   * @return bool
+   */
+  public function check_signature($request, $consumer, $token, $signature) {
+    $built = $this->build_signature($request, $consumer, $token);
+    return $built == $signature;
+  }
+}
+
+/**
+ * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] 
+ * where the Signature Base String is the text and the key is the concatenated values (each first 
+ * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' 
+ * character (ASCII code 38) even if empty.
+ *   - Chapter 9.2 ("HMAC-SHA1")
+ */
+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
+  function get_name() {
+    return "HMAC-SHA1";
+  }
+
+  public function build_signature($request, $consumer, $token) {
+    $base_string = $request->get_signature_base_string();
+    $request->base_string = $base_string;
+
+    $key_parts = array(
+      $consumer->secret,
+      ($token) ? $token->secret : ""
+    );
+
+    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
+    $key = implode('&', $key_parts);
+
+    return base64_encode(hash_hmac('sha1', $base_string, $key, true));
+  }
+}
+
+/**
+ * The PLAINTEXT method does not provide any security protection and SHOULD only be used 
+ * over a secure channel such as HTTPS. It does not use the Signature Base String.
+ *   - Chapter 9.4 ("PLAINTEXT")
+ */
+class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
+  public function get_name() {
+    return "PLAINTEXT";
+  }
+
+  /**
+   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and 
+   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is 
+   * empty. The result MUST be encoded again.
+   *   - Chapter 9.4.1 ("Generating Signatures")
+   *
+   * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
+   * OAuthRequest handles this!
+   */
+  public function build_signature($request, $consumer, $token) {
+    $key_parts = array(
+      $consumer->secret,
+      ($token) ? $token->secret : ""
+    );
+
+    $key_parts = OAuthUtil::urlencode_rfc3986($key_parts);
+    $key = implode('&', $key_parts);
+    $request->base_string = $key;
+
+    return $key;
+  }
+}
+
+/**
+ * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in 
+ * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for 
+ * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a 
+ * verified way to the Service Provider, in a manner which is beyond the scope of this 
+ * specification.
+ *   - Chapter 9.3 ("RSA-SHA1")
+ */
+abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
+  public function get_name() {
+    return "RSA-SHA1";
+  }
+
+  // Up to the SP to implement this lookup of keys. Possible ideas are:
+  // (1) do a lookup in a table of trusted certs keyed off of consumer
+  // (2) fetch via http using a url provided by the requester
+  // (3) some sort of specific discovery code based on request
+  //
+  // Either way should return a string representation of the certificate
+  protected abstract function fetch_public_cert(&$request);
+
+  // Up to the SP to implement this lookup of keys. Possible ideas are:
+  // (1) do a lookup in a table of trusted certs keyed off of consumer
+  //
+  // Either way should return a string representation of the certificate
+  protected abstract function fetch_private_cert(&$request);
+
+  public function build_signature($request, $consumer, $token) {
+    $base_string = $request->get_signature_base_string();
+    $request->base_string = $base_string;
+
+    // Fetch the private key cert based on the request
+    $cert = $this->fetch_private_cert($request);
+
+    // Pull the private key ID from the certificate
+    $privatekeyid = openssl_get_privatekey($cert);
+
+    // Sign using the key
+    $ok = openssl_sign($base_string, $signature, $privatekeyid);
+
+    // Release the key resource
+    openssl_free_key($privatekeyid);
+
+    return base64_encode($signature);
+  }
+
+  public function check_signature($request, $consumer, $token, $signature) {
+    $decoded_sig = base64_decode($signature);
+
+    $base_string = $request->get_signature_base_string();
+
+    // Fetch the public key cert based on the request
+    $cert = $this->fetch_public_cert($request);
+
+    // Pull the public key ID from the certificate
+    $publickeyid = openssl_get_publickey($cert);
+
+    // Check the computed signature against the one passed in the query
+    $ok = openssl_verify($base_string, $decoded_sig, $publickeyid);
+
+    // Release the key resource
+    openssl_free_key($publickeyid);
+
+    return $ok == 1;
+  }
+}
+
+class OAuthRequest {
+  private $parameters;
+  private $http_method;
+  private $http_url;
+  // for debug purposes
+  public $base_string;
+  public static $version = '1.0';
+  public static $POST_INPUT = 'php://input';
+
+  function __construct($http_method, $http_url, $parameters=NULL) {
+    @$parameters or $parameters = array();
+    $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
+    $this->parameters = $parameters;
+    $this->http_method = $http_method;
+    $this->http_url = $http_url;
+  }
+
+
+  /**
+   * attempt to build up a request from what was passed to the server
+   */
+  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
+    $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
+              ? 'http'
+              : 'https';
+    @$http_url or $http_url = $scheme .
+                              '://' . $_SERVER['HTTP_HOST'] .
+                              ':' .
+                              $_SERVER['SERVER_PORT'] .
+                              $_SERVER['REQUEST_URI'];
+    @$http_method or $http_method = $_SERVER['REQUEST_METHOD'];
+
+    // We weren't handed any parameters, so let's find the ones relevant to
+    // this request.
+    // If you run XML-RPC or similar you should use this to provide your own
+    // parsed parameter-list
+    if (!$parameters) {
+      // Find request headers
+      $request_headers = OAuthUtil::get_headers();
+
+      // Parse the query-string to find GET parameters
+      $parameters = OAuthUtil::parse_parameters($_SERVER['QUERY_STRING']);
+
+      // It's a POST request of the proper content-type, so parse POST
+      // parameters and add those overriding any duplicates from GET
+      if ($http_method == "POST"
+          && @strstr($request_headers["Content-Type"],
+                     "application/x-www-form-urlencoded")
+          ) {
+        $post_data = OAuthUtil::parse_parameters(
+          file_get_contents(self::$POST_INPUT)
+        );
+        $parameters = array_merge($parameters, $post_data);
+      }
+
+      // We have a Authorization-header with OAuth data. Parse the header
+      // and add those overriding any duplicates from GET or POST
+      if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") {
+        $header_parameters = OAuthUtil::split_header(
+          $request_headers['Authorization']
+        );
+        $parameters = array_merge($parameters, $header_parameters);
+      }
+
+    }
+
+    return new OAuthRequest($http_method, $http_url, $parameters);
+  }
+
+  /**
+   * pretty much a helper function to set up the request
+   */
+  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
+    @$parameters or $parameters = array();
+    $defaults = array("oauth_version" => OAuthRequest::$version,
+                      "oauth_nonce" => OAuthRequest::generate_nonce(),
+                      "oauth_timestamp" => OAuthRequest::generate_timestamp(),
+                      "oauth_consumer_key" => $consumer->key);
+    if ($token)
+      $defaults['oauth_token'] = $token->key;
+
+    $parameters = array_merge($defaults, $parameters);
+
+    return new OAuthRequest($http_method, $http_url, $parameters);
+  }
+
+  public function set_parameter($name, $value, $allow_duplicates = true) {
+    if ($allow_duplicates && isset($this->parameters[$name])) {
+      // We have already added parameter(s) with this name, so add to the list
+      if (is_scalar($this->parameters[$name])) {
+        // This is the first duplicate, so transform scalar (string)
+        // into an array so we can add the duplicates
+        $this->parameters[$name] = array($this->parameters[$name]);
+      }
+
+      $this->parameters[$name][] = $value;
+    } else {
+      $this->parameters[$name] = $value;
+    }
+  }
+
+  public function get_parameter($name) {
+    return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
+  }
+
+  public function get_parameters() {
+    return $this->parameters;
+  }
+
+  public function unset_parameter($name) {
+    unset($this->parameters[$name]);
+  }
+
+  /**
+   * The request parameters, sorted and concatenated into a normalized string.
+   * @return string
+   */
+  public function get_signable_parameters() {
+    // Grab all parameters
+    $params = $this->parameters;
+
+    // Remove oauth_signature if present
+    // Ref: Spec: 9.1.1 ("The oauth_signature parameter MUST be excluded.")
+    if (isset($params['oauth_signature'])) {
+      unset($params['oauth_signature']);
+    }
+
+    return OAuthUtil::build_http_query($params);
+  }
+
+  /**
+   * Returns the base string of this request
+   *
+   * The base string defined as the method, the url
+   * and the parameters (normalized), each urlencoded
+   * and the concated with &.
+   */
+  public function get_signature_base_string() {
+    $parts = array(
+      $this->get_normalized_http_method(),
+      $this->get_normalized_http_url(),
+      $this->get_signable_parameters()
+    );
+
+    $parts = OAuthUtil::urlencode_rfc3986($parts);
+
+    return implode('&', $parts);
+  }
+
+  /**
+   * just uppercases the http method
+   */
+  public function get_normalized_http_method() {
+    return strtoupper($this->http_method);
+  }
+
+  /**
+   * parses the url and rebuilds it to be
+   * scheme://host/path
+   */
+  public function get_normalized_http_url() {
+    $parts = parse_url($this->http_url);
+
+    $port = @$parts['port'];
+    $scheme = $parts['scheme'];
+    $host = $parts['host'];
+    $path = @$parts['path'];
+
+    $port or $port = ($scheme == 'https') ? '443' : '80';
+
+    if (($scheme == 'https' && $port != '443')
+        || ($scheme == 'http' && $port != '80')) {
+      $host = "$host:$port";
+    }
+    return "$scheme://$host$path";
+  }
+
+  /**
+   * builds a url usable for a GET request
+   */
+  public function to_url() {
+    $post_data = $this->to_postdata();
+    $out = $this->get_normalized_http_url();
+    if ($post_data) {
+      $out .= '?'.$post_data;
+    }
+    return $out;
+  }
+
+  /**
+   * builds the data one would send in a POST request
+   */
+  public function to_postdata() {
+    return OAuthUtil::build_http_query($this->parameters);
+  }
+
+  /**
+   * builds the Authorization: header
+   */
+  public function to_header($realm=null) {
+    $first = true;
+	if($realm) {
+      $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
+      $first = false;
+    } else
+      $out = 'Authorization: OAuth';
+
+    $total = array();
+    foreach ($this->parameters as $k => $v) {
+      if (substr($k, 0, 5) != "oauth") continue;
+      if (is_array($v)) {
+        throw new OAuthException('Arrays not supported in headers');
+      }
+      $out .= ($first) ? ' ' : ',';
+      $out .= OAuthUtil::urlencode_rfc3986($k) .
+              '="' .
+              OAuthUtil::urlencode_rfc3986($v) .
+              '"';
+      $first = false;
+    }
+    return $out;
+  }
+
+  public function __toString() {
+    return $this->to_url();
+  }
+
+
+  public function sign_request($signature_method, $consumer, $token) {
+    $this->set_parameter(
+      "oauth_signature_method",
+      $signature_method->get_name(),
+      false
+    );
+    $signature = $this->build_signature($signature_method, $consumer, $token);
+    $this->set_parameter("oauth_signature", $signature, false);
+  }
+
+  public function build_signature($signature_method, $consumer, $token) {
+    $signature = $signature_method->build_signature($this, $consumer, $token);
+    return $signature;
+  }
+
+  /**
+   * util function: current timestamp
+   */
+  private static function generate_timestamp() {
+    return time();
+  }
+
+  /**
+   * util function: current nonce
+   */
+  private static function generate_nonce() {
+    $mt = microtime();
+    $rand = mt_rand();
+
+    return md5($mt . $rand); // md5s look nicer than numbers
+  }
+}
+
+class OAuthServer {
+  protected $timestamp_threshold = 300; // in seconds, five minutes
+  protected $version = '1.0';             // hi blaine
+  protected $signature_methods = array();
+
+  protected $data_store;
+
+  function __construct($data_store) {
+    $this->data_store = $data_store;
+  }
+
+  public function add_signature_method($signature_method) {
+    $this->signature_methods[$signature_method->get_name()] =
+      $signature_method;
+  }
+
+  // high level functions
+
+  /**
+   * process a request_token request
+   * returns the request token on success
+   */
+  public function fetch_request_token(&$request) {
+    $this->get_version($request);
+
+    $consumer = $this->get_consumer($request);
+
+    // no token required for the initial token request
+    $token = NULL;
+
+    $this->check_signature($request, $consumer, $token);
+
+    // Rev A change
+    $callback = $request->get_parameter('oauth_callback');
+    $new_token = $this->data_store->new_request_token($consumer, $callback);
+
+    return $new_token;
+  }
+
+  /**
+   * process an access_token request
+   * returns the access token on success
+   */
+  public function fetch_access_token(&$request) {
+    $this->get_version($request);
+
+    $consumer = $this->get_consumer($request);
+
+    // requires authorized request token
+    $token = $this->get_token($request, $consumer, "request");
+
+    $this->check_signature($request, $consumer, $token);
+
+    // Rev A change
+    $verifier = $request->get_parameter('oauth_verifier');
+    $new_token = $this->data_store->new_access_token($token, $consumer, $verifier);
+
+    return $new_token;
+  }
+
+  /**
+   * verify an api call, checks all the parameters
+   */
+  public function verify_request(&$request) {
+    $this->get_version($request);
+    $consumer = $this->get_consumer($request);
+    $token = $this->get_token($request, $consumer, "access");
+    $this->check_signature($request, $consumer, $token);
+    return array($consumer, $token);
+  }
+
+  // Internals from here
+  /**
+   * version 1
+   */
+  private function get_version(&$request) {
+    $version = $request->get_parameter("oauth_version");
+    if (!$version) {
+      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. 
+      // Chapter 7.0 ("Accessing Protected Ressources")
+      $version = '1.0';
+    }
+    if ($version !== $this->version) {
+      throw new OAuthException("OAuth version '$version' not supported");
+    }
+    return $version;
+  }
+
+  /**
+   * figure out the signature with some defaults
+   */
+  private function get_signature_method(&$request) {
+    $signature_method =
+        @$request->get_parameter("oauth_signature_method");
+
+    if (!$signature_method) {
+      // According to chapter 7 ("Accessing Protected Ressources") the signature-method
+      // parameter is required, and we can't just fallback to PLAINTEXT
+      throw new OAuthException('No signature method parameter. This parameter is required');
+    }
+
+    if (!in_array($signature_method,
+                  array_keys($this->signature_methods))) {
+      throw new OAuthException(
+        "Signature method '$signature_method' not supported " .
+        "try one of the following: " .
+        implode(", ", array_keys($this->signature_methods))
+      );
+    }
+    return $this->signature_methods[$signature_method];
+  }
+
+  /**
+   * try to find the consumer for the provided request's consumer key
+   */
+  private function get_consumer(&$request) {
+    $consumer_key = @$request->get_parameter("oauth_consumer_key");
+    if (!$consumer_key) {
+      throw new OAuthException("Invalid consumer key");
+    }
+
+    $consumer = $this->data_store->lookup_consumer($consumer_key);
+    if (!$consumer) {
+      throw new OAuthException("Invalid consumer");
+    }
+
+    return $consumer;
+  }
+
+  /**
+   * try to find the token for the provided request's token key
+   */
+  private function get_token(&$request, $consumer, $token_type="access") {
+    $token_field = @$request->get_parameter('oauth_token');
+    $token = $this->data_store->lookup_token(
+      $consumer, $token_type, $token_field
+    );
+    if (!$token) {
+      throw new OAuthException("Invalid $token_type token: $token_field");
+    }
+    return $token;
+  }
+
+  /**
+   * all-in-one function to check the signature on a request
+   * should guess the signature method appropriately
+   */
+  private function check_signature(&$request, $consumer, $token) {
+    // this should probably be in a different method
+    $timestamp = @$request->get_parameter('oauth_timestamp');
+    $nonce = @$request->get_parameter('oauth_nonce');
+
+    $this->check_timestamp($timestamp);
+    $this->check_nonce($consumer, $token, $nonce, $timestamp);
+
+    $signature_method = $this->get_signature_method($request);
+
+    $signature = $request->get_parameter('oauth_signature');
+    $valid_sig = $signature_method->check_signature(
+      $request,
+      $consumer,
+      $token,
+      $signature
+    );
+
+    if (!$valid_sig) {
+      throw new OAuthException("Invalid signature");
+    }
+  }
+
+  /**
+   * check that the timestamp is new enough
+   */
+  private function check_timestamp($timestamp) {
+    if( ! $timestamp )
+      throw new OAuthException(
+        'Missing timestamp parameter. The parameter is required'
+      );
+    
+    // verify that timestamp is recentish
+    $now = time();
+    if (abs($now - $timestamp) > $this->timestamp_threshold) {
+      throw new OAuthException(
+        "Expired timestamp, yours $timestamp, ours $now"
+      );
+    }
+  }
+
+  /**
+   * check that the nonce is not repeated
+   */
+  private function check_nonce($consumer, $token, $nonce, $timestamp) {
+    if( ! $nonce )
+      throw new OAuthException(
+        'Missing nonce parameter. The parameter is required'
+      );
+
+    // verify that the nonce is uniqueish
+    $found = $this->data_store->lookup_nonce(
+      $consumer,
+      $token,
+      $nonce,
+      $timestamp
+    );
+    if ($found) {
+      throw new OAuthException("Nonce already used: $nonce");
+    }
+  }
+
+}
+
+class OAuthDataStore {
+  function lookup_consumer($consumer_key) {
+    // implement me
+  }
+
+  function lookup_token($consumer, $token_type, $token) {
+    // implement me
+  }
+
+  function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+    // implement me
+  }
+
+  function new_request_token($consumer, $callback = null) {
+    // return a new token attached to this consumer
+  }
+
+  function new_access_token($token, $consumer, $verifier = null) {
+    // return a new access token attached to this consumer
+    // for the user associated with this token if the request token
+    // is authorized
+    // should also invalidate the request token
+  }
+
+}
+
+class OAuthUtil {
+  public static function urlencode_rfc3986($input) {
+  if (is_array($input)) {
+    return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
+  } else if (is_scalar($input)) {
+    return str_replace(
+      '+',
+      ' ',
+      str_replace('%7E', '~', rawurlencode($input))
+    );
+  } else {
+    return '';
+  }
+}
+
+
+  // This decode function isn't taking into consideration the above
+  // modifications to the encoding process. However, this method doesn't
+  // seem to be used anywhere so leaving it as is.
+  public static function urldecode_rfc3986($string) {
+    return urldecode($string);
+  }
+
+  // Utility function for turning the Authorization: header into
+  // parameters, has to do some unescaping
+  // Can filter out any non-oauth parameters if needed (default behaviour)
+  public static function split_header($header, $only_allow_oauth_parameters = true) {
+    $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
+    $offset = 0;
+    $params = array();
+    while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
+      $match = $matches[0];
+      $header_name = $matches[2][0];
+      $header_content = (isset($matches[5])) ? $matches[5][0] : $matches[4][0];
+      if (preg_match('/^oauth_/', $header_name) || !$only_allow_oauth_parameters) {
+        $params[$header_name] = OAuthUtil::urldecode_rfc3986($header_content);
+      }
+      $offset = $match[1] + strlen($match[0]);
+    }
+
+    if (isset($params['realm'])) {
+      unset($params['realm']);
+    }
+
+    return $params;
+  }
+
+  // helper to try to sort out headers for people who aren't running apache
+  public static function get_headers() {
+    if (function_exists('apache_request_headers')) {
+      // we need this to get the actual Authorization: header
+      // because apache tends to tell us it doesn't exist
+      $headers = apache_request_headers();
+
+      // sanitize the output of apache_request_headers because
+      // we always want the keys to be Cased-Like-This and arh()
+      // returns the headers in the same case as they are in the
+      // request
+      $out = array();
+      foreach( $headers AS $key => $value ) {
+        $key = str_replace(
+            " ",
+            "-",
+            ucwords(strtolower(str_replace("-", " ", $key)))
+          );
+        $out[$key] = $value;
+      }
+    } else {
+      // otherwise we don't have apache and are just going to have to hope
+      // that $_SERVER actually contains what we need
+      $out = array();
+      if( isset($_SERVER['CONTENT_TYPE']) )
+        $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
+      if( isset($_ENV['CONTENT_TYPE']) )
+        $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
+
+      foreach ($_SERVER as $key => $value) {
+        if (substr($key, 0, 5) == "HTTP_") {
+          // this is chaos, basically it is just there to capitalize the first
+          // letter of every word that is not an initial HTTP and strip HTTP
+          // code from przemek
+          $key = str_replace(
+            " ",
+            "-",
+            ucwords(strtolower(str_replace("_", " ", substr($key, 5))))
+          );
+          $out[$key] = $value;
+        }
+      }
+    }
+    return $out;
+  }
+
+  // This function takes a input like a=b&a=c&d=e and returns the parsed
+  // parameters like this
+  // array('a' => array('b','c'), 'd' => 'e')
+  public static function parse_parameters( $input ) {
+    if (!isset($input) || !$input) return array();
+
+    $pairs = explode('&', $input);
+
+    $parsed_parameters = array();
+    foreach ($pairs as $pair) {
+      $split = explode('=', $pair, 2);
+      $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
+      $value = isset($split[1]) ? OAuthUtil::urldecode_rfc3986($split[1]) : '';
+
+      if (isset($parsed_parameters[$parameter])) {
+        // We have already recieved parameter(s) with this name, so add to the list
+        // of parameters with this name
+
+        if (is_scalar($parsed_parameters[$parameter])) {
+          // This is the first duplicate, so transform scalar (string) into an array
+          // so we can add the duplicates
+          $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
+        }
+
+        $parsed_parameters[$parameter][] = $value;
+      } else {
+        $parsed_parameters[$parameter] = $value;
+      }
+    }
+    return $parsed_parameters;
+  }
+
+  public static function build_http_query($params) {
+    if (!$params) return '';
+
+    // Urlencode both keys and values
+    $keys = OAuthUtil::urlencode_rfc3986(array_keys($params));
+    $values = OAuthUtil::urlencode_rfc3986(array_values($params));
+    $params = array_combine($keys, $values);
+
+    // Parameters are sorted by name, using lexicographical byte value ordering.
+    // Ref: Spec: 9.1.1 (1)
+    uksort($params, 'strcmp');
+
+    $pairs = array();
+    foreach ($params as $parameter => $value) {
+      if (is_array($value)) {
+        // If two or more parameters share the same name, they are sorted by their value
+        // Ref: Spec: 9.1.1 (1)
+        natsort($value);
+        foreach ($value as $duplicate_value) {
+          $pairs[] = $parameter . '=' . $duplicate_value;
+        }
+      } else {
+        $pairs[] = $parameter . '=' . $value;
+      }
+    }
+    // For each parameter, the name is separated from the corresponding value by an '=' character (ASCII code 61)
+    // Each name-value pair is separated by an '&' character (ASCII code 38)
+    return implode('&', $pairs);
+  }
+}
diff --git a/application/libraries/OpenID.php b/application/libraries/OpenID.php
new file mode 100644
index 0000000..54ce055
--- /dev/null
+++ b/application/libraries/OpenID.php
@@ -0,0 +1,742 @@
+<?php
+/**
+ * This class provides a simple interface for OpenID (1.1 and 2.0) authentication.
+ * Supports Yadis discovery.
+ * The authentication process is stateless/dumb.
+ *
+ * Usage:
+ * Sign-on with OpenID is a two step process:
+ * Step one is authentication with the provider:
+ * <code>
+ * $openid = new LightOpenID;
+ * $openid->identity = 'ID supplied by user';
+ * header('Location: ' . $openid->authUrl());
+ * </code>
+ * The provider then sends various parameters via GET, one of them is openid_mode.
+ * Step two is verification:
+ * <code>
+ * if ($this->data['openid_mode']) {
+ *     $openid = new LightOpenID;
+ *     echo $openid->validate() ? 'Logged in.' : 'Failed';
+ * }
+ * </code>
+ *
+ * Optionally, you can set $returnUrl and $realm (or $trustRoot, which is an alias).
+ * The default values for those are:
+ * $openid->realm     = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
+ * $openid->returnUrl = $openid->realm . $_SERVER['REQUEST_URI'];
+ * If you don't know their meaning, refer to any openid tutorial, or specification. Or just guess.
+ *
+ * AX and SREG extensions are supported.
+ * To use them, specify $openid->required and/or $openid->optional before calling $openid->authUrl().
+ * These are arrays, with values being AX schema paths (the 'path' part of the URL).
+ * For example:
+ *   $openid->required = array('namePerson/friendly', 'contact/email');
+ *   $openid->optional = array('namePerson/first');
+ * If the server supports only SREG or OpenID 1.1, these are automaticaly
+ * mapped to SREG names, so that user doesn't have to know anything about the server.
+ *
+ * To get the values, use $openid->getAttributes().
+ *
+ *
+ * The library requires PHP >= 5.1.2 with curl or http/https stream wrappers enabled.
+ * @author Mewp
+ * @copyright Copyright (c) 2010, Mewp
+ * @license http://www.opensource.org/licenses/mit-license.php MIT
+ */
+class OpenID_Core {
+	
+    public $returnUrl
+         , $required = array()
+         , $optional = array()
+         , $verify_peer = null
+         , $capath = null
+         , $cainfo = null;
+    private $identity, $claimed_id;
+    protected $server, $version, $trustRoot, $aliases, $identifier_select = false
+            , $ax = false, $sreg = false, $data;
+    static protected $ax_to_sreg = array(
+        'namePerson/friendly'     => 'nickname',
+        'contact/email'           => 'email',
+        'namePerson'              => 'fullname',
+        'birthDate'               => 'dob',
+        'person/gender'           => 'gender',
+        'contact/postalCode/home' => 'postcode',
+        'contact/country/home'    => 'country',
+        'pref/language'           => 'language',
+        'pref/timezone'           => 'timezone',
+        );
+
+    function __construct()
+    {
+        $this->trustRoot = (!empty($_SERVER['HTTPS']) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'];
+        $uri = rtrim(preg_replace('#((?<=\?)|&)openid\.[^&]+#', '', $_SERVER['REQUEST_URI']), '?');
+        $this->returnUrl = $this->trustRoot . $uri;
+
+        $this->data = $_POST + $_GET; # OPs may send data as POST or GET.
+    }
+
+    function __set($name, $value)
+    {
+        switch ($name) {
+        case 'identity':
+            if (strlen($value = trim((String) $value))) {
+                if (preg_match('#^xri:/*#i', $value, $m)) {
+                    $value = substr($value, strlen($m[0]));
+                } elseif (!preg_match('/^(?:[=@+\$!\(]|https?:)/i', $value)) {
+                    $value = "http://$value";
+                }
+                if (preg_match('#^https?://[^/]+$#i', $value, $m)) {
+                    $value .= '/';
+                }
+            }
+            $this->$name = $this->claimed_id = $value;
+            break;
+        case 'trustRoot':
+        case 'realm':
+            $this->trustRoot = trim($value);
+        }
+    }
+
+    function __get($name)
+    {
+        switch ($name) {
+        case 'identity':
+            # We return claimed_id instead of identity,
+            # because the developer should see the claimed identifier,
+            # i.e. what he set as identity, not the op-local identifier (which is what we verify)
+            return $this->claimed_id;
+        case 'trustRoot':
+        case 'realm':
+            return $this->trustRoot;
+        case 'mode':
+            return empty($this->data['openid_mode']) ? null : $this->data['openid_mode'];
+        case 'server':
+            return $this->server;
+        }
+    }
+
+    /**
+     * Checks if the server specified in the url exists.
+     *
+     * @param $url url to check
+     * @return true, if the server exists; false otherwise
+     */
+    function hostExists($url)
+    {
+        if (strpos($url, '/') === false) {
+            $server = $url;
+        } else {
+            $server = @parse_url($url, PHP_URL_HOST);
+        }
+
+        if (!$server) {
+            return false;
+        }
+
+        return !!gethostbynamel($server);
+    }
+
+    protected function request_curl($url, $method='GET', $params=array())
+    {
+        $params = http_build_query($params, '', '&');
+        $curl = curl_init($url . ($method == 'GET' && $params ? '?' . $params : ''));
+        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
+        curl_setopt($curl, CURLOPT_HEADER, false);
+        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_HTTPHEADER, array('Accept: application/xrds+xml, */*'));
+
+        if($this->verify_peer !== null) {
+            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, $this->verify_peer);
+            if($this->capath) {
+                curl_setopt($curl, CURLOPT_CAPATH, $this->capath);
+            }
+
+            if($this->cainfo) {
+                curl_setopt($curl, CURLOPT_CAINFO, $this->cainfo);
+            }
+        }
+
+        if ($method == 'POST') {
+            curl_setopt($curl, CURLOPT_POST, true);
+            curl_setopt($curl, CURLOPT_POSTFIELDS, $params);
+        } elseif ($method == 'HEAD') {
+            curl_setopt($curl, CURLOPT_HEADER, true);
+            curl_setopt($curl, CURLOPT_NOBODY, true);
+        } else {
+            curl_setopt($curl, CURLOPT_HTTPGET, true);
+        }
+        $response = curl_exec($curl);
+
+        if($method == 'HEAD') {
+            $headers = array();
+            foreach(explode("\n", $response) as $header) {
+                $pos = strpos($header,':');
+                $name = strtolower(trim(substr($header, 0, $pos)));
+                $headers[$name] = trim(substr($header, $pos+1));
+            }
+
+            # Updating claimed_id in case of redirections.
+            $effective_url = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL);
+            if($effective_url != $url) {
+                $this->identity = $this->claimed_id = $effective_url;
+            }
+
+            return $headers;
+        }
+
+        if (curl_errno($curl)) {
+            throw new ErrorException(curl_error($curl), curl_errno($curl));
+        }
+
+        return $response;
+    }
+
+    protected function request_streams($url, $method='GET', $params=array())
+    {
+        if(!$this->hostExists($url)) {
+            throw new ErrorException('Invalid request.');
+        }
+
+        $params = http_build_query($params, '', '&');
+        switch($method) {
+        case 'GET':
+            $opts = array(
+                'http' => array(
+                    'method' => 'GET',
+                    'header' => 'Accept: application/xrds+xml, */*',
+                    'ignore_errors' => true,
+                )
+            );
+            $url = $url . ($params ? '?' . $params : '');
+            break;
+        case 'POST':
+            $opts = array(
+                'http' => array(
+                    'method' => 'POST',
+                    'header'  => 'Content-type: application/x-www-form-urlencoded',
+                    'content' => $params,
+                    'ignore_errors' => true,
+                )
+            );
+            break;
+        case 'HEAD':
+            # We want to send a HEAD request,
+            # but since get_headers doesn't accept $context parameter,
+            # we have to change the defaults.
+            $default = stream_context_get_options(stream_context_get_default());
+            stream_context_get_default(
+                array('http' => array(
+                    'method' => 'HEAD',
+                    'header' => 'Accept: application/xrds+xml, */*',
+                    'ignore_errors' => true,
+                ))
+            );
+
+            $url = $url . ($params ? '?' . $params : '');
+            $headers_tmp = get_headers ($url);
+            if(!$headers_tmp) {
+                return array();
+            }
+
+            # Parsing headers.
+            $headers = array();
+            foreach($headers_tmp as $header) {
+                $pos = strpos($header,':');
+                $name = strtolower(trim(substr($header, 0, $pos)));
+                $headers[$name] = trim(substr($header, $pos+1));
+
+                # Following possible redirections. The point is just to have
+                # claimed_id change with them, because get_headers() will
+                # follow redirections automatically.
+                # We ignore redirections with relative paths.
+                # If any known provider uses them, file a bug report.
+                if($name == 'location') {
+                    if(strpos($headers[$name], 'http') === 0) {
+                        $this->identity = $this->claimed_id = $headers[$name];
+                    } elseif($headers[$name][0] == '/') {
+                        $parsed_url = parse_url($this->claimed_id);
+                        $this->identity =
+                        $this->claimed_id = $parsed_url['scheme'] . '://'
+                                          . $parsed_url['host']
+                                          . $headers[$name];
+                    }
+                }
+            }
+
+            # And restore them.
+            stream_context_get_default($default);
+            return $headers;
+        }
+
+        if($this->verify_peer) {
+            $opts += array('ssl' => array(
+                'verify_peer' => true,
+                'capath'      => $this->capath,
+                'cafile'      => $this->cainfo,
+            ));
+        }
+
+        $context = stream_context_create ($opts);
+
+        return file_get_contents($url, false, $context);
+    }
+
+    protected function request($url, $method='GET', $params=array())
+    {
+        if(function_exists('curl_init') && !ini_get('safe_mode')) {
+            return $this->request_curl($url, $method, $params);
+        }
+        return $this->request_streams($url, $method, $params);
+    }
+
+    protected function build_url($url, $parts)
+    {
+        if (isset($url['query'], $parts['query'])) {
+            $parts['query'] = $url['query'] . '&' . $parts['query'];
+        }
+
+        $url = $parts + $url;
+        $url = $url['scheme'] . '://'
+             . (empty($url['username'])?''
+                 :(empty($url['password'])? "{$url['username']}@"
+                 :"{$url['username']}:{$url['password']}@"))
+             . $url['host']
+             . (empty($url['port'])?'':":{$url['port']}")
+             . (empty($url['path'])?'':$url['path'])
+             . (empty($url['query'])?'':"?{$url['query']}")
+             . (empty($url['fragment'])?'':"#{$url['fragment']}");
+        return $url;
+    }
+
+    /**
+     * Helper function used to scan for <meta>/<link> tags and extract information
+     * from them
+     */
+    protected function htmlTag($content, $tag, $attrName, $attrValue, $valueName)
+    {
+        preg_match_all("#<{$tag}[^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*$valueName=['\"](.+?)['\"][^>]*/?>#i", $content, $matches1);
+        preg_match_all("#<{$tag}[^>]*$valueName=['\"](.+?)['\"][^>]*$attrName=['\"].*?$attrValue.*?['\"][^>]*/?>#i", $content, $matches2);
+
+        $result = array_merge($matches1[1], $matches2[1]);
+        return empty($result)?false:$result[0];
+    }
+
+    /**
+     * Performs Yadis and HTML discovery. Normally not used.
+     * @param $url Identity URL.
+     * @return String OP Endpoint (i.e. OpenID provider address).
+     * @throws ErrorException
+     */
+    function discover($url)
+    {
+        if (!$url) throw new ErrorException('No identity supplied.');
+        # Use xri.net proxy to resolve i-name identities
+        if (!preg_match('#^https?:#', $url)) {
+            $url = Kohana::config('config.external_site_protocol')."://xri.net/$url";
+        }
+
+        # We save the original url in case of Yadis discovery failure.
+        # It can happen when we'll be lead to an XRDS document
+        # which does not have any OpenID2 services.
+        $originalUrl = $url;
+
+        # A flag to disable yadis discovery in case of failure in headers.
+        $yadis = true;
+
+        # We'll jump a maximum of 5 times, to avoid endless redirections.
+        for ($i = 0; $i < 5; $i ++) {
+            if ($yadis) {
+                $headers = $this->request($url, 'HEAD');
+
+                $next = false;
+                if (isset($headers['x-xrds-location'])) {
+                    $url = $this->build_url(parse_url($url), parse_url(trim($headers['x-xrds-location'])));
+                    $next = true;
+                }
+
+                if (isset($headers['content-type'])
+                    && (strpos($headers['content-type'], 'application/xrds+xml') !== false
+                        || strpos($headers['content-type'], 'text/xml') !== false)
+                ) {
+                    # Apparently, some providers return XRDS documents as text/html.
+                    # While it is against the spec, allowing this here shouldn't break
+                    # compatibility with anything.
+                    # ---
+                    # Found an XRDS document, now let's find the server, and optionally delegate.
+                    $content = $this->request($url, 'GET');
+
+                    preg_match_all('#<Service.*?>(.*?)</Service>#s', $content, $m);
+                    foreach($m[1] as $content) {
+                        $content = ' ' . $content; # The space is added, so that strpos doesn't return 0.
+
+                        # OpenID 2
+                        $ns = preg_quote('http://specs.openid.net/auth/2.0/');
+                        if(preg_match('#<Type>\s*'.$ns.'(server|signon)\s*</Type>#s', $content, $type)) {
+                            if ($type[1] == 'server') $this->identifier_select = true;
+
+                            preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
+                            preg_match('#<(Local|Canonical)ID>(.*)</\1ID>#', $content, $delegate);
+                            if (empty($server)) {
+                                return false;
+                            }
+                            # Does the server advertise support for either AX or SREG?
+                            $this->ax   = (bool) strpos($content, '<Type>http://openid.net/srv/ax/1.0</Type>');
+                            $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>')
+                                       || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
+
+                            $server = $server[1];
+                            if (isset($delegate[2])) $this->identity = trim($delegate[2]);
+                            $this->version = 2;
+
+                            $this->server = $server;
+                            return $server;
+                        }
+
+                        # OpenID 1.1
+                        $ns = preg_quote('http://openid.net/signon/1.1');
+                        if (preg_match('#<Type>\s*'.$ns.'\s*</Type>#s', $content)) {
+
+                            preg_match('#<URI.*?>(.*)</URI>#', $content, $server);
+                            preg_match('#<.*?Delegate>(.*)</.*?Delegate>#', $content, $delegate);
+                            if (empty($server)) {
+                                return false;
+                            }
+                            # AX can be used only with OpenID 2.0, so checking only SREG
+                            $this->sreg = strpos($content, '<Type>http://openid.net/sreg/1.0</Type>')
+                                       || strpos($content, '<Type>http://openid.net/extensions/sreg/1.1</Type>');
+
+                            $server = $server[1];
+                            if (isset($delegate[1])) $this->identity = $delegate[1];
+                            $this->version = 1;
+
+                            $this->server = $server;
+                            return $server;
+                        }
+                    }
+
+                    $next = true;
+                    $yadis = false;
+                    $url = $originalUrl;
+                    $content = null;
+                    break;
+                }
+                if ($next) continue;
+
+                # There are no relevant information in headers, so we search the body.
+                $content = $this->request($url, 'GET');
+                $location = $this->htmlTag($content, 'meta', 'http-equiv', 'X-XRDS-Location', 'content');
+                if ($location) {
+                    $url = $this->build_url(parse_url($url), parse_url($location));
+                    continue;
+                }
+            }
+
+            if (!$content) $content = $this->request($url, 'GET');
+
+            # At this point, the YADIS Discovery has failed, so we'll switch
+            # to openid2 HTML discovery, then fallback to openid 1.1 discovery.
+            $server   = $this->htmlTag($content, 'link', 'rel', 'openid2.provider', 'href');
+            $delegate = $this->htmlTag($content, 'link', 'rel', 'openid2.local_id', 'href');
+            $this->version = 2;
+
+            if (!$server) {
+                # The same with openid 1.1
+                $server   = $this->htmlTag($content, 'link', 'rel', 'openid.server', 'href');
+                $delegate = $this->htmlTag($content, 'link', 'rel', 'openid.delegate', 'href');
+                $this->version = 1;
+            }
+
+            if ($server) {
+                # We found an OpenID2 OP Endpoint
+                if ($delegate) {
+                    # We have also found an OP-Local ID.
+                    $this->identity = $delegate;
+                }
+                $this->server = $server;
+                return $server;
+            }
+
+            throw new ErrorException('No servers found!');
+        }
+        throw new ErrorException('Endless redirection!');
+    }
+
+    protected function sregParams()
+    {
+        $params = array();
+        # We always use SREG 1.1, even if the server is advertising only support for 1.0.
+        # That's because it's fully backwards compatibile with 1.0, and some providers
+        # advertise 1.0 even if they accept only 1.1. One such provider is myopenid.com
+        $params['openid.ns.sreg'] = 'http://openid.net/extensions/sreg/1.1';
+        if ($this->required) {
+            $params['openid.sreg.required'] = array();
+            foreach ($this->required as $required) {
+                if (!isset(self::$ax_to_sreg[$required])) continue;
+                $params['openid.sreg.required'][] = self::$ax_to_sreg[$required];
+            }
+            $params['openid.sreg.required'] = implode(',', $params['openid.sreg.required']);
+        }
+
+        if ($this->optional) {
+            $params['openid.sreg.optional'] = array();
+            foreach ($this->optional as $optional) {
+                if (!isset(self::$ax_to_sreg[$optional])) continue;
+                $params['openid.sreg.optional'][] = self::$ax_to_sreg[$optional];
+            }
+            $params['openid.sreg.optional'] = implode(',', $params['openid.sreg.optional']);
+        }
+        return $params;
+    }
+
+    protected function axParams()
+    {
+        $params = array();
+        if ($this->required || $this->optional) {
+            $params['openid.ns.ax'] = 'http://openid.net/srv/ax/1.0';
+            $params['openid.ax.mode'] = 'fetch_request';
+            $this->aliases  = array();
+            $counts   = array();
+            $required = array();
+            $optional = array();
+            foreach (array('required','optional') as $type) {
+                foreach ($this->$type as $alias => $field) {
+                    if (is_int($alias)) $alias = strtr($field, '/', '_');
+                    $this->aliases[$alias] = 'http://axschema.org/' . $field;
+                    if (empty($counts[$alias])) $counts[$alias] = 0;
+                    $counts[$alias] += 1;
+                    ${$type}[] = $alias;
+                }
+            }
+            foreach ($this->aliases as $alias => $ns) {
+                $params['openid.ax.type.' . $alias] = $ns;
+            }
+            foreach ($counts as $alias => $count) {
+                if ($count == 1) continue;
+                $params['openid.ax.count.' . $alias] = $count;
+            }
+
+            # Don't send empty ax.requied and ax.if_available.
+            # Google and possibly other providers refuse to support ax when one of these is empty.
+            if($required) {
+                $params['openid.ax.required'] = implode(',', $required);
+            }
+            if($optional) {
+                $params['openid.ax.if_available'] = implode(',', $optional);
+            }
+        }
+        return $params;
+    }
+
+    protected function authUrl_v1()
+    {
+	$returnUrl = $this->returnUrl;
+        # If we have an openid.delegate that is different from our claimed id,
+        # we need to somehow preserve the claimed id between requests.
+        # The simplest way is to just send it along with the return_to url.
+        if($this->identity != $this->claimed_id) {
+            $returnUrl .= (strpos($returnUrl, '?') ? '&' : '?') . 'openid.claimed_id=' . $this->claimed_id;
+        }
+
+        $params = array(
+            'openid.return_to'  => $returnUrl,
+            'openid.mode'       => 'checkid_setup',
+            'openid.identity'   => $this->identity,
+            'openid.trust_root' => $this->trustRoot,
+            ) + $this->sregParams();
+
+        return $this->build_url(parse_url($this->server)
+                               , array('query' => http_build_query($params, '', '&')));
+    }
+
+    protected function authUrl_v2($identifier_select)
+    {
+        $params = array(
+            'openid.ns'          => 'http://specs.openid.net/auth/2.0',
+            'openid.mode'        => 'checkid_setup',
+            'openid.return_to'   => $this->returnUrl,
+            'openid.realm'       => $this->trustRoot,
+        );
+        if ($this->ax) {
+            $params += $this->axParams();
+        }
+        if ($this->sreg) {
+            $params += $this->sregParams();
+        }
+        if (!$this->ax && !$this->sreg) {
+            # If OP doesn't advertise either SREG, nor AX, let's send them both
+            # in worst case we don't get anything in return.
+            $params += $this->axParams() + $this->sregParams();
+        }
+
+        if ($identifier_select) {
+            $params['openid.identity'] = $params['openid.claimed_id']
+                 = 'http://specs.openid.net/auth/2.0/identifier_select';
+        } else {
+            $params['openid.identity'] = $this->identity;
+            $params['openid.claimed_id'] = $this->claimed_id;
+        }
+
+        return $this->build_url(parse_url($this->server)
+                               , array('query' => http_build_query($params, '', '&')));
+    }
+
+    /**
+     * Returns authentication url. Usually, you want to redirect your user to it.
+     * @return String The authentication url.
+     * @param String $select_identifier Whether to request OP to select identity for an user in OpenID 2. Does not affect OpenID 1.
+     * @throws ErrorException
+     */
+    function authUrl($identifier_select = null)
+    {
+        if (!$this->server) $this->discover($this->identity);
+
+        if ($this->version == 2) {
+            if ($identifier_select === null) {
+                return $this->authUrl_v2($this->identifier_select);
+            }
+            return $this->authUrl_v2($identifier_select);
+        }
+        return $this->authUrl_v1();
+    }
+
+    /**
+     * Performs OpenID verification with the OP.
+     * @return Bool Whether the verification was successful.
+     * @throws ErrorException
+     */
+    function validate()
+    {
+        $this->claimed_id = isset($this->data['openid_claimed_id'])?$this->data['openid_claimed_id']:$this->data['openid_identity'];
+        $params = array(
+            'openid.assoc_handle' => $this->data['openid_assoc_handle'],
+            'openid.signed'       => $this->data['openid_signed'],
+            'openid.sig'          => $this->data['openid_sig'],
+            );
+
+        if (isset($this->data['openid_ns'])) {
+            # We're dealing with an OpenID 2.0 server, so let's set an ns
+            # Even though we should know location of the endpoint,
+            # we still need to verify it by discovery, so $server is not set here
+            $params['openid.ns'] = 'http://specs.openid.net/auth/2.0';
+        } elseif (isset($this->data['openid_claimed_id'])
+            && $this->data['openid_claimed_id'] != $this->data['openid_identity']
+        ) {
+            # If it's an OpenID 1 provider, and we've got claimed_id,
+            # we have to append it to the returnUrl, like authUrl_v1 does.
+            $this->returnUrl .= (strpos($this->returnUrl, '?') ? '&' : '?')
+                             .  'openid.claimed_id=' . $this->claimed_id;
+        }
+
+        if ($this->data['openid_return_to'] != $this->returnUrl) {
+            # The return_to url must match the url of current request.
+            # I'm assuing that noone will set the returnUrl to something that doesn't make sense.
+            return false;
+        }
+
+        $server = $this->discover($this->claimed_id);
+
+        foreach (explode(',', $this->data['openid_signed']) as $item) {
+            # Checking whether magic_quotes_gpc is turned on, because
+            # the function may fail if it is. For example, when fetching
+            # AX namePerson, it might containg an apostrophe, which will be escaped.
+            # In such case, validation would fail, since we'd send different data than OP
+            # wants to verify. stripslashes() should solve that problem, but we can't
+            # use it when magic_quotes is off.
+            $value = $this->data['openid_' . str_replace('.','_',$item)];
+            $params['openid.' . $item] = get_magic_quotes_gpc() ? stripslashes($value) : $value;
+
+        }
+
+        $params['openid.mode'] = 'check_authentication';
+
+        $response = $this->request($server, 'POST', $params);
+
+        return preg_match('/is_valid\s*:\s*true/i', $response);
+    }
+
+    protected function getAxAttributes()
+    {
+        $alias = null;
+        if (isset($this->data['openid_ns_ax'])
+            && $this->data['openid_ns_ax'] != 'http://openid.net/srv/ax/1.0'
+        ) { # It's the most likely case, so we'll check it before
+            $alias = 'ax';
+        } else {
+            # 'ax' prefix is either undefined, or points to another extension,
+            # so we search for another prefix
+            foreach ($this->data as $key => $val) {
+                if (substr($key, 0, strlen('openid_ns_')) == 'openid_ns_'
+                    && $val == 'http://openid.net/srv/ax/1.0'
+                ) {
+                    $alias = substr($key, strlen('openid_ns_'));
+                    break;
+                }
+            }
+        }
+        if (!$alias) {
+            # An alias for AX schema has not been found,
+            # so there is no AX data in the OP's response
+            return array();
+        }
+
+        $attributes = array();
+        foreach ($this->data as $key => $value) {
+            $keyMatch = 'openid_' . $alias . '_value_';
+            if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
+                continue;
+            }
+            $key = substr($key, strlen($keyMatch));
+            if (!isset($this->data['openid_' . $alias . '_type_' . $key])) {
+                # OP is breaking the spec by returning a field without
+                # associated ns. This shouldn't happen, but it's better
+                # to check, than cause an E_NOTICE.
+                continue;
+            }
+            $key = substr($this->data['openid_' . $alias . '_type_' . $key],
+                          strlen('http://axschema.org/'));
+            $attributes[$key] = $value;
+        }
+        return $attributes;
+    }
+
+    protected function getSregAttributes()
+    {
+        $attributes = array();
+        $sreg_to_ax = array_flip(self::$ax_to_sreg);
+        foreach ($this->data as $key => $value) {
+            $keyMatch = 'openid_sreg_';
+            if (substr($key, 0, strlen($keyMatch)) != $keyMatch) {
+                continue;
+            }
+            $key = substr($key, strlen($keyMatch));
+            if (!isset($sreg_to_ax[$key])) {
+                # The field name isn't part of the SREG spec, so we ignore it.
+                continue;
+            }
+            $attributes[$sreg_to_ax[$key]] = $value;
+        }
+        return $attributes;
+    }
+
+    /**
+     * Gets AX/SREG attributes provided by OP. should be used only after successful validaton.
+     * Note that it does not guarantee that any of the required/optional parameters will be present,
+     * or that there will be no other attributes besides those specified.
+     * In other words. OP may provide whatever information it wants to.
+     *     * SREG names will be mapped to AX names.
+     *     * @return Array Array of attributes with keys being the AX schema names, e.g. 'contact/email'
+     * @see http://www.axschema.org/types/
+     */
+    function getAttributes()
+    {
+        if (isset($this->data['openid_ns'])
+            && $this->data['openid_ns'] == 'http://specs.openid.net/auth/2.0'
+        ) { # OpenID 2.0
+            # We search for both AX and SREG attributes, with AX taking precedence.
+            return $this->getAxAttributes() + $this->getSregAttributes();
+        }
+        return $this->getSregAttributes();
+    }
+}
diff --git a/application/libraries/Pclzip.php b/application/libraries/Pclzip.php
new file mode 100644
index 0000000..d05428f
--- /dev/null
+++ b/application/libraries/Pclzip.php
@@ -0,0 +1,5694 @@
+<?php
+// --------------------------------------------------------------------------------
+// PhpConcept Library - Zip Module 2.8.2
+// --------------------------------------------------------------------------------
+// License GNU/LGPL - Vincent Blavet - August 2009
+// http://www.phpconcept.net
+// --------------------------------------------------------------------------------
+//
+// Presentation :
+//   PclZip is a PHP library that manage ZIP archives.
+//   So far tests show that archives generated by PclZip are readable by
+//   WinZip application and other tools.
+//
+// Description :
+//   See readme.txt and http://www.phpconcept.net
+//
+// Warning :
+//   This library and the associated files are non commercial, non professional
+//   work.
+//   It should not have unexpected results. However if any damage is caused by
+//   this software the author can not be responsible.
+//   The use of this software is at the risk of the user.
+//
+// --------------------------------------------------------------------------------
+// $Id: pclzip.lib.php,v 1.60 2009/09/30 21:01:04 vblavet Exp $
+// --------------------------------------------------------------------------------
+
+  // ----- Constants
+  if (!defined('PCLZIP_READ_BLOCK_SIZE')) {
+    define( 'PCLZIP_READ_BLOCK_SIZE', 2048 );
+  }
+  
+  // ----- File list separator
+  // In version 1.x of PclZip, the separator for file list is a space
+  // (which is not a very smart choice, specifically for windows paths !).
+  // A better separator should be a comma (,). This constant gives you the
+  // abilty to change that.
+  // However notice that changing this value, may have impact on existing
+  // scripts, using space separated filenames.
+  // Recommanded values for compatibility with older versions :
+  //define( 'PCLZIP_SEPARATOR', ' ' );
+  // Recommanded values for smart separation of filenames.
+  if (!defined('PCLZIP_SEPARATOR')) {
+    define( 'PCLZIP_SEPARATOR', ',' );
+  }
+
+  // ----- Error configuration
+  // 0 : PclZip Class integrated error handling
+  // 1 : PclError external library error handling. By enabling this
+  //     you must ensure that you have included PclError library.
+  // [2,...] : reserved for futur use
+  if (!defined('PCLZIP_ERROR_EXTERNAL')) {
+    define( 'PCLZIP_ERROR_EXTERNAL', 0 );
+  }
+
+  // ----- Optional static temporary directory
+  //       By default temporary files are generated in the script current
+  //       path.
+  //       If defined :
+  //       - MUST BE terminated by a '/'.
+  //       - MUST be a valid, already created directory
+  //       Samples :
+  // define( 'PCLZIP_TEMPORARY_DIR', '/temp/' );
+  // define( 'PCLZIP_TEMPORARY_DIR', 'C:/Temp/' );
+  if (!defined('PCLZIP_TEMPORARY_DIR')) {
+    define( 'PCLZIP_TEMPORARY_DIR', '' );
+  }
+
+  // ----- Optional threshold ratio for use of temporary files
+  //       Pclzip sense the size of the file to add/extract and decide to
+  //       use or not temporary file. The algorythm is looking for 
+  //       memory_limit of PHP and apply a ratio.
+  //       threshold = memory_limit * ratio.
+  //       Recommended values are under 0.5. Default 0.47.
+  //       Samples :
+  // define( 'PCLZIP_TEMPORARY_FILE_RATIO', 0.5 );
+  if (!defined('PCLZIP_TEMPORARY_FILE_RATIO')) {
+    define( 'PCLZIP_TEMPORARY_FILE_RATIO', 0.47 );
+  }
+
+// --------------------------------------------------------------------------------
+// ***** UNDER THIS LINE NOTHING NEEDS TO BE MODIFIED *****
+// --------------------------------------------------------------------------------
+
+  // ----- Global variables
+  $g_pclzip_version = "2.8.2";
+
+  // ----- Error codes
+  //   -1 : Unable to open file in binary write mode
+  //   -2 : Unable to open file in binary read mode
+  //   -3 : Invalid parameters
+  //   -4 : File does not exist
+  //   -5 : Filename is too long (max. 255)
+  //   -6 : Not a valid zip file
+  //   -7 : Invalid extracted file size
+  //   -8 : Unable to create directory
+  //   -9 : Invalid archive extension
+  //  -10 : Invalid archive format
+  //  -11 : Unable to delete file (unlink)
+  //  -12 : Unable to rename file (rename)
+  //  -13 : Invalid header checksum
+  //  -14 : Invalid archive size
+  define( 'PCLZIP_ERR_USER_ABORTED', 2 );
+  define( 'PCLZIP_ERR_NO_ERROR', 0 );
+  define( 'PCLZIP_ERR_WRITE_OPEN_FAIL', -1 );
+  define( 'PCLZIP_ERR_READ_OPEN_FAIL', -2 );
+  define( 'PCLZIP_ERR_INVALID_PARAMETER', -3 );
+  define( 'PCLZIP_ERR_MISSING_FILE', -4 );
+  define( 'PCLZIP_ERR_FILENAME_TOO_LONG', -5 );
+  define( 'PCLZIP_ERR_INVALID_ZIP', -6 );
+  define( 'PCLZIP_ERR_BAD_EXTRACTED_FILE', -7 );
+  define( 'PCLZIP_ERR_DIR_CREATE_FAIL', -8 );
+  define( 'PCLZIP_ERR_BAD_EXTENSION', -9 );
+  define( 'PCLZIP_ERR_BAD_FORMAT', -10 );
+  define( 'PCLZIP_ERR_DELETE_FILE_FAIL', -11 );
+  define( 'PCLZIP_ERR_RENAME_FILE_FAIL', -12 );
+  define( 'PCLZIP_ERR_BAD_CHECKSUM', -13 );
+  define( 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP', -14 );
+  define( 'PCLZIP_ERR_MISSING_OPTION_VALUE', -15 );
+  define( 'PCLZIP_ERR_INVALID_OPTION_VALUE', -16 );
+  define( 'PCLZIP_ERR_ALREADY_A_DIRECTORY', -17 );
+  define( 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION', -18 );
+  define( 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION', -19 );
+  define( 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE', -20 );
+  define( 'PCLZIP_ERR_DIRECTORY_RESTRICTION', -21 );
+
+  // ----- Options values
+  define( 'PCLZIP_OPT_PATH', 77001 );
+  define( 'PCLZIP_OPT_ADD_PATH', 77002 );
+  define( 'PCLZIP_OPT_REMOVE_PATH', 77003 );
+  define( 'PCLZIP_OPT_REMOVE_ALL_PATH', 77004 );
+  define( 'PCLZIP_OPT_SET_CHMOD', 77005 );
+  define( 'PCLZIP_OPT_EXTRACT_AS_STRING', 77006 );
+  define( 'PCLZIP_OPT_NO_COMPRESSION', 77007 );
+  define( 'PCLZIP_OPT_BY_NAME', 77008 );
+  define( 'PCLZIP_OPT_BY_INDEX', 77009 );
+  define( 'PCLZIP_OPT_BY_EREG', 77010 );
+  define( 'PCLZIP_OPT_BY_PREG', 77011 );
+  define( 'PCLZIP_OPT_COMMENT', 77012 );
+  define( 'PCLZIP_OPT_ADD_COMMENT', 77013 );
+  define( 'PCLZIP_OPT_PREPEND_COMMENT', 77014 );
+  define( 'PCLZIP_OPT_EXTRACT_IN_OUTPUT', 77015 );
+  define( 'PCLZIP_OPT_REPLACE_NEWER', 77016 );
+  define( 'PCLZIP_OPT_STOP_ON_ERROR', 77017 );
+  // Having big trouble with crypt. Need to multiply 2 long int
+  // which is not correctly supported by PHP ...
+  //define( 'PCLZIP_OPT_CRYPT', 77018 );
+  define( 'PCLZIP_OPT_EXTRACT_DIR_RESTRICTION', 77019 );
+  define( 'PCLZIP_OPT_TEMP_FILE_THRESHOLD', 77020 );
+  define( 'PCLZIP_OPT_ADD_TEMP_FILE_THRESHOLD', 77020 ); // alias
+  define( 'PCLZIP_OPT_TEMP_FILE_ON', 77021 );
+  define( 'PCLZIP_OPT_ADD_TEMP_FILE_ON', 77021 ); // alias
+  define( 'PCLZIP_OPT_TEMP_FILE_OFF', 77022 );
+  define( 'PCLZIP_OPT_ADD_TEMP_FILE_OFF', 77022 ); // alias
+  
+  // ----- File description attributes
+  define( 'PCLZIP_ATT_FILE_NAME', 79001 );
+  define( 'PCLZIP_ATT_FILE_NEW_SHORT_NAME', 79002 );
+  define( 'PCLZIP_ATT_FILE_NEW_FULL_NAME', 79003 );
+  define( 'PCLZIP_ATT_FILE_MTIME', 79004 );
+  define( 'PCLZIP_ATT_FILE_CONTENT', 79005 );
+  define( 'PCLZIP_ATT_FILE_COMMENT', 79006 );
+
+  // ----- Call backs values
+  define( 'PCLZIP_CB_PRE_EXTRACT', 78001 );
+  define( 'PCLZIP_CB_POST_EXTRACT', 78002 );
+  define( 'PCLZIP_CB_PRE_ADD', 78003 );
+  define( 'PCLZIP_CB_POST_ADD', 78004 );
+  /* For futur use
+  define( 'PCLZIP_CB_PRE_LIST', 78005 );
+  define( 'PCLZIP_CB_POST_LIST', 78006 );
+  define( 'PCLZIP_CB_PRE_DELETE', 78007 );
+  define( 'PCLZIP_CB_POST_DELETE', 78008 );
+  */
+
+  // --------------------------------------------------------------------------------
+  // Class : PclZip
+  // Description :
+  //   PclZip is the class that represent a Zip archive.
+  //   The public methods allow the manipulation of the archive.
+  // Attributes :
+  //   Attributes must not be accessed directly.
+  // Methods :
+  //   PclZip() : Object creator
+  //   create() : Creates the Zip archive
+  //   listContent() : List the content of the Zip archive
+  //   extract() : Extract the content of the archive
+  //   properties() : List the properties of the archive
+  // --------------------------------------------------------------------------------
+  class PclZip
+  {
+    // ----- Filename of the zip file
+    var $zipname = '';
+
+    // ----- File descriptor of the zip file
+    var $zip_fd = 0;
+
+    // ----- Internal error handling
+    var $error_code = 1;
+    var $error_string = '';
+    
+    // ----- Current status of the magic_quotes_runtime
+    // This value store the php configuration for magic_quotes
+    // The class can then disable the magic_quotes and reset it after
+    var $magic_quotes_status;
+
+  // --------------------------------------------------------------------------------
+  // Function : PclZip()
+  // Description :
+  //   Creates a PclZip object and set the name of the associated Zip archive
+  //   filename.
+  //   Note that no real action is taken, if the archive does not exist it is not
+  //   created. Use create() for that.
+  // --------------------------------------------------------------------------------
+  function Pclzip($p_zipname)
+  {
+
+    // ----- Tests the zlib
+    if (!function_exists('gzopen'))
+    {
+      die('Abort '.basename(__FILE__).' : Missing zlib extensions');
+    }
+
+    // ----- Set the attributes
+    $this->zipname = $p_zipname;
+    $this->zip_fd = 0;
+    $this->magic_quotes_status = -1;
+
+    // ----- Return
+    return;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function :
+  //   create($p_filelist, $p_add_dir="", $p_remove_dir="")
+  //   create($p_filelist, $p_option, $p_option_value, ...)
+  // Description :
+  //   This method supports two different synopsis. The first one is historical.
+  //   This method creates a Zip Archive. The Zip file is created in the
+  //   filesystem. The files and directories indicated in $p_filelist
+  //   are added in the archive. See the parameters description for the
+  //   supported format of $p_filelist.
+  //   When a directory is in the list, the directory and its content is added
+  //   in the archive.
+  //   In this synopsis, the function takes an optional variable list of
+  //   options. See bellow the supported options.
+  // Parameters :
+  //   $p_filelist : An array containing file or directory names, or
+  //                 a string containing one filename or one directory name, or
+  //                 a string containing a list of filenames and/or directory
+  //                 names separated by spaces.
+  //   $p_add_dir : A path to add before the real path of the archived file,
+  //                in order to have it memorized in the archive.
+  //   $p_remove_dir : A path to remove from the real path of the file to archive,
+  //                   in order to have a shorter path memorized in the archive.
+  //                   When $p_add_dir and $p_remove_dir are set, $p_remove_dir
+  //                   is removed first, before $p_add_dir is added.
+  // Options :
+  //   PCLZIP_OPT_ADD_PATH :
+  //   PCLZIP_OPT_REMOVE_PATH :
+  //   PCLZIP_OPT_REMOVE_ALL_PATH :
+  //   PCLZIP_OPT_COMMENT :
+  //   PCLZIP_CB_PRE_ADD :
+  //   PCLZIP_CB_POST_ADD :
+  // Return Values :
+  //   0 on failure,
+  //   The list of the added files, with a status of the add action.
+  //   (see PclZip::listContent() for list entry format)
+  // --------------------------------------------------------------------------------
+  function create($p_filelist)
+  {
+    $v_result=1;
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Set default values
+    $v_options = array();
+    $v_options[PCLZIP_OPT_NO_COMPRESSION] = FALSE;
+
+    // ----- Look for variable options arguments
+    $v_size = func_num_args();
+
+    // ----- Look for arguments
+    if ($v_size > 1) {
+      // ----- Get the arguments
+      $v_arg_list = func_get_args();
+
+      // ----- Remove from the options list the first argument
+      array_shift($v_arg_list);
+      $v_size--;
+
+      // ----- Look for first arg
+      if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) {
+
+        // ----- Parse the options
+        $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+                                            array (PCLZIP_OPT_REMOVE_PATH => 'optional',
+                                                   PCLZIP_OPT_REMOVE_ALL_PATH => 'optional',
+                                                   PCLZIP_OPT_ADD_PATH => 'optional',
+                                                   PCLZIP_CB_PRE_ADD => 'optional',
+                                                   PCLZIP_CB_POST_ADD => 'optional',
+                                                   PCLZIP_OPT_NO_COMPRESSION => 'optional',
+                                                   PCLZIP_OPT_COMMENT => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_ON => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_OFF => 'optional'
+                                                   //, PCLZIP_OPT_CRYPT => 'optional'
+                                             ));
+        if ($v_result != 1) {
+          return 0;
+        }
+      }
+
+      // ----- Look for 2 args
+      // Here we need to support the first historic synopsis of the
+      // method.
+      else {
+
+        // ----- Get the first argument
+        $v_options[PCLZIP_OPT_ADD_PATH] = $v_arg_list[0];
+
+        // ----- Look for the optional second argument
+        if ($v_size == 2) {
+          $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1];
+        }
+        else if ($v_size > 2) {
+          PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER,
+		                       "Invalid number / type of arguments");
+          return 0;
+        }
+      }
+    }
+    
+    // ----- Look for default option values
+    $this->privOptionDefaultThreshold($v_options);
+
+    // ----- Init
+    $v_string_list = array();
+    $v_att_list = array();
+    $v_filedescr_list = array();
+    $p_result_list = array();
+    
+    // ----- Look if the $p_filelist is really an array
+    if (is_array($p_filelist)) {
+    
+      // ----- Look if the first element is also an array
+      //       This will mean that this is a file description entry
+      if (isset($p_filelist[0]) && is_array($p_filelist[0])) {
+        $v_att_list = $p_filelist;
+      }
+      
+      // ----- The list is a list of string names
+      else {
+        $v_string_list = $p_filelist;
+      }
+    }
+
+    // ----- Look if the $p_filelist is a string
+    else if (is_string($p_filelist)) {
+      // ----- Create a list from the string
+      $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist);
+    }
+
+    // ----- Invalid variable type for $p_filelist
+    else {
+      PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_filelist");
+      return 0;
+    }
+    
+    // ----- Reformat the string list
+    if (sizeof($v_string_list) != 0) {
+      foreach ($v_string_list as $v_string) {
+        if ($v_string != '') {
+          $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string;
+        }
+        else {
+        }
+      }
+    }
+    
+    // ----- For each file in the list check the attributes
+    $v_supported_attributes
+    = array ( PCLZIP_ATT_FILE_NAME => 'mandatory'
+             ,PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional'
+             ,PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional'
+             ,PCLZIP_ATT_FILE_MTIME => 'optional'
+             ,PCLZIP_ATT_FILE_CONTENT => 'optional'
+             ,PCLZIP_ATT_FILE_COMMENT => 'optional'
+						);
+    foreach ($v_att_list as $v_entry) {
+      $v_result = $this->privFileDescrParseAtt($v_entry,
+                                               $v_filedescr_list[],
+                                               $v_options,
+                                               $v_supported_attributes);
+      if ($v_result != 1) {
+        return 0;
+      }
+    }
+
+    // ----- Expand the filelist (expand directories)
+    $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options);
+    if ($v_result != 1) {
+      return 0;
+    }
+
+    // ----- Call the create fct
+    $v_result = $this->privCreate($v_filedescr_list, $p_result_list, $v_options);
+    if ($v_result != 1) {
+      return 0;
+    }
+
+    // ----- Return
+    return $p_result_list;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function :
+  //   add($p_filelist, $p_add_dir="", $p_remove_dir="")
+  //   add($p_filelist, $p_option, $p_option_value, ...)
+  // Description :
+  //   This method supports two synopsis. The first one is historical.
+  //   This methods add the list of files in an existing archive.
+  //   If a file with the same name already exists, it is added at the end of the
+  //   archive, the first one is still present.
+  //   If the archive does not exist, it is created.
+  // Parameters :
+  //   $p_filelist : An array containing file or directory names, or
+  //                 a string containing one filename or one directory name, or
+  //                 a string containing a list of filenames and/or directory
+  //                 names separated by spaces.
+  //   $p_add_dir : A path to add before the real path of the archived file,
+  //                in order to have it memorized in the archive.
+  //   $p_remove_dir : A path to remove from the real path of the file to archive,
+  //                   in order to have a shorter path memorized in the archive.
+  //                   When $p_add_dir and $p_remove_dir are set, $p_remove_dir
+  //                   is removed first, before $p_add_dir is added.
+  // Options :
+  //   PCLZIP_OPT_ADD_PATH :
+  //   PCLZIP_OPT_REMOVE_PATH :
+  //   PCLZIP_OPT_REMOVE_ALL_PATH :
+  //   PCLZIP_OPT_COMMENT :
+  //   PCLZIP_OPT_ADD_COMMENT :
+  //   PCLZIP_OPT_PREPEND_COMMENT :
+  //   PCLZIP_CB_PRE_ADD :
+  //   PCLZIP_CB_POST_ADD :
+  // Return Values :
+  //   0 on failure,
+  //   The list of the added files, with a status of the add action.
+  //   (see PclZip::listContent() for list entry format)
+  // --------------------------------------------------------------------------------
+  function add($p_filelist)
+  {
+    $v_result=1;
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Set default values
+    $v_options = array();
+    $v_options[PCLZIP_OPT_NO_COMPRESSION] = FALSE;
+
+    // ----- Look for variable options arguments
+    $v_size = func_num_args();
+
+    // ----- Look for arguments
+    if ($v_size > 1) {
+      // ----- Get the arguments
+      $v_arg_list = func_get_args();
+
+      // ----- Remove form the options list the first argument
+      array_shift($v_arg_list);
+      $v_size--;
+
+      // ----- Look for first arg
+      if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) {
+
+        // ----- Parse the options
+        $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+                                            array (PCLZIP_OPT_REMOVE_PATH => 'optional',
+                                                   PCLZIP_OPT_REMOVE_ALL_PATH => 'optional',
+                                                   PCLZIP_OPT_ADD_PATH => 'optional',
+                                                   PCLZIP_CB_PRE_ADD => 'optional',
+                                                   PCLZIP_CB_POST_ADD => 'optional',
+                                                   PCLZIP_OPT_NO_COMPRESSION => 'optional',
+                                                   PCLZIP_OPT_COMMENT => 'optional',
+                                                   PCLZIP_OPT_ADD_COMMENT => 'optional',
+                                                   PCLZIP_OPT_PREPEND_COMMENT => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_ON => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_OFF => 'optional'
+                                                   //, PCLZIP_OPT_CRYPT => 'optional'
+												   ));
+        if ($v_result != 1) {
+          return 0;
+        }
+      }
+
+      // ----- Look for 2 args
+      // Here we need to support the first historic synopsis of the
+      // method.
+      else {
+
+        // ----- Get the first argument
+        $v_options[PCLZIP_OPT_ADD_PATH] = $v_add_path = $v_arg_list[0];
+
+        // ----- Look for the optional second argument
+        if ($v_size == 2) {
+          $v_options[PCLZIP_OPT_REMOVE_PATH] = $v_arg_list[1];
+        }
+        else if ($v_size > 2) {
+          // ----- Error log
+          PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments");
+
+          // ----- Return
+          return 0;
+        }
+      }
+    }
+
+    // ----- Look for default option values
+    $this->privOptionDefaultThreshold($v_options);
+
+    // ----- Init
+    $v_string_list = array();
+    $v_att_list = array();
+    $v_filedescr_list = array();
+    $p_result_list = array();
+    
+    // ----- Look if the $p_filelist is really an array
+    if (is_array($p_filelist)) {
+    
+      // ----- Look if the first element is also an array
+      //       This will mean that this is a file description entry
+      if (isset($p_filelist[0]) && is_array($p_filelist[0])) {
+        $v_att_list = $p_filelist;
+      }
+      
+      // ----- The list is a list of string names
+      else {
+        $v_string_list = $p_filelist;
+      }
+    }
+
+    // ----- Look if the $p_filelist is a string
+    else if (is_string($p_filelist)) {
+      // ----- Create a list from the string
+      $v_string_list = explode(PCLZIP_SEPARATOR, $p_filelist);
+    }
+
+    // ----- Invalid variable type for $p_filelist
+    else {
+      PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type '".gettype($p_filelist)."' for p_filelist");
+      return 0;
+    }
+    
+    // ----- Reformat the string list
+    if (sizeof($v_string_list) != 0) {
+      foreach ($v_string_list as $v_string) {
+        $v_att_list[][PCLZIP_ATT_FILE_NAME] = $v_string;
+      }
+    }
+    
+    // ----- For each file in the list check the attributes
+    $v_supported_attributes
+    = array ( PCLZIP_ATT_FILE_NAME => 'mandatory'
+             ,PCLZIP_ATT_FILE_NEW_SHORT_NAME => 'optional'
+             ,PCLZIP_ATT_FILE_NEW_FULL_NAME => 'optional'
+             ,PCLZIP_ATT_FILE_MTIME => 'optional'
+             ,PCLZIP_ATT_FILE_CONTENT => 'optional'
+             ,PCLZIP_ATT_FILE_COMMENT => 'optional'
+						);
+    foreach ($v_att_list as $v_entry) {
+      $v_result = $this->privFileDescrParseAtt($v_entry,
+                                               $v_filedescr_list[],
+                                               $v_options,
+                                               $v_supported_attributes);
+      if ($v_result != 1) {
+        return 0;
+      }
+    }
+
+    // ----- Expand the filelist (expand directories)
+    $v_result = $this->privFileDescrExpand($v_filedescr_list, $v_options);
+    if ($v_result != 1) {
+      return 0;
+    }
+
+    // ----- Call the create fct
+    $v_result = $this->privAdd($v_filedescr_list, $p_result_list, $v_options);
+    if ($v_result != 1) {
+      return 0;
+    }
+
+    // ----- Return
+    return $p_result_list;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : listContent()
+  // Description :
+  //   This public method, gives the list of the files and directories, with their
+  //   properties.
+  //   The properties of each entries in the list are (used also in other functions) :
+  //     filename : Name of the file. For a create or add action it is the filename
+  //                given by the user. For an extract function it is the filename
+  //                of the extracted file.
+  //     stored_filename : Name of the file / directory stored in the archive.
+  //     size : Size of the stored file.
+  //     compressed_size : Size of the file's data compressed in the archive
+  //                       (without the headers overhead)
+  //     mtime : Last known modification date of the file (UNIX timestamp)
+  //     comment : Comment associated with the file
+  //     folder : true | false
+  //     index : index of the file in the archive
+  //     status : status of the action (depending of the action) :
+  //              Values are :
+  //                ok : OK !
+  //                filtered : the file / dir is not extracted (filtered by user)
+  //                already_a_directory : the file can not be extracted because a
+  //                                      directory with the same name already exists
+  //                write_protected : the file can not be extracted because a file
+  //                                  with the same name already exists and is
+  //                                  write protected
+  //                newer_exist : the file was not extracted because a newer file exists
+  //                path_creation_fail : the file is not extracted because the folder
+  //                                     does not exist and can not be created
+  //                write_error : the file was not extracted because there was a
+  //                              error while writing the file
+  //                read_error : the file was not extracted because there was a error
+  //                             while reading the file
+  //                invalid_header : the file was not extracted because of an archive
+  //                                 format error (bad file header)
+  //   Note that each time a method can continue operating when there
+  //   is an action error on a file, the error is only logged in the file status.
+  // Return Values :
+  //   0 on an unrecoverable failure,
+  //   The list of the files in the archive.
+  // --------------------------------------------------------------------------------
+  function listContent()
+  {
+    $v_result=1;
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Check archive
+    if (!$this->privCheckFormat()) {
+      return(0);
+    }
+
+    // ----- Call the extracting fct
+    $p_list = array();
+    if (($v_result = $this->privList($p_list)) != 1)
+    {
+      unset($p_list);
+      return(0);
+    }
+
+    // ----- Return
+    return $p_list;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function :
+  //   extract($p_path="./", $p_remove_path="")
+  //   extract([$p_option, $p_option_value, ...])
+  // Description :
+  //   This method supports two synopsis. The first one is historical.
+  //   This method extract all the files / directories from the archive to the
+  //   folder indicated in $p_path.
+  //   If you want to ignore the 'root' part of path of the memorized files
+  //   you can indicate this in the optional $p_remove_path parameter.
+  //   By default, if a newer file with the same name already exists, the
+  //   file is not extracted.
+  //
+  //   If both PCLZIP_OPT_PATH and PCLZIP_OPT_ADD_PATH aoptions
+  //   are used, the path indicated in PCLZIP_OPT_ADD_PATH is append
+  //   at the end of the path value of PCLZIP_OPT_PATH.
+  // Parameters :
+  //   $p_path : Path where the files and directories are to be extracted
+  //   $p_remove_path : First part ('root' part) of the memorized path
+  //                    (if any similar) to remove while extracting.
+  // Options :
+  //   PCLZIP_OPT_PATH :
+  //   PCLZIP_OPT_ADD_PATH :
+  //   PCLZIP_OPT_REMOVE_PATH :
+  //   PCLZIP_OPT_REMOVE_ALL_PATH :
+  //   PCLZIP_CB_PRE_EXTRACT :
+  //   PCLZIP_CB_POST_EXTRACT :
+  // Return Values :
+  //   0 or a negative value on failure,
+  //   The list of the extracted files, with a status of the action.
+  //   (see PclZip::listContent() for list entry format)
+  // --------------------------------------------------------------------------------
+  function extract()
+  {
+    $v_result=1;
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Check archive
+    if (!$this->privCheckFormat()) {
+      return(0);
+    }
+
+    // ----- Set default values
+    $v_options = array();
+//    $v_path = "./";
+    $v_path = '';
+    $v_remove_path = "";
+    $v_remove_all_path = false;
+
+    // ----- Look for variable options arguments
+    $v_size = func_num_args();
+
+    // ----- Default values for option
+    $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE;
+
+    // ----- Look for arguments
+    if ($v_size > 0) {
+      // ----- Get the arguments
+      $v_arg_list = func_get_args();
+
+      // ----- Look for first arg
+      if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) {
+
+        // ----- Parse the options
+        $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+                                            array (PCLZIP_OPT_PATH => 'optional',
+                                                   PCLZIP_OPT_REMOVE_PATH => 'optional',
+                                                   PCLZIP_OPT_REMOVE_ALL_PATH => 'optional',
+                                                   PCLZIP_OPT_ADD_PATH => 'optional',
+                                                   PCLZIP_CB_PRE_EXTRACT => 'optional',
+                                                   PCLZIP_CB_POST_EXTRACT => 'optional',
+                                                   PCLZIP_OPT_SET_CHMOD => 'optional',
+                                                   PCLZIP_OPT_BY_NAME => 'optional',
+                                                   PCLZIP_OPT_BY_EREG => 'optional',
+                                                   PCLZIP_OPT_BY_PREG => 'optional',
+                                                   PCLZIP_OPT_BY_INDEX => 'optional',
+                                                   PCLZIP_OPT_EXTRACT_AS_STRING => 'optional',
+                                                   PCLZIP_OPT_EXTRACT_IN_OUTPUT => 'optional',
+                                                   PCLZIP_OPT_REPLACE_NEWER => 'optional'
+                                                   ,PCLZIP_OPT_STOP_ON_ERROR => 'optional'
+                                                   ,PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_ON => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_OFF => 'optional'
+												    ));
+        if ($v_result != 1) {
+          return 0;
+        }
+
+        // ----- Set the arguments
+        if (isset($v_options[PCLZIP_OPT_PATH])) {
+          $v_path = $v_options[PCLZIP_OPT_PATH];
+        }
+        if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) {
+          $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH];
+        }
+        if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) {
+          $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH];
+        }
+        if (isset($v_options[PCLZIP_OPT_ADD_PATH])) {
+          // ----- Check for '/' in last path char
+          if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) {
+            $v_path .= '/';
+          }
+          $v_path .= $v_options[PCLZIP_OPT_ADD_PATH];
+        }
+      }
+
+      // ----- Look for 2 args
+      // Here we need to support the first historic synopsis of the
+      // method.
+      else {
+
+        // ----- Get the first argument
+        $v_path = $v_arg_list[0];
+
+        // ----- Look for the optional second argument
+        if ($v_size == 2) {
+          $v_remove_path = $v_arg_list[1];
+        }
+        else if ($v_size > 2) {
+          // ----- Error log
+          PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments");
+
+          // ----- Return
+          return 0;
+        }
+      }
+    }
+
+    // ----- Look for default option values
+    $this->privOptionDefaultThreshold($v_options);
+
+    // ----- Trace
+
+    // ----- Call the extracting fct
+    $p_list = array();
+    $v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path,
+	                                     $v_remove_all_path, $v_options);
+    if ($v_result < 1) {
+      unset($p_list);
+      return(0);
+    }
+
+    // ----- Return
+    return $p_list;
+  }
+  // --------------------------------------------------------------------------------
+
+
+  // --------------------------------------------------------------------------------
+  // Function :
+  //   extractByIndex($p_index, $p_path="./", $p_remove_path="")
+  //   extractByIndex($p_index, [$p_option, $p_option_value, ...])
+  // Description :
+  //   This method supports two synopsis. The first one is historical.
+  //   This method is doing a partial extract of the archive.
+  //   The extracted files or folders are identified by their index in the
+  //   archive (from 0 to n).
+  //   Note that if the index identify a folder, only the folder entry is
+  //   extracted, not all the files included in the archive.
+  // Parameters :
+  //   $p_index : A single index (integer) or a string of indexes of files to
+  //              extract. The form of the string is "0,4-6,8-12" with only numbers
+  //              and '-' for range or ',' to separate ranges. No spaces or ';'
+  //              are allowed.
+  //   $p_path : Path where the files and directories are to be extracted
+  //   $p_remove_path : First part ('root' part) of the memorized path
+  //                    (if any similar) to remove while extracting.
+  // Options :
+  //   PCLZIP_OPT_PATH :
+  //   PCLZIP_OPT_ADD_PATH :
+  //   PCLZIP_OPT_REMOVE_PATH :
+  //   PCLZIP_OPT_REMOVE_ALL_PATH :
+  //   PCLZIP_OPT_EXTRACT_AS_STRING : The files are extracted as strings and
+  //     not as files.
+  //     The resulting content is in a new field 'content' in the file
+  //     structure.
+  //     This option must be used alone (any other options are ignored).
+  //   PCLZIP_CB_PRE_EXTRACT :
+  //   PCLZIP_CB_POST_EXTRACT :
+  // Return Values :
+  //   0 on failure,
+  //   The list of the extracted files, with a status of the action.
+  //   (see PclZip::listContent() for list entry format)
+  // --------------------------------------------------------------------------------
+  //function extractByIndex($p_index, options...)
+  function extractByIndex($p_index)
+  {
+    $v_result=1;
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Check archive
+    if (!$this->privCheckFormat()) {
+      return(0);
+    }
+
+    // ----- Set default values
+    $v_options = array();
+//    $v_path = "./";
+    $v_path = '';
+    $v_remove_path = "";
+    $v_remove_all_path = false;
+
+    // ----- Look for variable options arguments
+    $v_size = func_num_args();
+
+    // ----- Default values for option
+    $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE;
+
+    // ----- Look for arguments
+    if ($v_size > 1) {
+      // ----- Get the arguments
+      $v_arg_list = func_get_args();
+
+      // ----- Remove form the options list the first argument
+      array_shift($v_arg_list);
+      $v_size--;
+
+      // ----- Look for first arg
+      if ((is_integer($v_arg_list[0])) && ($v_arg_list[0] > 77000)) {
+
+        // ----- Parse the options
+        $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+                                            array (PCLZIP_OPT_PATH => 'optional',
+                                                   PCLZIP_OPT_REMOVE_PATH => 'optional',
+                                                   PCLZIP_OPT_REMOVE_ALL_PATH => 'optional',
+                                                   PCLZIP_OPT_EXTRACT_AS_STRING => 'optional',
+                                                   PCLZIP_OPT_ADD_PATH => 'optional',
+                                                   PCLZIP_CB_PRE_EXTRACT => 'optional',
+                                                   PCLZIP_CB_POST_EXTRACT => 'optional',
+                                                   PCLZIP_OPT_SET_CHMOD => 'optional',
+                                                   PCLZIP_OPT_REPLACE_NEWER => 'optional'
+                                                   ,PCLZIP_OPT_STOP_ON_ERROR => 'optional'
+                                                   ,PCLZIP_OPT_EXTRACT_DIR_RESTRICTION => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_THRESHOLD => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_ON => 'optional',
+                                                   PCLZIP_OPT_TEMP_FILE_OFF => 'optional'
+												   ));
+        if ($v_result != 1) {
+          return 0;
+        }
+
+        // ----- Set the arguments
+        if (isset($v_options[PCLZIP_OPT_PATH])) {
+          $v_path = $v_options[PCLZIP_OPT_PATH];
+        }
+        if (isset($v_options[PCLZIP_OPT_REMOVE_PATH])) {
+          $v_remove_path = $v_options[PCLZIP_OPT_REMOVE_PATH];
+        }
+        if (isset($v_options[PCLZIP_OPT_REMOVE_ALL_PATH])) {
+          $v_remove_all_path = $v_options[PCLZIP_OPT_REMOVE_ALL_PATH];
+        }
+        if (isset($v_options[PCLZIP_OPT_ADD_PATH])) {
+          // ----- Check for '/' in last path char
+          if ((strlen($v_path) > 0) && (substr($v_path, -1) != '/')) {
+            $v_path .= '/';
+          }
+          $v_path .= $v_options[PCLZIP_OPT_ADD_PATH];
+        }
+        if (!isset($v_options[PCLZIP_OPT_EXTRACT_AS_STRING])) {
+          $v_options[PCLZIP_OPT_EXTRACT_AS_STRING] = FALSE;
+        }
+        else {
+        }
+      }
+
+      // ----- Look for 2 args
+      // Here we need to support the first historic synopsis of the
+      // method.
+      else {
+
+        // ----- Get the first argument
+        $v_path = $v_arg_list[0];
+
+        // ----- Look for the optional second argument
+        if ($v_size == 2) {
+          $v_remove_path = $v_arg_list[1];
+        }
+        else if ($v_size > 2) {
+          // ----- Error log
+          PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid number / type of arguments");
+
+          // ----- Return
+          return 0;
+        }
+      }
+    }
+
+    // ----- Trace
+
+    // ----- Trick
+    // Here I want to reuse extractByRule(), so I need to parse the $p_index
+    // with privParseOptions()
+    $v_arg_trick = array (PCLZIP_OPT_BY_INDEX, $p_index);
+    $v_options_trick = array();
+    $v_result = $this->privParseOptions($v_arg_trick, sizeof($v_arg_trick), $v_options_trick,
+                                        array (PCLZIP_OPT_BY_INDEX => 'optional' ));
+    if ($v_result != 1) {
+        return 0;
+    }
+    $v_options[PCLZIP_OPT_BY_INDEX] = $v_options_trick[PCLZIP_OPT_BY_INDEX];
+
+    // ----- Look for default option values
+    $this->privOptionDefaultThreshold($v_options);
+
+    // ----- Call the extracting fct
+    if (($v_result = $this->privExtractByRule($p_list, $v_path, $v_remove_path, $v_remove_all_path, $v_options)) < 1) {
+        return(0);
+    }
+
+    // ----- Return
+    return $p_list;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function :
+  //   delete([$p_option, $p_option_value, ...])
+  // Description :
+  //   This method removes files from the archive.
+  //   If no parameters are given, then all the archive is emptied.
+  // Parameters :
+  //   None or optional arguments.
+  // Options :
+  //   PCLZIP_OPT_BY_INDEX :
+  //   PCLZIP_OPT_BY_NAME :
+  //   PCLZIP_OPT_BY_EREG : 
+  //   PCLZIP_OPT_BY_PREG :
+  // Return Values :
+  //   0 on failure,
+  //   The list of the files which are still present in the archive.
+  //   (see PclZip::listContent() for list entry format)
+  // --------------------------------------------------------------------------------
+  function delete()
+  {
+    $v_result=1;
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Check archive
+    if (!$this->privCheckFormat()) {
+      return(0);
+    }
+
+    // ----- Set default values
+    $v_options = array();
+
+    // ----- Look for variable options arguments
+    $v_size = func_num_args();
+
+    // ----- Look for arguments
+    if ($v_size > 0) {
+      // ----- Get the arguments
+      $v_arg_list = func_get_args();
+
+      // ----- Parse the options
+      $v_result = $this->privParseOptions($v_arg_list, $v_size, $v_options,
+                                        array (PCLZIP_OPT_BY_NAME => 'optional',
+                                               PCLZIP_OPT_BY_EREG => 'optional',
+                                               PCLZIP_OPT_BY_PREG => 'optional',
+                                               PCLZIP_OPT_BY_INDEX => 'optional' ));
+      if ($v_result != 1) {
+          return 0;
+      }
+    }
+
+    // ----- Magic quotes trick
+    $this->privDisableMagicQuotes();
+
+    // ----- Call the delete fct
+    $v_list = array();
+    if (($v_result = $this->privDeleteByRule($v_list, $v_options)) != 1) {
+      $this->privSwapBackMagicQuotes();
+      unset($v_list);
+      return(0);
+    }
+
+    // ----- Magic quotes trick
+    $this->privSwapBackMagicQuotes();
+
+    // ----- Return
+    return $v_list;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : deleteByIndex()
+  // Description :
+  //   ***** Deprecated *****
+  //   delete(PCLZIP_OPT_BY_INDEX, $p_index) should be prefered.
+  // --------------------------------------------------------------------------------
+  function deleteByIndex($p_index)
+  {
+    
+    $p_list = $this->delete(PCLZIP_OPT_BY_INDEX, $p_index);
+
+    // ----- Return
+    return $p_list;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : properties()
+  // Description :
+  //   This method gives the properties of the archive.
+  //   The properties are :
+  //     nb : Number of files in the archive
+  //     comment : Comment associated with the archive file
+  //     status : not_exist, ok
+  // Parameters :
+  //   None
+  // Return Values :
+  //   0 on failure,
+  //   An array with the archive properties.
+  // --------------------------------------------------------------------------------
+  function properties()
+  {
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Magic quotes trick
+    $this->privDisableMagicQuotes();
+
+    // ----- Check archive
+    if (!$this->privCheckFormat()) {
+      $this->privSwapBackMagicQuotes();
+      return(0);
+    }
+
+    // ----- Default properties
+    $v_prop = array();
+    $v_prop['comment'] = '';
+    $v_prop['nb'] = 0;
+    $v_prop['status'] = 'not_exist';
+
+    // ----- Look if file exists
+    if (@is_file($this->zipname))
+    {
+      // ----- Open the zip file
+      if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0)
+      {
+        $this->privSwapBackMagicQuotes();
+        
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in binary read mode');
+
+        // ----- Return
+        return 0;
+      }
+
+      // ----- Read the central directory informations
+      $v_central_dir = array();
+      if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+      {
+        $this->privSwapBackMagicQuotes();
+        return 0;
+      }
+
+      // ----- Close the zip file
+      $this->privCloseFd();
+
+      // ----- Set the user attributes
+      $v_prop['comment'] = $v_central_dir['comment'];
+      $v_prop['nb'] = $v_central_dir['entries'];
+      $v_prop['status'] = 'ok';
+    }
+
+    // ----- Magic quotes trick
+    $this->privSwapBackMagicQuotes();
+
+    // ----- Return
+    return $v_prop;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : duplicate()
+  // Description :
+  //   This method creates an archive by copying the content of an other one. If
+  //   the archive already exist, it is replaced by the new one without any warning.
+  // Parameters :
+  //   $p_archive : The filename of a valid archive, or
+  //                a valid PclZip object.
+  // Return Values :
+  //   1 on success.
+  //   0 or a negative value on error (error code).
+  // --------------------------------------------------------------------------------
+  function duplicate($p_archive)
+  {
+    $v_result = 1;
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Look if the $p_archive is a PclZip object
+    if ((is_object($p_archive)) && (get_class($p_archive) == 'pclzip'))
+    {
+
+      // ----- Duplicate the archive
+      $v_result = $this->privDuplicate($p_archive->zipname);
+    }
+
+    // ----- Look if the $p_archive is a string (so a filename)
+    else if (is_string($p_archive))
+    {
+
+      // ----- Check that $p_archive is a valid zip file
+      // TBC : Should also check the archive format
+      if (!is_file($p_archive)) {
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "No file with filename '".$p_archive."'");
+        $v_result = PCLZIP_ERR_MISSING_FILE;
+      }
+      else {
+        // ----- Duplicate the archive
+        $v_result = $this->privDuplicate($p_archive);
+      }
+    }
+
+    // ----- Invalid variable
+    else
+    {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add");
+      $v_result = PCLZIP_ERR_INVALID_PARAMETER;
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : merge()
+  // Description :
+  //   This method merge the $p_archive_to_add archive at the end of the current
+  //   one ($this).
+  //   If the archive ($this) does not exist, the merge becomes a duplicate.
+  //   If the $p_archive_to_add archive does not exist, the merge is a success.
+  // Parameters :
+  //   $p_archive_to_add : It can be directly the filename of a valid zip archive,
+  //                       or a PclZip object archive.
+  // Return Values :
+  //   1 on success,
+  //   0 or negative values on error (see below).
+  // --------------------------------------------------------------------------------
+  function merge($p_archive_to_add)
+  {
+    $v_result = 1;
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Check archive
+    if (!$this->privCheckFormat()) {
+      return(0);
+    }
+
+    // ----- Look if the $p_archive_to_add is a PclZip object
+    if ((is_object($p_archive_to_add)) && (get_class($p_archive_to_add) == 'pclzip'))
+    {
+
+      // ----- Merge the archive
+      $v_result = $this->privMerge($p_archive_to_add);
+    }
+
+    // ----- Look if the $p_archive_to_add is a string (so a filename)
+    else if (is_string($p_archive_to_add))
+    {
+
+      // ----- Create a temporary archive
+      $v_object_archive = new PclZip($p_archive_to_add);
+
+      // ----- Merge the archive
+      $v_result = $this->privMerge($v_object_archive);
+    }
+
+    // ----- Invalid variable
+    else
+    {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid variable type p_archive_to_add");
+      $v_result = PCLZIP_ERR_INVALID_PARAMETER;
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+
+
+  // --------------------------------------------------------------------------------
+  // Function : errorCode()
+  // Description :
+  // Parameters :
+  // --------------------------------------------------------------------------------
+  function errorCode()
+  {
+    if (PCLZIP_ERROR_EXTERNAL == 1) {
+      return(PclErrorCode());
+    }
+    else {
+      return($this->error_code);
+    }
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : errorName()
+  // Description :
+  // Parameters :
+  // --------------------------------------------------------------------------------
+  function errorName($p_with_code=false)
+  {
+    $v_name = array ( PCLZIP_ERR_NO_ERROR => 'PCLZIP_ERR_NO_ERROR',
+                      PCLZIP_ERR_WRITE_OPEN_FAIL => 'PCLZIP_ERR_WRITE_OPEN_FAIL',
+                      PCLZIP_ERR_READ_OPEN_FAIL => 'PCLZIP_ERR_READ_OPEN_FAIL',
+                      PCLZIP_ERR_INVALID_PARAMETER => 'PCLZIP_ERR_INVALID_PARAMETER',
+                      PCLZIP_ERR_MISSING_FILE => 'PCLZIP_ERR_MISSING_FILE',
+                      PCLZIP_ERR_FILENAME_TOO_LONG => 'PCLZIP_ERR_FILENAME_TOO_LONG',
+                      PCLZIP_ERR_INVALID_ZIP => 'PCLZIP_ERR_INVALID_ZIP',
+                      PCLZIP_ERR_BAD_EXTRACTED_FILE => 'PCLZIP_ERR_BAD_EXTRACTED_FILE',
+                      PCLZIP_ERR_DIR_CREATE_FAIL => 'PCLZIP_ERR_DIR_CREATE_FAIL',
+                      PCLZIP_ERR_BAD_EXTENSION => 'PCLZIP_ERR_BAD_EXTENSION',
+                      PCLZIP_ERR_BAD_FORMAT => 'PCLZIP_ERR_BAD_FORMAT',
+                      PCLZIP_ERR_DELETE_FILE_FAIL => 'PCLZIP_ERR_DELETE_FILE_FAIL',
+                      PCLZIP_ERR_RENAME_FILE_FAIL => 'PCLZIP_ERR_RENAME_FILE_FAIL',
+                      PCLZIP_ERR_BAD_CHECKSUM => 'PCLZIP_ERR_BAD_CHECKSUM',
+                      PCLZIP_ERR_INVALID_ARCHIVE_ZIP => 'PCLZIP_ERR_INVALID_ARCHIVE_ZIP',
+                      PCLZIP_ERR_MISSING_OPTION_VALUE => 'PCLZIP_ERR_MISSING_OPTION_VALUE',
+                      PCLZIP_ERR_INVALID_OPTION_VALUE => 'PCLZIP_ERR_INVALID_OPTION_VALUE',
+                      PCLZIP_ERR_UNSUPPORTED_COMPRESSION => 'PCLZIP_ERR_UNSUPPORTED_COMPRESSION',
+                      PCLZIP_ERR_UNSUPPORTED_ENCRYPTION => 'PCLZIP_ERR_UNSUPPORTED_ENCRYPTION'
+                      ,PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE => 'PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE'
+                      ,PCLZIP_ERR_DIRECTORY_RESTRICTION => 'PCLZIP_ERR_DIRECTORY_RESTRICTION'
+                    );
+
+    if (isset($v_name[$this->error_code])) {
+      $v_value = $v_name[$this->error_code];
+    }
+    else {
+      $v_value = 'NoName';
+    }
+
+    if ($p_with_code) {
+      return($v_value.' ('.$this->error_code.')');
+    }
+    else {
+      return($v_value);
+    }
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : errorInfo()
+  // Description :
+  // Parameters :
+  // --------------------------------------------------------------------------------
+  function errorInfo($p_full=false)
+  {
+    if (PCLZIP_ERROR_EXTERNAL == 1) {
+      return(PclErrorString());
+    }
+    else {
+      if ($p_full) {
+        return($this->errorName(true)." : ".$this->error_string);
+      }
+      else {
+        return($this->error_string." [code ".$this->error_code."]");
+      }
+    }
+  }
+  // --------------------------------------------------------------------------------
+
+
+// --------------------------------------------------------------------------------
+// ***** UNDER THIS LINE ARE DEFINED PRIVATE INTERNAL FUNCTIONS *****
+// *****                                                        *****
+// *****       THESES FUNCTIONS MUST NOT BE USED DIRECTLY       *****
+// --------------------------------------------------------------------------------
+
+
+
+  // --------------------------------------------------------------------------------
+  // Function : privCheckFormat()
+  // Description :
+  //   This method check that the archive exists and is a valid zip archive.
+  //   Several level of check exists. (futur)
+  // Parameters :
+  //   $p_level : Level of check. Default 0.
+  //              0 : Check the first bytes (magic codes) (default value))
+  //              1 : 0 + Check the central directory (futur)
+  //              2 : 1 + Check each file header (futur)
+  // Return Values :
+  //   true on success,
+  //   false on error, the error code is set.
+  // --------------------------------------------------------------------------------
+  function privCheckFormat($p_level=0)
+  {
+    $v_result = true;
+
+	// ----- Reset the file system cache
+    clearstatcache();
+
+    // ----- Reset the error handler
+    $this->privErrorReset();
+
+    // ----- Look if the file exits
+    if (!is_file($this->zipname)) {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "Missing archive file '".$this->zipname."'");
+      return(false);
+    }
+
+    // ----- Check that the file is readeable
+    if (!is_readable($this->zipname)) {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to read archive '".$this->zipname."'");
+      return(false);
+    }
+
+    // ----- Check the magic code
+    // TBC
+
+    // ----- Check the central header
+    // TBC
+
+    // ----- Check each file header
+    // TBC
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privParseOptions()
+  // Description :
+  //   This internal methods reads the variable list of arguments ($p_options_list,
+  //   $p_size) and generate an array with the options and values ($v_result_list).
+  //   $v_requested_options contains the options that can be present and those that
+  //   must be present.
+  //   $v_requested_options is an array, with the option value as key, and 'optional',
+  //   or 'mandatory' as value.
+  // Parameters :
+  //   See above.
+  // Return Values :
+  //   1 on success.
+  //   0 on failure.
+  // --------------------------------------------------------------------------------
+  function privParseOptions(&$p_options_list, $p_size, &$v_result_list, $v_requested_options=false)
+  {
+    $v_result=1;
+    
+    // ----- Read the options
+    $i=0;
+    while ($i<$p_size) {
+
+      // ----- Check if the option is supported
+      if (!isset($v_requested_options[$p_options_list[$i]])) {
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid optional parameter '".$p_options_list[$i]."' for this method");
+
+        // ----- Return
+        return PclZip::errorCode();
+      }
+
+      // ----- Look for next option
+      switch ($p_options_list[$i]) {
+        // ----- Look for options that request a path value
+        case PCLZIP_OPT_PATH :
+        case PCLZIP_OPT_REMOVE_PATH :
+        case PCLZIP_OPT_ADD_PATH :
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value
+          $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i+1], FALSE);
+          $i++;
+        break;
+
+        case PCLZIP_OPT_TEMP_FILE_THRESHOLD :
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+            return PclZip::errorCode();
+          }
+          
+          // ----- Check for incompatible options
+          if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'");
+            return PclZip::errorCode();
+          }
+          
+          // ----- Check the value
+          $v_value = $p_options_list[$i+1];
+          if ((!is_integer($v_value)) || ($v_value<0)) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Integer expected for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value (and convert it in bytes)
+          $v_result_list[$p_options_list[$i]] = $v_value*1048576;
+          $i++;
+        break;
+
+        case PCLZIP_OPT_TEMP_FILE_ON :
+          // ----- Check for incompatible options
+          if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_OFF])) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_OFF'");
+            return PclZip::errorCode();
+          }
+          
+          $v_result_list[$p_options_list[$i]] = true;
+        break;
+
+        case PCLZIP_OPT_TEMP_FILE_OFF :
+          // ----- Check for incompatible options
+          if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_ON])) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_ON'");
+            return PclZip::errorCode();
+          }
+          // ----- Check for incompatible options
+          if (isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Option '".PclZipUtilOptionText($p_options_list[$i])."' can not be used with option 'PCLZIP_OPT_TEMP_FILE_THRESHOLD'");
+            return PclZip::errorCode();
+          }
+          
+          $v_result_list[$p_options_list[$i]] = true;
+        break;
+
+        case PCLZIP_OPT_EXTRACT_DIR_RESTRICTION :
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value
+          if (   is_string($p_options_list[$i+1])
+              && ($p_options_list[$i+1] != '')) {
+            $v_result_list[$p_options_list[$i]] = PclZipUtilTranslateWinPath($p_options_list[$i+1], FALSE);
+            $i++;
+          }
+          else {
+          }
+        break;
+
+        // ----- Look for options that request an array of string for value
+        case PCLZIP_OPT_BY_NAME :
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value
+          if (is_string($p_options_list[$i+1])) {
+              $v_result_list[$p_options_list[$i]][0] = $p_options_list[$i+1];
+          }
+          else if (is_array($p_options_list[$i+1])) {
+              $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1];
+          }
+          else {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+          $i++;
+        break;
+
+        // ----- Look for options that request an EREG or PREG expression
+        case PCLZIP_OPT_BY_EREG :
+          // ereg() is deprecated starting with PHP 5.3. Move PCLZIP_OPT_BY_EREG
+          // to PCLZIP_OPT_BY_PREG
+          $p_options_list[$i] = PCLZIP_OPT_BY_PREG;
+        case PCLZIP_OPT_BY_PREG :
+        //case PCLZIP_OPT_CRYPT :
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value
+          if (is_string($p_options_list[$i+1])) {
+              $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1];
+          }
+          else {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Wrong parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+          $i++;
+        break;
+
+        // ----- Look for options that takes a string
+        case PCLZIP_OPT_COMMENT :
+        case PCLZIP_OPT_ADD_COMMENT :
+        case PCLZIP_OPT_PREPEND_COMMENT :
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE,
+			                     "Missing parameter value for option '"
+								 .PclZipUtilOptionText($p_options_list[$i])
+								 ."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value
+          if (is_string($p_options_list[$i+1])) {
+              $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1];
+          }
+          else {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE,
+			                     "Wrong parameter value for option '"
+								 .PclZipUtilOptionText($p_options_list[$i])
+								 ."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+          $i++;
+        break;
+
+        // ----- Look for options that request an array of index
+        case PCLZIP_OPT_BY_INDEX :
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value
+          $v_work_list = array();
+          if (is_string($p_options_list[$i+1])) {
+
+              // ----- Remove spaces
+              $p_options_list[$i+1] = strtr($p_options_list[$i+1], ' ', '');
+
+              // ----- Parse items
+              $v_work_list = explode(",", $p_options_list[$i+1]);
+          }
+          else if (is_integer($p_options_list[$i+1])) {
+              $v_work_list[0] = $p_options_list[$i+1].'-'.$p_options_list[$i+1];
+          }
+          else if (is_array($p_options_list[$i+1])) {
+              $v_work_list = $p_options_list[$i+1];
+          }
+          else {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Value must be integer, string or array for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+          
+          // ----- Reduce the index list
+          // each index item in the list must be a couple with a start and
+          // an end value : [0,3], [5-5], [8-10], ...
+          // ----- Check the format of each item
+          $v_sort_flag=false;
+          $v_sort_value=0;
+          for ($j=0; $j<sizeof($v_work_list); $j++) {
+              // ----- Explode the item
+              $v_item_list = explode("-", $v_work_list[$j]);
+              $v_size_item_list = sizeof($v_item_list);
+              
+              // ----- TBC : Here we might check that each item is a
+              // real integer ...
+              
+              // ----- Look for single value
+              if ($v_size_item_list == 1) {
+                  // ----- Set the option value
+                  $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0];
+                  $v_result_list[$p_options_list[$i]][$j]['end'] = $v_item_list[0];
+              }
+              elseif ($v_size_item_list == 2) {
+                  // ----- Set the option value
+                  $v_result_list[$p_options_list[$i]][$j]['start'] = $v_item_list[0];
+                  $v_result_list[$p_options_list[$i]][$j]['end'] = $v_item_list[1];
+              }
+              else {
+                  // ----- Error log
+                  PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Too many values in index range for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+                  // ----- Return
+                  return PclZip::errorCode();
+              }
+
+
+              // ----- Look for list sort
+              if ($v_result_list[$p_options_list[$i]][$j]['start'] < $v_sort_value) {
+                  $v_sort_flag=true;
+
+                  // ----- TBC : An automatic sort should be writen ...
+                  // ----- Error log
+                  PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Invalid order of index range for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+                  // ----- Return
+                  return PclZip::errorCode();
+              }
+              $v_sort_value = $v_result_list[$p_options_list[$i]][$j]['start'];
+          }
+          
+          // ----- Sort the items
+          if ($v_sort_flag) {
+              // TBC : To Be Completed
+          }
+
+          // ----- Next option
+          $i++;
+        break;
+
+        // ----- Look for options that request no value
+        case PCLZIP_OPT_REMOVE_ALL_PATH :
+        case PCLZIP_OPT_EXTRACT_AS_STRING :
+        case PCLZIP_OPT_NO_COMPRESSION :
+        case PCLZIP_OPT_EXTRACT_IN_OUTPUT :
+        case PCLZIP_OPT_REPLACE_NEWER :
+        case PCLZIP_OPT_STOP_ON_ERROR :
+          $v_result_list[$p_options_list[$i]] = true;
+        break;
+
+        // ----- Look for options that request an octal value
+        case PCLZIP_OPT_SET_CHMOD :
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value
+          $v_result_list[$p_options_list[$i]] = $p_options_list[$i+1];
+          $i++;
+        break;
+
+        // ----- Look for options that request a call-back
+        case PCLZIP_CB_PRE_EXTRACT :
+        case PCLZIP_CB_POST_EXTRACT :
+        case PCLZIP_CB_PRE_ADD :
+        case PCLZIP_CB_POST_ADD :
+        /* for futur use
+        case PCLZIP_CB_PRE_DELETE :
+        case PCLZIP_CB_POST_DELETE :
+        case PCLZIP_CB_PRE_LIST :
+        case PCLZIP_CB_POST_LIST :
+        */
+          // ----- Check the number of parameters
+          if (($i+1) >= $p_size) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_MISSING_OPTION_VALUE, "Missing parameter value for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Get the value
+          $v_function_name = $p_options_list[$i+1];
+
+          // ----- Check that the value is a valid existing function
+          if (!function_exists($v_function_name)) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_OPTION_VALUE, "Function '".$v_function_name."()' is not an existing function for option '".PclZipUtilOptionText($p_options_list[$i])."'");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+
+          // ----- Set the attribute
+          $v_result_list[$p_options_list[$i]] = $v_function_name;
+          $i++;
+        break;
+
+        default :
+          // ----- Error log
+          PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER,
+		                       "Unknown parameter '"
+							   .$p_options_list[$i]."'");
+
+          // ----- Return
+          return PclZip::errorCode();
+      }
+
+      // ----- Next options
+      $i++;
+    }
+
+    // ----- Look for mandatory options
+    if ($v_requested_options !== false) {
+      for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) {
+        // ----- Look for mandatory option
+        if ($v_requested_options[$key] == 'mandatory') {
+          // ----- Look if present
+          if (!isset($v_result_list[$key])) {
+            // ----- Error log
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")");
+
+            // ----- Return
+            return PclZip::errorCode();
+          }
+        }
+      }
+    }
+    
+    // ----- Look for default values
+    if (!isset($v_result_list[PCLZIP_OPT_TEMP_FILE_THRESHOLD])) {
+      
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privOptionDefaultThreshold()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privOptionDefaultThreshold(&$p_options)
+  {
+    $v_result=1;
+    
+    if (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD])
+        || isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) {
+      return $v_result;
+    }
+    
+    // ----- Get 'memory_limit' configuration value
+    $v_memory_limit = ini_get('memory_limit');
+    $v_memory_limit = trim($v_memory_limit);
+    $last = strtolower(substr($v_memory_limit, -1));
+ 
+    if($last == 'g')
+        //$v_memory_limit = $v_memory_limit*1024*1024*1024;
+        $v_memory_limit = $v_memory_limit*1073741824;
+    if($last == 'm')
+        //$v_memory_limit = $v_memory_limit*1024*1024;
+        $v_memory_limit = $v_memory_limit*1048576;
+    if($last == 'k')
+        $v_memory_limit = $v_memory_limit*1024;
+            
+    $p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] = floor($v_memory_limit*PCLZIP_TEMPORARY_FILE_RATIO);
+    
+
+    // ----- Sanity check : No threshold if value lower than 1M
+    if ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] < 1048576) {
+      unset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD]);
+    }
+          
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privFileDescrParseAtt()
+  // Description :
+  // Parameters :
+  // Return Values :
+  //   1 on success.
+  //   0 on failure.
+  // --------------------------------------------------------------------------------
+  function privFileDescrParseAtt(&$p_file_list, &$p_filedescr, $v_options, $v_requested_options=false)
+  {
+    $v_result=1;
+    
+    // ----- For each file in the list check the attributes
+    foreach ($p_file_list as $v_key => $v_value) {
+    
+      // ----- Check if the option is supported
+      if (!isset($v_requested_options[$v_key])) {
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file attribute '".$v_key."' for this file");
+
+        // ----- Return
+        return PclZip::errorCode();
+      }
+
+      // ----- Look for attribute
+      switch ($v_key) {
+        case PCLZIP_ATT_FILE_NAME :
+          if (!is_string($v_value)) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'");
+            return PclZip::errorCode();
+          }
+
+          $p_filedescr['filename'] = PclZipUtilPathReduction($v_value);
+          
+          if ($p_filedescr['filename'] == '') {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty filename for attribute '".PclZipUtilOptionText($v_key)."'");
+            return PclZip::errorCode();
+          }
+
+        break;
+
+        case PCLZIP_ATT_FILE_NEW_SHORT_NAME :
+          if (!is_string($v_value)) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'");
+            return PclZip::errorCode();
+          }
+
+          $p_filedescr['new_short_name'] = PclZipUtilPathReduction($v_value);
+
+          if ($p_filedescr['new_short_name'] == '') {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty short filename for attribute '".PclZipUtilOptionText($v_key)."'");
+            return PclZip::errorCode();
+          }
+        break;
+
+        case PCLZIP_ATT_FILE_NEW_FULL_NAME :
+          if (!is_string($v_value)) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'");
+            return PclZip::errorCode();
+          }
+
+          $p_filedescr['new_full_name'] = PclZipUtilPathReduction($v_value);
+
+          if ($p_filedescr['new_full_name'] == '') {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid empty full filename for attribute '".PclZipUtilOptionText($v_key)."'");
+            return PclZip::errorCode();
+          }
+        break;
+
+        // ----- Look for options that takes a string
+        case PCLZIP_ATT_FILE_COMMENT :
+          if (!is_string($v_value)) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". String expected for attribute '".PclZipUtilOptionText($v_key)."'");
+            return PclZip::errorCode();
+          }
+
+          $p_filedescr['comment'] = $v_value;
+        break;
+
+        case PCLZIP_ATT_FILE_MTIME :
+          if (!is_integer($v_value)) {
+            PclZip::privErrorLog(PCLZIP_ERR_INVALID_ATTRIBUTE_VALUE, "Invalid type ".gettype($v_value).". Integer expected for attribute '".PclZipUtilOptionText($v_key)."'");
+            return PclZip::errorCode();
+          }
+
+          $p_filedescr['mtime'] = $v_value;
+        break;
+
+        case PCLZIP_ATT_FILE_CONTENT :
+          $p_filedescr['content'] = $v_value;
+        break;
+
+        default :
+          // ----- Error log
+          PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER,
+		                           "Unknown parameter '".$v_key."'");
+
+          // ----- Return
+          return PclZip::errorCode();
+      }
+
+      // ----- Look for mandatory options
+      if ($v_requested_options !== false) {
+        for ($key=reset($v_requested_options); $key=key($v_requested_options); $key=next($v_requested_options)) {
+          // ----- Look for mandatory option
+          if ($v_requested_options[$key] == 'mandatory') {
+            // ----- Look if present
+            if (!isset($p_file_list[$key])) {
+              PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Missing mandatory parameter ".PclZipUtilOptionText($key)."(".$key.")");
+              return PclZip::errorCode();
+            }
+          }
+        }
+      }
+    
+    // end foreach
+    }
+    
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privFileDescrExpand()
+  // Description :
+  //   This method look for each item of the list to see if its a file, a folder
+  //   or a string to be added as file. For any other type of files (link, other)
+  //   just ignore the item.
+  //   Then prepare the information that will be stored for that file.
+  //   When its a folder, expand the folder with all the files that are in that 
+  //   folder (recursively).
+  // Parameters :
+  // Return Values :
+  //   1 on success.
+  //   0 on failure.
+  // --------------------------------------------------------------------------------
+  function privFileDescrExpand(&$p_filedescr_list, &$p_options)
+  {
+    $v_result=1;
+    
+    // ----- Create a result list
+    $v_result_list = array();
+    
+    // ----- Look each entry
+    for ($i=0; $i<sizeof($p_filedescr_list); $i++) {
+      
+      // ----- Get filedescr
+      $v_descr = $p_filedescr_list[$i];
+      
+      // ----- Reduce the filename
+      $v_descr['filename'] = PclZipUtilTranslateWinPath($v_descr['filename'], false);
+      $v_descr['filename'] = PclZipUtilPathReduction($v_descr['filename']);
+      
+      // ----- Look for real file or folder
+      if (file_exists($v_descr['filename'])) {
+        if (@is_file($v_descr['filename'])) {
+          $v_descr['type'] = 'file';
+        }
+        else if (@is_dir($v_descr['filename'])) {
+          $v_descr['type'] = 'folder';
+        }
+        else if (@is_link($v_descr['filename'])) {
+          // skip
+          continue;
+        }
+        else {
+          // skip
+          continue;
+        }
+      }
+      
+      // ----- Look for string added as file
+      else if (isset($v_descr['content'])) {
+        $v_descr['type'] = 'virtual_file';
+      }
+      
+      // ----- Missing file
+      else {
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '".$v_descr['filename']."' does not exist");
+
+        // ----- Return
+        return PclZip::errorCode();
+      }
+      
+      // ----- Calculate the stored filename
+      $this->privCalculateStoredFilename($v_descr, $p_options);
+      
+      // ----- Add the descriptor in result list
+      $v_result_list[sizeof($v_result_list)] = $v_descr;
+      
+      // ----- Look for folder
+      if ($v_descr['type'] == 'folder') {
+        // ----- List of items in folder
+        $v_dirlist_descr = array();
+        $v_dirlist_nb = 0;
+        if ($v_folder_handler = @opendir($v_descr['filename'])) {
+          while (($v_item_handler = @readdir($v_folder_handler)) !== false) {
+
+            // ----- Skip '.' and '..'
+            if (($v_item_handler == '.') || ($v_item_handler == '..')) {
+                continue;
+            }
+            
+            // ----- Compose the full filename
+            $v_dirlist_descr[$v_dirlist_nb]['filename'] = $v_descr['filename'].'/'.$v_item_handler;
+            
+            // ----- Look for different stored filename
+            // Because the name of the folder was changed, the name of the
+            // files/sub-folders also change
+            if (($v_descr['stored_filename'] != $v_descr['filename'])
+                 && (!isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))) {
+              if ($v_descr['stored_filename'] != '') {
+                $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_descr['stored_filename'].'/'.$v_item_handler;
+              }
+              else {
+                $v_dirlist_descr[$v_dirlist_nb]['new_full_name'] = $v_item_handler;
+              }
+            }
+      
+            $v_dirlist_nb++;
+          }
+          
+          @closedir($v_folder_handler);
+        }
+        else {
+          // TBC : unable to open folder in read mode
+        }
+        
+        // ----- Expand each element of the list
+        if ($v_dirlist_nb != 0) {
+          // ----- Expand
+          if (($v_result = $this->privFileDescrExpand($v_dirlist_descr, $p_options)) != 1) {
+            return $v_result;
+          }
+          
+          // ----- Concat the resulting list
+          $v_result_list = array_merge($v_result_list, $v_dirlist_descr);
+        }
+        else {
+        }
+          
+        // ----- Free local array
+        unset($v_dirlist_descr);
+      }
+    }
+    
+    // ----- Get the result list
+    $p_filedescr_list = $v_result_list;
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privCreate()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privCreate($p_filedescr_list, &$p_result_list, &$p_options)
+  {
+    $v_result=1;
+    $v_list_detail = array();
+    
+    // ----- Magic quotes trick
+    $this->privDisableMagicQuotes();
+
+    // ----- Open the file in write mode
+    if (($v_result = $this->privOpenFd('wb')) != 1)
+    {
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Add the list of files
+    $v_result = $this->privAddList($p_filedescr_list, $p_result_list, $p_options);
+
+    // ----- Close
+    $this->privCloseFd();
+
+    // ----- Magic quotes trick
+    $this->privSwapBackMagicQuotes();
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privAdd()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privAdd($p_filedescr_list, &$p_result_list, &$p_options)
+  {
+    $v_result=1;
+    $v_list_detail = array();
+
+    // ----- Look if the archive exists or is empty
+    if ((!is_file($this->zipname)) || (filesize($this->zipname) == 0))
+    {
+
+      // ----- Do a create
+      $v_result = $this->privCreate($p_filedescr_list, $p_result_list, $p_options);
+
+      // ----- Return
+      return $v_result;
+    }
+    // ----- Magic quotes trick
+    $this->privDisableMagicQuotes();
+
+    // ----- Open the zip file
+    if (($v_result=$this->privOpenFd('rb')) != 1)
+    {
+      // ----- Magic quotes trick
+      $this->privSwapBackMagicQuotes();
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Read the central directory informations
+    $v_central_dir = array();
+    if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+    {
+      $this->privCloseFd();
+      $this->privSwapBackMagicQuotes();
+      return $v_result;
+    }
+
+    // ----- Go to beginning of File
+    @rewind($this->zip_fd);
+
+    // ----- Creates a temporay file
+    $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp';
+
+    // ----- Open the temporary file in write mode
+    if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0)
+    {
+      $this->privCloseFd();
+      $this->privSwapBackMagicQuotes();
+
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_zip_temp_name.'\' in binary write mode');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Copy the files from the archive to the temporary file
+    // TBC : Here I should better append the file and go back to erase the central dir
+    $v_size = $v_central_dir['offset'];
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = fread($this->zip_fd, $v_read_size);
+      @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Swap the file descriptor
+    // Here is a trick : I swap the temporary fd with the zip fd, in order to use
+    // the following methods on the temporary fil and not the real archive
+    $v_swap = $this->zip_fd;
+    $this->zip_fd = $v_zip_temp_fd;
+    $v_zip_temp_fd = $v_swap;
+
+    // ----- Add the files
+    $v_header_list = array();
+    if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1)
+    {
+      fclose($v_zip_temp_fd);
+      $this->privCloseFd();
+      @unlink($v_zip_temp_name);
+      $this->privSwapBackMagicQuotes();
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Store the offset of the central dir
+    $v_offset = @ftell($this->zip_fd);
+
+    // ----- Copy the block of file headers from the old archive
+    $v_size = $v_central_dir['size'];
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = @fread($v_zip_temp_fd, $v_read_size);
+      @fwrite($this->zip_fd, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Create the Central Dir files header
+    for ($i=0, $v_count=0; $i<sizeof($v_header_list); $i++)
+    {
+      // ----- Create the file header
+      if ($v_header_list[$i]['status'] == 'ok') {
+        if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) {
+          fclose($v_zip_temp_fd);
+          $this->privCloseFd();
+          @unlink($v_zip_temp_name);
+          $this->privSwapBackMagicQuotes();
+
+          // ----- Return
+          return $v_result;
+        }
+        $v_count++;
+      }
+
+      // ----- Transform the header to a 'usable' info
+      $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]);
+    }
+
+    // ----- Zip file comment
+    $v_comment = $v_central_dir['comment'];
+    if (isset($p_options[PCLZIP_OPT_COMMENT])) {
+      $v_comment = $p_options[PCLZIP_OPT_COMMENT];
+    }
+    if (isset($p_options[PCLZIP_OPT_ADD_COMMENT])) {
+      $v_comment = $v_comment.$p_options[PCLZIP_OPT_ADD_COMMENT];
+    }
+    if (isset($p_options[PCLZIP_OPT_PREPEND_COMMENT])) {
+      $v_comment = $p_options[PCLZIP_OPT_PREPEND_COMMENT].$v_comment;
+    }
+
+    // ----- Calculate the size of the central header
+    $v_size = @ftell($this->zip_fd)-$v_offset;
+
+    // ----- Create the central dir footer
+    if (($v_result = $this->privWriteCentralHeader($v_count+$v_central_dir['entries'], $v_size, $v_offset, $v_comment)) != 1)
+    {
+      // ----- Reset the file list
+      unset($v_header_list);
+      $this->privSwapBackMagicQuotes();
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Swap back the file descriptor
+    $v_swap = $this->zip_fd;
+    $this->zip_fd = $v_zip_temp_fd;
+    $v_zip_temp_fd = $v_swap;
+
+    // ----- Close
+    $this->privCloseFd();
+
+    // ----- Close the temporary file
+    @fclose($v_zip_temp_fd);
+
+    // ----- Magic quotes trick
+    $this->privSwapBackMagicQuotes();
+
+    // ----- Delete the zip file
+    // TBC : I should test the result ...
+    @unlink($this->zipname);
+
+    // ----- Rename the temporary file
+    // TBC : I should test the result ...
+    //@rename($v_zip_temp_name, $this->zipname);
+    PclZipUtilRename($v_zip_temp_name, $this->zipname);
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privOpenFd()
+  // Description :
+  // Parameters :
+  // --------------------------------------------------------------------------------
+  function privOpenFd($p_mode)
+  {
+    $v_result=1;
+
+    // ----- Look if already open
+    if ($this->zip_fd != 0)
+    {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Zip file \''.$this->zipname.'\' already open');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Open the zip file
+    if (($this->zip_fd = @fopen($this->zipname, $p_mode)) == 0)
+    {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in '.$p_mode.' mode');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privCloseFd()
+  // Description :
+  // Parameters :
+  // --------------------------------------------------------------------------------
+  function privCloseFd()
+  {
+    $v_result=1;
+
+    if ($this->zip_fd != 0)
+      @fclose($this->zip_fd);
+    $this->zip_fd = 0;
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privAddList()
+  // Description :
+  //   $p_add_dir and $p_remove_dir will give the ability to memorize a path which is
+  //   different from the real path of the file. This is usefull if you want to have PclTar
+  //   running in any directory, and memorize relative path from an other directory.
+  // Parameters :
+  //   $p_list : An array containing the file or directory names to add in the tar
+  //   $p_result_list : list of added files with their properties (specially the status field)
+  //   $p_add_dir : Path to add in the filename path archived
+  //   $p_remove_dir : Path to remove in the filename path archived
+  // Return Values :
+  // --------------------------------------------------------------------------------
+//  function privAddList($p_list, &$p_result_list, $p_add_dir, $p_remove_dir, $p_remove_all_dir, &$p_options)
+  function privAddList($p_filedescr_list, &$p_result_list, &$p_options)
+  {
+    $v_result=1;
+
+    // ----- Add the files
+    $v_header_list = array();
+    if (($v_result = $this->privAddFileList($p_filedescr_list, $v_header_list, $p_options)) != 1)
+    {
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Store the offset of the central dir
+    $v_offset = @ftell($this->zip_fd);
+
+    // ----- Create the Central Dir files header
+    for ($i=0,$v_count=0; $i<sizeof($v_header_list); $i++)
+    {
+      // ----- Create the file header
+      if ($v_header_list[$i]['status'] == 'ok') {
+        if (($v_result = $this->privWriteCentralFileHeader($v_header_list[$i])) != 1) {
+          // ----- Return
+          return $v_result;
+        }
+        $v_count++;
+      }
+
+      // ----- Transform the header to a 'usable' info
+      $this->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]);
+    }
+
+    // ----- Zip file comment
+    $v_comment = '';
+    if (isset($p_options[PCLZIP_OPT_COMMENT])) {
+      $v_comment = $p_options[PCLZIP_OPT_COMMENT];
+    }
+
+    // ----- Calculate the size of the central header
+    $v_size = @ftell($this->zip_fd)-$v_offset;
+
+    // ----- Create the central dir footer
+    if (($v_result = $this->privWriteCentralHeader($v_count, $v_size, $v_offset, $v_comment)) != 1)
+    {
+      // ----- Reset the file list
+      unset($v_header_list);
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privAddFileList()
+  // Description :
+  // Parameters :
+  //   $p_filedescr_list : An array containing the file description 
+  //                      or directory names to add in the zip
+  //   $p_result_list : list of added files with their properties (specially the status field)
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privAddFileList($p_filedescr_list, &$p_result_list, &$p_options)
+  {
+    $v_result=1;
+    $v_header = array();
+
+    // ----- Recuperate the current number of elt in list
+    $v_nb = sizeof($p_result_list);
+
+    // ----- Loop on the files
+    for ($j=0; ($j<sizeof($p_filedescr_list)) && ($v_result==1); $j++) {
+      // ----- Format the filename
+      $p_filedescr_list[$j]['filename']
+      = PclZipUtilTranslateWinPath($p_filedescr_list[$j]['filename'], false);
+      
+
+      // ----- Skip empty file names
+      // TBC : Can this be possible ? not checked in DescrParseAtt ?
+      if ($p_filedescr_list[$j]['filename'] == "") {
+        continue;
+      }
+
+      // ----- Check the filename
+      if (   ($p_filedescr_list[$j]['type'] != 'virtual_file')
+          && (!file_exists($p_filedescr_list[$j]['filename']))) {
+        PclZip::privErrorLog(PCLZIP_ERR_MISSING_FILE, "File '".$p_filedescr_list[$j]['filename']."' does not exist");
+        return PclZip::errorCode();
+      }
+
+      // ----- Look if it is a file or a dir with no all path remove option
+      // or a dir with all its path removed
+//      if (   (is_file($p_filedescr_list[$j]['filename']))
+//          || (   is_dir($p_filedescr_list[$j]['filename'])
+      if (   ($p_filedescr_list[$j]['type'] == 'file')
+          || ($p_filedescr_list[$j]['type'] == 'virtual_file')
+          || (   ($p_filedescr_list[$j]['type'] == 'folder')
+              && (   !isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])
+                  || !$p_options[PCLZIP_OPT_REMOVE_ALL_PATH]))
+          ) {
+
+        // ----- Add the file
+        $v_result = $this->privAddFile($p_filedescr_list[$j], $v_header,
+                                       $p_options);
+        if ($v_result != 1) {
+          return $v_result;
+        }
+
+        // ----- Store the file infos
+        $p_result_list[$v_nb++] = $v_header;
+      }
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privAddFile()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privAddFile($p_filedescr, &$p_header, &$p_options)
+  {
+    $v_result=1;
+    
+    // ----- Working variable
+    $p_filename = $p_filedescr['filename'];
+
+    // TBC : Already done in the fileAtt check ... ?
+    if ($p_filename == "") {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_INVALID_PARAMETER, "Invalid file list parameter (invalid or empty list)");
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+  
+    // ----- Look for a stored different filename 
+    /* TBC : Removed
+    if (isset($p_filedescr['stored_filename'])) {
+      $v_stored_filename = $p_filedescr['stored_filename'];
+    }
+    else {
+      $v_stored_filename = $p_filedescr['stored_filename'];
+    }
+    */
+
+    // ----- Set the file properties
+    clearstatcache();
+    $p_header['version'] = 20;
+    $p_header['version_extracted'] = 10;
+    $p_header['flag'] = 0;
+    $p_header['compression'] = 0;
+    $p_header['crc'] = 0;
+    $p_header['compressed_size'] = 0;
+    $p_header['filename_len'] = strlen($p_filename);
+    $p_header['extra_len'] = 0;
+    $p_header['disk'] = 0;
+    $p_header['internal'] = 0;
+    $p_header['offset'] = 0;
+    $p_header['filename'] = $p_filename;
+// TBC : Removed    $p_header['stored_filename'] = $v_stored_filename;
+    $p_header['stored_filename'] = $p_filedescr['stored_filename'];
+    $p_header['extra'] = '';
+    $p_header['status'] = 'ok';
+    $p_header['index'] = -1;
+
+    // ----- Look for regular file
+    if ($p_filedescr['type']=='file') {
+      $p_header['external'] = 0x00000000;
+      $p_header['size'] = filesize($p_filename);
+    }
+    
+    // ----- Look for regular folder
+    else if ($p_filedescr['type']=='folder') {
+      $p_header['external'] = 0x00000010;
+      $p_header['mtime'] = filemtime($p_filename);
+      $p_header['size'] = filesize($p_filename);
+    }
+    
+    // ----- Look for virtual file
+    else if ($p_filedescr['type'] == 'virtual_file') {
+      $p_header['external'] = 0x00000000;
+      $p_header['size'] = strlen($p_filedescr['content']);
+    }
+    
+
+    // ----- Look for filetime
+    if (isset($p_filedescr['mtime'])) {
+      $p_header['mtime'] = $p_filedescr['mtime'];
+    }
+    else if ($p_filedescr['type'] == 'virtual_file') {
+      $p_header['mtime'] = time();
+    }
+    else {
+      $p_header['mtime'] = filemtime($p_filename);
+    }
+
+    // ------ Look for file comment
+    if (isset($p_filedescr['comment'])) {
+      $p_header['comment_len'] = strlen($p_filedescr['comment']);
+      $p_header['comment'] = $p_filedescr['comment'];
+    }
+    else {
+      $p_header['comment_len'] = 0;
+      $p_header['comment'] = '';
+    }
+
+    // ----- Look for pre-add callback
+    if (isset($p_options[PCLZIP_CB_PRE_ADD])) {
+
+      // ----- Generate a local information
+      $v_local_header = array();
+      $this->privConvertHeader2FileInfo($p_header, $v_local_header);
+
+      // ----- Call the callback
+      // Here I do not use call_user_func() because I need to send a reference to the
+      // header.
+//      eval('$v_result = '.$p_options[PCLZIP_CB_PRE_ADD].'(PCLZIP_CB_PRE_ADD, $v_local_header);');
+      $v_result = $p_options[PCLZIP_CB_PRE_ADD](PCLZIP_CB_PRE_ADD, $v_local_header);
+      if ($v_result == 0) {
+        // ----- Change the file status
+        $p_header['status'] = "skipped";
+        $v_result = 1;
+      }
+
+      // ----- Update the informations
+      // Only some fields can be modified
+      if ($p_header['stored_filename'] != $v_local_header['stored_filename']) {
+        $p_header['stored_filename'] = PclZipUtilPathReduction($v_local_header['stored_filename']);
+      }
+    }
+
+    // ----- Look for empty stored filename
+    if ($p_header['stored_filename'] == "") {
+      $p_header['status'] = "filtered";
+    }
+    
+    // ----- Check the path length
+    if (strlen($p_header['stored_filename']) > 0xFF) {
+      $p_header['status'] = 'filename_too_long';
+    }
+
+    // ----- Look if no error, or file not skipped
+    if ($p_header['status'] == 'ok') {
+
+      // ----- Look for a file
+      if ($p_filedescr['type'] == 'file') {
+        // ----- Look for using temporary file to zip
+        if ( (!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) 
+            && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON])
+                || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD])
+                    && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_header['size'])) ) ) {
+          $v_result = $this->privAddFileUsingTempFile($p_filedescr, $p_header, $p_options);
+          if ($v_result < PCLZIP_ERR_NO_ERROR) {
+            return $v_result;
+          }
+        }
+        
+        // ----- Use "in memory" zip algo
+        else {
+
+        // ----- Open the source file
+        if (($v_file = @fopen($p_filename, "rb")) == 0) {
+          PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode");
+          return PclZip::errorCode();
+        }
+
+        // ----- Read the file content
+        $v_content = @fread($v_file, $p_header['size']);
+
+        // ----- Close the file
+        @fclose($v_file);
+
+        // ----- Calculate the CRC
+        $p_header['crc'] = @crc32($v_content);
+        
+        // ----- Look for no compression
+        if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) {
+          // ----- Set header parameters
+          $p_header['compressed_size'] = $p_header['size'];
+          $p_header['compression'] = 0;
+        }
+        
+        // ----- Look for normal compression
+        else {
+          // ----- Compress the content
+          $v_content = @gzdeflate($v_content);
+
+          // ----- Set header parameters
+          $p_header['compressed_size'] = strlen($v_content);
+          $p_header['compression'] = 8;
+        }
+        
+        // ----- Call the header generation
+        if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+          @fclose($v_file);
+          return $v_result;
+        }
+
+        // ----- Write the compressed (or not) content
+        @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']);
+
+        }
+
+      }
+
+      // ----- Look for a virtual file (a file from string)
+      else if ($p_filedescr['type'] == 'virtual_file') {
+          
+        $v_content = $p_filedescr['content'];
+
+        // ----- Calculate the CRC
+        $p_header['crc'] = @crc32($v_content);
+        
+        // ----- Look for no compression
+        if ($p_options[PCLZIP_OPT_NO_COMPRESSION]) {
+          // ----- Set header parameters
+          $p_header['compressed_size'] = $p_header['size'];
+          $p_header['compression'] = 0;
+        }
+        
+        // ----- Look for normal compression
+        else {
+          // ----- Compress the content
+          $v_content = @gzdeflate($v_content);
+
+          // ----- Set header parameters
+          $p_header['compressed_size'] = strlen($v_content);
+          $p_header['compression'] = 8;
+        }
+        
+        // ----- Call the header generation
+        if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+          @fclose($v_file);
+          return $v_result;
+        }
+
+        // ----- Write the compressed (or not) content
+        @fwrite($this->zip_fd, $v_content, $p_header['compressed_size']);
+      }
+
+      // ----- Look for a directory
+      else if ($p_filedescr['type'] == 'folder') {
+        // ----- Look for directory last '/'
+        if (@substr($p_header['stored_filename'], -1) != '/') {
+          $p_header['stored_filename'] .= '/';
+        }
+
+        // ----- Set the file properties
+        $p_header['size'] = 0;
+        //$p_header['external'] = 0x41FF0010;   // Value for a folder : to be checked
+        $p_header['external'] = 0x00000010;   // Value for a folder : to be checked
+
+        // ----- Call the header generation
+        if (($v_result = $this->privWriteFileHeader($p_header)) != 1)
+        {
+          return $v_result;
+        }
+      }
+    }
+
+    // ----- Look for post-add callback
+    if (isset($p_options[PCLZIP_CB_POST_ADD])) {
+
+      // ----- Generate a local information
+      $v_local_header = array();
+      $this->privConvertHeader2FileInfo($p_header, $v_local_header);
+
+      // ----- Call the callback
+      // Here I do not use call_user_func() because I need to send a reference to the
+      // header.
+//      eval('$v_result = '.$p_options[PCLZIP_CB_POST_ADD].'(PCLZIP_CB_POST_ADD, $v_local_header);');
+      $v_result = $p_options[PCLZIP_CB_POST_ADD](PCLZIP_CB_POST_ADD, $v_local_header);
+      if ($v_result == 0) {
+        // ----- Ignored
+        $v_result = 1;
+      }
+
+      // ----- Update the informations
+      // Nothing can be modified
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privAddFileUsingTempFile()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privAddFileUsingTempFile($p_filedescr, &$p_header, &$p_options)
+  {
+    $v_result=PCLZIP_ERR_NO_ERROR;
+    
+    // ----- Working variable
+    $p_filename = $p_filedescr['filename'];
+
+
+    // ----- Open the source file
+    if (($v_file = @fopen($p_filename, "rb")) == 0) {
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, "Unable to open file '$p_filename' in binary read mode");
+      return PclZip::errorCode();
+    }
+
+    // ----- Creates a compressed temporary file
+    $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.gz';
+    if (($v_file_compressed = @gzopen($v_gzip_temp_name, "wb")) == 0) {
+      fclose($v_file);
+      PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary write mode');
+      return PclZip::errorCode();
+    }
+
+    // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+    $v_size = filesize($p_filename);
+    while ($v_size != 0) {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = @fread($v_file, $v_read_size);
+      //$v_binary_data = pack('a'.$v_read_size, $v_buffer);
+      @gzputs($v_file_compressed, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Close the file
+    @fclose($v_file);
+    @gzclose($v_file_compressed);
+
+    // ----- Check the minimum file size
+    if (filesize($v_gzip_temp_name) < 18) {
+      PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'gzip temporary file \''.$v_gzip_temp_name.'\' has invalid filesize - should be minimum 18 bytes');
+      return PclZip::errorCode();
+    }
+
+    // ----- Extract the compressed attributes
+    if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0) {
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode');
+      return PclZip::errorCode();
+    }
+
+    // ----- Read the gzip file header
+    $v_binary_data = @fread($v_file_compressed, 10);
+    $v_data_header = unpack('a1id1/a1id2/a1cm/a1flag/Vmtime/a1xfl/a1os', $v_binary_data);
+
+    // ----- Check some parameters
+    $v_data_header['os'] = bin2hex($v_data_header['os']);
+
+    // ----- Read the gzip file footer
+    @fseek($v_file_compressed, filesize($v_gzip_temp_name)-8);
+    $v_binary_data = @fread($v_file_compressed, 8);
+    $v_data_footer = unpack('Vcrc/Vcompressed_size', $v_binary_data);
+
+    // ----- Set the attributes
+    $p_header['compression'] = ord($v_data_header['cm']);
+    //$p_header['mtime'] = $v_data_header['mtime'];
+    $p_header['crc'] = $v_data_footer['crc'];
+    $p_header['compressed_size'] = filesize($v_gzip_temp_name)-18;
+
+    // ----- Close the file
+    @fclose($v_file_compressed);
+
+    // ----- Call the header generation
+    if (($v_result = $this->privWriteFileHeader($p_header)) != 1) {
+      return $v_result;
+    }
+
+    // ----- Add the compressed data
+    if (($v_file_compressed = @fopen($v_gzip_temp_name, "rb")) == 0)
+    {
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode');
+      return PclZip::errorCode();
+    }
+
+    // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+    fseek($v_file_compressed, 10);
+    $v_size = $p_header['compressed_size'];
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = @fread($v_file_compressed, $v_read_size);
+      //$v_binary_data = pack('a'.$v_read_size, $v_buffer);
+      @fwrite($this->zip_fd, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Close the file
+    @fclose($v_file_compressed);
+
+    // ----- Unlink the temporary file
+    @unlink($v_gzip_temp_name);
+    
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privCalculateStoredFilename()
+  // Description :
+  //   Based on file descriptor properties and global options, this method
+  //   calculate the filename that will be stored in the archive.
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privCalculateStoredFilename(&$p_filedescr, &$p_options)
+  {
+    $v_result=1;
+    
+    // ----- Working variables
+    $p_filename = $p_filedescr['filename'];
+    if (isset($p_options[PCLZIP_OPT_ADD_PATH])) {
+      $p_add_dir = $p_options[PCLZIP_OPT_ADD_PATH];
+    }
+    else {
+      $p_add_dir = '';
+    }
+    if (isset($p_options[PCLZIP_OPT_REMOVE_PATH])) {
+      $p_remove_dir = $p_options[PCLZIP_OPT_REMOVE_PATH];
+    }
+    else {
+      $p_remove_dir = '';
+    }
+    if (isset($p_options[PCLZIP_OPT_REMOVE_ALL_PATH])) {
+      $p_remove_all_dir = $p_options[PCLZIP_OPT_REMOVE_ALL_PATH];
+    }
+    else {
+      $p_remove_all_dir = 0;
+    }
+
+
+    // ----- Look for full name change
+    if (isset($p_filedescr['new_full_name'])) {
+      // ----- Remove drive letter if any
+      $v_stored_filename = PclZipUtilTranslateWinPath($p_filedescr['new_full_name']);
+    }
+    
+    // ----- Look for path and/or short name change
+    else {
+
+      // ----- Look for short name change
+      // Its when we cahnge just the filename but not the path
+      if (isset($p_filedescr['new_short_name'])) {
+        $v_path_info = pathinfo($p_filename);
+        $v_dir = '';
+        if ($v_path_info['dirname'] != '') {
+          $v_dir = $v_path_info['dirname'].'/';
+        }
+        $v_stored_filename = $v_dir.$p_filedescr['new_short_name'];
+      }
+      else {
+        // ----- Calculate the stored filename
+        $v_stored_filename = $p_filename;
+      }
+
+      // ----- Look for all path to remove
+      if ($p_remove_all_dir) {
+        $v_stored_filename = basename($p_filename);
+      }
+      // ----- Look for partial path remove
+      else if ($p_remove_dir != "") {
+        if (substr($p_remove_dir, -1) != '/')
+          $p_remove_dir .= "/";
+
+        if (   (substr($p_filename, 0, 2) == "./")
+            || (substr($p_remove_dir, 0, 2) == "./")) {
+            
+          if (   (substr($p_filename, 0, 2) == "./")
+              && (substr($p_remove_dir, 0, 2) != "./")) {
+            $p_remove_dir = "./".$p_remove_dir;
+          }
+          if (   (substr($p_filename, 0, 2) != "./")
+              && (substr($p_remove_dir, 0, 2) == "./")) {
+            $p_remove_dir = substr($p_remove_dir, 2);
+          }
+        }
+
+        $v_compare = PclZipUtilPathInclusion($p_remove_dir,
+                                             $v_stored_filename);
+        if ($v_compare > 0) {
+          if ($v_compare == 2) {
+            $v_stored_filename = "";
+          }
+          else {
+            $v_stored_filename = substr($v_stored_filename,
+                                        strlen($p_remove_dir));
+          }
+        }
+      }
+      
+      // ----- Remove drive letter if any
+      $v_stored_filename = PclZipUtilTranslateWinPath($v_stored_filename);
+      
+      // ----- Look for path to add
+      if ($p_add_dir != "") {
+        if (substr($p_add_dir, -1) == "/")
+          $v_stored_filename = $p_add_dir.$v_stored_filename;
+        else
+          $v_stored_filename = $p_add_dir."/".$v_stored_filename;
+      }
+    }
+
+    // ----- Filename (reduce the path of stored name)
+    $v_stored_filename = PclZipUtilPathReduction($v_stored_filename);
+    $p_filedescr['stored_filename'] = $v_stored_filename;
+    
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privWriteFileHeader()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privWriteFileHeader(&$p_header)
+  {
+    $v_result=1;
+
+    // ----- Store the offset position of the file
+    $p_header['offset'] = ftell($this->zip_fd);
+
+    // ----- Transform UNIX mtime to DOS format mdate/mtime
+    $v_date = getdate($p_header['mtime']);
+    $v_mtime = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2;
+    $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday'];
+
+    // ----- Packed data
+    $v_binary_data = pack("VvvvvvVVVvv", 0x04034b50,
+	                      $p_header['version_extracted'], $p_header['flag'],
+                          $p_header['compression'], $v_mtime, $v_mdate,
+                          $p_header['crc'], $p_header['compressed_size'],
+						  $p_header['size'],
+                          strlen($p_header['stored_filename']),
+						  $p_header['extra_len']);
+
+    // ----- Write the first 148 bytes of the header in the archive
+    fputs($this->zip_fd, $v_binary_data, 30);
+
+    // ----- Write the variable fields
+    if (strlen($p_header['stored_filename']) != 0)
+    {
+      fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename']));
+    }
+    if ($p_header['extra_len'] != 0)
+    {
+      fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']);
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privWriteCentralFileHeader()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privWriteCentralFileHeader(&$p_header)
+  {
+    $v_result=1;
+
+    // TBC
+    //for(reset($p_header); $key = key($p_header); next($p_header)) {
+    //}
+
+    // ----- Transform UNIX mtime to DOS format mdate/mtime
+    $v_date = getdate($p_header['mtime']);
+    $v_mtime = ($v_date['hours']<<11) + ($v_date['minutes']<<5) + $v_date['seconds']/2;
+    $v_mdate = (($v_date['year']-1980)<<9) + ($v_date['mon']<<5) + $v_date['mday'];
+
+
+    // ----- Packed data
+    $v_binary_data = pack("VvvvvvvVVVvvvvvVV", 0x02014b50,
+	                      $p_header['version'], $p_header['version_extracted'],
+                          $p_header['flag'], $p_header['compression'],
+						  $v_mtime, $v_mdate, $p_header['crc'],
+                          $p_header['compressed_size'], $p_header['size'],
+                          strlen($p_header['stored_filename']),
+						  $p_header['extra_len'], $p_header['comment_len'],
+                          $p_header['disk'], $p_header['internal'],
+						  $p_header['external'], $p_header['offset']);
+
+    // ----- Write the 42 bytes of the header in the zip file
+    fputs($this->zip_fd, $v_binary_data, 46);
+
+    // ----- Write the variable fields
+    if (strlen($p_header['stored_filename']) != 0)
+    {
+      fputs($this->zip_fd, $p_header['stored_filename'], strlen($p_header['stored_filename']));
+    }
+    if ($p_header['extra_len'] != 0)
+    {
+      fputs($this->zip_fd, $p_header['extra'], $p_header['extra_len']);
+    }
+    if ($p_header['comment_len'] != 0)
+    {
+      fputs($this->zip_fd, $p_header['comment'], $p_header['comment_len']);
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privWriteCentralHeader()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privWriteCentralHeader($p_nb_entries, $p_size, $p_offset, $p_comment)
+  {
+    $v_result=1;
+
+    // ----- Packed data
+    $v_binary_data = pack("VvvvvVVv", 0x06054b50, 0, 0, $p_nb_entries,
+	                      $p_nb_entries, $p_size,
+						  $p_offset, strlen($p_comment));
+
+    // ----- Write the 22 bytes of the header in the zip file
+    fputs($this->zip_fd, $v_binary_data, 22);
+
+    // ----- Write the variable fields
+    if (strlen($p_comment) != 0)
+    {
+      fputs($this->zip_fd, $p_comment, strlen($p_comment));
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privList()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privList(&$p_list)
+  {
+    $v_result=1;
+
+    // ----- Magic quotes trick
+    $this->privDisableMagicQuotes();
+
+    // ----- Open the zip file
+    if (($this->zip_fd = @fopen($this->zipname, 'rb')) == 0)
+    {
+      // ----- Magic quotes trick
+      $this->privSwapBackMagicQuotes();
+      
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive \''.$this->zipname.'\' in binary read mode');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Read the central directory informations
+    $v_central_dir = array();
+    if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+    {
+      $this->privSwapBackMagicQuotes();
+      return $v_result;
+    }
+
+    // ----- Go to beginning of Central Dir
+    @rewind($this->zip_fd);
+    if (@fseek($this->zip_fd, $v_central_dir['offset']))
+    {
+      $this->privSwapBackMagicQuotes();
+
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Read each entry
+    for ($i=0; $i<$v_central_dir['entries']; $i++)
+    {
+      // ----- Read the file header
+      if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1)
+      {
+        $this->privSwapBackMagicQuotes();
+        return $v_result;
+      }
+      $v_header['index'] = $i;
+
+      // ----- Get the only interesting attributes
+      $this->privConvertHeader2FileInfo($v_header, $p_list[$i]);
+      unset($v_header);
+    }
+
+    // ----- Close the zip file
+    $this->privCloseFd();
+
+    // ----- Magic quotes trick
+    $this->privSwapBackMagicQuotes();
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privConvertHeader2FileInfo()
+  // Description :
+  //   This function takes the file informations from the central directory
+  //   entries and extract the interesting parameters that will be given back.
+  //   The resulting file infos are set in the array $p_info
+  //     $p_info['filename'] : Filename with full path. Given by user (add),
+  //                           extracted in the filesystem (extract).
+  //     $p_info['stored_filename'] : Stored filename in the archive.
+  //     $p_info['size'] = Size of the file.
+  //     $p_info['compressed_size'] = Compressed size of the file.
+  //     $p_info['mtime'] = Last modification date of the file.
+  //     $p_info['comment'] = Comment associated with the file.
+  //     $p_info['folder'] = true/false : indicates if the entry is a folder or not.
+  //     $p_info['status'] = status of the action on the file.
+  //     $p_info['crc'] = CRC of the file content.
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privConvertHeader2FileInfo($p_header, &$p_info)
+  {
+    $v_result=1;
+
+    // ----- Get the interesting attributes
+    $v_temp_path = PclZipUtilPathReduction($p_header['filename']);
+    $p_info['filename'] = $v_temp_path;
+    $v_temp_path = PclZipUtilPathReduction($p_header['stored_filename']);
+    $p_info['stored_filename'] = $v_temp_path;
+    $p_info['size'] = $p_header['size'];
+    $p_info['compressed_size'] = $p_header['compressed_size'];
+    $p_info['mtime'] = $p_header['mtime'];
+    $p_info['comment'] = $p_header['comment'];
+    $p_info['folder'] = (($p_header['external']&0x00000010)==0x00000010);
+    $p_info['index'] = $p_header['index'];
+    $p_info['status'] = $p_header['status'];
+    $p_info['crc'] = $p_header['crc'];
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privExtractByRule()
+  // Description :
+  //   Extract a file or directory depending of rules (by index, by name, ...)
+  // Parameters :
+  //   $p_file_list : An array where will be placed the properties of each
+  //                  extracted file
+  //   $p_path : Path to add while writing the extracted files
+  //   $p_remove_path : Path to remove (from the file memorized path) while writing the
+  //                    extracted files. If the path does not match the file path,
+  //                    the file is extracted with its memorized path.
+  //                    $p_remove_path does not apply to 'list' mode.
+  //                    $p_path and $p_remove_path are commulative.
+  // Return Values :
+  //   1 on success,0 or less on error (see error code list)
+  // --------------------------------------------------------------------------------
+  function privExtractByRule(&$p_file_list, $p_path, $p_remove_path, $p_remove_all_path, &$p_options)
+  {
+    $v_result=1;
+
+    // ----- Magic quotes trick
+    $this->privDisableMagicQuotes();
+
+    // ----- Check the path
+    if (   ($p_path == "")
+	    || (   (substr($p_path, 0, 1) != "/")
+		    && (substr($p_path, 0, 3) != "../")
+			&& (substr($p_path,1,2)!=":/")))
+      $p_path = "./".$p_path;
+
+    // ----- Reduce the path last (and duplicated) '/'
+    if (($p_path != "./") && ($p_path != "/"))
+    {
+      // ----- Look for the path end '/'
+      while (substr($p_path, -1) == "/")
+      {
+        $p_path = substr($p_path, 0, strlen($p_path)-1);
+      }
+    }
+
+    // ----- Look for path to remove format (should end by /)
+    if (($p_remove_path != "") && (substr($p_remove_path, -1) != '/'))
+    {
+      $p_remove_path .= '/';
+    }
+    $p_remove_path_size = strlen($p_remove_path);
+
+    // ----- Open the zip file
+    if (($v_result = $this->privOpenFd('rb')) != 1)
+    {
+      $this->privSwapBackMagicQuotes();
+      return $v_result;
+    }
+
+    // ----- Read the central directory informations
+    $v_central_dir = array();
+    if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+    {
+      // ----- Close the zip file
+      $this->privCloseFd();
+      $this->privSwapBackMagicQuotes();
+
+      return $v_result;
+    }
+
+    // ----- Start at beginning of Central Dir
+    $v_pos_entry = $v_central_dir['offset'];
+
+    // ----- Read each entry
+    $j_start = 0;
+    for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++)
+    {
+
+      // ----- Read next Central dir entry
+      @rewind($this->zip_fd);
+      if (@fseek($this->zip_fd, $v_pos_entry))
+      {
+        // ----- Close the zip file
+        $this->privCloseFd();
+        $this->privSwapBackMagicQuotes();
+
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+        // ----- Return
+        return PclZip::errorCode();
+      }
+
+      // ----- Read the file header
+      $v_header = array();
+      if (($v_result = $this->privReadCentralFileHeader($v_header)) != 1)
+      {
+        // ----- Close the zip file
+        $this->privCloseFd();
+        $this->privSwapBackMagicQuotes();
+
+        return $v_result;
+      }
+
+      // ----- Store the index
+      $v_header['index'] = $i;
+
+      // ----- Store the file position
+      $v_pos_entry = ftell($this->zip_fd);
+
+      // ----- Look for the specific extract rules
+      $v_extract = false;
+
+      // ----- Look for extract by name rule
+      if (   (isset($p_options[PCLZIP_OPT_BY_NAME]))
+          && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) {
+
+          // ----- Look if the filename is in the list
+          for ($j=0; ($j<sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_extract); $j++) {
+
+              // ----- Look for a directory
+              if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") {
+
+                  // ----- Look if the directory is in the filename path
+                  if (   (strlen($v_header['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j]))
+                      && (substr($v_header['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) {
+                      $v_extract = true;
+                  }
+              }
+              // ----- Look for a filename
+              elseif ($v_header['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) {
+                  $v_extract = true;
+              }
+          }
+      }
+
+      // ----- Look for extract by ereg rule
+      // ereg() is deprecated with PHP 5.3
+      /* 
+      else if (   (isset($p_options[PCLZIP_OPT_BY_EREG]))
+               && ($p_options[PCLZIP_OPT_BY_EREG] != "")) {
+
+          if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header['stored_filename'])) {
+              $v_extract = true;
+          }
+      }
+      */
+
+      // ----- Look for extract by preg rule
+      else if (   (isset($p_options[PCLZIP_OPT_BY_PREG]))
+               && ($p_options[PCLZIP_OPT_BY_PREG] != "")) {
+
+          if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header['stored_filename'])) {
+              $v_extract = true;
+          }
+      }
+
+      // ----- Look for extract by index rule
+      else if (   (isset($p_options[PCLZIP_OPT_BY_INDEX]))
+               && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) {
+          
+          // ----- Look if the index is in the list
+          for ($j=$j_start; ($j<sizeof($p_options[PCLZIP_OPT_BY_INDEX])) && (!$v_extract); $j++) {
+
+              if (($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i<=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) {
+                  $v_extract = true;
+              }
+              if ($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) {
+                  $j_start = $j+1;
+              }
+
+              if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start']>$i) {
+                  break;
+              }
+          }
+      }
+
+      // ----- Look for no rule, which means extract all the archive
+      else {
+          $v_extract = true;
+      }
+
+	  // ----- Check compression method
+	  if (   ($v_extract)
+	      && (   ($v_header['compression'] != 8)
+		      && ($v_header['compression'] != 0))) {
+          $v_header['status'] = 'unsupported_compression';
+
+          // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+          if (   (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+		      && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+              $this->privSwapBackMagicQuotes();
+              
+              PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_COMPRESSION,
+			                       "Filename '".$v_header['stored_filename']."' is "
+				  	    	  	   ."compressed by an unsupported compression "
+				  	    	  	   ."method (".$v_header['compression'].") ");
+
+              return PclZip::errorCode();
+		  }
+	  }
+	  
+	  // ----- Check encrypted files
+	  if (($v_extract) && (($v_header['flag'] & 1) == 1)) {
+          $v_header['status'] = 'unsupported_encryption';
+
+          // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+          if (   (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+		      && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+              $this->privSwapBackMagicQuotes();
+
+              PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION,
+			                       "Unsupported encryption for "
+				  	    	  	   ." filename '".$v_header['stored_filename']
+								   ."'");
+
+              return PclZip::errorCode();
+		  }
+    }
+
+      // ----- Look for real extraction
+      if (($v_extract) && ($v_header['status'] != 'ok')) {
+          $v_result = $this->privConvertHeader2FileInfo($v_header,
+		                                        $p_file_list[$v_nb_extracted++]);
+          if ($v_result != 1) {
+              $this->privCloseFd();
+              $this->privSwapBackMagicQuotes();
+              return $v_result;
+          }
+
+          $v_extract = false;
+      }
+      
+      // ----- Look for real extraction
+      if ($v_extract)
+      {
+
+        // ----- Go to the file position
+        @rewind($this->zip_fd);
+        if (@fseek($this->zip_fd, $v_header['offset']))
+        {
+          // ----- Close the zip file
+          $this->privCloseFd();
+
+          $this->privSwapBackMagicQuotes();
+
+          // ----- Error log
+          PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+          // ----- Return
+          return PclZip::errorCode();
+        }
+
+        // ----- Look for extraction as string
+        if ($p_options[PCLZIP_OPT_EXTRACT_AS_STRING]) {
+
+          $v_string = '';
+
+          // ----- Extracting the file
+          $v_result1 = $this->privExtractFileAsString($v_header, $v_string, $p_options);
+          if ($v_result1 < 1) {
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+            return $v_result1;
+          }
+
+          // ----- Get the only interesting attributes
+          if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted])) != 1)
+          {
+            // ----- Close the zip file
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+
+            return $v_result;
+          }
+
+          // ----- Set the file content
+          $p_file_list[$v_nb_extracted]['content'] = $v_string;
+
+          // ----- Next extracted file
+          $v_nb_extracted++;
+          
+          // ----- Look for user callback abort
+          if ($v_result1 == 2) {
+          	break;
+          }
+        }
+        // ----- Look for extraction in standard output
+        elseif (   (isset($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT]))
+		        && ($p_options[PCLZIP_OPT_EXTRACT_IN_OUTPUT])) {
+          // ----- Extracting the file in standard output
+          $v_result1 = $this->privExtractFileInOutput($v_header, $p_options);
+          if ($v_result1 < 1) {
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+            return $v_result1;
+          }
+
+          // ----- Get the only interesting attributes
+          if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1) {
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+            return $v_result;
+          }
+
+          // ----- Look for user callback abort
+          if ($v_result1 == 2) {
+          	break;
+          }
+        }
+        // ----- Look for normal extraction
+        else {
+          // ----- Extracting the file
+          $v_result1 = $this->privExtractFile($v_header,
+		                                      $p_path, $p_remove_path,
+											  $p_remove_all_path,
+											  $p_options);
+          if ($v_result1 < 1) {
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+            return $v_result1;
+          }
+
+          // ----- Get the only interesting attributes
+          if (($v_result = $this->privConvertHeader2FileInfo($v_header, $p_file_list[$v_nb_extracted++])) != 1)
+          {
+            // ----- Close the zip file
+            $this->privCloseFd();
+            $this->privSwapBackMagicQuotes();
+
+            return $v_result;
+          }
+
+          // ----- Look for user callback abort
+          if ($v_result1 == 2) {
+          	break;
+          }
+        }
+      }
+    }
+
+    // ----- Close the zip file
+    $this->privCloseFd();
+    $this->privSwapBackMagicQuotes();
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privExtractFile()
+  // Description :
+  // Parameters :
+  // Return Values :
+  //
+  // 1 : ... ?
+  // PCLZIP_ERR_USER_ABORTED(2) : User ask for extraction stop in callback
+  // --------------------------------------------------------------------------------
+  function privExtractFile(&$p_entry, $p_path, $p_remove_path, $p_remove_all_path, &$p_options)
+  {
+    $v_result=1;
+
+    // ----- Read the file header
+    if (($v_result = $this->privReadFileHeader($v_header)) != 1)
+    {
+      // ----- Return
+      return $v_result;
+    }
+
+
+    // ----- Check that the file header is coherent with $p_entry info
+    if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) {
+        // TBC
+    }
+
+    // ----- Look for all path to remove
+    if ($p_remove_all_path == true) {
+        // ----- Look for folder entry that not need to be extracted
+        if (($p_entry['external']&0x00000010)==0x00000010) {
+
+            $p_entry['status'] = "filtered";
+
+            return $v_result;
+        }
+
+        // ----- Get the basename of the path
+        $p_entry['filename'] = basename($p_entry['filename']);
+    }
+
+    // ----- Look for path to remove
+    else if ($p_remove_path != "")
+    {
+      if (PclZipUtilPathInclusion($p_remove_path, $p_entry['filename']) == 2)
+      {
+
+        // ----- Change the file status
+        $p_entry['status'] = "filtered";
+
+        // ----- Return
+        return $v_result;
+      }
+
+      $p_remove_path_size = strlen($p_remove_path);
+      if (substr($p_entry['filename'], 0, $p_remove_path_size) == $p_remove_path)
+      {
+
+        // ----- Remove the path
+        $p_entry['filename'] = substr($p_entry['filename'], $p_remove_path_size);
+
+      }
+    }
+
+    // ----- Add the path
+    if ($p_path != '') {
+      $p_entry['filename'] = $p_path."/".$p_entry['filename'];
+    }
+    
+    // ----- Check a base_dir_restriction
+    if (isset($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION])) {
+      $v_inclusion
+      = PclZipUtilPathInclusion($p_options[PCLZIP_OPT_EXTRACT_DIR_RESTRICTION],
+                                $p_entry['filename']); 
+      if ($v_inclusion == 0) {
+
+        PclZip::privErrorLog(PCLZIP_ERR_DIRECTORY_RESTRICTION,
+			                     "Filename '".$p_entry['filename']."' is "
+								 ."outside PCLZIP_OPT_EXTRACT_DIR_RESTRICTION");
+
+        return PclZip::errorCode();
+      }
+    }
+
+    // ----- Look for pre-extract callback
+    if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) {
+
+      // ----- Generate a local information
+      $v_local_header = array();
+      $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+      // ----- Call the callback
+      // Here I do not use call_user_func() because I need to send a reference to the
+      // header.
+//      eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);');
+      $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header);
+      if ($v_result == 0) {
+        // ----- Change the file status
+        $p_entry['status'] = "skipped";
+        $v_result = 1;
+      }
+      
+      // ----- Look for abort result
+      if ($v_result == 2) {
+        // ----- This status is internal and will be changed in 'skipped'
+        $p_entry['status'] = "aborted";
+      	$v_result = PCLZIP_ERR_USER_ABORTED;
+      }
+
+      // ----- Update the informations
+      // Only some fields can be modified
+      $p_entry['filename'] = $v_local_header['filename'];
+    }
+
+
+    // ----- Look if extraction should be done
+    if ($p_entry['status'] == 'ok') {
+
+    // ----- Look for specific actions while the file exist
+    if (file_exists($p_entry['filename']))
+    {
+
+      // ----- Look if file is a directory
+      if (is_dir($p_entry['filename']))
+      {
+
+        // ----- Change the file status
+        $p_entry['status'] = "already_a_directory";
+        
+        // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+        // For historical reason first PclZip implementation does not stop
+        // when this kind of error occurs.
+        if (   (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+		    && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+            PclZip::privErrorLog(PCLZIP_ERR_ALREADY_A_DIRECTORY,
+			                     "Filename '".$p_entry['filename']."' is "
+								 ."already used by an existing directory");
+
+            return PclZip::errorCode();
+		    }
+      }
+      // ----- Look if file is write protected
+      else if (!is_writeable($p_entry['filename']))
+      {
+
+        // ----- Change the file status
+        $p_entry['status'] = "write_protected";
+
+        // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+        // For historical reason first PclZip implementation does not stop
+        // when this kind of error occurs.
+        if (   (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+		    && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+            PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL,
+			                     "Filename '".$p_entry['filename']."' exists "
+								 ."and is write protected");
+
+            return PclZip::errorCode();
+		    }
+      }
+
+      // ----- Look if the extracted file is older
+      else if (filemtime($p_entry['filename']) > $p_entry['mtime'])
+      {
+        // ----- Change the file status
+        if (   (isset($p_options[PCLZIP_OPT_REPLACE_NEWER]))
+		    && ($p_options[PCLZIP_OPT_REPLACE_NEWER]===true)) {
+	  	  }
+		    else {
+            $p_entry['status'] = "newer_exist";
+
+            // ----- Look for PCLZIP_OPT_STOP_ON_ERROR
+            // For historical reason first PclZip implementation does not stop
+            // when this kind of error occurs.
+            if (   (isset($p_options[PCLZIP_OPT_STOP_ON_ERROR]))
+		        && ($p_options[PCLZIP_OPT_STOP_ON_ERROR]===true)) {
+
+                PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL,
+			             "Newer version of '".$p_entry['filename']."' exists "
+					    ."and option PCLZIP_OPT_REPLACE_NEWER is not selected");
+
+                return PclZip::errorCode();
+		      }
+		    }
+      }
+      else {
+      }
+    }
+
+    // ----- Check the directory availability and create it if necessary
+    else {
+      if ((($p_entry['external']&0x00000010)==0x00000010) || (substr($p_entry['filename'], -1) == '/'))
+        $v_dir_to_check = $p_entry['filename'];
+      else if (!strstr($p_entry['filename'], "/"))
+        $v_dir_to_check = "";
+      else
+        $v_dir_to_check = dirname($p_entry['filename']);
+
+        if (($v_result = $this->privDirCheck($v_dir_to_check, (($p_entry['external']&0x00000010)==0x00000010))) != 1) {
+  
+          // ----- Change the file status
+          $p_entry['status'] = "path_creation_fail";
+  
+          // ----- Return
+          //return $v_result;
+          $v_result = 1;
+        }
+      }
+    }
+
+    // ----- Look if extraction should be done
+    if ($p_entry['status'] == 'ok') {
+
+      // ----- Do the extraction (if not a folder)
+      if (!(($p_entry['external']&0x00000010)==0x00000010))
+      {
+        // ----- Look for not compressed file
+        if ($p_entry['compression'] == 0) {
+
+    		  // ----- Opening destination file
+          if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0)
+          {
+
+            // ----- Change the file status
+            $p_entry['status'] = "write_error";
+
+            // ----- Return
+            return $v_result;
+          }
+
+
+          // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+          $v_size = $p_entry['compressed_size'];
+          while ($v_size != 0)
+          {
+            $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+            $v_buffer = @fread($this->zip_fd, $v_read_size);
+            /* Try to speed up the code
+            $v_binary_data = pack('a'.$v_read_size, $v_buffer);
+            @fwrite($v_dest_file, $v_binary_data, $v_read_size);
+            */
+            @fwrite($v_dest_file, $v_buffer, $v_read_size);            
+            $v_size -= $v_read_size;
+          }
+
+          // ----- Closing the destination file
+          fclose($v_dest_file);
+
+          // ----- Change the file mtime
+          touch($p_entry['filename'], $p_entry['mtime']);
+          
+
+        }
+        else {
+          // ----- TBC
+          // Need to be finished
+          if (($p_entry['flag'] & 1) == 1) {
+            PclZip::privErrorLog(PCLZIP_ERR_UNSUPPORTED_ENCRYPTION, 'File \''.$p_entry['filename'].'\' is encrypted. Encrypted files are not supported.');
+            return PclZip::errorCode();
+          }
+
+
+          // ----- Look for using temporary file to unzip
+          if ( (!isset($p_options[PCLZIP_OPT_TEMP_FILE_OFF])) 
+              && (isset($p_options[PCLZIP_OPT_TEMP_FILE_ON])
+                  || (isset($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD])
+                      && ($p_options[PCLZIP_OPT_TEMP_FILE_THRESHOLD] <= $p_entry['size'])) ) ) {
+            $v_result = $this->privExtractFileUsingTempFile($p_entry, $p_options);
+            if ($v_result < PCLZIP_ERR_NO_ERROR) {
+              return $v_result;
+            }
+          }
+          
+          // ----- Look for extract in memory
+          else {
+
+          
+            // ----- Read the compressed file in a buffer (one shot)
+            $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']);
+            
+            // ----- Decompress the file
+            $v_file_content = @gzinflate($v_buffer);
+            unset($v_buffer);
+            if ($v_file_content === FALSE) {
+  
+              // ----- Change the file status
+              // TBC
+              $p_entry['status'] = "error";
+              
+              return $v_result;
+            }
+            
+            // ----- Opening destination file
+            if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
+  
+              // ----- Change the file status
+              $p_entry['status'] = "write_error";
+  
+              return $v_result;
+            }
+  
+            // ----- Write the uncompressed data
+            @fwrite($v_dest_file, $v_file_content, $p_entry['size']);
+            unset($v_file_content);
+  
+            // ----- Closing the destination file
+            @fclose($v_dest_file);
+            
+          }
+
+          // ----- Change the file mtime
+          @touch($p_entry['filename'], $p_entry['mtime']);
+        }
+
+        // ----- Look for chmod option
+        if (isset($p_options[PCLZIP_OPT_SET_CHMOD])) {
+
+          // ----- Change the mode of the file
+          @chmod($p_entry['filename'], $p_options[PCLZIP_OPT_SET_CHMOD]);
+        }
+
+      }
+    }
+
+  	// ----- Change abort status
+  	if ($p_entry['status'] == "aborted") {
+        $p_entry['status'] = "skipped";
+  	}
+	
+    // ----- Look for post-extract callback
+    elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) {
+
+      // ----- Generate a local information
+      $v_local_header = array();
+      $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+      // ----- Call the callback
+      // Here I do not use call_user_func() because I need to send a reference to the
+      // header.
+//      eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);');
+      $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header);
+
+      // ----- Look for abort result
+      if ($v_result == 2) {
+      	$v_result = PCLZIP_ERR_USER_ABORTED;
+      }
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privExtractFileUsingTempFile()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privExtractFileUsingTempFile(&$p_entry, &$p_options)
+  {
+    $v_result=1;
+        
+    // ----- Creates a temporary file
+    $v_gzip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.gz';
+    if (($v_dest_file = @fopen($v_gzip_temp_name, "wb")) == 0) {
+      fclose($v_file);
+      PclZip::privErrorLog(PCLZIP_ERR_WRITE_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary write mode');
+      return PclZip::errorCode();
+    }
+
+
+    // ----- Write gz file format header
+    $v_binary_data = pack('va1a1Va1a1', 0x8b1f, Chr($p_entry['compression']), Chr(0x00), time(), Chr(0x00), Chr(3));
+    @fwrite($v_dest_file, $v_binary_data, 10);
+
+    // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+    $v_size = $p_entry['compressed_size'];
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = @fread($this->zip_fd, $v_read_size);
+      //$v_binary_data = pack('a'.$v_read_size, $v_buffer);
+      @fwrite($v_dest_file, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Write gz file format footer
+    $v_binary_data = pack('VV', $p_entry['crc'], $p_entry['size']);
+    @fwrite($v_dest_file, $v_binary_data, 8);
+
+    // ----- Close the temporary file
+    @fclose($v_dest_file);
+
+    // ----- Opening destination file
+    if (($v_dest_file = @fopen($p_entry['filename'], 'wb')) == 0) {
+      $p_entry['status'] = "write_error";
+      return $v_result;
+    }
+
+    // ----- Open the temporary gz file
+    if (($v_src_file = @gzopen($v_gzip_temp_name, 'rb')) == 0) {
+      @fclose($v_dest_file);
+      $p_entry['status'] = "read_error";
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_gzip_temp_name.'\' in binary read mode');
+      return PclZip::errorCode();
+    }
+
+
+    // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks
+    $v_size = $p_entry['size'];
+    while ($v_size != 0) {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = @gzread($v_src_file, $v_read_size);
+      //$v_binary_data = pack('a'.$v_read_size, $v_buffer);
+      @fwrite($v_dest_file, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+    @fclose($v_dest_file);
+    @gzclose($v_src_file);
+
+    // ----- Delete the temporary file
+    @unlink($v_gzip_temp_name);
+    
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privExtractFileInOutput()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privExtractFileInOutput(&$p_entry, &$p_options)
+  {
+    $v_result=1;
+
+    // ----- Read the file header
+    if (($v_result = $this->privReadFileHeader($v_header)) != 1) {
+      return $v_result;
+    }
+
+
+    // ----- Check that the file header is coherent with $p_entry info
+    if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) {
+        // TBC
+    }
+
+    // ----- Look for pre-extract callback
+    if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) {
+
+      // ----- Generate a local information
+      $v_local_header = array();
+      $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+      // ----- Call the callback
+      // Here I do not use call_user_func() because I need to send a reference to the
+      // header.
+//      eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);');
+      $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header);
+      if ($v_result == 0) {
+        // ----- Change the file status
+        $p_entry['status'] = "skipped";
+        $v_result = 1;
+      }
+
+      // ----- Look for abort result
+      if ($v_result == 2) {
+        // ----- This status is internal and will be changed in 'skipped'
+        $p_entry['status'] = "aborted";
+      	$v_result = PCLZIP_ERR_USER_ABORTED;
+      }
+
+      // ----- Update the informations
+      // Only some fields can be modified
+      $p_entry['filename'] = $v_local_header['filename'];
+    }
+
+    // ----- Trace
+
+    // ----- Look if extraction should be done
+    if ($p_entry['status'] == 'ok') {
+
+      // ----- Do the extraction (if not a folder)
+      if (!(($p_entry['external']&0x00000010)==0x00000010)) {
+        // ----- Look for not compressed file
+        if ($p_entry['compressed_size'] == $p_entry['size']) {
+
+          // ----- Read the file in a buffer (one shot)
+          $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']);
+
+          // ----- Send the file to the output
+          echo $v_buffer;
+          unset($v_buffer);
+        }
+        else {
+
+          // ----- Read the compressed file in a buffer (one shot)
+          $v_buffer = @fread($this->zip_fd, $p_entry['compressed_size']);
+          
+          // ----- Decompress the file
+          $v_file_content = gzinflate($v_buffer);
+          unset($v_buffer);
+
+          // ----- Send the file to the output
+          echo $v_file_content;
+          unset($v_file_content);
+        }
+      }
+    }
+
+	// ----- Change abort status
+	if ($p_entry['status'] == "aborted") {
+      $p_entry['status'] = "skipped";
+	}
+
+    // ----- Look for post-extract callback
+    elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) {
+
+      // ----- Generate a local information
+      $v_local_header = array();
+      $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+      // ----- Call the callback
+      // Here I do not use call_user_func() because I need to send a reference to the
+      // header.
+//      eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);');
+      $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header);
+
+      // ----- Look for abort result
+      if ($v_result == 2) {
+      	$v_result = PCLZIP_ERR_USER_ABORTED;
+      }
+    }
+
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privExtractFileAsString()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privExtractFileAsString(&$p_entry, &$p_string, &$p_options)
+  {
+    $v_result=1;
+
+    // ----- Read the file header
+    $v_header = array();
+    if (($v_result = $this->privReadFileHeader($v_header)) != 1)
+    {
+      // ----- Return
+      return $v_result;
+    }
+
+
+    // ----- Check that the file header is coherent with $p_entry info
+    if ($this->privCheckFileHeaders($v_header, $p_entry) != 1) {
+        // TBC
+    }
+
+    // ----- Look for pre-extract callback
+    if (isset($p_options[PCLZIP_CB_PRE_EXTRACT])) {
+
+      // ----- Generate a local information
+      $v_local_header = array();
+      $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+
+      // ----- Call the callback
+      // Here I do not use call_user_func() because I need to send a reference to the
+      // header.
+//      eval('$v_result = '.$p_options[PCLZIP_CB_PRE_EXTRACT].'(PCLZIP_CB_PRE_EXTRACT, $v_local_header);');
+      $v_result = $p_options[PCLZIP_CB_PRE_EXTRACT](PCLZIP_CB_PRE_EXTRACT, $v_local_header);
+      if ($v_result == 0) {
+        // ----- Change the file status
+        $p_entry['status'] = "skipped";
+        $v_result = 1;
+      }
+      
+      // ----- Look for abort result
+      if ($v_result == 2) {
+        // ----- This status is internal and will be changed in 'skipped'
+        $p_entry['status'] = "aborted";
+      	$v_result = PCLZIP_ERR_USER_ABORTED;
+      }
+
+      // ----- Update the informations
+      // Only some fields can be modified
+      $p_entry['filename'] = $v_local_header['filename'];
+    }
+
+
+    // ----- Look if extraction should be done
+    if ($p_entry['status'] == 'ok') {
+
+      // ----- Do the extraction (if not a folder)
+      if (!(($p_entry['external']&0x00000010)==0x00000010)) {
+        // ----- Look for not compressed file
+  //      if ($p_entry['compressed_size'] == $p_entry['size'])
+        if ($p_entry['compression'] == 0) {
+  
+          // ----- Reading the file
+          $p_string = @fread($this->zip_fd, $p_entry['compressed_size']);
+        }
+        else {
+  
+          // ----- Reading the file
+          $v_data = @fread($this->zip_fd, $p_entry['compressed_size']);
+          
+          // ----- Decompress the file
+          if (($p_string = @gzinflate($v_data)) === FALSE) {
+              // TBC
+          }
+        }
+  
+        // ----- Trace
+      }
+      else {
+          // TBC : error : can not extract a folder in a string
+      }
+      
+    }
+
+  	// ----- Change abort status
+  	if ($p_entry['status'] == "aborted") {
+        $p_entry['status'] = "skipped";
+  	}
+	
+    // ----- Look for post-extract callback
+    elseif (isset($p_options[PCLZIP_CB_POST_EXTRACT])) {
+
+      // ----- Generate a local information
+      $v_local_header = array();
+      $this->privConvertHeader2FileInfo($p_entry, $v_local_header);
+      
+      // ----- Swap the content to header
+      $v_local_header['content'] = $p_string;
+      $p_string = '';
+
+      // ----- Call the callback
+      // Here I do not use call_user_func() because I need to send a reference to the
+      // header.
+//      eval('$v_result = '.$p_options[PCLZIP_CB_POST_EXTRACT].'(PCLZIP_CB_POST_EXTRACT, $v_local_header);');
+      $v_result = $p_options[PCLZIP_CB_POST_EXTRACT](PCLZIP_CB_POST_EXTRACT, $v_local_header);
+
+      // ----- Swap back the content to header
+      $p_string = $v_local_header['content'];
+      unset($v_local_header['content']);
+
+      // ----- Look for abort result
+      if ($v_result == 2) {
+      	$v_result = PCLZIP_ERR_USER_ABORTED;
+      }
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privReadFileHeader()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privReadFileHeader(&$p_header)
+  {
+    $v_result=1;
+
+    // ----- Read the 4 bytes signature
+    $v_binary_data = @fread($this->zip_fd, 4);
+    $v_data = unpack('Vid', $v_binary_data);
+
+    // ----- Check signature
+    if ($v_data['id'] != 0x04034b50)
+    {
+
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Read the first 42 bytes of the header
+    $v_binary_data = fread($this->zip_fd, 26);
+
+    // ----- Look for invalid block size
+    if (strlen($v_binary_data) != 26)
+    {
+      $p_header['filename'] = "";
+      $p_header['status'] = "invalid_header";
+
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data));
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Extract the values
+    $v_data = unpack('vversion/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len', $v_binary_data);
+
+    // ----- Get filename
+    $p_header['filename'] = fread($this->zip_fd, $v_data['filename_len']);
+
+    // ----- Get extra_fields
+    if ($v_data['extra_len'] != 0) {
+      $p_header['extra'] = fread($this->zip_fd, $v_data['extra_len']);
+    }
+    else {
+      $p_header['extra'] = '';
+    }
+
+    // ----- Extract properties
+    $p_header['version_extracted'] = $v_data['version'];
+    $p_header['compression'] = $v_data['compression'];
+    $p_header['size'] = $v_data['size'];
+    $p_header['compressed_size'] = $v_data['compressed_size'];
+    $p_header['crc'] = $v_data['crc'];
+    $p_header['flag'] = $v_data['flag'];
+    $p_header['filename_len'] = $v_data['filename_len'];
+
+    // ----- Recuperate date in UNIX format
+    $p_header['mdate'] = $v_data['mdate'];
+    $p_header['mtime'] = $v_data['mtime'];
+    if ($p_header['mdate'] && $p_header['mtime'])
+    {
+      // ----- Extract time
+      $v_hour = ($p_header['mtime'] & 0xF800) >> 11;
+      $v_minute = ($p_header['mtime'] & 0x07E0) >> 5;
+      $v_seconde = ($p_header['mtime'] & 0x001F)*2;
+
+      // ----- Extract date
+      $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980;
+      $v_month = ($p_header['mdate'] & 0x01E0) >> 5;
+      $v_day = $p_header['mdate'] & 0x001F;
+
+      // ----- Get UNIX date format
+      $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
+
+    }
+    else
+    {
+      $p_header['mtime'] = time();
+    }
+
+    // TBC
+    //for(reset($v_data); $key = key($v_data); next($v_data)) {
+    //}
+
+    // ----- Set the stored filename
+    $p_header['stored_filename'] = $p_header['filename'];
+
+    // ----- Set the status field
+    $p_header['status'] = "ok";
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privReadCentralFileHeader()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privReadCentralFileHeader(&$p_header)
+  {
+    $v_result=1;
+
+    // ----- Read the 4 bytes signature
+    $v_binary_data = @fread($this->zip_fd, 4);
+    $v_data = unpack('Vid', $v_binary_data);
+
+    // ----- Check signature
+    if ($v_data['id'] != 0x02014b50)
+    {
+
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Invalid archive structure');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Read the first 42 bytes of the header
+    $v_binary_data = fread($this->zip_fd, 42);
+
+    // ----- Look for invalid block size
+    if (strlen($v_binary_data) != 42)
+    {
+      $p_header['filename'] = "";
+      $p_header['status'] = "invalid_header";
+
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid block size : ".strlen($v_binary_data));
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Extract the values
+    $p_header = unpack('vversion/vversion_extracted/vflag/vcompression/vmtime/vmdate/Vcrc/Vcompressed_size/Vsize/vfilename_len/vextra_len/vcomment_len/vdisk/vinternal/Vexternal/Voffset', $v_binary_data);
+
+    // ----- Get filename
+    if ($p_header['filename_len'] != 0)
+      $p_header['filename'] = fread($this->zip_fd, $p_header['filename_len']);
+    else
+      $p_header['filename'] = '';
+
+    // ----- Get extra
+    if ($p_header['extra_len'] != 0)
+      $p_header['extra'] = fread($this->zip_fd, $p_header['extra_len']);
+    else
+      $p_header['extra'] = '';
+
+    // ----- Get comment
+    if ($p_header['comment_len'] != 0)
+      $p_header['comment'] = fread($this->zip_fd, $p_header['comment_len']);
+    else
+      $p_header['comment'] = '';
+
+    // ----- Extract properties
+
+    // ----- Recuperate date in UNIX format
+    //if ($p_header['mdate'] && $p_header['mtime'])
+    // TBC : bug : this was ignoring time with 0/0/0
+    if (1)
+    {
+      // ----- Extract time
+      $v_hour = ($p_header['mtime'] & 0xF800) >> 11;
+      $v_minute = ($p_header['mtime'] & 0x07E0) >> 5;
+      $v_seconde = ($p_header['mtime'] & 0x001F)*2;
+
+      // ----- Extract date
+      $v_year = (($p_header['mdate'] & 0xFE00) >> 9) + 1980;
+      $v_month = ($p_header['mdate'] & 0x01E0) >> 5;
+      $v_day = $p_header['mdate'] & 0x001F;
+
+      // ----- Get UNIX date format
+      $p_header['mtime'] = @mktime($v_hour, $v_minute, $v_seconde, $v_month, $v_day, $v_year);
+
+    }
+    else
+    {
+      $p_header['mtime'] = time();
+    }
+
+    // ----- Set the stored filename
+    $p_header['stored_filename'] = $p_header['filename'];
+
+    // ----- Set default status to ok
+    $p_header['status'] = 'ok';
+
+    // ----- Look if it is a directory
+    if (substr($p_header['filename'], -1) == '/') {
+      //$p_header['external'] = 0x41FF0010;
+      $p_header['external'] = 0x00000010;
+    }
+
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privCheckFileHeaders()
+  // Description :
+  // Parameters :
+  // Return Values :
+  //   1 on success,
+  //   0 on error;
+  // --------------------------------------------------------------------------------
+  function privCheckFileHeaders(&$p_local_header, &$p_central_header)
+  {
+    $v_result=1;
+
+  	// ----- Check the static values
+  	// TBC
+  	if ($p_local_header['filename'] != $p_central_header['filename']) {
+  	}
+  	if ($p_local_header['version_extracted'] != $p_central_header['version_extracted']) {
+  	}
+  	if ($p_local_header['flag'] != $p_central_header['flag']) {
+  	}
+  	if ($p_local_header['compression'] != $p_central_header['compression']) {
+  	}
+  	if ($p_local_header['mtime'] != $p_central_header['mtime']) {
+  	}
+  	if ($p_local_header['filename_len'] != $p_central_header['filename_len']) {
+  	}
+  
+  	// ----- Look for flag bit 3
+  	if (($p_local_header['flag'] & 8) == 8) {
+          $p_local_header['size'] = $p_central_header['size'];
+          $p_local_header['compressed_size'] = $p_central_header['compressed_size'];
+          $p_local_header['crc'] = $p_central_header['crc'];
+  	}
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privReadEndCentralDir()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privReadEndCentralDir(&$p_central_dir)
+  {
+    $v_result=1;
+
+    // ----- Go to the end of the zip file
+    $v_size = filesize($this->zipname);
+    @fseek($this->zip_fd, $v_size);
+    if (@ftell($this->zip_fd) != $v_size)
+    {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to go to the end of the archive \''.$this->zipname.'\'');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- First try : look if this is an archive with no commentaries (most of the time)
+    // in this case the end of central dir is at 22 bytes of the file end
+    $v_found = 0;
+    if ($v_size > 26) {
+      @fseek($this->zip_fd, $v_size-22);
+      if (($v_pos = @ftell($this->zip_fd)) != ($v_size-22))
+      {
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \''.$this->zipname.'\'');
+
+        // ----- Return
+        return PclZip::errorCode();
+      }
+
+      // ----- Read for bytes
+      $v_binary_data = @fread($this->zip_fd, 4);
+      $v_data = @unpack('Vid', $v_binary_data);
+
+      // ----- Check signature
+      if ($v_data['id'] == 0x06054b50) {
+        $v_found = 1;
+      }
+
+      $v_pos = ftell($this->zip_fd);
+    }
+
+    // ----- Go back to the maximum possible size of the Central Dir End Record
+    if (!$v_found) {
+      $v_maximum_size = 65557; // 0xFFFF + 22;
+      if ($v_maximum_size > $v_size)
+        $v_maximum_size = $v_size;
+      @fseek($this->zip_fd, $v_size-$v_maximum_size);
+      if (@ftell($this->zip_fd) != ($v_size-$v_maximum_size))
+      {
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, 'Unable to seek back to the middle of the archive \''.$this->zipname.'\'');
+
+        // ----- Return
+        return PclZip::errorCode();
+      }
+
+      // ----- Read byte per byte in order to find the signature
+      $v_pos = ftell($this->zip_fd);
+      $v_bytes = 0x00000000;
+      while ($v_pos < $v_size)
+      {
+        // ----- Read a byte
+        $v_byte = @fread($this->zip_fd, 1);
+
+        // -----  Add the byte
+        //$v_bytes = ($v_bytes << 8) | Ord($v_byte);
+        // Note we mask the old value down such that once shifted we can never end up with more than a 32bit number 
+        // Otherwise on systems where we have 64bit integers the check below for the magic number will fail. 
+        $v_bytes = ( ($v_bytes & 0xFFFFFF) << 8) | Ord($v_byte); 
+
+        // ----- Compare the bytes
+        if ($v_bytes == 0x504b0506)
+        {
+          $v_pos++;
+          break;
+        }
+
+        $v_pos++;
+      }
+
+      // ----- Look if not found end of central dir
+      if ($v_pos == $v_size)
+      {
+
+        // ----- Error log
+        PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Unable to find End of Central Dir Record signature");
+
+        // ----- Return
+        return PclZip::errorCode();
+      }
+    }
+
+    // ----- Read the first 18 bytes of the header
+    $v_binary_data = fread($this->zip_fd, 18);
+
+    // ----- Look for invalid block size
+    if (strlen($v_binary_data) != 18)
+    {
+
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT, "Invalid End of Central Dir Record size : ".strlen($v_binary_data));
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Extract the values
+    $v_data = unpack('vdisk/vdisk_start/vdisk_entries/ventries/Vsize/Voffset/vcomment_size', $v_binary_data);
+
+    // ----- Check the global size
+    if (($v_pos + $v_data['comment_size'] + 18) != $v_size) {
+
+	  // ----- Removed in release 2.2 see readme file
+	  // The check of the file size is a little too strict.
+	  // Some bugs where found when a zip is encrypted/decrypted with 'crypt'.
+	  // While decrypted, zip has training 0 bytes
+	  if (0) {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_BAD_FORMAT,
+	                       'The central dir is not at the end of the archive.'
+						   .' Some trailing bytes exists after the archive.');
+
+      // ----- Return
+      return PclZip::errorCode();
+	  }
+    }
+
+    // ----- Get comment
+    if ($v_data['comment_size'] != 0) {
+      $p_central_dir['comment'] = fread($this->zip_fd, $v_data['comment_size']);
+    }
+    else
+      $p_central_dir['comment'] = '';
+
+    $p_central_dir['entries'] = $v_data['entries'];
+    $p_central_dir['disk_entries'] = $v_data['disk_entries'];
+    $p_central_dir['offset'] = $v_data['offset'];
+    $p_central_dir['size'] = $v_data['size'];
+    $p_central_dir['disk'] = $v_data['disk'];
+    $p_central_dir['disk_start'] = $v_data['disk_start'];
+
+    // TBC
+    //for(reset($p_central_dir); $key = key($p_central_dir); next($p_central_dir)) {
+    //}
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privDeleteByRule()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privDeleteByRule(&$p_result_list, &$p_options)
+  {
+    $v_result=1;
+    $v_list_detail = array();
+
+    // ----- Open the zip file
+    if (($v_result=$this->privOpenFd('rb')) != 1)
+    {
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Read the central directory informations
+    $v_central_dir = array();
+    if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+    {
+      $this->privCloseFd();
+      return $v_result;
+    }
+
+    // ----- Go to beginning of File
+    @rewind($this->zip_fd);
+
+    // ----- Scan all the files
+    // ----- Start at beginning of Central Dir
+    $v_pos_entry = $v_central_dir['offset'];
+    @rewind($this->zip_fd);
+    if (@fseek($this->zip_fd, $v_pos_entry))
+    {
+      // ----- Close the zip file
+      $this->privCloseFd();
+
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Read each entry
+    $v_header_list = array();
+    $j_start = 0;
+    for ($i=0, $v_nb_extracted=0; $i<$v_central_dir['entries']; $i++)
+    {
+
+      // ----- Read the file header
+      $v_header_list[$v_nb_extracted] = array();
+      if (($v_result = $this->privReadCentralFileHeader($v_header_list[$v_nb_extracted])) != 1)
+      {
+        // ----- Close the zip file
+        $this->privCloseFd();
+
+        return $v_result;
+      }
+
+
+      // ----- Store the index
+      $v_header_list[$v_nb_extracted]['index'] = $i;
+
+      // ----- Look for the specific extract rules
+      $v_found = false;
+
+      // ----- Look for extract by name rule
+      if (   (isset($p_options[PCLZIP_OPT_BY_NAME]))
+          && ($p_options[PCLZIP_OPT_BY_NAME] != 0)) {
+
+          // ----- Look if the filename is in the list
+          for ($j=0; ($j<sizeof($p_options[PCLZIP_OPT_BY_NAME])) && (!$v_found); $j++) {
+
+              // ----- Look for a directory
+              if (substr($p_options[PCLZIP_OPT_BY_NAME][$j], -1) == "/") {
+
+                  // ----- Look if the directory is in the filename path
+                  if (   (strlen($v_header_list[$v_nb_extracted]['stored_filename']) > strlen($p_options[PCLZIP_OPT_BY_NAME][$j]))
+                      && (substr($v_header_list[$v_nb_extracted]['stored_filename'], 0, strlen($p_options[PCLZIP_OPT_BY_NAME][$j])) == $p_options[PCLZIP_OPT_BY_NAME][$j])) {
+                      $v_found = true;
+                  }
+                  elseif (   (($v_header_list[$v_nb_extracted]['external']&0x00000010)==0x00000010) /* Indicates a folder */
+                          && ($v_header_list[$v_nb_extracted]['stored_filename'].'/' == $p_options[PCLZIP_OPT_BY_NAME][$j])) {
+                      $v_found = true;
+                  }
+              }
+              // ----- Look for a filename
+              elseif ($v_header_list[$v_nb_extracted]['stored_filename'] == $p_options[PCLZIP_OPT_BY_NAME][$j]) {
+                  $v_found = true;
+              }
+          }
+      }
+
+      // ----- Look for extract by ereg rule
+      // ereg() is deprecated with PHP 5.3
+      /*
+      else if (   (isset($p_options[PCLZIP_OPT_BY_EREG]))
+               && ($p_options[PCLZIP_OPT_BY_EREG] != "")) {
+
+          if (ereg($p_options[PCLZIP_OPT_BY_EREG], $v_header_list[$v_nb_extracted]['stored_filename'])) {
+              $v_found = true;
+          }
+      }
+      */
+
+      // ----- Look for extract by preg rule
+      else if (   (isset($p_options[PCLZIP_OPT_BY_PREG]))
+               && ($p_options[PCLZIP_OPT_BY_PREG] != "")) {
+
+          if (preg_match($p_options[PCLZIP_OPT_BY_PREG], $v_header_list[$v_nb_extracted]['stored_filename'])) {
+              $v_found = true;
+          }
+      }
+
+      // ----- Look for extract by index rule
+      else if (   (isset($p_options[PCLZIP_OPT_BY_INDEX]))
+               && ($p_options[PCLZIP_OPT_BY_INDEX] != 0)) {
+
+          // ----- Look if the index is in the list
+          for ($j=$j_start; ($j<sizeof($p_options[PCLZIP_OPT_BY_INDEX])) && (!$v_found); $j++) {
+
+              if (($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['start']) && ($i<=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end'])) {
+                  $v_found = true;
+              }
+              if ($i>=$p_options[PCLZIP_OPT_BY_INDEX][$j]['end']) {
+                  $j_start = $j+1;
+              }
+
+              if ($p_options[PCLZIP_OPT_BY_INDEX][$j]['start']>$i) {
+                  break;
+              }
+          }
+      }
+      else {
+      	$v_found = true;
+      }
+
+      // ----- Look for deletion
+      if ($v_found)
+      {
+        unset($v_header_list[$v_nb_extracted]);
+      }
+      else
+      {
+        $v_nb_extracted++;
+      }
+    }
+
+    // ----- Look if something need to be deleted
+    if ($v_nb_extracted > 0) {
+
+        // ----- Creates a temporay file
+        $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp';
+
+        // ----- Creates a temporary zip archive
+        $v_temp_zip = new PclZip($v_zip_temp_name);
+
+        // ----- Open the temporary zip file in write mode
+        if (($v_result = $v_temp_zip->privOpenFd('wb')) != 1) {
+            $this->privCloseFd();
+
+            // ----- Return
+            return $v_result;
+        }
+
+        // ----- Look which file need to be kept
+        for ($i=0; $i<sizeof($v_header_list); $i++) {
+
+            // ----- Calculate the position of the header
+            @rewind($this->zip_fd);
+            if (@fseek($this->zip_fd,  $v_header_list[$i]['offset'])) {
+                // ----- Close the zip file
+                $this->privCloseFd();
+                $v_temp_zip->privCloseFd();
+                @unlink($v_zip_temp_name);
+
+                // ----- Error log
+                PclZip::privErrorLog(PCLZIP_ERR_INVALID_ARCHIVE_ZIP, 'Invalid archive size');
+
+                // ----- Return
+                return PclZip::errorCode();
+            }
+
+            // ----- Read the file header
+            $v_local_header = array();
+            if (($v_result = $this->privReadFileHeader($v_local_header)) != 1) {
+                // ----- Close the zip file
+                $this->privCloseFd();
+                $v_temp_zip->privCloseFd();
+                @unlink($v_zip_temp_name);
+
+                // ----- Return
+                return $v_result;
+            }
+            
+            // ----- Check that local file header is same as central file header
+            if ($this->privCheckFileHeaders($v_local_header,
+			                                $v_header_list[$i]) != 1) {
+                // TBC
+            }
+            unset($v_local_header);
+
+            // ----- Write the file header
+            if (($v_result = $v_temp_zip->privWriteFileHeader($v_header_list[$i])) != 1) {
+                // ----- Close the zip file
+                $this->privCloseFd();
+                $v_temp_zip->privCloseFd();
+                @unlink($v_zip_temp_name);
+
+                // ----- Return
+                return $v_result;
+            }
+
+            // ----- Read/write the data block
+            if (($v_result = PclZipUtilCopyBlock($this->zip_fd, $v_temp_zip->zip_fd, $v_header_list[$i]['compressed_size'])) != 1) {
+                // ----- Close the zip file
+                $this->privCloseFd();
+                $v_temp_zip->privCloseFd();
+                @unlink($v_zip_temp_name);
+
+                // ----- Return
+                return $v_result;
+            }
+        }
+
+        // ----- Store the offset of the central dir
+        $v_offset = @ftell($v_temp_zip->zip_fd);
+
+        // ----- Re-Create the Central Dir files header
+        for ($i=0; $i<sizeof($v_header_list); $i++) {
+            // ----- Create the file header
+            if (($v_result = $v_temp_zip->privWriteCentralFileHeader($v_header_list[$i])) != 1) {
+                $v_temp_zip->privCloseFd();
+                $this->privCloseFd();
+                @unlink($v_zip_temp_name);
+
+                // ----- Return
+                return $v_result;
+            }
+
+            // ----- Transform the header to a 'usable' info
+            $v_temp_zip->privConvertHeader2FileInfo($v_header_list[$i], $p_result_list[$i]);
+        }
+
+
+        // ----- Zip file comment
+        $v_comment = '';
+        if (isset($p_options[PCLZIP_OPT_COMMENT])) {
+          $v_comment = $p_options[PCLZIP_OPT_COMMENT];
+        }
+
+        // ----- Calculate the size of the central header
+        $v_size = @ftell($v_temp_zip->zip_fd)-$v_offset;
+
+        // ----- Create the central dir footer
+        if (($v_result = $v_temp_zip->privWriteCentralHeader(sizeof($v_header_list), $v_size, $v_offset, $v_comment)) != 1) {
+            // ----- Reset the file list
+            unset($v_header_list);
+            $v_temp_zip->privCloseFd();
+            $this->privCloseFd();
+            @unlink($v_zip_temp_name);
+
+            // ----- Return
+            return $v_result;
+        }
+
+        // ----- Close
+        $v_temp_zip->privCloseFd();
+        $this->privCloseFd();
+
+        // ----- Delete the zip file
+        // TBC : I should test the result ...
+        @unlink($this->zipname);
+
+        // ----- Rename the temporary file
+        // TBC : I should test the result ...
+        //@rename($v_zip_temp_name, $this->zipname);
+        PclZipUtilRename($v_zip_temp_name, $this->zipname);
+    
+        // ----- Destroy the temporary archive
+        unset($v_temp_zip);
+    }
+    
+    // ----- Remove every files : reset the file
+    else if ($v_central_dir['entries'] != 0) {
+        $this->privCloseFd();
+
+        if (($v_result = $this->privOpenFd('wb')) != 1) {
+          return $v_result;
+        }
+
+        if (($v_result = $this->privWriteCentralHeader(0, 0, 0, '')) != 1) {
+          return $v_result;
+        }
+
+        $this->privCloseFd();
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privDirCheck()
+  // Description :
+  //   Check if a directory exists, if not it creates it and all the parents directory
+  //   which may be useful.
+  // Parameters :
+  //   $p_dir : Directory path to check.
+  // Return Values :
+  //    1 : OK
+  //   -1 : Unable to create directory
+  // --------------------------------------------------------------------------------
+  function privDirCheck($p_dir, $p_is_dir=false)
+  {
+    $v_result = 1;
+
+
+    // ----- Remove the final '/'
+    if (($p_is_dir) && (substr($p_dir, -1)=='/'))
+    {
+      $p_dir = substr($p_dir, 0, strlen($p_dir)-1);
+    }
+
+    // ----- Check the directory availability
+    if ((is_dir($p_dir)) || ($p_dir == ""))
+    {
+      return 1;
+    }
+
+    // ----- Extract parent directory
+    $p_parent_dir = dirname($p_dir);
+
+    // ----- Just a check
+    if ($p_parent_dir != $p_dir)
+    {
+      // ----- Look for parent directory
+      if ($p_parent_dir != "")
+      {
+        if (($v_result = $this->privDirCheck($p_parent_dir)) != 1)
+        {
+          return $v_result;
+        }
+      }
+    }
+
+    // ----- Create the directory
+    if (!@mkdir($p_dir, 0777))
+    {
+      // ----- Error log
+      PclZip::privErrorLog(PCLZIP_ERR_DIR_CREATE_FAIL, "Unable to create directory '$p_dir'");
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privMerge()
+  // Description :
+  //   If $p_archive_to_add does not exist, the function exit with a success result.
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privMerge(&$p_archive_to_add)
+  {
+    $v_result=1;
+
+    // ----- Look if the archive_to_add exists
+    if (!is_file($p_archive_to_add->zipname))
+    {
+
+      // ----- Nothing to merge, so merge is a success
+      $v_result = 1;
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Look if the archive exists
+    if (!is_file($this->zipname))
+    {
+
+      // ----- Do a duplicate
+      $v_result = $this->privDuplicate($p_archive_to_add->zipname);
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Open the zip file
+    if (($v_result=$this->privOpenFd('rb')) != 1)
+    {
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Read the central directory informations
+    $v_central_dir = array();
+    if (($v_result = $this->privReadEndCentralDir($v_central_dir)) != 1)
+    {
+      $this->privCloseFd();
+      return $v_result;
+    }
+
+    // ----- Go to beginning of File
+    @rewind($this->zip_fd);
+
+    // ----- Open the archive_to_add file
+    if (($v_result=$p_archive_to_add->privOpenFd('rb')) != 1)
+    {
+      $this->privCloseFd();
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Read the central directory informations
+    $v_central_dir_to_add = array();
+    if (($v_result = $p_archive_to_add->privReadEndCentralDir($v_central_dir_to_add)) != 1)
+    {
+      $this->privCloseFd();
+      $p_archive_to_add->privCloseFd();
+
+      return $v_result;
+    }
+
+    // ----- Go to beginning of File
+    @rewind($p_archive_to_add->zip_fd);
+
+    // ----- Creates a temporay file
+    $v_zip_temp_name = PCLZIP_TEMPORARY_DIR.uniqid('pclzip-').'.tmp';
+
+    // ----- Open the temporary file in write mode
+    if (($v_zip_temp_fd = @fopen($v_zip_temp_name, 'wb')) == 0)
+    {
+      $this->privCloseFd();
+      $p_archive_to_add->privCloseFd();
+
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open temporary file \''.$v_zip_temp_name.'\' in binary write mode');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Copy the files from the archive to the temporary file
+    // TBC : Here I should better append the file and go back to erase the central dir
+    $v_size = $v_central_dir['offset'];
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = fread($this->zip_fd, $v_read_size);
+      @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Copy the files from the archive_to_add into the temporary file
+    $v_size = $v_central_dir_to_add['offset'];
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = fread($p_archive_to_add->zip_fd, $v_read_size);
+      @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Store the offset of the central dir
+    $v_offset = @ftell($v_zip_temp_fd);
+
+    // ----- Copy the block of file headers from the old archive
+    $v_size = $v_central_dir['size'];
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = @fread($this->zip_fd, $v_read_size);
+      @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Copy the block of file headers from the archive_to_add
+    $v_size = $v_central_dir_to_add['size'];
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = @fread($p_archive_to_add->zip_fd, $v_read_size);
+      @fwrite($v_zip_temp_fd, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Merge the file comments
+    $v_comment = $v_central_dir['comment'].' '.$v_central_dir_to_add['comment'];
+
+    // ----- Calculate the size of the (new) central header
+    $v_size = @ftell($v_zip_temp_fd)-$v_offset;
+
+    // ----- Swap the file descriptor
+    // Here is a trick : I swap the temporary fd with the zip fd, in order to use
+    // the following methods on the temporary fil and not the real archive fd
+    $v_swap = $this->zip_fd;
+    $this->zip_fd = $v_zip_temp_fd;
+    $v_zip_temp_fd = $v_swap;
+
+    // ----- Create the central dir footer
+    if (($v_result = $this->privWriteCentralHeader($v_central_dir['entries']+$v_central_dir_to_add['entries'], $v_size, $v_offset, $v_comment)) != 1)
+    {
+      $this->privCloseFd();
+      $p_archive_to_add->privCloseFd();
+      @fclose($v_zip_temp_fd);
+      $this->zip_fd = null;
+
+      // ----- Reset the file list
+      unset($v_header_list);
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Swap back the file descriptor
+    $v_swap = $this->zip_fd;
+    $this->zip_fd = $v_zip_temp_fd;
+    $v_zip_temp_fd = $v_swap;
+
+    // ----- Close
+    $this->privCloseFd();
+    $p_archive_to_add->privCloseFd();
+
+    // ----- Close the temporary file
+    @fclose($v_zip_temp_fd);
+
+    // ----- Delete the zip file
+    // TBC : I should test the result ...
+    @unlink($this->zipname);
+
+    // ----- Rename the temporary file
+    // TBC : I should test the result ...
+    //@rename($v_zip_temp_name, $this->zipname);
+    PclZipUtilRename($v_zip_temp_name, $this->zipname);
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privDuplicate()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privDuplicate($p_archive_filename)
+  {
+    $v_result=1;
+
+    // ----- Look if the $p_archive_filename exists
+    if (!is_file($p_archive_filename))
+    {
+
+      // ----- Nothing to duplicate, so duplicate is a success.
+      $v_result = 1;
+
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Open the zip file
+    if (($v_result=$this->privOpenFd('wb')) != 1)
+    {
+      // ----- Return
+      return $v_result;
+    }
+
+    // ----- Open the temporary file in write mode
+    if (($v_zip_temp_fd = @fopen($p_archive_filename, 'rb')) == 0)
+    {
+      $this->privCloseFd();
+
+      PclZip::privErrorLog(PCLZIP_ERR_READ_OPEN_FAIL, 'Unable to open archive file \''.$p_archive_filename.'\' in binary write mode');
+
+      // ----- Return
+      return PclZip::errorCode();
+    }
+
+    // ----- Copy the files from the archive to the temporary file
+    // TBC : Here I should better append the file and go back to erase the central dir
+    $v_size = filesize($p_archive_filename);
+    while ($v_size != 0)
+    {
+      $v_read_size = ($v_size < PCLZIP_READ_BLOCK_SIZE ? $v_size : PCLZIP_READ_BLOCK_SIZE);
+      $v_buffer = fread($v_zip_temp_fd, $v_read_size);
+      @fwrite($this->zip_fd, $v_buffer, $v_read_size);
+      $v_size -= $v_read_size;
+    }
+
+    // ----- Close
+    $this->privCloseFd();
+
+    // ----- Close the temporary file
+    @fclose($v_zip_temp_fd);
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privErrorLog()
+  // Description :
+  // Parameters :
+  // --------------------------------------------------------------------------------
+  function privErrorLog($p_error_code=0, $p_error_string='')
+  {
+    if (PCLZIP_ERROR_EXTERNAL == 1) {
+      PclError($p_error_code, $p_error_string);
+    }
+    else {
+      $this->error_code = $p_error_code;
+      $this->error_string = $p_error_string;
+    }
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privErrorReset()
+  // Description :
+  // Parameters :
+  // --------------------------------------------------------------------------------
+  function privErrorReset()
+  {
+    if (PCLZIP_ERROR_EXTERNAL == 1) {
+      PclErrorReset();
+    }
+    else {
+      $this->error_code = 0;
+      $this->error_string = '';
+    }
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privDisableMagicQuotes()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privDisableMagicQuotes()
+  {
+    $v_result=1;
+
+    // ----- Look if function exists
+    if (   (!function_exists("get_magic_quotes_runtime"))
+	    || (!function_exists("set_magic_quotes_runtime"))) {
+      return $v_result;
+	}
+
+    // ----- Look if already done
+    if ($this->magic_quotes_status != -1) {
+      return $v_result;
+	}
+
+	// ----- Get and memorize the magic_quote value
+	$this->magic_quotes_status = @get_magic_quotes_runtime();
+
+	// ----- Disable magic_quotes
+	if ($this->magic_quotes_status == 1) {
+	  @set_magic_quotes_runtime(0);
+	}
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : privSwapBackMagicQuotes()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function privSwapBackMagicQuotes()
+  {
+    $v_result=1;
+
+    // ----- Look if function exists
+    if (   (!function_exists("get_magic_quotes_runtime"))
+	    || (!function_exists("set_magic_quotes_runtime"))) {
+      return $v_result;
+	}
+
+    // ----- Look if something to do
+    if ($this->magic_quotes_status != -1) {
+      return $v_result;
+	}
+
+	// ----- Swap back magic_quotes
+	if ($this->magic_quotes_status == 1) {
+  	  @set_magic_quotes_runtime($this->magic_quotes_status);
+	}
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  }
+  // End of class
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : PclZipUtilPathReduction()
+  // Description :
+  // Parameters :
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function PclZipUtilPathReduction($p_dir)
+  {
+    $v_result = "";
+
+    // ----- Look for not empty path
+    if ($p_dir != "") {
+      // ----- Explode path by directory names
+      $v_list = explode("/", $p_dir);
+
+      // ----- Study directories from last to first
+      $v_skip = 0;
+      for ($i=sizeof($v_list)-1; $i>=0; $i--) {
+        // ----- Look for current path
+        if ($v_list[$i] == ".") {
+          // ----- Ignore this directory
+          // Should be the first $i=0, but no check is done
+        }
+        else if ($v_list[$i] == "..") {
+		  $v_skip++;
+        }
+        else if ($v_list[$i] == "") {
+		  // ----- First '/' i.e. root slash
+		  if ($i == 0) {
+            $v_result = "/".$v_result;
+		    if ($v_skip > 0) {
+		        // ----- It is an invalid path, so the path is not modified
+		        // TBC
+		        $v_result = $p_dir;
+                $v_skip = 0;
+		    }
+		  }
+		  // ----- Last '/' i.e. indicates a directory
+		  else if ($i == (sizeof($v_list)-1)) {
+            $v_result = $v_list[$i];
+		  }
+		  // ----- Double '/' inside the path
+		  else {
+            // ----- Ignore only the double '//' in path,
+            // but not the first and last '/'
+		  }
+        }
+        else {
+		  // ----- Look for item to skip
+		  if ($v_skip > 0) {
+		    $v_skip--;
+		  }
+		  else {
+            $v_result = $v_list[$i].($i!=(sizeof($v_list)-1)?"/".$v_result:"");
+		  }
+        }
+      }
+      
+      // ----- Look for skip
+      if ($v_skip > 0) {
+        while ($v_skip > 0) {
+            $v_result = '../'.$v_result;
+            $v_skip--;
+        }
+      }
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : PclZipUtilPathInclusion()
+  // Description :
+  //   This function indicates if the path $p_path is under the $p_dir tree. Or,
+  //   said in an other way, if the file or sub-dir $p_path is inside the dir
+  //   $p_dir.
+  //   The function indicates also if the path is exactly the same as the dir.
+  //   This function supports path with duplicated '/' like '//', but does not
+  //   support '.' or '..' statements.
+  // Parameters :
+  // Return Values :
+  //   0 if $p_path is not inside directory $p_dir
+  //   1 if $p_path is inside directory $p_dir
+  //   2 if $p_path is exactly the same as $p_dir
+  // --------------------------------------------------------------------------------
+  function PclZipUtilPathInclusion($p_dir, $p_path)
+  {
+    $v_result = 1;
+    
+    // ----- Look for path beginning by ./
+    if (   ($p_dir == '.')
+        || ((strlen($p_dir) >=2) && (substr($p_dir, 0, 2) == './'))) {
+      $p_dir = PclZipUtilTranslateWinPath(getcwd(), FALSE).'/'.substr($p_dir, 1);
+    }
+    if (   ($p_path == '.')
+        || ((strlen($p_path) >=2) && (substr($p_path, 0, 2) == './'))) {
+      $p_path = PclZipUtilTranslateWinPath(getcwd(), FALSE).'/'.substr($p_path, 1);
+    }
+
+    // ----- Explode dir and path by directory separator
+    $v_list_dir = explode("/", $p_dir);
+    $v_list_dir_size = sizeof($v_list_dir);
+    $v_list_path = explode("/", $p_path);
+    $v_list_path_size = sizeof($v_list_path);
+
+    // ----- Study directories paths
+    $i = 0;
+    $j = 0;
+    while (($i < $v_list_dir_size) && ($j < $v_list_path_size) && ($v_result)) {
+
+      // ----- Look for empty dir (path reduction)
+      if ($v_list_dir[$i] == '') {
+        $i++;
+        continue;
+      }
+      if ($v_list_path[$j] == '') {
+        $j++;
+        continue;
+      }
+
+      // ----- Compare the items
+      if (($v_list_dir[$i] != $v_list_path[$j]) && ($v_list_dir[$i] != '') && ( $v_list_path[$j] != ''))  {
+        $v_result = 0;
+      }
+
+      // ----- Next items
+      $i++;
+      $j++;
+    }
+
+    // ----- Look if everything seems to be the same
+    if ($v_result) {
+      // ----- Skip all the empty items
+      while (($j < $v_list_path_size) && ($v_list_path[$j] == '')) $j++;
+      while (($i < $v_list_dir_size) && ($v_list_dir[$i] == '')) $i++;
+
+      if (($i >= $v_list_dir_size) && ($j >= $v_list_path_size)) {
+        // ----- There are exactly the same
+        $v_result = 2;
+      }
+      else if ($i < $v_list_dir_size) {
+        // ----- The path is shorter than the dir
+        $v_result = 0;
+      }
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : PclZipUtilCopyBlock()
+  // Description :
+  // Parameters :
+  //   $p_mode : read/write compression mode
+  //             0 : src & dest normal
+  //             1 : src gzip, dest normal
+  //             2 : src normal, dest gzip
+  //             3 : src & dest gzip
+  // Return Values :
+  // --------------------------------------------------------------------------------
+  function PclZipUtilCopyBlock($p_src, $p_dest, $p_size, $p_mode=0)
+  {
+    $v_result = 1;
+
+    if ($p_mode==0)
+    {
+      while ($p_size != 0)
+      {
+        $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE);
+        $v_buffer = @fread($p_src, $v_read_size);
+        @fwrite($p_dest, $v_buffer, $v_read_size);
+        $p_size -= $v_read_size;
+      }
+    }
+    else if ($p_mode==1)
+    {
+      while ($p_size != 0)
+      {
+        $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE);
+        $v_buffer = @gzread($p_src, $v_read_size);
+        @fwrite($p_dest, $v_buffer, $v_read_size);
+        $p_size -= $v_read_size;
+      }
+    }
+    else if ($p_mode==2)
+    {
+      while ($p_size != 0)
+      {
+        $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE);
+        $v_buffer = @fread($p_src, $v_read_size);
+        @gzwrite($p_dest, $v_buffer, $v_read_size);
+        $p_size -= $v_read_size;
+      }
+    }
+    else if ($p_mode==3)
+    {
+      while ($p_size != 0)
+      {
+        $v_read_size = ($p_size < PCLZIP_READ_BLOCK_SIZE ? $p_size : PCLZIP_READ_BLOCK_SIZE);
+        $v_buffer = @gzread($p_src, $v_read_size);
+        @gzwrite($p_dest, $v_buffer, $v_read_size);
+        $p_size -= $v_read_size;
+      }
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : PclZipUtilRename()
+  // Description :
+  //   This function tries to do a simple rename() function. If it fails, it
+  //   tries to copy the $p_src file in a new $p_dest file and then unlink the
+  //   first one.
+  // Parameters :
+  //   $p_src : Old filename
+  //   $p_dest : New filename
+  // Return Values :
+  //   1 on success, 0 on failure.
+  // --------------------------------------------------------------------------------
+  function PclZipUtilRename($p_src, $p_dest)
+  {
+    $v_result = 1;
+
+    // ----- Try to rename the files
+    if (!@rename($p_src, $p_dest)) {
+
+      // ----- Try to copy & unlink the src
+      if (!@copy($p_src, $p_dest)) {
+        $v_result = 0;
+      }
+      else if (!@unlink($p_src)) {
+        $v_result = 0;
+      }
+    }
+
+    // ----- Return
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : PclZipUtilOptionText()
+  // Description :
+  //   Translate option value in text. Mainly for debug purpose.
+  // Parameters :
+  //   $p_option : the option value.
+  // Return Values :
+  //   The option text value.
+  // --------------------------------------------------------------------------------
+  function PclZipUtilOptionText($p_option)
+  {
+    
+    $v_list = get_defined_constants();
+    for (reset($v_list); $v_key = key($v_list); next($v_list)) {
+	    $v_prefix = substr($v_key, 0, 10);
+	    if ((   ($v_prefix == 'PCLZIP_OPT')
+           || ($v_prefix == 'PCLZIP_CB_')
+           || ($v_prefix == 'PCLZIP_ATT'))
+	        && ($v_list[$v_key] == $p_option)) {
+        return $v_key;
+	    }
+    }
+    
+    $v_result = 'Unknown';
+
+    return $v_result;
+  }
+  // --------------------------------------------------------------------------------
+
+  // --------------------------------------------------------------------------------
+  // Function : PclZipUtilTranslateWinPath()
+  // Description :
+  //   Translate windows path by replacing '\' by '/' and optionally removing
+  //   drive letter.
+  // Parameters :
+  //   $p_path : path to translate.
+  //   $p_remove_disk_letter : true | false
+  // Return Values :
+  //   The path translated.
+  // --------------------------------------------------------------------------------
+  function PclZipUtilTranslateWinPath($p_path, $p_remove_disk_letter=true)
+  {
+    if (stristr(php_uname(), 'windows')) {
+      // ----- Look for potential disk letter
+      if (($p_remove_disk_letter) && (($v_position = strpos($p_path, ':')) != false)) {
+          $p_path = substr($p_path, $v_position+1);
+      }
+      // ----- Change potential windows directory separator
+      if ((strpos($p_path, '\\') > 0) || (substr($p_path, 0,1) == '\\')) {
+          $p_path = strtr($p_path, '\\', '/');
+      }
+    }
+    return $p_path;
+  }
+  // --------------------------------------------------------------------------------
+
+
+?>
diff --git a/application/libraries/PemFtp.php b/application/libraries/PemFtp.php
new file mode 100644
index 0000000..2642534
--- /dev/null
+++ b/application/libraries/PemFtp.php
@@ -0,0 +1,739 @@
+<?php
+/**
+ * PemFTP - A Ftp implementation in pure PHP
+ *
+ * @package PemFTP
+ * @since 2.5
+ *
+ * @version 1.0
+ * @copyright Alexey Dotsenko
+ * @author Alexey Dotsenko
+ * @link http://www.phpclasses.org/browse/package/1743.html Site
+ * @license LGPL License http://www.opensource.org/licenses/lgpl-license.html
+ */
+
+if( ! defined('CRLF')) define('CRLF',"\r\n");
+if( ! defined("FTP_AUTOASCII")) define("FTP_AUTOASCII", -1);
+if( ! defined("FTP_BINARY")) define("FTP_BINARY", 1);
+if( ! defined("FTP_ASCII")) define("FTP_ASCII", 0);
+if( ! defined('FTP_FORCE')) define('FTP_FORCE', TRUE);
+define('FTP_OS_Unix','u');
+define('FTP_OS_Windows','w');
+define('FTP_OS_Mac','m');
+
+class Pemftp_Core {
+	/* Public variables */
+	var $LocalEcho;
+	var $Verbose;
+	var $OS_local;
+	var $OS_remote;
+	
+	/* Private variables */
+	var $_lastaction;
+	var $_errors;
+	var $_type;
+	var $_umask;
+	var $_timeout;
+	var $_passive;
+	var $_host;
+	var $_fullhost;
+	var $_port;
+	var $_datahost;
+	var $_dataport;
+	var $_ftp_control_sock;
+	var $_ftp_data_sock;
+	var $_ftp_temp_sock;
+	var $_ftp_buff_size;
+	var $_login;
+	var $_password;
+	var $_connected;
+	var $_ready;
+	var $_code;
+	var $_message;
+	var $_can_restore;
+	var $_port_available;
+	var $_curtype;
+	var $_features;
+
+	var $_error_array;
+	var $AuthorizedTransferMode;
+	var $OS_FullName;
+	var $_eol_code;
+	var $AutoAsciiExt;
+
+	/* Constructor */
+	function ftp_base($port_mode=FALSE) {
+		$this->__construct($port_mode);
+	}
+
+	function __construct($port_mode=FALSE, $verb=FALSE, $le=FALSE) {
+		$this->LocalEcho=$le;
+		$this->Verbose=$verb;
+		$this->_lastaction=NULL;
+		$this->_error_array=array();
+		$this->_eol_code=array(FTP_OS_Unix=>"\n", FTP_OS_Mac=>"\r", FTP_OS_Windows=>"\r\n");
+		$this->AuthorizedTransferMode=array(FTP_AUTOASCII, FTP_ASCII, FTP_BINARY);
+		$this->OS_FullName=array(FTP_OS_Unix => 'UNIX', FTP_OS_Windows => 'WINDOWS', FTP_OS_Mac => 'MACOS');
+		$this->AutoAsciiExt=array("ASP","BAT","C","CPP","CSS","CSV","JS","H","HTM","HTML","SHTML","INI","LOG","PHP3","PHTML","PL","PERL","SH","SQL","TXT");
+		$this->_port_available=($port_mode==TRUE);
+		$this->SendMSG("Staring FTP client class".($this->_port_available?"":" without PORT mode support"));
+		$this->_connected=FALSE;
+		$this->_ready=FALSE;
+		$this->_can_restore=FALSE;
+		$this->_code=0;
+		$this->_message="";
+		$this->_ftp_buff_size=4096;
+		$this->_curtype=NULL;
+		$this->SetUmask(0022);
+		$this->SetType(FTP_AUTOASCII);
+		$this->SetTimeout(30);
+		$this->Passive(!$this->_port_available);
+		$this->_login="anonymous";
+		$this->_password="anon at ftp.com";
+		$this->_features=array();
+	    $this->OS_local=FTP_OS_Unix;
+		$this->OS_remote=FTP_OS_Unix;
+		$this->features=array();
+		if(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') $this->OS_local=FTP_OS_Windows;
+		elseif(strtoupper(substr(PHP_OS, 0, 3)) === 'MAC') $this->OS_local=FTP_OS_Mac;
+	}
+
+// <!-- --------------------------------------------------------------------------------------- -->
+// <!--       Public functions                                                                  -->
+// <!-- --------------------------------------------------------------------------------------- -->
+	function parselisting($list) {
+//	Parses 1 line like:		"drwxrwx---  2 owner group 4096 Apr 23 14:57 text"
+		if(preg_match("/^([-ld])([rwxst-]+)\s+(\d+)\s+([^\s]+)\s+([^\s]+)\s+(\d+)\s+(\w{3})\s+(\d+)\s+([\:\d]+)\s+(.+)$/i", $list, $ret)) {
+			$v=array(
+				"type"	=> ($ret[1]=="-"?"f":$ret[1]),
+				"perms"	=> 0,
+				"inode"	=> $ret[3],
+				"owner"	=> $ret[4],
+				"group"	=> $ret[5],
+				"size"	=> $ret[6],
+				"date"	=> $ret[7]." ".$ret[8]." ".$ret[9],
+				"name"	=> $ret[10]
+			);
+			$bad=array("(?)");
+			if(in_array($v["owner"], $bad)) $v["owner"]=NULL;
+			if(in_array($v["group"], $bad)) $v["group"]=NULL;
+			$v["perms"]+=00400*(int)($ret[2]{0}=="r");
+			$v["perms"]+=00200*(int)($ret[2]{1}=="w");
+			$v["perms"]+=00100*(int)in_array($ret[2]{2}, array("x","s"));
+			$v["perms"]+=00040*(int)($ret[2]{3}=="r");
+			$v["perms"]+=00020*(int)($ret[2]{4}=="w");
+			$v["perms"]+=00010*(int)in_array($ret[2]{5}, array("x","s"));
+			$v["perms"]+=00004*(int)($ret[2]{6}=="r");
+			$v["perms"]+=00002*(int)($ret[2]{7}=="w");
+			$v["perms"]+=00001*(int)in_array($ret[2]{8}, array("x","t"));
+			$v["perms"]+=04000*(int)in_array($ret[2]{2}, array("S","s"));
+			$v["perms"]+=02000*(int)in_array($ret[2]{5}, array("S","s"));
+			$v["perms"]+=01000*(int)in_array($ret[2]{8}, array("T","t"));
+		} 
+		return $v;
+	}
+	
+	function SendMSG($message = "", $crlf=true) {
+		if ($this->Verbose) {
+			echo $message.($crlf?CRLF:"");
+			flush();
+		}
+		return TRUE;
+	}
+
+	function SetType($mode=FTP_AUTOASCII) {
+		if(!in_array($mode, $this->AuthorizedTransferMode)) {
+			$this->SendMSG("Wrong type");
+			return FALSE;
+		}
+		$this->_type=$mode;
+		$this->SendMSG("Transfer type: ".($this->_type==FTP_BINARY?"binary":($this->_type==FTP_ASCII?"ASCII":"auto ASCII") ) );
+		return TRUE;
+	}
+
+	function _settype($mode=FTP_ASCII) {
+		if($this->_ready) {
+			if($mode==FTP_BINARY) {
+				if($this->_curtype!=FTP_BINARY) {
+					if(!$this->_exec("TYPE I", "SetType")) return FALSE;
+					$this->_curtype=FTP_BINARY;
+				}
+			} elseif($this->_curtype!=FTP_ASCII) {
+				if(!$this->_exec("TYPE A", "SetType")) return FALSE;
+				$this->_curtype=FTP_ASCII;
+			}
+		} else return FALSE;
+		return TRUE;
+	}
+
+	function Passive($pasv=NULL) {
+		if(is_null($pasv)) $this->_passive=!$this->_passive;
+		else $this->_passive=$pasv;
+		if(!$this->_port_available and !$this->_passive) {
+			$this->SendMSG("Only passive connections available!");
+			$this->_passive=TRUE;
+			return FALSE;
+		}
+		$this->SendMSG("Passive mode ".($this->_passive?"on":"off"));
+		return TRUE;
+	}
+
+	function SetServer($host, $port=21, $reconnect=true) {
+		if(!is_long($port)) {
+	        $this->verbose=true;
+    	    $this->SendMSG("Incorrect port syntax");
+			return FALSE;
+		} else {
+			$ip=@gethostbyname($host);
+	        $dns=@gethostbyaddr($host);
+	        if(!$ip) $ip=$host;
+	        if(!$dns) $dns=$host;
+			if(ip2long($ip) === -1) {
+				$this->SendMSG("Wrong host name/address \"".$host."\"");
+				return FALSE;
+			}
+	        $this->_host=$ip;
+	        $this->_fullhost=$dns;
+	        $this->_port=$port;
+	        $this->_dataport=$port-1;
+		}
+		$this->SendMSG("Host \"".$this->_fullhost."(".$this->_host."):".$this->_port."\"");
+		if($reconnect){
+			if($this->_connected) {
+				$this->SendMSG("Reconnecting");
+				if(!$this->quit(FTP_FORCE)) return FALSE;
+				if(!$this->connect()) return FALSE;
+			}
+		}
+		return TRUE;
+	}
+
+	function SetUmask($umask=0022) {
+		$this->_umask=$umask;
+		umask($this->_umask);
+		$this->SendMSG("UMASK 0".decoct($this->_umask));
+		return TRUE;
+	}
+
+	function SetTimeout($timeout=30) {
+		$this->_timeout=$timeout;
+		$this->SendMSG("Timeout ".$this->_timeout);
+		if($this->_connected)
+			if(!$this->_settimeout($this->_ftp_control_sock)) return FALSE;
+		return TRUE;
+	}
+
+	function connect($server=NULL) {
+		if(!empty($server)) {
+			if(!$this->SetServer($server)) return false;
+		}
+		if($this->_ready) return true;
+	    $this->SendMsg('Local OS : '.$this->OS_FullName[$this->OS_local]);
+		if(!($this->_ftp_control_sock = $this->_connect($this->_host, $this->_port))) {
+			$this->SendMSG("Error : Cannot connect to remote host \"".$this->_fullhost." :".$this->_port."\"");
+			return FALSE;
+		}
+		$this->SendMSG("Connected to remote host \"".$this->_fullhost.":".$this->_port."\". Waiting for greeting.");
+		do {
+			if(!$this->_readmsg()) return FALSE;
+			if(!$this->_checkCode()) return FALSE;
+			$this->_lastaction=time();
+		} while($this->_code<200);
+		$this->_ready=true;
+		$syst=$this->systype();
+		if(!$syst) $this->SendMSG("Can't detect remote OS");
+		else {
+			if(preg_match("/win|dos|novell/i", $syst[0])) $this->OS_remote=FTP_OS_Windows;
+			elseif(preg_match("/os/i", $syst[0])) $this->OS_remote=FTP_OS_Mac;
+			elseif(preg_match("/(li|u)nix/i", $syst[0])) $this->OS_remote=FTP_OS_Unix;
+			else $this->OS_remote=FTP_OS_Mac;
+			$this->SendMSG("Remote OS: ".$this->OS_FullName[$this->OS_remote]);
+		}
+		if(!$this->features()) $this->SendMSG("Can't get features list. All supported - disabled");
+		else $this->SendMSG("Supported features: ".implode(", ", array_keys($this->_features)));
+		return TRUE;
+	}
+
+	function quit($force=false) {
+		if($this->_ready) {
+			if(!$this->_exec("QUIT") and !$force) return FALSE;
+			if(!$this->_checkCode() and !$force) return FALSE;
+			$this->_ready=false;
+			$this->SendMSG("Session finished");
+		}
+		$this->_quit();
+		return TRUE;
+	}
+
+	function login($user=NULL, $pass=NULL) {
+		if(!is_null($user)) $this->_login=$user;
+		else $this->_login="anonymous";
+		if(!is_null($pass)) $this->_password=$pass;
+		else $this->_password="anon at anon.com";
+		if(!$this->_exec("USER ".$this->_login, "login")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		if($this->_code!=230) {
+			if(!$this->_exec((($this->_code==331)?"PASS ":"ACCT ").$this->_password, "login")) return FALSE;
+			if(!$this->_checkCode()) return FALSE;
+		}
+		$this->SendMSG("Authentication succeeded");
+		if(empty($this->_features)) {
+			if(!$this->features()) $this->SendMSG("Can't get features list. All supported - disabled");
+			else $this->SendMSG("Supported features: ".implode(", ", array_keys($this->_features)));
+		}
+		return TRUE;
+	}
+
+	function pwd() {
+		if(!$this->_exec("PWD", "pwd")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return preg_replace("/^[0-9]{3} \"(.+)\" .+".CRLF."/", "\\1", $this->_message);
+	}
+
+	function cdup() {
+		if(!$this->_exec("CDUP", "cdup")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return true;
+	}
+
+	function chdir($pathname) {
+		if(!$this->_exec("CWD ".$pathname, "chdir")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return TRUE;
+	}
+
+	function rmdir($pathname) {
+		if(!$this->_exec("RMD ".$pathname, "rmdir")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return TRUE;
+	}
+
+	function mkdir($pathname) {
+		if(!$this->_exec("MKD ".$pathname, "mkdir")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return TRUE;
+	}
+
+	function rename($from, $to) {
+		if(!$this->_exec("RNFR ".$from, "rename")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		if($this->_code==350) {
+			if(!$this->_exec("RNTO ".$to, "rename")) return FALSE;
+			if(!$this->_checkCode()) return FALSE;
+		} else return FALSE;
+		return TRUE;
+	}
+
+	function filesize($pathname) {
+		if(!isset($this->_features["SIZE"])) {
+			$this->PushError("filesize", "not supported by server");
+			return FALSE;
+		}
+		if(!$this->_exec("SIZE ".$pathname, "filesize")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return preg_replace("/^[0-9]{3} ([0-9]+)".CRLF."/", "\\1", $this->_message);
+	}
+
+	function abort() {
+		if(!$this->_exec("ABOR", "abort")) return FALSE;
+		if(!$this->_checkCode()) {
+			if($this->_code!=426) return FALSE;
+			if(!$this->_readmsg("abort")) return FALSE;
+			if(!$this->_checkCode()) return FALSE;
+		}
+		return true;
+	}
+
+	function mdtm($pathname) {
+		if(!isset($this->_features["MDTM"])) {
+			$this->PushError("mdtm", "not supported by server");
+			return FALSE;
+		}
+		if(!$this->_exec("MDTM ".$pathname, "mdtm")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		$mdtm = preg_replace("/^[0-9]{3} ([0-9]+)".CRLF."/", "\\1", $this->_message);
+		$date = sscanf($mdtm, "%4d%2d%2d%2d%2d%2d");
+		$timestamp = mktime($date[3], $date[4], $date[5], $date[1], $date[2], $date[0]);
+		return $timestamp;
+	}
+
+	function systype() {
+		if(!$this->_exec("SYST", "systype")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		$DATA = explode(" ", $this->_message);
+		return array($DATA[1], $DATA[3]);
+	}
+
+	function delete($pathname) {
+		if(!$this->_exec("DELE ".$pathname, "delete")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return TRUE;
+	}
+
+	function site($command, $fnction="site") {
+		if(!$this->_exec("SITE ".$command, $fnction)) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return TRUE;
+	}
+
+	function chmod($pathname, $mode) {
+		if(!$this->site("CHMOD ".decoct($mode)." ".$pathname, "chmod")) return FALSE;
+		return TRUE;
+	}
+
+	function restore($from) {
+		if(!isset($this->_features["REST"])) {
+			$this->PushError("restore", "not supported by server");
+			return FALSE;
+		}
+		if($this->_curtype!=FTP_BINARY) {
+			$this->PushError("restore", "can't restore in ASCII mode");
+			return FALSE;
+		}
+		if(!$this->_exec("REST ".$from, "resore")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return TRUE;
+	}
+
+	function features() {
+		if(!$this->_exec("FEAT", "features")) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		$f=array_slice(preg_split("/[".CRLF."]+/", $this->_message, -1, PREG_SPLIT_NO_EMPTY), 1, -1);
+		array_walk($f, create_function('&$a', '$a=preg_replace("/[0-9]{3}[\s-]+/", "", trim($a));'));
+		$this->_features=array();
+		foreach($f as $k=>$v) {
+			$v=explode(" ", trim($v));
+			$this->_features[array_shift($v)]=$v;
+		}
+		return true;
+	}
+
+	function rawlist($pathname="", $arg="") {
+		return $this->_list(($arg?" ".$arg:"").($pathname?" ".$pathname:""), "LIST", "rawlist");
+	}
+
+	function nlist($pathname="", $arg="") {
+		return $this->_list(($arg?" ".$arg:"").($pathname?" ".$pathname:""), "NLST", "nlist");
+	}
+
+	function is_exists($pathname) {
+		return $this->file_exists($pathname);
+	}
+
+	function file_exists($pathname) {
+		$exists=true;
+		if(!$this->_exec("RNFR ".$pathname, "rename")) $exists=FALSE;
+		else {
+			if(!$this->_checkCode()) $exists=FALSE;
+			$this->abort();
+		}
+		if($exists) $this->SendMSG("Remote file ".$pathname." exists");
+		else $this->SendMSG("Remote file ".$pathname." does not exist");
+		return $exists;
+	}
+
+	function get($remotefile, $localfile=NULL, $rest=0) {
+		if(is_null($localfile)) $localfile=$remotefile;
+		if (@file_exists($localfile)) $this->SendMSG("Warning : local file will be overwritten");
+		$fp = @fopen($localfile, "w");
+		if (!$fp) {
+			$this->PushError("get","can't open local file", "Cannot create \"".$localfile."\"");
+			return FALSE;
+		}
+		if($this->_can_restore and $rest!=0) fseek($fp, $rest);
+		$pi=pathinfo($remotefile);
+		if($this->_type==FTP_ASCII or ($this->_type==FTP_AUTOASCII and in_array(strtoupper($pi["extension"]), $this->AutoAsciiExt))) $mode=FTP_ASCII;
+		else $mode=FTP_BINARY;
+		if(!$this->_data_prepare($mode)) {
+			fclose($fp);
+			return FALSE;
+		}
+		if($this->_can_restore and $rest!=0) $this->restore($rest);
+		if(!$this->_exec("RETR ".$remotefile, "get")) {
+			$this->_data_close();
+			fclose($fp);
+			return FALSE;
+		}
+		if(!$this->_checkCode()) {
+			$this->_data_close();
+			fclose($fp);
+			return FALSE;
+		}
+		$out=$this->_data_read($mode, $fp);
+		fclose($fp);
+		$this->_data_close();
+		if(!$this->_readmsg()) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return $out;
+	}
+
+	function put($localfile, $remotefile=NULL, $rest=0) {
+		if(is_null($remotefile)) $remotefile=$localfile;
+		if (!@file_exists($localfile)) {
+			$this->PushError("put","can't open local file", "No such file or directory \"".$localfile."\"");
+			return FALSE;
+		}
+		$fp = @fopen($localfile, "r");
+		if (!$fp) {
+			$this->PushError("put","can't open local file", "Cannot read file \"".$localfile."\"");
+			return FALSE;
+		}
+		if($this->_can_restore and $rest!=0) fseek($fp, $rest);
+		$pi=pathinfo($localfile);
+		if($this->_type==FTP_ASCII or ($this->_type==FTP_AUTOASCII and in_array(strtoupper($pi["extension"]), $this->AutoAsciiExt))) $mode=FTP_ASCII;
+		else $mode=FTP_BINARY;
+		if(!$this->_data_prepare($mode)) {
+			fclose($fp);
+			return FALSE;
+		}
+		if($this->_can_restore and $rest!=0) $this->restore($rest);
+		if(!$this->_exec("STOR ".$remotefile, "put")) {
+			$this->_data_close();
+			fclose($fp);
+			return FALSE;
+		}
+		if(!$this->_checkCode()) {
+			$this->_data_close();
+			fclose($fp);
+			return FALSE;
+		}
+		$ret=$this->_data_write($mode, $fp);
+		fclose($fp);
+		$this->_data_close();
+		if(!$this->_readmsg()) return FALSE;
+		if(!$this->_checkCode()) return FALSE;
+		return $ret;
+	}
+
+	function mput($local=".", $remote=NULL, $continious=false) {
+		$local=realpath($local);
+		if(!@file_exists($local)) {
+			$this->PushError("mput","can't open local folder", "Cannot stat folder \"".$local."\"");
+			return FALSE;
+		}
+		if(!is_dir($local)) return $this->put($local, $remote);
+		if(empty($remote)) $remote=".";
+		elseif(!$this->file_exists($remote) and !$this->mkdir($remote)) return FALSE;
+		if($handle = opendir($local)) {
+			$list=array();
+			while (false !== ($file = readdir($handle))) {
+				if ($file != "." && $file != "..") $list[]=$file;
+			}
+			closedir($handle);
+		} else {
+			$this->PushError("mput","can't open local folder", "Cannot read folder \"".$local."\"");
+			return FALSE;
+		}
+		if(empty($list)) return TRUE;
+		$ret=true;
+		foreach($list as $el) {
+			if(is_dir($local."/".$el)) $t=$this->mput($local."/".$el, $remote."/".$el);
+			else $t=$this->put($local."/".$el, $remote."/".$el);
+			if(!$t) {
+				$ret=FALSE;
+				if(!$continious) break;
+			}
+		}
+		return $ret;
+		
+	}
+
+	function mget($remote, $local=".", $continious=false) {
+		$list=$this->rawlist($remote, "-lA");
+		if($list===false) {
+			$this->PushError("mget","can't read remote folder list", "Can't read remote folder \"".$remote."\" contents");
+			return FALSE;
+		}
+		if(empty($list)) return true;
+		if(!@file_exists($local)) {
+			if(!@mkdir($local)) {
+				$this->PushError("mget","can't create local folder", "Cannot create folder \"".$local."\"");
+				return FALSE;
+			}
+		}
+		foreach($list as $k=>$v) {
+			$list[$k]=$this->parselisting($v);
+			if($list[$k]["name"]=="." or $list[$k]["name"]=="..") unset($list[$k]);
+		}
+		$ret=true;
+		foreach($list as $el) {
+			if($el["type"]=="d") {
+				if(!$this->mget($remote."/".$el["name"], $local."/".$el["name"], $continious)) {
+					$this->PushError("mget", "can't copy folder", "Can't copy remote folder \"".$remote."/".$el["name"]."\" to local \"".$local."/".$el["name"]."\"");
+					$ret=false;
+					if(!$continious) break;
+				}
+			} else {
+				if(!$this->get($remote."/".$el["name"], $local."/".$el["name"])) {
+					$this->PushError("mget", "can't copy file", "Can't copy remote file \"".$remote."/".$el["name"]."\" to local \"".$local."/".$el["name"]."\"");
+					$ret=false;
+					if(!$continious) break;
+				}
+			}
+			@chmod($local."/".$el["name"], $el["perms"]);
+			$t=strtotime($el["date"]);
+			if($t!==-1 and $t!==false) @touch($local."/".$el["name"], $t);
+		}
+		return $ret;
+	}
+
+	function mdel($remote, $continious=false) {
+		$list=$this->rawlist($remote, "-la");
+		if($list===false) {
+			$this->PushError("mdel","can't read remote folder list", "Can't read remote folder \"".$remote."\" contents");
+			return false;
+		}
+	
+		foreach($list as $k=>$v) {
+			$list[$k]=$this->parselisting($v);
+			if($list[$k]["name"]=="." or $list[$k]["name"]=="..") unset($list[$k]);
+		}
+		$ret=true;
+	
+		foreach($list as $el) {
+			if($el["type"]=="d") {
+				if(!$this->mdel($remote."/".$el["name"], $continious)) {
+					$ret=false;
+					if(!$continious) break;
+				}
+			} else {
+				if (!$this->delete($remote."/".$el["name"])) {
+					$this->PushError("mdel", "can't delete file", "Can't delete remote file \"".$remote."/".$el["name"]."\"");
+					$ret=false;
+					if(!$continious) break;
+				}
+			}
+		}
+	
+		if(!$this->rmdir($remote)) {
+			$this->PushError("mdel", "can't delete folder", "Can't delete remote folder \"".$remote."/".$el["name"]."\"");
+			$ret=false;
+		}
+		return $ret;
+	}
+
+	function mmkdir($dir, $mode = 0777) {
+		if(empty($dir)) return FALSE;
+		if($this->is_exists($dir) or $dir == "/" ) return TRUE;
+		if(!$this->mmkdir(dirname($dir), $mode)) return false;
+		$r=$this->mkdir($dir, $mode);
+		$this->chmod($dir,$mode);
+		return $r;
+	}
+
+	function glob($pattern, $handle=NULL) {
+		$path=$output=null;
+		if(PHP_OS=='WIN32') $slash='\\';
+		else $slash='/';
+		$lastpos=strrpos($pattern,$slash);
+		if(!($lastpos===false)) {
+			$path=substr($pattern,0,-$lastpos-1);
+			$pattern=substr($pattern,$lastpos);
+		} else $path=getcwd();
+		if(is_array($handle) and !empty($handle)) {
+			while($dir=each($handle)) {
+				if($this->glob_pattern_match($pattern,$dir))
+				$output[]=$dir;
+			}
+		} else {
+			$handle=@opendir($path);
+			if($handle===false) return false;
+			while($dir=readdir($handle)) {
+				if($this->glob_pattern_match($pattern,$dir))
+				$output[]=$dir;
+			}
+			closedir($handle);
+		}
+		if(is_array($output)) return $output;
+		return false;
+	}
+
+	function glob_pattern_match($pattern,$string) {
+		$out=null;
+		$chunks=explode(';',$pattern);
+		foreach($chunks as $pattern) {
+			$escape=array('$','^','.','{','}','(',')','[',']','|');
+			while(strpos($pattern,'**')!==false)
+				$pattern=str_replace('**','*',$pattern);
+			foreach($escape as $probe)
+				$pattern=str_replace($probe,"\\$probe",$pattern);
+			$pattern=str_replace('?*','*',
+				str_replace('*?','*',
+					str_replace('*',".*",
+						str_replace('?','.{1,1}',$pattern))));
+			$out[]=$pattern;
+		}
+		if(count($out)==1) return($this->glob_regexp("^$out[0]$",$string));
+		else {
+			foreach($out as $tester)
+				if($this->my_regexp("^$tester$",$string)) return true;
+		}
+		return false;
+	}
+
+	function glob_regexp($pattern,$probe) {
+		$sensitive=(PHP_OS!='WIN32');
+		return ($sensitive?
+			ereg($pattern,$probe):
+			eregi($pattern,$probe)
+		);
+	}
+// <!-- --------------------------------------------------------------------------------------- -->
+// <!--       Private functions                                                                 -->
+// <!-- --------------------------------------------------------------------------------------- -->
+	function _checkCode() {
+		return ($this->_code<400 and $this->_code>0);
+	}
+
+	function _list($arg="", $cmd="LIST", $fnction="_list") {
+		if(!$this->_data_prepare()) return false;
+		if(!$this->_exec($cmd.$arg, $fnction)) {
+			$this->_data_close();
+			return FALSE;
+		}
+		if(!$this->_checkCode()) {
+			$this->_data_close();
+			return FALSE;
+		}
+		$out="";
+		if($this->_code<200) {
+			$out=$this->_data_read();
+			$this->_data_close();
+			if(!$this->_readmsg()) return FALSE;
+			if(!$this->_checkCode()) return FALSE;
+			if($out === FALSE ) return FALSE;
+			$out=preg_split("/[".CRLF."]+/", $out, -1, PREG_SPLIT_NO_EMPTY);
+//			$this->SendMSG(implode($this->_eol_code[$this->OS_local], $out));
+		}
+		return $out;
+	}
+
+// <!-- --------------------------------------------------------------------------------------- -->
+// <!-- Partie : gestion des erreurs                                                            -->
+// <!-- --------------------------------------------------------------------------------------- -->
+// G�n�re une erreur pour traitement externe � la classe
+	function PushError($fctname,$msg,$desc=false){
+		$error=array();
+		$error['time']=time();
+		$error['fctname']=$fctname;
+		$error['msg']=$msg;
+		$error['desc']=$desc;
+		if($desc) $tmp=' ('.$desc.')'; else $tmp='';
+		$this->SendMSG($fctname.': '.$msg.$tmp);
+		return(array_push($this->_error_array,$error));
+	}
+	
+// R�cup�re une erreur externe
+	function PopError(){
+		if(count($this->_error_array)) return(array_pop($this->_error_array));
+			else return(false);
+	}
+}
+
+//$mod_sockets=TRUE;
+//if (!extension_loaded('sockets')) {
+//	$prefix = (PHP_SHLIB_SUFFIX == 'dll') ? 'php_' : '';
+//	if(!@dl($prefix . 'sockets.' . PHP_SHLIB_SUFFIX)) $mod_sockets=FALSE;
+//}
+require_once "ftp/ftp_class_pure.php";
+?>
diff --git a/application/libraries/Requirements.php b/application/libraries/Requirements.php
new file mode 100644
index 0000000..5f27113
--- /dev/null
+++ b/application/libraries/Requirements.php
@@ -0,0 +1,1192 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Requirements tracker, for javascript and css.
+ * 
+ * Based on Requirements class from sapphire framework (but heavily modified)
+ * https://github.com/silverstripe/sapphire/blob/master/view/Requirements.php
+ * 
+ * LICENSE: This source file is subject to BSD license
+ *
+ * Copyright (c) 2007-2011, SilverStripe Limited - www.silverstripe.com
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the 
+ *   documentation and/or other materials provided with the distribution.
+ * * Neither the name of SilverStripe nor the names of its contributors may be used to endorse or promote products derived from this software 
+ *   without specific prior written permission.
+
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
+ * OF SUCH DAMAGE.
+ * 
+ * @package	   Ushahidi - https://github.com/ushahidi/Ushahidi_Web
+ * @license	   https://github.com/silverstripe/sapphire Modified BSD License
+ */
+class Requirements {
+	
+	/**
+	 * Enable combining of css/javascript files.
+	 * @param boolean $enable
+	 */
+	public static function set_combined_files_enabled($enable) {
+		self::backend()->set_combined_files_enabled($enable);
+	}
+
+	/**
+	 * Checks whether combining of css/javascript files is enabled.
+	 * @return boolean
+	 */
+	public static function get_combined_files_enabled() {
+	  return self::backend()->get_combined_files_enabled();
+	}
+
+	/**
+	 * Set the relative folder e.g. "assets" for where to store combined files
+	 * @param string $folder Path to folder
+	 */
+	public static function set_combined_files_folder($folder) {
+		self::backend()->setCombinedFilesFolder($folder);
+	}
+
+	/**
+	 * Set whether we want to suffix requirements with the time / 
+	 * location on to the requirements
+	 * 
+	 * @param bool
+	 */
+	public static function set_suffix_requirements($var) {
+		self::backend()->set_suffix_requirements($var);
+	}
+	
+	/**
+	 * Return whether we want to suffix requirements
+	 * 
+	 * @return bool
+	 */
+	public static function get_suffix_requirements() {
+		return self::backend()->get_suffix_requirements();
+	}
+	
+	/**
+	 * Instance of requirements for storage
+	 *
+	 * @var Requirements
+	 */
+	private static $backend = null;
+	
+	public static function backend() {
+		if(!self::$backend) {
+			self::$backend = new Requirements_Backend();
+		}
+		return self::$backend;
+	}
+	
+	/**
+	 * Setter method for changing the Requirements backend
+	 *
+	 * @param Requirements $backend
+	 */
+	public static function set_backend(Requirements_Backend $backend) {
+		self::$backend = $backend;
+	}
+	
+	/**
+	 * Register the given javascript file as required.
+	 * 
+	 * See {@link Requirements_Backend::javascript()} for more info
+	 * 
+	 */
+	static function js($file, $uniquenessID = null) {
+		self::backend()->js($file, $uniquenessID);
+	}
+	
+	/**
+	 * Add the javascript code to the header of the page
+	 * 
+	 * See {@link Requirements_Backend::customJS()} for more info
+	 * @param script The script content
+	 * @param uniquenessID Use this to ensure that pieces of code only get added once.
+	 */
+	static function customJS($script, $uniquenessID) {
+		self::backend()->customJS($script, $uniquenessID);
+	}
+
+	/**
+	 * Include custom CSS styling to the header of the page.
+	 * 
+	 * See {@link Requirements_Backend::customCSS()}
+	 * 
+	 * @param string $script CSS selectors as a string (without <style> tag enclosing selectors).
+	 * @param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header
+	 */
+	static function customCSS($script, $uniquenessID, $media = null) {
+		self::backend()->customCSS($script, $uniquenessID);
+	}
+	
+	/**
+	 * Add the following custom code to the <head> section of the page.
+	 * See {@link Requirements_Backend::customHeadTags()}
+	 * 
+	 * @param string $html
+	 * @param string $uniquenessID
+	 */
+	static function customHeadTags($html, $uniquenessID) {
+		self::backend()->customHeadTags($html, $uniquenessID);
+	}
+	
+	/**
+	 * Register the given stylesheet file as required.
+	 * See {@link Requirements_Backend::css()}
+	 * 
+	 * @param $file String Filenames should be relative to the base, eg, 'framework/javascript/tree/tree.css'
+	 * @param $media String Comma-separated list of media-types (e.g. "screen,projector") 
+	 * @see http://www.w3.org/TR/REC-CSS2/media.html
+	 */
+	static function css($file, $uniquenessID = null, $media = null) {
+		self::backend()->css($file, $uniquenessID, $media);
+	}
+	
+	/**
+	 * Registers the given themeable stylesheet as required.
+	 *
+	 * A CSS file in the current theme path name "themename/css/$name.css" is
+	 * first searched for, and it that doesn't exist and the module parameter is
+	 * set then a CSS file with that name in the module is used.
+	 * If neither theme nor module css exists, then a file from media/css will
+	 * be used.
+	 *
+	 * @param string $name The name of the file - e.g. "/css/File.css" would have
+	 *        the name "File".
+	 * @param string $module The module to fall back to if the css file does not
+	 *        exist in the current theme.
+	 * @param string $media The CSS media attribute.
+	 */
+	public static function themedCSS($name, $module = null, $media = null) {
+		return self::backend()->themedCSS($name, $module, $media);
+	}
+	
+	/**
+	 * Registers the given stylesheet in an IE conditional comment
+	 *
+	 * @param string $version The conditional IE version string e.g. "lt IE 7"
+	 * @param string $name The name of the file - e.g. "/css/File.css" would have
+	 *        the name "File".
+	 * @param string $media The CSS media attribute.
+	 */
+	public static function ieCSS($version, $name, $media = null) {
+		return self::backend()->ieCSS($version, $name, $media);
+	}
+	
+	/**
+	 * Registers an IE themeable stylesheet 
+	 *
+	 * @param string $version The conditional IE version string e.g. "lt IE 7"
+	 * @param string $name The name of the file - e.g. "/css/File.css" would have
+	 *        the name "File".
+	 * @param string $module The module to fall back to if the css file does not
+	 *        exist in the current theme.
+	 * @param string $media The CSS media attribute.
+	 */
+	public static function ieThemedCSS($version, $name, $module = null, $media = null) {
+		return self::backend()->ieThemedCSS($version, $name, $module, $media);
+	}
+
+	/**
+	 * Clear either a single or all requirements.
+	 * Caution: Clearing single rules works only with customCSS and customJS if you specified a {@uniquenessID}. 
+	 * 
+	 * See {@link Requirements_Backend::clear()}
+	 * 
+	 * @param $file String
+	 */
+	static function clear($fileOrID = null) {
+		self::backend()->clear($fileOrID);
+	}
+
+	/**
+	 * Blocks inclusion of a specific file
+	 * See {@link Requirements_Backend::block()}
+	 *
+	 * @param unknown_type $fileOrID
+	 */
+	static function block($fileOrID) {
+		self::backend()->block($fileOrID);
+	}
+
+	/**
+	 * Removes an item from the blocking-list.
+	 * See {@link Requirements_Backend::unblock()}
+	 * 
+	 * @param string $fileOrID
+	 */
+	static function unblock($fileOrID) {
+		self::backend()->unblock($fileOrID);
+	}
+
+	/**
+	 * Removes all items from the blocking-list.
+	 * See {@link Requirements_Backend::unblock_all()}
+	 */
+	static function unblock_all() {
+		self::backend()->unblock_all();
+	}
+	
+	/**
+	 * Restore requirements cleared by call to Requirements::clear
+	 * See {@link Requirements_Backend::restore()}
+	 */
+	static function restore() {
+		self::backend()->restore();
+	}
+	
+	/**
+	 * Render the appropriate include tags for the registered requirements. 
+	 * See {@link Requirements_Backend::render()} for more information.
+	 * 
+	 * @param string $type which requirements to render? accepts values 'all' 'css' 'js' or 'headtag'
+	 * @return string HTML include tags for inclusion in template
+	 */
+	static function render($type = 'all') {
+		return self::backend()->render($type);
+	}
+	
+	/**
+	 * Concatenate several css or javascript files into a single dynamically generated file.
+	 * See {@link Requirements_Backend::combine_files()} for more info.
+	 *
+	 * @param string $combinedFileName
+	 * @param array $files
+	 */
+	static function combine_files($combinedFileName, $files) {
+		self::backend()->combine_files($combinedFileName, $files);
+	}
+	
+	/**
+	 * Returns all combined files.
+	 * See {@link Requirements_Backend::get_combine_files()}
+	 * 
+	 * @return array
+	 */
+	static function get_combine_files() {
+		return self::backend()->get_combine_files();
+	}
+	
+	/**
+	 * Deletes all dynamically generated combined files from the filesystem. 
+	 * See {@link Requirements_Backend::delete_combine_files()}
+	 * 
+	 * @param string $combinedFileName If left blank, all combined files are deleted.
+	 */
+	static function delete_combined_files($combinedFileName = null) {
+		return self::backend()->delete_combined_files($combinedFileName);
+	}
+	
+
+	/**
+	 * Re-sets the combined files definition. See {@link Requirements_Backend::clear_combined_files()}
+	 */
+	static function clear_combined_files() {
+		self::backend()->clear_combined_files();
+	}
+		
+	/**
+	 * See {@link combine_files()}.
+ 	 */
+	static function process_combined_files() {
+		return self::backend()->process_combined_files();
+	}
+
+	/**
+	 * Returns all custom scripts
+	 * See {@link Requirements_Backend::get_custom_scripts()}
+	 *
+	 * @return array
+	 */
+	static function get_custom_scripts() {
+		return self::backend()->get_custom_scripts();
+	}
+	
+	static function debug() {
+		return self::backend()->debug();
+	}
+
+}
+
+/**
+ * @package framework
+ * @subpackage view
+ */
+class Requirements_Backend {
+
+	/**
+	 * Do we want requirements to suffix onto the requirement link
+	 * tags for caching or is it disabled. Getter / Setter available
+	 * through {@link Requirements::set_suffix_requirements()}
+	 *
+	 * @var bool
+	 */
+	protected $suffix_requirements = true;
+
+	/**
+	 * Enable combining of css/javascript files.
+	 *
+	 * @var boolean
+	 */
+	protected $combined_files_enabled = true;
+
+	/**
+	 * Paths to all required .js files relative to the webroot.
+	 *
+	 * @var array $js
+	 */
+	protected $js = array();
+
+	/**
+	 * Paths to all required .css files relative to the webroot.
+	 *
+	 * @var array $css
+	 */
+	protected $css = array();
+
+	/**
+	 * All custom javascript code that is inserted
+	 * directly at the bottom of the HTML <head> tag.
+	 *
+	 * @var array $customJS
+	 */
+	protected $customJS = array();
+
+	/**
+	 * All custom CSS rules which are inserted
+	 * directly at the bottom of the HTML <head> tag.
+	 *
+	 * @var array $customCSS
+	 */
+	protected $customCSS = array();
+
+	/**
+	 * All custom HTML markup which is added before
+	 * the closing <head> tag, e.g. additional metatags.
+	 * This is preferred to entering tags directly into
+	 */
+	protected $customHeadTags = array();
+
+	/**
+	 * Remembers the filepaths of all cleared Requirements
+	 * through {@link clear()}.
+	 *
+	 * @var array $disabled
+	 */
+	protected $disabled = array();
+
+	/**
+	 * The filepaths (relative to webroot) or
+	 * uniquenessIDs of any included requirements
+	 * which should be blocked when executing {@link inlcudeInHTML()}.
+	 * This is useful to e.g. prevent core classes to modifying
+	 * Requirements without subclassing the entire functionality.
+	 * Use {@link unblock()} or {@link unblock_all()} to revert changes.
+	 *
+	 * @var array $blocked
+	 */
+	protected $blocked = array();
+
+	/**
+	 * See {@link combine_files()}.
+	 *
+	 * @var array $combine_files
+	 */
+	public $combine_files = array('js' => array(), 'css' => array());
+
+	/**
+	 * Using the JSMin library to minify any
+	 * javascript file passed to {@link combine_files()}.
+	 *
+	 * @var boolean
+	 */
+	public $combine_js_with_jsmin = true;
+	
+
+	/**
+	 * Using the CSSMin library to minify any
+	 * css file passed to {@link combine_files()}.
+	 *
+	 * @var boolean
+	 */
+	public $combine_css_with_cssmin = true;
+
+	/**
+	 * @var string By default, combined files are stored in assets/_combinedfiles.
+	 * Set this by calling Requirements::set_combined_files_folder()
+	 */
+	protected $combinedFilesFolder = null;
+
+	/**
+	 * Put all javascript includes at the bottom of the template
+	 * before the closing <body> tag instead of the <head> tag.
+	 * This means script downloads won't block other HTTP-requests,
+	 * which can be a performance improvement.
+	 * Caution: Doesn't work when modifying the DOM from those external
+	 * scripts without listening to window.onload/document.ready
+	 * (e.g. toplevel document.write() calls).
+	 *
+	 * @see http://developer.yahoo.com/performance/rules.html#js_bottom
+	 *
+	 * @var boolean
+	 */
+	public $write_js_to_body = true;
+
+	public function __construct()
+	{
+		// Set up defaults from config file
+		$this->combine_js_with_jsmin = Kohana::config('requirements.combine_js_with_jsmin');
+		$this->combine_css_with_cssmin = Kohana::config('requirements.combine_css_with_cssmin');
+		$this->set_suffix_requirements(Kohana::config('requirements.suffix_requirements'));
+		$this->set_combined_files_enabled(Kohana::config('requirements.combined_files_enabled'));
+	}
+
+	function set_combined_files_enabled($enable) {
+		$this->combined_files_enabled = (bool) $enable;
+	}
+	
+	function get_combined_files_enabled() {
+		return $this->combined_files_enabled;
+	}
+
+	/**
+	 * @param String $folder
+	 */
+	function setCombinedFilesFolder($folder) {
+		$this->combinedFilesFolder = $folder;
+	}
+	
+	/**
+	 * @return String Folder relative to the webroot
+	 */
+	function getCombinedFilesFolder() {
+		$default = Kohana::config('upload.relative_directory', FALSE);
+		
+		if (Kohana::config("cdn.cdn_store_dynamic_content") AND Kohana::config("requirements.cdn_store_combined_files"))
+		{
+			$subdomain_dir = '';
+			if (Kohana::config('settings.subdomain') != '') {
+				// Make sure there's a slash on the end
+				$subdomain_dir = rtrim(Kohana::config('settings.subdomain'), '/').'/';
+			}
+
+			$default = $subdomain_dir . Kohana::config('upload.relative_directory', FALSE);
+		}
+		
+		return ($this->combinedFilesFolder) ? $this->combinedFilesFolder : $default;
+	}
+	
+	/**
+	 * Set whether we want to suffix requirements with the time / 
+	 * location on to the requirements
+	 * 
+	 * @param bool
+	 */
+	function set_suffix_requirements($var) {
+		$this->suffix_requirements = $var;
+	}
+	
+	/**
+	 * Return whether we want to suffix requirements
+	 * 
+	 * @return bool
+	 */
+	function get_suffix_requirements() {
+		return $this->suffix_requirements;
+	}
+	
+	/**
+	 * Register the given javascript file as required.
+	 * Filenames should be relative to the base, eg, 'framework/javascript/loader.js'
+	 * @param $file String|Array Filenames should be relative to the base, eg, 'media/js/style.js'
+	 * @param string $uniquenessID
+	 */
+	
+	public function js($file, $uniquenessID = null) {
+		// If array, loop over array and add individual js files
+		if (is_array($file))
+		{
+			foreach($file as $name)
+			{
+				$this->js($name);
+			}
+			return;
+		}
+		
+		if (! $uniquenessID)
+		{
+			$uniquenessID = substr( $file, strrpos( $file, '/' ) +1 );
+		}
+		
+		$this->js[$uniquenessID] = $file;
+	}
+	
+	/**
+	 * Returns an array of all included javascript
+	 *
+	 * @return array
+	 */
+	public function get_js() {
+		$js = array_diff_key($this->js,$this->blocked); // removed blocked js by id
+		$js = array_diff($js, $this->blocked); // remove blocked js by filename
+		return $js;
+	}
+	
+	/**
+	 * Add the javascript code to the header of the page
+	 * @todo Make Requirements automatically put this into a separate file :-)
+	 * @param script The script content
+	 * @param uniquenessID Use this to ensure that pieces of code only get added once.
+	 */
+	public function customJS($script, $uniquenessID) {
+		$script .= "\n";
+		$this->customJS[$uniquenessID] = $script;
+	}
+	
+	/**
+	 * Include custom CSS styling to the header of the page.
+	 *
+	 * @param string $script CSS selectors as a string (without <style> tag enclosing selectors).
+	 * @param int $uniquenessID Group CSS by a unique ID as to avoid duplicate custom CSS in header
+	 */
+	function customCSS($script, $uniquenessID) {
+		$this->customCSS[$uniquenessID] = $script;
+	}
+	
+	/**
+	 * Add the following custom code to the <head> section of the page.
+	 *
+	 * @param string $html
+	 * @param string $uniquenessID
+	 */
+	function customHeadTags($html, $uniquenessID) {
+		$this->customHeadTags[$uniquenessID] = $html;
+	}
+
+	
+	/**
+	 * Register the given stylesheet file as required.
+	 * 
+	 * @param $file String|Array Filenames should be relative to the base, eg, 'meida/css/tree.css'
+	 * @param string $uniquenessID
+	 * @param $media String Comma-separated list of media-types (e.g. "screen,projector") 
+	 * @see http://www.w3.org/TR/REC-CSS2/media.html
+	 */
+	function css($file, $uniquenessID = null, $media = null) {
+		// If array, loop over array and add individual js files
+		if (is_array($file))
+		{
+			foreach($file as $name)
+			{
+				$this->css($name);
+			}
+			return;
+		}
+		
+		if (! $uniquenessID)
+		{
+			$uniquenessID = substr( $file, strrpos( $file, '/' ) +1 );
+		}
+		
+		$this->css[$uniquenessID] = array(
+			"file" => $file,
+			"media" => $media
+		);
+	}
+	
+	function get_css() {
+		$css = array_diff_key($this->css,$this->blocked); // removed blocked css by id
+		// Remove blocked css by filename
+		foreach($css as $id => $params)
+		{
+			if (in_array($params['file'], $this->blocked)) unset($css[$id]);
+		}
+		
+		return $css;
+	}
+	
+	/**
+	 * Needed to actively prevent the inclusion of a file,
+	 * e.g. when using your own prototype.js.
+	 * Blocking should only be used as an exception, because
+	 * it is hard to trace back. You can just block items with an
+	 * ID, so make sure you add an unique identifier to customCSS() and customJS().
+	 * 
+	 * @param string $fileOrID
+	 */
+	function block($fileOrID) {
+		$this->blocked[$fileOrID] = $fileOrID;
+	}
+	
+	/**
+	 * Clear either a single or all requirements.
+	 * Caution: Clearing single rules works only with customCSS and customJS if you specified a {@uniquenessID}. 
+	 * 
+	 * @param $file String
+	 */
+	function clear($fileOrID = null) {
+		if($fileOrID) {
+			foreach(array('js','css', 'customJS', 'customCSS', 'customHeadTags') as $type) {
+				if(isset($this->{$type}[$fileOrID])) {
+					$this->disabled[$type][$fileOrID] = $this->{$type}[$fileOrID];
+					unset($this->{$type}[$fileOrID]);
+				}
+			}
+		} else {
+			$this->disabled['js'] = $this->js;
+			$this->disabled['css'] = $this->css;
+			$this->disabled['customJS'] = $this->customJS;
+			$this->disabled['customCSS'] = $this->customCSS;
+			$this->disabled['customHeadTags'] = $this->customHeadTags;
+		
+			$this->js = array();
+			$this->css = array();
+			$this->customJS = array();
+			$this->customCSS = array();
+			$this->customHeadTags = array();
+		}
+	}
+	
+	/**
+	 * Removes an item from the blocking-list.
+	 * CAUTION: Does not "re-add" any previously blocked elements.
+	 * @param string $fileOrID
+	 */
+	function unblock($fileOrID) {
+		if(isset($this->blocked[$fileOrID])) unset($this->blocked[$fileOrID]);
+	}
+	/**
+	 * Removes all items from the blocking-list.
+	 */
+	function unblock_all() {
+		$this->blocked = array();
+	}
+	
+	/**
+	 * Restore requirements cleared by call to Requirements::clear
+	 */
+	function restore() {
+		$this->js = $this->disabled['js'];
+		$this->css = $this->disabled['css'];
+		$this->customJS = $this->disabled['customJS'];
+		$this->customCSS = $this->disabled['customCSS'];
+		$this->customHeadTags = $this->disabled['customHeadTags'];
+	}
+	
+	/**
+	 * Generate the appropriate include tags for the registered requirements. 
+	 * 
+	 * @param string $type which type of requirement to render? accepts values 'all' 'css' 'js' or 'headtags'
+	 * @return string HTML include tags for inclusion in template
+	 */
+	function render($type = 'all') {
+		$content = '';
+		
+		switch ($type)
+		{
+			case 'css':
+				$content .= $this->renderCSS();
+				break;
+			case 'js':
+				$content .= $this->renderJS();
+				break;
+			case 'headtags':
+				$content .= $this->renderHeadTags();
+				break;
+			case 'all':
+			default:
+				$content .= $this->renderCSS();
+				$content .= $this->renderHeadTags();
+				$content .= $this->renderJS();
+				break;
+		}
+		
+		return $content;
+	}
+
+	/**
+	 * Generate the appropriate include tags for the registered JS requirements. 
+	 * 
+	 * @return string HTML include tags for inclusion in template
+	 */
+	private function renderJS()
+	{
+		$content = '';
+		if($this->js || $this->customJS) {
+			// Combine files - updates $this->js
+			$this->process_combined_files('js');
+		
+			foreach($this->get_js() as $id => $file) {
+				$path = $this->path_for_file($file, 'js');
+				if($path) {
+					//$jsRequirements .= html::script($path, TRUE);
+					$content .= "<script type=\"text/javascript\" src=\"$path\"></script>\n";
+				}
+			}
+			
+			// add all inline javascript *after* including external files which
+			// they might rely on
+			if($this->customJS) {
+				foreach(array_diff_key($this->customJS,$this->blocked) as $script) { 
+					$content .= "<script type=\"text/javascript\">\n//<![CDATA[\n";
+					$content .= "$script\n";
+					$content .= "\n//]]>\n</script>\n";
+				}
+			}
+		}
+		
+		return $content;
+	}
+
+	
+	/**
+	 * Generate the appropriate include tags for the registered Head Tag requirements. 
+	 * 
+	 * @return string HTML include tags for inclusion in template
+	 */
+	private function renderHeadTags()
+	{
+		$content = '';
+		if($this->customHeadTags) {
+			foreach(array_diff_key($this->customHeadTags,$this->blocked) as $customHeadTag) { 
+				$content .= "$customHeadTag\n"; 
+			}
+		}
+		
+		return $content;
+	}
+
+	
+	/**
+	 * Generate the appropriate include tags for the registered CSS requirements. 
+	 * 
+	 * @return string HTML include tags for inclusion in template
+	 */
+	private function renderCSS()
+	{
+		
+		$content = '';
+		if($this->css || $this->customCSS) {
+			// Combine files - updates $this->css 
+			$this->process_combined_files('css');
+			
+			foreach($this->get_css() as $id => $params) {
+				$file = $params['file'];
+				$path = $this->path_for_file($file, 'css');
+				if($path) {
+					//$media = (isset($params['media']) && !empty($params['media'])) ? $params['media'] : "";
+					//$requirements .= html::stylesheet($path, $media, TRUE);
+					$media = (isset($params['media']) && !empty($params['media'])) ? " media=\"{$params['media']}\"" : "";
+					$content .= "<link rel=\"stylesheet\" type=\"text/css\"{$media} href=\"$path\" />\n";
+					
+				}
+			}
+			
+			foreach(array_diff_key($this->customCSS, $this->blocked) as $css) { 
+				$content .= "<style type=\"text/css\">\n$css\n</style>\n";
+			}
+		}
+		
+		return $content;
+	}
+	
+	/**
+	 * Finds the path for specified file.
+	 *
+	 * @param string $fileOrUrl
+	 * @param string $type file type ie. css or js
+	 * @return string|boolean 
+	 */
+	protected function path_for_file($fileOrUrl, $type) {
+		if(preg_match('/^http[s]?/', $fileOrUrl)) {
+			return $fileOrUrl;
+		} else {
+			// Split query from filename
+			$suffix = '';
+			if(strpos($fileOrUrl, '?') !== false) {
+				$suffix = substr($fileOrUrl, strpos($fileOrUrl, '?')+1);
+				$fileOrUrl = substr($fileOrUrl, 0, strpos($fileOrUrl, '?'));
+			}
+			
+			if (file_exists(DOCROOT . $fileOrUrl)) {
+				// Check for RTL replacements
+				if ($type == 'css' AND ush_locale::is_rtl_language())
+				{
+					$rtlFile = substr($fileOrUrl, 0, strpos($fileOrUrl, ".$type")) . "-rtl" . substr($fileOrUrl, strpos($fileOrUrl, ".$type"));
+					if (file_exists(DOCROOT . $rtlFile))
+					{
+						$fileOrUrl = $rtlFile;
+					}
+				}
+				
+				// Get url prefix, either site base url or CDN url
+				$prefix = url::file_loc($type);
+				
+				$mtimesuffix = "?";
+				if($this->suffix_requirements) {
+					$mtimesuffix .= "m=" . filemtime(DOCROOT . $fileOrUrl);
+					$suffix = '&'.$suffix;
+				}
+				return "{$prefix}{$fileOrUrl}{$mtimesuffix}{$suffix}";
+			}
+		}
+		Kohana::log('alert', "Requirements: file $fileOrUrl not found");
+		return false;
+	}
+	
+	/**
+	 * Concatenate several css or javascript files into a single dynamically generated
+	 * file. This increases performance by fewer HTTP requests.
+	 * 
+	 * The combined file is only included if one or more of the individual files was included
+	 * with Requirements::js() or Requirements::css() already.
+	 * 
+	 * The combined file is regenerated based on every file modification time.
+	 * Optionally a rebuild can be triggered by appending ?flush=1 to the URL.
+	 * If all files to be combined are javascript, we use the external JSMin library
+	 * to minify the javascript. This can be controlled by {@link $combine_js_with_jsmin}.
+	 * 
+	 * CAUTION: You're responsible for ensuring that the load order for combined files
+	 * is retained - otherwise combining javascript files can lead to functional errors
+	 * in the javascript logic, and combining css can lead to wrong styling inheritance.
+	 * Depending on the javascript logic, you also have to ensure that files are not included
+	 * in more than one combine_files() call.
+	 * 
+	 * Best practice is to include every javascript file in exactly *one* combine_files()
+	 * directive to avoid the issues mentioned above - this is enforced by this function.
+	 * 
+	 * CAUTION: Combining CSS Files discards any "media" information.
+	 *
+	 * Example for combined JavaScript:
+	 * <code>
+	 * Requirements::combine_files(
+	 *  'foobar.js',
+	 *  array(
+	 * 		'mysite/javascript/foo.js',
+	 * 		'mysite/javascript/bar.js',
+	 * 		'baz.min' => 'mysite/javascript/baz.min',
+	 * 	)
+	 * );
+	 * </code>
+	 *
+	 * Example for combined CSS:
+	 * <code>
+	 * Requirements::combine_files(
+	 *  'foobar.css',
+	 * 	array(
+	 * 		'mysite/javascript/foo.css',
+	 * 		'mysite/javascript/bar.css',
+	 * 	)
+	 * );
+	 * </code>
+	 *
+	 * @see http://code.google.com/p/jsmin-php/
+	 * 
+	 * @todo Should we enforce unique inclusion of files, or leave it to the developer? Can auto-detection cause breaks?
+	 * 
+	 * @param string $combinedFileName Filename of the combined file
+	 * @param array $files Array of filenames relative to the webroot
+	 */
+	function combine_files($combinedFileName, $files) {
+		$type = stripos($combinedFileName, '.js') ? 'js' : 'css';
+		
+		// duplicate check
+		foreach($this->combine_files[$type] as $_combinedFileName => $_files) {
+			$duplicates = array_intersect($_files, $files);
+			if($duplicates && $combinedFileName != $_combinedFileName) {
+				Kohana::log('info', "Requirements_Backend::combine_files(): Already included files " . implode(',', $duplicates) . " in combined file '{$_combinedFileName}'");
+				return false;
+			}
+		}
+		
+		// If the $files array is indexed, generate uniquenessID from last part of filename
+		if (array_values($files) === $files)
+		{
+			$new_files = array();
+			foreach($files as $file)
+			{
+				$uniquenessID = substr( $file, strrpos( $file, '/' ) +1 );
+				$new_files[$uniquenessID] = $file;
+			}
+			$files = $new_files;
+		}
+		
+		$this->combine_files[$type][$combinedFileName] = $files;
+	}
+	
+	/**
+	 * Returns all combined files.
+	 * @return array
+	 */
+	function get_combine_files() {
+		return $this->combine_files;
+	}
+	
+	/**
+	 * Deletes all dynamically generated combined files from the filesystem. 
+	 * 
+	 * @param string $combinedFileName If left blank, all combined files are deleted.
+	 */
+	function delete_combined_files($combinedFileName = null) {
+		$combinedFiles = ($combinedFileName) ? array($combinedFileName => null) : array_merge($this->combine_files['css'], $this->combine_files['js']);
+		$combinedFolder = DOCROOT . $this->getCombinedFilesFolder();
+		foreach($combinedFiles as $combinedFile => $sourceItems) {
+			$filePath = $combinedFolder . '/' . $combinedFile;
+			if(file_exists($filePath)) {
+				unlink($filePath);
+			}
+		}
+	}
+	
+	function clear_combined_files() {
+		$this->combine_files = array('js' => array(), 'css' => array());
+	}
+
+	/**
+	 * See {@link combine_files()}
+	 *
+	 */
+	function process_combined_files($type = 'all') {
+		if( !$this->combined_files_enabled) {
+			return;
+		}
+		
+		switch ($type)
+		{
+			case 'js':
+				$this->_process_combined_files('js');
+				break;
+			case 'css':
+				$this->_process_combined_files('css');
+				break;
+			case 'all':
+			default:
+				$this->_process_combined_files('js');
+				$this->_process_combined_files('css');
+				break;
+		}
+	}
+
+	/**
+	 * See {@link combine_files()}
+	 *
+	 */
+	private function _process_combined_files($type) {
+		// Make a map of files that could be potentially combined
+		$combinerCheck = array();
+		foreach($this->combine_files[$type] as $combinedFile => $sourceItems) {
+			foreach($sourceItems as $id => $sourceItem) {
+				if(isset($combinerCheck[$sourceItem]) && $combinerCheck[$sourceItem] != $combinedFile){ 
+					Kohana::log('alert',"Requirements_Backend::process_combined_files - file '$sourceItem' appears in two combined files:" .	" '{$combinerCheck[$sourceItem]}' and '$combinedFile'");
+				}
+				$combinerCheck[$sourceItem] = $combinedFile;
+				$combinerCheck[$id] = $combinedFile;
+			}
+		}
+
+		// Work out the relative URL for the combined files from the base folder
+		$combinedFilesFolder = ($this->getCombinedFilesFolder()) ? ($this->getCombinedFilesFolder() . '/') : '';
+
+		// Figure out which ones apply to this pageview
+		$combinedFiles = array();
+		$newRequirements = array();
+		foreach($this->$type as $id => $params) {
+			$file = ($type == 'js') ? $params : $params['file'];
+			if(isset($combinerCheck[$file])) {
+				$newRequirements[$combinerCheck[$file]] = ($type == 'js') ? $combinedFilesFolder . $combinerCheck[$file] : array('file' => $combinedFilesFolder . $combinerCheck[$file]);
+				$combinedFiles[$combinerCheck[$file]] = true;
+			} elseif(isset($combinerCheck[$id])) {
+				$newRequirements[$combinerCheck[$id]] = ($type == 'js') ? $combinedFilesFolder . $combinerCheck[$id] : array('file' => $combinedFilesFolder . $combinerCheck[$id]);
+				$combinedFiles[$combinerCheck[$id]] = true;
+			} else {
+				$newRequirements[$id] = $params;
+			}
+		}
+
+		// Process the combined files
+		$base = DOCROOT;
+		foreach(array_diff_key($combinedFiles, $this->blocked) as $combinedFile => $dummy) {
+			$fileList = $this->combine_files[$type][$combinedFile];
+			$combinedFilePath = $base . $combinedFilesFolder . $combinedFile;
+
+			// Check for RTL alternatives
+			if ($type == 'css' AND ush_locale::is_rtl_language())
+			{
+				$has_rtl_files = FALSE;
+				foreach($fileList as $index => $file)
+				{
+					$rtlFile = substr($file, 0, strpos($file, ".$type")) . "-rtl" . substr($file, strpos($file, ".$type"));
+					if (file_exists(DOCROOT . $rtlFile))
+					{
+						$fileList[$index] = $rtlFile;
+						$has_rtl_files = TRUE;
+					}
+				}
+				
+				// Update combined files details, only if the include RTL alternatives
+				// We store the RTL version separate from the LTR version, so we don't regenerate every time someone changes language
+				if ($has_rtl_files)
+				{
+					$combinedFile = substr($combinedFile, 0, -4).'-rtl.css';
+					$combinedFilePath = $base . $combinedFilesFolder . $combinedFile;
+					$newRequirements[$combinedFile] = ($type == 'js') ? $combinedFilesFolder . $combinedFile : array('file' => $combinedFilesFolder . $combinedFile);
+				}
+			}
+
+			// Make the folder if necessary
+			if(!file_exists(dirname($combinedFilePath))) {
+				mkdir(dirname($combinedFilePath));
+			}
+
+			// If the file isn't writebale, don't even bother trying to make the combined file
+			// Complex test because is_writable fails if the file doesn't exist yet.
+			if((file_exists($combinedFilePath) && !is_writable($combinedFilePath)) ||
+				(!file_exists($combinedFilePath) && !is_writable(dirname($combinedFilePath)))) {
+				Kohana::log('alert', "Requirements_Backend::process_combined_files(): Couldn't create '$combinedFilePath'");
+				continue;
+			}
+
+			 // Determine if we need to build the combined include
+			if(file_exists($combinedFilePath) && !isset($_GET['flush'])) {
+				// file exists, check modification date of every contained file
+				$srcLastMod = 0;
+				foreach($fileList as $file) {
+					$srcLastMod = max(filemtime($base . $file), $srcLastMod);
+				}
+				$refresh = $srcLastMod > filemtime($combinedFilePath);
+			} else {
+				// file doesn't exist, or refresh was explicitly required
+				$refresh = true;
+			}
+
+			if(!$refresh) continue;
+
+			$combinedData = "";
+			foreach(array_diff($fileList, $this->blocked) as $id => $file) {
+				$fileContent = file_get_contents($base . $file);
+				
+				// if we have a javascript file and jsmin is enabled, minify the content
+				if($type == 'js' && $this->combine_js_with_jsmin) {
+					$fileContent = JSMin::minify($fileContent);
+				}
+				
+				if($type == 'css') {
+					// Rewrite urls in css to be relative to the docroot
+					// Docroot param needs to be actual server docroot, not root of ushahidi site.
+					// Using $_SERVER['DOCUMENT_ROOT']. Should possibly use DOCROOT, but remove Kohana::config('core.site_domain', TRUE);
+					$fileContent = Minify_CSS_UriRewriter::rewrite($fileContent, pathinfo($base . $file, PATHINFO_DIRNAME), $_SERVER['DOCUMENT_ROOT']);
+					// compress css (if enabled)
+					if ($this->combine_css_with_cssmin)
+					{
+						$fileContent = CSSmin::go($fileContent);
+					}
+				}
+				
+				// write a header comment for each file for easier identification and debugging
+				// also the semicolon between each file is required for jQuery to be combinable properly
+				$combinedData .= "/****** FILE: $file *****/\n" . $fileContent . "\n" . ($type == 'js' ? ';' : '') . "\n";
+			}
+
+			$successfulWrite = false;
+			$fh = fopen($combinedFilePath, 'wb');
+			if($fh) {
+				if(fwrite($fh, $combinedData) == strlen($combinedData)) $successfulWrite = true;
+				fclose($fh);
+				unset($fh);
+			}
+			
+			// Should we push this to the CDN too?
+			if (Kohana::config("cdn.cdn_store_dynamic_content") AND Kohana::config("requirements.cdn_store_combined_files"))
+			{
+				$cdn_combined_path = cdn::upload($combinedFilesFolder . $combinedFile, FALSE);
+			}
+
+			// Unsuccessful write - just include the regular JS files, rather than the combined one
+			if(!$successfulWrite) {
+				Kohana::log('alert', "Requirements_Backend::process_combined_files(): Couldn't create '$combinedFilePath'");
+				continue;
+			}
+		}
+
+		// @todo Alters the original information, which means you can't call this
+		// method repeatedly - it will behave different on the second call!
+		$this->$type = $newRequirements;
+	}
+	
+	function get_custom_scripts() {
+		$requirements = "";
+		
+		if($this->customJS) {
+			foreach($this->customJS as $script) {
+				$requirements .= "$script\n";
+			}
+		}
+		
+		return $requirements;
+	}
+
+	/**
+	 * @see Requirements::themedCSS()
+	 */
+	public function themedCSS($name, $module = null, $media = null) {
+		$this->css($this->themedCSSPath($name, $module), $name, $media);
+		return;
+	}
+
+	/**
+	 * @see Requirements::ieCSS()
+	 */
+	public function ieCSS($version, $name, $media = null) {
+		$this->customHeadTags("<!--[if $version]>".html::stylesheet(url::file_loc('css').$name,$media,TRUE)."<![endif]-->",'iecss-'.$name);
+		return;
+	}
+
+	/**
+	 * @see Requirements::ieThemedCSS()
+	 */
+	public function ieThemedCSS($version, $name, $module = null, $media = null) {
+		$this->ieCSS($version, $this->themedCSSPath($name, $module), $media, FALSE);
+		return;
+	}
+	
+	private function themedCSSPath($name, $module = null)
+	{
+		// try to include from a loaded theme
+		foreach (Themes::loaded_themes() as $theme)
+		{
+			$path  = THEMEPATH . "$theme/css/$name";
+			if (file_exists($path)) {
+				return "themes/$theme/css/$name";
+			}
+		}
+
+		// Try to include from fall back module
+		if ($module AND file_exists(DOCROOT . "$module/css/$name")) {
+			return "$module/css/$name";
+		}
+		
+		// Try to include from global media
+		if (file_exists(DOCROOT . "media/css/$name")) {
+			return "media/css/$name";
+		}
+	}
+	
+	function debug() {
+		Debug::show($this->js);
+		Debug::show($this->css);
+		Debug::show($this->customCSS);
+		Debug::show($this->customJS);
+		Debug::show($this->customHeadTags);
+		Debug::show($this->combine_files);
+	}
+	
+}
diff --git a/application/libraries/RiverID.php b/application/libraries/RiverID.php
new file mode 100644
index 0000000..627b2e9
--- /dev/null
+++ b/application/libraries/RiverID.php
@@ -0,0 +1,533 @@
+<?php
+/**
+ * This class provides a simple interface for RiverID authentication
+ * with the Ushahidi Platform
+ */
+class RiverID_Core {
+
+	// RiverID Endpoint
+	public $endpoint;
+
+	// Endpoint Exists tells us if the server is there
+	public $endpoint_exists;
+
+	// Email
+	public $email;
+
+	// Password
+	public $password;
+
+	// New Password (Used when changing or setting a new password)
+	public $new_password;
+
+	// User_id (RiverID userid, maps to "riverid" in users table)
+	public $user_id;
+
+	// Session_id (RiverID sessionid)
+	public $session_id;
+
+	// Authenticated (bool, true if logged in, false if not)
+	public $authenticated;
+
+	// Token (used when performing some operations like setting a new password when forgotten)
+	public $token;
+
+	// RiverID Server Name
+	public $server_name;
+
+	// RiverID Server Version Number
+	public $server_version;
+
+	// RiverID Server URL
+	public $server_url;
+
+	// Array of error messages
+	public $error;
+
+	// Cache lifetime
+	public $cache_lifetime;
+
+	// Curl object
+	public $api;
+
+	function __construct()
+	{
+		// We haven't authenticated yet
+		$this->authenticated = false;
+
+		// We haven't encountered any errors yet
+		$this->error = false;
+
+		// Set endpoint
+		$this->endpoint = kohana::config('riverid.endpoint');
+		$this->api_key = kohana::config('riverid.api_key');
+
+		// Check if endpoint is there
+		$this->endpoint_exits = TRUE;
+		if ($this->endpoint_exists() == FALSE)
+		{
+			// This is how we check if the endpoint is up and exists
+			$this->error[] = Kohana::lang('auth.password.riverid server down');
+			$this->endpoint_exits = FALSE;
+		}
+
+		// These will be set only once if about() is called
+		$this->server_name = FALSE;
+		$this->server_version = FALSE;
+		$this->server_url = FALSE;
+
+		// Cache lifetime for variables that we cache
+		$this->cache_lifetime = kohana::config('riverid.cache_lifetime');
+
+	}
+
+	/**
+	* Returns values for variables that may need to be grabbed before being returned
+	*
+	* @return mixed, depending on the variable but usually a string
+	*/
+	function __get($field) {
+
+		switch ($field)
+		{
+			case 'version':
+				return $this->server_version();
+			break;
+			case 'name':
+				return $this->server_name();
+			break;
+			case 'url':
+				return $this->server_url();
+			break;
+			default:
+				throw new Kohana_Exception('libraries.riverid_variable_not_available', $field);
+		}
+
+		return FALSE;
+	}
+
+	function parseResponse($json, $raw = NULL, $params = array()) {
+		if (isset($json->success) AND $json->success) {
+			if (isset($params['returnResponse'])) {
+				if (isset($json->response)) {
+					return $json->response;
+				} else {
+					return FALSE;
+				}
+			} else {
+				if ($raw) {
+					return $raw;
+				} else {
+					return TRUE;
+				}
+			}
+		}
+
+		if (isset($json->error)) {
+			$this->error[] = $json->error;
+		} else {
+			$error = 'An unexpected authentication error occured. Please try again shortly.';
+
+			if (isset($params['error'])) {
+				$this->errorr[] = $error . " (" . $params['error'] . ")";
+			} else {
+				$this->error[] = $error;
+			}
+		}
+
+		return FALSE;
+	}
+
+	function buildURL($path = '/about', $parameters = array()) {
+		$parameters['api_secret'] = $this->api_key;
+		$url = $this->endpoint . '/' . ltrim($path, '/');
+
+		if ($parameters) {
+			$url .= '?';
+
+			foreach($parameters as $k => $v) {
+				$url .= trim($k) . '=' . trim($v) . '&';
+			}
+
+			$url = substr($url, 0, -1);
+		}
+
+		return $url;
+	}
+
+	/**
+	* Grabs some information about the server we're dealing with
+	*
+	* @return bool, true if success, false otherwise
+	*/
+	function about()
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+		// Pull from cache first, if we've already called these before
+		$cache = Cache::instance();
+		$this->server_name = $cache->get('riverid_server_name');
+		$this->server_version = $cache->get('riverid_server_version');
+		$this->server_url = $cache->get('riverid_server_url');
+
+		if ( ! $this->server_name
+			OR ! $this->server_version
+			OR ! $this->server_url )
+		{
+			// Cache is Empty so Re-Cache
+
+			$url = $this->endpoint . '/about';
+			$about_response = $this->_curl_req($url);
+			$about = json_decode($about_response);
+
+			if (isset($about->success) && $about->success)
+			{
+				// Successful signin, save some variables
+				$this->server_name = $about->response->name;
+				$this->server_version = $about->response->version;
+				$this->server_url = $about->response->info_url;
+
+				// Set cache so we don't have to keep calling this method
+				$cache->set('riverid_server_name', $this->server_name, array('riverid'), $this->cache_lifetime);
+				$cache->set('riverid_server_version', $this->server_version, array('riverid'), $this->cache_lifetime);
+				$cache->set('riverid_server_url', $this->server_url, array('riverid'), $this->cache_lifetime);
+			}
+			else
+			{
+				// Failed signin
+				$this->error[] = $about->error;
+				return FALSE;
+			}
+		}
+
+		return TRUE;
+	}
+
+	/**
+	* Checks if an email address has been registered
+	*
+	* @return string, json of the response from the riverid server
+	*/
+	function registered()
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+		return $this->_curl_req($this->buildURL('/registered', array('email' => urlencode($this->email))));
+	}
+
+	/**
+	* Makes calling registered function easier
+	*
+	* @return bool, true if registered
+	*/
+	function is_registered()
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+		return $this->parseResponse(json_decode($this->registered()), NULL, array('returnResponse' => TRUE));
+	}
+
+	/**
+	* Registers a user
+	*
+	* @return string, json of the response from the riverid server
+	*/
+	function register()
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+		$raw = $this->_curl_req($this->buildURL('/register', array('email' => urlencode($this->email), 'password' => urlencode($this->password))));
+		$response = json_decode($raw);
+
+		if ($this->parseResponse($response)) {
+			$this->user_id = $response->response;
+			return $raw;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	* Sign a user in
+	*
+	* @return string, json of the response from the riverid server
+	*/
+	function signin()
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+		$raw      = $this->_curl_req($this->buildURL('/signin', array('email' => urlencode($this->email), 'password' => urlencode($this->password))));
+		$response = json_decode($raw);
+
+		if ($this->parseResponse($response)) {
+			// Successful signin, save some variables
+			$this->user_id       = $response->response->user_id;
+			$this->session_id    = $response->response->session_id;
+			$this->authenticated = true;
+
+			return $raw;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	* Resets a users password
+	*
+	* @param  string $mailbody This is the body of the email to send the user. Use %token% in the string to be replaced with the token they need to enter.
+	* @return string, json of the response from the riverid server
+	*/
+	function requestpassword($mailbody)
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+		$raw      = $this->_curl_req($this->buildURL('/requestpassword', array('email' => urlencode($this->email), 'mailbody' => urlencode($mailbody))));
+		$response = json_decode($raw);
+
+		return $this->parseResponse($response, $raw);
+	}
+
+	/**
+	* Set forgotten password with a new one
+	*
+	* @return string, json of the response from the riverid server
+	*/
+	function setpassword()
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+
+		$raw      = $this->_curl_req($this->buildURL('/setpassword', array('email' => urlencode($this->email), 'password' => urlencode($this->new_password), 'token' => $this->token)));
+		$response = json_decode($raw);
+
+		if ($this->parseResponse($response)) {
+			$this->password = $this->new_password;
+			return $raw;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	* Change password when current password is known
+	*
+	* @return string, json of the response from the riverid server
+	*/
+	function changepassword()
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+		$raw      = $this->_curl_req($this->buildURL('/changepassword', array('email' => urlencode($this->email), 'oldpassword' => urlencode($this->password), 'newpassword' => urlencode($this->new_password))));
+		$response = json_decode($raw);
+
+		if ($this->parseResponse($response)) {
+			$this->password = $this->new_password;
+			return $raw;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	* Checks if a password is correct for a user
+	*
+	* @return string, json of the response from the riverid server
+	*/
+	function checkpassword()
+	{
+		// Check for errors first
+		if ($this->errors_exist())
+			return FALSE;
+
+		$raw      = $this->_curl_req($this->buildURL('/checkpassword', array('email' => urlencode($this->email), 'password' => urlencode($this->password))));
+		$response = json_decode($raw);
+
+		return $this->parseResponse($response, $raw);
+	}
+
+	function facebookAuthorized($appid, $appsecret, $permissions) {
+		$url = $this->endpoint .
+			'/facebook_authorized?email=' . $this->email .
+			'&session_id=' . $this->session_id .
+			'&api_secret=' . $this->api_key .
+			'&fb_appid=' . $appid .
+			'&fb_secret=' . $appsecret .
+			'&fb_scope=' . $permissions;
+
+		$apiResponse = json_decode($this->_curl_req($url));
+
+		if (isset($apiResponse->success) AND $apiResponse->success) {
+			return TRUE;
+		} else {
+			if (isset($apiResponse->response)) {
+				return $apiResponse->response;
+			}
+		}
+
+		return FALSE;
+	}
+
+	function facebookAction($appid, $appsecret, $permissions, $namespace, $action, $object, $url, $params = array()) {
+		$url = $this->endpoint .
+			'/facebook_authorized?email=' . $this->email .
+			'&session_id=' . $this->session_id .
+			'&api_secret=' . $this->api_key .
+			'&fb_appid=' . $appid .
+			'&fb_secret=' . $appsecret .
+			'&fb_scope=' . $permissions .
+			'&fb_namespace=' . $namespace .
+			'&fb_action=' . $action .
+			'&fb__object=' . $object .
+			'&fb_object_url=' . $url;
+
+		if ($params) {
+			foreach ($params as $param => $val) {
+				$url .= "&fb_graph_{$param}={$val}";
+			}
+		}
+
+		$apiResponse = json_decode($this->_curl_req($url));
+
+		if (isset($apiResponse->success) AND $apiResponse->success) {
+			return TRUE;
+		} else {
+			if (isset($apiResponse->response)) {
+				return $apiResponse->response;
+			}
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Helper function to send a cURL request
+	 * @param url - URL for cURL to hit
+	 */
+	function _curl_req($url)
+	{
+		// Make sure cURL is installed
+		if ( ! function_exists('curl_exec'))
+		{
+			throw new Kohana_Exception('stats.cURL_not_installed');
+			return FALSE;
+		}
+
+		$this->api = curl_init($url);
+
+		$curlFlags = array(
+			CURLOPT_URL             => $url,
+			CURLOPT_FOLLOWLOCATION  => TRUE,
+			CURLOPT_CONNECTTIMEOUT  => 15,
+			CURLOPT_RETURNTRANSFER  => 1,
+			CURLOPT_SSL_VERIFYPEER  => FALSE
+			);
+
+		if (function_exists('curl_setopt_array')) {
+			curl_setopt_array($this->api, $curlFlags);
+		} else {
+			foreach($curlFlags as $flag => $val) {
+				curl_setopt($this->api, $flag, $val);
+			}
+		}
+
+		$buffer = curl_exec($this->api);
+		curl_close($this->api);
+
+		return $buffer;
+	}
+
+	/**
+	* Checks if the server specified in the url exists.
+	*
+	* @return true, if the server exists; false otherwise
+	*/
+	function endpoint_exists()
+	{
+		if (strpos($this->endpoint, '/') === false) {
+			$server = $this->endpoint;
+		} else {
+			$server = @parse_url($this->endpoint, PHP_URL_HOST);
+		}
+
+		if ( ! $server) {
+			return FALSE;
+		}
+
+		return !!gethostbynamel($server);
+	}
+
+	/**
+	* Checks if there are any errors
+	*
+	* @return array of errors if they exist, false otherwise
+	*/
+	function errors_exist()
+	{
+		if (count($this->error) > 0)
+			return $this->error;
+
+		// No errors!
+		return FALSE;
+	}
+
+	/**
+	* Gets the server version
+	*
+	* @return string, version number
+	*/
+	function server_version()
+	{
+		if ($this->server_version == FALSE)
+		{
+			$this->about();
+		}
+
+		return $this->server_version;
+	}
+
+	/**
+	* Gets the server name
+	*
+	* @return string, name of the server (ie: CrowdmapID)
+	*/
+	function server_name()
+	{
+		if ($this->server_name == FALSE)
+		{
+			$this->about();
+		}
+
+		return $this->server_name;
+	}
+
+	/**
+	* Gets the server url
+	*
+	* @return string, url of the server (ie: https://crowdmapid.com)
+	*/
+	function server_url()
+	{
+		if ($this->server_url == FALSE)
+		{
+			$this->about();
+		}
+
+		return $this->server_url;
+	}
+}
diff --git a/application/libraries/SimplePie.php b/application/libraries/SimplePie.php
new file mode 100644
index 0000000..8782c7a
--- /dev/null
+++ b/application/libraries/SimplePie.php
@@ -0,0 +1,15001 @@
+<?php
+/**
+ * SimplePie
+ *
+ * A PHP-Based RSS and Atom Feed Framework.
+ * Takes the hard work out of managing a complete RSS/Atom solution.
+ *
+ * Copyright (c) 2004-2009, Ryan Parman and Geoffrey Sneddon
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * 	* Redistributions of source code must retain the above copyright notice, this list of
+ * 	  conditions and the following disclaimer.
+ *
+ * 	* Redistributions in binary form must reproduce the above copyright notice, this list
+ * 	  of conditions and the following disclaimer in the documentation and/or other materials
+ * 	  provided with the distribution.
+ *
+ * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
+ * 	  to endorse or promote products derived from this software without specific prior
+ * 	  written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
+ * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package SimplePie
+ * @version 1.2
+ * @copyright 2004-2009 Ryan Parman, Geoffrey Sneddon
+ * @author Ryan Parman
+ * @author Geoffrey Sneddon
+ * @link http://simplepie.org/ SimplePie
+ * @link http://simplepie.org/support/ Please submit all bug reports and feature requests to the SimplePie forums
+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
+ * @todo phpDoc comments
+ */
+
+/**
+ * SimplePie Name
+ */
+define('SIMPLEPIE_NAME', 'SimplePie');
+
+/**
+ * SimplePie Version
+ */
+define('SIMPLEPIE_VERSION', '1.2');
+
+/**
+ * SimplePie Build
+ */
+define('SIMPLEPIE_BUILD', '20090627192103');
+
+/**
+ * SimplePie Website URL
+ */
+define('SIMPLEPIE_URL', 'http://simplepie.org');
+
+/**
+ * SimplePie Useragent
+ * @see SimplePie::set_useragent()
+ */
+define('SIMPLEPIE_USERAGENT', SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION . ' (Feed Parser; ' . SIMPLEPIE_URL . '; Allow like Gecko) Build/' . SIMPLEPIE_BUILD);
+
+/**
+ * SimplePie Linkback
+ */
+define('SIMPLEPIE_LINKBACK', '<a href="' . SIMPLEPIE_URL . '" title="' . SIMPLEPIE_NAME . ' ' . SIMPLEPIE_VERSION . '">' . SIMPLEPIE_NAME . '</a>');
+
+/**
+ * No Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_NONE', 0);
+
+/**
+ * Feed Link Element Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_AUTODISCOVERY', 1);
+
+/**
+ * Local Feed Extension Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_LOCAL_EXTENSION', 2);
+
+/**
+ * Local Feed Body Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_LOCAL_BODY', 4);
+
+/**
+ * Remote Feed Extension Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_REMOTE_EXTENSION', 8);
+
+/**
+ * Remote Feed Body Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_REMOTE_BODY', 16);
+
+/**
+ * All Feed Autodiscovery
+ * @see SimplePie::set_autodiscovery_level()
+ */
+define('SIMPLEPIE_LOCATOR_ALL', 31);
+
+/**
+ * No known feed type
+ */
+define('SIMPLEPIE_TYPE_NONE', 0);
+
+/**
+ * RSS 0.90
+ */
+define('SIMPLEPIE_TYPE_RSS_090', 1);
+
+/**
+ * RSS 0.91 (Netscape)
+ */
+define('SIMPLEPIE_TYPE_RSS_091_NETSCAPE', 2);
+
+/**
+ * RSS 0.91 (Userland)
+ */
+define('SIMPLEPIE_TYPE_RSS_091_USERLAND', 4);
+
+/**
+ * RSS 0.91 (both Netscape and Userland)
+ */
+define('SIMPLEPIE_TYPE_RSS_091', 6);
+
+/**
+ * RSS 0.92
+ */
+define('SIMPLEPIE_TYPE_RSS_092', 8);
+
+/**
+ * RSS 0.93
+ */
+define('SIMPLEPIE_TYPE_RSS_093', 16);
+
+/**
+ * RSS 0.94
+ */
+define('SIMPLEPIE_TYPE_RSS_094', 32);
+
+/**
+ * RSS 1.0
+ */
+define('SIMPLEPIE_TYPE_RSS_10', 64);
+
+/**
+ * RSS 2.0
+ */
+define('SIMPLEPIE_TYPE_RSS_20', 128);
+
+/**
+ * RDF-based RSS
+ */
+define('SIMPLEPIE_TYPE_RSS_RDF', 65);
+
+/**
+ * Non-RDF-based RSS (truly intended as syndication format)
+ */
+define('SIMPLEPIE_TYPE_RSS_SYNDICATION', 190);
+
+/**
+ * All RSS
+ */
+define('SIMPLEPIE_TYPE_RSS_ALL', 255);
+
+/**
+ * Atom 0.3
+ */
+define('SIMPLEPIE_TYPE_ATOM_03', 256);
+
+/**
+ * Atom 1.0
+ */
+define('SIMPLEPIE_TYPE_ATOM_10', 512);
+
+/**
+ * All Atom
+ */
+define('SIMPLEPIE_TYPE_ATOM_ALL', 768);
+
+/**
+ * All feed types
+ */
+define('SIMPLEPIE_TYPE_ALL', 1023);
+
+/**
+ * No construct
+ */
+define('SIMPLEPIE_CONSTRUCT_NONE', 0);
+
+/**
+ * Text construct
+ */
+define('SIMPLEPIE_CONSTRUCT_TEXT', 1);
+
+/**
+ * HTML construct
+ */
+define('SIMPLEPIE_CONSTRUCT_HTML', 2);
+
+/**
+ * XHTML construct
+ */
+define('SIMPLEPIE_CONSTRUCT_XHTML', 4);
+
+/**
+ * base64-encoded construct
+ */
+define('SIMPLEPIE_CONSTRUCT_BASE64', 8);
+
+/**
+ * IRI construct
+ */
+define('SIMPLEPIE_CONSTRUCT_IRI', 16);
+
+/**
+ * A construct that might be HTML
+ */
+define('SIMPLEPIE_CONSTRUCT_MAYBE_HTML', 32);
+
+/**
+ * All constructs
+ */
+define('SIMPLEPIE_CONSTRUCT_ALL', 63);
+
+/**
+ * Don't change case
+ */
+define('SIMPLEPIE_SAME_CASE', 1);
+
+/**
+ * Change to lowercase
+ */
+define('SIMPLEPIE_LOWERCASE', 2);
+
+/**
+ * Change to uppercase
+ */
+define('SIMPLEPIE_UPPERCASE', 4);
+
+/**
+ * PCRE for HTML attributes
+ */
+define('SIMPLEPIE_PCRE_HTML_ATTRIBUTE', '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*');
+
+/**
+ * PCRE for XML attributes
+ */
+define('SIMPLEPIE_PCRE_XML_ATTRIBUTE', '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*');
+
+/**
+ * XML Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_XML', 'http://www.w3.org/XML/1998/namespace');
+
+/**
+ * Atom 1.0 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_ATOM_10', 'http://www.w3.org/2005/Atom');
+
+/**
+ * Atom 0.3 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_ATOM_03', 'http://purl.org/atom/ns#');
+
+/**
+ * RDF Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');
+
+/**
+ * RSS 0.90 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_RSS_090', 'http://my.netscape.com/rdf/simple/0.9/');
+
+/**
+ * RSS 1.0 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_RSS_10', 'http://purl.org/rss/1.0/');
+
+/**
+ * RSS 1.0 Content Module Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT', 'http://purl.org/rss/1.0/modules/content/');
+
+/**
+ * RSS 2.0 Namespace
+ * (Stupid, I know, but I'm certain it will confuse people less with support.)
+ */
+define('SIMPLEPIE_NAMESPACE_RSS_20', '');
+
+/**
+ * DC 1.0 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_DC_10', 'http://purl.org/dc/elements/1.0/');
+
+/**
+ * DC 1.1 Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_DC_11', 'http://purl.org/dc/elements/1.1/');
+
+/**
+ * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO', 'http://www.w3.org/2003/01/geo/wgs84_pos#');
+
+/**
+ * GeoRSS Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_GEORSS', 'http://www.georss.org/georss');
+
+/**
+ * Media RSS Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_MEDIARSS', 'http://search.yahoo.com/mrss/');
+
+/**
+ * Wrong Media RSS Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG', 'http://search.yahoo.com/mrss');
+
+/**
+ * iTunes RSS Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_ITUNES', 'http://www.itunes.com/dtds/podcast-1.0.dtd');
+
+/**
+ * XHTML Namespace
+ */
+define('SIMPLEPIE_NAMESPACE_XHTML', 'http://www.w3.org/1999/xhtml');
+
+/**
+ * IANA Link Relations Registry
+ */
+define('SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY', 'http://www.iana.org/assignments/relation/');
+
+/**
+ * Whether we're running on PHP5
+ */
+define('SIMPLEPIE_PHP5', version_compare(PHP_VERSION, '5.0.0', '>='));
+
+/**
+ * No file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_NONE', 0);
+
+/**
+ * Remote file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_REMOTE', 1);
+
+/**
+ * Local file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_LOCAL', 2);
+
+/**
+ * fsockopen() file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_FSOCKOPEN', 4);
+
+/**
+ * cURL file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_CURL', 8);
+
+/**
+ * file_get_contents() file source
+ */
+define('SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS', 16);
+
+/**
+ * SimplePie
+ *
+ * @package SimplePie
+ */
+class SimplePie
+{
+	/**
+	 * @var array Raw data
+	 * @access private
+	 */
+	var $data = array();
+
+	/**
+	 * @var mixed Error string
+	 * @access private
+	 */
+	var $error;
+
+	/**
+	 * @var object Instance of SimplePie_Sanitize (or other class)
+	 * @see SimplePie::set_sanitize_class()
+	 * @access private
+	 */
+	var $sanitize;
+
+	/**
+	 * @var string SimplePie Useragent
+	 * @see SimplePie::set_useragent()
+	 * @access private
+	 */
+	var $useragent = SIMPLEPIE_USERAGENT;
+
+	/**
+	 * @var string Feed URL
+	 * @see SimplePie::set_feed_url()
+	 * @access private
+	 */
+	var $feed_url;
+
+	/**
+	 * @var object Instance of SimplePie_File to use as a feed
+	 * @see SimplePie::set_file()
+	 * @access private
+	 */
+	var $file;
+
+	/**
+	 * @var string Raw feed data
+	 * @see SimplePie::set_raw_data()
+	 * @access private
+	 */
+	var $raw_data;
+
+	/**
+	 * @var int Timeout for fetching remote files
+	 * @see SimplePie::set_timeout()
+	 * @access private
+	 */
+	var $timeout = 10;
+
+	/**
+	 * @var bool Forces fsockopen() to be used for remote files instead
+	 * of cURL, even if a new enough version is installed
+	 * @see SimplePie::force_fsockopen()
+	 * @access private
+	 */
+	var $force_fsockopen = false;
+
+	/**
+	 * @var bool Force the given data/URL to be treated as a feed no matter what
+	 * it appears like
+	 * @see SimplePie::force_feed()
+	 * @access private
+	 */
+	var $force_feed = false;
+
+	/**
+	 * @var bool Enable/Disable XML dump
+	 * @see SimplePie::enable_xml_dump()
+	 * @access private
+	 */
+	var $xml_dump = false;
+
+	/**
+	 * @var bool Enable/Disable Caching
+	 * @see SimplePie::enable_cache()
+	 * @access private
+	 */
+	var $cache = true;
+
+	/**
+	 * @var int Cache duration (in seconds)
+	 * @see SimplePie::set_cache_duration()
+	 * @access private
+	 */
+	var $cache_duration = 3600;
+
+	/**
+	 * @var int Auto-discovery cache duration (in seconds)
+	 * @see SimplePie::set_autodiscovery_cache_duration()
+	 * @access private
+	 */
+	var $autodiscovery_cache_duration = 604800; // 7 Days.
+
+	/**
+	 * @var string Cache location (relative to executing script)
+	 * @see SimplePie::set_cache_location()
+	 * @access private
+	 */
+	var $cache_location = './cache';
+
+	/**
+	 * @var string Function that creates the cache filename
+	 * @see SimplePie::set_cache_name_function()
+	 * @access private
+	 */
+	var $cache_name_function = 'md5';
+
+	/**
+	 * @var bool Reorder feed by date descending
+	 * @see SimplePie::enable_order_by_date()
+	 * @access private
+	 */
+	var $order_by_date = true;
+
+	/**
+	 * @var mixed Force input encoding to be set to the follow value
+	 * (false, or anything type-cast to false, disables this feature)
+	 * @see SimplePie::set_input_encoding()
+	 * @access private
+	 */
+	var $input_encoding = false;
+
+	/**
+	 * @var int Feed Autodiscovery Level
+	 * @see SimplePie::set_autodiscovery_level()
+	 * @access private
+	 */
+	var $autodiscovery = SIMPLEPIE_LOCATOR_ALL;
+
+	/**
+	 * @var string Class used for caching feeds
+	 * @see SimplePie::set_cache_class()
+	 * @access private
+	 */
+	var $cache_class = 'SimplePie_Cache';
+
+	/**
+	 * @var string Class used for locating feeds
+	 * @see SimplePie::set_locator_class()
+	 * @access private
+	 */
+	var $locator_class = 'SimplePie_Locator';
+
+	/**
+	 * @var string Class used for parsing feeds
+	 * @see SimplePie::set_parser_class()
+	 * @access private
+	 */
+	var $parser_class = 'SimplePie_Parser';
+
+	/**
+	 * @var string Class used for fetching feeds
+	 * @see SimplePie::set_file_class()
+	 * @access private
+	 */
+	var $file_class = 'SimplePie_File';
+
+	/**
+	 * @var string Class used for items
+	 * @see SimplePie::set_item_class()
+	 * @access private
+	 */
+	var $item_class = 'SimplePie_Item';
+
+	/**
+	 * @var string Class used for authors
+	 * @see SimplePie::set_author_class()
+	 * @access private
+	 */
+	var $author_class = 'SimplePie_Author';
+
+	/**
+	 * @var string Class used for categories
+	 * @see SimplePie::set_category_class()
+	 * @access private
+	 */
+	var $category_class = 'SimplePie_Category';
+
+	/**
+	 * @var string Class used for enclosures
+	 * @see SimplePie::set_enclosures_class()
+	 * @access private
+	 */
+	var $enclosure_class = 'SimplePie_Enclosure';
+
+	/**
+	 * @var string Class used for Media RSS <media:text> captions
+	 * @see SimplePie::set_caption_class()
+	 * @access private
+	 */
+	var $caption_class = 'SimplePie_Caption';
+
+	/**
+	 * @var string Class used for Media RSS <media:copyright>
+	 * @see SimplePie::set_copyright_class()
+	 * @access private
+	 */
+	var $copyright_class = 'SimplePie_Copyright';
+
+	/**
+	 * @var string Class used for Media RSS <media:credit>
+	 * @see SimplePie::set_credit_class()
+	 * @access private
+	 */
+	var $credit_class = 'SimplePie_Credit';
+
+	/**
+	 * @var string Class used for Media RSS <media:rating>
+	 * @see SimplePie::set_rating_class()
+	 * @access private
+	 */
+	var $rating_class = 'SimplePie_Rating';
+
+	/**
+	 * @var string Class used for Media RSS <media:restriction>
+	 * @see SimplePie::set_restriction_class()
+	 * @access private
+	 */
+	var $restriction_class = 'SimplePie_Restriction';
+
+	/**
+	 * @var string Class used for content-type sniffing
+	 * @see SimplePie::set_content_type_sniffer_class()
+	 * @access private
+	 */
+	var $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer';
+
+	/**
+	 * @var string Class used for item sources.
+	 * @see SimplePie::set_source_class()
+	 * @access private
+	 */
+	var $source_class = 'SimplePie_Source';
+
+	/**
+	 * @var mixed Set javascript query string parameter (false, or
+	 * anything type-cast to false, disables this feature)
+	 * @see SimplePie::set_javascript()
+	 * @access private
+	 */
+	var $javascript = 'js';
+
+	/**
+	 * @var int Maximum number of feeds to check with autodiscovery
+	 * @see SimplePie::set_max_checked_feeds()
+	 * @access private
+	 */
+	var $max_checked_feeds = 10;
+
+	/**
+	 * @var array All the feeds found during the autodiscovery process
+	 * @see SimplePie::get_all_discovered_feeds()
+	 * @access private
+	 */
+	var $all_discovered_feeds = array();
+
+	/**
+	 * @var string Web-accessible path to the handler_favicon.php file.
+	 * @see SimplePie::set_favicon_handler()
+	 * @access private
+	 */
+	var $favicon_handler = '';
+
+	/**
+	 * @var string Web-accessible path to the handler_image.php file.
+	 * @see SimplePie::set_image_handler()
+	 * @access private
+	 */
+	var $image_handler = '';
+
+	/**
+	 * @var array Stores the URLs when multiple feeds are being initialized.
+	 * @see SimplePie::set_feed_url()
+	 * @access private
+	 */
+	var $multifeed_url = array();
+
+	/**
+	 * @var array Stores SimplePie objects when multiple feeds initialized.
+	 * @access private
+	 */
+	var $multifeed_objects = array();
+
+	/**
+	 * @var array Stores the get_object_vars() array for use with multifeeds.
+	 * @see SimplePie::set_feed_url()
+	 * @access private
+	 */
+	var $config_settings = null;
+
+	/**
+	 * @var integer Stores the number of items to return per-feed with multifeeds.
+	 * @see SimplePie::set_item_limit()
+	 * @access private
+	 */
+	var $item_limit = 0;
+
+	/**
+	 * @var array Stores the default attributes to be stripped by strip_attributes().
+	 * @see SimplePie::strip_attributes()
+	 * @access private
+	 */
+	var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');
+
+	/**
+	 * @var array Stores the default tags to be stripped by strip_htmltags().
+	 * @see SimplePie::strip_htmltags()
+	 * @access private
+	 */
+	var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style');
+
+	/**
+	 * The SimplePie class contains feed level data and options
+	 *
+	 * There are two ways that you can create a new SimplePie object. The first
+	 * is by passing a feed URL as a parameter to the SimplePie constructor
+	 * (as well as optionally setting the cache location and cache expiry). This
+	 * will initialise the whole feed with all of the default settings, and you
+	 * can begin accessing methods and properties immediately.
+	 *
+	 * The second way is to create the SimplePie object with no parameters
+	 * at all. This will enable you to set configuration options. After setting
+	 * them, you must initialise the feed using $feed->init(). At that point the
+	 * object's methods and properties will be available to you. This format is
+	 * what is used throughout this documentation.
+	 *
+	 * @access public
+	 * @since 1.0 Preview Release
+	 * @param string $feed_url This is the URL you want to parse.
+	 * @param string $cache_location This is where you want the cache to be stored.
+	 * @param int $cache_duration This is the number of seconds that you want to store the cache file for.
+	 */
+	function SimplePie($feed_url = null, $cache_location = null, $cache_duration = null)
+	{
+		// Other objects, instances created here so we can set options on them
+		$this->sanitize = new SimplePie_Sanitize;
+
+		// Set options if they're passed to the constructor
+		if ($cache_location !== null)
+		{
+			$this->set_cache_location($cache_location);
+		}
+
+		if ($cache_duration !== null)
+		{
+			$this->set_cache_duration($cache_duration);
+		}
+
+		// Only init the script if we're passed a feed URL
+		if ($feed_url !== null)
+		{
+			$this->set_feed_url($feed_url);
+			$this->init();
+		}
+	}
+
+	/**
+	 * Used for converting object to a string
+	 */
+	function __toString()
+	{
+		return md5(serialize($this->data));
+	}
+
+	/**
+	 * Remove items that link back to this before destroying this object
+	 */
+	function __destruct()
+	{
+		if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode'))
+		{
+			if (!empty($this->data['items']))
+			{
+				foreach ($this->data['items'] as $item)
+				{
+					$item->__destruct();
+				}
+				unset($item, $this->data['items']);
+			}
+			if (!empty($this->data['ordered_items']))
+			{
+				foreach ($this->data['ordered_items'] as $item)
+				{
+					$item->__destruct();
+				}
+				unset($item, $this->data['ordered_items']);
+			}
+		}
+	}
+
+	/**
+	 * Force the given data/URL to be treated as a feed no matter what it
+	 * appears like
+	 *
+	 * @access public
+	 * @since 1.1
+	 * @param bool $enable Force the given data/URL to be treated as a feed
+	 */
+	function force_feed($enable = false)
+	{
+		$this->force_feed = (bool) $enable;
+	}
+
+	/**
+	 * This is the URL of the feed you want to parse.
+	 *
+	 * This allows you to enter the URL of the feed you want to parse, or the
+	 * website you want to try to use auto-discovery on. This takes priority
+	 * over any set raw data.
+	 *
+	 * You can set multiple feeds to mash together by passing an array instead
+	 * of a string for the $url. Remember that with each additional feed comes
+	 * additional processing and resources.
+	 *
+	 * @access public
+	 * @since 1.0 Preview Release
+	 * @param mixed $url This is the URL (or array of URLs) that you want to parse.
+	 * @see SimplePie::set_raw_data()
+	 */
+	function set_feed_url($url)
+	{
+		if (is_array($url))
+		{
+			$this->multifeed_url = array();
+			foreach ($url as $value)
+			{
+				$this->multifeed_url[] = SimplePie_Misc::fix_protocol($value, 1);
+			}
+		}
+		else
+		{
+			$this->feed_url = SimplePie_Misc::fix_protocol($url, 1);
+		}
+	}
+
+	/**
+	 * Provides an instance of SimplePie_File to use as a feed
+	 *
+	 * @access public
+	 * @param object &$file Instance of SimplePie_File (or subclass)
+	 * @return bool True on success, false on failure
+	 */
+	function set_file(&$file)
+	{
+		if (is_a($file, 'SimplePie_File'))
+		{
+			$this->feed_url = $file->url;
+			$this->file =& $file;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to use a string of RSS/Atom data instead of a remote feed.
+	 *
+	 * If you have a feed available as a string in PHP, you can tell SimplePie
+	 * to parse that data string instead of a remote feed. Any set feed URL
+	 * takes precedence.
+	 *
+	 * @access public
+	 * @since 1.0 Beta 3
+	 * @param string $data RSS or Atom data as a string.
+	 * @see SimplePie::set_feed_url()
+	 */
+	function set_raw_data($data)
+	{
+		$this->raw_data = $data;
+	}
+
+	/**
+	 * Allows you to override the default timeout for fetching remote feeds.
+	 *
+	 * This allows you to change the maximum time the feed's server to respond
+	 * and send the feed back.
+	 *
+	 * @access public
+	 * @since 1.0 Beta 3
+	 * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed.
+	 */
+	function set_timeout($timeout = 10)
+	{
+		$this->timeout = (int) $timeout;
+	}
+
+	/**
+	 * Forces SimplePie to use fsockopen() instead of the preferred cURL
+	 * functions.
+	 *
+	 * @access public
+	 * @since 1.0 Beta 3
+	 * @param bool $enable Force fsockopen() to be used
+	 */
+	function force_fsockopen($enable = false)
+	{
+		$this->force_fsockopen = (bool) $enable;
+	}
+
+	/**
+	 * Outputs the raw XML content of the feed, after it has gone through
+	 * SimplePie's filters.
+	 *
+	 * Used only for debugging, this function will output the XML content as
+	 * text/xml. When SimplePie reads in a feed, it does a bit of cleaning up
+	 * before trying to parse it. Many parts of the feed are re-written in
+	 * memory, and in the end, you have a parsable feed. XML dump shows you the
+	 * actual XML that SimplePie tries to parse, which may or may not be very
+	 * different from the original feed.
+	 *
+	 * @access public
+	 * @since 1.0 Preview Release
+	 * @param bool $enable Enable XML dump
+	 */
+	function enable_xml_dump($enable = false)
+	{
+		$this->xml_dump = (bool) $enable;
+	}
+
+	/**
+	 * Enables/disables caching in SimplePie.
+	 *
+	 * This option allows you to disable caching all-together in SimplePie.
+	 * However, disabling the cache can lead to longer load times.
+	 *
+	 * @access public
+	 * @since 1.0 Preview Release
+	 * @param bool $enable Enable caching
+	 */
+	function enable_cache($enable = true)
+	{
+		$this->cache = (bool) $enable;
+	}
+
+	/**
+	 * Set the length of time (in seconds) that the contents of a feed
+	 * will be cached.
+	 *
+	 * @access public
+	 * @param int $seconds The feed content cache duration.
+	 */
+	function set_cache_duration($seconds = 3600)
+	{
+		$this->cache_duration = (int) $seconds;
+	}
+
+	/**
+	 * Set the length of time (in seconds) that the autodiscovered feed
+	 * URL will be cached.
+	 *
+	 * @access public
+	 * @param int $seconds The autodiscovered feed URL cache duration.
+	 */
+	function set_autodiscovery_cache_duration($seconds = 604800)
+	{
+		$this->autodiscovery_cache_duration = (int) $seconds;
+	}
+
+	/**
+	 * Set the file system location where the cached files should be stored.
+	 *
+	 * @access public
+	 * @param string $location The file system location.
+	 */
+	function set_cache_location($location = './cache')
+	{
+		$this->cache_location = (string) $location;
+	}
+
+	/**
+	 * Determines whether feed items should be sorted into reverse chronological order.
+	 *
+	 * @access public
+	 * @param bool $enable Sort as reverse chronological order.
+	 */
+	function enable_order_by_date($enable = true)
+	{
+		$this->order_by_date = (bool) $enable;
+	}
+
+	/**
+	 * Allows you to override the character encoding reported by the feed.
+	 *
+	 * @access public
+	 * @param string $encoding Character encoding.
+	 */
+	function set_input_encoding($encoding = false)
+	{
+		if ($encoding)
+		{
+			$this->input_encoding = (string) $encoding;
+		}
+		else
+		{
+			$this->input_encoding = false;
+		}
+	}
+
+	/**
+	 * Set how much feed autodiscovery to do
+	 *
+	 * @access public
+	 * @see SIMPLEPIE_LOCATOR_NONE
+	 * @see SIMPLEPIE_LOCATOR_AUTODISCOVERY
+	 * @see SIMPLEPIE_LOCATOR_LOCAL_EXTENSION
+	 * @see SIMPLEPIE_LOCATOR_LOCAL_BODY
+	 * @see SIMPLEPIE_LOCATOR_REMOTE_EXTENSION
+	 * @see SIMPLEPIE_LOCATOR_REMOTE_BODY
+	 * @see SIMPLEPIE_LOCATOR_ALL
+	 * @param int $level Feed Autodiscovery Level (level can be a
+	 * combination of the above constants, see bitwise OR operator)
+	 */
+	function set_autodiscovery_level($level = SIMPLEPIE_LOCATOR_ALL)
+	{
+		$this->autodiscovery = (int) $level;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for caching.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_cache_class($class = 'SimplePie_Cache')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Cache'))
+		{
+			$this->cache_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for auto-discovery.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_locator_class($class = 'SimplePie_Locator')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Locator'))
+		{
+			$this->locator_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for XML parsing.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_parser_class($class = 'SimplePie_Parser')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Parser'))
+		{
+			$this->parser_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for remote file fetching.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_file_class($class = 'SimplePie_File')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_File'))
+		{
+			$this->file_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for data sanitization.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_sanitize_class($class = 'SimplePie_Sanitize')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Sanitize'))
+		{
+			$this->sanitize = new $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for handling feed items.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_item_class($class = 'SimplePie_Item')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Item'))
+		{
+			$this->item_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for handling author data.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_author_class($class = 'SimplePie_Author')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Author'))
+		{
+			$this->author_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for handling category data.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_category_class($class = 'SimplePie_Category')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Category'))
+		{
+			$this->category_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for feed enclosures.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_enclosure_class($class = 'SimplePie_Enclosure')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Enclosure'))
+		{
+			$this->enclosure_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:text> captions
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_caption_class($class = 'SimplePie_Caption')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Caption'))
+		{
+			$this->caption_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:copyright>
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_copyright_class($class = 'SimplePie_Copyright')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Copyright'))
+		{
+			$this->copyright_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:credit>
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_credit_class($class = 'SimplePie_Credit')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Credit'))
+		{
+			$this->credit_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:rating>
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_rating_class($class = 'SimplePie_Rating')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Rating'))
+		{
+			$this->rating_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for <media:restriction>
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_restriction_class($class = 'SimplePie_Restriction')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Restriction'))
+		{
+			$this->restriction_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses for content-type sniffing.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_content_type_sniffer_class($class = 'SimplePie_Content_Type_Sniffer')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Content_Type_Sniffer'))
+		{
+			$this->content_type_sniffer_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to change which class SimplePie uses item sources.
+	 * Useful when you are overloading or extending SimplePie's default classes.
+	 *
+	 * @access public
+	 * @param string $class Name of custom class.
+	 * @link http://php.net/manual/en/keyword.extends.php PHP4 extends documentation
+	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
+	 */
+	function set_source_class($class = 'SimplePie_Source')
+	{
+		if (SimplePie_Misc::is_subclass_of($class, 'SimplePie_Source'))
+		{
+			$this->source_class = $class;
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * Allows you to override the default user agent string.
+	 *
+	 * @access public
+	 * @param string $ua New user agent string.
+	 */
+	function set_useragent($ua = SIMPLEPIE_USERAGENT)
+	{
+		$this->useragent = (string) $ua;
+	}
+
+	/**
+	 * Set callback function to create cache filename with
+	 *
+	 * @access public
+	 * @param mixed $function Callback function
+	 */
+	function set_cache_name_function($function = 'md5')
+	{
+		if (is_callable($function))
+		{
+			$this->cache_name_function = $function;
+		}
+	}
+
+	/**
+	 * Set javascript query string parameter
+	 *
+	 * @access public
+	 * @param mixed $get Javascript query string parameter
+	 */
+	function set_javascript($get = 'js')
+	{
+		if ($get)
+		{
+			$this->javascript = (string) $get;
+		}
+		else
+		{
+			$this->javascript = false;
+		}
+	}
+
+	/**
+	 * Set options to make SP as fast as possible.  Forgoes a
+	 * substantial amount of data sanitization in favor of speed.
+	 *
+	 * @access public
+	 * @param bool $set Whether to set them or not
+	 */
+	function set_stupidly_fast($set = false)
+	{
+		if ($set)
+		{
+			$this->enable_order_by_date(false);
+			$this->remove_div(false);
+			$this->strip_comments(false);
+			$this->strip_htmltags(false);
+			$this->strip_attributes(false);
+			$this->set_image_handler(false);
+		}
+	}
+
+	/**
+	 * Set maximum number of feeds to check with autodiscovery
+	 *
+	 * @access public
+	 * @param int $max Maximum number of feeds to check
+	 */
+	function set_max_checked_feeds($max = 10)
+	{
+		$this->max_checked_feeds = (int) $max;
+	}
+
+	function remove_div($enable = true)
+	{
+		$this->sanitize->remove_div($enable);
+	}
+
+	function strip_htmltags($tags = '', $encode = null)
+	{
+		if ($tags === '')
+		{
+			$tags = $this->strip_htmltags;
+		}
+		$this->sanitize->strip_htmltags($tags);
+		if ($encode !== null)
+		{
+			$this->sanitize->encode_instead_of_strip($tags);
+		}
+	}
+
+	function encode_instead_of_strip($enable = true)
+	{
+		$this->sanitize->encode_instead_of_strip($enable);
+	}
+
+	function strip_attributes($attribs = '')
+	{
+		if ($attribs === '')
+		{
+			$attribs = $this->strip_attributes;
+		}
+		$this->sanitize->strip_attributes($attribs);
+	}
+
+	function set_output_encoding($encoding = 'UTF-8')
+	{
+		$this->sanitize->set_output_encoding($encoding);
+	}
+
+	function strip_comments($strip = false)
+	{
+		$this->sanitize->strip_comments($strip);
+	}
+
+	/**
+	 * Set element/attribute key/value pairs of HTML attributes
+	 * containing URLs that need to be resolved relative to the feed
+	 *
+	 * @access public
+	 * @since 1.0
+	 * @param array $element_attribute Element/attribute key/value pairs
+	 */
+	function set_url_replacements($element_attribute = array('a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite'))
+	{
+		$this->sanitize->set_url_replacements($element_attribute);
+	}
+
+	/**
+	 * Set the handler to enable the display of cached favicons.
+	 *
+	 * @access public
+	 * @param str $page Web-accessible path to the handler_favicon.php file.
+	 * @param str $qs The query string that the value should be passed to.
+	 */
+	function set_favicon_handler($page = false, $qs = 'i')
+	{
+		if ($page !== false)
+		{
+			$this->favicon_handler = $page . '?' . $qs . '=';
+		}
+		else
+		{
+			$this->favicon_handler = '';
+		}
+	}
+
+	/**
+	 * Set the handler to enable the display of cached images.
+	 *
+	 * @access public
+	 * @param str $page Web-accessible path to the handler_image.php file.
+	 * @param str $qs The query string that the value should be passed to.
+	 */
+	function set_image_handler($page = false, $qs = 'i')
+	{
+		if ($page !== false)
+		{
+			$this->sanitize->set_image_handler($page . '?' . $qs . '=');
+		}
+		else
+		{
+			$this->image_handler = '';
+		}
+	}
+
+	/**
+	 * Set the limit for items returned per-feed with multifeeds.
+	 *
+	 * @access public
+	 * @param integer $limit The maximum number of items to return.
+	 */
+	function set_item_limit($limit = 0)
+	{
+		$this->item_limit = (int) $limit;
+	}
+
+	function init()
+	{
+		// Check absolute bare minimum requirements.
+		if ((function_exists('version_compare') && version_compare(PHP_VERSION, '4.3.0', '<')) || !extension_loaded('xml') || !extension_loaded('pcre'))
+		{
+			return false;
+		}
+		// Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader.
+		elseif (!extension_loaded('xmlreader'))
+		{
+			static $xml_is_sane = null;
+			if ($xml_is_sane === null)
+			{
+				$parser_check = xml_parser_create();
+				xml_parse_into_struct($parser_check, '<foo>&</foo>', $values);
+				xml_parser_free($parser_check);
+				$xml_is_sane = isset($values[0]['value']);
+			}
+			if (!$xml_is_sane)
+			{
+				return false;
+			}
+		}
+
+		if (isset($_GET[$this->javascript]))
+		{
+			SimplePie_Misc::output_javascript();
+			exit;
+		}
+
+		// Pass whatever was set with config options over to the sanitizer.
+		$this->sanitize->pass_cache_data($this->cache, $this->cache_location, $this->cache_name_function, $this->cache_class);
+		$this->sanitize->pass_file_data($this->file_class, $this->timeout, $this->useragent, $this->force_fsockopen);
+
+		if ($this->feed_url !== null || $this->raw_data !== null)
+		{
+			$this->data = array();
+			$this->multifeed_objects = array();
+			$cache = false;
+
+			if ($this->feed_url !== null)
+			{
+				$parsed_feed_url = SimplePie_Misc::parse_url($this->feed_url);
+				// Decide whether to enable caching
+				if ($this->cache && $parsed_feed_url['scheme'] !== '')
+				{
+					$cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $this->feed_url), 'spc');
+				}
+				// If it's enabled and we don't want an XML dump, use the cache
+				if ($cache && !$this->xml_dump)
+				{
+					// Load the Cache
+					$this->data = $cache->load();
+					if (!empty($this->data))
+					{
+						// If the cache is for an outdated build of SimplePie
+						if (!isset($this->data['build']) || $this->data['build'] !== SIMPLEPIE_BUILD)
+						{
+							$cache->unlink();
+							$this->data = array();
+						}
+						// If we've hit a collision just rerun it with caching disabled
+						elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url)
+						{
+							$cache = false;
+							$this->data = array();
+						}
+						// If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL.
+						elseif (isset($this->data['feed_url']))
+						{
+							// If the autodiscovery cache is still valid use it.
+							if ($cache->mtime() + $this->autodiscovery_cache_duration > time())
+							{
+								// Do not need to do feed autodiscovery yet.
+								if ($this->data['feed_url'] === $this->data['url'])
+								{
+									$cache->unlink();
+									$this->data = array();
+								}
+								else
+								{
+									$this->set_feed_url($this->data['feed_url']);
+									return $this->init();
+								}
+							}
+						}
+						// Check if the cache has been updated
+						elseif ($cache->mtime() + $this->cache_duration < time())
+						{
+							// If we have last-modified and/or etag set
+							if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag']))
+							{
+								$headers = array();
+								if (isset($this->data['headers']['last-modified']))
+								{
+									$headers['if-modified-since'] = $this->data['headers']['last-modified'];
+								}
+								if (isset($this->data['headers']['etag']))
+								{
+									$headers['if-none-match'] = '"' . $this->data['headers']['etag'] . '"';
+								}
+								$file = new $this->file_class($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen);
+								if ($file->success)
+								{
+									if ($file->status_code === 304)
+									{
+										$cache->touch();
+										return true;
+									}
+									else
+									{
+										$headers = $file->headers;
+									}
+								}
+								else
+								{
+									unset($file);
+								}
+							}
+						}
+						// If the cache is still valid, just return true
+						else
+						{
+							return true;
+						}
+					}
+					// If the cache is empty, delete it
+					else
+					{
+						$cache->unlink();
+						$this->data = array();
+					}
+				}
+				// If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it.
+				if (!isset($file))
+				{
+					if (is_a($this->file, 'SimplePie_File') && $this->file->url === $this->feed_url)
+					{
+						$file =& $this->file;
+					}
+					else
+					{
+						$file = new $this->file_class($this->feed_url, $this->timeout, 5, null, $this->useragent, $this->force_fsockopen);
+					}
+				}
+				// If the file connection has an error, set SimplePie::error to that and quit
+				if (!$file->success && !($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)))
+				{
+					$this->error = $file->error;
+					if (!empty($this->data))
+					{
+						return true;
+					}
+					else
+					{
+						return false;
+					}
+				}
+
+				if (!$this->force_feed)
+				{
+					// Check if the supplied URL is a feed, if it isn't, look for it.
+					$locate = new $this->locator_class($file, $this->timeout, $this->useragent, $this->file_class, $this->max_checked_feeds, $this->content_type_sniffer_class);
+					if (!$locate->is_feed($file))
+					{
+						// We need to unset this so that if SimplePie::set_file() has been called that object is untouched
+						unset($file);
+						if ($file = $locate->find($this->autodiscovery, $this->all_discovered_feeds))
+						{
+							if ($cache)
+							{
+								$this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD);
+								if (!$cache->save($this))
+								{
+									trigger_error("$this->cache_location is not writeable", E_USER_WARNING);
+								}
+								$cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, call_user_func($this->cache_name_function, $file->url), 'spc');
+							}
+							$this->feed_url = $file->url;
+						}
+						else
+						{
+							$this->error = "A feed could not be found at $this->feed_url";
+							SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__);
+							return false;
+						}
+					}
+					$locate = null;
+				}
+
+				$headers = $file->headers;
+				$data = $file->body;
+				$sniffer = new $this->content_type_sniffer_class($file);
+				$sniffed = $sniffer->get_type();
+			}
+			else
+			{
+				$data = $this->raw_data;
+			}
+
+			// Set up array of possible encodings
+			$encodings = array();
+
+			// First check to see if input has been overridden.
+			if ($this->input_encoding !== false)
+			{
+				$encodings[] = $this->input_encoding;
+			}
+
+			$application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity');
+			$text_types = array('text/xml', 'text/xml-external-parsed-entity');
+
+			// RFC 3023 (only applies to sniffed content)
+			if (isset($sniffed))
+			{
+				if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml')
+				{
+					if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset))
+					{
+						$encodings[] = strtoupper($charset[1]);
+					}
+					$encodings = array_merge($encodings, SimplePie_Misc::xml_encoding($data));
+					$encodings[] = 'UTF-8';
+				}
+				elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml')
+				{
+					if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset))
+					{
+						$encodings[] = $charset[1];
+					}
+					$encodings[] = 'US-ASCII';
+				}
+				// Text MIME-type default
+				elseif (substr($sniffed, 0, 5) === 'text/')
+				{
+					$encodings[] = 'US-ASCII';
+				}
+			}
+
+			// Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1
+			$encodings = array_merge($encodings, SimplePie_Misc::xml_encoding($data));
+			$encodings[] = 'UTF-8';
+			$encodings[] = 'ISO-8859-1';
+
+			// There's no point in trying an encoding twice
+			$encodings = array_unique($encodings);
+
+			// If we want the XML, just output that with the most likely encoding and quit
+			if ($this->xml_dump)
+			{
+				header('Content-type: text/xml; charset=' . $encodings[0]);
+				echo $data;
+				exit;
+			}
+
+			// Loop through each possible encoding, till we return something, or run out of possibilities
+			foreach ($encodings as $encoding)
+			{
+				// Change the encoding to UTF-8 (as we always use UTF-8 internally)
+				if ($utf8_data = SimplePie_Misc::change_encoding($data, $encoding, 'UTF-8'))
+				{
+					// Create new parser
+					$parser = new $this->parser_class();
+
+					// If it's parsed fine
+					if ($parser->parse($utf8_data, 'UTF-8'))
+					{
+						$this->data = $parser->get_data();
+						if ($this->get_type() & ~SIMPLEPIE_TYPE_NONE)
+						{
+							if (isset($headers))
+							{
+								$this->data['headers'] = $headers;
+							}
+							$this->data['build'] = SIMPLEPIE_BUILD;
+
+							// Cache the file if caching is enabled
+							if ($cache && !$cache->save($this))
+							{
+								trigger_error("$cache->name is not writeable", E_USER_WARNING);
+							}
+							return true;
+						}
+						else
+						{
+							$this->error = "A feed could not be found at $this->feed_url";
+							SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__);
+							return false;
+						}
+					}
+				}
+			}
+			if(isset($parser))
+			{
+				// We have an error, just set SimplePie_Misc::error to it and quit
+				$this->error = sprintf('XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column());
+			}
+			else
+			{
+				$this->error = 'The data could not be converted to UTF-8';
+			}
+			SimplePie_Misc::error($this->error, E_USER_NOTICE, __FILE__, __LINE__);
+			return false;
+		}
+		elseif (!empty($this->multifeed_url))
+		{
+			$i = 0;
+			$success = 0;
+			$this->multifeed_objects = array();
+			foreach ($this->multifeed_url as $url)
+			{
+				if (SIMPLEPIE_PHP5)
+				{
+					// This keyword needs to defy coding standards for PHP4 compatibility
+					$this->multifeed_objects[$i] = clone($this);
+				}
+				else
+				{
+					$this->multifeed_objects[$i] = $this;
+				}
+				$this->multifeed_objects[$i]->set_feed_url($url);
+				$success |= $this->multifeed_objects[$i]->init();
+				$i++;
+			}
+			return (bool) $success;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Return the error message for the occured error
+	 *
+	 * @access public
+	 * @return string Error message
+	 */
+	function error()
+	{
+		return $this->error;
+	}
+
+	function get_encoding()
+	{
+		return $this->sanitize->output_encoding;
+	}
+
+	function handle_content_type($mime = 'text/html')
+	{
+		if (!headers_sent())
+		{
+			$header = "Content-type: $mime;";
+			if ($this->get_encoding())
+			{
+				$header .= ' charset=' . $this->get_encoding();
+			}
+			else
+			{
+				$header .= ' charset=UTF-8';
+			}
+			header($header);
+		}
+	}
+
+	function get_type()
+	{
+		if (!isset($this->data['type']))
+		{
+			$this->data['type'] = SIMPLEPIE_TYPE_ALL;
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed']))
+			{
+				$this->data['type'] &= SIMPLEPIE_TYPE_ATOM_10;
+			}
+			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed']))
+			{
+				$this->data['type'] &= SIMPLEPIE_TYPE_ATOM_03;
+			}
+			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF']))
+			{
+				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['channel'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['image'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['textinput']))
+				{
+					$this->data['type'] &= SIMPLEPIE_TYPE_RSS_10;
+				}
+				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['channel'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['image'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item'])
+				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['textinput']))
+				{
+					$this->data['type'] &= SIMPLEPIE_TYPE_RSS_090;
+				}
+			}
+			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss']))
+			{
+				$this->data['type'] &= SIMPLEPIE_TYPE_RSS_ALL;
+				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version']))
+				{
+					switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version']))
+					{
+						case '0.91':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091;
+							if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data']))
+							{
+								switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data']))
+								{
+									case '0':
+										$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_NETSCAPE;
+										break;
+
+									case '24':
+										$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_USERLAND;
+										break;
+								}
+							}
+							break;
+
+						case '0.92':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_092;
+							break;
+
+						case '0.93':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_093;
+							break;
+
+						case '0.94':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_094;
+							break;
+
+						case '2.0':
+							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_20;
+							break;
+					}
+				}
+			}
+			else
+			{
+				$this->data['type'] = SIMPLEPIE_TYPE_NONE;
+			}
+		}
+		return $this->data['type'];
+	}
+
+	/**
+	 * Returns the URL for the favicon of the feed's website.
+	 *
+	 * @todo Cache atom:icon
+	 * @access public
+	 * @since 1.0
+	 */
+	function get_favicon()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif (($url = $this->get_link()) !== null && preg_match('/^http(s)?:\/\//i', $url))
+		{
+			$favicon = SimplePie_Misc::absolutize_url('/favicon.ico', $url);
+
+			if ($this->cache && $this->favicon_handler)
+			{
+				$favicon_filename = call_user_func($this->cache_name_function, $favicon);
+				$cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, $favicon_filename, 'spi');
+
+				if ($cache->load())
+				{
+					return $this->sanitize($this->favicon_handler . $favicon_filename, SIMPLEPIE_CONSTRUCT_IRI);
+				}
+				else
+				{
+					$file = new $this->file_class($favicon, $this->timeout / 10, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen);
+
+					if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)) && strlen($file->body) > 0)
+					{
+						$sniffer = new $this->content_type_sniffer_class($file);
+						if (substr($sniffer->get_type(), 0, 6) === 'image/')
+						{
+							if ($cache->save(array('headers' => $file->headers, 'body' => $file->body)))
+							{
+								return $this->sanitize($this->favicon_handler . $favicon_filename, SIMPLEPIE_CONSTRUCT_IRI);
+							}
+							else
+							{
+								trigger_error("$cache->name is not writeable", E_USER_WARNING);
+								return $this->sanitize($favicon, SIMPLEPIE_CONSTRUCT_IRI);
+							}
+						}
+						// not an image
+						else
+						{
+							return false;
+						}
+					}
+				}
+			}
+			else
+			{
+				return $this->sanitize($favicon, SIMPLEPIE_CONSTRUCT_IRI);
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * @todo If we have a perm redirect we should return the new URL
+	 * @todo When we make the above change, let's support <itunes:new-feed-url> as well
+	 * @todo Also, |atom:link|@rel=self
+	 */
+	function subscribe_url()
+	{
+		if ($this->feed_url !== null)
+		{
+			return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function subscribe_feed()
+	{
+		if ($this->feed_url !== null)
+		{
+			return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 2), SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function subscribe_outlook()
+	{
+		if ($this->feed_url !== null)
+		{
+			return $this->sanitize('outlook' . SimplePie_Misc::fix_protocol($this->feed_url, 2), SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function subscribe_podcast()
+	{
+		if ($this->feed_url !== null)
+		{
+			return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 3), SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function subscribe_itunes()
+	{
+		if ($this->feed_url !== null)
+		{
+			return $this->sanitize(SimplePie_Misc::fix_protocol($this->feed_url, 4), SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Creates the subscribe_* methods' return data
+	 *
+	 * @access private
+	 * @param string $feed_url String to prefix to the feed URL
+	 * @param string $site_url String to prefix to the site URL (and
+	 * suffix to the feed URL)
+	 * @return mixed URL if feed exists, false otherwise
+	 */
+	function subscribe_service($feed_url, $site_url = null)
+	{
+		if ($this->subscribe_url())
+		{
+			$return = $feed_url . rawurlencode($this->feed_url);
+			if ($site_url !== null && $this->get_link() !== null)
+			{
+				$return .= $site_url . rawurlencode($this->get_link());
+			}
+			return $this->sanitize($return, SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function subscribe_aol()
+	{
+		return $this->subscribe_service('http://feeds.my.aol.com/add.jsp?url=');
+	}
+
+	function subscribe_bloglines()
+	{
+		return $this->subscribe_service('http://www.bloglines.com/sub/');
+	}
+
+	function subscribe_eskobo()
+	{
+		return $this->subscribe_service('http://www.eskobo.com/?AddToMyPage=');
+	}
+
+	function subscribe_feedfeeds()
+	{
+		return $this->subscribe_service('http://www.feedfeeds.com/add?feed=');
+	}
+
+	function subscribe_feedster()
+	{
+		return $this->subscribe_service('http://www.feedster.com/myfeedster.php?action=addrss&confirm=no&rssurl=');
+	}
+
+	function subscribe_google()
+	{
+		return $this->subscribe_service('http://fusion.google.com/add?feedurl=');
+	}
+
+	function subscribe_gritwire()
+	{
+		return $this->subscribe_service('http://my.gritwire.com/feeds/addExternalFeed.aspx?FeedUrl=');
+	}
+
+	function subscribe_msn()
+	{
+		return $this->subscribe_service('http://my.msn.com/addtomymsn.armx?id=rss&ut=', '&ru=');
+	}
+
+	function subscribe_netvibes()
+	{
+		return $this->subscribe_service('http://www.netvibes.com/subscribe.php?url=');
+	}
+
+	function subscribe_newsburst()
+	{
+		return $this->subscribe_service('http://www.newsburst.com/Source/?add=');
+	}
+
+	function subscribe_newsgator()
+	{
+		return $this->subscribe_service('http://www.newsgator.com/ngs/subscriber/subext.aspx?url=');
+	}
+
+	function subscribe_odeo()
+	{
+		return $this->subscribe_service('http://www.odeo.com/listen/subscribe?feed=');
+	}
+
+	function subscribe_podnova()
+	{
+		return $this->subscribe_service('http://www.podnova.com/index_your_podcasts.srf?action=add&url=');
+	}
+
+	function subscribe_rojo()
+	{
+		return $this->subscribe_service('http://www.rojo.com/add-subscription?resource=');
+	}
+
+	function subscribe_yahoo()
+	{
+		return $this->subscribe_service('http://add.my.yahoo.com/rss?url=');
+	}
+
+	function get_feed_tags($namespace, $tag)
+	{
+		$type = $this->get_type();
+		if ($type & SIMPLEPIE_TYPE_ATOM_10)
+		{
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag]))
+			{
+				return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag];
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_ATOM_03)
+		{
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag]))
+			{
+				return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag];
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_RDF)
+		{
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag]))
+			{
+				return $this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag];
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
+		{
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag]))
+			{
+				return $this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag];
+			}
+		}
+		return null;
+	}
+
+	function get_channel_tags($namespace, $tag)
+	{
+		$type = $this->get_type();
+		if ($type & SIMPLEPIE_TYPE_ATOM_ALL)
+		{
+			if ($return = $this->get_feed_tags($namespace, $tag))
+			{
+				return $return;
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_10)
+		{
+			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'channel'))
+			{
+				if (isset($channel[0]['child'][$namespace][$tag]))
+				{
+					return $channel[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_090)
+		{
+			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'channel'))
+			{
+				if (isset($channel[0]['child'][$namespace][$tag]))
+				{
+					return $channel[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
+		{
+			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'channel'))
+			{
+				if (isset($channel[0]['child'][$namespace][$tag]))
+				{
+					return $channel[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		return null;
+	}
+
+	function get_image_tags($namespace, $tag)
+	{
+		$type = $this->get_type();
+		if ($type & SIMPLEPIE_TYPE_RSS_10)
+		{
+			if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'image'))
+			{
+				if (isset($image[0]['child'][$namespace][$tag]))
+				{
+					return $image[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_090)
+		{
+			if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'image'))
+			{
+				if (isset($image[0]['child'][$namespace][$tag]))
+				{
+					return $image[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
+		{
+			if ($image = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'image'))
+			{
+				if (isset($image[0]['child'][$namespace][$tag]))
+				{
+					return $image[0]['child'][$namespace][$tag];
+				}
+			}
+		}
+		return null;
+	}
+
+	function get_base($element = array())
+	{
+		if (!($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION) && !empty($element['xml_base_explicit']) && isset($element['xml_base']))
+		{
+			return $element['xml_base'];
+		}
+		elseif ($this->get_link() !== null)
+		{
+			return $this->get_link();
+		}
+		else
+		{
+			return $this->subscribe_url();
+		}
+	}
+
+	function sanitize($data, $type, $base = '')
+	{
+		return $this->sanitize->sanitize($data, $type, $base);
+	}
+
+	function get_title()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_category($key = 0)
+	{
+		$categories = $this->get_categories();
+		if (isset($categories[$key]))
+		{
+			return $categories[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_categories()
+	{
+		$categories = array();
+
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
+		{
+			$term = null;
+			$scheme = null;
+			$label = null;
+			if (isset($category['attribs']['']['term']))
+			{
+				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['scheme']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['label']))
+			{
+				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			$categories[] = new $this->category_class($term, $scheme, $label);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
+		{
+			// This is really the label, but keep this as the term also for BC.
+			// Label will also work on retrieving because that falls back to term.
+			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			if (isset($category['attribs']['']['domain']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			else
+			{
+				$scheme = null;
+			}
+			$categories[] = new $this->category_class($term, $scheme, null);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
+		{
+			$categories[] = new $this->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
+		{
+			$categories[] = new $this->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($categories))
+		{
+			return SimplePie_Misc::array_unique($categories);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_author($key = 0)
+	{
+		$authors = $this->get_authors();
+		if (isset($authors[$key]))
+		{
+			return $authors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_authors()
+	{
+		$authors = array();
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$authors[] = new $this->author_class($name, $uri, $email);
+			}
+		}
+		if ($author = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$authors[] = new $this->author_class($name, $url, $email);
+			}
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
+		{
+			$authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
+		{
+			$authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
+		{
+			$authors[] = new $this->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($authors))
+		{
+			return SimplePie_Misc::array_unique($authors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_contributor($key = 0)
+	{
+		$contributors = $this->get_contributors();
+		if (isset($contributors[$key]))
+		{
+			return $contributors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_contributors()
+	{
+		$contributors = array();
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$contributors[] = new $this->author_class($name, $uri, $email);
+			}
+		}
+		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$contributors[] = new $this->author_class($name, $url, $email);
+			}
+		}
+
+		if (!empty($contributors))
+		{
+			return SimplePie_Misc::array_unique($contributors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_link($key = 0, $rel = 'alternate')
+	{
+		$links = $this->get_links($rel);
+		if (isset($links[$key]))
+		{
+			return $links[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Added for parity between the parent-level and the item/entry-level.
+	 */
+	function get_permalink()
+	{
+		return $this->get_link(0);
+	}
+
+	function get_links($rel = 'alternate')
+	{
+		if (!isset($this->data['links']))
+		{
+			$this->data['links'] = array();
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'))
+			{
+				foreach ($links as $link)
+				{
+					if (isset($link['attribs']['']['href']))
+					{
+						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+					}
+				}
+			}
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'))
+			{
+				foreach ($links as $link)
+				{
+					if (isset($link['attribs']['']['href']))
+					{
+						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+
+					}
+				}
+			}
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+
+			$keys = array_keys($this->data['links']);
+			foreach ($keys as $key)
+			{
+				if (SimplePie_Misc::is_isegment_nz_nc($key))
+				{
+					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
+						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
+					}
+					else
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
+					}
+				}
+				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
+				{
+					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
+				}
+				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
+			}
+		}
+
+		if (isset($this->data['links'][$rel]))
+		{
+			return $this->data['links'][$rel];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_all_discovered_feeds()
+	{
+		return $this->all_discovered_feeds;
+	}
+
+	function get_description()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_copyright()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_language()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang']))
+		{
+			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang']))
+		{
+			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang']))
+		{
+			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['headers']['content-language']))
+		{
+			return $this->sanitize($this->data['headers']['content-language'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_latitude()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', $return[0]['data'], $match))
+		{
+			return (float) $match[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_longitude()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', $return[0]['data'], $match))
+		{
+			return (float) $match[2];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_image_title()
+	{
+		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_image_url()
+	{
+		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image'))
+		{
+			return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'url'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'url'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_image_link()
+	{
+		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_image_width()
+	{
+		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'width'))
+		{
+			return round($return[0]['data']);
+		}
+		elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
+		{
+			return 88.0;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_image_height()
+	{
+		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'height'))
+		{
+			return round($return[0]['data']);
+		}
+		elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
+		{
+			return 31.0;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_item_quantity($max = 0)
+	{
+		$max = (int) $max;
+		$qty = count($this->get_items());
+		if ($max === 0)
+		{
+			return $qty;
+		}
+		else
+		{
+			return ($qty > $max) ? $max : $qty;
+		}
+	}
+
+	function get_item($key = 0)
+	{
+		$items = $this->get_items();
+		if (isset($items[$key]))
+		{
+			return $items[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_items($start = 0, $end = 0)
+	{
+		if (!isset($this->data['items']))
+		{
+			if (!empty($this->multifeed_objects))
+			{
+				$this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit);
+			}
+			else
+			{
+				$this->data['items'] = array();
+				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'entry'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'entry'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'item'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'item'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+				if ($items = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'item'))
+				{
+					$keys = array_keys($items);
+					foreach ($keys as $key)
+					{
+						$this->data['items'][] = new $this->item_class($this, $items[$key]);
+					}
+				}
+			}
+		}
+
+		if (!empty($this->data['items']))
+		{
+			// If we want to order it by date, check if all items have a date, and then sort it
+			if ($this->order_by_date && empty($this->multifeed_objects))
+			{
+				if (!isset($this->data['ordered_items']))
+				{
+					$do_sort = true;
+					foreach ($this->data['items'] as $item)
+					{
+						if (!$item->get_date('U'))
+						{
+							$do_sort = false;
+							break;
+						}
+					}
+					$item = null;
+					$this->data['ordered_items'] = $this->data['items'];
+					if ($do_sort)
+					{
+						usort($this->data['ordered_items'], array(&$this, 'sort_items'));
+					}
+				}
+				$items = $this->data['ordered_items'];
+			}
+			else
+			{
+				$items = $this->data['items'];
+			}
+
+			// Slice the data as desired
+			if ($end === 0)
+			{
+				return array_slice($items, $start);
+			}
+			else
+			{
+				return array_slice($items, $start, $end);
+			}
+		}
+		else
+		{
+			return array();
+		}
+	}
+
+	/**
+	 * @static
+	 */
+	function sort_items($a, $b)
+	{
+		return $a->get_date('U') <= $b->get_date('U');
+	}
+
+	/**
+	 * @static
+	 */
+	function merge_items($urls, $start = 0, $end = 0, $limit = 0)
+	{
+		if (is_array($urls) && sizeof($urls) > 0)
+		{
+			$items = array();
+			foreach ($urls as $arg)
+			{
+				if (is_a($arg, 'SimplePie'))
+				{
+					$items = array_merge($items, $arg->get_items(0, $limit));
+				}
+				else
+				{
+					trigger_error('Arguments must be SimplePie objects', E_USER_WARNING);
+				}
+			}
+
+			$do_sort = true;
+			foreach ($items as $item)
+			{
+				if (!$item->get_date('U'))
+				{
+					$do_sort = false;
+					break;
+				}
+			}
+			$item = null;
+			if ($do_sort)
+			{
+				usort($items, array('SimplePie', 'sort_items'));
+			}
+
+			if ($end === 0)
+			{
+				return array_slice($items, $start);
+			}
+			else
+			{
+				return array_slice($items, $start, $end);
+			}
+		}
+		else
+		{
+			trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING);
+			return array();
+		}
+	}
+}
+
+class SimplePie_Item
+{
+	var $feed;
+	var $data = array();
+
+	function SimplePie_Item($feed, $data)
+	{
+		$this->feed = $feed;
+		$this->data = $data;
+	}
+
+	function __toString()
+	{
+		return md5(serialize($this->data));
+	}
+
+	/**
+	 * Remove items that link back to this before destroying this object
+	 */
+	function __destruct()
+	{
+		if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode'))
+		{
+			unset($this->feed);
+		}
+	}
+
+	function get_item_tags($namespace, $tag)
+	{
+		if (isset($this->data['child'][$namespace][$tag]))
+		{
+			return $this->data['child'][$namespace][$tag];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_base($element = array())
+	{
+		return $this->feed->get_base($element);
+	}
+
+	function sanitize($data, $type, $base = '')
+	{
+		return $this->feed->sanitize($data, $type, $base);
+	}
+
+	function get_feed()
+	{
+		return $this->feed;
+	}
+
+	function get_id($hash = false)
+	{
+		if (!$hash)
+		{
+			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'))
+			{
+				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif (($return = $this->get_permalink()) !== null)
+			{
+				return $return;
+			}
+			elseif (($return = $this->get_title()) !== null)
+			{
+				return $return;
+			}
+		}
+		if ($this->get_permalink() !== null || $this->get_title() !== null)
+		{
+			return md5($this->get_permalink() . $this->get_title());
+		}
+		else
+		{
+			return md5(serialize($this->data));
+		}
+	}
+
+	function get_title()
+	{
+		if (!isset($this->data['title']))
+		{
+			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
+			{
+				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			else
+			{
+				$this->data['title'] = null;
+			}
+		}
+		return $this->data['title'];
+	}
+
+	function get_description($description_only = false)
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (!$description_only)
+		{
+			return $this->get_content(true);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_content($content_only = false)
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'content'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_content_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'content'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif (!$content_only)
+		{
+			return $this->get_description(true);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_category($key = 0)
+	{
+		$categories = $this->get_categories();
+		if (isset($categories[$key]))
+		{
+			return $categories[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_categories()
+	{
+		$categories = array();
+
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
+		{
+			$term = null;
+			$scheme = null;
+			$label = null;
+			if (isset($category['attribs']['']['term']))
+			{
+				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['scheme']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['label']))
+			{
+				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			$categories[] = new $this->feed->category_class($term, $scheme, $label);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
+		{
+			// This is really the label, but keep this as the term also for BC.
+			// Label will also work on retrieving because that falls back to term.
+			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			if (isset($category['attribs']['']['domain']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			else
+			{
+				$scheme = null;
+			}
+			$categories[] = new $this->feed->category_class($term, $scheme, null);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
+		{
+			$categories[] = new $this->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
+		{
+			$categories[] = new $this->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($categories))
+		{
+			return SimplePie_Misc::array_unique($categories);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_author($key = 0)
+	{
+		$authors = $this->get_authors();
+		if (isset($authors[$key]))
+		{
+			return $authors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_contributor($key = 0)
+	{
+		$contributors = $this->get_contributors();
+		if (isset($contributors[$key]))
+		{
+			return $contributors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_contributors()
+	{
+		$contributors = array();
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$contributors[] = new $this->feed->author_class($name, $uri, $email);
+			}
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$contributors[] = new $this->feed->author_class($name, $url, $email);
+			}
+		}
+
+		if (!empty($contributors))
+		{
+			return SimplePie_Misc::array_unique($contributors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_authors()
+	{
+		$authors = array();
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$authors[] = new $this->feed->author_class($name, $uri, $email);
+			}
+		}
+		if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$authors[] = new $this->feed->author_class($name, $url, $email);
+			}
+		}
+		if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'author'))
+		{
+			$authors[] = new $this->feed->author_class(null, null, $this->sanitize($author[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
+		{
+			$authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
+		{
+			$authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
+		{
+			$authors[] = new $this->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($authors))
+		{
+			return SimplePie_Misc::array_unique($authors);
+		}
+		elseif (($source = $this->get_source()) && ($authors = $source->get_authors()))
+		{
+			return $authors;
+		}
+		elseif ($authors = $this->feed->get_authors())
+		{
+			return $authors;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_copyright()
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_date($date_format = 'j F Y, g:i a')
+	{
+		if (!isset($this->data['date']))
+		{
+			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date'))
+			{
+				$this->data['date']['raw'] = $return[0]['data'];
+			}
+
+			if (!empty($this->data['date']['raw']))
+			{
+				$parser = SimplePie_Parse_Date::get();
+				$this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']);
+			}
+			else
+			{
+				$this->data['date'] = null;
+			}
+		}
+		if ($this->data['date'])
+		{
+			$date_format = (string) $date_format;
+			switch ($date_format)
+			{
+				case '':
+					return $this->sanitize($this->data['date']['raw'], SIMPLEPIE_CONSTRUCT_TEXT);
+
+				case 'U':
+					return $this->data['date']['parsed'];
+
+				default:
+					return date($date_format, $this->data['date']['parsed']);
+			}
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_local_date($date_format = '%c')
+	{
+		if (!$date_format)
+		{
+			return $this->sanitize($this->get_date(''), SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (($date = $this->get_date('U')) !== null)
+		{
+			return strftime($date_format, $date);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_permalink()
+	{
+		$link = $this->get_link();
+		$enclosure = $this->get_enclosure(0);
+		if ($link !== null)
+		{
+			return $link;
+		}
+		elseif ($enclosure !== null)
+		{
+			return $enclosure->get_link();
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_link($key = 0, $rel = 'alternate')
+	{
+		$links = $this->get_links($rel);
+		if ($links[$key] !== null)
+		{
+			return $links[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_links($rel = 'alternate')
+	{
+		if (!isset($this->data['links']))
+		{
+			$this->data['links'] = array();
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link)
+			{
+				if (isset($link['attribs']['']['href']))
+				{
+					$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+					$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+
+				}
+			}
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link)
+			{
+				if (isset($link['attribs']['']['href']))
+				{
+					$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+					$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+				}
+			}
+			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'))
+			{
+				if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true')
+				{
+					$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+				}
+			}
+
+			$keys = array_keys($this->data['links']);
+			foreach ($keys as $key)
+			{
+				if (SimplePie_Misc::is_isegment_nz_nc($key))
+				{
+					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
+						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
+					}
+					else
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
+					}
+				}
+				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
+				{
+					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
+				}
+				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
+			}
+		}
+		if (isset($this->data['links'][$rel]))
+		{
+			return $this->data['links'][$rel];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * @todo Add ability to prefer one type of content over another (in a media group).
+	 */
+	function get_enclosure($key = 0, $prefer = null)
+	{
+		$enclosures = $this->get_enclosures();
+		if (isset($enclosures[$key]))
+		{
+			return $enclosures[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Grabs all available enclosures (podcasts, etc.)
+	 *
+	 * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS.
+	 *
+	 * At this point, we're pretty much assuming that all enclosures for an item are the same content.  Anything else is too complicated to properly support.
+	 *
+	 * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4).
+	 * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists).
+	 */
+	function get_enclosures()
+	{
+		if (!isset($this->data['enclosures']))
+		{
+			$this->data['enclosures'] = array();
+
+			// Elements
+			$captions_parent = null;
+			$categories_parent = null;
+			$copyrights_parent = null;
+			$credits_parent = null;
+			$description_parent = null;
+			$duration_parent = null;
+			$hashes_parent = null;
+			$keywords_parent = null;
+			$player_parent = null;
+			$ratings_parent = null;
+			$restrictions_parent = null;
+			$thumbnails_parent = null;
+			$title_parent = null;
+
+			// Let's do the channel and item-level ones first, and just re-use them if we need to.
+			$parent = $this->get_feed();
+
+			// CAPTIONS
+			if ($captions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text'))
+			{
+				foreach ($captions as $caption)
+				{
+					$caption_type = null;
+					$caption_lang = null;
+					$caption_startTime = null;
+					$caption_endTime = null;
+					$caption_text = null;
+					if (isset($caption['attribs']['']['type']))
+					{
+						$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['lang']))
+					{
+						$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['start']))
+					{
+						$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['end']))
+					{
+						$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['data']))
+					{
+						$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$captions_parent[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+				}
+			}
+			elseif ($captions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text'))
+			{
+				foreach ($captions as $caption)
+				{
+					$caption_type = null;
+					$caption_lang = null;
+					$caption_startTime = null;
+					$caption_endTime = null;
+					$caption_text = null;
+					if (isset($caption['attribs']['']['type']))
+					{
+						$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['lang']))
+					{
+						$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['start']))
+					{
+						$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['attribs']['']['end']))
+					{
+						$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($caption['data']))
+					{
+						$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$captions_parent[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+				}
+			}
+			if (is_array($captions_parent))
+			{
+				$captions_parent = array_values(SimplePie_Misc::array_unique($captions_parent));
+			}
+
+			// CATEGORIES
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category)
+			{
+				$term = null;
+				$scheme = null;
+				$label = null;
+				if (isset($category['data']))
+				{
+					$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				if (isset($category['attribs']['']['scheme']))
+				{
+					$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				else
+				{
+					$scheme = 'http://search.yahoo.com/mrss/category_schema';
+				}
+				if (isset($category['attribs']['']['label']))
+				{
+					$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$categories_parent[] = new $this->feed->category_class($term, $scheme, $label);
+			}
+			foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category)
+			{
+				$term = null;
+				$scheme = null;
+				$label = null;
+				if (isset($category['data']))
+				{
+					$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				if (isset($category['attribs']['']['scheme']))
+				{
+					$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				else
+				{
+					$scheme = 'http://search.yahoo.com/mrss/category_schema';
+				}
+				if (isset($category['attribs']['']['label']))
+				{
+					$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$categories_parent[] = new $this->feed->category_class($term, $scheme, $label);
+			}
+			foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'category') as $category)
+			{
+				$term = null;
+				$scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
+				$label = null;
+				if (isset($category['attribs']['']['text']))
+				{
+					$label = $this->sanitize($category['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$categories_parent[] = new $this->feed->category_class($term, $scheme, $label);
+
+				if (isset($category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category']))
+				{
+					foreach ((array) $category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'] as $subcategory)
+					{
+						if (isset($subcategory['attribs']['']['text']))
+						{
+							$label = $this->sanitize($subcategory['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						$categories_parent[] = new $this->feed->category_class($term, $scheme, $label);
+					}
+				}
+			}
+			if (is_array($categories_parent))
+			{
+				$categories_parent = array_values(SimplePie_Misc::array_unique($categories_parent));
+			}
+
+			// COPYRIGHT
+			if ($copyright = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright'))
+			{
+				$copyright_url = null;
+				$copyright_label = null;
+				if (isset($copyright[0]['attribs']['']['url']))
+				{
+					$copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				if (isset($copyright[0]['data']))
+				{
+					$copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$copyrights_parent = new $this->feed->copyright_class($copyright_url, $copyright_label);
+			}
+			elseif ($copyright = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright'))
+			{
+				$copyright_url = null;
+				$copyright_label = null;
+				if (isset($copyright[0]['attribs']['']['url']))
+				{
+					$copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				if (isset($copyright[0]['data']))
+				{
+					$copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+				$copyrights_parent = new $this->feed->copyright_class($copyright_url, $copyright_label);
+			}
+
+			// CREDITS
+			if ($credits = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit'))
+			{
+				foreach ($credits as $credit)
+				{
+					$credit_role = null;
+					$credit_scheme = null;
+					$credit_name = null;
+					if (isset($credit['attribs']['']['role']))
+					{
+						$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($credit['attribs']['']['scheme']))
+					{
+						$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$credit_scheme = 'urn:ebu';
+					}
+					if (isset($credit['data']))
+					{
+						$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$credits_parent[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+				}
+			}
+			elseif ($credits = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit'))
+			{
+				foreach ($credits as $credit)
+				{
+					$credit_role = null;
+					$credit_scheme = null;
+					$credit_name = null;
+					if (isset($credit['attribs']['']['role']))
+					{
+						$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($credit['attribs']['']['scheme']))
+					{
+						$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$credit_scheme = 'urn:ebu';
+					}
+					if (isset($credit['data']))
+					{
+						$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$credits_parent[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+				}
+			}
+			if (is_array($credits_parent))
+			{
+				$credits_parent = array_values(SimplePie_Misc::array_unique($credits_parent));
+			}
+
+			// DESCRIPTION
+			if ($description_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description'))
+			{
+				if (isset($description_parent[0]['data']))
+				{
+					$description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+			}
+			elseif ($description_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description'))
+			{
+				if (isset($description_parent[0]['data']))
+				{
+					$description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+			}
+
+			// DURATION
+			if ($duration_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'duration'))
+			{
+				$seconds = null;
+				$minutes = null;
+				$hours = null;
+				if (isset($duration_parent[0]['data']))
+				{
+					$temp = explode(':', $this->sanitize($duration_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					if (sizeof($temp) > 0)
+					{
+						(int) $seconds = array_pop($temp);
+					}
+					if (sizeof($temp) > 0)
+					{
+						(int) $minutes = array_pop($temp);
+						$seconds += $minutes * 60;
+					}
+					if (sizeof($temp) > 0)
+					{
+						(int) $hours = array_pop($temp);
+						$seconds += $hours * 3600;
+					}
+					unset($temp);
+					$duration_parent = $seconds;
+				}
+			}
+
+			// HASHES
+			if ($hashes_iterator = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash'))
+			{
+				foreach ($hashes_iterator as $hash)
+				{
+					$value = null;
+					$algo = null;
+					if (isset($hash['data']))
+					{
+						$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($hash['attribs']['']['algo']))
+					{
+						$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$algo = 'md5';
+					}
+					$hashes_parent[] = $algo.':'.$value;
+				}
+			}
+			elseif ($hashes_iterator = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash'))
+			{
+				foreach ($hashes_iterator as $hash)
+				{
+					$value = null;
+					$algo = null;
+					if (isset($hash['data']))
+					{
+						$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($hash['attribs']['']['algo']))
+					{
+						$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$algo = 'md5';
+					}
+					$hashes_parent[] = $algo.':'.$value;
+				}
+			}
+			if (is_array($hashes_parent))
+			{
+				$hashes_parent = array_values(SimplePie_Misc::array_unique($hashes_parent));
+			}
+
+			// KEYWORDS
+			if ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords'))
+			{
+				if (isset($keywords[0]['data']))
+				{
+					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					foreach ($temp as $word)
+					{
+						$keywords_parent[] = trim($word);
+					}
+				}
+				unset($temp);
+			}
+			elseif ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords'))
+			{
+				if (isset($keywords[0]['data']))
+				{
+					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					foreach ($temp as $word)
+					{
+						$keywords_parent[] = trim($word);
+					}
+				}
+				unset($temp);
+			}
+			elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords'))
+			{
+				if (isset($keywords[0]['data']))
+				{
+					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					foreach ($temp as $word)
+					{
+						$keywords_parent[] = trim($word);
+					}
+				}
+				unset($temp);
+			}
+			elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords'))
+			{
+				if (isset($keywords[0]['data']))
+				{
+					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+					foreach ($temp as $word)
+					{
+						$keywords_parent[] = trim($word);
+					}
+				}
+				unset($temp);
+			}
+			if (is_array($keywords_parent))
+			{
+				$keywords_parent = array_values(SimplePie_Misc::array_unique($keywords_parent));
+			}
+
+			// PLAYER
+			if ($player_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player'))
+			{
+				if (isset($player_parent[0]['attribs']['']['url']))
+				{
+					$player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+				}
+			}
+			elseif ($player_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player'))
+			{
+				if (isset($player_parent[0]['attribs']['']['url']))
+				{
+					$player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+				}
+			}
+
+			// RATINGS
+			if ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating'))
+			{
+				foreach ($ratings as $rating)
+				{
+					$rating_scheme = null;
+					$rating_value = null;
+					if (isset($rating['attribs']['']['scheme']))
+					{
+						$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$rating_scheme = 'urn:simple';
+					}
+					if (isset($rating['data']))
+					{
+						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+				}
+			}
+			elseif ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit'))
+			{
+				foreach ($ratings as $rating)
+				{
+					$rating_scheme = 'urn:itunes';
+					$rating_value = null;
+					if (isset($rating['data']))
+					{
+						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+				}
+			}
+			elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating'))
+			{
+				foreach ($ratings as $rating)
+				{
+					$rating_scheme = null;
+					$rating_value = null;
+					if (isset($rating['attribs']['']['scheme']))
+					{
+						$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					else
+					{
+						$rating_scheme = 'urn:simple';
+					}
+					if (isset($rating['data']))
+					{
+						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+				}
+			}
+			elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit'))
+			{
+				foreach ($ratings as $rating)
+				{
+					$rating_scheme = 'urn:itunes';
+					$rating_value = null;
+					if (isset($rating['data']))
+					{
+						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$ratings_parent[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+				}
+			}
+			if (is_array($ratings_parent))
+			{
+				$ratings_parent = array_values(SimplePie_Misc::array_unique($ratings_parent));
+			}
+
+			// RESTRICTIONS
+			if ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction'))
+			{
+				foreach ($restrictions as $restriction)
+				{
+					$restriction_relationship = null;
+					$restriction_type = null;
+					$restriction_value = null;
+					if (isset($restriction['attribs']['']['relationship']))
+					{
+						$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($restriction['attribs']['']['type']))
+					{
+						$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($restriction['data']))
+					{
+						$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+				}
+			}
+			elseif ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block'))
+			{
+				foreach ($restrictions as $restriction)
+				{
+					$restriction_relationship = 'allow';
+					$restriction_type = null;
+					$restriction_value = 'itunes';
+					if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes')
+					{
+						$restriction_relationship = 'deny';
+					}
+					$restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+				}
+			}
+			elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction'))
+			{
+				foreach ($restrictions as $restriction)
+				{
+					$restriction_relationship = null;
+					$restriction_type = null;
+					$restriction_value = null;
+					if (isset($restriction['attribs']['']['relationship']))
+					{
+						$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($restriction['attribs']['']['type']))
+					{
+						$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($restriction['data']))
+					{
+						$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					$restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+				}
+			}
+			elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block'))
+			{
+				foreach ($restrictions as $restriction)
+				{
+					$restriction_relationship = 'allow';
+					$restriction_type = null;
+					$restriction_value = 'itunes';
+					if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes')
+					{
+						$restriction_relationship = 'deny';
+					}
+					$restrictions_parent[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+				}
+			}
+			if (is_array($restrictions_parent))
+			{
+				$restrictions_parent = array_values(SimplePie_Misc::array_unique($restrictions_parent));
+			}
+
+			// THUMBNAILS
+			if ($thumbnails = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail'))
+			{
+				foreach ($thumbnails as $thumbnail)
+				{
+					if (isset($thumbnail['attribs']['']['url']))
+					{
+						$thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+					}
+				}
+			}
+			elseif ($thumbnails = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail'))
+			{
+				foreach ($thumbnails as $thumbnail)
+				{
+					if (isset($thumbnail['attribs']['']['url']))
+					{
+						$thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+					}
+				}
+			}
+
+			// TITLES
+			if ($title_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title'))
+			{
+				if (isset($title_parent[0]['data']))
+				{
+					$title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+			}
+			elseif ($title_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title'))
+			{
+				if (isset($title_parent[0]['data']))
+				{
+					$title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+				}
+			}
+
+			// Clear the memory
+			unset($parent);
+
+			// Attributes
+			$bitrate = null;
+			$channels = null;
+			$duration = null;
+			$expression = null;
+			$framerate = null;
+			$height = null;
+			$javascript = null;
+			$lang = null;
+			$length = null;
+			$medium = null;
+			$samplingrate = null;
+			$type = null;
+			$url = null;
+			$width = null;
+
+			// Elements
+			$captions = null;
+			$categories = null;
+			$copyrights = null;
+			$credits = null;
+			$description = null;
+			$hashes = null;
+			$keywords = null;
+			$player = null;
+			$ratings = null;
+			$restrictions = null;
+			$thumbnails = null;
+			$title = null;
+
+			// If we have media:group tags, loop through them.
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group') as $group)
+			{
+				// If we have media:content tags, loop through them.
+				foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content)
+				{
+					if (isset($content['attribs']['']['url']))
+					{
+						// Attributes
+						$bitrate = null;
+						$channels = null;
+						$duration = null;
+						$expression = null;
+						$framerate = null;
+						$height = null;
+						$javascript = null;
+						$lang = null;
+						$length = null;
+						$medium = null;
+						$samplingrate = null;
+						$type = null;
+						$url = null;
+						$width = null;
+
+						// Elements
+						$captions = null;
+						$categories = null;
+						$copyrights = null;
+						$credits = null;
+						$description = null;
+						$hashes = null;
+						$keywords = null;
+						$player = null;
+						$ratings = null;
+						$restrictions = null;
+						$thumbnails = null;
+						$title = null;
+
+						// Start checking the attributes of media:content
+						if (isset($content['attribs']['']['bitrate']))
+						{
+							$bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['channels']))
+						{
+							$channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['duration']))
+						{
+							$duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$duration = $duration_parent;
+						}
+						if (isset($content['attribs']['']['expression']))
+						{
+							$expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['framerate']))
+						{
+							$framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['height']))
+						{
+							$height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['lang']))
+						{
+							$lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['fileSize']))
+						{
+							$length = ceil($content['attribs']['']['fileSize']);
+						}
+						if (isset($content['attribs']['']['medium']))
+						{
+							$medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['samplingrate']))
+						{
+							$samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['type']))
+						{
+							$type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['width']))
+						{
+							$width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						$url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+
+						// Checking the other optional media: elements. Priority: media:content, media:group, item, channel
+
+						// CAPTIONS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
+							{
+								$caption_type = null;
+								$caption_lang = null;
+								$caption_startTime = null;
+								$caption_endTime = null;
+								$caption_text = null;
+								if (isset($caption['attribs']['']['type']))
+								{
+									$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['lang']))
+								{
+									$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['start']))
+								{
+									$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['end']))
+								{
+									$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['data']))
+								{
+									$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+							}
+							if (is_array($captions))
+							{
+								$captions = array_values(SimplePie_Misc::array_unique($captions));
+							}
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
+						{
+							foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
+							{
+								$caption_type = null;
+								$caption_lang = null;
+								$caption_startTime = null;
+								$caption_endTime = null;
+								$caption_text = null;
+								if (isset($caption['attribs']['']['type']))
+								{
+									$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['lang']))
+								{
+									$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['start']))
+								{
+									$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['end']))
+								{
+									$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['data']))
+								{
+									$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+							}
+							if (is_array($captions))
+							{
+								$captions = array_values(SimplePie_Misc::array_unique($captions));
+							}
+						}
+						else
+						{
+							$captions = $captions_parent;
+						}
+
+						// CATEGORIES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
+						{
+							foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
+							{
+								$term = null;
+								$scheme = null;
+								$label = null;
+								if (isset($category['data']))
+								{
+									$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($category['attribs']['']['scheme']))
+								{
+									$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$scheme = 'http://search.yahoo.com/mrss/category_schema';
+								}
+								if (isset($category['attribs']['']['label']))
+								{
+									$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$categories[] = new $this->feed->category_class($term, $scheme, $label);
+							}
+						}
+						if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
+						{
+							foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
+							{
+								$term = null;
+								$scheme = null;
+								$label = null;
+								if (isset($category['data']))
+								{
+									$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($category['attribs']['']['scheme']))
+								{
+									$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$scheme = 'http://search.yahoo.com/mrss/category_schema';
+								}
+								if (isset($category['attribs']['']['label']))
+								{
+									$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$categories[] = new $this->feed->category_class($term, $scheme, $label);
+							}
+						}
+						if (is_array($categories) && is_array($categories_parent))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique(array_merge($categories, $categories_parent)));
+						}
+						elseif (is_array($categories))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique($categories));
+						}
+						elseif (is_array($categories_parent))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique($categories_parent));
+						}
+
+						// COPYRIGHTS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
+						{
+							$copyright_url = null;
+							$copyright_label = null;
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
+							{
+								$copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
+							{
+								$copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							$copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label);
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
+						{
+							$copyright_url = null;
+							$copyright_label = null;
+							if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
+							{
+								$copyright_url = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
+							{
+								$copyright_label = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							$copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label);
+						}
+						else
+						{
+							$copyrights = $copyrights_parent;
+						}
+
+						// CREDITS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
+							{
+								$credit_role = null;
+								$credit_scheme = null;
+								$credit_name = null;
+								if (isset($credit['attribs']['']['role']))
+								{
+									$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($credit['attribs']['']['scheme']))
+								{
+									$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$credit_scheme = 'urn:ebu';
+								}
+								if (isset($credit['data']))
+								{
+									$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+							}
+							if (is_array($credits))
+							{
+								$credits = array_values(SimplePie_Misc::array_unique($credits));
+							}
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
+						{
+							foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
+							{
+								$credit_role = null;
+								$credit_scheme = null;
+								$credit_name = null;
+								if (isset($credit['attribs']['']['role']))
+								{
+									$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($credit['attribs']['']['scheme']))
+								{
+									$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$credit_scheme = 'urn:ebu';
+								}
+								if (isset($credit['data']))
+								{
+									$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+							}
+							if (is_array($credits))
+							{
+								$credits = array_values(SimplePie_Misc::array_unique($credits));
+							}
+						}
+						else
+						{
+							$credits = $credits_parent;
+						}
+
+						// DESCRIPTION
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
+						{
+							$description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
+						{
+							$description = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$description = $description_parent;
+						}
+
+						// HASHES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
+							{
+								$value = null;
+								$algo = null;
+								if (isset($hash['data']))
+								{
+									$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($hash['attribs']['']['algo']))
+								{
+									$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$algo = 'md5';
+								}
+								$hashes[] = $algo.':'.$value;
+							}
+							if (is_array($hashes))
+							{
+								$hashes = array_values(SimplePie_Misc::array_unique($hashes));
+							}
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
+						{
+							foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
+							{
+								$value = null;
+								$algo = null;
+								if (isset($hash['data']))
+								{
+									$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($hash['attribs']['']['algo']))
+								{
+									$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$algo = 'md5';
+								}
+								$hashes[] = $algo.':'.$value;
+							}
+							if (is_array($hashes))
+							{
+								$hashes = array_values(SimplePie_Misc::array_unique($hashes));
+							}
+						}
+						else
+						{
+							$hashes = $hashes_parent;
+						}
+
+						// KEYWORDS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
+						{
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
+							{
+								$temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+								foreach ($temp as $word)
+								{
+									$keywords[] = trim($word);
+								}
+								unset($temp);
+							}
+							if (is_array($keywords))
+							{
+								$keywords = array_values(SimplePie_Misc::array_unique($keywords));
+							}
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
+						{
+							if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
+							{
+								$temp = explode(',', $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+								foreach ($temp as $word)
+								{
+									$keywords[] = trim($word);
+								}
+								unset($temp);
+							}
+							if (is_array($keywords))
+							{
+								$keywords = array_values(SimplePie_Misc::array_unique($keywords));
+							}
+						}
+						else
+						{
+							$keywords = $keywords_parent;
+						}
+
+						// PLAYER
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
+						{
+							$player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
+						{
+							$player = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+						}
+						else
+						{
+							$player = $player_parent;
+						}
+
+						// RATINGS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
+							{
+								$rating_scheme = null;
+								$rating_value = null;
+								if (isset($rating['attribs']['']['scheme']))
+								{
+									$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$rating_scheme = 'urn:simple';
+								}
+								if (isset($rating['data']))
+								{
+									$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+							}
+							if (is_array($ratings))
+							{
+								$ratings = array_values(SimplePie_Misc::array_unique($ratings));
+							}
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
+						{
+							foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
+							{
+								$rating_scheme = null;
+								$rating_value = null;
+								if (isset($rating['attribs']['']['scheme']))
+								{
+									$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$rating_scheme = 'urn:simple';
+								}
+								if (isset($rating['data']))
+								{
+									$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+							}
+							if (is_array($ratings))
+							{
+								$ratings = array_values(SimplePie_Misc::array_unique($ratings));
+							}
+						}
+						else
+						{
+							$ratings = $ratings_parent;
+						}
+
+						// RESTRICTIONS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
+							{
+								$restriction_relationship = null;
+								$restriction_type = null;
+								$restriction_value = null;
+								if (isset($restriction['attribs']['']['relationship']))
+								{
+									$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($restriction['attribs']['']['type']))
+								{
+									$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($restriction['data']))
+								{
+									$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+							}
+							if (is_array($restrictions))
+							{
+								$restrictions = array_values(SimplePie_Misc::array_unique($restrictions));
+							}
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
+						{
+							foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
+							{
+								$restriction_relationship = null;
+								$restriction_type = null;
+								$restriction_value = null;
+								if (isset($restriction['attribs']['']['relationship']))
+								{
+									$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($restriction['attribs']['']['type']))
+								{
+									$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($restriction['data']))
+								{
+									$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+							}
+							if (is_array($restrictions))
+							{
+								$restrictions = array_values(SimplePie_Misc::array_unique($restrictions));
+							}
+						}
+						else
+						{
+							$restrictions = $restrictions_parent;
+						}
+
+						// THUMBNAILS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
+							{
+								$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+							}
+							if (is_array($thumbnails))
+							{
+								$thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails));
+							}
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
+						{
+							foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
+							{
+								$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+							}
+							if (is_array($thumbnails))
+							{
+								$thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails));
+							}
+						}
+						else
+						{
+							$thumbnails = $thumbnails_parent;
+						}
+
+						// TITLES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
+						{
+							$title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
+						{
+							$title = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$title = $title_parent;
+						}
+
+						$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width);
+					}
+				}
+			}
+
+			// If we have standalone media:content tags, loop through them.
+			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content']))
+			{
+				foreach ((array) $this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content)
+				{
+					if (isset($content['attribs']['']['url']))
+					{
+						// Attributes
+						$bitrate = null;
+						$channels = null;
+						$duration = null;
+						$expression = null;
+						$framerate = null;
+						$height = null;
+						$javascript = null;
+						$lang = null;
+						$length = null;
+						$medium = null;
+						$samplingrate = null;
+						$type = null;
+						$url = null;
+						$width = null;
+
+						// Elements
+						$captions = null;
+						$categories = null;
+						$copyrights = null;
+						$credits = null;
+						$description = null;
+						$hashes = null;
+						$keywords = null;
+						$player = null;
+						$ratings = null;
+						$restrictions = null;
+						$thumbnails = null;
+						$title = null;
+
+						// Start checking the attributes of media:content
+						if (isset($content['attribs']['']['bitrate']))
+						{
+							$bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['channels']))
+						{
+							$channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['duration']))
+						{
+							$duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$duration = $duration_parent;
+						}
+						if (isset($content['attribs']['']['expression']))
+						{
+							$expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['framerate']))
+						{
+							$framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['height']))
+						{
+							$height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['lang']))
+						{
+							$lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['fileSize']))
+						{
+							$length = ceil($content['attribs']['']['fileSize']);
+						}
+						if (isset($content['attribs']['']['medium']))
+						{
+							$medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['samplingrate']))
+						{
+							$samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['type']))
+						{
+							$type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						if (isset($content['attribs']['']['width']))
+						{
+							$width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						$url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+
+						// Checking the other optional media: elements. Priority: media:content, media:group, item, channel
+
+						// CAPTIONS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
+							{
+								$caption_type = null;
+								$caption_lang = null;
+								$caption_startTime = null;
+								$caption_endTime = null;
+								$caption_text = null;
+								if (isset($caption['attribs']['']['type']))
+								{
+									$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['lang']))
+								{
+									$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['start']))
+								{
+									$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['attribs']['']['end']))
+								{
+									$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($caption['data']))
+								{
+									$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$captions[] = new $this->feed->caption_class($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text);
+							}
+							if (is_array($captions))
+							{
+								$captions = array_values(SimplePie_Misc::array_unique($captions));
+							}
+						}
+						else
+						{
+							$captions = $captions_parent;
+						}
+
+						// CATEGORIES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
+						{
+							foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
+							{
+								$term = null;
+								$scheme = null;
+								$label = null;
+								if (isset($category['data']))
+								{
+									$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($category['attribs']['']['scheme']))
+								{
+									$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$scheme = 'http://search.yahoo.com/mrss/category_schema';
+								}
+								if (isset($category['attribs']['']['label']))
+								{
+									$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$categories[] = new $this->feed->category_class($term, $scheme, $label);
+							}
+						}
+						if (is_array($categories) && is_array($categories_parent))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique(array_merge($categories, $categories_parent)));
+						}
+						elseif (is_array($categories))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique($categories));
+						}
+						elseif (is_array($categories_parent))
+						{
+							$categories = array_values(SimplePie_Misc::array_unique($categories_parent));
+						}
+						else
+						{
+							$categories = null;
+						}
+
+						// COPYRIGHTS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
+						{
+							$copyright_url = null;
+							$copyright_label = null;
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
+							{
+								$copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
+							{
+								$copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+							}
+							$copyrights = new $this->feed->copyright_class($copyright_url, $copyright_label);
+						}
+						else
+						{
+							$copyrights = $copyrights_parent;
+						}
+
+						// CREDITS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
+							{
+								$credit_role = null;
+								$credit_scheme = null;
+								$credit_name = null;
+								if (isset($credit['attribs']['']['role']))
+								{
+									$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($credit['attribs']['']['scheme']))
+								{
+									$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$credit_scheme = 'urn:ebu';
+								}
+								if (isset($credit['data']))
+								{
+									$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$credits[] = new $this->feed->credit_class($credit_role, $credit_scheme, $credit_name);
+							}
+							if (is_array($credits))
+							{
+								$credits = array_values(SimplePie_Misc::array_unique($credits));
+							}
+						}
+						else
+						{
+							$credits = $credits_parent;
+						}
+
+						// DESCRIPTION
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
+						{
+							$description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$description = $description_parent;
+						}
+
+						// HASHES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
+							{
+								$value = null;
+								$algo = null;
+								if (isset($hash['data']))
+								{
+									$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($hash['attribs']['']['algo']))
+								{
+									$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$algo = 'md5';
+								}
+								$hashes[] = $algo.':'.$value;
+							}
+							if (is_array($hashes))
+							{
+								$hashes = array_values(SimplePie_Misc::array_unique($hashes));
+							}
+						}
+						else
+						{
+							$hashes = $hashes_parent;
+						}
+
+						// KEYWORDS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
+						{
+							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
+							{
+								$temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
+								foreach ($temp as $word)
+								{
+									$keywords[] = trim($word);
+								}
+								unset($temp);
+							}
+							if (is_array($keywords))
+							{
+								$keywords = array_values(SimplePie_Misc::array_unique($keywords));
+							}
+						}
+						else
+						{
+							$keywords = $keywords_parent;
+						}
+
+						// PLAYER
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
+						{
+							$player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+						}
+						else
+						{
+							$player = $player_parent;
+						}
+
+						// RATINGS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
+							{
+								$rating_scheme = null;
+								$rating_value = null;
+								if (isset($rating['attribs']['']['scheme']))
+								{
+									$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								else
+								{
+									$rating_scheme = 'urn:simple';
+								}
+								if (isset($rating['data']))
+								{
+									$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$ratings[] = new $this->feed->rating_class($rating_scheme, $rating_value);
+							}
+							if (is_array($ratings))
+							{
+								$ratings = array_values(SimplePie_Misc::array_unique($ratings));
+							}
+						}
+						else
+						{
+							$ratings = $ratings_parent;
+						}
+
+						// RESTRICTIONS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
+							{
+								$restriction_relationship = null;
+								$restriction_type = null;
+								$restriction_value = null;
+								if (isset($restriction['attribs']['']['relationship']))
+								{
+									$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($restriction['attribs']['']['type']))
+								{
+									$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								if (isset($restriction['data']))
+								{
+									$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+								}
+								$restrictions[] = new $this->feed->restriction_class($restriction_relationship, $restriction_type, $restriction_value);
+							}
+							if (is_array($restrictions))
+							{
+								$restrictions = array_values(SimplePie_Misc::array_unique($restrictions));
+							}
+						}
+						else
+						{
+							$restrictions = $restrictions_parent;
+						}
+
+						// THUMBNAILS
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
+						{
+							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
+							{
+								$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+							}
+							if (is_array($thumbnails))
+							{
+								$thumbnails = array_values(SimplePie_Misc::array_unique($thumbnails));
+							}
+						}
+						else
+						{
+							$thumbnails = $thumbnails_parent;
+						}
+
+						// TITLES
+						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
+						{
+							$title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+						}
+						else
+						{
+							$title = $title_parent;
+						}
+
+						$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width);
+					}
+				}
+			}
+
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link)
+			{
+				if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure')
+				{
+					// Attributes
+					$bitrate = null;
+					$channels = null;
+					$duration = null;
+					$expression = null;
+					$framerate = null;
+					$height = null;
+					$javascript = null;
+					$lang = null;
+					$length = null;
+					$medium = null;
+					$samplingrate = null;
+					$type = null;
+					$url = null;
+					$width = null;
+
+					$url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+					if (isset($link['attribs']['']['type']))
+					{
+						$type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($link['attribs']['']['length']))
+					{
+						$length = ceil($link['attribs']['']['length']);
+					}
+
+					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
+					$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width);
+				}
+			}
+
+			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link)
+			{
+				if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure')
+				{
+					// Attributes
+					$bitrate = null;
+					$channels = null;
+					$duration = null;
+					$expression = null;
+					$framerate = null;
+					$height = null;
+					$javascript = null;
+					$lang = null;
+					$length = null;
+					$medium = null;
+					$samplingrate = null;
+					$type = null;
+					$url = null;
+					$width = null;
+
+					$url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+					if (isset($link['attribs']['']['type']))
+					{
+						$type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($link['attribs']['']['length']))
+					{
+						$length = ceil($link['attribs']['']['length']);
+					}
+
+					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
+					$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width);
+				}
+			}
+
+			if ($enclosure = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure'))
+			{
+				if (isset($enclosure[0]['attribs']['']['url']))
+				{
+					// Attributes
+					$bitrate = null;
+					$channels = null;
+					$duration = null;
+					$expression = null;
+					$framerate = null;
+					$height = null;
+					$javascript = null;
+					$lang = null;
+					$length = null;
+					$medium = null;
+					$samplingrate = null;
+					$type = null;
+					$url = null;
+					$width = null;
+
+					$url = $this->sanitize($enclosure[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure[0]));
+					if (isset($enclosure[0]['attribs']['']['type']))
+					{
+						$type = $this->sanitize($enclosure[0]['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
+					}
+					if (isset($enclosure[0]['attribs']['']['length']))
+					{
+						$length = ceil($enclosure[0]['attribs']['']['length']);
+					}
+
+					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
+					$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width);
+				}
+			}
+
+			if (sizeof($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $restrictions_parent || $samplingrate || $thumbnails_parent || $title_parent || $width))
+			{
+				// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
+				$this->data['enclosures'][] = new $this->feed->enclosure_class($url, $type, $length, $this->feed->javascript, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width);
+			}
+
+			$this->data['enclosures'] = array_values(SimplePie_Misc::array_unique($this->data['enclosures']));
+		}
+		if (!empty($this->data['enclosures']))
+		{
+			return $this->data['enclosures'];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_latitude()
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', $return[0]['data'], $match))
+		{
+			return (float) $match[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_longitude()
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', $return[0]['data'], $match))
+		{
+			return (float) $match[2];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_source()
+	{
+		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'source'))
+		{
+			return new $this->feed->source_class($this, $return[0]);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Creates the add_to_* methods' return data
+	 *
+	 * @access private
+	 * @param string $item_url String to prefix to the item permalink
+	 * @param string $title_url String to prefix to the item title
+	 * (and suffix to the item permalink)
+	 * @return mixed URL if feed exists, false otherwise
+	 */
+	function add_to_service($item_url, $title_url = null, $summary_url = null)
+	{
+		if ($this->get_permalink() !== null)
+		{
+			$return = $item_url . rawurlencode($this->get_permalink());
+			if ($title_url !== null && $this->get_title() !== null)
+			{
+				$return .= $title_url . rawurlencode($this->get_title());
+			}
+			if ($summary_url !== null && $this->get_description() !== null)
+			{
+				$return .= $summary_url . rawurlencode($this->get_description());
+			}
+			return $this->sanitize($return, SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function add_to_blinklist()
+	{
+		return $this->add_to_service('http://www.blinklist.com/index.php?Action=Blink/addblink.php&Description=&Url=', '&Title=');
+	}
+
+	function add_to_blogmarks()
+	{
+		return $this->add_to_service('http://blogmarks.net/my/new.php?mini=1&simple=1&url=', '&title=');
+	}
+
+	function add_to_delicious()
+	{
+		return $this->add_to_service('http://del.icio.us/post/?v=4&url=', '&title=');
+	}
+
+	function add_to_digg()
+	{
+		return $this->add_to_service('http://digg.com/submit?url=', '&title=', '&bodytext=');
+	}
+
+	function add_to_furl()
+	{
+		return $this->add_to_service('http://www.furl.net/storeIt.jsp?u=', '&t=');
+	}
+
+	function add_to_magnolia()
+	{
+		return $this->add_to_service('http://ma.gnolia.com/bookmarklet/add?url=', '&title=');
+	}
+
+	function add_to_myweb20()
+	{
+		return $this->add_to_service('http://myweb2.search.yahoo.com/myresults/bookmarklet?u=', '&t=');
+	}
+
+	function add_to_newsvine()
+	{
+		return $this->add_to_service('http://www.newsvine.com/_wine/save?u=', '&h=');
+	}
+
+	function add_to_reddit()
+	{
+		return $this->add_to_service('http://reddit.com/submit?url=', '&title=');
+	}
+
+	function add_to_segnalo()
+	{
+		return $this->add_to_service('http://segnalo.com/post.html.php?url=', '&title=');
+	}
+
+	function add_to_simpy()
+	{
+		return $this->add_to_service('http://www.simpy.com/simpy/LinkAdd.do?href=', '&title=');
+	}
+
+	function add_to_spurl()
+	{
+		return $this->add_to_service('http://www.spurl.net/spurl.php?v=3&url=', '&title=');
+	}
+
+	function add_to_wists()
+	{
+		return $this->add_to_service('http://wists.com/r.php?c=&r=', '&title=');
+	}
+
+	function search_technorati()
+	{
+		return $this->add_to_service('http://www.technorati.com/search/');
+	}
+}
+
+class SimplePie_Source
+{
+	var $item;
+	var $data = array();
+
+	function SimplePie_Source($item, $data)
+	{
+		$this->item = $item;
+		$this->data = $data;
+	}
+
+	function __toString()
+	{
+		return md5(serialize($this->data));
+	}
+
+	function get_source_tags($namespace, $tag)
+	{
+		if (isset($this->data['child'][$namespace][$tag]))
+		{
+			return $this->data['child'][$namespace][$tag];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_base($element = array())
+	{
+		return $this->item->get_base($element);
+	}
+
+	function sanitize($data, $type, $base = '')
+	{
+		return $this->item->sanitize($data, $type, $base);
+	}
+
+	function get_item()
+	{
+		return $this->item;
+	}
+
+	function get_title()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_category($key = 0)
+	{
+		$categories = $this->get_categories();
+		if (isset($categories[$key]))
+		{
+			return $categories[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_categories()
+	{
+		$categories = array();
+
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
+		{
+			$term = null;
+			$scheme = null;
+			$label = null;
+			if (isset($category['attribs']['']['term']))
+			{
+				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['scheme']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($category['attribs']['']['label']))
+			{
+				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			$categories[] = new $this->item->feed->category_class($term, $scheme, $label);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
+		{
+			// This is really the label, but keep this as the term also for BC.
+			// Label will also work on retrieving because that falls back to term.
+			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			if (isset($category['attribs']['']['domain']))
+			{
+				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			else
+			{
+				$scheme = null;
+			}
+			$categories[] = new $this->item->feed->category_class($term, $scheme, null);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
+		{
+			$categories[] = new $this->item->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
+		{
+			$categories[] = new $this->item->feed->category_class($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($categories))
+		{
+			return SimplePie_Misc::array_unique($categories);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_author($key = 0)
+	{
+		$authors = $this->get_authors();
+		if (isset($authors[$key]))
+		{
+			return $authors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_authors()
+	{
+		$authors = array();
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$authors[] = new $this->item->feed->author_class($name, $uri, $email);
+			}
+		}
+		if ($author = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$authors[] = new $this->item->feed->author_class($name, $url, $email);
+			}
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
+		{
+			$authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
+		{
+			$authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
+		{
+			$authors[] = new $this->item->feed->author_class($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null);
+		}
+
+		if (!empty($authors))
+		{
+			return SimplePie_Misc::array_unique($authors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_contributor($key = 0)
+	{
+		$contributors = $this->get_contributors();
+		if (isset($contributors[$key]))
+		{
+			return $contributors[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_contributors()
+	{
+		$contributors = array();
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
+		{
+			$name = null;
+			$uri = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
+			{
+				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $uri !== null)
+			{
+				$contributors[] = new $this->item->feed->author_class($name, $uri, $email);
+			}
+		}
+		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
+		{
+			$name = null;
+			$url = null;
+			$email = null;
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
+			{
+				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
+			{
+				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
+			}
+			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
+			{
+				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+			}
+			if ($name !== null || $email !== null || $url !== null)
+			{
+				$contributors[] = new $this->item->feed->author_class($name, $url, $email);
+			}
+		}
+
+		if (!empty($contributors))
+		{
+			return SimplePie_Misc::array_unique($contributors);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_link($key = 0, $rel = 'alternate')
+	{
+		$links = $this->get_links($rel);
+		if (isset($links[$key]))
+		{
+			return $links[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Added for parity between the parent-level and the item/entry-level.
+	 */
+	function get_permalink()
+	{
+		return $this->get_link(0);
+	}
+
+	function get_links($rel = 'alternate')
+	{
+		if (!isset($this->data['links']))
+		{
+			$this->data['links'] = array();
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'))
+			{
+				foreach ($links as $link)
+				{
+					if (isset($link['attribs']['']['href']))
+					{
+						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+					}
+				}
+			}
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'))
+			{
+				foreach ($links as $link)
+				{
+					if (isset($link['attribs']['']['href']))
+					{
+						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
+						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
+
+					}
+				}
+			}
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
+			{
+				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
+			}
+
+			$keys = array_keys($this->data['links']);
+			foreach ($keys as $key)
+			{
+				if (SimplePie_Misc::is_isegment_nz_nc($key))
+				{
+					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
+						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
+					}
+					else
+					{
+						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
+					}
+				}
+				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
+				{
+					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
+				}
+				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
+			}
+		}
+
+		if (isset($this->data['links'][$rel]))
+		{
+			return $this->data['links'][$rel];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_description()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_copyright()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_10_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright'))
+		{
+			return $this->sanitize($return[0]['data'], SimplePie_Misc::atom_03_construct_type($return[0]['attribs']), $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_language()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		elseif (isset($this->data['xml_lang']))
+		{
+			return $this->sanitize($this->data['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_latitude()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', $return[0]['data'], $match))
+		{
+			return (float) $match[1];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_longitude()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
+		{
+			return (float) $return[0]['data'];
+		}
+		elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', $return[0]['data'], $match))
+		{
+			return (float) $match[2];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_image_url()
+	{
+		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image'))
+		{
+			return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI);
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon'))
+		{
+			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Author
+{
+	var $name;
+	var $link;
+	var $email;
+
+	// Constructor, used to input the data
+	function SimplePie_Author($name = null, $link = null, $email = null)
+	{
+		$this->name = $name;
+		$this->link = $link;
+		$this->email = $email;
+	}
+
+	function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	function get_name()
+	{
+		if ($this->name !== null)
+		{
+			return $this->name;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_link()
+	{
+		if ($this->link !== null)
+		{
+			return $this->link;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_email()
+	{
+		if ($this->email !== null)
+		{
+			return $this->email;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Category
+{
+	var $term;
+	var $scheme;
+	var $label;
+
+	// Constructor, used to input the data
+	function SimplePie_Category($term = null, $scheme = null, $label = null)
+	{
+		$this->term = $term;
+		$this->scheme = $scheme;
+		$this->label = $label;
+	}
+
+	function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	function get_term()
+	{
+		if ($this->term !== null)
+		{
+			return $this->term;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_scheme()
+	{
+		if ($this->scheme !== null)
+		{
+			return $this->scheme;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_label()
+	{
+		if ($this->label !== null)
+		{
+			return $this->label;
+		}
+		else
+		{
+			return $this->get_term();
+		}
+	}
+}
+
+class SimplePie_Enclosure
+{
+	var $bitrate;
+	var $captions;
+	var $categories;
+	var $channels;
+	var $copyright;
+	var $credits;
+	var $description;
+	var $duration;
+	var $expression;
+	var $framerate;
+	var $handler;
+	var $hashes;
+	var $height;
+	var $javascript;
+	var $keywords;
+	var $lang;
+	var $length;
+	var $link;
+	var $medium;
+	var $player;
+	var $ratings;
+	var $restrictions;
+	var $samplingrate;
+	var $thumbnails;
+	var $title;
+	var $type;
+	var $width;
+
+	// Constructor, used to input the data
+	function SimplePie_Enclosure($link = null, $type = null, $length = null, $javascript = null, $bitrate = null, $captions = null, $categories = null, $channels = null, $copyright = null, $credits = null, $description = null, $duration = null, $expression = null, $framerate = null, $hashes = null, $height = null, $keywords = null, $lang = null, $medium = null, $player = null, $ratings = null, $restrictions = null, $samplingrate = null, $thumbnails = null, $title = null, $width = null)
+	{
+		$this->bitrate = $bitrate;
+		$this->captions = $captions;
+		$this->categories = $categories;
+		$this->channels = $channels;
+		$this->copyright = $copyright;
+		$this->credits = $credits;
+		$this->description = $description;
+		$this->duration = $duration;
+		$this->expression = $expression;
+		$this->framerate = $framerate;
+		$this->hashes = $hashes;
+		$this->height = $height;
+		$this->javascript = $javascript;
+		$this->keywords = $keywords;
+		$this->lang = $lang;
+		$this->length = $length;
+		$this->link = $link;
+		$this->medium = $medium;
+		$this->player = $player;
+		$this->ratings = $ratings;
+		$this->restrictions = $restrictions;
+		$this->samplingrate = $samplingrate;
+		$this->thumbnails = $thumbnails;
+		$this->title = $title;
+		$this->type = $type;
+		$this->width = $width;
+		if (class_exists('idna_convert'))
+		{
+			$idn = new idna_convert;
+			$parsed = SimplePie_Misc::parse_url($link);
+			$this->link = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']);
+		}
+		$this->handler = $this->get_handler(); // Needs to load last
+	}
+
+	function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	function get_bitrate()
+	{
+		if ($this->bitrate !== null)
+		{
+			return $this->bitrate;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_caption($key = 0)
+	{
+		$captions = $this->get_captions();
+		if (isset($captions[$key]))
+		{
+			return $captions[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_captions()
+	{
+		if ($this->captions !== null)
+		{
+			return $this->captions;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_category($key = 0)
+	{
+		$categories = $this->get_categories();
+		if (isset($categories[$key]))
+		{
+			return $categories[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_categories()
+	{
+		if ($this->categories !== null)
+		{
+			return $this->categories;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_channels()
+	{
+		if ($this->channels !== null)
+		{
+			return $this->channels;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_copyright()
+	{
+		if ($this->copyright !== null)
+		{
+			return $this->copyright;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_credit($key = 0)
+	{
+		$credits = $this->get_credits();
+		if (isset($credits[$key]))
+		{
+			return $credits[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_credits()
+	{
+		if ($this->credits !== null)
+		{
+			return $this->credits;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_description()
+	{
+		if ($this->description !== null)
+		{
+			return $this->description;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_duration($convert = false)
+	{
+		if ($this->duration !== null)
+		{
+			if ($convert)
+			{
+				$time = SimplePie_Misc::time_hms($this->duration);
+				return $time;
+			}
+			else
+			{
+				return $this->duration;
+			}
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_expression()
+	{
+		if ($this->expression !== null)
+		{
+			return $this->expression;
+		}
+		else
+		{
+			return 'full';
+		}
+	}
+
+	function get_extension()
+	{
+		if ($this->link !== null)
+		{
+			$url = SimplePie_Misc::parse_url($this->link);
+			if ($url['path'] !== '')
+			{
+				return pathinfo($url['path'], PATHINFO_EXTENSION);
+			}
+		}
+		return null;
+	}
+
+	function get_framerate()
+	{
+		if ($this->framerate !== null)
+		{
+			return $this->framerate;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_handler()
+	{
+		return $this->get_real_type(true);
+	}
+
+	function get_hash($key = 0)
+	{
+		$hashes = $this->get_hashes();
+		if (isset($hashes[$key]))
+		{
+			return $hashes[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_hashes()
+	{
+		if ($this->hashes !== null)
+		{
+			return $this->hashes;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_height()
+	{
+		if ($this->height !== null)
+		{
+			return $this->height;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_language()
+	{
+		if ($this->lang !== null)
+		{
+			return $this->lang;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_keyword($key = 0)
+	{
+		$keywords = $this->get_keywords();
+		if (isset($keywords[$key]))
+		{
+			return $keywords[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_keywords()
+	{
+		if ($this->keywords !== null)
+		{
+			return $this->keywords;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_length()
+	{
+		if ($this->length !== null)
+		{
+			return $this->length;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_link()
+	{
+		if ($this->link !== null)
+		{
+			return urldecode($this->link);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_medium()
+	{
+		if ($this->medium !== null)
+		{
+			return $this->medium;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_player()
+	{
+		if ($this->player !== null)
+		{
+			return $this->player;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_rating($key = 0)
+	{
+		$ratings = $this->get_ratings();
+		if (isset($ratings[$key]))
+		{
+			return $ratings[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_ratings()
+	{
+		if ($this->ratings !== null)
+		{
+			return $this->ratings;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_restriction($key = 0)
+	{
+		$restrictions = $this->get_restrictions();
+		if (isset($restrictions[$key]))
+		{
+			return $restrictions[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_restrictions()
+	{
+		if ($this->restrictions !== null)
+		{
+			return $this->restrictions;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_sampling_rate()
+	{
+		if ($this->samplingrate !== null)
+		{
+			return $this->samplingrate;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_size()
+	{
+		$length = $this->get_length();
+		if ($length !== null)
+		{
+			return round($length/1048576, 2);
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_thumbnail($key = 0)
+	{
+		$thumbnails = $this->get_thumbnails();
+		if (isset($thumbnails[$key]))
+		{
+			return $thumbnails[$key];
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_thumbnails()
+	{
+		if ($this->thumbnails !== null)
+		{
+			return $this->thumbnails;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_title()
+	{
+		if ($this->title !== null)
+		{
+			return $this->title;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_type()
+	{
+		if ($this->type !== null)
+		{
+			return $this->type;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_width()
+	{
+		if ($this->width !== null)
+		{
+			return $this->width;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function native_embed($options='')
+	{
+		return $this->embed($options, true);
+	}
+
+	/**
+	 * @todo If the dimensions for media:content are defined, use them when width/height are set to 'auto'.
+	 */
+	function embed($options = '', $native = false)
+	{
+		// Set up defaults
+		$audio = '';
+		$video = '';
+		$alt = '';
+		$altclass = '';
+		$loop = 'false';
+		$width = 'auto';
+		$height = 'auto';
+		$bgcolor = '#ffffff';
+		$mediaplayer = '';
+		$widescreen = false;
+		$handler = $this->get_handler();
+		$type = $this->get_real_type();
+
+		// Process options and reassign values as necessary
+		if (is_array($options))
+		{
+			extract($options);
+		}
+		else
+		{
+			$options = explode(',', $options);
+			foreach($options as $option)
+			{
+				$opt = explode(':', $option, 2);
+				if (isset($opt[0], $opt[1]))
+				{
+					$opt[0] = trim($opt[0]);
+					$opt[1] = trim($opt[1]);
+					switch ($opt[0])
+					{
+						case 'audio':
+							$audio = $opt[1];
+							break;
+
+						case 'video':
+							$video = $opt[1];
+							break;
+
+						case 'alt':
+							$alt = $opt[1];
+							break;
+
+						case 'altclass':
+							$altclass = $opt[1];
+							break;
+
+						case 'loop':
+							$loop = $opt[1];
+							break;
+
+						case 'width':
+							$width = $opt[1];
+							break;
+
+						case 'height':
+							$height = $opt[1];
+							break;
+
+						case 'bgcolor':
+							$bgcolor = $opt[1];
+							break;
+
+						case 'mediaplayer':
+							$mediaplayer = $opt[1];
+							break;
+
+						case 'widescreen':
+							$widescreen = $opt[1];
+							break;
+					}
+				}
+			}
+		}
+
+		$mime = explode('/', $type, 2);
+		$mime = $mime[0];
+
+		// Process values for 'auto'
+		if ($width === 'auto')
+		{
+			if ($mime === 'video')
+			{
+				if ($height === 'auto')
+				{
+					$width = 480;
+				}
+				elseif ($widescreen)
+				{
+					$width = round((intval($height)/9)*16);
+				}
+				else
+				{
+					$width = round((intval($height)/3)*4);
+				}
+			}
+			else
+			{
+				$width = '100%';
+			}
+		}
+
+		if ($height === 'auto')
+		{
+			if ($mime === 'audio')
+			{
+				$height = 0;
+			}
+			elseif ($mime === 'video')
+			{
+				if ($width === 'auto')
+				{
+					if ($widescreen)
+					{
+						$height = 270;
+					}
+					else
+					{
+						$height = 360;
+					}
+				}
+				elseif ($widescreen)
+				{
+					$height = round((intval($width)/16)*9);
+				}
+				else
+				{
+					$height = round((intval($width)/4)*3);
+				}
+			}
+			else
+			{
+				$height = 376;
+			}
+		}
+		elseif ($mime === 'audio')
+		{
+			$height = 0;
+		}
+
+		// Set proper placeholder value
+		if ($mime === 'audio')
+		{
+			$placeholder = $audio;
+		}
+		elseif ($mime === 'video')
+		{
+			$placeholder = $video;
+		}
+
+		$embed = '';
+
+		// Make sure the JS library is included
+		if (!$native)
+		{
+			static $javascript_outputted = null;
+			if (!$javascript_outputted && $this->javascript)
+			{
+				$embed .= '<script type="text/javascript" src="?' . htmlspecialchars($this->javascript) . '"></script>';
+				$javascript_outputted = true;
+			}
+		}
+
+		// Odeo Feed MP3's
+		if ($handler === 'odeo')
+		{
+			if ($native)
+			{
+				$embed .= '<embed src="http://odeo.com/flash/audio_player_fullsize.swf" pluginspage="http://adobe.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="440" height="80" wmode="transparent" allowScriptAccess="any" flashvars="valid_sample_rate=true&external_url=' . $this->get_link() . '"></embed>';
+			}
+			else
+			{
+				$embed .= '<script type="text/javascript">embed_odeo("' . $this->get_link() . '");</script>';
+			}
+		}
+
+		// Flash
+		elseif ($handler === 'flash')
+		{
+			if ($native)
+			{
+				$embed .= "<embed src=\"" . $this->get_link() . "\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\"></embed>";
+			}
+			else
+			{
+				$embed .= "<script type='text/javascript'>embed_flash('$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$loop', '$type');</script>";
+			}
+		}
+
+		// Flash Media Player file types.
+		// Preferred handler for MP3 file types.
+		elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== ''))
+		{
+			$height += 20;
+			if ($native)
+			{
+				$embed .= "<embed src=\"$mediaplayer\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" quality=\"high\" width=\"$width\" height=\"$height\" wmode=\"transparent\" flashvars=\"file=" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\"></embed>";
+			}
+			else
+			{
+				$embed .= "<script type='text/javascript'>embed_flv('$width', '$height', '" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "', '$placeholder', '$loop', '$mediaplayer');</script>";
+			}
+		}
+
+		// QuickTime 7 file types.  Need to test with QuickTime 6.
+		// Only handle MP3's if the Flash Media Player is not present.
+		elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === ''))
+		{
+			$height += 16;
+			if ($native)
+			{
+				if ($placeholder !== '')
+				{
+					$embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" href=\"" . $this->get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>";
+				}
+				else
+				{
+					$embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" src=\"" . $this->get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>";
+				}
+			}
+			else
+			{
+				$embed .= "<script type='text/javascript'>embed_quicktime('$type', '$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$placeholder', '$loop');</script>";
+			}
+		}
+
+		// Windows Media
+		elseif ($handler === 'wmedia')
+		{
+			$height += 45;
+			if ($native)
+			{
+				$embed .= "<embed type=\"application/x-mplayer2\" src=\"" . $this->get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\"></embed>";
+			}
+			else
+			{
+				$embed .= "<script type='text/javascript'>embed_wmedia('$width', '$height', '" . $this->get_link() . "');</script>";
+			}
+		}
+
+		// Everything else
+		else $embed .= '<a href="' . $this->get_link() . '" class="' . $altclass . '">' . $alt . '</a>';
+
+		return $embed;
+	}
+
+	function get_real_type($find_handler = false)
+	{
+		// If it's Odeo, let's get it out of the way.
+		if (substr(strtolower($this->get_link()), 0, 15) === 'http://odeo.com')
+		{
+			return 'odeo';
+		}
+
+		// Mime-types by handler.
+		$types_flash = array('application/x-shockwave-flash', 'application/futuresplash'); // Flash
+		$types_fmedia = array('video/flv', 'video/x-flv','flv-application/octet-stream'); // Flash Media Player
+		$types_quicktime = array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video'); // QuickTime
+		$types_wmedia = array('application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'); // Windows Media
+		$types_mp3 = array('audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg'); // MP3
+
+		if ($this->get_type() !== null)
+		{
+			$type = strtolower($this->type);
+		}
+		else
+		{
+			$type = null;
+		}
+
+		// If we encounter an unsupported mime-type, check the file extension and guess intelligently.
+		if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3)))
+		{
+			switch (strtolower($this->get_extension()))
+			{
+				// Audio mime-types
+				case 'aac':
+				case 'adts':
+					$type = 'audio/acc';
+					break;
+
+				case 'aif':
+				case 'aifc':
+				case 'aiff':
+				case 'cdda':
+					$type = 'audio/aiff';
+					break;
+
+				case 'bwf':
+					$type = 'audio/wav';
+					break;
+
+				case 'kar':
+				case 'mid':
+				case 'midi':
+				case 'smf':
+					$type = 'audio/midi';
+					break;
+
+				case 'm4a':
+					$type = 'audio/x-m4a';
+					break;
+
+				case 'mp3':
+				case 'swa':
+					$type = 'audio/mp3';
+					break;
+
+				case 'wav':
+					$type = 'audio/wav';
+					break;
+
+				case 'wax':
+					$type = 'audio/x-ms-wax';
+					break;
+
+				case 'wma':
+					$type = 'audio/x-ms-wma';
+					break;
+
+				// Video mime-types
+				case '3gp':
+				case '3gpp':
+					$type = 'video/3gpp';
+					break;
+
+				case '3g2':
+				case '3gp2':
+					$type = 'video/3gpp2';
+					break;
+
+				case 'asf':
+					$type = 'video/x-ms-asf';
+					break;
+
+				case 'flv':
+					$type = 'video/x-flv';
+					break;
+
+				case 'm1a':
+				case 'm1s':
+				case 'm1v':
+				case 'm15':
+				case 'm75':
+				case 'mp2':
+				case 'mpa':
+				case 'mpeg':
+				case 'mpg':
+				case 'mpm':
+				case 'mpv':
+					$type = 'video/mpeg';
+					break;
+
+				case 'm4v':
+					$type = 'video/x-m4v';
+					break;
+
+				case 'mov':
+				case 'qt':
+					$type = 'video/quicktime';
+					break;
+
+				case 'mp4':
+				case 'mpg4':
+					$type = 'video/mp4';
+					break;
+
+				case 'sdv':
+					$type = 'video/sd-video';
+					break;
+
+				case 'wm':
+					$type = 'video/x-ms-wm';
+					break;
+
+				case 'wmv':
+					$type = 'video/x-ms-wmv';
+					break;
+
+				case 'wvx':
+					$type = 'video/x-ms-wvx';
+					break;
+
+				// Flash mime-types
+				case 'spl':
+					$type = 'application/futuresplash';
+					break;
+
+				case 'swf':
+					$type = 'application/x-shockwave-flash';
+					break;
+			}
+		}
+
+		if ($find_handler)
+		{
+			if (in_array($type, $types_flash))
+			{
+				return 'flash';
+			}
+			elseif (in_array($type, $types_fmedia))
+			{
+				return 'fmedia';
+			}
+			elseif (in_array($type, $types_quicktime))
+			{
+				return 'quicktime';
+			}
+			elseif (in_array($type, $types_wmedia))
+			{
+				return 'wmedia';
+			}
+			elseif (in_array($type, $types_mp3))
+			{
+				return 'mp3';
+			}
+			else
+			{
+				return null;
+			}
+		}
+		else
+		{
+			return $type;
+		}
+	}
+}
+
+class SimplePie_Caption
+{
+	var $type;
+	var $lang;
+	var $startTime;
+	var $endTime;
+	var $text;
+
+	// Constructor, used to input the data
+	function SimplePie_Caption($type = null, $lang = null, $startTime = null, $endTime = null, $text = null)
+	{
+		$this->type = $type;
+		$this->lang = $lang;
+		$this->startTime = $startTime;
+		$this->endTime = $endTime;
+		$this->text = $text;
+	}
+
+	function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	function get_endtime()
+	{
+		if ($this->endTime !== null)
+		{
+			return $this->endTime;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_language()
+	{
+		if ($this->lang !== null)
+		{
+			return $this->lang;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_starttime()
+	{
+		if ($this->startTime !== null)
+		{
+			return $this->startTime;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_text()
+	{
+		if ($this->text !== null)
+		{
+			return $this->text;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_type()
+	{
+		if ($this->type !== null)
+		{
+			return $this->type;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Credit
+{
+	var $role;
+	var $scheme;
+	var $name;
+
+	// Constructor, used to input the data
+	function SimplePie_Credit($role = null, $scheme = null, $name = null)
+	{
+		$this->role = $role;
+		$this->scheme = $scheme;
+		$this->name = $name;
+	}
+
+	function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	function get_role()
+	{
+		if ($this->role !== null)
+		{
+			return $this->role;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_scheme()
+	{
+		if ($this->scheme !== null)
+		{
+			return $this->scheme;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_name()
+	{
+		if ($this->name !== null)
+		{
+			return $this->name;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Copyright
+{
+	var $url;
+	var $label;
+
+	// Constructor, used to input the data
+	function SimplePie_Copyright($url = null, $label = null)
+	{
+		$this->url = $url;
+		$this->label = $label;
+	}
+
+	function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	function get_url()
+	{
+		if ($this->url !== null)
+		{
+			return $this->url;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_attribution()
+	{
+		if ($this->label !== null)
+		{
+			return $this->label;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Rating
+{
+	var $scheme;
+	var $value;
+
+	// Constructor, used to input the data
+	function SimplePie_Rating($scheme = null, $value = null)
+	{
+		$this->scheme = $scheme;
+		$this->value = $value;
+	}
+
+	function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	function get_scheme()
+	{
+		if ($this->scheme !== null)
+		{
+			return $this->scheme;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_value()
+	{
+		if ($this->value !== null)
+		{
+			return $this->value;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+class SimplePie_Restriction
+{
+	var $relationship;
+	var $type;
+	var $value;
+
+	// Constructor, used to input the data
+	function SimplePie_Restriction($relationship = null, $type = null, $value = null)
+	{
+		$this->relationship = $relationship;
+		$this->type = $type;
+		$this->value = $value;
+	}
+
+	function __toString()
+	{
+		// There is no $this->data here
+		return md5(serialize($this));
+	}
+
+	function get_relationship()
+	{
+		if ($this->relationship !== null)
+		{
+			return $this->relationship;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_type()
+	{
+		if ($this->type !== null)
+		{
+			return $this->type;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	function get_value()
+	{
+		if ($this->value !== null)
+		{
+			return $this->value;
+		}
+		else
+		{
+			return null;
+		}
+	}
+}
+
+/**
+ * @todo Move to properly supporting RFC2616 (HTTP/1.1)
+ */
+class SimplePie_File
+{
+	var $url;
+	var $useragent;
+	var $success = true;
+	var $headers = array();
+	var $body;
+	var $status_code;
+	var $redirects = 0;
+	var $error;
+	var $method = SIMPLEPIE_FILE_SOURCE_NONE;
+
+	function SimplePie_File($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false)
+	{
+		if (class_exists('idna_convert'))
+		{
+			$idn = new idna_convert;
+			$parsed = SimplePie_Misc::parse_url($url);
+			$url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']);
+		}
+		$this->url = $url;
+		$this->useragent = $useragent;
+		if (preg_match('/^http(s)?:\/\//i', $url))
+		{
+			if ($useragent === null)
+			{
+				$useragent = ini_get('user_agent');
+				$this->useragent = $useragent;
+			}
+			if (!is_array($headers))
+			{
+				$headers = array();
+			}
+			if (!$force_fsockopen && function_exists('curl_exec'))
+			{
+				$this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL;
+				$fp = curl_init();
+				$headers2 = array();
+				foreach ($headers as $key => $value)
+				{
+					$headers2[] = "$key: $value";
+				}
+				if (version_compare(SimplePie_Misc::get_curl_version(), '7.10.5', '>='))
+				{
+					curl_setopt($fp, CURLOPT_ENCODING, '');
+				}
+				curl_setopt($fp, CURLOPT_URL, $url);
+				curl_setopt($fp, CURLOPT_HEADER, 1);
+				curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1);
+				curl_setopt($fp, CURLOPT_TIMEOUT, $timeout);
+				curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout);
+				curl_setopt($fp, CURLOPT_REFERER, $url);
+				curl_setopt($fp, CURLOPT_USERAGENT, $useragent);
+				curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2);
+				if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>='))
+				{
+					curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1);
+					curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects);
+				}
+
+				$this->headers = curl_exec($fp);
+				if (curl_errno($fp) === 23 || curl_errno($fp) === 61)
+				{
+					curl_setopt($fp, CURLOPT_ENCODING, 'none');
+					$this->headers = curl_exec($fp);
+				}
+				if (curl_errno($fp))
+				{
+					$this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp);
+					$this->success = false;
+				}
+				else
+				{
+					$info = curl_getinfo($fp);
+					curl_close($fp);
+					$this->headers = explode("\r\n\r\n", $this->headers, $info['redirect_count'] + 1);
+					$this->headers = array_pop($this->headers);
+					$parser = new SimplePie_HTTP_Parser($this->headers);
+					if ($parser->parse())
+					{
+						$this->headers = $parser->headers;
+						$this->body = $parser->body;
+						$this->status_code = $parser->status_code;
+						if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects)
+						{
+							$this->redirects++;
+							$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
+							return $this->SimplePie_File($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
+						}
+					}
+				}
+			}
+			else
+			{
+				$this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN;
+				$url_parts = parse_url($url);
+				if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https')
+				{
+					$url_parts['host'] = "ssl://$url_parts[host]";
+					$url_parts['port'] = 443;
+				}
+				if (!isset($url_parts['port']))
+				{
+					$url_parts['port'] = 80;
+				}
+				$fp = @fsockopen($url_parts['host'], $url_parts['port'], $errno, $errstr, $timeout);
+				if (!$fp)
+				{
+					$this->error = 'fsockopen error: ' . $errstr;
+					$this->success = false;
+				}
+				else
+				{
+					stream_set_timeout($fp, $timeout);
+					if (isset($url_parts['path']))
+					{
+						if (isset($url_parts['query']))
+						{
+							$get = "$url_parts[path]?$url_parts[query]";
+						}
+						else
+						{
+							$get = $url_parts['path'];
+						}
+					}
+					else
+					{
+						$get = '/';
+					}
+					$out = "GET $get HTTP/1.0\r\n";
+					$out .= "Host: $url_parts[host]\r\n";
+					$out .= "User-Agent: $useragent\r\n";
+					if (extension_loaded('zlib'))
+					{
+						$out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n";
+					}
+
+					if (isset($url_parts['user']) && isset($url_parts['pass']))
+					{
+						$out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n";
+					}
+					foreach ($headers as $key => $value)
+					{
+						$out .= "$key: $value\r\n";
+					}
+					$out .= "Connection: Close\r\n\r\n";
+					fwrite($fp, $out);
+
+					$info = stream_get_meta_data($fp);
+
+					$this->headers = '';
+					while (!$info['eof'] && !$info['timed_out'])
+					{
+						$this->headers .= fread($fp, 1160);
+						$info = stream_get_meta_data($fp);
+					}
+					if (!$info['timed_out'])
+					{
+						$parser = new SimplePie_HTTP_Parser($this->headers);
+						if ($parser->parse())
+						{
+							$this->headers = $parser->headers;
+							$this->body = $parser->body;
+							$this->status_code = $parser->status_code;
+							if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects)
+							{
+								$this->redirects++;
+								$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
+								return $this->SimplePie_File($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
+							}
+							if (isset($this->headers['content-encoding']))
+							{
+								// Hey, we act dumb elsewhere, so let's do that here too
+								switch (strtolower(trim($this->headers['content-encoding'], "\x09\x0A\x0D\x20")))
+								{
+									case 'gzip':
+									case 'x-gzip':
+										$decoder = new SimplePie_gzdecode($this->body);
+										if (!$decoder->parse())
+										{
+											$this->error = 'Unable to decode HTTP "gzip" stream';
+											$this->success = false;
+										}
+										else
+										{
+											$this->body = $decoder->data;
+										}
+										break;
+
+									case 'deflate':
+										if (($body = gzuncompress($this->body)) === false)
+										{
+											if (($body = gzinflate($this->body)) === false)
+											{
+												$this->error = 'Unable to decode HTTP "deflate" stream';
+												$this->success = false;
+											}
+										}
+										$this->body = $body;
+										break;
+
+									default:
+										$this->error = 'Unknown content coding';
+										$this->success = false;
+								}
+							}
+						}
+					}
+					else
+					{
+						$this->error = 'fsocket timed out';
+						$this->success = false;
+					}
+					fclose($fp);
+				}
+			}
+		}
+		else
+		{
+			$this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS;
+			if (!$this->body = file_get_contents($url))
+			{
+				$this->error = 'file_get_contents could not read the file';
+				$this->success = false;
+			}
+		}
+	}
+}
+
+/**
+ * HTTP Response Parser
+ *
+ * @package SimplePie
+ */
+class SimplePie_HTTP_Parser
+{
+	/**
+	 * HTTP Version
+	 *
+	 * @access public
+	 * @var float
+	 */
+	var $http_version = 0.0;
+
+	/**
+	 * Status code
+	 *
+	 * @access public
+	 * @var int
+	 */
+	var $status_code = 0;
+
+	/**
+	 * Reason phrase
+	 *
+	 * @access public
+	 * @var string
+	 */
+	var $reason = '';
+
+	/**
+	 * Key/value pairs of the headers
+	 *
+	 * @access public
+	 * @var array
+	 */
+	var $headers = array();
+
+	/**
+	 * Body of the response
+	 *
+	 * @access public
+	 * @var string
+	 */
+	var $body = '';
+
+	/**
+	 * Current state of the state machine
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $state = 'http_version';
+
+	/**
+	 * Input data
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $data = '';
+
+	/**
+	 * Input data length (to avoid calling strlen() everytime this is needed)
+	 *
+	 * @access private
+	 * @var int
+	 */
+	var $data_length = 0;
+
+	/**
+	 * Current position of the pointer
+	 *
+	 * @var int
+	 * @access private
+	 */
+	var $position = 0;
+
+	/**
+	 * Name of the hedaer currently being parsed
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $name = '';
+
+	/**
+	 * Value of the hedaer currently being parsed
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $value = '';
+
+	/**
+	 * Create an instance of the class with the input data
+	 *
+	 * @access public
+	 * @param string $data Input data
+	 */
+	function SimplePie_HTTP_Parser($data)
+	{
+		$this->data = $data;
+		$this->data_length = strlen($this->data);
+	}
+
+	/**
+	 * Parse the input data
+	 *
+	 * @access public
+	 * @return bool true on success, false on failure
+	 */
+	function parse()
+	{
+		while ($this->state && $this->state !== 'emit' && $this->has_data())
+		{
+			$state = $this->state;
+			$this->$state();
+		}
+		$this->data = '';
+		if ($this->state === 'emit' || $this->state === 'body')
+		{
+			return true;
+		}
+		else
+		{
+			$this->http_version = '';
+			$this->status_code = '';
+			$this->reason = '';
+			$this->headers = array();
+			$this->body = '';
+			return false;
+		}
+	}
+
+	/**
+	 * Check whether there is data beyond the pointer
+	 *
+	 * @access private
+	 * @return bool true if there is further data, false if not
+	 */
+	function has_data()
+	{
+		return (bool) ($this->position < $this->data_length);
+	}
+
+	/**
+	 * See if the next character is LWS
+	 *
+	 * @access private
+	 * @return bool true if the next character is LWS, false if not
+	 */
+	function is_linear_whitespace()
+	{
+		return (bool) ($this->data[$this->position] === "\x09"
+			|| $this->data[$this->position] === "\x20"
+			|| ($this->data[$this->position] === "\x0A"
+				&& isset($this->data[$this->position + 1])
+				&& ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20")));
+	}
+
+	/**
+	 * Parse the HTTP version
+	 *
+	 * @access private
+	 */
+	function http_version()
+	{
+		if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/')
+		{
+			$len = strspn($this->data, '0123456789.', 5);
+			$this->http_version = substr($this->data, 5, $len);
+			$this->position += 5 + $len;
+			if (substr_count($this->http_version, '.') <= 1)
+			{
+				$this->http_version = (float) $this->http_version;
+				$this->position += strspn($this->data, "\x09\x20", $this->position);
+				$this->state = 'status';
+			}
+			else
+			{
+				$this->state = false;
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	/**
+	 * Parse the status code
+	 *
+	 * @access private
+	 */
+	function status()
+	{
+		if ($len = strspn($this->data, '0123456789', $this->position))
+		{
+			$this->status_code = (int) substr($this->data, $this->position, $len);
+			$this->position += $len;
+			$this->state = 'reason';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	/**
+	 * Parse the reason phrase
+	 *
+	 * @access private
+	 */
+	function reason()
+	{
+		$len = strcspn($this->data, "\x0A", $this->position);
+		$this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20");
+		$this->position += $len + 1;
+		$this->state = 'new_line';
+	}
+
+	/**
+	 * Deal with a new line, shifting data around as needed
+	 *
+	 * @access private
+	 */
+	function new_line()
+	{
+		$this->value = trim($this->value, "\x0D\x20");
+		if ($this->name !== '' && $this->value !== '')
+		{
+			$this->name = strtolower($this->name);
+			if (isset($this->headers[$this->name]))
+			{
+				$this->headers[$this->name] .= ', ' . $this->value;
+			}
+			else
+			{
+				$this->headers[$this->name] = $this->value;
+			}
+		}
+		$this->name = '';
+		$this->value = '';
+		if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A")
+		{
+			$this->position += 2;
+			$this->state = 'body';
+		}
+		elseif ($this->data[$this->position] === "\x0A")
+		{
+			$this->position++;
+			$this->state = 'body';
+		}
+		else
+		{
+			$this->state = 'name';
+		}
+	}
+
+	/**
+	 * Parse a header name
+	 *
+	 * @access private
+	 */
+	function name()
+	{
+		$len = strcspn($this->data, "\x0A:", $this->position);
+		if (isset($this->data[$this->position + $len]))
+		{
+			if ($this->data[$this->position + $len] === "\x0A")
+			{
+				$this->position += $len;
+				$this->state = 'new_line';
+			}
+			else
+			{
+				$this->name = substr($this->data, $this->position, $len);
+				$this->position += $len + 1;
+				$this->state = 'value';
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	/**
+	 * Parse LWS, replacing consecutive LWS characters with a single space
+	 *
+	 * @access private
+	 */
+	function linear_whitespace()
+	{
+		do
+		{
+			if (substr($this->data, $this->position, 2) === "\x0D\x0A")
+			{
+				$this->position += 2;
+			}
+			elseif ($this->data[$this->position] === "\x0A")
+			{
+				$this->position++;
+			}
+			$this->position += strspn($this->data, "\x09\x20", $this->position);
+		} while ($this->has_data() && $this->is_linear_whitespace());
+		$this->value .= "\x20";
+	}
+
+	/**
+	 * See what state to move to while within non-quoted header values
+	 *
+	 * @access private
+	 */
+	function value()
+	{
+		if ($this->is_linear_whitespace())
+		{
+			$this->linear_whitespace();
+		}
+		else
+		{
+			switch ($this->data[$this->position])
+			{
+				case '"':
+					$this->position++;
+					$this->state = 'quote';
+					break;
+
+				case "\x0A":
+					$this->position++;
+					$this->state = 'new_line';
+					break;
+
+				default:
+					$this->state = 'value_char';
+					break;
+			}
+		}
+	}
+
+	/**
+	 * Parse a header value while outside quotes
+	 *
+	 * @access private
+	 */
+	function value_char()
+	{
+		$len = strcspn($this->data, "\x09\x20\x0A\"", $this->position);
+		$this->value .= substr($this->data, $this->position, $len);
+		$this->position += $len;
+		$this->state = 'value';
+	}
+
+	/**
+	 * See what state to move to while within quoted header values
+	 *
+	 * @access private
+	 */
+	function quote()
+	{
+		if ($this->is_linear_whitespace())
+		{
+			$this->linear_whitespace();
+		}
+		else
+		{
+			switch ($this->data[$this->position])
+			{
+				case '"':
+					$this->position++;
+					$this->state = 'value';
+					break;
+
+				case "\x0A":
+					$this->position++;
+					$this->state = 'new_line';
+					break;
+
+				case '\\':
+					$this->position++;
+					$this->state = 'quote_escaped';
+					break;
+
+				default:
+					$this->state = 'quote_char';
+					break;
+			}
+		}
+	}
+
+	/**
+	 * Parse a header value while within quotes
+	 *
+	 * @access private
+	 */
+	function quote_char()
+	{
+		$len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position);
+		$this->value .= substr($this->data, $this->position, $len);
+		$this->position += $len;
+		$this->state = 'value';
+	}
+
+	/**
+	 * Parse an escaped character within quotes
+	 *
+	 * @access private
+	 */
+	function quote_escaped()
+	{
+		$this->value .= $this->data[$this->position];
+		$this->position++;
+		$this->state = 'quote';
+	}
+
+	/**
+	 * Parse the body
+	 *
+	 * @access private
+	 */
+	function body()
+	{
+		$this->body = substr($this->data, $this->position);
+		$this->state = 'emit';
+	}
+}
+
+/**
+ * gzdecode
+ *
+ * @package SimplePie
+ */
+class SimplePie_gzdecode
+{
+	/**
+	 * Compressed data
+	 *
+	 * @access private
+	 * @see gzdecode::$data
+	 */
+	var $compressed_data;
+
+	/**
+	 * Size of compressed data
+	 *
+	 * @access private
+	 */
+	var $compressed_size;
+
+	/**
+	 * Minimum size of a valid gzip string
+	 *
+	 * @access private
+	 */
+	var $min_compressed_size = 18;
+
+	/**
+	 * Current position of pointer
+	 *
+	 * @access private
+	 */
+	var $position = 0;
+
+	/**
+	 * Flags (FLG)
+	 *
+	 * @access private
+	 */
+	var $flags;
+
+	/**
+	 * Uncompressed data
+	 *
+	 * @access public
+	 * @see gzdecode::$compressed_data
+	 */
+	var $data;
+
+	/**
+	 * Modified time
+	 *
+	 * @access public
+	 */
+	var $MTIME;
+
+	/**
+	 * Extra Flags
+	 *
+	 * @access public
+	 */
+	var $XFL;
+
+	/**
+	 * Operating System
+	 *
+	 * @access public
+	 */
+	var $OS;
+
+	/**
+	 * Subfield ID 1
+	 *
+	 * @access public
+	 * @see gzdecode::$extra_field
+	 * @see gzdecode::$SI2
+	 */
+	var $SI1;
+
+	/**
+	 * Subfield ID 2
+	 *
+	 * @access public
+	 * @see gzdecode::$extra_field
+	 * @see gzdecode::$SI1
+	 */
+	var $SI2;
+
+	/**
+	 * Extra field content
+	 *
+	 * @access public
+	 * @see gzdecode::$SI1
+	 * @see gzdecode::$SI2
+	 */
+	var $extra_field;
+
+	/**
+	 * Original filename
+	 *
+	 * @access public
+	 */
+	var $filename;
+
+	/**
+	 * Human readable comment
+	 *
+	 * @access public
+	 */
+	var $comment;
+
+	/**
+	 * Don't allow anything to be set
+	 *
+	 * @access public
+	 */
+	function __set($name, $value)
+	{
+		trigger_error("Cannot write property $name", E_USER_ERROR);
+	}
+
+	/**
+	 * Set the compressed string and related properties
+	 *
+	 * @access public
+	 */
+	function SimplePie_gzdecode($data)
+	{
+		$this->compressed_data = $data;
+		$this->compressed_size = strlen($data);
+	}
+
+	/**
+	 * Decode the GZIP stream
+	 *
+	 * @access public
+	 */
+	function parse()
+	{
+		if ($this->compressed_size >= $this->min_compressed_size)
+		{
+			// Check ID1, ID2, and CM
+			if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08")
+			{
+				return false;
+			}
+
+			// Get the FLG (FLaGs)
+			$this->flags = ord($this->compressed_data[3]);
+
+			// FLG bits above (1 << 4) are reserved
+			if ($this->flags > 0x1F)
+			{
+				return false;
+			}
+
+			// Advance the pointer after the above
+			$this->position += 4;
+
+			// MTIME
+			$mtime = substr($this->compressed_data, $this->position, 4);
+			// Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness
+			if (current(unpack('S', "\x00\x01")) === 1)
+			{
+				$mtime = strrev($mtime);
+			}
+			$this->MTIME = current(unpack('l', $mtime));
+			$this->position += 4;
+
+			// Get the XFL (eXtra FLags)
+			$this->XFL = ord($this->compressed_data[$this->position++]);
+
+			// Get the OS (Operating System)
+			$this->OS = ord($this->compressed_data[$this->position++]);
+
+			// Parse the FEXTRA
+			if ($this->flags & 4)
+			{
+				// Read subfield IDs
+				$this->SI1 = $this->compressed_data[$this->position++];
+				$this->SI2 = $this->compressed_data[$this->position++];
+
+				// SI2 set to zero is reserved for future use
+				if ($this->SI2 === "\x00")
+				{
+					return false;
+				}
+
+				// Get the length of the extra field
+				$len = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
+				$position += 2;
+
+				// Check the length of the string is still valid
+				$this->min_compressed_size += $len + 4;
+				if ($this->compressed_size >= $this->min_compressed_size)
+				{
+					// Set the extra field to the given data
+					$this->extra_field = substr($this->compressed_data, $this->position, $len);
+					$this->position += $len;
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Parse the FNAME
+			if ($this->flags & 8)
+			{
+				// Get the length of the filename
+				$len = strcspn($this->compressed_data, "\x00", $this->position);
+
+				// Check the length of the string is still valid
+				$this->min_compressed_size += $len + 1;
+				if ($this->compressed_size >= $this->min_compressed_size)
+				{
+					// Set the original filename to the given string
+					$this->filename = substr($this->compressed_data, $this->position, $len);
+					$this->position += $len + 1;
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Parse the FCOMMENT
+			if ($this->flags & 16)
+			{
+				// Get the length of the comment
+				$len = strcspn($this->compressed_data, "\x00", $this->position);
+
+				// Check the length of the string is still valid
+				$this->min_compressed_size += $len + 1;
+				if ($this->compressed_size >= $this->min_compressed_size)
+				{
+					// Set the original comment to the given string
+					$this->comment = substr($this->compressed_data, $this->position, $len);
+					$this->position += $len + 1;
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Parse the FHCRC
+			if ($this->flags & 2)
+			{
+				// Check the length of the string is still valid
+				$this->min_compressed_size += $len + 2;
+				if ($this->compressed_size >= $this->min_compressed_size)
+				{
+					// Read the CRC
+					$crc = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
+
+					// Check the CRC matches
+					if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc)
+					{
+						$this->position += 2;
+					}
+					else
+					{
+						return false;
+					}
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Decompress the actual data
+			if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false)
+			{
+				return false;
+			}
+			else
+			{
+				$this->position = $this->compressed_size - 8;
+			}
+
+			// Check CRC of data
+			$crc = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
+			$this->position += 4;
+			/*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc))
+			{
+				return false;
+			}*/
+
+			// Check ISIZE of data
+			$isize = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
+			$this->position += 4;
+			if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize))
+			{
+				return false;
+			}
+
+			// Wow, against all odds, we've actually got a valid gzip string
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+}
+
+class SimplePie_Cache
+{
+	/**
+	 * Don't call the constructor. Please.
+	 *
+	 * @access private
+	 */
+	function SimplePie_Cache()
+	{
+		trigger_error('Please call SimplePie_Cache::create() instead of the constructor', E_USER_ERROR);
+	}
+
+	/**
+	 * Create a new SimplePie_Cache object
+	 *
+	 * @static
+	 * @access public
+	 */
+	function create($location, $filename, $extension)
+	{
+		$location_iri = new SimplePie_IRI($location);
+		switch ($location_iri->get_scheme())
+		{
+			case 'mysql':
+				if (extension_loaded('mysql'))
+				{
+					return new SimplePie_Cache_MySQL($location_iri, $filename, $extension);
+				}
+				break;
+
+			default:
+				return new SimplePie_Cache_File($location, $filename, $extension);
+		}
+	}
+}
+
+class SimplePie_Cache_File
+{
+	var $location;
+	var $filename;
+	var $extension;
+	var $name;
+
+	function SimplePie_Cache_File($location, $filename, $extension)
+	{
+		$this->location = $location;
+		$this->filename = $filename;
+		$this->extension = $extension;
+		$this->name = "$this->location/$this->filename.$this->extension";
+	}
+
+	function save($data)
+	{
+		if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location))
+		{
+			if (is_a($data, 'SimplePie'))
+			{
+				$data = $data->data;
+			}
+
+			$data = serialize($data);
+
+			if (function_exists('file_put_contents'))
+			{
+				return (bool) file_put_contents($this->name, $data);
+			}
+			else
+			{
+				$fp = fopen($this->name, 'wb');
+				if ($fp)
+				{
+					fwrite($fp, $data);
+					fclose($fp);
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	function load()
+	{
+		if (file_exists($this->name) && is_readable($this->name))
+		{
+			return unserialize(file_get_contents($this->name));
+		}
+		return false;
+	}
+
+	function mtime()
+	{
+		if (file_exists($this->name))
+		{
+			return filemtime($this->name);
+		}
+		return false;
+	}
+
+	function touch()
+	{
+		if (file_exists($this->name))
+		{
+			return touch($this->name);
+		}
+		return false;
+	}
+
+	function unlink()
+	{
+		if (file_exists($this->name))
+		{
+			return unlink($this->name);
+		}
+		return false;
+	}
+}
+
+class SimplePie_Cache_DB
+{
+	function prepare_simplepie_object_for_cache($data)
+	{
+		$items = $data->get_items();
+		$items_by_id = array();
+
+		if (!empty($items))
+		{
+			foreach ($items as $item)
+			{
+				$items_by_id[$item->get_id()] = $item;
+			}
+
+			if (count($items_by_id) !== count($items))
+			{
+				$items_by_id = array();
+				foreach ($items as $item)
+				{
+					$items_by_id[$item->get_id(true)] = $item;
+				}
+			}
+
+			if (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
+			{
+				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
+			}
+			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
+			{
+				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
+			}
+			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
+			{
+				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
+			}
+			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0]))
+			{
+				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0];
+			}
+			else
+			{
+				$channel = null;
+			}
+
+			if ($channel !== null)
+			{
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']);
+				}
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']);
+				}
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']);
+				}
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']);
+				}
+				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']))
+				{
+					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']);
+				}
+			}
+			if (isset($data->data['items']))
+			{
+				unset($data->data['items']);
+			}
+			if (isset($data->data['ordered_items']))
+			{
+				unset($data->data['ordered_items']);
+			}
+		}
+		return array(serialize($data->data), $items_by_id);
+	}
+}
+
+class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
+{
+	var $mysql;
+	var $options;
+	var $id;
+
+	function SimplePie_Cache_MySQL($mysql_location, $name, $extension)
+	{
+		$host = $mysql_location->get_host();
+		if (SimplePie_Misc::stripos($host, 'unix(') === 0 && substr($host, -1) === ')')
+		{
+			$server = ':' . substr($host, 5, -1);
+		}
+		else
+		{
+			$server = $host;
+			if ($mysql_location->get_port() !== null)
+			{
+				$server .= ':' . $mysql_location->get_port();
+			}
+		}
+
+		if (strpos($mysql_location->get_userinfo(), ':') !== false)
+		{
+			list($username, $password) = explode(':', $mysql_location->get_userinfo(), 2);
+		}
+		else
+		{
+			$username = $mysql_location->get_userinfo();
+			$password = null;
+		}
+
+		if ($this->mysql = mysql_connect($server, $username, $password))
+		{
+			$this->id = $name . $extension;
+			$this->options = SimplePie_Misc::parse_str($mysql_location->get_query());
+			if (!isset($this->options['prefix'][0]))
+			{
+				$this->options['prefix'][0] = '';
+			}
+
+			if (mysql_select_db(ltrim($mysql_location->get_path(), '/'))
+				&& mysql_query('SET NAMES utf8')
+				&& ($query = mysql_unbuffered_query('SHOW TABLES')))
+			{
+				$db = array();
+				while ($row = mysql_fetch_row($query))
+				{
+					$db[] = $row[0];
+				}
+
+				if (!in_array($this->options['prefix'][0] . 'cache_data', $db))
+				{
+					if (!mysql_query('CREATE TABLE `' . $this->options['prefix'][0] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))'))
+					{
+						$this->mysql = null;
+					}
+				}
+
+				if (!in_array($this->options['prefix'][0] . 'items', $db))
+				{
+					if (!mysql_query('CREATE TABLE `' . $this->options['prefix'][0] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'))
+					{
+						$this->mysql = null;
+					}
+				}
+			}
+			else
+			{
+				$this->mysql = null;
+			}
+		}
+	}
+
+	function save($data)
+	{
+		if ($this->mysql)
+		{
+			$feed_id = "'" . mysql_real_escape_string($this->id) . "'";
+
+			if (is_a($data, 'SimplePie'))
+			{
+				if (SIMPLEPIE_PHP5)
+				{
+					// This keyword needs to defy coding standards for PHP4 compatibility
+					$data = clone($data);
+				}
+
+				$prepared = $this->prepare_simplepie_object_for_cache($data);
+
+				if ($query = mysql_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = ' . $feed_id, $this->mysql))
+				{
+					if (mysql_num_rows($query))
+					{
+						$items = count($prepared[1]);
+						if ($items)
+						{
+							$sql = 'UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `items` = ' . $items . ', `data` = \'' . mysql_real_escape_string($prepared[0]) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id;
+						}
+						else
+						{
+							$sql = 'UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `data` = \'' . mysql_real_escape_string($prepared[0]) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id;
+						}
+
+						if (!mysql_query($sql, $this->mysql))
+						{
+							return false;
+						}
+					}
+					elseif (!mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(' . $feed_id . ', ' . count($prepared[1]) . ', \'' . mysql_real_escape_string($prepared[0]) . '\', ' . time() . ')', $this->mysql))
+					{
+						return false;
+					}
+
+					$ids = array_keys($prepared[1]);
+					if (!empty($ids))
+					{
+						foreach ($ids as $id)
+						{
+							$database_ids[] = mysql_real_escape_string($id);
+						}
+
+						if ($query = mysql_unbuffered_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'items` WHERE `id` = \'' . implode('\' OR `id` = \'', $database_ids) . '\' AND `feed_id` = ' . $feed_id, $this->mysql))
+						{
+							$existing_ids = array();
+							while ($row = mysql_fetch_row($query))
+							{
+								$existing_ids[] = $row[0];
+							}
+
+							$new_ids = array_diff($ids, $existing_ids);
+
+							foreach ($new_ids as $new_id)
+							{
+								if (!($date = $prepared[1][$new_id]->get_date('U')))
+								{
+									$date = time();
+								}
+
+								if (!mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(' . $feed_id . ', \'' . mysql_real_escape_string($new_id) . '\', \'' . mysql_real_escape_string(serialize($prepared[1][$new_id]->data)) . '\', ' . $date . ')', $this->mysql))
+								{
+									return false;
+								}
+							}
+							return true;
+						}
+					}
+					else
+					{
+						return true;
+					}
+				}
+			}
+			elseif ($query = mysql_query('SELECT `id` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = ' . $feed_id, $this->mysql))
+			{
+				if (mysql_num_rows($query))
+				{
+					if (mysql_query('UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `items` = 0, `data` = \'' . mysql_real_escape_string(serialize($data)) . '\', `mtime` = ' . time() . ' WHERE `id` = ' . $feed_id, $this->mysql))
+					{
+						return true;
+					}
+				}
+				elseif (mysql_query('INSERT INTO `' . $this->options['prefix'][0] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(\'' . mysql_real_escape_string($this->id) . '\', 0, \'' . mysql_real_escape_string(serialize($data)) . '\', ' . time() . ')', $this->mysql))
+				{
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	function load()
+	{
+		if ($this->mysql && ($query = mysql_query('SELECT `items`, `data` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($row = mysql_fetch_row($query)))
+		{
+			$data = unserialize($row[1]);
+
+			if (isset($this->options['items'][0]))
+			{
+				$items = (int) $this->options['items'][0];
+			}
+			else
+			{
+				$items = (int) $row[0];
+			}
+
+			if ($items !== 0)
+			{
+				if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
+				{
+					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
+				}
+				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
+				{
+					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
+				}
+				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
+				{
+					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
+				}
+				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]))
+				{
+					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0];
+				}
+				else
+				{
+					$feed = null;
+				}
+
+				if ($feed !== null)
+				{
+					$sql = 'SELECT `data` FROM `' . $this->options['prefix'][0] . 'items` WHERE `feed_id` = \'' . mysql_real_escape_string($this->id) . '\' ORDER BY `posted` DESC';
+					if ($items > 0)
+					{
+						$sql .= ' LIMIT ' . $items;
+					}
+
+					if ($query = mysql_unbuffered_query($sql, $this->mysql))
+					{
+						while ($row = mysql_fetch_row($query))
+						{
+							$feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row[0]);
+						}
+					}
+					else
+					{
+						return false;
+					}
+				}
+			}
+			return $data;
+		}
+		return false;
+	}
+
+	function mtime()
+	{
+		if ($this->mysql && ($query = mysql_query('SELECT `mtime` FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($row = mysql_fetch_row($query)))
+		{
+			return $row[0];
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	function touch()
+	{
+		if ($this->mysql && ($query = mysql_query('UPDATE `' . $this->options['prefix'][0] . 'cache_data` SET `mtime` = ' . time() . ' WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && mysql_affected_rows($this->mysql))
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	function unlink()
+	{
+		if ($this->mysql && ($query = mysql_query('DELETE FROM `' . $this->options['prefix'][0] . 'cache_data` WHERE `id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)) && ($query2 = mysql_query('DELETE FROM `' . $this->options['prefix'][0] . 'items` WHERE `feed_id` = \'' . mysql_real_escape_string($this->id) . "'", $this->mysql)))
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+}
+
+class SimplePie_Misc
+{
+	function time_hms($seconds)
+	{
+		$time = '';
+
+		$hours = floor($seconds / 3600);
+		$remainder = $seconds % 3600;
+		if ($hours > 0)
+		{
+			$time .= $hours.':';
+		}
+
+		$minutes = floor($remainder / 60);
+		$seconds = $remainder % 60;
+		if ($minutes < 10 && $hours > 0)
+		{
+			$minutes = '0' . $minutes;
+		}
+		if ($seconds < 10)
+		{
+			$seconds = '0' . $seconds;
+		}
+
+		$time .= $minutes.':';
+		$time .= $seconds;
+
+		return $time;
+	}
+
+	function absolutize_url($relative, $base)
+	{
+		$iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative);
+		return $iri->get_iri();
+	}
+
+	function remove_dot_segments($input)
+	{
+		$output = '';
+		while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..')
+		{
+			// A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise,
+			if (strpos($input, '../') === 0)
+			{
+				$input = substr($input, 3);
+			}
+			elseif (strpos($input, './') === 0)
+			{
+				$input = substr($input, 2);
+			}
+			// B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise,
+			elseif (strpos($input, '/./') === 0)
+			{
+				$input = substr_replace($input, '/', 0, 3);
+			}
+			elseif ($input === '/.')
+			{
+				$input = '/';
+			}
+			// C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise,
+			elseif (strpos($input, '/../') === 0)
+			{
+				$input = substr_replace($input, '/', 0, 4);
+				$output = substr_replace($output, '', strrpos($output, '/'));
+			}
+			elseif ($input === '/..')
+			{
+				$input = '/';
+				$output = substr_replace($output, '', strrpos($output, '/'));
+			}
+			// D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise,
+			elseif ($input === '.' || $input === '..')
+			{
+				$input = '';
+			}
+			// E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer
+			elseif (($pos = strpos($input, '/', 1)) !== false)
+			{
+				$output .= substr($input, 0, $pos);
+				$input = substr_replace($input, '', 0, $pos);
+			}
+			else
+			{
+				$output .= $input;
+				$input = '';
+			}
+		}
+		return $output . $input;
+	}
+
+	function get_element($realname, $string)
+	{
+		$return = array();
+		$name = preg_quote($realname, '/');
+		if (preg_match_all("/<($name)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
+		{
+			for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++)
+			{
+				$return[$i]['tag'] = $realname;
+				$return[$i]['full'] = $matches[$i][0][0];
+				$return[$i]['offset'] = $matches[$i][0][1];
+				if (strlen($matches[$i][3][0]) <= 2)
+				{
+					$return[$i]['self_closing'] = true;
+				}
+				else
+				{
+					$return[$i]['self_closing'] = false;
+					$return[$i]['content'] = $matches[$i][4][0];
+				}
+				$return[$i]['attribs'] = array();
+				if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER))
+				{
+					for ($j = 0, $total_attribs = count($attribs); $j < $total_attribs; $j++)
+					{
+						if (count($attribs[$j]) === 2)
+						{
+							$attribs[$j][2] = $attribs[$j][1];
+						}
+						$return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8');
+					}
+				}
+			}
+		}
+		return $return;
+	}
+
+	function element_implode($element)
+	{
+		$full = "<$element[tag]";
+		foreach ($element['attribs'] as $key => $value)
+		{
+			$key = strtolower($key);
+			$full .= " $key=\"" . htmlspecialchars($value['data']) . '"';
+		}
+		if ($element['self_closing'])
+		{
+			$full .= ' />';
+		}
+		else
+		{
+			$full .= ">$element[content]</$element[tag]>";
+		}
+		return $full;
+	}
+
+	function error($message, $level, $file, $line)
+	{
+		if ((ini_get('error_reporting') & $level) > 0)
+		{
+			switch ($level)
+			{
+				case E_USER_ERROR:
+					$note = 'PHP Error';
+					break;
+				case E_USER_WARNING:
+					$note = 'PHP Warning';
+					break;
+				case E_USER_NOTICE:
+					$note = 'PHP Notice';
+					break;
+				default:
+					$note = 'Unknown Error';
+					break;
+			}
+			error_log("$note: $message in $file on line $line", 0);
+		}
+		return $message;
+	}
+
+	/**
+	 * If a file has been cached, retrieve and display it.
+	 *
+	 * This is most useful for caching images (get_favicon(), etc.),
+	 * however it works for all cached files.  This WILL NOT display ANY
+	 * file/image/page/whatever, but rather only display what has already
+	 * been cached by SimplePie.
+	 *
+	 * @access public
+	 * @see SimplePie::get_favicon()
+	 * @param str $identifier_url URL that is used to identify the content.
+	 * This may or may not be the actual URL of the live content.
+	 * @param str $cache_location Location of SimplePie's cache.  Defaults
+	 * to './cache'.
+	 * @param str $cache_extension The file extension that the file was
+	 * cached with.  Defaults to 'spc'.
+	 * @param str $cache_class Name of the cache-handling class being used
+	 * in SimplePie.  Defaults to 'SimplePie_Cache', and should be left
+	 * as-is unless you've overloaded the class.
+	 * @param str $cache_name_function Obsolete. Exists for backwards
+	 * compatibility reasons only.
+	 */
+	function display_cached_file($identifier_url, $cache_location = './cache', $cache_extension = 'spc', $cache_class = 'SimplePie_Cache', $cache_name_function = 'md5')
+	{
+		$cache = call_user_func(array($cache_class, 'create'), $cache_location, $identifier_url, $cache_extension);
+
+		if ($file = $cache->load())
+		{
+			if (isset($file['headers']['content-type']))
+			{
+				header('Content-type:' . $file['headers']['content-type']);
+			}
+			else
+			{
+				header('Content-type: application/octet-stream');
+			}
+			header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days
+			echo $file['body'];
+			exit;
+		}
+
+		die('Cached file for ' . $identifier_url . ' cannot be found.');
+	}
+
+	function fix_protocol($url, $http = 1)
+	{
+		$url = SimplePie_Misc::normalize_url($url);
+		$parsed = SimplePie_Misc::parse_url($url);
+		if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https')
+		{
+			return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http);
+		}
+
+		if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url))
+		{
+			return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http);
+		}
+
+		if ($http === 2 && $parsed['scheme'] !== '')
+		{
+			return "feed:$url";
+		}
+		elseif ($http === 3 && strtolower($parsed['scheme']) === 'http')
+		{
+			return substr_replace($url, 'podcast', 0, 4);
+		}
+		elseif ($http === 4 && strtolower($parsed['scheme']) === 'http')
+		{
+			return substr_replace($url, 'itpc', 0, 4);
+		}
+		else
+		{
+			return $url;
+		}
+	}
+
+	function parse_url($url)
+	{
+		$iri = new SimplePie_IRI($url);
+		return array(
+			'scheme' => (string) $iri->get_scheme(),
+			'authority' => (string) $iri->get_authority(),
+			'path' => (string) $iri->get_path(),
+			'query' => (string) $iri->get_query(),
+			'fragment' => (string) $iri->get_fragment()
+		);
+	}
+
+	function compress_parse_url($scheme = '', $authority = '', $path = '', $query = '', $fragment = '')
+	{
+		$iri = new SimplePie_IRI('');
+		$iri->set_scheme($scheme);
+		$iri->set_authority($authority);
+		$iri->set_path($path);
+		$iri->set_query($query);
+		$iri->set_fragment($fragment);
+		return $iri->get_iri();
+	}
+
+	function normalize_url($url)
+	{
+		$iri = new SimplePie_IRI($url);
+		return $iri->get_iri();
+	}
+
+	function percent_encoding_normalization($match)
+	{
+		$integer = hexdec($match[1]);
+		if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E)
+		{
+			return chr($integer);
+		}
+		else
+		{
+			return strtoupper($match[0]);
+		}
+	}
+
+	/**
+	 * Remove bad UTF-8 bytes
+	 *
+	 * PCRE Pattern to locate bad bytes in a UTF-8 string comes from W3C
+	 * FAQ: Multilingual Forms (modified to include full ASCII range)
+	 *
+	 * @author Geoffrey Sneddon
+	 * @see http://www.w3.org/International/questions/qa-forms-utf-8
+	 * @param string $str String to remove bad UTF-8 bytes from
+	 * @return string UTF-8 string
+	 */
+	function utf8_bad_replace($str)
+	{
+		if (function_exists('iconv') && ($return = @iconv('UTF-8', 'UTF-8//IGNORE', $str)))
+		{
+			return $return;
+		}
+		elseif (function_exists('mb_convert_encoding') && ($return = @mb_convert_encoding($str, 'UTF-8', 'UTF-8')))
+		{
+			return $return;
+		}
+		elseif (preg_match_all('/(?:[\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})+/', $str, $matches))
+		{
+			return implode("\xEF\xBF\xBD", $matches[0]);
+		}
+		elseif ($str !== '')
+		{
+			return "\xEF\xBF\xBD";
+		}
+		else
+		{
+			return '';
+		}
+	}
+
+	/**
+	 * Converts a Windows-1252 encoded string to a UTF-8 encoded string
+	 *
+	 * @static
+	 * @access public
+	 * @param string $string Windows-1252 encoded string
+	 * @return string UTF-8 encoded string
+	 */
+	function windows_1252_to_utf8($string)
+	{
+		static $convert_table = array("\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\ [...]
+
+		return strtr($string, $convert_table);
+	}
+
+	function change_encoding($data, $input, $output)
+	{
+		$input = SimplePie_Misc::encoding($input);
+		$output = SimplePie_Misc::encoding($output);
+
+		// We fail to fail on non US-ASCII bytes
+		if ($input === 'US-ASCII')
+		{
+			static $non_ascii_octects = '';
+			if (!$non_ascii_octects)
+			{
+				for ($i = 0x80; $i <= 0xFF; $i++)
+				{
+					$non_ascii_octects .= chr($i);
+				}
+			}
+			$data = substr($data, 0, strcspn($data, $non_ascii_octects));
+		}
+
+		// This is first, as behaviour of this is completely predictable
+		if ($input === 'Windows-1252' && $output === 'UTF-8')
+		{
+			return SimplePie_Misc::windows_1252_to_utf8($data);
+		}
+		// This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported).
+		elseif (function_exists('mb_convert_encoding') && @mb_convert_encoding("\x80", 'UTF-16BE', $input) !== "\x00\x80" && ($return = @mb_convert_encoding($data, $output, $input)))
+		{
+			return $return;
+		}
+		// This is last, as behaviour of this varies with OS userland and PHP version
+		elseif (function_exists('iconv') && ($return = @iconv($input, $output, $data)))
+		{
+			return $return;
+		}
+		// If we can't do anything, just fail
+		else
+		{
+			return false;
+		}
+	}
+
+	function encoding($charset)
+	{
+		// Normalization from UTS #22
+		switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset)))
+		{
+			case 'adobestandardencoding':
+			case 'csadobestandardencoding':
+				return 'Adobe-Standard-Encoding';
+
+			case 'adobesymbolencoding':
+			case 'cshppsmath':
+				return 'Adobe-Symbol-Encoding';
+
+			case 'ami1251':
+			case 'amiga1251':
+				return 'Amiga-1251';
+
+			case 'ansix31101983':
+			case 'csat5001983':
+			case 'csiso99naplps':
+			case 'isoir99':
+			case 'naplps':
+				return 'ANSI_X3.110-1983';
+
+			case 'arabic7':
+			case 'asmo449':
+			case 'csiso89asmo449':
+			case 'iso9036':
+			case 'isoir89':
+				return 'ASMO_449';
+
+			case 'big5':
+			case 'csbig5':
+			case 'xxbig5':
+				return 'Big5';
+
+			case 'big5hkscs':
+				return 'Big5-HKSCS';
+
+			case 'bocu1':
+			case 'csbocu1':
+				return 'BOCU-1';
+
+			case 'brf':
+			case 'csbrf':
+				return 'BRF';
+
+			case 'bs4730':
+			case 'csiso4unitedkingdom':
+			case 'gb':
+			case 'iso646gb':
+			case 'isoir4':
+			case 'uk':
+				return 'BS_4730';
+
+			case 'bsviewdata':
+			case 'csiso47bsviewdata':
+			case 'isoir47':
+				return 'BS_viewdata';
+
+			case 'cesu8':
+			case 'cscesu8':
+				return 'CESU-8';
+
+			case 'ca':
+			case 'csa71':
+			case 'csaz243419851':
+			case 'csiso121canadian1':
+			case 'iso646ca':
+			case 'isoir121':
+				return 'CSA_Z243.4-1985-1';
+
+			case 'csa72':
+			case 'csaz243419852':
+			case 'csiso122canadian2':
+			case 'iso646ca2':
+			case 'isoir122':
+				return 'CSA_Z243.4-1985-2';
+
+			case 'csaz24341985gr':
+			case 'csiso123csaz24341985gr':
+			case 'isoir123':
+				return 'CSA_Z243.4-1985-gr';
+
+			case 'csiso139csn369103':
+			case 'csn369103':
+			case 'isoir139':
+				return 'CSN_369103';
+
+			case 'csdecmcs':
+			case 'dec':
+			case 'decmcs':
+				return 'DEC-MCS';
+
+			case 'csiso21german':
+			case 'de':
+			case 'din66003':
+			case 'iso646de':
+			case 'isoir21':
+				return 'DIN_66003';
+
+			case 'csdkus':
+			case 'dkus':
+				return 'dk-us';
+
+			case 'csiso646danish':
+			case 'dk':
+			case 'ds2089':
+			case 'iso646dk':
+				return 'DS_2089';
+
+			case 'csibmebcdicatde':
+			case 'ebcdicatde':
+				return 'EBCDIC-AT-DE';
+
+			case 'csebcdicatdea':
+			case 'ebcdicatdea':
+				return 'EBCDIC-AT-DE-A';
+
+			case 'csebcdiccafr':
+			case 'ebcdiccafr':
+				return 'EBCDIC-CA-FR';
+
+			case 'csebcdicdkno':
+			case 'ebcdicdkno':
+				return 'EBCDIC-DK-NO';
+
+			case 'csebcdicdknoa':
+			case 'ebcdicdknoa':
+				return 'EBCDIC-DK-NO-A';
+
+			case 'csebcdices':
+			case 'ebcdices':
+				return 'EBCDIC-ES';
+
+			case 'csebcdicesa':
+			case 'ebcdicesa':
+				return 'EBCDIC-ES-A';
+
+			case 'csebcdicess':
+			case 'ebcdicess':
+				return 'EBCDIC-ES-S';
+
+			case 'csebcdicfise':
+			case 'ebcdicfise':
+				return 'EBCDIC-FI-SE';
+
+			case 'csebcdicfisea':
+			case 'ebcdicfisea':
+				return 'EBCDIC-FI-SE-A';
+
+			case 'csebcdicfr':
+			case 'ebcdicfr':
+				return 'EBCDIC-FR';
+
+			case 'csebcdicit':
+			case 'ebcdicit':
+				return 'EBCDIC-IT';
+
+			case 'csebcdicpt':
+			case 'ebcdicpt':
+				return 'EBCDIC-PT';
+
+			case 'csebcdicuk':
+			case 'ebcdicuk':
+				return 'EBCDIC-UK';
+
+			case 'csebcdicus':
+			case 'ebcdicus':
+				return 'EBCDIC-US';
+
+			case 'csiso111ecmacyrillic':
+			case 'ecmacyrillic':
+			case 'isoir111':
+			case 'koi8e':
+				return 'ECMA-cyrillic';
+
+			case 'csiso17spanish':
+			case 'es':
+			case 'iso646es':
+			case 'isoir17':
+				return 'ES';
+
+			case 'csiso85spanish2':
+			case 'es2':
+			case 'iso646es2':
+			case 'isoir85':
+				return 'ES2';
+
+			case 'cseucfixwidjapanese':
+			case 'extendedunixcodefixedwidthforjapanese':
+				return 'Extended_UNIX_Code_Fixed_Width_for_Japanese';
+
+			case 'cseucpkdfmtjapanese':
+			case 'eucjp':
+			case 'extendedunixcodepackedformatforjapanese':
+				return 'Extended_UNIX_Code_Packed_Format_for_Japanese';
+
+			case 'gb18030':
+				return 'GB18030';
+
+			case 'chinese':
+			case 'cp936':
+			case 'csgb2312':
+			case 'csiso58gb231280':
+			case 'gb2312':
+			case 'gb231280':
+			case 'gbk':
+			case 'isoir58':
+			case 'ms936':
+			case 'windows936':
+				return 'GBK';
+
+			case 'cn':
+			case 'csiso57gb1988':
+			case 'gb198880':
+			case 'iso646cn':
+			case 'isoir57':
+				return 'GB_1988-80';
+
+			case 'csiso153gost1976874':
+			case 'gost1976874':
+			case 'isoir153':
+			case 'stsev35888':
+				return 'GOST_19768-74';
+
+			case 'csiso150':
+			case 'csiso150greekccitt':
+			case 'greekccitt':
+			case 'isoir150':
+				return 'greek-ccitt';
+
+			case 'csiso88greek7':
+			case 'greek7':
+			case 'isoir88':
+				return 'greek7';
+
+			case 'csiso18greek7old':
+			case 'greek7old':
+			case 'isoir18':
+				return 'greek7-old';
+
+			case 'cshpdesktop':
+			case 'hpdesktop':
+				return 'HP-DeskTop';
+
+			case 'cshplegal':
+			case 'hplegal':
+				return 'HP-Legal';
+
+			case 'cshpmath8':
+			case 'hpmath8':
+				return 'HP-Math8';
+
+			case 'cshppifont':
+			case 'hppifont':
+				return 'HP-Pi-font';
+
+			case 'cshproman8':
+			case 'hproman8':
+			case 'r8':
+			case 'roman8':
+				return 'hp-roman8';
+
+			case 'hzgb2312':
+				return 'HZ-GB-2312';
+
+			case 'csibmsymbols':
+			case 'ibmsymbols':
+				return 'IBM-Symbols';
+
+			case 'csibmthai':
+			case 'ibmthai':
+				return 'IBM-Thai';
+
+			case 'ccsid858':
+			case 'cp858':
+			case 'ibm858':
+			case 'pcmultilingual850euro':
+				return 'IBM00858';
+
+			case 'ccsid924':
+			case 'cp924':
+			case 'ebcdiclatin9euro':
+			case 'ibm924':
+				return 'IBM00924';
+
+			case 'ccsid1140':
+			case 'cp1140':
+			case 'ebcdicus37euro':
+			case 'ibm1140':
+				return 'IBM01140';
+
+			case 'ccsid1141':
+			case 'cp1141':
+			case 'ebcdicde273euro':
+			case 'ibm1141':
+				return 'IBM01141';
+
+			case 'ccsid1142':
+			case 'cp1142':
+			case 'ebcdicdk277euro':
+			case 'ebcdicno277euro':
+			case 'ibm1142':
+				return 'IBM01142';
+
+			case 'ccsid1143':
+			case 'cp1143':
+			case 'ebcdicfi278euro':
+			case 'ebcdicse278euro':
+			case 'ibm1143':
+				return 'IBM01143';
+
+			case 'ccsid1144':
+			case 'cp1144':
+			case 'ebcdicit280euro':
+			case 'ibm1144':
+				return 'IBM01144';
+
+			case 'ccsid1145':
+			case 'cp1145':
+			case 'ebcdices284euro':
+			case 'ibm1145':
+				return 'IBM01145';
+
+			case 'ccsid1146':
+			case 'cp1146':
+			case 'ebcdicgb285euro':
+			case 'ibm1146':
+				return 'IBM01146';
+
+			case 'ccsid1147':
+			case 'cp1147':
+			case 'ebcdicfr297euro':
+			case 'ibm1147':
+				return 'IBM01147';
+
+			case 'ccsid1148':
+			case 'cp1148':
+			case 'ebcdicinternational500euro':
+			case 'ibm1148':
+				return 'IBM01148';
+
+			case 'ccsid1149':
+			case 'cp1149':
+			case 'ebcdicis871euro':
+			case 'ibm1149':
+				return 'IBM01149';
+
+			case 'cp37':
+			case 'csibm37':
+			case 'ebcdiccpca':
+			case 'ebcdiccpnl':
+			case 'ebcdiccpus':
+			case 'ebcdiccpwt':
+			case 'ibm37':
+				return 'IBM037';
+
+			case 'cp38':
+			case 'csibm38':
+			case 'ebcdicint':
+			case 'ibm38':
+				return 'IBM038';
+
+			case 'cp273':
+			case 'csibm273':
+			case 'ibm273':
+				return 'IBM273';
+
+			case 'cp274':
+			case 'csibm274':
+			case 'ebcdicbe':
+			case 'ibm274':
+				return 'IBM274';
+
+			case 'cp275':
+			case 'csibm275':
+			case 'ebcdicbr':
+			case 'ibm275':
+				return 'IBM275';
+
+			case 'csibm277':
+			case 'ebcdiccpdk':
+			case 'ebcdiccpno':
+			case 'ibm277':
+				return 'IBM277';
+
+			case 'cp278':
+			case 'csibm278':
+			case 'ebcdiccpfi':
+			case 'ebcdiccpse':
+			case 'ibm278':
+				return 'IBM278';
+
+			case 'cp280':
+			case 'csibm280':
+			case 'ebcdiccpit':
+			case 'ibm280':
+				return 'IBM280';
+
+			case 'cp281':
+			case 'csibm281':
+			case 'ebcdicjpe':
+			case 'ibm281':
+				return 'IBM281';
+
+			case 'cp284':
+			case 'csibm284':
+			case 'ebcdiccpes':
+			case 'ibm284':
+				return 'IBM284';
+
+			case 'cp285':
+			case 'csibm285':
+			case 'ebcdiccpgb':
+			case 'ibm285':
+				return 'IBM285';
+
+			case 'cp290':
+			case 'csibm290':
+			case 'ebcdicjpkana':
+			case 'ibm290':
+				return 'IBM290';
+
+			case 'cp297':
+			case 'csibm297':
+			case 'ebcdiccpfr':
+			case 'ibm297':
+				return 'IBM297';
+
+			case 'cp420':
+			case 'csibm420':
+			case 'ebcdiccpar1':
+			case 'ibm420':
+				return 'IBM420';
+
+			case 'cp423':
+			case 'csibm423':
+			case 'ebcdiccpgr':
+			case 'ibm423':
+				return 'IBM423';
+
+			case 'cp424':
+			case 'csibm424':
+			case 'ebcdiccphe':
+			case 'ibm424':
+				return 'IBM424';
+
+			case '437':
+			case 'cp437':
+			case 'cspc8codepage437':
+			case 'ibm437':
+				return 'IBM437';
+
+			case 'cp500':
+			case 'csibm500':
+			case 'ebcdiccpbe':
+			case 'ebcdiccpch':
+			case 'ibm500':
+				return 'IBM500';
+
+			case 'cp775':
+			case 'cspc775baltic':
+			case 'ibm775':
+				return 'IBM775';
+
+			case '850':
+			case 'cp850':
+			case 'cspc850multilingual':
+			case 'ibm850':
+				return 'IBM850';
+
+			case '851':
+			case 'cp851':
+			case 'csibm851':
+			case 'ibm851':
+				return 'IBM851';
+
+			case '852':
+			case 'cp852':
+			case 'cspcp852':
+			case 'ibm852':
+				return 'IBM852';
+
+			case '855':
+			case 'cp855':
+			case 'csibm855':
+			case 'ibm855':
+				return 'IBM855';
+
+			case '857':
+			case 'cp857':
+			case 'csibm857':
+			case 'ibm857':
+				return 'IBM857';
+
+			case '860':
+			case 'cp860':
+			case 'csibm860':
+			case 'ibm860':
+				return 'IBM860';
+
+			case '861':
+			case 'cp861':
+			case 'cpis':
+			case 'csibm861':
+			case 'ibm861':
+				return 'IBM861';
+
+			case '862':
+			case 'cp862':
+			case 'cspc862latinhebrew':
+			case 'ibm862':
+				return 'IBM862';
+
+			case '863':
+			case 'cp863':
+			case 'csibm863':
+			case 'ibm863':
+				return 'IBM863';
+
+			case 'cp864':
+			case 'csibm864':
+			case 'ibm864':
+				return 'IBM864';
+
+			case '865':
+			case 'cp865':
+			case 'csibm865':
+			case 'ibm865':
+				return 'IBM865';
+
+			case '866':
+			case 'cp866':
+			case 'csibm866':
+			case 'ibm866':
+				return 'IBM866';
+
+			case 'cp868':
+			case 'cpar':
+			case 'csibm868':
+			case 'ibm868':
+				return 'IBM868';
+
+			case '869':
+			case 'cp869':
+			case 'cpgr':
+			case 'csibm869':
+			case 'ibm869':
+				return 'IBM869';
+
+			case 'cp870':
+			case 'csibm870':
+			case 'ebcdiccproece':
+			case 'ebcdiccpyu':
+			case 'ibm870':
+				return 'IBM870';
+
+			case 'cp871':
+			case 'csibm871':
+			case 'ebcdiccpis':
+			case 'ibm871':
+				return 'IBM871';
+
+			case 'cp880':
+			case 'csibm880':
+			case 'ebcdiccyrillic':
+			case 'ibm880':
+				return 'IBM880';
+
+			case 'cp891':
+			case 'csibm891':
+			case 'ibm891':
+				return 'IBM891';
+
+			case 'cp903':
+			case 'csibm903':
+			case 'ibm903':
+				return 'IBM903';
+
+			case '904':
+			case 'cp904':
+			case 'csibbm904':
+			case 'ibm904':
+				return 'IBM904';
+
+			case 'cp905':
+			case 'csibm905':
+			case 'ebcdiccptr':
+			case 'ibm905':
+				return 'IBM905';
+
+			case 'cp918':
+			case 'csibm918':
+			case 'ebcdiccpar2':
+			case 'ibm918':
+				return 'IBM918';
+
+			case 'cp1026':
+			case 'csibm1026':
+			case 'ibm1026':
+				return 'IBM1026';
+
+			case 'ibm1047':
+				return 'IBM1047';
+
+			case 'csiso143iecp271':
+			case 'iecp271':
+			case 'isoir143':
+				return 'IEC_P27-1';
+
+			case 'csiso49inis':
+			case 'inis':
+			case 'isoir49':
+				return 'INIS';
+
+			case 'csiso50inis8':
+			case 'inis8':
+			case 'isoir50':
+				return 'INIS-8';
+
+			case 'csiso51iniscyrillic':
+			case 'iniscyrillic':
+			case 'isoir51':
+				return 'INIS-cyrillic';
+
+			case 'csinvariant':
+			case 'invariant':
+				return 'INVARIANT';
+
+			case 'iso2022cn':
+				return 'ISO-2022-CN';
+
+			case 'iso2022cnext':
+				return 'ISO-2022-CN-EXT';
+
+			case 'csiso2022jp':
+			case 'iso2022jp':
+				return 'ISO-2022-JP';
+
+			case 'csiso2022jp2':
+			case 'iso2022jp2':
+				return 'ISO-2022-JP-2';
+
+			case 'csiso2022kr':
+			case 'iso2022kr':
+				return 'ISO-2022-KR';
+
+			case 'cswindows30latin1':
+			case 'iso88591windows30latin1':
+				return 'ISO-8859-1-Windows-3.0-Latin-1';
+
+			case 'cswindows31latin1':
+			case 'iso88591windows31latin1':
+				return 'ISO-8859-1-Windows-3.1-Latin-1';
+
+			case 'csisolatin2':
+			case 'iso88592':
+			case 'iso885921987':
+			case 'isoir101':
+			case 'l2':
+			case 'latin2':
+				return 'ISO-8859-2';
+
+			case 'cswindows31latin2':
+			case 'iso88592windowslatin2':
+				return 'ISO-8859-2-Windows-Latin-2';
+
+			case 'csisolatin3':
+			case 'iso88593':
+			case 'iso885931988':
+			case 'isoir109':
+			case 'l3':
+			case 'latin3':
+				return 'ISO-8859-3';
+
+			case 'csisolatin4':
+			case 'iso88594':
+			case 'iso885941988':
+			case 'isoir110':
+			case 'l4':
+			case 'latin4':
+				return 'ISO-8859-4';
+
+			case 'csisolatincyrillic':
+			case 'cyrillic':
+			case 'iso88595':
+			case 'iso885951988':
+			case 'isoir144':
+				return 'ISO-8859-5';
+
+			case 'arabic':
+			case 'asmo708':
+			case 'csisolatinarabic':
+			case 'ecma114':
+			case 'iso88596':
+			case 'iso885961987':
+			case 'isoir127':
+				return 'ISO-8859-6';
+
+			case 'csiso88596e':
+			case 'iso88596e':
+				return 'ISO-8859-6-E';
+
+			case 'csiso88596i':
+			case 'iso88596i':
+				return 'ISO-8859-6-I';
+
+			case 'csisolatingreek':
+			case 'ecma118':
+			case 'elot928':
+			case 'greek':
+			case 'greek8':
+			case 'iso88597':
+			case 'iso885971987':
+			case 'isoir126':
+				return 'ISO-8859-7';
+
+			case 'csisolatinhebrew':
+			case 'hebrew':
+			case 'iso88598':
+			case 'iso885981988':
+			case 'isoir138':
+				return 'ISO-8859-8';
+
+			case 'csiso88598e':
+			case 'iso88598e':
+				return 'ISO-8859-8-E';
+
+			case 'csiso88598i':
+			case 'iso88598i':
+				return 'ISO-8859-8-I';
+
+			case 'cswindows31latin5':
+			case 'iso88599windowslatin5':
+				return 'ISO-8859-9-Windows-Latin-5';
+
+			case 'csisolatin6':
+			case 'iso885910':
+			case 'iso8859101992':
+			case 'isoir157':
+			case 'l6':
+			case 'latin6':
+				return 'ISO-8859-10';
+
+			case 'iso885913':
+				return 'ISO-8859-13';
+
+			case 'iso885914':
+			case 'iso8859141998':
+			case 'isoceltic':
+			case 'isoir199':
+			case 'l8':
+			case 'latin8':
+				return 'ISO-8859-14';
+
+			case 'iso885915':
+			case 'latin9':
+				return 'ISO-8859-15';
+
+			case 'iso885916':
+			case 'iso8859162001':
+			case 'isoir226':
+			case 'l10':
+			case 'latin10':
+				return 'ISO-8859-16';
+
+			case 'iso10646j1':
+				return 'ISO-10646-J-1';
+
+			case 'csunicode':
+			case 'iso10646ucs2':
+				return 'ISO-10646-UCS-2';
+
+			case 'csucs4':
+			case 'iso10646ucs4':
+				return 'ISO-10646-UCS-4';
+
+			case 'csunicodeascii':
+			case 'iso10646ucsbasic':
+				return 'ISO-10646-UCS-Basic';
+
+			case 'csunicodelatin1':
+			case 'iso10646':
+			case 'iso10646unicodelatin1':
+				return 'ISO-10646-Unicode-Latin1';
+
+			case 'csiso10646utf1':
+			case 'iso10646utf1':
+				return 'ISO-10646-UTF-1';
+
+			case 'csiso115481':
+			case 'iso115481':
+			case 'isotr115481':
+				return 'ISO-11548-1';
+
+			case 'csiso90':
+			case 'isoir90':
+				return 'iso-ir-90';
+
+			case 'csunicodeibm1261':
+			case 'isounicodeibm1261':
+				return 'ISO-Unicode-IBM-1261';
+
+			case 'csunicodeibm1264':
+			case 'isounicodeibm1264':
+				return 'ISO-Unicode-IBM-1264';
+
+			case 'csunicodeibm1265':
+			case 'isounicodeibm1265':
+				return 'ISO-Unicode-IBM-1265';
+
+			case 'csunicodeibm1268':
+			case 'isounicodeibm1268':
+				return 'ISO-Unicode-IBM-1268';
+
+			case 'csunicodeibm1276':
+			case 'isounicodeibm1276':
+				return 'ISO-Unicode-IBM-1276';
+
+			case 'csiso646basic1983':
+			case 'iso646basic1983':
+			case 'ref':
+				return 'ISO_646.basic:1983';
+
+			case 'csiso2intlrefversion':
+			case 'irv':
+			case 'iso646irv1983':
+			case 'isoir2':
+				return 'ISO_646.irv:1983';
+
+			case 'csiso2033':
+			case 'e13b':
+			case 'iso20331983':
+			case 'isoir98':
+				return 'ISO_2033-1983';
+
+			case 'csiso5427cyrillic':
+			case 'iso5427':
+			case 'isoir37':
+				return 'ISO_5427';
+
+			case 'iso5427cyrillic1981':
+			case 'iso54271981':
+			case 'isoir54':
+				return 'ISO_5427:1981';
+
+			case 'csiso5428greek':
+			case 'iso54281980':
+			case 'isoir55':
+				return 'ISO_5428:1980';
+
+			case 'csiso6937add':
+			case 'iso6937225':
+			case 'isoir152':
+				return 'ISO_6937-2-25';
+
+			case 'csisotextcomm':
+			case 'iso69372add':
+			case 'isoir142':
+				return 'ISO_6937-2-add';
+
+			case 'csiso8859supp':
+			case 'iso8859supp':
+			case 'isoir154':
+			case 'latin125':
+				return 'ISO_8859-supp';
+
+			case 'csiso10367box':
+			case 'iso10367box':
+			case 'isoir155':
+				return 'ISO_10367-box';
+
+			case 'csiso15italian':
+			case 'iso646it':
+			case 'isoir15':
+			case 'it':
+				return 'IT';
+
+			case 'csiso13jisc6220jp':
+			case 'isoir13':
+			case 'jisc62201969':
+			case 'jisc62201969jp':
+			case 'katakana':
+			case 'x2017':
+				return 'JIS_C6220-1969-jp';
+
+			case 'csiso14jisc6220ro':
+			case 'iso646jp':
+			case 'isoir14':
+			case 'jisc62201969ro':
+			case 'jp':
+				return 'JIS_C6220-1969-ro';
+
+			case 'csiso42jisc62261978':
+			case 'isoir42':
+			case 'jisc62261978':
+				return 'JIS_C6226-1978';
+
+			case 'csiso87jisx208':
+			case 'isoir87':
+			case 'jisc62261983':
+			case 'jisx2081983':
+			case 'x208':
+				return 'JIS_C6226-1983';
+
+			case 'csiso91jisc62291984a':
+			case 'isoir91':
+			case 'jisc62291984a':
+			case 'jpocra':
+				return 'JIS_C6229-1984-a';
+
+			case 'csiso92jisc62991984b':
+			case 'iso646jpocrb':
+			case 'isoir92':
+			case 'jisc62291984b':
+			case 'jpocrb':
+				return 'JIS_C6229-1984-b';
+
+			case 'csiso93jis62291984badd':
+			case 'isoir93':
+			case 'jisc62291984badd':
+			case 'jpocrbadd':
+				return 'JIS_C6229-1984-b-add';
+
+			case 'csiso94jis62291984hand':
+			case 'isoir94':
+			case 'jisc62291984hand':
+			case 'jpocrhand':
+				return 'JIS_C6229-1984-hand';
+
+			case 'csiso95jis62291984handadd':
+			case 'isoir95':
+			case 'jisc62291984handadd':
+			case 'jpocrhandadd':
+				return 'JIS_C6229-1984-hand-add';
+
+			case 'csiso96jisc62291984kana':
+			case 'isoir96':
+			case 'jisc62291984kana':
+				return 'JIS_C6229-1984-kana';
+
+			case 'csjisencoding':
+			case 'jisencoding':
+				return 'JIS_Encoding';
+
+			case 'cshalfwidthkatakana':
+			case 'jisx201':
+			case 'x201':
+				return 'JIS_X0201';
+
+			case 'csiso159jisx2121990':
+			case 'isoir159':
+			case 'jisx2121990':
+			case 'x212':
+				return 'JIS_X0212-1990';
+
+			case 'csiso141jusib1002':
+			case 'iso646yu':
+			case 'isoir141':
+			case 'js':
+			case 'jusib1002':
+			case 'yu':
+				return 'JUS_I.B1.002';
+
+			case 'csiso147macedonian':
+			case 'isoir147':
+			case 'jusib1003mac':
+			case 'macedonian':
+				return 'JUS_I.B1.003-mac';
+
+			case 'csiso146serbian':
+			case 'isoir146':
+			case 'jusib1003serb':
+			case 'serbian':
+				return 'JUS_I.B1.003-serb';
+
+			case 'koi7switched':
+				return 'KOI7-switched';
+
+			case 'cskoi8r':
+			case 'koi8r':
+				return 'KOI8-R';
+
+			case 'koi8u':
+				return 'KOI8-U';
+
+			case 'csksc5636':
+			case 'iso646kr':
+			case 'ksc5636':
+				return 'KSC5636';
+
+			case 'cskz1048':
+			case 'kz1048':
+			case 'rk1048':
+			case 'strk10482002':
+				return 'KZ-1048';
+
+			case 'csiso19latingreek':
+			case 'isoir19':
+			case 'latingreek':
+				return 'latin-greek';
+
+			case 'csiso27latingreek1':
+			case 'isoir27':
+			case 'latingreek1':
+				return 'Latin-greek-1';
+
+			case 'csiso158lap':
+			case 'isoir158':
+			case 'lap':
+			case 'latinlap':
+				return 'latin-lap';
+
+			case 'csmacintosh':
+			case 'mac':
+			case 'macintosh':
+				return 'macintosh';
+
+			case 'csmicrosoftpublishing':
+			case 'microsoftpublishing':
+				return 'Microsoft-Publishing';
+
+			case 'csmnem':
+			case 'mnem':
+				return 'MNEM';
+
+			case 'csmnemonic':
+			case 'mnemonic':
+				return 'MNEMONIC';
+
+			case 'csiso86hungarian':
+			case 'hu':
+			case 'iso646hu':
+			case 'isoir86':
+			case 'msz77953':
+				return 'MSZ_7795.3';
+
+			case 'csnatsdano':
+			case 'isoir91':
+			case 'natsdano':
+				return 'NATS-DANO';
+
+			case 'csnatsdanoadd':
+			case 'isoir92':
+			case 'natsdanoadd':
+				return 'NATS-DANO-ADD';
+
+			case 'csnatssefi':
+			case 'isoir81':
+			case 'natssefi':
+				return 'NATS-SEFI';
+
+			case 'csnatssefiadd':
+			case 'isoir82':
+			case 'natssefiadd':
+				return 'NATS-SEFI-ADD';
+
+			case 'csiso151cuba':
+			case 'cuba':
+			case 'iso646cu':
+			case 'isoir151':
+			case 'ncnc1081':
+				return 'NC_NC00-10:81';
+
+			case 'csiso69french':
+			case 'fr':
+			case 'iso646fr':
+			case 'isoir69':
+			case 'nfz62010':
+				return 'NF_Z_62-010';
+
+			case 'csiso25french':
+			case 'iso646fr1':
+			case 'isoir25':
+			case 'nfz620101973':
+				return 'NF_Z_62-010_(1973)';
+
+			case 'csiso60danishnorwegian':
+			case 'csiso60norwegian1':
+			case 'iso646no':
+			case 'isoir60':
+			case 'no':
+			case 'ns45511':
+				return 'NS_4551-1';
+
+			case 'csiso61norwegian2':
+			case 'iso646no2':
+			case 'isoir61':
+			case 'no2':
+			case 'ns45512':
+				return 'NS_4551-2';
+
+			case 'osdebcdicdf3irv':
+				return 'OSD_EBCDIC_DF03_IRV';
+
+			case 'osdebcdicdf41':
+				return 'OSD_EBCDIC_DF04_1';
+
+			case 'osdebcdicdf415':
+				return 'OSD_EBCDIC_DF04_15';
+
+			case 'cspc8danishnorwegian':
+			case 'pc8danishnorwegian':
+				return 'PC8-Danish-Norwegian';
+
+			case 'cspc8turkish':
+			case 'pc8turkish':
+				return 'PC8-Turkish';
+
+			case 'csiso16portuguese':
+			case 'iso646pt':
+			case 'isoir16':
+			case 'pt':
+				return 'PT';
+
+			case 'csiso84portuguese2':
+			case 'iso646pt2':
+			case 'isoir84':
+			case 'pt2':
+				return 'PT2';
+
+			case 'cp154':
+			case 'csptcp154':
+			case 'cyrillicasian':
+			case 'pt154':
+			case 'ptcp154':
+				return 'PTCP154';
+
+			case 'scsu':
+				return 'SCSU';
+
+			case 'csiso10swedish':
+			case 'fi':
+			case 'iso646fi':
+			case 'iso646se':
+			case 'isoir10':
+			case 'se':
+			case 'sen850200b':
+				return 'SEN_850200_B';
+
+			case 'csiso11swedishfornames':
+			case 'iso646se2':
+			case 'isoir11':
+			case 'se2':
+			case 'sen850200c':
+				return 'SEN_850200_C';
+
+			case 'csshiftjis':
+			case 'mskanji':
+			case 'shiftjis':
+				return 'Shift_JIS';
+
+			case 'csiso102t617bit':
+			case 'isoir102':
+			case 't617bit':
+				return 'T.61-7bit';
+
+			case 'csiso103t618bit':
+			case 'isoir103':
+			case 't61':
+			case 't618bit':
+				return 'T.61-8bit';
+
+			case 'csiso128t101g2':
+			case 'isoir128':
+			case 't101g2':
+				return 'T.101-G2';
+
+			case 'cstscii':
+			case 'tscii':
+				return 'TSCII';
+
+			case 'csunicode11':
+			case 'unicode11':
+				return 'UNICODE-1-1';
+
+			case 'csunicode11utf7':
+			case 'unicode11utf7':
+				return 'UNICODE-1-1-UTF-7';
+
+			case 'csunknown8bit':
+			case 'unknown8bit':
+				return 'UNKNOWN-8BIT';
+
+			case 'ansix341968':
+			case 'ansix341986':
+			case 'ascii':
+			case 'cp367':
+			case 'csascii':
+			case 'ibm367':
+			case 'iso646irv1991':
+			case 'iso646us':
+			case 'isoir6':
+			case 'us':
+			case 'usascii':
+				return 'US-ASCII';
+
+			case 'csusdk':
+			case 'usdk':
+				return 'us-dk';
+
+			case 'utf7':
+				return 'UTF-7';
+
+			case 'utf8':
+				return 'UTF-8';
+
+			case 'utf16':
+				return 'UTF-16';
+
+			case 'utf16be':
+				return 'UTF-16BE';
+
+			case 'utf16le':
+				return 'UTF-16LE';
+
+			case 'utf32':
+				return 'UTF-32';
+
+			case 'utf32be':
+				return 'UTF-32BE';
+
+			case 'utf32le':
+				return 'UTF-32LE';
+
+			case 'csventurainternational':
+			case 'venturainternational':
+				return 'Ventura-International';
+
+			case 'csventuramath':
+			case 'venturamath':
+				return 'Ventura-Math';
+
+			case 'csventuraus':
+			case 'venturaus':
+				return 'Ventura-US';
+
+			case 'csiso70videotexsupp1':
+			case 'isoir70':
+			case 'videotexsuppl':
+				return 'videotex-suppl';
+
+			case 'csviqr':
+			case 'viqr':
+				return 'VIQR';
+
+			case 'csviscii':
+			case 'viscii':
+				return 'VISCII';
+
+			case 'cswindows31j':
+			case 'windows31j':
+				return 'Windows-31J';
+
+			case 'iso885911':
+			case 'tis620':
+				return 'windows-874';
+
+			case 'cseuckr':
+			case 'csksc56011987':
+			case 'euckr':
+			case 'isoir149':
+			case 'korean':
+			case 'ksc5601':
+			case 'ksc56011987':
+			case 'ksc56011989':
+			case 'windows949':
+				return 'windows-949';
+
+			case 'windows1250':
+				return 'windows-1250';
+
+			case 'windows1251':
+				return 'windows-1251';
+
+			case 'cp819':
+			case 'csisolatin1':
+			case 'ibm819':
+			case 'iso88591':
+			case 'iso885911987':
+			case 'isoir100':
+			case 'l1':
+			case 'latin1':
+			case 'windows1252':
+				return 'windows-1252';
+
+			case 'windows1253':
+				return 'windows-1253';
+
+			case 'csisolatin5':
+			case 'iso88599':
+			case 'iso885991989':
+			case 'isoir148':
+			case 'l5':
+			case 'latin5':
+			case 'windows1254':
+				return 'windows-1254';
+
+			case 'windows1255':
+				return 'windows-1255';
+
+			case 'windows1256':
+				return 'windows-1256';
+
+			case 'windows1257':
+				return 'windows-1257';
+
+			case 'windows1258':
+				return 'windows-1258';
+
+			default:
+				return $charset;
+		}
+	}
+
+	function get_curl_version()
+	{
+		if (is_array($curl = curl_version()))
+		{
+			$curl = $curl['version'];
+		}
+		elseif (substr($curl, 0, 5) === 'curl/')
+		{
+			$curl = substr($curl, 5, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 5));
+		}
+		elseif (substr($curl, 0, 8) === 'libcurl/')
+		{
+			$curl = substr($curl, 8, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 8));
+		}
+		else
+		{
+			$curl = 0;
+		}
+		return $curl;
+	}
+
+	function is_subclass_of($class1, $class2)
+	{
+		if (func_num_args() !== 2)
+		{
+			trigger_error('Wrong parameter count for SimplePie_Misc::is_subclass_of()', E_USER_WARNING);
+		}
+		elseif (version_compare(PHP_VERSION, '5.0.3', '>=') || is_object($class1))
+		{
+			return is_subclass_of($class1, $class2);
+		}
+		elseif (is_string($class1) && is_string($class2))
+		{
+			if (class_exists($class1))
+			{
+				if (class_exists($class2))
+				{
+					$class2 = strtolower($class2);
+					while ($class1 = strtolower(get_parent_class($class1)))
+					{
+						if ($class1 === $class2)
+						{
+							return true;
+						}
+					}
+				}
+			}
+			else
+			{
+				trigger_error('Unknown class passed as parameter', E_USER_WARNNG);
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Strip HTML comments
+	 *
+	 * @access public
+	 * @param string $data Data to strip comments from
+	 * @return string Comment stripped string
+	 */
+	function strip_comments($data)
+	{
+		$output = '';
+		while (($start = strpos($data, '<!--')) !== false)
+		{
+			$output .= substr($data, 0, $start);
+			if (($end = strpos($data, '-->', $start)) !== false)
+			{
+				$data = substr_replace($data, '', 0, $end + 3);
+			}
+			else
+			{
+				$data = '';
+			}
+		}
+		return $output . $data;
+	}
+
+	function parse_date($dt)
+	{
+		$parser = SimplePie_Parse_Date::get();
+		return $parser->parse($dt);
+	}
+
+	/**
+	 * Decode HTML entities
+	 *
+	 * @static
+	 * @access public
+	 * @param string $data Input data
+	 * @return string Output data
+	 */
+	function entities_decode($data)
+	{
+		$decoder = new SimplePie_Decode_HTML_Entities($data);
+		return $decoder->parse();
+	}
+
+	/**
+	 * Remove RFC822 comments
+	 *
+	 * @access public
+	 * @param string $data Data to strip comments from
+	 * @return string Comment stripped string
+	 */
+	function uncomment_rfc822($string)
+	{
+		$string = (string) $string;
+		$position = 0;
+		$length = strlen($string);
+		$depth = 0;
+
+		$output = '';
+
+		while ($position < $length && ($pos = strpos($string, '(', $position)) !== false)
+		{
+			$output .= substr($string, $position, $pos - $position);
+			$position = $pos + 1;
+			if ($string[$pos - 1] !== '\\')
+			{
+				$depth++;
+				while ($depth && $position < $length)
+				{
+					$position += strcspn($string, '()', $position);
+					if ($string[$position - 1] === '\\')
+					{
+						$position++;
+						continue;
+					}
+					elseif (isset($string[$position]))
+					{
+						switch ($string[$position])
+						{
+							case '(':
+								$depth++;
+								break;
+
+							case ')':
+								$depth--;
+								break;
+						}
+						$position++;
+					}
+					else
+					{
+						break;
+					}
+				}
+			}
+			else
+			{
+				$output .= '(';
+			}
+		}
+		$output .= substr($string, $position);
+
+		return $output;
+	}
+
+	function parse_mime($mime)
+	{
+		if (($pos = strpos($mime, ';')) === false)
+		{
+			return trim($mime);
+		}
+		else
+		{
+			return trim(substr($mime, 0, $pos));
+		}
+	}
+
+	function htmlspecialchars_decode($string, $quote_style)
+	{
+		if (function_exists('htmlspecialchars_decode'))
+		{
+			return htmlspecialchars_decode($string, $quote_style);
+		}
+		else
+		{
+			return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
+		}
+	}
+
+	function atom_03_construct_type($attribs)
+	{
+		if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode']) === 'base64'))
+		{
+			$mode = SIMPLEPIE_CONSTRUCT_BASE64;
+		}
+		else
+		{
+			$mode = SIMPLEPIE_CONSTRUCT_NONE;
+		}
+		if (isset($attribs['']['type']))
+		{
+			switch (strtolower(trim($attribs['']['type'])))
+			{
+				case 'text':
+				case 'text/plain':
+					return SIMPLEPIE_CONSTRUCT_TEXT | $mode;
+
+				case 'html':
+				case 'text/html':
+					return SIMPLEPIE_CONSTRUCT_HTML | $mode;
+
+				case 'xhtml':
+				case 'application/xhtml+xml':
+					return SIMPLEPIE_CONSTRUCT_XHTML | $mode;
+
+				default:
+					return SIMPLEPIE_CONSTRUCT_NONE | $mode;
+			}
+		}
+		else
+		{
+			return SIMPLEPIE_CONSTRUCT_TEXT | $mode;
+		}
+	}
+
+	function atom_10_construct_type($attribs)
+	{
+		if (isset($attribs['']['type']))
+		{
+			switch (strtolower(trim($attribs['']['type'])))
+			{
+				case 'text':
+					return SIMPLEPIE_CONSTRUCT_TEXT;
+
+				case 'html':
+					return SIMPLEPIE_CONSTRUCT_HTML;
+
+				case 'xhtml':
+					return SIMPLEPIE_CONSTRUCT_XHTML;
+
+				default:
+					return SIMPLEPIE_CONSTRUCT_NONE;
+			}
+		}
+		return SIMPLEPIE_CONSTRUCT_TEXT;
+	}
+
+	function atom_10_content_construct_type($attribs)
+	{
+		if (isset($attribs['']['type']))
+		{
+			$type = strtolower(trim($attribs['']['type']));
+			switch ($type)
+			{
+				case 'text':
+					return SIMPLEPIE_CONSTRUCT_TEXT;
+
+				case 'html':
+					return SIMPLEPIE_CONSTRUCT_HTML;
+
+				case 'xhtml':
+					return SIMPLEPIE_CONSTRUCT_XHTML;
+			}
+			if (in_array(substr($type, -4), array('+xml', '/xml')) || substr($type, 0, 5) === 'text/')
+			{
+				return SIMPLEPIE_CONSTRUCT_NONE;
+			}
+			else
+			{
+				return SIMPLEPIE_CONSTRUCT_BASE64;
+			}
+		}
+		else
+		{
+			return SIMPLEPIE_CONSTRUCT_TEXT;
+		}
+	}
+
+	function is_isegment_nz_nc($string)
+	{
+		return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string);
+	}
+
+	function space_seperated_tokens($string)
+	{
+		$space_characters = "\x20\x09\x0A\x0B\x0C\x0D";
+		$string_length = strlen($string);
+
+		$position = strspn($string, $space_characters);
+		$tokens = array();
+
+		while ($position < $string_length)
+		{
+			$len = strcspn($string, $space_characters, $position);
+			$tokens[] = substr($string, $position, $len);
+			$position += $len;
+			$position += strspn($string, $space_characters, $position);
+		}
+
+		return $tokens;
+	}
+
+	function array_unique($array)
+	{
+		if (version_compare(PHP_VERSION, '5.2', '>='))
+		{
+			return array_unique($array);
+		}
+		else
+		{
+			$array = (array) $array;
+			$new_array = array();
+			$new_array_strings = array();
+			foreach ($array as $key => $value)
+			{
+				if (is_object($value))
+				{
+					if (method_exists($value, '__toString'))
+					{
+						$cmp = $value->__toString();
+					}
+					else
+					{
+						trigger_error('Object of class ' . get_class($value) . ' could not be converted to string', E_USER_ERROR);
+					}
+				}
+				elseif (is_array($value))
+				{
+					$cmp = (string) reset($value);
+				}
+				else
+				{
+					$cmp = (string) $value;
+				}
+				if (!in_array($cmp, $new_array_strings))
+				{
+					$new_array[$key] = $value;
+					$new_array_strings[] = $cmp;
+				}
+			}
+			return $new_array;
+		}
+	}
+
+	/**
+	 * Converts a unicode codepoint to a UTF-8 character
+	 *
+	 * @static
+	 * @access public
+	 * @param int $codepoint Unicode codepoint
+	 * @return string UTF-8 character
+	 */
+	function codepoint_to_utf8($codepoint)
+	{
+		$codepoint = (int) $codepoint;
+		if ($codepoint < 0)
+		{
+			return false;
+		}
+		else if ($codepoint <= 0x7f)
+		{
+			return chr($codepoint);
+		}
+		else if ($codepoint <= 0x7ff)
+		{
+			return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f));
+		}
+		else if ($codepoint <= 0xffff)
+		{
+			return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f));
+		}
+		else if ($codepoint <= 0x10ffff)
+		{
+			return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f));
+		}
+		else
+		{
+			// U+FFFD REPLACEMENT CHARACTER
+			return "\xEF\xBF\xBD";
+		}
+	}
+
+	/**
+	 * Re-implementation of PHP 5's stripos()
+	 *
+	 * Returns the numeric position of the first occurrence of needle in the
+	 * haystack string.
+	 *
+	 * @static
+	 * @access string
+	 * @param object $haystack
+	 * @param string $needle Note that the needle may be a string of one or more
+	 *     characters. If needle is not a string, it is converted to an integer
+	 *     and applied as the ordinal value of a character.
+	 * @param int $offset The optional offset parameter allows you to specify which
+	 *     character in haystack to start searching. The position returned is still
+	 *     relative to the beginning of haystack.
+	 * @return bool If needle is not found, stripos() will return boolean false.
+	 */
+	function stripos($haystack, $needle, $offset = 0)
+	{
+		if (function_exists('stripos'))
+		{
+			return stripos($haystack, $needle, $offset);
+		}
+		else
+		{
+			if (is_string($needle))
+			{
+				$needle = strtolower($needle);
+			}
+			elseif (is_int($needle) || is_bool($needle) || is_double($needle))
+			{
+				$needle = strtolower(chr($needle));
+			}
+			else
+			{
+				trigger_error('needle is not a string or an integer', E_USER_WARNING);
+				return false;
+			}
+
+			return strpos(strtolower($haystack), $needle, $offset);
+		}
+	}
+
+	/**
+	 * Similar to parse_str()
+	 *
+	 * Returns an associative array of name/value pairs, where the value is an
+	 * array of values that have used the same name
+	 *
+	 * @static
+	 * @access string
+	 * @param string $str The input string.
+	 * @return array
+	 */
+	function parse_str($str)
+	{
+		$return = array();
+		$str = explode('&', $str);
+
+		foreach ($str as $section)
+		{
+			if (strpos($section, '=') !== false)
+			{
+				list($name, $value) = explode('=', $section, 2);
+				$return[urldecode($name)][] = urldecode($value);
+			}
+			else
+			{
+				$return[urldecode($section)][] = null;
+			}
+		}
+
+		return $return;
+	}
+
+	/**
+	 * Detect XML encoding, as per XML 1.0 Appendix F.1
+	 *
+	 * @todo Add support for EBCDIC
+	 * @param string $data XML data
+	 * @return array Possible encodings
+	 */
+	function xml_encoding($data)
+	{
+		// UTF-32 Big Endian BOM
+		if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
+		{
+			$encoding[] = 'UTF-32BE';
+		}
+		// UTF-32 Little Endian BOM
+		elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
+		{
+			$encoding[] = 'UTF-32LE';
+		}
+		// UTF-16 Big Endian BOM
+		elseif (substr($data, 0, 2) === "\xFE\xFF")
+		{
+			$encoding[] = 'UTF-16BE';
+		}
+		// UTF-16 Little Endian BOM
+		elseif (substr($data, 0, 2) === "\xFF\xFE")
+		{
+			$encoding[] = 'UTF-16LE';
+		}
+		// UTF-8 BOM
+		elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
+		{
+			$encoding[] = 'UTF-8';
+		}
+		// UTF-32 Big Endian Without BOM
+		elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C")
+		{
+			if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8'));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-32BE';
+		}
+		// UTF-32 Little Endian Without BOM
+		elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00")
+		{
+			if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8'));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-32LE';
+		}
+		// UTF-16 Big Endian Without BOM
+		elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C")
+		{
+			if ($pos = strpos($data, "\x00\x3F\x00\x3E"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8'));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-16BE';
+		}
+		// UTF-16 Little Endian Without BOM
+		elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00")
+		{
+			if ($pos = strpos($data, "\x3F\x00\x3E\x00"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8'));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-16LE';
+		}
+		// US-ASCII (or superset)
+		elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C")
+		{
+			if ($pos = strpos($data, "\x3F\x3E"))
+			{
+				$parser = new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5));
+				if ($parser->parse())
+				{
+					$encoding[] = $parser->encoding;
+				}
+			}
+			$encoding[] = 'UTF-8';
+		}
+		// Fallback to UTF-8
+		else
+		{
+			$encoding[] = 'UTF-8';
+		}
+		return $encoding;
+	}
+
+	function output_javascript()
+	{
+		if (function_exists('ob_gzhandler'))
+		{
+			ob_start('ob_gzhandler');
+		}
+		header('Content-type: text/javascript; charset: UTF-8');
+		header('Cache-Control: must-revalidate');
+		header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days
+		?>
+function embed_odeo(link) {
+	document.writeln('<embed src="http://odeo.com/flash/audio_player_fullsize.swf" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="440" height="80" wmode="transparent" allowScriptAccess="any" flashvars="valid_sample_rate=true&external_url='+link+'"></embed>');
+}
+
+function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) {
+	if (placeholder != '') {
+		document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" href="'+link+'" src="'+placeholder+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="false" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>');
+	}
+	else {
+		document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" src="'+link+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="true" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>');
+	}
+}
+
+function embed_flash(bgcolor, width, height, link, loop, type) {
+	document.writeln('<embed src="'+link+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="'+type+'" quality="high" width="'+width+'" height="'+height+'" bgcolor="'+bgcolor+'" loop="'+loop+'"></embed>');
+}
+
+function embed_flv(width, height, link, placeholder, loop, player) {
+	document.writeln('<embed src="'+player+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="'+width+'" height="'+height+'" wmode="transparent" flashvars="file='+link+'&autostart=false&repeat='+loop+'&showdigits=true&showfsbutton=false"></embed>');
+}
+
+function embed_wmedia(width, height, link) {
+	document.writeln('<embed type="application/x-mplayer2" src="'+link+'" autosize="1" width="'+width+'" height="'+height+'" showcontrols="1" showstatusbar="0" showdisplay="0" autostart="0"></embed>');
+}
+		<?php
+	}
+}
+
+/**
+ * Decode HTML Entities
+ *
+ * This implements HTML5 as of revision 967 (2007-06-28)
+ *
+ * @package SimplePie
+ */
+class SimplePie_Decode_HTML_Entities
+{
+	/**
+	 * Data to be parsed
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $data = '';
+
+	/**
+	 * Currently consumed bytes
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $consumed = '';
+
+	/**
+	 * Position of the current byte being parsed
+	 *
+	 * @access private
+	 * @var int
+	 */
+	var $position = 0;
+
+	/**
+	 * Create an instance of the class with the input data
+	 *
+	 * @access public
+	 * @param string $data Input data
+	 */
+	function SimplePie_Decode_HTML_Entities($data)
+	{
+		$this->data = $data;
+	}
+
+	/**
+	 * Parse the input data
+	 *
+	 * @access public
+	 * @return string Output data
+	 */
+	function parse()
+	{
+		while (($this->position = strpos($this->data, '&', $this->position)) !== false)
+		{
+			$this->consume();
+			$this->entity();
+			$this->consumed = '';
+		}
+		return $this->data;
+	}
+
+	/**
+	 * Consume the next byte
+	 *
+	 * @access private
+	 * @return mixed The next byte, or false, if there is no more data
+	 */
+	function consume()
+	{
+		if (isset($this->data[$this->position]))
+		{
+			$this->consumed .= $this->data[$this->position];
+			return $this->data[$this->position++];
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Consume a range of characters
+	 *
+	 * @access private
+	 * @param string $chars Characters to consume
+	 * @return mixed A series of characters that match the range, or false
+	 */
+	function consume_range($chars)
+	{
+		if ($len = strspn($this->data, $chars, $this->position))
+		{
+			$data = substr($this->data, $this->position, $len);
+			$this->consumed .= $data;
+			$this->position += $len;
+			return $data;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Unconsume one byte
+	 *
+	 * @access private
+	 */
+	function unconsume()
+	{
+		$this->consumed = substr($this->consumed, 0, -1);
+		$this->position--;
+	}
+
+	/**
+	 * Decode an entity
+	 *
+	 * @access private
+	 */
+	function entity()
+	{
+		switch ($this->consume())
+		{
+			case "\x09":
+			case "\x0A":
+			case "\x0B":
+			case "\x0B":
+			case "\x0C":
+			case "\x20":
+			case "\x3C":
+			case "\x26":
+			case false:
+				break;
+
+			case "\x23":
+				switch ($this->consume())
+				{
+					case "\x78":
+					case "\x58":
+						$range = '0123456789ABCDEFabcdef';
+						$hex = true;
+						break;
+
+					default:
+						$range = '0123456789';
+						$hex = false;
+						$this->unconsume();
+						break;
+				}
+
+				if ($codepoint = $this->consume_range($range))
+				{
+					static $windows_1252_specials = array(0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99 [...]
+
+					if ($hex)
+					{
+						$codepoint = hexdec($codepoint);
+					}
+					else
+					{
+						$codepoint = intval($codepoint);
+					}
+
+					if (isset($windows_1252_specials[$codepoint]))
+					{
+						$replacement = $windows_1252_specials[$codepoint];
+					}
+					else
+					{
+						$replacement = SimplePie_Misc::codepoint_to_utf8($codepoint);
+					}
+
+					if (!in_array($this->consume(), array(';', false), true))
+					{
+						$this->unconsume();
+					}
+
+					$consumed_length = strlen($this->consumed);
+					$this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length);
+					$this->position += strlen($replacement) - $consumed_length;
+				}
+				break;
+
+			default:
+				static $entities = array('Aacute' => "\xC3\x81", 'aacute' => "\xC3\xA1", 'Aacute;' => "\xC3\x81", 'aacute;' => "\xC3\xA1", 'Acirc' => "\xC3\x82", 'acirc' => "\xC3\xA2", 'Acirc;' => "\xC3\x82", 'acirc;' => "\xC3\xA2", 'acute' => "\xC2\xB4", 'acute;' => "\xC2\xB4", 'AElig' => "\xC3\x86", 'aelig' => "\xC3\xA6", 'AElig;' => "\xC3\x86", 'aelig;' => "\xC3\xA6", 'Agrave' => "\xC3\x80", 'agrave' => "\xC3\xA0", 'Agrave;' => "\xC3\x80", 'agrave;' => "\xC3\xA0", 'alefsym;' => "\xE2\x84\xB5", 'A [...]
+
+				for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++)
+				{
+					$consumed = substr($this->consumed, 1);
+					if (isset($entities[$consumed]))
+					{
+						$match = $consumed;
+					}
+				}
+
+				if ($match !== null)
+				{
+ 					$this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1);
+					$this->position += strlen($entities[$match]) - strlen($consumed) - 1;
+				}
+				break;
+		}
+	}
+}
+
+/**
+ * IRI parser/serialiser
+ *
+ * @package SimplePie
+ */
+class SimplePie_IRI
+{
+	/**
+	 * Scheme
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $scheme;
+
+	/**
+	 * User Information
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $userinfo;
+
+	/**
+	 * Host
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $host;
+
+	/**
+	 * Port
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $port;
+
+	/**
+	 * Path
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $path;
+
+	/**
+	 * Query
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $query;
+
+	/**
+	 * Fragment
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $fragment;
+
+	/**
+	 * Whether the object represents a valid IRI
+	 *
+	 * @access private
+	 * @var array
+	 */
+	var $valid = array();
+
+	/**
+	 * Return the entire IRI when you try and read the object as a string
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function __toString()
+	{
+		return $this->get_iri();
+	}
+
+	/**
+	 * Create a new IRI object, from a specified string
+	 *
+	 * @access public
+	 * @param string $iri
+	 * @return SimplePie_IRI
+	 */
+	function SimplePie_IRI($iri)
+	{
+		$iri = (string) $iri;
+		if ($iri !== '')
+		{
+			$parsed = $this->parse_iri($iri);
+			$this->set_scheme($parsed['scheme']);
+			$this->set_authority($parsed['authority']);
+			$this->set_path($parsed['path']);
+			$this->set_query($parsed['query']);
+			$this->set_fragment($parsed['fragment']);
+		}
+	}
+
+	/**
+	 * Create a new IRI object by resolving a relative IRI
+	 *
+	 * @static
+	 * @access public
+	 * @param SimplePie_IRI $base Base IRI
+	 * @param string $relative Relative IRI
+	 * @return SimplePie_IRI
+	 */
+	function absolutize($base, $relative)
+	{
+		$relative = (string) $relative;
+		if ($relative !== '')
+		{
+			$relative = new SimplePie_IRI($relative);
+			if ($relative->get_scheme() !== null)
+			{
+				$target = $relative;
+			}
+			elseif ($base->get_iri() !== null)
+			{
+				if ($relative->get_authority() !== null)
+				{
+					$target = $relative;
+					$target->set_scheme($base->get_scheme());
+				}
+				else
+				{
+					$target = new SimplePie_IRI('');
+					$target->set_scheme($base->get_scheme());
+					$target->set_userinfo($base->get_userinfo());
+					$target->set_host($base->get_host());
+					$target->set_port($base->get_port());
+					if ($relative->get_path() !== null)
+					{
+						if (strpos($relative->get_path(), '/') === 0)
+						{
+							$target->set_path($relative->get_path());
+						}
+						elseif (($base->get_userinfo() !== null || $base->get_host() !== null || $base->get_port() !== null) && $base->get_path() === null)
+						{
+							$target->set_path('/' . $relative->get_path());
+						}
+						elseif (($last_segment = strrpos($base->get_path(), '/')) !== false)
+						{
+							$target->set_path(substr($base->get_path(), 0, $last_segment + 1) . $relative->get_path());
+						}
+						else
+						{
+							$target->set_path($relative->get_path());
+						}
+						$target->set_query($relative->get_query());
+					}
+					else
+					{
+						$target->set_path($base->get_path());
+						if ($relative->get_query() !== null)
+						{
+							$target->set_query($relative->get_query());
+						}
+						elseif ($base->get_query() !== null)
+						{
+							$target->set_query($base->get_query());
+						}
+					}
+				}
+				$target->set_fragment($relative->get_fragment());
+			}
+			else
+			{
+				// No base URL, just return the relative URL
+				$target = $relative;
+			}
+		}
+		else
+		{
+			$target = $base;
+		}
+		return $target;
+	}
+
+	/**
+	 * Parse an IRI into scheme/authority/path/query/fragment segments
+	 *
+	 * @access private
+	 * @param string $iri
+	 * @return array
+	 */
+	function parse_iri($iri)
+	{
+		preg_match('/^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?$/', $iri, $match);
+		for ($i = count($match); $i <= 9; $i++)
+		{
+			$match[$i] = '';
+		}
+		return array('scheme' => $match[2], 'authority' => $match[4], 'path' => $match[5], 'query' => $match[7], 'fragment' => $match[9]);
+	}
+
+	/**
+	 * Remove dot segments from a path
+	 *
+	 * @access private
+	 * @param string $input
+	 * @return string
+	 */
+	function remove_dot_segments($input)
+	{
+		$output = '';
+		while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..')
+		{
+			// A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise,
+			if (strpos($input, '../') === 0)
+			{
+				$input = substr($input, 3);
+			}
+			elseif (strpos($input, './') === 0)
+			{
+				$input = substr($input, 2);
+			}
+			// B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise,
+			elseif (strpos($input, '/./') === 0)
+			{
+				$input = substr_replace($input, '/', 0, 3);
+			}
+			elseif ($input === '/.')
+			{
+				$input = '/';
+			}
+			// C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise,
+			elseif (strpos($input, '/../') === 0)
+			{
+				$input = substr_replace($input, '/', 0, 4);
+				$output = substr_replace($output, '', strrpos($output, '/'));
+			}
+			elseif ($input === '/..')
+			{
+				$input = '/';
+				$output = substr_replace($output, '', strrpos($output, '/'));
+			}
+			// D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise,
+			elseif ($input === '.' || $input === '..')
+			{
+				$input = '';
+			}
+			// E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer
+			elseif (($pos = strpos($input, '/', 1)) !== false)
+			{
+				$output .= substr($input, 0, $pos);
+				$input = substr_replace($input, '', 0, $pos);
+			}
+			else
+			{
+				$output .= $input;
+				$input = '';
+			}
+		}
+		return $output . $input;
+	}
+
+	/**
+	 * Replace invalid character with percent encoding
+	 *
+	 * @access private
+	 * @param string $string Input string
+	 * @param string $valid_chars Valid characters
+	 * @param int $case Normalise case
+	 * @return string
+	 */
+	function replace_invalid_with_pct_encoding($string, $valid_chars, $case = SIMPLEPIE_SAME_CASE)
+	{
+		// Normalise case
+		if ($case & SIMPLEPIE_LOWERCASE)
+		{
+			$string = strtolower($string);
+		}
+		elseif ($case & SIMPLEPIE_UPPERCASE)
+		{
+			$string = strtoupper($string);
+		}
+
+		// Store position and string length (to avoid constantly recalculating this)
+		$position = 0;
+		$strlen = strlen($string);
+
+		// Loop as long as we have invalid characters, advancing the position to the next invalid character
+		while (($position += strspn($string, $valid_chars, $position)) < $strlen)
+		{
+			// If we have a % character
+			if ($string[$position] === '%')
+			{
+				// If we have a pct-encoded section
+				if ($position + 2 < $strlen && strspn($string, '0123456789ABCDEFabcdef', $position + 1, 2) === 2)
+				{
+					// Get the the represented character
+					$chr = chr(hexdec(substr($string, $position + 1, 2)));
+
+					// If the character is valid, replace the pct-encoded with the actual character while normalising case
+					if (strpos($valid_chars, $chr) !== false)
+					{
+						if ($case & SIMPLEPIE_LOWERCASE)
+						{
+							$chr = strtolower($chr);
+						}
+						elseif ($case & SIMPLEPIE_UPPERCASE)
+						{
+							$chr = strtoupper($chr);
+						}
+						$string = substr_replace($string, $chr, $position, 3);
+						$strlen -= 2;
+						$position++;
+					}
+
+					// Otherwise just normalise the pct-encoded to uppercase
+					else
+					{
+						$string = substr_replace($string, strtoupper(substr($string, $position + 1, 2)), $position + 1, 2);
+						$position += 3;
+					}
+				}
+				// If we don't have a pct-encoded section, just replace the % with its own esccaped form
+				else
+				{
+					$string = substr_replace($string, '%25', $position, 1);
+					$strlen += 2;
+					$position += 3;
+				}
+			}
+			// If we have an invalid character, change into its pct-encoded form
+			else
+			{
+				$replacement = sprintf("%%%02X", ord($string[$position]));
+				$string = str_replace($string[$position], $replacement, $string);
+				$strlen = strlen($string);
+			}
+		}
+		return $string;
+	}
+
+	/**
+	 * Check if the object represents a valid IRI
+	 *
+	 * @access public
+	 * @return bool
+	 */
+	function is_valid()
+	{
+		return array_sum($this->valid) === count($this->valid);
+	}
+
+	/**
+	 * Set the scheme. Returns true on success, false on failure (if there are
+	 * any invalid characters).
+	 *
+	 * @access public
+	 * @param string $scheme
+	 * @return bool
+	 */
+	function set_scheme($scheme)
+	{
+		if ($scheme === null || $scheme === '')
+		{
+			$this->scheme = null;
+		}
+		else
+		{
+			$len = strlen($scheme);
+			switch (true)
+			{
+				case $len > 1:
+					if (!strspn($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-.', 1))
+					{
+						$this->scheme = null;
+						$this->valid[__FUNCTION__] = false;
+						return false;
+					}
+
+				case $len > 0:
+					if (!strspn($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 0, 1))
+					{
+						$this->scheme = null;
+						$this->valid[__FUNCTION__] = false;
+						return false;
+					}
+			}
+			$this->scheme = strtolower($scheme);
+		}
+		$this->valid[__FUNCTION__] = true;
+		return true;
+	}
+
+	/**
+	 * Set the authority. Returns true on success, false on failure (if there are
+	 * any invalid characters).
+	 *
+	 * @access public
+	 * @param string $authority
+	 * @return bool
+	 */
+	function set_authority($authority)
+	{
+		if (($userinfo_end = strrpos($authority, '@')) !== false)
+		{
+			$userinfo = substr($authority, 0, $userinfo_end);
+			$authority = substr($authority, $userinfo_end + 1);
+		}
+		else
+		{
+			$userinfo = null;
+		}
+
+		if (($port_start = strpos($authority, ':')) !== false)
+		{
+			$port = substr($authority, $port_start + 1);
+			$authority = substr($authority, 0, $port_start);
+		}
+		else
+		{
+			$port = null;
+		}
+
+		return $this->set_userinfo($userinfo) && $this->set_host($authority) && $this->set_port($port);
+	}
+
+	/**
+	 * Set the userinfo.
+	 *
+	 * @access public
+	 * @param string $userinfo
+	 * @return bool
+	 */
+	function set_userinfo($userinfo)
+	{
+		if ($userinfo === null || $userinfo === '')
+		{
+			$this->userinfo = null;
+		}
+		else
+		{
+			$this->userinfo = $this->replace_invalid_with_pct_encoding($userinfo, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:');
+		}
+		$this->valid[__FUNCTION__] = true;
+		return true;
+	}
+
+	/**
+	 * Set the host. Returns true on success, false on failure (if there are
+	 * any invalid characters).
+	 *
+	 * @access public
+	 * @param string $host
+	 * @return bool
+	 */
+	function set_host($host)
+	{
+		if ($host === null || $host === '')
+		{
+			$this->host = null;
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+		elseif ($host[0] === '[' && substr($host, -1) === ']')
+		{
+			if (Net_IPv6::checkIPv6(substr($host, 1, -1)))
+			{
+				$this->host = $host;
+				$this->valid[__FUNCTION__] = true;
+				return true;
+			}
+			else
+			{
+				$this->host = null;
+				$this->valid[__FUNCTION__] = false;
+				return false;
+			}
+		}
+		else
+		{
+			$this->host = $this->replace_invalid_with_pct_encoding($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=', SIMPLEPIE_LOWERCASE);
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+	}
+
+	/**
+	 * Set the port. Returns true on success, false on failure (if there are
+	 * any invalid characters).
+	 *
+	 * @access public
+	 * @param string $port
+	 * @return bool
+	 */
+	function set_port($port)
+	{
+		if ($port === null || $port === '')
+		{
+			$this->port = null;
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+		elseif (strspn($port, '0123456789') === strlen($port))
+		{
+			$this->port = (int) $port;
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+		else
+		{
+			$this->port = null;
+			$this->valid[__FUNCTION__] = false;
+			return false;
+		}
+	}
+
+	/**
+	 * Set the path.
+	 *
+	 * @access public
+	 * @param string $path
+	 * @return bool
+	 */
+	function set_path($path)
+	{
+		if ($path === null || $path === '')
+		{
+			$this->path = null;
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+		elseif (substr($path, 0, 2) === '//' && $this->userinfo === null && $this->host === null && $this->port === null)
+		{
+			$this->path = null;
+			$this->valid[__FUNCTION__] = false;
+			return false;
+		}
+		else
+		{
+			$this->path = $this->replace_invalid_with_pct_encoding($path, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=@/');
+			if ($this->scheme !== null)
+			{
+				$this->path = $this->remove_dot_segments($this->path);
+			}
+			$this->valid[__FUNCTION__] = true;
+			return true;
+		}
+	}
+
+	/**
+	 * Set the query.
+	 *
+	 * @access public
+	 * @param string $query
+	 * @return bool
+	 */
+	function set_query($query)
+	{
+		if ($query === null || $query === '')
+		{
+			$this->query = null;
+		}
+		else
+		{
+			$this->query = $this->replace_invalid_with_pct_encoding($query, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:@/?');
+		}
+		$this->valid[__FUNCTION__] = true;
+		return true;
+	}
+
+	/**
+	 * Set the fragment.
+	 *
+	 * @access public
+	 * @param string $fragment
+	 * @return bool
+	 */
+	function set_fragment($fragment)
+	{
+		if ($fragment === null || $fragment === '')
+		{
+			$this->fragment = null;
+		}
+		else
+		{
+			$this->fragment = $this->replace_invalid_with_pct_encoding($fragment, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$&\'()*+,;=:@/?');
+		}
+		$this->valid[__FUNCTION__] = true;
+		return true;
+	}
+
+	/**
+	 * Get the complete IRI
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_iri()
+	{
+		$iri = '';
+		if ($this->scheme !== null)
+		{
+			$iri .= $this->scheme . ':';
+		}
+		if (($authority = $this->get_authority()) !== null)
+		{
+			$iri .= '//' . $authority;
+		}
+		if ($this->path !== null)
+		{
+			$iri .= $this->path;
+		}
+		if ($this->query !== null)
+		{
+			$iri .= '?' . $this->query;
+		}
+		if ($this->fragment !== null)
+		{
+			$iri .= '#' . $this->fragment;
+		}
+
+		if ($iri !== '')
+		{
+			return $iri;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Get the scheme
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_scheme()
+	{
+		return $this->scheme;
+	}
+
+	/**
+	 * Get the complete authority
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_authority()
+	{
+		$authority = '';
+		if ($this->userinfo !== null)
+		{
+			$authority .= $this->userinfo . '@';
+		}
+		if ($this->host !== null)
+		{
+			$authority .= $this->host;
+		}
+		if ($this->port !== null)
+		{
+			$authority .= ':' . $this->port;
+		}
+
+		if ($authority !== '')
+		{
+			return $authority;
+		}
+		else
+		{
+			return null;
+		}
+	}
+
+	/**
+	 * Get the user information
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_userinfo()
+	{
+		return $this->userinfo;
+	}
+
+	/**
+	 * Get the host
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_host()
+	{
+		return $this->host;
+	}
+
+	/**
+	 * Get the port
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_port()
+	{
+		return $this->port;
+	}
+
+	/**
+	 * Get the path
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_path()
+	{
+		return $this->path;
+	}
+
+	/**
+	 * Get the query
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_query()
+	{
+		return $this->query;
+	}
+
+	/**
+	 * Get the fragment
+	 *
+	 * @access public
+	 * @return string
+	 */
+	function get_fragment()
+	{
+		return $this->fragment;
+	}
+}
+
+/**
+ * Class to validate and to work with IPv6 addresses.
+ *
+ * @package SimplePie
+ * @copyright 2003-2005 The PHP Group
+ * @license http://www.opensource.org/licenses/bsd-license.php
+ * @link http://pear.php.net/package/Net_IPv6
+ * @author Alexander Merz <alexander.merz at web.de>
+ * @author elfrink at introweb dot nl
+ * @author Josh Peck <jmp at joshpeck dot org>
+ * @author Geoffrey Sneddon <geoffers at gmail.com>
+ */
+class SimplePie_Net_IPv6
+{
+	/**
+	 * Removes a possible existing netmask specification of an IP address.
+	 *
+	 * @param string $ip the (compressed) IP as Hex representation
+	 * @return string the IP the without netmask
+	 * @since 1.1.0
+	 * @access public
+	 * @static
+	 */
+	function removeNetmaskSpec($ip)
+	{
+		if (strpos($ip, '/') !== false)
+		{
+			list($addr, $nm) = explode('/', $ip);
+		}
+		else
+		{
+			$addr = $ip;
+		}
+		return $addr;
+	}
+
+	/**
+	 * Uncompresses an IPv6 address
+	 *
+	 * RFC 2373 allows you to compress zeros in an address to '::'. This
+	 * function expects an valid IPv6 address and expands the '::' to
+	 * the required zeros.
+	 *
+	 * Example:	 FF01::101	->	FF01:0:0:0:0:0:0:101
+	 *			 ::1		->	0:0:0:0:0:0:0:1
+	 *
+	 * @access public
+	 * @static
+	 * @param string $ip a valid IPv6-address (hex format)
+	 * @return string the uncompressed IPv6-address (hex format)
+	 */
+	function Uncompress($ip)
+	{
+		$uip = SimplePie_Net_IPv6::removeNetmaskSpec($ip);
+		$c1 = -1;
+		$c2 = -1;
+		if (strpos($ip, '::') !== false)
+		{
+			list($ip1, $ip2) = explode('::', $ip);
+			if ($ip1 === '')
+			{
+				$c1 = -1;
+			}
+			else
+			{
+				$pos = 0;
+				if (($pos = substr_count($ip1, ':')) > 0)
+				{
+					$c1 = $pos;
+				}
+				else
+				{
+					$c1 = 0;
+				}
+			}
+			if ($ip2 === '')
+			{
+				$c2 = -1;
+			}
+			else
+			{
+				$pos = 0;
+				if (($pos = substr_count($ip2, ':')) > 0)
+				{
+					$c2 = $pos;
+				}
+				else
+				{
+					$c2 = 0;
+				}
+			}
+			if (strstr($ip2, '.'))
+			{
+				$c2++;
+			}
+			// ::
+			if ($c1 === -1 && $c2 === -1)
+			{
+				$uip = '0:0:0:0:0:0:0:0';
+			}
+			// ::xxx
+			else if ($c1 === -1)
+			{
+				$fill = str_repeat('0:', 7 - $c2);
+				$uip =	str_replace('::', $fill, $uip);
+			}
+			// xxx::
+			else if ($c2 === -1)
+			{
+				$fill = str_repeat(':0', 7 - $c1);
+				$uip =	str_replace('::', $fill, $uip);
+			}
+			// xxx::xxx
+			else
+			{
+				$fill = str_repeat(':0:', 6 - $c2 - $c1);
+				$uip =	str_replace('::', $fill, $uip);
+				$uip =	str_replace('::', ':', $uip);
+			}
+		}
+		return $uip;
+	}
+
+	/**
+	 * Splits an IPv6 address into the IPv6 and a possible IPv4 part
+	 *
+	 * RFC 2373 allows you to note the last two parts of an IPv6 address as
+	 * an IPv4 compatible address
+	 *
+	 * Example:	 0:0:0:0:0:0:13.1.68.3
+	 *			 0:0:0:0:0:FFFF:129.144.52.38
+	 *
+	 * @access public
+	 * @static
+	 * @param string $ip a valid IPv6-address (hex format)
+	 * @return array [0] contains the IPv6 part, [1] the IPv4 part (hex format)
+	 */
+	function SplitV64($ip)
+	{
+		$ip = SimplePie_Net_IPv6::Uncompress($ip);
+		if (strstr($ip, '.'))
+		{
+			$pos = strrpos($ip, ':');
+			$ip[$pos] = '_';
+			$ipPart = explode('_', $ip);
+			return $ipPart;
+		}
+		else
+		{
+			return array($ip, '');
+		}
+	}
+
+	/**
+	 * Checks an IPv6 address
+	 *
+	 * Checks if the given IP is IPv6-compatible
+	 *
+	 * @access public
+	 * @static
+	 * @param string $ip a valid IPv6-address
+	 * @return bool true if $ip is an IPv6 address
+	 */
+	function checkIPv6($ip)
+	{
+		$ipPart = SimplePie_Net_IPv6::SplitV64($ip);
+		$count = 0;
+		if (!empty($ipPart[0]))
+		{
+			$ipv6 = explode(':', $ipPart[0]);
+			for ($i = 0; $i < count($ipv6); $i++)
+			{
+				$dec = hexdec($ipv6[$i]);
+				$hex = strtoupper(preg_replace('/^[0]{1,3}(.*[0-9a-fA-F])$/', '\\1', $ipv6[$i]));
+				if ($ipv6[$i] >= 0 && $dec <= 65535 && $hex === strtoupper(dechex($dec)))
+				{
+					$count++;
+				}
+			}
+			if ($count === 8)
+			{
+				return true;
+			}
+			elseif ($count === 6 && !empty($ipPart[1]))
+			{
+				$ipv4 = explode('.', $ipPart[1]);
+				$count = 0;
+				foreach ($ipv4 as $ipv4_part)
+				{
+					if ($ipv4_part >= 0 && $ipv4_part <= 255 && preg_match('/^\d{1,3}$/', $ipv4_part))
+					{
+						$count++;
+					}
+				}
+				if ($count === 4)
+				{
+					return true;
+				}
+			}
+			else
+			{
+				return false;
+			}
+
+		}
+		else
+		{
+			return false;
+		}
+	}
+}
+
+/**
+ * Date Parser
+ *
+ * @package SimplePie
+ */
+class SimplePie_Parse_Date
+{
+	/**
+	 * Input data
+	 *
+	 * @access protected
+	 * @var string
+	 */
+	var $date;
+
+	/**
+	 * List of days, calendar day name => ordinal day number in the week
+	 *
+	 * @access protected
+	 * @var array
+	 */
+	var $day = array(
+		// English
+		'mon' => 1,
+		'monday' => 1,
+		'tue' => 2,
+		'tuesday' => 2,
+		'wed' => 3,
+		'wednesday' => 3,
+		'thu' => 4,
+		'thursday' => 4,
+		'fri' => 5,
+		'friday' => 5,
+		'sat' => 6,
+		'saturday' => 6,
+		'sun' => 7,
+		'sunday' => 7,
+		// Dutch
+		'maandag' => 1,
+		'dinsdag' => 2,
+		'woensdag' => 3,
+		'donderdag' => 4,
+		'vrijdag' => 5,
+		'zaterdag' => 6,
+		'zondag' => 7,
+		// French
+		'lundi' => 1,
+		'mardi' => 2,
+		'mercredi' => 3,
+		'jeudi' => 4,
+		'vendredi' => 5,
+		'samedi' => 6,
+		'dimanche' => 7,
+		// German
+		'montag' => 1,
+		'dienstag' => 2,
+		'mittwoch' => 3,
+		'donnerstag' => 4,
+		'freitag' => 5,
+		'samstag' => 6,
+		'sonnabend' => 6,
+		'sonntag' => 7,
+		// Italian
+		'lunedì' => 1,
+		'martedì' => 2,
+		'mercoledì' => 3,
+		'giovedì' => 4,
+		'venerdì' => 5,
+		'sabato' => 6,
+		'domenica' => 7,
+		// Spanish
+		'lunes' => 1,
+		'martes' => 2,
+		'miércoles' => 3,
+		'jueves' => 4,
+		'viernes' => 5,
+		'sábado' => 6,
+		'domingo' => 7,
+		// Finnish
+		'maanantai' => 1,
+		'tiistai' => 2,
+		'keskiviikko' => 3,
+		'torstai' => 4,
+		'perjantai' => 5,
+		'lauantai' => 6,
+		'sunnuntai' => 7,
+		// Hungarian
+		'hétfő' => 1,
+		'kedd' => 2,
+		'szerda' => 3,
+		'csütörtok' => 4,
+		'péntek' => 5,
+		'szombat' => 6,
+		'vasárnap' => 7,
+		// Greek
+		'Δευ' => 1,
+		'Τρι' => 2,
+		'Τετ' => 3,
+		'Πεμ' => 4,
+		'Παρ' => 5,
+		'Σαβ' => 6,
+		'Κυρ' => 7,
+	);
+
+	/**
+	 * List of months, calendar month name => calendar month number
+	 *
+	 * @access protected
+	 * @var array
+	 */
+	var $month = array(
+		// English
+		'jan' => 1,
+		'january' => 1,
+		'feb' => 2,
+		'february' => 2,
+		'mar' => 3,
+		'march' => 3,
+		'apr' => 4,
+		'april' => 4,
+		'may' => 5,
+		// No long form of May
+		'jun' => 6,
+		'june' => 6,
+		'jul' => 7,
+		'july' => 7,
+		'aug' => 8,
+		'august' => 8,
+		'sep' => 9,
+		'september' => 8,
+		'oct' => 10,
+		'october' => 10,
+		'nov' => 11,
+		'november' => 11,
+		'dec' => 12,
+		'december' => 12,
+		// Dutch
+		'januari' => 1,
+		'februari' => 2,
+		'maart' => 3,
+		'april' => 4,
+		'mei' => 5,
+		'juni' => 6,
+		'juli' => 7,
+		'augustus' => 8,
+		'september' => 9,
+		'oktober' => 10,
+		'november' => 11,
+		'december' => 12,
+		// French
+		'janvier' => 1,
+		'février' => 2,
+		'mars' => 3,
+		'avril' => 4,
+		'mai' => 5,
+		'juin' => 6,
+		'juillet' => 7,
+		'août' => 8,
+		'septembre' => 9,
+		'octobre' => 10,
+		'novembre' => 11,
+		'décembre' => 12,
+		// German
+		'januar' => 1,
+		'februar' => 2,
+		'märz' => 3,
+		'april' => 4,
+		'mai' => 5,
+		'juni' => 6,
+		'juli' => 7,
+		'august' => 8,
+		'september' => 9,
+		'oktober' => 10,
+		'november' => 11,
+		'dezember' => 12,
+		// Italian
+		'gennaio' => 1,
+		'febbraio' => 2,
+		'marzo' => 3,
+		'aprile' => 4,
+		'maggio' => 5,
+		'giugno' => 6,
+		'luglio' => 7,
+		'agosto' => 8,
+		'settembre' => 9,
+		'ottobre' => 10,
+		'novembre' => 11,
+		'dicembre' => 12,
+		// Spanish
+		'enero' => 1,
+		'febrero' => 2,
+		'marzo' => 3,
+		'abril' => 4,
+		'mayo' => 5,
+		'junio' => 6,
+		'julio' => 7,
+		'agosto' => 8,
+		'septiembre' => 9,
+		'setiembre' => 9,
+		'octubre' => 10,
+		'noviembre' => 11,
+		'diciembre' => 12,
+		// Finnish
+		'tammikuu' => 1,
+		'helmikuu' => 2,
+		'maaliskuu' => 3,
+		'huhtikuu' => 4,
+		'toukokuu' => 5,
+		'kesäkuu' => 6,
+		'heinäkuu' => 7,
+		'elokuu' => 8,
+		'suuskuu' => 9,
+		'lokakuu' => 10,
+		'marras' => 11,
+		'joulukuu' => 12,
+		// Hungarian
+		'január' => 1,
+		'február' => 2,
+		'március' => 3,
+		'április' => 4,
+		'május' => 5,
+		'június' => 6,
+		'július' => 7,
+		'augusztus' => 8,
+		'szeptember' => 9,
+		'október' => 10,
+		'november' => 11,
+		'december' => 12,
+		// Greek
+		'Ιαν' => 1,
+		'Φεβ' => 2,
+		'Μάώ' => 3,
+		'Μαώ' => 3,
+		'Απρ' => 4,
+		'Μάι' => 5,
+		'Μαϊ' => 5,
+		'Μαι' => 5,
+		'Ιούν' => 6,
+		'Ιον' => 6,
+		'Ιούλ' => 7,
+		'Ιολ' => 7,
+		'Αύγ' => 8,
+		'Αυγ' => 8,
+		'Σεπ' => 9,
+		'Οκτ' => 10,
+		'Νοέ' => 11,
+		'Δεκ' => 12,
+	);
+
+	/**
+	 * List of timezones, abbreviation => offset from UTC
+	 *
+	 * @access protected
+	 * @var array
+	 */
+	var $timezone = array(
+		'ACDT' => 37800,
+		'ACIT' => 28800,
+		'ACST' => 34200,
+		'ACT' => -18000,
+		'ACWDT' => 35100,
+		'ACWST' => 31500,
+		'AEDT' => 39600,
+		'AEST' => 36000,
+		'AFT' => 16200,
+		'AKDT' => -28800,
+		'AKST' => -32400,
+		'AMDT' => 18000,
+		'AMT' => -14400,
+		'ANAST' => 46800,
+		'ANAT' => 43200,
+		'ART' => -10800,
+		'AZOST' => -3600,
+		'AZST' => 18000,
+		'AZT' => 14400,
+		'BIOT' => 21600,
+		'BIT' => -43200,
+		'BOT' => -14400,
+		'BRST' => -7200,
+		'BRT' => -10800,
+		'BST' => 3600,
+		'BTT' => 21600,
+		'CAST' => 18000,
+		'CAT' => 7200,
+		'CCT' => 23400,
+		'CDT' => -18000,
+		'CEDT' => 7200,
+		'CET' => 3600,
+		'CGST' => -7200,
+		'CGT' => -10800,
+		'CHADT' => 49500,
+		'CHAST' => 45900,
+		'CIST' => -28800,
+		'CKT' => -36000,
+		'CLDT' => -10800,
+		'CLST' => -14400,
+		'COT' => -18000,
+		'CST' => -21600,
+		'CVT' => -3600,
+		'CXT' => 25200,
+		'DAVT' => 25200,
+		'DTAT' => 36000,
+		'EADT' => -18000,
+		'EAST' => -21600,
+		'EAT' => 10800,
+		'ECT' => -18000,
+		'EDT' => -14400,
+		'EEST' => 10800,
+		'EET' => 7200,
+		'EGT' => -3600,
+		'EKST' => 21600,
+		'EST' => -18000,
+		'FJT' => 43200,
+		'FKDT' => -10800,
+		'FKST' => -14400,
+		'FNT' => -7200,
+		'GALT' => -21600,
+		'GEDT' => 14400,
+		'GEST' => 10800,
+		'GFT' => -10800,
+		'GILT' => 43200,
+		'GIT' => -32400,
+		'GST' => 14400,
+		'GST' => -7200,
+		'GYT' => -14400,
+		'HAA' => -10800,
+		'HAC' => -18000,
+		'HADT' => -32400,
+		'HAE' => -14400,
+		'HAP' => -25200,
+		'HAR' => -21600,
+		'HAST' => -36000,
+		'HAT' => -9000,
+		'HAY' => -28800,
+		'HKST' => 28800,
+		'HMT' => 18000,
+		'HNA' => -14400,
+		'HNC' => -21600,
+		'HNE' => -18000,
+		'HNP' => -28800,
+		'HNR' => -25200,
+		'HNT' => -12600,
+		'HNY' => -32400,
+		'IRDT' => 16200,
+		'IRKST' => 32400,
+		'IRKT' => 28800,
+		'IRST' => 12600,
+		'JFDT' => -10800,
+		'JFST' => -14400,
+		'JST' => 32400,
+		'KGST' => 21600,
+		'KGT' => 18000,
+		'KOST' => 39600,
+		'KOVST' => 28800,
+		'KOVT' => 25200,
+		'KRAST' => 28800,
+		'KRAT' => 25200,
+		'KST' => 32400,
+		'LHDT' => 39600,
+		'LHST' => 37800,
+		'LINT' => 50400,
+		'LKT' => 21600,
+		'MAGST' => 43200,
+		'MAGT' => 39600,
+		'MAWT' => 21600,
+		'MDT' => -21600,
+		'MESZ' => 7200,
+		'MEZ' => 3600,
+		'MHT' => 43200,
+		'MIT' => -34200,
+		'MNST' => 32400,
+		'MSDT' => 14400,
+		'MSST' => 10800,
+		'MST' => -25200,
+		'MUT' => 14400,
+		'MVT' => 18000,
+		'MYT' => 28800,
+		'NCT' => 39600,
+		'NDT' => -9000,
+		'NFT' => 41400,
+		'NMIT' => 36000,
+		'NOVST' => 25200,
+		'NOVT' => 21600,
+		'NPT' => 20700,
+		'NRT' => 43200,
+		'NST' => -12600,
+		'NUT' => -39600,
+		'NZDT' => 46800,
+		'NZST' => 43200,
+		'OMSST' => 25200,
+		'OMST' => 21600,
+		'PDT' => -25200,
+		'PET' => -18000,
+		'PETST' => 46800,
+		'PETT' => 43200,
+		'PGT' => 36000,
+		'PHOT' => 46800,
+		'PHT' => 28800,
+		'PKT' => 18000,
+		'PMDT' => -7200,
+		'PMST' => -10800,
+		'PONT' => 39600,
+		'PST' => -28800,
+		'PWT' => 32400,
+		'PYST' => -10800,
+		'PYT' => -14400,
+		'RET' => 14400,
+		'ROTT' => -10800,
+		'SAMST' => 18000,
+		'SAMT' => 14400,
+		'SAST' => 7200,
+		'SBT' => 39600,
+		'SCDT' => 46800,
+		'SCST' => 43200,
+		'SCT' => 14400,
+		'SEST' => 3600,
+		'SGT' => 28800,
+		'SIT' => 28800,
+		'SRT' => -10800,
+		'SST' => -39600,
+		'SYST' => 10800,
+		'SYT' => 7200,
+		'TFT' => 18000,
+		'THAT' => -36000,
+		'TJT' => 18000,
+		'TKT' => -36000,
+		'TMT' => 18000,
+		'TOT' => 46800,
+		'TPT' => 32400,
+		'TRUT' => 36000,
+		'TVT' => 43200,
+		'TWT' => 28800,
+		'UYST' => -7200,
+		'UYT' => -10800,
+		'UZT' => 18000,
+		'VET' => -14400,
+		'VLAST' => 39600,
+		'VLAT' => 36000,
+		'VOST' => 21600,
+		'VUT' => 39600,
+		'WAST' => 7200,
+		'WAT' => 3600,
+		'WDT' => 32400,
+		'WEST' => 3600,
+		'WFT' => 43200,
+		'WIB' => 25200,
+		'WIT' => 32400,
+		'WITA' => 28800,
+		'WKST' => 18000,
+		'WST' => 28800,
+		'YAKST' => 36000,
+		'YAKT' => 32400,
+		'YAPT' => 36000,
+		'YEKST' => 21600,
+		'YEKT' => 18000,
+	);
+
+	/**
+	 * Cached PCRE for SimplePie_Parse_Date::$day
+	 *
+	 * @access protected
+	 * @var string
+	 */
+	var $day_pcre;
+
+	/**
+	 * Cached PCRE for SimplePie_Parse_Date::$month
+	 *
+	 * @access protected
+	 * @var string
+	 */
+	var $month_pcre;
+
+	/**
+	 * Array of user-added callback methods
+	 *
+	 * @access private
+	 * @var array
+	 */
+	var $built_in = array();
+
+	/**
+	 * Array of user-added callback methods
+	 *
+	 * @access private
+	 * @var array
+	 */
+	var $user = array();
+
+	/**
+	 * Create new SimplePie_Parse_Date object, and set self::day_pcre,
+	 * self::month_pcre, and self::built_in
+	 *
+	 * @access private
+	 */
+	function SimplePie_Parse_Date()
+	{
+		$this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')';
+		$this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')';
+
+		static $cache;
+		if (!isset($cache[get_class($this)]))
+		{
+			$all_methods = get_class_methods($this);
+
+			foreach ($all_methods as $method)
+			{
+				if (strtolower(substr($method, 0, 5)) === 'date_')
+				{
+					$cache[get_class($this)][] = $method;
+				}
+			}
+		}
+
+		foreach ($cache[get_class($this)] as $method)
+		{
+			$this->built_in[] = $method;
+		}
+	}
+
+	/**
+	 * Get the object
+	 *
+	 * @access public
+	 */
+	function get()
+	{
+		static $object;
+		if (!$object)
+		{
+			$object = new SimplePie_Parse_Date;
+		}
+		return $object;
+	}
+
+	/**
+	 * Parse a date
+	 *
+	 * @final
+	 * @access public
+	 * @param string $date Date to parse
+	 * @return int Timestamp corresponding to date string, or false on failure
+	 */
+	function parse($date)
+	{
+		foreach ($this->user as $method)
+		{
+			if (($returned = call_user_func($method, $date)) !== false)
+			{
+				return $returned;
+			}
+		}
+
+		foreach ($this->built_in as $method)
+		{
+			if (($returned = call_user_func(array(&$this, $method), $date)) !== false)
+			{
+				return $returned;
+			}
+		}
+
+		return false;
+	}
+
+	/**
+	 * Add a callback method to parse a date
+	 *
+	 * @final
+	 * @access public
+	 * @param callback $callback
+	 */
+	function add_callback($callback)
+	{
+		if (is_callable($callback))
+		{
+			$this->user[] = $callback;
+		}
+		else
+		{
+			trigger_error('User-supplied function must be a valid callback', E_USER_WARNING);
+		}
+	}
+
+	/**
+	 * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as
+	 * well as allowing any of upper or lower case "T", horizontal tabs, or
+	 * spaces to be used as the time seperator (including more than one))
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	function date_w3cdtf($date)
+	{
+		static $pcre;
+		if (!$pcre)
+		{
+			$year = '([0-9]{4})';
+			$month = $day = $hour = $minute = $second = '([0-9]{2})';
+			$decimal = '([0-9]*)';
+			$zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))';
+			$pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/';
+		}
+		if (preg_match($pcre, $date, $match))
+		{
+			/*
+			Capturing subpatterns:
+			1: Year
+			2: Month
+			3: Day
+			4: Hour
+			5: Minute
+			6: Second
+			7: Decimal fraction of a second
+			8: Zulu
+			9: Timezone ±
+			10: Timezone hours
+			11: Timezone minutes
+			*/
+
+			// Fill in empty matches
+			for ($i = count($match); $i <= 3; $i++)
+			{
+				$match[$i] = '1';
+			}
+
+			for ($i = count($match); $i <= 7; $i++)
+			{
+				$match[$i] = '0';
+			}
+
+			// Numeric timezone
+			if (isset($match[9]) && $match[9] !== '')
+			{
+				$timezone = $match[10] * 3600;
+				$timezone += $match[11] * 60;
+				if ($match[9] === '-')
+				{
+					$timezone = 0 - $timezone;
+				}
+			}
+			else
+			{
+				$timezone = 0;
+			}
+
+			// Convert the number of seconds to an integer, taking decimals into account
+			$second = round($match[6] + $match[7] / pow(10, strlen($match[7])));
+
+			return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Remove RFC822 comments
+	 *
+	 * @access protected
+	 * @param string $data Data to strip comments from
+	 * @return string Comment stripped string
+	 */
+	function remove_rfc2822_comments($string)
+	{
+		$string = (string) $string;
+		$position = 0;
+		$length = strlen($string);
+		$depth = 0;
+
+		$output = '';
+
+		while ($position < $length && ($pos = strpos($string, '(', $position)) !== false)
+		{
+			$output .= substr($string, $position, $pos - $position);
+			$position = $pos + 1;
+			if ($string[$pos - 1] !== '\\')
+			{
+				$depth++;
+				while ($depth && $position < $length)
+				{
+					$position += strcspn($string, '()', $position);
+					if ($string[$position - 1] === '\\')
+					{
+						$position++;
+						continue;
+					}
+					elseif (isset($string[$position]))
+					{
+						switch ($string[$position])
+						{
+							case '(':
+								$depth++;
+								break;
+
+							case ')':
+								$depth--;
+								break;
+						}
+						$position++;
+					}
+					else
+					{
+						break;
+					}
+				}
+			}
+			else
+			{
+				$output .= '(';
+			}
+		}
+		$output .= substr($string, $position);
+
+		return $output;
+	}
+
+	/**
+	 * Parse RFC2822's date format
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	function date_rfc2822($date)
+	{
+		static $pcre;
+		if (!$pcre)
+		{
+			$wsp = '[\x09\x20]';
+			$fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)';
+			$optional_fws = $fws . '?';
+			$day_name = $this->day_pcre;
+			$month = $this->month_pcre;
+			$day = '([0-9]{1,2})';
+			$hour = $minute = $second = '([0-9]{2})';
+			$year = '([0-9]{2,4})';
+			$num_zone = '([+\-])([0-9]{2})([0-9]{2})';
+			$character_zone = '([A-Z]{1,5})';
+			$zone = '(?:' . $num_zone . '|' . $character_zone . ')';
+			$pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i';
+		}
+		if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match))
+		{
+			/*
+			Capturing subpatterns:
+			1: Day name
+			2: Day
+			3: Month
+			4: Year
+			5: Hour
+			6: Minute
+			7: Second
+			8: Timezone ±
+			9: Timezone hours
+			10: Timezone minutes
+			11: Alphabetic timezone
+			*/
+
+			// Find the month number
+			$month = $this->month[strtolower($match[3])];
+
+			// Numeric timezone
+			if ($match[8] !== '')
+			{
+				$timezone = $match[9] * 3600;
+				$timezone += $match[10] * 60;
+				if ($match[8] === '-')
+				{
+					$timezone = 0 - $timezone;
+				}
+			}
+			// Character timezone
+			elseif (isset($this->timezone[strtoupper($match[11])]))
+			{
+				$timezone = $this->timezone[strtoupper($match[11])];
+			}
+			// Assume everything else to be -0000
+			else
+			{
+				$timezone = 0;
+			}
+
+			// Deal with 2/3 digit years
+			if ($match[4] < 50)
+			{
+				$match[4] += 2000;
+			}
+			elseif ($match[4] < 1000)
+			{
+				$match[4] += 1900;
+			}
+
+			// Second is optional, if it is empty set it to zero
+			if ($match[7] !== '')
+			{
+				$second = $match[7];
+			}
+			else
+			{
+				$second = 0;
+			}
+
+			return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Parse RFC850's date format
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	function date_rfc850($date)
+	{
+		static $pcre;
+		if (!$pcre)
+		{
+			$space = '[\x09\x20]+';
+			$day_name = $this->day_pcre;
+			$month = $this->month_pcre;
+			$day = '([0-9]{1,2})';
+			$year = $hour = $minute = $second = '([0-9]{2})';
+			$zone = '([A-Z]{1,5})';
+			$pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i';
+		}
+		if (preg_match($pcre, $date, $match))
+		{
+			/*
+			Capturing subpatterns:
+			1: Day name
+			2: Day
+			3: Month
+			4: Year
+			5: Hour
+			6: Minute
+			7: Second
+			8: Timezone
+			*/
+
+			// Month
+			$month = $this->month[strtolower($match[3])];
+
+			// Character timezone
+			if (isset($this->timezone[strtoupper($match[8])]))
+			{
+				$timezone = $this->timezone[strtoupper($match[8])];
+			}
+			// Assume everything else to be -0000
+			else
+			{
+				$timezone = 0;
+			}
+
+			// Deal with 2 digit year
+			if ($match[4] < 50)
+			{
+				$match[4] += 2000;
+			}
+			else
+			{
+				$match[4] += 1900;
+			}
+
+			return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Parse C99's asctime()'s date format
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	function date_asctime($date)
+	{
+		static $pcre;
+		if (!$pcre)
+		{
+			$space = '[\x09\x20]+';
+			$wday_name = $this->day_pcre;
+			$mon_name = $this->month_pcre;
+			$day = '([0-9]{1,2})';
+			$hour = $sec = $min = '([0-9]{2})';
+			$year = '([0-9]{4})';
+			$terminator = '\x0A?\x00?';
+			$pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i';
+		}
+		if (preg_match($pcre, $date, $match))
+		{
+			/*
+			Capturing subpatterns:
+			1: Day name
+			2: Month
+			3: Day
+			4: Hour
+			5: Minute
+			6: Second
+			7: Year
+			*/
+
+			$month = $this->month[strtolower($match[2])];
+			return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]);
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Parse dates using strtotime()
+	 *
+	 * @access protected
+	 * @return int Timestamp
+	 */
+	function date_strtotime($date)
+	{
+		$strtotime = strtotime($date);
+		if ($strtotime === -1 || $strtotime === false)
+		{
+			return false;
+		}
+		else
+		{
+			return $strtotime;
+		}
+	}
+}
+
+/**
+ * Content-type sniffing
+ *
+ * @package SimplePie
+ */
+class SimplePie_Content_Type_Sniffer
+{
+	/**
+	 * File object
+	 *
+	 * @var SimplePie_File
+	 * @access private
+	 */
+	var $file;
+
+	/**
+	 * Create an instance of the class with the input file
+	 *
+	 * @access public
+	 * @param SimplePie_Content_Type_Sniffer $file Input file
+	 */
+	function SimplePie_Content_Type_Sniffer($file)
+	{
+		$this->file = $file;
+	}
+
+	/**
+	 * Get the Content-Type of the specified file
+	 *
+	 * @access public
+	 * @return string Actual Content-Type
+	 */
+	function get_type()
+	{
+		if (isset($this->file->headers['content-type']))
+		{
+			if (!isset($this->file->headers['content-encoding'])
+				&& ($this->file->headers['content-type'] === 'text/plain'
+					|| $this->file->headers['content-type'] === 'text/plain; charset=ISO-8859-1'
+					|| $this->file->headers['content-type'] === 'text/plain; charset=iso-8859-1'))
+			{
+				return $this->text_or_binary();
+			}
+
+			if (($pos = strpos($this->file->headers['content-type'], ';')) !== false)
+			{
+				$official = substr($this->file->headers['content-type'], 0, $pos);
+			}
+			else
+			{
+				$official = $this->file->headers['content-type'];
+			}
+			$official = strtolower($official);
+
+			if ($official === 'unknown/unknown'
+				|| $official === 'application/unknown')
+			{
+				return $this->unknown();
+			}
+			elseif (substr($official, -4) === '+xml'
+				|| $official === 'text/xml'
+				|| $official === 'application/xml')
+			{
+				return $official;
+			}
+			elseif (substr($official, 0, 6) === 'image/')
+			{
+				if ($return = $this->image())
+				{
+					return $return;
+				}
+				else
+				{
+					return $official;
+				}
+			}
+			elseif ($official === 'text/html')
+			{
+				return $this->feed_or_html();
+			}
+			else
+			{
+				return $official;
+			}
+		}
+		else
+		{
+			return $this->unknown();
+		}
+	}
+
+	/**
+	 * Sniff text or binary
+	 *
+	 * @access private
+	 * @return string Actual Content-Type
+	 */
+	function text_or_binary()
+	{
+		if (substr($this->file->body, 0, 2) === "\xFE\xFF"
+			|| substr($this->file->body, 0, 2) === "\xFF\xFE"
+			|| substr($this->file->body, 0, 4) === "\x00\x00\xFE\xFF"
+			|| substr($this->file->body, 0, 3) === "\xEF\xBB\xBF")
+		{
+			return 'text/plain';
+		}
+		elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $this->file->body))
+		{
+			return 'application/octect-stream';
+		}
+		else
+		{
+			return 'text/plain';
+		}
+	}
+
+	/**
+	 * Sniff unknown
+	 *
+	 * @access private
+	 * @return string Actual Content-Type
+	 */
+	function unknown()
+	{
+		$ws = strspn($this->file->body, "\x09\x0A\x0B\x0C\x0D\x20");
+		if (strtolower(substr($this->file->body, $ws, 14)) === '<!doctype html'
+			|| strtolower(substr($this->file->body, $ws, 5)) === '<html'
+			|| strtolower(substr($this->file->body, $ws, 7)) === '<script')
+		{
+			return 'text/html';
+		}
+		elseif (substr($this->file->body, 0, 5) === '%PDF-')
+		{
+			return 'application/pdf';
+		}
+		elseif (substr($this->file->body, 0, 11) === '%!PS-Adobe-')
+		{
+			return 'application/postscript';
+		}
+		elseif (substr($this->file->body, 0, 6) === 'GIF87a'
+			|| substr($this->file->body, 0, 6) === 'GIF89a')
+		{
+			return 'image/gif';
+		}
+		elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
+		{
+			return 'image/png';
+		}
+		elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF")
+		{
+			return 'image/jpeg';
+		}
+		elseif (substr($this->file->body, 0, 2) === "\x42\x4D")
+		{
+			return 'image/bmp';
+		}
+		else
+		{
+			return $this->text_or_binary();
+		}
+	}
+
+	/**
+	 * Sniff images
+	 *
+	 * @access private
+	 * @return string Actual Content-Type
+	 */
+	function image()
+	{
+		if (substr($this->file->body, 0, 6) === 'GIF87a'
+			|| substr($this->file->body, 0, 6) === 'GIF89a')
+		{
+			return 'image/gif';
+		}
+		elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
+		{
+			return 'image/png';
+		}
+		elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF")
+		{
+			return 'image/jpeg';
+		}
+		elseif (substr($this->file->body, 0, 2) === "\x42\x4D")
+		{
+			return 'image/bmp';
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	/**
+	 * Sniff HTML
+	 *
+	 * @access private
+	 * @return string Actual Content-Type
+	 */
+	function feed_or_html()
+	{
+		$len = strlen($this->file->body);
+		$pos = strspn($this->file->body, "\x09\x0A\x0D\x20");
+
+		while ($pos < $len)
+		{
+			switch ($this->file->body[$pos])
+			{
+				case "\x09":
+				case "\x0A":
+				case "\x0D":
+				case "\x20":
+					$pos += strspn($this->file->body, "\x09\x0A\x0D\x20", $pos);
+					continue 2;
+
+				case '<':
+					$pos++;
+					break;
+
+				default:
+					return 'text/html';
+			}
+
+			if (substr($this->file->body, $pos, 3) === '!--')
+			{
+				$pos += 3;
+				if ($pos < $len && ($pos = strpos($this->file->body, '-->', $pos)) !== false)
+				{
+					$pos += 3;
+				}
+				else
+				{
+					return 'text/html';
+				}
+			}
+			elseif (substr($this->file->body, $pos, 1) === '!')
+			{
+				if ($pos < $len && ($pos = strpos($this->file->body, '>', $pos)) !== false)
+				{
+					$pos++;
+				}
+				else
+				{
+					return 'text/html';
+				}
+			}
+			elseif (substr($this->file->body, $pos, 1) === '?')
+			{
+				if ($pos < $len && ($pos = strpos($this->file->body, '?>', $pos)) !== false)
+				{
+					$pos += 2;
+				}
+				else
+				{
+					return 'text/html';
+				}
+			}
+			elseif (substr($this->file->body, $pos, 3) === 'rss'
+				|| substr($this->file->body, $pos, 7) === 'rdf:RDF')
+			{
+				return 'application/rss+xml';
+			}
+			elseif (substr($this->file->body, $pos, 4) === 'feed')
+			{
+				return 'application/atom+xml';
+			}
+			else
+			{
+				return 'text/html';
+			}
+		}
+
+		return 'text/html';
+	}
+}
+
+/**
+ * Parses the XML Declaration
+ *
+ * @package SimplePie
+ */
+class SimplePie_XML_Declaration_Parser
+{
+	/**
+	 * XML Version
+	 *
+	 * @access public
+	 * @var string
+	 */
+	var $version = '1.0';
+
+	/**
+	 * Encoding
+	 *
+	 * @access public
+	 * @var string
+	 */
+	var $encoding = 'UTF-8';
+
+	/**
+	 * Standalone
+	 *
+	 * @access public
+	 * @var bool
+	 */
+	var $standalone = false;
+
+	/**
+	 * Current state of the state machine
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $state = 'before_version_name';
+
+	/**
+	 * Input data
+	 *
+	 * @access private
+	 * @var string
+	 */
+	var $data = '';
+
+	/**
+	 * Input data length (to avoid calling strlen() everytime this is needed)
+	 *
+	 * @access private
+	 * @var int
+	 */
+	var $data_length = 0;
+
+	/**
+	 * Current position of the pointer
+	 *
+	 * @var int
+	 * @access private
+	 */
+	var $position = 0;
+
+	/**
+	 * Create an instance of the class with the input data
+	 *
+	 * @access public
+	 * @param string $data Input data
+	 */
+	function SimplePie_XML_Declaration_Parser($data)
+	{
+		$this->data = $data;
+		$this->data_length = strlen($this->data);
+	}
+
+	/**
+	 * Parse the input data
+	 *
+	 * @access public
+	 * @return bool true on success, false on failure
+	 */
+	function parse()
+	{
+		while ($this->state && $this->state !== 'emit' && $this->has_data())
+		{
+			$state = $this->state;
+			$this->$state();
+		}
+		$this->data = '';
+		if ($this->state === 'emit')
+		{
+			return true;
+		}
+		else
+		{
+			$this->version = '';
+			$this->encoding = '';
+			$this->standalone = '';
+			return false;
+		}
+	}
+
+	/**
+	 * Check whether there is data beyond the pointer
+	 *
+	 * @access private
+	 * @return bool true if there is further data, false if not
+	 */
+	function has_data()
+	{
+		return (bool) ($this->position < $this->data_length);
+	}
+
+	/**
+	 * Advance past any whitespace
+	 *
+	 * @return int Number of whitespace characters passed
+	 */
+	function skip_whitespace()
+	{
+		$whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position);
+		$this->position += $whitespace;
+		return $whitespace;
+	}
+
+	/**
+	 * Read value
+	 */
+	function get_value()
+	{
+		$quote = substr($this->data, $this->position, 1);
+		if ($quote === '"' || $quote === "'")
+		{
+			$this->position++;
+			$len = strcspn($this->data, $quote, $this->position);
+			if ($this->has_data())
+			{
+				$value = substr($this->data, $this->position, $len);
+				$this->position += $len + 1;
+				return $value;
+			}
+		}
+		return false;
+	}
+
+	function before_version_name()
+	{
+		if ($this->skip_whitespace())
+		{
+			$this->state = 'version_name';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	function version_name()
+	{
+		if (substr($this->data, $this->position, 7) === 'version')
+		{
+			$this->position += 7;
+			$this->skip_whitespace();
+			$this->state = 'version_equals';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	function version_equals()
+	{
+		if (substr($this->data, $this->position, 1) === '=')
+		{
+			$this->position++;
+			$this->skip_whitespace();
+			$this->state = 'version_value';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	function version_value()
+	{
+		if ($this->version = $this->get_value())
+		{
+			$this->skip_whitespace();
+			if ($this->has_data())
+			{
+				$this->state = 'encoding_name';
+			}
+			else
+			{
+				$this->state = 'emit';
+			}
+		}
+		else
+		{
+			$this->state = 'standalone_name';
+		}
+	}
+
+	function encoding_name()
+	{
+		if (substr($this->data, $this->position, 8) === 'encoding')
+		{
+			$this->position += 8;
+			$this->skip_whitespace();
+			$this->state = 'encoding_equals';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	function encoding_equals()
+	{
+		if (substr($this->data, $this->position, 1) === '=')
+		{
+			$this->position++;
+			$this->skip_whitespace();
+			$this->state = 'encoding_value';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	function encoding_value()
+	{
+		if ($this->encoding = $this->get_value())
+		{
+			$this->skip_whitespace();
+			if ($this->has_data())
+			{
+				$this->state = 'standalone_name';
+			}
+			else
+			{
+				$this->state = 'emit';
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	function standalone_name()
+	{
+		if (substr($this->data, $this->position, 10) === 'standalone')
+		{
+			$this->position += 10;
+			$this->skip_whitespace();
+			$this->state = 'standalone_equals';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	function standalone_equals()
+	{
+		if (substr($this->data, $this->position, 1) === '=')
+		{
+			$this->position++;
+			$this->skip_whitespace();
+			$this->state = 'standalone_value';
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+
+	function standalone_value()
+	{
+		if ($standalone = $this->get_value())
+		{
+			switch ($standalone)
+			{
+				case 'yes':
+					$this->standalone = true;
+					break;
+
+				case 'no':
+					$this->standalone = false;
+					break;
+
+				default:
+					$this->state = false;
+					return;
+			}
+
+			$this->skip_whitespace();
+			if ($this->has_data())
+			{
+				$this->state = false;
+			}
+			else
+			{
+				$this->state = 'emit';
+			}
+		}
+		else
+		{
+			$this->state = false;
+		}
+	}
+}
+
+class SimplePie_Locator
+{
+	var $useragent;
+	var $timeout;
+	var $file;
+	var $local = array();
+	var $elsewhere = array();
+	var $file_class = 'SimplePie_File';
+	var $cached_entities = array();
+	var $http_base;
+	var $base;
+	var $base_location = 0;
+	var $checked_feeds = 0;
+	var $max_checked_feeds = 10;
+	var $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer';
+
+	function SimplePie_Locator(&$file, $timeout = 10, $useragent = null, $file_class = 'SimplePie_File', $max_checked_feeds = 10, $content_type_sniffer_class = 'SimplePie_Content_Type_Sniffer')
+	{
+		$this->file =& $file;
+		$this->file_class = $file_class;
+		$this->useragent = $useragent;
+		$this->timeout = $timeout;
+		$this->max_checked_feeds = $max_checked_feeds;
+		$this->content_type_sniffer_class = $content_type_sniffer_class;
+	}
+
+	function find($type = SIMPLEPIE_LOCATOR_ALL, &$working)
+	{
+		if ($this->is_feed($this->file))
+		{
+			return $this->file;
+		}
+
+		if ($this->file->method & SIMPLEPIE_FILE_SOURCE_REMOTE)
+		{
+			$sniffer = new $this->content_type_sniffer_class($this->file);
+			if ($sniffer->get_type() !== 'text/html')
+			{
+				return null;
+			}
+		}
+
+		if ($type & ~SIMPLEPIE_LOCATOR_NONE)
+		{
+			$this->get_base();
+		}
+
+		if ($type & SIMPLEPIE_LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery())
+		{
+			return $working[0];
+		}
+
+		if ($type & (SIMPLEPIE_LOCATOR_LOCAL_EXTENSION | SIMPLEPIE_LOCATOR_LOCAL_BODY | SIMPLEPIE_LOCATOR_REMOTE_EXTENSION | SIMPLEPIE_LOCATOR_REMOTE_BODY) && $this->get_links())
+		{
+			if ($type & SIMPLEPIE_LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local))
+			{
+				return $working;
+			}
+
+			if ($type & SIMPLEPIE_LOCATOR_LOCAL_BODY && $working = $this->body($this->local))
+			{
+				return $working;
+			}
+
+			if ($type & SIMPLEPIE_LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere))
+			{
+				return $working;
+			}
+
+			if ($type & SIMPLEPIE_LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere))
+			{
+				return $working;
+			}
+		}
+		return null;
+	}
+
+	function is_feed(&$file)
+	{
+		if ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE)
+		{
+			$sniffer = new $this->content_type_sniffer_class($file);
+			$sniffed = $sniffer->get_type();
+			if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml')))
+			{
+				return true;
+			}
+			else
+			{
+				return false;
+			}
+		}
+		elseif ($file->method & SIMPLEPIE_FILE_SOURCE_LOCAL)
+		{
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	function get_base()
+	{
+		$this->http_base = $this->file->url;
+		$this->base = $this->http_base;
+		$elements = SimplePie_Misc::get_element('base', $this->file->body);
+		foreach ($elements as $element)
+		{
+			if ($element['attribs']['href']['data'] !== '')
+			{
+				$this->base = SimplePie_Misc::absolutize_url(trim($element['attribs']['href']['data']), $this->http_base);
+				$this->base_location = $element['offset'];
+				break;
+			}
+		}
+	}
+
+	function autodiscovery()
+	{
+		$links = array_merge(SimplePie_Misc::get_element('link', $this->file->body), SimplePie_Misc::get_element('a', $this->file->body), SimplePie_Misc::get_element('area', $this->file->body));
+		$done = array();
+		$feeds = array();
+		foreach ($links as $link)
+		{
+			if ($this->checked_feeds === $this->max_checked_feeds)
+			{
+				break;
+			}
+			if (isset($link['attribs']['href']['data']) && isset($link['attribs']['rel']['data']))
+			{
+				$rel = array_unique(SimplePie_Misc::space_seperated_tokens(strtolower($link['attribs']['rel']['data'])));
+
+				if ($this->base_location < $link['offset'])
+				{
+					$href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->base);
+				}
+				else
+				{
+					$href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->http_base);
+				}
+
+				if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !empty($link['attribs']['type']['data']) && in_array(strtolower(SimplePie_Misc::parse_mime($link['attribs']['type']['data'])), array('application/rss+xml', 'application/atom+xml'))) && !isset($feeds[$href]))
+				{
+					$this->checked_feeds++;
+					$feed = new $this->file_class($href, $this->timeout, 5, null, $this->useragent);
+					if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
+					{
+						$feeds[$href] = $feed;
+					}
+				}
+				$done[] = $href;
+			}
+		}
+
+		if (!empty($feeds))
+		{
+			return array_values($feeds);
+		}
+		else {
+			return null;
+		}
+	}
+
+	function get_links()
+	{
+		$links = SimplePie_Misc::get_element('a', $this->file->body);
+		foreach ($links as $link)
+		{
+			if (isset($link['attribs']['href']['data']))
+			{
+				$href = trim($link['attribs']['href']['data']);
+				$parsed = SimplePie_Misc::parse_url($href);
+				if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme']))
+				{
+					if ($this->base_location < $link['offset'])
+					{
+						$href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->base);
+					}
+					else
+					{
+						$href = SimplePie_Misc::absolutize_url(trim($link['attribs']['href']['data']), $this->http_base);
+					}
+
+					$current = SimplePie_Misc::parse_url($this->file->url);
+
+					if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority'])
+					{
+						$this->local[] = $href;
+					}
+					else
+					{
+						$this->elsewhere[] = $href;
+					}
+				}
+			}
+		}
+		$this->local = array_unique($this->local);
+		$this->elsewhere = array_unique($this->elsewhere);
+		if (!empty($this->local) || !empty($this->elsewhere))
+		{
+			return true;
+		}
+		return null;
+	}
+
+	function extension(&$array)
+	{
+		foreach ($array as $key => $value)
+		{
+			if ($this->checked_feeds === $this->max_checked_feeds)
+			{
+				break;
+			}
+			if (in_array(strtolower(strrchr($value, '.')), array('.rss', '.rdf', '.atom', '.xml')))
+			{
+				$this->checked_feeds++;
+				$feed = new $this->file_class($value, $this->timeout, 5, null, $this->useragent);
+				if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
+				{
+					return $feed;
+				}
+				else
+				{
+					unset($array[$key]);
+				}
+			}
+		}
+		return null;
+	}
+
+	function body(&$array)
+	{
+		foreach ($array as $key => $value)
+		{
+			if ($this->checked_feeds === $this->max_checked_feeds)
+			{
+				break;
+			}
+			if (preg_match('/(rss|rdf|atom|xml)/i', $value))
+			{
+				$this->checked_feeds++;
+				$feed = new $this->file_class($value, $this->timeout, 5, null, $this->useragent);
+				if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
+				{
+					return $feed;
+				}
+				else
+				{
+					unset($array[$key]);
+				}
+			}
+		}
+		return null;
+	}
+}
+
+class SimplePie_Parser
+{
+	var $error_code;
+	var $error_string;
+	var $current_line;
+	var $current_column;
+	var $current_byte;
+	var $separator = ' ';
+	var $namespace = array('');
+	var $element = array('');
+	var $xml_base = array('');
+	var $xml_base_explicit = array(false);
+	var $xml_lang = array('');
+	var $data = array();
+	var $datas = array(array());
+	var $current_xhtml_construct = -1;
+	var $encoding;
+
+	function parse(&$data, $encoding)
+	{
+		// Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character
+		if (strtoupper($encoding) === 'US-ASCII')
+		{
+			$this->encoding = 'UTF-8';
+		}
+		else
+		{
+			$this->encoding = $encoding;
+		}
+
+		// Strip BOM:
+		// UTF-32 Big Endian BOM
+		if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
+		{
+			$data = substr($data, 4);
+		}
+		// UTF-32 Little Endian BOM
+		elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
+		{
+			$data = substr($data, 4);
+		}
+		// UTF-16 Big Endian BOM
+		elseif (substr($data, 0, 2) === "\xFE\xFF")
+		{
+			$data = substr($data, 2);
+		}
+		// UTF-16 Little Endian BOM
+		elseif (substr($data, 0, 2) === "\xFF\xFE")
+		{
+			$data = substr($data, 2);
+		}
+		// UTF-8 BOM
+		elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
+		{
+			$data = substr($data, 3);
+		}
+
+		if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false)
+		{
+			$declaration = new SimplePie_XML_Declaration_Parser(substr($data, 5, $pos - 5));
+			if ($declaration->parse())
+			{
+				$data = substr($data, $pos + 2);
+				$data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data;
+			}
+			else
+			{
+				$this->error_string = 'SimplePie bug! Please report this!';
+				return false;
+			}
+		}
+
+		$return = true;
+
+		static $xml_is_sane = null;
+		if ($xml_is_sane === null)
+		{
+			$parser_check = xml_parser_create();
+			xml_parse_into_struct($parser_check, '<foo>&</foo>', $values);
+			xml_parser_free($parser_check);
+			$xml_is_sane = isset($values[0]['value']);
+		}
+
+		// Create the parser
+		if ($xml_is_sane)
+		{
+			$xml = xml_parser_create_ns($this->encoding, $this->separator);
+			xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1);
+			xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0);
+			xml_set_object($xml, $this);
+			xml_set_character_data_handler($xml, 'cdata');
+			xml_set_element_handler($xml, 'tag_open', 'tag_close');
+
+			// Parse!
+			if (!xml_parse($xml, $data, true))
+			{
+				$this->error_code = xml_get_error_code($xml);
+				$this->error_string = xml_error_string($this->error_code);
+				$return = false;
+			}
+			$this->current_line = xml_get_current_line_number($xml);
+			$this->current_column = xml_get_current_column_number($xml);
+			$this->current_byte = xml_get_current_byte_index($xml);
+			xml_parser_free($xml);
+			return $return;
+		}
+		else
+		{
+			libxml_clear_errors();
+			$xml = new XMLReader();
+			$xml->xml($data);
+			while (@$xml->read())
+			{
+				switch ($xml->nodeType)
+				{
+
+					case constant('XMLReader::END_ELEMENT'):
+						if ($xml->namespaceURI !== '')
+						{
+							$tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
+						}
+						else
+						{
+							$tagName = $xml->localName;
+						}
+						$this->tag_close(null, $tagName);
+						break;
+					case constant('XMLReader::ELEMENT'):
+						$empty = $xml->isEmptyElement;
+						if ($xml->namespaceURI !== '')
+						{
+							$tagName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
+						}
+						else
+						{
+							$tagName = $xml->localName;
+						}
+						$attributes = array();
+						while ($xml->moveToNextAttribute())
+						{
+							if ($xml->namespaceURI !== '')
+							{
+								$attrName = "{$xml->namespaceURI}{$this->separator}{$xml->localName}";
+							}
+							else
+							{
+								$attrName = $xml->localName;
+							}
+							$attributes[$attrName] = $xml->value;
+						}
+						$this->tag_open(null, $tagName, $attributes);
+						if ($empty)
+						{
+							$this->tag_close(null, $tagName);
+						}
+						break;
+					case constant('XMLReader::TEXT'):
+
+					case constant('XMLReader::CDATA'):
+						$this->cdata(null, $xml->value);
+						break;
+				}
+			}
+			if ($error = libxml_get_last_error())
+			{
+				$this->error_code = $error->code;
+				$this->error_string = $error->message;
+				$this->current_line = $error->line;
+				$this->current_column = $error->column;
+				return false;
+			}
+			else
+			{
+				return true;
+			}
+		}
+	}
+
+	function get_error_code()
+	{
+		return $this->error_code;
+	}
+
+	function get_error_string()
+	{
+		return $this->error_string;
+	}
+
+	function get_current_line()
+	{
+		return $this->current_line;
+	}
+
+	function get_current_column()
+	{
+		return $this->current_column;
+	}
+
+	function get_current_byte()
+	{
+		return $this->current_byte;
+	}
+
+	function get_data()
+	{
+		return $this->data;
+	}
+
+	function tag_open($parser, $tag, $attributes)
+	{
+		list($this->namespace[], $this->element[]) = $this->split_ns($tag);
+
+		$attribs = array();
+		foreach ($attributes as $name => $value)
+		{
+			list($attrib_namespace, $attribute) = $this->split_ns($name);
+			$attribs[$attrib_namespace][$attribute] = $value;
+		}
+
+		if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['base']))
+		{
+			$this->xml_base[] = SimplePie_Misc::absolutize_url($attribs[SIMPLEPIE_NAMESPACE_XML]['base'], end($this->xml_base));
+			$this->xml_base_explicit[] = true;
+		}
+		else
+		{
+			$this->xml_base[] = end($this->xml_base);
+			$this->xml_base_explicit[] = end($this->xml_base_explicit);
+		}
+
+		if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['lang']))
+		{
+			$this->xml_lang[] = $attribs[SIMPLEPIE_NAMESPACE_XML]['lang'];
+		}
+		else
+		{
+			$this->xml_lang[] = end($this->xml_lang);
+		}
+
+		if ($this->current_xhtml_construct >= 0)
+		{
+			$this->current_xhtml_construct++;
+			if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML)
+			{
+				$this->data['data'] .= '<' . end($this->element);
+				if (isset($attribs['']))
+				{
+					foreach ($attribs[''] as $name => $value)
+					{
+						$this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"';
+					}
+				}
+				$this->data['data'] .= '>';
+			}
+		}
+		else
+		{
+			$this->datas[] =& $this->data;
+			$this->data =& $this->data['child'][end($this->namespace)][end($this->element)][];
+			$this->data = array('data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang));
+			if ((end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_03 && in_array(end($this->element), array('title', 'tagline', 'copyright', 'info', 'summary', 'content')) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml')
+			|| (end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_10 && in_array(end($this->element), array('rights', 'subtitle', 'summary', 'info', 'title', 'content')) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml'))
+			{
+				$this->current_xhtml_construct = 0;
+			}
+		}
+	}
+
+	function cdata($parser, $cdata)
+	{
+		if ($this->current_xhtml_construct >= 0)
+		{
+			$this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding);
+		}
+		else
+		{
+			$this->data['data'] .= $cdata;
+		}
+	}
+
+	function tag_close($parser, $tag)
+	{
+		if ($this->current_xhtml_construct >= 0)
+		{
+			$this->current_xhtml_construct--;
+			if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML && !in_array(end($this->element), array('area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param')))
+			{
+				$this->data['data'] .= '</' . end($this->element) . '>';
+			}
+		}
+		if ($this->current_xhtml_construct === -1)
+		{
+			$this->data =& $this->datas[count($this->datas) - 1];
+			array_pop($this->datas);
+		}
+
+		array_pop($this->element);
+		array_pop($this->namespace);
+		array_pop($this->xml_base);
+		array_pop($this->xml_base_explicit);
+		array_pop($this->xml_lang);
+	}
+
+	function split_ns($string)
+	{
+		static $cache = array();
+		if (!isset($cache[$string]))
+		{
+			if ($pos = strpos($string, $this->separator))
+			{
+				static $separator_length;
+				if (!$separator_length)
+				{
+					$separator_length = strlen($this->separator);
+				}
+				$namespace = substr($string, 0, $pos);
+				$local_name = substr($string, $pos + $separator_length);
+				if (strtolower($namespace) === SIMPLEPIE_NAMESPACE_ITUNES)
+				{
+					$namespace = SIMPLEPIE_NAMESPACE_ITUNES;
+				}
+
+				// Normalize the Media RSS namespaces
+				if ($namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG)
+				{
+					$namespace = SIMPLEPIE_NAMESPACE_MEDIARSS;
+				}
+				$cache[$string] = array($namespace, $local_name);
+			}
+			else
+			{
+				$cache[$string] = array('', $string);
+			}
+		}
+		return $cache[$string];
+	}
+}
+
+/**
+ * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags
+ */
+class SimplePie_Sanitize
+{
+	// Private vars
+	var $base;
+
+	// Options
+	var $remove_div = true;
+	var $image_handler = '';
+	var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style');
+	var $encode_instead_of_strip = false;
+	var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');
+	var $strip_comments = false;
+	var $output_encoding = 'UTF-8';
+	var $enable_cache = true;
+	var $cache_location = './cache';
+	var $cache_name_function = 'md5';
+	var $cache_class = 'SimplePie_Cache';
+	var $file_class = 'SimplePie_File';
+	var $timeout = 10;
+	var $useragent = '';
+	var $force_fsockopen = false;
+
+	var $replace_url_attributes = array(
+		'a' => 'href',
+		'area' => 'href',
+		'blockquote' => 'cite',
+		'del' => 'cite',
+		'form' => 'action',
+		'img' => array('longdesc', 'src'),
+		'input' => 'src',
+		'ins' => 'cite',
+		'q' => 'cite'
+	);
+
+	function remove_div($enable = true)
+	{
+		$this->remove_div = (bool) $enable;
+	}
+
+	function set_image_handler($page = false)
+	{
+		if ($page)
+		{
+			$this->image_handler = (string) $page;
+		}
+		else
+		{
+			$this->image_handler = false;
+		}
+	}
+
+	function pass_cache_data($enable_cache = true, $cache_location = './cache', $cache_name_function = 'md5', $cache_class = 'SimplePie_Cache')
+	{
+		if (isset($enable_cache))
+		{
+			$this->enable_cache = (bool) $enable_cache;
+		}
+
+		if ($cache_location)
+		{
+			$this->cache_location = (string) $cache_location;
+		}
+
+		if ($cache_name_function)
+		{
+			$this->cache_name_function = (string) $cache_name_function;
+		}
+
+		if ($cache_class)
+		{
+			$this->cache_class = (string) $cache_class;
+		}
+	}
+
+	function pass_file_data($file_class = 'SimplePie_File', $timeout = 10, $useragent = '', $force_fsockopen = false)
+	{
+		if ($file_class)
+		{
+			$this->file_class = (string) $file_class;
+		}
+
+		if ($timeout)
+		{
+			$this->timeout = (string) $timeout;
+		}
+
+		if ($useragent)
+		{
+			$this->useragent = (string) $useragent;
+		}
+
+		if ($force_fsockopen)
+		{
+			$this->force_fsockopen = (string) $force_fsockopen;
+		}
+	}
+
+	function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'))
+	{
+		if ($tags)
+		{
+			if (is_array($tags))
+			{
+				$this->strip_htmltags = $tags;
+			}
+			else
+			{
+				$this->strip_htmltags = explode(',', $tags);
+			}
+		}
+		else
+		{
+			$this->strip_htmltags = false;
+		}
+	}
+
+	function encode_instead_of_strip($encode = false)
+	{
+		$this->encode_instead_of_strip = (bool) $encode;
+	}
+
+	function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'))
+	{
+		if ($attribs)
+		{
+			if (is_array($attribs))
+			{
+				$this->strip_attributes = $attribs;
+			}
+			else
+			{
+				$this->strip_attributes = explode(',', $attribs);
+			}
+		}
+		else
+		{
+			$this->strip_attributes = false;
+		}
+	}
+
+	function strip_comments($strip = false)
+	{
+		$this->strip_comments = (bool) $strip;
+	}
+
+	function set_output_encoding($encoding = 'UTF-8')
+	{
+		$this->output_encoding = (string) $encoding;
+	}
+
+	/**
+	 * Set element/attribute key/value pairs of HTML attributes
+	 * containing URLs that need to be resolved relative to the feed
+	 *
+	 * @access public
+	 * @since 1.0
+	 * @param array $element_attribute Element/attribute key/value pairs
+	 */
+	function set_url_replacements($element_attribute = array('a' => 'href', 'area' => 'href', 'blockquote' => 'cite', 'del' => 'cite', 'form' => 'action', 'img' => array('longdesc', 'src'), 'input' => 'src', 'ins' => 'cite', 'q' => 'cite'))
+	{
+		$this->replace_url_attributes = (array) $element_attribute;
+	}
+
+	function sanitize($data, $type, $base = '')
+	{
+		$data = trim($data);
+		if ($data !== '' || $type & SIMPLEPIE_CONSTRUCT_IRI)
+		{
+			if ($type & SIMPLEPIE_CONSTRUCT_MAYBE_HTML)
+			{
+				if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>)/', $data))
+				{
+					$type |= SIMPLEPIE_CONSTRUCT_HTML;
+				}
+				else
+				{
+					$type |= SIMPLEPIE_CONSTRUCT_TEXT;
+				}
+			}
+
+			if ($type & SIMPLEPIE_CONSTRUCT_BASE64)
+			{
+				$data = base64_decode($data);
+			}
+
+			if ($type & SIMPLEPIE_CONSTRUCT_XHTML)
+			{
+				if ($this->remove_div)
+				{
+					$data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '', $data);
+					$data = preg_replace('/<\/div>$/', '', $data);
+				}
+				else
+				{
+					$data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '<div>', $data);
+				}
+			}
+
+			if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML))
+			{
+				// Strip comments
+				if ($this->strip_comments)
+				{
+					$data = SimplePie_Misc::strip_comments($data);
+				}
+
+				// Strip out HTML tags and attributes that might cause various security problems.
+				// Based on recommendations by Mark Pilgrim at:
+				// http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely
+				if ($this->strip_htmltags)
+				{
+					foreach ($this->strip_htmltags as $tag)
+					{
+						$pcre = "/<($tag)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$tag" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>|(\/)?>)/siU';
+						while (preg_match($pcre, $data))
+						{
+							$data = preg_replace_callback($pcre, array(&$this, 'do_strip_htmltags'), $data);
+						}
+					}
+				}
+
+				if ($this->strip_attributes)
+				{
+					foreach ($this->strip_attributes as $attrib)
+					{
+						$data = preg_replace('/(<[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*)' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . trim($attrib) . '(?:\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>/', '\1\2\3>', $data);
+					}
+				}
+
+				// Replace relative URLs
+				$this->base = $base;
+				foreach ($this->replace_url_attributes as $element => $attributes)
+				{
+					$data = $this->replace_urls($data, $element, $attributes);
+				}
+
+				// If image handling (caching, etc.) is enabled, cache and rewrite all the image tags.
+				if (isset($this->image_handler) && ((string) $this->image_handler) !== '' && $this->enable_cache)
+				{
+					$images = SimplePie_Misc::get_element('img', $data);
+					foreach ($images as $img)
+					{
+						if (isset($img['attribs']['src']['data']))
+						{
+							$image_url = call_user_func($this->cache_name_function, $img['attribs']['src']['data']);
+							$cache = call_user_func(array($this->cache_class, 'create'), $this->cache_location, $image_url, 'spi');
+
+							if ($cache->load())
+							{
+								$img['attribs']['src']['data'] = $this->image_handler . $image_url;
+								$data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data);
+							}
+							else
+							{
+								$file = new $this->file_class($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen);
+								$headers = $file->headers;
+
+								if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)))
+								{
+									if ($cache->save(array('headers' => $file->headers, 'body' => $file->body)))
+									{
+										$img['attribs']['src']['data'] = $this->image_handler . $image_url;
+										$data = str_replace($img['full'], SimplePie_Misc::element_implode($img), $data);
+									}
+									else
+									{
+										trigger_error("$this->cache_location is not writeable", E_USER_WARNING);
+									}
+								}
+							}
+						}
+					}
+				}
+
+				// Having (possibly) taken stuff out, there may now be whitespace at the beginning/end of the data
+				$data = trim($data);
+			}
+
+			if ($type & SIMPLEPIE_CONSTRUCT_IRI)
+			{
+				$data = SimplePie_Misc::absolutize_url($data, $base);
+			}
+
+			if ($type & (SIMPLEPIE_CONSTRUCT_TEXT | SIMPLEPIE_CONSTRUCT_IRI))
+			{
+				$data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8');
+			}
+
+			if ($this->output_encoding !== 'UTF-8')
+			{
+				$data = SimplePie_Misc::change_encoding($data, 'UTF-8', $this->output_encoding);
+			}
+		}
+		return $data;
+	}
+
+	function replace_urls($data, $tag, $attributes)
+	{
+		if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags))
+		{
+			$elements = SimplePie_Misc::get_element($tag, $data);
+			foreach ($elements as $element)
+			{
+				if (is_array($attributes))
+				{
+					foreach ($attributes as $attribute)
+					{
+						if (isset($element['attribs'][$attribute]['data']))
+						{
+							$element['attribs'][$attribute]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attribute]['data'], $this->base);
+							$new_element = SimplePie_Misc::element_implode($element);
+							$data = str_replace($element['full'], $new_element, $data);
+							$element['full'] = $new_element;
+						}
+					}
+				}
+				elseif (isset($element['attribs'][$attributes]['data']))
+				{
+					$element['attribs'][$attributes]['data'] = SimplePie_Misc::absolutize_url($element['attribs'][$attributes]['data'], $this->base);
+					$data = str_replace($element['full'], SimplePie_Misc::element_implode($element), $data);
+				}
+			}
+		}
+		return $data;
+	}
+
+	function do_strip_htmltags($match)
+	{
+		if ($this->encode_instead_of_strip)
+		{
+			if (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style')))
+			{
+				$match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8');
+				$match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8');
+				return "<$match[1]$match[2]>$match[3]</$match[1]>";
+			}
+			else
+			{
+				return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8');
+			}
+		}
+		elseif (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style')))
+		{
+			return $match[4];
+		}
+		else
+		{
+			return '';
+		}
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/application/libraries/Sms_Provider.php b/application/libraries/Sms_Provider.php
new file mode 100644
index 0000000..7954111
--- /dev/null
+++ b/application/libraries/Sms_Provider.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Interface that must be implemented by SMS libraries
+ *
+ * @package     Ushahidi
+ * @category    Libraries
+ * @author      Ushahidi Team
+ * @copyright   (c) 2008-2011 Ushahidi Team 
+ * @license     http://www.gnu.org/copyleft/lesser.html GNU Less Public General License (LGPL)
+ */
+interface Sms_Provider_Core {
+
+	/**
+	 * Sends an SMS - All sub-classes must implement this method
+	 *
+	 * @param string $to MSISDN of the recipent
+	 * @param string $from MSISDN of the sender
+	 * @param string $message Message to be transmitted to the recipient
+	 */
+	public function send($to = NULL, $from = NULL, $message = NULL);
+
+}
+?>
\ No newline at end of file
diff --git a/application/libraries/Themes.php b/application/libraries/Themes.php
new file mode 100644
index 0000000..901872a
--- /dev/null
+++ b/application/libraries/Themes.php
@@ -0,0 +1,733 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Themes Library
+ * These are regularly used templating functions
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module	   Themes Library
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Themes_Core {
+
+	public $frontend = false;
+	public $admin = false;
+	
+	public $map_enabled = false;
+	public $api_url = null;
+	public $this_page = false;
+	public $treeview_enabled = false;
+	public $validator_enabled = false;
+	public $photoslider_enabled = false;
+	public $colorpicker_enabled = false;
+	public $datepicker_enabled = false;
+	public $editor_enabled = false;
+	public $protochart_enabled = false;
+	public $raphael_enabled = false;
+	public $tablerowsort_enabled = false;
+	public $json2_enabled = false;
+	public $hovertip_enabled = false;
+	public $slider_enabled = false;
+	public $timeline_enabled = false;
+	
+	// Custom JS to be added
+	public $js = null;
+
+	public $css_url = null;
+	public $js_url = null;
+
+	public function __construct()
+	{
+		// Load cache
+		$this->cache = new Cache;
+
+		// Load Session
+		$this->session = Session::instance();
+	}
+
+	/**
+	 * Header Block Contains CSS, JS and Feeds
+	 * Css is loaded before JS
+	 */
+	public function header_block()
+	{
+		$content = '';
+		// For backward compatibility render Requirements here rather than in the view
+		if (Kohana::config('requirements.write_js_to_body'))
+		{
+			$content .= Requirements::render('css');
+			$content .= Requirements::render('headtags');
+		}
+		else
+		{
+			$content .= Requirements::render();
+		}
+		
+		// Filter::header_block - Modify Header Block
+		if ($this->admin)
+		{
+			Event::run('ushahidi_filter.admin_header_block', $content);
+		}
+		elseif ($this->frontend)
+		{
+		Event::run('ushahidi_filter.header_block', $content);
+		}
+
+		return $content;
+	}
+
+	/**
+	* Admin Header Block
+	*   The admin header has different requirements so it has a special function
+	*/
+	public function admin_header_block()
+	{
+		$this->header_block();
+	}
+
+	/**
+	 * CSS/JS requirements
+	 */
+	public function requirements()
+	{
+		Requirements::customHeadTags(Kohana::config("globalcode.head"),'globalcode-head');
+		
+		Requirements::js("media/js/jquery.js");
+		Requirements::js("media/js/jquery.ui.min.js");
+		//Requirements::js(Kohana::config('core.site_protocol')."://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/jquery-ui.min.js");
+		Requirements::js("media/js/jquery.pngFix.pack.js");
+		Requirements::js("media/js/jquery.timeago.js");
+		
+		Requirements::css("media/css/jquery-ui-themeroller.css");
+		
+		Requirements::js('media/js/global.js');
+		Requirements::css('media/css/global.css');
+
+		if ($this->map_enabled)
+		{
+			Requirements::js("media/js/OpenLayers.js");
+			Requirements::js("media/js/ushahidi.js");
+			if ($this->api_url)
+			{
+				Requirements::js($this->api_url);
+			}
+			Requirements::customJS("OpenLayers.ImgPath = '".url::file_loc('js')."media/img/openlayers/"."';",'openlayers-imgpath');
+			
+			Requirements::css("media/css/openlayers.css");
+		}
+		
+		if ($this->hovertip_enabled)
+		{
+			Requirements::js('media/js/jquery.hovertip-1.0.js');
+			Requirements::css('media/css/jquery.hovertip-1.0.css', '');
+			Requirements::customJS(
+				"$(function() {
+						if($('.tooltip[title]') != null)
+						$('.tooltip[title]').hovertip();
+					});",
+				'tooltip-js'
+			);
+		}
+		
+		if ($this->slider_enabled)
+		{
+			Requirements::js('media/js/selectToUISlider.jQuery.js');
+		}
+
+		if ($this->timeline_enabled)
+		{
+			Requirements::js("media/js/jquery.jqplot.min.js");
+			Requirements::css("media/css/jquery.jqplot.min.css");
+			Requirements::js("media/js/jqplot.dateAxisRenderer.min.js");
+			Requirements::js("media/js/jqplot.barRenderer.min.js"); // HT: added for bar graph
+			Requirements::js("media/js/jqplot.pointLabels.min.js"); // HT: added for showing point label
+		}
+
+		if ($this->treeview_enabled)
+		{
+			Requirements::css("media/css/jquery.treeview.css");
+			Requirements::js("media/js/jquery.treeview.js");
+		}
+	
+		// Load ProtoChart
+		if ($this->protochart_enabled)
+		{
+			Requirements::customJS("jQuery.noConflict()", 'jquery-noconflict');
+			Requirements::js('media/js/protochart/prototype.js');
+			Requirements::customHeadTags(
+				'<!--[if IE]>'.html::script(url::file_loc('js').'media/js/protochart/excanvas-compressed', TRUE).'<![endif]-->', 
+				'ie-excanvas-compressed');
+			Requirements::js('media/js/protochart/ProtoChart.js');
+		}
+	
+		// Load Raphael
+		if ($this->raphael_enabled)
+		{
+			// The only reason we include prototype is to keep the div element naming convention consistent
+			//Requirements::js('media/js/protochart/prototype.js');
+			Requirements::js('media/js/raphael.js');
+			Requirements::customJS('var impact_json = '.$this->impact_json .';','impact_json');
+			Requirements::js('media/js/raphael-ushahidi-impact.js');
+		}
+
+		if ($this->validator_enabled)
+		{
+			Requirements::js("media/js/jquery.validate.min.js");
+		}
+
+		if ($this->photoslider_enabled)
+		{
+			Requirements::css("media/css/picbox/picbox.css");
+			Requirements::js("media/js/picbox.js");
+		}
+
+		if ($this->colorpicker_enabled)
+		{
+			Requirements::css("media/css/colorpicker.css");
+			Requirements::js("media/js/colorpicker.js");
+		}
+
+		// Load jwysiwyg
+		if ($this->editor_enabled)
+		{
+			Requirements::css('media/js/jwysiwyg/jquery.wysiwyg.css');
+			Requirements::css('media/js/jwysiwyg/plugins/fileManager/wysiwyg.fileManager.css');
+			if (Kohana::config("cdn.cdn_ignore_jwysiwyg") == TRUE)
+			{
+				Requirements::js(url::file_loc('ignore').'media/js/jwysiwyg/jquery.wysiwyg.js'); // not sure what the hell to do about this
+				Requirements::js(url::file_loc('ignore').'media/js/jwysiwyg/controls/wysiwyg.link.js');
+				Requirements::js(url::file_loc('ignore').'media/js/jwysiwyg/controls/wysiwyg.image.js');
+				Requirements::js(url::file_loc('ignore').'media/js/jwysiwyg/controls/wysiwyg.table.js');
+				Requirements::js(url::file_loc('ignore').'media/js/jwysiwyg/plugins/wysiwyg.fullscreen.js');
+				Requirements::js(url::file_loc('ignore').'media/js/jwysiwyg/plugins/wysiwyg.rmFormat.js');
+				Requirements::js(url::file_loc('ignore').'media/js/jwysiwyg/plugins/wysiwyg.fileManager.js');
+			}
+			else
+			{
+				Requirements::js('media/js/jwysiwyg/jquery.wysiwyg.js');
+				Requirements::js('media/js/jwysiwyg/controls/wysiwyg.link.js');
+				Requirements::js('media/js/jwysiwyg/controls/wysiwyg.image.js');
+				Requirements::js('media/js/jwysiwyg/controls/wysiwyg.table.js');
+				Requirements::js('media/js/jwysiwyg/plugins/wysiwyg.fullscreen.js');
+				Requirements::js('media/js/jwysiwyg/plugins/wysiwyg.rmFormat.js');
+				Requirements::js('media/js/jwysiwyg/plugins/wysiwyg.fileManager.js');
+			}
+		}
+	
+		// Table Row Sort
+		if ($this->tablerowsort_enabled)
+		{
+			Requirements::js('media/js/jquery.tablednd_0_5.js');
+		}
+	
+		// JSON2 for IE+
+		if ($this->json2_enabled)
+		{
+			Requirements::js('media/js/json2.js');
+		}
+		
+		if ($this->datepicker_enabled)
+		{
+			Requirements::customJS("
+				Date.dayNames = [
+				    '". Kohana::lang('datetime.sunday.full') ."',
+				    '". Kohana::lang('datetime.monday.full') ."',
+				    '". Kohana::lang('datetime.tuesday.full') ."',
+				    '". Kohana::lang('datetime.wednesday.full') ."',
+				    '". Kohana::lang('datetime.thursday.full') ."',
+				    '". Kohana::lang('datetime.friday.full') ."',
+				    '". Kohana::lang('datetime.saturday.full') ."'
+				];
+				Date.abbrDayNames = [
+				    '". Kohana::lang('datetime.sunday.abbv') ."',
+				    '". Kohana::lang('datetime.monday.abbv') ."',
+				    '". Kohana::lang('datetime.tuesday.abbv') ."',
+				    '". Kohana::lang('datetime.wednesday.abbv') ."',
+				    '". Kohana::lang('datetime.thursday.abbv') ."',
+				    '". Kohana::lang('datetime.friday.abbv') ."',
+				    '". Kohana::lang('datetime.saturday.abbv') ."'
+				];
+				Date.monthNames = [
+				    '". Kohana::lang('datetime.january.full') ."',
+				    '". Kohana::lang('datetime.february.full') ."',
+				    '". Kohana::lang('datetime.march.full') ."',
+				    '". Kohana::lang('datetime.april.full') ."',
+				    '". Kohana::lang('datetime.may.full') ."',
+				    '". Kohana::lang('datetime.june.full') ."',
+				    '". Kohana::lang('datetime.july.full') ."',
+				    '". Kohana::lang('datetime.august.full') ."',
+				    '". Kohana::lang('datetime.september.full') ."',
+				    '". Kohana::lang('datetime.october.full') ."',
+				    '". Kohana::lang('datetime.november.full') ."',
+				    '". Kohana::lang('datetime.december.full') ."'
+				];
+				Date.abbrMonthNames = [
+				    '". Kohana::lang('datetime.january.abbv') ."',
+				    '". Kohana::lang('datetime.february.abbv') ."',
+				    '". Kohana::lang('datetime.march.abbv') ."',
+				    '". Kohana::lang('datetime.april.abbv') ."',
+				    '". Kohana::lang('datetime.may.abbv') ."',
+				    '". Kohana::lang('datetime.june.abbv') ."',
+				    '". Kohana::lang('datetime.july.abbv') ."',
+				    '". Kohana::lang('datetime.august.abbv') ."',
+				    '". Kohana::lang('datetime.september.abbv') ."',
+				    '". Kohana::lang('datetime.october.abbv') ."',
+				    '". Kohana::lang('datetime.november.abbv') ."',
+				    '". Kohana::lang('datetime.december.abbv') ."'
+				];
+				Date.firstDayOfWeek = 1;
+				Date.format = 'mm/dd/yyyy';
+			",'locale-dates');
+	
+			Requirements::js('media/js/jquery.datePicker.js');
+			Requirements::customHeadTags(
+				'<!--[if IE]>'.html::script(url::file_loc('js').'media/js/jquery.bgiframe.min', TRUE).'<![endif]-->','jquery.bgiframe.min');
+		}
+		
+		// JS base combines
+		$base_js = array(
+			'media/js/jquery.js',
+			'media/js/jquery.ui.min.js',
+			'media/js/jquery.pngFix.pack.js',
+			'media/js/jquery.timeago.js',
+			'media/js/global.js',
+			//'media/js/OpenLayers.js',
+			//'media/js/ushahidi.js',
+			'media/js/jquery.treeview.js',
+			'media/js/selectToUISlider.jQuery.js',
+			'media/js/jquery.validate.min.js',
+			'media/js/colorpicker.js',
+		);
+		if ($this->timeline_enabled)
+		{
+			$base_js[] = 'media/js/jquery.jqplot.min.js';
+			$base_js[] = 'media/js/jqplot.dateAxisRenderer.min.js';
+			$base_js[] = 'media/js/jqplot.barRenderer.min.js';  // HT: added for bar graph
+			$base_js[] = 'media/js/jqplot.pointLabels.min.js';  // HT: added for showing point label
+		}
+		Requirements::combine_files('0_base.js', $base_js);
+		
+		// CSS base combines
+		$base_css = array(
+			'media/css/jquery-ui-themeroller.css',
+			'media/css/global.css',
+			'media/css/openlayers.css',
+			'media/css/jquery.treeview.css',
+			'media/css/colorpicker.css',
+		);
+		if ($this->timeline_enabled)
+		{
+			$base_css[] = 'media/css/jquery.jqplot.min.css';
+		}
+		Requirements::combine_files('0_base.css', $base_css);
+		
+		
+		Event::run('ushahidi_action.themes_add_requirements_pre_theme', $this);
+		
+		if ($this->admin)
+		{
+			$this->admin_requirements();
+		}
+		
+		if ($this->frontend)
+		{
+			$this->frontend_requirements();
+		}
+		
+		// Inline Javascript
+		if (!empty($this->js))
+		{
+			//@todo add deprecated warning
+			Requirements::customJS($this->js,'pagejs');
+		}
+		
+		Event::run('ushahidi_action.themes_add_requirements', $this);
+	}
+
+	public function admin_requirements()
+	{
+		Requirements::js('media/js/jquery.form.js');
+		Requirements::js('media/js/jquery.validate.min.js');
+		Requirements::js('media/js/jquery.base64.js');
+		Requirements::js('media/js/admin.js');
+		Requirements::css('media/css/admin.css');
+		Requirements::ieCSS("lt IE 7", 'media/css/ie6.css');
+		
+		// JS admin combies
+		Requirements::combine_files('1_admin.js', array(
+			'media/js/jquery.form.js',
+			'media/js/jquery.base64.js',
+			'media/js/admin.js',
+			'media/js/jquery.hovertip-1.0.js',
+		));
+		
+		// CSS admin combines
+		Requirements::combine_files('1_admin.css', array(
+			'media/css/jquery.hovertip-1.0.css',
+			'media/css/admin.css'
+		));
+	}
+	
+	public function frontend_requirements()
+	{
+		// Add RSS feed if enabled
+		if (Kohana::config("settings.allow_feed"))
+		{
+			Requirements::customHeadTags("<link rel=\"alternate\" type=\"application/rss+xml\" href=\"".url::site('feed')."\" title=\"RSS2\" />",'rss-feed');
+		}
+		
+		// Theme CSS
+		foreach(self::$theme_css as $css)
+		{
+			Requirements::css($css);
+		}
+		
+		Requirements::combine_files("theme_".Kohana::config("settings.site_style").".css", self::$theme_css);
+		
+		// Theme JS
+		foreach(self::$theme_js as $js)
+		{
+			Requirements::js($js);
+		}
+		
+		Requirements::combine_files("theme_".Kohana::config("settings.site_style").".js", self::$theme_js);
+		
+		Requirements::ieThemedCSS("lte IE 7", "iehacks.css");
+		Requirements::ieThemedCSS("IE 7", "ie7hacks.css");
+		Requirements::ieThemedCSS("IE 6", "ie6hacks.css");
+	}
+
+	/**
+	 *  Add plugin css and js
+	 */
+	public function plugin_requirements()
+	{
+		foreach (plugin::get_requirements('javascript') as $js)
+		{
+			Requirements::js($js);
+		}
+		foreach (plugin::get_requirements('stylesheet') as $css)
+		{
+			Requirements::css($css);
+		}
+	}
+
+	/**
+	 * Footer Block potentially holds tracking codes or other code that needs
+	 * to run in the footer
+	 */
+	public function footer_block()
+	{
+		$content = Kohana::config("globalcode.foot").
+				$this->google_analytics()."\n".
+				$this->ushahidi_stats_js()."\n".
+				$this->scheduler_js();
+		
+		if (Kohana::config('requirements.write_js_to_body'))
+		{
+			$content .= Requirements::render('js');
+		}
+
+		// Filter::footer_block - Modify Footer Block
+		Event::run('ushahidi_filter.footer_block', $content);
+
+		return $content;
+	}
+
+	public function languages()
+	{
+		// *** Locales/Languages ***
+		// First Get Available Locales
+
+		$locales = ush_locale::get_i18n();
+
+		$languages = "";
+		$languages .= "<div class=\"language-box\">";
+		$languages .= form::open(NULL, array('method' => 'get'));
+
+		/**
+		 * E.Kala - 05/01/2011
+		 *
+		 * Fix to ensure to ensure that a change in language loads the page with the same data
+		 *
+		 * Only fetch the $_GET data to prevent double submission of data already submitted via $_POST
+		 * and create hidden form fields for each variable so that these are submitted along with the selected language
+		 *
+		 * The assumption is that previously submitted data had already been sanitized!
+		 */
+		foreach ($_GET as $name => $value)
+		{
+			if (is_array($value)) continue;
+			$languages .= form::hidden($name, $value);
+		}
+
+		// Do a case insensitive sort of locales so it comes up in a rough alphabetical order
+
+		natcasesort($locales);
+
+		$languages .= form::dropdown('l', $locales, Kohana::config('locale.language'),
+			' onchange="this.form.submit()" ');
+		$languages .= form::close();
+		$languages .= "</div>";
+
+		return $languages;
+	}
+
+	public function search()
+	{
+		$search = "";
+		$search .= "<div class=\"search-form\">";
+		$search .= form::open("search", array('method' => 'get', 'id' => 'search'));
+		$search .= "<ul>";
+		$search .= "<li><input type=\"text\" name=\"k\" value=\"\" class=\"text\" /></li>";
+		$search .= "<li><input type=\"submit\" name=\"b\" class=\"searchbtn\" value=\"".Kohana::lang('ui_main.search')."\" /></li>";
+		$search .= "</ul>";
+		$search .= form::close();
+		$search .= "</div>";
+
+		return $search;
+	}
+
+	public function submit_btn()
+	{
+		$btn = "";
+
+		// Action::pre_nav_submit - Add items before the submit button
+		$btn .= Event::run('ushahidi_action.pre_nav_submit');
+
+		if (Kohana::config('settings.allow_reports'))
+		{
+			$btn .= "<div class=\"submit-incident clearingfix\">";
+			$btn .= "<a href=\"".url::site()."reports/submit"."\">".Kohana::lang('ui_main.submit')."</a>";
+			$btn .= "</div>";
+		}
+
+		// Action::post_nav_submit - Add items after the submit button
+		$btn .= Event::run('ushahidi_action.post_nav_submit');
+
+		return $btn;
+	}
+
+	/*
+	* Google Analytics
+	* @param text mixed	 Input google analytics web property ID.
+	* @return mixed	 Return google analytics HTML code.
+	*/
+	public function google_analytics()
+	{
+		$html = "";
+		if (Kohana::config('settings.google_analytics') == TRUE) {
+			$html = "<script type=\"text/javascript\">
+
+			var _gaq = _gaq || [];
+			_gaq.push(['_setAccount', '".Kohana::config('settings.google_analytics')."']);
+			_gaq.push(['_trackPageview']);
+
+			(function() {
+			var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+			ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+			var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+			})();
+
+			</script>";
+		}
+
+		// See if we need to disqualify showing the tag on the admin panel
+		if (Kohana::config('config.google_analytics_in_admin') == FALSE
+			AND isset(Router::$segments[0])
+			AND Router::$segments[0] == 'admin')
+		{
+			// Site is configured to not use the google analytics tag in the admin panel
+			//   and we are in the admin panel. Wipe out the tag.
+			$html = '';
+		}
+
+
+		return $html;
+	}
+
+	/**
+	 * Scheduler JS Call
+	 *
+	 * @return string
+	 */
+	public function scheduler_js()
+	{
+		if (Kohana::config('config.output_scheduler_js'))
+		{
+			$schedulerPath = url::site('scheduler');
+			$schedulerCode = <<< SCHEDULER
+				<!-- Task Scheduler -->
+				<script type="text/javascript">
+				setTimeout(function() {
+					var scheduler = document.createElement('img');
+					    scheduler.src = "$schedulerPath";
+					    scheduler.style.cssText = "width: 1px; height: 1px; opacity: 0.1;";
+
+					document.body.appendChild(scheduler);
+				}, 200);
+				</script>
+				<!-- End Task Scheduler -->
+SCHEDULER;
+			return $schedulerCode;
+		}
+		return '';
+	}
+
+	/*
+	* Ushahidi Stats JS Call
+	*    If a deployer is using Ushahidi to track their stats, this is the JS
+	*    call for that
+	*/
+	public function ushahidi_stats_js()
+	{
+		if (Kohana::config('settings.allow_stat_sharing') == 1)
+		{
+			return Stats_Model::get_javascript();
+		}
+		return '';
+	}
+	
+	/* Static functions for theme loading */
+	
+	private static $themes = array();
+	private static $loaded_themes = array();
+	private static $theme_js = array();
+	private static $theme_css = array();
+	
+
+	/**
+	 * Loads ushahidi themes
+	 */
+	public static function register_theme()
+	{
+		self::$themes = addon::get_addons('theme', TRUE);
+		
+		$theme = Kohana::config("settings.site_style");
+		$theme = empty($theme) ? 'default' : $theme;
+		self::load_theme($theme);
+}
+	
+	/**
+	 * Load theme
+	 * Loads theme into modules, includes its hooks and recursively loads parent themes
+	 * @param string $theme theme name/directory
+	 **/
+	public static function load_theme($theme)
+	{
+		// Record loading this theme, so we can avoid dependency loops
+		self::$loaded_themes[] = $theme;
+		
+		// Get meta data to check the base theme
+		$meta = self::$themes[$theme];
+		
+		// If base theme is set, the base theme exists, and we haven't loaded it yet
+		// Load the base theme
+		if (! empty($meta['Base Theme'])
+				AND isset(self::$themes[$meta['Base Theme']])
+				AND ! in_array($meta['Base Theme'], self::$loaded_themes)
+			)
+		{
+			self::load_theme($meta['Base Theme']);
+		}
+		
+		// Add theme to modules
+		$theme_base = THEMEPATH . $theme;
+		Kohana::config_set('core.modules', array_merge(array($theme_base), Kohana::config("core.modules")));
+
+		// We need to manually include the hook file for each theme
+		if (file_exists($theme_base.'/hooks'))
+		{
+			$d = dir($theme_base.'/hooks'); // Load all the hooks
+			while (($entry = $d->read()) !== FALSE)
+			{
+				if ($entry[0] != '.')
+				{
+					include $theme_base.'/hooks/'.$entry;
+				}
+			}
+		}
+		
+		self::load_theme_css($theme);
+		self::load_theme_js($theme);
+	}
+
+	/*
+	 * Find theme css and store for inclusion later
+	 */
+	private static function load_theme_css($theme)
+	{
+		$meta = self::$themes[$theme];
+		// Add special cases for old themes
+		if (empty($meta['CSS']))
+		{
+			$meta['CSS'] = array();
+			$meta['CSS'][] = 'base';
+			$meta['CSS'][] = 'style';
+			$meta['CSS'][] = '_default';
+			$meta['CSS'][] = $theme;
+		}
+		else
+		{
+			$meta['CSS'] = explode(',', $meta['CSS']);
+			$meta['CSS'] = array_map('trim',$meta['CSS']);
+		}
+		
+		// Add specified theme stylesheets
+		foreach ($meta['CSS'] as $css)
+		{
+			if (file_exists(THEMEPATH."$theme/css/$css.css"))
+				self::$theme_css[$css] = "themes/$theme/css/$css.css";
+		}
+		
+		// Check for overrides of already added stylesheets
+		foreach (self::$theme_css as $css => $path)
+		{
+			if (file_exists(THEMEPATH."$theme/css/$css.css"))
+				self::$theme_css[$css] = "themes/$theme/css/$css.css";
+		}
+	}
+
+	/*
+	 * Find theme css and store for inclusion later
+	 */
+	private static function load_theme_js($theme)
+	{
+		$meta = self::$themes[$theme];
+		// Add special cases for old themes
+		if (empty($meta['JS']))
+		{
+			$meta['JS'] = array();
+		}
+		else
+		{
+			$meta['JS'] = explode(',', $meta['JS']);
+			$meta['JS'] = array_map('trim',$meta['JS']);
+		}
+		
+		// Add specified theme js
+		foreach ($meta['JS'] as $js)
+		{
+			if (file_exists(THEMEPATH."$theme/js/$js.js"))
+				self::$theme_js[$js] = "themes/$theme/js/$js.js";
+		}
+		
+		// Check for overrides of already added js
+		foreach (self::$theme_css as $js => $path)
+		{
+			if (file_exists(THEMEPATH."$theme/js/$js.js"))
+				self::$theme_js[$js] = "themes/$theme/js/$js.js";
+		}
+	}
+	
+	public static function loaded_themes()
+	{
+		return self::$loaded_themes;
+	}
+}
diff --git a/application/libraries/Twitter_Oauth.php b/application/libraries/Twitter_Oauth.php
new file mode 100644
index 0000000..492a64d
--- /dev/null
+++ b/application/libraries/Twitter_Oauth.php
@@ -0,0 +1,245 @@
+<?php
+
+/*
+ * Abraham Williams (abraham at abrah.am) http://abrah.am
+ *
+ * The first PHP Library to support OAuth for Twitter's REST API.
+ */
+
+/* Load OAuth lib. You can find it at http://oauth.net */
+require_once('OAuth.php');
+
+/**
+ * Twitter OAuth class
+ */
+class Twitter_Oauth {
+  /* Contains the last HTTP status code returned. */
+  public $http_code;
+  /* Contains the last API call. */
+  public $url;
+  /* Set up the API root URL. */
+  public $host = "https://api.twitter.com/1.1/";
+  /* Set timeout default. */
+  public $timeout = 30;
+  /* Set connect timeout. */
+  public $connecttimeout = 30; 
+  /* Verify SSL Cert. */
+  public $ssl_verifypeer = FALSE;
+  /* Respons format. */
+  public $format = 'json';
+  /* Decode returned json data. */
+  public $decode_json = TRUE;
+  /* Contains the last HTTP headers returned. */
+  public $http_info;
+  /* Set the useragnet. */
+  public $useragent = 'TwitterOAuth v0.2.0-beta2';
+  /* Immediately retry the API call if the response was not successful. */
+  //public $retry = TRUE;
+
+
+
+
+  /**
+   * Set API URLS
+   */
+  function accessTokenURL()  { return 'https://api.twitter.com/oauth/access_token'; }
+  function authenticateURL() { return 'https://api.twitter.com/oauth/authenticate'; }
+  function authorizeURL()    { return 'https://api.twitter.com/oauth/authorize'; }
+  function requestTokenURL() { return 'https://api.twitter.com/oauth/request_token'; }
+
+  /**
+   * Debug helpers
+   */
+  function lastStatusCode() { return $this->http_status; }
+  function lastAPICall() { return $this->last_api_call; }
+
+  /**
+   * construct TwitterOAuth object
+   */
+  function __construct($consumer_key, $consumer_secret, $oauth_token = NULL, $oauth_token_secret = NULL) {
+    $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
+    $this->consumer = new OAuthConsumer($consumer_key, $consumer_secret);
+    if (!empty($oauth_token) && !empty($oauth_token_secret)) {
+      $this->token = new OAuthConsumer($oauth_token, $oauth_token_secret);
+    } else {
+      $this->token = NULL;
+    }
+  }
+
+
+  /**
+   * Get a request_token from Twitter
+   *
+   * @returns a key/value array containing oauth_token and oauth_token_secret
+   */
+  function getRequestToken($oauth_callback = NULL) {
+    $parameters = array();
+    if (!empty($oauth_callback)) {
+      $parameters['oauth_callback'] = $oauth_callback;
+    } 
+    $request = $this->oAuthRequest($this->requestTokenURL(), 'GET', $parameters);
+    $token = OAuthUtil::parse_parameters($request);
+    $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
+    return $token;
+  }
+
+  /**
+   * Get the authorize URL
+   *
+   * @returns a string
+   */
+  function getAuthorizeURL($token, $sign_in_with_twitter = TRUE) {
+    if (is_array($token)) {
+      $token = $token['oauth_token'];
+    }
+    if (empty($sign_in_with_twitter)) {
+      return $this->authorizeURL() . "?oauth_token={$token}";
+    } else {
+       return $this->authenticateURL() . "?oauth_token={$token}";
+    }
+  }
+
+  /**
+   * Exchange request token and secret for an access token and
+   * secret, to sign API calls.
+   *
+   * @returns array("oauth_token" => "the-access-token",
+   *                "oauth_token_secret" => "the-access-secret",
+   *                "user_id" => "9436992",
+   *                "screen_name" => "abraham")
+   */
+  function getAccessToken($oauth_verifier = FALSE) {
+    $parameters = array();
+    if (!empty($oauth_verifier)) {
+      $parameters['oauth_verifier'] = $oauth_verifier;
+    }
+    $request = $this->oAuthRequest($this->accessTokenURL(), 'GET', $parameters);
+    $token = OAuthUtil::parse_parameters($request);
+    $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
+    return $token;
+  }
+
+  /**
+   * One time exchange of username and password for access token and secret.
+   *
+   * @returns array("oauth_token" => "the-access-token",
+   *                "oauth_token_secret" => "the-access-secret",
+   *                "user_id" => "9436992",
+   *                "screen_name" => "abraham",
+   *                "x_auth_expires" => "0")
+   */  
+  function getXAuthToken($username, $password) {
+    $parameters = array();
+    $parameters['x_auth_username'] = $username;
+    $parameters['x_auth_password'] = $password;
+    $parameters['x_auth_mode'] = 'client_auth';
+    $request = $this->oAuthRequest($this->accessTokenURL(), 'POST', $parameters);
+    $token = OAuthUtil::parse_parameters($request);
+    $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']);
+    return $token;
+  }
+
+  /**
+   * GET wrapper for oAuthRequest.
+   */
+  function get($url, $parameters = array()) {
+    $response = $this->oAuthRequest($url, 'GET', $parameters);
+    if ($this->format === 'json' && $this->decode_json) {
+      return json_decode($response);
+    }
+    return $response;
+  }
+  
+  /**
+   * POST wrapper for oAuthRequest.
+   */
+  function post($url, $parameters = array()) {
+    $response = $this->oAuthRequest($url, 'POST', $parameters);
+    if ($this->format === 'json' && $this->decode_json) {
+      return json_decode($response);
+    }
+    return $response;
+  }
+
+  /**
+   * DELETE wrapper for oAuthReqeust.
+   */
+  function delete($url, $parameters = array()) {
+    $response = $this->oAuthRequest($url, 'DELETE', $parameters);
+    if ($this->format === 'json' && $this->decode_json) {
+      return json_decode($response);
+    }
+    return $response;
+  }
+
+  /**
+   * Format and sign an OAuth / API request
+   */
+  function oAuthRequest($url, $method, $parameters) {
+    if (strrpos($url, 'https://') !== 0 && strrpos($url, 'http://') !== 0) {
+      $url = "{$this->host}{$url}.{$this->format}";
+    }
+    $request = OAuthRequest::from_consumer_and_token($this->consumer, $this->token, $method, $url, $parameters);
+    $request->sign_request($this->sha1_method, $this->consumer, $this->token);
+    switch ($method) {
+    case 'GET':
+      return $this->http($request->to_url(), 'GET');
+    default:
+      return $this->http($request->get_normalized_http_url(), $method, $request->to_postdata());
+    }
+  }
+
+  /**
+   * Make an HTTP request
+   *
+   * @return API results
+   */
+  function http($url, $method, $postfields = NULL) {
+    $this->http_info = array();
+    $ci = curl_init();
+    /* Curl settings */
+    curl_setopt($ci, CURLOPT_USERAGENT, $this->useragent);
+    curl_setopt($ci, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout);
+    curl_setopt($ci, CURLOPT_TIMEOUT, $this->timeout);
+    curl_setopt($ci, CURLOPT_RETURNTRANSFER, TRUE);
+    curl_setopt($ci, CURLOPT_HTTPHEADER, array('Expect:'));
+    curl_setopt($ci, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer);
+    curl_setopt($ci, CURLOPT_HEADERFUNCTION, array($this, 'getHeader'));
+    curl_setopt($ci, CURLOPT_HEADER, FALSE);
+
+    switch ($method) {
+      case 'POST':
+        curl_setopt($ci, CURLOPT_POST, TRUE);
+        if (!empty($postfields)) {
+          curl_setopt($ci, CURLOPT_POSTFIELDS, $postfields);
+        }
+        break;
+      case 'DELETE':
+        curl_setopt($ci, CURLOPT_CUSTOMREQUEST, 'DELETE');
+        if (!empty($postfields)) {
+          $url = "{$url}?{$postfields}";
+        }
+    }
+
+    curl_setopt($ci, CURLOPT_URL, $url);
+    $response = curl_exec($ci);
+    $this->http_code = curl_getinfo($ci, CURLINFO_HTTP_CODE);
+    $this->http_info = array_merge($this->http_info, curl_getinfo($ci));
+    $this->url = $url;
+    curl_close ($ci);
+    return $response;
+  }
+
+  /**
+   * Get the header info to store.
+   */
+  function getHeader($ch, $header) {
+    $i = strpos($header, ':');
+    if (!empty($i)) {
+      $key = str_replace('-', '_', strtolower(substr($header, 0, $i)));
+      $value = trim(substr($header, $i + 2));
+      $this->http_header[$key] = $value;
+    }
+    return strlen($header);
+  }
+}
diff --git a/application/libraries/Upgrade.php b/application/libraries/Upgrade.php
new file mode 100644
index 0000000..9f827da
--- /dev/null
+++ b/application/libraries/Upgrade.php
@@ -0,0 +1,637 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Upgrading  Library
+ * Provides the necessary functions to do the automatic upgrade
+ * 
+ * @package	   Upgrade
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+ 
+ class Upgrade 
+{
+	
+	public $notices;
+	public $errors;
+	public $success;
+	public $error_level;
+	public $session;
+	public $ftp;
+	public $ftp_server;
+	public $ftp_user_name;
+	public $ftp_user_pass;
+	
+	public function __construct() 
+	{
+		$this->log = array();
+		$this->errors = array();
+		$this->error_level = ini_get('error_reporting');
+		$this->session = Session::instance();
+		
+		if ( ! $this->session->get('upgrade_session'))
+		{
+			$this->session->set('upgrade_session', date("Y_m_d-H_i_s"));
+		}
+	}
+	
+	/**
+	 * Fetches ushahidi from download.ushahidi.com
+	 * 
+	 * @param String url-- download URL
+	 */
+    public function download_ushahidi($url) {
+        $http_client = new HttpClient($url,30);
+        $results = $http_client->execute();
+		$this->log[] = "Starting to download the latest ushahidi build...";
+		
+		if ( $results) 
+		{
+			$this->log[] = "Download of latest ushahidi went successful.";
+			$this->success = true;		  
+			return $results;
+		} 
+		
+		else 
+		{			
+			$this->errors[] = sprintf(Kohana::lang('libraries.upgrade_failed').": %d", $http_client->get_error_msg());	
+			$this->success = false;
+			return $results;
+		}
+			
+	}
+	
+	
+	/**
+	 * FTP files recursively. 
+	 * 
+	 * @param String source-- the source directory.
+	 * @param String dest -- the destination directory.
+	 * @param $options //folderPermission,filePermission
+	 * @return boolean 
+	 */
+	function ftp_recursively($source, $dest, $options=array('folderPermission'=>0775,'filePermission'=>0664))
+	{
+		if ( ! $this->ftp_connect() )
+		{
+			$this->success = false;
+			return false;
+		}
+		
+		if ( ! $ftp_base_path = $this->ftp_base_path())
+		{
+			$this->success = false;
+			return false;
+		}
+		
+		$this->ftp->chdir($ftp_base_path);
+		
+		if (is_file($source))
+		{	
+			$__dest=$dest;
+			
+			// Turn off error reporting temporarily
+			error_reporting(0);
+			$result = $this->ftp_copy($source, $__dest, $options);
+			if ($result)
+			{
+				$this->success = true;
+				$this->logger("Copied to ".$__dest);
+				//Turn on error reporting again
+				error_reporting($this->error_level);
+			}
+			else
+			{
+				$this->success = false;
+				$this->logger("** Failed writing ".$__dest);
+				//Turn on error reporting again
+				error_reporting($this->error_level);
+				return false;
+			}
+
+		}
+		elseif(is_dir($source))
+		{
+			if ($dest[strlen($dest)-1] == '/')
+			{
+				if ($source[strlen($source)-1] == '/')
+				{
+					//Copy only contents
+				}
+				else
+				{
+					//Change parent itself and its contents
+					$dest = $dest.basename($source);
+					if ( ! $this->ftp->mkdir(str_replace(DOCROOT,"",$dest)))
+					{
+						$this->logger("** Failed creating directory ".$dest.". It might already exist.");
+					}
+					else
+					{
+						$this->logger("Created directory ".$dest);
+					}
+					$this->ftp->chmod(str_replace(DOCROOT,"",$dest),$options['folderPermission']);
+				}
+			}
+			else
+			{	
+				if ($source[strlen($source)-1] == '/')
+				{
+					//Copy parent directory with new name and all its content
+					if ( ! $this->ftp->mkdir(str_replace(DOCROOT,"",$dest)))
+					{
+						$this->logger("** Failed creating directory ".$dest.". It might already exist.");
+					}
+					else
+					{
+						$this->logger("Created directory ".$dest);
+					}
+					$this->ftp->chmod(str_replace(DOCROOT,"",$dest),$options['folderPermission']);
+				}
+				else
+				{
+					//Copy parent directory with new name and all its content
+					if ( ! $this->ftp->mkdir(str_replace(DOCROOT,"",$dest)))
+					{
+						$this->logger("** Failed creating directory ".$dest.". It might already exist.");
+					}
+					else
+					{
+						$this->logger("Created directory ".$dest);
+					}
+					$this->ftp->chmod(str_replace(DOCROOT,"",$dest),$options['folderPermission']);
+				}
+			}
+			
+			
+			$dirHandle=opendir($source);
+			while($file=readdir($dirHandle))
+			{
+				if($file != "." AND $file != ".." AND substr($file, 0, 1) != '.')
+				{
+					if( ! is_dir($source."/".$file))
+					{
+						$__dest=$dest."/".$file;
+						$__dest=str_replace("//", "/", $__dest);
+					}
+					else
+					{
+						$__dest=$dest."/".$file;
+						$__dest=str_replace("//", "/", $__dest);
+					}
+					$source_file = $source."/".$file;
+					$source_file = str_replace("//", "/", $source_file);
+					$result = $this->ftp_recursively($source_file, $__dest, $options);
+				}
+			}
+			closedir($dirHandle);
+		}
+	}
+	
+	
+	function ftp_connect()
+	{
+		//** temporary
+		$this->ftp_server = $this->session->get('ftp_server');
+		$this->ftp_user_name = $this->session->get('ftp_user_name');
+		$this->ftp_user_pass = $this->session->get('ftp_user_pass');
+		
+		// Turn off error reporting temporarily
+		//error_reporting(0);
+		
+		$ftp_init = new PemFtp();
+		$this->ftp = new ftp();
+		
+		if ( ! $this->ftp_server OR
+			 ! $this->ftp_user_name OR
+			 ! $this->ftp_user_pass)
+		{
+			// Failed Connecting
+			$this->logger("** Can't connect to FTP Server. Server, Username and/or Password not specified.");
+			error_reporting($this->error_level);
+			return false;
+		}
+
+		// Set FTP Server
+		if ( ! $this->ftp->SetServer($this->ftp_server))
+		{
+			// Failed Connecting
+			$this->logger("** Can't connect to FTP Server");
+			error_reporting($this->error_level);
+			return false;
+		}
+		
+		// Connect to FTP Server
+		if ( ! $this->ftp->connect() )
+		{
+			// Failed Connecting
+			$this->logger("** Can't connect to FTP Server");
+			error_reporting($this->error_level);
+			return false;
+		}
+		
+		// Authenticate at FTP Server
+		if ( ! $this->ftp->login($this->ftp_user_name, $this->ftp_user_pass) )
+		{
+			// Failed Connecting
+			$this->logger("** Can't connect to FTP Server - Incorrect Username or Password");
+			error_reporting($this->error_level);
+			return false;
+		}
+		
+		$this->ftp->setTimeout(30);
+		$this->ftp->SetType(-1);
+		$this->ftp->Passive(true);
+		
+		error_reporting($this->error_level);
+		
+		return true;
+	}
+	
+	function ftp_base_path()
+	{
+		$absolute_parts = explode('/', DOCROOT);
+		$ushahidi_folder = end($absolute_parts);
+		$ftp_parts = $this->ftp->nlist();
+		
+		// Are we already in the Ushahidi directory?
+		if ($this->ftp->is_exists("application/config/config.php"))
+		{
+			return "./";
+		}
+		
+		// We'll cycle through both to find out which
+		// part of the DOCROOT we're in
+		$ftp_base = "";
+		foreach ($absolute_parts as $part)
+		{
+			foreach ($ftp_parts as $key => $value)
+			{
+				if ($value == $part)
+				{
+					$ftp_base .= $value."/";
+					if ($this->ftp->is_exists($ftp_base."application/config/config.php"))
+					{ // We've arrived at the right folder
+						break;
+					}
+				}
+			}
+		}
+		
+		// Verify once again that we're in the right directory
+		if ($this->ftp->is_exists($ftp_base."application/config/config.php"))
+		{ // We've arrived at the right folder
+			return $ftp_base;
+		}
+		else
+		{
+			return false;
+		}
+	}
+	
+	function ftp_copy($source, $dest, $options)
+	{
+		$dest = str_replace(DOCROOT,"",$dest);
+		
+		if ( ! $this->ftp->put($source, $dest) )
+		{
+			return false;			
+		}
+		
+		$this->ftp->chmod($dest,$options['filePermission']);
+		
+		return true;
+	}
+	
+	
+	/**
+	 * Copy files recursively. 
+	 * 
+	 * @param String source-- the source directory.
+	 * @param String dest -- the destination directory.
+	 * @param $options //folderPermission,filePermission
+	 * @return boolean 
+	 */
+	function copy_recursively($source, $dest, $options=array('folderPermission'=>0755,'filePermission'=>0755))
+	{				
+		if (is_file($source)) {
+			if ($dest[strlen($dest)-1]=='/')
+			{
+				if (!file_exists($dest))
+				{
+					cmfcDirectory::makeAll($dest,$options['folderPermission'],true);
+				}
+				$__dest = $dest."/".basename($source);
+			}
+			else
+			{
+				$__dest=$dest;
+			}
+			// Turn off error reporting temporarily
+			error_reporting(0);
+			$result = copy($source, $__dest);
+			if ($result)
+			{
+				chmod($__dest,$options['filePermission']);
+				$this->success = true;
+				$this->logger("Copied to ".$__dest);
+				//Turn on error reporting again
+				error_reporting($this->error_level);
+			}
+			else
+			{
+				$this->success = false;
+				$this->logger("** Failed writing ".$__dest);
+				//Turn on error reporting again
+				error_reporting($this->error_level);
+				return false;
+			}
+
+		}
+		elseif(is_dir($source))
+		{
+			if ($dest[strlen($dest)-1] == '/')
+			{
+				if ($source[strlen($source)-1] == '/')
+				{
+					//Copy only contents
+				}
+				else
+				{
+					//Change parent itself and its contents
+					$dest = $dest.basename($source);
+					if ( ! is_writable($dest))
+					{
+						$this->success = false;
+						$this->logger("** Can't write to ".$dest);
+						return false;
+					}
+					@mkdir($dest);
+					chmod($dest,$options['filePermission']);
+				}
+			}
+			else
+			{
+				if ( ! is_writable($dest))
+				{
+					$this->success = false;
+					$this->logger("** Can't write to ".$dest);
+					return false;
+				}
+				
+				if ($source[strlen($source)-1] == '/')
+				{
+					//Copy parent directory with new name and all its content
+					@mkdir($dest,$options['folderPermission']);
+					chmod($dest,$options['filePermission']);
+				}
+				else
+				{
+					//Copy parent directory with new name and all its content
+					@mkdir($dest,$options['folderPermission']);
+					chmod($dest,$options['filePermission']);
+				}
+			}
+
+			$dirHandle=opendir($source);
+			while($file=readdir($dirHandle))
+			{
+				if($file!="." AND $file!=".." AND substr($file, 0, 1) != '.')
+				{
+					if(!is_dir($source."/".$file))
+					{
+						$__dest=$dest."/".$file;
+					}
+					else
+					{
+						$__dest=$dest."/".$file;
+					}
+					//echo "$source/$file ||| $__dest<br />";
+					if ( ! is_writable($__dest))
+					{
+						$this->success = false;
+						$this->logger("** Can't write to - ".$__dest);
+						return false;
+					}
+					$result = $this->copy_recursively($source."/".$file, $__dest, $options);
+				}
+			}
+			closedir($dirHandle);
+		}
+	}
+	
+	
+	/**
+	 * Remove files recursively.
+	 * 
+	 * @param String dir-- the directory to delete.
+	 */
+	public function remove_recursively($dir) 
+	{
+		if (empty($dir) || !is_dir($dir))
+			return false;
+		if (substr($dir,-1) != "/")
+			$dir .= "/";
+		if (($dh = opendir($dir)) !== false) {
+			while (($entry = readdir($dh)) !== false) {
+			if ($entry != "." && $entry != "..") {
+				if ( is_file($dir . $entry) ) {
+				if ( !@unlink($dir . $entry) ) {
+					$this->errors[] = sprintf(Kohana::lang('libraries.upgrade_file_not_deleted'), $dir.$entry );
+					$this->success = false;
+				}
+			} elseif (is_dir($dir . $entry)) {
+				$this->remove_recursively($dir . $entry);
+				$this->success = true;
+			}
+			}
+		}
+		closedir($dh);
+		if ( !@rmdir($dir) ) {
+			$this->errors[] = sprintf(Kohana::lang('libraries.upgrade_directory_not_deleted'), $dir.$entry);
+			$this->success = false;
+		}
+			$this->success = true;
+			return true;
+		}
+		return false;
+		
+	}
+	
+	/**
+	 * Unzip the file.
+	 * 
+	 * @param String zip_file-- the zip file to be extracted.
+	 * @param String destdir-- destination directory
+	 */
+	
+	public function unzip_ushahidi($zip_file, $destdir) 
+	{
+		$archive = new Pclzip($zip_file);
+		$this->log[] = sprintf("Unpacking %s ",$zip_file);
+		
+		if (@$archive->extract(PCLZIP_OPT_PATH, $destdir) == 0)
+		{
+			$this->errors[] = sprintf(Kohana::lang('libraries.upgrade_extracting_error'),$archive->errorInfo(true) ) ;
+			return false;
+		}
+		
+		$this->log[] = sprintf("Unpacking went successful");
+		$this->success = true;
+		return true;
+	} 
+	
+	/**
+	 * Write the zile file to a file.
+	 * 
+	 * @param String zip_file-- the zip file to be written.
+	 * @param String dest_file-- the file to write.
+	 */
+	public function write_to_file($zip_file, $dest_file) 
+	{
+		$handler = fopen( $dest_file,'w');
+		$fwritten = fwrite($handler,$zip_file);
+		$this->log[] = sprintf("Writting to a file ");
+		if( !$fwritten ) {
+			$this->errors[] = sprintf(Kohana::lang('libraries.upgrade_zip_error'),$dest_file);
+			$this->success = false;
+			return false;
+		}
+		fclose($handler);
+		$this->success = true;
+		$this->log[] = sprintf("Zip file successfully written to a file ");
+		return true;
+	}
+
+	/**
+	 * Fetch latest ushahidi version from a remote instance then
+	 * compare it with local instance version number.
+	 */
+	public function _fetch_core_release()
+	{
+		// Current Version
+		$current = urlencode(Kohana::config('settings.ushahidi_version'));
+
+		// Extra Stats
+		$url = urlencode(preg_replace("/^https?:\/\/(.+)$/i","\\1",
+					url::base()));
+		$ip_address = (isset($_SERVER['REMOTE_ADDR'])) ?
+			urlencode($_SERVER['REMOTE_ADDR']) : "";
+
+		$version_url = "http://version.ushahidi.com/2/?v=".$current.
+			"&u=".$url."&ip=".$ip_address;
+
+		$request = new HttpClient($version_url);
+		$version = $request->execute();
+
+		preg_match('/({.*})/', $version, $matches);
+
+		$version_json_string = false;
+		if(isset($matches[0]))
+		{
+			$version_json_string = $matches[0];
+		}
+
+		// If we didn't get anything back...
+		if ( ! $version_json_string )
+		{
+			 return "";
+		}
+
+		$version_details = json_decode($version_json_string);
+		return $version_details;
+	}
+
+	/**
+	 * Log Messages To File
+	 */
+	public function logger($message)
+	{
+		$filter_crlf = array("\n", "\r");
+		$message = date("Y-m-d H:i:s")." : ".$message;
+		$mesg = str_replace($filter_crlf,'',$message);
+		$mesg .= "\n";
+		$logfile = DOCROOT."application/logs/upgrade_".$this->session->get('upgrade_session').".txt";
+		$logfile = fopen($logfile, 'a+');
+		fwrite($logfile, $mesg);
+		fclose($logfile);
+	}
+	
+	/**
+	 * Delete files that no longer exist in latest version
+	 **/
+	public function remove_old($file, $base_directory)
+	{
+		if ( ! $this->ftp_connect() )
+		{
+			$this->success = false;
+			return false;
+		}
+		
+		if ( ! $ftp_base_path = $this->ftp_base_path())
+		{
+			$this->success = false;
+			return false;
+		}
+		
+		$this->ftp->chdir($ftp_base_path);
+		
+		$old_files = file($file, FILE_IGNORE_NEW_LINES);
+		foreach ($old_files as $old_file)
+		{
+			$ftp_filename = str_replace(DOCROOT,"",$old_file);
+			
+			// Skip removed config files
+			if (stripos($old_file,'application/config/') !== FALSE) continue;
+			
+			if (is_file($old_file))
+			{
+				// Turn off error reporting temporarily
+				error_reporting(0);
+				$result = $this->ftp->delete($ftp_filename);
+				if ($result)
+				{
+					$this->success = true;
+					$this->logger("Removed ".$old_file);
+					//Turn on error reporting again
+					error_reporting($this->error_level);
+				}
+				else
+				{
+					$this->success = false;
+					$this->logger("** Failed removing ".$old_file);
+					//Turn on error reporting again
+					error_reporting($this->error_level);
+					return false;
+				}
+			}
+			elseif(is_dir($old_file))
+			{
+				error_reporting(0);
+				$result = $this->ftp->mdel($ftp_filename);
+				if ($result)
+				{
+					$this->success = true;
+					$this->logger("Removed ".$old_file);
+					//Turn on error reporting again
+					error_reporting($this->error_level);
+				}
+				else
+				{
+					$this->success = false;
+					$this->logger("** Failed removing ".$old_file);
+					//Turn on error reporting again
+					error_reporting($this->error_level);
+					return false;
+				}
+			}
+			
+		}
+
+		// Remove upgrader removed files list
+		error_reporting(0);
+		$result = $this->ftp->delete('upgrader_removed_files.txt');
+		error_reporting($this->error_level);
+	}
+}
+
diff --git a/application/libraries/Validation.php b/application/libraries/Validation.php
new file mode 100644
index 0000000..3285c8e
--- /dev/null
+++ b/application/libraries/Validation.php
@@ -0,0 +1,859 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Validation library.
+ *
+ * $Id: Validation.php 3127 2008-07-16 14:43:52Z Shadowhand $
+ *
+ * @package    Validation
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Validation_Core extends ArrayObject {
+
+	// Unique "any field" key
+	protected $any_field;
+
+	// Array fields
+	protected $array_fields = array();
+
+	// Filters
+	protected $pre_filters = array();
+	protected $post_filters = array();
+
+	// Rules and callbacks
+	protected $rules = array();
+	protected $callbacks = array();
+
+	// Rules that are allowed to run on empty fields
+	protected $empty_rules = array('required', 'matches');
+
+	// Errors
+	protected $errors = array();
+	protected $error_message_args = array();
+	protected $messages = array();
+
+	// Checks if there is data to validate.
+	protected $submitted;
+
+	/**
+	 * Whether CSRF validation has succeeded
+	 * @var bool
+	 */
+	protected $csrf_validation_failed = FALSE;
+
+	public static $is_api_request = FALSE;
+
+	/**
+	 * Creates a new Validation instance.
+	 *
+	 * @param   array   array to use for validation
+	 * @return  object
+	 */
+	public static function factory($array = NULL)
+	{
+		return new Validation( ! is_array($array) ? $_POST : $array);
+	}
+
+	/**
+	 * Sets the unique "any field" key and creates an ArrayObject from the
+	 * passed array.
+	 *
+	 * @param   array   array to validate
+	 * @return  void
+	 */
+	public function __construct(array $array)
+	{
+		// Set a dynamic, unique "any field" key
+		$this->any_field = uniqid(NULL, TRUE);
+
+		// Test if there is any actual data
+		$this->submitted = (count($array) > 0);
+
+		parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
+	}
+
+	/**
+	 * Test if the data has been submitted.
+	 *
+	 * @return  boolean
+	 */
+	public function submitted($value = NULL)
+	{
+		if (is_bool($value))
+		{
+			$this->submitted = $value;
+		}
+
+		return $this->submitted;
+	}
+
+	/**
+	 * Returns the ArrayObject values.
+	 *
+	 * @return  array
+	 */
+	public function as_array()
+	{
+		return $this->getArrayCopy();
+	}
+
+	/**
+	 * Returns the ArrayObject values, removing all inputs without rules.
+	 * To choose specific inputs, list the field name as arguments.
+	 *
+	 * @return  array
+	 */
+	public function safe_array()
+	{
+		// All the fields that are being validated
+		$all_fields = array_unique(array_merge
+		(
+			array_keys($this->pre_filters),
+			array_keys($this->rules),
+			array_keys($this->callbacks),
+			array_keys($this->post_filters)
+		));
+
+		// Load choices
+		$choices = func_get_args();
+		$choices = empty($choices) ? NULL : array_combine($choices, $choices);
+
+		$safe = array();
+		foreach ($all_fields as $i => $field)
+		{
+			// Ignore "any field" key
+			if ($field === $this->any_field) continue;
+
+			if (isset($this->array_fields[$field]))
+			{
+				// Use the key field
+				$field = $this->array_fields[$field];
+			}
+
+			if ($choices === NULL OR isset($choices[$field]))
+			{
+				// Make sure all fields are defined
+				$safe[$field] = isset($this[$field]) ? $this[$field] : NULL;
+			}
+		}
+
+		return $safe;
+	}
+
+	/**
+	 * Add additional rules that will forced, even for empty fields. All arguments
+	 * passed will be appended to the list.
+	 *
+	 * @chainable
+	 * @param   string   rule name
+	 * @return  object
+	 */
+	public function allow_empty_rules($rules)
+	{
+		// Any number of args are supported
+		$rules = func_get_args();
+
+		// Merge the allowed rules
+		$this->empty_rules = array_merge($this->empty_rules, $rules);
+
+		return $this;
+	}
+
+	/**
+	 * Add a pre-filter to one or more inputs.
+	 *
+	 * @chainable
+	 * @param   callback  filter
+	 * @param   string    fields to apply filter to, use TRUE for all fields
+	 * @return  object
+	 */
+	public function pre_filter($filter, $field = TRUE)
+	{
+		if ( ! is_callable($filter))
+			throw new Kohana_Exception('validation.filter_not_callable');
+
+		$filter = (is_string($filter) AND strpos($filter, '::') !== FALSE) ? explode('::', $filter) : $filter;
+
+		if ($field === TRUE)
+		{
+			// Handle "any field" filters
+			$fields = array($this->any_field);
+		}
+		else
+		{
+			// Add the filter to specific inputs
+			$fields = func_get_args();
+			$fields = array_slice($fields, 1);
+		}
+
+		foreach ($fields as $field)
+		{
+			if (strpos($field, '.') > 0)
+			{
+				// Field keys
+				$keys = explode('.', $field);
+
+				// Add to array fields
+				$this->array_fields[$field] = $keys[0];
+			}
+
+			// Add the filter to specified field
+			$this->pre_filters[$field][] = $filter;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add a post-filter to one or more inputs.
+	 *
+	 * @chainable
+	 * @param   callback  filter
+	 * @param   string    fields to apply filter to, use TRUE for all fields
+	 * @return  object
+	 */
+	public function post_filter($filter, $field = TRUE)
+	{
+		if ( ! is_callable($filter, TRUE))
+			throw new Kohana_Exception('validation.filter_not_callable');
+
+		$filter = (is_string($filter) AND strpos($filter, '::') !== FALSE) ? explode('::', $filter) : $filter;
+
+		if ($field === TRUE)
+		{
+			// Handle "any field" filters
+			$fields = array($this->any_field);
+		}
+		else
+		{
+			// Add the filter to specific inputs
+			$fields = func_get_args();
+			$fields = array_slice($fields, 1);
+		}
+
+		foreach ($fields as $field)
+		{
+			if (strpos($field, '.') > 0)
+			{
+				// Field keys
+				$keys = explode('.', $field);
+
+				// Add to array fields
+				$this->array_fields[$field] = $keys[0];
+			}
+
+			// Add the filter to specified field
+			$this->post_filters[$field][] = $filter;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add rules to a field. Rules are callbacks or validation methods. Rules can
+	 * only return TRUE or FALSE.
+	 *
+	 * @chainable
+	 * @param   string    field name
+	 * @param   callback  rules (unlimited number)
+	 * @return  object
+	 */
+	public function add_rules($field, $rules)
+	{
+		// Handle "any field" filters
+		($field === TRUE) and $field = $this->any_field;
+
+		// Get the rules
+		$rules = func_get_args();
+		$rules = array_slice($rules, 1);
+
+		foreach ($rules as $rule)
+		{
+			// Rule arguments
+			$args = NULL;
+
+			if (is_string($rule))
+			{
+				if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches))
+				{
+					// Split the rule into the function and args
+					$rule = $matches[1];
+					$args = preg_split('/(?<!\\\\),\s*/', $matches[2]);
+
+					// Replace escaped comma with comma
+					$args = str_replace('\,', ',', $args);
+				}
+
+				if (method_exists($this, $rule))
+				{
+					// Make the rule a valid callback
+					$rule = array($this, $rule);
+				}
+				elseif (method_exists('valid', $rule))
+				{
+					// Make the rule a callback for the valid:: helper
+					$rule = array('valid', $rule);
+				}
+			}
+
+			if ( ! is_callable($rule, TRUE))
+				throw new Kohana_Exception('validation.rule_not_callable');
+
+			$rule = (is_string($rule) AND strpos($rule, '::') !== FALSE) ? explode('::', $rule) : $rule;
+
+			if (strpos($field, '.') > 0)
+			{
+				// Field keys
+				$keys = explode('.', $field);
+
+				// Add to array fields
+				$this->array_fields[$field] = $keys[0];
+			}
+
+			// Add the rule to specified field
+			$this->rules[$field][] = array($rule, $args);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add callbacks to a field. Callbacks must accept the Validation object
+	 * and the input name. Callback returns are not processed.
+	 *
+	 * @chainable
+	 * @param   string     field name
+	 * @param   callbacks  callbacks (unlimited number)
+	 * @return  object
+	 */
+	public function add_callbacks($field, $callbacks)
+	{
+		// Handle "any field" filters
+		($field === TRUE) and $field = $this->any_field;
+
+		if (func_get_args() > 2)
+		{
+			// Multiple callback
+			$callbacks = array_slice(func_get_args(), 1);
+		}
+		else
+		{
+			// Only one callback
+			$callbacks = array($callbacks);
+		}
+
+		foreach ($callbacks as $callback)
+		{
+			if ( ! is_callable($callback, TRUE))
+				throw new Kohana_Exception('validation.callback_not_callable');
+
+			$callback = (is_string($callback) AND strpos($callback, '::') !== FALSE) ? explode('::', $callback) : $callback;
+
+			if (strpos($field, '.') > 0)
+			{
+				// Field keys
+				$keys = explode('.', $field);
+
+				// Add to array fields
+				$this->array_fields[$field] = $keys[0];
+			}
+
+			// Add the callback to specified field
+			$this->callbacks[$field][] = $callback;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Validate by processing pre-filters, rules, callbacks, and post-filters.
+	 * All fields that have filters, rules, or callbacks will be initialized if
+	 * they are undefined. Validation will only be run if there is data already
+	 * in the array.
+	 *
+	 * @param bool $validate_csrf When TRUE, performs CSRF token validation
+	 * @return bool
+	 */
+	public function validate($validate_csrf = TRUE)
+	{
+		// CSRF token field
+		$csrf_token_key = 'form_auth_token';
+
+		if (array_key_exists($csrf_token_key, $this))
+		{
+			unset ($this[$csrf_token_key]);
+		}
+
+		// Delete the CSRF token field if it's in the validation
+		// rules
+		if (array_key_exists($csrf_token_key, $this->callbacks))
+		{
+			unset ($this->callbacks[$csrf_token_key]);
+		}
+		elseif (array_key_exists($csrf_token_key, $this->rules))
+		{
+			unset ($this->rules[$csrf_token_key]);
+		}
+
+		// Disable CSRF for XHR
+		// Same method as django CSRF protection: 
+		//     http://michael-coates.blogspot.co.nz/2010/12/djangos-built-in-csrf-defense-for-ajax.html
+		if (request::is_ajax())
+		{
+			$validate_csrf = FALSE;
+		}
+
+		// Perform CSRF validation for all HTTP POST requests
+		// where CSRF validation is enabled and the request
+		// was not submitted via the API
+		if ($_POST AND $validate_csrf AND ! Validation::$is_api_request)
+		{
+			// Check if CSRF module is loaded
+			if (in_array(MODPATH.'csrf', Kohana::config('config.modules')))
+			{
+				// Check for presence of CSRF token in HTTP POST payload
+				$form_auth_token = (isset($_POST[$csrf_token_key]))
+				    ? $_POST[$csrf_token_key]
+
+					    // Generate invalid token
+				    : text::random('alnum', 10);
+				
+				// Validate the token
+				if ( ! csrf::valid($form_auth_token))
+				{
+					Kohana::log('debug', 'Invalid CSRF token: '.$form_auth_token);
+					Kohana::log('debug', 'Actual CSRF token: '.csrf::token());
+					// Flag CSRF validation as having failed
+					$this->csrf_validation_failed = TRUE;
+
+					// Set the error message
+					$this->errors[$csrf_token_key] = Kohana::lang('csrf.form_auth_token.error');
+
+					return FALSE;
+				}
+			}
+		}
+
+		// All the fields that are being validated
+		$all_fields = array_unique(array_merge
+		(
+			array_keys($this->pre_filters),
+			array_keys($this->rules),
+			array_keys($this->callbacks),
+			array_keys($this->post_filters)
+		));
+
+		// Copy the array from the object, to optimize multiple sets
+		$object_array = $this->getArrayCopy();
+
+		foreach ($all_fields as $i => $field)
+		{
+			if ($field === $this->any_field)
+			{
+				// Remove "any field" from the list of fields
+				unset($all_fields[$i]);
+				continue;
+			}
+
+			if (substr($field, -2) === '.*')
+			{
+				// Set the key to be an array
+				Kohana::key_string_set($object_array, substr($field, 0, -2), array());
+			}
+			else
+			{
+				// Set the key to be NULL
+				Kohana::key_string_set($object_array, $field, NULL);
+			}
+		}
+
+		// Swap the array back into the object
+		$this->exchangeArray($object_array);
+
+		// Reset all fields to ALL defined fields
+		$all_fields = array_keys($this->getArrayCopy());
+
+		foreach ($this->pre_filters as $field => $calls)
+		{
+			foreach ($calls as $func)
+			{
+				if ($field === $this->any_field)
+				{
+					foreach ($all_fields as $f)
+					{
+						// Process each filter
+						$this[$f] = is_array($this[$f]) ? arr::map_recursive($func, $this[$f]) : call_user_func($func, $this[$f]);
+					}
+				}
+				else
+				{
+					// Process each filter
+					$this[$field] = is_array($this[$field]) ? arr::map_recursive($func, $this[$field]) : call_user_func($func, $this[$field]);
+				}
+			}
+		}
+
+		if ($this->submitted === FALSE)
+			return FALSE;
+
+		foreach ($this->rules as $field => $calls)
+		{
+			foreach ($calls as $call)
+			{
+				// Split the rule into function and args
+				list($func, $args) = $call;
+
+				if ($field === $this->any_field)
+				{
+					foreach ($all_fields as $f)
+					{
+						if (isset($this->array_fields[$f]))
+						{
+							// Use the field key
+							$f_key = $this->array_fields[$f];
+
+							// Prevent other rules from running when this field already has errors
+							if ( ! empty($this->errors[$f_key])) break;
+
+							// Don't process rules on empty fields
+							if ( ! in_array($func[1], $this->empty_rules, TRUE) AND $this[$f_key] == NULL)
+								continue;
+
+							foreach ($this[$f_key] as $k => $v)
+							{
+								if ( ! call_user_func($func, $this[$f_key][$k], $args))
+								{
+									// Run each rule
+									$this->errors[$f_key] = is_array($func) ? $func[1] : $func;
+								}
+							}
+						}
+						else
+						{
+							// Prevent other rules from running when this field already has errors
+							if ( ! empty($this->errors[$f])) break;
+
+							// Don't process rules on empty fields
+							if ( ! in_array($func[1], $this->empty_rules, TRUE) AND $this[$f] == NULL)
+								continue;
+
+							if ( ! call_user_func($func, $this[$f], $args))
+							{
+								// Run each rule
+								$this->errors[$f] = is_array($func) ? $func[1] : $func;
+							}
+						}
+					}
+				}
+				else
+				{
+					if (isset($this->array_fields[$field]))
+					{
+						// Use the field key
+						$field_key = $this->array_fields[$field];
+
+						// Prevent other rules from running when this field already has errors
+						if ( ! empty($this->errors[$field_key])) break;
+
+						// Don't process rules on empty fields
+						if ( ! in_array($func[1], $this->empty_rules, TRUE) AND $this[$field_key] == NULL)
+							continue;
+
+						foreach ($this[$field_key] as $k => $val)
+						{
+							if ( ! call_user_func($func, $this[$field_key][$k], $args))
+							{
+								// Run each rule
+								$this->errors[$field_key] = is_array($func) ? $func[1] : $func;
+
+								// Stop after an error is found
+								break 2;
+							}
+						}
+					}
+					else
+					{
+						// Prevent other rules from running when this field already has errors
+						if ( ! empty($this->errors[$field])) break;
+
+						// Don't process rules on empty fields
+						if ( ! in_array($func[1], $this->empty_rules, TRUE) AND $this[$field] == NULL)
+							continue;
+
+						if ( ! call_user_func($func, $this[$field], $args))
+						{
+							// Run each rule
+							$this->errors[$field] = is_array($func) ? $func[1] : $func;
+
+							// Stop after an error is found
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		foreach ($this->callbacks as $field => $calls)
+		{
+			foreach ($calls as $func)
+			{
+				if ($field === $this->any_field)
+				{
+					foreach ($all_fields as $f)
+					{
+						// Execute the callback
+						call_user_func($func, $this, $f);
+
+						// Stop after an error is found
+						if ( ! empty($errors[$f])) break 2;
+					}
+				}
+				else
+				{
+					// Execute the callback
+					call_user_func($func, $this, $field);
+
+					// Stop after an error is found
+					if ( ! empty($errors[$field])) break;
+				}
+			}
+		}
+
+		foreach ($this->post_filters as $field => $calls)
+		{
+			foreach ($calls as $func)
+			{
+				if ($field === $this->any_field)
+				{
+					foreach ($all_fields as $f)
+					{
+						if (isset($this->array_fields[$f]))
+						{
+							// Use the field key
+							$f = $this->array_fields[$f];
+						}
+
+						// Process each filter
+						$this[$f] = is_array($this[$f]) ? array_map($func, $this[$f]) : call_user_func($func, $this[$f]);
+					}
+				}
+				else
+				{
+					if (isset($this->array_fields[$field]))
+					{
+						// Use the field key
+						$field = $this->array_fields[$field];
+					}
+
+					// Process each filter
+					$this[$field] = is_array($this[$field]) ? array_map($func, $this[$field]) : call_user_func($func, $this[$field]);
+				}
+			}
+		}
+
+		// Return TRUE if there are no errors
+		return (count($this->errors) === 0);
+	}
+
+	/**
+	 * Add an error to an input.
+	 *
+	 * @chainable
+	 * @param   string  input name
+	 * @param   string  unique error name
+	 * @param   array   extra vars to pass to kohana::lang()
+	 * @return  object
+	 */
+	public function add_error($field, $name, $lang_vars = FALSE)
+	{
+		if (isset($this[$field]) OR $field == 'custom')
+		{
+			$this->errors[$field] = $name;
+			// Save error message vars
+			if ($lang_vars)
+			{
+				$this->error_message_args[$field] = $lang_vars;
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Sets or returns the message for an input.
+	 *
+	 * @chainable
+	 * @param   string   input key
+	 * @param   string   message to set
+	 * @return  string|object
+	 */
+	public function message($input = NULL, $message = NULL)
+	{
+		if ($message === NULL)
+		{
+			if ($input === NULL)
+			{
+				$messages = array();
+				$keys     = array_keys($this->messages);
+
+				foreach ($keys as $input)
+				{
+					$messages[] = $this->message($input);
+				}
+
+				return implode("\n", $messages);
+			}
+
+			// Return nothing if no message exists
+			if (empty($this->messages[$input]))
+				return '';
+
+			// Return the HTML message string
+			return $this->messages[$input];
+		}
+		else
+		{
+			$this->messages[$input] = $message;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Return the errors array.
+	 *
+	 * @param   boolean  load errors from a lang file
+	 * @return  array
+	 */
+	public function errors($file = NULL)
+	{
+		if ($file === NULL)
+		{
+			return $this->errors;
+		}
+		else
+		{
+			$errors = array();
+			foreach ($this->errors as $input => $error)
+			{
+				// Key for this input error
+				$key = "$file.$input.$error";
+				
+				$message_vars = isset($this->error_message_args[$input]) ? $this->error_message_args[$input] : array();
+
+				if (($errors[$input] = Kohana::lang($key, $message_vars)) === $key)
+				{
+					// Get the default error message.      Note: commented out by BH
+					//$errors[$input] = Kohana::lang("$file.$input.default");
+
+					// Don't get the default message because we rarely set it. Pass the key since it will
+					//   provide more clues as to the problem than what we are currently providing. Also,
+					//   this allows "custom" inputs to pass through, bypassing localization by design
+					//$errors[$input] = $key;
+				}
+			}
+
+			// CSRF validation errors MUST always be returned
+			if ($this->csrf_validation_failed)
+			{
+				$errors['form_auth_token'] = $this->errors['form_auth_token'];
+			}
+			return $errors;
+		}
+	}
+
+	/**
+	 * Rule: required. Generates an error if the field has an empty value.
+	 *
+	 * @param   mixed   input value
+	 * @return  bool
+	 */
+	public function required($str)
+	{
+		return ! ($str === '' OR $str === NULL OR $str === FALSE OR (is_array($str) AND empty($str)));
+	}
+
+	/**
+	 * Rule: matches. Generates an error if the field does not match one or more
+	 * other fields.
+	 *
+	 * @param   mixed   input value
+	 * @param   array   input names to match against
+	 * @return  bool
+	 */
+	public function matches($str, array $inputs)
+	{
+		foreach ($inputs as $key)
+		{
+			if ($str !== (isset($this[$key]) ? $this[$key] : NULL))
+				return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Rule: length. Generates an error if the field is too long or too short.
+	 *
+	 * @param   mixed   input value
+	 * @param   array   minimum, maximum, or exact length to match
+	 * @return  bool
+	 */
+	public function length($str, array $length)
+	{
+		if ( ! is_string($str))
+			return FALSE;
+
+		$size = utf8::strlen($str);
+		$status = FALSE;
+
+		if (count($length) > 1)
+		{
+			list ($min, $max) = $length;
+
+			if ($size >= $min AND $size <= $max)
+			{
+				$status = TRUE;
+			}
+		}
+		else
+		{
+			$status = ($size === (int) $length[0]);
+		}
+
+		return $status;
+	}
+
+	/**
+	 * Rule: depends_on. Generates an error if the field does not depend on one
+	 * or more other fields.
+	 *
+	 * @param   mixed   field name
+	 * @param   array   field names to check dependency
+	 * @return  bool
+	 */
+	public function depends_on($field, array $fields)
+	{
+		foreach ($fields as $depends_on)
+		{
+			if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL)
+				return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Rule: chars. Generates an error if the field contains characters outside of the list.
+	 *
+	 * @param   string  field value
+	 * @param   array   allowed characters
+	 * @return  bool
+	 */
+	public function chars($value, array $chars)
+	{
+		return ! preg_match('![^'.preg_quote(implode(',', $chars)).']!', $value);
+	}
+
+} // End Validation
diff --git a/application/libraries/VideoEmbed.php b/application/libraries/VideoEmbed.php
new file mode 100644
index 0000000..5ccf9d0
--- /dev/null
+++ b/application/libraries/VideoEmbed.php
@@ -0,0 +1,256 @@
+<?php
+/**
+ * Video embedding libary
+ * Provides a feature for embedding videos (YouTube, Google Video, Revver, Metacafe, LiveLeak, 
+ * Dostub and Vimeo) in a report
+ * 
+ * @package	   VideoEmbed
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+class VideoEmbed 
+{
+	/**
+	 * @var current video url
+	 */
+	private $url = FALSE;
+	
+	/**
+	 * @var name of current service
+	 */
+	private $service_name = FALSE;
+	
+	/**
+	 * @var config array for current service
+	 */
+	private $service = array();
+	
+	/**
+	 * Get the services supported by VideoEmbed
+	 * 
+	 * @return array
+	 */
+	private function services()
+	{
+		$services = array(
+			"youtube" => array(
+				'baseurl' => "http://www.youtube.com/watch?v=",
+				'searchstring' => 'youtube.com',
+				'oembed' => 'http://www.youtube.com/oembed',
+				'keep-params' => 'v'
+			),
+			// May now be defunct
+			"google" => array(
+				'baseurl' => "http://video.google.com/videoplay?docid=-",
+				'searchstring' => 'google.com',
+				'keep-params' => 'docid'
+			),
+			"metacafe" => array(
+				'baseurl' => "http://www.metacafe.com/watch/", 
+				'searchstring' => 'metacafe.com',
+			),
+			"dotsub" => array(
+				'baseurl' => "http://dotsub.com/view/",
+				'searchstring' => 'dotsub.com',
+				'oembed' => 'http://dotsub.com/services/oembed',
+			),
+			"vimeo" => array(
+				'baseurl' => "http://vimeo.com/",
+				'searchstring' => 'vimeo.com',
+				'oembed' => 'http://vimeo.com/api/oembed.json',
+			),
+		);
+		
+		Event::run('ushahidi_filter.video_embed_services', $services);
+		
+		return $services;
+	}
+	
+	/**
+	 * Set current video url and preprocess
+	 * 
+	 * @param string $url video url
+	 **/
+	public function set_url($url)
+	{
+		$this->service_name = $this->detect_service($url);
+		if ($this->service_name !== FALSE)
+		{
+			$services = $this->services();
+			$this->service = $services[$this->service_name];
+		}
+		
+		$this->url = $this->clean_url($url);
+	}
+	
+	/**
+	 * Convert raw url to standard structure
+	 * Particularly needed for youtube where v= param must be first
+	 * 
+	 * @param string $raw raw url
+	 * @return string standarized url
+	 */
+	private function clean_url($raw)
+	{
+		if (isset($this->service['keep-params']))
+		{
+			$components = parse_url($raw);
+			parse_str($components['query'], $query);
+			if (! isset($query[$this->service['keep-params']]) ) break;
+			$raw = $this->service['baseurl']. $query[$this->service['keep-params']];
+		}
+
+		return $raw;
+	}
+	
+	/**
+	 * Detect video services based on URL
+	 * 
+	 * @param string $raw video url
+	 * @return string $service_name service name
+	 */
+	private function detect_service($raw)
+	{
+		// To hold the name of the video service
+		$service_name = "";
+		
+		// Trim whitespaces from the raw data
+		$raw = trim($raw);
+		
+		// Array of the supportted video services
+		$services = $this->services();
+		
+		// Determine the video service to use
+		$service_name = FALSE;
+		foreach ($services as $key => $value)
+		{
+			// Match raw url against service search string
+			if (strpos($raw, $value['searchstring']))
+			{
+				$service_name = $key;
+				break;
+			}
+		}
+		
+		$data = array($services, $service_name);
+		Event::run('ushahidi_filter.video_embed_detect_services', $data);
+		list($services, $service_name) = $data;
+		
+		return $service_name;
+	}
+	
+	/**
+	 * Generates the HTML for embedding a video
+	 *
+	 * @param string $raw URL of the video to be embedded
+	 * @param boolean $auto Autoplays the video as soon as its loaded
+	 * @param boolean $echo Should we echo the embed code or just return it
+	 * @return
+	 */
+	public function embed($raw, $auto = FALSE, $echo = TRUE)
+	{
+		$this->set_url($raw);
+		$output = FALSE;
+		
+		// Get video code from url.
+		if (isset($this->service['baseurl']))
+		{
+			$code = str_replace($this->service['baseurl'], "", $this->url);
+		}
+		
+		switch($this->service_name)
+		{
+			case "youtube":
+				// Check for autoplay
+				$you_auto = ($auto) ? "&autoplay=1" : "";
+				
+				$output = '<iframe id="ytplayer" type="text/html" width="320" height="265" '
+					. 'src="//www.youtube.com/embed/'.html::escape($code).'?origin='.urlencode(url::base()).html::escape($you_auto).'" '
+					. 'frameborder="0"></iframe>';
+			break;
+			
+			case "google":
+				// Check for autoplay
+				$google_auto = ($auto) ? "&autoPlay=true" : "";
+				
+				$output = "<embed style='width:320px; height:265px;' id='VideoPlayback' type='application/x-shockwave-flash'"
+					. "	src='//video.google.com/googleplayer.swf?docId=-".html::escape($code.$google_auto)."&hl=en' flashvars=''>"
+					. "</embed>";
+			break;
+			
+			case "metacafe":
+				// Sanitize input
+				$code = strrev(trim(strrev($code), "/"));
+				
+				$output = "<embed src='http://www.metacafe.com/fplayer/".html::escape($code).".swf'"
+					. "	width='320' height='265' wmode='transparent' pluginspage='http://get.adobe.com/flashplayer/'"
+					. "	type='application/x-shockwave-flash'> "
+					. "</embed>";
+			break;
+			
+			case "dotsub":
+				$output = "<iframe src='http://dotsub.com/media/".html::escape($code)."' frameborder='0' width='320' height='500'></iframe>";
+			
+			break;
+			
+			case "vimeo":
+				$vimeo_auto = ($auto) ? "?autoplay=1" : "";
+				
+				$output = '<iframe src="//player.vimeo.com/video/'.html::escape($code.$vimeo_auto).'" width="320" height="265" frameborder="0">'
+					. '</iframe>';
+			break;
+		}
+
+		$data = array($this->service_name, $output);
+		Event::run('ushahidi_filter.video_embed_embed', $data);
+		list($this->service_name, $output) = $data;
+		
+		if (!$output)
+		{
+			$output = '<a href="'.$this->url.'" target="_blank">'.Kohana::lang('ui_main.view_video').'</a>';
+		}
+
+		if ($echo) echo $output;
+
+		return $output;
+	}
+	
+	/**
+	 * Generates the thumbnail a video
+	 *
+	 * @param string $raw URL of the video
+	 * @return string url of video thumbnail
+	 */
+	public function thumbnail($raw)
+	{
+		$this->set_url($raw);
+		$output = FALSE;
+
+		if (isset($this->service['oembed']))
+		{
+
+			$url = $this->service['oembed']."?url=".urlencode($this->url);
+
+			$request = new HttpClient($url);
+			$result = $request->execute();
+
+			if ($result !== FALSE)
+			{
+				$oembed = json_decode($result);
+
+				if (!empty($oembed) AND ! empty($oembed->thumbnail_url))
+				{
+					$output = $oembed->thumbnail_url;
+				}
+			}
+		}
+
+		$data = array($this->service_name, $output);
+		Event::run('ushahidi_filter.video_embed_thumbnail', $data);
+		list($this->service_name, $output) = $data;
+
+		return $output;
+	}
+}
+?>
diff --git a/application/libraries/Wkt.php b/application/libraries/Wkt.php
new file mode 100644
index 0000000..2d39964
--- /dev/null
+++ b/application/libraries/Wkt.php
@@ -0,0 +1,601 @@
+<?php
+/**
+ * PHP Geometry/WKT encoder/decoder
+ *
+ * Mainly inspired/adapted from OpenLayers( http://www.openlayers.org ) 
+ *	 Openlayers/format/WKT.js
+ *
+ * @package		GeoJSON
+ * @subpackage	WKT
+ * @author		Camptocamp <info at camptocamp.com>
+ * @author    	Ushahidi Team <team at ushahidi.com> 
+ * @copyright  	Copyright (c) 2009, Camptocamp <info at camptocamp.com>
+ * @copyright  	Ushahidi - http://www.ushahidi.com
+ * @license    	http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class WKT {
+
+	private $regExes = array(
+		'typeStr'			=> '/^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/',
+		'spaces'			=> '/\s+/',
+		'parenComma'		=> '/\)\s*,\s*\(/',
+		'doubleParenComma'	=> '/\)\s*\)\s*,\s*\(\s*\(/',
+		'trimParens'		=> '/^\s*\(?(.*?)\)?\s*$/'
+	);
+
+	const POINT				= 'point';
+	const MULTIPOINT		= 'multipoint';
+	const LINESTRING		= 'linestring';
+	const MULTILINESTRING	= 'multilinestring';
+	const LINEARRING		= 'linearring';
+	const POLYGON			= 'polygon';
+	const MULTIPOLYGON		= 'multipolygon';
+	const GEOMETRYCOLLECTION= 'geometrycollection';
+
+	/**
+	 * Read WKT string into geometry objects
+	 *
+	 * @param string $WKT A WKT string
+	 *
+	 * @return Geometry|GeometryCollection
+	 */
+	public function read($WKT)
+	{
+		$matches = array();
+		if (!preg_match($this->regExes['typeStr'], $WKT, $matches))
+		{
+			return null;
+		}
+
+		return $this->parse(strtolower($matches[1]), $matches[2]);
+	}
+
+	/**
+	 * Parse WKT string into geometry objects
+	 *
+	 * @param string $WKT A WKT string
+	 *
+	 * @return Geometry|GeometryCollection
+	 */
+	public function parse($type, $str)
+	{
+		$matches = array();
+		$components = array();
+
+		switch ($type)
+		{
+			case self::POINT:
+				$coords = $this->pregExplode('spaces', $str);
+				return new Point($coords[0], $coords[1]);
+
+			case self::MULTIPOINT:
+				foreach (explode(',', trim($str)) as $point)
+				{
+					$components[] = $this->parse(self::POINT, $point);
+				}
+				return new MultiPoint($components);
+
+			case self::LINESTRING:
+				foreach (explode(',', trim($str)) as $point)
+				{
+					$components[] = $this->parse(self::POINT, $point);
+				}
+				return new LineString($components);
+
+			case self::MULTILINESTRING:
+				$lines = $this->pregExplode('parenComma', $str);
+				foreach ($lines as $l)
+				{
+					$line = preg_replace($this->regExes['trimParens'], '$1', $l);
+					$components[] = $this->parse(self::LINESTRING, $line);
+				}
+				return new MultiLineString($components);
+
+			case self::POLYGON:
+				$rings= $this->pregExplode('parenComma', $str);
+				foreach ($rings as $r)
+				{
+					$ring = preg_replace($this->regExes['trimParens'], '$1', $r);
+					$linestring = $this->parse(self::LINESTRING, $ring);
+					$components[] = new LinearRing($linestring->getComponents());
+				}
+				return new Polygon($components);
+
+			case self::MULTIPOLYGON:
+				$polygons = $this->pregExplode('doubleParenComma', $str);
+				foreach ($polygons as $p)
+				{
+					$polygon = preg_replace($this->regExes['trimParens'], '$1', $p);
+					$components[] = $this->parse(self::POLYGON, $polygon);
+				}
+				return new MultiPolygon($components);
+
+			case self::GEOMETRYCOLLECTION:
+				$str = preg_replace('/,\s*([A-Za-z])/', '|$1', $str);
+				$wktArray = explode('|', trim($str));
+				foreach ($wktArray as $wkt)
+				{
+					$components[] = $this->read($wkt);
+				}
+				return new GeometryCollection($components);
+
+			default:
+				return null;
+		}
+	}
+
+	/**
+	 * Split string according to first match of passed regEx index of $regExes
+	 *
+	 */
+	protected function pregExplode($regEx, $str)
+	{
+		$matches = array();
+		preg_match($this->regExes[$regEx], $str, $matches);
+		return empty($matches)?array(trim($str)):explode($matches[0], trim($str));
+	}
+
+	/**
+	 * Serialize geometries into a WKT string.
+	 *
+	 * @param Geometry $geometry
+	 *
+	 * @return string The WKT string representation of the input geometries
+	 */
+	public function write(Geometry $geometry)
+	{
+		$type = strtolower(get_class($geometry));
+
+		if (is_null($data = $this->extract($geometry)))
+		{
+			return null;
+		}
+
+		return strtoupper($type).'('.$data.')';
+	}
+
+	/**
+	 * Extract geometry to a WKT string
+	 *
+	 * @param Geometry $geometry A Geometry object
+	 *
+	 * @return strin
+	 */
+	public function extract(Geometry $geometry)
+	{
+		$array = array();
+		switch (strtolower(get_class($geometry)))
+		{
+			case self::POINT:
+				return $geometry->getX().' '.$geometry->getY();
+			case self::MULTIPOINT:
+			case self::LINESTRING:
+			case self::LINEARRING:
+				foreach ($geometry as $geom)
+				{
+					$array[] = $this->extract($geom);
+				}
+				return implode(',', $array);
+			case self::MULTILINESTRING:
+			case self::POLYGON:
+			case self::MULTIPOLYGON:
+				foreach ($geometry as $geom)
+				{
+					$array[] = '('.$this->extract($geom).')';
+				}
+				return implode(',', $array);
+			case self::GEOMETRYCOLLECTION:
+				foreach ($geometry as $geom)
+				{
+					$array[] = strtoupper(get_class($geom)).'('.$this->extract($geom).')';
+				}
+				return implode(',', $array);
+			default:
+				return null;
+		}
+	}
+
+	/**
+	 * Loads a WKT string into a Geometry Object
+	 *
+	 * @param string $WKT
+	 *
+	 * @return	Geometry
+	 */
+	static public function load($WKT)
+	{
+		$instance = new self;
+		return $instance->read($WKT);
+	}
+
+	/**
+	 * Dumps a Geometry Object into a	 WKT string
+	 *
+	 * @param Geometry $geometry
+	 *
+	 * @return String A WKT string corresponding to passed object
+	 */
+	static public function dump(Geometry $geometry)
+	{
+		$instance = new self;
+		return $instance->write($geometry);
+	}
+
+	/**
+	 * Helper function to recusively collapse points to lat,lon strings
+	 *
+	 * @param Array $point
+	 *
+	 * @return String Latitude,Longitude string
+	 */
+	public static function collapse_points(&$item, $key) 
+	{
+		if (is_array($item[0]))
+		{
+			array_walk($item, 'self::collapse_points');
+		}
+		else
+		{
+			$item = $item[1].','.$item[0];
+		}
+	}
+
+	/**
+	 * Helper to flatten arrays to single dimension
+	 * Reference: http://www.robpeck.com/2010/06/diffing-flattening-and-expanding-multidimensional-arrays-in-php/
+	 *
+	 * @param Array $coordinates
+	 *
+	 * @return Array $flatten_coordinates
+	 */
+	public static function flatten(array $array) 
+	{
+		$return = array();
+	
+		if (is_array($array)) 
+		{
+			foreach ($array as $v) 
+			{
+				if (is_array($v)) 
+				{
+					$tmp_array = self::flatten($v);
+					$return = array_merge($return, $tmp_array);
+				} else {
+					$return[] = $v;
+				}
+			}
+		}
+
+		return $return;	
+	}
+}
+
+abstract class Geometry 
+{
+	protected $geom_type;
+
+	abstract public function getCoordinates();
+	
+	/**
+	 * Accessor for the geometry type
+	 *
+	 * @return string The Geometry type.
+	 */
+	public function getGeomType()
+	{
+		return $this->geom_type;
+	}
+
+	/**
+	 * Returns an array suitable for serialization
+	 *
+	 * @return array
+	 */
+	public function getGeoInterface() 
+	{
+		return array(
+			'type'=> $this->getGeomType(),
+			'coordinates'=> $this->getCoordinates()
+		);
+	}
+
+	/**
+	 * Shortcut to dump geometry as GeoJSON
+	 *
+	 * @return string The GeoJSON representation of the geometry
+	 */
+	public function __toString()
+	{
+		return $this->toGeoJSON();
+	}
+
+	/**
+	 * Dumps Geometry as GeoJSON
+	 *
+	 * @return string The GeoJSON representation of the geometry
+	 */
+	public function toGeoJSON()
+	{
+		return json_encode($this->getGeoInterface());
+	}
+}
+
+abstract class Collection extends Geometry implements Iterator
+{
+	protected $components = array();
+
+	/**
+	 * Constructor
+	 *
+	 * @param array $components The components array
+	 */
+	public function __construct(array $components)
+	{
+		foreach ($components as $component)
+		{
+			$this->add($component);
+		}
+	}
+
+	private function add($component)
+	{
+		$this->components[] = $component;
+	}
+
+	/**
+	 * An accessor method which recursively calls itself to build the coordinates array
+	 *
+	 * @return array The coordinates array
+	 */
+	public function getCoordinates()
+	{
+		$coordinates = array();
+		foreach ($this->components as $component)
+		{
+			$coordinates[] = $component->getCoordinates();
+		}
+		return $coordinates;
+	}
+
+	/**
+	 * Returns Colection components
+	 *
+	 * @return array
+	 */
+	public function getComponents()
+	{
+		return $this->components;
+	}
+
+	# Iterator Interface functions
+
+	public function rewind()
+	{
+		reset($this->components);
+	}
+
+	public function current()
+	{
+		return current($this->components);
+	}
+
+	public function key()
+	{
+		return key($this->components);
+	}
+
+	public function next()
+	{
+		return next($this->components);
+	}
+
+	public function valid()
+	{
+		return $this->current() !== false;
+	}
+}
+
+class GeometryCollection extends Collection 
+{
+	protected $geom_type = 'GeometryCollection';
+	
+	/**
+	 * Constructor
+	 *
+	 * @param array $geometries The Geometries array
+	 */
+	public function __construct(array $geometries = null) 
+	{
+		parent::__construct($geometries);
+	}
+
+	/**
+	 * Returns an array suitable for serialization
+	 *
+	 * Overrides the one defined in parent class
+	 *
+	 * @return array
+	 */
+	public function getGeoInterface() 
+	{
+		$geometries = array();
+		foreach ($this->components as $geometry) 
+		{
+			$geometries[] = $geometry->getGeoInterface();
+		}
+		return array(
+			'type' => $this->getGeomType(),
+			'geometries' => $geometries
+		);
+	}
+}
+
+class Point extends Geometry
+{
+	private $position = array(2);
+
+	protected $geom_type = 'Point';
+
+	/**
+	 * Constructor
+	 *
+	 * @param float $x The x coordinate (or longitude)
+	 * @param float $y The y coordinate (or latitude)
+	 */
+	public function __construct($x, $y)
+	{
+		if (!is_numeric($x) || !is_numeric($y))
+		{
+			throw new Exception("Bad coordinates: x and y should be numeric");
+		}
+		$this->position = array($x, $y);
+	}
+
+	/**
+	 * An accessor method which returns the coordinates array
+	 *
+	 * @return array The coordinates array
+	 */
+	public function getCoordinates()
+	{
+		return $this->position;
+	}
+
+	/**
+	 * Returns X coordinate of the point
+	 *
+	 * @return integer The X coordinate
+	 */
+	public function getX()
+	{
+		return $this->position[0];
+	}
+
+	/**
+	 * Returns X coordinate of the point
+	 *
+	 * @return integer The X coordinate
+	 */
+	public function getY()
+	{
+		return $this->position[1];
+	}
+}
+
+class LineString extends Collection 
+{
+	protected $geom_type = 'LineString';
+	
+	/**
+	 * Constructor
+	 *
+	 * @param array $positions The Point array
+	 */
+	public function __construct(array $positions) 
+	{
+		if (count($positions) > 1)
+		{
+			parent::__construct($positions);
+		}
+		else
+		{
+			throw new Exception("Linestring with less than two points");
+		}
+	}
+}
+
+class LinearRing extends LineString
+{
+	protected $geom_type = 'LinearRing';
+
+	/**
+	 * Constructor
+	 *
+	 * @param array $positions The Point array
+	 */
+	public function __construct(array $positions)
+	{
+		if (count($positions) > 1)
+		{
+			parent::__construct($positions);
+		}
+		else
+		{
+			throw new Exception("Linestring with less than two points");
+		}
+	}
+}
+
+class Polygon extends Collection 
+{
+	protected $geom_type = 'Polygon';
+	
+	/**
+	 * Constructor
+	 *
+	 * The first linestring is the outer ring
+	 * The subsequent ones are holes
+	 * All linestrings should be linearrings
+	 *
+	 * @param array $linestrings The LineString array
+	 */
+	public function __construct(array $linestrings) 
+	{
+		// the GeoJSON spec (http://geojson.org/geojson-spec.html) says nothing about linestring count. 
+		// What should we do ?
+		if (count($linestrings) > 0) 
+		{
+			parent::__construct($linestrings);
+		}
+		else
+		{
+			throw new Exception("Polygon without an exterior ring");
+		}
+	}
+}
+
+class MultiPoint extends Collection 
+{
+	protected $geom_type = 'MultiPoint';
+	
+	/**
+	 * Constructor
+	 *
+	 * @param array $points The Point array
+	 */
+	public function __construct(array $points) 
+	{
+		parent::__construct($points);
+	}
+}
+
+class MultiLineString extends Collection 
+{
+	protected $geom_type = 'MultiLineString';
+	
+	/**
+	 * Constructor
+	 *
+	 * @param array $linestrings The LineString array
+	 */
+	public function __construct(array $linestrings) 
+	{
+		parent::__construct($linestrings);
+	}
+}
+
+class MultiPolygon extends Collection 
+{
+	protected $geom_type = 'MultiPolygon';
+	
+	/**
+	 * Constructor
+	 *
+	 * @param array $polygons The Polygon array
+	 */
+	public function __construct(array $polygons) 
+	{
+		parent::__construct($polygons);
+	}
+	
+}
\ No newline at end of file
diff --git a/application/libraries/XMLImporter.php b/application/libraries/XMLImporter.php
new file mode 100644
index 0000000..63909de
--- /dev/null
+++ b/application/libraries/XMLImporter.php
@@ -0,0 +1,981 @@
+<?php
+/**
+ * XML Report Importer Library
+ *
+ * Imports reports within XML file referenced by filehandle.
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ *
+ */
+class XMLImporter {
+	
+	/**
+	 * Notices to be passed on successful data import
+	 * @var array
+	 */
+	public $notices = array();
+	
+	/**
+	 * Errors to be passed on failed data import
+	 * @var array
+	 */
+	public $errors = array();
+	
+	/**
+	 * Total number of reports within XML file
+	 * @var int
+	 */		
+	public $totalreports = 0;
+	
+	/**
+	 * Total number of reports successfully imported
+	 * @var int
+	 */
+	public $importedreports = 0;
+	
+	/**
+	 * Total number of categories within XML file
+	 * @var int
+	 */
+	private $totalcategories = 0;
+	
+	/**
+	 * Total number of forms within XML file
+	 * @var int
+	 */
+	private $totalforms = 0;
+	
+	/**
+	 * Allowable database value options
+	 * @var array
+	 */
+	private $allowable = array(0,1);
+	
+	/**
+	 * Categories successfully imported
+	 * @var array
+	 */
+	private $categories_added = array();
+	
+	/**
+	 * Category Translations successfully imported
+	 * @var array
+	 */
+	private $category_translations_added = array();
+	
+	/**
+	 * Forms successfully imported
+	 * @var array
+	 */
+	private $forms_added = array();
+	
+	/**
+	 * Custom fields successfully imported
+	 * @var array
+	 */
+	private $fields_added = array();
+	
+	/**
+	 * Custom field options successfully imported
+	 * @var array
+	 */
+	private $field_options_added = array();
+	
+	/**
+	 * Reports successfully imported
+	 * @var array
+	 */
+	private $incidents_added = array();
+	
+	/**
+	 * Incident persons successfully imported
+	 * @var array
+	 */
+	private $incident_persons_added = array();
+	
+	/**
+	 * Custom form field responses successfully imported
+	 * @var array
+	 */
+	private $incident_responses_added = array();
+	
+	/**
+	 * Incident locations successfully imported
+	 * @var array
+	 */
+	private $locations_added = array();
+	
+	/**
+	 * Incident categories successfully imported
+	 * @var array
+	 */
+	private $incident_categories_added = array();
+	
+	/**
+	 * Incident Media successfully imported
+	 * @var array
+	 */
+	private $incident_media_added = array();
+	
+	/**
+	 * Function to import a report form a row in the CSV file
+	 * @param file $file
+	 * @return bool
+	 */
+	public function import($file)
+	{
+		/* For purposes of checking whether the data we're trying to import already exists */
+		// Pick out existing categories 
+		$this->existing_categories = ORM::factory('category')->select_list('category_title','id');
+		$temp_cat = array(); 
+		foreach ($this->existing_categories as $title => $id)
+		{
+			$temp_cat[utf8::strtoupper($title)] = $id;
+		}
+		$this->existing_categories = $temp_cat;
+		
+		// Pick out existing reports
+		$this->incident_ids = ORM::factory('incident')->select_list('id','id'); 
+	
+		// Pick out existing forms
+		$this->existing_forms = ORM::factory('form')->select_list('form_title', 'id');
+		$temp_forms = array();
+		foreach ($this->existing_forms as $title => $id)
+		{
+			$temp_forms[utf8::strtoupper($title)] = $id;
+		}
+		$this->existing_forms = $temp_forms;
+	
+		// Pick out existing form fields
+		$form_fields = customforms::get_custom_form_fields(FALSE, '', FALSE);
+		$temp_fields = array();
+		foreach ($form_fields as $existing_field)
+		{
+			$field_name = $existing_field['field_name'];
+			$form_id = $existing_field['form_id'];
+			$field_id = $existing_field['field_id'];
+			$temp_fields[utf8::strtoupper($field_name)][$form_id] = $field_id;
+		}
+		$this->existing_fields = $temp_fields;
+		 
+		// For purposes of adding location time
+		$this->time = date("Y-m-d H:i:s",time());
+		
+		// Initialize DOMDocument
+		$xml= new DOMDocument('1.0');
+		
+		// Make sure we're not trying to open an empty file
+		if (@$xml->load($file) !== FALSE)
+		{
+			$depcategories = $xml->getElementsByTagName('categories');
+			$depcustomforms = $xml->getElementsByTagName('custom_forms');
+			$depreports = $xml->getElementsByTagName('reports');
+			
+			if ($depcategories->length == 0 AND $depcustomforms->length == 0 AND $depreports->length == 0)
+			{
+				$this->errors[] = Kohana::lang('import.xml.missing_elements');
+			}
+		
+			// If we're importing categories
+			if( $depcategories->length > 0)
+			{	
+				$categories = $depcategories->item(0);
+				if ($categories->nodeValue != 'There are no categories on this deployment.')
+				{
+					if ($this->import_categories($categories) == false)
+					{
+						// Undo Data Import
+						$this->rollback();
+						return false;
+					}
+				}
+				else
+				{
+					$this->notices[] = Kohana::lang('import.xml.no_categories');
+				}	
+			}
+		
+			// If we're importing custom forms
+			if ($depcustomforms->length > 0)
+			{
+				$customforms = $depcustomforms->item(0);
+				if ($customforms->nodeValue != 'There are no custom forms on this deployment.')
+				{
+					if ($this->import_customforms($customforms) == false)
+					{
+						// Undo Data Import
+						$this->rollback();
+						return FALSE;
+					}
+				}
+				else
+				{
+					$this->notices[] = Kohana::lang('import.xml.no_custom_forms');
+				}	
+			}
+		
+			// If we are importing Reports 
+			if ($depreports->length > 0)
+			{
+				$reports = $depreports->item(0);
+				if ($reports->nodeValue != 'There are no reports on this deployment.')
+				{
+					if ($this->import_reports($reports) == false)
+					{
+						// Undo Data Import
+						$this->rollback();
+						return FALSE;						
+					}
+				}
+				else
+				{
+					$this->notices[] = Kohana::lang('import.xml.no_reports');
+				}
+			}	
+		}
+		
+		// The file we're trying to load is empty
+		else
+		{
+			$this->errors[] = Kohana::lang('import.xml.file_empty');;
+		}
+		
+		// If we have errors, return FALSE, else TRUE
+		return count($this->errors) === 0;
+	}
+	
+	/**
+	 * Import categories via XML
+	 * @param DOMNodeList Object $categories
+	 * @return bool
+	 */
+	public function import_categories($categories)
+	{
+		/* Import individual categories*/
+		foreach ($categories->getElementsByTagName('category') as $category)
+		{
+			// Increment category counter
+			$this->totalcategories++;
+			
+			// Category Title
+			$cat_title = xml::get_node_text($category, 'title');
+			
+			// Category Description
+			$cat_description = xml::get_node_text($category, 'description');
+				
+			// If either the category title or description is not provided
+			if ( ! $cat_title OR  ! $cat_description )
+			{
+				$this->errors[] = Kohana::lang('import.xml.category_error').$this->totalcategories;
+			}
+		
+			// Both category title and descriptions exist
+			else
+			{
+				// If this category does not already exist in the database	
+				if ( ! isset($this->existing_categories[utf8::strtoupper($cat_title)]))
+				{
+					// Get category attributes
+					$cat_color = xml::get_node_text($category, 'color', FALSE);
+					$cat_visible = $category->getAttribute('visible');
+					$cat_trusted = $category->getAttribute('trusted');
+					
+					/* Get other category elements */
+					// Parent Category
+					$cat_parent = xml::get_node_text($category, 'parent');
+					if ($cat_parent)
+					{
+						$parent_id = isset($this->existing_categories[utf8::strtoupper($cat_parent)])
+						 			? $this->existing_categories[utf8::strtoupper($cat_parent)] 
+									: 0;
+					}	 	
+			
+					// Save the Category
+					$new_category = new Category_Model;
+					$new_category->category_title = $cat_title;
+					$new_category->category_description = $cat_description ? $cat_description : NULL;
+					$new_category->parent_id = isset($parent_id) ? $parent_id : 0;
+					$new_category->category_color = $cat_color ? $cat_color : '000000';
+					$new_category->category_visible = ( isset($cat_visible) AND in_array($cat_visible, $this->allowable)) ? $cat_visible : 1;
+					$new_category->category_trusted = ( isset($cat_trusted) AND in_array($cat_trusted, $this->allowable)) ? $cat_trusted : 0;
+					$new_category->category_position = count($this->existing_categories);
+					$new_category->save();
+				
+					// Add this new category to array of existing categories
+					$this->existing_categories[utf8::strtoupper($cat_title)] = $new_category->id;
+					
+					// Also add it to the array of categories added during import
+					$this->categories_added[] = $new_category->id;
+					$this->notices[] = Kohana::lang('import.new_category').html::escape($cat_title);
+				}
+
+				/* Category Translations */
+				$c_translations = $category->getElementsByTagName('translations');
+				
+				// Get the current category's id
+				$cat_id = $this->existing_categories[utf8::strtoupper($cat_title)];
+		
+				// If category translations exist
+				if ($c_translations->length > 0)
+				{
+					$cat_translations = $c_translations->item(0); 	
+					foreach ($cat_translations->getElementsByTagName('translation') as $translation)
+					{
+						// Get Localization
+						$locale = xml::get_node_text($translation,'locale', FALSE);
+						
+						// Does the locale attribute exist in the document? And is it empty?
+						if ($locale)
+						{	
+							// Check if category translation exists for this localization
+							$existing_translations = ORM::factory('category_lang')
+													->where('category_id',$cat_id)
+													->where('locale', $locale)
+													->find_all();
+
+							// If Category translation does not exist, save it
+							if (count($existing_translations) == 0)
+							{
+								// Get category title for this localization
+								$trans_title = xml::get_node_text($translation, 'translation_title');
+								
+								// Category Description
+								$trans_description = xml::get_node_text($translation, 'translation_description');
+								
+								// If we're missing the translated category title
+								if ( ! $trans_title)
+								{
+									$this->notices[] = Kohana::lang('import.xml.translation_title').$this->totalcategories
+														.': '.utf8::strtoupper($locale);
+								}
+								else
+								{
+									// Save Category Translations
+									$cl = new Category_Lang_Model();
+									$cl->locale = $locale;
+									$cl->category_id = $cat_id;
+									$cl->category_title = $trans_title;
+									$cl->category_description = $trans_description ? $trans_description : NULL;
+									$cl->save();
+									
+									// Add this to array of category translations added during import
+									$this->category_translations_added[] = $cl->id;
+									$this->notices[] = Kohana::lang('import.xml.translation_added')
+														.'"'.utf8::strtoupper($locale).'" for '.$cat_title;
+								}
+							}	
+						}
+						
+						// Locale attribute does not exist
+						else
+						{
+							$this->notices[] = Kohana::lang('import.xml.missing_localization').$this->totalcategories;
+						}
+					}	
+				}
+			}
+		}
+		// End individual category import
+		
+		// If we have errors, return FALSE, else TRUE
+		return count($this->errors) === 0;
+	}
+	
+	/**
+	 * Import Custom Forms and their respective form fields via XML
+	 * @param DOMNodeList Object $customforms
+	 * @return bool
+	 */
+	public function import_customforms($customforms)
+	{
+		$forms = $customforms->getElementsByTagName('form');
+		foreach ($forms as $form)
+		{
+			// Increment forms counter
+			$this->totalforms++;
+			$totalfields = 0;
+			
+			// Form Title
+			$title = xml::get_node_text($form, 'title');
+			
+			// If the form title is missing
+			if ( ! $title)
+			{
+				$this->errors[] = Kohana::lang('import.xml.missing_form_title').$this->totalforms;
+			}
+			
+			// Form title exists, proceed
+			else
+			{
+				// If the form does not already exist
+				if ( ! isset($this->existing_forms[utf8::strtoupper($title)]))
+				{
+					// Form Active status
+					$form_active = $form->getAttribute('active');
+
+					// Make sure form status value is allowable
+					$active = (isset($form_active) AND in_array($form_active, $this->allowable))? $form_active : 1;
+					
+					// Form Description
+					$description = xml::get_node_text($form, 'description');
+					
+					// Save it
+					$new_form = new Form_Model();
+					$new_form->form_title = $title;
+					$new_form->form_description = $description ? $description : NULL;
+					$new_form->form_active = $active;
+					$new_form->save();
+
+					// Add new form to array of existing forms
+					$this->existing_forms[utf8::strtoupper($title)] = $new_form->id;
+
+					// Add new form to array of forms added during import
+					$this->forms_added[] = $new_form->id;
+					$this->notices[] = Kohana::lang('import.xml.new_form').'"'.$title.'"';
+				}
+
+				// Form Fields
+				$this_form = $this->existing_forms[utf8::strtoupper($title)];
+				$fields = $form->getElementsByTagName('field');
+				if ($fields->length > 0)
+				{
+					foreach ($fields as $field)
+					{
+						// Increment form fields counter for this form
+						$totalfields++;
+						
+						// Field Name
+						$name = xml::get_node_text($field, 'name');
+					
+						// Field Type
+						$field_type = $field->getAttribute('type');
+						$allowable_types = array(1,2,3,4,5,6,7);
+						
+						// Make sure field_type value is allowable
+						$type = (isset($field_type) AND in_array($field_type, $allowable_types) )? $field_type : NULL;
+						
+						// If field name is missing or field type is null 
+						if (! $name OR ! isset($type))
+						{
+							$this->notices[] = Kohana::lang('import.xml.field_error').'"'.$title.'" : Field #'.$totalfields;
+						}
+						
+						// Field name is provided, proceed
+						else
+						{
+							// If the field does not already exist in this form
+							if ( ! isset($this->existing_fields[utf8::strtoupper($name)][$this_form]))
+							{
+								// Field Required
+								$field_required = $field->getAttribute('required');
+								$required = (isset($field_required) AND in_array($field_required, $this->allowable)) ? $field_required : 0;
+								
+								// Field Publicly Visible
+								$field_visible = $field->getAttribute('visible_by');
+								$public_visible = (isset($field_visible) AND in_array($field_visible, $this->allowable)) ? $field_visible : 0;
+								
+								// Field Publicly submit?
+								$field_submit = $field->getAttribute('submit_by');
+								$public_submit = (isset($field_submit) AND in_array($field_submit, $this->allowable)) ? $field_submit : 0;
+
+								// Field Default
+								$default = xml::get_node_text($field, 'default');
+								$default_values = $default ? $default : NULL;
+								
+								// Make sure we have default values for Radio buttons, Checkboxes and drop down fields
+								// If not provided, don't import this custom field
+								$default_required = array(5, 6, 7);
+								if ( ! isset($default_values) AND in_array($type, $default_required))
+								{
+									$this->notices[] = Kohana::lang('import.xml.field_default').'"'.$title.'" : Field "'.$name.'"';
+								}
+								
+								// Defaults have been provided / Not required
+								else
+								{
+									// Save the form field
+									$new_field = new Form_Field_Model();
+									$new_field->form_id = $this_form;
+									$new_field->field_name = $name;
+									$new_field->field_type = $type;
+									$new_field->field_required = $required;
+									$new_field->field_default = isset($default_values) ? $default_values : NULL;
+									$new_field->field_ispublic_visible  = $public_visible;
+									$new_field->field_ispublic_submit  = $public_submit;
+									$new_field->save();
+									
+									// Add this field to array of existing fields
+									$this->existing_fields[utf8::strtoupper($name)][$this_form] = $new_field->id;
+									
+									// Also add it to array of fields added during import
+									$this->fields_added[] = $new_field->id;
+									$this->notices[] = Kohana::lang('import.xml.new_field').'"'.$name.'"';
+									
+									// Field Options exist?
+									if ($field->hasAttribute('datatype') OR $field->hasAttribute('hidden'))
+									{
+										// Get current field_id
+										$fieldid = $this->existing_fields[utf8::strtoupper($name)][$this_form];
+
+										if ($field->hasAttribute('datatype'))
+										{
+											// Does datatype option already exist for this field?
+											$existing_datatype = ORM::factory('form_field_option')
+																->where('form_field_id', $fieldid)
+																->where('option_name','field_datatype')
+																->find_all();
+											// No, none exists
+											if (count($existing_datatype) == 0)
+											{
+												$datatype = xml::get_node_text($field,'datatype', FALSE);
+												$allowed_types = array('email', 'phonenumber', 'numeric', 'text');
+												$field_datatype = ($datatype AND in_array($datatype, $allowed_types))? $datatype : NULL;
+
+												// If field datatype is not null, save
+												if ($field_datatype != NULL)
+												{
+													$datatype_option = new Form_Field_Option_Model();
+													$this->_save_field_option($datatype_option, $fieldid, 'field_datatype', $field_datatype);
+
+													// Add to array of field options added during import
+													$this->field_options_added[] = $datatype_option->id;
+													$this->notices[] = Kohana::lang('import.xml.field_datatype').'"'.$name.'"';
+												}
+											}								
+										}
+
+										if ($field->hasAttribute('hidden'))
+										{
+											// Does hidden option already exist for this field?
+											$existing_hidden = ORM::factory('form_field_option')
+																->where('form_field_id', $fieldid)
+																->where('option_name','field_hidden')
+																->find_all();
+
+											// No, none exists
+											if (count($existing_hidden) == 0)
+											{
+												$hidden = $field->getAttribute('hidden');
+												$field_hidden = ($hidden != '' AND in_array($hidden, $this->allowable)) ? $hidden : NULL;
+
+												// If field datatype is not null, save
+												if ($field_hidden != NULL)
+												{
+													$hidden_option = new Form_Field_Option_Model();
+													$this->_save_field_option($hidden_option, $fieldid, 'field_hidden', $field_hidden);
+
+													// Add to array of field options added during import
+													$this->field_options_added[] = $hidden_option->id;
+													$this->notices[] = Kohana::lang('import.xml.field_hidden').'"'.$name.'"';
+												}
+											} 
+										}
+										// End field hidden option exists
+									}
+									// End field options exist	
+								}
+								// End defaults provided
+							}
+							// End field does not exist
+						}
+						// End field name provided
+					}
+					// End individual form field import
+				}
+				// End if fields exist
+			}
+			// End form title exists			
+		}
+		// End individual form import
+		
+		// If we have errors, return FALSE, else TRUE
+		return count($this->errors) === 0;
+	}
+	
+	/**
+	 * Import Reports via XML
+	 * @param DOMNodeList Object $report
+	 * @return bool
+	 */
+	public function import_reports($reports)
+	{
+		/* Import individual reports */
+		foreach ($reports->getElementsByTagName('report') as $report)
+		{
+			$this->totalreports++;
+			// Get Report id
+			$report_id = $report->getAttribute('id');
+			
+			// Check if this incident already exists in the db
+			if (isset($report_id) AND isset($this->incident_ids[$report_id]))
+			{
+				$this->notices[] = Kohana::lang('import.incident_exists').$report_id;
+			}
+			
+			// Otherwise, begin import
+			else
+			{
+				/* Step 1: Location information */
+				$locations = $report->getElementsByTagName('location');
+				
+				// If location information has been provided
+				if ($locations->length > 0)
+				{
+					$report_location = $locations->item(0);
+					
+					// Location Name
+					$location_name = xml::get_node_text($report_location, 'name');
+					
+					// Longitude
+					$longitude = xml::get_node_text($report_location, 'longitude');
+					
+					// Latitude
+					$latitude = xml::get_node_text($report_location, 'latitude');
+									
+					if ($location_name)
+					{
+						// For geocoding purposes
+						$location_geocoded = map::geocode($location_name);
+						
+						// Save the location
+						$new_location = new Location_Model();
+						$new_location->location_name = $location_name ? $location_name : NULL;
+						$new_location->location_date = $this->time;
+						
+						// If longitude/latitude values are present
+						if ($latitude AND $longitude)
+						{
+							$new_location->latitude = $latitude ? $latitude: 0;
+							$new_location->longitude = $longitude ? $longitude: 0;
+							
+						}
+						else
+						{
+							// Get geocoded lat/lon values
+							$new_location->latitude = $location_geocoded ? $location_geocoded['latitude'] : $latitude;
+							$new_location->longitude = $location_geocoded ? $location_geocoded['longitude'] : $longitude;
+						} 
+						$new_location->country_id = $location_geocoded ? $location_geocoded['country_id'] : 0;
+						$new_location->save();
+						
+						// Add this location to array of imported locations
+						$this->locations_added[] = $new_location->id;
+					}
+					
+				}
+				
+				/* Step 2: Save Report */
+				// Report Title
+				$report_title = xml::get_node_text($report, 'title');
+				
+				// Report Date
+				$report_date = xml::get_node_text($report, 'date');
+				
+				// Missing report title or report date?
+				if ( ! $report_title OR ! $report_date)
+				{
+					$this->errors[] = Kohana::lang('import.xml.incident_title_date').$this->totalreports;
+				}
+				
+				// If report date is not in the required format
+				if ( ! strtotime($report_date))
+				{
+					$this->errors[] = Kohana::lang('import.incident_date').$this->totalreports.': '.html::escape($report_date);
+				}
+				
+				// Report title and date(in correct format) both provided, proceed
+				else
+				{
+					// Approval status?
+					$approved = $report->getAttribute('approved');
+					$report_approved = (isset($approved) AND in_array($approved, $this->allowable)) ? $approved : 0;
+					
+					// Verified Status?
+					$verified = $report->getAttribute('verified');
+					$report_verified = (isset($verified) AND in_array($verified, $this->allowable)) ? $verified : 0;
+					
+					// Report mode?
+					$allowed_modes = array(1, 2, 3, 4);
+					$mode = $report->getAttribute('mode');
+					$report_mode = (isset($mode) AND in_array($mode, $allowed_modes)) ? $mode : 1;
+					
+					// Report Form
+					$report_form = xml::get_node_text($report,'form_name', FALSE);
+					if ($report_form)
+					{
+						if (! isset($this->existing_forms[utf8::strtoupper($report_form)]))
+						{
+							$this->notices[] = Kohana::lang('import.xml.no_form_exists').$this->totalreports
+												.': "'.$report_form.'"';
+						}
+						
+						$form_id = isset($this->existing_forms[utf8::strtoupper($report_form)])
+						 			? $this->existing_forms[utf8::strtoupper($report_form)] 
+									: 1;
+					}
+					
+					// Report Date added
+					$dateadd = xml::get_node_text($report, 'dateadd');
+					
+					// Report Description
+					$report_description = xml::get_node_text($report, 'description');
+
+					$new_report = new Incident_Model();
+					$new_report->location_id = isset($new_location) ? $new_location->id : 0;
+					$new_report->user_id = 0;
+					$new_report->incident_title = $report_title;
+					$new_report->incident_description = $report_description ? $report_description : '';
+					$new_report->incident_date = date("Y-m-d H:i:s",strtotime($report_date));
+					$new_report->incident_dateadd = ($dateadd AND strtotime($dateadd))? $dateadd : $this->time;
+					$new_report->incident_active = $report_approved;
+					$new_report->incident_verified = $report_verified;
+					$new_report->incident_mode = $report_mode;
+					$new_report->form_id = isset($form_id) ? $form_id : 1;
+					$new_report->save();
+					
+					// Increment imported rows counter
+					$this->importedreports++;
+					
+					// Add this report to array of reports added during import
+					$this->incidents_added[] = $new_report->id;
+					
+					/* Step 3: Save Report Categories*/
+					// Report Categories exist?
+					$reportcategories = $report->getElementsByTagName('report_categories') ;
+					if ($reportcategories->length > 0)
+					{
+						$report_categories = $reportcategories->item(0);
+						
+						foreach($report_categories->getElementsByTagName('category') as $r_category)
+						{
+							$category = trim($r_category->nodeValue);
+							$report_category = (isset($category) AND $category != '') ? $category : '';
+							if ($report_category != '' AND isset($this->existing_categories[utf8::strtoupper($report_category)]))
+							{
+								// Save the incident category
+								$new_incident_category = new Incident_Category_Model();
+								$new_incident_category->incident_id = $new_report->id;
+								$new_incident_category->category_id = $this->existing_categories[utf8::strtoupper($report_category)];
+								$new_incident_category->save();
+								
+								// Add this to array of incident categories added
+								$this->incident_categories_added[] = $new_incident_category->id;
+							}
+							
+							if ($report_category != '' AND ! isset($this->existing_categories[utf8::strtoupper($report_category)]))
+							{
+								$this->notices[] = Kohana::lang('import.xml.no_category_exists').$this->totalreports.': "'.$report_category.'"';
+							}
+						}	
+					}
+						
+					/* Step 4: Save Custom form field responses for this report */
+					// Report Custom Fields
+					$this_form = $new_report->form_id;
+					$reportfields = $report->getElementsByTagName('custom_fields');
+					if ($reportfields->length > 0)
+					{
+						$report_fields = $reportfields->item(0);
+						$custom_fields = $report_fields->getElementsByTagName('field');
+						if ($custom_fields->length > 0)
+						{
+							foreach ($custom_fields as $field)
+							{
+								// Field Name
+								$field_name = $field->hasAttribute('name') ? xml::get_node_text($field, 'name', FALSE) : FALSE;
+								if ($field_name)
+								{
+									// If this field exists in the form listed for this report
+									if(isset($this->existing_fields[utf8::strtoupper($field_name)][$this_form]))
+									{
+										// Get field type and default values
+										$match_field_id = $this->existing_fields[utf8::strtoupper($field_name)][$this_form];
+										
+										// Grab form field object
+										$match_fields = ORM::Factory('form_field', $match_field_id);
+										$match_field_type = $match_fields->field_type;
+										$match_field_defaults = $match_fields->field_default;
+											
+										// Grab form responses
+										$field_response = trim($field->nodeValue);
+										if ($field_response != '')
+										{
+											// Initialize form response model
+											$new_form_response = new Form_Response_Model();
+											$new_form_response->incident_id = $new_report->id;
+											$new_form_response->form_field_id = $match_field_id;
+												
+											// For radio buttons, checkbox fields and drop downs, make sure form responses are
+											// within bounds of allowable options for that field
+											// Split field defaults into individual values
+											$field_defaults = explode(',',$match_field_defaults);
+												
+											/* Radio buttons and Drop down fields which take single responses */
+											if ($match_field_type == 5 OR $match_field_type == 7)
+											{
+												foreach ($field_defaults as $match_field_default)
+												{
+													// Carry out a case insensitive string comparison
+													$new_form_response->form_response = strcasecmp($match_field_default, $field_response) == 0
+																						? $match_field_default 
+																						: NULL;
+												}
+											}
+												
+											// Checkboxes which 
+											if ($match_field_type == 6)
+											{
+												// Split user responses into individual value
+												$responses = explode(',', $field_response);
+												$values = array();
+												foreach ($match_field_defaults as $match_field_default)
+												{
+													foreach ($responses as $response)
+													{
+														$values[] = strcasecmp($match_field_default, $response) == 0
+														 			? $match_field_default 
+																	: NULL;
+													}
+												}
+													
+												// Concatenate checkbox values into a string, separated by a comma
+												$new_form_response->form_response = implode(",", $values);
+											}
+											
+											// For all other fields
+											else
+											{
+												$new_form_response->form_response = $field_response;
+											}
+												
+											// Only save if form response is not empty
+											if ($new_form_response->form_response != NULL)
+											{
+												$new_form_response->save();
+											}
+												
+											// Add this to array of form responses added
+											$this->incident_responses_added[] = $new_form_response->id;	
+										}								
+									}
+									else
+									{
+										$this->notices[] = Kohana::lang('import.xml.form_field_no_match')
+															.$this->totalreports.': "'.$field_name.'" on form "'.$new_report->form->form_title.'"';
+									}
+								}
+							}
+						}	
+					}
+				
+					
+					/* Step 5: Save incident persons for this report */
+					// Report Personal Information
+					$personal_info = $report->getElementsByTagName('personal_info');
+					
+					// If personal info exists
+					if ($personal_info->length > 0)
+					{
+						$report_info = $personal_info->item(0);
+
+						// First Name
+						$firstname = xml::get_node_text($report_info, 'first_name');
+
+						// Last Name
+						$lastname = xml::get_node_text($report_info, 'last_name');
+
+						// Email
+						$r_email = xml::get_node_text($report_info, 'email');	
+						$email = ($r_email AND valid::email($r_email)) ? $r_email : NULL;
+
+						$new_incident_person = new Incident_Person_Model();
+						$new_incident_person->incident_id = $new_report->id;
+						$new_incident_person->person_date = $new_report->incident_dateadd;
+
+						// Make sure that at least one of the personal info field entries is provided
+						if ($firstname OR $lastname OR $email != NULL)
+						{
+							$new_incident_person->person_first = $firstname ? $firstname: NULL;
+							$new_incident_person->person_last = $lastname ? $firstname: NULL;
+							$new_incident_person->person_email = $email;
+							$new_incident_person->save();
+
+							// Add this to array of incident persons added during import
+							$this->incident_persons_added[] = $new_incident_person->id;
+						}	
+					}			
+
+
+					/* Step 6: Save media links for this report */
+					// Report Media
+					$media = $report->getElementsByTagName('media') ;
+					if ($media->length > 0)
+					{
+						$media = $media->item(0);
+
+						foreach($media->getElementsByTagName('item') as $media_element)
+						{
+							$media_link = trim($media_element->nodeValue);
+							$media_date = $media_element->getAttribute('date');
+							if ( ! empty($media_link))
+							{
+								$media_item = new Media_Model();
+								$media_item->location_id = isset($new_location) ? $new_location->id : 0;
+								$media_item->incident_id = $new_report->id;
+								$media_item->media_type = $media_element->getAttribute('type');
+								$media_item->media_link = $media_link;
+								$media_item->media_date = ! empty($media_date) ? $media_date : $new_report->incident_date;
+								$media_item->save();
+							}
+						}
+					}
+				}
+			}
+		}
+		// end individual report import
+		
+		// If we have errors, return FALSE, else TRUE
+		return count($this->errors) === 0;
+	}
+	
+	/**
+	 * Function to undo import of data
+	 */
+	private function rollback()
+	{
+		if (count($this->categories_added)) ORM::factory('category')->delete_all($this->categories_added);
+		if (count($this->category_translations_added)) ORM::factory('category_lang')->delete_all($this->category_translations_added);
+		if (count($this->forms_added)) ORM::factory('form')->delete_all($this->forms_added);
+		if (count($this->fields_added)) ORM::factory('form_field')->delete_all($this->fields_added);
+		if (count($this->field_options_added)) ORM::factory('form_field_option')->delete_all($this->field_options_added);
+		if (count($this->incidents_added)) ORM::factory('incident')->delete_all($this->incidents_added);
+		if (count($this->locations_added)) ORM::factory('location')->delete_all($this->locations_added);
+		if (count($this->incident_categories_added)) ORM::factory('incident_category')->delete_all($this->incident_categories_added);
+		if (count($this->incident_persons_added)) ORM::factory('incident_person')->delete_all($this->incident_persons_added);
+		if (count($this->incident_responses_added)) ORM::factory('form_response')->delete_all($this->incident_responses_added);
+	}
+	
+	/**
+	 * Function to save form field options
+	 * @param ORM instance $model Form Field Option Model instance
+	 * @param int $fieldid Form Field id
+	 * @param string $option_name Either field_datatype or field_hidden
+	 * @param mixed $option_value Dependent on whether its datatype/hidden option
+ 	 *
+	 */
+	private function _save_field_option($model, $fieldid, $option_name, $option_value)
+	{
+		$model->form_field_id = $fieldid;
+		$model->option_name = $option_name;
+		$model->option_value = $option_value;
+		$model->save();
+	}
+}
+?>
diff --git a/application/libraries/api/Api_Object.php b/application/libraries/api/Api_Object.php
new file mode 100755
index 0000000..fd3256a
--- /dev/null
+++ b/application/libraries/api/Api_Object.php
@@ -0,0 +1,456 @@
+<?php defined('SYSPATH') or die('No direct script access allowed');
+/**
+ * Api_Object
+ *
+ * Base abstract class for all API library implementations.
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+abstract class Api_Object_Core {
+	
+	/**
+	 * Database object for processing queries
+	 * @var Database
+	 */
+	protected $db;
+	/**
+	 * Prefix for the database tables
+	 * @var string
+	 */
+	protected $table_prefix;
+	
+	/**
+	 * Form validation error messages
+	 * @var array
+	 */
+	protected $messages = array();
+	
+	/**
+	 * API validation error message
+	 * @var mixed
+	 */
+	protected $error_messages = '';
+	
+	/**
+	 * Domain name of the URL accessing the API
+	 * @var string
+	 */
+	protected $domain;
+	
+	/**
+	 * HTTP POST and/or GET data submitted via the API
+	 * @var array
+	 */
+	protected $request = array();
+	
+	/**
+	 * Format in which the data is to be returned to the client - defaults to JSON
+	 * if none has been specified
+	 * @var string
+	 */
+	protected $response_type;
+	
+	/**
+	 * Response data to be returned to the client
+	 * @var string
+	 */
+	protected $response_data;
+	
+	/**
+	 * Maximum number of records that can be returned by a single API request
+	 * @var int
+	 */
+	protected $list_limit;
+	
+	/**
+	 * Api_Service object
+	 * @var Api_Service
+	 */
+	protected $api_service;
+	
+	/**
+	 * SQL query for fetching teh requested data
+	 * @var string
+	 */
+	protected $query;
+	
+	/**
+	 * Assists in proper XML generation
+	 * @todo Review the value of having this has a class property
+	 * @var mixed
+	 */
+	protected $replar;
+	
+	/**
+	 * Attribute to be used for fetching the requested data
+	 * @var string
+	 */
+	protected $by;
+	
+	/**
+	 * Database ID of the requested item
+	 * @var int
+	 */
+	protected $id;
+	
+	/**
+	 * No. of records returned by the API request
+	 * @var int
+	 */
+	protected $record_count;
+	
+	/**
+	 * Api_Settings_Model object
+	 * @var Api_Settings_Model
+	 */
+	private $api_settings;
+
+	public function __construct($api_service)
+	{
+		$this->db = new Database();
+
+		$this->api_settings = new Api_Settings_Model(1);
+
+		// Set the list limit
+		$this->list_limit = ((int) $this->api_settings->default_record_limit > 0) 
+				? $this->api_settings->default_record_limit
+				: (int) Kohana::config('settings.items_per_api_request');
+
+		$this->domain = url::base();
+		$this->record_count = 1;
+		$this->table_prefix = Kohana::config('database.default.table_prefix');
+		$this->api_service = $api_service;
+
+		// Check if the response type for the API service has already been set
+		if ( ! is_null($api_service->get_response_type()))
+		{
+			$this->request = $api_service->get_request();
+			$this->response_type = $api_service->get_response_type();
+		}
+		else
+		{
+			$this->set_request($api_service->get_request());
+		}
+	}
+
+	/**
+	 * Sets the request and determines the format in which the request data is
+	 * to be returned to the client
+	 */
+	public function set_request($request)
+	{
+		$this->request = $request;
+
+		// Determine the response type
+		if ( ! $this->api_service->verify_array_index($request, 'resp'))
+		{
+			$this->set_response_type('json');
+		}
+		else
+		{
+			$this->set_response_type($request['resp']);
+		}
+	}
+
+	/**
+	 * Gets the response type
+	 *
+	 * @return string
+	 */
+	public function get_response_type()
+	{
+		return $this->response_type;
+	}
+
+	/**
+	 * Sets the response type
+	 *
+	 * @param $type Type of response for the output data
+	 */
+	public function set_response_type($type)
+	{
+		// Set the response type for the API library object
+		$this->response_type = $type;
+
+		// Set the response type for the API service
+		$this->api_service->set_response_type($type);
+	}
+
+	/**
+	 * Gets the response data 
+	 * If the error message has already been set, the error is returned instead
+	 *
+	 * @return mixed The data fetched by the API request
+	 */
+	public function get_response_data()
+	{
+		return (isset($this->error_message)) 
+				? $this->error_message
+				: $this->response_data;
+	}
+
+	/**
+	 * Gets the number of records fetched by the API library. If the error message
+	 * property has been set, a zero (0) will always be returned
+	 *
+	 * @return int The number of records returned by the API request
+	 */
+	public function get_record_count()
+	{
+		if ($_SERVER['REQUEST_METHOD'] == 'GET') // For get methods, more than one record may be returned
+		{
+			return ((int) $this->record_count > 0 AND !isset($this->error_message)) 
+					? $this->record_count
+					: 0;
+		}
+		elseif ($_SERVER['REQUEST_METHOD'] == 'POST') // For post, only 1 record can be can worked on
+		{
+			return $this->record_count;
+		}
+	}
+
+	/**
+	 * Sets the error message
+	 *
+	 * @param string $error_message Error message for the Api request
+	 */
+	public function set_error_message($error_message)
+	{
+		if (is_array($error_message))
+		{
+			$this->error_message = ($this->response_type == 'json') 
+					? $this->array_as_json($error_message)
+					: $this->array_as_xml($error_message);
+		}
+		else
+		{
+			$this->error_message = $error_message;
+		}
+	}
+
+	/**
+	 * Abstract method that must be implemented by all subclasses
+	 * It is this method that services the API request
+	 */
+	abstract public function perform_task();
+
+	/**
+	 * Sets the list limit for the maximum no. of records to be fetched. The value
+	 * of @param $limit must be numeric else the list limit is set to a default
+	 * value of 20
+	 *
+	 * @param $limit Numerical value specifying the maximium no. of records to be fetched
+	 */
+	protected function set_list_limit($limit)
+	{
+		// Check if the specified limit (@patam limit) is more than the specified system setting
+		if ((isset($this->api_settings->max_record_limit)))
+		{
+			if ((int) $limit > $this->api_settings->max_record_limit)
+			{
+				// Limit exceeds maximum therefore scale it down to the allowed maximum
+				$limit = $this->api_settings->max_record_limit;
+			}
+		}
+
+		// Check if the specified limit is
+		// Set the list limit
+		$this->list_limit = (intval($limit) > 0) ? intval($limit) : $this->list_limit;
+	}
+
+	/**
+	 * Response
+	 * 
+	 * @param int ret_value
+	 * @param string response_type = XML or JSON
+	 * @param string error_message - The error message to display
+	 * 
+	 * @return string
+	 */
+	protected function response($ret_value, $error_messages='')
+	{
+		$response = array();
+
+		// Set the record count to zero where the value of @param ret_val <> 0
+		$this->record_count = ($ret_value != 0) ? 0 : 1;
+
+		if ($ret_value == 0)
+		{
+			$response = array(
+				"payload" => array(
+					"domain" => $this->domain,
+					"success" => "true"
+				),
+				"error" => $this->api_service->get_error_msg(0)
+			);
+		}
+		elseif ($ret_value == 1)
+		{
+			$response = array(
+				"payload" => array(
+					"domain" => $this->domain,
+					"success" => "false"
+				),
+				"error" => $this->api_service->get_error_msg(003, '', $error_messages)
+			);
+		}
+		elseif ($ret_value == 2)
+		{
+			// Authentication Failed. Invalid User or App Key
+			$response = array(
+				"payload" => array("domain" => $this->domain, "success" =>
+					"false"),
+				"error" => $this->api_service->get_error_msg(005)
+			);
+		}
+		elseif ($ret_value == 4)
+		{
+			// No results got from the database query
+			$response = array(
+				"payload" => array(
+					"domain" => $this->domain,
+					"success" => "true"
+				),
+				"error" => $this->api_service->get_error_msg(007)
+			);
+		}
+		else
+		{
+			$response = array(
+				"payload" => array(
+					"domain" => $this->domain,
+					"success" => "false"
+				),
+				"error" => $this->api_service->get_error_msg(004)
+			);
+		}
+
+		return ($this->response_type == 'json')
+			? $this->array_as_json($response)
+			: $this->array_as_xml($response, array());
+	}
+
+	/**
+	 * Creates a JSON response given an array.
+	 *
+	 * @param array $data Array to be converted to JSON
+	 * @return string JSON representation of the data in @param $array
+	 */
+	protected function array_as_json($data)
+	{
+		return json_encode($data);
+	}
+
+	/**
+	 * Converts an object to an array.
+	 *
+	 * @param object $object The object to be converted into array.
+	 * @return array Array representation of the object
+	 */
+	protected function object_to_array($object)
+	{
+		$array = array();
+		if (is_object($object))
+		{
+			foreach ($object as $key => $value)
+			{
+				$array[$key] = $value;
+			}
+		}
+		else
+		{
+			$array = $object;
+		}
+		return $array;
+	}
+
+	/**
+	 * Creates a XML response given an array
+	 * CREDIT TO: http://snippets.dzone.com/posts/show/3391
+	 *
+	 * @param  XMLWriter xml - the XMLWriter object.
+	 * @param array data - the data to be formatted into XML.
+	 * @param string replar - the replar
+	 */
+	protected function write(XMLWriter $xml, $data, $replar = "")
+	{
+
+		foreach ($data as $key => $value)
+		{
+			if (is_a($value, 'stdClass'))
+			{
+				$value = $this->object_to_array($value);
+			}
+
+			if (is_array($value))
+			{
+
+				$toprint = true;
+
+				if (in_array($key, $replar))
+				{
+					//move up one level
+					$keys = array_keys($value);
+					$key = $keys[0];
+					$value = $value[$key];
+				}
+
+				$xml->startElement($key);
+				$this->write($xml, $value, $replar);
+				$xml->endElement();
+
+				continue;
+			}
+
+			$xml->writeElement($key, $value);
+		}
+	}
+
+	/**
+	 * Creates a XML response given an array
+	 * CREDIT TO: http://snippets.dzone.com/posts/show/3391
+	 *
+	 * @param string data - The data to be formatted as XML
+	 * @param array replar - The replar.
+	 *
+	 * @return object xml - The formatted XML.
+	 */
+	protected function array_as_xml($data, $replar = array())
+	{
+		$xml = new XMLWriter();
+		$xml->openMemory();
+		$xml->startDocument('1.0', 'UTF-8');
+		$xml->startElement('response');
+
+		$this->write($xml, $data, $replar);
+
+		$xml->endElement();
+		return $xml->outputMemory(true);
+	}
+
+	/**
+	 * Check the id value receieved from the URL is of the right datatype
+	 * int
+	 *
+	 * @param int id - The ID value
+	 *
+	 * @return int
+	 */
+	protected function check_id_value($id)
+	{
+		// The id value must be positive and non-zero
+		$this->id = (preg_match('/^[1-9](\d*)$/', $id) > 0) ? (int) $id : 0;
+
+		return $this->id;
+    }
+    
+}
+
+?>
diff --git a/application/libraries/api/MY_Admin_Category_Api_Object.php b/application/libraries/api/MY_Admin_Category_Api_Object.php
new file mode 100755
index 0000000..d09ca89
--- /dev/null
+++ b/application/libraries/api/MY_Admin_Category_Api_Object.php
@@ -0,0 +1,484 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles GET request for KML via the API.
+ *
+ * @version 25 - Emmanuel Kala 2010-10-26
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Admin_Category_Api_Object extends Api_Object_Core {
+
+	public function __construct($api_service)
+	{
+		parent::__construct($api_service);
+	}
+
+	/**
+	 * API service request handler
+	 */
+	public function perform_task()
+	{
+	}
+
+	/**
+	 * Handles admin category actions performed via the API service
+	 */
+	public function category_action()
+	{
+		$action = '';
+		// Will hold the report action
+
+		// Authenticate the user
+		if (!$this->api_service->_login(TRUE))
+		{
+			$this->set_error_message($this->response(2));
+			return;
+		}
+
+		//Check if the action has been specified
+		if (!$this->api_service->verify_array_index($this->request, 'action'))
+		{
+			$this->set_error_message(array("error" => $this->api_service->get_error_msg(001, 'action')));
+
+			return;
+		}
+		else
+		{
+			$action = $this->request['action'];
+		}
+
+		// Route category actions to their various handlers
+		switch ($action)
+		{
+			// Delete category
+			case "delete" :
+			// Check if the category id has been specified
+				if (!$this->api_service->verify_array_index($this->request, 'category_id'))
+				{
+					$this->set_error_message(array("error" => $this->api_service->get_error_msg(001, 'category_id')));
+					return;
+				}
+				else
+				{
+					$this->_del_category();
+				}
+				break;
+
+			// Edit category
+			case "edit" :
+			// Check if the category id has been specified
+				if (!$this->api_service->verify_array_index($this->request, 'category_id'))
+				{
+					$this->set_error_message(array("error" => $this->api_service->get_error_msg(001, 'category_id')));
+					return;
+				}
+				else
+				{
+					$this->_edit_category();
+				}
+				break;
+
+			//Add category
+			case "add" :
+				$this->_add_category();
+				break;
+
+			default :
+				$this->set_error_message(array("error" => $this->api_service->get_error_msg(002)));
+		}
+	}
+
+	/**
+	 * Add new category
+	 *
+	 * @param string response_type - XML or JSON
+	 *
+	 * @return Array
+	 */
+	private function _add_category()
+	{
+		$ret_value = $this->_submit_categories();
+
+		$this->response_data = $this->response($ret_value, $this->error_messages);
+	}
+
+	/**
+	 * Edit existing category
+	 *
+	 * @param string response_type - XML or JSON
+	 * authenticated
+	 *
+	 * @return array
+	 */
+	private function _edit_category()
+	{
+		// Setup and initialize form field names
+		$form = array('category_id' => '', 'parent_id' => '', 'category_title' => '', 'category_description' => '', 'category_color' => '', 'category_image' => '');
+
+		// Copy the form as errors, so the errors will be stored
+		// with keys corresponding to the form field names
+		$errors = $form;
+		$parents_array = array();
+
+		$ret_value = 0;
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't
+			//overwrite $_POST fields with our own things
+			$post = Validation::factory(array_merge($_POST, $_FILES));
+
+			//  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list
+			//of checks, carried out in order
+			$post->add_rules('parent_id', 'required', 'numeric');
+			$post->add_rules('category_title', 'required', 'length[3,80]');
+			$post->add_rules('category_description', 'required');
+			$post->add_rules('category_color', 'required', 'length[6,6]');
+			$post->add_rules('category_image', 'upload::valid', 'upload::type[gif,jpg,png]', 'upload::size[50K]');
+			$post->add_callbacks('parent_id', array($this, 'parent_id_chk'));
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+
+				// Update Action
+				$category_id = $post->category_id;
+				$category = new Category_Model($category_id);
+				$category->parent_id = $post->parent_id;
+				$category->category_title = $post->category_title;
+				$category->category_description = $post->category_description;
+				$category->category_color = $post->category_color;
+				$category->save();
+
+				// Optional
+				if (!empty($post->category_image))
+				{
+					// Upload Image/Icon
+					$filename = upload::save('category_image');
+
+					if ($filename)
+					{
+						$new_filename = "category_" . $category->id . "_" . time();
+
+						// Resize Image to 32px if greater
+						Image::factory($filename)->resize(32, 32, Image::HEIGHT)->save(Kohana::config('upload.directory', TRUE) . $new_filename . ".png");
+
+						// Remove the temporary file
+						unlink($filename);
+
+						// Delete Old Image
+						$category_old_image = $category->category_image;
+
+						if (!empty($category_old_image) AND file_exists(Kohana::config('upload.directory', TRUE) . $category_old_image))
+						{
+							unlink(Kohana::config('upload.directory', TRUE) . $category_old_image);
+						}
+
+						// Save
+						$category->category_image = $new_filename . ".png";
+						$category->save();
+
+					}
+				}
+			}
+			// No! We have validation errors, we need to show the form
+			// again, with the errors
+			else
+			{
+				// Populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('category'));
+
+				foreach ($errors as $error_item => $error_description)
+				{
+					if (!is_array($error_description))
+					{
+						$this->error_messages .= $error_description;
+
+						if ($error_description != end($errors))
+						{
+							$this->error_messages .= " - ";
+						}
+					}
+				}
+
+				$ret_value = 1;
+				// Validation error
+			}
+
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		// Set the response data
+		$this->response_data = $this->response($ret_value, $this->error_messages);
+	}
+
+	/**
+	 * Delete existing category
+	 *
+	 * @param string response_type - XML or JSON
+	 *
+	 * @return string
+	 */
+	private function _del_category()
+	{
+		// setup and initialize form field names
+		$form = array('category_id' => '', );
+
+		// copy the form as errors, so the errors will be stored
+		//with keys corresponding to the form field names
+		$errors = $form;
+
+		$ret_value = 0;
+
+		// Check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't
+			//overwrite $_POST fields with our own things
+			$post = Validation::factory(array_merge($_POST, $_FILES));
+
+			//  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list
+			//of checks, carried out in order
+			$post->add_rules('category_id', 'required', 'numeric');
+
+			// Test to see if things passed the rule checks
+			if ($post->validate())
+			{
+				$category_id = $post->category_id;
+				$category = new Category_Model($category_id);
+				$category->delete($category_id);
+
+			}
+			else
+			{
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('category'));
+
+				foreach ($errors as $error_item => $error_description)
+				{
+					if (!is_array($error_description))
+					{
+						$this->error_messages .= $error_description;
+
+						if ($error_description != end($errors))
+						{
+							$this->error_messages .= " - ";
+						}
+					}
+				}
+
+				$ret_value = 1;
+				// validation error
+
+			}
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		// Set the response data
+		$this->response_data = $this->response($ret_value, $this->error_messages);
+
+	}
+
+	/**
+	 * Checks if parent_id for this category exists
+	 * @param Validation $post $_POST variable with validation rules
+	 */
+	public function parent_id_chk(Validation $post)
+	{
+		// If add->rules validation found any errors, get me out of here!
+		if (array_key_exists('parent_id', $post->errors()))
+			return;
+
+		if (isset($category_id))
+		{
+			$category_id = $post->category_id;
+		}
+		
+		$parent_id = $post->parent_id;
+
+		// This is a parent category - exit
+		if ($parent_id == 0)
+			return;
+
+		$parent_exists = ORM::factory('category')->find($parent_id);
+
+		if (!$parent_exists->loaded)
+		{
+			// Parent Category Doesn't Exist
+			$post->add_error('parent_id', 'exists');
+		}
+		
+		else
+		{
+			// Parent category is special
+			if($parent_exists->category_trusted == 1)
+			{
+				$post->add_error('parent_id', 'parent_trusted');
+			}
+		
+
+			if (!empty($post->category_id))
+			{
+				$this_cat = ORM::factory('category')->find($post->category_id);
+			
+				// Category ID and Parent ID can't be the same!
+				if($this_cat->id == $parent_exists->id)
+				{
+					$post->add_error('parent_id', 'same');
+				}
+			
+				// Don't subcategorize a special category
+				if($this_cat->category_trusted == 1)
+				{
+					$post->add_error('parent_id', 'special');
+				}
+			
+				// Don't add subcategories to a special category
+				if($parent_exists->category_trusted == 1)
+				{
+					$post->add_error('parent_id', 'parent_trusted');
+				}
+			
+				// Don't subcategorise a category that already has subcategories
+				$children = ORM::factory('category')->where('parent_id',$this_cat->id)->count_all();
+				if($children > 0)
+				{
+					$post->add_error('parent_id', 'already_parent');
+				}
+			}
+		}
+	}
+
+	/**
+	 * Submit categories details
+	 *
+	 * @return int
+	 */
+	private function _submit_categories()
+	{
+
+		// setup and initialize form field names
+		$form = array('parent_id' => '', 'category_title' => '', 'category_description' => '', 'category_color' => '', 'category_image' => '');
+
+		// copy the form as errors, so the errors will be stored
+		//with keys corresponding to the form field names
+		$errors = $form;
+		$parents_array = array();
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't
+			//overwrite $_POST fields with our own things
+			$post = Validation::factory(array_merge($_POST, $_FILES));
+
+			//  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list
+			//of checks, carried out in order
+			$post->add_rules('parent_id', 'required', 'numeric');
+			$post->add_rules('category_title', 'required', 'length[3,80]');
+			$post->add_rules('category_description', 'required');
+			$post->add_rules('category_color', 'required', 'length[6,6]');
+			$post->add_rules('category_image', 'upload::valid', 'upload::type[gif,jpg,png]', 'upload::size[50K]');
+			$post->add_callbacks('parent_id', array($this, 'parent_id_chk'));
+
+			// Test to see if things passed the rule checks
+			if ($post->validate(FALSE))
+			{
+				$category = new Category_Model();
+
+				// Save Action
+				$category->parent_id = $post->parent_id;
+				$category->category_title = $post->category_title;
+				$category->category_description = $post->category_description;
+				$category->category_color = $post->category_color;
+				$category->save();
+
+				//optional
+				if (!empty($post->category_image))
+				{
+					// Upload Image/Icon
+					$filename = upload::save('category_image');
+					if ($filename)
+					{
+						$new_filename = "category_" . $category->id . "_" . time();
+
+						// Resize Image to 32px if greater
+						Image::factory($filename)->resize(32, 32, Image::HEIGHT)->save(Kohana::config('upload.directory', TRUE) . $new_filename . ".png");
+
+						// Remove the temporary file
+						unlink($filename);
+
+						// Delete Old Image
+						$category_old_image = $category->category_image;
+
+						if (!empty($category_old_image) AND file_exists(Kohana::config('upload.directory', TRUE) . $category_old_image))
+							unlink(Kohana::config('upload.directory', TRUE) . $category_old_image);
+
+						// Save
+						$category->category_image = $new_filename . ".png";
+						$category->save();
+					}
+				}
+
+				// Empty $form array
+				array_fill_keys($form, '');
+
+			}
+			// No! We have validation errors, we need to show the form
+			//again, with the errors
+			else
+			{
+
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('category'));
+				foreach ($errors as $error_item => $error_description)
+				{
+					if (!is_array($error_description))
+					{
+						$this->error_messages .= $error_description;
+
+						if ($error_description != end($errors))
+						{
+							$this->error_messages .= " - ";
+						}
+					}
+				}
+
+				return 1;
+				// Validation error
+			}
+		}
+		else
+		{
+			return 3;
+			// Not sent by post method.
+		}
+
+	}
+
+}
diff --git a/application/libraries/api/MY_Admin_Reports_Api_Object.php b/application/libraries/api/MY_Admin_Reports_Api_Object.php
new file mode 100755
index 0000000..ba911b8
--- /dev/null
+++ b/application/libraries/api/MY_Admin_Reports_Api_Object.php
@@ -0,0 +1,548 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles GET request for KML via the API.
+ *
+ * @version 25 - Emmanuel Kala 2010-10-25
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+require_once Kohana::find_file('libraries/api', Kohana::config('config.extension_prefix').'Incidents_Api_Object');
+
+class Admin_Reports_Api_Object extends Incidents_Api_Object {
+	
+	public function __construct($api_service)
+	{
+		parent::__construct($api_service);
+	}
+
+	/**
+	 *  Handles admin report task requests submitted via the API service
+	 */
+	public function perform_task()
+	{
+		// Authenticate the user
+		if ( ! $this->api_service->_login(TRUE))
+		{
+			$this->set_error_message($this->response(2));
+			return;
+		}
+
+		// by request
+		if ($this->api_service->verify_array_index($this->request, 'by'))
+		{
+			$this->_check_optional_parameters();
+			
+			$this->by = $this->request['by'];
+
+			switch ($this->by)
+			{
+				case "approved" :
+					$this->response_data = $this->_get_approved_reports();
+					break;
+
+				case "unapproved" :
+					$this->response_data = $this->_get_unapproved_reports();
+					break;
+
+				case "verified" :
+					$this->response_data = $this->_get_verified_reports();
+					break;
+
+				case "unverified" :
+					$this->response_data = $this->_get_unverified_reports();
+					break;
+				
+				case "incidentid":
+					if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+					{
+						$this->set_error_message(array(
+							"error" => $this->api_service->get_error_msg(001, 'id')
+						));
+					}
+					
+					$where = array('i.id = '.$this->check_id_value($this->request['id']));
+					$where['all_reports'] = TRUE;
+					$this->response_data = $this->_get_incidents($where);
+				break;
+				
+				case "all" :
+					$this->response_data = $this->_get_all_reports();
+					break;
+
+				default :
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(002)
+					));
+			}
+			return;
+		}
+
+		// action request
+		else if ($this->api_service->verify_array_index($this->request, 'action'))
+		{
+			$this->report_action();
+			return;
+		}
+		else
+		{
+			$this->set_error_message(array("error" => $this->api_service->get_error_msg(001, 'by or action')));
+			return;
+		}
+	}
+
+	/**
+	 * Handles report actions performed via the API service
+	 */
+	public function report_action()
+	{
+		$action = '';
+		// Will hold the report action
+		$incident_id = -1;
+		// Will hold the ID of the incident/report to be acted upon
+
+		// Authenticate the user
+		if ( ! $this->api_service->_login())
+		{
+			$this->set_error_message($this->response(2));
+			return;
+		}
+
+		// Check if the action has been specified
+		if ( ! $this->api_service->verify_array_index($this->request, 'action'))
+		{
+			$this->set_error_message(array(
+				"error" => $this->api_service->get_error_msg(001, 'action')
+			));
+
+			return;
+		}
+		else
+		{
+			$action = $this->request['action'];
+		}
+
+		// Route report actions to their various handlers
+		switch ($action)
+		{
+			// Delete report
+			case "delete" :
+				$this->_delete_report();
+				break;
+
+			// Approve report
+			case "approve" :
+				$this->_approve_report();
+				break;
+
+			// Verify report
+			case "verify" :
+				$this->_verify_report();
+				break;
+
+			// Edit report
+			case "edit" :
+				$this->_edit_report();
+				break;
+
+			default :
+				$this->set_error_message(array(
+					"error" => $this->api_service->get_error_msg(002)
+				));
+		}
+	}
+
+	/**
+	 * List unapproved reports
+	 *
+	 * @param string response - The response to return.XML or JSON
+	 *
+	 * @return array
+	 */
+	private function _get_unapproved_reports()
+	{
+		$where = array();
+		$where['all_reports'] = TRUE;
+		$where[] = "i.incident_active = 0";
+		return $this->_get_incidents($where);
+	}
+
+	/**
+	 * List first 15 reports
+	 *
+	 * @return array
+	 */
+	private function _get_all_reports()
+	{
+		$where = array();
+		$where['all_reports'] = TRUE;
+		return $this->_get_incidents($where);
+	}
+
+	/**
+	 * List first 15 approved reports
+	 *
+	 * @return array
+	 */
+	private function _get_approved_reports()
+	{
+		return $this->_get_incidents();
+	}
+
+	/**
+	 * List first 15 approved reports
+	 *
+	 * @return array
+	 */
+	private function _get_verified_reports()
+	{
+		$where = array();
+		$where['all_reports'] = TRUE;
+		$where[] = 'i.incident_verified = 1';
+		return $this->_get_incidents($where);
+	}
+
+	/**
+	 * List first 15 approved reports
+	 *
+	 * @param string response_type - The response type to return XML or JSON
+	 *
+	 * @return array
+	 */
+	private function _get_unverified_reports()
+	{
+		$where = array();
+		$where['all_reports'] = TRUE;
+		$where[] = 'i.incident_verified = 0';
+		return $this->_get_incidents($where);
+	}
+
+	/**
+	 * Edit existing report
+	 *
+	 * @return array
+	 */
+	public function _edit_report()
+	{
+		print $this->_submit_report();
+	}
+
+	/**
+	 * Delete existing report
+	 *
+	 * @param int incident_id - the id of the report to be deleted.
+	 */
+	private function _delete_report()
+	{
+		$form = array('incident_id' => '', );
+
+		$ret_value = 0;
+		// Return error value; start with no error
+
+		$errors = $form;
+
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			//  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list
+			// of checks, carried out in order
+			$post->add_rules('incident_id', 'required', 'numeric');
+
+			if ($post->validate(FALSE))
+			{
+				$incident_id = $post->incident_id;
+				$update = new Incident_Model($incident_id);
+
+				if ($update->loaded)
+				{
+					$update->delete();
+				}
+			}
+			else
+			{
+				//TODO i18nize the string
+				$this->error_messages .= "Incident ID is required.";
+				$ret_value = 1;
+			}
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		// Set the reponse info to be sent back to client
+		$this->response_data = $this->response($ret_value, $this->error_messages);
+
+	}
+
+	/**
+	 * Approve / unapprove an existing report
+	 *
+	 * @param int report_id - the id of the report to be approved.
+	 *
+	 * @return
+	 */
+	private function _approve_report()
+	{
+		$form = array('incident_id' => '', );
+
+		$errors = $form;
+
+		$ret_value = 0;
+		// will hold the return value
+
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			//  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list
+			// of checks, carried out in order
+			$post->add_rules('incident_id', 'required', 'numeric');
+
+			if ($post->validate())
+			{
+				$incident_id = $post->incident_id;
+				$update = new Incident_Model($incident_id);
+
+				if ($update->loaded == true)
+				{
+					if ($update->incident_active == 0)
+					{
+						$update->incident_active = '1';
+					}
+					else
+					{
+						$update->incident_active = '0';
+					}
+
+					// Tag this as a report that needs to be sent
+					// out as an alert
+					if ($update->incident_alert_status != '2')
+					{
+						// 2 = report that has had an alert sent
+						$update->incident_alert_status = '1';
+					}
+
+					$update->save();
+					reports::verify_approve($update);
+
+				}
+				else
+				{
+					//TODO i18nize the string
+					//couldin't approve the report
+					$this->error_messages .= "Couldn't approve the report id " . $post->incident_id;
+					$ret_value = 1;
+				}
+
+			}
+			else
+			{
+				//TODO i18nize the string
+				$this->error_messages .= "Incident ID is required.";
+				$ret_value = 1;
+			}
+
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		// Set the response data
+		$this->response_data = $this->response($ret_value, $this->error_messages);
+
+	}
+
+	/**
+	 * Verify or unverify an existing report
+	 * @param int report_id - the id of the report to be verified
+	 * unverified.
+	 */
+	private function _verify_report()
+	{
+		$form = array('incident_id' => '', );
+
+		$ret_value = 0;
+		// Will hold the return value; start off with a "no error" value
+
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			//  Add some filters
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of
+			//checks, carried out in order
+			$post->add_rules('incident_id', 'required', 'numeric');
+
+			if ($post->validate())
+			{
+				$incident_id = $post->incident_id;
+				$update = new Incident_Model($incident_id);
+
+				if ($update->loaded == true)
+				{
+					if ($update->incident_verified == '1')
+					{
+						$update->incident_verified = '0';
+					}
+					else
+					{
+						$update->incident_verified = '1';
+					}
+					$update->save();
+
+					reports::verify_approve($update);
+				}
+				else
+				{
+					//TODO i18nize the string
+					$this->error_messages .= "Could not verify this report " . $post->incident_id;
+					$ret_value = 1;
+				}
+
+			}
+			else
+			{
+				//TODO i18nize the string
+				$this->error_messages .= "Incident ID is required.";
+				$ret_value = 1;
+			}
+
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		$this->response_data = $this->response($ret_value, $this->error_messages);
+
+	}
+
+	/**
+	 * The actual reporting -
+	 *
+	 * @return int
+	 */
+	private function _submit_report()
+	{
+		// setup and initialize form field names
+		$form = array(
+		    'location_id' => '',
+		    'incident_id' => '',
+		    'incident_title' => '',
+		    'incident_description' => '',
+		    'incident_date' => '',
+		    'incident_hour' => '',
+		    'incident_minute' => '',
+		    'incident_ampm' => '',
+		    'latitude' => '',
+		    'longitude' => '',
+		    'location_name' => '',
+		    'country_id' => '',
+		    'incident_category' => '',
+		    'incident_news' => array(),
+		    'incident_video' => array(),
+		    'incident_photo' => array(),
+		    'person_first' => '',
+		    'person_last' => '',
+		    'person_email' => '',
+		    'incident_active ' => '',
+		    'incident_verified' => ''
+		);
+
+		$errors = $form;
+
+		// check, has the form been submitted, if so, setup validation
+		if ($_POST)
+		{
+			// Instantiate Validation, use $post, so we don't overwrite 
+			// $_POST fields with our own things
+			$post = array_merge($_POST, $_FILES);
+			$post['incident_category'] = explode(',', $post['incident_category']);
+
+			// Action::report_submit_admin - Report Posted
+			Event::run('ushahidi_action.report_submit_admin', $post);
+
+			// Test to see if things passed the rule checks
+			if (reports::validate($post))
+			{
+				// Yes! everything is valid
+				$location_id = $post->location_id;
+
+				// STEP 1: SAVE LOCATION
+				$location = new Location_Model($location_id);
+				reports::save_location($post, $location);
+
+				// STEP 2: SAVE INCIDENT
+				$incident = new Incident_Model(isset($post->incident_id) ? $post->incident_id : 0);
+				reports::save_report($post, $incident, $location->id);
+
+				// STEP 2b: Record Approval/Verification Action
+				reports::verify_approve($incident);
+
+				// STEP 2c: SAVE INCIDENT GEOMETRIES
+				reports::save_report_geometry($post, $incident);
+
+				// STEP 3: SAVE CATEGORIES
+				reports::save_category($post, $incident);
+
+				// STEP 4: SAVE MEDIA
+				reports::save_media($post, $incident);
+
+				// STEP 5: SAVE PERSONAL INFORMATION
+				reports::save_personal_info($post, $incident);
+
+				// Action::report_edit - Edited a Report
+				Event::run('ushahidi_action.report_edit', $incident);
+
+				// Success
+				return $this->response(0);
+			}
+			else
+			{
+				// populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('report'));
+
+				foreach ($errors as $error_item => $error_description)
+				{
+					if ( ! is_array($error_description))
+					{
+						$this->error_messages .= $error_description;
+						if ($error_description != end($errors))
+						{
+							$this->error_messages .= " - ";
+						}
+					}
+				}
+
+				//FAILED!!! //validation error
+				return $this->response(1, $this->error_messages);
+			}
+		}
+		else
+		{
+			// Not sent by post method.
+			return $this->response(3);
+
+		}
+	}
+
+}
diff --git a/application/libraries/api/MY_Categories_Api_Object.php b/application/libraries/api/MY_Categories_Api_Object.php
new file mode 100755
index 0000000..40b3d52
--- /dev/null
+++ b/application/libraries/api/MY_Categories_Api_Object.php
@@ -0,0 +1,224 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Categories_Api_Object
+ *
+ * This class handles categories activities via the API.
+ *
+ * @version 24 - Emmanuel Kala 2010-10-22
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Categories_Api_Object extends Api_Object_Core {
+
+	public function __construct($api_service)
+	{
+		parent::__construct($api_service);
+	}
+
+	/**
+	 * Implementation of abstract method in superclass
+	 *
+	 * Handles the API task parameters
+	 */
+	public function perform_task()
+	{
+		if ($this->api_service->verify_array_index($this->request, 'by'))
+		{
+			$this->by = $this->request['by'];
+		}
+
+		switch($this->by)
+		{
+			case "catid" :
+				if (!$this->api_service->verify_array_index($this->request, 'id'))
+				{
+					$this->set_error_message(array("error" => $this->api_service->get_error_msg(001, 'id')));
+					return;
+				}
+				else
+				{
+					$this->response_data = $this->_get_categories_by_id($this->check_id_value($this->request['id']));
+				}
+
+				break;
+
+			default :
+				$this->response_data = $this->get_categories_by_all();
+		}
+	}
+
+	/**
+	 * Get a single category
+	 *
+	 * @param int id - The category id.
+	 * @param string response_type - XML or JSON
+	 */
+	private function _get_categories_by_id($id)
+	{
+		// Find incidents
+		$this->query = "SELECT c.*, c.category_image_thumb AS category_icon ";
+		$this->query .= "FROM `" . $this->table_prefix . "category` c ";
+		$this->query .= "LEFT JOIN `" . $this->table_prefix . "category` c_parent ON (c.parent_id = c_parent.id) ";
+		$this->query .= "WHERE c.category_visible = 1 AND (c_parent.category_visible = 1 OR c.parent_id = 0) AND c.id = :id ";	
+		$this->query .= "ORDER BY category_position ASC";
+
+		$items = $this->db->query($this->query, array(':id' => $id));
+		$translations = Category_Lang_Model::category_langs();
+
+		// Set the no. of records fetched
+		$this->record_count = $items->count();
+
+		$i = 0;
+
+		$json_categories = array();
+		$ret_json_or_xml = '';
+
+		//No record found.
+		if ($items->count() == 0)
+		{
+			return $this->response(4);
+		}
+
+		$url_prefix = url::base() . Kohana::config('upload.relative_directory') . '/';
+		foreach ($items as $item)
+		{
+			$item->icon = isset($item->icon) ? $url_prefix . $item->icon : '';
+
+			// Needs different treatment depending on the output
+			if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+			{
+				$json_categories[] = array(
+					"category" => $item,
+					"translations" => isset($translations[$item->id]) ? $translations[$item->id] : array()
+				);
+			}
+			else
+			{
+				$item->translations = array();
+				if (isset($translations[$item->id]))
+				{
+					foreach ($translations[$item->id] as $lang => $translation)
+					{
+						$translation['lang'] = $lang;
+						$item->translations['translation' . $translation['id']] = array('translation' => $translation);
+						$this->replar[] = 'translation' . $translation['id'];
+					}
+				}
+				$json_categories['category' . $i] = array("category" => $item);
+				$this->replar[] = 'category' . $i;
+			}
+
+			$i++;
+		}
+
+		// Create the json array
+		$data = array("payload" => array("domain" => $this->domain, "categories" => $json_categories), "error" => $this->api_service->get_error_msg(0));
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			$ret_json_or_xml = $this->array_as_json($data);
+		}
+		else
+		{
+			$ret_json_or_xml = $this->array_as_xml($data, $this->replar);
+		}
+
+		return $ret_json_or_xml;
+	}
+
+	/**
+	 * Get all categories
+	 *
+	 * @param string response_type - XML or JSON
+	 *
+	 * @return string
+	 */
+	public function get_categories_by_all()
+	{
+		$items = array();
+		//will hold the items from the query
+		$data = array();
+		//items to parse to json
+		$json_categories = array();
+		//incidents to parse to json
+
+		$ret_json_or_xml = '';
+		//will hold the json/xml string to return
+
+		//find incidents
+		$this->query = "SELECT c.id, c.parent_id, c.category_title as title, c.category_description as description, 
+                c.category_color as color, c.category_position as position, c.category_image_thumb AS icon
+                FROM `" . $this->table_prefix . "category` c
+                LEFT JOIN `" . $this->table_prefix . "category` c_parent ON (c.parent_id = c_parent.id) WHERE
+                c.category_visible = 1 AND (c_parent.category_visible = 1 OR c.parent_id = 0) ORDER BY c.category_position ASC";
+
+		$items = $this->db->query($this->query);
+
+		$translations = Category_Lang_Model::category_langs();
+
+		// Set the no. of records fetched
+		$this->record_count = $items->count();
+
+		$i = 0;
+
+		$this->replar = array();
+		//assists in proper xml generation
+
+		$url_prefix = url::base() . Kohana::config('upload.relative_directory') . '/';
+		foreach ($items as $item)
+		{
+			$item->icon = $item->icon ? $url_prefix . $item->icon : '';
+
+			//needs different treatment depending on the output
+			if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+			{
+				$json_categories[] = array(
+					"category" => $item,
+					"translations" => isset($translations[$item->id]) ? $translations[$item->id] : array()
+				);
+			}
+			else
+			{
+				$item->translations = array();
+				if (isset($translations[$item->id]))
+				{
+					foreach ($translations[$item->id] as $lang => $translation)
+					{
+						$translation['lang'] = $lang;
+						$item->translations['translation' . $translation['id']] = array('translation' => $translation);
+						$this->replar[] = 'translation' . $translation['id'];
+					}
+				}
+				$json_categories['category' . $i] = array("category" => $item);
+				$this->replar[] = 'category' . $i;
+			}
+
+			$i++;
+		}
+
+		//create the json array
+		$data = array("payload" => array("domain" => $this->domain, "categories" => $json_categories), "error" => $this->api_service->get_error_msg(0));
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			$ret_json_or_xml = $this->array_as_json($data);
+		}
+		else
+		{
+			$ret_json_or_xml = $this->array_as_xml($data, $this->replar);
+		}
+
+		return $ret_json_or_xml;
+	}
+
+}
+
diff --git a/application/libraries/api/MY_Checkin_Api_Object.php b/application/libraries/api/MY_Checkin_Api_Object.php
new file mode 100644
index 0000000..840463f
--- /dev/null
+++ b/application/libraries/api/MY_Checkin_Api_Object.php
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Checkin_Api_Object
+ *
+ * This class handles reports activities via the API.
+ *
+ * @version 1 - Brian Herbert (not sure what this version is for, though)
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module	   API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Checkin_Api_Object extends Api_Object_Core {
+	
+	public function __construct($api_service)
+	{
+		parent::__construct($api_service);		
+	}
+	
+	/**
+	 * Implementation of abstract method in parent
+	 *
+	 * Handles the API task parameters
+	 */
+	public function perform_task()
+	{	
+		// Checkins have been removed from Ushahidi now
+		$this->set_ci_error_message(array(
+			"error" => $this->api_service->get_error_msg(010)
+		));
+
+		$this->show_response();
+	}
+	
+	public function show_response()
+	{
+		if ($this->response_type == 'json')
+		{
+			echo json_encode($this->response);
+		} 
+		else 
+		{
+			echo $this->array_as_xml($this->response, array());
+		}
+	}
+
+	public function set_ci_error_message($resp)
+	{
+		$this->response = $resp;
+	}
+}
diff --git a/application/libraries/api/MY_Comments_Api_Object.php b/application/libraries/api/MY_Comments_Api_Object.php
new file mode 100644
index 0000000..508f983
--- /dev/null
+++ b/application/libraries/api/MY_Comments_Api_Object.php
@@ -0,0 +1,794 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Comments_Api_Object
+ *
+ * This class handles commenting activities via the API.
+ *
+ * @version 25 - Emmanuel Kala 2010-10-26
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Comments_Api_Object extends Api_Object_Core {
+
+	public function __construct($api_service)
+	{
+		parent::__construct($api_service);
+	}
+
+	public function perform_task()
+	{
+		// by request
+		if ($this->api_service->verify_array_index($this->request, 'by'))
+		{
+			$this->by = $this->request['by'];
+
+			switch ($this->by)
+			{
+				case "all" :
+					// Check for admin access on all comments
+					if (!$this->api_service->_login(TRUE))
+					{
+						$this->response_data = $this->_get_approved_comments();
+						return;
+					}
+					
+					$this->response_data = $this->_get_all_comments();
+					break;
+
+				case "spam" :
+					// Check for admin access on all comments
+					if (!$this->api_service->_login(TRUE))
+					{
+						$this->set_error_message($this->response(2));
+						return;
+					}
+
+					$this->response_data = $this->_get_spam_comments();
+					break;
+
+				case "pending" :
+					// Check for admin access on all comments
+					if (!$this->api_service->_login(TRUE))
+					{
+						$this->set_error_message($this->response(2));
+						return;
+					}
+
+					$this->response_data = $this->_get_pending_comments();
+					break;
+
+				case "approved" :
+					$this->response_data = $this->_get_approved_comments();
+					break;
+
+				case "reportid" :
+					if (!$this->api_service->verify_array_index($this->request, 'id'))
+					{
+						$this->set_error_message(array("error" => $this->api_service->get_error_msg(001, 'id')));
+						return;
+					}
+					else
+					{
+						$this->response_data = $this->_get_comment_by_report_id($this->check_id_value($this->request['id']));
+					}
+					break;
+
+				default :
+					$this->set_error_message(array("error" => $this->api_service->get_error_msg(002)));
+			}
+
+			return;
+		}
+
+		//action request
+		else if ($this->api_service->verify_array_index($this->request, 'action'))
+		{
+			$this->comment_action();
+			return;
+		}
+		else
+		{
+
+			$this->set_error_message(array("error" => $this->api_service->get_error_msg(001, 'by or action')));
+			return;
+
+		}
+
+	}
+
+	/**
+	 * Handles comment action API requests
+	 */
+	public function comment_action()
+	{
+		$action = '';
+		//Will hold comment action
+
+		if (!$this->api_service->verify_array_index($this->request, 'action'))
+		{
+			$this->set_error_message(array("error" => $this->api_service->get_error_msg(001, 'action')));
+			return;
+		}
+		else
+		{
+			$action = $this->request['action'];
+		}
+
+		switch ($action)
+		{
+			case "approve" :
+				// Check for admin access on all comments
+				if (!$this->api_service->_login(TRUE))
+				{
+					$this->set_error_message($this->response(2));
+					return;
+				}
+
+				//Aprrove / Unapprove comment
+				$this->response_data = $this->_approve_comment();
+				break;
+
+			case "add" :
+				// Add a new comment
+				$this->response_data = $this->_add_comment();
+				break;
+
+			case "delete" :
+				// Check for admin access on all comments
+				if (!$this->api_service->_login(TRUE))
+				{
+					$this->set_error_message($this->response(2));
+					return;
+				}
+				
+				// Delete an existing comment
+				$this->response_data = $this->_delete_comment();
+				break;
+
+			case "spam" :
+				// Check for admin access on all comments
+				if (!$this->api_service->_login(TRUE))
+				{
+					$this->set_error_message($this->response(2));
+					return;
+				}
+				
+				// Spam or Unspam a comment
+				$this->response_data = $this->_spam_comment();
+				break;
+
+			default :
+				$this->set_error_message(array("error" => $this->api_service->get_error_msg(002)));
+		}
+	}
+
+	/**
+	 * Gets a list of comments by
+	 *
+	 * @param string status - List comments by status.
+	 * @param string response_type - XML or JSON
+	 *
+	 * @return array
+	 */
+	private function _get_comment_list($where = array(), $limit = '')
+	{
+
+		$xml = new XMLWriter();
+		$json = array();
+		$json_items = array();
+
+		$query = ORM::factory('comment')
+			->orderby('comment_date','desc')
+			->where($where)
+			->limit($this->list_limit);
+		//$sql = "SELECT id, incident_id, comment_author, comment_description, comment_date, user_id FROM comment $where $limit";
+
+		$items = $query->find_all();
+
+		// Set the no. of records returned
+		$this->record_count = $items->count();
+
+		if ($this->response_type == "xml")
+		{
+			$xml->openMemory();
+			$xml->startDocument('1.0', 'UTF-8');
+			$xml->startElement('response');
+			$xml->startElement('payload');
+			$xml->writeElement('domain', $this->domain);
+			$xml->startElement('comments');
+		}
+
+		//No record found.
+		if ($items->count() == 0)
+		{
+			return $this->response(4);
+		}
+
+		foreach ($items as $list_item)
+		{
+			if ($this->response_type == "json" OR $this->response_type == "jsonp")
+			{
+				$json_items[] = $list_item->as_array();
+			}
+			else
+			{
+				$xml->startElement('comment');
+
+				$xml->writeElement('id', $list_item->id);
+				$xml->writeElement('incident_id', $list_item->incident_id);
+				$xml->writeElement('user_id', $list_item->user_id);
+				$xml->writeElement('comment_author', $list_item->comment_author);
+				$xml->writeElement('comment_description', $list_item->comment_description);
+				$xml->writeElement('comment_date', $list_item->comment_date);
+
+				$xml->endElement();
+				// comment
+			}
+		}
+
+		if ($this->response_type == "xml")
+		{
+			$xml->endElement();
+			// comments
+			$xml->endElement();
+			// payload
+			$xml->startElement('error');
+			$xml->writeElement('code', 0);
+			$xml->writeElement('message', 'No Error');
+			$xml->endElement();
+			//end error
+			$xml->endElement();
+			// end response
+
+			return $xml->outputMemory(true);
+		}
+		else
+		{
+			$json = array("payload" => array("comments" => $json_items));
+
+			return $this->array_as_json($json);
+		}
+	}
+
+	/**
+	 * List all comments marked as spam
+	 *
+	 * @return array
+	 */
+	private function _get_spam_comments()
+	{
+		$where = array('comment_spam' => 1);
+
+		return $this->_get_comment_list($where);
+	}
+
+	/**
+	 * List all comments submited to Ushahidi
+	 *
+	 * @param string response_type - The format of the response needed.
+	 * XML or JSON
+	 *
+	 * @return array
+	 */
+	private function _get_all_comments()
+	{
+		$where = array('comment_spam' => 0);
+
+		return $this->_get_comment_list($where);
+	}
+
+	/**
+	 * List all approved comments
+	 *
+	 * @param string response_type - The format of the response needed.
+	 * XML or JSON
+	 *
+	 * @return array
+	 */
+	private function _get_approved_comments()
+	{
+		$where = array('comment_spam' => 0,'comment_active' => 1);
+
+		return $this->_get_comment_list($where);
+
+	}
+
+	/**
+	 * List all pending comments
+	 *
+	 * @param string response_type - The format of the response needed.
+	 * XML or JSON
+	 *
+	 * @return array
+	 */
+	private function _get_pending_comments()
+	{
+		$where = array('comment_spam' => 0, 'comment_active' => 0);
+
+		return $this->_get_comment_list($where);
+	}
+
+	/**
+	 * Spams / Unspams a comment
+	 *
+	 * @param string response_type - The response to return. XML or JSON
+	 *
+	 * @return array
+	 */
+	private function _spam_comment()
+	{
+		$form = array('comment_id' => '');
+
+		$ret_value = 0;
+
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+			// Add some rules, the input field, followed by a list of
+			//checks, carried out in order
+			$post->add_rules('comment_id', 'required', 'numeric');
+
+			if ($post->validate(FALSE))
+			{
+				$comment_id = $post->comment_id;
+				$comment = new Comment_Model($comment_id);
+
+				if ($comment->loaded == true)
+				{
+					//spam
+					if ($post->action == strtolower('s'))
+					{
+						$comment->comment_active = '0';
+						$comment->comment_spam = '1';
+					}
+					//unspam
+					elseif ($post->action == strtolower('n'))
+					{
+						$comment->comment_active = '1';
+						$comment->comment_spam = '0';
+					}
+
+					$comment->save();
+				}
+				else
+				{
+					//Comment id doesn't exist in DB
+					//TODO i18nize the string
+					$this->error_messages .= "Comment ID does not exist.";
+					$ret_value = 1;
+
+				}
+			}
+			else
+			{
+				//TODO i18nize the string
+				$this->error_messages .= "Comment ID is required.";
+				$ret_value = 1;
+			}
+
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		return $this->response($ret_value, $this->error_messages);
+
+	}
+
+	/**
+	 * Deletes a comment
+	 *
+	 * @param string response_type - The type of response to return XML or
+	 * JSON.
+	 *
+	 * @return Array
+	 */
+	private function _delete_comment()
+	{
+		$form = array('comment_id' => '');
+
+		$errors = $form;
+		$form_error = FALSE;
+
+		$ret_value = 0;
+
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+			// Add some rules, the input field, followed by a list of
+			//checks, carried out in order
+			$post->add_rules('comment_id', 'required', 'numeric');
+
+			if ($post->validate(FALSE))
+			{
+				$comment_id = $post->comment_id;
+				$comment = new Comment_Model($comment_id);
+				if ($comment->loaded == true)
+				{
+					$comment->delete();
+				}
+				else
+				{
+					//Comment id doesn't exist in DB
+					//TODO i18nize the string
+					$this->error_messages .= "Comment ID does not exist.";
+					$this->ret_value = 1;
+
+				}
+			}
+			else
+			{
+				//TODO i18nize the string
+				$this->error_messages .= "Comment ID is required.";
+				$ret_value = 1;
+			}
+
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		return $this->response($ret_value, $this->error_messages);
+	}
+
+	/**
+	 * Approves / Dissaproves a comment
+	 *
+	 * @param string response_type - The resposne format to return.XML
+	 * or JSON
+	 *
+	 * @return Array
+	 */
+	private function _approve_comment()
+	{
+		$form = array('comment_id' => '', );
+
+		$errors = $form;
+		$form_error = FALSE;
+
+		$ret_value = 0;
+
+		if ($_POST)
+		{
+			$post = Validation::factory($_POST);
+
+			// Add some filters
+			$post->pre_filter('trim', TRUE);
+			// Add some rules, the input field, followed by a list of
+			//checks, carried out in order
+			$post->add_rules('comment_id', 'required', 'numeric');
+
+			if ($post->validate(FALSE))
+			{
+				$comment_id = $post->comment_id;
+				$comment = new Comment_Model($comment_id);
+				if ($comment->loaded == true)
+				{
+					//approve
+					if ($post->action == strtolower('a'))
+					{
+						$comment->comment_active = '1';
+						$comment->comment_spam = '0';
+					}
+					else if ($post->action == strtolower('u'))
+					{
+						$comment->comment_active = '0';
+					}
+
+					$comment->save();
+				}
+				else
+				{
+					//Comment id doesn't exist in DB
+					//TODO i18nize the string
+					$this->error_messages .= "Comment ID does not exist.";
+					$ret_value = 1;
+
+				}
+			}
+			else
+			{
+				//TODO i18nize the string
+				$this->error_messages .= "Comment ID is required.";
+				$ret_value = 1;
+			}
+
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		return $this->response($ret_value, $this->error_messages);
+	}
+
+	/**
+	 * Submit comments
+	 *
+	 * @return int
+	 */
+	private function _add_comment()
+	{
+		$api_akismet = Kohana::config('settings.api_akismet');
+
+		// Comment Post?
+		// Setup and initialize form field names
+
+		$form = array('incident_id' => '', 'comment_author' => '', 'comment_description' => '', 'comment_email' => '', );
+
+		$captcha = Captcha::factory();
+		$errors = $form;
+		$form_error = FALSE;
+		$ret_value = 0;
+
+		// Check, has the form been submitted, if so, setup validation
+
+		if ($_POST AND Kohana::config('settings.allow_comments'))
+		{
+			// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+
+			$post = Validation::factory($_POST);
+
+			// Add some filters
+
+			$post->pre_filter('trim', TRUE);
+
+			// Add some rules, the input field, followed by a list of checks, carried out in order
+
+			// We have an incident id (used to be so we could have check in ids too)
+			$post->add_rules('incident_id', 'required');
+
+			// If the user isn't posting an email and author name, then they need to log in
+			if (empty($post->comment_author) OR empty($post->comment_email))
+			{
+				if (!$this->api_service->_login())
+				{
+					$this->set_error_message($this->response(2));
+					return;
+				}
+			}
+
+			// Grab variables for a logged in user
+			$auth = Auth::instance();
+
+			// Is user previously authenticated?
+			if ($auth->logged_in())
+			{
+				$user_id = $auth->get_user()->id;
+			}
+			else
+			{
+				$user_id = NULL;
+				$post->add_rules('comment_author', 'required', 'length[3,100]');
+				$post->add_rules('comment_email', 'required', 'email', 'length[4,100]');
+			}
+			
+			$post->add_rules('comment_description', 'required');
+
+			// Test to see if things passed the rule checks
+
+			if ($post->validate(FALSE))
+			{
+				// Yes! everything is valid
+
+				$incident_id = NULL;
+
+				$incident = ORM::factory('incident')->where('id', $post->incident_id)->where('incident_active', 1)->find();
+				if ($incident->id == 0)// Not Found
+				{
+					return $this->response(1, "No incidents with that ID");
+				}
+				$incident_id = $post->incident_id;
+				$comment_url = url::base() . 'reports/view/' . $post->incident_id;
+				$comment_title = $incident->incident_title;
+
+				if ($user_id == NULL)
+				{
+					$comment_author = $post->comment_author;
+					$comment_email = $post->comment_email;
+				}
+				else
+				{
+					$comment_author = $auth->get_user()->name;
+					$comment_email = $auth->get_user()->email;
+				}
+
+				if ($api_akismet != "")
+				{
+					// Run Akismet Spam Checker
+
+					$akismet = new Akismet();
+
+					// Comment data
+
+					$comment = array('author' => $comment_author, 'email' => $comment_email, 'website' => "", 'body' => $post->comment_description, 'user_ip' => $_SERVER['REMOTE_ADDR']);
+
+					$config = array('blog_url' => url::site(), 'api_key' => $api_akismet, 'comment' => $comment);
+
+					$akismet->init($config);
+
+					if ($akismet->errors_exist())
+					{
+						if ($akismet->is_error('AKISMET_INVALID_KEY'))
+						{
+							// throw new Kohana_Exception('akismet.api_key');
+
+						}
+						elseif ($akismet->is_error('AKISMET_RESPONSE_FAILED'))
+						{
+
+							// throw new Kohana_Exception('akismet.server_failed');
+
+						}
+						elseif ($akismet->is_error('AKISMET_SERVER_NOT_FOUND'))
+						{
+
+							// throw new Kohana_Exception('akismet.server_not_found');
+
+						}
+
+						// If the server is down, we have to post
+						// the comment :(
+						// $this->_post_comment($comment);
+
+						$comment_spam = 0;
+					}
+					else
+					{
+						$comment_span = ($akismet->is_spam()) ? 1 : 0;
+					}
+				}
+				else
+				{
+					// No API Key!!
+					$comment_spam = 0;
+				}
+
+				$comment = new Comment_Model();
+				$comment->incident_id = intval($incident_id);
+				$comment->comment_author = html::strip_tags($comment_author, FALSE);
+				$comment->comment_description = html::strip_tags($post->comment_description, FALSE);
+				$comment->comment_email = html::strip_tags($comment_email, FALSE);
+				$comment->comment_ip = $_SERVER['REMOTE_ADDR'];
+				$comment->comment_date = date("Y-m-d H:i:s", time());
+
+				// Activate comment for now
+				if ($comment_spam == 1)
+				{
+					$comment->comment_spam = 1;
+					$comment->comment_active = 0;
+				}
+				else
+				{
+					$comment->comment_spam = 0;
+					// 1 - Auto-approve, 0 - Manually approve
+					$comment->comment_active = (Kohana::config('settings.allow_comments') == 1) ? 1 : 0;
+				}
+				$comment->save();
+
+				// Notify Admin Of New Comment
+				$send = notifications::notify_admins("[" . Kohana::config('settings.site_name') . "] " . Kohana::lang('notifications.admin_new_comment.subject'), Kohana::lang('notifications.admin_new_comment.message') . "\n\n'" . $comment_title . "'" . "\n" . $comment_url . "\n\n" . strip_tags($post->comment_description));
+			}
+			else
+			{
+
+				// No! We have validation errors, we need to show the form again, with the errors
+
+				// Repopulate the form fields
+
+				$form = arr::overwrite($form, $post->as_array());
+
+				// Populate the error fields, if any
+
+				$errors = arr::overwrite($errors, $post->errors('comments'));
+
+				foreach ($errors as $error_item => $error_description)
+				{
+					if (!is_array($error_description))
+					{
+						$this->error_messages .= $error_description;
+
+						if ($error_description != end($errors))
+						{
+							$this->error_messages .= " - ";
+						}
+					}
+				}
+
+				$ret_value = 1;
+				// Validation error
+			}
+		}
+		else
+		{
+			$ret_value = 3;
+		}
+
+		return $this->response($ret_value, $this->error_messages);
+	}
+
+	/**
+	 *
+	 * Get comments by report id
+	 *
+	 * @param int id - The report id
+	 *
+	 * @return String XML or JSON string
+	 */
+	private function _get_comment_by_report_id($id)
+	{
+		$json_comments = array();
+		$ret_json_or_xml = '';
+		$i = 0;
+
+		//Check if comments are enabled by that deployments
+		if (Kohana::config('settings.allow_comments'))
+		{
+
+			$incident_comments = array();
+			if ($id)
+			{
+				$this->query = "SELECT id, incident_id, comment_author, ";
+				$this->query .= "comment_description, comment_date ";
+				$this->query .= "FROM `" . $this->table_prefix . "comment`";
+				$this->query .= " WHERE `incident_id` = " . $this->db->escape_str($id) . " AND `comment_active` = '1' ";
+				$this->query .= "AND `comment_spam` = '0' ORDER BY `comment_date` ASC";
+				$incident_comments = $this->db->query($this->query);
+
+				if ($incident_comments->count() == 0)
+					return $this->response(4);
+
+				foreach ($incident_comments as $comment)
+				{
+					// Needs different treatment depending on the output
+					if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+					{
+						$json_comments[] = array("comment" => $comment);
+					}
+					else
+					{
+						$json_comments['comment' . $i] = array("comment" => $comment);
+						$this->replar[] = 'comment' . $i;
+					}
+
+					$i++;
+				}
+				// Create the json array
+				$data = array("payload" => array("domain" => $this->domain, "comments" => $json_comments), "error" => $this->api_service->get_error_msg(0));
+
+				$ret_json_or_xml = ($this->response_type == 'json' OR $this->response_type == 'jsonp') ? $this->array_as_json($data) : $this->array_as_xml($data, $this->replar);
+
+				return $ret_json_or_xml;
+			}
+			else
+			{
+				//prompt user for a valid ID
+				return $this->response(1, "No report with that ID");
+			}
+
+		}
+		else
+		{
+			//prompt user about commenting not enabled on this deployment
+			return $this->response(1, "Commenting is not activated on this deployment");
+		}
+	}
+
+}
diff --git a/application/libraries/api/MY_Countries_Api_Object.php b/application/libraries/api/MY_Countries_Api_Object.php
new file mode 100755
index 0000000..66da4a2
--- /dev/null
+++ b/application/libraries/api/MY_Countries_Api_Object.php
@@ -0,0 +1,215 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles countries activities via the API.
+ *
+ * @version 24 - Emmanuel Kala 2010-10-22
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Countries_Api_Object extends Api_Object_Core {
+
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+    }
+    
+    public function perform_task()
+    {
+        if ($this->api_service->verify_array_index($this->request, 'by'))
+        {
+            $this->by = $this->request['by'];
+        }
+        
+        switch ($this->by)
+        {
+            // Get country by id (unique id in the database)
+            case "countryid":
+                if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+                {
+                    $this->set_error_message(array(
+                        "error" => $this->api_service->get_error_msg(001, 'id')
+                    ));
+                    
+                    return;
+                }
+                else
+                {
+                    $this->response_data = $this->_get_country_by_id(
+                        $this->check_id_value($this->request['id']));
+                }
+            break;
+            
+            // Get country by name
+            case "countryname":
+                if ( ! $this->api_service->verify_array_index($this->request, 'name'))
+                {
+                    $this->set_error_message(array(
+                        "error" => $this->api_service->get_error_msg(001, 'name')
+                    ));
+                    
+                    return;
+                }
+                else
+                {
+                    $this->response_data = $this->_get_country_by_name(
+                            $this->request['name']);
+
+                }
+            break;
+            
+            // Get country by ISO
+            case "countryiso":
+                if ( ! $this->api_service->verify_array_index($this->request, 'iso'))
+                {
+                    $this->set_error_message(array(
+                        "error" => $this->api_service->get_error_msg(001, 'iso')
+                    ));
+                                        
+                    return;
+                }
+                else
+                {
+                    $this->response_data = $this->_get_country_by_iso($this->request['iso']);
+                }
+            break;
+            
+            default:
+                $this->response_data = $this->_get_countries_by_all();
+        }
+    }
+
+    /**
+     * Fetch all countries
+     *
+     * @param array where - array to pass to query builder
+     * @param integer limit - number of results to return
+     *
+     * @return string 
+     */
+    private function _get_countries($where = array(), $limit = FALSE)
+    {
+
+        // Fetch countries
+				$items = ORM::factory('Country')
+						->select('country as name', 'country.*')
+						->where($where)
+						->orderby('id','DESC');
+				
+				if ($limit)
+					$items->limit($limit);
+				
+				$items = $items->find_all();
+				
+        // Set the record count
+        $this->record_count = count($items);
+        
+        $i = 0;
+
+        $json_countries = array();
+        $ret_json_or_xml = '';
+        
+				//No record found.
+        if ($this->record_count == 0)
+        {
+            return $this->response(4);
+        }
+
+        foreach ($items as $item)
+        {
+
+            // Needs different treatment depending on the output
+            if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+            {
+                $json_countries[] = array("country" => $item->as_array());
+            } 
+            else 
+            {
+                $json_countries['country'.$i] = array(
+                        "country" => $item->as_array());
+
+                $this->replar[] = 'country'.$i;
+            }
+
+            $i++;
+        }
+
+        // Create the json array
+        $data = array(
+                "payload" => array(
+                    "domain" => $this->domain,
+                    "countries" => $json_countries
+                ),
+                "error" => $this->api_service->get_error_msg(0)
+        );
+        
+        if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+        {
+            $ret_json_or_xml = $this->array_as_json($data);
+        } 
+        else 
+        {
+            $ret_json_or_xml = $this->array_as_xml($data, $this->replar);
+        }
+
+        return $ret_json_or_xml;
+    }
+
+    /**
+     * Get a list of all countries
+     *
+     * @param string response_type - XML or JSON
+     *
+     * @return string
+     */
+    private function _get_countries_by_all()
+    {
+        return $this->_get_countries();
+    }
+
+    /**
+     * Get a country by name
+     *
+     * @param string name - the name of country
+     * @param string response_type - XML or JSON
+     */
+    private function _get_country_by_name($name)
+    {
+				$where = array('country' => $name);
+        
+        return $this->_get_countries($where, $this->list_limit);
+    }
+
+    /**
+     * Get a country by id
+     * @param string id - the id of the country from an ushahidi deployment
+     *
+     * @param string response_type - XML or JSON
+     */
+    private function _get_country_by_id($id)
+    {
+        $where = array('id' => $id);
+        
+        return $this->_get_countries($where, $this->list_limit);
+    }
+
+    /**
+     * Get a country by iso
+     *
+     * @param string response_type - XML or JSON
+     */
+    private function _get_country_by_iso($iso)
+    {
+        $where = array('iso' => $iso);
+        return $this->_get_countries($where, $this->list_limit);
+    }
+}
+
diff --git a/application/libraries/api/MY_Customforms_Api_Object.php b/application/libraries/api/MY_Customforms_Api_Object.php
new file mode 100644
index 0000000..aba3f66
--- /dev/null
+++ b/application/libraries/api/MY_Customforms_Api_Object.php
@@ -0,0 +1,445 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * CustomForms_Api_Object
+ *
+ * This class handles reports activities via the API.
+ *
+ * @version 1 - Antonio Lettieri 2011-01-31
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Konpagroup <info at konpagroup.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Konpagroup - http://konpagroup.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+
+class CustomForms_Api_Object extends Api_Object_Core {
+
+
+	public function __construct($api_service)
+	{
+		parent::__construct($api_service);
+	}
+	
+   	/**
+     * Implementation of abstract method in parent
+     *
+     * Handles the API task parameters
+     */
+	public function perform_task()
+	{
+		$this->_get_custom_forms();	
+	}
+
+	/**
+	 * Handles API Task
+	 *
+	 * Sets the response_data property of the parent
+	 */
+	private function _get_custom_forms()
+	{
+	 	// Verify that the by query parameter is set
+		if (!$this->api_service->verify_array_index($this->request, 'by')) 
+		{
+			return $this->set_error_message(array(
+				"error" => $this->api_service->get_error_msg(001, 'by')
+            	));       
+		}
+		else 
+		{
+			$this->by = $this->request['by'];
+		}
+
+		// Verify which call is being made
+		switch ($this->by) {
+			
+			// Get all forms
+			case "all":
+				$this->response_data = $this->_get_all_forms();
+			break;
+			
+			// Get custom field values and meta
+			case "fields":
+				$this->response_data = $this->_get_custom_form_fields();
+			break;
+			
+			// Get custom field meta
+			case "meta":
+				$this->response_data = $this->_get_custom_form_meta();
+			break;
+		}
+	}
+
+	/**
+     * Returns all form details in the platform
+	 *
+     */
+	private function _get_all_forms()
+	{
+		// Call to customforms helper to return all forms
+		$forms = customforms::get_custom_forms();
+
+		$is_json = $this->_is_json();
+
+		if ($forms->count() == 0)
+		{
+			// Nothing was returned
+			// We don't have any forms.	
+			return $this->response(4); 
+		}
+
+		if ($is_json)
+		{
+			$json_item = array();
+			$json = array();
+		}
+		else
+		{
+			$xml = new XmlWriter();
+	        $xml->openMemory();
+	        $xml->startDocument('1.0', 'UTF-8');
+	        $xml->startElement('response');
+	        $xml->startElement('payload');
+	        $xml->writeElement('domain',$this->domain);
+	        $xml->startElement('customforms');
+	        $xml->startElement('forms');	        	
+		}
+
+		foreach ($forms as $form)
+		{
+			if ($is_json)
+			{
+				// Setup JSON array
+				$json_item[] = array(
+					"id" => $form->id,
+					"title" => $form->form_title,
+					"description" => $form->form_description
+					);
+			}
+			else
+			{
+				// Setup XML Elements
+				$xml->startElement("form");
+				$xml->writeElement("id",$form->id);
+				$xml->writeElement("title",$form->form_title);
+				$xml->writeElement("description",$form->form_description);
+				$xml->endElement(); 
+
+				//End form
+			}
+		}
+		
+		if ($is_json)
+		{
+			$json = array(
+				"payload" => array("customforms" => $json_item),
+				"error" =>$this->api_service->get_error_msg(0)
+				);
+			
+			//Return array as json
+			return $this->array_as_json($json); 
+		}
+		else
+		{
+			// End forms
+			$xml->endElement();
+			
+			// End customforms 
+	        $xml->endElement();
+	
+	 		// End payload
+	        $xml->endElement(); 
+	        $xml->startElement('error');
+            $xml->writeElement('code',0);
+            $xml->writeElement('message','No Error');
+
+			// End error
+            $xml->endElement();
+
+			// End response
+            $xml->endElement(); 
+
+			// Return XML output
+	      	return $xml->outputMemory(true); 
+		}
+	}
+
+	/**
+	 * Gets the custom form field values and 
+	 * meta information by incidentid
+	 */	
+	private function _get_custom_form_fields(){
+
+		$is_json = $this->_is_json();
+
+		if ( !$this->api_service->verify_array_index($this->request, 'id')) 
+		{
+          // Ensure that the incidentid is set, error out if not
+          return $this -> set_error_message(array(
+                        "error" => $this->api_service->get_error_msg(001, 'id')
+						));        
+        }
+		else
+		{
+        	$incident_id = $this->request['id'];
+        }
+        
+        // Retrieve the form_id from the incident object
+        $incident = ORM::factory("incident")->select("form_id")->where("id",$this->check_id_value($incident_id))->find();
+        
+        if ( !$incident ) 
+		{
+			// We don't have this incident
+        	return $this->response(4); 
+        } 
+        $form_id = $incident->form_id;
+        
+		// Call the customforms helper method to return the field values
+		$custom_form_fields = customforms::get_custom_form_fields($incident_id, $form_id, true);
+
+		// Call the customforms helper method to return the field meta information
+		$custom_form_field_meta = customforms::get_custom_form_fields($incident_id, $form_id, false);
+
+		if (count($custom_form_fields) == 0)
+		{
+			// We don't have any forms for this incident.
+			return $this->response(4); 
+		}
+
+		if ($is_json)
+		{
+			$json_item = array();
+			$json = array();
+
+		}
+		else 
+		{
+			$xml = new XmlWriter();
+	        $xml->openMemory();
+	        $xml->startDocument('1.0', 'UTF-8');
+	        $xml->startElement('response');
+	        $xml->startElement('payload');
+	        $xml->writeElement('domain',$this->domain);
+	        $xml->startElement('customforms');
+	        $xml->startElement("fields");        
+        }
+
+
+		foreach ($custom_form_fields as $field_id => $field)
+		{
+			$field_value = $field;
+			$field_meta = $custom_form_field_meta[$field_id];
+
+			// Always return values as array
+			if (customforms::field_is_multi_value($field_meta))
+			{
+				// This is a multi-select field, return it as an multi value array
+				$field_value = explode(",",$field_value);
+			}
+			else
+			{
+				// This is either text or html, return as single array object
+				$field_value = array($field_value);
+			}
+
+			if ($is_json)
+			{
+				$json_item["fields"][] = array("values"=>$field_value, "meta"=>$this->_meta_fields($field_meta));
+			}
+			else
+			{
+				$xml->startElement("field");
+				$xml->startElement("values");
+
+				foreach ($field_value as $val)
+				{
+					// Write the field value
+					$xml->writeElement("value",html::specialchars($val,FALSE)); 
+				}
+
+				// End values;
+				$xml->endElement(); 
+				$xml->startElement("meta");
+				$this->_meta_fields($field_meta,$xml);
+
+				// End meta
+				$xml->endElement();
+				
+				// Field; 
+				$xml->endElement(); 
+			}
+		}
+
+		if ($is_json)
+		{
+			$json = array(
+				"payload"=>array("customforms"=>$json_item),
+				"error" => $this->api_service->get_error_msg(0)
+				);
+			$json_item = null;
+			
+			// Write the json array
+			return $this->array_as_json($json);
+
+		}
+		else
+		{
+			// End fields
+			$xml->endElement(); 
+			
+			// End customforms
+        	$xml->endElement(); 
+
+			// End payload
+        	$xml->endElement(); 
+        	$xml->startElement('error');
+            $xml->writeElement('code',0);
+            $xml->writeElement('message','No Error');
+
+			// End error
+            $xml->endElement();
+
+			// End response
+            $xml->endElement(); 
+
+			// Write out the xml stream
+        	return $xml->outputMemory(true); 
+        }
+	}
+
+	/**
+	 * Gets the form field meta values
+	 *	
+	 */
+	private function _get_custom_form_meta()
+	{
+		$is_json = $this->_is_json();
+
+		if (!$this->api_service->verify_array_index($this->request, 'formid')) 
+		{
+			return $this->set_error_message(array(
+                "error" => $this->api_service->get_error_msg(001, 'formid')
+            	));    
+		}
+		else 
+		{
+			$this->formid = $this->request['formid'];
+		}
+
+		$form_meta = customforms::get_custom_form_fields(false,$this->formid); //Get the meta fields for the specified formId
+
+		if(count($form_meta) == 0)
+		{
+			return $this->response(4); //We don't have any fields for this form
+		}
+
+		if($is_json)
+		{
+			$json_item = array();
+			$json = array();
+		}
+		else
+		{
+			$xml = new XmlWriter();
+	        $xml->openMemory();
+	        $xml->startDocument('1.0', 'UTF-8');
+	        $xml->startElement('response');
+	        $xml->startElement('payload');
+	        $xml->writeElement('domain',$this->domain);
+	        $xml->startElement('customforms');
+	        $xml->startElement("fields");
+		}
+
+		foreach ($form_meta as $meta_val)
+		{
+
+			if($is_json)
+			{
+				$json_item["fields"][] = $this->_meta_fields($meta_val); //return meta key array
+			}
+			else
+			{
+				$this->_meta_fields($meta_val,$xml);		//write the xml nodes for the meta fields
+			}
+		}
+
+		if($is_json)
+		{
+			$json = array(
+					"payload"=>array("customforms"=>$json_item),
+					"error" => $this->api_service->get_error_msg(0)
+					);
+			$json_item = null;
+
+			return $this->array_as_json($json); //return json as response_data
+		}
+		else
+		{
+			$xml->endElement(); //end fields
+			$xml->endElement(); //end customforms
+			$xml->endElement(); //end payload
+			$xml->startElement('error');
+            $xml->writeElement('code',0);
+            $xml->writeElement('message','No Error');
+            $xml->endElement();//end error
+            $xml->endElement(); //end response
+			return $xml->outputMemory(true); //write out the xml stream	as response_data
+		}
+
+	}
+
+	/**
+	 * Helper function to manage keynames for meta fields
+	 *
+	 * @param $meta_val Array of meta key names and values
+	 * @optional $xml XML object used when QueryString:resp=xml
+	 */
+	private function _meta_fields($meta_val, &$xml=null)
+	{
+		if (!isset($xml))
+		{
+			return array(
+				'id' => $meta_val['field_id'],
+				'name' => $meta_val['field_name'],
+				'type' => $meta_val['field_type'],
+				'default' => $meta_val['field_default'],
+				'required' => $meta_val['field_required'],
+				'maxlen' => $meta_val['field_maxlength'],
+				'isdate' => $meta_val['field_isdate'],
+				'ispublicvisible' => $meta_val['field_ispublic_visible'],
+				'ispublicsubmit' => $meta_val['field_ispublic_submit']
+			);
+		}
+		else
+		{
+			$xml->startElement("meta");
+			$xml->writeElement("id",$meta_val['field_id']);
+			$xml->writeElement("name",$meta_val["field_name"]);
+			$xml->writeElement("type",$meta_val["field_type"]);
+			$xml->writeElement("default",$meta_val["field_default"]);
+			$xml->writeElement("required",$meta_val["field_required"]);
+			$xml->writeElement("maxlen",$meta_val["field_maxlength"]);
+			$xml->writeElement("height", $meta_val["field_height"]);
+			$xml->writeElement("isdate",$meta_val["field_isdate"]);
+			$xml->writeElement("ispublicvisible",$meta_val["field_ispublic_visible"]);
+			$xml->writeElement("ispublicsubmit",$meta_val["field_ispublic_submit"]);
+			
+			// End meta
+			$xml->endElement(); 	
+		}
+	}
+
+	/**
+	 * Checks to see if the response is asking for xml or json return
+	 *
+	 */
+	private function _is_json()
+	{
+		return ($this->response_type == "json" OR $this->response_type == "jsonp") ? TRUE : FALSE;
+	}
+}
diff --git a/application/libraries/api/MY_Email_Api_Object.php b/application/libraries/api/MY_Email_Api_Object.php
new file mode 100644
index 0000000..343891b
--- /dev/null
+++ b/application/libraries/api/MY_Email_Api_Object.php
@@ -0,0 +1,259 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles GET request for Email via the API.
+ *
+ * @version 25 - Emmanuel Kala 2010-10-27
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class Email_Api_Object extends Api_Object_Core {
+
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+    }
+
+    /**
+     * Empty declaration for OOP compliance
+     */
+    public function perform_task()
+    {
+			// Authenticate the user
+			if ( ! $this->api_service->_login(TRUE))
+			{
+				$this->set_error_message($this->response(2));
+				return;
+			}
+			
+        $this->_list_all_email_msgs();
+    }
+
+    /**
+     * Handles actions for email messages
+     */
+    public function email_action()
+    {
+			// Authenticate the user
+			if ( ! $this->api_service->_login(TRUE))
+			{
+				$this->set_error_message($this->response(2));
+				return;
+			}
+			
+        if ( ! $this->api_service->verify_array_index($this->request, 'action'))
+        {
+            $this->set_error_message(array(
+                "error" => $this->api_service->get_error_msg(001,'action')
+            )); 
+            return;
+        }
+        else
+        {
+            $this->by = $this->request['action'];
+        }
+        
+        switch ($this->by)
+        {
+            case "d":
+                $this->_delete_email_msg();
+            break;
+            
+            case "s":
+                $this->_spam_email_msg();
+            break;
+
+            default:
+                $this->set_error_message(array(
+                    "error" => $this->api_service->get_error_msg(001)
+                ));
+        }
+
+    }
+
+    /**
+     * List first 20 email messages
+     *
+     * @return array
+     */
+    public function _list_all_email_msgs()
+    {
+        $ret_json_or_xml = ''; // Will hold the return JSON/XML string
+  
+        $items = ORM::factory('message')
+            ->where('service_messageid', '2')
+            ->where('message_type','1')
+            ->orderby('message_date','desc')
+            ->find_all($this->list_limit);
+
+        // Set the no. of records fetched
+        $this->record_count = $items->count();
+        
+        $json_categories = array();
+        
+        $i = 0;
+
+        //No record found.
+        if ($items->count() == 0)
+        {
+            $this->response_data = $this->response(4);
+            return;
+        }
+
+        foreach ($items as $email)
+        {
+            if ( $response_type == 'json' OR $response_type == 'jsonp')
+            {
+                $json_categories[] = array("email" => $item);        
+            }
+            else
+            {
+                $json_categories['email'.$i] = array('email' => 
+                        $twitter);
+                $this->replar[] = 'email'.$i;
+            }
+        }
+        
+        // Create the json array
+        $data = array("payload" => array(
+            "domain" => $this->domain,
+            "count" => $json_categories),
+            "error" => $this->api_service->get_error_msg(0));
+
+        if ($this->response_type == 'json' OR $this->response_type == 'jsonp') 
+        {
+            $ret_json_or_xml = $this->array_as_json($data);
+        }
+        else
+        {
+            $ret_json_or_xml = $this->array_as_xml($data, $this->replar);
+        }
+
+        $this->response_data = $ret_json_or_xml;
+
+    }
+    
+    /**
+     * Delete existing email message
+     *
+     * @return Array
+     */
+    public function _delete_email_msg()
+    {
+        $ret_val = 0; // Initialize a 0 return value; successful execution
+        
+        if ($_POST)
+        {
+            $post = Validation::factory($_POST);
+
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+            // Add some rules, the input field, followed by a list of 
+            //checks, carried out in order
+            $post->add_rules('action','required', 'alpha', 'length[1,1]');
+            $post->add_rules('message_id','required','numeric');
+
+            if ($post->validate(FALSE))
+            {
+                $email_id = $post->message_id;
+                $email = new Message_Model($email_id);
+                if ($email->loaded == true)
+                {
+                    $email->delete();
+                }
+                else
+                {
+                    //email id doesn't exist in DB
+                    //TODO i18nize the string
+                    $this->error_messages .= "Email ID does not exist.";
+                    $ret_value = 1;
+
+                }
+            }
+            else
+            {
+                //TODO i18nize the string
+                $this->error_messages .= "Email ID is required.";
+                $ret_value = 1;
+            }
+
+        }
+        else
+        {
+            $ret_value = 3;
+        }
+        
+        $this->response_data = $this->response($ret_value);
+    }
+
+    /**
+     * Spam / Unspam existing email message
+     *
+     * @return Array
+     */
+    public function _spam_email_msg()
+    {
+        $ret_val = 0; // Initialize a 0 return value; successful execution
+        
+        if($_POST)
+        {
+            $post = Validation::factory($_POST);
+
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+            // Add some rules, the input field, followed by a list of 
+            //checks, carried out in order
+            $post->add_rules('action','required', 'alpha', 'length[1,1]');
+            $post->add_rules('message_id','required','numeric');
+
+            if ($post->validate(FALSE))
+            {
+                $email_id = $post->message_id;
+                $email = new Message_Model($email_id);
+                if ($email->loaded == true)
+                {
+                    if ($email->message_level == '1')
+                    {
+                        $email->message_level = '99';
+                    }
+                    else
+                    {
+                        $email->message_level = '1';
+                    }
+
+                    $email->save();
+                }
+                else
+                {
+                    //email id doesn't exist in DB
+                    //TODO i18nize the string
+                    $this->error_messages .= "Email ID does not exist.";
+                    $ret_value = 1;
+
+                }
+            }
+            else
+            {
+                //TODO i18nize the string
+                $this->error_messages .= "Email ID is required.";
+                $ret_value = 1;
+            }
+
+        }
+        else
+        {
+            $ret_value = 3;
+        }
+        
+        $this->response_data = $this->response($ret_value);
+    }
+
+}
+
diff --git a/application/libraries/api/MY_Incidents_Api_Object.php b/application/libraries/api/MY_Incidents_Api_Object.php
new file mode 100755
index 0000000..1a1ef67
--- /dev/null
+++ b/application/libraries/api/MY_Incidents_Api_Object.php
@@ -0,0 +1,901 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Incidents_Api_Object
+ *
+ * This class handles reports activities via the API.
+ *
+ * @version 26 - Emmanuel Kala 2010-10-22
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Incidents_Api_Object extends Api_Object_Core {
+	/**
+	 * Record sorting order ASC or DESC
+	 * @var string
+	 */
+	protected $sort;
+
+	/**
+	 * Column name by which to order the records
+	 * @var string
+	 */
+	protected $order_field;
+
+	/**
+	 * Should the response include comments
+	 * @var string
+	 */
+	protected $comments;
+
+	/**
+	 * Constructor
+	 */
+	public function __construct($api_service)
+	{
+		parent::__construct($api_service);
+	}
+
+	/**
+	 * Implementation of abstract method in parent
+	 *
+	 * Handles the API task parameters
+	 */
+	public function perform_task()
+	{
+		// Check if the 'by' parameter has been specified
+		if ( ! $this->api_service->verify_array_index($this->request, 'by'))
+		{
+			// Set "all" as the default method for fetching incidents
+			$this->by = 'all';
+		}
+		else
+		{
+			$this->by = $this->request['by'];
+		}
+
+		// Check optional parameters
+		$this->_check_optional_parameters();
+
+		// Begin task switching
+		switch ($this->by)
+		{
+			// Get all incidents
+			case "all":
+				$this->response_data = $this->_get_incidents();
+			break;
+
+			// Get specific incident by ID
+			case "incidentid":
+				if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+				{
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(001, 'id')
+					));
+
+					return;
+				}
+				else
+				{
+					$params = array('i.id = '.$this->check_id_value($this->request['id']));
+					$this->response_data = $this->_get_incidents($params);
+				}
+			break;
+
+			// Get incidents by latitude and longitude
+			case "latlon":
+				if ($this->api_service->verify_array_index($this->request, 'latitude')
+					AND $this->api_service->verify_array_index($this->request, 'longitude'))
+				{
+					// Build out the parameters
+					$lat = $this->check_cordinate_value($this->request['latitude']);
+					$lon = $this->check_cordinate_value($this->request['longitude']);
+					$params = array(
+						'l.latitude = '.$this->request['latitude'],
+						'l.longitude = '.$this->request['longitude']
+					);
+					if ($lat==0 or $lon==0)
+					{
+						$this->set_error_message(array(
+							"error" => $this->api_service->get_error_msg(001, 'invalid latitude or longitude values')
+						));
+						
+						return;
+					}
+					else
+					{
+						if(isset($this->request['radius']))
+						{
+							$rad = $this->check_id_value($this->request['radius']);
+							//we take this to be radius of the earth, this sems to be more efficient than perming them directly at the query level
+							$R = 6371;
+							$maxLat = $lat + rad2deg($rad/$R);
+							$minLat = $lat - rad2deg($rad/$R);
+							// compensate for degrees longitude getting smaller with increasing latitude
+							$maxLon = $lon + rad2deg($rad/$R/cos(deg2rad($lat)));
+							$minLon = $lon - rad2deg($rad/$R/cos(deg2rad($lat)));
+							$params = array(
+								'l.latitude > '.$minLat,
+								'l.latitude < '.$maxLat,
+								'l.longitude > '.$minLon,
+								'l.longitude < '.$maxLon
+							);
+						}
+						$this->response_data = $this->_get_incidents($params);
+					}
+				}
+				else
+				{
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(001, 'latitude or longitude')
+					));
+
+					return;
+				}
+			break;
+
+			// Get incidents by location id
+			case "locid":
+				if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+				{
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(001, 'id')
+					));
+
+					return;
+				}
+				else
+				{
+					$params = array(
+						'i.location_id = '.$this->check_id_value($this->request['id'])
+					);
+
+					$this->response_data = $this->_get_incidents($params);
+				}
+			break;
+
+			// Get incidents by location name
+			case "locname":
+				if ( ! $this->api_service->verify_array_index($this->request, 'name'))
+				{
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(001, 'name')
+					));
+
+					return;
+				}
+				else
+				{
+					$params = array(
+						'l.location_name = "'.$this->request['name'].'"'
+					);
+
+					$this->response_data = $this->_get_incidents($params);
+				}
+			break;
+
+			// Get incidents by category id
+			case "catid":
+				if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+				{
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(001, 'id')
+					));
+
+					return;
+				}
+				else
+				{
+					$category_id = $this->check_id_value($this->request['id']);
+					$params = array(
+						'c.id = '.$category_id,
+						'c.category_visible = 1'
+					);
+
+					$this->response_data = $this->_get_incidents($params);
+				}
+			break;
+
+			// Get incidents by category name
+			case "catname":
+				if ( ! $this->api_service->verify_array_index($this->request, 'name'))
+				{
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(001, 'name')
+					));
+					return;
+				}
+				else
+				{
+					$params = array(
+						'c.category_title LIKE "%'.$this->request['name'].'%"',
+						'c.category_visible = 1'
+					);
+
+					$this->response_data = $this->_get_incidents($params);
+				}
+			break;
+
+			// Get the number of reports in each category
+			case "catcount":
+				$this->response_data = $this->_get_incident_counts_per_category();
+			break;
+
+			// Get incidents greater than a specific incident_id in the DB
+			case "sinceid":
+				if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+				{
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(001, 'id')
+					));
+
+					return;
+				}
+				else
+				{
+					$params = array(
+						'i.id > '.$this->check_id_value($this->request['id'])
+					);
+
+					$this->response_data = $this->_get_incidents($params);
+				}
+			break;
+
+			// Get incidents less that a specific incident_id
+			case "maxid":
+				if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+				{
+					$this->set_error_message(array(
+						"error" => $this->api_service->get_error_msg(001, 'id')
+					));
+
+					return;
+				}
+				else
+				{
+					$params = array(
+						'i.id < '.$this->check_id_value($this->request['id'])
+					);
+
+					$this->response_data = $this->_get_incidents($params);
+				}
+			break;
+
+			// Get incidents based on a box using two lat,lon coords
+			case "bounds":
+				$c = isset($this->request['c']) ? $this->request['c'] : 0;
+				$this->response_data = $this->_get_incidents_by_bounds($this->request['sw'],$this->request['ne'],$c);
+			break;
+
+			// Error therefore set error message
+			default:
+				$this->set_error_message(array(
+					"error" => $this->api_service->get_error_msg(002)
+				));
+		}
+	}
+
+	/**
+	 * Checks for optional parameters in the request and sets the values
+	 * in the respective class members
+	 */
+	protected function _check_optional_parameters()
+	{
+		// Check if the sort parameter has been specified
+		if ($this->api_service->verify_array_index($this->request, 'sort'))
+		{
+			$this->sort = ($this->request['sort'] == '0') ? 'ASC' : 'DESC';
+		}
+		else
+		{
+			$this->sort = 'DESC';
+		}
+
+		// Check if the limit parameter has been specified
+		if ($this->api_service->verify_array_index($this->request, 'limit'))
+		{
+			$this->set_list_limit($this->request['limit']);
+		}
+
+		// Check if the orderfield parameter has been specified
+		if ($this->api_service->verify_array_index($this->request, 'orderfield'))
+		{
+			switch ($this->request['orderfield'])
+			{
+				case "incidentid":
+					$this->order_field = 'i.id';
+				break;
+
+				case "locationid":
+					$this->order_field = 'i.location_id';
+				break;
+
+				case "incidentdate":
+					$this->order_field = 'i.incident_date';
+				break;
+
+				default:
+					$this->order_field = 'i.incident_date';
+			}
+		}
+		else
+		{
+			$this->order_field = 'i.incident_date';
+		}
+
+
+		// Check if the 'comments' parameter has been specified
+		if ( ! $this->api_service->verify_array_index($this->request, 'comments'))
+		{
+			// Default to not including comments
+			$this->comments = 0;
+		}
+		else
+		{
+			$this->comments = $this->request['comments'];
+		}
+	}
+
+	/**
+	 * Generic function to get reports by given set of parameters
+	 *
+	 * @param string $where SQL where clause
+	 * @return string XML or JSON string
+	 */
+	public function _get_incidents($where = array())
+	{
+		// STEP 1.
+		// Get the incidents
+		$items = Incident_Model::get_incidents($where, $this->list_limit, $this->order_field, $this->sort);
+
+		//No record found.
+		if ($items->count() == 0)
+		{
+			return $this->response(4, $this->error_messages);
+		}
+
+		// Records found - proceed
+
+		// Set the no. of records returned
+		$this->record_count = $items->count();
+
+		// Will hold the XML/JSON string to return
+		$ret_json_or_xml = '';
+
+		$json_reports = array();
+		$json_report_media = array();
+		$json_report_categories = array();
+		$json_incident_media = array();
+		$upload_path = str_replace("media/uploads/", "", Kohana::config('upload.relative_directory')."/");
+
+		//XML elements
+		$xml = new XmlWriter();
+		$xml->openMemory();
+		$xml->startDocument('1.0', 'UTF-8');
+		$xml->startElement('response');
+		$xml->startElement('payload');
+		$xml->writeElement('domain',$this->domain);
+		$xml->startElement('incidents');
+
+		// Records found, proceed
+		// Store the incident ids
+		$incidents_ids = array();
+		$custom_field_items = array();
+		foreach ($items as $item)
+		{
+			$incident_ids[] = $item->incident_id;
+			$thiscustomfields = customforms::get_custom_form_fields($item->incident_id, null, false, "view");
+			if(!empty($thiscustomfields))
+			{
+				$custom_field_items[$item->incident_id] = $thiscustomfields;
+			}
+		}
+
+		//
+		// STEP 2.
+		// Fetch the incident categories
+		//
+
+		// Execute the query
+		$incident_categories = ORM::factory('category')
+			->select('category.*, incident_category.incident_id')
+			->join('incident_category','category.id','incident_category.category_id')
+			->in('incident_category.incident_id', $incident_ids)
+			->find_all();
+
+		// To hold the incident category items
+		$category_items = array();
+
+		// Fetch items into array
+		foreach ($incident_categories as $incident_category)
+		{
+			$category_items[$incident_category->incident_id][] = $incident_category->as_array();
+		}
+
+		// Free temporary variables from memory
+		unset ($incident_categories);
+
+		//
+		// STEP 3.
+		// Fetch the media associated with all the incidents
+		//
+		$media_items_result = ORM::factory('media')->in('incident_id', $incident_ids)->find_all();
+
+		// To store the fetched media items
+		$media_items = array();
+
+		// Fetch items into array
+		foreach ($media_items_result as $media_item)
+		{
+			$media_item_array = $media_item->as_array();
+			if ( $media_item->media_type == 1 AND ! empty($media_item->media_thumb))
+			{
+				$media_item_array["media_thumb_url"] =  url::convert_uploaded_to_abs($media_item->media_thumb);
+				$media_item_array["media_link_url"] = url::convert_uploaded_to_abs($media_item->media_link);
+			}
+			$media_items[$media_item->incident_id][] = $media_item_array;
+		}
+
+		// Free temporary variables
+		unset ($media_items_result, $media_item_array);
+
+		//
+		// STEP 4.
+		// Fetch the comments associated with the incidents
+		//
+		if ($this->comments) {
+			// Execute the query
+			$incident_comments = ORM::factory('comment')
+				->in('incident_id', $incident_ids)
+				->where('comment_spam', 0)
+				->find_all();
+
+			// To hold the incident category items
+			$comment_items = array();
+
+			// Fetch items into array
+			foreach ($incident_comments as $incident_comment)
+			{
+				$comment_items[$incident_comment->incident_id][] = $incident_comment->as_array();
+			}
+			// Free temporary variables from memory
+			unset ($incident_comments);
+		}
+
+		//
+		// STEP 5.
+		// Return XML
+		//
+		foreach ($items as $item)
+		{
+			// Build xml file
+			$xml->startElement('incident');
+
+			$xml->writeElement('id',$item->incident_id);
+			$xml->writeElement('title',$item->incident_title);
+			$xml->writeElement('description',$item->incident_description);
+			$xml->writeElement('date',$item->incident_date);
+			$xml->writeElement('mode',$item->incident_mode);
+			$xml->writeElement('active',$item->incident_active);
+			$xml->writeElement('verified',$item->incident_verified);
+			$xml->startElement('location');
+			$xml->writeElement('id',$item->location_id);
+			$xml->writeElement('name',$item->location_name);
+			$xml->writeElement('latitude',$item->latitude);
+			$xml->writeElement('longitude',$item->longitude);
+			$xml->endElement();
+			$xml->startElement('categories');
+
+			$json_report_categories[$item->incident_id] = array();
+
+			// Check if the incident id exists
+			if (isset($category_items[$item->incident_id]))
+			{
+				foreach ($category_items[$item->incident_id] as $category_item)
+				{
+					if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+					{
+						$json_report_categories[$item->incident_id][] = array(
+							"category"=> array(
+								"id" => $category_item['id'],
+								"title" => $category_item['category_title']
+							)
+						);
+					}
+					else
+					{
+						$xml->startElement('category');
+						$xml->writeElement('id', $category_item['id']);
+						$xml->writeElement('title', $category_item['category_title'] );
+						$xml->endElement();
+					}
+				}
+			}
+
+			// End categories
+			$xml->endElement();
+
+			$xml->startElement('comments');
+
+			$json_report_comments[$item->incident_id] = array();
+
+			// Check if the incident id exists
+			if (isset($comment_items[$item->incident_id]))
+			{
+				foreach ($comment_items[$item->incident_id] as $comment_item)
+				{
+					if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+					{
+						$json_report_comments[$item->incident_id][] = array(
+							"comment"=> $comment_item
+						);
+					}
+					else
+					{
+						$xml->startElement('comment');
+						$xml->writeElement('id',$comment_item['id']);
+						$xml->writeElement('comment_author',$comment_item['comment_author']);
+						$xml->writeElement('comment_email',$comment_item['comment_email']);
+						$xml->writeElement('comment_description',$comment_item['comment_description']);
+						$xml->writeElement('comment_date',$comment_item['comment_date']);
+						$xml->endElement();
+					}
+				}
+			}
+
+			// End comments
+			$xml->endElement();
+
+			$json_report_media[$item->incident_id] = array();
+
+			if (count($media_items) > 0)
+			{
+				if (isset($media_items[$item->incident_id]) AND count($media_items[$item->incident_id]) > 0)
+				{
+					$xml->startElement('mediaItems');
+
+					foreach ($media_items[$item->incident_id] as $media_item)
+					{
+
+						if($this->response_type == 'json' OR $this->response_type == 'jsonp')
+						{
+							 $json_media_array = array(
+								"id" => $media_item['id'],
+								"type" => $media_item['media_type'],
+								"link" => $media_item['media_link'],
+								"thumb" => $media_item['media_thumb'],
+							);
+
+							// If we are look at certain types of media, add some fields
+							if($media_item['media_type'] == 1 AND isset($media_item['media_thumb_url']))
+							{
+								// Give a full absolute URL to the image
+								$json_media_array["thumb_url"] =  $media_item['media_thumb_url'];
+								$json_media_array["link_url"] = $media_item['media_link_url'];
+							}
+							$json_report_media[$item->incident_id][] = $json_media_array;
+						}
+						else
+						{
+							$xml->startElement('media');
+
+							if( $media_item['id'] != "" )
+							{
+								$xml->writeElement('id',$media_item['id']);
+							}
+
+							if( $media_item['media_title'] != "" )
+							{
+								$xml->writeElement('title', $media_item['media_title']);
+							}
+
+							if( $media_item['media_type'] != "" )
+							{
+								$xml->writeElement('type', $media_item['media_type']);
+							}
+
+							if( $media_item['media_link'] != "" )
+							{
+								$xml->writeElement('link', $upload_path.$media_item['media_link']);
+							}
+
+							if( $media_item['media_thumb'] != "" )
+							{
+								$xml->writeElement('thumb', $upload_path.$media_item['media_thumb']);
+							}
+
+							if($media_item['media_type'] == 1 AND isset($media_item['media_thumb_url']))
+							{
+								$xml->writeElement('thumb_url', $media_item['media_thumb_url']);
+
+								$xml->writeElement('link_url', $media_item['media_link_url']);
+							}
+							$xml->endElement();
+						}
+					}
+					$xml->endElement(); // Media
+				}
+			}
+			
+			if (count($custom_field_items) > 0 AND $this->response_type != 'json' AND $this->response_type != 'jsonp')
+			{
+				if (isset($custom_field_items[$item->incident_id]) AND count($custom_field_items[$item->incident_id]) > 0)
+				{
+					$xml->startElement('customFields');
+					foreach ($custom_field_items[$item->incident_id] as $field_item)
+					{
+						$xml->startElement('field');
+						foreach($field_item as $fname => $fval){
+							$xml->writeElement($fname, $fval);
+						}
+						$xml->endElement(); // field
+					}
+					$xml->endElement(); // customFields
+				}
+			}
+
+			$xml->endElement(); // End incident
+
+			// Check for response type
+			if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+			{
+				$json_reports[] = array(
+					"incident" => array(
+						"incidentid" => $item->incident_id,
+						"incidenttitle" => $item->incident_title,
+						"incidentdescription" => $item->incident_description,
+						"incidentdate" => $item->incident_date,
+						"incidentmode" => $item->incident_mode,
+						"incidentactive" => $item->incident_active,
+						"incidentverified" => $item->incident_verified,
+						"locationid" => $item->location_id,
+						"locationname" => $item->location_name,
+						"locationlatitude" => $item->latitude,
+						"locationlongitude" => $item->longitude
+					),
+					"categories" => $json_report_categories[$item->incident_id],
+					"media" => $json_report_media[$item->incident_id],
+					"comments" => $json_report_comments[$item->incident_id],
+					"customfields" => isset($custom_field_items[$item->incident_id]) ? $custom_field_items[$item->incident_id] : array()
+				);
+			}
+		}
+
+		// Create the JSON array
+		$data = array(
+			"payload" => array(
+				"domain" => $this->domain,
+				"incidents" => $json_reports
+			),
+			"error" => $this->api_service->get_error_msg(0)
+		);
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			return $this->array_as_json($data);
+
+		}
+		else
+		{
+			$xml->endElement(); //end incidents
+			$xml->endElement(); // end payload
+			$xml->startElement('error');
+			$xml->writeElement('code',0);
+			$xml->writeElement('message','No Error');
+			$xml->endElement();//end error
+			$xml->endElement(); // end response
+			return $xml->outputMemory(true);
+		}
+	}
+
+	/**
+	 * Returns the number of reports in each category
+	 */
+	private function _get_incident_counts_per_category()
+	{
+		$this->query = 'SELECT category_id, COUNT(category_id) AS reports FROM '.$this->table_prefix.'incident_category '
+					. 'WHERE incident_id IN (SELECT id FROM '.$this->table_prefix.'incident WHERE incident_active = 1) '
+					. 'GROUP BY category_id';
+
+		$items = $this->db->query($this->query);
+
+		$category_counts = array();
+
+		foreach ($items as $item)
+		{
+			$category_counts[] = array('category_id' => $item->category_id, 'reports' => $item->reports);
+		}
+
+		$this->query = 'SELECT COUNT(id) AS total_count FROM '.$this->table_prefix.'incident WHERE incident_active = 1;';
+
+		$count = $this->db->query($this->query);
+
+		foreach($count as $c)
+		{
+			$total_count = $c->total_count;
+			break;
+		}
+
+		//create the json array
+		$data = array(
+			"payload" => array(
+				"domain" => $this->domain,
+				"category_counts" => $category_counts,
+				"total_reports" => $total_count
+			),
+			"error" => $this->api_service->get_error_msg(0)
+		);
+
+		// Return data
+		$this->response_data =  ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+			? $this->array_as_json($data)
+			: $this->array_as_xml($data, $replar);
+
+		echo $this->response_data;
+	}
+
+	/**
+	 * Get incidents within a certain lat,lon bounding box
+	 *
+	 * @param double $sw is the southwest lat,lon of the box
+	 * @param double $ne is the northeast lat,lon of the box
+	 * @param int $c is the categoryid
+	 * @return string XML or JSON string containing the fetched incidents
+	 */
+	private function _get_incidents_by_bounds($sw, $ne, $c)
+	{
+		// Break apart location variables, if necessary
+		$southwest = array();
+		if (isset($sw))
+		{
+			$southwest = explode(",",$sw);
+		}
+
+		$northeast = array();
+		if (isset($ne))
+		{
+			$northeast = explode(",",$ne);
+		}
+
+		// To hold the parameters
+		$params = array();
+		if ( count($southwest) == 2 AND count($northeast) == 2 )
+		{
+			$lon_min = (float) $southwest[0];
+			$lon_max = (float) $northeast[0];
+			$lat_min = (float) $southwest[1];
+			$lat_max = (float) $northeast[1];
+
+			// Add parameters
+			array_push($params,
+				'l.latitude >= '.$lat_min,
+				'l.latitude <= '.$lat_max,
+				'l.longitude >= '.$lon_min,
+				'l.longitude <= '.$lon_max
+			);
+		}
+
+		// Fix for pulling categories using the bounding box
+		// Credits to Antonoio Lettieri http://github.com/alettieri
+		// Check if the specified category id is valid
+		if (Category_Model::is_valid_category($c))
+		{
+			array_push($params, 'c.id = '.$c);
+		}
+		return $this->_get_incidents($params);
+	}
+
+	/**
+	 * Gets the number of approved reports
+	 *
+	 * @param string response_type - XML or JSON
+	 * @return string
+	 */
+	public function get_incident_count()
+	{
+		$json_count = array();
+
+		$this->query = 'SELECT COUNT(*) as count FROM '.$this->table_prefix.'incident WHERE incident_active = 1';
+
+		$items = $this->db->query($this->query);
+
+		foreach ($items as $item)
+		{
+			$count = $item->count;
+			break;
+		}
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			$json_count[] = array("count" => $count);
+		}
+		else
+		{
+			$json_count['count'] = array("count" => $count);
+			$this->replar[] = 'count';
+		}
+
+		// Create the JSON array
+		$data = array(
+				"payload" => array(
+				"domain" => $this->domain,
+				"count" => $json_count
+			),
+			"error" => $this->api_service->get_error_msg(0)
+		);
+
+		$this->response_data = ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+			? $this->array_as_json($data)
+			: $this->array_as_xml($data, $this->replar);
+	}
+
+	/**
+	 * Get an approximate geographic midpoint of al approved reports.
+	 *
+	 * @param string $response_type - XML or JSON
+	 * @return string
+	 */
+	public function get_geographic_midpoint()
+	{
+		$json_latlon = array();
+
+		$this->query = 'SELECT AVG( latitude ) AS avglat, AVG( longitude )
+					AS avglon FROM '.$this->table_prefix.'location WHERE id IN
+					(SELECT location_id FROM '.$this->table_prefix.'incident WHERE
+					incident_active = 1)';
+
+		$items = $this->db->query($this->query);
+
+		foreach ($items as $item)
+		{
+			$latitude = $item->avglat;
+			$longitude = $item->avglon;
+			break;
+		}
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			$json_latlon[] = array(
+				"latitude" => $latitude,
+				"longitude" => $longitude
+			);
+		}
+		else
+		{
+			$json_latlon['geographic_midpoint'] = array(
+				"latitude" => $latitude,
+				"longitude" => $longitude
+			);
+
+			$replar[] = 'geographic_midpoint';
+		}
+
+		// Create the JSON array
+		$data = array(
+			"payload" => array(
+				"domain" => $this->domain,
+				"geographic_midpoint" => $json_latlon
+			),
+			"error" => $this->api_service->get_error_msg(0)
+		);
+
+		// Return data
+		$this->response_data =  ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+			? $this->array_as_json($data)
+			: $this->array_as_xml($data, $replar);
+	}
+
+	protected function check_cordinate_value($cord)
+	{
+		return floatval($cord);
+	}
+
+}
diff --git a/application/libraries/api/MY_Kml_Api_Object.php b/application/libraries/api/MY_Kml_Api_Object.php
new file mode 100755
index 0000000..e1883da
--- /dev/null
+++ b/application/libraries/api/MY_Kml_Api_Object.php
@@ -0,0 +1,198 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Kml_Api_Object
+ *
+ * This class handles GET request for KML via the API.
+ *
+ * @version 24 - Emmanuel Kala 2010-10-22
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+ 
+require_once Kohana::find_file('libraries/api', Kohana::config('config.extension_prefix').'Categories_Api_Object');
+require_once Kohana::find_file('libraries/api', Kohana::config('config.extension_prefix').'Incidents_Api_Object');
+
+class Kml_Api_Object extends Api_Object_Core {
+
+    private $categories_api_object; // Categories API Object
+    private $incidents_api_object; // Reports API Object
+    
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+        
+        // Set the response tpye for the API service to JSON
+        $api_service->set_response_type('json');
+        
+        // Instantitate API objects to be used
+        $this->categories_api_object = new Categories_Api_Object($api_service);
+        $this->incidents_api_object = new Incidents_Api_Object($api_service);
+        
+        // Set the response type for this instance to json
+        $this->set_response_type('json');
+                
+    }
+
+    /**
+     * Implementation of abstract method declared in superclass
+     *
+     * API task handler
+     */
+    public function perform_task()
+    {
+        $this->response_data  = $this->_3dkml();
+    }
+    
+    /**
+     * Get a list of incident categories
+     * returns an array
+     * FIXME: Might as well add functionality to return this in the API
+     *
+     * Return format: array[incident_id][] = category_id;
+     *
+     */
+    private function _report_categories()
+    {
+
+        $this->query = "SELECT incident_id, category_id FROM `".
+            $this->table_prefix."incident_category` ORDER BY id DESC";
+            
+        $items = $this->db->query($this->query);
+        
+        $data = array(); // Array to hold the return data
+        
+        foreach ($items as $item)
+        {
+            $data[$item->incident_id][] = $item->category_id;
+        }
+        return $data;
+    }
+
+    /**
+     * return KML for 3d "geo spatial temporal" map
+     * FIXME: This could probably be done in less than >5 foreach loops
+     *
+     * @param string response_type - XML or JSON
+     */
+    private function _3dkml()
+    {
+        $kml = '<?xml version="1.0" encoding="UTF-8"?>
+        <kml xmlns="http://earth.google.com/kml/2.2">
+        <Document>
+        <name><![CDATA['.Kohana::config('settings.site_name').']]></name>'."\n";
+
+        // Get the categories that each incident belongs to
+        $incident_categories = $this->_report_categories();
+
+        // Get category colors in this format: $category_colors[id] = color
+        $categories = json_decode($this->categories_api_object->get_categories_by_all());
+        
+        $categories = $categories->payload->categories;
+        $category_colors = array();
+
+        foreach ($categories as $category) 
+        {
+            $category_colors[$category->category->id] = $category->category->color;
+        }
+        
+        // Finally, grab the incidents
+        $incidents = json_decode($this->incidents_api_object->_get_incidents());
+
+        $incidents = $incidents->payload->incidents;
+        
+        // Set the no. of incidents fetched
+        $this->record_count = sizeof($incidents);
+        
+        // Calculate times for relative altitudes (
+        // This is the whole idea behind 3D maps)
+
+        $incident_times = array();
+
+        foreach ($incidents as $inc_obj) 
+        {
+            $incident = $inc_obj->incident;
+            $incident_times[$incident->incidentid] = strtotime(
+                $incident->incidentdate);
+        }
+
+        // All times to be adjusted according to max altitude.
+
+        $max_altitude = 10000;
+        $newest = 0;
+
+        foreach ($incident_times as $incident_id => $timestamp) 
+        {
+            if ( ! isset($oldest)) $oldest = $timestamp;
+
+            $incident_times[$incident_id] -= $oldest;
+            
+            if ($newest < $incident_times[$incident_id]) $newest = 
+                $incident_times[$incident_id];
+        }
+
+        foreach ($incident_times as $incident_id => $timestamp) 
+        {
+            $incident_altitude[$incident_id] = 0;
+            
+            if($newest != 0) $incident_altitude[$incident_id] =
+                floor(($timestamp / $newest) * $max_altitude);
+        }
+
+        // Compile KML and output
+        foreach ($incidents as $inc_obj) 
+        {
+            $incident = $inc_obj->incident;
+
+            if (array_key_exists($incident->incidentid, $incident_categories))
+            {
+                $category_id = $incident_categories[
+                    $incident->incidentid][0]; // Could be multiple categories. Pick the first one.
+            
+                if (array_key_exists($category_id, $category_colors))
+                {
+                    $hex_color =  $category_colors[$category_id];
+
+                    // Color for KML is not the traditional HTML Hex of (rrggbb). It's (aabbggrr). aa = alpha or transparency
+                    $color = 'FF'.$hex_color{4}.$hex_color{5}.
+                        $hex_color{2}.$hex_color{3}.$hex_color{0}.$hex_color{1};
+
+                    $kml .= '<Placemark>
+                        <name><![CDATA['.$incident->incidenttitle.']]></name>
+                        <description><![CDATA['.$incident->incidentdescription.']]></description>
+                        <Style>
+                            <IconStyle>
+                                <Icon>
+                                    <href>'.url::base().'media/img/color_icon.php?c='.$hex_color.'</href>
+                                </Icon>
+                            </IconStyle>
+                            <LineStyle>
+                                <color>'.$color.'</color>
+                                <width>2</width>
+                            </LineStyle>
+                        </Style>
+                        <Point>
+                            <extrude>1</extrude>
+                            <altitudeMode>relativeToGround</altitudeMode>
+                            <coordinates>'.$incident->locationlongitude.','.$incident->locationlatitude.','.$incident_altitude[$incident->incidentid].'</coordinates>
+                        </Point>
+                    </Placemark>'."\n";
+                }
+            }
+        }
+
+        $kml .= '</Document>
+        </kml>';
+        $this->set_response_type('xml');
+
+        return $kml;
+    }
+
+}
diff --git a/application/libraries/api/MY_Locations_Api_Object.php b/application/libraries/api/MY_Locations_Api_Object.php
new file mode 100755
index 0000000..e2eea35
--- /dev/null
+++ b/application/libraries/api/MY_Locations_Api_Object.php
@@ -0,0 +1,137 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles locations activities via the API.
+ *
+ * @version 25- Emmanuel Kala 2011-07-08
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Locations_Api_Object extends Api_Object_Core {
+
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+    }
+    
+    public function perform_task()
+    {
+        // Check if the by parameter has been set    
+        if ($this->api_service->verify_array_index($this->request, 'by'))
+        {
+            $this->by = $this->request['by'];
+        }
+                
+        switch ($this->by)
+        {
+            case "latlon":
+            break;
+            
+            // Get location by id
+            case "locid":
+                if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+                {
+                    $this->set_error_message(array(
+                        "error" => $this->api_service->get_error_msg(001, 'id')
+                    ));
+                    
+                    return;
+                }
+                else
+                {
+                    $this->response_data = $this->_get_locations(array('id' => $this->request['id']));
+                }
+            break;
+            
+            // Get locations by country id
+            case "country":
+                if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+                {
+                    $this->set_error_message(array(
+                        "error" => $this->api_service->get_error_msg(001, 'id')
+                    ));
+                    
+                    return;
+                }
+                else
+                {
+                    $this->response_data = $this->_get_locations(array('country_id' => $this->request['id']));
+                }
+            break;
+            
+            default:
+                $this->response_data = $this->_get_locations();
+        }        
+    }
+
+	/**
+	 * Get a list of locations
+	 * 
+	 * @param array $where Key->value array of the set of filters to apply
+	 * @return string JSON/XML string with the location data
+	 */
+	private function _get_locations($where = array())
+	{
+		// Fetch the location items
+		$items = ORM::factory('Location')
+				->select('location_name AS name', 'location.*') // Add extra name field for backwards compat
+				->where($where)
+				->where('location_visible', 1)
+				->limit($this->list_limit)
+				->find_all();
+        
+		//No record found.
+		if ($items->count() == 0)
+		{
+			return $this->response(4);
+		}
+		
+		// Counter
+		$i = 0;
+		
+		// To hold the json data
+		$json_locations = array();
+		
+		foreach ($items as $item)
+		{
+			$item = $item->as_array();
+			// Hide variables we don't want publicly exposed
+			unset($item['location_visible']);
+			
+			// Needs different treatment depending on the output
+			if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+			{
+				$json_locations[] = array("location" => $item);
+			} 
+			else 
+			{
+				$json_locations['location'.$i] = array("location" => $item);
+
+				$this->replar[] = 'location'.$i;
+			}
+
+			$i++;
+		}
+		
+		// Array to be converted to either JSON or xml
+		$data = array(
+			"payload" => array(
+			"domain" => $this->domain,
+			"locations" => $json_locations),
+			"error" => $this->api_service->get_error_msg(0)
+		);
+
+		return ($this->response_type == 'json' OR $this->response_type == 'jsonp') 
+			? $this->array_as_json($data)
+			: $this->array_as_xml($data, $this->replar);
+	}
+}
+?>
diff --git a/application/libraries/api/MY_Private_Func_Api_Object.php b/application/libraries/api/MY_Private_Func_Api_Object.php
new file mode 100755
index 0000000..ae74254
--- /dev/null
+++ b/application/libraries/api/MY_Private_Func_Api_Object.php
@@ -0,0 +1,300 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles private functions that not accessbile by the public 
+ * via the API.
+ *
+ * @version 24 - Emmanuel Kala 2010-10-25
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Private_Func_Api_Object extends Api_Object_Core {
+
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+    }
+
+    /**
+     * Empty declaration for OOP compliance
+     */
+    public function perform_task()
+    {
+    }
+    
+    /**
+     * FrontlineSMS Key Validation
+     *
+     * @param string app_key FrontlineSMS Key
+     * @return bool, false if authentication fails
+     */
+    public function _chk_key($app_key = 0)
+    {
+        // Is this a valid FrontlineSMS Key?
+        $keycheck = Kohana::confg('settings.frontlinesms_key');
+
+        if ($keycheck->loaded)
+        {
+            return true;
+        }
+        else
+        {
+            return false;
+        }
+    }
+
+    /**
+     * Log user in.
+     *
+     * @param string $username User's username.
+     * @param string $password User's password.
+     * @return int user_id, false if authentication fails
+     */
+    public function _login($username, $password)
+    {
+        $auth = Auth::instance();
+
+        // Is user previously authenticated?
+        if ($auth->logged_in())
+        {
+            return $auth->get_user()->id;
+        }
+        else
+        {
+            // Attempt a login
+            if ($auth->login($username, $password))
+            {
+                return $auth->get_user()->id;
+            }
+            else
+            {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Provide statistics for the deployment
+     * 
+     */
+    public function statistics()
+    {
+
+        $messages_total = 0;
+        $messages_services = array();
+        $services = ORM::factory('service')->find_all();
+        
+        foreach ($services as $service) 
+        {
+            $message_count = ORM::factory('message')
+                ->join('reporter','message.reporter_id','reporter.id')
+                ->where('service_id', $service->id)
+                ->where('message_type', '1')
+                ->count_all();
+            
+            $service_name = $service->service_name;
+            
+            $messages_stats[$service_name] = $message_count;
+            
+            $messages_total += $message_count;
+        }
+
+        $messages_stats['total'] = $messages_total;
+
+        $incidents_total = ORM::factory('incident')->count_all();
+        $incidents_unapproved = ORM::factory('incident')->
+            where('incident_active', '0')->count_all();
+            
+        $incidents_approved = $incidents_total - $incidents_unapproved;
+        $incomingmedia_total = ORM::factory('feed_item')->count_all();
+        $categories_total = ORM::factory('category')->count_all();
+        $locations_total = ORM::factory('location')->count_all();
+
+        $data = array(
+                'incidents' => array(
+                'total' => $incidents_total,
+                'approved' => $incidents_approved,
+                'unapproved' => $incidents_unapproved
+            ),
+
+            'incoming_media' => array(
+                'total_feed_items' => $incomingmedia_total
+            ),
+
+            'categories' => array(
+                'total' => $categories_total
+            ),
+
+            'locations' => array(
+                'total' => $locations_total
+            ),
+
+            'messages' => $messages_stats,
+
+
+        );
+
+        $this->response_data = ($this->response_type == 'json')
+            ? $this->array_as_json($data)
+            : $this->array_as_xml($data);
+
+    }
+
+    /**
+     * Receive SMS's via FrontlineSMS or via Mobile Phone Native Apps
+     *
+     * @return string
+     */
+    public function sms()
+    {
+        $reponse = array();
+
+        // Validate User
+        // Should either be authenticated or have app_key
+        $username = isset($this->request['username']) ? $this->request['username'] : "";
+        $password = isset($this->request['password']) ? $this->request['password'] : "";
+
+        $app_key = isset($this->request['key']) ? $this->request['key'] : "";
+
+        if ( $user_id = $this->_login($username, $password))
+        {
+            // Process POST
+            // setup and initialize form field names
+            $form = array
+            (
+                'message_from' => '',
+                'message_description' => '',
+                'message_date' => ''
+            );
+
+            /**
+             * Instantiate Validation, 
+             * use $post, so we don't overwrite $_POST fields with our 
+             * own things
+             */
+            $post = Validation::factory($_POST);
+
+            //  Add some filters
+            $post->pre_filter('trim', TRUE);
+
+            /**
+             * Add some rules, the input field, followed by a list of 
+             * checks, carried out in order.
+             */
+            $post->add_rules('message_from', 'required', 'numeric',
+                    'length[6,20]');
+            $post->add_rules('message_description', 'required', 
+                    'length[3,300]');
+            $post->add_rules('message_date', 'date_mmddyyyy');
+
+            // Test to see if things passed the rule checks
+            if ($post->validate())
+            {
+
+                sms::add($post->message_from, $post->message_description);
+                // success!
+                $reponse = array(
+                    "payload" => array(
+                        "domain" => $this->domain,
+                        "success" => "true"
+                    ),
+                    "error" => $this->api_service->get_error_msg(0)
+                );
+
+            }
+
+            else
+            {
+                // Required parameters are missing or invalid
+                $reponse = array(
+                    "payload" => array(
+                        "domain" => $this->domain,
+                        "success" => "false"
+                    ),
+                    "error" => $this->api_service->get_error_msg(002)
+                );
+            }
+
+        }
+
+        else
+        {
+            // Authentication Failed. Invalid User or App Key
+            $reponse = array(
+                "payload" => array(
+                    "domain" => $this->domain,
+                    "success" => "false"
+                ),
+                "error" => $this->api_service->get_error_msg(005)
+            );
+        }
+        
+        // Set the response data
+        $this->response_data = ($this->response_type == 'json')
+            ? $this->array_as_json($reponse)
+            : $this->array_as_xml($reponse, array());
+    }
+
+
+
+    /**
+     * Get the latitude and longitude for the default centre of the map.
+     *
+     * @return string
+     */
+    public function map_center()
+    {
+        $json_mapcenters = array(); //lat and lon string to parse to json
+
+        // Find incidents
+        $this->query = "SELECT default_lat AS latitude, default_lon AS 
+            longitude FROM `".$this->table_prefix."settings`
+            ORDER BY id DESC ;";
+
+        $items = $this->db->query($this->query);
+        
+        // Set the no. of records fetched
+        $this->record_count = $items->count();
+        
+        $i = 0;
+
+        foreach ($items as $item)
+        {
+            // Needs different treatment depending on the output
+            if($this->response_type == 'json')
+            {
+                $json_mapcenters[] = array("mapcenter" => $item);
+            } 
+            else 
+            {
+                $json_mapcenters['mapcenter'.$i] = array(
+                        "mapcenter" => $item) ;
+                        
+                $this->replar[] = 'mapcenter'.$i;
+            }
+
+            $i++;
+        }
+
+        // Create the json array
+        $data = array("payload" => array(
+                    "domain" => $this->domain,
+                    "mapcenters" => $json_mapcenters
+                ),
+                "error" => $this->api_service->get_error_msg(0));
+
+        // Set the response data
+        $this->response_data =($this->response_type == 'json')
+            ? $this->array_as_json($data)
+            : $this->array_as_xml($data, $this->replar);
+    }
+
+}
diff --git a/application/libraries/api/MY_Report_Api_Object.php b/application/libraries/api/MY_Report_Api_Object.php
new file mode 100755
index 0000000..19ac266
--- /dev/null
+++ b/application/libraries/api/MY_Report_Api_Object.php
@@ -0,0 +1,160 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles posting report to ushahidi via the API.
+ *
+ * @version 24 - Emmanuel Kala 2010-10-25
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class Report_Api_Object extends Api_Object_Core {
+
+    private $error_string = ''; // To hold the string of error messages
+    
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+    }
+
+    /**
+     * Services the request for reporting an incident via the API
+     */
+    public function perform_task()
+    {
+  		// If user doesn't have member perms and allow_reports is disabled, Throw auth error
+  		if ( ! Kohana::config('settings.allow_reports') AND ! $this->api_service->_login(FALSE, TRUE) )
+			{
+				$this->set_error_message($this->response(2));
+				return;
+			}
+			
+        $ret_value = $this->_submit();
+        
+        $this->response_data =  $this->response($ret_value, $this->error_string);
+    }
+    
+    /**
+     * The actual reporting -
+     *
+     * @return int
+     */
+    private function _submit() 
+    {
+        // Setup and initialize form field names
+        $form = array(
+            'incident_title' => '',
+            'incident_description' => '',
+            'incident_date' => '',
+            'incident_hour' => '',
+            'incident_minute' => '',
+            'incident_ampm' => '',
+            'latitude' => '',
+            'longitude' => '',
+            'location_name' => '',
+            'country_id' => '',
+            'incident_category' => '',
+            'incident_news' => array(),
+            'incident_video' => array(),
+            'incident_photo' => array(),
+            'person_first' => '',
+            'person_last' => '',
+            'person_email' => ''
+        );
+        
+        $this->messages = $form;
+        
+        // Check for HTTP POST, setup validation
+        if ($_POST) 
+        {
+            // Instantiate Validation, use $post, so we don't overwrite 
+            // $_POST fields with our own things
+            $post = array_merge($_POST, $_FILES);
+            $post['incident_category'] = explode(',', $post['incident_category']);
+
+            // 
+            // EK <emmanuel at ushahidi.com> - 17/05/2012
+            // Commenting out this event ('ushahidi_action.report_submit_api') because
+            // of the following:
+            // The 'ushahidi_action.report_submit' and 'ushahidi_action.report_add'
+            // events should suffice for all plugins that wish to run extra
+            // operations once a report has been submitted and saved - avoid
+            // superfluous events
+            // 
+
+            // In case there's a plugin that would like to know about 
+            // this new incident, I mean report
+            // Event::run('ushahidi_action.report_submit_api', $post);
+
+            if (reports::validate($post))
+            {
+                // STEP 1: SAVE LOCATION
+                $location = new Location_Model();
+                reports::save_location($post, $location);
+
+                // STEP 2: SAVE INCIDENT
+                $incident = new Incident_Model();
+                reports::save_report($post, $incident, $location->id);
+
+                // STEP 2b: SAVE INCIDENT GEOMETRIES
+                reports::save_report_geometry($post, $incident);
+
+                // STEP 3: SAVE CATEGORIES
+                reports::save_category($post, $incident);
+
+                // STEP 4: SAVE MEDIA
+                reports::save_media($post, $incident);
+
+                // STEP 5: SAVE CUSTOM FORM FIELDS
+                reports::save_custom_fields($post, $incident);
+
+                // STEP 6: SAVE PERSONAL INFORMATION
+                reports::save_personal_info($post, $incident);
+
+                // Run events
+                Event::run('ushahidi_action.report_submit', $post);
+                Event::run('ushahidi_action.report_add', $incident);
+                
+                // Action::report_edit_api - Edited a Report
+                Event::run('ushahidi_action.report_edit_api', $incident);
+
+                // Success
+                return 0;
+
+            } 
+            else 
+            {               
+                // Populate the error fields, if any
+                $this->messages = arr::overwrite($this->messages, 
+                        $post->errors('report'));
+
+                foreach ($this->messages as $error_item => $error_description) 
+                {
+                    if( ! is_array($error_description)) 
+                    {
+                        $this->error_string .= $error_description;
+                        
+                        if ($error_description != end($this->messages)) 
+                        {
+                            $this->error_string .= " - ";
+                        }
+                    }
+                }
+
+                //FAILED!!!
+                return 1; //validation error
+            }
+        } 
+        else 
+        {
+            return 3; // Not sent by post method.
+        }
+    }
+
+}
diff --git a/application/libraries/api/MY_Sms_Api_Object.php b/application/libraries/api/MY_Sms_Api_Object.php
new file mode 100644
index 0000000..2354037
--- /dev/null
+++ b/application/libraries/api/MY_Sms_Api_Object.php
@@ -0,0 +1,234 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles GET request for KML via the API.
+ *
+ * @version 25 - Emmanuel Kala 2010-10-27
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class Sms_Api_Object extends Api_Object_Core {
+
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+    }
+
+    /**
+     * Handles the API request
+     */
+    public function perform_task()
+    {
+        // List the SMS messages by default
+        $this->_list_sms_msgs();
+    }
+    
+    /**
+     * Handles API action requests on an SMS
+     */
+    public function sms_action()
+    {
+        if ( ! $this->api_service->verify_array_index($this->request, 'action'))
+        {
+            $this->set_error_message(array(
+                "error" => $this->api_service->get_error_msg(001, 'action')
+            ));
+            
+            return;
+        }
+        else
+        {
+            $this->by  = $this->request['action'];
+        }
+        
+        switch ($this->by)
+        {
+            case "d":
+                $this->_delete_sms_msg();
+            break;
+            
+            case "s":
+                $this->_spam_sms_msg();
+            break;
+
+            default:
+                $this->set_error_message(array(
+                    "error" => $this->api_service->get_error_msg(002)
+                ));
+        }
+    }
+    
+    /**
+     * List first 20 sms messages
+     *
+     */
+    private function _list_sms_msgs()
+    {
+        $items = ORM::factory('message')
+            ->where('service_id', '1')
+            ->where('message_type','1')
+            ->orderby('message_date','desc')
+            ->find_all($this->list_limit);
+
+        $json_categories = array();
+        
+        // Set the no. of records fetched
+        $this->record_count = $items->count();
+        
+        $i = 0;
+
+        //No record found.
+        if ($items->count() == 0)
+        {
+            return $this->response(4);
+        }
+
+        foreach ($items as $sms)
+        {
+            if ( $this->response_type == 'json')
+            {
+                $json_categories[] = array("sms" => $item);        
+            }
+            else
+            {
+                $json_categories['sms'.$i] = array('sms' => $sms);
+                $this->replar[] = 'sms'.$i;
+            }
+        }
+        
+        // Create the json array
+        $data = array("payload" => array(
+            "domain" => $this->domain,
+            "count" => $json_categories),
+            "error" => $this->api_service->get_error_msg(0));
+
+        $this->response_data = ($this->response_type == 'json') 
+            ? $this->array_as_xml($data)
+            : $this->array_as_xml($data, $this->replar);
+    }
+   
+
+    /**
+     * Delete existing SMS message
+     *
+     */
+    private function _delete_sms_msg()
+    {
+        $ret_value = 0; // Start off with successful execution
+        
+        if($_POST)
+        {
+            $post = Validation::factory($_POST);
+
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+            // Add some rules, the input field, followed by a list of 
+            //checks, carried out in order
+            $post->add_rules('action','required', 'alpha', 'length[1,1]');
+            $post->add_rules('message_id.*','required','numeric');
+
+            if ($post->validate())
+            {
+                $sms_id = $post->message_id;
+                $sms = new Message_Model($sms_id);
+                if ($sms->loaded == true)
+                {
+                    $sms->delete();
+                }
+                else
+                {
+                    //Comment id doesn't exist in DB
+                    //TODO i18nize the string
+                    $this->error_messages .= "SMS ID does not exist.";
+                    $ret_value = 1;
+
+                }
+            }
+            else
+            {
+                //TODO i18nize the string
+                $this->error_messages .= "SMS ID is required.";
+                $ret_value = 1;
+            }
+
+        }
+        else
+        {
+            $ret_value = 3;
+        }
+        
+        $this->response_data = $this->response($ret_value);
+
+    }
+
+    /**
+     * Spam / Unspam existing SMS message
+     *
+     * @return Array
+     */
+    public function _spam_sms_msg()
+    {
+        $ret_val = 0; // Initialize a 0 return value; successful execution
+        
+        if ($_POST)
+        {
+            $post = Validation::factory($_POST);
+
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+            // Add some rules, the input field, followed by a list of 
+            //checks, carried out in order
+            $post->add_rules('action','required', 'alpha', 'length[1,1]');
+            $post->add_rules('message_id','required','numeric');
+
+            if ($post->validate())
+            {
+                $sms_id = $post->message_id;
+                $sms = new Message_Model($sms_id);
+                if ($sms->loaded == true)
+                {
+                    if ($sms->message_level == '1')
+                    {
+                        $sms->message_level = '99';
+                    }
+                    else
+                    {
+                        $sms->message_level = '1';
+                    }
+
+                    $sms->save();
+                }
+                else
+                {
+                    //twitter id doesn't exist in DB
+                    //TODO i18nize the string
+                    $this->error_messages .= "SMS ID does not exist.";
+                    $this->ret_value = 1;
+
+                }
+            }
+            else
+            {
+                //TODO i18nize the string
+                $this->error_messages .= "SMS ID is required.";
+                $ret_value = 1;
+            }
+
+        }
+        else
+        {
+            $ret_value = 3;
+        }
+        
+        $this->response_data = $this->response($ret_value);
+    }
+
+}
+
diff --git a/application/libraries/api/MY_Swiftriver_Report_Api_Object.php b/application/libraries/api/MY_Swiftriver_Report_Api_Object.php
new file mode 100755
index 0000000..e619f9c
--- /dev/null
+++ b/application/libraries/api/MY_Swiftriver_Report_Api_Object.php
@@ -0,0 +1,314 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles posting report to ushahidi via the API with the 
+ * report automatically approved.
+ *
+ * @version 24 - Emmanuel Kala 2010-10-25
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class Swiftriver_Report_Api_Object extends Api_Object_Core {
+
+    private $error_string = ''; // To hold the string of error messages
+    
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+    }
+
+    /**
+     * Services the request for reporting an incident via the API
+     */
+    public function perform_task()
+    {
+        // Submit the report and get the return value
+        $ret_value = $this->_submit();
+        
+        // Set the response data
+        $this->response_data = $this->response($ret_value, $this->error_string);
+    }
+    
+
+    /**
+     * The actual reporting -
+     *
+     * @return int
+     */
+    private function _submit() 
+    {
+        // Setup and initialize form field names
+        $form = array
+        (
+            'incident_title' => '',
+            'incident_description' => '',
+            'incident_date' => '',
+            'incident_hour' => '',
+            'incident_minute' => '',
+            'incident_ampm' => '',
+            'latitude' => '',
+            'longitude' => '',
+            'location_name' => '',
+            'country_id' => '',
+            'incident_category' => '',
+            'incident_news' => array(),
+            'incident_video' => array(),
+            'incident_photo' => array(),
+            'person_first' => '',
+            'person_last' => '',
+            'person_email' => ''
+        );
+        
+        $this->messages = $form;
+        
+        // check, has the form been submitted, if so, setup validation
+        if ($_POST) 
+        {
+            // Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
+            $post = Validation::factory(array_merge($_POST, $_FILES));
+
+            //  Add some filters
+            $post->pre_filter('trim', TRUE);
+
+            // Add some rules, the input field, followed by a list of checks, carried out in order
+            $post->add_rules('incident_title','required', 'length[3,200]');
+            $post->add_rules('incident_description','required');
+            $post->add_rules('incident_date','required','date_mmddyyyy');
+            $post->add_rules('incident_hour','required','between[0,23]');
+            //$post->add_rules('incident_minute','required','between[0,59]');
+
+            if ($this->api_service->verify_array_index($_POST, 'incident_ampm')) 
+            {
+                if ($_POST['incident_ampm'] != "am" AND  
+                        $_POST['incident_ampm'] != "pm") 
+                {
+                    $post->add_error('incident_ampm','values');
+                }
+            }
+
+            $post->add_rules('latitude','required','between[-90,90]');  
+            $post->add_rules('longitude','required','between[-180,180]');
+            $post->add_rules('location_name','required', 'length[3,200]');
+            $post->add_rules('incident_category','required',
+                    'length[1,100]');
+
+            // Validate Personal Information
+            if ( ! empty($post->person_first)) 
+            {
+                $post->add_rules('person_first', 'length[3,100]');
+            }
+
+            if ( ! empty($post->person_last)) 
+            {
+                $post->add_rules('person_last', 'length[3,100]');
+            }
+
+            if ( ! empty($post->person_email)) 
+            {
+                $post->add_rules('person_email', 'email', 'length[3,100]');
+            }
+
+            // Test to see if things passed the rule checks
+            if ($post->validate(FALSE)) 
+            {
+                // SAVE LOCATION (***IF IT DOES NOT EXIST***)
+                $location = new Location_Model();
+                $location->location_name = $post->location_name;
+                $location->latitude = $post->latitude;
+                $location->longitude = $post->longitude;
+                $location->location_date = date("Y-m-d H:i:s",time());
+                $location->save();
+
+                // SAVE INCIDENT
+                $incident = new Incident_Model();
+                $incident->location_id = $location->id;
+                $incident->user_id = 0;
+                $incident->incident_title = $post->incident_title;
+                $incident->incident_description = $post->incident_description;
+
+                // Incident Evaluation Info
+                $incident->incident_active = 1;
+
+                $incident_date=explode("/",$post->incident_date);
+                /**
+                * where the $_POST['date'] is a value posted by form in
+                * mm/dd/yyyy format
+                */
+                $incident_date=$incident_date[2]."-".$incident_date[0]."-".
+                    $incident_date[1];
+
+                $incident_time = $post->incident_hour . ":" . 
+                    $post->incident_minute . ":00 " . $post->incident_ampm;
+                $incident->incident_date = $incident_date . " " .
+                    $incident_time;
+                $incident->incident_dateadd = date("Y-m-d H:i:s",time());
+
+                // SwiftRiver automatically verifies reports - if the location (lat / lon) is set
+                $incident->incident_verified = 1;
+
+                if(($location->latitude == "0") && ($location->longitude == "0")) {
+                    $incident->incident_verified = 0;
+                }
+
+                // Save the dataset
+                $incident->save();
+
+                // SAVE CATEGORIES
+                // Check if data is csv or a single value.
+                $pos = strpos($post->incident_category,",");
+                
+                if($pos === false)
+                {
+                    //for backward compactibility. will drop support for it in the future.
+                    if (@unserialize($post->incident_category)) 
+                    {
+                        $categories = unserialize($post->incident_category);
+                    } 
+                    else 
+                    {
+                        $categories = array($post->incident_category);
+                    }
+                } 
+                else 
+                {
+                    $categories = explode(",",$post->incident_category);
+                }
+
+                if( ! empty($categories) AND is_array($categories))
+                {
+                    foreach ($categories as $item)
+                    {
+                        $incident_category = new Incident_Category_Model();
+                        $incident_category->incident_id = $incident->id;
+                        $incident_category->category_id = $item;
+                        $incident_category->save();
+                    }
+                }
+
+                // STEP 4: SAVE MEDIA
+                // a. News
+                if ( ! empty( $post->incident_news ) AND is_array($post->incident_news))
+                {
+                    foreach ($post->incident_news as $item) 
+                    {
+                        if( ! empty($item)) 
+                        {
+                            $news = new Media_Model();
+                            $news->location_id = $location->id;
+                            $news->incident_id = $incident->id;
+                            $news->media_type = 4;      // News
+                            $news->media_link = $item;
+                            $news->media_date = date("Y-m-d H:i:s",time());
+                            $news->save();
+                        }
+                    }
+                }
+
+                // b. Video
+                if( ! empty($post->incident_video) AND is_array($post->incident_video))
+                {
+                    foreach($post->incident_video as $item) 
+                    {
+                        if ( ! empty($item)) 
+                        {
+                            $video = new Media_Model();
+                            $video->location_id = $location->id;
+                            $video->incident_id = $incident->id;
+                            $video->media_type = 2;     // Video
+                            $video->media_link = $item;
+                            $video->media_date = date("Y-m-d H:i:s",time());
+                            $video->save();
+                        }
+                    }
+                }
+
+                // c. Photos
+                if ( ! empty($post->incident_photo))
+                {
+                    $filenames = upload::save('incident_photo');
+                    $i = 1;
+                    
+                    foreach ($filenames as $filename)
+                    {
+                        $new_filename = $incident->id . "_" . $i . "_" . time();
+
+                        // Resize original file... make sure its max 408px wide
+                        Image::factory($filename)->resize(408,248,
+                                Image::AUTO)->save(
+                                    Kohana::config('upload.directory',
+                                        TRUE) . $new_filename . ".jpg");
+
+                        // Create thumbnail
+                        Image::factory($filename)->resize(70,41,
+                                Image::HEIGHT)->save(
+                                    Kohana::config('upload.directory',
+                                        TRUE) . $new_filename . "_t.jpg");
+
+                        // Remove the temporary file
+                        unlink($filename);
+
+                        // Save to DB
+                        $photo = new Media_Model();
+                        $photo->location_id = $location->id;
+                        $photo->incident_id = $incident->id;
+                        $photo->media_type = 1; // Images
+                        $photo->media_link = $new_filename . ".jpg";
+                        $photo->media_thumb = $new_filename . "_t.jpg";
+                        $photo->media_date = date("Y-m-d H:i:s",time());
+                        $photo->save();
+                        $i++;
+                    }
+                }
+
+                // SAVE PERSONAL INFORMATION IF ITS FILLED UP
+                if ( ! empty($post->person_first) OR ! empty($post->person_last))
+                {
+                    $person = new Incident_Person_Model();
+                    $person->incident_id = $incident->id;
+                    $person->person_first = $post->person_first;
+                    $person->person_last = $post->person_last;
+                    $person->person_email = $post->person_email;
+                    $person->person_date = date("Y-m-d H:i:s",time());
+                    $person->save();
+                }
+
+                return 0; //success
+
+            } 
+            else 
+            {               
+                // Populate the error fields, if any
+                $this->messages = arr::overwrite($this->messages, 
+                        $post->errors('report'));
+
+                foreach ($this->messages as $error_item => $error_description) 
+                {
+                    if( ! is_array($error_description)) 
+                    {
+                        $this->error_string .= $error_description;
+                        
+                        if ($error_description != end($this->messages)) 
+                        {
+                            $this->error_string .= " - ";
+                        }
+                    }
+                }
+
+                //FAILED!!!
+                return 1; //validation error
+            }
+        } 
+        else 
+        {
+            return 2; // Not sent by post method.
+        }
+    }
+
+}
diff --git a/application/libraries/api/MY_System_Api_Object.php b/application/libraries/api/MY_System_Api_Object.php
new file mode 100755
index 0000000..b040693
--- /dev/null
+++ b/application/libraries/api/MY_System_Api_Object.php
@@ -0,0 +1,173 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * System_Api_Object
+ *
+ * This class handles private functions that not accessbile by the public
+ * via the API.
+ *
+ * @version 24 - Emmanuel Kala 2010-10-22
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class System_Api_Object extends Api_Object_Core {
+
+	protected $replar;
+
+	public function __construct($api_service)
+	{
+		$this->replar = array();
+		parent::__construct($api_service);
+	}
+
+	/**
+	 * Implementation of abstract method declared in superclass
+	 */
+	public function perform_task()
+	{
+		// System information mainly obtained through use of callback
+		// Therefore set the default response to "not found"
+		$this->set_error_message(array("error" => $this->api_service->get_error_msg(999)));
+	}
+
+	/**
+	 * Get an ushahidi deployment version number.
+	 *
+	 * @param string response_type - JSON or XML
+	 *
+	 * @return string
+	 */
+	public function get_version_number()
+	{
+		$json_version = array();
+		$version = Kohana::config('settings.ushahidi_version');
+		$database = Kohana::config('version.ushahidi_db_version');
+
+		$ret_json_or_xml = '';
+		// Will hold the JSON/XML string to return
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			$json_version[] = array("version" => $version, "database" => $database);
+		}
+		else
+		{
+			$json_version['version'] = array("version" => $version, "database" => $database);
+			$this->replar[] = 'version';
+		}
+
+		// Get Active Plugins
+		$plugins = ORM::factory('plugin')->where('plugin_active = 1')->orderby('plugin_name', 'ASC')->find_all();
+		$active_plugins = array();
+		foreach ($plugins as $plugin)
+		{
+			$active_plugins[] = $plugin->plugin_name;
+		}
+
+		$features = array(
+			'admin_reports_v2' => TRUE,
+			'api_key' => FALSE,
+			'jsonp' => TRUE,
+		);
+
+		// Create the json array
+		$data = array("payload" =>
+			array(
+				"domain" => $this->domain,
+				"version" => $json_version,
+				"email" => Kohana::config('settings.site_email'),
+				"sms" => Kohana::config('settings.sms_no1'),
+				"plugins" => $active_plugins,
+				"features" => $features,
+			),
+			"error" => $this->api_service->get_error_msg(0)
+		);
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			$ret_json_or_xml = $this->array_as_json($data);
+		}
+		else
+		{
+			$ret_json_or_xml = $this->array_as_xml($data, $this->replar);
+		}
+
+		$this->response_data = $ret_json_or_xml;
+	}
+
+	/**
+	 * Get true or false depending on MHI being enabled or not
+	 *
+	 * @param string response_type - JSON or XML
+	 *
+	 * @return string
+	 */
+	public function get_mhi_enabled()
+	{
+		$enabled = 'FALSE';
+		$ret_json_or_xml = '';
+		// Will hold the JSON/XML string to return
+
+		if (Kohana::config('config.enable_mhi') == TRUE)
+		{
+			$enabled = 'TRUE';
+		}
+
+		//create the json array
+		$data = array("payload" => array("domain" => $this->domain, "mhienabled" => $enabled), "error" => $this->api_service->get_error_msg(0));
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			$ret_json_or_xml = $this->array_as_json($data);
+		}
+		else
+		{
+			$ret_json_or_xml = $this->array_as_xml($data, $this->replar);
+		}
+
+		$this->response_data = $ret_json_or_xml;
+	}
+
+	/**
+	 * Get true or false depending on whether HTTPS has been enabled or not
+	 *
+	 * @param string response_type - JSON or XML
+	 *
+	 * @return string
+	 */
+	public function get_https_enabled()
+	{
+		$enabled = 'FALSE';
+		$ret_json_or_xml = '';
+		// Will hold the JSON/XML string to return
+
+		if (Kohana::config('core.site_protocol') == 'https')
+		{
+			$enabled = 'TRUE';
+		}
+
+		//create the json array
+		$data = array("payload" => array("domain" => $this->domain, "httpsenabled" => $enabled), "error" => $this->api_service->get_error_msg(0));
+
+		if ($this->response_type == 'json' OR $this->response_type == 'jsonp')
+		{
+			$ret_json_or_xml = $this->array_as_json($data);
+		}
+		else
+		{
+			$ret_json_or_xml = $this->array_as_xml($data, $this->replar);
+		}
+
+		$this->response_data = $ret_json_or_xml;
+
+	}
+
+}
diff --git a/application/libraries/api/MY_Tag_Media_Api_Object.php b/application/libraries/api/MY_Tag_Media_Api_Object.php
new file mode 100755
index 0000000..545709c
--- /dev/null
+++ b/application/libraries/api/MY_Tag_Media_Api_Object.php
@@ -0,0 +1,193 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles activities regarding tagging media to existing report.
+ *
+ * @version 24 - Emmanuel Kala 2010-10-25
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Tag_Media_Api_Object extends Api_Object_Core {
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);
+    }
+    
+    /**
+     * Handler for the API task request
+     */
+    public function perform_task()
+    {
+        if ( ! $this->api_service->verify_array_index($this->request, 'id'))
+        {
+            $this->set_error_message(array(
+                "error" => $this->api_service->get_error_msg(001, 'id')
+            ));
+            return;
+        }
+        else
+        {
+            // Get the media type
+            $media_type = $this->_get_media_type(strtolower($this->api_service->get_task_name()));
+
+            // Tag the media and set the response data
+            $this->response_data = $this->_tag_media($this->check_id_value($this->request['id']), $media_type);
+        }
+    }
+    
+    /**
+     * Get the media type id
+     *
+     * @param $task_name Name of the task
+     */
+    private function _get_media_type($task_name)
+    {
+        switch ($task_name)
+        {
+            case "tagnews":
+                return 4;
+            case "tagvideo":
+                return 2;
+            case "tagphoto":
+                return 1;
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Tag a news item to an incident.
+     * 
+     * @param int incidentid - The incident id.
+     * @param string mediatype - The media type,video, picture,etc
+     *
+     * @return Array
+     */
+    private function _tag_media($incidentid, $mediatype) 
+    {
+        if ($_POST) 
+        {
+
+            // Check if incident ID exist 
+            $incidentid_exist = Incident_Model::is_valid_incident($incidentid);
+
+            if(!$incidentid_exist) 
+            {
+                return $this->set_error_message(array("error" => 
+                    $this->api_service->get_error_msg(012)));
+            }
+
+            // Get the locationid for the incidentid
+            $locationid = 0;
+
+            $items = ORM::factory('incident')->select(array('location_id'))->where(array('incident.id' => $incidentid))->find();
+
+            if ($items->count_all() > 0)
+            {
+                $locationid = $items->location_id;
+            }
+
+            $media = new Media_Model(); //create media model object
+
+            $url = '';
+
+            $post = Validation::factory(array_merge($_POST, $_FILES));
+
+            if ($mediatype == 2 OR $mediatype == 4)
+            {
+                //require a url
+                if ( ! $this->api_service->verify_array_index($this->request, 'url'))
+                {
+                    return $this->set_error_message(array("error" => 
+                        $this->api_service->get_error_msg(001, 'url')));
+                } 
+                else 
+                {
+                    $url = $this->request['url'];
+                    $media->media_link = $url;
+                }
+            } 
+            else 
+            {
+                if ( ! $this->api_service->verify_array_index($this->request, 'photo'))
+                {
+                    $this->set_error_message(array("error" => 
+                        $this->api_service->get_error_msg(001),'photo'));
+                }
+
+                $post->add_rules('photo', 'upload::valid', 
+                    'upload::type[gif,jpg,png]', 'upload::size[1M]');
+
+                if ($post->validate(FALSE))
+                {
+                    //assuming this is a photo
+                    $filename = upload::save('photo');
+                    $new_filename = $incidentid . "_" . $i . "_" . time();
+
+                    // Resize original file... make sure its max 408px wide
+                    Image::factory($filename)->resize(408,248,Image::AUTO)->
+                        save(Kohana::config('upload.directory', TRUE) .
+                            $new_filename . ".jpg");
+
+                    // Create thumbnail
+                    Image::factory($filename)->resize(70,41,Image::HEIGHT)->
+                        save(Kohana::config('upload.directory', TRUE) .
+                                $new_filename . "_t.jpg");
+
+                    // Remove the temporary file
+                    unlink($filename);
+
+                    $media->media_link = $new_filename . ".jpg";
+                    $media->media_thumb = $new_filename . "_t.jpg";
+                }
+            }
+
+            // Optional title & description
+            $title = '';
+            
+            if ($this->api_service->verify_array_index($_POST, 'title'))
+            {
+                $title = $_POST['title'];
+            }
+
+            $description = '';
+            
+            if ($this->api_service->verify_array_index($_POST, 'description'))
+            {
+                $description = $_POST['description'];
+            }
+
+            $media->location_id = $locationid;
+            $media->incident_id = $incidentid;
+            $media->media_type = $mediatype;
+            $media->media_title = $title;
+            $media->media_description = $description;
+            $media->media_date = date("Y-m-d H:i:s",time());
+
+            $media->save(); //save the thing
+
+            // SUCESS!!!
+            $ret = array(
+                "payload" => array(
+                    "domain" => $this->domain,
+                    "success" => "true"
+                ),
+                "error" => $this->api_service->get_error_msg(0)
+            );
+            return $this->set_error_message($ret);
+        } 
+        else 
+        {
+            return $this->set_error_message(array("error" => 
+                $this->api_service->get_error_msg(003)));
+        }
+    }
+}
diff --git a/application/libraries/api/MY_Twitter_Api_Object.php b/application/libraries/api/MY_Twitter_Api_Object.php
new file mode 100644
index 0000000..bc89353
--- /dev/null
+++ b/application/libraries/api/MY_Twitter_Api_Object.php
@@ -0,0 +1,247 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * This class handles GET request for KML via the API.
+ *
+ * @version 25 - Emmanuel Kala 2010-10-27
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Twitter_Api_Object extends Api_Object_Core {
+
+    public function __construct($api_service)
+    {
+        parent::__construct($api_service);  
+    }  
+
+    /**
+     * List all twitter messages by default
+     */
+    public function perform_task()
+    {
+        $this->_list_twitter_msgs();
+    }
+    
+    /**
+     * Handles actions for twitter messages
+     */
+    public function twitter_action()
+    {
+        if ( ! $this->api_service->verify_array_index($this->request, 'action'))
+        {
+            $this->set_error_message(array(
+                "error" => $this->api_service->get_error_msg(001,'action')
+            )); 
+            return;
+        }
+        else
+        {
+            $this->by = $this->request['action'];
+        }
+        
+        switch ($this->by)
+        {
+            case "d":
+                $this->_delete_twitter_msg();
+            break;
+            
+            case "s":
+                $this->_spam_twitter_msg();
+            break;
+
+            default:
+                $this->set_error_message(array(
+                    "error" => $this->api_service->get_error_msg(001)
+                ));
+        }
+    }
+    
+    /**
+     * List first 15 twitter messages.
+     *
+     * @return array
+     */
+    private function _list_twitter_msgs()
+    {
+        $ret_json_or_xml = ''; // Will hold the return JSON/XML string
+  
+        $items = ORM::factory('message')
+            ->where('service_id', '3')
+            ->where('message_type','1')
+            ->orderby('message_date','desc')
+            ->find_all($this->list_limit);
+
+        // Set the no. of records fetched
+        $this->record_count = $items->count();
+        
+        $json_categories = array();
+        
+        $i = 0;
+
+        //No record found.
+        if ($items->count() == 0)
+        {
+            $this->response_data = $this->response(4);
+            return;
+        }
+
+        foreach ($items as $twitter)
+        {
+            if ( $response_type == 'json')
+            {
+                $json_categories[] = array("twitter" => $item);        
+            }
+            else
+            {
+                $json_categories['twitter'.$i] = array('twitter' => 
+                        $twitter);
+                $this->replar[] = 'twitter'.$i;
+            }
+        }
+        
+        // Create the json array
+        $data = array("payload" => array(
+            "domain" => $this->domain,
+            "count" => $json_categories),
+            "error" => $this->api_service->get_error_msg(0));
+
+        if ($this->response_type == 'json') 
+        {
+            $ret_json_or_xml = $this->array_as_json($data);
+        }
+        else
+        {
+            $ret_json_or_xml = $this->array_as_xml($data, $this->replar);
+        }
+
+        $this->response_data = $ret_json_or_xml;
+
+    }
+
+    /**
+     * Delete existing Twitter message
+     *
+     * @return Array
+     */
+    private function _delete_twitter_msg()
+    {
+        $ret_value = 0;
+        
+        if ($_POST)
+        {
+            $post = Validation::factory($_POST);
+
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+            // Add some rules, the input field, followed by a list of 
+            //checks, carried out in order
+            $post->add_rules('action','required', 'alpha', 'length[1,1]');
+            $post->add_rules('message_id.*','required','numeric');
+
+            if ($post->validate())
+            {
+                $twitter_id = $post->message_id;
+                $sms = new Message_Model($twitter_id);
+                if ($sms->loaded == true)
+                {
+                    $sms->delete();
+                }
+                else
+                {
+                    //Comment id doesn't exist in DB
+                    //TODO i18nize the string
+                    $this->error_messages .= "Twitter ID does not exist.";
+                    $ret_value = 1;
+
+                }
+            }
+            else
+            {
+                //TODO i18nize the string
+                $this->error_messages .= "Twitter ID is required.";
+                $ret_value = 1;
+            }
+
+        }
+        else
+        {
+            $ret_value = 3;
+        }
+        
+        $this->response_data = $this->response($ret_value, 
+            $this->error_messages);
+
+    }
+
+    /**
+     * Spam / Unspam existing email message
+     *
+     * @return Array
+     */
+    public function _spam_twitter_msg()
+    {
+        $ret_val = 0; // Initialize a 0 return value; successful execution
+        
+        if ($_POST)
+        {
+            $post = Validation::factory($_POST);
+
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+            // Add some rules, the input field, followed by a list of 
+            //checks, carried out in order
+            $post->add_rules('action','required', 'alpha', 'length[1,1]');
+            $post->add_rules('message_id','required','numeric');
+
+            if ($post->validate())
+            {
+                $twitter_id = $post->message_id;
+                $twitter = new Message_Model($twitter_id);
+                if ($twitter->loaded == true)
+                {
+                    if ($twitter->message_level == '1')
+                    {
+                        $twitter->message_level = '99';
+                    }
+                    else
+                    {
+                        $twitter->message_level = '1';
+                    }
+
+                    $twitter->save();
+                }
+                else
+                {
+                    //twitter id doesn't exist in DB
+                    //TODO i18nize the string
+                    $this->error_messages .= "Twitter ID does not exist.";
+                    $this->ret_value = 1;
+
+                }
+            }
+            else
+            {
+                //TODO i18nize the string
+                $this->error_messages .= "Twitter ID is required.";
+                $ret_value = 1;
+            }
+
+        }
+        else
+        {
+            $ret_value = 3;
+        }
+        
+        $this->response_data = $this->response($ret_value);
+    }
+
+}
+
diff --git a/application/libraries/cloudfiles/CF_Authentication.php b/application/libraries/cloudfiles/CF_Authentication.php
new file mode 100644
index 0000000..c5fd457
--- /dev/null
+++ b/application/libraries/cloudfiles/CF_Authentication.php
@@ -0,0 +1,229 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Class for handling Cloud Files Authentication, call it's {@link authenticate()}
+ * method to obtain authorized service urls and an authentication token.
+ *
+ * Example:
+ * <code>
+ * # Create the authentication instance
+ * #
+ * $auth = new CF_Authentication("username", "api_key");
+ *
+ * # NOTE: For UK Customers please specify your AuthURL Manually
+ * # There is a Predfined constant to use EX:
+ * #
+ * # $auth = new CF_Authentication("username, "api_key", NULL, UK_AUTHURL);
+ * # Using the UK_AUTHURL keyword will force the api to use the UK AuthUrl.
+ * # rather then the US one. The NULL Is passed for legacy purposes and must
+ * # be passed to function correctly.
+ *
+ * # NOTE: Some versions of cURL include an outdated certificate authority (CA)
+ * #       file.  This API ships with a newer version obtained directly from
+ * #       cURL's web site (http://curl.haxx.se).  To use the newer CA bundle,
+ * #       call the CF_Authentication instance's 'ssl_use_cabundle()' method.
+ * #
+ * # $auth->ssl_use_cabundle(); # bypass cURL's old CA bundle
+ *
+ * # Perform authentication request
+ * #
+ * $auth->authenticate();
+ * </code>
+ *
+ * @package php-cloudfiles
+ */
+class CF_Authentication
+{
+    public $dbug;
+    public $username;
+    public $api_key;
+    public $auth_host;
+    public $account;
+
+    /**
+     * Instance variables that are set after successful authentication
+     */
+    public $storage_url;
+    public $cdnm_url;
+    public $auth_token;
+
+    /**
+     * Class constructor (PHP 5 syntax)
+     *
+     * @param string $username Mosso username
+     * @param string $api_key Mosso API Access Key
+     * @param string $account  <i>Account name</i>
+     * @param string $auth_host  <i>Authentication service URI</i>
+     */
+    function __construct($username=NULL, $api_key=NULL, $account=NULL, $auth_host=US_AUTHURL)
+    {
+
+        $this->dbug = False;
+        $this->username = $username;
+        $this->api_key = $api_key;
+        $this->account_name = $account;
+        $this->auth_host = $auth_host;
+
+        $this->storage_url = NULL;
+        $this->cdnm_url = NULL;
+        $this->auth_token = NULL;
+
+        $this->cfs_http = new CF_Http(DEFAULT_CF_API_VERSION);
+    }
+
+    /**
+     * Use the Certificate Authority bundle included with this API
+     *
+     * Most versions of PHP with cURL support include an outdated Certificate
+     * Authority (CA) bundle (the file that lists all valid certificate
+     * signing authorities).  The SSL certificates used by the Cloud Files
+     * storage system are perfectly valid but have been created/signed by
+     * a CA not listed in these outdated cURL distributions.
+     *
+     * As a work-around, we've included an updated CA bundle obtained
+     * directly from cURL's web site (http://curl.haxx.se).  You can direct
+     * the API to use this CA bundle by calling this method prior to making
+     * any remote calls.  The best place to use this method is right after
+     * the CF_Authentication instance has been instantiated.
+     *
+     * You can specify your own CA bundle by passing in the full pathname
+     * to the bundle.  You can use the included CA bundle by leaving the
+     * argument blank.
+     *
+     * @param string $path Specify path to CA bundle (default to included)
+     */
+    function ssl_use_cabundle($path=NULL)
+    {
+        $this->cfs_http->ssl_use_cabundle($path);
+    }
+
+    /**
+     * Attempt to validate Username/API Access Key
+     *
+     * Attempts to validate credentials with the authentication service.  It
+     * either returns <kbd>True</kbd> or throws an Exception.  Accepts a single
+     * (optional) argument for the storage system API version.
+     *
+     * Example:
+     * <code>
+     * # Create the authentication instance
+     * #
+     * $auth = new CF_Authentication("username", "api_key");
+     *
+     * # Perform authentication request
+     * #
+     * $auth->authenticate();
+     * </code>
+     *
+     * @param string $version API version for Auth service (optional)
+     * @return boolean <kbd>True</kbd> if successfully authenticated
+     * @throws AuthenticationException invalid credentials
+     * @throws InvalidResponseException invalid response
+     */
+    function authenticate($version=DEFAULT_CF_API_VERSION)
+    {
+        list($status,$reason,$surl,$curl,$atoken) =
+                $this->cfs_http->authenticate($this->username, $this->api_key,
+                $this->account_name, $this->auth_host);
+
+        if ($status == 401) {
+            throw new Kohana_Exception("Invalid username or access key.");
+        }
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Unexpected response (".$status."): ".$reason);
+        }
+
+        if (!($surl || $curl) || !$atoken) {
+            throw new Kohana_Exception(
+                "Expected headers missing from auth service.");
+        }
+        $this->storage_url = $surl;
+        $this->cdnm_url = $curl;
+        $this->auth_token = $atoken;
+        return True;
+    }
+	/**
+	 * Use Cached Token and Storage URL's rather then grabbing from the Auth System
+         *
+         * Example:
+ 	 * <code>
+         * #Create an Auth instance
+         * $auth = new CF_Authentication();
+         * #Pass Cached URL's and Token as Args
+	 * $auth->load_cached_credentials("auth_token", "storage_url", "cdn_management_url");
+         * </code>
+	 *
+	 * @param string $auth_token A Cloud Files Auth Token (Required)
+         * @param string $storage_url The Cloud Files Storage URL (Required)
+         * @param string $cdnm_url CDN Management URL (Required)
+         * @return boolean <kbd>True</kbd> if successful
+	 * @throws SyntaxException If any of the Required Arguments are missing
+         */
+	function load_cached_credentials($auth_token, $storage_url, $cdnm_url)
+    {
+        if(!$storage_url || !$cdnm_url)
+        {
+                throw new Kohana_Exception("Missing Required Interface URL's!");
+                return False;
+        }
+        if(!$auth_token)
+        {
+                throw new Kohana_Exception("Missing Auth Token!");
+                return False;
+        }
+
+        $this->storage_url = $storage_url;
+        $this->cdnm_url    = $cdnm_url;
+        $this->auth_token  = $auth_token;
+        return True;
+    }
+	/**
+         * Grab Cloud Files info to be Cached for later use with the load_cached_credentials method.
+         *
+	 * Example:
+         * <code>
+         * #Create an Auth instance
+         * $auth = new CF_Authentication("UserName","API_Key");
+         * $auth->authenticate();
+         * $array = $auth->export_credentials();
+         * </code>
+         *
+	 * @return array of url's and an auth token.
+         */
+    function export_credentials()
+    {
+        $arr = array();
+        $arr['storage_url'] = $this->storage_url;
+        $arr['cdnm_url']    = $this->cdnm_url;
+        $arr['auth_token']  = $this->auth_token;
+
+        return $arr;
+    }
+
+
+    /**
+     * Make sure the CF_Authentication instance has authenticated.
+     *
+     * Ensures that the instance variables necessary to communicate with
+     * Cloud Files have been set from a previous authenticate() call.
+     *
+     * @return boolean <kbd>True</kbd> if successfully authenticated
+     */
+    function authenticated()
+    {
+        if (!($this->storage_url || $this->cdnm_url) || !$this->auth_token) {
+            return False;
+        }
+        return True;
+    }
+
+    /**
+     * Toggle debugging - set cURL verbose flag
+     */
+    function setDebug($bool)
+    {
+        $this->dbug = $bool;
+        $this->cfs_http->setDebug($bool);
+    }
+}
+?>
\ No newline at end of file
diff --git a/application/libraries/cloudfiles/CF_Connection.php b/application/libraries/cloudfiles/CF_Connection.php
new file mode 100644
index 0000000..766e629
--- /dev/null
+++ b/application/libraries/cloudfiles/CF_Connection.php
@@ -0,0 +1,596 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Class for establishing connections to the Cloud Files storage system.
+ * Connection instances are used to communicate with the storage system at
+ * the account level; listing and deleting Containers and returning Container
+ * instances.
+ *
+ * Example:
+ * <code>
+ * # Create the authentication instance
+ * #
+ * $auth = new CF_Authentication("username", "api_key");
+ *
+ * # Perform authentication request
+ * #
+ * $auth->authenticate();
+ *
+ * # Create a connection to the storage/cdn system(s) and pass in the
+ * # validated CF_Authentication instance.
+ * #
+ * $conn = new CF_Connection($auth);
+ *
+ * # NOTE: Some versions of cURL include an outdated certificate authority (CA)
+ * #       file.  This API ships with a newer version obtained directly from
+ * #       cURL's web site (http://curl.haxx.se).  To use the newer CA bundle,
+ * #       call the CF_Authentication instance's 'ssl_use_cabundle()' method.
+ * #
+ * # $conn->ssl_use_cabundle(); # bypass cURL's old CA bundle
+ * </code>
+ *
+ * @package php-cloudfiles
+ */
+class CF_Connection
+{
+    public $dbug;
+    public $cfs_http;
+    public $cfs_auth;
+
+    /**
+     * Pass in a previously authenticated CF_Authentication instance.
+     *
+     * Example:
+     * <code>
+     * # Create the authentication instance
+     * #
+     * $auth = new CF_Authentication("username", "api_key");
+     *
+     * # Perform authentication request
+     * #
+     * $auth->authenticate();
+     *
+     * # Create a connection to the storage/cdn system(s) and pass in the
+     * # validated CF_Authentication instance.
+     * #
+     * $conn = new CF_Connection($auth);
+     *
+     * # If you are connecting via Rackspace servers and have access
+     * # to the servicenet network you can set the $servicenet to True
+     * # like this.
+     *
+     * $conn = new CF_Connection($auth, $servicenet=True);
+     *
+     * </code>
+     *
+     * If the environement variable RACKSPACE_SERVICENET is defined it will
+     * force to connect via the servicenet.
+     *
+     * @param obj $cfs_auth previously authenticated CF_Authentication instance
+     * @param boolean $servicenet enable/disable access via Rackspace servicenet.
+     * @throws AuthenticationException not authenticated
+     */
+    function __construct($cfs_auth, $servicenet=False)
+    {
+        if (isset($_ENV['RACKSPACE_SERVICENET']))
+            $servicenet=True;
+        $this->cfs_http = new CF_Http(DEFAULT_CF_API_VERSION);
+        $this->cfs_auth = $cfs_auth;
+        if (!$this->cfs_auth->authenticated()) {
+            $e = "Need to pass in a previously authenticated ";
+            $e .= "CF_Authentication instance.";
+            throw new Kohana_Exception($e);
+        }
+        $this->cfs_http->setCFAuth($this->cfs_auth, $servicenet=$servicenet);
+        $this->dbug = False;
+    }
+
+    /**
+     * Toggle debugging of instance and back-end HTTP module
+     *
+     * @param boolean $bool enable/disable cURL debugging
+     */
+    function setDebug($bool)
+    {
+        $this->dbug = (boolean) $bool;
+        $this->cfs_http->setDebug($this->dbug);
+    }
+
+    /**
+     * Close a connection
+     *
+     * Example:
+     * <code>
+     *
+     * $conn->close();
+     *
+     * </code>
+     *
+     * Will close all current cUrl active connections.
+     *
+     */
+    public function close()
+    {
+        $this->cfs_http->close();
+    }
+
+    /**
+     * Cloud Files account information
+     *
+     * Return an array of two floats (since PHP only supports 32-bit integers);
+     * number of containers on the account and total bytes used for the account.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * list($quantity, $bytes) = $conn->get_info();
+     * print "Number of containers: " . $quantity . "\n";
+     * print "Bytes stored in container: " . $bytes . "\n";
+     * </code>
+     *
+     * @return array (number of containers, total bytes stored)
+     * @throws InvalidResponseException unexpected response
+     */
+    function get_info()
+    {
+        list($status, $reason, $container_count, $total_bytes) =
+                $this->cfs_http->head_account();
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->get_info();
+        #}
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        return array($container_count, $total_bytes);
+    }
+
+    /**
+     * Create a Container
+     *
+     * Given a Container name, return a Container instance, creating a new
+     * remote Container if it does not exit.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $images = $conn->create_container("my photos");
+     * </code>
+     *
+     * @param string $container_name container name
+     * @return CF_Container
+     * @throws SyntaxException invalid name
+     * @throws InvalidResponseException unexpected response
+     */
+    function create_container($container_name=NULL)
+    {
+        if ($container_name != "0" and !isset($container_name))
+            throw new Kohana_Exception("Container name not set.");
+
+        if (!isset($container_name) or $container_name == "")
+            throw new Kohana_Exception("Container name not set.");
+
+        if (strpos($container_name, "/") !== False) {
+            $r = "Container name '".$container_name;
+            $r .= "' cannot contain a '/' character.";
+            throw new Kohana_Exception($r);
+        }
+        if (strlen($container_name) > MAX_CONTAINER_NAME_LEN) {
+            throw new Kohana_Exception(sprintf(
+                "Container name exeeds %d bytes.",
+                MAX_CONTAINER_NAME_LEN));
+        }
+
+        $return_code = $this->cfs_http->create_container($container_name);
+        if (!$return_code) {
+            throw new Kohana_Exception("Invalid response ("
+                . $return_code. "): " . $this->cfs_http->get_error());
+        }
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->create_container($container_name);
+        #}
+        if ($return_code != 201 && $return_code != 202) {
+            throw new Kohana_Exception(
+                "Invalid response (".$return_code."): "
+                    . $this->cfs_http->get_error());
+        }
+        return new CF_Container($this->cfs_auth, $this->cfs_http, $container_name);
+    }
+
+    /**
+     * Delete a Container
+     *
+     * Given either a Container instance or name, remove the remote Container.
+     * The Container must be empty prior to removing it.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $conn->delete_container("my photos");
+     * </code>
+     *
+     * @param string|obj $container container name or instance
+     * @return boolean <kbd>True</kbd> if successfully deleted
+     * @throws SyntaxException missing proper argument
+     * @throws InvalidResponseException invalid response
+     * @throws NonEmptyContainerException container not empty
+     * @throws NoSuchContainerException remote container does not exist
+     */
+    function delete_container($container=NULL)
+    {
+        $container_name = NULL;
+
+        if (is_object($container)) {
+            if (get_class($container) == "CF_Container") {
+                $container_name = $container->name;
+            }
+        }
+        if (is_string($container)) {
+            $container_name = $container;
+        }
+
+        if ($container_name != "0" and !isset($container_name))
+            throw new Kohana_Exception("Must specify container object or name.");
+
+        $return_code = $this->cfs_http->delete_container($container_name);
+
+        if (!$return_code) {
+            throw new Kohana_Exception("Failed to obtain http response");
+        }
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->delete_container($container);
+        #}
+        if ($return_code == 409) {
+            throw new Kohana_Exception(
+                "Container must be empty prior to removing it.");
+        }
+        if ($return_code == 404) {
+            throw new Kohana_Exception(
+                "Specified container did not exist to delete.");
+        }
+        if ($return_code != 204) {
+            throw new Kohana_Exception(
+                "Invalid response (".$return_code."): "
+                . $this->cfs_http->get_error());
+        }
+        return True;
+    }
+
+    /**
+     * Return a Container instance
+     *
+     * For the given name, return a Container instance if the remote Container
+     * exists, otherwise throw a Not Found exception.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $images = $conn->get_container("my photos");
+     * print "Number of Objects: " . $images->count . "\n";
+     * print "Bytes stored in container: " . $images->bytes . "\n";
+     * </code>
+     *
+     * @param string $container_name name of the remote Container
+     * @return container CF_Container instance
+     * @throws NoSuchContainerException thrown if no remote Container
+     * @throws InvalidResponseException unexpected response
+     */
+    function get_container($container_name=NULL)
+    {
+        list($status, $reason, $count, $bytes) =
+                $this->cfs_http->head_container($container_name);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->get_container($container_name);
+        #}
+        if ($status == 404) {
+            throw new Kohana_Exception("Container not found.");
+        }
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response: ".$this->cfs_http->get_error());
+        }
+        return new CF_Container($this->cfs_auth, $this->cfs_http,
+            $container_name, $count, $bytes);
+    }
+
+    /**
+     * Return array of Container instances
+     *
+     * Return an array of CF_Container instances on the account.  The instances
+     * will be fully populated with Container attributes (bytes stored and
+     * Object count)
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $clist = $conn->get_containers();
+     * foreach ($clist as $cont) {
+     *     print "Container name: " . $cont->name . "\n";
+     *     print "Number of Objects: " . $cont->count . "\n";
+     *     print "Bytes stored in container: " . $cont->bytes . "\n";
+     * }
+     * </code>
+     *
+     * @return array An array of CF_Container instances
+     * @throws InvalidResponseException unexpected response
+     */
+    function get_containers($limit=0, $marker=NULL)
+    {
+        list($status, $reason, $container_info) =
+                $this->cfs_http->list_containers_info($limit, $marker);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->get_containers();
+        #}
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response: ".$this->cfs_http->get_error());
+        }
+        $containers = array();
+        foreach ($container_info as $name => $info) {
+            $containers[] = new CF_Container($this->cfs_auth, $this->cfs_http,
+                $info['name'], $info["count"], $info["bytes"], False);
+        }
+        return $containers;
+    }
+
+    /**
+     * Return list of remote Containers
+     *
+     * Return an array of strings containing the names of all remote Containers.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $container_list = $conn->list_containers();
+     * print_r($container_list);
+     * Array
+     * (
+     *     [0] => "my photos",
+     *     [1] => "my docs"
+     * )
+     * </code>
+     *
+     * @param integer $limit restrict results to $limit Containers
+     * @param string $marker return results greater than $marker
+     * @return array list of remote Containers
+     * @throws InvalidResponseException unexpected response
+     */
+    function list_containers($limit=0, $marker=NULL)
+    {
+        list($status, $reason, $containers) =
+            $this->cfs_http->list_containers($limit, $marker);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->list_containers($limit, $marker);
+        #}
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        return $containers;
+    }
+
+    /**
+     * Return array of information about remote Containers
+     *
+     * Return a nested array structure of Container info.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     *
+     * $container_info = $conn->list_containers_info();
+     * print_r($container_info);
+     * Array
+     * (
+     *     ["my photos"] =>
+     *         Array
+     *         (
+     *             ["bytes"] => 78,
+     *             ["count"] => 2
+     *         )
+     *     ["docs"] =>
+     *         Array
+     *         (
+     *             ["bytes"] => 37323,
+     *             ["count"] => 12
+     *         )
+     * )
+     * </code>
+     *
+     * @param integer $limit restrict results to $limit Containers
+     * @param string $marker return results greater than $marker
+     * @return array nested array structure of Container info
+     * @throws InvalidResponseException unexpected response
+     */
+    function list_containers_info($limit=0, $marker=NULL)
+    {
+        list($status, $reason, $container_info) =
+                $this->cfs_http->list_containers_info($limit, $marker);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->list_containers_info($limit, $marker);
+        #}
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        return $container_info;
+    }
+
+    /**
+     * Return list of Containers that have been published to the CDN.
+     *
+     * Return an array of strings containing the names of published Containers.
+     * Note that this function returns the list of any Container that has
+     * ever been CDN-enabled regardless of it's existence in the storage
+     * system.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_containers = $conn->list_public_containers();
+     * print_r($public_containers);
+     * Array
+     * (
+     *     [0] => "images",
+     *     [1] => "css",
+     *     [2] => "javascript"
+     * )
+     * </code>
+     *
+     * @param bool $enabled_only Will list all containers ever CDN enabled if     * set to false or only currently enabled CDN containers if set to true.      * Defaults to false.
+     * @return array list of published Container names
+     * @throws InvalidResponseException unexpected response
+     */
+    function list_public_containers($enabled_only=False)
+    {
+        list($status, $reason, $containers) =
+                $this->cfs_http->list_cdn_containers($enabled_only);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->list_public_containers();
+        #}
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        return $containers;
+    }
+
+    /**
+     * Set a user-supplied callback function to report download progress
+     *
+     * The callback function is used to report incremental progress of a data
+     * download functions (e.g. $container->list_objects(), $obj->read(), etc).
+     * The specified function will be periodically called with the number of
+     * bytes transferred until the entire download is complete.  This callback
+     * function can be useful for implementing "progress bars" for large
+     * downloads.
+     *
+     * The specified callback function should take a single integer parameter.
+     *
+     * <code>
+     * function read_callback($bytes_transferred) {
+     *     print ">> downloaded " . $bytes_transferred . " bytes.\n";
+     *     # ... do other things ...
+     *     return;
+     * }
+     *
+     * $conn = new CF_Connection($auth_obj);
+     * $conn->set_read_progress_function("read_callback");
+     * print_r($conn->list_containers());
+     *
+     * # output would look like this:
+     * #
+     * >> downloaded 10 bytes.
+     * >> downloaded 11 bytes.
+     * Array
+     * (
+     *      [0] => fuzzy.txt
+     *      [1] => space name
+     * )
+     * </code>
+     *
+     * @param string $func_name the name of the user callback function
+     */
+    function set_read_progress_function($func_name)
+    {
+        $this->cfs_http->setReadProgressFunc($func_name);
+    }
+
+    /**
+     * Set a user-supplied callback function to report upload progress
+     *
+     * The callback function is used to report incremental progress of a data
+     * upload functions (e.g. $obj->write() call).  The specified function will
+     * be periodically called with the number of bytes transferred until the
+     * entire upload is complete.  This callback function can be useful
+     * for implementing "progress bars" for large uploads/downloads.
+     *
+     * The specified callback function should take a single integer parameter.
+     *
+     * <code>
+     * function write_callback($bytes_transferred) {
+     *     print ">> uploaded " . $bytes_transferred . " bytes.\n";
+     *     # ... do other things ...
+     *     return;
+     * }
+     *
+     * $conn = new CF_Connection($auth_obj);
+     * $conn->set_write_progress_function("write_callback");
+     * $container = $conn->create_container("stuff");
+     * $obj = $container->create_object("foo");
+     * $obj->write("The callback function will be called during upload.");
+     *
+     * # output would look like this:
+     * # >> uploaded 51 bytes.
+     * #
+     * </code>
+     *
+     * @param string $func_name the name of the user callback function
+     */
+    function set_write_progress_function($func_name)
+    {
+        $this->cfs_http->setWriteProgressFunc($func_name);
+    }
+
+    /**
+     * Use the Certificate Authority bundle included with this API
+     *
+     * Most versions of PHP with cURL support include an outdated Certificate
+     * Authority (CA) bundle (the file that lists all valid certificate
+     * signing authorities).  The SSL certificates used by the Cloud Files
+     * storage system are perfectly valid but have been created/signed by
+     * a CA not listed in these outdated cURL distributions.
+     *
+     * As a work-around, we've included an updated CA bundle obtained
+     * directly from cURL's web site (http://curl.haxx.se).  You can direct
+     * the API to use this CA bundle by calling this method prior to making
+     * any remote calls.  The best place to use this method is right after
+     * the CF_Authentication instance has been instantiated.
+     *
+     * You can specify your own CA bundle by passing in the full pathname
+     * to the bundle.  You can use the included CA bundle by leaving the
+     * argument blank.
+     *
+     * @param string $path Specify path to CA bundle (default to included)
+     */
+    function ssl_use_cabundle($path=NULL)
+    {
+        $this->cfs_http->ssl_use_cabundle($path);
+    }
+
+    #private function _re_auth()
+    #{
+    #    $new_auth = new CF_Authentication(
+    #        $this->cfs_auth->username,
+    #        $this->cfs_auth->api_key,
+    #        $this->cfs_auth->auth_host,
+    #        $this->cfs_auth->account);
+    #    $new_auth->authenticate();
+    #    $this->cfs_auth = $new_auth;
+    #    $this->cfs_http->setCFAuth($this->cfs_auth);
+    #    return True;
+    #}
+}
+
+?>
\ No newline at end of file
diff --git a/application/libraries/cloudfiles/CF_Container.php b/application/libraries/cloudfiles/CF_Container.php
new file mode 100644
index 0000000..dc47c17
--- /dev/null
+++ b/application/libraries/cloudfiles/CF_Container.php
@@ -0,0 +1,971 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Container operations
+ *
+ * Containers are storage compartments where you put your data (objects).
+ * A container is similar to a directory or folder on a conventional filesystem
+ * with the exception that they exist in a flat namespace, you can not create
+ * containers inside of containers.
+ *
+ * You also have the option of marking a Container as "public" so that the
+ * Objects stored in the Container are publicly available via the CDN.
+ *
+ * @package php-cloudfiles
+ */
+class CF_Container
+{
+    public $cfs_auth;
+    public $cfs_http;
+    public $name;
+    public $object_count;
+    public $bytes_used;
+
+    public $cdn_enabled;
+    public $cdn_streaming_uri;
+    public $cdn_ssl_uri;
+    public $cdn_uri;
+    public $cdn_ttl;
+    public $cdn_log_retention;
+    public $cdn_acl_user_agent;
+    public $cdn_acl_referrer;
+
+    /**
+     * Class constructor
+     *
+     * Constructor for Container
+     *
+     * @param obj $cfs_auth CF_Authentication instance
+     * @param obj $cfs_http HTTP connection manager
+     * @param string $name name of Container
+     * @param int $count number of Objects stored in this Container
+     * @param int $bytes number of bytes stored in this Container
+     * @throws SyntaxException invalid Container name
+     */
+    function __construct(&$cfs_auth, &$cfs_http, $name, $count=0,
+        $bytes=0, $docdn=True)
+    {
+        if (strlen($name) > MAX_CONTAINER_NAME_LEN) {
+            throw new Kohana_Exception("Container name exceeds "
+                . "maximum allowed length.");
+        }
+        if (strpos($name, "/") !== False) {
+            throw new Kohana_Exception(
+                "Container names cannot contain a '/' character.");
+        }
+        $this->cfs_auth = $cfs_auth;
+        $this->cfs_http = $cfs_http;
+        $this->name = $name;
+        $this->object_count = $count;
+        $this->bytes_used = $bytes;
+        $this->cdn_enabled = NULL;
+        $this->cdn_uri = NULL;
+        $this->cdn_ssl_uri = NULL;
+        $this->cdn_streaming_uri = NULL;
+        $this->cdn_ttl = NULL;
+        $this->cdn_log_retention = NULL;
+        $this->cdn_acl_user_agent = NULL;
+        $this->cdn_acl_referrer = NULL;
+        if ($this->cfs_http->getCDNMUrl() != NULL && $docdn) {
+            $this->_cdn_initialize();
+        }
+    }
+
+    /**
+     * String representation of Container
+     *
+     * Pretty print the Container instance.
+     *
+     * @return string Container details
+     */
+    function __toString()
+    {
+        $me = sprintf("name: %s, count: %.0f, bytes: %.0f",
+            $this->name, $this->object_count, $this->bytes_used);
+        if ($this->cfs_http->getCDNMUrl() != NULL) {
+            $me .= sprintf(", cdn: %s, cdn uri: %s, cdn ttl: %.0f, logs retention: %s",
+                $this->is_public() ? "Yes" : "No",
+                $this->cdn_uri, $this->cdn_ttl,
+                $this->cdn_log_retention ? "Yes" : "No"
+                );
+
+            if ($this->cdn_acl_user_agent != NULL) {
+                $me .= ", cdn acl user agent: " . $this->cdn_acl_user_agent;
+            }
+
+            if ($this->cdn_acl_referrer != NULL) {
+                $me .= ", cdn acl referrer: " . $this->cdn_acl_referrer;
+            }
+
+
+        }
+        return $me;
+    }
+
+    /**
+     * Enable Container content to be served via CDN or modify CDN attributes
+     *
+     * Either enable this Container's content to be served via CDN or
+     * adjust its CDN attributes.  This Container will always return the
+     * same CDN-enabled URI each time it is toggled public/private/public.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_container = $conn->create_container("public");
+     *
+     * # CDN-enable the container and set it's TTL for a month
+     * #
+     * $public_container->make_public(86400/2); # 12 hours (86400 seconds/day)
+     * </code>
+     *
+     * @param int $ttl the time in seconds content will be cached in the CDN
+     * @returns string the CDN enabled Container's URI
+     * @throws CDNNotEnabledException CDN functionality not returned during auth
+     * @throws AuthenticationException if auth token is not valid/expired
+     * @throws InvalidResponseException unexpected response
+     */
+    function make_public($ttl=86400)
+    {
+        if ($this->cfs_http->getCDNMUrl() == NULL) {
+            throw new Kohana_Exception(
+                "Authentication response did not indicate CDN availability");
+        }
+        if ($this->cdn_uri != NULL) {
+            # previously published, assume we're setting new attributes
+            list($status, $reason, $cdn_uri, $cdn_ssl_uri) =
+                $this->cfs_http->update_cdn_container($this->name,$ttl,
+                                                      $this->cdn_log_retention,
+                                                      $this->cdn_acl_user_agent,
+                                                      $this->cdn_acl_referrer);
+            #if ($status == 401 && $this->_re_auth()) {
+            #    return $this->make_public($ttl);
+            #}
+            if ($status == 404) {
+                # this instance _thinks_ the container was published, but the
+                # cdn management system thinks otherwise - try again with a PUT
+                list($status, $reason, $cdn_uri, $cdn_ssl_uri) =
+                    $this->cfs_http->add_cdn_container($this->name,$ttl);
+
+            }
+        } else {
+            # publish it for first time
+            list($status, $reason, $cdn_uri, $cdn_ssl_uri) =
+                $this->cfs_http->add_cdn_container($this->name,$ttl);
+        }
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->make_public($ttl);
+        #}
+        if (!in_array($status, array(201,202))) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        $this->cdn_enabled = True;
+        $this->cdn_ttl = $ttl;
+        $this->cdn_ssl_uri = $cdn_ssl_uri;
+        $this->cdn_uri = $cdn_uri;
+        $this->cdn_log_retention = False;
+        $this->cdn_acl_user_agent = "";
+        $this->cdn_acl_referrer = "";
+        return $this->cdn_uri;
+    }
+    /**
+     * Purge Containers objects from CDN Cache.
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     * $container = $conn->get_container("cdn_enabled");
+     * $container->purge_from_cdn("user at domain.com");
+     * # or
+     * $container->purge_from_cdn();
+     * # or
+     * $container->purge_from_cdn("user1 at domain.com,user2 at domain.com");
+     * @returns boolean True if successful
+     * @throws CDNNotEnabledException if CDN Is not enabled on this connection
+     * @throws InvalidResponseException if the response expected is not returned
+     */
+    function purge_from_cdn($email=null)
+    {
+        if (!$this->cfs_http->getCDNMUrl())
+        {
+            throw new Kohana_Exception(
+                "Authentication response did not indicate CDN availability");
+        }
+        $status = $this->cfs_http->purge_from_cdn($this->name, $email);
+        if ($status < 199 or $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        return True;
+    }
+    /**
+     * Enable ACL restriction by User Agent for this container.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_container = $conn->get_container("public");
+     *
+     * # Enable ACL by Referrer
+     * $public_container->acl_referrer("Mozilla");
+     * </code>
+     *
+     * @returns boolean True if successful
+     * @throws CDNNotEnabledException CDN functionality not returned during auth
+     * @throws AuthenticationException if auth token is not valid/expired
+     * @throws InvalidResponseException unexpected response
+     */
+    function acl_user_agent($cdn_acl_user_agent="") {
+        if ($this->cfs_http->getCDNMUrl() == NULL) {
+            throw new Kohana_Exception(
+                "Authentication response did not indicate CDN availability");
+        }
+        list($status,$reason) =
+            $this->cfs_http->update_cdn_container($this->name,
+                                                  $this->cdn_ttl,
+                                                  $this->cdn_log_retention,
+                                                  $cdn_acl_user_agent,
+                                                  $this->cdn_acl_referrer
+                );
+        if (!in_array($status, array(202,404))) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        $this->cdn_acl_user_agent = $cdn_acl_user_agent;
+        return True;
+    }
+
+    /**
+     * Enable ACL restriction by referer for this container.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_container = $conn->get_container("public");
+     *
+     * # Enable Referrer
+     * $public_container->acl_referrer("http://www.example.com/gallery.php");
+     * </code>
+     *
+     * @returns boolean True if successful
+     * @throws CDNNotEnabledException CDN functionality not returned during auth
+     * @throws AuthenticationException if auth token is not valid/expired
+     * @throws InvalidResponseException unexpected response
+     */
+    function acl_referrer($cdn_acl_referrer="") {
+        if ($this->cfs_http->getCDNMUrl() == NULL) {
+            throw new Kohana_Exception(
+                "Authentication response did not indicate CDN availability");
+        }
+        list($status,$reason) =
+            $this->cfs_http->update_cdn_container($this->name,
+                                                  $this->cdn_ttl,
+                                                  $this->cdn_log_retention,
+                                                  $this->cdn_acl_user_agent,
+                                                  $cdn_acl_referrer
+                );
+        if (!in_array($status, array(202,404))) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        $this->cdn_acl_referrer = $cdn_acl_referrer;
+        return True;
+    }
+
+    /**
+     * Enable log retention for this CDN container.
+     *
+     * Enable CDN log retention on the container. If enabled logs will
+     * be periodically (at unpredictable intervals) compressed and
+     * uploaded to a ".CDN_ACCESS_LOGS" container in the form of
+     * "container_name.YYYYMMDDHH-XXXX.gz". Requires CDN be enabled on
+     * the account.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_container = $conn->get_container("public");
+     *
+     * # Enable logs retention.
+     * $public_container->log_retention(True);
+     * </code>
+     *
+     * @returns boolean True if successful
+     * @throws CDNNotEnabledException CDN functionality not returned during auth
+     * @throws AuthenticationException if auth token is not valid/expired
+     * @throws InvalidResponseException unexpected response
+     */
+    function log_retention($cdn_log_retention=False) {
+        if ($this->cfs_http->getCDNMUrl() == NULL) {
+            throw new Kohana_Exception(
+                "Authentication response did not indicate CDN availability");
+        }
+        list($status,$reason) =
+            $this->cfs_http->update_cdn_container($this->name,
+                                                  $this->cdn_ttl,
+                                                  $cdn_log_retention,
+                                                  $this->cdn_acl_user_agent,
+                                                  $this->cdn_acl_referrer
+                );
+        if (!in_array($status, array(202,404))) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        $this->cdn_log_retention = $cdn_log_retention;
+        return True;
+    }
+
+    /**
+     * Disable the CDN sharing for this container
+     *
+     * Use this method to disallow distribution into the CDN of this Container's
+     * content.
+     *
+     * NOTE: Any content already cached in the CDN will continue to be served
+     *       from its cache until the TTL expiration transpires.  The default
+     *       TTL is typically one day, so "privatizing" the Container will take
+     *       up to 24 hours before the content is purged from the CDN cache.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_container = $conn->get_container("public");
+     *
+     * # Disable CDN accessability
+     * # ... still cached up to a month based on previous example
+     * #
+     * $public_container->make_private();
+     * </code>
+     *
+     * @returns boolean True if successful
+     * @throws CDNNotEnabledException CDN functionality not returned during auth
+     * @throws AuthenticationException if auth token is not valid/expired
+     * @throws InvalidResponseException unexpected response
+     */
+    function make_private()
+    {
+        if ($this->cfs_http->getCDNMUrl() == NULL) {
+            throw new Kohana_Exception(
+                "Authentication response did not indicate CDN availability");
+        }
+        list($status,$reason) = $this->cfs_http->remove_cdn_container($this->name);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->make_private();
+        #}
+        if (!in_array($status, array(202,404))) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        $this->cdn_enabled = False;
+        $this->cdn_ttl = NULL;
+        $this->cdn_uri = NULL;
+        $this->cdn_ssl_uri = NULL;
+        $this->cdn_streaming_uri - NULL;
+        $this->cdn_log_retention = NULL;
+        $this->cdn_acl_user_agent = NULL;
+        $this->cdn_acl_referrer = NULL;
+        return True;
+    }
+
+    /**
+     * Check if this Container is being publicly served via CDN
+     *
+     * Use this method to determine if the Container's content is currently
+     * available through the CDN.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_container = $conn->get_container("public");
+     *
+     * # Display CDN accessability
+     * #
+     * $public_container->is_public() ? print "Yes" : print "No";
+     * </code>
+     *
+     * @returns boolean True if enabled, False otherwise
+     */
+    function is_public()
+    {
+        return $this->cdn_enabled == True ? True : False;
+    }
+
+    /**
+     * Create a new remote storage Object
+     *
+     * Return a new Object instance.  If the remote storage Object exists,
+     * the instance's attributes are populated.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_container = $conn->get_container("public");
+     *
+     * # This creates a local instance of a storage object but only creates
+     * # it in the storage system when the object's write() method is called.
+     * #
+     * $pic = $public_container->create_object("baby.jpg");
+     * </code>
+     *
+     * @param string $obj_name name of storage Object
+     * @return obj CF_Object instance
+     */
+    function create_object($obj_name=NULL)
+    {
+        return new CF_Object($this, $obj_name);
+    }
+
+    /**
+     * Return an Object instance for the remote storage Object
+     *
+     * Given a name, return a Object instance representing the
+     * remote storage object.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $public_container = $conn->get_container("public");
+     *
+     * # This call only fetches header information and not the content of
+     * # the storage object.  Use the Object's read() or stream() methods
+     * # to obtain the object's data.
+     * #
+     * $pic = $public_container->get_object("baby.jpg");
+     * </code>
+     *
+     * @param string $obj_name name of storage Object
+     * @return obj CF_Object instance
+     */
+    function get_object($obj_name=NULL)
+    {
+        return new CF_Object($this, $obj_name, True);
+    }
+
+    /**
+     * Return a list of Objects
+     *
+     * Return an array of strings listing the Object names in this Container.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $images = $conn->get_container("my photos");
+     *
+     * # Grab the list of all storage objects
+     * #
+     * $all_objects = $images->list_objects();
+     *
+     * # Grab subsets of all storage objects
+     * #
+     * $first_ten = $images->list_objects(10);
+     *
+     * # Note the use of the previous result's last object name being
+     * # used as the 'marker' parameter to fetch the next 10 objects
+     * #
+     * $next_ten = $images->list_objects(10, $first_ten[count($first_ten)-1]);
+     *
+     * # Grab images starting with "birthday_party" and default limit/marker
+     * # to match all photos with that prefix
+     * #
+     * $prefixed = $images->list_objects(0, NULL, "birthday");
+     *
+     * # Assuming you have created the appropriate directory marker Objects,
+     * # you can traverse your pseudo-hierarchical containers
+     * # with the "path" argument.
+     * #
+     * $animals = $images->list_objects(0,NULL,NULL,"pictures/animals");
+     * $dogs = $images->list_objects(0,NULL,NULL,"pictures/animals/dogs");
+     * </code>
+     *
+     * @param int $limit <i>optional</i> only return $limit names
+     * @param int $marker <i>optional</i> subset of names starting at $marker
+     * @param string $prefix <i>optional</i> Objects whose names begin with $prefix
+     * @param string $path <i>optional</i> only return results under "pathname"
+     * @return array array of strings
+     * @throws InvalidResponseException unexpected response
+     */
+    function list_objects($limit=0, $marker=NULL, $prefix=NULL, $path=NULL)
+    {
+        list($status, $reason, $obj_list) =
+            $this->cfs_http->list_objects($this->name, $limit,
+                $marker, $prefix, $path);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->list_objects($limit, $marker, $prefix, $path);
+        #}
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        return $obj_list;
+    }
+
+    /**
+     * Return an array of Objects
+     *
+     * Return an array of Object instances in this Container.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $images = $conn->get_container("my photos");
+     *
+     * # Grab the list of all storage objects
+     * #
+     * $all_objects = $images->get_objects();
+     *
+     * # Grab subsets of all storage objects
+     * #
+     * $first_ten = $images->get_objects(10);
+     *
+     * # Note the use of the previous result's last object name being
+     * # used as the 'marker' parameter to fetch the next 10 objects
+     * #
+     * $next_ten = $images->list_objects(10, $first_ten[count($first_ten)-1]);
+     *
+     * # Grab images starting with "birthday_party" and default limit/marker
+     * # to match all photos with that prefix
+     * #
+     * $prefixed = $images->get_objects(0, NULL, "birthday");
+     *
+     * # Assuming you have created the appropriate directory marker Objects,
+     * # you can traverse your pseudo-hierarchical containers
+     * # with the "path" argument.
+     * #
+     * $animals = $images->get_objects(0,NULL,NULL,"pictures/animals");
+     * $dogs = $images->get_objects(0,NULL,NULL,"pictures/animals/dogs");
+     * </code>
+     *
+     * @param int $limit <i>optional</i> only return $limit names
+     * @param int $marker <i>optional</i> subset of names starting at $marker
+     * @param string $prefix <i>optional</i> Objects whose names begin with $prefix
+     * @param string $path <i>optional</i> only return results under "pathname"
+     * @return array array of strings
+     * @throws InvalidResponseException unexpected response
+     */
+    function get_objects($limit=0, $marker=NULL, $prefix=NULL, $path=NULL)
+    {
+        list($status, $reason, $obj_array) =
+            $this->cfs_http->get_objects($this->name, $limit,
+                $marker, $prefix, $path);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->get_objects($limit, $marker, $prefix, $path);
+        #}
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        $objects = array();
+        foreach ($obj_array as $obj) {
+            $tmp = new CF_Object($this, $obj["name"], False, False);
+            $tmp->content_type = $obj["content_type"];
+            $tmp->content_length = (float) $obj["bytes"];
+            $tmp->set_etag($obj["hash"]);
+            $tmp->last_modified = $obj["last_modified"];
+            $objects[] = $tmp;
+        }
+        return $objects;
+    }
+
+    /**
+     * Copy a remote storage Object to a target Container
+     *
+     * Given an Object instance or name and a target Container instance or name, copy copies the remote Object
+     * and all associated metadata.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $images = $conn->get_container("my photos");
+     *
+     * # Copy specific object
+     * #
+     * $images->copy_object_to("disco_dancing.jpg","container_target");
+     * </code>
+     *
+     * @param obj $obj name or instance of Object to copy
+     * @param obj $container_target name or instance of target Container
+     * @param string $dest_obj_name name of target object (optional - uses source name if omitted)
+     * @param array $metadata metadata array for new object (optional)
+     * @param array $headers header fields array for the new object (optional)
+     * @return boolean <kbd>true</kbd> if successfully copied
+     * @throws SyntaxException invalid Object/Container name
+     * @throws NoSuchObjectException remote Object does not exist
+     * @throws InvalidResponseException unexpected response
+     */
+    function copy_object_to($obj,$container_target,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL)
+    {
+        $obj_name = NULL;
+        if (is_object($obj)) {
+            if (get_class($obj) == "CF_Object") {
+                $obj_name = $obj->name;
+            }
+        }
+        if (is_string($obj)) {
+            $obj_name = $obj;
+        }
+        if (!$obj_name) {
+            throw new Kohana_Exception("Object name not set.");
+        }
+
+				if ($dest_obj_name === NULL) {
+            $dest_obj_name = $obj_name;
+				}
+
+        $container_name_target = NULL;
+        if (is_object($container_target)) {
+            if (get_class($container_target) == "CF_Container") {
+                $container_name_target = $container_target->name;
+            }
+        }
+        if (is_string($container_target)) {
+            $container_name_target = $container_target;
+        }
+        if (!$container_name_target) {
+            throw new Kohana_Exception("Container name target not set.");
+        }
+
+        $status = $this->cfs_http->copy_object($obj_name,$dest_obj_name,$this->name,$container_name_target,$metadata,$headers);
+        if ($status == 404) {
+            $m = "Specified object '".$this->name."/".$obj_name;
+            $m.= "' did not exist as source to copy from or '".$container_name_target."' did not exist as target to copy to.";
+            throw new Kohana_Exception($m);
+        }
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        return true;
+    }
+
+    /**
+     * Copy a remote storage Object from a source Container
+     *
+     * Given an Object instance or name and a source Container instance or name, copy copies the remote Object
+     * and all associated metadata.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $images = $conn->get_container("my photos");
+     *
+     * # Copy specific object
+     * #
+     * $images->copy_object_from("disco_dancing.jpg","container_source");
+     * </code>
+     *
+     * @param obj $obj name or instance of Object to copy
+     * @param obj $container_source name or instance of source Container
+     * @param string $dest_obj_name name of target object (optional - uses source name if omitted)
+     * @param array $metadata metadata array for new object (optional)
+     * @param array $headers header fields array for the new object (optional)
+     * @return boolean <kbd>true</kbd> if successfully copied
+     * @throws SyntaxException invalid Object/Container name
+     * @throws NoSuchObjectException remote Object does not exist
+     * @throws InvalidResponseException unexpected response
+     */
+    function copy_object_from($obj,$container_source,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL)
+    {
+        $obj_name = NULL;
+        if (is_object($obj)) {
+            if (get_class($obj) == "CF_Object") {
+                $obj_name = $obj->name;
+            }
+        }
+        if (is_string($obj)) {
+            $obj_name = $obj;
+        }
+        if (!$obj_name) {
+            throw new Kohana_Exception("Object name not set.");
+        }
+
+				if ($dest_obj_name === NULL) {
+            $dest_obj_name = $obj_name;
+				}
+
+        $container_name_source = NULL;
+        if (is_object($container_source)) {
+            if (get_class($container_source) == "CF_Container") {
+                $container_name_source = $container_source->name;
+            }
+        }
+        if (is_string($container_source)) {
+            $container_name_source = $container_source;
+        }
+        if (!$container_name_source) {
+            throw new Kohana_Exception("Container name source not set.");
+        }
+
+        $status = $this->cfs_http->copy_object($obj_name,$dest_obj_name,$container_name_source,$this->name,$metadata,$headers);
+        if ($status == 404) {
+            $m = "Specified object '".$container_name_source."/".$obj_name;
+            $m.= "' did not exist as source to copy from or '".$this->name."/".$obj_name."' did not exist as target to copy to.";
+            throw new Kohana_Exception($m);
+        }
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+
+        return true;
+    }
+
+    /**
+     * Move a remote storage Object to a target Container
+     *
+     * Given an Object instance or name and a target Container instance or name, move copies the remote Object
+     * and all associated metadata and deletes the source Object afterwards
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $images = $conn->get_container("my photos");
+     *
+     * # Move specific object
+     * #
+     * $images->move_object_to("disco_dancing.jpg","container_target");
+     * </code>
+     *
+     * @param obj $obj name or instance of Object to move
+     * @param obj $container_target name or instance of target Container
+     * @param string $dest_obj_name name of target object (optional - uses source name if omitted)
+     * @param array $metadata metadata array for new object (optional)
+     * @param array $headers header fields array for the new object (optional)
+     * @return boolean <kbd>true</kbd> if successfully moved
+     * @throws SyntaxException invalid Object/Container name
+     * @throws NoSuchObjectException remote Object does not exist
+     * @throws InvalidResponseException unexpected response
+     */
+    function move_object_to($obj,$container_target,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL)
+    {
+    	$retVal = false;
+
+        if(self::copy_object_to($obj,$container_target,$dest_obj_name,$metadata,$headers)) {
+        	$retVal = self::delete_object($obj,$this->name);
+        }
+
+        return $retVal;
+    }
+
+    /**
+     * Move a remote storage Object from a source Container
+     *
+     * Given an Object instance or name and a source Container instance or name, move copies the remote Object
+     * and all associated metadata and deletes the source Object afterwards
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $images = $conn->get_container("my photos");
+     *
+     * # Move specific object
+     * #
+     * $images->move_object_from("disco_dancing.jpg","container_target");
+     * </code>
+     *
+     * @param obj $obj name or instance of Object to move
+     * @param obj $container_source name or instance of target Container
+     * @param string $dest_obj_name name of target object (optional - uses source name if omitted)
+     * @param array $metadata metadata array for new object (optional)
+     * @param array $headers header fields array for the new object (optional)
+     * @return boolean <kbd>true</kbd> if successfully moved
+     * @throws SyntaxException invalid Object/Container name
+     * @throws NoSuchObjectException remote Object does not exist
+     * @throws InvalidResponseException unexpected response
+     */
+    function move_object_from($obj,$container_source,$dest_obj_name=NULL,$metadata=NULL,$headers=NULL)
+    {
+    	$retVal = false;
+
+        if(self::copy_object_from($obj,$container_source,$dest_obj_name,$metadata,$headers)) {
+        	$retVal = self::delete_object($obj,$container_source);
+        }
+
+        return $retVal;
+    }
+
+    /**
+     * Delete a remote storage Object
+     *
+     * Given an Object instance or name, permanently remove the remote Object
+     * and all associated metadata.
+     *
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     *
+     * $images = $conn->get_container("my photos");
+     *
+     * # Delete specific object
+     * #
+     * $images->delete_object("disco_dancing.jpg");
+     * </code>
+     *
+     * @param obj $obj name or instance of Object to delete
+     * @param obj $container name or instance of Container in which the object resides (optional)
+     * @return boolean <kbd>True</kbd> if successfully removed
+     * @throws SyntaxException invalid Object name
+     * @throws NoSuchObjectException remote Object does not exist
+     * @throws InvalidResponseException unexpected response
+     */
+    function delete_object($obj,$container=NULL)
+    {
+        $obj_name = NULL;
+        if (is_object($obj)) {
+            if (get_class($obj) == "CF_Object") {
+                $obj_name = $obj->name;
+            }
+        }
+        if (is_string($obj)) {
+            $obj_name = $obj;
+        }
+        if (!$obj_name) {
+            throw new Kohana_Exception("Object name not set.");
+        }
+
+        $container_name = NULL;
+
+        if($container === NULL) {
+        	$container_name = $this->name;
+        }
+        else {
+	        if (is_object($container)) {
+	            if (get_class($container) == "CF_Container") {
+	                $container_name = $container->name;
+	            }
+	        }
+	        if (is_string($container)) {
+	            $container_name = $container;
+	        }
+	        if (!$container_name) {
+	            throw new Kohana_Exception("Container name source not set.");
+	        }
+        }
+
+        $status = $this->cfs_http->delete_object($container_name, $obj_name);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->delete_object($obj);
+        #}
+        if ($status == 404) {
+            $m = "Specified object '".$container_name."/".$obj_name;
+            $m.= "' did not exist to delete.";
+            throw new Kohana_Exception($m);
+        }
+        if ($status != 204) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        return True;
+    }
+
+    /**
+     * Helper function to create "path" elements for a given Object name
+     *
+     * Given an Object whos name contains '/' path separators, this function
+     * will create the "directory marker" Objects of one byte with the
+     * Content-Type of "application/directory".
+     *
+     * It assumes the last element of the full path is the "real" Object
+     * and does NOT create a remote storage Object for that last element.
+     */
+    function create_paths($path_name)
+    {
+        if (isset($path_name[0]) && $path_name[0] == '/') {
+            $path_name = mb_substr($path_name, 0, 1);
+        }
+        $elements = explode('/', $path_name, -1);
+        $build_path = "";
+        foreach ($elements as $idx => $val) {
+            if (!$build_path) {
+                $build_path = $val;
+            } else {
+                $build_path .= "/" . $val;
+            }
+            $obj = new CF_Object($this, $build_path);
+            $obj->content_type = "application/directory";
+            $obj->write(".", 1);
+        }
+    }
+
+    /**
+     * Internal method to grab CDN/Container info if appropriate to do so
+     *
+     * @throws InvalidResponseException unexpected response
+     */
+    private function _cdn_initialize()
+    {
+        list($status, $reason, $cdn_enabled, $cdn_ssl_uri, $cdn_streaming_uri, $cdn_uri, $cdn_ttl,
+             $cdn_log_retention, $cdn_acl_user_agent, $cdn_acl_referrer) =
+            $this->cfs_http->head_cdn_container($this->name);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->_cdn_initialize();
+        #}
+        if (!in_array($status, array(204,404))) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->cfs_http->get_error());
+        }
+        $this->cdn_enabled = $cdn_enabled;
+        $this->cdn_streaming_uri = $cdn_streaming_uri;
+        $this->cdn_ssl_uri = $cdn_ssl_uri;
+        $this->cdn_uri = $cdn_uri;
+        $this->cdn_ttl = $cdn_ttl;
+        $this->cdn_log_retention = $cdn_log_retention;
+        $this->cdn_acl_user_agent = $cdn_acl_user_agent;
+        $this->cdn_acl_referrer = $cdn_acl_referrer;
+    }
+
+    #private function _re_auth()
+    #{
+    #    $new_auth = new CF_Authentication(
+    #        $this->cfs_auth->username,
+    #        $this->cfs_auth->api_key,
+    #        $this->cfs_auth->auth_host,
+    #        $this->cfs_auth->account);
+    #    $new_auth->authenticate();
+    #    $this->cfs_auth = $new_auth;
+    #    $this->cfs_http->setCFAuth($this->cfs_auth);
+    #    return True;
+    #}
+}
+
+?>
\ No newline at end of file
diff --git a/application/libraries/cloudfiles/CF_Http.php b/application/libraries/cloudfiles/CF_Http.php
new file mode 100644
index 0000000..52c1a4b
--- /dev/null
+++ b/application/libraries/cloudfiles/CF_Http.php
@@ -0,0 +1,1412 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * HTTP/cURL wrapper for Cloud Files
+ *
+ * This class should not be used directly.  It's only purpose is to abstract
+ * out the HTTP communication from the main API.
+ *
+ * @package php-cloudfiles-http
+ */
+class CF_Http
+{
+    private $error_str;
+    private $dbug;
+    private $cabundle_path;
+    private $api_version;
+
+    # Authentication instance variables
+    #
+    private $storage_url;
+    private $cdnm_url;
+    private $auth_token;
+
+    # Request/response variables
+    #
+    private $response_status;
+    private $response_reason;
+    private $connections;
+
+    # Variables used for content/header callbacks
+    #
+    private $_user_read_progress_callback_func;
+    private $_user_write_progress_callback_func;
+    private $_write_callback_type;
+    private $_text_list;
+    private $_account_container_count;
+    private $_account_bytes_used;
+    private $_container_object_count;
+    private $_container_bytes_used;
+    private $_obj_etag;
+    private $_obj_last_modified;
+    private $_obj_content_type;
+    private $_obj_content_length;
+    private $_obj_metadata;
+    private $_obj_headers;
+    private $_obj_manifest;
+    private $_obj_write_resource;
+    private $_obj_write_string;
+    private $_cdn_enabled;
+    private $_cdn_ssl_uri;
+    private $_cdn_streaming_uri;
+    private $_cdn_uri;
+    private $_cdn_ttl;
+    private $_cdn_log_retention;
+    private $_cdn_acl_user_agent;
+    private $_cdn_acl_referrer;
+
+    function __construct($api_version)
+    {
+        $this->dbug = False;
+        $this->cabundle_path = NULL;
+        $this->api_version = $api_version;
+        $this->error_str = NULL;
+
+        $this->storage_url = NULL;
+        $this->cdnm_url = NULL;
+        $this->auth_token = NULL;
+
+        $this->response_status = NULL;
+        $this->response_reason = NULL;
+
+        # Curl connections array - since there is no way to "re-set" the
+        # connection paramaters for a cURL handle, we keep an array of
+        # the unique use-cases and funnel all of those same type
+        # requests through the appropriate curl connection.
+        #
+        $this->connections = array(
+            "GET_CALL"  => NULL, # GET objects/containers/lists
+            "PUT_OBJ"   => NULL, # PUT object
+            "HEAD"      => NULL, # HEAD requests
+            "PUT_CONT"  => NULL, # PUT container
+            "DEL_POST"  => NULL, # DELETE containers/objects, POST objects
+            "COPY"      => null, # COPY objects
+        );
+
+        $this->_user_read_progress_callback_func = NULL;
+        $this->_user_write_progress_callback_func = NULL;
+        $this->_write_callback_type = NULL;
+        $this->_text_list = array();
+	$this->_return_list = NULL;
+        $this->_account_container_count = 0;
+        $this->_account_bytes_used = 0;
+        $this->_container_object_count = 0;
+        $this->_container_bytes_used = 0;
+        $this->_obj_write_resource = NULL;
+        $this->_obj_write_string = "";
+        $this->_obj_etag = NULL;
+        $this->_obj_last_modified = NULL;
+        $this->_obj_content_type = NULL;
+        $this->_obj_content_length = NULL;
+        $this->_obj_metadata = array();
+        $this->_obj_manifest = NULL;
+        $this->_obj_headers = NULL;
+        $this->_cdn_enabled = NULL;
+        $this->_cdn_ssl_uri = NULL;
+        $this->_cdn_streaming_uri = NULL;
+        $this->_cdn_uri = NULL;
+        $this->_cdn_ttl = NULL;
+        $this->_cdn_log_retention = NULL;
+        $this->_cdn_acl_user_agent = NULL;
+        $this->_cdn_acl_referrer = NULL;
+
+        # The OS list with a PHP without an updated CA File for CURL to
+        # connect to SSL Websites. It is the first 3 letters of the PHP_OS
+        # variable.
+        $OS_CAFILE_NONUPDATED=array(
+            "win","dar"
+        );
+
+        if (in_array((strtolower (substr(PHP_OS, 0,3))), $OS_CAFILE_NONUPDATED))
+            $this->ssl_use_cabundle();
+
+    }
+
+    function ssl_use_cabundle($path=NULL)
+    {
+        if ($path) {
+            $this->cabundle_path = $path;
+        } else {
+            $this->cabundle_path = dirname(__FILE__) . "/share/cacert.pem";
+        }
+        if (!file_exists($this->cabundle_path)) {
+            throw new Kohana_Exception("Could not use CA bundle: "
+                . $this->cabundle_path);
+        }
+        return;
+    }
+
+    # Uses separate cURL connection to authenticate
+    #
+    function authenticate($user, $pass, $acct=NULL, $host=NULL)
+    {
+        $path = array();
+        if (isset($acct)){
+            $headers = array(
+                sprintf("%s: %s", AUTH_USER_HEADER_LEGACY, $user),
+                sprintf("%s: %s", AUTH_KEY_HEADER_LEGACY, $pass),
+                );
+            $path[] = $host;
+            $path[] = rawurlencode(sprintf("v%d",$this->api_version));
+            $path[] = rawurlencode($acct);
+        } else {
+            $headers = array(
+                sprintf("%s: %s", AUTH_USER_HEADER, $user),
+                sprintf("%s: %s", AUTH_KEY_HEADER, $pass),
+                );
+	    $path[] = $host;
+        }
+	$path[] = "v1.0";
+        $url = implode("/", $path);
+
+        $curl_ch = curl_init();
+        if (!is_null($this->cabundle_path)) {
+            curl_setopt($curl_ch, CURLOPT_SSL_VERIFYPEER, True);
+            curl_setopt($curl_ch, CURLOPT_CAINFO, $this->cabundle_path);
+        }
+        curl_setopt($curl_ch, CURLOPT_VERBOSE, $this->dbug);
+        curl_setopt($curl_ch, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($curl_ch, CURLOPT_MAXREDIRS, 4);
+        curl_setopt($curl_ch, CURLOPT_HEADER, 0);
+        curl_setopt($curl_ch, CURLOPT_HTTPHEADER, $headers);
+        curl_setopt($curl_ch, CURLOPT_USERAGENT, USER_AGENT);
+        curl_setopt($curl_ch, CURLOPT_RETURNTRANSFER, TRUE);
+        curl_setopt($curl_ch, CURLOPT_HEADERFUNCTION,array(&$this,'_auth_hdr_cb'));
+        curl_setopt($curl_ch, CURLOPT_CONNECTTIMEOUT, 10);
+        curl_setopt($curl_ch, CURLOPT_URL, $url);
+        curl_exec($curl_ch);
+        curl_close($curl_ch);
+
+        return array($this->response_status, $this->response_reason,
+            $this->storage_url, $this->cdnm_url, $this->auth_token);
+    }
+
+    # (CDN) GET /v1/Account
+    #
+    function list_cdn_containers($enabled_only)
+    {
+        $conn_type = "GET_CALL";
+        $url_path = $this->_make_path("CDN");
+
+        $this->_write_callback_type = "TEXT_LIST";
+        if ($enabled_only)
+        {
+            $return_code = $this->_send_request($conn_type, $url_path .
+            '/?enabled_only=true');
+        }
+        else
+        {
+            $return_code = $this->_send_request($conn_type, $url_path);
+        }
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,array());
+        }
+        if ($return_code == 401) {
+            return array($return_code,"Unauthorized",array());
+        }
+        if ($return_code == 404) {
+            return array($return_code,"Account not found.",array());
+        }
+        if ($return_code == 204) {
+            return array($return_code,"Account has no CDN enabled Containers.",
+                array());
+        }
+        if ($return_code == 200) {
+	    $this->create_array();
+            return array($return_code,$this->response_reason,$this->_text_list);
+        }
+        $this->error_str = "Unexpected HTTP response: ".$this->response_reason;
+        return array($return_code,$this->error_str,array());
+    }
+
+    # (CDN) DELETE /v1/Account/Container or /v1/Account/Container/Object
+    #
+    function purge_from_cdn($path, $email=null)
+    {
+        if(!$path)
+            throw new Kohana_Exception("Path not set");
+        $url_path = $this->_make_path("CDN", NULL, $path);
+        if($email)
+        {
+            $hdrs = array(CDN_EMAIL => $email);
+            $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"DELETE");
+        }
+        else
+            $return_code = $this->_send_request("DEL_POST",$url_path,null,"DELETE");
+        return $return_code;
+    }
+
+    # (CDN) POST /v1/Account/Container
+    function update_cdn_container($container_name, $ttl=86400, $cdn_log_retention=False,
+                                  $cdn_acl_user_agent="", $cdn_acl_referrer)
+    {
+        if ($container_name == "")
+            throw new Kohana_Exception("Container name not set.");
+
+        if ($container_name != "0" and !isset($container_name))
+            throw new Kohana_Exception("Container name not set.");
+
+        $url_path = $this->_make_path("CDN", $container_name);
+        $hdrs = array(
+            CDN_ENABLED => "True",
+            CDN_TTL => $ttl,
+            CDN_LOG_RETENTION => $cdn_log_retention ?  "True" : "False",
+            CDN_ACL_USER_AGENT => $cdn_acl_user_agent,
+            CDN_ACL_REFERRER => $cdn_acl_referrer,
+            );
+        $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST");
+        if ($return_code == 401) {
+            $this->error_str = "Unauthorized";
+            return array($return_code, $this->error_str, NULL);
+        }
+        if ($return_code == 404) {
+            $this->error_str = "Container not found.";
+            return array($return_code, $this->error_str, NULL);
+        }
+        if ($return_code != 202) {
+            $this->error_str="Unexpected HTTP response: ".$this->response_reason;
+            return array($return_code, $this->error_str, NULL);
+        }
+        return array($return_code, "Accepted", $this->_cdn_uri, $this->_cdn_ssl_uri);
+
+    }
+
+    # (CDN) PUT /v1/Account/Container
+    #
+    function add_cdn_container($container_name, $ttl=86400)
+    {
+        if ($container_name == "")
+            throw new Kohana_Exception("Container name not set.");
+
+        if ($container_name != "0" and !isset($container_name))
+            throw new Kohana_Exception("Container name not set.");
+
+        $url_path = $this->_make_path("CDN", $container_name);
+        $hdrs = array(
+            CDN_ENABLED => "True",
+            CDN_TTL => $ttl,
+            );
+        $return_code = $this->_send_request("PUT_CONT", $url_path, $hdrs);
+        if ($return_code == 401) {
+            $this->error_str = "Unauthorized";
+            return array($return_code,$this->response_reason,False);
+        }
+        if (!in_array($return_code, array(201,202))) {
+            $this->error_str="Unexpected HTTP response: ".$this->response_reason;
+            return array($return_code,$this->response_reason,False);
+        }
+        return array($return_code,$this->response_reason,$this->_cdn_uri,
+                     $this->_cdn_ssl_uri);
+    }
+
+    # (CDN) POST /v1/Account/Container
+    #
+    function remove_cdn_container($container_name)
+    {
+        if ($container_name == "")
+            throw new Kohana_Exception("Container name not set.");
+
+        if ($container_name != "0" and !isset($container_name))
+            throw new Kohana_Exception("Container name not set.");
+
+        $url_path = $this->_make_path("CDN", $container_name);
+        $hdrs = array(CDN_ENABLED => "False");
+        $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST");
+        if ($return_code == 401) {
+            $this->error_str = "Unauthorized";
+            return array($return_code, $this->error_str);
+        }
+        if ($return_code == 404) {
+            $this->error_str = "Container not found.";
+            return array($return_code, $this->error_str);
+        }
+        if ($return_code != 202) {
+            $this->error_str="Unexpected HTTP response: ".$this->response_reason;
+            return array($return_code, $this->error_str);
+        }
+        return array($return_code, "Accepted");
+    }
+
+    # (CDN) HEAD /v1/Account
+    #
+    function head_cdn_container($container_name)
+    {
+        if ($container_name == "")
+            throw new Kohana_Exception("Container name not set.");
+
+        if ($container_name != "0" and !isset($container_name))
+            throw new Kohana_Exception("Container name not set.");
+
+        $conn_type = "HEAD";
+        $url_path = $this->_make_path("CDN", $container_name);
+        $return_code = $this->_send_request($conn_type, $url_path, NULL, "GET", True);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+        }
+        if ($return_code == 401) {
+            return array($return_code,"Unauthorized",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+        }
+        if ($return_code == 404) {
+            return array($return_code,"Account not found.",NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
+        }
+        if ($return_code == 204) {
+            return array($return_code,$this->response_reason,
+                $this->_cdn_enabled, $this->_cdn_ssl_uri,
+                $this->_cdn_streaming_uri,
+                $this->_cdn_uri, $this->_cdn_ttl,
+                $this->_cdn_log_retention,
+                $this->_cdn_acl_user_agent,
+                $this->_cdn_acl_referrer
+                );
+        }
+        return array($return_code,$this->response_reason,
+                     NULL,NULL,NULL,NULL,
+                     $this->_cdn_log_retention,
+                     $this->_cdn_acl_user_agent,
+                     $this->_cdn_acl_referrer,
+                     NULL
+            );
+    }
+
+    # GET /v1/Account
+    #
+    function list_containers($limit=0, $marker=NULL)
+    {
+        $conn_type = "GET_CALL";
+        $url_path = $this->_make_path();
+
+        $limit = intval($limit);
+        $params = array();
+        if ($limit > 0) {
+            $params[] = "limit=$limit";
+        }
+        if ($marker) {
+            $params[] = "marker=".rawurlencode($marker);
+        }
+        if (!empty($params)) {
+            $url_path .= "?" . implode("&", $params);
+        }
+
+        $this->_write_callback_type = "TEXT_LIST";
+        $return_code = $this->_send_request($conn_type, $url_path);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,array());
+        }
+        if ($return_code == 204) {
+            return array($return_code, "Account has no containers.", array());
+        }
+        if ($return_code == 404) {
+            $this->error_str = "Invalid account name for authentication token.";
+            return array($return_code,$this->error_str,array());
+        }
+        if ($return_code == 200) {
+	    $this->create_array();
+            return array($return_code, $this->response_reason, $this->_text_list);
+        }
+        $this->error_str = "Unexpected HTTP response: ".$this->response_reason;
+        return array($return_code,$this->error_str,array());
+    }
+
+    # GET /v1/Account?format=json
+    #
+    function list_containers_info($limit=0, $marker=NULL)
+    {
+        $conn_type = "GET_CALL";
+        $url_path = $this->_make_path() . "?format=json";
+
+        $limit = intval($limit);
+        $params = array();
+        if ($limit > 0) {
+            $params[] = "limit=$limit";
+        }
+        if ($marker) {
+            $params[] = "marker=".rawurlencode($marker);
+        }
+        if (!empty($params)) {
+            $url_path .= "&" . implode("&", $params);
+        }
+
+        $this->_write_callback_type = "OBJECT_STRING";
+        $return_code = $this->_send_request($conn_type, $url_path);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,array());
+        }
+        if ($return_code == 204) {
+            return array($return_code, "Account has no containers.", array());
+        }
+        if ($return_code == 404) {
+            $this->error_str = "Invalid account name for authentication token.";
+            return array($return_code,$this->error_str,array());
+        }
+        if ($return_code == 200) {
+            $json_body = json_decode($this->_obj_write_string, True);
+            return array($return_code, $this->response_reason, $json_body);
+        }
+        $this->error_str = "Unexpected HTTP response: ".$this->response_reason;
+        return array($return_code,$this->error_str,array());
+    }
+
+    # HEAD /v1/Account
+    #
+    function head_account()
+    {
+        $conn_type = "HEAD";
+
+        $url_path = $this->_make_path();
+        $return_code = $this->_send_request($conn_type,$url_path);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,0,0);
+        }
+        if ($return_code == 404) {
+            return array($return_code,"Account not found.",0,0);
+        }
+        if ($return_code == 204) {
+            return array($return_code,$this->response_reason,
+                $this->_account_container_count, $this->_account_bytes_used);
+        }
+        return array($return_code,$this->response_reason,0,0);
+    }
+
+    # PUT /v1/Account/Container
+    #
+    function create_container($container_name)
+    {
+        if ($container_name == "")
+            throw new Kohana_Exception("Container name not set.");
+
+        if ($container_name != "0" and !isset($container_name))
+            throw new Kohana_Exception("Container name not set.");
+
+        $url_path = $this->_make_path("STORAGE", $container_name);
+        $return_code = $this->_send_request("PUT_CONT",$url_path);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return False;
+        }
+        return $return_code;
+    }
+
+    # DELETE /v1/Account/Container
+    #
+    function delete_container($container_name)
+    {
+        if ($container_name == "")
+            throw new Kohana_Exception("Container name not set.");
+
+        if ($container_name != "0" and !isset($container_name))
+            throw new Kohana_Exception("Container name not set.");
+
+        $url_path = $this->_make_path("STORAGE", $container_name);
+        $return_code = $this->_send_request("DEL_POST",$url_path,array(),"DELETE");
+
+        switch ($return_code) {
+        case 204:
+            break;
+        case 0:
+            $this->error_str .= ": Failed to obtain valid HTTP response.";;
+            break;
+        case 409:
+            $this->error_str = "Container must be empty prior to removing it.";
+            break;
+        case 404:
+            $this->error_str = "Specified container did not exist to delete.";
+            break;
+        default:
+            $this->error_str = "Unexpected HTTP return code: $return_code.";
+        }
+        return $return_code;
+    }
+
+    # GET /v1/Account/Container
+    #
+    function list_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL)
+    {
+        if (!$cname) {
+            $this->error_str = "Container name not set.";
+            return array(0, $this->error_str, array());
+        }
+
+        $url_path = $this->_make_path("STORAGE", $cname);
+
+        $limit = intval($limit);
+        $params = array();
+        if ($limit > 0) {
+            $params[] = "limit=$limit";
+        }
+        if ($marker) {
+            $params[] = "marker=".rawurlencode($marker);
+        }
+        if ($prefix) {
+            $params[] = "prefix=".rawurlencode($prefix);
+        }
+        if ($path) {
+            $params[] = "path=".rawurlencode($path);
+        }
+        if (!empty($params)) {
+            $url_path .= "?" . implode("&", $params);
+        }
+
+        $conn_type = "GET_CALL";
+        $this->_write_callback_type = "TEXT_LIST";
+        $return_code = $this->_send_request($conn_type,$url_path);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,array());
+        }
+        if ($return_code == 204) {
+            $this->error_str = "Container has no Objects.";
+            return array($return_code,$this->error_str,array());
+        }
+        if ($return_code == 404) {
+            $this->error_str = "Container has no Objects.";
+            return array($return_code,$this->error_str,array());
+        }
+        if ($return_code == 200) {
+	    $this->create_array();
+            return array($return_code,$this->response_reason, $this->_text_list);
+        }
+        $this->error_str = "Unexpected HTTP response code: $return_code";
+        return array(0,$this->error_str,array());
+    }
+
+    # GET /v1/Account/Container?format=json
+    #
+    function get_objects($cname,$limit=0,$marker=NULL,$prefix=NULL,$path=NULL)
+    {
+        if (!$cname) {
+            $this->error_str = "Container name not set.";
+            return array(0, $this->error_str, array());
+        }
+
+        $url_path = $this->_make_path("STORAGE", $cname);
+
+        $limit = intval($limit);
+        $params = array();
+        $params[] = "format=json";
+        if ($limit > 0) {
+            $params[] = "limit=$limit";
+        }
+        if ($marker) {
+            $params[] = "marker=".rawurlencode($marker);
+        }
+        if ($prefix) {
+            $params[] = "prefix=".rawurlencode($prefix);
+        }
+        if ($path) {
+            $params[] = "path=".rawurlencode($path);
+        }
+        if (!empty($params)) {
+            $url_path .= "?" . implode("&", $params);
+        }
+
+        $conn_type = "GET_CALL";
+        $this->_write_callback_type = "OBJECT_STRING";
+        $return_code = $this->_send_request($conn_type,$url_path);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,array());
+        }
+        if ($return_code == 204) {
+            $this->error_str = "Container has no Objects.";
+            return array($return_code,$this->error_str,array());
+        }
+        if ($return_code == 404) {
+            $this->error_str = "Container has no Objects.";
+            return array($return_code,$this->error_str,array());
+        }
+        if ($return_code == 200) {
+            $json_body = json_decode($this->_obj_write_string, True);
+            return array($return_code,$this->response_reason, $json_body);
+        }
+        $this->error_str = "Unexpected HTTP response code: $return_code";
+        return array(0,$this->error_str,array());
+    }
+
+
+    # HEAD /v1/Account/Container
+    #
+    function head_container($container_name)
+    {
+
+        if ($container_name == "") {
+            $this->error_str = "Container name not set.";
+            return False;
+        }
+
+        if ($container_name != "0" and !isset($container_name)) {
+            $this->error_str = "Container name not set.";
+            return False;
+        }
+
+        $conn_type = "HEAD";
+
+        $url_path = $this->_make_path("STORAGE", $container_name);
+        $return_code = $this->_send_request($conn_type,$url_path);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,0,0);
+        }
+        if ($return_code == 404) {
+            return array($return_code,"Container not found.",0,0);
+        }
+        if ($return_code == 204 || $return_code == 200) {
+            return array($return_code,$this->response_reason,
+                $this->_container_object_count, $this->_container_bytes_used);
+        }
+        return array($return_code,$this->response_reason,0,0);
+    }
+
+    # GET /v1/Account/Container/Object
+    #
+    function get_object_to_string(&$obj, $hdrs=array())
+    {
+        if (!is_object($obj) || get_class($obj) != "CF_Object") {
+            throw new Kohana_Exception(
+                "Method argument is not a valid CF_Object.");
+        }
+
+        $conn_type = "GET_CALL";
+
+        $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name);
+        $this->_write_callback_type = "OBJECT_STRING";
+        $return_code = $this->_send_request($conn_type,$url_path,$hdrs);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array($return_code0,$this->error_str,NULL);
+        }
+        if ($return_code == 404) {
+            $this->error_str = "Object not found.";
+            return array($return_code0,$this->error_str,NULL);
+        }
+        if (($return_code < 200) || ($return_code > 299
+                && $return_code != 412 && $return_code != 304)) {
+            $this->error_str = "Unexpected HTTP return code: $return_code";
+            return array($return_code,$this->error_str,NULL);
+        }
+        return array($return_code,$this->response_reason, $this->_obj_write_string);
+    }
+
+    # GET /v1/Account/Container/Object
+    #
+    function get_object_to_stream(&$obj, &$resource=NULL, $hdrs=array())
+    {
+        if (!is_object($obj) || get_class($obj) != "CF_Object") {
+            throw new Kohana_Exception(
+                "Method argument is not a valid CF_Object.");
+        }
+        if (!is_resource($resource)) {
+            throw new Kohana_Exception(
+                "Resource argument not a valid PHP resource.");
+        }
+
+        $conn_type = "GET_CALL";
+
+        $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name);
+        $this->_obj_write_resource = $resource;
+        $this->_write_callback_type = "OBJECT_STREAM";
+        $return_code = $this->_send_request($conn_type,$url_path,$hdrs);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array($return_code,$this->error_str);
+        }
+        if ($return_code == 404) {
+            $this->error_str = "Object not found.";
+            return array($return_code,$this->error_str);
+        }
+        if (($return_code < 200) || ($return_code > 299
+                && $return_code != 412 && $return_code != 304)) {
+            $this->error_str = "Unexpected HTTP return code: $return_code";
+            return array($return_code,$this->error_str);
+        }
+        return array($return_code,$this->response_reason);
+    }
+
+    # PUT /v1/Account/Container/Object
+    #
+    function put_object(&$obj, &$fp)
+    {
+        if (!is_object($obj) || get_class($obj) != "CF_Object") {
+            throw new Kohana_Exception(
+                "Method argument is not a valid CF_Object.");
+        }
+        if (!is_resource($fp)) {
+            throw new Kohana_Exception(
+                "File pointer argument is not a valid resource.");
+        }
+
+        $conn_type = "PUT_OBJ";
+        $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name);
+
+        $hdrs = $this->_headers($obj);
+
+        $etag = $obj->getETag();
+        if (isset($etag)) {
+            $hdrs[] = "ETag: " . $etag;
+        }
+        if (!$obj->content_type) {
+            $hdrs[] = "Content-Type: application/octet-stream";
+        } else {
+            $hdrs[] = "Content-Type: " . $obj->content_type;
+        }
+
+        $this->_init($conn_type);
+        curl_setopt($this->connections[$conn_type],
+                CURLOPT_INFILE, $fp);
+        if (!$obj->content_length) {
+            # We don''t know the Content-Length, so assumed "chunked" PUT
+            #
+            curl_setopt($this->connections[$conn_type], CURLOPT_UPLOAD, True);
+            $hdrs[] = 'Transfer-Encoding: chunked';
+        } else {
+            # We know the Content-Length, so use regular transfer
+            #
+            curl_setopt($this->connections[$conn_type],
+                    CURLOPT_INFILESIZE, $obj->content_length);
+        }
+        $return_code = $this->_send_request($conn_type,$url_path,$hdrs);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0,$this->error_str,NULL);
+        }
+        if ($return_code == 412) {
+            $this->error_str = "Missing Content-Type header";
+            return array($return_code,$this->error_str,NULL);
+        }
+        if ($return_code == 422) {
+            $this->error_str = "Derived and computed checksums do not match.";
+            return array($return_code,$this->error_str,NULL);
+        }
+        if ($return_code != 201) {
+            $this->error_str = "Unexpected HTTP return code: $return_code";
+            return array($return_code,$this->error_str,NULL);
+        }
+        return array($return_code,$this->response_reason,$this->_obj_etag);
+    }
+
+    # POST /v1/Account/Container/Object
+    #
+    function update_object(&$obj)
+    {
+        if (!is_object($obj) || get_class($obj) != "CF_Object") {
+            throw new Kohana_Exception(
+                "Method argument is not a valid CF_Object.");
+        }
+
+        # TODO: The is_array check isn't in sync with the error message
+        if (!$obj->manifest && !(is_array($obj->metadata) || is_array($obj->headers))) {
+            $this->error_str = "Metadata and headers arrays are empty.";
+            return 0;
+        }
+
+        $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name);
+
+        $hdrs = $this->_headers($obj);
+        $return_code = $this->_send_request("DEL_POST",$url_path,$hdrs,"POST");
+        switch ($return_code) {
+        case 202:
+            break;
+        case 0:
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            $return_code = 0;
+            break;
+        case 404:
+            $this->error_str = "Account, Container, or Object not found.";
+            break;
+        default:
+            $this->error_str = "Unexpected HTTP return code: $return_code";
+            break;
+        }
+        return $return_code;
+    }
+
+    # HEAD /v1/Account/Container/Object
+    #
+    function head_object(&$obj)
+    {
+        if (!is_object($obj) || get_class($obj) != "CF_Object") {
+            throw new Kohana_Exception(
+                "Method argument is not a valid CF_Object.");
+        }
+
+        $conn_type = "HEAD";
+
+        $url_path = $this->_make_path("STORAGE", $obj->container->name,$obj->name);
+        $return_code = $this->_send_request($conn_type,$url_path);
+
+        if (!$return_code) {
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            return array(0, $this->error_str." ".$this->response_reason,
+                NULL, NULL, NULL, NULL, array(), NULL, array());
+        }
+
+        if ($return_code == 404) {
+            return array($return_code, $this->response_reason,
+                NULL, NULL, NULL, NULL, array(), NULL, array());
+        }
+        if ($return_code == 204 || $return_code == 200) {
+            return array($return_code,$this->response_reason,
+                $this->_obj_etag,
+                $this->_obj_last_modified,
+                $this->_obj_content_type,
+                $this->_obj_content_length,
+                $this->_obj_metadata,
+                $this->_obj_manifest,
+                $this->_obj_headers);
+        }
+        $this->error_str = "Unexpected HTTP return code: $return_code";
+        return array($return_code, $this->error_str." ".$this->response_reason,
+                NULL, NULL, NULL, NULL, array(), NULL, array());
+    }
+
+    # COPY /v1/Account/Container/Object
+    #
+    function copy_object($src_obj_name, $dest_obj_name, $container_name_source, $container_name_target, $metadata=NULL, $headers=NULL)
+    {
+        if (!$src_obj_name) {
+            $this->error_str = "Object name not set.";
+            return 0;
+        }
+
+        if ($container_name_source == "") {
+            $this->error_str = "Container name source not set.";
+            return 0;
+        }
+
+        if ($container_name_source != "0" and !isset($container_name_source)) {
+            $this->error_str = "Container name source not set.";
+            return 0;
+        }
+
+        if ($container_name_target == "") {
+            $this->error_str = "Container name target not set.";
+            return 0;
+        }
+
+        if ($container_name_target != "0" and !isset($container_name_target)) {
+            $this->error_str = "Container name target not set.";
+            return 0;
+        }
+
+        $conn_type = "COPY";
+
+        $url_path = $this->_make_path("STORAGE", $container_name_source, $src_obj_name);
+        $destination = $container_name_target."/".$dest_obj_name;
+
+        $hdrs = self::_process_headers($metadata, $headers);
+        $hdrs[DESTINATION] = $destination;
+
+        $return_code = $this->_send_request($conn_type,$url_path,$hdrs,"COPY");
+        switch ($return_code) {
+        case 201:
+            break;
+        case 0:
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            $return_code = 0;
+            break;
+        case 404:
+            $this->error_str = "Specified container/object did not exist.";
+            break;
+        default:
+            $this->error_str = "Unexpected HTTP return code: $return_code.";
+        }
+        return $return_code;
+    }
+
+    # DELETE /v1/Account/Container/Object
+    #
+    function delete_object($container_name, $object_name)
+    {
+        if ($container_name == "") {
+            $this->error_str = "Container name not set.";
+            return 0;
+        }
+
+        if ($container_name != "0" and !isset($container_name)) {
+            $this->error_str = "Container name not set.";
+            return 0;
+        }
+
+        if (!$object_name) {
+            $this->error_str = "Object name not set.";
+            return 0;
+        }
+
+        $url_path = $this->_make_path("STORAGE", $container_name,$object_name);
+        $return_code = $this->_send_request("DEL_POST",$url_path,NULL,"DELETE");
+        switch ($return_code) {
+        case 204:
+            break;
+        case 0:
+            $this->error_str .= ": Failed to obtain valid HTTP response.";
+            $return_code = 0;
+            break;
+        case 404:
+            $this->error_str = "Specified container did not exist to delete.";
+            break;
+        default:
+            $this->error_str = "Unexpected HTTP return code: $return_code.";
+        }
+        return $return_code;
+    }
+
+    function get_error()
+    {
+        return $this->error_str;
+    }
+
+    function setDebug($bool)
+    {
+        $this->dbug = $bool;
+        foreach ($this->connections as $k => $v) {
+            if (!is_null($v)) {
+                curl_setopt($this->connections[$k], CURLOPT_VERBOSE, $this->dbug);
+            }
+        }
+    }
+
+    function getCDNMUrl()
+    {
+        return $this->cdnm_url;
+    }
+
+    function getStorageUrl()
+    {
+        return $this->storage_url;
+    }
+
+    function getAuthToken()
+    {
+        return $this->auth_token;
+    }
+
+    function setCFAuth($cfs_auth, $servicenet=False)
+    {
+        if ($servicenet) {
+            $this->storage_url = "https://snet-" . substr($cfs_auth->storage_url, 8);
+        } else {
+            $this->storage_url = $cfs_auth->storage_url;
+        }
+        $this->auth_token = $cfs_auth->auth_token;
+        $this->cdnm_url = $cfs_auth->cdnm_url;
+    }
+
+    function setReadProgressFunc($func_name)
+    {
+        $this->_user_read_progress_callback_func = $func_name;
+    }
+
+    function setWriteProgressFunc($func_name)
+    {
+        $this->_user_write_progress_callback_func = $func_name;
+    }
+
+    private function _header_cb($ch, $header)
+    {
+        $header_len = strlen($header);
+
+        if (preg_match("/^(HTTP\/1\.[01]) (\d{3}) (.*)/", $header, $matches)) {
+            $this->response_status = $matches[2];
+            $this->response_reason = $matches[3];
+            return $header_len;
+        }
+
+        if (strpos($header, ":") === False)
+            return $header_len;
+        list($name, $value) = explode(":", $header, 2);
+        $value = trim($value);
+
+        switch (strtolower($name)) {
+        case strtolower(CDN_ENABLED):
+            $this->_cdn_enabled = strtolower($value) == "true";
+            break;
+        case strtolower(CDN_URI):
+            $this->_cdn_uri = $value;
+            break;
+        case strtolower(CDN_SSL_URI):
+            $this->_cdn_ssl_uri = $value;
+            break;
+        case strtolower(CDN_STREAMING_URI):
+            $this->_cdn_streaming_uri = $value;
+            break;
+        case strtolower(CDN_TTL):
+            $this->_cdn_ttl = $value;
+            break;
+        case strtolower(MANIFEST_HEADER):
+            $this->_obj_manifest = $value;
+            break;
+        case strtolower(CDN_LOG_RETENTION):
+            $this->_cdn_log_retention = strtolower($value) == "true";
+            break;
+        case strtolower(CDN_ACL_USER_AGENT):
+            $this->_cdn_acl_user_agent = $value;
+            break;
+        case strtolower(CDN_ACL_REFERRER):
+            $this->_cdn_acl_referrer = $value;
+            break;
+        case strtolower(ACCOUNT_CONTAINER_COUNT):
+            $this->_account_container_count = (float)$value+0;
+            break;
+        case strtolower(ACCOUNT_BYTES_USED):
+            $this->_account_bytes_used = (float)$value+0;
+            break;
+        case strtolower(CONTAINER_OBJ_COUNT):
+            $this->_container_object_count = (float)$value+0;
+            break;
+        case strtolower(CONTAINER_BYTES_USED):
+            $this->_container_bytes_used = (float)$value+0;
+            break;
+        case strtolower(ETAG_HEADER):
+            $this->_obj_etag = $value;
+            break;
+        case strtolower(LAST_MODIFIED_HEADER):
+            $this->_obj_last_modified = $value;
+            break;
+        case strtolower(CONTENT_TYPE_HEADER):
+            $this->_obj_content_type = $value;
+            break;
+        case strtolower(CONTENT_LENGTH_HEADER):
+            $this->_obj_content_length = (float)$value+0;
+            break;
+        case strtolower(ORIGIN_HEADER):
+            $this->_obj_headers[ORIGIN_HEADER] = $value;
+            break;
+        default:
+            if (strncasecmp($name, METADATA_HEADER_PREFIX, strlen(METADATA_HEADER_PREFIX)) == 0) {
+                $name = substr($name, strlen(METADATA_HEADER_PREFIX));
+                $this->_obj_metadata[$name] = $value;
+            }
+            elseif ((strncasecmp($name, CONTENT_HEADER_PREFIX, strlen(CONTENT_HEADER_PREFIX)) == 0) ||
+                    (strncasecmp($name, ACCESS_CONTROL_HEADER_PREFIX, strlen(ACCESS_CONTROL_HEADER_PREFIX)) == 0)) {
+                $this->_obj_headers[$name] = $value;
+            }
+        }
+        return $header_len;
+    }
+
+    private function _read_cb($ch, $fd, $length)
+    {
+        $data = fread($fd, $length);
+        $len = strlen($data);
+        if (isset($this->_user_write_progress_callback_func)) {
+            call_user_func($this->_user_write_progress_callback_func, $len);
+        }
+        return $data;
+    }
+
+    private function _write_cb($ch, $data)
+    {
+        $dlen = strlen($data);
+        switch ($this->_write_callback_type) {
+        case "TEXT_LIST":
+	     $this->_return_list = $this->_return_list . $data;
+	     //= explode("\n",$data); # keep tab,space
+	     //his->_text_list[] = rtrim($data,"\n\r\x0B"); # keep tab,space
+            break;
+        case "OBJECT_STREAM":
+            fwrite($this->_obj_write_resource, $data, $dlen);
+            break;
+        case "OBJECT_STRING":
+            $this->_obj_write_string .= $data;
+            break;
+        }
+        if (isset($this->_user_read_progress_callback_func)) {
+            call_user_func($this->_user_read_progress_callback_func, $dlen);
+        }
+        return $dlen;
+    }
+
+    private function _auth_hdr_cb($ch, $header)
+    {
+        preg_match("/^HTTP\/1\.[01] (\d{3}) (.*)/", $header, $matches);
+        if (isset($matches[1])) {
+            $this->response_status = $matches[1];
+        }
+        if (isset($matches[2])) {
+            $this->response_reason = $matches[2];
+        }
+        if (stripos($header, STORAGE_URL) === 0) {
+            $this->storage_url = trim(substr($header, strlen(STORAGE_URL)+1));
+        }
+        if (stripos($header, CDNM_URL) === 0) {
+            $this->cdnm_url = trim(substr($header, strlen(CDNM_URL)+1));
+        }
+        if (stripos($header, AUTH_TOKEN) === 0) {
+            $this->auth_token = trim(substr($header, strlen(AUTH_TOKEN)+1));
+        }
+        if (stripos($header, AUTH_TOKEN_LEGACY) === 0) {
+            $this->auth_token = trim(substr($header,strlen(AUTH_TOKEN_LEGACY)+1));
+        }
+        return strlen($header);
+    }
+
+    private function _make_headers($hdrs=NULL)
+    {
+        $new_headers = array();
+        $has_stoken = False;
+        $has_uagent = False;
+        if (is_array($hdrs)) {
+            foreach ($hdrs as $h => $v) {
+                if (is_int($h)) {
+                    list($h, $v) = explode(":", $v, 2);
+                }
+
+                if (strncasecmp($h, AUTH_TOKEN, strlen(AUTH_TOKEN)) === 0) {
+                    $has_stoken = True;
+                }
+                if (strncasecmp($h, USER_AGENT_HEADER, strlen(USER_AGENT_HEADER)) === 0) {
+                    $has_uagent = True;
+                }
+                $new_headers[] = $h . ": " . trim($v);
+            }
+        }
+        if (!$has_stoken) {
+            $new_headers[] = AUTH_TOKEN . ": " . $this->auth_token;
+        }
+        if (!$has_uagent) {
+            $new_headers[] = USER_AGENT_HEADER . ": " . USER_AGENT;
+        }
+        return $new_headers;
+    }
+
+    private function _init($conn_type, $force_new=False)
+    {
+        if (!array_key_exists($conn_type, $this->connections)) {
+            $this->error_str = "Invalid CURL_XXX connection type";
+            return False;
+        }
+
+        if (is_null($this->connections[$conn_type]) || $force_new) {
+            $ch = curl_init();
+        } else {
+            return;
+        }
+
+        if ($this->dbug) { curl_setopt($ch, CURLOPT_VERBOSE, 1); }
+
+        if (!is_null($this->cabundle_path)) {
+            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, True);
+            curl_setopt($ch, CURLOPT_CAINFO, $this->cabundle_path);
+        }
+	curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, True);
+        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
+        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
+        curl_setopt($ch, CURLOPT_MAXREDIRS, 4);
+        curl_setopt($ch, CURLOPT_HEADER, 0);
+        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array(&$this, '_header_cb'));
+
+        if ($conn_type == "GET_CALL") {
+            curl_setopt($ch, CURLOPT_WRITEFUNCTION, array(&$this, '_write_cb'));
+        }
+
+        if ($conn_type == "PUT_OBJ") {
+            curl_setopt($ch, CURLOPT_PUT, 1);
+            curl_setopt($ch, CURLOPT_READFUNCTION, array(&$this, '_read_cb'));
+	    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+        }
+        if ($conn_type == "HEAD") {
+            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "HEAD");
+            curl_setopt($ch, CURLOPT_NOBODY, 1);
+        }
+        if ($conn_type == "PUT_CONT") {
+            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
+            curl_setopt($ch, CURLOPT_INFILESIZE, 0);
+	    curl_setopt($ch, CURLOPT_NOBODY, 1);
+        }
+        if ($conn_type == "DEL_POST") {
+        	curl_setopt($ch, CURLOPT_NOBODY, 1);
+	}
+        if ($conn_type == "COPY") {
+            curl_setopt($ch, CURLOPT_NOBODY, 1);
+        }
+        $this->connections[$conn_type] = $ch;
+        return;
+    }
+
+    private function _reset_callback_vars()
+    {
+        $this->_text_list = array();
+	$this->_return_list = NULL;
+        $this->_account_container_count = 0;
+        $this->_account_bytes_used = 0;
+        $this->_container_object_count = 0;
+        $this->_container_bytes_used = 0;
+        $this->_obj_etag = NULL;
+        $this->_obj_last_modified = NULL;
+        $this->_obj_content_type = NULL;
+        $this->_obj_content_length = NULL;
+        $this->_obj_metadata = array();
+        $this->_obj_manifest = NULL;
+        $this->_obj_headers = NULL;
+        $this->_obj_write_string = "";
+        $this->_cdn_streaming_uri = NULL;
+        $this->_cdn_enabled = NULL;
+        $this->_cdn_ssl_uri = NULL;
+        $this->_cdn_uri = NULL;
+        $this->_cdn_ttl = NULL;
+        $this->response_status = 0;
+        $this->response_reason = "";
+    }
+
+    private function _make_path($t="STORAGE",$c=NULL,$o=NULL)
+    {
+        $path = array();
+        switch ($t) {
+        case "STORAGE":
+            $path[] = $this->storage_url; break;
+        case "CDN":
+            $path[] = $this->cdnm_url; break;
+        }
+        if ($c == "0")
+            $path[] = rawurlencode($c);
+
+        if ($c) {
+            $path[] = rawurlencode($c);
+        }
+        if ($o) {
+            # mimic Python''s urllib.quote() feature of a "safe" '/' character
+            #
+            $path[] = str_replace("%2F","/",rawurlencode($o));
+        }
+        return implode("/",$path);
+    }
+
+    private function _headers(&$obj)
+    {
+        $hdrs = self::_process_headers($obj->metadata, $obj->headers);
+        if ($obj->manifest)
+            $hdrs[MANIFEST_HEADER] = $obj->manifest;
+
+        return $hdrs;
+    }
+
+    private function _process_headers($metadata=null, $headers=null)
+    {
+        $rules = array(
+            array(
+                'prefix' => METADATA_HEADER_PREFIX,
+            ),
+            array(
+                'prefix' => '',
+                'filter' => array( # key order is important, first match decides
+                    CONTENT_TYPE_HEADER          => false,
+                    CONTENT_LENGTH_HEADER        => false,
+                    CONTENT_HEADER_PREFIX        => true,
+                    ACCESS_CONTROL_HEADER_PREFIX => true,
+                    ORIGIN_HEADER                => true,
+                ),
+            ),
+        );
+
+        $hdrs = array();
+        $argc = func_num_args();
+        $argv = func_get_args();
+        for ($argi = 0; $argi < $argc; $argi++) {
+            if(!is_array($argv[$argi])) continue;
+
+            $rule = $rules[$argi];
+            foreach ($argv[$argi] as $k => $v) {
+                $k = trim($k);
+                $v = trim($v);
+                if (strpos($k, ":") !== False) throw new Kohana_Exception(
+                    "Header names cannot contain a ':' character.");
+
+                if (array_key_exists('filter', $rule)) {
+                    $result = null;
+                    foreach ($rule['filter'] as $p => $f) {
+                        if (strncasecmp($k, $p, strlen($p)) == 0) {
+                            $result = $f;
+                            break;
+                        }
+                    }
+                    if (!$result) throw new Kohana_Exception(sprintf(
+                        "Header name %s is not allowed", $k));
+                }
+
+                $k = $rule['prefix'] . $k;
+                if (strlen($k) > MAX_HEADER_NAME_LEN || strlen($v) > MAX_HEADER_VALUE_LEN)
+                    throw new Kohana_Exception(sprintf(
+                        "Header %s exceeds maximum length: %d/%d",
+                            $k, strlen($k), strlen($v)));
+
+                $hdrs[$k] = $v;
+            }
+        }
+
+        return $hdrs;
+    }
+
+    private function _send_request($conn_type, $url_path, $hdrs=NULL, $method="GET", $force_new=False)
+    {
+        $this->_init($conn_type, $force_new);
+        $this->_reset_callback_vars();
+        $headers = $this->_make_headers($hdrs);
+
+        if (gettype($this->connections[$conn_type]) == "unknown type")
+            throw new Kohana_Exception (
+                "Connection is not open."
+                );
+
+        switch ($method) {
+        case "COPY":
+            curl_setopt($this->connections[$conn_type],
+                CURLOPT_CUSTOMREQUEST, "COPY");
+            break;
+        case "DELETE":
+            curl_setopt($this->connections[$conn_type],
+                CURLOPT_CUSTOMREQUEST, "DELETE");
+            break;
+        case "POST":
+            curl_setopt($this->connections[$conn_type],
+                CURLOPT_CUSTOMREQUEST, "POST");
+        default:
+            break;
+        }
+
+        curl_setopt($this->connections[$conn_type],
+                    CURLOPT_HTTPHEADER, $headers);
+
+        curl_setopt($this->connections[$conn_type],
+            CURLOPT_URL, $url_path);
+
+        if (!curl_exec($this->connections[$conn_type]) && curl_errno($this->connections[$conn_type]) !== 0) {
+            $this->error_str = "(curl error: "
+                . curl_errno($this->connections[$conn_type]) . ") ";
+            $this->error_str .= curl_error($this->connections[$conn_type]);
+            return False;
+        }
+        return curl_getinfo($this->connections[$conn_type], CURLINFO_HTTP_CODE);
+    }
+
+    function close()
+    {
+        foreach ($this->connections as $cnx) {
+            if (isset($cnx)) {
+                curl_close($cnx);
+                $this->connections[$cnx] = NULL;
+            }
+        }
+    }
+    private function create_array()
+    {
+	$this->_text_list = explode("\n",rtrim($this->_return_list,"\n\x0B"));
+	return True;
+    }
+
+}
+
+?>
\ No newline at end of file
diff --git a/application/libraries/cloudfiles/CF_Object.php b/application/libraries/cloudfiles/CF_Object.php
new file mode 100644
index 0000000..326b50e
--- /dev/null
+++ b/application/libraries/cloudfiles/CF_Object.php
@@ -0,0 +1,711 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Object operations
+ *
+ * An Object is analogous to a file on a conventional filesystem. You can
+ * read data from, or write data to your Objects. You can also associate
+ * arbitrary metadata with them.
+ *
+ * @package php-cloudfiles
+ */
+class CF_Object
+{
+    public $container;
+    public $name;
+    public $last_modified;
+    public $content_type;
+    public $content_length;
+    public $metadata;
+    public $headers;
+    public $manifest;
+    private $etag;
+
+    /**
+     * Class constructor
+     *
+     * @param obj $container CF_Container instance
+     * @param string $name name of Object
+     * @param boolean $force_exists if set, throw an error if Object doesn't exist
+     */
+    function __construct(&$container, $name, $force_exists=False, $dohead=True)
+    {
+        if (isset($name[0]) && $name[0] == "/") {
+            $r = "Object name '".$name;
+            $r .= "' cannot contain begin with a '/' character.";
+            throw new Kohana_Exception($r);
+        }
+        if (strlen($name) > MAX_OBJECT_NAME_LEN) {
+            throw new Kohana_Exception("Object name exceeds "
+                . "maximum allowed length.");
+        }
+        $this->container = $container;
+        $this->name = $name;
+        $this->etag = NULL;
+        $this->_etag_override = False;
+        $this->last_modified = NULL;
+        $this->content_type = NULL;
+        $this->content_length = 0;
+        $this->metadata = array();
+        $this->headers = array();
+        $this->manifest = NULL;
+        if ($dohead) {
+            if (!$this->_initialize() && $force_exists) {
+                throw new Kohana_Exception("No such object '".$name."'");
+            }
+        }
+    }
+
+    /**
+     * String representation of Object
+     *
+     * Pretty print the Object's location and name
+     *
+     * @return string Object information
+     */
+    function __toString()
+    {
+        return $this->container->name . "/" . $this->name;
+    }
+
+    /**
+     * Internal check to get the proper mimetype.
+     *
+     * This function would go over the available PHP methods to get
+     * the MIME type.
+     *
+     * By default it will try to use the PHP fileinfo library which is
+     * available from PHP 5.3 or as an PECL extension
+     * (http://pecl.php.net/package/Fileinfo).
+     *
+     * It will get the magic file by default from the system wide file
+     * which is usually available in /usr/share/magic on Unix or try
+     * to use the file specified in the source directory of the API
+     * (share directory).
+     *
+     * if fileinfo is not available it will try to use the internal
+     * mime_content_type function.
+     *
+     * @param string $handle name of file or buffer to guess the type from
+     * @return boolean <kbd>True</kbd> if successful
+     * @throws BadContentTypeException
+     */
+    function _guess_content_type($handle) {
+        if ($this->content_type)
+            return;
+
+        if (function_exists("finfo_open")) {
+            $local_magic = dirname(__FILE__) . "/share/magic";
+            $finfo = @finfo_open(FILEINFO_MIME, $local_magic);
+
+            if (!$finfo)
+                $finfo = @finfo_open(FILEINFO_MIME);
+
+            if ($finfo) {
+
+                if (is_file((string)$handle))
+                    $ct = @finfo_file($finfo, $handle);
+                else
+                    $ct = @finfo_buffer($finfo, $handle);
+
+                /* PHP 5.3 fileinfo display extra information like
+                   charset so we remove everything after the ; since
+                   we are not into that stuff */
+                if ($ct) {
+                    $extra_content_type_info = strpos($ct, "; ");
+                    if ($extra_content_type_info)
+                        $ct = substr($ct, 0, $extra_content_type_info);
+                }
+
+                if ($ct && $ct != 'application/octet-stream')
+                    $this->content_type = $ct;
+
+                @finfo_close($finfo);
+            }
+        }
+
+        if (!$this->content_type && (string)is_file($handle) && function_exists("mime_content_type")) {
+            $this->content_type = @mime_content_type($handle);
+        }
+
+        if (!$this->content_type) {
+            throw new Kohana_Exception("Required Content-Type not set");
+        }
+        return True;
+    }
+
+    /**
+     * String representation of the Object's public URI
+     *
+     * A string representing the Object's public URI assuming that it's
+     * parent Container is CDN-enabled.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * # Print out the Object's CDN URI (if it has one) in an HTML img-tag
+     * #
+     * print "<img src='$pic->public_uri()' />\n";
+     * </code>
+     *
+     * @return string Object's public URI or NULL
+     */
+    function public_uri()
+    {
+        if ($this->container->cdn_enabled) {
+            return $this->container->cdn_uri . "/" . $this->name;
+        }
+        return NULL;
+    }
+
+       /**
+     * String representation of the Object's public SSL URI
+     *
+     * A string representing the Object's public SSL URI assuming that it's
+     * parent Container is CDN-enabled.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * # Print out the Object's CDN SSL URI (if it has one) in an HTML img-tag
+     * #
+     * print "<img src='$pic->public_ssl_uri()' />\n";
+     * </code>
+     *
+     * @return string Object's public SSL URI or NULL
+     */
+    function public_ssl_uri()
+    {
+        if ($this->container->cdn_enabled) {
+            return $this->container->cdn_ssl_uri . "/" . $this->name;
+        }
+        return NULL;
+    }
+    /**
+     * String representation of the Object's public Streaming URI
+     *
+     * A string representing the Object's public Streaming URI assuming that it's
+     * parent Container is CDN-enabled.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * # Print out the Object's CDN Streaming URI (if it has one) in an HTML img-tag
+     * #
+     * print "<img src='$pic->public_streaming_uri()' />\n";
+     * </code>
+     *
+     * @return string Object's public Streaming URI or NULL
+     */
+    function public_streaming_uri()
+    {
+        if ($this->container->cdn_enabled) {
+            return $this->container->cdn_streaming_uri . "/" . $this->name;
+        }
+        return NULL;
+    }
+
+    /**
+     * Read the remote Object's data
+     *
+     * Returns the Object's data.  This is useful for smaller Objects such
+     * as images or office documents.  Object's with larger content should use
+     * the stream() method below.
+     *
+     * Pass in $hdrs array to set specific custom HTTP headers such as
+     * If-Match, If-None-Match, If-Modified-Since, Range, etc.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * $my_docs = $conn->get_container("documents");
+     * $doc = $my_docs->get_object("README");
+     * $data = $doc->read(); # read image content into a string variable
+     * print $data;
+     *
+     * # Or see stream() below for a different example.
+     * #
+     * </code>
+     *
+     * @param array $hdrs user-defined headers (Range, If-Match, etc.)
+     * @return string Object's data
+     * @throws InvalidResponseException unexpected response
+     */
+    function read($hdrs=array())
+    {
+        list($status, $reason, $data) =
+            $this->container->cfs_http->get_object_to_string($this, $hdrs);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->read($hdrs);
+        #}
+        if (($status < 200) || ($status > 299
+                && $status != 412 && $status != 304)) {
+            throw new Kohana_Exception("Invalid response (".$status."): "
+                . $this->container->cfs_http->get_error());
+        }
+        return $data;
+    }
+
+    /**
+     * Streaming read of Object's data
+     *
+     * Given an open PHP resource (see PHP's fopen() method), fetch the Object's
+     * data and write it to the open resource handle.  This is useful for
+     * streaming an Object's content to the browser (videos, images) or for
+     * fetching content to a local file.
+     *
+     * Pass in $hdrs array to set specific custom HTTP headers such as
+     * If-Match, If-None-Match, If-Modified-Since, Range, etc.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * # Assuming this is a web script to display the README to the
+     * # user's browser:
+     * #
+     * <?php
+     * // grab README from storage system
+     * //
+     * $my_docs = $conn->get_container("documents");
+     * $doc = $my_docs->get_object("README");
+     *
+     * // Hand it back to user's browser with appropriate content-type
+     * //
+     * header("Content-Type: " . $doc->content_type);
+     * $output = fopen("php://output", "w");
+     * $doc->stream($output); # stream object content to PHP's output buffer
+     * fclose($output);
+     * ?>
+     *
+     * # See read() above for a more simple example.
+     * #
+     * </code>
+     *
+     * @param resource $fp open resource for writing data to
+     * @param array $hdrs user-defined headers (Range, If-Match, etc.)
+     * @return string Object's data
+     * @throws InvalidResponseException unexpected response
+     */
+    function stream(&$fp, $hdrs=array())
+    {
+        list($status, $reason) =
+                $this->container->cfs_http->get_object_to_stream($this,$fp,$hdrs);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->stream($fp, $hdrs);
+        #}
+        if (($status < 200) || ($status > 299
+                && $status != 412 && $status != 304)) {
+            throw new Kohana_Exception("Invalid response (".$status."): "
+                .$reason);
+        }
+        return True;
+    }
+
+    /**
+     * Store new Object metadata
+     *
+     * Write's an Object's metadata to the remote Object.  This will overwrite
+     * an prior Object metadata.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * $my_docs = $conn->get_container("documents");
+     * $doc = $my_docs->get_object("README");
+     *
+     * # Define new metadata for the object
+     * #
+     * $doc->metadata = array(
+     *     "Author" => "EJ",
+     *     "Subject" => "How to use the PHP tests",
+     *     "Version" => "1.2.2"
+     * );
+     *
+     * # Define additional headers for the object
+     * #
+     * $doc->headers = array(
+     *     "Content-Disposition" => "attachment",
+     * );
+     *
+     * # Push the new metadata up to the storage system
+     * #
+     * $doc->sync_metadata();
+     * </code>
+     *
+     * @return boolean <kbd>True</kbd> if successful, <kbd>False</kbd> otherwise
+     * @throws InvalidResponseException unexpected response
+     */
+    function sync_metadata()
+    {
+        if (!empty($this->metadata) || !empty($this->headers) || $this->manifest) {
+            $status = $this->container->cfs_http->update_object($this);
+            #if ($status == 401 && $this->_re_auth()) {
+            #    return $this->sync_metadata();
+            #}
+            if ($status != 202) {
+                throw new Kohana_Exception("Invalid response ("
+                    .$status."): ".$this->container->cfs_http->get_error());
+            }
+            return True;
+        }
+        return False;
+    }
+    /**
+     * Store new Object manifest
+     *
+     * Write's an Object's manifest to the remote Object.  This will overwrite
+     * an prior Object manifest.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * $my_docs = $conn->get_container("documents");
+     * $doc = $my_docs->get_object("README");
+     *
+     * # Define new manifest for the object
+     * #
+     * $doc->manifest = "container/prefix";
+     *
+     * # Push the new manifest up to the storage system
+     * #
+     * $doc->sync_manifest();
+     * </code>
+     *
+     * @return boolean <kbd>True</kbd> if successful, <kbd>False</kbd> otherwise
+     * @throws InvalidResponseException unexpected response
+     */
+
+    function sync_manifest()
+    {
+        return $this->sync_metadata();
+    }
+    /**
+     * Upload Object's data to Cloud Files
+     *
+     * Write data to the remote Object.  The $data argument can either be a
+     * PHP resource open for reading (see PHP's fopen() method) or an in-memory
+     * variable.  If passing in a PHP resource, you must also include the $bytes
+     * parameter.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * $my_docs = $conn->get_container("documents");
+     * $doc = $my_docs->get_object("README");
+     *
+     * # Upload placeholder text in my README
+     * #
+     * $doc->write("This is just placeholder text for now...");
+     * </code>
+     *
+     * @param string|resource $data string or open resource
+     * @param float $bytes amount of data to upload (required for resources)
+     * @param boolean $verify generate, send, and compare MD5 checksums
+     * @return boolean <kbd>True</kbd> when data uploaded successfully
+     * @throws SyntaxException missing required parameters
+     * @throws BadContentTypeException if no Content-Type was/could be set
+     * @throws MisMatchedChecksumException $verify is set and checksums unequal
+     * @throws InvalidResponseException unexpected response
+     */
+    function write($data=NULL, $bytes=0, $verify=True)
+    {
+        if (!$data && !is_string($data)) {
+            throw new Kohana_Exception("Missing data source.");
+        }
+        if ($bytes > MAX_OBJECT_SIZE) {
+            throw new Kohana_Exception("Bytes exceeds maximum object size.");
+        }
+        if ($verify) {
+            if (!$this->_etag_override) {
+                $this->etag = $this->compute_md5sum($data);
+            }
+        } else {
+            $this->etag = NULL;
+        }
+
+        $close_fh = False;
+        if (!is_resource($data)) {
+            # A hack to treat string data as a file handle.  php://memory feels
+            # like a better option, but it seems to break on Windows so use
+            # a temporary file instead.
+            #
+            $fp = fopen("php://temp", "wb+");
+            #$fp = fopen("php://memory", "wb+");
+            fwrite($fp, $data, strlen($data));
+            rewind($fp);
+            $close_fh = True;
+            $this->content_length = (float) strlen($data);
+            if ($this->content_length > MAX_OBJECT_SIZE) {
+                throw new Kohana_Exception("Data exceeds maximum object size");
+            }
+            $ct_data = substr($data, 0, 64);
+        } else {
+            $this->content_length = $bytes;
+            $fp = $data;
+            $ct_data = fread($data, 64);
+            rewind($data);
+        }
+
+        $this->_guess_content_type($ct_data);
+
+        list($status, $reason, $etag) =
+                $this->container->cfs_http->put_object($this, $fp);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->write($data, $bytes, $verify);
+        #}
+        if ($status == 412) {
+            if ($close_fh) { fclose($fp); }
+            throw new Kohana_Exception("Missing Content-Type header");
+        }
+        if ($status == 422) {
+            if ($close_fh) { fclose($fp); }
+            throw new Kohana_Exception(
+                "Supplied and computed checksums do not match.");
+        }
+        if ($status != 201) {
+            if ($close_fh) { fclose($fp); }
+            throw new Kohana_Exception("Invalid response (".$status."): "
+                . $this->container->cfs_http->get_error());
+        }
+        if (!$verify) {
+            $this->etag = $etag;
+        }
+        if ($close_fh) { fclose($fp); }
+        return True;
+    }
+
+    /**
+     * Upload Object data from local filename
+     *
+     * This is a convenience function to upload the data from a local file.  A
+     * True value for $verify will cause the method to compute the Object's MD5
+     * checksum prior to uploading.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * $my_docs = $conn->get_container("documents");
+     * $doc = $my_docs->get_object("README");
+     *
+     * # Upload my local README's content
+     * #
+     * $doc->load_from_filename("/home/ej/cloudfiles/readme");
+     * </code>
+     *
+     * @param string $filename full path to local file
+     * @param boolean $verify enable local/remote MD5 checksum validation
+     * @return boolean <kbd>True</kbd> if data uploaded successfully
+     * @throws SyntaxException missing required parameters
+     * @throws BadContentTypeException if no Content-Type was/could be set
+     * @throws MisMatchedChecksumException $verify is set and checksums unequal
+     * @throws InvalidResponseException unexpected response
+     * @throws IOException error opening file
+     */
+    function load_from_filename($filename, $verify=True)
+    {
+        $fp = @fopen($filename, "r");
+        if (!$fp) {
+            throw new Kohana_Exception("Could not open file for reading: ".$filename);
+        }
+
+        clearstatcache();
+
+        $size = (float) sprintf("%u", filesize($filename));
+        if ($size > MAX_OBJECT_SIZE) {
+            throw new Kohana_Exception("File size exceeds maximum object size.");
+        }
+
+        $this->_guess_content_type($filename);
+
+        $this->write($fp, $size, $verify);
+        fclose($fp);
+        return True;
+    }
+
+    /**
+     * Save Object's data to local filename
+     *
+     * Given a local filename, the Object's data will be written to the newly
+     * created file.
+     *
+     * Example:
+     * <code>
+     * # ... authentication/connection/container code excluded
+     * # ... see previous examples
+     *
+     * # Whoops!  I deleted my local README, let me download/save it
+     * #
+     * $my_docs = $conn->get_container("documents");
+     * $doc = $my_docs->get_object("README");
+     *
+     * $doc->save_to_filename("/home/ej/cloudfiles/readme.restored");
+     * </code>
+     *
+     * @param string $filename name of local file to write data to
+     * @return boolean <kbd>True</kbd> if successful
+     * @throws IOException error opening file
+     * @throws InvalidResponseException unexpected response
+     */
+    function save_to_filename($filename)
+    {
+        $fp = @fopen($filename, "wb");
+        if (!$fp) {
+            throw new Kohana_Exception("Could not open file for writing: ".$filename);
+        }
+        $result = $this->stream($fp);
+        fclose($fp);
+        return $result;
+    }
+       /**
+     * Purge this Object from CDN Cache.
+     * Example:
+     * <code>
+     * # ... authentication code excluded (see previous examples) ...
+     * #
+     * $conn = new CF_Authentication($auth);
+     * $container = $conn->get_container("cdn_enabled");
+     * $obj = $container->get_object("object");
+     * $obj->purge_from_cdn("user at domain.com");
+     * # or
+     * $obj->purge_from_cdn();
+     * # or
+     * $obj->purge_from_cdn("user1 at domain.com,user2 at domain.com");
+     * @returns boolean True if successful
+     * @throws CDNNotEnabledException if CDN Is not enabled on this connection
+     * @throws InvalidResponseException if the response expected is not returned
+     */
+    function purge_from_cdn($email=null)
+    {
+        if (!$this->container->cfs_http->getCDNMUrl())
+        {
+            throw new Kohana_Exception(
+                "Authentication response did not indicate CDN availability");
+        }
+        $status = $this->container->cfs_http->purge_from_cdn($this->container->name . "/" . $this->name, $email);
+        if ($status < 199 or $status > 299) {
+            throw new Kohana_Exception(
+                "Invalid response (".$status."): ".$this->container->cfs_http->get_error());
+        }
+        return True;
+    }
+
+    /**
+     * Set Object's MD5 checksum
+     *
+     * Manually set the Object's ETag.  Including the ETag is mandatory for
+     * Cloud Files to perform end-to-end verification.  Omitting the ETag forces
+     * the user to handle any data integrity checks.
+     *
+     * @param string $etag MD5 checksum hexidecimal string
+     */
+    function set_etag($etag)
+    {
+        $this->etag = $etag;
+        $this->_etag_override = True;
+    }
+
+    /**
+     * Object's MD5 checksum
+     *
+     * Accessor method for reading Object's private ETag attribute.
+     *
+     * @return string MD5 checksum hexidecimal string
+     */
+    function getETag()
+    {
+        return $this->etag;
+    }
+
+    /**
+     * Compute the MD5 checksum
+     *
+     * Calculate the MD5 checksum on either a PHP resource or data.  The argument
+     * may either be a local filename, open resource for reading, or a string.
+     *
+     * <b>WARNING:</b> if you are uploading a big file over a stream
+     * it could get very slow to compute the md5 you probably want to
+     * set the $verify parameter to False in the write() method and
+     * compute yourself the md5 before if you have it.
+     *
+     * @param filename|obj|string $data filename, open resource, or string
+     * @return string MD5 checksum hexidecimal string
+     */
+    function compute_md5sum(&$data)
+    {
+
+        if (function_exists("hash_init") && is_resource($data)) {
+            $ctx = hash_init('md5');
+            while (!feof($data)) {
+                $buffer = fgets($data, 65536);
+                hash_update($ctx, $buffer);
+            }
+            $md5 = hash_final($ctx, false);
+            rewind($data);
+        } elseif ((string)is_file($data)) {
+            $md5 = md5_file($data);
+        } else {
+            $md5 = md5($data);
+        }
+        return $md5;
+    }
+
+    /**
+     * PRIVATE: fetch information about the remote Object if it exists
+     */
+    private function _initialize()
+    {
+        list($status, $reason, $etag, $last_modified, $content_type,
+            $content_length, $metadata, $manifest, $headers) =
+                $this->container->cfs_http->head_object($this);
+        #if ($status == 401 && $this->_re_auth()) {
+        #    return $this->_initialize();
+        #}
+        if ($status == 404) {
+            return False;
+        }
+        if ($status < 200 || $status > 299) {
+            throw new Kohana_Exception("Invalid response (".$status."): "
+                . $this->container->cfs_http->get_error());
+        }
+        $this->etag = $etag;
+        $this->last_modified = $last_modified;
+        $this->content_type = $content_type;
+        $this->content_length = $content_length;
+        $this->metadata = $metadata;
+        $this->headers = $headers;
+        $this->manifest = $manifest;
+        return True;
+    }
+
+    #private function _re_auth()
+    #{
+    #    $new_auth = new CF_Authentication(
+    #        $this->cfs_auth->username,
+    #        $this->cfs_auth->api_key,
+    #        $this->cfs_auth->auth_host,
+    #        $this->cfs_auth->account);
+    #    $new_auth->authenticate();
+    #    $this->container->cfs_auth = $new_auth;
+    #    $this->container->cfs_http->setCFAuth($this->cfs_auth);
+    #    return True;
+    #}
+}
+
+?>
\ No newline at end of file
diff --git a/application/libraries/drivers/Database.php b/application/libraries/drivers/Database.php
new file mode 100755
index 0000000..787d51c
--- /dev/null
+++ b/application/libraries/drivers/Database.php
@@ -0,0 +1,655 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Database API driver
+ * 
+ * Overrides core database driver to backport some KO3 features
+ * - modified escape()
+ * 
+ * @package	   Ushahidi
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+abstract class Database_Driver {
+
+	static $query_cache;
+
+	/**
+	 * Connect to our database.
+	 * Returns FALSE on failure or a MySQL resource.
+	 *
+	 * @return mixed
+	 */
+	abstract public function connect();
+
+	/**
+	 * Perform a query based on a manually written query.
+	 *
+	 * @param  string  SQL query to execute
+	 * @return Database_Result
+	 */
+	abstract public function query($sql);
+
+	/**
+	 * Builds a DELETE query.
+	 *
+	 * @param   string  table name
+	 * @param   array   where clause
+	 * @return  string
+	 */
+	public function delete($table, $where)
+	{
+		return 'DELETE FROM '.$this->escape_table($table).' WHERE '.implode(' ', $where);
+	}
+
+	/**
+	 * Builds an UPDATE query.
+	 *
+	 * @param   string  table name
+	 * @param   array   key => value pairs
+	 * @param   array   where clause
+	 * @return  string
+	 */
+	public function update($table, $values, $where)
+	{
+		foreach ($values as $key => $val)
+		{
+			$valstr[] = $this->escape_column($key).' = '.$val;
+		}
+		return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ',$where);
+	}
+
+	/**
+	 * Set the charset using 'SET NAMES <charset>'.
+	 *
+	 * @param  string  character set to use
+	 */
+	public function set_charset($charset)
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 * Wrap the tablename in backticks, has support for: table.field syntax.
+	 *
+	 * @param   string  table name
+	 * @return  string
+	 */
+	abstract public function escape_table($table);
+
+	/**
+	 * Escape a column/field name, has support for special commands.
+	 *
+	 * @param   string  column name
+	 * @return  string
+	 */
+	abstract public function escape_column($column);
+
+	/**
+	 * Builds a WHERE portion of a query.
+	 *
+	 * @param   mixed    key
+	 * @param   string   value
+	 * @param   string   type
+	 * @param   int      number of where clauses
+	 * @param   boolean  escape the value
+	 * @return  string
+	 */
+	public function where($key, $value, $type, $num_wheres, $quote)
+	{
+		$prefix = ($num_wheres == 0) ? '' : $type;
+
+		if ($quote === -1)
+		{
+			$value = '';
+		}
+		else
+		{
+			if ($value === NULL)
+			{
+				if ( ! $this->has_operator($key))
+				{
+					$key .= ' IS';
+				}
+
+				$value = ' NULL';
+			}
+			elseif (is_bool($value))
+			{
+				if ( ! $this->has_operator($key))
+				{
+					$key .= ' =';
+				}
+
+				$value = ($value == TRUE) ? ' 1' : ' 0';
+			}
+			else
+			{
+				if ( ! $this->has_operator($key))
+				{
+					$key = $this->escape_column($key).' =';
+				}
+				else
+				{
+					preg_match('/^(.+?)([<>!=]+|\bIS(?:\s+NULL))\s*$/i', $key, $matches);
+					if (isset($matches[1]) AND isset($matches[2]))
+					{
+						$key = $this->escape_column(trim($matches[1])).' '.trim($matches[2]);
+					}
+				}
+
+				$value = ' '.(($quote == TRUE) ? $this->escape($value) : $value);
+			}
+		}
+
+		return $prefix.$key.$value;
+	}
+
+	/**
+	 * Builds a LIKE portion of a query.
+	 *
+	 * @param   mixed    field name
+	 * @param   string   value to match with field
+	 * @param   boolean  add wildcards before and after the match
+	 * @param   string   clause type (AND or OR)
+	 * @param   int      number of likes
+	 * @return  string
+	 */
+	public function like($field, $match = '', $auto = TRUE, $type = 'AND ', $num_likes)
+	{
+		$prefix = ($num_likes == 0) ? '' : $type;
+
+		$match = $this->escape_str($match);
+
+		if ($auto === TRUE)
+		{
+			// Add the start and end quotes
+			$match = '%'.str_replace('%', '\\%', $match).'%';
+		}
+
+		return $prefix.' '.$this->escape_column($field).' LIKE \''.$match . '\'';
+	}
+
+	/**
+	 * Builds a NOT LIKE portion of a query.
+	 *
+	 * @param   mixed   field name
+	 * @param   string  value to match with field
+	 * @param   string  clause type (AND or OR)
+	 * @param   int     number of likes
+	 * @return  string
+	 */
+	public function notlike($field, $match = '', $auto = TRUE, $type = 'AND ', $num_likes)
+	{
+		$prefix = ($num_likes == 0) ? '' : $type;
+
+		$match = $this->escape_str($match);
+
+		if ($auto === TRUE)
+		{
+			// Add the start and end quotes
+			$match = '%'.$match.'%';
+		}
+
+		return $prefix.' '.$this->escape_column($field).' NOT LIKE \''.$match.'\'';
+	}
+
+	/**
+	 * Builds a REGEX portion of a query.
+	 *
+	 * @param   string   field name
+	 * @param   string   value to match with field
+	 * @param   string   clause type (AND or OR)
+	 * @param   integer  number of regexes
+	 * @return  string
+	 */
+	public function regex($field, $match, $type, $num_regexs)
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 * Builds a NOT REGEX portion of a query.
+	 *
+	 * @param   string   field name
+	 * @param   string   value to match with field
+	 * @param   string   clause type (AND or OR)
+	 * @param   integer  number of regexes
+	 * @return  string
+	 */
+	public function notregex($field, $match, $type, $num_regexs)
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 * Builds an INSERT query.
+	 *
+	 * @param   string  table name
+	 * @param   array   keys
+	 * @param   array   values
+	 * @return  string
+	 */
+	public function insert($table, $keys, $values)
+	{
+		// Escape the column names
+		foreach ($keys as $key => $value)
+		{
+			$keys[$key] = $this->escape_column($value);
+		}
+		return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+	}
+
+	/**
+	 * Builds a MERGE portion of a query.
+	 *
+	 * @param   string  table name
+	 * @param   array   keys
+	 * @param   array   values
+	 * @return  string
+	 */
+	public function merge($table, $keys, $values)
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 * Builds a LIMIT portion of a query.
+	 *
+	 * @param   integer  limit
+	 * @param   integer  offset
+	 * @return  string
+	 */
+	abstract public function limit($limit, $offset = 0);
+
+	/**
+	 * Creates a prepared statement.
+	 *
+	 * @param   string  SQL query
+	 * @return  Database_Stmt
+	 */
+	public function stmt_prepare($sql = '')
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 *  Compiles the SELECT statement.
+	 *  Generates a query string based on which functions were used.
+	 *  Should not be called directly, the get() function calls it.
+	 *
+	 * @param   array   select query values
+	 * @return  string
+	 */
+	abstract public function compile_select($database);
+
+	/**
+	 * Determines if the string has an arithmetic operator in it.
+	 *
+	 * @param   string   string to check
+	 * @return  boolean
+	 */
+	public function has_operator($str)
+	{
+		return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b/i', trim($str));
+	}
+
+	/**
+	 * Escapes any input value.
+	 * 
+	 * Customised to handle array and Database_Expression types
+	 *
+	 * @param   mixed   value to escape
+	 * @return  string
+	 */
+	public function escape($value)
+	{
+		if ( ! $this->db_config['escape'])
+			return $value;
+
+		switch (gettype($value))
+		{
+			case 'string':
+				$value = '\''.$this->escape_str($value).'\'';
+			break;
+			case 'boolean':
+				$value = (int) $value;
+			break;
+			case 'double':
+				// Convert to non-locale aware float to prevent possible commas
+				$value = sprintf('%F', $value);
+			break;
+			case 'array':
+				// Array handling copied from KO3
+				$value = '('.implode(', ', array_map(array($this, __FUNCTION__), $value)).')';
+			break;
+			case 'object':
+				// Object handling copied from KO3
+				if ($value instanceof Database_Expression)
+				{
+					// Compile the expression
+					$value = $value->compile($this);
+				}
+				else
+				{
+					// Otherwise convert to string and escape
+					$value =  $this->escape( (string) $value);
+				}
+			default:
+				$value = ($value === NULL) ? 'NULL' : $value;
+			break;
+		}
+
+		return (string) $value;
+	}
+
+	/**
+	 * Escapes a string for a query.
+	 *
+	 * @param   mixed   value to escape
+	 * @return  string
+	 */
+	abstract public function escape_str($str);
+
+	/**
+	 * Lists all tables in the database.
+	 *
+	 * @return  array
+	 */
+	abstract public function list_tables(Database $db);
+
+	/**
+	 * Lists all fields in a table.
+	 *
+	 * @param   string  table name
+	 * @return  array
+	 */
+	abstract function list_fields($table);
+
+	/**
+	 * Returns the last database error.
+	 *
+	 * @return  string
+	 */
+	abstract public function show_error();
+
+	/**
+	 * Returns field data about a table.
+	 *
+	 * @param   string  table name
+	 * @return  array
+	 */
+	abstract public function field_data($table);
+
+	/**
+	 * Fetches SQL type information about a field, in a generic format.
+	 *
+	 * @param   string  field datatype
+	 * @return  array
+	 */
+	protected function sql_type($str)
+	{
+		static $sql_types;
+
+		if ($sql_types === NULL)
+		{
+			// Load SQL data types
+			$sql_types = Kohana::config('sql_types');
+		}
+
+		$str = strtolower(trim($str));
+
+		if (($open  = strpos($str, '(')) !== FALSE)
+		{
+			// Find closing bracket
+			$close = strpos($str, ')', $open) - 1;
+
+			// Find the type without the size
+			$type = substr($str, 0, $open);
+		}
+		else
+		{
+			// No length
+			$type = $str;
+		}
+
+		empty($sql_types[$type]) and exit
+		(
+			'Unknown field type: '.$type.'. '.
+			'Please report this: http://trac.kohanaphp.com/newticket'
+		);
+
+		// Fetch the field definition
+		$field = $sql_types[$type];
+
+		switch ($field['type'])
+		{
+			case 'string':
+			case 'float':
+				if (isset($close))
+				{
+					// Add the length to the field info
+					$field['length'] = substr($str, $open + 1, $close - $open);
+				}
+			break;
+			case 'int':
+				// Add unsigned value
+				$field['unsigned'] = (strpos($str, 'unsigned') !== FALSE);
+			break;
+		}
+
+		return $field;
+	}
+
+	/**
+	 * Clears the internal query cache.
+	 *
+	 * @param  string  SQL query
+	 */
+	public function clear_cache($sql = NULL)
+	{
+		if (empty($sql))
+		{
+			self::$query_cache = array();
+		}
+		else
+		{
+			unset(self::$query_cache[$this->query_hash($sql)]);
+		}
+
+		Kohana::log('debug', 'Database cache cleared: '.get_class($this));
+	}
+
+	/**
+	 * Creates a hash for an SQL query string. Replaces newlines with spaces,
+	 * trims, and hashes.
+	 *
+	 * @param   string  SQL query
+	 * @return  string
+	 */
+	protected function query_hash($sql)
+	{
+		return sha1(str_replace("\n", ' ', trim($sql)));
+	}
+
+} // End Database Driver Interface
+
+/**
+ * Database_Result
+ *
+ */
+abstract class Database_Result implements ArrayAccess, Iterator, Countable {
+
+	// Result resource, insert id, and SQL
+	protected $result;
+	protected $insert_id;
+	protected $sql;
+
+	// Current and total rows
+	protected $current_row = 0;
+	protected $total_rows  = 0;
+
+	// Fetch function and return type
+	protected $fetch_type;
+	protected $return_type;
+
+	/**
+	 * Returns the SQL used to fetch the result.
+	 *
+	 * @return  string
+	 */
+	public function sql()
+	{
+		return $this->sql;
+	}
+
+	/**
+	 * Returns the insert id from the result.
+	 *
+	 * @return  mixed
+	 */
+	public function insert_id()
+	{
+		return $this->insert_id;
+	}
+
+	/**
+	 * Prepares the query result.
+	 *
+	 * @param   boolean   return rows as objects
+	 * @param   mixed     type
+	 * @return  Database_Result
+	 */
+	abstract function result($object = TRUE, $type = FALSE);
+
+	/**
+	 * Builds an array of query results.
+	 *
+	 * @param   boolean   return rows as objects
+	 * @param   mixed     type
+	 * @return  array
+	 */
+	abstract function result_array($object = NULL, $type = FALSE);
+
+	/**
+	 * Gets the fields of an already run query.
+	 *
+	 * @return  array
+	 */
+	abstract public function list_fields();
+
+	/**
+	 * Seek to an offset in the results.
+	 *
+	 * @return  boolean
+	 */
+	abstract public function seek($offset);
+
+	/**
+	 * Countable: count
+	 */
+	public function count()
+	{
+		return $this->total_rows;
+	}
+
+	/**
+	 * ArrayAccess: offsetExists
+	 */
+	public function offsetExists($offset)
+	{
+		if ($this->total_rows > 0)
+		{
+			$min = 0;
+			$max = $this->total_rows - 1;
+
+			return ! ($offset < $min OR $offset > $max);
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * ArrayAccess: offsetGet
+	 */
+	public function offsetGet($offset)
+	{
+		if ( ! $this->seek($offset))
+			return FALSE;
+
+		// Return the row by calling the defined fetching callback
+		return call_user_func($this->fetch_type, $this->result, $this->return_type);
+	}
+
+	/**
+	 * ArrayAccess: offsetSet
+	 *
+	 * @throws  Kohana_Database_Exception
+	 */
+	final public function offsetSet($offset, $value)
+	{
+		throw new Kohana_Database_Exception('database.result_read_only');
+	}
+
+	/**
+	 * ArrayAccess: offsetUnset
+	 *
+	 * @throws  Kohana_Database_Exception
+	 */
+	final public function offsetUnset($offset)
+	{
+		throw new Kohana_Database_Exception('database.result_read_only');
+	}
+
+	/**
+	 * Iterator: current
+	 */
+	public function current()
+	{
+		return $this->offsetGet($this->current_row);
+	}
+
+	/**
+	 * Iterator: key
+	 */
+	public function key()
+	{
+		return $this->current_row;
+	}
+
+	/**
+	 * Iterator: next
+	 */
+	public function next()
+	{
+		++$this->current_row;
+		return $this;
+	}
+
+	/**
+	 * Iterator: prev
+	 */
+	public function prev()
+	{
+		--$this->current_row;
+		return $this;
+	}
+
+	/**
+	 * Iterator: rewind
+	 */
+	public function rewind()
+	{
+		$this->current_row = 0;
+		return $this;
+	}
+
+	/**
+	 * Iterator: valid
+	 */
+	public function valid()
+	{
+		return $this->offsetExists($this->current_row);
+	}
+
+} // End Database Result Interface
\ No newline at end of file
diff --git a/application/libraries/drivers/Database/Mysql.php b/application/libraries/drivers/Database/Mysql.php
new file mode 100755
index 0000000..faa23cc
--- /dev/null
+++ b/application/libraries/drivers/Database/Mysql.php
@@ -0,0 +1,499 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MySQL Database Driver
+ *
+ * Overrides core database mysql driver to backport some KO3 features
+ * - modified escape_column()
+ * 
+ * @package	   Ushahidi
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+class Database_Mysql_Driver extends Database_Driver {
+
+	/**
+	 * Database connection link
+	 */
+	protected $link;
+
+	/**
+	 * Database configuration
+	 */
+	protected $db_config;
+
+	/**
+	 * Sets the config for the class.
+	 *
+	 * @param  array  database configuration
+	 */
+	public function __construct($config)
+	{
+		$this->db_config = $config;
+
+		Kohana::log('debug', 'MySQL Database Driver Initialized');
+	}
+
+	/**
+	 * Closes the database connection.
+	 */
+	public function __destruct()
+	{
+		is_resource($this->link) and mysql_close($this->link);
+	}
+
+	public function connect()
+	{
+		// Check if link already exists
+		if (is_resource($this->link))
+			return $this->link;
+
+		// Import the connect variables
+		extract($this->db_config['connection']);
+
+		// Persistent connections enabled?
+		$connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect';
+
+		// Build the connection info
+		$host = isset($host) ? $host : $socket;
+		$port = isset($port) ? ':'.$port : '';
+
+		// Make the connection and select the database
+		if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link))
+		{
+			if ($charset = $this->db_config['character_set'])
+			{
+				$this->set_charset($charset);
+			}
+
+			// Clear password after successful connect
+			$this->config['connection']['pass'] = NULL;
+
+			return $this->link;
+		}
+
+		return FALSE;
+	}
+
+	public function query($sql)
+	{
+		// Only cache if it's turned on, and only cache if it's not a write statement
+		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
+		{
+			$hash = $this->query_hash($sql);
+
+			if ( ! isset(self::$query_cache[$hash]))
+			{
+				// Set the cached object
+				self::$query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+			}
+
+			// Return the cached query
+			return self::$query_cache[$hash];
+		}
+
+		return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+	}
+
+	public function set_charset($charset)
+	{
+		$this->query('SET NAMES '.$this->escape_str($charset));
+	}
+
+	public function escape_table($table)
+	{
+		if (!$this->db_config['escape'])
+			return $table;
+
+		if (stripos($table, ' AS ') !== FALSE)
+		{
+			// Force 'AS' to uppercase
+			$table = str_ireplace(' AS ', ' AS ', $table);
+
+			// Runs escape_table on both sides of an AS statement
+			$table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
+
+			// Re-create the AS statement
+			return implode(' AS ', $table);
+		}
+		return '`'.str_replace('.', '`.`', $table).'`';
+	}
+
+	/**
+	 * Modified to handle Database_Expression
+	 **/
+	public function escape_column($column)
+	{
+		if (!$this->db_config['escape'])
+			return $column;
+
+		if (strtolower($column) == 'count(*)' OR $column == '*')
+			return $column;
+
+		if ($column instanceof Database_Expression)
+			return $column->compile($this);
+
+		// This matches any modifiers we support to SELECT.
+		if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
+		{
+			if (stripos($column, ' AS ') !== FALSE)
+			{
+				// Force 'AS' to uppercase
+				$column = str_ireplace(' AS ', ' AS ', $column);
+
+				// Runs escape_column on both sides of an AS statement
+				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+				// Re-create the AS statement
+				return implode(' AS ', $column);
+			}
+
+			return preg_replace('/[^.*]+/', '`$0`', $column);
+		}
+
+		$parts = explode(' ', $column);
+		$column = '';
+
+		for ($i = 0, $c = count($parts); $i < $c; $i++)
+		{
+			// The column is always last
+			if ($i == ($c - 1))
+			{
+				$column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
+			}
+			else // otherwise, it's a modifier
+			{
+				$column .= $parts[$i].' ';
+			}
+		}
+		return $column;
+	}
+
+	public function regex($field, $match = '', $type = 'AND ', $num_regexs)
+	{
+		$prefix = ($num_regexs == 0) ? '' : $type;
+
+		return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
+	}
+
+	public function notregex($field, $match = '', $type = 'AND ', $num_regexs)
+	{
+		$prefix = $num_regexs == 0 ? '' : $type;
+
+		return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
+	}
+
+	public function merge($table, $keys, $values)
+	{
+		// Escape the column names
+		foreach ($keys as $key => $value)
+		{
+			$keys[$key] = $this->escape_column($value);
+		}
+		return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+	}
+
+	public function limit($limit, $offset = 0)
+	{
+		return 'LIMIT '.$offset.', '.$limit;
+	}
+
+	public function compile_select($database)
+	{
+		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+		if (count($database['from']) > 0)
+		{
+			// Escape the tables
+			$froms = array();
+			foreach ($database['from'] as $from)
+			{
+				$froms[] = $this->escape_column($from);
+			}
+			$sql .= "\nFROM ";
+			$sql .= implode(', ', $froms);
+		}
+
+		if (count($database['join']) > 0)
+		{
+			foreach($database['join'] AS $join)
+			{
+				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+			}
+		}
+
+		if (count($database['where']) > 0)
+		{
+			$sql .= "\nWHERE ";
+		}
+
+		$sql .= implode("\n", $database['where']);
+
+		if (count($database['groupby']) > 0)
+		{
+			$sql .= "\nGROUP BY ";
+			$sql .= implode(', ', $database['groupby']);
+		}
+
+		if (count($database['having']) > 0)
+		{
+			$sql .= "\nHAVING ";
+			$sql .= implode("\n", $database['having']);
+		}
+
+		if (count($database['orderby']) > 0)
+		{
+			$sql .= "\nORDER BY ";
+			$sql .= implode(', ', $database['orderby']);
+		}
+
+		if (is_numeric($database['limit']))
+		{
+			$sql .= "\n";
+			$sql .= $this->limit($database['limit'], $database['offset']);
+		}
+
+		return $sql;
+	}
+
+	public function escape_str($str)
+	{
+		if (!$this->db_config['escape'])
+			return $str;
+
+		is_resource($this->link) or $this->connect();
+
+		return mysql_real_escape_string($str, $this->link);
+	}
+
+	public function list_tables(Database $db)
+	{
+		static $tables;
+
+		if (empty($tables) AND $query = $db->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database'])))
+		{
+			foreach ($query->result(FALSE) as $row)
+			{
+				$tables[] = current($row);
+			}
+		}
+
+		return $tables;
+	}
+
+	public function show_error()
+	{
+		return mysql_error($this->link);
+	}
+
+	public function list_fields($table)
+	{
+		static $tables;
+
+		if (empty($tables[$table]))
+		{
+			foreach ($this->field_data($table) as $row)
+			{
+				// Make an associative array
+				$tables[$table][$row->Field] = $this->sql_type($row->Type);
+
+				if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment')
+				{
+					// For sequenced (AUTO_INCREMENT) tables
+					$tables[$table][$row->Field]['sequenced'] = TRUE;
+				}
+
+				if ($row->Null === 'YES')
+				{
+					// Set NULL status
+					$tables[$table][$row->Field]['null'] = TRUE;
+				}
+			}
+		}
+
+		if (!isset($tables[$table]))
+			throw new Kohana_Database_Exception('database.table_not_found', $table);
+
+		return $tables[$table];
+	}
+
+	public function field_data($table)
+	{
+		$columns = array();
+
+		if ($query = mysql_query('SHOW COLUMNS FROM '.$this->escape_table($table), $this->link))
+		{
+			if (mysql_num_rows($query))
+			{
+				while ($row = mysql_fetch_object($query))
+				{
+					$columns[] = $row;
+				}
+			}
+		}
+
+		return $columns;
+	}
+
+} // End Database_Mysql_Driver Class
+
+/**
+ * MySQL Result
+ */
+class Mysql_Result extends Database_Result {
+
+	// Fetch function and return type
+	protected $fetch_type  = 'mysql_fetch_object';
+	protected $return_type = MYSQL_ASSOC;
+
+	/**
+	 * Sets up the result variables.
+	 *
+	 * @param  resource  query result
+	 * @param  resource  database link
+	 * @param  boolean   return objects or arrays
+	 * @param  string    SQL query that was run
+	 */
+	public function __construct($result, $link, $object = TRUE, $sql)
+	{
+		$this->result = $result;
+
+		// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+		if (is_resource($result))
+		{
+			$this->current_row = 0;
+			$this->total_rows  = mysql_num_rows($this->result);
+			$this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array';
+		}
+		elseif (is_bool($result))
+		{
+			if ($result == FALSE)
+			{
+				// SQL error
+				throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql);
+			}
+			else
+			{
+				// Its an DELETE, INSERT, REPLACE, or UPDATE query
+				$this->insert_id  = mysql_insert_id($link);
+				$this->total_rows = mysql_affected_rows($link);
+			}
+		}
+
+		// Set result type
+		$this->result($object);
+
+		// Store the SQL
+		$this->sql = $sql;
+	}
+
+	/**
+	 * Destruct, the cleanup crew!
+	 */
+	public function __destruct()
+	{
+		if (is_resource($this->result))
+		{
+			mysql_free_result($this->result);
+		}
+	}
+
+	public function result($object = TRUE, $type = MYSQL_ASSOC)
+	{
+		$this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array';
+
+		// This check has to be outside the previous statement, because we do not
+		// know the state of fetch_type when $object = NULL
+		// NOTE - The class set by $type must be defined before fetching the result,
+		// autoloading is disabled to save a lot of stupid overhead.
+		if ($this->fetch_type == 'mysql_fetch_object' AND $object === TRUE)
+		{
+			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+		}
+		else
+		{
+			$this->return_type = $type;
+		}
+
+		return $this;
+	}
+
+	public function as_array($object = NULL, $type = MYSQL_ASSOC)
+	{
+		return $this->result_array($object, $type);
+	}
+
+	public function result_array($object = NULL, $type = MYSQL_ASSOC)
+	{
+		$rows = array();
+
+		if (is_string($object))
+		{
+			$fetch = $object;
+		}
+		elseif (is_bool($object))
+		{
+			if ($object === TRUE)
+			{
+				$fetch = 'mysql_fetch_object';
+
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+			else
+			{
+				$fetch = 'mysql_fetch_array';
+			}
+		}
+		else
+		{
+			// Use the default config values
+			$fetch = $this->fetch_type;
+
+			if ($fetch == 'mysql_fetch_object')
+			{
+				$type = (is_string($this->return_type) AND Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass';
+			}
+		}
+
+		if (mysql_num_rows($this->result))
+		{
+			// Reset the pointer location to make sure things work properly
+			mysql_data_seek($this->result, 0);
+
+			while ($row = $fetch($this->result, $type))
+			{
+				$rows[] = $row;
+			}
+		}
+
+		return isset($rows) ? $rows : array();
+	}
+
+	public function list_fields()
+	{
+		$field_names = array();
+		while ($field = mysql_fetch_field($this->result))
+		{
+			$field_names[] = $field->name;
+		}
+
+		return $field_names;
+	}
+
+	public function seek($offset)
+	{
+		if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
+		{
+			// Set the current row to the offset
+			$this->current_row = $offset;
+
+			return TRUE;
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+} // End Mysql_Result Class
\ No newline at end of file
diff --git a/application/libraries/ftp/ftp_class_pure.php b/application/libraries/ftp/ftp_class_pure.php
new file mode 100644
index 0000000..02e6547
--- /dev/null
+++ b/application/libraries/ftp/ftp_class_pure.php
@@ -0,0 +1,163 @@
+<?php
+class ftp extends Pemftp_Core {
+
+	function ftp($verb=FALSE, $le=FALSE) {
+		$this->__construct($verb, $le);
+	}
+
+	function __construct($verb=FALSE, $le=FALSE) {
+		parent::__construct(false, $verb, $le);
+	}
+
+// <!-- --------------------------------------------------------------------------------------- -->
+// <!--       Private functions                                                                 -->
+// <!-- --------------------------------------------------------------------------------------- -->
+
+	function _settimeout($sock) {
+		if(!@stream_set_timeout($sock, $this->_timeout)) {
+			$this->PushError('_settimeout','socket set send timeout');
+			$this->_quit();
+			return FALSE;
+		}
+		return TRUE;
+	}
+
+	function _connect($host, $port) {
+		$this->SendMSG("Creating socket");
+		$sock = @fsockopen($host, $port, $errno, $errstr, $this->_timeout);
+		if (!$sock) {
+			$this->PushError('_connect','socket connect failed', $errstr." (".$errno.")");
+			return FALSE;
+		}
+		$this->_connected=true;
+		return $sock;
+	}
+
+	function _readmsg($fnction="_readmsg"){
+		if(!$this->_connected) {
+			$this->PushError($fnction, 'Connect first');
+			return FALSE;
+		}
+		$result=true;
+		$this->_message="";
+		$this->_code=0;
+		$go=true;
+		do {
+			$tmp=@fgets($this->_ftp_control_sock, 512);
+			if($tmp===false) {
+				$go=$result=false;
+				$this->PushError($fnction,'Read failed');
+			} else {
+				$this->_message.=$tmp;
+				if(preg_match("/^([0-9]{3})(-(.*[".CRLF."]{1,2})+\\1)? [^".CRLF."]+[".CRLF."]{1,2}$/", $this->_message, $regs)) $go=false;
+			}
+		} while($go);
+		if($this->LocalEcho) echo "GET < ".rtrim($this->_message, CRLF).CRLF;
+		$this->_code=(int)$regs[1];
+		return $result;
+	}
+
+	function _exec($cmd, $fnction="_exec") {
+		if(!$this->_ready) {
+			$this->PushError($fnction,'Connect first');
+			return FALSE;
+		}
+		if($this->LocalEcho) echo "PUT > ",$cmd,CRLF;
+		$status=@fputs($this->_ftp_control_sock, $cmd.CRLF);
+		if($status===false) {
+			$this->PushError($fnction,'socket write failed');
+			return FALSE;
+		}
+		$this->_lastaction=time();
+		if(!$this->_readmsg($fnction)) return FALSE;
+		return TRUE;
+	}
+
+	function _data_prepare($mode=FTP_ASCII) {
+		if(!$this->_settype($mode)) return FALSE;
+		if($this->_passive) {
+			if(!$this->_exec("PASV", "pasv")) {
+				$this->_data_close();
+				return FALSE;
+			}
+			if(!$this->_checkCode()) {
+				$this->_data_close();
+				return FALSE;
+			}
+			$ip_port = explode(",", preg_replace("/^.+ \\(?([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]+,[0-9]+)\\)?.*".CRLF."$/", "\\1", $this->_message));
+			$this->_datahost=$ip_port[0].".".$ip_port[1].".".$ip_port[2].".".$ip_port[3];
+            $this->_dataport=(((int)$ip_port[4])<<8) + ((int)$ip_port[5]);
+			$this->SendMSG("Connecting to ".$this->_datahost.":".$this->_dataport);
+			$this->_ftp_data_sock=@fsockopen($this->_datahost, $this->_dataport, $errno, $errstr, $this->_timeout);
+			if(!$this->_ftp_data_sock) {
+				$this->PushError("_data_prepare","fsockopen fails", $errstr." (".$errno.")");
+				$this->_data_close();
+				return FALSE;
+			}
+			else $this->_ftp_data_sock;
+		} else {
+			$this->SendMSG("Only passive connections available!");
+			return FALSE;
+		}
+		return TRUE;
+	}
+
+	function _data_read($mode=FTP_ASCII, $fp=NULL) {
+		if(is_resource($fp)) $out=0;
+		else $out="";
+		if(!$this->_passive) {
+			$this->SendMSG("Only passive connections available!");
+			return FALSE;
+		}
+		while (!feof($this->_ftp_data_sock)) {
+			$block=fread($this->_ftp_data_sock, $this->_ftp_buff_size);
+			if($mode!=FTP_BINARY) $block=preg_replace("/\r\n|\r|\n/", $this->_eol_code[$this->OS_local], $block);
+			if(is_resource($fp)) $out+=fwrite($fp, $block, strlen($block));
+			else $out.=$block;
+		}
+		return $out;
+	}
+
+	function _data_write($mode=FTP_ASCII, $fp=NULL) {
+		if(is_resource($fp)) $out=0;
+		else $out="";
+		if(!$this->_passive) {
+			$this->SendMSG("Only passive connections available!");
+			return FALSE;
+		}
+		if(is_resource($fp)) {
+			while(!feof($fp)) {
+				$block=fread($fp, $this->_ftp_buff_size);
+				if(!$this->_data_write_block($mode, $block)) return false;
+			}
+		} elseif(!$this->_data_write_block($mode, $fp)) return false;
+		return TRUE;
+	}
+
+	function _data_write_block($mode, $block) {
+		if($mode!=FTP_BINARY) $block=preg_replace("/\r\n|\r|\n/", $this->_eol_code[$this->OS_remote], $block);
+		do {
+			if(($t=@fwrite($this->_ftp_data_sock, $block))===FALSE) {
+				$this->PushError("_data_write","Can't write to socket");
+				return FALSE;
+			}
+			$block=substr($block, $t);
+		} while(!empty($block));
+		return true;
+	}
+
+	function _data_close() {
+		@fclose($this->_ftp_data_sock);
+		$this->SendMSG("Disconnected data from remote host");
+		return TRUE;
+	}
+
+	function _quit($force=FALSE) {
+		if($this->_connected or $force) {
+			@fclose($this->_ftp_control_sock);
+			$this->_connected=false;
+			$this->SendMSG("Socket closed");
+		}
+	}
+}
+?>
diff --git a/application/libraries/ftp/ftp_class_sockets.php b/application/libraries/ftp/ftp_class_sockets.php
new file mode 100644
index 0000000..e8c3c15
--- /dev/null
+++ b/application/libraries/ftp/ftp_class_sockets.php
@@ -0,0 +1,224 @@
+<?php
+class ftp extends Ftp_Core {
+
+	function ftp($verb=FALSE, $le=FALSE) {
+		$this->__construct($verb, $le);
+	}
+
+	function __construct($verb=FALSE, $le=FALSE) {
+		parent::__construct(true, $verb, $le);
+	}
+
+// <!-- --------------------------------------------------------------------------------------- -->
+// <!--       Private functions                                                                 -->
+// <!-- --------------------------------------------------------------------------------------- -->
+
+	function _settimeout($sock) {
+		if(!@socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>$this->_timeout, "usec"=>0))) {
+			$this->PushError('_connect','socket set receive timeout',socket_strerror(socket_last_error($sock)));
+			@socket_close($sock);
+			return FALSE;
+		}
+		if(!@socket_set_option($sock, SOL_SOCKET , SO_SNDTIMEO, array("sec"=>$this->_timeout, "usec"=>0))) {
+			$this->PushError('_connect','socket set send timeout',socket_strerror(socket_last_error($sock)));
+			@socket_close($sock);
+			return FALSE;
+		}
+		return true;
+	}
+
+	function _connect($host, $port) {
+		$this->SendMSG("Creating socket");
+		if(!($sock = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP))) {
+			$this->PushError('_connect','socket create failed',socket_strerror(socket_last_error($sock)));
+			return FALSE;
+		}
+		if(!$this->_settimeout($sock)) return FALSE;
+		$this->SendMSG("Connecting to \"".$host.":".$port."\"");
+		if (!($res = @socket_connect($sock, $host, $port))) {
+			$this->PushError('_connect','socket connect failed',socket_strerror(socket_last_error($sock)));
+			@socket_close($sock);
+			return FALSE;
+		}
+		$this->_connected=true;
+		return $sock;
+	}
+
+	function _readmsg($fnction="_readmsg"){
+		if(!$this->_connected) {
+			$this->PushError($fnction,'Connect first');
+			return FALSE;
+		}
+		$result=true;
+		$this->_message="";
+		$this->_code=0;
+		$go=true;
+		do {
+			$tmp=@socket_read($this->_ftp_control_sock, 4096, PHP_BINARY_READ);
+			if($tmp===false) {
+				$go=$result=false;
+				$this->PushError($fnction,'Read failed', socket_strerror(socket_last_error($this->_ftp_control_sock)));
+			} else {
+				$this->_message.=$tmp;
+				$go = !preg_match("/^([0-9]{3})(-.+\\1)? [^".CRLF."]+".CRLF."$/Us", $this->_message, $regs);
+			}
+		} while($go);
+		if($this->LocalEcho) echo "GET < ".rtrim($this->_message, CRLF).CRLF;
+		$this->_code=(int)$regs[1];
+		return $result;
+	}
+
+	function _exec($cmd, $fnction="_exec") {
+		if(!$this->_ready) {
+			$this->PushError($fnction,'Connect first');
+			return FALSE;
+		}
+		if($this->LocalEcho) echo "PUT > ",$cmd,CRLF;
+		$status=@socket_write($this->_ftp_control_sock, $cmd.CRLF);
+		if($status===false) {
+			$this->PushError($fnction,'socket write failed', socket_strerror(socket_last_error($this->stream)));
+			return FALSE;
+		}
+		$this->_lastaction=time();
+		if(!$this->_readmsg($fnction)) return FALSE;
+		return TRUE;
+	}
+
+	function _data_prepare($mode=FTP_ASCII) {
+		if(!$this->_settype($mode)) return FALSE;
+		$this->SendMSG("Creating data socket");
+		$this->_ftp_data_sock = @socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+		if ($this->_ftp_data_sock < 0) {
+			$this->PushError('_data_prepare','socket create failed',socket_strerror(socket_last_error($this->_ftp_data_sock)));
+			return FALSE;
+		}
+		if(!$this->_settimeout($this->_ftp_data_sock)) {
+			$this->_data_close();
+			return FALSE;
+		}
+		if($this->_passive) {
+			if(!$this->_exec("PASV", "pasv")) {
+				$this->_data_close();
+				return FALSE;
+			}
+			if(!$this->_checkCode()) {
+				$this->_data_close();
+				return FALSE;
+			}
+			$ip_port = explode(",", preg_replace("/^.+ \\(?([0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]+,[0-9]+)\\)?.*".CRLF."$/", "\\1", $this->_message));
+			$this->_datahost=$ip_port[0].".".$ip_port[1].".".$ip_port[2].".".$ip_port[3];
+            $this->_dataport=(((int)$ip_port[4])<<8) + ((int)$ip_port[5]);
+			$this->SendMSG("Connecting to ".$this->_datahost.":".$this->_dataport);
+			if(!@socket_connect($this->_ftp_data_sock, $this->_datahost, $this->_dataport)) {
+				$this->PushError("_data_prepare","socket_connect", socket_strerror(socket_last_error($this->_ftp_data_sock)));
+				$this->_data_close();
+				return FALSE;
+			}
+			else $this->_ftp_temp_sock=$this->_ftp_data_sock;
+		} else {
+			if(!@socket_getsockname($this->_ftp_control_sock, $addr, $port)) {
+				$this->PushError("_data_prepare","can't get control socket information", socket_strerror(socket_last_error($this->_ftp_control_sock)));
+				$this->_data_close();
+				return FALSE;
+			}
+			if(!@socket_bind($this->_ftp_data_sock,$addr)){
+				$this->PushError("_data_prepare","can't bind data socket", socket_strerror(socket_last_error($this->_ftp_data_sock)));
+				$this->_data_close();
+				return FALSE;
+			}
+			if(!@socket_listen($this->_ftp_data_sock)) {
+				$this->PushError("_data_prepare","can't listen data socket", socket_strerror(socket_last_error($this->_ftp_data_sock)));
+				$this->_data_close();
+				return FALSE;
+			}
+			if(!@socket_getsockname($this->_ftp_data_sock, $this->_datahost, $this->_dataport)) {
+				$this->PushError("_data_prepare","can't get data socket information", socket_strerror(socket_last_error($this->_ftp_data_sock)));
+				$this->_data_close();
+				return FALSE;
+			}
+			if(!$this->_exec('PORT '.str_replace('.',',',$this->_datahost.'.'.($this->_dataport>>8).'.'.($this->_dataport&0x00FF)), "_port")) {
+				$this->_data_close();
+				return FALSE;
+			}
+			if(!$this->_checkCode()) {
+				$this->_data_close();
+				return FALSE;
+			}
+		}
+		return TRUE;
+	}
+
+	function _data_read($mode=FTP_ASCII, $fp=NULL) {
+		$NewLine=$this->_eol_code[$this->OS_local];
+		if(is_resource($fp)) $out=0;
+		else $out="";
+		if(!$this->_passive) {
+			$this->SendMSG("Connecting to ".$this->_datahost.":".$this->_dataport);
+			$this->_ftp_temp_sock=socket_accept($this->_ftp_data_sock);
+			if($this->_ftp_temp_sock===FALSE) {
+				$this->PushError("_data_read","socket_accept", socket_strerror(socket_last_error($this->_ftp_temp_sock)));
+				$this->_data_close();
+				return FALSE;
+			}
+		}
+
+		while(($block=@socket_read($this->_ftp_temp_sock, $this->_ftp_buff_size, PHP_BINARY_READ))!==false) {
+			if($block==="") break;
+			if($mode!=FTP_BINARY) $block=preg_replace("/\r\n|\r|\n/", $this->_eol_code[$this->OS_local], $block);
+			if(is_resource($fp)) $out+=fwrite($fp, $block, strlen($block));
+			else $out.=$block;
+		}
+		return $out;
+	}
+
+	function _data_write($mode=FTP_ASCII, $fp=NULL) {
+		$NewLine=$this->_eol_code[$this->OS_local];
+		if(is_resource($fp)) $out=0;
+		else $out="";
+		if(!$this->_passive) {
+			$this->SendMSG("Connecting to ".$this->_datahost.":".$this->_dataport);
+			$this->_ftp_temp_sock=socket_accept($this->_ftp_data_sock);
+			if($this->_ftp_temp_sock===FALSE) {
+				$this->PushError("_data_write","socket_accept", socket_strerror(socket_last_error($this->_ftp_temp_sock)));
+				$this->_data_close();
+				return false;
+			}
+		}
+		if(is_resource($fp)) {
+			while(!feof($fp)) {
+				$block=fread($fp, $this->_ftp_buff_size);
+				if(!$this->_data_write_block($mode, $block)) return false;
+			}
+		} elseif(!$this->_data_write_block($mode, $fp)) return false;
+		return true;
+	}
+
+	function _data_write_block($mode, $block) {
+		if($mode!=FTP_BINARY) $block=preg_replace("/\r\n|\r|\n/", $this->_eol_code[$this->OS_remote], $block);
+		do {
+			if(($t=@socket_write($this->_ftp_temp_sock, $block))===FALSE) {
+				$this->PushError("_data_write","socket_write", socket_strerror(socket_last_error($this->_ftp_temp_sock)));
+				$this->_data_close();
+				return FALSE;
+			}
+			$block=substr($block, $t);
+		} while(!empty($block));
+		return true;
+	}
+
+	function _data_close() {
+		@socket_close($this->_ftp_temp_sock);
+		@socket_close($this->_ftp_data_sock);
+		$this->SendMSG("Disconnected data from remote host");
+		return TRUE;
+	}
+
+	function _quit() {
+		if($this->_connected) {
+			@socket_close($this->_ftp_control_sock);
+			$this->_connected=false;
+			$this->SendMSG("Socket closed");
+		}
+	}
+}
+?>
diff --git a/application/libraries/htmlpurifier/CREDITS b/application/libraries/htmlpurifier/CREDITS
new file mode 100644
index 0000000..7921b45
--- /dev/null
+++ b/application/libraries/htmlpurifier/CREDITS
@@ -0,0 +1,9 @@
+
+CREDITS
+
+Almost everything written by Edward Z. Yang (Ambush Commander).  Lots of thanks
+to the DevNetwork Community for their help (see docs/ref-devnetwork.html for
+more details), Feyd especially (namely IPv6 and optimization).  Thanks to RSnake
+for letting me package his fantastic XSS cheatsheet for a smoketest.
+
+    vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.auto.php b/application/libraries/htmlpurifier/HTMLPurifier.auto.php
new file mode 100644
index 0000000..1960c39
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.auto.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * This is a stub include that automatically configures the include path.
+ */
+
+set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
+require_once 'HTMLPurifier/Bootstrap.php';
+require_once 'HTMLPurifier.autoload.php';
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.autoload.php b/application/libraries/htmlpurifier/HTMLPurifier.autoload.php
new file mode 100644
index 0000000..62da5b6
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.autoload.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Convenience file that registers autoload handler for HTML Purifier.
+ * It also does some sanity checks.
+ */
+
+if (function_exists('spl_autoload_register') && function_exists('spl_autoload_unregister')) {
+    // We need unregister for our pre-registering functionality
+    HTMLPurifier_Bootstrap::registerAutoload();
+    if (function_exists('__autoload')) {
+        // Be polite and ensure that userland autoload gets retained
+        spl_autoload_register('__autoload');
+    }
+} elseif (!function_exists('__autoload')) {
+    function __autoload($class) {
+        return HTMLPurifier_Bootstrap::autoload($class);
+    }
+}
+
+if (ini_get('zend.ze1_compatibility_mode')) {
+    trigger_error("HTML Purifier is not compatible with zend.ze1_compatibility_mode; please turn it off", E_USER_ERROR);
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.composer.php b/application/libraries/htmlpurifier/HTMLPurifier.composer.php
new file mode 100644
index 0000000..6706f4e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.composer.php
@@ -0,0 +1,4 @@
+<?php
+if (!defined('HTMLPURIFIER_PREFIX')) {
+    define('HTMLPURIFIER_PREFIX', __DIR__);
+}
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.func.php b/application/libraries/htmlpurifier/HTMLPurifier.func.php
new file mode 100644
index 0000000..56a55b2
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.func.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Defines a function wrapper for HTML Purifier for quick use.
+ * @note ''HTMLPurifier()'' is NOT the same as ''new HTMLPurifier()''
+ */
+
+/**
+ * Purify HTML.
+ * @param $html String HTML to purify
+ * @param $config Configuration to use, can be any value accepted by
+ *        HTMLPurifier_Config::create()
+ */
+function HTMLPurifier($html, $config = null) {
+    static $purifier = false;
+    if (!$purifier) {
+        $purifier = new HTMLPurifier();
+    }
+    return $purifier->purify($html, $config);
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.includes.php b/application/libraries/htmlpurifier/HTMLPurifier.includes.php
new file mode 100644
index 0000000..18cb001
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.includes.php
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * @file
+ * This file was auto-generated by generate-includes.php and includes all of
+ * the core files required by HTML Purifier. Use this if performance is a
+ * primary concern and you are using an opcode cache. PLEASE DO NOT EDIT THIS
+ * FILE, changes will be overwritten the next time the script is run.
+ *
+ * @version 4.5.0
+ *
+ * @warning
+ *      You must *not* include any other HTML Purifier files before this file,
+ *      because 'require' not 'require_once' is used.
+ *
+ * @warning
+ *      This file requires that the include path contains the HTML Purifier
+ *      library directory; this is not auto-set.
+ */
+
+require 'HTMLPurifier.php';
+require 'HTMLPurifier/AttrCollections.php';
+require 'HTMLPurifier/AttrDef.php';
+require 'HTMLPurifier/AttrTransform.php';
+require 'HTMLPurifier/AttrTypes.php';
+require 'HTMLPurifier/AttrValidator.php';
+require 'HTMLPurifier/Bootstrap.php';
+require 'HTMLPurifier/Definition.php';
+require 'HTMLPurifier/CSSDefinition.php';
+require 'HTMLPurifier/ChildDef.php';
+require 'HTMLPurifier/Config.php';
+require 'HTMLPurifier/ConfigSchema.php';
+require 'HTMLPurifier/ContentSets.php';
+require 'HTMLPurifier/Context.php';
+require 'HTMLPurifier/DefinitionCache.php';
+require 'HTMLPurifier/DefinitionCacheFactory.php';
+require 'HTMLPurifier/Doctype.php';
+require 'HTMLPurifier/DoctypeRegistry.php';
+require 'HTMLPurifier/ElementDef.php';
+require 'HTMLPurifier/Encoder.php';
+require 'HTMLPurifier/EntityLookup.php';
+require 'HTMLPurifier/EntityParser.php';
+require 'HTMLPurifier/ErrorCollector.php';
+require 'HTMLPurifier/ErrorStruct.php';
+require 'HTMLPurifier/Exception.php';
+require 'HTMLPurifier/Filter.php';
+require 'HTMLPurifier/Generator.php';
+require 'HTMLPurifier/HTMLDefinition.php';
+require 'HTMLPurifier/HTMLModule.php';
+require 'HTMLPurifier/HTMLModuleManager.php';
+require 'HTMLPurifier/IDAccumulator.php';
+require 'HTMLPurifier/Injector.php';
+require 'HTMLPurifier/Language.php';
+require 'HTMLPurifier/LanguageFactory.php';
+require 'HTMLPurifier/Length.php';
+require 'HTMLPurifier/Lexer.php';
+require 'HTMLPurifier/PercentEncoder.php';
+require 'HTMLPurifier/PropertyList.php';
+require 'HTMLPurifier/PropertyListIterator.php';
+require 'HTMLPurifier/Strategy.php';
+require 'HTMLPurifier/StringHash.php';
+require 'HTMLPurifier/StringHashParser.php';
+require 'HTMLPurifier/TagTransform.php';
+require 'HTMLPurifier/Token.php';
+require 'HTMLPurifier/TokenFactory.php';
+require 'HTMLPurifier/URI.php';
+require 'HTMLPurifier/URIDefinition.php';
+require 'HTMLPurifier/URIFilter.php';
+require 'HTMLPurifier/URIParser.php';
+require 'HTMLPurifier/URIScheme.php';
+require 'HTMLPurifier/URISchemeRegistry.php';
+require 'HTMLPurifier/UnitConverter.php';
+require 'HTMLPurifier/VarParser.php';
+require 'HTMLPurifier/VarParserException.php';
+require 'HTMLPurifier/AttrDef/CSS.php';
+require 'HTMLPurifier/AttrDef/Clone.php';
+require 'HTMLPurifier/AttrDef/Enum.php';
+require 'HTMLPurifier/AttrDef/Integer.php';
+require 'HTMLPurifier/AttrDef/Lang.php';
+require 'HTMLPurifier/AttrDef/Switch.php';
+require 'HTMLPurifier/AttrDef/Text.php';
+require 'HTMLPurifier/AttrDef/URI.php';
+require 'HTMLPurifier/AttrDef/CSS/Number.php';
+require 'HTMLPurifier/AttrDef/CSS/AlphaValue.php';
+require 'HTMLPurifier/AttrDef/CSS/Background.php';
+require 'HTMLPurifier/AttrDef/CSS/BackgroundPosition.php';
+require 'HTMLPurifier/AttrDef/CSS/Border.php';
+require 'HTMLPurifier/AttrDef/CSS/Color.php';
+require 'HTMLPurifier/AttrDef/CSS/Composite.php';
+require 'HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
+require 'HTMLPurifier/AttrDef/CSS/Filter.php';
+require 'HTMLPurifier/AttrDef/CSS/Font.php';
+require 'HTMLPurifier/AttrDef/CSS/FontFamily.php';
+require 'HTMLPurifier/AttrDef/CSS/Ident.php';
+require 'HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
+require 'HTMLPurifier/AttrDef/CSS/Length.php';
+require 'HTMLPurifier/AttrDef/CSS/ListStyle.php';
+require 'HTMLPurifier/AttrDef/CSS/Multiple.php';
+require 'HTMLPurifier/AttrDef/CSS/Percentage.php';
+require 'HTMLPurifier/AttrDef/CSS/TextDecoration.php';
+require 'HTMLPurifier/AttrDef/CSS/URI.php';
+require 'HTMLPurifier/AttrDef/HTML/Bool.php';
+require 'HTMLPurifier/AttrDef/HTML/Nmtokens.php';
+require 'HTMLPurifier/AttrDef/HTML/Class.php';
+require 'HTMLPurifier/AttrDef/HTML/Color.php';
+require 'HTMLPurifier/AttrDef/HTML/FrameTarget.php';
+require 'HTMLPurifier/AttrDef/HTML/ID.php';
+require 'HTMLPurifier/AttrDef/HTML/Pixels.php';
+require 'HTMLPurifier/AttrDef/HTML/Length.php';
+require 'HTMLPurifier/AttrDef/HTML/LinkTypes.php';
+require 'HTMLPurifier/AttrDef/HTML/MultiLength.php';
+require 'HTMLPurifier/AttrDef/URI/Email.php';
+require 'HTMLPurifier/AttrDef/URI/Host.php';
+require 'HTMLPurifier/AttrDef/URI/IPv4.php';
+require 'HTMLPurifier/AttrDef/URI/IPv6.php';
+require 'HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php';
+require 'HTMLPurifier/AttrTransform/Background.php';
+require 'HTMLPurifier/AttrTransform/BdoDir.php';
+require 'HTMLPurifier/AttrTransform/BgColor.php';
+require 'HTMLPurifier/AttrTransform/BoolToCSS.php';
+require 'HTMLPurifier/AttrTransform/Border.php';
+require 'HTMLPurifier/AttrTransform/EnumToCSS.php';
+require 'HTMLPurifier/AttrTransform/ImgRequired.php';
+require 'HTMLPurifier/AttrTransform/ImgSpace.php';
+require 'HTMLPurifier/AttrTransform/Input.php';
+require 'HTMLPurifier/AttrTransform/Lang.php';
+require 'HTMLPurifier/AttrTransform/Length.php';
+require 'HTMLPurifier/AttrTransform/Name.php';
+require 'HTMLPurifier/AttrTransform/NameSync.php';
+require 'HTMLPurifier/AttrTransform/Nofollow.php';
+require 'HTMLPurifier/AttrTransform/SafeEmbed.php';
+require 'HTMLPurifier/AttrTransform/SafeObject.php';
+require 'HTMLPurifier/AttrTransform/SafeParam.php';
+require 'HTMLPurifier/AttrTransform/ScriptRequired.php';
+require 'HTMLPurifier/AttrTransform/TargetBlank.php';
+require 'HTMLPurifier/AttrTransform/Textarea.php';
+require 'HTMLPurifier/ChildDef/Chameleon.php';
+require 'HTMLPurifier/ChildDef/Custom.php';
+require 'HTMLPurifier/ChildDef/Empty.php';
+require 'HTMLPurifier/ChildDef/List.php';
+require 'HTMLPurifier/ChildDef/Required.php';
+require 'HTMLPurifier/ChildDef/Optional.php';
+require 'HTMLPurifier/ChildDef/StrictBlockquote.php';
+require 'HTMLPurifier/ChildDef/Table.php';
+require 'HTMLPurifier/DefinitionCache/Decorator.php';
+require 'HTMLPurifier/DefinitionCache/Null.php';
+require 'HTMLPurifier/DefinitionCache/Serializer.php';
+require 'HTMLPurifier/DefinitionCache/Decorator/Cleanup.php';
+require 'HTMLPurifier/DefinitionCache/Decorator/Memory.php';
+require 'HTMLPurifier/HTMLModule/Bdo.php';
+require 'HTMLPurifier/HTMLModule/CommonAttributes.php';
+require 'HTMLPurifier/HTMLModule/Edit.php';
+require 'HTMLPurifier/HTMLModule/Forms.php';
+require 'HTMLPurifier/HTMLModule/Hypertext.php';
+require 'HTMLPurifier/HTMLModule/Iframe.php';
+require 'HTMLPurifier/HTMLModule/Image.php';
+require 'HTMLPurifier/HTMLModule/Legacy.php';
+require 'HTMLPurifier/HTMLModule/List.php';
+require 'HTMLPurifier/HTMLModule/Name.php';
+require 'HTMLPurifier/HTMLModule/Nofollow.php';
+require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
+require 'HTMLPurifier/HTMLModule/Object.php';
+require 'HTMLPurifier/HTMLModule/Presentation.php';
+require 'HTMLPurifier/HTMLModule/Proprietary.php';
+require 'HTMLPurifier/HTMLModule/Ruby.php';
+require 'HTMLPurifier/HTMLModule/SafeEmbed.php';
+require 'HTMLPurifier/HTMLModule/SafeObject.php';
+require 'HTMLPurifier/HTMLModule/SafeScripting.php';
+require 'HTMLPurifier/HTMLModule/Scripting.php';
+require 'HTMLPurifier/HTMLModule/StyleAttribute.php';
+require 'HTMLPurifier/HTMLModule/Tables.php';
+require 'HTMLPurifier/HTMLModule/Target.php';
+require 'HTMLPurifier/HTMLModule/TargetBlank.php';
+require 'HTMLPurifier/HTMLModule/Text.php';
+require 'HTMLPurifier/HTMLModule/Tidy.php';
+require 'HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
+require 'HTMLPurifier/HTMLModule/Tidy/Name.php';
+require 'HTMLPurifier/HTMLModule/Tidy/Proprietary.php';
+require 'HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php';
+require 'HTMLPurifier/HTMLModule/Tidy/Strict.php';
+require 'HTMLPurifier/HTMLModule/Tidy/Transitional.php';
+require 'HTMLPurifier/HTMLModule/Tidy/XHTML.php';
+require 'HTMLPurifier/Injector/AutoParagraph.php';
+require 'HTMLPurifier/Injector/DisplayLinkURI.php';
+require 'HTMLPurifier/Injector/Linkify.php';
+require 'HTMLPurifier/Injector/PurifierLinkify.php';
+require 'HTMLPurifier/Injector/RemoveEmpty.php';
+require 'HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php';
+require 'HTMLPurifier/Injector/SafeObject.php';
+require 'HTMLPurifier/Lexer/DOMLex.php';
+require 'HTMLPurifier/Lexer/DirectLex.php';
+require 'HTMLPurifier/Strategy/Composite.php';
+require 'HTMLPurifier/Strategy/Core.php';
+require 'HTMLPurifier/Strategy/FixNesting.php';
+require 'HTMLPurifier/Strategy/MakeWellFormed.php';
+require 'HTMLPurifier/Strategy/RemoveForeignElements.php';
+require 'HTMLPurifier/Strategy/ValidateAttributes.php';
+require 'HTMLPurifier/TagTransform/Font.php';
+require 'HTMLPurifier/TagTransform/Simple.php';
+require 'HTMLPurifier/Token/Comment.php';
+require 'HTMLPurifier/Token/Tag.php';
+require 'HTMLPurifier/Token/Empty.php';
+require 'HTMLPurifier/Token/End.php';
+require 'HTMLPurifier/Token/Start.php';
+require 'HTMLPurifier/Token/Text.php';
+require 'HTMLPurifier/URIFilter/DisableExternal.php';
+require 'HTMLPurifier/URIFilter/DisableExternalResources.php';
+require 'HTMLPurifier/URIFilter/DisableResources.php';
+require 'HTMLPurifier/URIFilter/HostBlacklist.php';
+require 'HTMLPurifier/URIFilter/MakeAbsolute.php';
+require 'HTMLPurifier/URIFilter/Munge.php';
+require 'HTMLPurifier/URIFilter/SafeIframe.php';
+require 'HTMLPurifier/URIScheme/data.php';
+require 'HTMLPurifier/URIScheme/file.php';
+require 'HTMLPurifier/URIScheme/ftp.php';
+require 'HTMLPurifier/URIScheme/http.php';
+require 'HTMLPurifier/URIScheme/https.php';
+require 'HTMLPurifier/URIScheme/mailto.php';
+require 'HTMLPurifier/URIScheme/news.php';
+require 'HTMLPurifier/URIScheme/nntp.php';
+require 'HTMLPurifier/VarParser/Flexible.php';
+require 'HTMLPurifier/VarParser/Native.php';
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.kses.php b/application/libraries/htmlpurifier/HTMLPurifier.kses.php
new file mode 100644
index 0000000..3143feb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.kses.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Emulation layer for code that used kses(), substituting in HTML Purifier.
+ */
+
+require_once dirname(__FILE__) . '/HTMLPurifier.auto.php';
+
+function kses($string, $allowed_html, $allowed_protocols = null) {
+    $config = HTMLPurifier_Config::createDefault();
+    $allowed_elements = array();
+    $allowed_attributes = array();
+    foreach ($allowed_html as $element => $attributes) {
+        $allowed_elements[$element] = true;
+        foreach ($attributes as $attribute => $x) {
+            $allowed_attributes["$element.$attribute"] = true;
+        }
+    }
+    $config->set('HTML.AllowedElements', $allowed_elements);
+    $config->set('HTML.AllowedAttributes', $allowed_attributes);
+    $allowed_schemes = array();
+    if ($allowed_protocols !== null) {
+        $config->set('URI.AllowedSchemes', $allowed_protocols);
+    }
+    $purifier = new HTMLPurifier($config);
+    return $purifier->purify($string);
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.path.php b/application/libraries/htmlpurifier/HTMLPurifier.path.php
new file mode 100644
index 0000000..39b1b65
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.path.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @file
+ * Convenience stub file that adds HTML Purifier's library file to the path
+ * without any other side-effects.
+ */
+
+set_include_path(dirname(__FILE__) . PATH_SEPARATOR . get_include_path() );
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.php b/application/libraries/htmlpurifier/HTMLPurifier.php
new file mode 100644
index 0000000..fe5a9d5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.php
@@ -0,0 +1,237 @@
+<?php
+
+/*! @mainpage
+ *
+ * HTML Purifier is an HTML filter that will take an arbitrary snippet of
+ * HTML and rigorously test, validate and filter it into a version that
+ * is safe for output onto webpages. It achieves this by:
+ *
+ *  -# Lexing (parsing into tokens) the document,
+ *  -# Executing various strategies on the tokens:
+ *      -# Removing all elements not in the whitelist,
+ *      -# Making the tokens well-formed,
+ *      -# Fixing the nesting of the nodes, and
+ *      -# Validating attributes of the nodes; and
+ *  -# Generating HTML from the purified tokens.
+ *
+ * However, most users will only need to interface with the HTMLPurifier
+ * and HTMLPurifier_Config.
+ */
+
+/*
+    HTML Purifier 4.5.0 - Standards Compliant HTML Filtering
+    Copyright (C) 2006-2008 Edward Z. Yang
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * Facade that coordinates HTML Purifier's subsystems in order to purify HTML.
+ *
+ * @note There are several points in which configuration can be specified
+ *       for HTML Purifier.  The precedence of these (from lowest to
+ *       highest) is as follows:
+ *          -# Instance: new HTMLPurifier($config)
+ *          -# Invocation: purify($html, $config)
+ *       These configurations are entirely independent of each other and
+ *       are *not* merged (this behavior may change in the future).
+ *
+ * @todo We need an easier way to inject strategies using the configuration
+ *       object.
+ */
+class HTMLPurifier
+{
+
+    /** Version of HTML Purifier */
+    public $version = '4.5.0';
+
+    /** Constant with version of HTML Purifier */
+    const VERSION = '4.5.0';
+
+    /** Global configuration object */
+    public $config;
+
+    /** Array of extra HTMLPurifier_Filter objects to run on HTML, for backwards compatibility */
+    private $filters = array();
+
+    /** Single instance of HTML Purifier */
+    private static $instance;
+
+    protected $strategy, $generator;
+
+    /**
+     * Resultant HTMLPurifier_Context of last run purification. Is an array
+     * of contexts if the last called method was purifyArray().
+     */
+    public $context;
+
+    /**
+     * Initializes the purifier.
+     * @param $config Optional HTMLPurifier_Config object for all instances of
+     *                the purifier, if omitted, a default configuration is
+     *                supplied (which can be overridden on a per-use basis).
+     *                The parameter can also be any type that
+     *                HTMLPurifier_Config::create() supports.
+     */
+    public function __construct($config = null) {
+
+        $this->config = HTMLPurifier_Config::create($config);
+
+        $this->strategy     = new HTMLPurifier_Strategy_Core();
+
+    }
+
+    /**
+     * Adds a filter to process the output. First come first serve
+     * @param $filter HTMLPurifier_Filter object
+     */
+    public function addFilter($filter) {
+        trigger_error('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom', E_USER_WARNING);
+        $this->filters[] = $filter;
+    }
+
+    /**
+     * Filters an HTML snippet/document to be XSS-free and standards-compliant.
+     *
+     * @param $html String of HTML to purify
+     * @param $config HTMLPurifier_Config object for this operation, if omitted,
+     *                defaults to the config object specified during this
+     *                object's construction. The parameter can also be any type
+     *                that HTMLPurifier_Config::create() supports.
+     * @return Purified HTML
+     */
+    public function purify($html, $config = null) {
+
+        // :TODO: make the config merge in, instead of replace
+        $config = $config ? HTMLPurifier_Config::create($config) : $this->config;
+
+        // implementation is partially environment dependant, partially
+        // configuration dependant
+        $lexer = HTMLPurifier_Lexer::create($config);
+
+        $context = new HTMLPurifier_Context();
+
+        // setup HTML generator
+        $this->generator = new HTMLPurifier_Generator($config, $context);
+        $context->register('Generator', $this->generator);
+
+        // set up global context variables
+        if ($config->get('Core.CollectErrors')) {
+            // may get moved out if other facilities use it
+            $language_factory = HTMLPurifier_LanguageFactory::instance();
+            $language = $language_factory->create($config, $context);
+            $context->register('Locale', $language);
+
+            $error_collector = new HTMLPurifier_ErrorCollector($context);
+            $context->register('ErrorCollector', $error_collector);
+        }
+
+        // setup id_accumulator context, necessary due to the fact that
+        // AttrValidator can be called from many places
+        $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
+        $context->register('IDAccumulator', $id_accumulator);
+
+        $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context);
+
+        // setup filters
+        $filter_flags = $config->getBatch('Filter');
+        $custom_filters = $filter_flags['Custom'];
+        unset($filter_flags['Custom']);
+        $filters = array();
+        foreach ($filter_flags as $filter => $flag) {
+            if (!$flag) continue;
+            if (strpos($filter, '.') !== false) continue;
+            $class = "HTMLPurifier_Filter_$filter";
+            $filters[] = new $class;
+        }
+        foreach ($custom_filters as $filter) {
+            // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat
+            $filters[] = $filter;
+        }
+        $filters = array_merge($filters, $this->filters);
+        // maybe prepare(), but later
+
+        for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) {
+            $html = $filters[$i]->preFilter($html, $config, $context);
+        }
+
+        // purified HTML
+        $html =
+            $this->generator->generateFromTokens(
+                // list of tokens
+                $this->strategy->execute(
+                    // list of un-purified tokens
+                    $lexer->tokenizeHTML(
+                        // un-purified HTML
+                        $html, $config, $context
+                    ),
+                    $config, $context
+                )
+            );
+
+        for ($i = $filter_size - 1; $i >= 0; $i--) {
+            $html = $filters[$i]->postFilter($html, $config, $context);
+        }
+
+        $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context);
+        $this->context =& $context;
+        return $html;
+    }
+
+    /**
+     * Filters an array of HTML snippets
+     * @param $config Optional HTMLPurifier_Config object for this operation.
+     *                See HTMLPurifier::purify() for more details.
+     * @return Array of purified HTML
+     */
+    public function purifyArray($array_of_html, $config = null) {
+        $context_array = array();
+        foreach ($array_of_html as $key => $html) {
+            $array_of_html[$key] = $this->purify($html, $config);
+            $context_array[$key] = $this->context;
+        }
+        $this->context = $context_array;
+        return $array_of_html;
+    }
+
+    /**
+     * Singleton for enforcing just one HTML Purifier in your system
+     * @param $prototype Optional prototype HTMLPurifier instance to
+     *                   overload singleton with, or HTMLPurifier_Config
+     *                   instance to configure the generated version with.
+     */
+    public static function instance($prototype = null) {
+        if (!self::$instance || $prototype) {
+            if ($prototype instanceof HTMLPurifier) {
+                self::$instance = $prototype;
+            } elseif ($prototype) {
+                self::$instance = new HTMLPurifier($prototype);
+            } else {
+                self::$instance = new HTMLPurifier();
+            }
+        }
+        return self::$instance;
+    }
+
+    /**
+     * @note Backwards compatibility, see instance()
+     */
+    public static function getInstance($prototype = null) {
+        return HTMLPurifier::instance($prototype);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier.safe-includes.php b/application/libraries/htmlpurifier/HTMLPurifier.safe-includes.php
new file mode 100644
index 0000000..e23a81a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier.safe-includes.php
@@ -0,0 +1,216 @@
+<?php
+
+/**
+ * @file
+ * This file was auto-generated by generate-includes.php and includes all of
+ * the core files required by HTML Purifier. This is a convenience stub that
+ * includes all files using dirname(__FILE__) and require_once. PLEASE DO NOT
+ * EDIT THIS FILE, changes will be overwritten the next time the script is run.
+ *
+ * Changes to include_path are not necessary.
+ */
+
+$__dir = dirname(__FILE__);
+
+require_once $__dir . '/HTMLPurifier.php';
+require_once $__dir . '/HTMLPurifier/AttrCollections.php';
+require_once $__dir . '/HTMLPurifier/AttrDef.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform.php';
+require_once $__dir . '/HTMLPurifier/AttrTypes.php';
+require_once $__dir . '/HTMLPurifier/AttrValidator.php';
+require_once $__dir . '/HTMLPurifier/Bootstrap.php';
+require_once $__dir . '/HTMLPurifier/Definition.php';
+require_once $__dir . '/HTMLPurifier/CSSDefinition.php';
+require_once $__dir . '/HTMLPurifier/ChildDef.php';
+require_once $__dir . '/HTMLPurifier/Config.php';
+require_once $__dir . '/HTMLPurifier/ConfigSchema.php';
+require_once $__dir . '/HTMLPurifier/ContentSets.php';
+require_once $__dir . '/HTMLPurifier/Context.php';
+require_once $__dir . '/HTMLPurifier/DefinitionCache.php';
+require_once $__dir . '/HTMLPurifier/DefinitionCacheFactory.php';
+require_once $__dir . '/HTMLPurifier/Doctype.php';
+require_once $__dir . '/HTMLPurifier/DoctypeRegistry.php';
+require_once $__dir . '/HTMLPurifier/ElementDef.php';
+require_once $__dir . '/HTMLPurifier/Encoder.php';
+require_once $__dir . '/HTMLPurifier/EntityLookup.php';
+require_once $__dir . '/HTMLPurifier/EntityParser.php';
+require_once $__dir . '/HTMLPurifier/ErrorCollector.php';
+require_once $__dir . '/HTMLPurifier/ErrorStruct.php';
+require_once $__dir . '/HTMLPurifier/Exception.php';
+require_once $__dir . '/HTMLPurifier/Filter.php';
+require_once $__dir . '/HTMLPurifier/Generator.php';
+require_once $__dir . '/HTMLPurifier/HTMLDefinition.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule.php';
+require_once $__dir . '/HTMLPurifier/HTMLModuleManager.php';
+require_once $__dir . '/HTMLPurifier/IDAccumulator.php';
+require_once $__dir . '/HTMLPurifier/Injector.php';
+require_once $__dir . '/HTMLPurifier/Language.php';
+require_once $__dir . '/HTMLPurifier/LanguageFactory.php';
+require_once $__dir . '/HTMLPurifier/Length.php';
+require_once $__dir . '/HTMLPurifier/Lexer.php';
+require_once $__dir . '/HTMLPurifier/PercentEncoder.php';
+require_once $__dir . '/HTMLPurifier/PropertyList.php';
+require_once $__dir . '/HTMLPurifier/PropertyListIterator.php';
+require_once $__dir . '/HTMLPurifier/Strategy.php';
+require_once $__dir . '/HTMLPurifier/StringHash.php';
+require_once $__dir . '/HTMLPurifier/StringHashParser.php';
+require_once $__dir . '/HTMLPurifier/TagTransform.php';
+require_once $__dir . '/HTMLPurifier/Token.php';
+require_once $__dir . '/HTMLPurifier/TokenFactory.php';
+require_once $__dir . '/HTMLPurifier/URI.php';
+require_once $__dir . '/HTMLPurifier/URIDefinition.php';
+require_once $__dir . '/HTMLPurifier/URIFilter.php';
+require_once $__dir . '/HTMLPurifier/URIParser.php';
+require_once $__dir . '/HTMLPurifier/URIScheme.php';
+require_once $__dir . '/HTMLPurifier/URISchemeRegistry.php';
+require_once $__dir . '/HTMLPurifier/UnitConverter.php';
+require_once $__dir . '/HTMLPurifier/VarParser.php';
+require_once $__dir . '/HTMLPurifier/VarParserException.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/Clone.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/Enum.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/Integer.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/Lang.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/Switch.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/Text.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/URI.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Number.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/AlphaValue.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Background.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Border.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Color.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Composite.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Filter.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Font.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/FontFamily.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Ident.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Length.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/ListStyle.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Multiple.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/Percentage.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/TextDecoration.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/CSS/URI.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Bool.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Nmtokens.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Class.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Color.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/FrameTarget.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/ID.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Pixels.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/Length.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/LinkTypes.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/HTML/MultiLength.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/URI/Email.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/URI/Host.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/URI/IPv4.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/URI/IPv6.php';
+require_once $__dir . '/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Background.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/BdoDir.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/BgColor.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/BoolToCSS.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Border.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/EnumToCSS.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/ImgRequired.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/ImgSpace.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Input.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Lang.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Nofollow.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/ScriptRequired.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/TargetBlank.php';
+require_once $__dir . '/HTMLPurifier/AttrTransform/Textarea.php';
+require_once $__dir . '/HTMLPurifier/ChildDef/Chameleon.php';
+require_once $__dir . '/HTMLPurifier/ChildDef/Custom.php';
+require_once $__dir . '/HTMLPurifier/ChildDef/Empty.php';
+require_once $__dir . '/HTMLPurifier/ChildDef/List.php';
+require_once $__dir . '/HTMLPurifier/ChildDef/Required.php';
+require_once $__dir . '/HTMLPurifier/ChildDef/Optional.php';
+require_once $__dir . '/HTMLPurifier/ChildDef/StrictBlockquote.php';
+require_once $__dir . '/HTMLPurifier/ChildDef/Table.php';
+require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator.php';
+require_once $__dir . '/HTMLPurifier/DefinitionCache/Null.php';
+require_once $__dir . '/HTMLPurifier/DefinitionCache/Serializer.php';
+require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php';
+require_once $__dir . '/HTMLPurifier/DefinitionCache/Decorator/Memory.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Bdo.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/CommonAttributes.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Edit.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Forms.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Hypertext.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Iframe.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Image.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/List.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Nofollow.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Proprietary.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Ruby.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/SafeEmbed.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/SafeObject.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/SafeScripting.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Scripting.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/StyleAttribute.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Tables.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Target.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/TargetBlank.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Text.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/XMLCommonAttributes.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Name.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Proprietary.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Strict.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/Transitional.php';
+require_once $__dir . '/HTMLPurifier/HTMLModule/Tidy/XHTML.php';
+require_once $__dir . '/HTMLPurifier/Injector/AutoParagraph.php';
+require_once $__dir . '/HTMLPurifier/Injector/DisplayLinkURI.php';
+require_once $__dir . '/HTMLPurifier/Injector/Linkify.php';
+require_once $__dir . '/HTMLPurifier/Injector/PurifierLinkify.php';
+require_once $__dir . '/HTMLPurifier/Injector/RemoveEmpty.php';
+require_once $__dir . '/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php';
+require_once $__dir . '/HTMLPurifier/Injector/SafeObject.php';
+require_once $__dir . '/HTMLPurifier/Lexer/DOMLex.php';
+require_once $__dir . '/HTMLPurifier/Lexer/DirectLex.php';
+require_once $__dir . '/HTMLPurifier/Strategy/Composite.php';
+require_once $__dir . '/HTMLPurifier/Strategy/Core.php';
+require_once $__dir . '/HTMLPurifier/Strategy/FixNesting.php';
+require_once $__dir . '/HTMLPurifier/Strategy/MakeWellFormed.php';
+require_once $__dir . '/HTMLPurifier/Strategy/RemoveForeignElements.php';
+require_once $__dir . '/HTMLPurifier/Strategy/ValidateAttributes.php';
+require_once $__dir . '/HTMLPurifier/TagTransform/Font.php';
+require_once $__dir . '/HTMLPurifier/TagTransform/Simple.php';
+require_once $__dir . '/HTMLPurifier/Token/Comment.php';
+require_once $__dir . '/HTMLPurifier/Token/Tag.php';
+require_once $__dir . '/HTMLPurifier/Token/Empty.php';
+require_once $__dir . '/HTMLPurifier/Token/End.php';
+require_once $__dir . '/HTMLPurifier/Token/Start.php';
+require_once $__dir . '/HTMLPurifier/Token/Text.php';
+require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternal.php';
+require_once $__dir . '/HTMLPurifier/URIFilter/DisableExternalResources.php';
+require_once $__dir . '/HTMLPurifier/URIFilter/DisableResources.php';
+require_once $__dir . '/HTMLPurifier/URIFilter/HostBlacklist.php';
+require_once $__dir . '/HTMLPurifier/URIFilter/MakeAbsolute.php';
+require_once $__dir . '/HTMLPurifier/URIFilter/Munge.php';
+require_once $__dir . '/HTMLPurifier/URIFilter/SafeIframe.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/data.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/file.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/ftp.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/http.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/https.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/mailto.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/news.php';
+require_once $__dir . '/HTMLPurifier/URIScheme/nntp.php';
+require_once $__dir . '/HTMLPurifier/VarParser/Flexible.php';
+require_once $__dir . '/HTMLPurifier/VarParser/Native.php';
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrCollections.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrCollections.php
new file mode 100644
index 0000000..555b86d
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrCollections.php
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * Defines common attribute collections that modules reference
+ */
+
+class HTMLPurifier_AttrCollections
+{
+
+    /**
+     * Associative array of attribute collections, indexed by name
+     */
+    public $info = array();
+
+    /**
+     * Performs all expansions on internal data for use by other inclusions
+     * It also collects all attribute collection extensions from
+     * modules
+     * @param $attr_types HTMLPurifier_AttrTypes instance
+     * @param $modules Hash array of HTMLPurifier_HTMLModule members
+     */
+    public function __construct($attr_types, $modules) {
+        // load extensions from the modules
+        foreach ($modules as $module) {
+            foreach ($module->attr_collections as $coll_i => $coll) {
+                if (!isset($this->info[$coll_i])) {
+                    $this->info[$coll_i] = array();
+                }
+                foreach ($coll as $attr_i => $attr) {
+                    if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
+                        // merge in includes
+                        $this->info[$coll_i][$attr_i] = array_merge(
+                            $this->info[$coll_i][$attr_i], $attr);
+                        continue;
+                    }
+                    $this->info[$coll_i][$attr_i] = $attr;
+                }
+            }
+        }
+        // perform internal expansions and inclusions
+        foreach ($this->info as $name => $attr) {
+            // merge attribute collections that include others
+            $this->performInclusions($this->info[$name]);
+            // replace string identifiers with actual attribute objects
+            $this->expandIdentifiers($this->info[$name], $attr_types);
+        }
+    }
+
+    /**
+     * Takes a reference to an attribute associative array and performs
+     * all inclusions specified by the zero index.
+     * @param &$attr Reference to attribute array
+     */
+    public function performInclusions(&$attr) {
+        if (!isset($attr[0])) return;
+        $merge = $attr[0];
+        $seen  = array(); // recursion guard
+        // loop through all the inclusions
+        for ($i = 0; isset($merge[$i]); $i++) {
+            if (isset($seen[$merge[$i]])) continue;
+            $seen[$merge[$i]] = true;
+            // foreach attribute of the inclusion, copy it over
+            if (!isset($this->info[$merge[$i]])) continue;
+            foreach ($this->info[$merge[$i]] as $key => $value) {
+                if (isset($attr[$key])) continue; // also catches more inclusions
+                $attr[$key] = $value;
+            }
+            if (isset($this->info[$merge[$i]][0])) {
+                // recursion
+                $merge = array_merge($merge, $this->info[$merge[$i]][0]);
+            }
+        }
+        unset($attr[0]);
+    }
+
+    /**
+     * Expands all string identifiers in an attribute array by replacing
+     * them with the appropriate values inside HTMLPurifier_AttrTypes
+     * @param &$attr Reference to attribute array
+     * @param $attr_types HTMLPurifier_AttrTypes instance
+     */
+    public function expandIdentifiers(&$attr, $attr_types) {
+
+        // because foreach will process new elements we add, make sure we
+        // skip duplicates
+        $processed = array();
+
+        foreach ($attr as $def_i => $def) {
+            // skip inclusions
+            if ($def_i === 0) continue;
+
+            if (isset($processed[$def_i])) continue;
+
+            // determine whether or not attribute is required
+            if ($required = (strpos($def_i, '*') !== false)) {
+                // rename the definition
+                unset($attr[$def_i]);
+                $def_i = trim($def_i, '*');
+                $attr[$def_i] = $def;
+            }
+
+            $processed[$def_i] = true;
+
+            // if we've already got a literal object, move on
+            if (is_object($def)) {
+                // preserve previous required
+                $attr[$def_i]->required = ($required || $attr[$def_i]->required);
+                continue;
+            }
+
+            if ($def === false) {
+                unset($attr[$def_i]);
+                continue;
+            }
+
+            if ($t = $attr_types->get($def)) {
+                $attr[$def_i] = $t;
+                $attr[$def_i]->required = $required;
+            } else {
+                unset($attr[$def_i]);
+            }
+        }
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef.php
new file mode 100644
index 0000000..b2e4f36
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * Base class for all validating attribute definitions.
+ *
+ * This family of classes forms the core for not only HTML attribute validation,
+ * but also any sort of string that needs to be validated or cleaned (which
+ * means CSS properties and composite definitions are defined here too).
+ * Besides defining (through code) what precisely makes the string valid,
+ * subclasses are also responsible for cleaning the code if possible.
+ */
+
+abstract class HTMLPurifier_AttrDef
+{
+
+    /**
+     * Tells us whether or not an HTML attribute is minimized. Has no
+     * meaning in other contexts.
+     */
+    public $minimized = false;
+
+    /**
+     * Tells us whether or not an HTML attribute is required. Has no
+     * meaning in other contexts
+     */
+    public $required = false;
+
+    /**
+     * Validates and cleans passed string according to a definition.
+     *
+     * @param $string String to be validated and cleaned.
+     * @param $config Mandatory HTMLPurifier_Config object.
+     * @param $context Mandatory HTMLPurifier_AttrContext object.
+     */
+    abstract public function validate($string, $config, $context);
+
+    /**
+     * Convenience method that parses a string as if it were CDATA.
+     *
+     * This method process a string in the manner specified at
+     * <http://www.w3.org/TR/html4/types.html#h-6.2> by removing
+     * leading and trailing whitespace, ignoring line feeds, and replacing
+     * carriage returns and tabs with spaces.  While most useful for HTML
+     * attributes specified as CDATA, it can also be applied to most CSS
+     * values.
+     *
+     * @note This method is not entirely standards compliant, as trim() removes
+     *       more types of whitespace than specified in the spec. In practice,
+     *       this is rarely a problem, as those extra characters usually have
+     *       already been removed by HTMLPurifier_Encoder.
+     *
+     * @warning This processing is inconsistent with XML's whitespace handling
+     *          as specified by section 3.3.3 and referenced XHTML 1.0 section
+     *          4.7.  However, note that we are NOT necessarily
+     *          parsing XML, thus, this behavior may still be correct. We
+     *          assume that newlines have been normalized.
+     */
+    public function parseCDATA($string) {
+        $string = trim($string);
+        $string = str_replace(array("\n", "\t", "\r"), ' ', $string);
+        return $string;
+    }
+
+    /**
+     * Factory method for creating this class from a string.
+     * @param $string String construction info
+     * @return Created AttrDef object corresponding to $string
+     */
+    public function make($string) {
+        // default implementation, return a flyweight of this object.
+        // If $string has an effect on the returned object (i.e. you
+        // need to overload this method), it is best
+        // to clone or instantiate new copies. (Instantiation is safer.)
+        return $this;
+    }
+
+    /**
+     * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work
+     * properly. THIS IS A HACK!
+     */
+    protected function mungeRgb($string) {
+        return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string);
+    }
+
+    /**
+     * Parses a possibly escaped CSS string and returns the "pure" 
+     * version of it.
+     */
+    protected function expandCSSEscape($string) {
+        // flexibly parse it
+        $ret = '';
+        for ($i = 0, $c = strlen($string); $i < $c; $i++) {
+            if ($string[$i] === '\\') {
+                $i++;
+                if ($i >= $c) {
+                    $ret .= '\\';
+                    break;
+                }
+                if (ctype_xdigit($string[$i])) {
+                    $code = $string[$i];
+                    for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) {
+                        if (!ctype_xdigit($string[$i])) break;
+                        $code .= $string[$i];
+                    }
+                    // We have to be extremely careful when adding
+                    // new characters, to make sure we're not breaking
+                    // the encoding.
+                    $char = HTMLPurifier_Encoder::unichr(hexdec($code));
+                    if (HTMLPurifier_Encoder::cleanUTF8($char) === '') continue;
+                    $ret .= $char;
+                    if ($i < $c && trim($string[$i]) !== '') $i--;
+                    continue;
+                }
+                if ($string[$i] === "\n") continue;
+            }
+            $ret .= $string[$i];
+        }
+        return $ret;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS.php
new file mode 100644
index 0000000..953e706
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * Validates the HTML attribute style, otherwise known as CSS.
+ * @note We don't implement the whole CSS specification, so it might be
+ *       difficult to reuse this component in the context of validating
+ *       actual stylesheet declarations.
+ * @note If we were really serious about validating the CSS, we would
+ *       tokenize the styles and then parse the tokens. Obviously, we
+ *       are not doing that. Doing that could seriously harm performance,
+ *       but would make these components a lot more viable for a CSS
+ *       filtering solution.
+ */
+class HTMLPurifier_AttrDef_CSS extends HTMLPurifier_AttrDef
+{
+
+    public function validate($css, $config, $context) {
+
+        $css = $this->parseCDATA($css);
+
+        $definition = $config->getCSSDefinition();
+
+        // we're going to break the spec and explode by semicolons.
+        // This is because semicolon rarely appears in escaped form
+        // Doing this is generally flaky but fast
+        // IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI
+        // for details
+
+        $declarations = explode(';', $css);
+        $propvalues = array();
+
+        /**
+         * Name of the current CSS property being validated.
+         */
+        $property = false;
+        $context->register('CurrentCSSProperty', $property);
+
+        foreach ($declarations as $declaration) {
+            if (!$declaration) continue;
+            if (!strpos($declaration, ':')) continue;
+            list($property, $value) = explode(':', $declaration, 2);
+            $property = trim($property);
+            $value    = trim($value);
+            $ok = false;
+            do {
+                if (isset($definition->info[$property])) {
+                    $ok = true;
+                    break;
+                }
+                if (ctype_lower($property)) break;
+                $property = strtolower($property);
+                if (isset($definition->info[$property])) {
+                    $ok = true;
+                    break;
+                }
+            } while(0);
+            if (!$ok) continue;
+            // inefficient call, since the validator will do this again
+            if (strtolower(trim($value)) !== 'inherit') {
+                // inherit works for everything (but only on the base property)
+                $result = $definition->info[$property]->validate(
+                    $value, $config, $context );
+            } else {
+                $result = 'inherit';
+            }
+            if ($result === false) continue;
+            $propvalues[$property] = $result;
+        }
+
+        $context->destroy('CurrentCSSProperty');
+
+        // procedure does not write the new CSS simultaneously, so it's
+        // slightly inefficient, but it's the only way of getting rid of
+        // duplicates. Perhaps config to optimize it, but not now.
+
+        $new_declarations = '';
+        foreach ($propvalues as $prop => $value) {
+            $new_declarations .= "$prop:$value;";
+        }
+
+        return $new_declarations ? $new_declarations : false;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/AlphaValue.php
new file mode 100644
index 0000000..292c040
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/AlphaValue.php
@@ -0,0 +1,21 @@
+<?php
+
+class HTMLPurifier_AttrDef_CSS_AlphaValue extends HTMLPurifier_AttrDef_CSS_Number
+{
+
+    public function __construct() {
+        parent::__construct(false); // opacity is non-negative, but we will clamp it
+    }
+
+    public function validate($number, $config, $context) {
+        $result = parent::validate($number, $config, $context);
+        if ($result === false) return $result;
+        $float = (float) $result;
+        if ($float < 0.0) $result = '0';
+        if ($float > 1.0) $result = '1';
+        return $result;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Background.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Background.php
new file mode 100644
index 0000000..e5b7438
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Background.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * Validates shorthand CSS property background.
+ * @warning Does not support url tokens that have internal spaces.
+ */
+class HTMLPurifier_AttrDef_CSS_Background extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Local copy of component validators.
+     * @note See HTMLPurifier_AttrDef_Font::$info for a similar impl.
+     */
+    protected $info;
+
+    public function __construct($config) {
+        $def = $config->getCSSDefinition();
+        $this->info['background-color'] = $def->info['background-color'];
+        $this->info['background-image'] = $def->info['background-image'];
+        $this->info['background-repeat'] = $def->info['background-repeat'];
+        $this->info['background-attachment'] = $def->info['background-attachment'];
+        $this->info['background-position'] = $def->info['background-position'];
+    }
+
+    public function validate($string, $config, $context) {
+
+        // regular pre-processing
+        $string = $this->parseCDATA($string);
+        if ($string === '') return false;
+
+        // munge rgb() decl if necessary
+        $string = $this->mungeRgb($string);
+
+        // assumes URI doesn't have spaces in it
+        $bits = explode(' ', $string); // bits to process
+
+        $caught = array();
+        $caught['color']    = false;
+        $caught['image']    = false;
+        $caught['repeat']   = false;
+        $caught['attachment'] = false;
+        $caught['position'] = false;
+
+        $i = 0; // number of catches
+        $none = false;
+
+        foreach ($bits as $bit) {
+            if ($bit === '') continue;
+            foreach ($caught as $key => $status) {
+                if ($key != 'position') {
+                    if ($status !== false) continue;
+                    $r = $this->info['background-' . $key]->validate($bit, $config, $context);
+                } else {
+                    $r = $bit;
+                }
+                if ($r === false) continue;
+                if ($key == 'position') {
+                    if ($caught[$key] === false) $caught[$key] = '';
+                    $caught[$key] .= $r . ' ';
+                } else {
+                    $caught[$key] = $r;
+                }
+                $i++;
+                break;
+            }
+        }
+
+        if (!$i) return false;
+        if ($caught['position'] !== false) {
+            $caught['position'] = $this->info['background-position']->
+                validate($caught['position'], $config, $context);
+        }
+
+        $ret = array();
+        foreach ($caught as $value) {
+            if ($value === false) continue;
+            $ret[] = $value;
+        }
+
+        if (empty($ret)) return false;
+        return implode(' ', $ret);
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php
new file mode 100644
index 0000000..fae82ea
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php
@@ -0,0 +1,133 @@
+<?php
+
+/* W3C says:
+    [ // adjective and number must be in correct order, even if
+      // you could switch them without introducing ambiguity.
+      // some browsers support that syntax
+        [
+            <percentage> | <length> | left | center | right
+        ]
+        [
+            <percentage> | <length> | top | center | bottom
+        ]?
+    ] |
+    [ // this signifies that the vertical and horizontal adjectives
+      // can be arbitrarily ordered, however, there can only be two,
+      // one of each, or none at all
+        [
+            left | center | right
+        ] ||
+        [
+            top | center | bottom
+        ]
+    ]
+    top, left = 0%
+    center, (none) = 50%
+    bottom, right = 100%
+*/
+
+/* QuirksMode says:
+    keyword + length/percentage must be ordered correctly, as per W3C
+
+    Internet Explorer and Opera, however, support arbitrary ordering. We
+    should fix it up.
+
+    Minor issue though, not strictly necessary.
+*/
+
+// control freaks may appreciate the ability to convert these to
+// percentages or something, but it's not necessary
+
+/**
+ * Validates the value of background-position.
+ */
+class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef
+{
+
+    protected $length;
+    protected $percentage;
+
+    public function __construct() {
+        $this->length     = new HTMLPurifier_AttrDef_CSS_Length();
+        $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage();
+    }
+
+    public function validate($string, $config, $context) {
+        $string = $this->parseCDATA($string);
+        $bits = explode(' ', $string);
+
+        $keywords = array();
+        $keywords['h'] = false; // left, right
+        $keywords['v'] = false; // top, bottom
+        $keywords['ch'] = false; // center (first word)
+        $keywords['cv'] = false; // center (second word)
+        $measures = array();
+
+        $i = 0;
+
+        $lookup = array(
+            'top' => 'v',
+            'bottom' => 'v',
+            'left' => 'h',
+            'right' => 'h',
+            'center' => 'c'
+        );
+
+        foreach ($bits as $bit) {
+            if ($bit === '') continue;
+
+            // test for keyword
+            $lbit = ctype_lower($bit) ? $bit : strtolower($bit);
+            if (isset($lookup[$lbit])) {
+                $status = $lookup[$lbit];
+                if ($status == 'c') {
+                    if ($i == 0) {
+                        $status = 'ch';
+                    } else {
+                        $status = 'cv';
+                    }
+                }
+                $keywords[$status] = $lbit;
+                $i++;
+            }
+
+            // test for length
+            $r = $this->length->validate($bit, $config, $context);
+            if ($r !== false) {
+                $measures[] = $r;
+                $i++;
+            }
+
+            // test for percentage
+            $r = $this->percentage->validate($bit, $config, $context);
+            if ($r !== false) {
+                $measures[] = $r;
+                $i++;
+            }
+
+        }
+
+        if (!$i) return false; // no valid values were caught
+
+        $ret = array();
+
+        // first keyword
+        if     ($keywords['h'])     $ret[] = $keywords['h'];
+        elseif ($keywords['ch']) {
+            $ret[] = $keywords['ch'];
+            $keywords['cv'] = false; // prevent re-use: center = center center
+        }
+        elseif (count($measures))   $ret[] = array_shift($measures);
+
+        if     ($keywords['v'])     $ret[] = $keywords['v'];
+        elseif ($keywords['cv'])    $ret[] = $keywords['cv'];
+        elseif (count($measures))   $ret[] = array_shift($measures);
+
+        if (empty($ret)) return false;
+        return implode(' ', $ret);
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Border.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Border.php
new file mode 100644
index 0000000..42a1d1b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Border.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Validates the border property as defined by CSS.
+ */
+class HTMLPurifier_AttrDef_CSS_Border extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Local copy of properties this property is shorthand for.
+     */
+    protected $info = array();
+
+    public function __construct($config) {
+        $def = $config->getCSSDefinition();
+        $this->info['border-width'] = $def->info['border-width'];
+        $this->info['border-style'] = $def->info['border-style'];
+        $this->info['border-top-color'] = $def->info['border-top-color'];
+    }
+
+    public function validate($string, $config, $context) {
+        $string = $this->parseCDATA($string);
+        $string = $this->mungeRgb($string);
+        $bits = explode(' ', $string);
+        $done = array(); // segments we've finished
+        $ret = ''; // return value
+        foreach ($bits as $bit) {
+            foreach ($this->info as $propname => $validator) {
+                if (isset($done[$propname])) continue;
+                $r = $validator->validate($bit, $config, $context);
+                if ($r !== false) {
+                    $ret .= $r . ' ';
+                    $done[$propname] = true;
+                    break;
+                }
+            }
+        }
+        return rtrim($ret);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Color.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Color.php
new file mode 100644
index 0000000..07f95a6
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Color.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * Validates Color as defined by CSS.
+ */
+class HTMLPurifier_AttrDef_CSS_Color extends HTMLPurifier_AttrDef
+{
+
+    public function validate($color, $config, $context) {
+
+        static $colors = null;
+        if ($colors === null) $colors = $config->get('Core.ColorKeywords');
+
+        $color = trim($color);
+        if ($color === '') return false;
+
+        $lower = strtolower($color);
+        if (isset($colors[$lower])) return $colors[$lower];
+
+        if (strpos($color, 'rgb(') !== false) {
+            // rgb literal handling
+            $length = strlen($color);
+            if (strpos($color, ')') !== $length - 1) return false;
+            $triad = substr($color, 4, $length - 4 - 1);
+            $parts = explode(',', $triad);
+            if (count($parts) !== 3) return false;
+            $type = false; // to ensure that they're all the same type
+            $new_parts = array();
+            foreach ($parts as $part) {
+                $part = trim($part);
+                if ($part === '') return false;
+                $length = strlen($part);
+                if ($part[$length - 1] === '%') {
+                    // handle percents
+                    if (!$type) {
+                        $type = 'percentage';
+                    } elseif ($type !== 'percentage') {
+                        return false;
+                    }
+                    $num = (float) substr($part, 0, $length - 1);
+                    if ($num < 0) $num = 0;
+                    if ($num > 100) $num = 100;
+                    $new_parts[] = "$num%";
+                } else {
+                    // handle integers
+                    if (!$type) {
+                        $type = 'integer';
+                    } elseif ($type !== 'integer') {
+                        return false;
+                    }
+                    $num = (int) $part;
+                    if ($num < 0) $num = 0;
+                    if ($num > 255) $num = 255;
+                    $new_parts[] = (string) $num;
+                }
+            }
+            $new_triad = implode(',', $new_parts);
+            $color = "rgb($new_triad)";
+        } else {
+            // hexadecimal handling
+            if ($color[0] === '#') {
+                $hex = substr($color, 1);
+            } else {
+                $hex = $color;
+                $color = '#' . $color;
+            }
+            $length = strlen($hex);
+            if ($length !== 3 && $length !== 6) return false;
+            if (!ctype_xdigit($hex)) return false;
+        }
+
+        return $color;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Composite.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Composite.php
new file mode 100644
index 0000000..de1289c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Composite.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Allows multiple validators to attempt to validate attribute.
+ *
+ * Composite is just what it sounds like: a composite of many validators.
+ * This means that multiple HTMLPurifier_AttrDef objects will have a whack
+ * at the string.  If one of them passes, that's what is returned.  This is
+ * especially useful for CSS values, which often are a choice between
+ * an enumerated set of predefined values or a flexible data type.
+ */
+class HTMLPurifier_AttrDef_CSS_Composite extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * List of HTMLPurifier_AttrDef objects that may process strings
+     * @todo Make protected
+     */
+    public $defs;
+
+    /**
+     * @param $defs List of HTMLPurifier_AttrDef objects
+     */
+    public function __construct($defs) {
+        $this->defs = $defs;
+    }
+
+    public function validate($string, $config, $context) {
+        foreach ($this->defs as $i => $def) {
+            $result = $this->defs[$i]->validate($string, $config, $context);
+            if ($result !== false) return $result;
+        }
+        return false;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php
new file mode 100644
index 0000000..6599c5b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Decorator which enables CSS properties to be disabled for specific elements.
+ */
+class HTMLPurifier_AttrDef_CSS_DenyElementDecorator extends HTMLPurifier_AttrDef
+{
+    public $def, $element;
+
+    /**
+     * @param $def Definition to wrap
+     * @param $element Element to deny
+     */
+    public function __construct($def, $element) {
+        $this->def = $def;
+        $this->element = $element;
+    }
+    /**
+     * Checks if CurrentToken is set and equal to $this->element
+     */
+    public function validate($string, $config, $context) {
+        $token = $context->get('CurrentToken', true);
+        if ($token && $token->name == $this->element) return false;
+        return $this->def->validate($string, $config, $context);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Filter.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Filter.php
new file mode 100644
index 0000000..147894b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Filter.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Microsoft's proprietary filter: CSS property
+ * @note Currently supports the alpha filter. In the future, this will
+ *       probably need an extensible framework
+ */
+class HTMLPurifier_AttrDef_CSS_Filter extends HTMLPurifier_AttrDef
+{
+
+    protected $intValidator;
+
+    public function __construct() {
+        $this->intValidator = new HTMLPurifier_AttrDef_Integer();
+    }
+
+    public function validate($value, $config, $context) {
+        $value = $this->parseCDATA($value);
+        if ($value === 'none') return $value;
+        // if we looped this we could support multiple filters
+        $function_length = strcspn($value, '(');
+        $function = trim(substr($value, 0, $function_length));
+        if ($function !== 'alpha' &&
+            $function !== 'Alpha' &&
+            $function !== 'progid:DXImageTransform.Microsoft.Alpha'
+            ) return false;
+        $cursor = $function_length + 1;
+        $parameters_length = strcspn($value, ')', $cursor);
+        $parameters = substr($value, $cursor, $parameters_length);
+        $params = explode(',', $parameters);
+        $ret_params = array();
+        $lookup = array();
+        foreach ($params as $param) {
+            list($key, $value) = explode('=', $param);
+            $key   = trim($key);
+            $value = trim($value);
+            if (isset($lookup[$key])) continue;
+            if ($key !== 'opacity') continue;
+            $value = $this->intValidator->validate($value, $config, $context);
+            if ($value === false) continue;
+            $int = (int) $value;
+            if ($int > 100) $value = '100';
+            if ($int < 0) $value = '0';
+            $ret_params[] = "$key=$value";
+            $lookup[$key] = true;
+        }
+        $ret_parameters = implode(',', $ret_params);
+        $ret_function = "$function($ret_parameters)";
+        return $ret_function;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Font.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Font.php
new file mode 100644
index 0000000..699ee0b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Font.php
@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * Validates shorthand CSS property font.
+ */
+class HTMLPurifier_AttrDef_CSS_Font extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Local copy of component validators.
+     *
+     * @note If we moved specific CSS property definitions to their own
+     *       classes instead of having them be assembled at run time by
+     *       CSSDefinition, this wouldn't be necessary.  We'd instantiate
+     *       our own copies.
+     */
+    protected $info = array();
+
+    public function __construct($config) {
+        $def = $config->getCSSDefinition();
+        $this->info['font-style']   = $def->info['font-style'];
+        $this->info['font-variant'] = $def->info['font-variant'];
+        $this->info['font-weight']  = $def->info['font-weight'];
+        $this->info['font-size']    = $def->info['font-size'];
+        $this->info['line-height']  = $def->info['line-height'];
+        $this->info['font-family']  = $def->info['font-family'];
+    }
+
+    public function validate($string, $config, $context) {
+
+        static $system_fonts = array(
+            'caption' => true,
+            'icon' => true,
+            'menu' => true,
+            'message-box' => true,
+            'small-caption' => true,
+            'status-bar' => true
+        );
+
+        // regular pre-processing
+        $string = $this->parseCDATA($string);
+        if ($string === '') return false;
+
+        // check if it's one of the keywords
+        $lowercase_string = strtolower($string);
+        if (isset($system_fonts[$lowercase_string])) {
+            return $lowercase_string;
+        }
+
+        $bits = explode(' ', $string); // bits to process
+        $stage = 0; // this indicates what we're looking for
+        $caught = array(); // which stage 0 properties have we caught?
+        $stage_1 = array('font-style', 'font-variant', 'font-weight');
+        $final = ''; // output
+
+        for ($i = 0, $size = count($bits); $i < $size; $i++) {
+            if ($bits[$i] === '') continue;
+            switch ($stage) {
+
+                // attempting to catch font-style, font-variant or font-weight
+                case 0:
+                    foreach ($stage_1 as $validator_name) {
+                        if (isset($caught[$validator_name])) continue;
+                        $r = $this->info[$validator_name]->validate(
+                                                $bits[$i], $config, $context);
+                        if ($r !== false) {
+                            $final .= $r . ' ';
+                            $caught[$validator_name] = true;
+                            break;
+                        }
+                    }
+                    // all three caught, continue on
+                    if (count($caught) >= 3) $stage = 1;
+                    if ($r !== false) break;
+
+                // attempting to catch font-size and perhaps line-height
+                case 1:
+                    $found_slash = false;
+                    if (strpos($bits[$i], '/') !== false) {
+                        list($font_size, $line_height) =
+                                                    explode('/', $bits[$i]);
+                        if ($line_height === '') {
+                            // ooh, there's a space after the slash!
+                            $line_height = false;
+                            $found_slash = true;
+                        }
+                    } else {
+                        $font_size = $bits[$i];
+                        $line_height = false;
+                    }
+                    $r = $this->info['font-size']->validate(
+                                              $font_size, $config, $context);
+                    if ($r !== false) {
+                        $final .= $r;
+                        // attempt to catch line-height
+                        if ($line_height === false) {
+                            // we need to scroll forward
+                            for ($j = $i + 1; $j < $size; $j++) {
+                                if ($bits[$j] === '') continue;
+                                if ($bits[$j] === '/') {
+                                    if ($found_slash) {
+                                        return false;
+                                    } else {
+                                        $found_slash = true;
+                                        continue;
+                                    }
+                                }
+                                $line_height = $bits[$j];
+                                break;
+                            }
+                        } else {
+                            // slash already found
+                            $found_slash = true;
+                            $j = $i;
+                        }
+                        if ($found_slash) {
+                            $i = $j;
+                            $r = $this->info['line-height']->validate(
+                                              $line_height, $config, $context);
+                            if ($r !== false) {
+                                $final .= '/' . $r;
+                            }
+                        }
+                        $final .= ' ';
+                        $stage = 2;
+                        break;
+                    }
+                    return false;
+
+                // attempting to catch font-family
+                case 2:
+                    $font_family =
+                        implode(' ', array_slice($bits, $i, $size - $i));
+                    $r = $this->info['font-family']->validate(
+                                              $font_family, $config, $context);
+                    if ($r !== false) {
+                        $final .= $r . ' ';
+                        // processing completed successfully
+                        return rtrim($final);
+                    }
+                    return false;
+            }
+        }
+        return false;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php
new file mode 100644
index 0000000..98dcf82
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/FontFamily.php
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * Validates a font family list according to CSS spec
+ */
+class HTMLPurifier_AttrDef_CSS_FontFamily extends HTMLPurifier_AttrDef
+{
+
+    protected $mask = null;
+
+    public function __construct() {
+        $this->mask = '_- ';
+        for ($c = 'a'; $c <= 'z'; $c++) $this->mask .= $c;
+        for ($c = 'A'; $c <= 'Z'; $c++) $this->mask .= $c;
+        for ($c = '0'; $c <= '9'; $c++) $this->mask .= $c; // cast-y, but should be fine
+        // special bytes used by UTF-8
+        for ($i = 0x80; $i <= 0xFF; $i++) {
+            // We don't bother excluding invalid bytes in this range,
+            // because the our restriction of well-formed UTF-8 will
+            // prevent these from ever occurring.
+            $this->mask .= chr($i);
+        }
+
+        /*
+            PHP's internal strcspn implementation is
+            O(length of string * length of mask), making it inefficient
+            for large masks.  However, it's still faster than
+            preg_match 8)
+          for (p = s1;;) {
+            spanp = s2;
+            do {
+              if (*spanp == c || p == s1_end) {
+                return p - s1;
+              }
+            } while (spanp++ < (s2_end - 1));
+            c = *++p;
+          }
+         */
+        // possible optimization: invert the mask.
+    }
+
+    public function validate($string, $config, $context) {
+        static $generic_names = array(
+            'serif' => true,
+            'sans-serif' => true,
+            'monospace' => true,
+            'fantasy' => true,
+            'cursive' => true
+        );
+        $allowed_fonts = $config->get('CSS.AllowedFonts');
+
+        // assume that no font names contain commas in them
+        $fonts = explode(',', $string);
+        $final = '';
+        foreach($fonts as $font) {
+            $font = trim($font);
+            if ($font === '') continue;
+            // match a generic name
+            if (isset($generic_names[$font])) {
+                if ($allowed_fonts === null || isset($allowed_fonts[$font])) {
+                    $final .= $font . ', ';
+                }
+                continue;
+            }
+            // match a quoted name
+            if ($font[0] === '"' || $font[0] === "'") {
+                $length = strlen($font);
+                if ($length <= 2) continue;
+                $quote = $font[0];
+                if ($font[$length - 1] !== $quote) continue;
+                $font = substr($font, 1, $length - 2);
+            }
+
+            $font = $this->expandCSSEscape($font);
+
+            // $font is a pure representation of the font name
+
+            if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) {
+                continue;
+            }
+
+            if (ctype_alnum($font) && $font !== '') {
+                // very simple font, allow it in unharmed
+                $final .= $font . ', ';
+                continue;
+            }
+
+            // bugger out on whitespace.  form feed (0C) really
+            // shouldn't show up regardless
+            $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font);
+
+            // Here, there are various classes of characters which need
+            // to be treated differently:
+            //  - Alphanumeric characters are essentially safe.  We
+            //    handled these above.
+            //  - Spaces require quoting, though most parsers will do
+            //    the right thing if there aren't any characters that
+            //    can be misinterpreted
+            //  - Dashes rarely occur, but they fairly unproblematic
+            //    for parsing/rendering purposes.
+            //  The above characters cover the majority of Western font
+            //  names.
+            //  - Arbitrary Unicode characters not in ASCII.  Because
+            //    most parsers give little thought to Unicode, treatment
+            //    of these codepoints is basically uniform, even for
+            //    punctuation-like codepoints.  These characters can
+            //    show up in non-Western pages and are supported by most
+            //    major browsers, for example: "MS 明朝" is a
+            //    legitimate font-name
+            //    <http://ja.wikipedia.org/wiki/MS_明朝>.  See
+            //    the CSS3 spec for more examples:
+            //    <http://www.w3.org/TR/2011/WD-css3-fonts-20110324/localizedfamilynames.png>
+            //    You can see live samples of these on the Internet:
+            //    <http://www.google.co.jp/search?q=font-family+MS+明朝|ゴシック>
+            //    However, most of these fonts have ASCII equivalents:
+            //    for example, 'MS Mincho', and it's considered
+            //    professional to use ASCII font names instead of
+            //    Unicode font names.  Thanks Takeshi Terada for
+            //    providing this information.
+            //  The following characters, to my knowledge, have not been
+            //  used to name font names.
+            //  - Single quote.  While theoretically you might find a
+            //    font name that has a single quote in its name (serving
+            //    as an apostrophe, e.g. Dave's Scribble), I haven't
+            //    been able to find any actual examples of this.
+            //    Internet Explorer's cssText translation (which I
+            //    believe is invoked by innerHTML) normalizes any
+            //    quoting to single quotes, and fails to escape single
+            //    quotes.  (Note that this is not IE's behavior for all
+            //    CSS properties, just some sort of special casing for
+            //    font-family).  So a single quote *cannot* be used
+            //    safely in the font-family context if there will be an
+            //    innerHTML/cssText translation.  Note that Firefox 3.x
+            //    does this too.
+            //  - Double quote.  In IE, these get normalized to
+            //    single-quotes, no matter what the encoding.  (Fun
+            //    fact, in IE8, the 'content' CSS property gained
+            //    support, where they special cased to preserve encoded
+            //    double quotes, but still translate unadorned double
+            //    quotes into single quotes.)  So, because their
+            //    fixpoint behavior is identical to single quotes, they
+            //    cannot be allowed either.  Firefox 3.x displays
+            //    single-quote style behavior.
+            //  - Backslashes are reduced by one (so \\ -> \) every
+            //    iteration, so they cannot be used safely.  This shows
+            //    up in IE7, IE8 and FF3
+            //  - Semicolons, commas and backticks are handled properly.
+            //  - The rest of the ASCII punctuation is handled properly.
+            // We haven't checked what browsers do to unadorned
+            // versions, but this is not important as long as the
+            // browser doesn't /remove/ surrounding quotes (as IE does
+            // for HTML).
+            //
+            // With these results in hand, we conclude that there are
+            // various levels of safety:
+            //  - Paranoid: alphanumeric, spaces and dashes(?)
+            //  - International: Paranoid + non-ASCII Unicode
+            //  - Edgy: Everything except quotes, backslashes
+            //  - NoJS: Standards compliance, e.g. sod IE. Note that
+            //    with some judicious character escaping (since certain
+            //    types of escaping doesn't work) this is theoretically
+            //    OK as long as innerHTML/cssText is not called.
+            // We believe that international is a reasonable default
+            // (that we will implement now), and once we do more
+            // extensive research, we may feel comfortable with dropping
+            // it down to edgy.
+
+            // Edgy: alphanumeric, spaces, dashes, underscores and Unicode.  Use of
+            // str(c)spn assumes that the string was already well formed
+            // Unicode (which of course it is).
+            if (strspn($font, $this->mask) !== strlen($font)) {
+                continue;
+            }
+
+            // Historical:
+            // In the absence of innerHTML/cssText, these ugly
+            // transforms don't pose a security risk (as \\ and \"
+            // might--these escapes are not supported by most browsers).
+            // We could try to be clever and use single-quote wrapping
+            // when there is a double quote present, but I have choosen
+            // not to implement that.  (NOTE: you can reduce the amount
+            // of escapes by one depending on what quoting style you use)
+            // $font = str_replace('\\', '\\5C ', $font);
+            // $font = str_replace('"',  '\\22 ', $font);
+            // $font = str_replace("'",  '\\27 ', $font);
+
+            // font possibly with spaces, requires quoting
+            $final .= "'$font', ";
+        }
+        $final = rtrim($final, ', ');
+        if ($final === '') return false;
+        return $final;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Ident.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Ident.php
new file mode 100644
index 0000000..779794a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Ident.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Validates based on {ident} CSS grammar production
+ */
+class HTMLPurifier_AttrDef_CSS_Ident extends HTMLPurifier_AttrDef
+{
+
+    public function validate($string, $config, $context) {
+
+        $string = trim($string);
+
+        // early abort: '' and '0' (strings that convert to false) are invalid
+        if (!$string) return false;
+
+        $pattern = '/^(-?[A-Za-z_][A-Za-z_\-0-9]*)$/';
+        if (!preg_match($pattern, $string)) return false;
+        return $string;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php
new file mode 100644
index 0000000..4e6b35e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/ImportantDecorator.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Decorator which enables !important to be used in CSS values.
+ */
+class HTMLPurifier_AttrDef_CSS_ImportantDecorator extends HTMLPurifier_AttrDef
+{
+    public $def, $allow;
+
+    /**
+     * @param $def Definition to wrap
+     * @param $allow Whether or not to allow !important
+     */
+    public function __construct($def, $allow = false) {
+        $this->def = $def;
+        $this->allow = $allow;
+    }
+    /**
+     * Intercepts and removes !important if necessary
+     */
+    public function validate($string, $config, $context) {
+        // test for ! and important tokens
+        $string = trim($string);
+        $is_important = false;
+        // :TODO: optimization: test directly for !important and ! important
+        if (strlen($string) >= 9 && substr($string, -9) === 'important') {
+            $temp = rtrim(substr($string, 0, -9));
+            // use a temp, because we might want to restore important
+            if (strlen($temp) >= 1 && substr($temp, -1) === '!') {
+                $string = rtrim(substr($temp, 0, -1));
+                $is_important = true;
+            }
+        }
+        $string = $this->def->validate($string, $config, $context);
+        if ($this->allow && $is_important) $string .= ' !important';
+        return $string;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php
new file mode 100644
index 0000000..a07ec58
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Length.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Represents a Length as defined by CSS.
+ */
+class HTMLPurifier_AttrDef_CSS_Length extends HTMLPurifier_AttrDef
+{
+
+    protected $min, $max;
+
+    /**
+     * @param HTMLPurifier_Length $max Minimum length, or null for no bound. String is also acceptable.
+     * @param HTMLPurifier_Length $max Maximum length, or null for no bound. String is also acceptable.
+     */
+    public function __construct($min = null, $max = null) {
+        $this->min = $min !== null ? HTMLPurifier_Length::make($min) : null;
+        $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null;
+    }
+
+    public function validate($string, $config, $context) {
+        $string = $this->parseCDATA($string);
+
+        // Optimizations
+        if ($string === '') return false;
+        if ($string === '0') return '0';
+        if (strlen($string) === 1) return false;
+
+        $length = HTMLPurifier_Length::make($string);
+        if (!$length->isValid()) return false;
+
+        if ($this->min) {
+            $c = $length->compareTo($this->min);
+            if ($c === false) return false;
+            if ($c < 0) return false;
+        }
+        if ($this->max) {
+            $c = $length->compareTo($this->max);
+            if ($c === false) return false;
+            if ($c > 0) return false;
+        }
+
+        return $length->toString();
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/ListStyle.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/ListStyle.php
new file mode 100644
index 0000000..4406868
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/ListStyle.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * Validates shorthand CSS property list-style.
+ * @warning Does not support url tokens that have internal spaces.
+ */
+class HTMLPurifier_AttrDef_CSS_ListStyle extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Local copy of component validators.
+     * @note See HTMLPurifier_AttrDef_CSS_Font::$info for a similar impl.
+     */
+    protected $info;
+
+    public function __construct($config) {
+        $def = $config->getCSSDefinition();
+        $this->info['list-style-type']     = $def->info['list-style-type'];
+        $this->info['list-style-position'] = $def->info['list-style-position'];
+        $this->info['list-style-image'] = $def->info['list-style-image'];
+    }
+
+    public function validate($string, $config, $context) {
+
+        // regular pre-processing
+        $string = $this->parseCDATA($string);
+        if ($string === '') return false;
+
+        // assumes URI doesn't have spaces in it
+        $bits = explode(' ', strtolower($string)); // bits to process
+
+        $caught = array();
+        $caught['type']     = false;
+        $caught['position'] = false;
+        $caught['image']    = false;
+
+        $i = 0; // number of catches
+        $none = false;
+
+        foreach ($bits as $bit) {
+            if ($i >= 3) return; // optimization bit
+            if ($bit === '') continue;
+            foreach ($caught as $key => $status) {
+                if ($status !== false) continue;
+                $r = $this->info['list-style-' . $key]->validate($bit, $config, $context);
+                if ($r === false) continue;
+                if ($r === 'none') {
+                    if ($none) continue;
+                    else $none = true;
+                    if ($key == 'image') continue;
+                }
+                $caught[$key] = $r;
+                $i++;
+                break;
+            }
+        }
+
+        if (!$i) return false;
+
+        $ret = array();
+
+        // construct type
+        if ($caught['type']) $ret[] = $caught['type'];
+
+        // construct image
+        if ($caught['image']) $ret[] = $caught['image'];
+
+        // construct position
+        if ($caught['position']) $ret[] = $caught['position'];
+
+        if (empty($ret)) return false;
+        return implode(' ', $ret);
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Multiple.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Multiple.php
new file mode 100644
index 0000000..4d62a40
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Multiple.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Framework class for strings that involve multiple values.
+ *
+ * Certain CSS properties such as border-width and margin allow multiple
+ * lengths to be specified.  This class can take a vanilla border-width
+ * definition and multiply it, usually into a max of four.
+ *
+ * @note Even though the CSS specification isn't clear about it, inherit
+ *       can only be used alone: it will never manifest as part of a multi
+ *       shorthand declaration.  Thus, this class does not allow inherit.
+ */
+class HTMLPurifier_AttrDef_CSS_Multiple extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Instance of component definition to defer validation to.
+     * @todo Make protected
+     */
+    public $single;
+
+    /**
+     * Max number of values allowed.
+     * @todo Make protected
+     */
+    public $max;
+
+    /**
+     * @param $single HTMLPurifier_AttrDef to multiply
+     * @param $max Max number of values allowed (usually four)
+     */
+    public function __construct($single, $max = 4) {
+        $this->single = $single;
+        $this->max = $max;
+    }
+
+    public function validate($string, $config, $context) {
+        $string = $this->parseCDATA($string);
+        if ($string === '') return false;
+        $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n
+        $length = count($parts);
+        $final = '';
+        for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) {
+            if (ctype_space($parts[$i])) continue;
+            $result = $this->single->validate($parts[$i], $config, $context);
+            if ($result !== false) {
+                $final .= $result . ' ';
+                $num++;
+            }
+        }
+        if ($final === '') return false;
+        return rtrim($final);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php
new file mode 100644
index 0000000..3f99e12
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Number.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * Validates a number as defined by the CSS spec.
+ */
+class HTMLPurifier_AttrDef_CSS_Number extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Bool indicating whether or not only positive values allowed.
+     */
+    protected $non_negative = false;
+
+    /**
+     * @param $non_negative Bool indicating whether negatives are forbidden
+     */
+    public function __construct($non_negative = false) {
+        $this->non_negative = $non_negative;
+    }
+
+    /**
+     * @warning Some contexts do not pass $config, $context. These
+     *          variables should not be used without checking HTMLPurifier_Length
+     */
+    public function validate($number, $config, $context) {
+
+        $number = $this->parseCDATA($number);
+
+        if ($number === '') return false;
+        if ($number === '0') return '0';
+
+        $sign = '';
+        switch ($number[0]) {
+            case '-':
+                if ($this->non_negative) return false;
+                $sign = '-';
+            case '+':
+                $number = substr($number, 1);
+        }
+
+        if (ctype_digit($number)) {
+            $number = ltrim($number, '0');
+            return $number ? $sign . $number : '0';
+        }
+
+        // Period is the only non-numeric character allowed
+        if (strpos($number, '.') === false) return false;
+
+        list($left, $right) = explode('.', $number, 2);
+
+        if ($left === '' && $right === '') return false;
+        if ($left !== '' && !ctype_digit($left)) return false;
+
+        $left  = ltrim($left,  '0');
+        $right = rtrim($right, '0');
+
+        if ($right === '') {
+            return $left ? $sign . $left : '0';
+        } elseif (!ctype_digit($right)) {
+            return false;
+        }
+
+        return $sign . $left . '.' . $right;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Percentage.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Percentage.php
new file mode 100644
index 0000000..c34b8fc
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/Percentage.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Validates a Percentage as defined by the CSS spec.
+ */
+class HTMLPurifier_AttrDef_CSS_Percentage extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Instance of HTMLPurifier_AttrDef_CSS_Number to defer number validation
+     */
+    protected $number_def;
+
+    /**
+     * @param Bool indicating whether to forbid negative values
+     */
+    public function __construct($non_negative = false) {
+        $this->number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative);
+    }
+
+    public function validate($string, $config, $context) {
+
+        $string = $this->parseCDATA($string);
+
+        if ($string === '') return false;
+        $length = strlen($string);
+        if ($length === 1) return false;
+        if ($string[$length - 1] !== '%') return false;
+
+        $number = substr($string, 0, $length - 1);
+        $number = $this->number_def->validate($number, $config, $context);
+
+        if ($number === false) return false;
+        return "$number%";
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php
new file mode 100644
index 0000000..772c922
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/TextDecoration.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Validates the value for the CSS property text-decoration
+ * @note This class could be generalized into a version that acts sort of
+ *       like Enum except you can compound the allowed values.
+ */
+class HTMLPurifier_AttrDef_CSS_TextDecoration extends HTMLPurifier_AttrDef
+{
+
+    public function validate($string, $config, $context) {
+
+        static $allowed_values = array(
+            'line-through' => true,
+            'overline' => true,
+            'underline' => true,
+        );
+
+        $string = strtolower($this->parseCDATA($string));
+
+        if ($string === 'none') return $string;
+
+        $parts = explode(' ', $string);
+        $final = '';
+        foreach ($parts as $part) {
+            if (isset($allowed_values[$part])) {
+                $final .= $part . ' ';
+            }
+        }
+        $final = rtrim($final);
+        if ($final === '') return false;
+        return $final;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php
new file mode 100644
index 0000000..c2f767e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/CSS/URI.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Validates a URI in CSS syntax, which uses url('http://example.com')
+ * @note While theoretically speaking a URI in a CSS document could
+ *       be non-embedded, as of CSS2 there is no such usage so we're
+ *       generalizing it. This may need to be changed in the future.
+ * @warning Since HTMLPurifier_AttrDef_CSS blindly uses semicolons as
+ *          the separator, you cannot put a literal semicolon in
+ *          in the URI. Try percent encoding it, in that case.
+ */
+class HTMLPurifier_AttrDef_CSS_URI extends HTMLPurifier_AttrDef_URI
+{
+
+    public function __construct() {
+        parent::__construct(true); // always embedded
+    }
+
+    public function validate($uri_string, $config, $context) {
+        // parse the URI out of the string and then pass it onto
+        // the parent object
+
+        $uri_string = $this->parseCDATA($uri_string);
+        if (strpos($uri_string, 'url(') !== 0) return false;
+        $uri_string = substr($uri_string, 4);
+        $new_length = strlen($uri_string) - 1;
+        if ($uri_string[$new_length] != ')') return false;
+        $uri = trim(substr($uri_string, 0, $new_length));
+
+        if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) {
+            $quote = $uri[0];
+            $new_length = strlen($uri) - 1;
+            if ($uri[$new_length] !== $quote) return false;
+            $uri = substr($uri, 1, $new_length - 1);
+        }
+
+        $uri = $this->expandCSSEscape($uri);
+
+        $result = parent::validate($uri, $config, $context);
+
+        if ($result === false) return false;
+
+        // extra sanity check; should have been done by URI
+        $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result);
+
+        // suspicious characters are ()'; we're going to percent encode
+        // them for safety.
+        $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result);
+
+        // there's an extra bug where ampersands lose their escaping on
+        // an innerHTML cycle, so a very unlucky query parameter could
+        // then change the meaning of the URL.  Unfortunately, there's
+        // not much we can do about that...
+
+        return "url(\"$result\")";
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Clone.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Clone.php
new file mode 100644
index 0000000..ce68dbd
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Clone.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Dummy AttrDef that mimics another AttrDef, BUT it generates clones
+ * with make.
+ */
+class HTMLPurifier_AttrDef_Clone extends HTMLPurifier_AttrDef
+{
+    /**
+     * What we're cloning
+     */
+    protected $clone;
+
+    public function __construct($clone) {
+        $this->clone = $clone;
+    }
+
+    public function validate($v, $config, $context) {
+        return $this->clone->validate($v, $config, $context);
+    }
+
+    public function make($string) {
+        return clone $this->clone;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Enum.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Enum.php
new file mode 100644
index 0000000..5d603eb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Enum.php
@@ -0,0 +1,65 @@
+<?php
+
+// Enum = Enumerated
+/**
+ * Validates a keyword against a list of valid values.
+ * @warning The case-insensitive compare of this function uses PHP's
+ *          built-in strtolower and ctype_lower functions, which may
+ *          cause problems with international comparisons
+ */
+class HTMLPurifier_AttrDef_Enum extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Lookup table of valid values.
+     * @todo Make protected
+     */
+    public $valid_values   = array();
+
+    /**
+     * Bool indicating whether or not enumeration is case sensitive.
+     * @note In general this is always case insensitive.
+     */
+    protected $case_sensitive = false; // values according to W3C spec
+
+    /**
+     * @param $valid_values List of valid values
+     * @param $case_sensitive Bool indicating whether or not case sensitive
+     */
+    public function __construct(
+        $valid_values = array(), $case_sensitive = false
+    ) {
+        $this->valid_values = array_flip($valid_values);
+        $this->case_sensitive = $case_sensitive;
+    }
+
+    public function validate($string, $config, $context) {
+        $string = trim($string);
+        if (!$this->case_sensitive) {
+            // we may want to do full case-insensitive libraries
+            $string = ctype_lower($string) ? $string : strtolower($string);
+        }
+        $result = isset($this->valid_values[$string]);
+
+        return $result ? $string : false;
+    }
+
+    /**
+     * @param $string In form of comma-delimited list of case-insensitive
+     *      valid values. Example: "foo,bar,baz". Prepend "s:" to make
+     *      case sensitive
+     */
+    public function make($string) {
+        if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') {
+            $string = substr($string, 2);
+            $sensitive = true;
+        } else {
+            $sensitive = false;
+        }
+        $values = explode(',', $string);
+        return new HTMLPurifier_AttrDef_Enum($values, $sensitive);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Bool.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Bool.php
new file mode 100644
index 0000000..e06987e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Bool.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Validates a boolean attribute
+ */
+class HTMLPurifier_AttrDef_HTML_Bool extends HTMLPurifier_AttrDef
+{
+
+    protected $name;
+    public $minimized = true;
+
+    public function __construct($name = false) {$this->name = $name;}
+
+    public function validate($string, $config, $context) {
+        if (empty($string)) return false;
+        return $this->name;
+    }
+
+    /**
+     * @param $string Name of attribute
+     */
+    public function make($string) {
+        return new HTMLPurifier_AttrDef_HTML_Bool($string);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Class.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Class.php
new file mode 100644
index 0000000..370068d
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Class.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Implements special behavior for class attribute (normally NMTOKENS)
+ */
+class HTMLPurifier_AttrDef_HTML_Class extends HTMLPurifier_AttrDef_HTML_Nmtokens
+{
+    protected function split($string, $config, $context) {
+        // really, this twiddle should be lazy loaded
+        $name = $config->getDefinition('HTML')->doctype->name;
+        if ($name == "XHTML 1.1" || $name == "XHTML 2.0") {
+            return parent::split($string, $config, $context);
+        } else {
+            return preg_split('/\s+/', $string);
+        }
+    }
+    protected function filter($tokens, $config, $context) {
+        $allowed = $config->get('Attr.AllowedClasses');
+        $forbidden = $config->get('Attr.ForbiddenClasses');
+        $ret = array();
+        foreach ($tokens as $token) {
+            if (
+                ($allowed === null || isset($allowed[$token])) &&
+                !isset($forbidden[$token]) &&
+                // We need this O(n) check because of PHP's array
+                // implementation that casts -0 to 0.
+                !in_array($token, $ret, true)
+            ) {
+                $ret[] = $token;
+            }
+        }
+        return $ret;
+    }
+}
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Color.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Color.php
new file mode 100644
index 0000000..e02abb0
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Color.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Validates a color according to the HTML spec.
+ */
+class HTMLPurifier_AttrDef_HTML_Color extends HTMLPurifier_AttrDef
+{
+
+    public function validate($string, $config, $context) {
+
+        static $colors = null;
+        if ($colors === null) $colors = $config->get('Core.ColorKeywords');
+
+        $string = trim($string);
+
+        if (empty($string)) return false;
+        $lower = strtolower($string);
+        if (isset($colors[$lower])) return $colors[$lower];
+        if ($string[0] === '#') $hex = substr($string, 1);
+        else $hex = $string;
+
+        $length = strlen($hex);
+        if ($length !== 3 && $length !== 6) return false;
+        if (!ctype_xdigit($hex)) return false;
+        if ($length === 3) $hex = $hex[0].$hex[0].$hex[1].$hex[1].$hex[2].$hex[2];
+
+        return "#$hex";
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/FrameTarget.php
new file mode 100644
index 0000000..ae6ea7c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/FrameTarget.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Special-case enum attribute definition that lazy loads allowed frame targets
+ */
+class HTMLPurifier_AttrDef_HTML_FrameTarget extends HTMLPurifier_AttrDef_Enum
+{
+
+    public $valid_values = false; // uninitialized value
+    protected $case_sensitive = false;
+
+    public function __construct() {}
+
+    public function validate($string, $config, $context) {
+        if ($this->valid_values === false) $this->valid_values = $config->get('Attr.AllowedFrameTargets');
+        return parent::validate($string, $config, $context);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/ID.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/ID.php
new file mode 100644
index 0000000..0015fa1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/ID.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * Validates the HTML attribute ID.
+ * @warning Even though this is the id processor, it
+ *          will ignore the directive Attr:IDBlacklist, since it will only
+ *          go according to the ID accumulator. Since the accumulator is
+ *          automatically generated, it will have already absorbed the
+ *          blacklist. If you're hacking around, make sure you use load()!
+ */
+
+class HTMLPurifier_AttrDef_HTML_ID extends HTMLPurifier_AttrDef
+{
+
+    // selector is NOT a valid thing to use for IDREFs, because IDREFs
+    // *must* target IDs that exist, whereas selector #ids do not.
+
+    /**
+     * Determines whether or not we're validating an ID in a CSS
+     * selector context.
+     */
+    protected $selector;
+
+    public function __construct($selector = false) {
+        $this->selector = $selector;
+    }
+
+    public function validate($id, $config, $context) {
+
+        if (!$this->selector && !$config->get('Attr.EnableID')) return false;
+
+        $id = trim($id); // trim it first
+
+        if ($id === '') return false;
+
+        $prefix = $config->get('Attr.IDPrefix');
+        if ($prefix !== '') {
+            $prefix .= $config->get('Attr.IDPrefixLocal');
+            // prevent re-appending the prefix
+            if (strpos($id, $prefix) !== 0) $id = $prefix . $id;
+        } elseif ($config->get('Attr.IDPrefixLocal') !== '') {
+            trigger_error('%Attr.IDPrefixLocal cannot be used unless '.
+                '%Attr.IDPrefix is set', E_USER_WARNING);
+        }
+
+        if (!$this->selector) {
+            $id_accumulator =& $context->get('IDAccumulator');
+            if (isset($id_accumulator->ids[$id])) return false;
+        }
+
+        // we purposely avoid using regex, hopefully this is faster
+
+        if (ctype_alpha($id)) {
+            $result = true;
+        } else {
+            if (!ctype_alpha(@$id[0])) return false;
+            $trim = trim( // primitive style of regexps, I suppose
+                $id,
+                'A..Za..z0..9:-._'
+              );
+            $result = ($trim === '');
+        }
+
+        $regexp = $config->get('Attr.IDBlacklistRegexp');
+        if ($regexp && preg_match($regexp, $id)) {
+            return false;
+        }
+
+        if (!$this->selector && $result) $id_accumulator->add($id);
+
+        // if no change was made to the ID, return the result
+        // else, return the new id if stripping whitespace made it
+        //     valid, or return false.
+        return $result ? $id : false;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Length.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Length.php
new file mode 100644
index 0000000..a242f9c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Length.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Validates the HTML type length (not to be confused with CSS's length).
+ *
+ * This accepts integer pixels or percentages as lengths for certain
+ * HTML attributes.
+ */
+
+class HTMLPurifier_AttrDef_HTML_Length extends HTMLPurifier_AttrDef_HTML_Pixels
+{
+
+    public function validate($string, $config, $context) {
+
+        $string = trim($string);
+        if ($string === '') return false;
+
+        $parent_result = parent::validate($string, $config, $context);
+        if ($parent_result !== false) return $parent_result;
+
+        $length = strlen($string);
+        $last_char = $string[$length - 1];
+
+        if ($last_char !== '%') return false;
+
+        $points = substr($string, 0, $length - 1);
+
+        if (!is_numeric($points)) return false;
+
+        $points = (int) $points;
+
+        if ($points < 0) return '0%';
+        if ($points > 100) return '100%';
+
+        return ((string) $points) . '%';
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/LinkTypes.php
new file mode 100644
index 0000000..76d25ed
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/LinkTypes.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Validates a rel/rev link attribute against a directive of allowed values
+ * @note We cannot use Enum because link types allow multiple
+ *       values.
+ * @note Assumes link types are ASCII text
+ */
+class HTMLPurifier_AttrDef_HTML_LinkTypes extends HTMLPurifier_AttrDef
+{
+
+    /** Name config attribute to pull. */
+    protected $name;
+
+    public function __construct($name) {
+        $configLookup = array(
+            'rel' => 'AllowedRel',
+            'rev' => 'AllowedRev'
+        );
+        if (!isset($configLookup[$name])) {
+            trigger_error('Unrecognized attribute name for link '.
+                'relationship.', E_USER_ERROR);
+            return;
+        }
+        $this->name = $configLookup[$name];
+    }
+
+    public function validate($string, $config, $context) {
+
+        $allowed = $config->get('Attr.' . $this->name);
+        if (empty($allowed)) return false;
+
+        $string = $this->parseCDATA($string);
+        $parts = explode(' ', $string);
+
+        // lookup to prevent duplicates
+        $ret_lookup = array();
+        foreach ($parts as $part) {
+            $part = strtolower(trim($part));
+            if (!isset($allowed[$part])) continue;
+            $ret_lookup[$part] = true;
+        }
+
+        if (empty($ret_lookup)) return false;
+        $string = implode(' ', array_keys($ret_lookup));
+
+        return $string;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/MultiLength.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/MultiLength.php
new file mode 100644
index 0000000..c72fc76
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/MultiLength.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Validates a MultiLength as defined by the HTML spec.
+ *
+ * A multilength is either a integer (pixel count), a percentage, or
+ * a relative number.
+ */
+class HTMLPurifier_AttrDef_HTML_MultiLength extends HTMLPurifier_AttrDef_HTML_Length
+{
+
+    public function validate($string, $config, $context) {
+
+        $string = trim($string);
+        if ($string === '') return false;
+
+        $parent_result = parent::validate($string, $config, $context);
+        if ($parent_result !== false) return $parent_result;
+
+        $length = strlen($string);
+        $last_char = $string[$length - 1];
+
+        if ($last_char !== '*') return false;
+
+        $int = substr($string, 0, $length - 1);
+
+        if ($int == '') return '*';
+        if (!is_numeric($int)) return false;
+
+        $int = (int) $int;
+
+        if ($int < 0) return false;
+        if ($int == 0) return '0';
+        if ($int == 1) return '*';
+        return ((string) $int) . '*';
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Nmtokens.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Nmtokens.php
new file mode 100644
index 0000000..aa34120
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Nmtokens.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Validates contents based on NMTOKENS attribute type.
+ */
+class HTMLPurifier_AttrDef_HTML_Nmtokens extends HTMLPurifier_AttrDef
+{
+
+    public function validate($string, $config, $context) {
+
+        $string = trim($string);
+
+        // early abort: '' and '0' (strings that convert to false) are invalid
+        if (!$string) return false;
+
+        $tokens = $this->split($string, $config, $context);
+        $tokens = $this->filter($tokens, $config, $context);
+        if (empty($tokens)) return false;
+        return implode(' ', $tokens);
+
+    }
+
+    /**
+     * Splits a space separated list of tokens into its constituent parts.
+     */
+    protected function split($string, $config, $context) {
+        // OPTIMIZABLE!
+        // do the preg_match, capture all subpatterns for reformulation
+
+        // we don't support U+00A1 and up codepoints or
+        // escaping because I don't know how to do that with regexps
+        // and plus it would complicate optimization efforts (you never
+        // see that anyway).
+        $pattern = '/(?:(?<=\s)|\A)'. // look behind for space or string start
+                   '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)'.
+                   '(?:(?=\s)|\z)/'; // look ahead for space or string end
+        preg_match_all($pattern, $string, $matches);
+        return $matches[1];
+    }
+
+    /**
+     * Template method for removing certain tokens based on arbitrary criteria.
+     * @note If we wanted to be really functional, we'd do an array_filter
+     *       with a callback. But... we're not.
+     */
+    protected function filter($tokens, $config, $context) {
+        return $tokens;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php
new file mode 100644
index 0000000..4cb2c1b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/HTML/Pixels.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Validates an integer representation of pixels according to the HTML spec.
+ */
+class HTMLPurifier_AttrDef_HTML_Pixels extends HTMLPurifier_AttrDef
+{
+
+    protected $max;
+
+    public function __construct($max = null) {
+        $this->max = $max;
+    }
+
+    public function validate($string, $config, $context) {
+
+        $string = trim($string);
+        if ($string === '0') return $string;
+        if ($string === '')  return false;
+        $length = strlen($string);
+        if (substr($string, $length - 2) == 'px') {
+            $string = substr($string, 0, $length - 2);
+        }
+        if (!is_numeric($string)) return false;
+        $int = (int) $string;
+
+        if ($int < 0) return '0';
+
+        // upper-bound value, extremely high values can
+        // crash operating systems, see <http://ha.ckers.org/imagecrash.html>
+        // WARNING, above link WILL crash you if you're using Windows
+
+        if ($this->max !== null && $int > $this->max) return (string) $this->max;
+
+        return (string) $int;
+
+    }
+
+    public function make($string) {
+        if ($string === '') $max = null;
+        else $max = (int) $string;
+        $class = get_class($this);
+        return new $class($max);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Integer.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Integer.php
new file mode 100644
index 0000000..d59738d
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Integer.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * Validates an integer.
+ * @note While this class was modeled off the CSS definition, no currently
+ *       allowed CSS uses this type.  The properties that do are: widows,
+ *       orphans, z-index, counter-increment, counter-reset.  Some of the
+ *       HTML attributes, however, find use for a non-negative version of this.
+ */
+class HTMLPurifier_AttrDef_Integer extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Bool indicating whether or not negative values are allowed
+     */
+    protected $negative = true;
+
+    /**
+     * Bool indicating whether or not zero is allowed
+     */
+    protected $zero = true;
+
+    /**
+     * Bool indicating whether or not positive values are allowed
+     */
+    protected $positive = true;
+
+    /**
+     * @param $negative Bool indicating whether or not negative values are allowed
+     * @param $zero Bool indicating whether or not zero is allowed
+     * @param $positive Bool indicating whether or not positive values are allowed
+     */
+    public function __construct(
+        $negative = true, $zero = true, $positive = true
+    ) {
+        $this->negative = $negative;
+        $this->zero     = $zero;
+        $this->positive = $positive;
+    }
+
+    public function validate($integer, $config, $context) {
+
+        $integer = $this->parseCDATA($integer);
+        if ($integer === '') return false;
+
+        // we could possibly simply typecast it to integer, but there are
+        // certain fringe cases that must not return an integer.
+
+        // clip leading sign
+        if ( $this->negative && $integer[0] === '-' ) {
+            $digits = substr($integer, 1);
+            if ($digits === '0') $integer = '0'; // rm minus sign for zero
+        } elseif( $this->positive && $integer[0] === '+' ) {
+            $digits = $integer = substr($integer, 1); // rm unnecessary plus
+        } else {
+            $digits = $integer;
+        }
+
+        // test if it's numeric
+        if (!ctype_digit($digits)) return false;
+
+        // perform scope tests
+        if (!$this->zero     && $integer == 0) return false;
+        if (!$this->positive && $integer > 0) return false;
+        if (!$this->negative && $integer < 0) return false;
+
+        return $integer;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Lang.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Lang.php
new file mode 100644
index 0000000..10e6da5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Lang.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * Validates the HTML attribute lang, effectively a language code.
+ * @note Built according to RFC 3066, which obsoleted RFC 1766
+ */
+class HTMLPurifier_AttrDef_Lang extends HTMLPurifier_AttrDef
+{
+
+    public function validate($string, $config, $context) {
+
+        $string = trim($string);
+        if (!$string) return false;
+
+        $subtags = explode('-', $string);
+        $num_subtags = count($subtags);
+
+        if ($num_subtags == 0) return false; // sanity check
+
+        // process primary subtag : $subtags[0]
+        $length = strlen($subtags[0]);
+        switch ($length) {
+            case 0:
+                return false;
+            case 1:
+                if (! ($subtags[0] == 'x' || $subtags[0] == 'i') ) {
+                    return false;
+                }
+                break;
+            case 2:
+            case 3:
+                if (! ctype_alpha($subtags[0]) ) {
+                    return false;
+                } elseif (! ctype_lower($subtags[0]) ) {
+                    $subtags[0] = strtolower($subtags[0]);
+                }
+                break;
+            default:
+                return false;
+        }
+
+        $new_string = $subtags[0];
+        if ($num_subtags == 1) return $new_string;
+
+        // process second subtag : $subtags[1]
+        $length = strlen($subtags[1]);
+        if ($length == 0 || ($length == 1 && $subtags[1] != 'x') || $length > 8 || !ctype_alnum($subtags[1])) {
+            return $new_string;
+        }
+        if (!ctype_lower($subtags[1])) $subtags[1] = strtolower($subtags[1]);
+
+        $new_string .= '-' . $subtags[1];
+        if ($num_subtags == 2) return $new_string;
+
+        // process all other subtags, index 2 and up
+        for ($i = 2; $i < $num_subtags; $i++) {
+            $length = strlen($subtags[$i]);
+            if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) {
+                return $new_string;
+            }
+            if (!ctype_lower($subtags[$i])) {
+                $subtags[$i] = strtolower($subtags[$i]);
+            }
+            $new_string .= '-' . $subtags[$i];
+        }
+
+        return $new_string;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Switch.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Switch.php
new file mode 100644
index 0000000..c9e3ed1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Switch.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Decorator that, depending on a token, switches between two definitions.
+ */
+class HTMLPurifier_AttrDef_Switch
+{
+
+    protected $tag;
+    protected $withTag, $withoutTag;
+
+    /**
+     * @param string $tag Tag name to switch upon
+     * @param HTMLPurifier_AttrDef $with_tag Call if token matches tag
+     * @param HTMLPurifier_AttrDef $without_tag Call if token doesn't match, or there is no token
+     */
+    public function __construct($tag, $with_tag, $without_tag) {
+        $this->tag = $tag;
+        $this->withTag = $with_tag;
+        $this->withoutTag = $without_tag;
+    }
+
+    public function validate($string, $config, $context) {
+        $token = $context->get('CurrentToken', true);
+        if (!$token || $token->name !== $this->tag) {
+            return $this->withoutTag->validate($string, $config, $context);
+        } else {
+            return $this->withTag->validate($string, $config, $context);
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Text.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Text.php
new file mode 100644
index 0000000..c6216cc
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/Text.php
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * Validates arbitrary text according to the HTML spec.
+ */
+class HTMLPurifier_AttrDef_Text extends HTMLPurifier_AttrDef
+{
+
+    public function validate($string, $config, $context) {
+        return $this->parseCDATA($string);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI.php
new file mode 100644
index 0000000..c2b6846
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Validates a URI as defined by RFC 3986.
+ * @note Scheme-specific mechanics deferred to HTMLPurifier_URIScheme
+ */
+class HTMLPurifier_AttrDef_URI extends HTMLPurifier_AttrDef
+{
+
+    protected $parser;
+    protected $embedsResource;
+
+    /**
+     * @param $embeds_resource_resource Does the URI here result in an extra HTTP request?
+     */
+    public function __construct($embeds_resource = false) {
+        $this->parser = new HTMLPurifier_URIParser();
+        $this->embedsResource = (bool) $embeds_resource;
+    }
+
+    public function make($string) {
+        $embeds = ($string === 'embedded');
+        return new HTMLPurifier_AttrDef_URI($embeds);
+    }
+
+    public function validate($uri, $config, $context) {
+
+        if ($config->get('URI.Disable')) return false;
+
+        $uri = $this->parseCDATA($uri);
+
+        // parse the URI
+        $uri = $this->parser->parse($uri);
+        if ($uri === false) return false;
+
+        // add embedded flag to context for validators
+        $context->register('EmbeddedURI', $this->embedsResource);
+
+        $ok = false;
+        do {
+
+            // generic validation
+            $result = $uri->validate($config, $context);
+            if (!$result) break;
+
+            // chained filtering
+            $uri_def = $config->getDefinition('URI');
+            $result = $uri_def->filter($uri, $config, $context);
+            if (!$result) break;
+
+            // scheme-specific validation
+            $scheme_obj = $uri->getSchemeObj($config, $context);
+            if (!$scheme_obj) break;
+            if ($this->embedsResource && !$scheme_obj->browsable) break;
+            $result = $scheme_obj->validate($uri, $config, $context);
+            if (!$result) break;
+
+            // Post chained filtering
+            $result = $uri_def->postFilter($uri, $config, $context);
+            if (!$result) break;
+
+            // survived gauntlet
+            $ok = true;
+
+        } while (false);
+
+        $context->destroy('EmbeddedURI');
+        if (!$ok) return false;
+
+        // back to string
+        return $uri->toString();
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Email.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Email.php
new file mode 100644
index 0000000..bfee9d1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Email.php
@@ -0,0 +1,17 @@
+<?php
+
+abstract class HTMLPurifier_AttrDef_URI_Email extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Unpacks a mailbox into its display-name and address
+     */
+    function unpack($string) {
+        // needs to be implemented
+    }
+
+}
+
+// sub-implementations
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php
new file mode 100644
index 0000000..94c715a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Email/SimpleCheck.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Primitive email validation class based on the regexp found at
+ * http://www.regular-expressions.info/email.html
+ */
+class HTMLPurifier_AttrDef_URI_Email_SimpleCheck extends HTMLPurifier_AttrDef_URI_Email
+{
+
+    public function validate($string, $config, $context) {
+        // no support for named mailboxes i.e. "Bob <bob at example.com>"
+        // that needs more percent encoding to be done
+        if ($string == '') return false;
+        $string = trim($string);
+        $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string);
+        return $result ? $string : false;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php
new file mode 100644
index 0000000..125decb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/Host.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * Validates a host according to the IPv4, IPv6 and DNS (future) specifications.
+ */
+class HTMLPurifier_AttrDef_URI_Host extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * Instance of HTMLPurifier_AttrDef_URI_IPv4 sub-validator
+     */
+    protected $ipv4;
+
+    /**
+     * Instance of HTMLPurifier_AttrDef_URI_IPv6 sub-validator
+     */
+    protected $ipv6;
+
+    public function __construct() {
+        $this->ipv4 = new HTMLPurifier_AttrDef_URI_IPv4();
+        $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6();
+    }
+
+    public function validate($string, $config, $context) {
+        $length = strlen($string);
+        // empty hostname is OK; it's usually semantically equivalent:
+        // the default host as defined by a URI scheme is used:
+        //
+        //      If the URI scheme defines a default for host, then that
+        //      default applies when the host subcomponent is undefined
+        //      or when the registered name is empty (zero length).
+        if ($string === '') return '';
+        if ($length > 1 && $string[0] === '[' && $string[$length-1] === ']') {
+            //IPv6
+            $ip = substr($string, 1, $length - 2);
+            $valid = $this->ipv6->validate($ip, $config, $context);
+            if ($valid === false) return false;
+            return '['. $valid . ']';
+        }
+
+        // need to do checks on unusual encodings too
+        $ipv4 = $this->ipv4->validate($string, $config, $context);
+        if ($ipv4 !== false) return $ipv4;
+
+        // A regular domain name.
+
+        // This doesn't match I18N domain names, but we don't have proper IRI support,
+        // so force users to insert Punycode.
+
+        // The productions describing this are:
+        $a   = '[a-z]';     // alpha
+        $an  = '[a-z0-9]';  // alphanum
+        $and = '[a-z0-9-]'; // alphanum | "-"
+        // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
+        $domainlabel   = "$an($and*$an)?";
+        // toplabel    = alpha | alpha *( alphanum | "-" ) alphanum
+        $toplabel      = "$a($and*$an)?";
+        // hostname    = *( domainlabel "." ) toplabel [ "." ]
+        if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
+            return $string;
+        }
+
+        // If we have Net_IDNA2 support, we can support IRIs by
+        // punycoding them. (This is the most portable thing to do,
+        // since otherwise we have to assume browsers support
+
+        if ($config->get('Core.EnableIDNA')) {
+            $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true));
+            // we need to encode each period separately
+            $parts = explode('.', $string);
+            try {
+                $new_parts = array();
+                foreach ($parts as $part) {
+                    $encodable = false;
+                    for ($i = 0, $c = strlen($part); $i < $c; $i++) {
+                        if (ord($part[$i]) > 0x7a) {
+                            $encodable = true;
+                            break;
+                        }
+                    }
+                    if (!$encodable) {
+                        $new_parts[] = $part;
+                    } else {
+                        $new_parts[] = $idna->encode($part);
+                    }
+                }
+                $string = implode('.', $new_parts);
+                if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) {
+                    return $string;
+                }
+            } catch (Exception $e) {
+                // XXX error reporting
+            }
+        }
+
+        return false;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/IPv4.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/IPv4.php
new file mode 100644
index 0000000..ec4cf59
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/IPv4.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Validates an IPv4 address
+ * @author Feyd @ forums.devnetwork.net (public domain)
+ */
+class HTMLPurifier_AttrDef_URI_IPv4 extends HTMLPurifier_AttrDef
+{
+
+    /**
+     * IPv4 regex, protected so that IPv6 can reuse it
+     */
+    protected $ip4;
+
+    public function validate($aIP, $config, $context) {
+
+        if (!$this->ip4) $this->_loadRegex();
+
+        if (preg_match('#^' . $this->ip4 . '$#s', $aIP))
+        {
+                return $aIP;
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Lazy load function to prevent regex from being stuffed in
+     * cache.
+     */
+    protected function _loadRegex() {
+        $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255
+        $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})";
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/IPv6.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/IPv6.php
new file mode 100644
index 0000000..9454e9b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrDef/URI/IPv6.php
@@ -0,0 +1,99 @@
+<?php
+
+/**
+ * Validates an IPv6 address.
+ * @author Feyd @ forums.devnetwork.net (public domain)
+ * @note This function requires brackets to have been removed from address
+ *       in URI.
+ */
+class HTMLPurifier_AttrDef_URI_IPv6 extends HTMLPurifier_AttrDef_URI_IPv4
+{
+
+    public function validate($aIP, $config, $context) {
+
+        if (!$this->ip4) $this->_loadRegex();
+
+        $original = $aIP;
+
+        $hex = '[0-9a-fA-F]';
+        $blk = '(?:' . $hex . '{1,4})';
+        $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))';   // /0 - /128
+
+        //      prefix check
+        if (strpos($aIP, '/') !== false)
+        {
+                if (preg_match('#' . $pre . '$#s', $aIP, $find))
+                {
+                        $aIP = substr($aIP, 0, 0-strlen($find[0]));
+                        unset($find);
+                }
+                else
+                {
+                        return false;
+                }
+        }
+
+        //      IPv4-compatiblity check
+        if (preg_match('#(?<=:'.')' . $this->ip4 . '$#s', $aIP, $find))
+        {
+                $aIP = substr($aIP, 0, 0-strlen($find[0]));
+                $ip = explode('.', $find[0]);
+                $ip = array_map('dechex', $ip);
+                $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3];
+                unset($find, $ip);
+        }
+
+        //      compression check
+        $aIP = explode('::', $aIP);
+        $c = count($aIP);
+        if ($c > 2)
+        {
+                return false;
+        }
+        elseif ($c == 2)
+        {
+                list($first, $second) = $aIP;
+                $first = explode(':', $first);
+                $second = explode(':', $second);
+
+                if (count($first) + count($second) > 8)
+                {
+                        return false;
+                }
+
+                while(count($first) < 8)
+                {
+                        array_push($first, '0');
+                }
+
+                array_splice($first, 8 - count($second), 8, $second);
+                $aIP = $first;
+                unset($first,$second);
+        }
+        else
+        {
+                $aIP = explode(':', $aIP[0]);
+        }
+        $c = count($aIP);
+
+        if ($c != 8)
+        {
+                return false;
+        }
+
+        //      All the pieces should be 16-bit hex strings. Are they?
+        foreach ($aIP as $piece)
+        {
+                if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece)))
+                {
+                        return false;
+                }
+        }
+
+        return $original;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform.php
new file mode 100644
index 0000000..e61d3e0
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Processes an entire attribute array for corrections needing multiple values.
+ *
+ * Occasionally, a certain attribute will need to be removed and popped onto
+ * another value.  Instead of creating a complex return syntax for
+ * HTMLPurifier_AttrDef, we just pass the whole attribute array to a
+ * specialized object and have that do the special work.  That is the
+ * family of HTMLPurifier_AttrTransform.
+ *
+ * An attribute transformation can be assigned to run before or after
+ * HTMLPurifier_AttrDef validation.  See HTMLPurifier_HTMLDefinition for
+ * more details.
+ */
+
+abstract class HTMLPurifier_AttrTransform
+{
+
+    /**
+     * Abstract: makes changes to the attributes dependent on multiple values.
+     *
+     * @param $attr Assoc array of attributes, usually from
+     *              HTMLPurifier_Token_Tag::$attr
+     * @param $config Mandatory HTMLPurifier_Config object.
+     * @param $context Mandatory HTMLPurifier_Context object
+     * @returns Processed attribute array.
+     */
+    abstract public function transform($attr, $config, $context);
+
+    /**
+     * Prepends CSS properties to the style attribute, creating the
+     * attribute if it doesn't exist.
+     * @param $attr Attribute array to process (passed by reference)
+     * @param $css CSS to prepend
+     */
+    public function prependCSS(&$attr, $css) {
+        $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
+        $attr['style'] = $css . $attr['style'];
+    }
+
+    /**
+     * Retrieves and removes an attribute
+     * @param $attr Attribute array to process (passed by reference)
+     * @param $key Key of attribute to confiscate
+     */
+    public function confiscateAttr(&$attr, $key) {
+        if (!isset($attr[$key])) return null;
+        $value = $attr[$key];
+        unset($attr[$key]);
+        return $value;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Background.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Background.php
new file mode 100644
index 0000000..0e1ff24
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Background.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Pre-transform that changes proprietary background attribute to CSS.
+ */
+class HTMLPurifier_AttrTransform_Background extends HTMLPurifier_AttrTransform {
+
+    public function transform($attr, $config, $context) {
+
+        if (!isset($attr['background'])) return $attr;
+
+        $background = $this->confiscateAttr($attr, 'background');
+        // some validation should happen here
+
+        $this->prependCSS($attr, "background-image:url($background);");
+
+        return $attr;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BdoDir.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BdoDir.php
new file mode 100644
index 0000000..4d1a056
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BdoDir.php
@@ -0,0 +1,19 @@
+<?php
+
+// this MUST be placed in post, as it assumes that any value in dir is valid
+
+/**
+ * Post-trasnform that ensures that bdo tags have the dir attribute set.
+ */
+class HTMLPurifier_AttrTransform_BdoDir extends HTMLPurifier_AttrTransform
+{
+
+    public function transform($attr, $config, $context) {
+        if (isset($attr['dir'])) return $attr;
+        $attr['dir'] = $config->get('Attr.DefaultTextDir');
+        return $attr;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BgColor.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BgColor.php
new file mode 100644
index 0000000..ad3916b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BgColor.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Pre-transform that changes deprecated bgcolor attribute to CSS.
+ */
+class HTMLPurifier_AttrTransform_BgColor extends HTMLPurifier_AttrTransform {
+
+    public function transform($attr, $config, $context) {
+
+        if (!isset($attr['bgcolor'])) return $attr;
+
+        $bgcolor = $this->confiscateAttr($attr, 'bgcolor');
+        // some validation should happen here
+
+        $this->prependCSS($attr, "background-color:$bgcolor;");
+
+        return $attr;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BoolToCSS.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BoolToCSS.php
new file mode 100644
index 0000000..51159b6
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/BoolToCSS.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Pre-transform that changes converts a boolean attribute to fixed CSS
+ */
+class HTMLPurifier_AttrTransform_BoolToCSS extends HTMLPurifier_AttrTransform {
+
+    /**
+     * Name of boolean attribute that is trigger
+     */
+    protected $attr;
+
+    /**
+     * CSS declarations to add to style, needs trailing semicolon
+     */
+    protected $css;
+
+    /**
+     * @param $attr string attribute name to convert from
+     * @param $css string CSS declarations to add to style (needs semicolon)
+     */
+    public function __construct($attr, $css) {
+        $this->attr = $attr;
+        $this->css  = $css;
+    }
+
+    public function transform($attr, $config, $context) {
+        if (!isset($attr[$this->attr])) return $attr;
+        unset($attr[$this->attr]);
+        $this->prependCSS($attr, $this->css);
+        return $attr;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Border.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Border.php
new file mode 100644
index 0000000..476b0b0
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Border.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * Pre-transform that changes deprecated border attribute to CSS.
+ */
+class HTMLPurifier_AttrTransform_Border extends HTMLPurifier_AttrTransform {
+
+    public function transform($attr, $config, $context) {
+        if (!isset($attr['border'])) return $attr;
+        $border_width = $this->confiscateAttr($attr, 'border');
+        // some validation should happen here
+        $this->prependCSS($attr, "border:{$border_width}px solid;");
+        return $attr;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/EnumToCSS.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/EnumToCSS.php
new file mode 100644
index 0000000..2a5b451
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/EnumToCSS.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Generic pre-transform that converts an attribute with a fixed number of
+ * values (enumerated) to CSS.
+ */
+class HTMLPurifier_AttrTransform_EnumToCSS extends HTMLPurifier_AttrTransform {
+
+    /**
+     * Name of attribute to transform from
+     */
+    protected $attr;
+
+    /**
+     * Lookup array of attribute values to CSS
+     */
+    protected $enumToCSS = array();
+
+    /**
+     * Case sensitivity of the matching
+     * @warning Currently can only be guaranteed to work with ASCII
+     *          values.
+     */
+    protected $caseSensitive = false;
+
+    /**
+     * @param $attr String attribute name to transform from
+     * @param $enumToCSS Lookup array of attribute values to CSS
+     * @param $case_sensitive Boolean case sensitivity indicator, default false
+     */
+    public function __construct($attr, $enum_to_css, $case_sensitive = false) {
+        $this->attr = $attr;
+        $this->enumToCSS = $enum_to_css;
+        $this->caseSensitive = (bool) $case_sensitive;
+    }
+
+    public function transform($attr, $config, $context) {
+
+        if (!isset($attr[$this->attr])) return $attr;
+
+        $value = trim($attr[$this->attr]);
+        unset($attr[$this->attr]);
+
+        if (!$this->caseSensitive) $value = strtolower($value);
+
+        if (!isset($this->enumToCSS[$value])) {
+            return $attr;
+        }
+
+        $this->prependCSS($attr, $this->enumToCSS[$value]);
+
+        return $attr;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ImgRequired.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ImgRequired.php
new file mode 100644
index 0000000..7f0e4b7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ImgRequired.php
@@ -0,0 +1,43 @@
+<?php
+
+// must be called POST validation
+
+/**
+ * Transform that supplies default values for the src and alt attributes
+ * in img tags, as well as prevents the img tag from being removed
+ * because of a missing alt tag. This needs to be registered as both
+ * a pre and post attribute transform.
+ */
+class HTMLPurifier_AttrTransform_ImgRequired extends HTMLPurifier_AttrTransform
+{
+
+    public function transform($attr, $config, $context) {
+
+        $src = true;
+        if (!isset($attr['src'])) {
+            if ($config->get('Core.RemoveInvalidImg')) return $attr;
+            $attr['src'] = $config->get('Attr.DefaultInvalidImage');
+            $src = false;
+        }
+
+        if (!isset($attr['alt'])) {
+            if ($src) {
+                $alt = $config->get('Attr.DefaultImageAlt');
+                if ($alt === null) {
+                    // truncate if the alt is too long
+                    $attr['alt'] = substr(basename($attr['src']),0,40);
+                } else {
+                    $attr['alt'] = $alt;
+                }
+            } else {
+                $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt');
+            }
+        }
+
+        return $attr;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ImgSpace.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ImgSpace.php
new file mode 100644
index 0000000..fd84c10
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ImgSpace.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Pre-transform that changes deprecated hspace and vspace attributes to CSS
+ */
+class HTMLPurifier_AttrTransform_ImgSpace extends HTMLPurifier_AttrTransform {
+
+    protected $attr;
+    protected $css = array(
+        'hspace' => array('left', 'right'),
+        'vspace' => array('top', 'bottom')
+    );
+
+    public function __construct($attr) {
+        $this->attr = $attr;
+        if (!isset($this->css[$attr])) {
+            trigger_error(htmlspecialchars($attr) . ' is not valid space attribute');
+        }
+    }
+
+    public function transform($attr, $config, $context) {
+
+        if (!isset($attr[$this->attr])) return $attr;
+
+        $width = $this->confiscateAttr($attr, $this->attr);
+        // some validation could happen here
+
+        if (!isset($this->css[$this->attr])) return $attr;
+
+        $style = '';
+        foreach ($this->css[$this->attr] as $suffix) {
+            $property = "margin-$suffix";
+            $style .= "$property:{$width}px;";
+        }
+
+        $this->prependCSS($attr, $style);
+
+        return $attr;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Input.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Input.php
new file mode 100644
index 0000000..1682955
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Input.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Performs miscellaneous cross attribute validation and filtering for
+ * input elements. This is meant to be a post-transform.
+ */
+class HTMLPurifier_AttrTransform_Input extends HTMLPurifier_AttrTransform {
+
+    protected $pixels;
+
+    public function __construct() {
+        $this->pixels = new HTMLPurifier_AttrDef_HTML_Pixels();
+    }
+
+    public function transform($attr, $config, $context) {
+        if (!isset($attr['type'])) $t = 'text';
+        else $t = strtolower($attr['type']);
+        if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') {
+            unset($attr['checked']);
+        }
+        if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') {
+            unset($attr['maxlength']);
+        }
+        if (isset($attr['size']) && $t !== 'text' && $t !== 'password') {
+            $result = $this->pixels->validate($attr['size'], $config, $context);
+            if ($result === false) unset($attr['size']);
+            else $attr['size'] = $result;
+        }
+        if (isset($attr['src']) && $t !== 'image') {
+            unset($attr['src']);
+        }
+        if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) {
+            $attr['value'] = '';
+        }
+        return $attr;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Lang.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Lang.php
new file mode 100644
index 0000000..5869e7f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Lang.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Post-transform that copies lang's value to xml:lang (and vice-versa)
+ * @note Theoretically speaking, this could be a pre-transform, but putting
+ *       post is more efficient.
+ */
+class HTMLPurifier_AttrTransform_Lang extends HTMLPurifier_AttrTransform
+{
+
+    public function transform($attr, $config, $context) {
+
+        $lang     = isset($attr['lang']) ? $attr['lang'] : false;
+        $xml_lang = isset($attr['xml:lang']) ? $attr['xml:lang'] : false;
+
+        if ($lang !== false && $xml_lang === false) {
+            $attr['xml:lang'] = $lang;
+        } elseif ($xml_lang !== false) {
+            $attr['lang'] = $xml_lang;
+        }
+
+        return $attr;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Length.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Length.php
new file mode 100644
index 0000000..ea2f304
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Length.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Class for handling width/height length attribute transformations to CSS
+ */
+class HTMLPurifier_AttrTransform_Length extends HTMLPurifier_AttrTransform
+{
+
+    protected $name;
+    protected $cssName;
+
+    public function __construct($name, $css_name = null) {
+        $this->name = $name;
+        $this->cssName = $css_name ? $css_name : $name;
+    }
+
+    public function transform($attr, $config, $context) {
+        if (!isset($attr[$this->name])) return $attr;
+        $length = $this->confiscateAttr($attr, $this->name);
+        if(ctype_digit($length)) $length .= 'px';
+        $this->prependCSS($attr, $this->cssName . ":$length;");
+        return $attr;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Name.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Name.php
new file mode 100644
index 0000000..15315bc
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Name.php
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * Pre-transform that changes deprecated name attribute to ID if necessary
+ */
+class HTMLPurifier_AttrTransform_Name extends HTMLPurifier_AttrTransform
+{
+
+    public function transform($attr, $config, $context) {
+        // Abort early if we're using relaxed definition of name
+        if ($config->get('HTML.Attr.Name.UseCDATA')) return $attr;
+        if (!isset($attr['name'])) return $attr;
+        $id = $this->confiscateAttr($attr, 'name');
+        if ( isset($attr['id']))   return $attr;
+        $attr['id'] = $id;
+        return $attr;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/NameSync.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/NameSync.php
new file mode 100644
index 0000000..a95638c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/NameSync.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Post-transform that performs validation to the name attribute; if
+ * it is present with an equivalent id attribute, it is passed through;
+ * otherwise validation is performed.
+ */
+class HTMLPurifier_AttrTransform_NameSync extends HTMLPurifier_AttrTransform
+{
+
+    public function __construct() {
+        $this->idDef = new HTMLPurifier_AttrDef_HTML_ID();
+    }
+
+    public function transform($attr, $config, $context) {
+        if (!isset($attr['name'])) return $attr;
+        $name = $attr['name'];
+        if (isset($attr['id']) && $attr['id'] === $name) return $attr;
+        $result = $this->idDef->validate($name, $config, $context);
+        if ($result === false) unset($attr['name']);
+        else $attr['name'] = $result;
+        return $attr;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Nofollow.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Nofollow.php
new file mode 100644
index 0000000..e699c79
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Nofollow.php
@@ -0,0 +1,45 @@
+<?php
+
+// must be called POST validation
+
+/**
+ * Adds rel="nofollow" to all outbound links.  This transform is
+ * only attached if Attr.Nofollow is TRUE.
+ */
+class HTMLPurifier_AttrTransform_Nofollow extends HTMLPurifier_AttrTransform
+{
+    private $parser;
+
+    public function __construct() {
+        $this->parser = new HTMLPurifier_URIParser();
+    }
+
+    public function transform($attr, $config, $context) {
+
+        if (!isset($attr['href'])) {
+            return $attr;
+        }
+
+        // XXX Kind of inefficient
+        $url = $this->parser->parse($attr['href']);
+        $scheme = $url->getSchemeObj($config, $context);
+
+        if ($scheme->browsable && !$url->isLocal($config, $context)) {
+            if (isset($attr['rel'])) {
+                $rels = explode(' ', $attr['rel']);
+                if (!in_array('nofollow', $rels)) {
+                    $rels[] = 'nofollow';
+                }
+                $attr['rel'] = implode(' ', $rels);
+            } else {
+                $attr['rel'] = 'nofollow';
+            }
+        }
+
+        return $attr;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeEmbed.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeEmbed.php
new file mode 100644
index 0000000..4da4499
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeEmbed.php
@@ -0,0 +1,15 @@
+<?php
+
+class HTMLPurifier_AttrTransform_SafeEmbed extends HTMLPurifier_AttrTransform
+{
+    public $name = "SafeEmbed";
+
+    public function transform($attr, $config, $context) {
+        $attr['allowscriptaccess'] = 'never';
+        $attr['allownetworking'] = 'internal';
+        $attr['type'] = 'application/x-shockwave-flash';
+        return $attr;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeObject.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeObject.php
new file mode 100644
index 0000000..1ed7489
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeObject.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * Writes default type for all objects. Currently only supports flash.
+ */
+class HTMLPurifier_AttrTransform_SafeObject extends HTMLPurifier_AttrTransform
+{
+    public $name = "SafeObject";
+
+    function transform($attr, $config, $context) {
+        if (!isset($attr['type'])) $attr['type'] = 'application/x-shockwave-flash';
+        return $attr;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeParam.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeParam.php
new file mode 100644
index 0000000..bd86a74
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/SafeParam.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Validates name/value pairs in param tags to be used in safe objects. This
+ * will only allow name values it recognizes, and pre-fill certain attributes
+ * with required values.
+ *
+ * @note
+ *      This class only supports Flash. In the future, Quicktime support
+ *      may be added.
+ *
+ * @warning
+ *      This class expects an injector to add the necessary parameters tags.
+ */
+class HTMLPurifier_AttrTransform_SafeParam extends HTMLPurifier_AttrTransform
+{
+    public $name = "SafeParam";
+    private $uri;
+
+    public function __construct() {
+        $this->uri = new HTMLPurifier_AttrDef_URI(true); // embedded
+        $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent'));
+    }
+
+    public function transform($attr, $config, $context) {
+        // If we add support for other objects, we'll need to alter the
+        // transforms.
+        switch ($attr['name']) {
+            // application/x-shockwave-flash
+            // Keep this synchronized with Injector/SafeObject.php
+            case 'allowScriptAccess':
+                $attr['value'] = 'never';
+                break;
+            case 'allowNetworking':
+                $attr['value'] = 'internal';
+                break;
+            case 'allowFullScreen':
+                if ($config->get('HTML.FlashAllowFullScreen')) {
+                    $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false';
+                } else {
+                    $attr['value'] = 'false';
+                }
+                break;
+            case 'wmode':
+                $attr['value'] = $this->wmode->validate($attr['value'], $config, $context);
+                break;
+            case 'movie':
+            case 'src':
+                $attr['name'] = "movie";
+                $attr['value'] = $this->uri->validate($attr['value'], $config, $context);
+                break;
+            case 'flashvars':
+                // we're going to allow arbitrary inputs to the SWF, on
+                // the reasoning that it could only hack the SWF, not us.
+                break;
+            // add other cases to support other param name/value pairs
+            default:
+                $attr['name'] = $attr['value'] = null;
+        }
+        return $attr;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ScriptRequired.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ScriptRequired.php
new file mode 100644
index 0000000..4499050
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/ScriptRequired.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * Implements required attribute stipulation for <script>
+ */
+class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform
+{
+    public function transform($attr, $config, $context) {
+        if (!isset($attr['type'])) {
+            $attr['type'] = 'text/javascript';
+        }
+        return $attr;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/TargetBlank.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/TargetBlank.php
new file mode 100644
index 0000000..deba8b4
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/TargetBlank.php
@@ -0,0 +1,38 @@
+<?php
+
+// must be called POST validation
+
+/**
+ * Adds target="blank" to all outbound links.  This transform is
+ * only attached if Attr.TargetBlank is TRUE.  This works regardless
+ * of whether or not Attr.AllowedFrameTargets
+ */
+class HTMLPurifier_AttrTransform_TargetBlank extends HTMLPurifier_AttrTransform
+{
+    private $parser;
+
+    public function __construct() {
+        $this->parser = new HTMLPurifier_URIParser();
+    }
+
+    public function transform($attr, $config, $context) {
+
+        if (!isset($attr['href'])) {
+            return $attr;
+        }
+
+        // XXX Kind of inefficient
+        $url = $this->parser->parse($attr['href']);
+        $scheme = $url->getSchemeObj($config, $context);
+
+        if ($scheme->browsable && !$url->isBenign($config, $context)) {
+            $attr['target'] = '_blank';
+        }
+
+        return $attr;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Textarea.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Textarea.php
new file mode 100644
index 0000000..81ac348
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTransform/Textarea.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * Sets height/width defaults for <textarea>
+ */
+class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform
+{
+
+    public function transform($attr, $config, $context) {
+        // Calculated from Firefox
+        if (!isset($attr['cols'])) $attr['cols'] = '22';
+        if (!isset($attr['rows'])) $attr['rows'] = '3';
+        return $attr;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrTypes.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrTypes.php
new file mode 100644
index 0000000..6f985ff
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrTypes.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * Provides lookup array of attribute types to HTMLPurifier_AttrDef objects
+ */
+class HTMLPurifier_AttrTypes
+{
+    /**
+     * Lookup array of attribute string identifiers to concrete implementations
+     */
+    protected $info = array();
+
+    /**
+     * Constructs the info array, supplying default implementations for attribute
+     * types.
+     */
+    public function __construct() {
+        // XXX This is kind of poor, since we don't actually /clone/
+        // instances; instead, we use the supplied make() attribute. So,
+        // the underlying class must know how to deal with arguments.
+        // With the old implementation of Enum, that ignored its
+        // arguments when handling a make dispatch, the IAlign
+        // definition wouldn't work.
+
+        // pseudo-types, must be instantiated via shorthand
+        $this->info['Enum']    = new HTMLPurifier_AttrDef_Enum();
+        $this->info['Bool']    = new HTMLPurifier_AttrDef_HTML_Bool();
+
+        $this->info['CDATA']    = new HTMLPurifier_AttrDef_Text();
+        $this->info['ID']       = new HTMLPurifier_AttrDef_HTML_ID();
+        $this->info['Length']   = new HTMLPurifier_AttrDef_HTML_Length();
+        $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength();
+        $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens();
+        $this->info['Pixels']   = new HTMLPurifier_AttrDef_HTML_Pixels();
+        $this->info['Text']     = new HTMLPurifier_AttrDef_Text();
+        $this->info['URI']      = new HTMLPurifier_AttrDef_URI();
+        $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang();
+        $this->info['Color']    = new HTMLPurifier_AttrDef_HTML_Color();
+        $this->info['IAlign']   = self::makeEnum('top,middle,bottom,left,right');
+        $this->info['LAlign']   = self::makeEnum('top,bottom,left,right');
+        $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget();
+
+        // unimplemented aliases
+        $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text();
+        $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text();
+        $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text();
+        $this->info['Character'] = new HTMLPurifier_AttrDef_Text();
+
+        // "proprietary" types
+        $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class();
+
+        // number is really a positive integer (one or more digits)
+        // FIXME: ^^ not always, see start and value of list items
+        $this->info['Number']   = new HTMLPurifier_AttrDef_Integer(false, false, true);
+    }
+
+    private static function makeEnum($in) {
+        return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in)));
+    }
+
+    /**
+     * Retrieves a type
+     * @param $type String type name
+     * @return Object AttrDef for type
+     */
+    public function get($type) {
+
+        // determine if there is any extra info tacked on
+        if (strpos($type, '#') !== false) list($type, $string) = explode('#', $type, 2);
+        else $string = '';
+
+        if (!isset($this->info[$type])) {
+            trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR);
+            return;
+        }
+
+        return $this->info[$type]->make($string);
+
+    }
+
+    /**
+     * Sets a new implementation for a type
+     * @param $type String type name
+     * @param $impl Object AttrDef for type
+     */
+    public function set($type, $impl) {
+        $this->info[$type] = $impl;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/AttrValidator.php b/application/libraries/htmlpurifier/HTMLPurifier/AttrValidator.php
new file mode 100644
index 0000000..829a0f8
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/AttrValidator.php
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * Validates the attributes of a token. Doesn't manage required attributes
+ * very well. The only reason we factored this out was because RemoveForeignElements
+ * also needed it besides ValidateAttributes.
+ */
+class HTMLPurifier_AttrValidator
+{
+
+    /**
+     * Validates the attributes of a token, returning a modified token
+     * that has valid tokens
+     * @param $token Reference to token to validate. We require a reference
+     *     because the operation this class performs on the token are
+     *     not atomic, so the context CurrentToken to be updated
+     *     throughout
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     */
+    public function validateToken(&$token, &$config, $context) {
+
+        $definition = $config->getHTMLDefinition();
+        $e =& $context->get('ErrorCollector', true);
+
+        // initialize IDAccumulator if necessary
+        $ok =& $context->get('IDAccumulator', true);
+        if (!$ok) {
+            $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context);
+            $context->register('IDAccumulator', $id_accumulator);
+        }
+
+        // initialize CurrentToken if necessary
+        $current_token =& $context->get('CurrentToken', true);
+        if (!$current_token) $context->register('CurrentToken', $token);
+
+        if (
+            !$token instanceof HTMLPurifier_Token_Start &&
+            !$token instanceof HTMLPurifier_Token_Empty
+        ) return $token;
+
+        // create alias to global definition array, see also $defs
+        // DEFINITION CALL
+        $d_defs = $definition->info_global_attr;
+
+        // don't update token until the very end, to ensure an atomic update
+        $attr = $token->attr;
+
+        // do global transformations (pre)
+        // nothing currently utilizes this
+        foreach ($definition->info_attr_transform_pre as $transform) {
+            $attr = $transform->transform($o = $attr, $config, $context);
+            if ($e) {
+                if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+            }
+        }
+
+        // do local transformations only applicable to this element (pre)
+        // ex. <p align="right"> to <p style="text-align:right;">
+        foreach ($definition->info[$token->name]->attr_transform_pre as $transform) {
+            $attr = $transform->transform($o = $attr, $config, $context);
+            if ($e) {
+                if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+            }
+        }
+
+        // create alias to this element's attribute definition array, see
+        // also $d_defs (global attribute definition array)
+        // DEFINITION CALL
+        $defs = $definition->info[$token->name]->attr;
+
+        $attr_key = false;
+        $context->register('CurrentAttr', $attr_key);
+
+        // iterate through all the attribute keypairs
+        // Watch out for name collisions: $key has previously been used
+        foreach ($attr as $attr_key => $value) {
+
+            // call the definition
+            if ( isset($defs[$attr_key]) ) {
+                // there is a local definition defined
+                if ($defs[$attr_key] === false) {
+                    // We've explicitly been told not to allow this element.
+                    // This is usually when there's a global definition
+                    // that must be overridden.
+                    // Theoretically speaking, we could have a
+                    // AttrDef_DenyAll, but this is faster!
+                    $result = false;
+                } else {
+                    // validate according to the element's definition
+                    $result = $defs[$attr_key]->validate(
+                                    $value, $config, $context
+                               );
+                }
+            } elseif ( isset($d_defs[$attr_key]) ) {
+                // there is a global definition defined, validate according
+                // to the global definition
+                $result = $d_defs[$attr_key]->validate(
+                                $value, $config, $context
+                           );
+            } else {
+                // system never heard of the attribute? DELETE!
+                $result = false;
+            }
+
+            // put the results into effect
+            if ($result === false || $result === null) {
+                // this is a generic error message that should replaced
+                // with more specific ones when possible
+                if ($e) $e->send(E_ERROR, 'AttrValidator: Attribute removed');
+
+                // remove the attribute
+                unset($attr[$attr_key]);
+            } elseif (is_string($result)) {
+                // generally, if a substitution is happening, there
+                // was some sort of implicit correction going on. We'll
+                // delegate it to the attribute classes to say exactly what.
+
+                // simple substitution
+                $attr[$attr_key] = $result;
+            } else {
+                // nothing happens
+            }
+
+            // we'd also want slightly more complicated substitution
+            // involving an array as the return value,
+            // although we're not sure how colliding attributes would
+            // resolve (certain ones would be completely overriden,
+            // others would prepend themselves).
+        }
+
+        $context->destroy('CurrentAttr');
+
+        // post transforms
+
+        // global (error reporting untested)
+        foreach ($definition->info_attr_transform_post as $transform) {
+            $attr = $transform->transform($o = $attr, $config, $context);
+            if ($e) {
+                if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+            }
+        }
+
+        // local (error reporting untested)
+        foreach ($definition->info[$token->name]->attr_transform_post as $transform) {
+            $attr = $transform->transform($o = $attr, $config, $context);
+            if ($e) {
+                if ($attr != $o) $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr);
+            }
+        }
+
+        $token->attr = $attr;
+
+        // destroy CurrentToken if we made it ourselves
+        if (!$current_token) $context->destroy('CurrentToken');
+
+    }
+
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Bootstrap.php b/application/libraries/htmlpurifier/HTMLPurifier/Bootstrap.php
new file mode 100644
index 0000000..ae50332
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Bootstrap.php
@@ -0,0 +1,109 @@
+<?php
+
+// constants are slow, so we use as few as possible
+if (!defined('HTMLPURIFIER_PREFIX')) {
+    define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..'));
+}
+
+// accomodations for versions earlier than 5.0.2
+// borrowed from PHP_Compat, LGPL licensed, by Aidan Lister <aidan at php.net>
+if (!defined('PHP_EOL')) {
+    switch (strtoupper(substr(PHP_OS, 0, 3))) {
+        case 'WIN':
+            define('PHP_EOL', "\r\n");
+            break;
+        case 'DAR':
+            define('PHP_EOL', "\r");
+            break;
+        default:
+            define('PHP_EOL', "\n");
+    }
+}
+
+/**
+ * Bootstrap class that contains meta-functionality for HTML Purifier such as
+ * the autoload function.
+ *
+ * @note
+ *      This class may be used without any other files from HTML Purifier.
+ */
+class HTMLPurifier_Bootstrap
+{
+
+    /**
+     * Autoload function for HTML Purifier
+     * @param $class Class to load
+     */
+    public static function autoload($class) {
+        $file = HTMLPurifier_Bootstrap::getPath($class);
+        if (!$file) return false;
+        // Technically speaking, it should be ok and more efficient to
+        // just do 'require', but Antonio Parraga reports that with
+        // Zend extensions such as Zend debugger and APC, this invariant
+        // may be broken.  Since we have efficient alternatives, pay
+        // the cost here and avoid the bug.
+        require_once HTMLPURIFIER_PREFIX . '/' . $file;
+        return true;
+    }
+
+    /**
+     * Returns the path for a specific class.
+     */
+    public static function getPath($class) {
+        if (strncmp('HTMLPurifier', $class, 12) !== 0) return false;
+        // Custom implementations
+        if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) {
+            $code = str_replace('_', '-', substr($class, 22));
+            $file = 'HTMLPurifier/Language/classes/' . $code . '.php';
+        } else {
+            $file = str_replace('_', '/', $class) . '.php';
+        }
+        if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) return false;
+        return $file;
+    }
+
+    /**
+     * "Pre-registers" our autoloader on the SPL stack.
+     */
+    public static function registerAutoload() {
+        $autoload = array('HTMLPurifier_Bootstrap', 'autoload');
+        if ( ($funcs = spl_autoload_functions()) === false ) {
+            spl_autoload_register($autoload);
+        } elseif (function_exists('spl_autoload_unregister')) {
+            if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
+                // prepend flag exists, no need for shenanigans
+                spl_autoload_register($autoload, true, true);
+            } else {
+                $buggy  = version_compare(PHP_VERSION, '5.2.11', '<');
+                $compat = version_compare(PHP_VERSION, '5.1.2', '<=') &&
+                          version_compare(PHP_VERSION, '5.1.0', '>=');
+                foreach ($funcs as $func) {
+                    if ($buggy && is_array($func)) {
+                        // :TRICKY: There are some compatibility issues and some
+                        // places where we need to error out
+                        $reflector = new ReflectionMethod($func[0], $func[1]);
+                        if (!$reflector->isStatic()) {
+                            throw new Exception('
+                                HTML Purifier autoloader registrar is not compatible
+                                with non-static object methods due to PHP Bug #44144;
+                                Please do not use HTMLPurifier.autoload.php (or any
+                                file that includes this file); instead, place the code:
+                                spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\'))
+                                after your own autoloaders.
+                            ');
+                        }
+                        // Suprisingly, spl_autoload_register supports the
+                        // Class::staticMethod callback format, although call_user_func doesn't
+                        if ($compat) $func = implode('::', $func);
+                    }
+                    spl_autoload_unregister($func);
+                }
+                spl_autoload_register($autoload);
+                foreach ($funcs as $func) spl_autoload_register($func);
+            }
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/CSSDefinition.php b/application/libraries/htmlpurifier/HTMLPurifier/CSSDefinition.php
new file mode 100644
index 0000000..8c4c312
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/CSSDefinition.php
@@ -0,0 +1,328 @@
+<?php
+
+/**
+ * Defines allowed CSS attributes and what their values are.
+ * @see HTMLPurifier_HTMLDefinition
+ */
+class HTMLPurifier_CSSDefinition extends HTMLPurifier_Definition
+{
+
+    public $type = 'CSS';
+
+    /**
+     * Assoc array of attribute name to definition object.
+     */
+    public $info = array();
+
+    /**
+     * Constructs the info array.  The meat of this class.
+     */
+    protected function doSetup($config) {
+
+        $this->info['text-align'] = new HTMLPurifier_AttrDef_Enum(
+            array('left', 'right', 'center', 'justify'), false);
+
+        $border_style =
+        $this->info['border-bottom-style'] =
+        $this->info['border-right-style'] =
+        $this->info['border-left-style'] =
+        $this->info['border-top-style'] =  new HTMLPurifier_AttrDef_Enum(
+            array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double',
+            'groove', 'ridge', 'inset', 'outset'), false);
+
+        $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style);
+
+        $this->info['clear'] = new HTMLPurifier_AttrDef_Enum(
+            array('none', 'left', 'right', 'both'), false);
+        $this->info['float'] = new HTMLPurifier_AttrDef_Enum(
+            array('none', 'left', 'right'), false);
+        $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum(
+            array('normal', 'italic', 'oblique'), false);
+        $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum(
+            array('normal', 'small-caps'), false);
+
+        $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite(
+            array(
+                new HTMLPurifier_AttrDef_Enum(array('none')),
+                new HTMLPurifier_AttrDef_CSS_URI()
+            )
+        );
+
+        $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum(
+            array('inside', 'outside'), false);
+        $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum(
+            array('disc', 'circle', 'square', 'decimal', 'lower-roman',
+            'upper-roman', 'lower-alpha', 'upper-alpha', 'none'), false);
+        $this->info['list-style-image'] = $uri_or_none;
+
+        $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config);
+
+        $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum(
+            array('capitalize', 'uppercase', 'lowercase', 'none'), false);
+        $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color();
+
+        $this->info['background-image'] = $uri_or_none;
+        $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum(
+            array('repeat', 'repeat-x', 'repeat-y', 'no-repeat')
+        );
+        $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum(
+            array('scroll', 'fixed')
+        );
+        $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition();
+
+        $border_color =
+        $this->info['border-top-color'] =
+        $this->info['border-bottom-color'] =
+        $this->info['border-left-color'] =
+        $this->info['border-right-color'] =
+        $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_Enum(array('transparent')),
+            new HTMLPurifier_AttrDef_CSS_Color()
+        ));
+
+        $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config);
+
+        $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color);
+
+        $border_width =
+        $this->info['border-top-width'] =
+        $this->info['border-bottom-width'] =
+        $this->info['border-left-width'] =
+        $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')),
+            new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative
+        ));
+
+        $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width);
+
+        $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_Enum(array('normal')),
+            new HTMLPurifier_AttrDef_CSS_Length()
+        ));
+
+        $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_Enum(array('normal')),
+            new HTMLPurifier_AttrDef_CSS_Length()
+        ));
+
+        $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_Enum(array('xx-small', 'x-small',
+                'small', 'medium', 'large', 'x-large', 'xx-large',
+                'larger', 'smaller')),
+            new HTMLPurifier_AttrDef_CSS_Percentage(),
+            new HTMLPurifier_AttrDef_CSS_Length()
+        ));
+
+        $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_Enum(array('normal')),
+            new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives
+            new HTMLPurifier_AttrDef_CSS_Length('0'),
+            new HTMLPurifier_AttrDef_CSS_Percentage(true)
+        ));
+
+        $margin =
+        $this->info['margin-top'] =
+        $this->info['margin-bottom'] =
+        $this->info['margin-left'] =
+        $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_CSS_Length(),
+            new HTMLPurifier_AttrDef_CSS_Percentage(),
+            new HTMLPurifier_AttrDef_Enum(array('auto'))
+        ));
+
+        $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin);
+
+        // non-negative
+        $padding =
+        $this->info['padding-top'] =
+        $this->info['padding-bottom'] =
+        $this->info['padding-left'] =
+        $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_CSS_Length('0'),
+            new HTMLPurifier_AttrDef_CSS_Percentage(true)
+        ));
+
+        $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding);
+
+        $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_CSS_Length(),
+            new HTMLPurifier_AttrDef_CSS_Percentage()
+        ));
+
+        $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_CSS_Length('0'),
+            new HTMLPurifier_AttrDef_CSS_Percentage(true),
+            new HTMLPurifier_AttrDef_Enum(array('auto'))
+        ));
+        $max = $config->get('CSS.MaxImgLength');
+
+        $this->info['width'] =
+        $this->info['height'] =
+            $max === null ?
+            $trusted_wh :
+            new HTMLPurifier_AttrDef_Switch('img',
+                // For img tags:
+                new HTMLPurifier_AttrDef_CSS_Composite(array(
+                    new HTMLPurifier_AttrDef_CSS_Length('0', $max),
+                    new HTMLPurifier_AttrDef_Enum(array('auto'))
+                )),
+                // For everyone else:
+                $trusted_wh
+            );
+
+        $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration();
+
+        $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily();
+
+        // this could use specialized code
+        $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum(
+            array('normal', 'bold', 'bolder', 'lighter', '100', '200', '300',
+            '400', '500', '600', '700', '800', '900'), false);
+
+        // MUST be called after other font properties, as it references
+        // a CSSDefinition object
+        $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config);
+
+        // same here
+        $this->info['border'] =
+        $this->info['border-bottom'] =
+        $this->info['border-top'] =
+        $this->info['border-left'] =
+        $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config);
+
+        $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum(array(
+            'collapse', 'separate'));
+
+        $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum(array(
+            'top', 'bottom'));
+
+        $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum(array(
+            'auto', 'fixed'));
+
+        $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_Enum(array('baseline', 'sub', 'super',
+                'top', 'text-top', 'middle', 'bottom', 'text-bottom')),
+            new HTMLPurifier_AttrDef_CSS_Length(),
+            new HTMLPurifier_AttrDef_CSS_Percentage()
+        ));
+
+        $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2);
+
+        // These CSS properties don't work on many browsers, but we live
+        // in THE FUTURE!
+        $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum(array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line'));
+
+        if ($config->get('CSS.Proprietary')) {
+            $this->doSetupProprietary($config);
+        }
+
+        if ($config->get('CSS.AllowTricky')) {
+            $this->doSetupTricky($config);
+        }
+
+        if ($config->get('CSS.Trusted')) {
+            $this->doSetupTrusted($config);
+        }
+
+        $allow_important = $config->get('CSS.AllowImportant');
+        // wrap all attr-defs with decorator that handles !important
+        foreach ($this->info as $k => $v) {
+            $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important);
+        }
+
+        $this->setupConfigStuff($config);
+    }
+
+    protected function doSetupProprietary($config) {
+        // Internet Explorer only scrollbar colors
+        $this->info['scrollbar-arrow-color']        = new HTMLPurifier_AttrDef_CSS_Color();
+        $this->info['scrollbar-base-color']         = new HTMLPurifier_AttrDef_CSS_Color();
+        $this->info['scrollbar-darkshadow-color']   = new HTMLPurifier_AttrDef_CSS_Color();
+        $this->info['scrollbar-face-color']         = new HTMLPurifier_AttrDef_CSS_Color();
+        $this->info['scrollbar-highlight-color']    = new HTMLPurifier_AttrDef_CSS_Color();
+        $this->info['scrollbar-shadow-color']       = new HTMLPurifier_AttrDef_CSS_Color();
+
+        // technically not proprietary, but CSS3, and no one supports it
+        $this->info['opacity']          = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+        $this->info['-moz-opacity']     = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+        $this->info['-khtml-opacity']   = new HTMLPurifier_AttrDef_CSS_AlphaValue();
+
+        // only opacity, for now
+        $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter();
+
+        // more CSS3
+        $this->info['page-break-after'] =
+        $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum(array('auto','always','avoid','left','right'));
+        $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto','avoid'));
+
+    }
+
+    protected function doSetupTricky($config) {
+        $this->info['display'] = new HTMLPurifier_AttrDef_Enum(array(
+            'inline', 'block', 'list-item', 'run-in', 'compact',
+            'marker', 'table', 'inline-block', 'inline-table', 'table-row-group',
+            'table-header-group', 'table-footer-group', 'table-row',
+            'table-column-group', 'table-column', 'table-cell', 'table-caption', 'none'
+        ));
+        $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum(array(
+            'visible', 'hidden', 'collapse'
+        ));
+        $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll'));
+    }
+
+    protected function doSetupTrusted($config) {
+        $this->info['position'] = new HTMLPurifier_AttrDef_Enum(array(
+            'static', 'relative', 'absolute', 'fixed'
+        ));
+        $this->info['top'] =
+        $this->info['left'] =
+        $this->info['right'] =
+        $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_CSS_Length(),
+            new HTMLPurifier_AttrDef_CSS_Percentage(),
+            new HTMLPurifier_AttrDef_Enum(array('auto')),
+        ));
+        $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite(array(
+            new HTMLPurifier_AttrDef_Integer(),
+            new HTMLPurifier_AttrDef_Enum(array('auto')),
+        ));
+    }
+
+    /**
+     * Performs extra config-based processing. Based off of
+     * HTMLPurifier_HTMLDefinition.
+     * @todo Refactor duplicate elements into common class (probably using
+     *       composition, not inheritance).
+     */
+    protected function setupConfigStuff($config) {
+
+        // setup allowed elements
+        $support = "(for information on implementing this, see the ".
+                   "support forums) ";
+        $allowed_properties = $config->get('CSS.AllowedProperties');
+        if ($allowed_properties !== null) {
+            foreach ($this->info as $name => $d) {
+                if(!isset($allowed_properties[$name])) unset($this->info[$name]);
+                unset($allowed_properties[$name]);
+            }
+            // emit errors
+            foreach ($allowed_properties as $name => $d) {
+                // :TODO: Is this htmlspecialchars() call really necessary?
+                $name = htmlspecialchars($name);
+                trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING);
+            }
+        }
+
+        $forbidden_properties = $config->get('CSS.ForbiddenProperties');
+        if ($forbidden_properties !== null) {
+            foreach ($this->info as $name => $d) {
+                if (isset($forbidden_properties[$name])) {
+                    unset($this->info[$name]);
+                }
+            }
+        }
+
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef.php
new file mode 100644
index 0000000..c5d5216
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Defines allowed child nodes and validates tokens against it.
+ */
+abstract class HTMLPurifier_ChildDef
+{
+    /**
+     * Type of child definition, usually right-most part of class name lowercase.
+     * Used occasionally in terms of context.
+     */
+    public $type;
+
+    /**
+     * Bool that indicates whether or not an empty array of children is okay
+     *
+     * This is necessary for redundant checking when changes affecting
+     * a child node may cause a parent node to now be disallowed.
+     */
+    public $allow_empty;
+
+    /**
+     * Lookup array of all elements that this definition could possibly allow
+     */
+    public $elements = array();
+
+    /**
+     * Get lookup of tag names that should not close this element automatically.
+     * All other elements will do so.
+     */
+    public function getAllowedElements($config) {
+        return $this->elements;
+    }
+
+    /**
+     * Validates nodes according to definition and returns modification.
+     *
+     * @param $tokens_of_children Array of HTMLPurifier_Token
+     * @param $config HTMLPurifier_Config object
+     * @param $context HTMLPurifier_Context object
+     * @return bool true to leave nodes as is
+     * @return bool false to remove parent node
+     * @return array of replacement child tokens
+     */
+    abstract public function validateChildren($tokens_of_children, $config, $context);
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Chameleon.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Chameleon.php
new file mode 100644
index 0000000..15c364e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Chameleon.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * Definition that uses different definitions depending on context.
+ *
+ * The del and ins tags are notable because they allow different types of
+ * elements depending on whether or not they're in a block or inline context.
+ * Chameleon allows this behavior to happen by using two different
+ * definitions depending on context.  While this somewhat generalized,
+ * it is specifically intended for those two tags.
+ */
+class HTMLPurifier_ChildDef_Chameleon extends HTMLPurifier_ChildDef
+{
+
+    /**
+     * Instance of the definition object to use when inline. Usually stricter.
+     */
+    public $inline;
+
+    /**
+     * Instance of the definition object to use when block.
+     */
+    public $block;
+
+    public $type = 'chameleon';
+
+    /**
+     * @param $inline List of elements to allow when inline.
+     * @param $block List of elements to allow when block.
+     */
+    public function __construct($inline, $block) {
+        $this->inline = new HTMLPurifier_ChildDef_Optional($inline);
+        $this->block  = new HTMLPurifier_ChildDef_Optional($block);
+        $this->elements = $this->block->elements;
+    }
+
+    public function validateChildren($tokens_of_children, $config, $context) {
+        if ($context->get('IsInline') === false) {
+            return $this->block->validateChildren(
+                $tokens_of_children, $config, $context);
+        } else {
+            return $this->inline->validateChildren(
+                $tokens_of_children, $config, $context);
+        }
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Custom.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Custom.php
new file mode 100644
index 0000000..b68047b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Custom.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Custom validation class, accepts DTD child definitions
+ *
+ * @warning Currently this class is an all or nothing proposition, that is,
+ *          it will only give a bool return value.
+ */
+class HTMLPurifier_ChildDef_Custom extends HTMLPurifier_ChildDef
+{
+    public $type = 'custom';
+    public $allow_empty = false;
+    /**
+     * Allowed child pattern as defined by the DTD
+     */
+    public $dtd_regex;
+    /**
+     * PCRE regex derived from $dtd_regex
+     * @private
+     */
+    private $_pcre_regex;
+    /**
+     * @param $dtd_regex Allowed child pattern from the DTD
+     */
+    public function __construct($dtd_regex) {
+        $this->dtd_regex = $dtd_regex;
+        $this->_compileRegex();
+    }
+    /**
+     * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex)
+     */
+    protected function _compileRegex() {
+        $raw = str_replace(' ', '', $this->dtd_regex);
+        if ($raw{0} != '(') {
+            $raw = "($raw)";
+        }
+        $el = '[#a-zA-Z0-9_.-]+';
+        $reg = $raw;
+
+        // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M
+        // DOING! Seriously: if there's problems, please report them.
+
+        // collect all elements into the $elements array
+        preg_match_all("/$el/", $reg, $matches);
+        foreach ($matches[0] as $match) {
+            $this->elements[$match] = true;
+        }
+
+        // setup all elements as parentheticals with leading commas
+        $reg = preg_replace("/$el/", '(,\\0)', $reg);
+
+        // remove commas when they were not solicited
+        $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg);
+
+        // remove all non-paranthetical commas: they are handled by first regex
+        $reg = preg_replace("/,\(/", '(', $reg);
+
+        $this->_pcre_regex = $reg;
+    }
+    public function validateChildren($tokens_of_children, $config, $context) {
+        $list_of_children = '';
+        $nesting = 0; // depth into the nest
+        foreach ($tokens_of_children as $token) {
+            if (!empty($token->is_whitespace)) continue;
+
+            $is_child = ($nesting == 0); // direct
+
+            if ($token instanceof HTMLPurifier_Token_Start) {
+                $nesting++;
+            } elseif ($token instanceof HTMLPurifier_Token_End) {
+                $nesting--;
+            }
+
+            if ($is_child) {
+                $list_of_children .= $token->name . ',';
+            }
+        }
+        // add leading comma to deal with stray comma declarations
+        $list_of_children = ',' . rtrim($list_of_children, ',');
+        $okay =
+            preg_match(
+                '/^,?'.$this->_pcre_regex.'$/',
+                $list_of_children
+            );
+
+        return (bool) $okay;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Empty.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Empty.php
new file mode 100644
index 0000000..13171f6
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Empty.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Definition that disallows all elements.
+ * @warning validateChildren() in this class is actually never called, because
+ *          empty elements are corrected in HTMLPurifier_Strategy_MakeWellFormed
+ *          before child definitions are parsed in earnest by
+ *          HTMLPurifier_Strategy_FixNesting.
+ */
+class HTMLPurifier_ChildDef_Empty extends HTMLPurifier_ChildDef
+{
+    public $allow_empty = true;
+    public $type = 'empty';
+    public function __construct() {}
+    public function validateChildren($tokens_of_children, $config, $context) {
+        return array();
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/List.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/List.php
new file mode 100644
index 0000000..cdaa289
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/List.php
@@ -0,0 +1,120 @@
+<?php
+
+/**
+ * Definition for list containers ul and ol.
+ */
+class HTMLPurifier_ChildDef_List extends HTMLPurifier_ChildDef
+{
+    public $type = 'list';
+    // lying a little bit, so that we can handle ul and ol ourselves
+    // XXX: This whole business with 'wrap' is all a bit unsatisfactory
+    public $elements = array('li' => true, 'ul' => true, 'ol' => true);
+    public function validateChildren($tokens_of_children, $config, $context) {
+        // Flag for subclasses
+        $this->whitespace = false;
+
+        // if there are no tokens, delete parent node
+        if (empty($tokens_of_children)) return false;
+
+        // the new set of children
+        $result = array();
+
+        // current depth into the nest
+        $nesting = 0;
+
+        // a little sanity check to make sure it's not ALL whitespace
+        $all_whitespace = true;
+
+        $seen_li = false;
+        $need_close_li = false;
+
+        foreach ($tokens_of_children as $token) {
+            if (!empty($token->is_whitespace)) {
+                $result[] = $token;
+                continue;
+            }
+            $all_whitespace = false; // phew, we're not talking about whitespace
+
+            if ($nesting == 1 && $need_close_li) {
+                $result[] = new HTMLPurifier_Token_End('li');
+                $nesting--;
+                $need_close_li = false;
+            }
+
+            $is_child = ($nesting == 0);
+
+            if ($token instanceof HTMLPurifier_Token_Start) {
+                $nesting++;
+            } elseif ($token instanceof HTMLPurifier_Token_End) {
+                $nesting--;
+            }
+
+            if ($is_child) {
+                if ($token->name === 'li') {
+                    // good
+                    $seen_li = true;
+                } elseif ($token->name === 'ul' || $token->name === 'ol') {
+                    // we want to tuck this into the previous li
+                    $need_close_li = true;
+                    $nesting++;
+                    if (!$seen_li) {
+                        // create a new li element
+                        $result[] = new HTMLPurifier_Token_Start('li');
+                    } else {
+                        // backtrack until </li> found
+                        while(true) {
+                            $t = array_pop($result);
+                            if ($t instanceof HTMLPurifier_Token_End) {
+                                // XXX actually, these invariants could very plausibly be violated
+                                // if we are doing silly things with modifying the set of allowed elements.
+                                // FORTUNATELY, it doesn't make a difference, since the allowed
+                                // elements are hard-coded here!
+                                if ($t->name !== 'li') {
+                                    trigger_error("Only li present invariant violated in List ChildDef", E_USER_ERROR);
+                                    return false;
+                                }
+                                break;
+                            } elseif ($t instanceof HTMLPurifier_Token_Empty) { // bleagh
+                                if ($t->name !== 'li') {
+                                    trigger_error("Only li present invariant violated in List ChildDef", E_USER_ERROR);
+                                    return false;
+                                }
+                                // XXX this should have a helper for it...
+                                $result[] = new HTMLPurifier_Token_Start('li', $t->attr, $t->line, $t->col, $t->armor);
+                                break;
+                            } else {
+                                if (!$t->is_whitespace) {
+                                    trigger_error("Only whitespace present invariant violated in List ChildDef", E_USER_ERROR);
+                                    return false;
+                                }
+                            }
+                        }
+                    }
+                } else {
+                    // start wrapping (this doesn't precisely mimic
+                    // browser behavior, but what browsers do is kind of
+                    // hard to mimic in a standards compliant way
+                    // XXX Actually, this has no impact in practice,
+                    // because this gets handled earlier. Arguably,
+                    // we should rip out all of that processing
+                    $result[] = new HTMLPurifier_Token_Start('li');
+                    $nesting++;
+                    $seen_li = true;
+                    $need_close_li = true;
+                }
+            }
+            $result[] = $token;
+        }
+        if ($need_close_li) {
+            $result[] = new HTMLPurifier_Token_End('li');
+        }
+        if (empty($result)) return false;
+        if ($all_whitespace) {
+            return false;
+        }
+        if ($tokens_of_children == $result) return true;
+        return $result;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Optional.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Optional.php
new file mode 100644
index 0000000..32bcb98
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Optional.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Definition that allows a set of elements, and allows no children.
+ * @note This is a hack to reuse code from HTMLPurifier_ChildDef_Required,
+ *       really, one shouldn't inherit from the other.  Only altered behavior
+ *       is to overload a returned false with an array.  Thus, it will never
+ *       return false.
+ */
+class HTMLPurifier_ChildDef_Optional extends HTMLPurifier_ChildDef_Required
+{
+    public $allow_empty = true;
+    public $type = 'optional';
+    public function validateChildren($tokens_of_children, $config, $context) {
+        $result = parent::validateChildren($tokens_of_children, $config, $context);
+        // we assume that $tokens_of_children is not modified
+        if ($result === false) {
+            if (empty($tokens_of_children)) return true;
+            elseif ($this->whitespace) return $tokens_of_children;
+            else return array();
+        }
+        return $result;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Required.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Required.php
new file mode 100644
index 0000000..4889f24
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Required.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * Definition that allows a set of elements, but disallows empty children.
+ */
+class HTMLPurifier_ChildDef_Required extends HTMLPurifier_ChildDef
+{
+    /**
+     * Lookup table of allowed elements.
+     * @public
+     */
+    public $elements = array();
+    /**
+     * Whether or not the last passed node was all whitespace.
+     */
+    protected $whitespace = false;
+    /**
+     * @param $elements List of allowed element names (lowercase).
+     */
+    public function __construct($elements) {
+        if (is_string($elements)) {
+            $elements = str_replace(' ', '', $elements);
+            $elements = explode('|', $elements);
+        }
+        $keys = array_keys($elements);
+        if ($keys == array_keys($keys)) {
+            $elements = array_flip($elements);
+            foreach ($elements as $i => $x) {
+                $elements[$i] = true;
+                if (empty($i)) unset($elements[$i]); // remove blank
+            }
+        }
+        $this->elements = $elements;
+    }
+    public $allow_empty = false;
+    public $type = 'required';
+    public function validateChildren($tokens_of_children, $config, $context) {
+        // Flag for subclasses
+        $this->whitespace = false;
+
+        // if there are no tokens, delete parent node
+        if (empty($tokens_of_children)) return false;
+
+        // the new set of children
+        $result = array();
+
+        // current depth into the nest
+        $nesting = 0;
+
+        // whether or not we're deleting a node
+        $is_deleting = false;
+
+        // whether or not parsed character data is allowed
+        // this controls whether or not we silently drop a tag
+        // or generate escaped HTML from it
+        $pcdata_allowed = isset($this->elements['#PCDATA']);
+
+        // a little sanity check to make sure it's not ALL whitespace
+        $all_whitespace = true;
+
+        // some configuration
+        $escape_invalid_children = $config->get('Core.EscapeInvalidChildren');
+
+        // generator
+        $gen = new HTMLPurifier_Generator($config, $context);
+
+        foreach ($tokens_of_children as $token) {
+            if (!empty($token->is_whitespace)) {
+                $result[] = $token;
+                continue;
+            }
+            $all_whitespace = false; // phew, we're not talking about whitespace
+
+            $is_child = ($nesting == 0);
+
+            if ($token instanceof HTMLPurifier_Token_Start) {
+                $nesting++;
+            } elseif ($token instanceof HTMLPurifier_Token_End) {
+                $nesting--;
+            }
+
+            if ($is_child) {
+                $is_deleting = false;
+                if (!isset($this->elements[$token->name])) {
+                    $is_deleting = true;
+                    if ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text) {
+                        $result[] = $token;
+                    } elseif ($pcdata_allowed && $escape_invalid_children) {
+                        $result[] = new HTMLPurifier_Token_Text(
+                            $gen->generateFromToken($token)
+                        );
+                    }
+                    continue;
+                }
+            }
+            if (!$is_deleting || ($pcdata_allowed && $token instanceof HTMLPurifier_Token_Text)) {
+                $result[] = $token;
+            } elseif ($pcdata_allowed && $escape_invalid_children) {
+                $result[] =
+                    new HTMLPurifier_Token_Text(
+                        $gen->generateFromToken($token)
+                    );
+            } else {
+                // drop silently
+            }
+        }
+        if (empty($result)) return false;
+        if ($all_whitespace) {
+            $this->whitespace = true;
+            return false;
+        }
+        if ($tokens_of_children == $result) return true;
+        return $result;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/StrictBlockquote.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/StrictBlockquote.php
new file mode 100644
index 0000000..dfae8a6
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/StrictBlockquote.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Takes the contents of blockquote when in strict and reformats for validation.
+ */
+class HTMLPurifier_ChildDef_StrictBlockquote extends HTMLPurifier_ChildDef_Required
+{
+    protected $real_elements;
+    protected $fake_elements;
+    public $allow_empty = true;
+    public $type = 'strictblockquote';
+    protected $init = false;
+
+    /**
+     * @note We don't want MakeWellFormed to auto-close inline elements since
+     *       they might be allowed.
+     */
+    public function getAllowedElements($config) {
+        $this->init($config);
+        return $this->fake_elements;
+    }
+
+    public function validateChildren($tokens_of_children, $config, $context) {
+
+        $this->init($config);
+
+        // trick the parent class into thinking it allows more
+        $this->elements = $this->fake_elements;
+        $result = parent::validateChildren($tokens_of_children, $config, $context);
+        $this->elements = $this->real_elements;
+
+        if ($result === false) return array();
+        if ($result === true) $result = $tokens_of_children;
+
+        $def = $config->getHTMLDefinition();
+        $block_wrap_start = new HTMLPurifier_Token_Start($def->info_block_wrapper);
+        $block_wrap_end   = new HTMLPurifier_Token_End(  $def->info_block_wrapper);
+        $is_inline = false;
+        $depth = 0;
+        $ret = array();
+
+        // assuming that there are no comment tokens
+        foreach ($result as $i => $token) {
+            $token = $result[$i];
+            // ifs are nested for readability
+            if (!$is_inline) {
+                if (!$depth) {
+                     if (
+                        ($token instanceof HTMLPurifier_Token_Text && !$token->is_whitespace) ||
+                        (!$token instanceof HTMLPurifier_Token_Text && !isset($this->elements[$token->name]))
+                     ) {
+                        $is_inline = true;
+                        $ret[] = $block_wrap_start;
+                     }
+                }
+            } else {
+                if (!$depth) {
+                    // starting tokens have been inline text / empty
+                    if ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) {
+                        if (isset($this->elements[$token->name])) {
+                            // ended
+                            $ret[] = $block_wrap_end;
+                            $is_inline = false;
+                        }
+                    }
+                }
+            }
+            $ret[] = $token;
+            if ($token instanceof HTMLPurifier_Token_Start) $depth++;
+            if ($token instanceof HTMLPurifier_Token_End)   $depth--;
+        }
+        if ($is_inline) $ret[] = $block_wrap_end;
+        return $ret;
+    }
+
+    private function init($config) {
+        if (!$this->init) {
+            $def = $config->getHTMLDefinition();
+            // allow all inline elements
+            $this->real_elements = $this->elements;
+            $this->fake_elements = $def->info_content_sets['Flow'];
+            $this->fake_elements['#PCDATA'] = true;
+            $this->init = true;
+        }
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Table.php b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Table.php
new file mode 100644
index 0000000..9a93421
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ChildDef/Table.php
@@ -0,0 +1,227 @@
+<?php
+
+/**
+ * Definition for tables.  The general idea is to extract out all of the
+ * essential bits, and then reconstruct it later.
+ *
+ * This is a bit confusing, because the DTDs and the W3C
+ * validators seem to disagree on the appropriate definition. The
+ * DTD claims:
+ *
+ *      (CAPTION?, (COL*|COLGROUP*), THEAD?, TFOOT?, TBODY+)
+ *
+ * But actually, the HTML4 spec then has this to say:
+ *
+ *      The TBODY start tag is always required except when the table
+ *      contains only one table body and no table head or foot sections.
+ *      The TBODY end tag may always be safely omitted.
+ *
+ * So the DTD is kind of wrong.  The validator is, unfortunately, kind
+ * of on crack.
+ *
+ * The definition changed again in XHTML1.1; and in my opinion, this
+ * formulation makes the most sense.
+ *
+ *      caption?, ( col* | colgroup* ), (( thead?, tfoot?, tbody+ ) | ( tr+ ))
+ *
+ * Essentially, we have two modes: thead/tfoot/tbody mode, and tr mode.
+ * If we encounter a thead, tfoot or tbody, we are placed in the former
+ * mode, and we *must* wrap any stray tr segments with a tbody. But if
+ * we don't run into any of them, just have tr tags is OK.
+ */
+class HTMLPurifier_ChildDef_Table extends HTMLPurifier_ChildDef
+{
+    public $allow_empty = false;
+    public $type = 'table';
+    public $elements = array('tr' => true, 'tbody' => true, 'thead' => true,
+        'tfoot' => true, 'caption' => true, 'colgroup' => true, 'col' => true);
+    public function __construct() {}
+    public function validateChildren($tokens_of_children, $config, $context) {
+        if (empty($tokens_of_children)) return false;
+
+        // this ensures that the loop gets run one last time before closing
+        // up. It's a little bit of a hack, but it works! Just make sure you
+        // get rid of the token later.
+        $tokens_of_children[] = false;
+
+        // only one of these elements is allowed in a table
+        $caption = false;
+        $thead   = false;
+        $tfoot   = false;
+
+        // as many of these as you want
+        $cols    = array();
+        $content = array();
+
+        $nesting = 0; // current depth so we can determine nodes
+        $is_collecting = false; // are we globbing together tokens to package
+                                // into one of the collectors?
+        $collection = array(); // collected nodes
+        $tag_index = 0; // the first node might be whitespace,
+                            // so this tells us where the start tag is
+        $tbody_mode = false; // if true, then we need to wrap any stray
+                             // <tr>s with a <tbody>.
+
+        foreach ($tokens_of_children as $token) {
+            $is_child = ($nesting == 0);
+
+            if ($token === false) {
+                // terminating sequence started
+            } elseif ($token instanceof HTMLPurifier_Token_Start) {
+                $nesting++;
+            } elseif ($token instanceof HTMLPurifier_Token_End) {
+                $nesting--;
+            }
+
+            // handle node collection
+            if ($is_collecting) {
+                if ($is_child) {
+                    // okay, let's stash the tokens away
+                    // first token tells us the type of the collection
+                    switch ($collection[$tag_index]->name) {
+                        case 'tbody':
+                            $tbody_mode = true;
+                        case 'tr':
+                            $content[] = $collection;
+                            break;
+                        case 'caption':
+                            if ($caption !== false) break;
+                            $caption = $collection;
+                            break;
+                        case 'thead':
+                        case 'tfoot':
+                            $tbody_mode = true;
+                            // XXX This breaks rendering properties with
+                            // Firefox, which never floats a <thead> to
+                            // the top. Ever. (Our scheme will float the
+                            // first <thead> to the top.)  So maybe
+                            // <thead>s that are not first should be
+                            // turned into <tbody>? Very tricky, indeed.
+
+                            // access the appropriate variable, $thead or $tfoot
+                            $var = $collection[$tag_index]->name;
+                            if ($$var === false) {
+                                $$var = $collection;
+                            } else {
+                                // Oops, there's a second one! What
+                                // should we do?  Current behavior is to
+                                // transmutate the first and last entries into
+                                // tbody tags, and then put into content.
+                                // Maybe a better idea is to *attach
+                                // it* to the existing thead or tfoot?
+                                // We don't do this, because Firefox
+                                // doesn't float an extra tfoot to the
+                                // bottom like it does for the first one.
+                                $collection[$tag_index]->name = 'tbody';
+                                $collection[count($collection)-1]->name = 'tbody';
+                                $content[] = $collection;
+                            }
+                            break;
+                         case 'colgroup':
+                            $cols[] = $collection;
+                            break;
+                    }
+                    $collection = array();
+                    $is_collecting = false;
+                    $tag_index = 0;
+                } else {
+                    // add the node to the collection
+                    $collection[] = $token;
+                }
+            }
+
+            // terminate
+            if ($token === false) break;
+
+            if ($is_child) {
+                // determine what we're dealing with
+                if ($token->name == 'col') {
+                    // the only empty tag in the possie, we can handle it
+                    // immediately
+                    $cols[] = array_merge($collection, array($token));
+                    $collection = array();
+                    $tag_index = 0;
+                    continue;
+                }
+                switch($token->name) {
+                    case 'caption':
+                    case 'colgroup':
+                    case 'thead':
+                    case 'tfoot':
+                    case 'tbody':
+                    case 'tr':
+                        $is_collecting = true;
+                        $collection[] = $token;
+                        continue;
+                    default:
+                        if (!empty($token->is_whitespace)) {
+                            $collection[] = $token;
+                            $tag_index++;
+                        }
+                        continue;
+                }
+            }
+        }
+
+        if (empty($content)) return false;
+
+        $ret = array();
+        if ($caption !== false) $ret = array_merge($ret, $caption);
+        if ($cols !== false)    foreach ($cols as $token_array) $ret = array_merge($ret, $token_array);
+        if ($thead !== false)   $ret = array_merge($ret, $thead);
+        if ($tfoot !== false)   $ret = array_merge($ret, $tfoot);
+
+        if ($tbody_mode) {
+            // a little tricky, since the start of the collection may be
+            // whitespace
+            $inside_tbody = false;
+            foreach ($content as $token_array) {
+                // find the starting token
+                foreach ($token_array as $t) {
+                    if ($t->name === 'tr' || $t->name === 'tbody') {
+                        break;
+                    }
+                } // iterator variable carries over
+                if ($t->name === 'tr') {
+                    if ($inside_tbody) {
+                        $ret = array_merge($ret, $token_array);
+                    } else {
+                        $ret[] = new HTMLPurifier_Token_Start('tbody');
+                        $ret = array_merge($ret, $token_array);
+                        $inside_tbody = true;
+                    }
+                } elseif ($t->name === 'tbody') {
+                    if ($inside_tbody) {
+                        $ret[] = new HTMLPurifier_Token_End('tbody');
+                        $inside_tbody = false;
+                        $ret = array_merge($ret, $token_array);
+                    } else {
+                        $ret = array_merge($ret, $token_array);
+                    }
+                } else {
+                    trigger_error("tr/tbody in content invariant failed in Table ChildDef", E_USER_ERROR);
+                }
+            }
+            if ($inside_tbody) {
+                $ret[] = new HTMLPurifier_Token_End('tbody');
+            }
+        } else {
+            foreach ($content as $token_array) {
+                // invariant: everything in here is <tr>s
+                $ret = array_merge($ret, $token_array);
+            }
+        }
+
+        if (!empty($collection) && $is_collecting == false){
+            // grab the trailing space
+            $ret = array_merge($ret, $collection);
+        }
+
+        array_pop($tokens_of_children); // remove phantom token
+
+        return ($ret === $tokens_of_children) ? true : $ret;
+
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Config.php b/application/libraries/htmlpurifier/HTMLPurifier/Config.php
new file mode 100644
index 0000000..489ea04
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Config.php
@@ -0,0 +1,710 @@
+<?php
+
+/**
+ * Configuration object that triggers customizable behavior.
+ *
+ * @warning This class is strongly defined: that means that the class
+ *          will fail if an undefined directive is retrieved or set.
+ *
+ * @note Many classes that could (although many times don't) use the
+ *       configuration object make it a mandatory parameter.  This is
+ *       because a configuration object should always be forwarded,
+ *       otherwise, you run the risk of missing a parameter and then
+ *       being stumped when a configuration directive doesn't work.
+ *
+ * @todo Reconsider some of the public member variables
+ */
+class HTMLPurifier_Config
+{
+
+    /**
+     * HTML Purifier's version
+     */
+    public $version = '4.5.0';
+
+    /**
+     * Bool indicator whether or not to automatically finalize
+     * the object if a read operation is done
+     */
+    public $autoFinalize = true;
+
+    // protected member variables
+
+    /**
+     * Namespace indexed array of serials for specific namespaces (see
+     * getSerial() for more info).
+     */
+    protected $serials = array();
+
+    /**
+     * Serial for entire configuration object
+     */
+    protected $serial;
+
+    /**
+     * Parser for variables
+     */
+    protected $parser = null;
+
+    /**
+     * Reference HTMLPurifier_ConfigSchema for value checking
+     * @note This is public for introspective purposes. Please don't
+     *       abuse!
+     */
+    public $def;
+
+    /**
+     * Indexed array of definitions
+     */
+    protected $definitions;
+
+    /**
+     * Bool indicator whether or not config is finalized
+     */
+    protected $finalized = false;
+
+    /**
+     * Property list containing configuration directives.
+     */
+    protected $plist;
+
+    /**
+     * Whether or not a set is taking place due to an
+     * alias lookup.
+     */
+    private $aliasMode;
+
+    /**
+     * Set to false if you do not want line and file numbers in errors
+     * (useful when unit testing).  This will also compress some errors
+     * and exceptions.
+     */
+    public $chatty = true;
+
+    /**
+     * Current lock; only gets to this namespace are allowed.
+     */
+    private $lock;
+
+    /**
+     * @param $definition HTMLPurifier_ConfigSchema that defines what directives
+     *                    are allowed.
+     */
+    public function __construct($definition, $parent = null) {
+        $parent = $parent ? $parent : $definition->defaultPlist;
+        $this->plist = new HTMLPurifier_PropertyList($parent);
+        $this->def = $definition; // keep a copy around for checking
+        $this->parser = new HTMLPurifier_VarParser_Flexible();
+    }
+
+    /**
+     * Convenience constructor that creates a config object based on a mixed var
+     * @param mixed $config Variable that defines the state of the config
+     *                      object. Can be: a HTMLPurifier_Config() object,
+     *                      an array of directives based on loadArray(),
+     *                      or a string filename of an ini file.
+     * @param HTMLPurifier_ConfigSchema Schema object
+     * @return Configured HTMLPurifier_Config object
+     */
+    public static function create($config, $schema = null) {
+        if ($config instanceof HTMLPurifier_Config) {
+            // pass-through
+            return $config;
+        }
+        if (!$schema) {
+            $ret = HTMLPurifier_Config::createDefault();
+        } else {
+            $ret = new HTMLPurifier_Config($schema);
+        }
+        if (is_string($config)) $ret->loadIni($config);
+        elseif (is_array($config)) $ret->loadArray($config);
+        return $ret;
+    }
+
+    /**
+     * Creates a new config object that inherits from a previous one.
+     * @param HTMLPurifier_Config $config Configuration object to inherit
+     *        from.
+     * @return HTMLPurifier_Config object with $config as its parent.
+     */
+    public static function inherit(HTMLPurifier_Config $config) {
+        return new HTMLPurifier_Config($config->def, $config->plist);
+    }
+
+    /**
+     * Convenience constructor that creates a default configuration object.
+     * @return Default HTMLPurifier_Config object.
+     */
+    public static function createDefault() {
+        $definition = HTMLPurifier_ConfigSchema::instance();
+        $config = new HTMLPurifier_Config($definition);
+        return $config;
+    }
+
+    /**
+     * Retreives a value from the configuration.
+     * @param $key String key
+     */
+    public function get($key, $a = null) {
+        if ($a !== null) {
+            $this->triggerError("Using deprecated API: use \$config->get('$key.$a') instead", E_USER_WARNING);
+            $key = "$key.$a";
+        }
+        if (!$this->finalized) $this->autoFinalize();
+        if (!isset($this->def->info[$key])) {
+            // can't add % due to SimpleTest bug
+            $this->triggerError('Cannot retrieve value of undefined directive ' . htmlspecialchars($key),
+                E_USER_WARNING);
+            return;
+        }
+        if (isset($this->def->info[$key]->isAlias)) {
+            $d = $this->def->info[$key];
+            $this->triggerError('Cannot get value from aliased directive, use real name ' . $d->key,
+                E_USER_ERROR);
+            return;
+        }
+        if ($this->lock) {
+            list($ns) = explode('.', $key);
+            if ($ns !== $this->lock) {
+                $this->triggerError('Cannot get value of namespace ' . $ns . ' when lock for ' . $this->lock . ' is active, this probably indicates a Definition setup method is accessing directives that are not within its namespace', E_USER_ERROR);
+                return;
+            }
+        }
+        return $this->plist->get($key);
+    }
+
+    /**
+     * Retreives an array of directives to values from a given namespace
+     * @param $namespace String namespace
+     */
+    public function getBatch($namespace) {
+        if (!$this->finalized) $this->autoFinalize();
+        $full = $this->getAll();
+        if (!isset($full[$namespace])) {
+            $this->triggerError('Cannot retrieve undefined namespace ' . htmlspecialchars($namespace),
+                E_USER_WARNING);
+            return;
+        }
+        return $full[$namespace];
+    }
+
+    /**
+     * Returns a SHA-1 signature of a segment of the configuration object
+     * that uniquely identifies that particular configuration
+     * @note Revision is handled specially and is removed from the batch
+     *       before processing!
+     * @param $namespace Namespace to get serial for
+     */
+    public function getBatchSerial($namespace) {
+        if (empty($this->serials[$namespace])) {
+            $batch = $this->getBatch($namespace);
+            unset($batch['DefinitionRev']);
+            $this->serials[$namespace] = sha1(serialize($batch));
+        }
+        return $this->serials[$namespace];
+    }
+
+    /**
+     * Returns a SHA-1 signature for the entire configuration object
+     * that uniquely identifies that particular configuration
+     */
+    public function getSerial() {
+        if (empty($this->serial)) {
+            $this->serial = sha1(serialize($this->getAll()));
+        }
+        return $this->serial;
+    }
+
+    /**
+     * Retrieves all directives, organized by namespace
+     * @warning This is a pretty inefficient function, avoid if you can
+     */
+    public function getAll() {
+        if (!$this->finalized) $this->autoFinalize();
+        $ret = array();
+        foreach ($this->plist->squash() as $name => $value) {
+            list($ns, $key) = explode('.', $name, 2);
+            $ret[$ns][$key] = $value;
+        }
+        return $ret;
+    }
+
+    /**
+     * Sets a value to configuration.
+     * @param $key String key
+     * @param $value Mixed value
+     */
+    public function set($key, $value, $a = null) {
+        if (strpos($key, '.') === false) {
+            $namespace = $key;
+            $directive = $value;
+            $value = $a;
+            $key = "$key.$directive";
+            $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE);
+        } else {
+            list($namespace) = explode('.', $key);
+        }
+        if ($this->isFinalized('Cannot set directive after finalization')) return;
+        if (!isset($this->def->info[$key])) {
+            $this->triggerError('Cannot set undefined directive ' . htmlspecialchars($key) . ' to value',
+                E_USER_WARNING);
+            return;
+        }
+        $def = $this->def->info[$key];
+
+        if (isset($def->isAlias)) {
+            if ($this->aliasMode) {
+                $this->triggerError('Double-aliases not allowed, please fix '.
+                    'ConfigSchema bug with' . $key, E_USER_ERROR);
+                return;
+            }
+            $this->aliasMode = true;
+            $this->set($def->key, $value);
+            $this->aliasMode = false;
+            $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE);
+            return;
+        }
+
+        // Raw type might be negative when using the fully optimized form
+        // of stdclass, which indicates allow_null == true
+        $rtype = is_int($def) ? $def : $def->type;
+        if ($rtype < 0) {
+            $type = -$rtype;
+            $allow_null = true;
+        } else {
+            $type = $rtype;
+            $allow_null = isset($def->allow_null);
+        }
+
+        try {
+            $value = $this->parser->parse($value, $type, $allow_null);
+        } catch (HTMLPurifier_VarParserException $e) {
+            $this->triggerError('Value for ' . $key . ' is of invalid type, should be ' . HTMLPurifier_VarParser::getTypeName($type), E_USER_WARNING);
+            return;
+        }
+        if (is_string($value) && is_object($def)) {
+            // resolve value alias if defined
+            if (isset($def->aliases[$value])) {
+                $value = $def->aliases[$value];
+            }
+            // check to see if the value is allowed
+            if (isset($def->allowed) && !isset($def->allowed[$value])) {
+                $this->triggerError('Value not supported, valid values are: ' .
+                    $this->_listify($def->allowed), E_USER_WARNING);
+                return;
+            }
+        }
+        $this->plist->set($key, $value);
+
+        // reset definitions if the directives they depend on changed
+        // this is a very costly process, so it's discouraged
+        // with finalization
+        if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') {
+            $this->definitions[$namespace] = null;
+        }
+
+        $this->serials[$namespace] = false;
+    }
+
+    /**
+     * Convenience function for error reporting
+     */
+    private function _listify($lookup) {
+        $list = array();
+        foreach ($lookup as $name => $b) $list[] = $name;
+        return implode(', ', $list);
+    }
+
+    /**
+     * Retrieves object reference to the HTML definition.
+     * @param $raw Return a copy that has not been setup yet. Must be
+     *             called before it's been setup, otherwise won't work.
+     * @param $optimized If true, this method may return null, to
+     *             indicate that a cached version of the modified
+     *             definition object is available and no further edits
+     *             are necessary.  Consider using
+     *             maybeGetRawHTMLDefinition, which is more explicitly
+     *             named, instead.
+     */
+    public function getHTMLDefinition($raw = false, $optimized = false) {
+        return $this->getDefinition('HTML', $raw, $optimized);
+    }
+
+    /**
+     * Retrieves object reference to the CSS definition
+     * @param $raw Return a copy that has not been setup yet. Must be
+     *             called before it's been setup, otherwise won't work.
+     * @param $optimized If true, this method may return null, to
+     *             indicate that a cached version of the modified
+     *             definition object is available and no further edits
+     *             are necessary.  Consider using
+     *             maybeGetRawCSSDefinition, which is more explicitly
+     *             named, instead.
+     */
+    public function getCSSDefinition($raw = false, $optimized = false) {
+        return $this->getDefinition('CSS', $raw, $optimized);
+    }
+
+    /**
+     * Retrieves object reference to the URI definition
+     * @param $raw Return a copy that has not been setup yet. Must be
+     *             called before it's been setup, otherwise won't work.
+     * @param $optimized If true, this method may return null, to
+     *             indicate that a cached version of the modified
+     *             definition object is available and no further edits
+     *             are necessary.  Consider using
+     *             maybeGetRawURIDefinition, which is more explicitly
+     *             named, instead.
+     */
+    public function getURIDefinition($raw = false, $optimized = false) {
+        return $this->getDefinition('URI', $raw, $optimized);
+    }
+
+    /**
+     * Retrieves a definition
+     * @param $type Type of definition: HTML, CSS, etc
+     * @param $raw  Whether or not definition should be returned raw
+     * @param $optimized Only has an effect when $raw is true.  Whether
+     *        or not to return null if the result is already present in
+     *        the cache.  This is off by default for backwards
+     *        compatibility reasons, but you need to do things this
+     *        way in order to ensure that caching is done properly.
+     *        Check out enduser-customize.html for more details.
+     *        We probably won't ever change this default, as much as the
+     *        maybe semantics is the "right thing to do."
+     */
+    public function getDefinition($type, $raw = false, $optimized = false) {
+        if ($optimized && !$raw) {
+            throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false");
+        }
+        if (!$this->finalized) $this->autoFinalize();
+        // temporarily suspend locks, so we can handle recursive definition calls
+        $lock = $this->lock;
+        $this->lock = null;
+        $factory = HTMLPurifier_DefinitionCacheFactory::instance();
+        $cache = $factory->create($type, $this);
+        $this->lock = $lock;
+        if (!$raw) {
+            // full definition
+            // ---------------
+            // check if definition is in memory
+            if (!empty($this->definitions[$type])) {
+                $def = $this->definitions[$type];
+                // check if the definition is setup
+                if ($def->setup) {
+                    return $def;
+                } else {
+                    $def->setup($this);
+                    if ($def->optimized) $cache->add($def, $this);
+                    return $def;
+                }
+            }
+            // check if definition is in cache
+            $def = $cache->get($this);
+            if ($def) {
+                // definition in cache, save to memory and return it
+                $this->definitions[$type] = $def;
+                return $def;
+            }
+            // initialize it
+            $def = $this->initDefinition($type);
+            // set it up
+            $this->lock = $type;
+            $def->setup($this);
+            $this->lock = null;
+            // save in cache
+            $cache->add($def, $this);
+            // return it
+            return $def;
+        } else {
+            // raw definition
+            // --------------
+            // check preconditions
+            $def = null;
+            if ($optimized) {
+                if (is_null($this->get($type . '.DefinitionID'))) {
+                    // fatally error out if definition ID not set
+                    throw new HTMLPurifier_Exception("Cannot retrieve raw version without specifying %$type.DefinitionID");
+                }
+            }
+            if (!empty($this->definitions[$type])) {
+                $def = $this->definitions[$type];
+                if ($def->setup && !$optimized) {
+                    $extra = $this->chatty ? " (try moving this code block earlier in your initialization)" : "";
+                    throw new HTMLPurifier_Exception("Cannot retrieve raw definition after it has already been setup" . $extra);
+                }
+                if ($def->optimized === null) {
+                    $extra = $this->chatty ? " (try flushing your cache)" : "";
+                    throw new HTMLPurifier_Exception("Optimization status of definition is unknown" . $extra);
+                }
+                if ($def->optimized !== $optimized) {
+                    $msg = $optimized ? "optimized" : "unoptimized";
+                    $extra = $this->chatty ? " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" : "";
+                    throw new HTMLPurifier_Exception("Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra);
+                }
+            }
+            // check if definition was in memory
+            if ($def) {
+                if ($def->setup) {
+                    // invariant: $optimized === true (checked above)
+                    return null;
+                } else {
+                    return $def;
+                }
+            }
+            // if optimized, check if definition was in cache
+            // (because we do the memory check first, this formulation
+            // is prone to cache slamming, but I think
+            // guaranteeing that either /all/ of the raw
+            // setup code or /none/ of it is run is more important.)
+            if ($optimized) {
+                // This code path only gets run once; once we put
+                // something in $definitions (which is guaranteed by the
+                // trailing code), we always short-circuit above.
+                $def = $cache->get($this);
+                if ($def) {
+                    // save the full definition for later, but don't
+                    // return it yet
+                    $this->definitions[$type] = $def;
+                    return null;
+                }
+            }
+            // check invariants for creation
+            if (!$optimized) {
+                if (!is_null($this->get($type . '.DefinitionID'))) {
+                    if ($this->chatty) {
+                        $this->triggerError("Due to a documentation error in previous version of HTML Purifier, your definitions are not being cached.  If this is OK, you can remove the %$type.DefinitionRev and %$type.DefinitionID declaration.  Otherwise, modify your code to use maybeGetRawDefinition, and test if the returned value is null before making any edits (if it is null, that means that a cached version is available, and no raw operations are necessary).  See <a href='http://html [...]
+                    } else {
+                        $this->triggerError("Useless DefinitionID declaration", E_USER_WARNING);
+                    }
+                }
+            }
+            // initialize it
+            $def = $this->initDefinition($type);
+            $def->optimized = $optimized;
+            return $def;
+        }
+        throw new HTMLPurifier_Exception("The impossible happened!");
+    }
+
+    private function initDefinition($type) {
+        // quick checks failed, let's create the object
+        if ($type == 'HTML') {
+            $def = new HTMLPurifier_HTMLDefinition();
+        } elseif ($type == 'CSS') {
+            $def = new HTMLPurifier_CSSDefinition();
+        } elseif ($type == 'URI') {
+            $def = new HTMLPurifier_URIDefinition();
+        } else {
+            throw new HTMLPurifier_Exception("Definition of $type type not supported");
+        }
+        $this->definitions[$type] = $def;
+        return $def;
+    }
+
+    public function maybeGetRawDefinition($name) {
+        return $this->getDefinition($name, true, true);
+    }
+
+    public function maybeGetRawHTMLDefinition() {
+        return $this->getDefinition('HTML', true, true);
+    }
+
+    public function maybeGetRawCSSDefinition() {
+        return $this->getDefinition('CSS', true, true);
+    }
+
+    public function maybeGetRawURIDefinition() {
+        return $this->getDefinition('URI', true, true);
+    }
+
+    /**
+     * Loads configuration values from an array with the following structure:
+     * Namespace.Directive => Value
+     * @param $config_array Configuration associative array
+     */
+    public function loadArray($config_array) {
+        if ($this->isFinalized('Cannot load directives after finalization')) return;
+        foreach ($config_array as $key => $value) {
+            $key = str_replace('_', '.', $key);
+            if (strpos($key, '.') !== false) {
+                $this->set($key, $value);
+            } else {
+                $namespace = $key;
+                $namespace_values = $value;
+                foreach ($namespace_values as $directive => $value) {
+                    $this->set($namespace .'.'. $directive, $value);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns a list of array(namespace, directive) for all directives
+     * that are allowed in a web-form context as per an allowed
+     * namespaces/directives list.
+     * @param $allowed List of allowed namespaces/directives
+     */
+    public static function getAllowedDirectivesForForm($allowed, $schema = null) {
+        if (!$schema) {
+            $schema = HTMLPurifier_ConfigSchema::instance();
+        }
+        if ($allowed !== true) {
+             if (is_string($allowed)) $allowed = array($allowed);
+             $allowed_ns = array();
+             $allowed_directives = array();
+             $blacklisted_directives = array();
+             foreach ($allowed as $ns_or_directive) {
+                 if (strpos($ns_or_directive, '.') !== false) {
+                     // directive
+                     if ($ns_or_directive[0] == '-') {
+                         $blacklisted_directives[substr($ns_or_directive, 1)] = true;
+                     } else {
+                         $allowed_directives[$ns_or_directive] = true;
+                     }
+                 } else {
+                     // namespace
+                     $allowed_ns[$ns_or_directive] = true;
+                 }
+             }
+        }
+        $ret = array();
+        foreach ($schema->info as $key => $def) {
+            list($ns, $directive) = explode('.', $key, 2);
+            if ($allowed !== true) {
+                if (isset($blacklisted_directives["$ns.$directive"])) continue;
+                if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) continue;
+            }
+            if (isset($def->isAlias)) continue;
+            if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') continue;
+            $ret[] = array($ns, $directive);
+        }
+        return $ret;
+    }
+
+    /**
+     * Loads configuration values from $_GET/$_POST that were posted
+     * via ConfigForm
+     * @param $array $_GET or $_POST array to import
+     * @param $index Index/name that the config variables are in
+     * @param $allowed List of allowed namespaces/directives
+     * @param $mq_fix Boolean whether or not to enable magic quotes fix
+     * @param $schema Instance of HTMLPurifier_ConfigSchema to use, if not global copy
+     */
+    public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
+        $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema);
+        $config = HTMLPurifier_Config::create($ret, $schema);
+        return $config;
+    }
+
+    /**
+     * Merges in configuration values from $_GET/$_POST to object. NOT STATIC.
+     * @note Same parameters as loadArrayFromForm
+     */
+    public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) {
+         $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def);
+         $this->loadArray($ret);
+    }
+
+    /**
+     * Prepares an array from a form into something usable for the more
+     * strict parts of HTMLPurifier_Config
+     */
+    public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) {
+        if ($index !== false) $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array();
+        $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc();
+
+        $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema);
+        $ret = array();
+        foreach ($allowed as $key) {
+            list($ns, $directive) = $key;
+            $skey = "$ns.$directive";
+            if (!empty($array["Null_$skey"])) {
+                $ret[$ns][$directive] = null;
+                continue;
+            }
+            if (!isset($array[$skey])) continue;
+            $value = $mq ? stripslashes($array[$skey]) : $array[$skey];
+            $ret[$ns][$directive] = $value;
+        }
+        return $ret;
+    }
+
+    /**
+     * Loads configuration values from an ini file
+     * @param $filename Name of ini file
+     */
+    public function loadIni($filename) {
+        if ($this->isFinalized('Cannot load directives after finalization')) return;
+        $array = parse_ini_file($filename, true);
+        $this->loadArray($array);
+    }
+
+    /**
+     * Checks whether or not the configuration object is finalized.
+     * @param $error String error message, or false for no error
+     */
+    public function isFinalized($error = false) {
+        if ($this->finalized && $error) {
+            $this->triggerError($error, E_USER_ERROR);
+        }
+        return $this->finalized;
+    }
+
+    /**
+     * Finalizes configuration only if auto finalize is on and not
+     * already finalized
+     */
+    public function autoFinalize() {
+        if ($this->autoFinalize) {
+            $this->finalize();
+        } else {
+            $this->plist->squash(true);
+        }
+    }
+
+    /**
+     * Finalizes a configuration object, prohibiting further change
+     */
+    public function finalize() {
+        $this->finalized = true;
+        $this->parser = null;
+    }
+
+    /**
+     * Produces a nicely formatted error message by supplying the
+     * stack frame information OUTSIDE of HTMLPurifier_Config.
+     */
+    protected function triggerError($msg, $no) {
+        // determine previous stack frame
+        $extra = '';
+        if ($this->chatty) {
+            $trace = debug_backtrace();
+            // zip(tail(trace), trace) -- but PHP is not Haskell har har
+            for ($i = 0, $c = count($trace); $i < $c - 1; $i++) {
+                // XXX this is not correct on some versions of HTML Purifier
+                if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') {
+                    continue;
+                }
+                $frame = $trace[$i];
+                $extra = " invoked on line {$frame['line']} in file {$frame['file']}";
+                break;
+            }
+        }
+        trigger_error($msg . $extra, $no);
+    }
+
+    /**
+     * Returns a serialized form of the configuration object that can
+     * be reconstituted.
+     */
+    public function serialize() {
+        $this->getDefinition('HTML');
+        $this->getDefinition('CSS');
+        $this->getDefinition('URI');
+        return serialize($this);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema.php
new file mode 100644
index 0000000..fadf7a5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema.php
@@ -0,0 +1,164 @@
+<?php
+
+/**
+ * Configuration definition, defines directives and their defaults.
+ */
+class HTMLPurifier_ConfigSchema {
+
+    /**
+     * Defaults of the directives and namespaces.
+     * @note This shares the exact same structure as HTMLPurifier_Config::$conf
+     */
+    public $defaults = array();
+
+    /**
+     * The default property list. Do not edit this property list.
+     */
+    public $defaultPlist;
+
+    /**
+     * Definition of the directives. The structure of this is:
+     *
+     *  array(
+     *      'Namespace' => array(
+     *          'Directive' => new stdclass(),
+     *      )
+     *  )
+     *
+     * The stdclass may have the following properties:
+     *
+     *  - If isAlias isn't set:
+     *      - type: Integer type of directive, see HTMLPurifier_VarParser for definitions
+     *      - allow_null: If set, this directive allows null values
+     *      - aliases: If set, an associative array of value aliases to real values
+     *      - allowed: If set, a lookup array of allowed (string) values
+     *  - If isAlias is set:
+     *      - namespace: Namespace this directive aliases to
+     *      - name: Directive name this directive aliases to
+     *
+     * In certain degenerate cases, stdclass will actually be an integer. In
+     * that case, the value is equivalent to an stdclass with the type
+     * property set to the integer. If the integer is negative, type is
+     * equal to the absolute value of integer, and allow_null is true.
+     *
+     * This class is friendly with HTMLPurifier_Config. If you need introspection
+     * about the schema, you're better of using the ConfigSchema_Interchange,
+     * which uses more memory but has much richer information.
+     */
+    public $info = array();
+
+    /**
+     * Application-wide singleton
+     */
+    static protected $singleton;
+
+    public function __construct() {
+        $this->defaultPlist = new HTMLPurifier_PropertyList();
+    }
+
+    /**
+     * Unserializes the default ConfigSchema.
+     */
+    public static function makeFromSerial() {
+        $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser');
+        $r = unserialize($contents);
+        if (!$r) {
+            $hash = sha1($contents);
+            trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR);
+        }
+        return $r;
+    }
+
+    /**
+     * Retrieves an instance of the application-wide configuration definition.
+     */
+    public static function instance($prototype = null) {
+        if ($prototype !== null) {
+            HTMLPurifier_ConfigSchema::$singleton = $prototype;
+        } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) {
+            HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial();
+        }
+        return HTMLPurifier_ConfigSchema::$singleton;
+    }
+
+    /**
+     * Defines a directive for configuration
+     * @warning Will fail of directive's namespace is defined.
+     * @warning This method's signature is slightly different from the legacy
+     *          define() static method! Beware!
+     * @param $namespace Namespace the directive is in
+     * @param $name Key of directive
+     * @param $default Default value of directive
+     * @param $type Allowed type of the directive. See
+     *      HTMLPurifier_DirectiveDef::$type for allowed values
+     * @param $allow_null Whether or not to allow null values
+     */
+    public function add($key, $default, $type, $allow_null) {
+        $obj = new stdclass();
+        $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type];
+        if ($allow_null) $obj->allow_null = true;
+        $this->info[$key] = $obj;
+        $this->defaults[$key] = $default;
+        $this->defaultPlist->set($key, $default);
+    }
+
+    /**
+     * Defines a directive value alias.
+     *
+     * Directive value aliases are convenient for developers because it lets
+     * them set a directive to several values and get the same result.
+     * @param $namespace Directive's namespace
+     * @param $name Name of Directive
+     * @param $aliases Hash of aliased values to the real alias
+     */
+    public function addValueAliases($key, $aliases) {
+        if (!isset($this->info[$key]->aliases)) {
+            $this->info[$key]->aliases = array();
+        }
+        foreach ($aliases as $alias => $real) {
+            $this->info[$key]->aliases[$alias] = $real;
+        }
+    }
+
+    /**
+     * Defines a set of allowed values for a directive.
+     * @warning This is slightly different from the corresponding static
+     *          method definition.
+     * @param $namespace Namespace of directive
+     * @param $name Name of directive
+     * @param $allowed Lookup array of allowed values
+     */
+    public function addAllowedValues($key, $allowed) {
+        $this->info[$key]->allowed = $allowed;
+    }
+
+    /**
+     * Defines a directive alias for backwards compatibility
+     * @param $namespace
+     * @param $name Directive that will be aliased
+     * @param $new_namespace
+     * @param $new_name Directive that the alias will be to
+     */
+    public function addAlias($key, $new_key) {
+        $obj = new stdclass;
+        $obj->key = $new_key;
+        $obj->isAlias = true;
+        $this->info[$key] = $obj;
+    }
+
+    /**
+     * Replaces any stdclass that only has the type property with type integer.
+     */
+    public function postProcess() {
+        foreach ($this->info as $key => $v) {
+            if (count((array) $v) == 1) {
+                $this->info[$key] = $v->type;
+            } elseif (count((array) $v) == 2 && isset($v->allow_null)) {
+                $this->info[$key] = -$v->type;
+            }
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
new file mode 100644
index 0000000..c05668a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Converts HTMLPurifier_ConfigSchema_Interchange to our runtime
+ * representation used to perform checks on user configuration.
+ */
+class HTMLPurifier_ConfigSchema_Builder_ConfigSchema
+{
+
+    public function build($interchange) {
+        $schema = new HTMLPurifier_ConfigSchema();
+        foreach ($interchange->directives as $d) {
+            $schema->add(
+                $d->id->key,
+                $d->default,
+                $d->type,
+                $d->typeAllowsNull
+            );
+            if ($d->allowed !== null) {
+                $schema->addAllowedValues(
+                    $d->id->key,
+                    $d->allowed
+                );
+            }
+            foreach ($d->aliases as $alias) {
+                $schema->addAlias(
+                    $alias->key,
+                    $d->id->key
+                );
+            }
+            if ($d->valueAliases !== null) {
+                $schema->addValueAliases(
+                    $d->id->key,
+                    $d->valueAliases
+                );
+            }
+        }
+        $schema->postProcess();
+        return $schema;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/Xml.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/Xml.php
new file mode 100644
index 0000000..244561a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Builder/Xml.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * Converts HTMLPurifier_ConfigSchema_Interchange to an XML format,
+ * which can be further processed to generate documentation.
+ */
+class HTMLPurifier_ConfigSchema_Builder_Xml extends XMLWriter
+{
+
+    protected $interchange;
+    private $namespace;
+
+    protected function writeHTMLDiv($html) {
+        $this->startElement('div');
+
+        $purifier = HTMLPurifier::getInstance();
+        $html = $purifier->purify($html);
+        $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
+        $this->writeRaw($html);
+
+        $this->endElement(); // div
+    }
+
+    protected function export($var) {
+        if ($var === array()) return 'array()';
+        return var_export($var, true);
+    }
+
+    public function build($interchange) {
+        // global access, only use as last resort
+        $this->interchange = $interchange;
+
+        $this->setIndent(true);
+        $this->startDocument('1.0', 'UTF-8');
+        $this->startElement('configdoc');
+        $this->writeElement('title', $interchange->name);
+
+        foreach ($interchange->directives as $directive) {
+            $this->buildDirective($directive);
+        }
+
+        if ($this->namespace) $this->endElement(); // namespace
+
+        $this->endElement(); // configdoc
+        $this->flush();
+    }
+
+    public function buildDirective($directive) {
+
+        // Kludge, although I suppose having a notion of a "root namespace"
+        // certainly makes things look nicer when documentation is built.
+        // Depends on things being sorted.
+        if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) {
+            if ($this->namespace) $this->endElement(); // namespace
+            $this->namespace = $directive->id->getRootNamespace();
+            $this->startElement('namespace');
+            $this->writeAttribute('id', $this->namespace);
+            $this->writeElement('name', $this->namespace);
+        }
+
+        $this->startElement('directive');
+        $this->writeAttribute('id', $directive->id->toString());
+
+        $this->writeElement('name', $directive->id->getDirective());
+
+        $this->startElement('aliases');
+            foreach ($directive->aliases as $alias) $this->writeElement('alias', $alias->toString());
+        $this->endElement(); // aliases
+
+        $this->startElement('constraints');
+            if ($directive->version) $this->writeElement('version', $directive->version);
+            $this->startElement('type');
+                if ($directive->typeAllowsNull) $this->writeAttribute('allow-null', 'yes');
+                $this->text($directive->type);
+            $this->endElement(); // type
+            if ($directive->allowed) {
+                $this->startElement('allowed');
+                    foreach ($directive->allowed as $value => $x) $this->writeElement('value', $value);
+                $this->endElement(); // allowed
+            }
+            $this->writeElement('default', $this->export($directive->default));
+            $this->writeAttribute('xml:space', 'preserve');
+            if ($directive->external) {
+                $this->startElement('external');
+                    foreach ($directive->external as $project) $this->writeElement('project', $project);
+                $this->endElement();
+            }
+        $this->endElement(); // constraints
+
+        if ($directive->deprecatedVersion) {
+            $this->startElement('deprecated');
+                $this->writeElement('version', $directive->deprecatedVersion);
+                $this->writeElement('use', $directive->deprecatedUse->toString());
+            $this->endElement(); // deprecated
+        }
+
+        $this->startElement('description');
+            $this->writeHTMLDiv($directive->description);
+        $this->endElement(); // description
+
+        $this->endElement(); // directive
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Exception.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Exception.php
new file mode 100644
index 0000000..2671516
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Exception.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Exceptions related to configuration schema
+ */
+class HTMLPurifier_ConfigSchema_Exception extends HTMLPurifier_Exception
+{
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange.php
new file mode 100644
index 0000000..91a5aa7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Generic schema interchange format that can be converted to a runtime
+ * representation (HTMLPurifier_ConfigSchema) or HTML documentation. Members
+ * are completely validated.
+ */
+class HTMLPurifier_ConfigSchema_Interchange
+{
+
+    /**
+     * Name of the application this schema is describing.
+     */
+    public $name;
+
+    /**
+     * Array of Directive ID => array(directive info)
+     */
+    public $directives = array();
+
+    /**
+     * Adds a directive array to $directives
+     */
+    public function addDirective($directive) {
+        if (isset($this->directives[$i = $directive->id->toString()])) {
+            throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'");
+        }
+        $this->directives[$i] = $directive;
+    }
+
+    /**
+     * Convenience function to perform standard validation. Throws exception
+     * on failed validation.
+     */
+    public function validate() {
+        $validator = new HTMLPurifier_ConfigSchema_Validator();
+        return $validator->validate($this);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange/Directive.php
new file mode 100644
index 0000000..ac8be0d
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange/Directive.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Interchange component class describing configuration directives.
+ */
+class HTMLPurifier_ConfigSchema_Interchange_Directive
+{
+
+    /**
+     * ID of directive, instance of HTMLPurifier_ConfigSchema_Interchange_Id.
+     */
+    public $id;
+
+    /**
+     * String type, e.g. 'integer' or 'istring'.
+     */
+    public $type;
+
+    /**
+     * Default value, e.g. 3 or 'DefaultVal'.
+     */
+    public $default;
+
+    /**
+     * HTML description.
+     */
+    public $description;
+
+    /**
+     * Boolean whether or not null is allowed as a value.
+     */
+    public $typeAllowsNull = false;
+
+    /**
+     * Lookup table of allowed scalar values, e.g. array('allowed' => true).
+     * Null if all values are allowed.
+     */
+    public $allowed;
+
+    /**
+     * List of aliases for the directive,
+     * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))).
+     */
+    public $aliases = array();
+
+    /**
+     * Hash of value aliases, e.g. array('alt' => 'real'). Null if value
+     * aliasing is disabled (necessary for non-scalar types).
+     */
+    public $valueAliases;
+
+    /**
+     * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'.
+     * Null if the directive has always existed.
+     */
+    public $version;
+
+    /**
+     * ID of directive that supercedes this old directive, is an instance
+     * of HTMLPurifier_ConfigSchema_Interchange_Id. Null if not deprecated.
+     */
+    public $deprecatedUse;
+
+    /**
+     * Version of HTML Purifier this directive was deprecated. Null if not
+     * deprecated.
+     */
+    public $deprecatedVersion;
+
+    /**
+     * List of external projects this directive depends on, e.g. array('CSSTidy').
+     */
+    public $external = array();
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange/Id.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange/Id.php
new file mode 100644
index 0000000..b9b3c6f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Interchange/Id.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Represents a directive ID in the interchange format.
+ */
+class HTMLPurifier_ConfigSchema_Interchange_Id
+{
+
+    public $key;
+
+    public function __construct($key) {
+        $this->key = $key;
+    }
+
+    /**
+     * @warning This is NOT magic, to ensure that people don't abuse SPL and
+     *          cause problems for PHP 5.0 support.
+     */
+    public function toString() {
+        return $this->key;
+    }
+
+    public function getRootNamespace() {
+        return substr($this->key, 0, strpos($this->key, "."));
+    }
+
+    public function getDirective() {
+        return substr($this->key, strpos($this->key, ".") + 1);
+    }
+
+    public static function make($id) {
+        return new HTMLPurifier_ConfigSchema_Interchange_Id($id);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
new file mode 100644
index 0000000..785b72c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/InterchangeBuilder.php
@@ -0,0 +1,180 @@
+<?php
+
+class HTMLPurifier_ConfigSchema_InterchangeBuilder
+{
+
+    /**
+     * Used for processing DEFAULT, nothing else.
+     */
+    protected $varParser;
+
+    public function __construct($varParser = null) {
+        $this->varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native();
+    }
+
+    public static function buildFromDirectory($dir = null) {
+        $builder     = new HTMLPurifier_ConfigSchema_InterchangeBuilder();
+        $interchange = new HTMLPurifier_ConfigSchema_Interchange();
+        return $builder->buildDir($interchange, $dir);
+    }
+
+    public function buildDir($interchange, $dir = null) {
+        if (!$dir) $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema';
+        if (file_exists($dir . '/info.ini')) {
+            $info = parse_ini_file($dir . '/info.ini');
+            $interchange->name = $info['name'];
+        }
+
+        $files = array();
+        $dh = opendir($dir);
+        while (false !== ($file = readdir($dh))) {
+            if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') {
+                continue;
+            }
+            $files[] = $file;
+        }
+        closedir($dh);
+
+        sort($files);
+        foreach ($files as $file) {
+            $this->buildFile($interchange, $dir . '/' . $file);
+        }
+
+        return $interchange;
+    }
+
+    public function buildFile($interchange, $file) {
+        $parser = new HTMLPurifier_StringHashParser();
+        $this->build(
+            $interchange,
+            new HTMLPurifier_StringHash( $parser->parseFile($file) )
+        );
+    }
+
+    /**
+     * Builds an interchange object based on a hash.
+     * @param $interchange HTMLPurifier_ConfigSchema_Interchange object to build
+     * @param $hash HTMLPurifier_ConfigSchema_StringHash source data
+     */
+    public function build($interchange, $hash) {
+        if (!$hash instanceof HTMLPurifier_StringHash) {
+            $hash = new HTMLPurifier_StringHash($hash);
+        }
+        if (!isset($hash['ID'])) {
+            throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID');
+        }
+        if (strpos($hash['ID'], '.') === false) {
+            if (count($hash) == 2 && isset($hash['DESCRIPTION'])) {
+                $hash->offsetGet('DESCRIPTION'); // prevent complaining
+            } else {
+                throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace');
+            }
+        } else {
+            $this->buildDirective($interchange, $hash);
+        }
+        $this->_findUnused($hash);
+    }
+
+    public function buildDirective($interchange, $hash) {
+        $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive();
+
+        // These are required elements:
+        $directive->id = $this->id($hash->offsetGet('ID'));
+        $id = $directive->id->toString(); // convenience
+
+        if (isset($hash['TYPE'])) {
+            $type = explode('/', $hash->offsetGet('TYPE'));
+            if (isset($type[1])) $directive->typeAllowsNull = true;
+            $directive->type = $type[0];
+        } else {
+            throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined");
+        }
+
+        if (isset($hash['DEFAULT'])) {
+            try {
+                $directive->default = $this->varParser->parse($hash->offsetGet('DEFAULT'), $directive->type, $directive->typeAllowsNull);
+            } catch (HTMLPurifier_VarParserException $e) {
+                throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'");
+            }
+        }
+
+        if (isset($hash['DESCRIPTION'])) {
+            $directive->description = $hash->offsetGet('DESCRIPTION');
+        }
+
+        if (isset($hash['ALLOWED'])) {
+            $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED')));
+        }
+
+        if (isset($hash['VALUE-ALIASES'])) {
+            $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES'));
+        }
+
+        if (isset($hash['ALIASES'])) {
+            $raw_aliases = trim($hash->offsetGet('ALIASES'));
+            $aliases = preg_split('/\s*,\s*/', $raw_aliases);
+            foreach ($aliases as $alias) {
+                $directive->aliases[] = $this->id($alias);
+            }
+        }
+
+        if (isset($hash['VERSION'])) {
+            $directive->version = $hash->offsetGet('VERSION');
+        }
+
+        if (isset($hash['DEPRECATED-USE'])) {
+            $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE'));
+        }
+
+        if (isset($hash['DEPRECATED-VERSION'])) {
+            $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION');
+        }
+
+        if (isset($hash['EXTERNAL'])) {
+            $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL')));
+        }
+
+        $interchange->addDirective($directive);
+    }
+
+    /**
+     * Evaluates an array PHP code string without array() wrapper
+     */
+    protected function evalArray($contents) {
+        return eval('return array('. $contents .');');
+    }
+
+    /**
+     * Converts an array list into a lookup array.
+     */
+    protected function lookup($array) {
+        $ret = array();
+        foreach ($array as $val) $ret[$val] = true;
+        return $ret;
+    }
+
+    /**
+     * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id
+     * object based on a string Id.
+     */
+    protected function id($id) {
+        return HTMLPurifier_ConfigSchema_Interchange_Id::make($id);
+    }
+
+    /**
+     * Triggers errors for any unused keys passed in the hash; such keys
+     * may indicate typos, missing values, etc.
+     * @param $hash Instance of ConfigSchema_StringHash to check.
+     */
+    protected function _findUnused($hash) {
+        $accessed = $hash->getAccessed();
+        foreach ($hash as $k => $v) {
+            if (!isset($accessed[$k])) {
+                trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE);
+            }
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Validator.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Validator.php
new file mode 100644
index 0000000..f374f6a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/Validator.php
@@ -0,0 +1,206 @@
+<?php
+
+/**
+ * Performs validations on HTMLPurifier_ConfigSchema_Interchange
+ *
+ * @note If you see '// handled by InterchangeBuilder', that means a
+ *       design decision in that class would prevent this validation from
+ *       ever being necessary. We have them anyway, however, for
+ *       redundancy.
+ */
+class HTMLPurifier_ConfigSchema_Validator
+{
+
+    /**
+     * Easy to access global objects.
+     */
+    protected $interchange, $aliases;
+
+    /**
+     * Context-stack to provide easy to read error messages.
+     */
+    protected $context = array();
+
+    /**
+     * HTMLPurifier_VarParser to test default's type.
+     */
+    protected $parser;
+
+    public function __construct() {
+        $this->parser = new HTMLPurifier_VarParser();
+    }
+
+    /**
+     * Validates a fully-formed interchange object. Throws an
+     * HTMLPurifier_ConfigSchema_Exception if there's a problem.
+     */
+    public function validate($interchange) {
+        $this->interchange = $interchange;
+        $this->aliases = array();
+        // PHP is a bit lax with integer <=> string conversions in
+        // arrays, so we don't use the identical !== comparison
+        foreach ($interchange->directives as $i => $directive) {
+            $id = $directive->id->toString();
+            if ($i != $id) $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'");
+            $this->validateDirective($directive);
+        }
+        return true;
+    }
+
+    /**
+     * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object.
+     */
+    public function validateId($id) {
+        $id_string = $id->toString();
+        $this->context[] = "id '$id_string'";
+        if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) {
+            // handled by InterchangeBuilder
+            $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id');
+        }
+        // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.)
+        // we probably should check that it has at least one namespace
+        $this->with($id, 'key')
+            ->assertNotEmpty()
+            ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder
+        array_pop($this->context);
+    }
+
+    /**
+     * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object.
+     */
+    public function validateDirective($d) {
+        $id = $d->id->toString();
+        $this->context[] = "directive '$id'";
+        $this->validateId($d->id);
+
+        $this->with($d, 'description')
+            ->assertNotEmpty();
+
+        // BEGIN - handled by InterchangeBuilder
+        $this->with($d, 'type')
+            ->assertNotEmpty();
+        $this->with($d, 'typeAllowsNull')
+            ->assertIsBool();
+        try {
+            // This also tests validity of $d->type
+            $this->parser->parse($d->default, $d->type, $d->typeAllowsNull);
+        } catch (HTMLPurifier_VarParserException $e) {
+            $this->error('default', 'had error: ' . $e->getMessage());
+        }
+        // END - handled by InterchangeBuilder
+
+        if (!is_null($d->allowed) || !empty($d->valueAliases)) {
+            // allowed and valueAliases require that we be dealing with
+            // strings, so check for that early.
+            $d_int = HTMLPurifier_VarParser::$types[$d->type];
+            if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) {
+                $this->error('type', 'must be a string type when used with allowed or value aliases');
+            }
+        }
+
+        $this->validateDirectiveAllowed($d);
+        $this->validateDirectiveValueAliases($d);
+        $this->validateDirectiveAliases($d);
+
+        array_pop($this->context);
+    }
+
+    /**
+     * Extra validation if $allowed member variable of
+     * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+     */
+    public function validateDirectiveAllowed($d) {
+        if (is_null($d->allowed)) return;
+        $this->with($d, 'allowed')
+            ->assertNotEmpty()
+            ->assertIsLookup(); // handled by InterchangeBuilder
+        if (is_string($d->default) && !isset($d->allowed[$d->default])) {
+            $this->error('default', 'must be an allowed value');
+        }
+        $this->context[] = 'allowed';
+        foreach ($d->allowed as $val => $x) {
+            if (!is_string($val)) $this->error("value $val", 'must be a string');
+        }
+        array_pop($this->context);
+    }
+
+    /**
+     * Extra validation if $valueAliases member variable of
+     * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+     */
+    public function validateDirectiveValueAliases($d) {
+        if (is_null($d->valueAliases)) return;
+        $this->with($d, 'valueAliases')
+            ->assertIsArray(); // handled by InterchangeBuilder
+        $this->context[] = 'valueAliases';
+        foreach ($d->valueAliases as $alias => $real) {
+            if (!is_string($alias)) $this->error("alias $alias", 'must be a string');
+            if (!is_string($real))  $this->error("alias target $real from alias '$alias'",  'must be a string');
+            if ($alias === $real) {
+                $this->error("alias '$alias'", "must not be an alias to itself");
+            }
+        }
+        if (!is_null($d->allowed)) {
+            foreach ($d->valueAliases as $alias => $real) {
+                if (isset($d->allowed[$alias])) {
+                    $this->error("alias '$alias'", 'must not be an allowed value');
+                } elseif (!isset($d->allowed[$real])) {
+                    $this->error("alias '$alias'", 'must be an alias to an allowed value');
+                }
+            }
+        }
+        array_pop($this->context);
+    }
+
+    /**
+     * Extra validation if $aliases member variable of
+     * HTMLPurifier_ConfigSchema_Interchange_Directive is defined.
+     */
+    public function validateDirectiveAliases($d) {
+        $this->with($d, 'aliases')
+            ->assertIsArray(); // handled by InterchangeBuilder
+        $this->context[] = 'aliases';
+        foreach ($d->aliases as $alias) {
+            $this->validateId($alias);
+            $s = $alias->toString();
+            if (isset($this->interchange->directives[$s])) {
+                $this->error("alias '$s'", 'collides with another directive');
+            }
+            if (isset($this->aliases[$s])) {
+                $other_directive = $this->aliases[$s];
+                $this->error("alias '$s'", "collides with alias for directive '$other_directive'");
+            }
+            $this->aliases[$s] = $d->id->toString();
+        }
+        array_pop($this->context);
+    }
+
+    // protected helper functions
+
+    /**
+     * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom
+     * for validating simple member variables of objects.
+     */
+    protected function with($obj, $member) {
+        return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member);
+    }
+
+    /**
+     * Emits an error, providing helpful context.
+     */
+    protected function error($target, $msg) {
+        if ($target !== false) $prefix = ucfirst($target) . ' in ' .  $this->getFormattedContext();
+        else $prefix = ucfirst($this->getFormattedContext());
+        throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg));
+    }
+
+    /**
+     * Returns a formatted context string.
+     */
+    protected function getFormattedContext() {
+        return implode(' in ', array_reverse($this->context));
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/ValidatorAtom.php
new file mode 100644
index 0000000..b95aea1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/ValidatorAtom.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * Fluent interface for validating the contents of member variables.
+ * This should be immutable. See HTMLPurifier_ConfigSchema_Validator for
+ * use-cases. We name this an 'atom' because it's ONLY for validations that
+ * are independent and usually scalar.
+ */
+class HTMLPurifier_ConfigSchema_ValidatorAtom
+{
+
+    protected $context, $obj, $member, $contents;
+
+    public function __construct($context, $obj, $member) {
+        $this->context     = $context;
+        $this->obj         = $obj;
+        $this->member      = $member;
+        $this->contents    =& $obj->$member;
+    }
+
+    public function assertIsString() {
+        if (!is_string($this->contents)) $this->error('must be a string');
+        return $this;
+    }
+
+    public function assertIsBool() {
+        if (!is_bool($this->contents)) $this->error('must be a boolean');
+        return $this;
+    }
+
+    public function assertIsArray() {
+        if (!is_array($this->contents)) $this->error('must be an array');
+        return $this;
+    }
+
+    public function assertNotNull() {
+        if ($this->contents === null) $this->error('must not be null');
+        return $this;
+    }
+
+    public function assertAlnum() {
+        $this->assertIsString();
+        if (!ctype_alnum($this->contents)) $this->error('must be alphanumeric');
+        return $this;
+    }
+
+    public function assertNotEmpty() {
+        if (empty($this->contents)) $this->error('must not be empty');
+        return $this;
+    }
+
+    public function assertIsLookup() {
+        $this->assertIsArray();
+        foreach ($this->contents as $v) {
+            if ($v !== true) $this->error('must be a lookup array');
+        }
+        return $this;
+    }
+
+    protected function error($msg) {
+        throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser
new file mode 100644
index 0000000..fa0bacb
Binary files /dev/null and b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema.ser differ
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
new file mode 100644
index 0000000..0517fed
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt
@@ -0,0 +1,8 @@
+Attr.AllowedClasses
+TYPE: lookup/null
+VERSION: 4.0.0
+DEFAULT: null
+--DESCRIPTION--
+List of allowed class values in the class attribute. By default, this is null,
+which means all classes are allowed.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
new file mode 100644
index 0000000..249edd6
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt
@@ -0,0 +1,12 @@
+Attr.AllowedFrameTargets
+TYPE: lookup
+DEFAULT: array()
+--DESCRIPTION--
+Lookup table of all allowed link frame targets.  Some commonly used link
+targets include _blank, _self, _parent and _top. Values should be
+lowercase, as validation will be done in a case-sensitive manner despite
+W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute
+so this directive will have no effect in that doctype. XHTML 1.1 does not
+enable the Target module by default, you will have to manually enable it
+(see the module documentation for more details.)
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
new file mode 100644
index 0000000..9a8fa6a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt
@@ -0,0 +1,9 @@
+Attr.AllowedRel
+TYPE: lookup
+VERSION: 1.6.0
+DEFAULT: array()
+--DESCRIPTION--
+List of allowed forward document relationships in the rel attribute. Common
+values may be nofollow or print. By default, this is empty, meaning that no
+document relationships are allowed.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
new file mode 100644
index 0000000..b017883
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt
@@ -0,0 +1,9 @@
+Attr.AllowedRev
+TYPE: lookup
+VERSION: 1.6.0
+DEFAULT: array()
+--DESCRIPTION--
+List of allowed reverse document relationships in the rev attribute. This
+attribute is a bit of an edge-case; if you don't know what it is for, stay
+away.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
new file mode 100644
index 0000000..e774b82
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt
@@ -0,0 +1,19 @@
+Attr.ClassUseCDATA
+TYPE: bool/null
+DEFAULT: null
+VERSION: 4.0.0
+--DESCRIPTION--
+If null, class will auto-detect the doctype and, if matching XHTML 1.1 or
+XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise,
+it will use a relaxed CDATA definition.  If true, the relaxed CDATA definition
+is forced; if false, the NMTOKENS definition is forced.  To get behavior
+of HTML Purifier prior to 4.0.0, set this directive to false.
+
+Some rational behind the auto-detection:
+in previous versions of HTML Purifier, it was assumed that the form of
+class was NMTOKENS, as specified by the XHTML Modularization (representing
+XHTML 1.1 and XHTML 2.0).  The DTDs for HTML 4.01 and XHTML 1.0, however
+specify class as CDATA.  HTML 5 effectively defines it as CDATA, but
+with the additional constraint that each name should be unique (this is not
+explicitly outlined in previous specifications).
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
new file mode 100644
index 0000000..533165e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt
@@ -0,0 +1,11 @@
+Attr.DefaultImageAlt
+TYPE: string/null
+DEFAULT: null
+VERSION: 3.2.0
+--DESCRIPTION--
+This is the content of the alt tag of an image if the user had not
+previously specified an alt attribute.  This applies to all images without
+a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which
+only applies to invalid images, and overrides in the case of an invalid image.
+Default behavior with null is to use the basename of the src tag for the alt.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
new file mode 100644
index 0000000..9eb7e38
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt
@@ -0,0 +1,9 @@
+Attr.DefaultInvalidImage
+TYPE: string
+DEFAULT: ''
+--DESCRIPTION--
+This is the default image an img tag will be pointed to if it does not have
+a valid src attribute.  In future versions, we may allow the image tag to
+be removed completely, but due to design issues, this is not possible right
+now.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
new file mode 100644
index 0000000..2f17bf4
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt
@@ -0,0 +1,8 @@
+Attr.DefaultInvalidImageAlt
+TYPE: string
+DEFAULT: 'Invalid image'
+--DESCRIPTION--
+This is the content of the alt tag of an invalid image if the user had not
+previously specified an alt attribute.  It has no effect when the image is
+valid but there was no alt attribute present.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
new file mode 100644
index 0000000..52654b5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt
@@ -0,0 +1,10 @@
+Attr.DefaultTextDir
+TYPE: string
+DEFAULT: 'ltr'
+--DESCRIPTION--
+Defines the default text direction (ltr or rtl) of the document being
+parsed.  This generally is the same as the value of the dir attribute in
+HTML, or ltr if that is not specified.
+--ALLOWED--
+'ltr', 'rtl'
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
new file mode 100644
index 0000000..6440d21
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt
@@ -0,0 +1,16 @@
+Attr.EnableID
+TYPE: bool
+DEFAULT: false
+VERSION: 1.2.0
+--DESCRIPTION--
+Allows the ID attribute in HTML.  This is disabled by default due to the
+fact that without proper configuration user input can easily break the
+validation of a webpage by specifying an ID that is already on the
+surrounding HTML.  If you don't mind throwing caution to the wind, enable
+this directive, but I strongly recommend you also consider blacklisting IDs
+you use (%Attr.IDBlacklist) or prefixing all user supplied IDs
+(%Attr.IDPrefix).  When set to true HTML Purifier reverts to the behavior of
+pre-1.2.0 versions.
+--ALIASES--
+HTML.EnableAttrID
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
new file mode 100644
index 0000000..f31d226
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt
@@ -0,0 +1,8 @@
+Attr.ForbiddenClasses
+TYPE: lookup
+VERSION: 4.0.0
+DEFAULT: array()
+--DESCRIPTION--
+List of forbidden class values in the class attribute. By default, this is
+empty, which means that no classes are forbidden. See also %Attr.AllowedClasses.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
new file mode 100644
index 0000000..5f2b5e3
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt
@@ -0,0 +1,5 @@
+Attr.IDBlacklist
+TYPE: list
+DEFAULT: array()
+DESCRIPTION: Array of IDs not allowed in the document.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
new file mode 100644
index 0000000..6f58245
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt
@@ -0,0 +1,9 @@
+Attr.IDBlacklistRegexp
+TYPE: string/null
+VERSION: 1.6.0
+DEFAULT: NULL
+--DESCRIPTION--
+PCRE regular expression to be matched against all IDs. If the expression is
+matches, the ID is rejected. Use this with care: may cause significant
+degradation. ID matching is done after all other validation.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
new file mode 100644
index 0000000..cc49d43
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt
@@ -0,0 +1,12 @@
+Attr.IDPrefix
+TYPE: string
+VERSION: 1.2.0
+DEFAULT: ''
+--DESCRIPTION--
+String to prefix to IDs.  If you have no idea what IDs your pages may use,
+you may opt to simply add a prefix to all user-submitted ID attributes so
+that they are still usable, but will not conflict with core page IDs.
+Example: setting the directive to 'user_' will result in a user submitted
+'foo' to become 'user_foo'  Be sure to set %HTML.EnableAttrID to true
+before using this.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
new file mode 100644
index 0000000..2c5924a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt
@@ -0,0 +1,14 @@
+Attr.IDPrefixLocal
+TYPE: string
+VERSION: 1.2.0
+DEFAULT: ''
+--DESCRIPTION--
+Temporary prefix for IDs used in conjunction with %Attr.IDPrefix.  If you
+need to allow multiple sets of user content on web page, you may need to
+have a seperate prefix that changes with each iteration.  This way,
+seperately submitted user content displayed on the same page doesn't
+clobber each other. Ideal values are unique identifiers for the content it
+represents (i.e. the id of the row in the database). Be sure to add a
+seperator (like an underscore) at the end.  Warning: this directive will
+not work unless %Attr.IDPrefix is set to a non-empty value!
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
new file mode 100644
index 0000000..d5caa1b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt
@@ -0,0 +1,31 @@
+AutoFormat.AutoParagraph
+TYPE: bool
+VERSION: 2.0.1
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+  This directive turns on auto-paragraphing, where double newlines are
+  converted in to paragraphs whenever possible. Auto-paragraphing:
+</p>
+<ul>
+  <li>Always applies to inline elements or text in the root node,</li>
+  <li>Applies to inline elements or text with double newlines in nodes
+      that allow paragraph tags,</li>
+  <li>Applies to double newlines in paragraph tags</li>
+</ul>
+<p>
+  <code>p</code> tags must be allowed for this directive to take effect.
+  We do not use <code>br</code> tags for paragraphing, as that is
+  semantically incorrect.
+</p>
+<p>
+  To prevent auto-paragraphing as a content-producer, refrain from using
+  double-newlines except to specify a new paragraph or in contexts where
+  it has special meaning (whitespace usually has no meaning except in
+  tags like <code>pre</code>, so this should not be difficult.) To prevent
+  the paragraphing of inline text adjacent to block elements, wrap them
+  in <code>div</code> tags (the behavior is slightly different outside of
+  the root node.)
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt
new file mode 100644
index 0000000..2a47648
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt
@@ -0,0 +1,12 @@
+AutoFormat.Custom
+TYPE: list
+VERSION: 2.0.1
+DEFAULT: array()
+--DESCRIPTION--
+
+<p>
+  This directive can be used to add custom auto-format injectors.
+  Specify an array of injector names (class name minus the prefix)
+  or concrete implementations. Injector class must exist.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt
new file mode 100644
index 0000000..663064a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt
@@ -0,0 +1,11 @@
+AutoFormat.DisplayLinkURI
+TYPE: bool
+VERSION: 3.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+  This directive turns on the in-text display of URIs in <a> tags, and disables
+  those links. For example, <a href="http://example.com">example</a> becomes
+  example (<a>http://example.com</a>).
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt
new file mode 100644
index 0000000..3a48ba9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt
@@ -0,0 +1,12 @@
+AutoFormat.Linkify
+TYPE: bool
+VERSION: 2.0.1
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+  This directive turns on linkification, auto-linking http, ftp and
+  https URLs. <code>a</code> tags with the <code>href</code> attribute
+  must be allowed.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt
new file mode 100644
index 0000000..db58b13
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt
@@ -0,0 +1,12 @@
+AutoFormat.PurifierLinkify.DocURL
+TYPE: string
+VERSION: 2.0.1
+DEFAULT: '#%s'
+ALIASES: AutoFormatParam.PurifierLinkifyDocURL
+--DESCRIPTION--
+<p>
+  Location of configuration documentation to link to, let %s substitute
+  into the configuration's namespace and directive names sans the percent
+  sign.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt
new file mode 100644
index 0000000..7996488
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt
@@ -0,0 +1,12 @@
+AutoFormat.PurifierLinkify
+TYPE: bool
+VERSION: 2.0.1
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+  Internal auto-formatter that converts configuration directives in
+  syntax <a>%Namespace.Directive</a> to links. <code>a</code> tags
+  with the <code>href</code> attribute must be allowed.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt
new file mode 100644
index 0000000..35c393b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt
@@ -0,0 +1,11 @@
+AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions
+TYPE: lookup
+VERSION: 4.0.0
+DEFAULT: array('td' => true, 'th' => true)
+--DESCRIPTION--
+<p>
+  When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp
+  are enabled, this directive defines what HTML elements should not be
+  removede if they have only a non-breaking space in them.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt
new file mode 100644
index 0000000..ca17eb1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt
@@ -0,0 +1,15 @@
+AutoFormat.RemoveEmpty.RemoveNbsp
+TYPE: bool
+VERSION: 4.0.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+  When enabled, HTML Purifier will treat any elements that contain only
+  non-breaking spaces as well as regular whitespace as empty, and remove
+  them when %AutoForamt.RemoveEmpty is enabled.
+</p>
+<p>
+  See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements
+  that don't have this behavior applied to them.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt
new file mode 100644
index 0000000..34657ba
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt
@@ -0,0 +1,46 @@
+AutoFormat.RemoveEmpty
+TYPE: bool
+VERSION: 3.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+  When enabled, HTML Purifier will attempt to remove empty elements that
+  contribute no semantic information to the document. The following types
+  of nodes will be removed:
+</p>
+<ul><li>
+    Tags with no attributes and no content, and that are not empty
+    elements (remove <code><a></a></code> but not
+    <code><br /></code>), and
+  </li>
+  <li>
+    Tags with no content, except for:<ul>
+      <li>The <code>colgroup</code> element, or</li>
+      <li>
+        Elements with the <code>id</code> or <code>name</code> attribute,
+        when those attributes are permitted on those elements.
+      </li>
+    </ul></li>
+</ul>
+<p>
+  Please be very careful when using this functionality; while it may not
+  seem that empty elements contain useful information, they can alter the
+  layout of a document given appropriate styling. This directive is most
+  useful when you are processing machine-generated HTML, please avoid using
+  it on regular user HTML.
+</p>
+<p>
+  Elements that contain only whitespace will be treated as empty. Non-breaking
+  spaces, however, do not count as whitespace. See
+  %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior.
+</p>
+<p>
+  This algorithm is not perfect; you may still notice some empty tags,
+  particularly if a node had elements, but those elements were later removed
+  because they were not permitted in that context, or tags that, after
+  being auto-closed by another tag, where empty. This is for safety reasons
+  to prevent clever code from breaking validation. The general rule of thumb:
+  if a tag looked empty on the way in, it will get removed; if HTML Purifier
+  made it empty, it will stay.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt
new file mode 100644
index 0000000..dde990a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt
@@ -0,0 +1,11 @@
+AutoFormat.RemoveSpansWithoutAttributes
+TYPE: bool
+VERSION: 4.0.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+  This directive causes <code>span</code> tags without any attributes
+  to be removed. It will also remove spans that had all attributes
+  removed during processing.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt
new file mode 100644
index 0000000..b324608
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt
@@ -0,0 +1,8 @@
+CSS.AllowImportant
+TYPE: bool
+DEFAULT: false
+VERSION: 3.1.0
+--DESCRIPTION--
+This parameter determines whether or not !important cascade modifiers should
+be allowed in user CSS. If false, !important will stripped.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt
new file mode 100644
index 0000000..748be0e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt
@@ -0,0 +1,11 @@
+CSS.AllowTricky
+TYPE: bool
+DEFAULT: false
+VERSION: 3.1.0
+--DESCRIPTION--
+This parameter determines whether or not to allow "tricky" CSS properties and
+values. Tricky CSS properties/values can drastically modify page layout or
+be used for deceptive practices but do not directly constitute a security risk.
+For example, <code>display:none;</code> is considered a tricky property that
+will only be allowed if this directive is set to true.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
new file mode 100644
index 0000000..3fd4654
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt
@@ -0,0 +1,12 @@
+CSS.AllowedFonts
+TYPE: lookup/null
+VERSION: 4.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+<p>
+    Allows you to manually specify a set of allowed fonts.  If
+    <code>NULL</code>, all fonts are allowed.  This directive
+    affects generic names (serif, sans-serif, monospace, cursive,
+    fantasy) as well as specific font families.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt
new file mode 100644
index 0000000..460112e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt
@@ -0,0 +1,18 @@
+CSS.AllowedProperties
+TYPE: lookup/null
+VERSION: 3.1.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    If HTML Purifier's style attributes set is unsatisfactory for your needs,
+    you can overload it with your own list of tags to allow.  Note that this
+    method is subtractive: it does its job by taking away from HTML Purifier
+    usual feature set, so you cannot add an attribute that HTML Purifier never
+    supported in the first place.
+</p>
+<p>
+    <strong>Warning:</strong> If another directive conflicts with the
+    elements here, <em>that</em> directive will win and override.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt
new file mode 100644
index 0000000..5cb7dda
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt
@@ -0,0 +1,11 @@
+CSS.DefinitionRev
+TYPE: int
+VERSION: 2.0.0
+DEFAULT: 1
+--DESCRIPTION--
+
+<p>
+    Revision identifier for your custom definition. See
+    %HTML.DefinitionRev for details.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt
new file mode 100644
index 0000000..f1f5c5f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt
@@ -0,0 +1,13 @@
+CSS.ForbiddenProperties
+TYPE: lookup
+VERSION: 4.2.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+    This is the logical inverse of %CSS.AllowedProperties, and it will
+    override that directive or any other directive.  If possible,
+    %CSS.AllowedProperties is recommended over this directive,
+    because it can sometimes be difficult to tell whether or not you've
+    forbidden all of the CSS properties you truly would like to disallow.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt
new file mode 100644
index 0000000..7a32914
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt
@@ -0,0 +1,16 @@
+CSS.MaxImgLength
+TYPE: string/null
+DEFAULT: '1200px'
+VERSION: 3.1.1
+--DESCRIPTION--
+<p>
+ This parameter sets the maximum allowed length on <code>img</code> tags,
+ effectively the <code>width</code> and <code>height</code> properties.
+ Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is
+ in place to prevent imagecrash attacks, disable with null at your own risk.
+ This directive is similar to %HTML.MaxImgLength, and both should be
+ concurrently edited, although there are
+ subtle differences in the input format (the CSS max is a number with
+ a unit).
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt
new file mode 100644
index 0000000..148eedb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt
@@ -0,0 +1,10 @@
+CSS.Proprietary
+TYPE: bool
+VERSION: 3.0.0
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+    Whether or not to allow safe, proprietary CSS values.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt
new file mode 100644
index 0000000..e733a61
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt
@@ -0,0 +1,9 @@
+CSS.Trusted
+TYPE: bool
+VERSION: 4.2.1
+DEFAULT: false
+--DESCRIPTION--
+Indicates whether or not the user's CSS input is trusted or not. If the
+input is trusted, a more expansive set of allowed properties.  See
+also %HTML.Trusted.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt
new file mode 100644
index 0000000..c486724
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt
@@ -0,0 +1,14 @@
+Cache.DefinitionImpl
+TYPE: string/null
+VERSION: 2.0.0
+DEFAULT: 'Serializer'
+--DESCRIPTION--
+
+This directive defines which method to use when caching definitions,
+the complex data-type that makes HTML Purifier tick. Set to null
+to disable caching (not recommended, as you will see a definite
+performance degradation).
+
+--ALIASES--
+Core.DefinitionCache
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt
new file mode 100644
index 0000000..5403650
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt
@@ -0,0 +1,13 @@
+Cache.SerializerPath
+TYPE: string/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    Absolute path with no trailing slash to store serialized definitions in.
+    Default is within the
+    HTML Purifier library inside DefinitionCache/Serializer. This
+    path must be writable by the webserver.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
new file mode 100644
index 0000000..b2b83d9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt
@@ -0,0 +1,11 @@
+Cache.SerializerPermissions
+TYPE: int
+VERSION: 4.3.0
+DEFAULT: 0755
+--DESCRIPTION--
+
+<p>
+    Directory permissions of the files and directories created inside
+    the DefinitionCache/Serializer or other custom serializer path.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt
new file mode 100644
index 0000000..568cbf3
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt
@@ -0,0 +1,18 @@
+Core.AggressivelyFixLt
+TYPE: bool
+VERSION: 2.1.0
+DEFAULT: true
+--DESCRIPTION--
+<p>
+    This directive enables aggressive pre-filter fixes HTML Purifier can
+    perform in order to ensure that open angled-brackets do not get killed
+    during parsing stage. Enabling this will result in two preg_replace_callback
+    calls and at least two preg_replace calls for every HTML document parsed;
+    if your users make very well-formed HTML, you can set this directive false.
+    This has no effect when DirectLex is used.
+</p>
+<p>
+    <strong>Notice:</strong> This directive's default turned from false to true
+    in HTML Purifier 3.2.0.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt
new file mode 100644
index 0000000..d731791
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt
@@ -0,0 +1,12 @@
+Core.CollectErrors
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: false
+--DESCRIPTION--
+
+Whether or not to collect errors found while filtering the document. This
+is a useful way to give feedback to your users. <strong>Warning:</strong>
+Currently this feature is very patchy and experimental, with lots of
+possible error messages not yet implemented. It will not cause any
+problems, but it may not help your users either.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt
new file mode 100644
index 0000000..c572c14
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt
@@ -0,0 +1,29 @@
+Core.ColorKeywords
+TYPE: hash
+VERSION: 2.0.0
+--DEFAULT--
+array (
+  'maroon' => '#800000',
+  'red' => '#FF0000',
+  'orange' => '#FFA500',
+  'yellow' => '#FFFF00',
+  'olive' => '#808000',
+  'purple' => '#800080',
+  'fuchsia' => '#FF00FF',
+  'white' => '#FFFFFF',
+  'lime' => '#00FF00',
+  'green' => '#008000',
+  'navy' => '#000080',
+  'blue' => '#0000FF',
+  'aqua' => '#00FFFF',
+  'teal' => '#008080',
+  'black' => '#000000',
+  'silver' => '#C0C0C0',
+  'gray' => '#808080',
+)
+--DESCRIPTION--
+
+Lookup array of color names to six digit hexadecimal number corresponding
+to color, with preceding hash mark. Used when parsing colors.  The lookup
+is done in a case-insensitive manner.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt
new file mode 100644
index 0000000..64b114f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt
@@ -0,0 +1,14 @@
+Core.ConvertDocumentToFragment
+TYPE: bool
+DEFAULT: true
+--DESCRIPTION--
+
+This parameter determines whether or not the filter should convert
+input that is a full document with html and body tags to a fragment
+of just the contents of a body tag. This parameter is simply something
+HTML Purifier can do during an edge-case: for most inputs, this
+processing is not necessary.
+
+--ALIASES--
+Core.AcceptFullDocuments
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt
new file mode 100644
index 0000000..36f16e0
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt
@@ -0,0 +1,17 @@
+Core.DirectLexLineNumberSyncInterval
+TYPE: int
+VERSION: 2.0.0
+DEFAULT: 0
+--DESCRIPTION--
+
+<p>
+  Specifies the number of tokens the DirectLex line number tracking
+  implementations should process before attempting to resyncronize the
+  current line count by manually counting all previous new-lines. When
+  at 0, this functionality is disabled. Lower values will decrease
+  performance, and this is only strictly necessary if the counting
+  algorithm is buggy (in which case you should report it as a bug).
+  This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is
+  not being used.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt
new file mode 100644
index 0000000..1cd4c2c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt
@@ -0,0 +1,14 @@
+Core.DisableExcludes
+TYPE: bool
+DEFAULT: false
+VERSION: 4.5.0
+--DESCRIPTION--
+<p>
+  This directive disables SGML-style exclusions, e.g. the exclusion of
+  <code><object></code> in any descendant of a
+  <code><pre></code> tag.  Disabling excludes will allow some
+  invalid documents to pass through HTML Purifier, but HTML Purifier
+  will also be less likely to accidentally remove large documents during
+  processing.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt
new file mode 100644
index 0000000..ce243c3
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt
@@ -0,0 +1,9 @@
+Core.EnableIDNA
+TYPE: bool
+DEFAULT: false
+VERSION: 4.4.0
+--DESCRIPTION--
+Allows international domain names in URLs.  This configuration option
+requires the PEAR Net_IDNA2 module to be installed.  It operates by
+punycoding any internationalized host names for maximum portability.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt
new file mode 100644
index 0000000..8bfb47c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt
@@ -0,0 +1,15 @@
+Core.Encoding
+TYPE: istring
+DEFAULT: 'utf-8'
+--DESCRIPTION--
+If for some reason you are unable to convert all webpages to UTF-8, you can
+use this directive as a stop-gap compatibility change to let HTML Purifier
+deal with non UTF-8 input.  This technique has notable deficiencies:
+absolutely no characters outside of the selected character encoding will be
+preserved, not even the ones that have been ampersand escaped (this is due
+to a UTF-8 specific <em>feature</em> that automatically resolves all
+entities), making it pretty useless for anything except the most I18N-blind
+applications, although %Core.EscapeNonASCIICharacters offers fixes this
+trouble with another tradeoff. This directive only accepts ISO-8859-1 if
+iconv is not enabled.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
new file mode 100644
index 0000000..4d5b505
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt
@@ -0,0 +1,10 @@
+Core.EscapeInvalidChildren
+TYPE: bool
+DEFAULT: false
+--DESCRIPTION--
+When true, a child is found that is not allowed in the context of the
+parent element will be transformed into text as if it were ASCII. When
+false, that element and all internal tags will be dropped, though text will
+be preserved.  There is no option for dropping the element but preserving
+child nodes.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt
new file mode 100644
index 0000000..a7a5b24
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt
@@ -0,0 +1,7 @@
+Core.EscapeInvalidTags
+TYPE: bool
+DEFAULT: false
+--DESCRIPTION--
+When true, invalid tags will be written back to the document as plain text.
+Otherwise, they are silently dropped.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt
new file mode 100644
index 0000000..abb4999
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt
@@ -0,0 +1,13 @@
+Core.EscapeNonASCIICharacters
+TYPE: bool
+VERSION: 1.4.0
+DEFAULT: false
+--DESCRIPTION--
+This directive overcomes a deficiency in %Core.Encoding by blindly
+converting all non-ASCII characters into decimal numeric entities before
+converting it to its native encoding. This means that even characters that
+can be expressed in the non-UTF-8 encoding will be entity-ized, which can
+be a real downer for encodings like Big5. It also assumes that the ASCII
+repetoire is available, although this is the case for almost all encodings.
+Anyway, use UTF-8!
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt
new file mode 100644
index 0000000..915391e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt
@@ -0,0 +1,19 @@
+Core.HiddenElements
+TYPE: lookup
+--DEFAULT--
+array (
+  'script' => true,
+  'style' => true,
+)
+--DESCRIPTION--
+
+<p>
+  This directive is a lookup array of elements which should have their
+  contents removed when they are not allowed by the HTML definition.
+  For example, the contents of a <code>script</code> tag are not
+  normally shown in a document, so if script tags are to be removed,
+  their contents should be removed to. This is opposed to a <code>b</code>
+  tag, which defines some presentational changes but does not hide its
+  contents.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.Language.txt
new file mode 100644
index 0000000..233fca1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.Language.txt
@@ -0,0 +1,10 @@
+Core.Language
+TYPE: string
+VERSION: 2.0.0
+DEFAULT: 'en'
+--DESCRIPTION--
+
+ISO 639 language code for localizable things in HTML Purifier to use,
+which is mainly error reporting. There is currently only an English (en)
+translation, so this directive is currently useless.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt
new file mode 100644
index 0000000..8983e2c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt
@@ -0,0 +1,34 @@
+Core.LexerImpl
+TYPE: mixed/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+  This parameter determines what lexer implementation can be used. The
+  valid values are:
+</p>
+<dl>
+  <dt><em>null</em></dt>
+  <dd>
+    Recommended, the lexer implementation will be auto-detected based on
+    your PHP-version and configuration.
+  </dd>
+  <dt><em>string</em> lexer identifier</dt>
+  <dd>
+    This is a slim way of manually overridding the implementation.
+    Currently recognized values are: DOMLex (the default PHP5
+implementation)
+    and DirectLex (the default PHP4 implementation). Only use this if
+    you know what you are doing: usually, the auto-detection will
+    manage things for cases you aren't even aware of.
+  </dd>
+  <dt><em>object</em> lexer instance</dt>
+  <dd>
+    Super-advanced: you can specify your own, custom, implementation that
+    implements the interface defined by <code>HTMLPurifier_Lexer</code>.
+    I may remove this option simply because I don't expect anyone
+    to use it.
+  </dd>
+</dl>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
new file mode 100644
index 0000000..eb841a7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt
@@ -0,0 +1,16 @@
+Core.MaintainLineNumbers
+TYPE: bool/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+  If true, HTML Purifier will add line number information to all tokens.
+  This is useful when error reporting is turned on, but can result in
+  significant performance degradation and should not be used when
+  unnecessary. This directive must be used with the DirectLex lexer,
+  as the DOMLex lexer does not (yet) support this functionality.
+  If the value is null, an appropriate value will be selected based
+  on other configuration.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt
new file mode 100644
index 0000000..d77f536
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt
@@ -0,0 +1,11 @@
+Core.NormalizeNewlines
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: true
+--DESCRIPTION--
+<p>
+    Whether or not to normalize newlines to the operating
+    system default.  When <code>false</code>, HTML Purifier
+    will attempt to preserve mixed newline files.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt
new file mode 100644
index 0000000..4070c2a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt
@@ -0,0 +1,12 @@
+Core.RemoveInvalidImg
+TYPE: bool
+DEFAULT: true
+VERSION: 1.3.0
+--DESCRIPTION--
+
+<p>
+  This directive enables pre-emptive URI checking in <code>img</code>
+  tags, as the attribute validation strategy is not authorized to
+  remove elements from the document. Revert to pre-1.3.0 behavior by setting to false.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt
new file mode 100644
index 0000000..3397d9f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt
@@ -0,0 +1,11 @@
+Core.RemoveProcessingInstructions
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+Instead of escaping processing instructions in the form <code><? ...
+?></code>, remove it out-right.  This may be useful if the HTML
+you are validating contains XML processing instruction gunk, however,
+it can also be user-unfriendly for people attempting to post PHP
+snippets.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
new file mode 100644
index 0000000..a4cd966
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt
@@ -0,0 +1,12 @@
+Core.RemoveScriptContents
+TYPE: bool/null
+DEFAULT: NULL
+VERSION: 2.0.0
+DEPRECATED-VERSION: 2.1.0
+DEPRECATED-USE: Core.HiddenElements
+--DESCRIPTION--
+<p>
+  This directive enables HTML Purifier to remove not only script tags
+  but all of their contents.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt
new file mode 100644
index 0000000..3db50ef
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt
@@ -0,0 +1,11 @@
+Filter.Custom
+TYPE: list
+VERSION: 3.1.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+  This directive can be used to add custom filters; it is nearly the
+  equivalent of the now deprecated <code>HTMLPurifier->addFilter()</code>
+  method. Specify an array of concrete implementations.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt
new file mode 100644
index 0000000..16829bc
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt
@@ -0,0 +1,14 @@
+Filter.ExtractStyleBlocks.Escaping
+TYPE: bool
+VERSION: 3.0.0
+DEFAULT: true
+ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping
+--DESCRIPTION--
+
+<p>
+  Whether or not to escape the dangerous characters <, > and &
+  as \3C, \3E and \26, respectively. This is can be safely set to false
+  if the contents of StyleBlocks will be placed in an external stylesheet,
+  where there is no risk of it being interpreted as HTML.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt
new file mode 100644
index 0000000..7f95f54
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt
@@ -0,0 +1,29 @@
+Filter.ExtractStyleBlocks.Scope
+TYPE: string/null
+VERSION: 3.0.0
+DEFAULT: NULL
+ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope
+--DESCRIPTION--
+
+<p>
+  If you would like users to be able to define external stylesheets, but
+  only allow them to specify CSS declarations for a specific node and
+  prevent them from fiddling with other elements, use this directive.
+  It accepts any valid CSS selector, and will prepend this to any
+  CSS declaration extracted from the document. For example, if this
+  directive is set to <code>#user-content</code> and a user uses the
+  selector <code>a:hover</code>, the final selector will be
+  <code>#user-content a:hover</code>.
+</p>
+<p>
+  The comma shorthand may be used; consider the above example, with
+  <code>#user-content, #user-content2</code>, the final selector will
+  be <code>#user-content a:hover, #user-content2 a:hover</code>.
+</p>
+<p>
+  <strong>Warning:</strong> It is possible for users to bypass this measure
+  using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML
+  Purifier, and I am working to get it fixed. Until then, HTML Purifier
+  performs a basic check to prevent this.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt
new file mode 100644
index 0000000..6c231b2
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt
@@ -0,0 +1,16 @@
+Filter.ExtractStyleBlocks.TidyImpl
+TYPE: mixed/null
+VERSION: 3.1.0
+DEFAULT: NULL
+ALIASES: FilterParam.ExtractStyleBlocksTidyImpl
+--DESCRIPTION--
+<p>
+  If left NULL, HTML Purifier will attempt to instantiate a <code>csstidy</code>
+  class to use for internal cleaning. This will usually be good enough.
+</p>
+<p>
+  However, for trusted user input, you can set this to <code>false</code> to
+  disable cleaning. In addition, you can supply your own concrete implementation
+  of Tidy's interface to use, although I don't know why you'd want to do that.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt
new file mode 100644
index 0000000..078d087
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt
@@ -0,0 +1,74 @@
+Filter.ExtractStyleBlocks
+TYPE: bool
+VERSION: 3.1.0
+DEFAULT: false
+EXTERNAL: CSSTidy
+--DESCRIPTION--
+<p>
+  This directive turns on the style block extraction filter, which removes
+  <code>style</code> blocks from input HTML, cleans them up with CSSTidy,
+  and places them in the <code>StyleBlocks</code> context variable, for further
+  use by you, usually to be placed in an external stylesheet, or a
+  <code>style</code> block in the <code>head</code> of your document.
+</p>
+<p>
+  Sample usage:
+</p>
+<pre><![CDATA[
+<?php
+    header('Content-type: text/html; charset=utf-8');
+    echo '<?xml version="1.0" encoding="UTF-8"?>';
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+  <title>Filter.ExtractStyleBlocks</title>
+<?php
+    require_once '/path/to/library/HTMLPurifier.auto.php';
+    require_once '/path/to/csstidy.class.php';
+
+    $dirty = '<style>body {color:#F00;}</style> Some text';
+
+    $config = HTMLPurifier_Config::createDefault();
+    $config->set('Filter', 'ExtractStyleBlocks', true);
+    $purifier = new HTMLPurifier($config);
+
+    $html = $purifier->purify($dirty);
+
+    // This implementation writes the stylesheets to the styles/ directory.
+    // You can also echo the styles inside the document, but it's a bit
+    // more difficult to make sure they get interpreted properly by
+    // browsers; try the usual CSS armoring techniques.
+    $styles = $purifier->context->get('StyleBlocks');
+    $dir = 'styles/';
+    if (!is_dir($dir)) mkdir($dir);
+    $hash = sha1($_GET['html']);
+    foreach ($styles as $i => $style) {
+        file_put_contents($name = $dir . $hash . "_$i");
+        echo '<link rel="stylesheet" type="text/css" href="'.$name.'" />';
+    }
+?>
+</head>
+<body>
+  <div>
+    <?php echo $html; ?>
+  </div>
+</b]]><![CDATA[ody>
+</html>
+]]></pre>
+<p>
+  <strong>Warning:</strong> It is possible for a user to mount an
+  imagecrash attack using this CSS. Counter-measures are difficult;
+  it is not simply enough to limit the range of CSS lengths (using
+  relative lengths with many nesting levels allows for large values
+  to be attained without actually specifying them in the stylesheet),
+  and the flexible nature of selectors makes it difficult to selectively
+  disable lengths on image tags (HTML Purifier, however, does disable
+  CSS width and height in inline styling). There are probably two effective
+  counter measures: an explicit width and height set to auto in all
+  images in your document (unlikely) or the disabling of width and
+  height (somewhat reasonable). Whether or not these measures should be
+  used is left to the reader.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt
new file mode 100644
index 0000000..321eaa2
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt
@@ -0,0 +1,16 @@
+Filter.YouTube
+TYPE: bool
+VERSION: 3.1.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+  <strong>Warning:</strong> Deprecated in favor of %HTML.SafeObject and
+  %Output.FlashCompat (turn both on to allow YouTube videos and other
+  Flash content).
+</p>
+<p>
+  This directive enables YouTube video embedding in HTML Purifier. Check
+  <a href="http://htmlpurifier.org/docs/enduser-youtube.html">this document
+  on embedding videos</a> for more information on what this filter does.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt
new file mode 100644
index 0000000..0b2c106
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt
@@ -0,0 +1,25 @@
+HTML.Allowed
+TYPE: itext/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    This is a preferred convenience directive that combines
+    %HTML.AllowedElements and %HTML.AllowedAttributes.
+    Specify elements and attributes that are allowed using:
+    <code>element1[attr1|attr2],element2...</code>.  For example,
+    if you would like to only allow paragraphs and links, specify
+    <code>a[href],p</code>.  You can specify attributes that apply
+    to all elements using an asterisk, e.g. <code>*[lang]</code>.
+    You can also use newlines instead of commas to separate elements.
+</p>
+<p>
+    <strong>Warning</strong>:
+    All of the constraints on the component directives are still enforced.
+    The syntax is a <em>subset</em> of TinyMCE's <code>valid_elements</code>
+    whitelist: directly copy-pasting it here will probably result in
+    broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes
+    are set, this directive has no effect.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt
new file mode 100644
index 0000000..fcf093f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt
@@ -0,0 +1,19 @@
+HTML.AllowedAttributes
+TYPE: lookup/null
+VERSION: 1.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    If HTML Purifier's attribute set is unsatisfactory, overload it!
+    The syntax is "tag.attr" or "*.attr" for the global attributes
+    (style, id, class, dir, lang, xml:lang).
+</p>
+<p>
+    <strong>Warning:</strong> If another directive conflicts with the
+    elements here, <em>that</em> directive will win and override. For
+    example, %HTML.EnableAttrID will take precedence over *.id in this
+    directive.  You must set that directive to true before you can use
+    IDs at all.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt
new file mode 100644
index 0000000..140e214
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt
@@ -0,0 +1,10 @@
+HTML.AllowedComments
+TYPE: lookup
+VERSION: 4.4.0
+DEFAULT: array()
+--DESCRIPTION--
+A whitelist which indicates what explicit comment bodies should be
+allowed, modulo leading and trailing whitespace.  See also %HTML.AllowedCommentsRegexp
+(these directives are union'ed together, so a comment is considered
+valid if any directive deems it valid.)
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt
new file mode 100644
index 0000000..f22e977
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt
@@ -0,0 +1,15 @@
+HTML.AllowedCommentsRegexp
+TYPE: string/null
+VERSION: 4.4.0
+DEFAULT: NULL
+--DESCRIPTION--
+A regexp, which if it matches the body of a comment, indicates that
+it should be allowed. Trailing and leading spaces are removed prior
+to running this regular expression.
+<strong>Warning:</strong> Make sure you specify
+correct anchor metacharacters <code>^regex$</code>, otherwise you may accept
+comments that you did not mean to! In particular, the regex <code>/foo|bar/</code>
+is probably not sufficiently strict, since it also allows <code>foobar</code>.
+See also %HTML.AllowedComments (these directives are union'ed together,
+so a comment is considered valid if any directive deems it valid.)
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
new file mode 100644
index 0000000..1d3fa79
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt
@@ -0,0 +1,23 @@
+HTML.AllowedElements
+TYPE: lookup/null
+VERSION: 1.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+<p>
+    If HTML Purifier's tag set is unsatisfactory for your needs, you can
+    overload it with your own list of tags to allow.  If you change
+    this, you probably also want to change %HTML.AllowedAttributes; see
+    also %HTML.Allowed which lets you set allowed elements and
+    attributes at the same time.
+</p>
+<p>
+    If you attempt to allow an element that HTML Purifier does not know
+    about, HTML Purifier will raise an error.  You will need to manually
+    tell HTML Purifier about this element by using the
+    <a href="http://htmlpurifier.org/docs/enduser-customize.html">advanced customization features.</a>
+</p>
+<p>
+    <strong>Warning:</strong> If another directive conflicts with the
+    elements here, <em>that</em> directive will win and override.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt
new file mode 100644
index 0000000..5a59a55
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt
@@ -0,0 +1,20 @@
+HTML.AllowedModules
+TYPE: lookup/null
+VERSION: 2.0.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    A doctype comes with a set of usual modules to use. Without having
+    to mucking about with the doctypes, you can quickly activate or
+    disable these modules by specifying which modules you wish to allow
+    with this directive. This is most useful for unit testing specific
+    modules, although end users may find it useful for their own ends.
+</p>
+<p>
+    If you specify a module that does not exist, the manager will silently
+    fail to use it, so be careful! User-defined modules are not affected
+    by this directive. Modules defined in %HTML.CoreModules are not
+    affected by this directive.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
new file mode 100644
index 0000000..151fb7b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt
@@ -0,0 +1,11 @@
+HTML.Attr.Name.UseCDATA
+TYPE: bool
+DEFAULT: false
+VERSION: 4.0.0
+--DESCRIPTION--
+The W3C specification DTD defines the name attribute to be CDATA, not ID, due
+to limitations of DTD.  In certain documents, this relaxed behavior is desired,
+whether it is to specify duplicate names, or to specify names that would be
+illegal IDs (for example, names that begin with a digit.) Set this configuration
+directive to true to use the relaxed parsing rules.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
new file mode 100644
index 0000000..45ae469
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt
@@ -0,0 +1,18 @@
+HTML.BlockWrapper
+TYPE: string
+VERSION: 1.3.0
+DEFAULT: 'p'
+--DESCRIPTION--
+
+<p>
+    String name of element to wrap inline elements that are inside a block
+    context.  This only occurs in the children of blockquote in strict mode.
+</p>
+<p>
+    Example: by default value,
+    <code><blockquote>Foo</blockquote></code> would become
+    <code><blockquote><p>Foo</p></blockquote></code>.
+    The <code><p></code> tags can be replaced with whatever you desire,
+    as long as it is a block level element.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt
new file mode 100644
index 0000000..5246188
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt
@@ -0,0 +1,23 @@
+HTML.CoreModules
+TYPE: lookup
+VERSION: 2.0.0
+--DEFAULT--
+array (
+  'Structure' => true,
+  'Text' => true,
+  'Hypertext' => true,
+  'List' => true,
+  'NonXMLCommonAttributes' => true,
+  'XMLCommonAttributes' => true,
+  'CommonAttributes' => true,
+)
+--DESCRIPTION--
+
+<p>
+    Certain modularized doctypes (XHTML, namely), have certain modules
+    that must be included for the doctype to be an conforming document
+    type: put those modules here. By default, XHTML's core modules
+    are used. You can set this to a blank array to disable core module
+    protection, but this is not recommended.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
new file mode 100644
index 0000000..a64e3d7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt
@@ -0,0 +1,9 @@
+HTML.CustomDoctype
+TYPE: string/null
+VERSION: 2.0.1
+DEFAULT: NULL
+--DESCRIPTION--
+
+A custom doctype for power-users who defined there own document
+type. This directive only applies when %HTML.Doctype is blank.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
new file mode 100644
index 0000000..103db75
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt
@@ -0,0 +1,33 @@
+HTML.DefinitionID
+TYPE: string/null
+DEFAULT: NULL
+VERSION: 2.0.0
+--DESCRIPTION--
+
+<p>
+    Unique identifier for a custom-built HTML definition. If you edit
+    the raw version of the HTMLDefinition, introducing changes that the
+    configuration object does not reflect, you must specify this variable.
+    If you change your custom edits, you should change this directive, or
+    clear your cache. Example:
+</p>
+<pre>
+$config = HTMLPurifier_Config::createDefault();
+$config->set('HTML', 'DefinitionID', '1');
+$def = $config->getHTMLDefinition();
+$def->addAttribute('a', 'tabindex', 'Number');
+</pre>
+<p>
+    In the above example, the configuration is still at the defaults, but
+    using the advanced API, an extra attribute has been added. The
+    configuration object normally has no way of knowing that this change
+    has taken place, so it needs an extra directive: %HTML.DefinitionID.
+    If someone else attempts to use the default configuration, these two
+    pieces of code will not clobber each other in the cache, since one has
+    an extra directive attached to it.
+</p>
+<p>
+    You <em>must</em> specify a value to this directive to use the
+    advanced API features.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
new file mode 100644
index 0000000..229ae02
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt
@@ -0,0 +1,16 @@
+HTML.DefinitionRev
+TYPE: int
+VERSION: 2.0.0
+DEFAULT: 1
+--DESCRIPTION--
+
+<p>
+    Revision identifier for your custom definition specified in
+    %HTML.DefinitionID.  This serves the same purpose: uniquely identifying
+    your custom definition, but this one does so in a chronological
+    context: revision 3 is more up-to-date then revision 2.  Thus, when
+    this gets incremented, the cache handling is smart enough to clean
+    up any older revisions of your definition as well as flush the
+    cache.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
new file mode 100644
index 0000000..9dab497
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt
@@ -0,0 +1,11 @@
+HTML.Doctype
+TYPE: string/null
+DEFAULT: NULL
+--DESCRIPTION--
+Doctype to use during filtering. Technically speaking this is not actually
+a doctype (as it does not identify a corresponding DTD), but we are using
+this name for sake of simplicity. When non-blank, this will override any
+older directives like %HTML.XHTML or %HTML.Strict.
+--ALLOWED--
+'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1'
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt
new file mode 100644
index 0000000..7878dc0
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt
@@ -0,0 +1,11 @@
+HTML.FlashAllowFullScreen
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    Whether or not to permit embedded Flash content from
+    %HTML.SafeObject to expand to the full screen.  Corresponds to
+    the <code>allowFullScreen</code> parameter.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt
new file mode 100644
index 0000000..57358f9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt
@@ -0,0 +1,21 @@
+HTML.ForbiddenAttributes
+TYPE: lookup
+VERSION: 3.1.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+    While this directive is similar to %HTML.AllowedAttributes, for
+    forwards-compatibility with XML, this attribute has a different syntax. Instead of
+    <code>tag.attr</code>, use <code>tag at attr</code>. To disallow <code>href</code>
+    attributes in <code>a</code> tags, set this directive to
+    <code>a at href</code>. You can also disallow an attribute globally with
+    <code>attr</code> or <code>*@attr</code> (either syntax is fine; the latter
+    is provided for consistency with %HTML.AllowedAttributes).
+</p>
+<p>
+    <strong>Warning:</strong> This directive complements %HTML.ForbiddenElements,
+    accordingly, check
+    out that directive for a discussion of why you
+    should think twice before using this directive.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt
new file mode 100644
index 0000000..93a53e1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt
@@ -0,0 +1,20 @@
+HTML.ForbiddenElements
+TYPE: lookup
+VERSION: 3.1.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+    This was, perhaps, the most requested feature ever in HTML
+    Purifier. Please don't abuse it! This is the logical inverse of
+    %HTML.AllowedElements, and it will override that directive, or any
+    other directive.
+</p>
+<p>
+    If possible, %HTML.Allowed is recommended over this directive, because it
+    can sometimes be difficult to tell whether or not you've forbidden all of
+    the behavior you would like to disallow. If you forbid <code>img</code>
+    with the expectation of preventing images on your site, you'll be in for
+    a nasty surprise when people start using the <code>background-image</code>
+    CSS property.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt
new file mode 100644
index 0000000..e424c38
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt
@@ -0,0 +1,14 @@
+HTML.MaxImgLength
+TYPE: int/null
+DEFAULT: 1200
+VERSION: 3.1.1
+--DESCRIPTION--
+<p>
+ This directive controls the maximum number of pixels in the width and
+ height attributes in <code>img</code> tags. This is
+ in place to prevent imagecrash attacks, disable with null at your own risk.
+ This directive is similar to %CSS.MaxImgLength, and both should be
+ concurrently edited, although there are
+ subtle differences in the input format (the HTML max is an integer).
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
new file mode 100644
index 0000000..700b309
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt
@@ -0,0 +1,7 @@
+HTML.Nofollow
+TYPE: bool
+VERSION: 4.3.0
+DEFAULT: FALSE
+--DESCRIPTION--
+If enabled, nofollow rel attributes are added to all outgoing links.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
new file mode 100644
index 0000000..62e8e16
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt
@@ -0,0 +1,12 @@
+HTML.Parent
+TYPE: string
+VERSION: 1.3.0
+DEFAULT: 'div'
+--DESCRIPTION--
+
+<p>
+    String name of element that HTML fragment passed to library will be
+    inserted in.  An interesting variation would be using span as the
+    parent element, meaning that only inline tags would be allowed.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt
new file mode 100644
index 0000000..dfb7204
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt
@@ -0,0 +1,12 @@
+HTML.Proprietary
+TYPE: bool
+VERSION: 3.1.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    Whether or not to allow proprietary elements and attributes in your
+    documents, as per <code>HTMLPurifier_HTMLModule_Proprietary</code>.
+    <strong>Warning:</strong> This can cause your documents to stop
+    validating!
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt
new file mode 100644
index 0000000..cdda09a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt
@@ -0,0 +1,13 @@
+HTML.SafeEmbed
+TYPE: bool
+VERSION: 3.1.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    Whether or not to permit embed tags in documents, with a number of extra
+    security features added to prevent script execution. This is similar to
+    what websites like MySpace do to embed tags. Embed is a proprietary
+    element and will cause your website to stop validating; you should
+    see if you can use %Output.FlashCompat with %HTML.SafeObject instead
+    first.</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt
new file mode 100644
index 0000000..5eb6ec2
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt
@@ -0,0 +1,13 @@
+HTML.SafeIframe
+TYPE: bool
+VERSION: 4.4.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    Whether or not to permit iframe tags in untrusted documents.  This
+    directive must be accompanied by a whitelist of permitted iframes,
+    such as %URI.SafeIframeRegexp, otherwise it will fatally error.
+    This directive has no effect on strict doctypes, as iframes are not
+    valid.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt
new file mode 100644
index 0000000..ceb342e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt
@@ -0,0 +1,13 @@
+HTML.SafeObject
+TYPE: bool
+VERSION: 3.1.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    Whether or not to permit object tags in documents, with a number of extra
+    security features added to prevent script execution. This is similar to
+    what websites like MySpace do to object tags.  You should also enable
+    %Output.FlashCompat in order to generate Internet Explorer
+    compatibility code for your object tags.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt
new file mode 100644
index 0000000..5ebc7a1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt
@@ -0,0 +1,10 @@
+HTML.SafeScripting
+TYPE: lookup
+VERSION: 4.5.0
+DEFAULT: array()
+--DESCRIPTION--
+<p>
+    Whether or not to permit script tags to external scripts in documents.
+    Inline scripting is not allowed, and the script must match an explicit whitelist.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
new file mode 100644
index 0000000..a8b1de5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt
@@ -0,0 +1,9 @@
+HTML.Strict
+TYPE: bool
+VERSION: 1.3.0
+DEFAULT: false
+DEPRECATED-VERSION: 1.7.0
+DEPRECATED-USE: HTML.Doctype
+--DESCRIPTION--
+Determines whether or not to use Transitional (loose) or Strict rulesets.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt
new file mode 100644
index 0000000..587a167
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt
@@ -0,0 +1,8 @@
+HTML.TargetBlank
+TYPE: bool
+VERSION: 4.4.0
+DEFAULT: FALSE
+--DESCRIPTION--
+If enabled, <code>target=blank</code> attributes are added to all outgoing links.
+(This includes links from an HTTPS version of a page to an HTTP version.)
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
new file mode 100644
index 0000000..b4c271b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt
@@ -0,0 +1,8 @@
+HTML.TidyAdd
+TYPE: lookup
+VERSION: 2.0.0
+DEFAULT: array()
+--DESCRIPTION--
+
+Fixes to add to the default set of Tidy fixes as per your level.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
new file mode 100644
index 0000000..4186ccd
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt
@@ -0,0 +1,24 @@
+HTML.TidyLevel
+TYPE: string
+VERSION: 2.0.0
+DEFAULT: 'medium'
+--DESCRIPTION--
+
+<p>General level of cleanliness the Tidy module should enforce.
+There are four allowed values:</p>
+<dl>
+    <dt>none</dt>
+    <dd>No extra tidying should be done</dd>
+    <dt>light</dt>
+    <dd>Only fix elements that would be discarded otherwise due to
+    lack of support in doctype</dd>
+    <dt>medium</dt>
+    <dd>Enforce best practices</dd>
+    <dt>heavy</dt>
+    <dd>Transform all deprecated elements and attributes to standards
+    compliant equivalents</dd>
+</dl>
+
+--ALLOWED--
+'none', 'light', 'medium', 'heavy'
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
new file mode 100644
index 0000000..996762b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt
@@ -0,0 +1,8 @@
+HTML.TidyRemove
+TYPE: lookup
+VERSION: 2.0.0
+DEFAULT: array()
+--DESCRIPTION--
+
+Fixes to remove from the default set of Tidy fixes as per your level.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
new file mode 100644
index 0000000..1db9237
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt
@@ -0,0 +1,9 @@
+HTML.Trusted
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: false
+--DESCRIPTION--
+Indicates whether or not the user input is trusted or not. If the input is
+trusted, a more expansive set of allowed tags and attributes will be used.
+See also %CSS.Trusted.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
new file mode 100644
index 0000000..2a47e38
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt
@@ -0,0 +1,11 @@
+HTML.XHTML
+TYPE: bool
+DEFAULT: true
+VERSION: 1.1.0
+DEPRECATED-VERSION: 1.7.0
+DEPRECATED-USE: HTML.Doctype
+--DESCRIPTION--
+Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor.
+--ALIASES--
+Core.XHTML
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
new file mode 100644
index 0000000..08921fd
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt
@@ -0,0 +1,10 @@
+Output.CommentScriptContents
+TYPE: bool
+VERSION: 2.0.0
+DEFAULT: true
+--DESCRIPTION--
+Determines whether or not HTML Purifier should attempt to fix up the
+contents of script tags for legacy browsers with comments.
+--ALIASES--
+Core.CommentScriptContents
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
new file mode 100644
index 0000000..d6f0d9f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt
@@ -0,0 +1,15 @@
+Output.FixInnerHTML
+TYPE: bool
+VERSION: 4.3.0
+DEFAULT: true
+--DESCRIPTION--
+<p>
+  If true, HTML Purifier will protect against Internet Explorer's
+  mishandling of the <code>innerHTML</code> attribute by appending
+  a space to any attribute that does not contain angled brackets, spaces
+  or quotes, but contains a backtick.  This slightly changes the
+  semantics of any given attribute, so if this is unacceptable and
+  you do not use <code>innerHTML</code> on any of your pages, you can
+  turn this directive off.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt
new file mode 100644
index 0000000..93398e8
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt
@@ -0,0 +1,11 @@
+Output.FlashCompat
+TYPE: bool
+VERSION: 4.1.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+  If true, HTML Purifier will generate Internet Explorer compatibility
+  code for all object code.  This is highly recommended if you enable
+  %HTML.SafeObject.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt
new file mode 100644
index 0000000..79f8ad8
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt
@@ -0,0 +1,13 @@
+Output.Newline
+TYPE: string/null
+VERSION: 2.0.1
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    Newline string to format final output with. If left null, HTML Purifier
+    will auto-detect the default newline type of the system and use that;
+    you can manually override it here. Remember, \r\n is Windows, \r
+    is Mac, and \n is Unix.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt
new file mode 100644
index 0000000..232b023
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt
@@ -0,0 +1,14 @@
+Output.SortAttr
+TYPE: bool
+VERSION: 3.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+  If true, HTML Purifier will sort attributes by name before writing them back
+  to the document, converting a tag like: <code><el b="" a="" c="" /></code>
+  to <code><el a="" b="" c="" /></code>. This is a workaround for
+  a bug in FCKeditor which causes it to swap attributes order, adding noise
+  to text diffs. If you're not seeing this bug, chances are, you don't need
+  this directive.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt
new file mode 100644
index 0000000..06bab00
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt
@@ -0,0 +1,25 @@
+Output.TidyFormat
+TYPE: bool
+VERSION: 1.1.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    Determines whether or not to run Tidy on the final output for pretty
+    formatting reasons, such as indentation and wrap.
+</p>
+<p>
+    This can greatly improve readability for editors who are hand-editing
+    the HTML, but is by no means necessary as HTML Purifier has already
+    fixed all major errors the HTML may have had. Tidy is a non-default
+    extension, and this directive will silently fail if Tidy is not
+    available.
+</p>
+<p>
+    If you are looking to make the overall look of your page's source
+    better, I recommend running Tidy on the entire page rather than just
+    user-content (after all, the indentation relative to the containing
+    blocks will be incorrect).
+</p>
+--ALIASES--
+Core.TidyFormat
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
new file mode 100644
index 0000000..071bc02
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt
@@ -0,0 +1,7 @@
+Test.ForceNoIconv
+TYPE: bool
+DEFAULT: false
+--DESCRIPTION--
+When set to true, HTMLPurifier_Encoder will act as if iconv does not exist
+and use only pure PHP implementations.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
new file mode 100644
index 0000000..666635a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt
@@ -0,0 +1,17 @@
+URI.AllowedSchemes
+TYPE: lookup
+--DEFAULT--
+array (
+  'http' => true,
+  'https' => true,
+  'mailto' => true,
+  'ftp' => true,
+  'nntp' => true,
+  'news' => true,
+)
+--DESCRIPTION--
+Whitelist that defines the schemes that a URI is allowed to have.  This
+prevents XSS attacks from using pseudo-schemes like javascript or mocha.
+There is also support for the <code>data</code> and <code>file</code>
+URI schemes, but they are not enabled by default.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
new file mode 100644
index 0000000..876f068
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Base.txt
@@ -0,0 +1,17 @@
+URI.Base
+TYPE: string/null
+VERSION: 2.1.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    The base URI is the URI of the document this purified HTML will be
+    inserted into.  This information is important if HTML Purifier needs
+    to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute
+    is on.  You may use a non-absolute URI for this value, but behavior
+    may vary (%URI.MakeAbsolute deals nicely with both absolute and
+    relative paths, but forwards-compatibility is not guaranteed).
+    <strong>Warning:</strong> If set, the scheme on this URI
+    overrides the one specified by %URI.DefaultScheme.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
new file mode 100644
index 0000000..728e378
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt
@@ -0,0 +1,10 @@
+URI.DefaultScheme
+TYPE: string
+DEFAULT: 'http'
+--DESCRIPTION--
+
+<p>
+    Defines through what scheme the output will be served, in order to
+    select the proper object validator when no scheme information is present.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt
new file mode 100644
index 0000000..f05312b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt
@@ -0,0 +1,11 @@
+URI.DefinitionID
+TYPE: string/null
+VERSION: 2.1.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    Unique identifier for a custom-built URI definition. If you  want
+    to add custom URIFilters, you must specify this value.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt
new file mode 100644
index 0000000..80cfea9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt
@@ -0,0 +1,11 @@
+URI.DefinitionRev
+TYPE: int
+VERSION: 2.1.0
+DEFAULT: 1
+--DESCRIPTION--
+
+<p>
+    Revision identifier for your custom definition. See
+    %HTML.DefinitionRev for details.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt
new file mode 100644
index 0000000..71ce025
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt
@@ -0,0 +1,14 @@
+URI.Disable
+TYPE: bool
+VERSION: 1.3.0
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+    Disables all URIs in all forms. Not sure why you'd want to do that
+    (after all, the Internet's founded on the notion of a hyperlink).
+</p>
+
+--ALIASES--
+Attr.DisableURI
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
new file mode 100644
index 0000000..13c122c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt
@@ -0,0 +1,11 @@
+URI.DisableExternal
+TYPE: bool
+VERSION: 1.2.0
+DEFAULT: false
+--DESCRIPTION--
+Disables links to external websites.  This is a highly effective anti-spam
+and anti-pagerank-leech measure, but comes at a hefty price: nolinks or
+images outside of your domain will be allowed.  Non-linkified URIs will
+still be preserved.  If you want to be able to link to subdomains or use
+absolute URIs, specify %URI.Host for your website.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
new file mode 100644
index 0000000..abcc1ef
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt
@@ -0,0 +1,13 @@
+URI.DisableExternalResources
+TYPE: bool
+VERSION: 1.3.0
+DEFAULT: false
+--DESCRIPTION--
+Disables the embedding of external resources, preventing users from
+embedding things like images from other hosts. This prevents access
+tracking (good for email viewers), bandwidth leeching, cross-site request
+forging, goatse.cx posting, and other nasties, but also results in a loss
+of end-user functionality (they can't directly post a pic they posted from
+Flickr anymore). Use it if you don't have a robust user-content moderation
+team.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
new file mode 100644
index 0000000..f891de4
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt
@@ -0,0 +1,15 @@
+URI.DisableResources
+TYPE: bool
+VERSION: 4.2.0
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    Disables embedding resources, essentially meaning no pictures. You can
+    still link to them though. See %URI.DisableExternalResources for why
+    this might be a good idea.
+</p>
+<p>
+    <em>Note:</em> While this directive has been available since 1.3.0,
+    it didn't actually start doing anything until 4.2.0.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Host.txt
new file mode 100644
index 0000000..ee83b12
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Host.txt
@@ -0,0 +1,19 @@
+URI.Host
+TYPE: string/null
+VERSION: 1.2.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    Defines the domain name of the server, so we can determine whether or
+    an absolute URI is from your website or not.  Not strictly necessary,
+    as users should be using relative URIs to reference resources on your
+    website.  It will, however, let you use absolute URIs to link to
+    subdomains of the domain you post here: i.e. example.com will allow
+    sub.example.com.  However, higher up domains will still be excluded:
+    if you set %URI.Host to sub.example.com, example.com will be blocked.
+    <strong>Note:</strong> This directive overrides %URI.Base because
+    a given page may be on a sub-domain, but you wish HTML Purifier to be
+    more relaxed and allow some of the parent domains too.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
new file mode 100644
index 0000000..0b6df76
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt
@@ -0,0 +1,9 @@
+URI.HostBlacklist
+TYPE: list
+VERSION: 1.3.0
+DEFAULT: array()
+--DESCRIPTION--
+List of strings that are forbidden in the host of any URI. Use it to kill
+domain names of spam, etc. Note that it will catch anything in the domain,
+so <tt>moo.com</tt> will catch <tt>moo.com.example.com</tt>.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
new file mode 100644
index 0000000..4214900
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt
@@ -0,0 +1,13 @@
+URI.MakeAbsolute
+TYPE: bool
+VERSION: 2.1.0
+DEFAULT: false
+--DESCRIPTION--
+
+<p>
+    Converts all URIs into absolute forms. This is useful when the HTML
+    being filtered assumes a specific base path, but will actually be
+    viewed in a different context (and setting an alternate base URI is
+    not possible). %URI.Base must be set for this directive to work.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
new file mode 100644
index 0000000..58c81dc
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt
@@ -0,0 +1,83 @@
+URI.Munge
+TYPE: string/null
+VERSION: 1.3.0
+DEFAULT: NULL
+--DESCRIPTION--
+
+<p>
+    Munges all browsable (usually http, https and ftp)
+    absolute URIs into another URI, usually a URI redirection service.
+    This directive accepts a URI, formatted with a <code>%s</code> where
+    the url-encoded original URI should be inserted (sample:
+    <code>http://www.google.com/url?q=%s</code>).
+</p>
+<p>
+    Uses for this directive:
+</p>
+<ul>
+    <li>
+        Prevent PageRank leaks, while being fairly transparent
+        to users (you may also want to add some client side JavaScript to
+        override the text in the statusbar). <strong>Notice</strong>:
+        Many security experts believe that this form of protection does not deter spam-bots.
+    </li>
+    <li>
+        Redirect users to a splash page telling them they are leaving your
+        website. While this is poor usability practice, it is often mandated
+        in corporate environments.
+    </li>
+</ul>
+<p>
+    Prior to HTML Purifier 3.1.1, this directive also enabled the munging
+    of browsable external resources, which could break things if your redirection
+    script was a splash page or used <code>meta</code> tags. To revert to
+    previous behavior, please use %URI.MungeResources.
+</p>
+<p>
+    You may want to also use %URI.MungeSecretKey along with this directive
+    in order to enforce what URIs your redirector script allows. Open
+    redirector scripts can be a security risk and negatively affect the
+    reputation of your domain name.
+</p>
+<p>
+    Starting with HTML Purifier 3.1.1, there is also these substitutions:
+</p>
+<table>
+    <thead>
+        <tr>
+            <th>Key</th>
+            <th>Description</th>
+            <th>Example <code><a href=""></code></th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+            <td>%r</td>
+            <td>1 - The URI embeds a resource<br />(blank) - The URI is merely a link</td>
+            <td></td>
+        </tr>
+        <tr>
+            <td>%n</td>
+            <td>The name of the tag this URI came from</td>
+            <td>a</td>
+        </tr>
+        <tr>
+            <td>%m</td>
+            <td>The name of the attribute this URI came from</td>
+            <td>href</td>
+        </tr>
+        <tr>
+            <td>%p</td>
+            <td>The name of the CSS property this URI came from, or blank if irrelevant</td>
+            <td></td>
+        </tr>
+    </tbody>
+</table>
+<p>
+    Admittedly, these letters are somewhat arbitrary; the only stipulation
+    was that they couldn't be a through f. r is for resource (I would have preferred
+    e, but you take what you can get), n is for name, m
+    was picked because it came after n (and I couldn't use a), p is for
+    property.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt
new file mode 100644
index 0000000..6fce0fd
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt
@@ -0,0 +1,17 @@
+URI.MungeResources
+TYPE: bool
+VERSION: 3.1.1
+DEFAULT: false
+--DESCRIPTION--
+<p>
+    If true, any URI munging directives like %URI.Munge
+    will also apply to embedded resources, such as <code><img src=""></code>.
+    Be careful enabling this directive if you have a redirector script
+    that does not use the <code>Location</code> HTTP header; all of your images
+    and other embedded resources will break.
+</p>
+<p>
+    <strong>Warning:</strong> It is strongly advised you use this in conjunction
+    %URI.MungeSecretKey to mitigate the security risk of an open redirector.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
new file mode 100644
index 0000000..0d00f62
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt
@@ -0,0 +1,30 @@
+URI.MungeSecretKey
+TYPE: string/null
+VERSION: 3.1.1
+DEFAULT: NULL
+--DESCRIPTION--
+<p>
+    This directive enables secure checksum generation along with %URI.Munge.
+    It should be set to a secure key that is not shared with anyone else.
+    The checksum can be placed in the URI using %t. Use of this checksum
+    affords an additional level of protection by allowing a redirector
+    to check if a URI has passed through HTML Purifier with this line:
+</p>
+
+<pre>$checksum === sha1($secret_key . ':' . $url)</pre>
+
+<p>
+    If the output is TRUE, the redirector script should accept the URI.
+</p>
+
+<p>
+    Please note that it would still be possible for an attacker to procure
+    secure hashes en-mass by abusing your website's Preview feature or the
+    like, but this service affords an additional level of protection
+    that should be combined with website blacklisting.
+</p>
+
+<p>
+    Remember this has no effect if %URI.Munge is not on.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
new file mode 100644
index 0000000..23331a4
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt
@@ -0,0 +1,9 @@
+URI.OverrideAllowedSchemes
+TYPE: bool
+DEFAULT: true
+--DESCRIPTION--
+If this is set to true (which it is by default), you can override
+%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the
+registry.  If false, you will also have to update that directive in order
+to add more schemes.
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt
new file mode 100644
index 0000000..7908483
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt
@@ -0,0 +1,22 @@
+URI.SafeIframeRegexp
+TYPE: string/null
+VERSION: 4.4.0
+DEFAULT: NULL
+--DESCRIPTION--
+<p>
+    A PCRE regular expression that will be matched against an iframe URI.  This is
+    a relatively inflexible scheme, but works well enough for the most common
+    use-case of iframes: embedded video.  This directive only has an effect if
+    %HTML.SafeIframe is enabled.  Here are some example values:
+</p>
+<ul>
+    <li><code>%^http://www.youtube.com/embed/%</code> - Allow YouTube videos</li>
+    <li><code>%^http://player.vimeo.com/video/%</code> - Allow Vimeo videos</li>
+    <li><code>%^http://(www.youtube.com/embed/|player.vimeo.com/video/)%</code> - Allow both</li>
+</ul>
+<p>
+    Note that this directive does not give you enough granularity to, say, disable
+    all <code>autoplay</code> videos.  Pipe up on the HTML Purifier forums if this
+    is a capability you want.
+</p>
+--# vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/info.ini b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/info.ini
new file mode 100644
index 0000000..5de4505
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ConfigSchema/schema/info.ini
@@ -0,0 +1,3 @@
+name = "HTML Purifier"
+
+; vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ContentSets.php b/application/libraries/htmlpurifier/HTMLPurifier/ContentSets.php
new file mode 100644
index 0000000..3b6e96f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ContentSets.php
@@ -0,0 +1,155 @@
+<?php
+
+/**
+ * @todo Unit test
+ */
+class HTMLPurifier_ContentSets
+{
+
+    /**
+     * List of content set strings (pipe seperators) indexed by name.
+     */
+    public $info = array();
+
+    /**
+     * List of content set lookups (element => true) indexed by name.
+     * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets
+     */
+    public $lookup = array();
+
+    /**
+     * Synchronized list of defined content sets (keys of info)
+     */
+    protected $keys = array();
+    /**
+     * Synchronized list of defined content values (values of info)
+     */
+    protected $values = array();
+
+    /**
+     * Merges in module's content sets, expands identifiers in the content
+     * sets and populates the keys, values and lookup member variables.
+     * @param $modules List of HTMLPurifier_HTMLModule
+     */
+    public function __construct($modules) {
+        if (!is_array($modules)) $modules = array($modules);
+        // populate content_sets based on module hints
+        // sorry, no way of overloading
+        foreach ($modules as $module_i => $module) {
+            foreach ($module->content_sets as $key => $value) {
+                $temp = $this->convertToLookup($value);
+                if (isset($this->lookup[$key])) {
+                    // add it into the existing content set
+                    $this->lookup[$key] = array_merge($this->lookup[$key], $temp);
+                } else {
+                    $this->lookup[$key] = $temp;
+                }
+            }
+        }
+        $old_lookup = false;
+        while ($old_lookup !== $this->lookup) {
+            $old_lookup = $this->lookup;
+            foreach ($this->lookup as $i => $set) {
+                $add = array();
+                foreach ($set as $element => $x) {
+                    if (isset($this->lookup[$element])) {
+                        $add += $this->lookup[$element];
+                        unset($this->lookup[$i][$element]);
+                    }
+                }
+                $this->lookup[$i] += $add;
+            }
+        }
+
+        foreach ($this->lookup as $key => $lookup) {
+            $this->info[$key] = implode(' | ', array_keys($lookup));
+        }
+        $this->keys   = array_keys($this->info);
+        $this->values = array_values($this->info);
+    }
+
+    /**
+     * Accepts a definition; generates and assigns a ChildDef for it
+     * @param $def HTMLPurifier_ElementDef reference
+     * @param $module Module that defined the ElementDef
+     */
+    public function generateChildDef(&$def, $module) {
+        if (!empty($def->child)) return; // already done!
+        $content_model = $def->content_model;
+        if (is_string($content_model)) {
+            // Assume that $this->keys is alphanumeric
+            $def->content_model = preg_replace_callback(
+                '/\b(' . implode('|', $this->keys) . ')\b/',
+                array($this, 'generateChildDefCallback'),
+                $content_model
+            );
+            //$def->content_model = str_replace(
+            //    $this->keys, $this->values, $content_model);
+        }
+        $def->child = $this->getChildDef($def, $module);
+    }
+
+    public function generateChildDefCallback($matches) {
+        return $this->info[$matches[0]];
+    }
+
+    /**
+     * Instantiates a ChildDef based on content_model and content_model_type
+     * member variables in HTMLPurifier_ElementDef
+     * @note This will also defer to modules for custom HTMLPurifier_ChildDef
+     *       subclasses that need content set expansion
+     * @param $def HTMLPurifier_ElementDef to have ChildDef extracted
+     * @return HTMLPurifier_ChildDef corresponding to ElementDef
+     */
+    public function getChildDef($def, $module) {
+        $value = $def->content_model;
+        if (is_object($value)) {
+            trigger_error(
+                'Literal object child definitions should be stored in '.
+                'ElementDef->child not ElementDef->content_model',
+                E_USER_NOTICE
+            );
+            return $value;
+        }
+        switch ($def->content_model_type) {
+            case 'required':
+                return new HTMLPurifier_ChildDef_Required($value);
+            case 'optional':
+                return new HTMLPurifier_ChildDef_Optional($value);
+            case 'empty':
+                return new HTMLPurifier_ChildDef_Empty();
+            case 'custom':
+                return new HTMLPurifier_ChildDef_Custom($value);
+        }
+        // defer to its module
+        $return = false;
+        if ($module->defines_child_def) { // save a func call
+            $return = $module->getChildDef($def);
+        }
+        if ($return !== false) return $return;
+        // error-out
+        trigger_error(
+            'Could not determine which ChildDef class to instantiate',
+            E_USER_ERROR
+        );
+        return false;
+    }
+
+    /**
+     * Converts a string list of elements separated by pipes into
+     * a lookup array.
+     * @param $string List of elements
+     * @return Lookup array of elements
+     */
+    protected function convertToLookup($string) {
+        $array = explode('|', str_replace(' ', '', $string));
+        $ret = array();
+        foreach ($array as $i => $k) {
+            $ret[$k] = true;
+        }
+        return $ret;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Context.php b/application/libraries/htmlpurifier/HTMLPurifier/Context.php
new file mode 100644
index 0000000..9ddf0c5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Context.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * Registry object that contains information about the current context.
+ * @warning Is a bit buggy when variables are set to null: it thinks
+ *          they don't exist! So use false instead, please.
+ * @note Since the variables Context deals with may not be objects,
+ *       references are very important here! Do not remove!
+ */
+class HTMLPurifier_Context
+{
+
+    /**
+     * Private array that stores the references.
+     */
+    private $_storage = array();
+
+    /**
+     * Registers a variable into the context.
+     * @param $name String name
+     * @param $ref Reference to variable to be registered
+     */
+    public function register($name, &$ref) {
+        if (isset($this->_storage[$name])) {
+            trigger_error("Name $name produces collision, cannot re-register",
+                          E_USER_ERROR);
+            return;
+        }
+        $this->_storage[$name] =& $ref;
+    }
+
+    /**
+     * Retrieves a variable reference from the context.
+     * @param $name String name
+     * @param $ignore_error Boolean whether or not to ignore error
+     */
+    public function &get($name, $ignore_error = false) {
+        if (!isset($this->_storage[$name])) {
+            if (!$ignore_error) {
+                trigger_error("Attempted to retrieve non-existent variable $name",
+                              E_USER_ERROR);
+            }
+            $var = null; // so we can return by reference
+            return $var;
+        }
+        return $this->_storage[$name];
+    }
+
+    /**
+     * Destorys a variable in the context.
+     * @param $name String name
+     */
+    public function destroy($name) {
+        if (!isset($this->_storage[$name])) {
+            trigger_error("Attempted to destroy non-existent variable $name",
+                          E_USER_ERROR);
+            return;
+        }
+        unset($this->_storage[$name]);
+    }
+
+    /**
+     * Checks whether or not the variable exists.
+     * @param $name String name
+     */
+    public function exists($name) {
+        return isset($this->_storage[$name]);
+    }
+
+    /**
+     * Loads a series of variables from an associative array
+     * @param $context_array Assoc array of variables to load
+     */
+    public function loadArray($context_array) {
+        foreach ($context_array as $key => $discard) {
+            $this->register($key, $context_array[$key]);
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Definition.php b/application/libraries/htmlpurifier/HTMLPurifier/Definition.php
new file mode 100644
index 0000000..c7f82eb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Definition.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Super-class for definition datatype objects, implements serialization
+ * functions for the class.
+ */
+abstract class HTMLPurifier_Definition
+{
+
+    /**
+     * Has setup() been called yet?
+     */
+    public $setup = false;
+
+    /**
+     * If true, write out the final definition object to the cache after
+     * setup.  This will be true only if all invocations to get a raw
+     * definition object are also optimized.  This does not cause file
+     * system thrashing because on subsequent calls the cached object
+     * is used and any writes to the raw definition object are short
+     * circuited.  See enduser-customize.html for the high-level
+     * picture.
+     */
+    public $optimized = null;
+
+    /**
+     * What type of definition is it?
+     */
+    public $type;
+
+    /**
+     * Sets up the definition object into the final form, something
+     * not done by the constructor
+     * @param $config HTMLPurifier_Config instance
+     */
+    abstract protected function doSetup($config);
+
+    /**
+     * Setup function that aborts if already setup
+     * @param $config HTMLPurifier_Config instance
+     */
+    public function setup($config) {
+        if ($this->setup) return;
+        $this->setup = true;
+        $this->doSetup($config);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache.php b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache.php
new file mode 100644
index 0000000..c6e1e38
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * Abstract class representing Definition cache managers that implements
+ * useful common methods and is a factory.
+ * @todo Create a separate maintenance file advanced users can use to
+ *       cache their custom HTMLDefinition, which can be loaded
+ *       via a configuration directive
+ * @todo Implement memcached
+ */
+abstract class HTMLPurifier_DefinitionCache
+{
+
+    public $type;
+
+    /**
+     * @param $name Type of definition objects this instance of the
+     *      cache will handle.
+     */
+    public function __construct($type) {
+        $this->type = $type;
+    }
+
+    /**
+     * Generates a unique identifier for a particular configuration
+     * @param Instance of HTMLPurifier_Config
+     */
+    public function generateKey($config) {
+        return $config->version . ',' . // possibly replace with function calls
+               $config->getBatchSerial($this->type) . ',' .
+               $config->get($this->type . '.DefinitionRev');
+    }
+
+    /**
+     * Tests whether or not a key is old with respect to the configuration's
+     * version and revision number.
+     * @param $key Key to test
+     * @param $config Instance of HTMLPurifier_Config to test against
+     */
+    public function isOld($key, $config) {
+        if (substr_count($key, ',') < 2) return true;
+        list($version, $hash, $revision) = explode(',', $key, 3);
+        $compare = version_compare($version, $config->version);
+        // version mismatch, is always old
+        if ($compare != 0) return true;
+        // versions match, ids match, check revision number
+        if (
+            $hash == $config->getBatchSerial($this->type) &&
+            $revision < $config->get($this->type . '.DefinitionRev')
+        ) return true;
+        return false;
+    }
+
+    /**
+     * Checks if a definition's type jives with the cache's type
+     * @note Throws an error on failure
+     * @param $def Definition object to check
+     * @return Boolean true if good, false if not
+     */
+    public function checkDefType($def) {
+        if ($def->type !== $this->type) {
+            trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Adds a definition object to the cache
+     */
+    abstract public function add($def, $config);
+
+    /**
+     * Unconditionally saves a definition object to the cache
+     */
+    abstract public function set($def, $config);
+
+    /**
+     * Replace an object in the cache
+     */
+    abstract public function replace($def, $config);
+
+    /**
+     * Retrieves a definition object from the cache
+     */
+    abstract public function get($config);
+
+    /**
+     * Removes a definition object to the cache
+     */
+    abstract public function remove($config);
+
+    /**
+     * Clears all objects from cache
+     */
+    abstract public function flush($config);
+
+    /**
+     * Clears all expired (older version or revision) objects from cache
+     * @note Be carefuly implementing this method as flush. Flush must
+     *       not interfere with other Definition types, and cleanup()
+     *       should not be repeatedly called by userland code.
+     */
+    abstract public function cleanup($config);
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator.php b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator.php
new file mode 100644
index 0000000..b0fb6d0
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator.php
@@ -0,0 +1,62 @@
+<?php
+
+class HTMLPurifier_DefinitionCache_Decorator extends HTMLPurifier_DefinitionCache
+{
+
+    /**
+     * Cache object we are decorating
+     */
+    public $cache;
+
+    public function __construct() {}
+
+    /**
+     * Lazy decorator function
+     * @param $cache Reference to cache object to decorate
+     */
+    public function decorate(&$cache) {
+        $decorator = $this->copy();
+        // reference is necessary for mocks in PHP 4
+        $decorator->cache =& $cache;
+        $decorator->type  = $cache->type;
+        return $decorator;
+    }
+
+    /**
+     * Cross-compatible clone substitute
+     */
+    public function copy() {
+        return new HTMLPurifier_DefinitionCache_Decorator();
+    }
+
+    public function add($def, $config) {
+        return $this->cache->add($def, $config);
+    }
+
+    public function set($def, $config) {
+        return $this->cache->set($def, $config);
+    }
+
+    public function replace($def, $config) {
+        return $this->cache->replace($def, $config);
+    }
+
+    public function get($config) {
+        return $this->cache->get($config);
+    }
+
+    public function remove($config) {
+        return $this->cache->remove($config);
+    }
+
+    public function flush($config) {
+        return $this->cache->flush($config);
+    }
+
+    public function cleanup($config) {
+        return $this->cache->cleanup($config);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php
new file mode 100644
index 0000000..d4cc35c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Definition cache decorator class that cleans up the cache
+ * whenever there is a cache miss.
+ */
+class HTMLPurifier_DefinitionCache_Decorator_Cleanup extends
+      HTMLPurifier_DefinitionCache_Decorator
+{
+
+    public $name = 'Cleanup';
+
+    public function copy() {
+        return new HTMLPurifier_DefinitionCache_Decorator_Cleanup();
+    }
+
+    public function add($def, $config) {
+        $status = parent::add($def, $config);
+        if (!$status) parent::cleanup($config);
+        return $status;
+    }
+
+    public function set($def, $config) {
+        $status = parent::set($def, $config);
+        if (!$status) parent::cleanup($config);
+        return $status;
+    }
+
+    public function replace($def, $config) {
+        $status = parent::replace($def, $config);
+        if (!$status) parent::cleanup($config);
+        return $status;
+    }
+
+    public function get($config) {
+        $ret = parent::get($config);
+        if (!$ret) parent::cleanup($config);
+        return $ret;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Memory.php b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Memory.php
new file mode 100644
index 0000000..18f16d3
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Memory.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Definition cache decorator class that saves all cache retrievals
+ * to PHP's memory; good for unit tests or circumstances where
+ * there are lots of configuration objects floating around.
+ */
+class HTMLPurifier_DefinitionCache_Decorator_Memory extends
+      HTMLPurifier_DefinitionCache_Decorator
+{
+
+    protected $definitions;
+    public $name = 'Memory';
+
+    public function copy() {
+        return new HTMLPurifier_DefinitionCache_Decorator_Memory();
+    }
+
+    public function add($def, $config) {
+        $status = parent::add($def, $config);
+        if ($status) $this->definitions[$this->generateKey($config)] = $def;
+        return $status;
+    }
+
+    public function set($def, $config) {
+        $status = parent::set($def, $config);
+        if ($status) $this->definitions[$this->generateKey($config)] = $def;
+        return $status;
+    }
+
+    public function replace($def, $config) {
+        $status = parent::replace($def, $config);
+        if ($status) $this->definitions[$this->generateKey($config)] = $def;
+        return $status;
+    }
+
+    public function get($config) {
+        $key = $this->generateKey($config);
+        if (isset($this->definitions[$key])) return $this->definitions[$key];
+        $this->definitions[$key] = parent::get($config);
+        return $this->definitions[$key];
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Template.php.in
new file mode 100644
index 0000000..21a8fcf
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Decorator/Template.php.in
@@ -0,0 +1,47 @@
+<?php
+
+require_once 'HTMLPurifier/DefinitionCache/Decorator.php';
+
+/**
+ * Definition cache decorator template.
+ */
+class HTMLPurifier_DefinitionCache_Decorator_Template extends
+      HTMLPurifier_DefinitionCache_Decorator
+{
+
+    var $name = 'Template'; // replace this
+
+    function copy() {
+        // replace class name with yours
+        return new HTMLPurifier_DefinitionCache_Decorator_Template();
+    }
+
+    // remove methods you don't need
+
+    function add($def, $config) {
+        return parent::add($def, $config);
+    }
+
+    function set($def, $config) {
+        return parent::set($def, $config);
+    }
+
+    function replace($def, $config) {
+        return parent::replace($def, $config);
+    }
+
+    function get($config) {
+        return parent::get($config);
+    }
+
+    function flush() {
+        return parent::flush();
+    }
+
+    function cleanup($config) {
+        return parent::cleanup($config);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Null.php b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Null.php
new file mode 100644
index 0000000..41d97e7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Null.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Null cache object to use when no caching is on.
+ */
+class HTMLPurifier_DefinitionCache_Null extends HTMLPurifier_DefinitionCache
+{
+
+    public function add($def, $config) {
+        return false;
+    }
+
+    public function set($def, $config) {
+        return false;
+    }
+
+    public function replace($def, $config) {
+        return false;
+    }
+
+    public function remove($config) {
+        return false;
+    }
+
+    public function get($config) {
+        return false;
+    }
+
+    public function flush($config) {
+        return false;
+    }
+
+    public function cleanup($config) {
+        return false;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer.php b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer.php
new file mode 100644
index 0000000..73d5e90
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer.php
@@ -0,0 +1,191 @@
+<?php
+
+class HTMLPurifier_DefinitionCache_Serializer extends
+      HTMLPurifier_DefinitionCache
+{
+
+    public function add($def, $config) {
+        if (!$this->checkDefType($def)) return;
+        $file = $this->generateFilePath($config);
+        if (file_exists($file)) return false;
+        if (!$this->_prepareDir($config)) return false;
+        return $this->_write($file, serialize($def), $config);
+    }
+
+    public function set($def, $config) {
+        if (!$this->checkDefType($def)) return;
+        $file = $this->generateFilePath($config);
+        if (!$this->_prepareDir($config)) return false;
+        return $this->_write($file, serialize($def), $config);
+    }
+
+    public function replace($def, $config) {
+        if (!$this->checkDefType($def)) return;
+        $file = $this->generateFilePath($config);
+        if (!file_exists($file)) return false;
+        if (!$this->_prepareDir($config)) return false;
+        return $this->_write($file, serialize($def), $config);
+    }
+
+    public function get($config) {
+        $file = $this->generateFilePath($config);
+        if (!file_exists($file)) return false;
+        return unserialize(file_get_contents($file));
+    }
+
+    public function remove($config) {
+        $file = $this->generateFilePath($config);
+        if (!file_exists($file)) return false;
+        return unlink($file);
+    }
+
+    public function flush($config) {
+        if (!$this->_prepareDir($config)) return false;
+        $dir = $this->generateDirectoryPath($config);
+        $dh  = opendir($dir);
+        while (false !== ($filename = readdir($dh))) {
+            if (empty($filename)) continue;
+            if ($filename[0] === '.') continue;
+            unlink($dir . '/' . $filename);
+        }
+    }
+
+    public function cleanup($config) {
+        if (!$this->_prepareDir($config)) return false;
+        $dir = $this->generateDirectoryPath($config);
+        $dh  = opendir($dir);
+        while (false !== ($filename = readdir($dh))) {
+            if (empty($filename)) continue;
+            if ($filename[0] === '.') continue;
+            $key = substr($filename, 0, strlen($filename) - 4);
+            if ($this->isOld($key, $config)) unlink($dir . '/' . $filename);
+        }
+    }
+
+    /**
+     * Generates the file path to the serial file corresponding to
+     * the configuration and definition name
+     * @todo Make protected
+     */
+    public function generateFilePath($config) {
+        $key = $this->generateKey($config);
+        return $this->generateDirectoryPath($config) . '/' . $key . '.ser';
+    }
+
+    /**
+     * Generates the path to the directory contain this cache's serial files
+     * @note No trailing slash
+     * @todo Make protected
+     */
+    public function generateDirectoryPath($config) {
+        $base = $this->generateBaseDirectoryPath($config);
+        return $base . '/' . $this->type;
+    }
+
+    /**
+     * Generates path to base directory that contains all definition type
+     * serials
+     * @todo Make protected
+     */
+    public function generateBaseDirectoryPath($config) {
+        $base = $config->get('Cache.SerializerPath');
+        $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base;
+        return $base;
+    }
+
+    /**
+     * Convenience wrapper function for file_put_contents
+     * @param $file File name to write to
+     * @param $data Data to write into file
+     * @param $config Config object
+     * @return Number of bytes written if success, or false if failure.
+     */
+    private function _write($file, $data, $config) {
+        $result = file_put_contents($file, $data);
+        if ($result !== false) {
+            // set permissions of the new file (no execute)
+            $chmod = $config->get('Cache.SerializerPermissions');
+            if (!$chmod) {
+                $chmod = 0644; // invalid config or simpletest
+            }
+            $chmod = $chmod & 0666;
+            chmod($file, $chmod);
+        }
+        return $result;
+    }
+
+    /**
+     * Prepares the directory that this type stores the serials in
+     * @param $config Config object
+     * @return True if successful
+     */
+    private function _prepareDir($config) {
+        $directory = $this->generateDirectoryPath($config);
+        $chmod = $config->get('Cache.SerializerPermissions');
+        if (!$chmod) {
+            $chmod = 0755; // invalid config or simpletest
+        }
+        if (!is_dir($directory)) {
+            $base = $this->generateBaseDirectoryPath($config);
+            if (!is_dir($base)) {
+                trigger_error('Base directory '.$base.' does not exist,
+                    please create or change using %Cache.SerializerPath',
+                    E_USER_WARNING);
+                return false;
+            } elseif (!$this->_testPermissions($base, $chmod)) {
+                return false;
+            }
+            $old = umask(0000);
+            mkdir($directory, $chmod);
+            umask($old);
+        } elseif (!$this->_testPermissions($directory, $chmod)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Tests permissions on a directory and throws out friendly
+     * error messages and attempts to chmod it itself if possible
+     * @param $dir Directory path
+     * @param $chmod Permissions
+     * @return True if directory writable
+     */
+    private function _testPermissions($dir, $chmod) {
+        // early abort, if it is writable, everything is hunky-dory
+        if (is_writable($dir)) return true;
+        if (!is_dir($dir)) {
+            // generally, you'll want to handle this beforehand
+            // so a more specific error message can be given
+            trigger_error('Directory '.$dir.' does not exist',
+                E_USER_WARNING);
+            return false;
+        }
+        if (function_exists('posix_getuid')) {
+            // POSIX system, we can give more specific advice
+            if (fileowner($dir) === posix_getuid()) {
+                // we can chmod it ourselves
+                $chmod = $chmod | 0700;
+                if (chmod($dir, $chmod)) return true;
+            } elseif (filegroup($dir) === posix_getgid()) {
+                $chmod = $chmod | 0070;
+            } else {
+                // PHP's probably running as nobody, so we'll
+                // need to give global permissions
+                $chmod = $chmod | 0777;
+            }
+            trigger_error('Directory '.$dir.' not writable, '.
+                'please chmod to ' . decoct($chmod),
+                E_USER_WARNING);
+        } else {
+            // generic error message
+            trigger_error('Directory '.$dir.' not writable, '.
+                'please alter file permissions',
+                E_USER_WARNING);
+        }
+        return false;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,13c3933dafea215dd3045ef97f3cf8230304e6ae,1.ser b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,13c3933dafea215dd3045ef97f3cf8230304e6ae,1.ser
new file mode 100644
index 0000000..7f667a8
Binary files /dev/null and b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,13c3933dafea215dd3045ef97f3cf8230304e6ae,1.ser differ
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,48c66b36f832d31ddeb0bd1df231a8b404e9ce91,1.ser b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,48c66b36f832d31ddeb0bd1df231a8b404e9ce91,1.ser
new file mode 100644
index 0000000..7913d6c
Binary files /dev/null and b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,48c66b36f832d31ddeb0bd1df231a8b404e9ce91,1.ser differ
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,bd08c5afbc77123dbd4e9e026a723c450e9f844b,1.ser b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,bd08c5afbc77123dbd4e9e026a723c450e9f844b,1.ser
new file mode 100644
index 0000000..2b741ba
Binary files /dev/null and b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,bd08c5afbc77123dbd4e9e026a723c450e9f844b,1.ser differ
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,c025fd58185e35b4d1117abfd9869ab4087f03ce,1.ser b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,c025fd58185e35b4d1117abfd9869ab4087f03ce,1.ser
new file mode 100644
index 0000000..057ef32
Binary files /dev/null and b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/HTML/4.5.0,c025fd58185e35b4d1117abfd9869ab4087f03ce,1.ser differ
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/README b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/README
new file mode 100755
index 0000000..2e35c1c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/README
@@ -0,0 +1,3 @@
+This is a dummy file to prevent Git from ignoring this empty directory.
+
+    vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/URI/4.5.0,10a7f1a4d1fdb0b461bc5b6e5a9c2f9a4a0ec765,1.ser b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/URI/4.5.0,10a7f1a4d1fdb0b461bc5b6e5a9c2f9a4a0ec765,1.ser
new file mode 100644
index 0000000..5b8a574
Binary files /dev/null and b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/URI/4.5.0,10a7f1a4d1fdb0b461bc5b6e5a9c2f9a4a0ec765,1.ser differ
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/URI/4.5.0,8d03c8ec0e84e7feb92afd4c0f1735841b5fdacf,1.ser b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/URI/4.5.0,8d03c8ec0e84e7feb92afd4c0f1735841b5fdacf,1.ser
new file mode 100644
index 0000000..f6d3e81
Binary files /dev/null and b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCache/Serializer/URI/4.5.0,8d03c8ec0e84e7feb92afd4c0f1735841b5fdacf,1.ser differ
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCacheFactory.php b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCacheFactory.php
new file mode 100644
index 0000000..a6ead62
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DefinitionCacheFactory.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * Responsible for creating definition caches.
+ */
+class HTMLPurifier_DefinitionCacheFactory
+{
+
+    protected $caches = array('Serializer' => array());
+    protected $implementations = array();
+    protected $decorators = array();
+
+    /**
+     * Initialize default decorators
+     */
+    public function setup() {
+        $this->addDecorator('Cleanup');
+    }
+
+    /**
+     * Retrieves an instance of global definition cache factory.
+     */
+    public static function instance($prototype = null) {
+        static $instance;
+        if ($prototype !== null) {
+            $instance = $prototype;
+        } elseif ($instance === null || $prototype === true) {
+            $instance = new HTMLPurifier_DefinitionCacheFactory();
+            $instance->setup();
+        }
+        return $instance;
+    }
+
+    /**
+     * Registers a new definition cache object
+     * @param $short Short name of cache object, for reference
+     * @param $long Full class name of cache object, for construction
+     */
+    public function register($short, $long) {
+        $this->implementations[$short] = $long;
+    }
+
+    /**
+     * Factory method that creates a cache object based on configuration
+     * @param $name Name of definitions handled by cache
+     * @param $config Instance of HTMLPurifier_Config
+     */
+    public function create($type, $config) {
+        $method = $config->get('Cache.DefinitionImpl');
+        if ($method === null) {
+            return new HTMLPurifier_DefinitionCache_Null($type);
+        }
+        if (!empty($this->caches[$method][$type])) {
+            return $this->caches[$method][$type];
+        }
+        if (
+          isset($this->implementations[$method]) &&
+          class_exists($class = $this->implementations[$method], false)
+        ) {
+            $cache = new $class($type);
+        } else {
+            if ($method != 'Serializer') {
+                trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING);
+            }
+            $cache = new HTMLPurifier_DefinitionCache_Serializer($type);
+        }
+        foreach ($this->decorators as $decorator) {
+            $new_cache = $decorator->decorate($cache);
+            // prevent infinite recursion in PHP 4
+            unset($cache);
+            $cache = $new_cache;
+        }
+        $this->caches[$method][$type] = $cache;
+        return $this->caches[$method][$type];
+    }
+
+    /**
+     * Registers a decorator to add to all new cache objects
+     * @param
+     */
+    public function addDecorator($decorator) {
+        if (is_string($decorator)) {
+            $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator";
+            $decorator = new $class;
+        }
+        $this->decorators[$decorator->name] = $decorator;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Doctype.php b/application/libraries/htmlpurifier/HTMLPurifier/Doctype.php
new file mode 100644
index 0000000..1e3c574
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Doctype.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * Represents a document type, contains information on which modules
+ * need to be loaded.
+ * @note This class is inspected by Printer_HTMLDefinition->renderDoctype.
+ *       If structure changes, please update that function.
+ */
+class HTMLPurifier_Doctype
+{
+    /**
+     * Full name of doctype
+     */
+    public $name;
+
+    /**
+     * List of standard modules (string identifiers or literal objects)
+     * that this doctype uses
+     */
+    public $modules = array();
+
+    /**
+     * List of modules to use for tidying up code
+     */
+    public $tidyModules = array();
+
+    /**
+     * Is the language derived from XML (i.e. XHTML)?
+     */
+    public $xml = true;
+
+    /**
+     * List of aliases for this doctype
+     */
+    public $aliases = array();
+
+    /**
+     * Public DTD identifier
+     */
+    public $dtdPublic;
+
+    /**
+     * System DTD identifier
+     */
+    public $dtdSystem;
+
+    public function __construct($name = null, $xml = true, $modules = array(),
+        $tidyModules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null
+    ) {
+        $this->name         = $name;
+        $this->xml          = $xml;
+        $this->modules      = $modules;
+        $this->tidyModules  = $tidyModules;
+        $this->aliases      = $aliases;
+        $this->dtdPublic    = $dtd_public;
+        $this->dtdSystem    = $dtd_system;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/DoctypeRegistry.php b/application/libraries/htmlpurifier/HTMLPurifier/DoctypeRegistry.php
new file mode 100644
index 0000000..86049e9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/DoctypeRegistry.php
@@ -0,0 +1,103 @@
+<?php
+
+class HTMLPurifier_DoctypeRegistry
+{
+
+    /**
+     * Hash of doctype names to doctype objects
+     */
+    protected $doctypes;
+
+    /**
+     * Lookup table of aliases to real doctype names
+     */
+    protected $aliases;
+
+    /**
+     * Registers a doctype to the registry
+     * @note Accepts a fully-formed doctype object, or the
+     *       parameters for constructing a doctype object
+     * @param $doctype Name of doctype or literal doctype object
+     * @param $modules Modules doctype will load
+     * @param $modules_for_modes Modules doctype will load for certain modes
+     * @param $aliases Alias names for doctype
+     * @return Editable registered doctype
+     */
+    public function register($doctype, $xml = true, $modules = array(),
+        $tidy_modules = array(), $aliases = array(), $dtd_public = null, $dtd_system = null
+    ) {
+        if (!is_array($modules)) $modules = array($modules);
+        if (!is_array($tidy_modules)) $tidy_modules = array($tidy_modules);
+        if (!is_array($aliases)) $aliases = array($aliases);
+        if (!is_object($doctype)) {
+            $doctype = new HTMLPurifier_Doctype(
+                $doctype, $xml, $modules, $tidy_modules, $aliases, $dtd_public, $dtd_system
+            );
+        }
+        $this->doctypes[$doctype->name] = $doctype;
+        $name = $doctype->name;
+        // hookup aliases
+        foreach ($doctype->aliases as $alias) {
+            if (isset($this->doctypes[$alias])) continue;
+            $this->aliases[$alias] = $name;
+        }
+        // remove old aliases
+        if (isset($this->aliases[$name])) unset($this->aliases[$name]);
+        return $doctype;
+    }
+
+    /**
+     * Retrieves reference to a doctype of a certain name
+     * @note This function resolves aliases
+     * @note When possible, use the more fully-featured make()
+     * @param $doctype Name of doctype
+     * @return Editable doctype object
+     */
+    public function get($doctype) {
+        if (isset($this->aliases[$doctype])) $doctype = $this->aliases[$doctype];
+        if (!isset($this->doctypes[$doctype])) {
+            trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR);
+            $anon = new HTMLPurifier_Doctype($doctype);
+            return $anon;
+        }
+        return $this->doctypes[$doctype];
+    }
+
+    /**
+     * Creates a doctype based on a configuration object,
+     * will perform initialization on the doctype
+     * @note Use this function to get a copy of doctype that config
+     *       can hold on to (this is necessary in order to tell
+     *       Generator whether or not the current document is XML
+     *       based or not).
+     */
+    public function make($config) {
+        return clone $this->get($this->getDoctypeFromConfig($config));
+    }
+
+    /**
+     * Retrieves the doctype from the configuration object
+     */
+    public function getDoctypeFromConfig($config) {
+        // recommended test
+        $doctype = $config->get('HTML.Doctype');
+        if (!empty($doctype)) return $doctype;
+        $doctype = $config->get('HTML.CustomDoctype');
+        if (!empty($doctype)) return $doctype;
+        // backwards-compatibility
+        if ($config->get('HTML.XHTML')) {
+            $doctype = 'XHTML 1.0';
+        } else {
+            $doctype = 'HTML 4.01';
+        }
+        if ($config->get('HTML.Strict')) {
+            $doctype .= ' Strict';
+        } else {
+            $doctype .= ' Transitional';
+        }
+        return $doctype;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ElementDef.php b/application/libraries/htmlpurifier/HTMLPurifier/ElementDef.php
new file mode 100644
index 0000000..10f7ab7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ElementDef.php
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * Structure that stores an HTML element definition. Used by
+ * HTMLPurifier_HTMLDefinition and HTMLPurifier_HTMLModule.
+ * @note This class is inspected by HTMLPurifier_Printer_HTMLDefinition.
+ *       Please update that class too.
+ * @warning If you add new properties to this class, you MUST update
+ *          the mergeIn() method.
+ */
+class HTMLPurifier_ElementDef
+{
+
+    /**
+     * Does the definition work by itself, or is it created solely
+     * for the purpose of merging into another definition?
+     */
+    public $standalone = true;
+
+    /**
+     * Associative array of attribute name to HTMLPurifier_AttrDef
+     * @note Before being processed by HTMLPurifier_AttrCollections
+     *       when modules are finalized during
+     *       HTMLPurifier_HTMLDefinition->setup(), this array may also
+     *       contain an array at index 0 that indicates which attribute
+     *       collections to load into the full array. It may also
+     *       contain string indentifiers in lieu of HTMLPurifier_AttrDef,
+     *       see HTMLPurifier_AttrTypes on how they are expanded during
+     *       HTMLPurifier_HTMLDefinition->setup() processing.
+     */
+    public $attr = array();
+
+    // XXX: Design note: currently, it's not possible to override
+    // previously defined AttrTransforms without messing around with
+    // the final generated config. This is by design; a previous version
+    // used an associated list of attr_transform, but it was extremely
+    // easy to accidentally override other attribute transforms by
+    // forgetting to specify an index (and just using 0.)  While we
+    // could check this by checking the index number and complaining,
+    // there is a second problem which is that it is not at all easy to
+    // tell when something is getting overridden. Combine this with a
+    // codebase where this isn't really being used, and it's perfect for
+    // nuking.
+
+    /**
+     * List of tags HTMLPurifier_AttrTransform to be done before validation
+     */
+    public $attr_transform_pre = array();
+
+    /**
+     * List of tags HTMLPurifier_AttrTransform to be done after validation
+     */
+    public $attr_transform_post = array();
+
+    /**
+     * HTMLPurifier_ChildDef of this tag.
+     */
+    public $child;
+
+    /**
+     * Abstract string representation of internal ChildDef rules. See
+     * HTMLPurifier_ContentSets for how this is parsed and then transformed
+     * into an HTMLPurifier_ChildDef.
+     * @warning This is a temporary variable that is not available after
+     *      being processed by HTMLDefinition
+     */
+    public $content_model;
+
+    /**
+     * Value of $child->type, used to determine which ChildDef to use,
+     * used in combination with $content_model.
+     * @warning This must be lowercase
+     * @warning This is a temporary variable that is not available after
+     *      being processed by HTMLDefinition
+     */
+    public $content_model_type;
+
+
+
+    /**
+     * Does the element have a content model (#PCDATA | Inline)*? This
+     * is important for chameleon ins and del processing in
+     * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't
+     * have to worry about this one.
+     */
+    public $descendants_are_inline = false;
+
+    /**
+     * List of the names of required attributes this element has. Dynamically
+     * populated by HTMLPurifier_HTMLDefinition::getElement
+     */
+    public $required_attr = array();
+
+    /**
+     * Lookup table of tags excluded from all descendants of this tag.
+     * @note SGML permits exclusions for all descendants, but this is
+     *       not possible with DTDs or XML Schemas. W3C has elected to
+     *       use complicated compositions of content_models to simulate
+     *       exclusion for children, but we go the simpler, SGML-style
+     *       route of flat-out exclusions, which correctly apply to
+     *       all descendants and not just children. Note that the XHTML
+     *       Modularization Abstract Modules are blithely unaware of such
+     *       distinctions.
+     */
+    public $excludes = array();
+
+    /**
+     * This tag is explicitly auto-closed by the following tags.
+     */
+    public $autoclose = array();
+
+    /**
+     * If a foreign element is found in this element, test if it is
+     * allowed by this sub-element; if it is, instead of closing the
+     * current element, place it inside this element.
+     */
+    public $wrap;
+
+    /**
+     * Whether or not this is a formatting element affected by the
+     * "Active Formatting Elements" algorithm.
+     */
+    public $formatting;
+
+    /**
+     * Low-level factory constructor for creating new standalone element defs
+     */
+    public static function create($content_model, $content_model_type, $attr) {
+        $def = new HTMLPurifier_ElementDef();
+        $def->content_model = $content_model;
+        $def->content_model_type = $content_model_type;
+        $def->attr = $attr;
+        return $def;
+    }
+
+    /**
+     * Merges the values of another element definition into this one.
+     * Values from the new element def take precedence if a value is
+     * not mergeable.
+     */
+    public function mergeIn($def) {
+
+        // later keys takes precedence
+        foreach($def->attr as $k => $v) {
+            if ($k === 0) {
+                // merge in the includes
+                // sorry, no way to override an include
+                foreach ($v as $v2) {
+                    $this->attr[0][] = $v2;
+                }
+                continue;
+            }
+            if ($v === false) {
+                if (isset($this->attr[$k])) unset($this->attr[$k]);
+                continue;
+            }
+            $this->attr[$k] = $v;
+        }
+        $this->_mergeAssocArray($this->excludes, $def->excludes);
+        $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre);
+        $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post);
+
+        if(!empty($def->content_model)) {
+            $this->content_model =
+                str_replace("#SUPER", $this->content_model, $def->content_model);
+            $this->child = false;
+        }
+        if(!empty($def->content_model_type)) {
+            $this->content_model_type = $def->content_model_type;
+            $this->child = false;
+        }
+        if(!is_null($def->child)) $this->child = $def->child;
+        if(!is_null($def->formatting)) $this->formatting = $def->formatting;
+        if($def->descendants_are_inline) $this->descendants_are_inline = $def->descendants_are_inline;
+
+    }
+
+    /**
+     * Merges one array into another, removes values which equal false
+     * @param $a1 Array by reference that is merged into
+     * @param $a2 Array that merges into $a1
+     */
+    private function _mergeAssocArray(&$a1, $a2) {
+        foreach ($a2 as $k => $v) {
+            if ($v === false) {
+                if (isset($a1[$k])) unset($a1[$k]);
+                continue;
+            }
+            $a1[$k] = $v;
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Encoder.php b/application/libraries/htmlpurifier/HTMLPurifier/Encoder.php
new file mode 100644
index 0000000..77988a1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Encoder.php
@@ -0,0 +1,545 @@
+<?php
+
+/**
+ * A UTF-8 specific character encoder that handles cleaning and transforming.
+ * @note All functions in this class should be static.
+ */
+class HTMLPurifier_Encoder
+{
+
+    /**
+     * Constructor throws fatal error if you attempt to instantiate class
+     */
+    private function __construct() {
+        trigger_error('Cannot instantiate encoder, call methods statically', E_USER_ERROR);
+    }
+
+    /**
+     * Error-handler that mutes errors, alternative to shut-up operator.
+     */
+    public static function muteErrorHandler() {}
+
+    /**
+     * iconv wrapper which mutes errors, but doesn't work around bugs.
+     */
+    public static function unsafeIconv($in, $out, $text) {
+        set_error_handler(array('HTMLPurifier_Encoder', 'muteErrorHandler'));
+        $r = iconv($in, $out, $text);
+        restore_error_handler();
+        return $r;
+    }
+
+    /**
+     * iconv wrapper which mutes errors and works around bugs.
+     */
+    public static function iconv($in, $out, $text, $max_chunk_size = 8000) {
+        $code = self::testIconvTruncateBug();
+        if ($code == self::ICONV_OK) {
+            return self::unsafeIconv($in, $out, $text);
+        } elseif ($code == self::ICONV_TRUNCATES) {
+            // we can only work around this if the input character set
+            // is utf-8
+            if ($in == 'utf-8') {
+                if ($max_chunk_size < 4) {
+                    trigger_error('max_chunk_size is too small', E_USER_WARNING);
+                    return false;
+                }
+                // split into 8000 byte chunks, but be careful to handle
+                // multibyte boundaries properly
+                if (($c = strlen($text)) <= $max_chunk_size) {
+                    return self::unsafeIconv($in, $out, $text);
+                }
+                $r = '';
+                $i = 0;
+                while (true) {
+                    if ($i + $max_chunk_size >= $c) {
+                        $r .= self::unsafeIconv($in, $out, substr($text, $i));
+                        break;
+                    }
+                    // wibble the boundary
+                    if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) {
+                        $chunk_size = $max_chunk_size;
+                    } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) {
+                        $chunk_size = $max_chunk_size - 1;
+                    } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) {
+                        $chunk_size = $max_chunk_size - 2;
+                    } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) {
+                        $chunk_size = $max_chunk_size - 3;
+                    } else {
+                        return false; // rather confusing UTF-8...
+                    }
+                    $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths
+                    $r .= self::unsafeIconv($in, $out, $chunk);
+                    $i += $chunk_size;
+                }
+                return $r;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Cleans a UTF-8 string for well-formedness and SGML validity
+     *
+     * It will parse according to UTF-8 and return a valid UTF8 string, with
+     * non-SGML codepoints excluded.
+     *
+     * @note Just for reference, the non-SGML code points are 0 to 31 and
+     *       127 to 159, inclusive.  However, we allow code points 9, 10
+     *       and 13, which are the tab, line feed and carriage return
+     *       respectively. 128 and above the code points map to multibyte
+     *       UTF-8 representations.
+     *
+     * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and
+     *       hsivonen at iki.fi at <http://iki.fi/hsivonen/php-utf8/> under the
+     *       LGPL license.  Notes on what changed are inside, but in general,
+     *       the original code transformed UTF-8 text into an array of integer
+     *       Unicode codepoints. Understandably, transforming that back to
+     *       a string would be somewhat expensive, so the function was modded to
+     *       directly operate on the string.  However, this discourages code
+     *       reuse, and the logic enumerated here would be useful for any
+     *       function that needs to be able to understand UTF-8 characters.
+     *       As of right now, only smart lossless character encoding converters
+     *       would need that, and I'm probably not going to implement them.
+     *       Once again, PHP 6 should solve all our problems.
+     */
+    public static function cleanUTF8($str, $force_php = false) {
+
+        // UTF-8 validity is checked since PHP 4.3.5
+        // This is an optimization: if the string is already valid UTF-8, no
+        // need to do PHP stuff. 99% of the time, this will be the case.
+        // The regexp matches the XML char production, as well as well as excluding
+        // non-SGML codepoints U+007F to U+009F
+        if (preg_match('/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', $str)) {
+            return $str;
+        }
+
+        $mState = 0; // cached expected number of octets after the current octet
+                     // until the beginning of the next UTF8 character sequence
+        $mUcs4  = 0; // cached Unicode character
+        $mBytes = 1; // cached expected number of octets in the current sequence
+
+        // original code involved an $out that was an array of Unicode
+        // codepoints.  Instead of having to convert back into UTF-8, we've
+        // decided to directly append valid UTF-8 characters onto a string
+        // $out once they're done.  $char accumulates raw bytes, while $mUcs4
+        // turns into the Unicode code point, so there's some redundancy.
+
+        $out = '';
+        $char = '';
+
+        $len = strlen($str);
+        for($i = 0; $i < $len; $i++) {
+            $in = ord($str{$i});
+            $char .= $str[$i]; // append byte to char
+            if (0 == $mState) {
+                // When mState is zero we expect either a US-ASCII character
+                // or a multi-octet sequence.
+                if (0 == (0x80 & ($in))) {
+                    // US-ASCII, pass straight through.
+                    if (($in <= 31 || $in == 127) &&
+                        !($in == 9 || $in == 13 || $in == 10) // save \r\t\n
+                    ) {
+                        // control characters, remove
+                    } else {
+                        $out .= $char;
+                    }
+                    // reset
+                    $char = '';
+                    $mBytes = 1;
+                } elseif (0xC0 == (0xE0 & ($in))) {
+                    // First octet of 2 octet sequence
+                    $mUcs4 = ($in);
+                    $mUcs4 = ($mUcs4 & 0x1F) << 6;
+                    $mState = 1;
+                    $mBytes = 2;
+                } elseif (0xE0 == (0xF0 & ($in))) {
+                    // First octet of 3 octet sequence
+                    $mUcs4 = ($in);
+                    $mUcs4 = ($mUcs4 & 0x0F) << 12;
+                    $mState = 2;
+                    $mBytes = 3;
+                } elseif (0xF0 == (0xF8 & ($in))) {
+                    // First octet of 4 octet sequence
+                    $mUcs4 = ($in);
+                    $mUcs4 = ($mUcs4 & 0x07) << 18;
+                    $mState = 3;
+                    $mBytes = 4;
+                } elseif (0xF8 == (0xFC & ($in))) {
+                    // First octet of 5 octet sequence.
+                    //
+                    // This is illegal because the encoded codepoint must be
+                    // either:
+                    // (a) not the shortest form or
+                    // (b) outside the Unicode range of 0-0x10FFFF.
+                    // Rather than trying to resynchronize, we will carry on
+                    // until the end of the sequence and let the later error
+                    // handling code catch it.
+                    $mUcs4 = ($in);
+                    $mUcs4 = ($mUcs4 & 0x03) << 24;
+                    $mState = 4;
+                    $mBytes = 5;
+                } elseif (0xFC == (0xFE & ($in))) {
+                    // First octet of 6 octet sequence, see comments for 5
+                    // octet sequence.
+                    $mUcs4 = ($in);
+                    $mUcs4 = ($mUcs4 & 1) << 30;
+                    $mState = 5;
+                    $mBytes = 6;
+                } else {
+                    // Current octet is neither in the US-ASCII range nor a
+                    // legal first octet of a multi-octet sequence.
+                    $mState = 0;
+                    $mUcs4  = 0;
+                    $mBytes = 1;
+                    $char = '';
+                }
+            } else {
+                // When mState is non-zero, we expect a continuation of the
+                // multi-octet sequence
+                if (0x80 == (0xC0 & ($in))) {
+                    // Legal continuation.
+                    $shift = ($mState - 1) * 6;
+                    $tmp = $in;
+                    $tmp = ($tmp & 0x0000003F) << $shift;
+                    $mUcs4 |= $tmp;
+
+                    if (0 == --$mState) {
+                        // End of the multi-octet sequence. mUcs4 now contains
+                        // the final Unicode codepoint to be output
+
+                        // Check for illegal sequences and codepoints.
+
+                        // From Unicode 3.1, non-shortest form is illegal
+                        if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
+                            ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
+                            ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
+                            (4 < $mBytes) ||
+                            // From Unicode 3.2, surrogate characters = illegal
+                            (($mUcs4 & 0xFFFFF800) == 0xD800) ||
+                            // Codepoints outside the Unicode range are illegal
+                            ($mUcs4 > 0x10FFFF)
+                        ) {
+
+                        } elseif (0xFEFF != $mUcs4 && // omit BOM
+                            // check for valid Char unicode codepoints
+                            (
+                                0x9 == $mUcs4 ||
+                                0xA == $mUcs4 ||
+                                0xD == $mUcs4 ||
+                                (0x20 <= $mUcs4 && 0x7E >= $mUcs4) ||
+                                // 7F-9F is not strictly prohibited by XML,
+                                // but it is non-SGML, and thus we don't allow it
+                                (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) ||
+                                (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4)
+                            )
+                        ) {
+                            $out .= $char;
+                        }
+                        // initialize UTF8 cache (reset)
+                        $mState = 0;
+                        $mUcs4  = 0;
+                        $mBytes = 1;
+                        $char = '';
+                    }
+                } else {
+                    // ((0xC0 & (*in) != 0x80) && (mState != 0))
+                    // Incomplete multi-octet sequence.
+                    // used to result in complete fail, but we'll reset
+                    $mState = 0;
+                    $mUcs4  = 0;
+                    $mBytes = 1;
+                    $char ='';
+                }
+            }
+        }
+        return $out;
+    }
+
+    /**
+     * Translates a Unicode codepoint into its corresponding UTF-8 character.
+     * @note Based on Feyd's function at
+     *       <http://forums.devnetwork.net/viewtopic.php?p=191404#191404>,
+     *       which is in public domain.
+     * @note While we're going to do code point parsing anyway, a good
+     *       optimization would be to refuse to translate code points that
+     *       are non-SGML characters.  However, this could lead to duplication.
+     * @note This is very similar to the unichr function in
+     *       maintenance/generate-entity-file.php (although this is superior,
+     *       due to its sanity checks).
+     */
+
+    // +----------+----------+----------+----------+
+    // | 33222222 | 22221111 | 111111   |          |
+    // | 10987654 | 32109876 | 54321098 | 76543210 | bit
+    // +----------+----------+----------+----------+
+    // |          |          |          | 0xxxxxxx | 1 byte 0x00000000..0x0000007F
+    // |          |          | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF
+    // |          | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF
+    // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF
+    // +----------+----------+----------+----------+
+    // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF)
+    // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes
+    // +----------+----------+----------+----------+
+
+    public static function unichr($code) {
+        if($code > 1114111 or $code < 0 or
+          ($code >= 55296 and $code <= 57343) ) {
+            // bits are set outside the "valid" range as defined
+            // by UNICODE 4.1.0
+            return '';
+        }
+
+        $x = $y = $z = $w = 0;
+        if ($code < 128) {
+            // regular ASCII character
+            $x = $code;
+        } else {
+            // set up bits for UTF-8
+            $x = ($code & 63) | 128;
+            if ($code < 2048) {
+                $y = (($code & 2047) >> 6) | 192;
+            } else {
+                $y = (($code & 4032) >> 6) | 128;
+                if($code < 65536) {
+                    $z = (($code >> 12) & 15) | 224;
+                } else {
+                    $z = (($code >> 12) & 63) | 128;
+                    $w = (($code >> 18) & 7)  | 240;
+                }
+            }
+        }
+        // set up the actual character
+        $ret = '';
+        if($w) $ret .= chr($w);
+        if($z) $ret .= chr($z);
+        if($y) $ret .= chr($y);
+        $ret .= chr($x);
+
+        return $ret;
+    }
+
+    public static function iconvAvailable() {
+        static $iconv = null;
+        if ($iconv === null) {
+            $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE;
+        }
+        return $iconv;
+    }
+
+    /**
+     * Converts a string to UTF-8 based on configuration.
+     */
+    public static function convertToUTF8($str, $config, $context) {
+        $encoding = $config->get('Core.Encoding');
+        if ($encoding === 'utf-8') return $str;
+        static $iconv = null;
+        if ($iconv === null) $iconv = self::iconvAvailable();
+        if ($iconv && !$config->get('Test.ForceNoIconv')) {
+            // unaffected by bugs, since UTF-8 support all characters
+            $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str);
+            if ($str === false) {
+                // $encoding is not a valid encoding
+                trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR);
+                return '';
+            }
+            // If the string is bjorked by Shift_JIS or a similar encoding
+            // that doesn't support all of ASCII, convert the naughty
+            // characters to their true byte-wise ASCII/UTF-8 equivalents.
+            $str = strtr($str, self::testEncodingSupportsASCII($encoding));
+            return $str;
+        } elseif ($encoding === 'iso-8859-1') {
+            $str = utf8_encode($str);
+            return $str;
+        }
+        $bug = HTMLPurifier_Encoder::testIconvTruncateBug();
+        if ($bug == self::ICONV_OK) {
+            trigger_error('Encoding not supported, please install iconv', E_USER_ERROR);
+        } else {
+            trigger_error('You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 and http://sourceware.org/bugzilla/show_bug.cgi?id=13541', E_USER_ERROR);
+        }
+    }
+
+    /**
+     * Converts a string from UTF-8 based on configuration.
+     * @note Currently, this is a lossy conversion, with unexpressable
+     *       characters being omitted.
+     */
+    public static function convertFromUTF8($str, $config, $context) {
+        $encoding = $config->get('Core.Encoding');
+        if ($escape = $config->get('Core.EscapeNonASCIICharacters')) {
+            $str = self::convertToASCIIDumbLossless($str);
+        }
+        if ($encoding === 'utf-8') return $str;
+        static $iconv = null;
+        if ($iconv === null) $iconv = self::iconvAvailable();
+        if ($iconv && !$config->get('Test.ForceNoIconv')) {
+            // Undo our previous fix in convertToUTF8, otherwise iconv will barf
+            $ascii_fix = self::testEncodingSupportsASCII($encoding);
+            if (!$escape && !empty($ascii_fix)) {
+                $clear_fix = array();
+                foreach ($ascii_fix as $utf8 => $native) $clear_fix[$utf8] = '';
+                $str = strtr($str, $clear_fix);
+            }
+            $str = strtr($str, array_flip($ascii_fix));
+            // Normal stuff
+            $str = self::iconv('utf-8', $encoding . '//IGNORE', $str);
+            return $str;
+        } elseif ($encoding === 'iso-8859-1') {
+            $str = utf8_decode($str);
+            return $str;
+        }
+        trigger_error('Encoding not supported', E_USER_ERROR);
+        // You might be tempted to assume that the ASCII representation
+        // might be OK, however, this is *not* universally true over all
+        // encodings.  So we take the conservative route here, rather
+        // than forcibly turn on %Core.EscapeNonASCIICharacters
+    }
+
+    /**
+     * Lossless (character-wise) conversion of HTML to ASCII
+     * @param $str UTF-8 string to be converted to ASCII
+     * @returns ASCII encoded string with non-ASCII character entity-ized
+     * @warning Adapted from MediaWiki, claiming fair use: this is a common
+     *       algorithm. If you disagree with this license fudgery,
+     *       implement it yourself.
+     * @note Uses decimal numeric entities since they are best supported.
+     * @note This is a DUMB function: it has no concept of keeping
+     *       character entities that the projected character encoding
+     *       can allow. We could possibly implement a smart version
+     *       but that would require it to also know which Unicode
+     *       codepoints the charset supported (not an easy task).
+     * @note Sort of with cleanUTF8() but it assumes that $str is
+     *       well-formed UTF-8
+     */
+    public static function convertToASCIIDumbLossless($str) {
+        $bytesleft = 0;
+        $result = '';
+        $working = 0;
+        $len = strlen($str);
+        for( $i = 0; $i < $len; $i++ ) {
+            $bytevalue = ord( $str[$i] );
+            if( $bytevalue <= 0x7F ) { //0xxx xxxx
+                $result .= chr( $bytevalue );
+                $bytesleft = 0;
+            } elseif( $bytevalue <= 0xBF ) { //10xx xxxx
+                $working = $working << 6;
+                $working += ($bytevalue & 0x3F);
+                $bytesleft--;
+                if( $bytesleft <= 0 ) {
+                    $result .= "&#" . $working . ";";
+                }
+            } elseif( $bytevalue <= 0xDF ) { //110x xxxx
+                $working = $bytevalue & 0x1F;
+                $bytesleft = 1;
+            } elseif( $bytevalue <= 0xEF ) { //1110 xxxx
+                $working = $bytevalue & 0x0F;
+                $bytesleft = 2;
+            } else { //1111 0xxx
+                $working = $bytevalue & 0x07;
+                $bytesleft = 3;
+            }
+        }
+        return $result;
+    }
+
+    /** No bugs detected in iconv. */
+    const ICONV_OK = 0;
+
+    /** Iconv truncates output if converting from UTF-8 to another
+     *  character set with //IGNORE, and a non-encodable character is found */
+    const ICONV_TRUNCATES = 1;
+
+    /** Iconv does not support //IGNORE, making it unusable for
+     *  transcoding purposes */
+    const ICONV_UNUSABLE = 2;
+
+    /**
+     * glibc iconv has a known bug where it doesn't handle the magic
+     * //IGNORE stanza correctly.  In particular, rather than ignore
+     * characters, it will return an EILSEQ after consuming some number
+     * of characters, and expect you to restart iconv as if it were
+     * an E2BIG.  Old versions of PHP did not respect the errno, and
+     * returned the fragment, so as a result you would see iconv
+     * mysteriously truncating output. We can work around this by
+     * manually chopping our input into segments of about 8000
+     * characters, as long as PHP ignores the error code.  If PHP starts
+     * paying attention to the error code, iconv becomes unusable.
+     *
+     * @returns Error code indicating severity of bug.
+     */
+    public static function testIconvTruncateBug() {
+        static $code = null;
+        if ($code === null) {
+            // better not use iconv, otherwise infinite loop!
+            $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000));
+            if ($r === false) {
+                $code = self::ICONV_UNUSABLE;
+            } elseif (($c = strlen($r)) < 9000) {
+                $code = self::ICONV_TRUNCATES;
+            } elseif ($c > 9000) {
+                trigger_error('Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: include your iconv version as per phpversion()', E_USER_ERROR);
+            } else {
+                $code = self::ICONV_OK;
+            }
+        }
+        return $code;
+    }
+
+    /**
+     * This expensive function tests whether or not a given character
+     * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will
+     * fail this test, and require special processing. Variable width
+     * encodings shouldn't ever fail.
+     *
+     * @param string $encoding Encoding name to test, as per iconv format
+     * @param bool $bypass Whether or not to bypass the precompiled arrays.
+     * @return Array of UTF-8 characters to their corresponding ASCII,
+     *      which can be used to "undo" any overzealous iconv action.
+     */
+    public static function testEncodingSupportsASCII($encoding, $bypass = false) {
+        // All calls to iconv here are unsafe, proof by case analysis:
+        // If ICONV_OK, no difference.
+        // If ICONV_TRUNCATE, all calls involve one character inputs,
+        // so bug is not triggered.
+        // If ICONV_UNUSABLE, this call is irrelevant
+        static $encodings = array();
+        if (!$bypass) {
+            if (isset($encodings[$encoding])) return $encodings[$encoding];
+            $lenc = strtolower($encoding);
+            switch ($lenc) {
+                case 'shift_jis':
+                    return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~');
+                case 'johab':
+                    return array("\xE2\x82\xA9" => '\\');
+            }
+            if (strpos($lenc, 'iso-8859-') === 0) return array();
+        }
+        $ret = array();
+        if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) return false;
+        for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars
+            $c = chr($i); // UTF-8 char
+            $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion
+            if (
+                $r === '' ||
+                // This line is needed for iconv implementations that do not
+                // omit characters that do not exist in the target character set
+                ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c)
+            ) {
+                // Reverse engineer: what's the UTF-8 equiv of this byte
+                // sequence? This assumes that there's no variable width
+                // encoding that doesn't support ASCII.
+                $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c;
+            }
+        }
+        $encodings[$encoding] = $ret;
+        return $ret;
+    }
+
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/EntityLookup.php b/application/libraries/htmlpurifier/HTMLPurifier/EntityLookup.php
new file mode 100644
index 0000000..b4dfce9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/EntityLookup.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Object that provides entity lookup table from entity name to character
+ */
+class HTMLPurifier_EntityLookup {
+
+    /**
+     * Assoc array of entity name to character represented.
+     */
+    public $table;
+
+    /**
+     * Sets up the entity lookup table from the serialized file contents.
+     * @note The serialized contents are versioned, but were generated
+     *       using the maintenance script generate_entity_file.php
+     * @warning This is not in constructor to help enforce the Singleton
+     */
+    public function setup($file = false) {
+        if (!$file) {
+            $file = HTMLPURIFIER_PREFIX . '/HTMLPurifier/EntityLookup/entities.ser';
+        }
+        $this->table = unserialize(file_get_contents($file));
+    }
+
+    /**
+     * Retrieves sole instance of the object.
+     * @param Optional prototype of custom lookup table to overload with.
+     */
+    public static function instance($prototype = false) {
+        // no references, since PHP doesn't copy unless modified
+        static $instance = null;
+        if ($prototype) {
+            $instance = $prototype;
+        } elseif (!$instance) {
+            $instance = new HTMLPurifier_EntityLookup();
+            $instance->setup();
+        }
+        return $instance;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/EntityLookup/entities.ser b/application/libraries/htmlpurifier/HTMLPurifier/EntityLookup/entities.ser
new file mode 100644
index 0000000..e8b0812
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/EntityLookup/entities.ser
@@ -0,0 +1 @@
+a:253:{s:4:"fnof";s:2:"ƒ";s:5:"Alpha";s:2:"Α";s:4:"Beta";s:2:"Β";s:5:"Gamma";s:2:"Γ";s:5:"Delta";s:2:"Δ";s:7:"Epsilon";s:2:"Ε";s:4:"Zeta";s:2:"Ζ";s:3:"Eta";s:2:"Η";s:5:"Theta";s:2:"Θ";s:4:"Iota";s:2:"Ι";s:5:"Kappa";s:2:"Κ";s:6:"Lambda";s:2:"Λ";s:2:"Mu";s:2:"Μ";s:2:"Nu";s:2:"Ν";s:2:"Xi";s:2:"Ξ";s:7:"Omicron";s:2:"Ο";s:2:"Pi";s:2:"Π";s:3:"Rho";s:2:"Ρ";s:5:"Sigma";s:2:"Σ";s:3:"Tau";s:2:"Τ";s:7:"Upsilon";s:2:"Υ";s:3:"Phi";s:2:"Φ";s:3:"Chi";s:2:"Χ";s:3:"Psi";s:2:"Ψ";s:5:"Omega";s:2:"Ω";s:5:"a [...]
\ No newline at end of file
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/EntityParser.php b/application/libraries/htmlpurifier/HTMLPurifier/EntityParser.php
new file mode 100644
index 0000000..8c38447
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/EntityParser.php
@@ -0,0 +1,144 @@
+<?php
+
+// if want to implement error collecting here, we'll need to use some sort
+// of global data (probably trigger_error) because it's impossible to pass
+// $config or $context to the callback functions.
+
+/**
+ * Handles referencing and derefencing character entities
+ */
+class HTMLPurifier_EntityParser
+{
+
+    /**
+     * Reference to entity lookup table.
+     */
+    protected $_entity_lookup;
+
+    /**
+     * Callback regex string for parsing entities.
+     */
+    protected $_substituteEntitiesRegex =
+'/&(?:[#]x([a-fA-F0-9]+)|[#]0*(\d+)|([A-Za-z_:][A-Za-z0-9.\-_:]*));?/';
+//     1. hex             2. dec      3. string (XML style)
+
+
+    /**
+     * Decimal to parsed string conversion table for special entities.
+     */
+    protected $_special_dec2str =
+            array(
+                    34 => '"',
+                    38 => '&',
+                    39 => "'",
+                    60 => '<',
+                    62 => '>'
+            );
+
+    /**
+     * Stripped entity names to decimal conversion table for special entities.
+     */
+    protected $_special_ent2dec =
+            array(
+                    'quot' => 34,
+                    'amp'  => 38,
+                    'lt'   => 60,
+                    'gt'   => 62
+            );
+
+    /**
+     * Substitutes non-special entities with their parsed equivalents. Since
+     * running this whenever you have parsed character is t3h 5uck, we run
+     * it before everything else.
+     *
+     * @param $string String to have non-special entities parsed.
+     * @returns Parsed string.
+     */
+    public function substituteNonSpecialEntities($string) {
+        // it will try to detect missing semicolons, but don't rely on it
+        return preg_replace_callback(
+            $this->_substituteEntitiesRegex,
+            array($this, 'nonSpecialEntityCallback'),
+            $string
+            );
+    }
+
+    /**
+     * Callback function for substituteNonSpecialEntities() that does the work.
+     *
+     * @param $matches  PCRE matches array, with 0 the entire match, and
+     *                  either index 1, 2 or 3 set with a hex value, dec value,
+     *                  or string (respectively).
+     * @returns Replacement string.
+     */
+
+    protected function nonSpecialEntityCallback($matches) {
+        // replaces all but big five
+        $entity = $matches[0];
+        $is_num = (@$matches[0][1] === '#');
+        if ($is_num) {
+            $is_hex = (@$entity[2] === 'x');
+            $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
+
+            // abort for special characters
+            if (isset($this->_special_dec2str[$code]))  return $entity;
+
+            return HTMLPurifier_Encoder::unichr($code);
+        } else {
+            if (isset($this->_special_ent2dec[$matches[3]])) return $entity;
+            if (!$this->_entity_lookup) {
+                $this->_entity_lookup = HTMLPurifier_EntityLookup::instance();
+            }
+            if (isset($this->_entity_lookup->table[$matches[3]])) {
+                return $this->_entity_lookup->table[$matches[3]];
+            } else {
+                return $entity;
+            }
+        }
+    }
+
+    /**
+     * Substitutes only special entities with their parsed equivalents.
+     *
+     * @notice We try to avoid calling this function because otherwise, it
+     * would have to be called a lot (for every parsed section).
+     *
+     * @param $string String to have non-special entities parsed.
+     * @returns Parsed string.
+     */
+    public function substituteSpecialEntities($string) {
+        return preg_replace_callback(
+            $this->_substituteEntitiesRegex,
+            array($this, 'specialEntityCallback'),
+            $string);
+    }
+
+    /**
+     * Callback function for substituteSpecialEntities() that does the work.
+     *
+     * This callback has same syntax as nonSpecialEntityCallback().
+     *
+     * @param $matches  PCRE-style matches array, with 0 the entire match, and
+     *                  either index 1, 2 or 3 set with a hex value, dec value,
+     *                  or string (respectively).
+     * @returns Replacement string.
+     */
+    protected function specialEntityCallback($matches) {
+        $entity = $matches[0];
+        $is_num = (@$matches[0][1] === '#');
+        if ($is_num) {
+            $is_hex = (@$entity[2] === 'x');
+            $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2];
+            return isset($this->_special_dec2str[$int]) ?
+                $this->_special_dec2str[$int] :
+                $entity;
+        } else {
+            return isset($this->_special_ent2dec[$matches[3]]) ?
+                $this->_special_ent2dec[$matches[3]] :
+                $entity;
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ErrorCollector.php b/application/libraries/htmlpurifier/HTMLPurifier/ErrorCollector.php
new file mode 100644
index 0000000..6713eaf
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ErrorCollector.php
@@ -0,0 +1,209 @@
+<?php
+
+/**
+ * Error collection class that enables HTML Purifier to report HTML
+ * problems back to the user
+ */
+class HTMLPurifier_ErrorCollector
+{
+
+    /**
+     * Identifiers for the returned error array. These are purposely numeric
+     * so list() can be used.
+     */
+    const LINENO   = 0;
+    const SEVERITY = 1;
+    const MESSAGE  = 2;
+    const CHILDREN = 3;
+
+    protected $errors;
+    protected $_current;
+    protected $_stacks = array(array());
+    protected $locale;
+    protected $generator;
+    protected $context;
+
+    protected $lines = array();
+
+    public function __construct($context) {
+        $this->locale    =& $context->get('Locale');
+        $this->context   = $context;
+        $this->_current  =& $this->_stacks[0];
+        $this->errors    =& $this->_stacks[0];
+    }
+
+    /**
+     * Sends an error message to the collector for later use
+     * @param $severity int Error severity, PHP error style (don't use E_USER_)
+     * @param $msg string Error message text
+     * @param $subst1 string First substitution for $msg
+     * @param $subst2 string ...
+     */
+    public function send($severity, $msg) {
+
+        $args = array();
+        if (func_num_args() > 2) {
+            $args = func_get_args();
+            array_shift($args);
+            unset($args[0]);
+        }
+
+        $token = $this->context->get('CurrentToken', true);
+        $line  = $token ? $token->line : $this->context->get('CurrentLine', true);
+        $col   = $token ? $token->col  : $this->context->get('CurrentCol',  true);
+        $attr  = $this->context->get('CurrentAttr', true);
+
+        // perform special substitutions, also add custom parameters
+        $subst = array();
+        if (!is_null($token)) {
+            $args['CurrentToken'] = $token;
+        }
+        if (!is_null($attr)) {
+            $subst['$CurrentAttr.Name'] = $attr;
+            if (isset($token->attr[$attr])) $subst['$CurrentAttr.Value'] = $token->attr[$attr];
+        }
+
+        if (empty($args)) {
+            $msg = $this->locale->getMessage($msg);
+        } else {
+            $msg = $this->locale->formatMessage($msg, $args);
+        }
+
+        if (!empty($subst)) $msg = strtr($msg, $subst);
+
+        // (numerically indexed)
+        $error = array(
+            self::LINENO   => $line,
+            self::SEVERITY => $severity,
+            self::MESSAGE  => $msg,
+            self::CHILDREN => array()
+        );
+        $this->_current[] = $error;
+
+
+        // NEW CODE BELOW ...
+
+        $struct = null;
+        // Top-level errors are either:
+        //  TOKEN type, if $value is set appropriately, or
+        //  "syntax" type, if $value is null
+        $new_struct = new HTMLPurifier_ErrorStruct();
+        $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN;
+        if ($token) $new_struct->value = clone $token;
+        if (is_int($line) && is_int($col)) {
+            if (isset($this->lines[$line][$col])) {
+                $struct = $this->lines[$line][$col];
+            } else {
+                $struct = $this->lines[$line][$col] = $new_struct;
+            }
+            // These ksorts may present a performance problem
+            ksort($this->lines[$line], SORT_NUMERIC);
+        } else {
+            if (isset($this->lines[-1])) {
+                $struct = $this->lines[-1];
+            } else {
+                $struct = $this->lines[-1] = $new_struct;
+            }
+        }
+        ksort($this->lines, SORT_NUMERIC);
+
+        // Now, check if we need to operate on a lower structure
+        if (!empty($attr)) {
+            $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr);
+            if (!$struct->value) {
+                $struct->value = array($attr, 'PUT VALUE HERE');
+            }
+        }
+        if (!empty($cssprop)) {
+            $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop);
+            if (!$struct->value) {
+                // if we tokenize CSS this might be a little more difficult to do
+                $struct->value = array($cssprop, 'PUT VALUE HERE');
+            }
+        }
+
+        // Ok, structs are all setup, now time to register the error
+        $struct->addError($severity, $msg);
+    }
+
+    /**
+     * Retrieves raw error data for custom formatter to use
+     * @param List of arrays in format of array(line of error,
+     *        error severity, error message,
+     *        recursive sub-errors array)
+     */
+    public function getRaw() {
+        return $this->errors;
+    }
+
+    /**
+     * Default HTML formatting implementation for error messages
+     * @param $config Configuration array, vital for HTML output nature
+     * @param $errors Errors array to display; used for recursion.
+     */
+    public function getHTMLFormatted($config, $errors = null) {
+        $ret = array();
+
+        $this->generator = new HTMLPurifier_Generator($config, $this->context);
+        if ($errors === null) $errors = $this->errors;
+
+        // 'At line' message needs to be removed
+
+        // generation code for new structure goes here. It needs to be recursive.
+        foreach ($this->lines as $line => $col_array) {
+            if ($line == -1) continue;
+            foreach ($col_array as $col => $struct) {
+                $this->_renderStruct($ret, $struct, $line, $col);
+            }
+        }
+        if (isset($this->lines[-1])) {
+            $this->_renderStruct($ret, $this->lines[-1]);
+        }
+
+        if (empty($errors)) {
+            return '<p>' . $this->locale->getMessage('ErrorCollector: No errors') . '</p>';
+        } else {
+            return '<ul><li>' . implode('</li><li>', $ret) . '</li></ul>';
+        }
+
+    }
+
+    private function _renderStruct(&$ret, $struct, $line = null, $col = null) {
+        $stack = array($struct);
+        $context_stack = array(array());
+        while ($current = array_pop($stack)) {
+            $context = array_pop($context_stack);
+            foreach ($current->errors as $error) {
+                list($severity, $msg) = $error;
+                $string = '';
+                $string .= '<div>';
+                // W3C uses an icon to indicate the severity of the error.
+                $error = $this->locale->getErrorName($severity);
+                $string .= "<span class=\"error e$severity\"><strong>$error</strong></span> ";
+                if (!is_null($line) && !is_null($col)) {
+                    $string .= "<em class=\"location\">Line $line, Column $col: </em> ";
+                } else {
+                    $string .= '<em class="location">End of Document: </em> ';
+                }
+                $string .= '<strong class="description">' . $this->generator->escape($msg) . '</strong> ';
+                $string .= '</div>';
+                // Here, have a marker for the character on the column appropriate.
+                // Be sure to clip extremely long lines.
+                //$string .= '<pre>';
+                //$string .= '';
+                //$string .= '</pre>';
+                $ret[] = $string;
+            }
+            foreach ($current->children as $type => $array) {
+                $context[] = $current;
+                $stack = array_merge($stack, array_reverse($array, true));
+                for ($i = count($array); $i > 0; $i--) {
+                    $context_stack[] = $context;
+                }
+            }
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/ErrorStruct.php b/application/libraries/htmlpurifier/HTMLPurifier/ErrorStruct.php
new file mode 100644
index 0000000..9bc8996
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/ErrorStruct.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * Records errors for particular segments of an HTML document such as tokens,
+ * attributes or CSS properties. They can contain error structs (which apply
+ * to components of what they represent), but their main purpose is to hold
+ * errors applying to whatever struct is being used.
+ */
+class HTMLPurifier_ErrorStruct
+{
+
+    /**
+     * Possible values for $children first-key. Note that top-level structures
+     * are automatically token-level.
+     */
+    const TOKEN     = 0;
+    const ATTR      = 1;
+    const CSSPROP   = 2;
+
+    /**
+     * Type of this struct.
+     */
+    public $type;
+
+    /**
+     * Value of the struct we are recording errors for. There are various
+     * values for this:
+     *  - TOKEN: Instance of HTMLPurifier_Token
+     *  - ATTR: array('attr-name', 'value')
+     *  - CSSPROP: array('prop-name', 'value')
+     */
+    public $value;
+
+    /**
+     * Errors registered for this structure.
+     */
+    public $errors = array();
+
+    /**
+     * Child ErrorStructs that are from this structure. For example, a TOKEN
+     * ErrorStruct would contain ATTR ErrorStructs. This is a multi-dimensional
+     * array in structure: [TYPE]['identifier']
+     */
+    public $children = array();
+
+    public function getChild($type, $id) {
+        if (!isset($this->children[$type][$id])) {
+            $this->children[$type][$id] = new HTMLPurifier_ErrorStruct();
+            $this->children[$type][$id]->type = $type;
+        }
+        return $this->children[$type][$id];
+    }
+
+    public function addError($severity, $message) {
+        $this->errors[] = array($severity, $message);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Exception.php b/application/libraries/htmlpurifier/HTMLPurifier/Exception.php
new file mode 100644
index 0000000..be85b4c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Exception.php
@@ -0,0 +1,12 @@
+<?php
+
+/**
+ * Global exception class for HTML Purifier; any exceptions we throw
+ * are from here.
+ */
+class HTMLPurifier_Exception extends Exception
+{
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Filter.php b/application/libraries/htmlpurifier/HTMLPurifier/Filter.php
new file mode 100644
index 0000000..9a0e7b0
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Filter.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Represents a pre or post processing filter on HTML Purifier's output
+ *
+ * Sometimes, a little ad-hoc fixing of HTML has to be done before
+ * it gets sent through HTML Purifier: you can use filters to acheive
+ * this effect. For instance, YouTube videos can be preserved using
+ * this manner. You could have used a decorator for this task, but
+ * PHP's support for them is not terribly robust, so we're going
+ * to just loop through the filters.
+ *
+ * Filters should be exited first in, last out. If there are three filters,
+ * named 1, 2 and 3, the order of execution should go 1->preFilter,
+ * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter,
+ * 1->postFilter.
+ *
+ * @note Methods are not declared abstract as it is perfectly legitimate
+ *       for an implementation not to want anything to happen on a step
+ */
+
+class HTMLPurifier_Filter
+{
+
+    /**
+     * Name of the filter for identification purposes
+     */
+    public $name;
+
+    /**
+     * Pre-processor function, handles HTML before HTML Purifier
+     */
+    public function preFilter($html, $config, $context) {
+        return $html;
+    }
+
+    /**
+     * Post-processor function, handles HTML after HTML Purifier
+     */
+    public function postFilter($html, $config, $context) {
+        return $html;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Filter/ExtractStyleBlocks.php b/application/libraries/htmlpurifier/HTMLPurifier/Filter/ExtractStyleBlocks.php
new file mode 100644
index 0000000..df937ac
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Filter/ExtractStyleBlocks.php
@@ -0,0 +1,289 @@
+<?php
+
+// why is this a top level function? Because PHP 5.2.0 doesn't seem to
+// understand how to interpret this filter if it's a static method.
+// It's all really silly, but if we go this route it might be reasonable
+// to coalesce all of these methods into one.
+function htmlpurifier_filter_extractstyleblocks_muteerrorhandler() {}
+
+/**
+ * This filter extracts <style> blocks from input HTML, cleans them up
+ * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks')
+ * so they can be used elsewhere in the document.
+ *
+ * @note
+ *      See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for
+ *      sample usage.
+ *
+ * @note
+ *      This filter can also be used on stylesheets not included in the
+ *      document--something purists would probably prefer. Just directly
+ *      call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS()
+ */
+class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter
+{
+
+    public $name = 'ExtractStyleBlocks';
+    private $_styleMatches = array();
+    private $_tidy;
+
+    private $_id_attrdef;
+    private $_class_attrdef;
+    private $_enum_attrdef;
+
+    public function __construct() {
+        $this->_tidy = new csstidy();
+        $this->_tidy->set_cfg('lowercase_s', false);
+        $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true);
+        $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident();
+        $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum(array('first-child', 'link', 'visited', 'active', 'hover', 'focus'));
+    }
+
+    /**
+     * Save the contents of CSS blocks to style matches
+     * @param $matches preg_replace style $matches array
+     */
+    protected function styleCallback($matches) {
+        $this->_styleMatches[] = $matches[1];
+    }
+
+    /**
+     * Removes inline <style> tags from HTML, saves them for later use
+     * @todo Extend to indicate non-text/css style blocks
+     */
+    public function preFilter($html, $config, $context) {
+        $tidy = $config->get('Filter.ExtractStyleBlocks.TidyImpl');
+        if ($tidy !== null) $this->_tidy = $tidy;
+        $html = preg_replace_callback('#<style(?:\s.*)?>(.+)</style>#isU', array($this, 'styleCallback'), $html);
+        $style_blocks = $this->_styleMatches;
+        $this->_styleMatches = array(); // reset
+        $context->register('StyleBlocks', $style_blocks); // $context must not be reused
+        if ($this->_tidy) {
+            foreach ($style_blocks as &$style) {
+                $style = $this->cleanCSS($style, $config, $context);
+            }
+        }
+        return $html;
+    }
+
+    /**
+     * Takes CSS (the stuff found in <style>) and cleans it.
+     * @warning Requires CSSTidy <http://csstidy.sourceforge.net/>
+     * @param $css     CSS styling to clean
+     * @param $config  Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     * @return Cleaned CSS
+     */
+    public function cleanCSS($css, $config, $context) {
+        // prepare scope
+        $scope = $config->get('Filter.ExtractStyleBlocks.Scope');
+        if ($scope !== null) {
+            $scopes = array_map('trim', explode(',', $scope));
+        } else {
+            $scopes = array();
+        }
+        // remove comments from CSS
+        $css = trim($css);
+        if (strncmp('<!--', $css, 4) === 0) {
+            $css = substr($css, 4);
+        }
+        if (strlen($css) > 3 && substr($css, -3) == '-->') {
+            $css = substr($css, 0, -3);
+        }
+        $css = trim($css);
+        set_error_handler('htmlpurifier_filter_extractstyleblocks_muteerrorhandler');
+        $this->_tidy->parse($css);
+        restore_error_handler();
+        $css_definition = $config->getDefinition('CSS');
+        $html_definition = $config->getDefinition('HTML');
+        $new_css = array();
+        foreach ($this->_tidy->css as $k => $decls) {
+            // $decls are all CSS declarations inside an @ selector
+            $new_decls = array();
+            foreach ($decls as $selector => $style) {
+                $selector = trim($selector);
+                if ($selector === '') continue; // should not happen
+                // Parse the selector
+                // Here is the relevant part of the CSS grammar:
+                //
+                // ruleset
+                //   : selector [ ',' S* selector ]* '{' ...
+                // selector
+                //   : simple_selector [ combinator selector | S+ [ combinator? selector ]? ]?
+                // combinator
+                //   : '+' S*
+                //   : '>' S*
+                // simple_selector
+                //   : element_name [ HASH | class | attrib | pseudo ]*
+                //   | [ HASH | class | attrib | pseudo ]+
+                // element_name
+                //   : IDENT | '*'
+                //   ;
+                // class
+                //   : '.' IDENT
+                //   ;
+                // attrib
+                //   : '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S*
+                //     [ IDENT | STRING ] S* ]? ']'
+                //   ;
+                // pseudo
+                //   : ':' [ IDENT | FUNCTION S* [IDENT S*]? ')' ]
+                //   ;
+                //
+                // For reference, here are the relevant tokens:
+                //
+                // HASH         #{name}
+                // IDENT        {ident}
+                // INCLUDES     ==
+                // DASHMATCH    |=
+                // STRING       {string}
+                // FUNCTION     {ident}\(
+                //
+                // And the lexical scanner tokens
+                //
+                // name         {nmchar}+
+                // nmchar       [_a-z0-9-]|{nonascii}|{escape}
+                // nonascii     [\240-\377]
+                // escape       {unicode}|\\[^\r\n\f0-9a-f]
+                // unicode      \\{h}}{1,6}(\r\n|[ \t\r\n\f])?
+                // ident        -?{nmstart}{nmchar*}
+                // nmstart      [_a-z]|{nonascii}|{escape}
+                // string       {string1}|{string2}
+                // string1      \"([^\n\r\f\\"]|\\{nl}|{escape})*\"
+                // string2      \'([^\n\r\f\\"]|\\{nl}|{escape})*\'
+                //
+                // We'll implement a subset (in order to reduce attack
+                // surface); in particular:
+                //
+                //      - No Unicode support
+                //      - No escapes support
+                //      - No string support (by proxy no attrib support)
+                //      - element_name is matched against allowed
+                //        elements (some people might find this
+                //        annoying...)
+                //      - Pseudo-elements one of :first-child, :link,
+                //        :visited, :active, :hover, :focus
+
+                // handle ruleset
+                $selectors = array_map('trim', explode(',', $selector));
+                $new_selectors = array();
+                foreach ($selectors as $sel) {
+                    // split on +, > and spaces
+                    $basic_selectors = preg_split('/\s*([+> ])\s*/', $sel, -1, PREG_SPLIT_DELIM_CAPTURE);
+                    // even indices are chunks, odd indices are
+                    // delimiters
+                    $nsel = null;
+                    $delim = null; // guaranteed to be non-null after
+                                   // two loop iterations
+                    for ($i = 0, $c = count($basic_selectors); $i < $c; $i++) {
+                        $x = $basic_selectors[$i];
+                        if ($i % 2) {
+                            // delimiter
+                            if ($x === ' ') {
+                                $delim = ' ';
+                            } else {
+                                $delim = ' ' . $x . ' ';
+                            }
+                        } else {
+                            // simple selector
+                            $components = preg_split('/([#.:])/', $x, -1, PREG_SPLIT_DELIM_CAPTURE);
+                            $sdelim = null;
+                            $nx = null;
+                            for ($j = 0, $cc = count($components); $j < $cc; $j ++) {
+                                $y = $components[$j];
+                                if ($j === 0) {
+                                    if ($y === '*' || isset($html_definition->info[$y = strtolower($y)])) {
+                                        $nx = $y;
+                                    } else {
+                                        // $nx stays null; this matters
+                                        // if we don't manage to find
+                                        // any valid selector content,
+                                        // in which case we ignore the
+                                        // outer $delim
+                                    }
+                                } elseif ($j % 2) {
+                                    // set delimiter
+                                    $sdelim = $y;
+                                } else {
+                                    $attrdef = null;
+                                    if ($sdelim === '#') {
+                                        $attrdef = $this->_id_attrdef;
+                                    } elseif ($sdelim === '.') {
+                                        $attrdef = $this->_class_attrdef;
+                                    } elseif ($sdelim === ':') {
+                                        $attrdef = $this->_enum_attrdef;
+                                    } else {
+                                        throw new HTMLPurifier_Exception('broken invariant sdelim and preg_split');
+                                    }
+                                    $r = $attrdef->validate($y, $config, $context);
+                                    if ($r !== false) {
+                                        if ($r !== true) {
+                                            $y = $r;
+                                        }
+                                        if ($nx === null) {
+                                            $nx = '';
+                                        }
+                                        $nx .= $sdelim . $y;
+                                    }
+                                }
+                            }
+                            if ($nx !== null) {
+                                if ($nsel === null) {
+                                    $nsel = $nx;
+                                } else {
+                                    $nsel .= $delim . $nx;
+                                }
+                            } else {
+                                // delimiters to the left of invalid
+                                // basic selector ignored
+                            }
+                        }
+                    }
+                    if ($nsel !== null) {
+                        if (!empty($scopes)) {
+                            foreach ($scopes as $s) {
+                                $new_selectors[] = "$s $nsel";
+                            }
+                        } else {
+                            $new_selectors[] = $nsel;
+                        }
+                    }
+                }
+                if (empty($new_selectors)) continue;
+                $selector = implode(', ', $new_selectors);
+                foreach ($style as $name => $value) {
+                    if (!isset($css_definition->info[$name])) {
+                        unset($style[$name]);
+                        continue;
+                    }
+                    $def = $css_definition->info[$name];
+                    $ret = $def->validate($value, $config, $context);
+                    if ($ret === false) unset($style[$name]);
+                    else $style[$name] = $ret;
+                }
+                $new_decls[$selector] = $style;
+            }
+            $new_css[$k] = $new_decls;
+        }
+        // remove stuff that shouldn't be used, could be reenabled
+        // after security risks are analyzed
+        $this->_tidy->css = $new_css;
+        $this->_tidy->import = array();
+        $this->_tidy->charset = null;
+        $this->_tidy->namespace = null;
+        $css = $this->_tidy->print->plain();
+        // we are going to escape any special characters <>& to ensure
+        // that no funny business occurs (i.e. </style> in a font-family prop).
+        if ($config->get('Filter.ExtractStyleBlocks.Escaping')) {
+            $css = str_replace(
+                array('<',    '>',    '&'),
+                array('\3C ', '\3E ', '\26 '),
+                $css
+            );
+        }
+        return $css;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Filter/YouTube.php b/application/libraries/htmlpurifier/HTMLPurifier/Filter/YouTube.php
new file mode 100644
index 0000000..23df221
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Filter/YouTube.php
@@ -0,0 +1,39 @@
+<?php
+
+class HTMLPurifier_Filter_YouTube extends HTMLPurifier_Filter
+{
+
+    public $name = 'YouTube';
+
+    public function preFilter($html, $config, $context) {
+        $pre_regex = '#<object[^>]+>.+?'.
+            'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?</object>#s';
+        $pre_replace = '<span class="youtube-embed">\1</span>';
+        return preg_replace($pre_regex, $pre_replace, $html);
+    }
+
+    public function postFilter($html, $config, $context) {
+        $post_regex = '#<span class="youtube-embed">((?:v|cp)/[A-Za-z0-9\-_=]+)</span>#';
+        return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html);
+    }
+
+    protected function armorUrl($url) {
+        return str_replace('--', '--', $url);
+    }
+
+    protected function postFilterCallback($matches) {
+        $url = $this->armorUrl($matches[1]);
+        return '<object width="425" height="350" type="application/x-shockwave-flash" '.
+            'data="http://www.youtube.com/'.$url.'">'.
+            '<param name="movie" value="http://www.youtube.com/'.$url.'"></param>'.
+            '<!--[if IE]>'.
+            '<embed src="http://www.youtube.com/'.$url.'"'.
+            'type="application/x-shockwave-flash"'.
+            'wmode="transparent" width="425" height="350" />'.
+            '<![endif]-->'.
+            '</object>';
+
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Generator.php b/application/libraries/htmlpurifier/HTMLPurifier/Generator.php
new file mode 100644
index 0000000..fee1a5f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Generator.php
@@ -0,0 +1,254 @@
+<?php
+
+/**
+ * Generates HTML from tokens.
+ * @todo Refactor interface so that configuration/context is determined
+ *       upon instantiation, no need for messy generateFromTokens() calls
+ * @todo Make some of the more internal functions protected, and have
+ *       unit tests work around that
+ */
+class HTMLPurifier_Generator
+{
+
+    /**
+     * Whether or not generator should produce XML output
+     */
+    private $_xhtml = true;
+
+    /**
+     * :HACK: Whether or not generator should comment the insides of <script> tags
+     */
+    private $_scriptFix = false;
+
+    /**
+     * Cache of HTMLDefinition during HTML output to determine whether or
+     * not attributes should be minimized.
+     */
+    private $_def;
+
+    /**
+     * Cache of %Output.SortAttr
+     */
+    private $_sortAttr;
+
+    /**
+     * Cache of %Output.FlashCompat
+     */
+    private $_flashCompat;
+
+    /**
+     * Cache of %Output.FixInnerHTML
+     */
+    private $_innerHTMLFix;
+
+    /**
+     * Stack for keeping track of object information when outputting IE
+     * compatibility code.
+     */
+    private $_flashStack = array();
+
+    /**
+     * Configuration for the generator
+     */
+    protected $config;
+
+    /**
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     */
+    public function __construct($config, $context) {
+        $this->config = $config;
+        $this->_scriptFix = $config->get('Output.CommentScriptContents');
+        $this->_innerHTMLFix = $config->get('Output.FixInnerHTML');
+        $this->_sortAttr = $config->get('Output.SortAttr');
+        $this->_flashCompat = $config->get('Output.FlashCompat');
+        $this->_def = $config->getHTMLDefinition();
+        $this->_xhtml = $this->_def->doctype->xml;
+    }
+
+    /**
+     * Generates HTML from an array of tokens.
+     * @param $tokens Array of HTMLPurifier_Token
+     * @param $config HTMLPurifier_Config object
+     * @return Generated HTML
+     */
+    public function generateFromTokens($tokens) {
+        if (!$tokens) return '';
+
+        // Basic algorithm
+        $html = '';
+        for ($i = 0, $size = count($tokens); $i < $size; $i++) {
+            if ($this->_scriptFix && $tokens[$i]->name === 'script'
+                && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) {
+                // script special case
+                // the contents of the script block must be ONE token
+                // for this to work.
+                $html .= $this->generateFromToken($tokens[$i++]);
+                $html .= $this->generateScriptFromToken($tokens[$i++]);
+            }
+            $html .= $this->generateFromToken($tokens[$i]);
+        }
+
+        // Tidy cleanup
+        if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) {
+            $tidy = new Tidy;
+            $tidy->parseString($html, array(
+               'indent'=> true,
+               'output-xhtml' => $this->_xhtml,
+               'show-body-only' => true,
+               'indent-spaces' => 2,
+               'wrap' => 68,
+            ), 'utf8');
+            $tidy->cleanRepair();
+            $html = (string) $tidy; // explicit cast necessary
+        }
+
+        // Normalize newlines to system defined value
+        if ($this->config->get('Core.NormalizeNewlines')) {
+            $nl = $this->config->get('Output.Newline');
+            if ($nl === null) $nl = PHP_EOL;
+            if ($nl !== "\n") $html = str_replace("\n", $nl, $html);
+        }
+        return $html;
+    }
+
+    /**
+     * Generates HTML from a single token.
+     * @param $token HTMLPurifier_Token object.
+     * @return Generated HTML
+     */
+    public function generateFromToken($token) {
+        if (!$token instanceof HTMLPurifier_Token) {
+            trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING);
+            return '';
+
+        } elseif ($token instanceof HTMLPurifier_Token_Start) {
+            $attr = $this->generateAttributes($token->attr, $token->name);
+            if ($this->_flashCompat) {
+                if ($token->name == "object") {
+                    $flash = new stdclass();
+                    $flash->attr = $token->attr;
+                    $flash->param = array();
+                    $this->_flashStack[] = $flash;
+                }
+            }
+            return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>';
+
+        } elseif ($token instanceof HTMLPurifier_Token_End) {
+            $_extra = '';
+            if ($this->_flashCompat) {
+                if ($token->name == "object" && !empty($this->_flashStack)) {
+                    // doesn't do anything for now
+                }
+            }
+            return $_extra . '</' . $token->name . '>';
+
+        } elseif ($token instanceof HTMLPurifier_Token_Empty) {
+            if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) {
+                $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value'];
+            }
+            $attr = $this->generateAttributes($token->attr, $token->name);
+             return '<' . $token->name . ($attr ? ' ' : '') . $attr .
+                ( $this->_xhtml ? ' /': '' ) // <br /> v. <br>
+                . '>';
+
+        } elseif ($token instanceof HTMLPurifier_Token_Text) {
+            return $this->escape($token->data, ENT_NOQUOTES);
+
+        } elseif ($token instanceof HTMLPurifier_Token_Comment) {
+            return '<!--' . $token->data . '-->';
+        } else {
+            return '';
+
+        }
+    }
+
+    /**
+     * Special case processor for the contents of script tags
+     * @warning This runs into problems if there's already a literal
+     *          --> somewhere inside the script contents.
+     */
+    public function generateScriptFromToken($token) {
+        if (!$token instanceof HTMLPurifier_Token_Text) return $this->generateFromToken($token);
+        // Thanks <http://lachy.id.au/log/2005/05/script-comments>
+        $data = preg_replace('#//\s*$#', '', $token->data);
+        return '<!--//--><![CDATA[//><!--' . "\n" . trim($data) . "\n" . '//--><!]]>';
+    }
+
+    /**
+     * Generates attribute declarations from attribute array.
+     * @note This does not include the leading or trailing space.
+     * @param $assoc_array_of_attributes Attribute array
+     * @param $element Name of element attributes are for, used to check
+     *        attribute minimization.
+     * @return Generate HTML fragment for insertion.
+     */
+    public function generateAttributes($assoc_array_of_attributes, $element = false) {
+        $html = '';
+        if ($this->_sortAttr) ksort($assoc_array_of_attributes);
+        foreach ($assoc_array_of_attributes as $key => $value) {
+            if (!$this->_xhtml) {
+                // Remove namespaced attributes
+                if (strpos($key, ':') !== false) continue;
+                // Check if we should minimize the attribute: val="val" -> val
+                if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) {
+                    $html .= $key . ' ';
+                    continue;
+                }
+            }
+            // Workaround for Internet Explorer innerHTML bug.
+            // Essentially, Internet Explorer, when calculating
+            // innerHTML, omits quotes if there are no instances of
+            // angled brackets, quotes or spaces.  However, when parsing
+            // HTML (for example, when you assign to innerHTML), it
+            // treats backticks as quotes.  Thus,
+            //      <img alt="``" />
+            // becomes
+            //      <img alt=`` />
+            // becomes
+            //      <img alt='' />
+            // Fortunately, all we need to do is trigger an appropriate
+            // quoting style, which we do by adding an extra space.
+            // This also is consistent with the W3C spec, which states
+            // that user agents may ignore leading or trailing
+            // whitespace (in fact, most don't, at least for attributes
+            // like alt, but an extra space at the end is barely
+            // noticeable).  Still, we have a configuration knob for
+            // this, since this transformation is not necesary if you
+            // don't process user input with innerHTML or you don't plan
+            // on supporting Internet Explorer.
+            if ($this->_innerHTMLFix) {
+                if (strpos($value, '`') !== false) {
+                    // check if correct quoting style would not already be
+                    // triggered
+                    if (strcspn($value, '"\' <>') === strlen($value)) {
+                        // protect!
+                        $value .= ' ';
+                    }
+                }
+            }
+            $html .= $key.'="'.$this->escape($value).'" ';
+        }
+        return rtrim($html);
+    }
+
+    /**
+     * Escapes raw text data.
+     * @todo This really ought to be protected, but until we have a facility
+     *       for properly generating HTML here w/o using tokens, it stays
+     *       public.
+     * @param $string String data to escape for HTML.
+     * @param $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is
+     *               permissible for non-attribute output.
+     * @return String escaped data.
+     */
+    public function escape($string, $quote = null) {
+        // Workaround for APC bug on Mac Leopard reported by sidepodcast
+        // http://htmlpurifier.org/phorum/read.php?3,4823,4846
+        if ($quote === null) $quote = ENT_COMPAT;
+        return htmlspecialchars($string, $quote, 'UTF-8');
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLDefinition.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLDefinition.php
new file mode 100644
index 0000000..b079d44
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLDefinition.php
@@ -0,0 +1,425 @@
+<?php
+
+/**
+ * Definition of the purified HTML that describes allowed children,
+ * attributes, and many other things.
+ *
+ * Conventions:
+ *
+ * All member variables that are prefixed with info
+ * (including the main $info array) are used by HTML Purifier internals
+ * and should not be directly edited when customizing the HTMLDefinition.
+ * They can usually be set via configuration directives or custom
+ * modules.
+ *
+ * On the other hand, member variables without the info prefix are used
+ * internally by the HTMLDefinition and MUST NOT be used by other HTML
+ * Purifier internals. Many of them, however, are public, and may be
+ * edited by userspace code to tweak the behavior of HTMLDefinition.
+ *
+ * @note This class is inspected by Printer_HTMLDefinition; please
+ *       update that class if things here change.
+ *
+ * @warning Directives that change this object's structure must be in
+ *          the HTML or Attr namespace!
+ */
+class HTMLPurifier_HTMLDefinition extends HTMLPurifier_Definition
+{
+
+    // FULLY-PUBLIC VARIABLES ---------------------------------------------
+
+    /**
+     * Associative array of element names to HTMLPurifier_ElementDef
+     */
+    public $info = array();
+
+    /**
+     * Associative array of global attribute name to attribute definition.
+     */
+    public $info_global_attr = array();
+
+    /**
+     * String name of parent element HTML will be going into.
+     */
+    public $info_parent = 'div';
+
+    /**
+     * Definition for parent element, allows parent element to be a
+     * tag that's not allowed inside the HTML fragment.
+     */
+    public $info_parent_def;
+
+    /**
+     * String name of element used to wrap inline elements in block context
+     * @note This is rarely used except for BLOCKQUOTEs in strict mode
+     */
+    public $info_block_wrapper = 'p';
+
+    /**
+     * Associative array of deprecated tag name to HTMLPurifier_TagTransform
+     */
+    public $info_tag_transform = array();
+
+    /**
+     * Indexed list of HTMLPurifier_AttrTransform to be performed before validation.
+     */
+    public $info_attr_transform_pre = array();
+
+    /**
+     * Indexed list of HTMLPurifier_AttrTransform to be performed after validation.
+     */
+    public $info_attr_transform_post = array();
+
+    /**
+     * Nested lookup array of content set name (Block, Inline) to
+     * element name to whether or not it belongs in that content set.
+     */
+    public $info_content_sets = array();
+
+    /**
+     * Indexed list of HTMLPurifier_Injector to be used.
+     */
+    public $info_injector = array();
+
+    /**
+     * Doctype object
+     */
+    public $doctype;
+
+
+
+    // RAW CUSTOMIZATION STUFF --------------------------------------------
+
+    /**
+     * Adds a custom attribute to a pre-existing element
+     * @note This is strictly convenience, and does not have a corresponding
+     *       method in HTMLPurifier_HTMLModule
+     * @param $element_name String element name to add attribute to
+     * @param $attr_name String name of attribute
+     * @param $def Attribute definition, can be string or object, see
+     *             HTMLPurifier_AttrTypes for details
+     */
+    public function addAttribute($element_name, $attr_name, $def) {
+        $module = $this->getAnonymousModule();
+        if (!isset($module->info[$element_name])) {
+            $element = $module->addBlankElement($element_name);
+        } else {
+            $element = $module->info[$element_name];
+        }
+        $element->attr[$attr_name] = $def;
+    }
+
+    /**
+     * Adds a custom element to your HTML definition
+     * @note See HTMLPurifier_HTMLModule::addElement for detailed
+     *       parameter and return value descriptions.
+     */
+    public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) {
+        $module = $this->getAnonymousModule();
+        // assume that if the user is calling this, the element
+        // is safe. This may not be a good idea
+        $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes);
+        return $element;
+    }
+
+    /**
+     * Adds a blank element to your HTML definition, for overriding
+     * existing behavior
+     * @note See HTMLPurifier_HTMLModule::addBlankElement for detailed
+     *       parameter and return value descriptions.
+     */
+    public function addBlankElement($element_name) {
+        $module  = $this->getAnonymousModule();
+        $element = $module->addBlankElement($element_name);
+        return $element;
+    }
+
+    /**
+     * Retrieves a reference to the anonymous module, so you can
+     * bust out advanced features without having to make your own
+     * module.
+     */
+    public function getAnonymousModule() {
+        if (!$this->_anonModule) {
+            $this->_anonModule = new HTMLPurifier_HTMLModule();
+            $this->_anonModule->name = 'Anonymous';
+        }
+        return $this->_anonModule;
+    }
+
+    private $_anonModule = null;
+
+
+    // PUBLIC BUT INTERNAL VARIABLES --------------------------------------
+
+    public $type = 'HTML';
+    public $manager; /**< Instance of HTMLPurifier_HTMLModuleManager */
+
+    /**
+     * Performs low-cost, preliminary initialization.
+     */
+    public function __construct() {
+        $this->manager = new HTMLPurifier_HTMLModuleManager();
+    }
+
+    protected function doSetup($config) {
+        $this->processModules($config);
+        $this->setupConfigStuff($config);
+        unset($this->manager);
+
+        // cleanup some of the element definitions
+        foreach ($this->info as $k => $v) {
+            unset($this->info[$k]->content_model);
+            unset($this->info[$k]->content_model_type);
+        }
+    }
+
+    /**
+     * Extract out the information from the manager
+     */
+    protected function processModules($config) {
+
+        if ($this->_anonModule) {
+            // for user specific changes
+            // this is late-loaded so we don't have to deal with PHP4
+            // reference wonky-ness
+            $this->manager->addModule($this->_anonModule);
+            unset($this->_anonModule);
+        }
+
+        $this->manager->setup($config);
+        $this->doctype = $this->manager->doctype;
+
+        foreach ($this->manager->modules as $module) {
+            foreach($module->info_tag_transform as $k => $v) {
+                if ($v === false) unset($this->info_tag_transform[$k]);
+                else $this->info_tag_transform[$k] = $v;
+            }
+            foreach($module->info_attr_transform_pre as $k => $v) {
+                if ($v === false) unset($this->info_attr_transform_pre[$k]);
+                else $this->info_attr_transform_pre[$k] = $v;
+            }
+            foreach($module->info_attr_transform_post as $k => $v) {
+                if ($v === false) unset($this->info_attr_transform_post[$k]);
+                else $this->info_attr_transform_post[$k] = $v;
+            }
+            foreach ($module->info_injector as $k => $v) {
+                if ($v === false) unset($this->info_injector[$k]);
+                else $this->info_injector[$k] = $v;
+            }
+        }
+
+        $this->info = $this->manager->getElements();
+        $this->info_content_sets = $this->manager->contentSets->lookup;
+
+    }
+
+    /**
+     * Sets up stuff based on config. We need a better way of doing this.
+     */
+    protected function setupConfigStuff($config) {
+
+        $block_wrapper = $config->get('HTML.BlockWrapper');
+        if (isset($this->info_content_sets['Block'][$block_wrapper])) {
+            $this->info_block_wrapper = $block_wrapper;
+        } else {
+            trigger_error('Cannot use non-block element as block wrapper',
+                E_USER_ERROR);
+        }
+
+        $parent = $config->get('HTML.Parent');
+        $def = $this->manager->getElement($parent, true);
+        if ($def) {
+            $this->info_parent = $parent;
+            $this->info_parent_def = $def;
+        } else {
+            trigger_error('Cannot use unrecognized element as parent',
+                E_USER_ERROR);
+            $this->info_parent_def = $this->manager->getElement($this->info_parent, true);
+        }
+
+        // support template text
+        $support = "(for information on implementing this, see the ".
+                   "support forums) ";
+
+        // setup allowed elements -----------------------------------------
+
+        $allowed_elements = $config->get('HTML.AllowedElements');
+        $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early
+
+        if (!is_array($allowed_elements) && !is_array($allowed_attributes)) {
+            $allowed = $config->get('HTML.Allowed');
+            if (is_string($allowed)) {
+                list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed);
+            }
+        }
+
+        if (is_array($allowed_elements)) {
+            foreach ($this->info as $name => $d) {
+                if(!isset($allowed_elements[$name])) unset($this->info[$name]);
+                unset($allowed_elements[$name]);
+            }
+            // emit errors
+            foreach ($allowed_elements as $element => $d) {
+                $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful!
+                trigger_error("Element '$element' is not supported $support", E_USER_WARNING);
+            }
+        }
+
+        // setup allowed attributes ---------------------------------------
+
+        $allowed_attributes_mutable = $allowed_attributes; // by copy!
+        if (is_array($allowed_attributes)) {
+
+            // This actually doesn't do anything, since we went away from
+            // global attributes. It's possible that userland code uses
+            // it, but HTMLModuleManager doesn't!
+            foreach ($this->info_global_attr as $attr => $x) {
+                $keys = array($attr, "*@$attr", "*.$attr");
+                $delete = true;
+                foreach ($keys as $key) {
+                    if ($delete && isset($allowed_attributes[$key])) {
+                        $delete = false;
+                    }
+                    if (isset($allowed_attributes_mutable[$key])) {
+                        unset($allowed_attributes_mutable[$key]);
+                    }
+                }
+                if ($delete) unset($this->info_global_attr[$attr]);
+            }
+
+            foreach ($this->info as $tag => $info) {
+                foreach ($info->attr as $attr => $x) {
+                    $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr");
+                    $delete = true;
+                    foreach ($keys as $key) {
+                        if ($delete && isset($allowed_attributes[$key])) {
+                            $delete = false;
+                        }
+                        if (isset($allowed_attributes_mutable[$key])) {
+                            unset($allowed_attributes_mutable[$key]);
+                        }
+                    }
+                    if ($delete) {
+                        if ($this->info[$tag]->attr[$attr]->required) {
+                            trigger_error("Required attribute '$attr' in element '$tag' was not allowed, which means '$tag' will not be allowed either", E_USER_WARNING);
+                        }
+                        unset($this->info[$tag]->attr[$attr]);
+                    }
+                }
+            }
+            // emit errors
+            foreach ($allowed_attributes_mutable as $elattr => $d) {
+                $bits = preg_split('/[.@]/', $elattr, 2);
+                $c = count($bits);
+                switch ($c) {
+                    case 2:
+                        if ($bits[0] !== '*') {
+                            $element = htmlspecialchars($bits[0]);
+                            $attribute = htmlspecialchars($bits[1]);
+                            if (!isset($this->info[$element])) {
+                                trigger_error("Cannot allow attribute '$attribute' if element '$element' is not allowed/supported $support");
+                            } else {
+                                trigger_error("Attribute '$attribute' in element '$element' not supported $support",
+                                    E_USER_WARNING);
+                            }
+                            break;
+                        }
+                        // otherwise fall through
+                    case 1:
+                        $attribute = htmlspecialchars($bits[0]);
+                        trigger_error("Global attribute '$attribute' is not ".
+                            "supported in any elements $support",
+                            E_USER_WARNING);
+                        break;
+                }
+            }
+
+        }
+
+        // setup forbidden elements ---------------------------------------
+
+        $forbidden_elements   = $config->get('HTML.ForbiddenElements');
+        $forbidden_attributes = $config->get('HTML.ForbiddenAttributes');
+
+        foreach ($this->info as $tag => $info) {
+            if (isset($forbidden_elements[$tag])) {
+                unset($this->info[$tag]);
+                continue;
+            }
+            foreach ($info->attr as $attr => $x) {
+                if (
+                    isset($forbidden_attributes["$tag@$attr"]) ||
+                    isset($forbidden_attributes["*@$attr"]) ||
+                    isset($forbidden_attributes[$attr])
+                ) {
+                    unset($this->info[$tag]->attr[$attr]);
+                    continue;
+                } // this segment might get removed eventually
+                elseif (isset($forbidden_attributes["$tag.$attr"])) {
+                    // $tag.$attr are not user supplied, so no worries!
+                    trigger_error("Error with $tag.$attr: tag.attr syntax not supported for HTML.ForbiddenAttributes; use tag at attr instead", E_USER_WARNING);
+                }
+            }
+        }
+        foreach ($forbidden_attributes as $key => $v) {
+            if (strlen($key) < 2) continue;
+            if ($key[0] != '*') continue;
+            if ($key[1] == '.') {
+                trigger_error("Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", E_USER_WARNING);
+            }
+        }
+
+        // setup injectors -----------------------------------------------------
+        foreach ($this->info_injector as $i => $injector) {
+            if ($injector->checkNeeded($config) !== false) {
+                // remove injector that does not have it's required
+                // elements/attributes present, and is thus not needed.
+                unset($this->info_injector[$i]);
+            }
+        }
+    }
+
+    /**
+     * Parses a TinyMCE-flavored Allowed Elements and Attributes list into
+     * separate lists for processing. Format is element[attr1|attr2],element2...
+     * @warning Although it's largely drawn from TinyMCE's implementation,
+     *      it is different, and you'll probably have to modify your lists
+     * @param $list String list to parse
+     * @param array($allowed_elements, $allowed_attributes)
+     * @todo Give this its own class, probably static interface
+     */
+    public function parseTinyMCEAllowedList($list) {
+
+        $list = str_replace(array(' ', "\t"), '', $list);
+
+        $elements = array();
+        $attributes = array();
+
+        $chunks = preg_split('/(,|[\n\r]+)/', $list);
+        foreach ($chunks as $chunk) {
+            if (empty($chunk)) continue;
+            // remove TinyMCE element control characters
+            if (!strpos($chunk, '[')) {
+                $element = $chunk;
+                $attr = false;
+            } else {
+                list($element, $attr) = explode('[', $chunk);
+            }
+            if ($element !== '*') $elements[$element] = true;
+            if (!$attr) continue;
+            $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ]
+            $attr = explode('|', $attr);
+            foreach ($attr as $key) {
+                $attributes["$element.$key"] = true;
+            }
+        }
+
+        return array($elements, $attributes);
+
+    }
+
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule.php
new file mode 100644
index 0000000..072cf68
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule.php
@@ -0,0 +1,244 @@
+<?php
+
+/**
+ * Represents an XHTML 1.1 module, with information on elements, tags
+ * and attributes.
+ * @note Even though this is technically XHTML 1.1, it is also used for
+ *       regular HTML parsing. We are using modulization as a convenient
+ *       way to represent the internals of HTMLDefinition, and our
+ *       implementation is by no means conforming and does not directly
+ *       use the normative DTDs or XML schemas.
+ * @note The public variables in a module should almost directly
+ *       correspond to the variables in HTMLPurifier_HTMLDefinition.
+ *       However, the prefix info carries no special meaning in these
+ *       objects (include it anyway if that's the correspondence though).
+ * @todo Consider making some member functions protected
+ */
+
+class HTMLPurifier_HTMLModule
+{
+
+    // -- Overloadable ----------------------------------------------------
+
+    /**
+     * Short unique string identifier of the module
+     */
+    public $name;
+
+    /**
+     * Informally, a list of elements this module changes. Not used in
+     * any significant way.
+     */
+    public $elements = array();
+
+    /**
+     * Associative array of element names to element definitions.
+     * Some definitions may be incomplete, to be merged in later
+     * with the full definition.
+     */
+    public $info = array();
+
+    /**
+     * Associative array of content set names to content set additions.
+     * This is commonly used to, say, add an A element to the Inline
+     * content set. This corresponds to an internal variable $content_sets
+     * and NOT info_content_sets member variable of HTMLDefinition.
+     */
+    public $content_sets = array();
+
+    /**
+     * Associative array of attribute collection names to attribute
+     * collection additions. More rarely used for adding attributes to
+     * the global collections. Example is the StyleAttribute module adding
+     * the style attribute to the Core. Corresponds to HTMLDefinition's
+     * attr_collections->info, since the object's data is only info,
+     * with extra behavior associated with it.
+     */
+    public $attr_collections = array();
+
+    /**
+     * Associative array of deprecated tag name to HTMLPurifier_TagTransform
+     */
+    public $info_tag_transform = array();
+
+    /**
+     * List of HTMLPurifier_AttrTransform to be performed before validation.
+     */
+    public $info_attr_transform_pre = array();
+
+    /**
+     * List of HTMLPurifier_AttrTransform to be performed after validation.
+     */
+    public $info_attr_transform_post = array();
+
+    /**
+     * List of HTMLPurifier_Injector to be performed during well-formedness fixing.
+     * An injector will only be invoked if all of it's pre-requisites are met;
+     * if an injector fails setup, there will be no error; it will simply be
+     * silently disabled.
+     */
+    public $info_injector = array();
+
+    /**
+     * Boolean flag that indicates whether or not getChildDef is implemented.
+     * For optimization reasons: may save a call to a function. Be sure
+     * to set it if you do implement getChildDef(), otherwise it will have
+     * no effect!
+     */
+    public $defines_child_def = false;
+
+    /**
+     * Boolean flag whether or not this module is safe. If it is not safe, all
+     * of its members are unsafe. Modules are safe by default (this might be
+     * slightly dangerous, but it doesn't make much sense to force HTML Purifier,
+     * which is based off of safe HTML, to explicitly say, "This is safe," even
+     * though there are modules which are "unsafe")
+     *
+     * @note Previously, safety could be applied at an element level granularity.
+     *       We've removed this ability, so in order to add "unsafe" elements
+     *       or attributes, a dedicated module with this property set to false
+     *       must be used.
+     */
+    public $safe = true;
+
+    /**
+     * Retrieves a proper HTMLPurifier_ChildDef subclass based on
+     * content_model and content_model_type member variables of
+     * the HTMLPurifier_ElementDef class. There is a similar function
+     * in HTMLPurifier_HTMLDefinition.
+     * @param $def HTMLPurifier_ElementDef instance
+     * @return HTMLPurifier_ChildDef subclass
+     */
+    public function getChildDef($def) {return false;}
+
+    // -- Convenience -----------------------------------------------------
+
+    /**
+     * Convenience function that sets up a new element
+     * @param $element Name of element to add
+     * @param $type What content set should element be registered to?
+     *              Set as false to skip this step.
+     * @param $contents Allowed children in form of:
+     *              "$content_model_type: $content_model"
+     * @param $attr_includes What attribute collections to register to
+     *              element?
+     * @param $attr What unique attributes does the element define?
+     * @note See ElementDef for in-depth descriptions of these parameters.
+     * @return Created element definition object, so you
+     *         can set advanced parameters
+     */
+    public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()) {
+        $this->elements[] = $element;
+        // parse content_model
+        list($content_model_type, $content_model) = $this->parseContents($contents);
+        // merge in attribute inclusions
+        $this->mergeInAttrIncludes($attr, $attr_includes);
+        // add element to content sets
+        if ($type) $this->addElementToContentSet($element, $type);
+        // create element
+        $this->info[$element] = HTMLPurifier_ElementDef::create(
+            $content_model, $content_model_type, $attr
+        );
+        // literal object $contents means direct child manipulation
+        if (!is_string($contents)) $this->info[$element]->child = $contents;
+        return $this->info[$element];
+    }
+
+    /**
+     * Convenience function that creates a totally blank, non-standalone
+     * element.
+     * @param $element Name of element to create
+     * @return Created element
+     */
+    public function addBlankElement($element) {
+        if (!isset($this->info[$element])) {
+            $this->elements[] = $element;
+            $this->info[$element] = new HTMLPurifier_ElementDef();
+            $this->info[$element]->standalone = false;
+        } else {
+            trigger_error("Definition for $element already exists in module, cannot redefine");
+        }
+        return $this->info[$element];
+    }
+
+    /**
+     * Convenience function that registers an element to a content set
+     * @param Element to register
+     * @param Name content set (warning: case sensitive, usually upper-case
+     *        first letter)
+     */
+    public function addElementToContentSet($element, $type) {
+        if (!isset($this->content_sets[$type])) $this->content_sets[$type] = '';
+        else $this->content_sets[$type] .= ' | ';
+        $this->content_sets[$type] .= $element;
+    }
+
+    /**
+     * Convenience function that transforms single-string contents
+     * into separate content model and content model type
+     * @param $contents Allowed children in form of:
+     *                  "$content_model_type: $content_model"
+     * @note If contents is an object, an array of two nulls will be
+     *       returned, and the callee needs to take the original $contents
+     *       and use it directly.
+     */
+    public function parseContents($contents) {
+        if (!is_string($contents)) return array(null, null); // defer
+        switch ($contents) {
+            // check for shorthand content model forms
+            case 'Empty':
+                return array('empty', '');
+            case 'Inline':
+                return array('optional', 'Inline | #PCDATA');
+            case 'Flow':
+                return array('optional', 'Flow | #PCDATA');
+        }
+        list($content_model_type, $content_model) = explode(':', $contents);
+        $content_model_type = strtolower(trim($content_model_type));
+        $content_model = trim($content_model);
+        return array($content_model_type, $content_model);
+    }
+
+    /**
+     * Convenience function that merges a list of attribute includes into
+     * an attribute array.
+     * @param $attr Reference to attr array to modify
+     * @param $attr_includes Array of includes / string include to merge in
+     */
+    public function mergeInAttrIncludes(&$attr, $attr_includes) {
+        if (!is_array($attr_includes)) {
+            if (empty($attr_includes)) $attr_includes = array();
+            else $attr_includes = array($attr_includes);
+        }
+        $attr[0] = $attr_includes;
+    }
+
+    /**
+     * Convenience function that generates a lookup table with boolean
+     * true as value.
+     * @param $list List of values to turn into a lookup
+     * @note You can also pass an arbitrary number of arguments in
+     *       place of the regular argument
+     * @return Lookup array equivalent of list
+     */
+    public function makeLookup($list) {
+        if (is_string($list)) $list = func_get_args();
+        $ret = array();
+        foreach ($list as $value) {
+            if (is_null($value)) continue;
+            $ret[$value] = true;
+        }
+        return $ret;
+    }
+
+    /**
+     * Lazy load construction of the module after determining whether
+     * or not it's needed, and also when a finalized configuration object
+     * is available.
+     * @param $config Instance of HTMLPurifier_Config
+     */
+    public function setup($config) {}
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php
new file mode 100644
index 0000000..23ac3da
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Bdo.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * XHTML 1.1 Bi-directional Text Module, defines elements that
+ * declare directionality of content. Text Extension Module.
+ */
+class HTMLPurifier_HTMLModule_Bdo extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Bdo';
+    public $attr_collections = array(
+        'I18N' => array('dir' => false)
+    );
+
+    public function setup($config) {
+        $bdo = $this->addElement(
+            'bdo', 'Inline', 'Inline', array('Core', 'Lang'),
+            array(
+                'dir' => 'Enum#ltr,rtl', // required
+                // The Abstract Module specification has the attribute
+                // inclusions wrong for bdo: bdo allows Lang
+            )
+        );
+        $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir();
+
+        $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl';
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/CommonAttributes.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/CommonAttributes.php
new file mode 100644
index 0000000..7c15da8
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/CommonAttributes.php
@@ -0,0 +1,26 @@
+<?php
+
+class HTMLPurifier_HTMLModule_CommonAttributes extends HTMLPurifier_HTMLModule
+{
+    public $name = 'CommonAttributes';
+
+    public $attr_collections = array(
+        'Core' => array(
+            0 => array('Style'),
+            // 'xml:space' => false,
+            'class' => 'Class',
+            'id' => 'ID',
+            'title' => 'CDATA',
+        ),
+        'Lang' => array(),
+        'I18N' => array(
+            0 => array('Lang'), // proprietary, for xml:lang/lang
+        ),
+        'Common' => array(
+            0 => array('Core', 'I18N')
+        )
+    );
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php
new file mode 100644
index 0000000..ff93690
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Edit.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
+ * Module.
+ */
+class HTMLPurifier_HTMLModule_Edit extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Edit';
+
+    public function setup($config) {
+        $contents = 'Chameleon: #PCDATA | Inline ! #PCDATA | Flow';
+        $attr = array(
+            'cite' => 'URI',
+            // 'datetime' => 'Datetime', // not implemented
+        );
+        $this->addElement('del', 'Inline', $contents, 'Common', $attr);
+        $this->addElement('ins', 'Inline', $contents, 'Common', $attr);
+    }
+
+    // HTML 4.01 specifies that ins/del must not contain block
+    // elements when used in an inline context, chameleon is
+    // a complicated workaround to acheive this effect
+
+    // Inline context ! Block context (exclamation mark is
+    // separator, see getChildDef for parsing)
+
+    public $defines_child_def = true;
+    public function getChildDef($def) {
+        if ($def->content_model_type != 'chameleon') return false;
+        $value = explode('!', $def->content_model);
+        return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Forms.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Forms.php
new file mode 100644
index 0000000..b963529
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Forms.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * XHTML 1.1 Forms module, defines all form-related elements found in HTML 4.
+ */
+class HTMLPurifier_HTMLModule_Forms extends HTMLPurifier_HTMLModule
+{
+    public $name = 'Forms';
+    public $safe = false;
+
+    public $content_sets = array(
+        'Block' => 'Form',
+        'Inline' => 'Formctrl',
+    );
+
+    public function setup($config) {
+        $form = $this->addElement('form', 'Form',
+          'Required: Heading | List | Block | fieldset', 'Common', array(
+            'accept' => 'ContentTypes',
+            'accept-charset' => 'Charsets',
+            'action*' => 'URI',
+            'method' => 'Enum#get,post',
+            // really ContentType, but these two are the only ones used today
+            'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data',
+        ));
+        $form->excludes = array('form' => true);
+
+        $input = $this->addElement('input', 'Formctrl', 'Empty', 'Common', array(
+            'accept' => 'ContentTypes',
+            'accesskey' => 'Character',
+            'alt' => 'Text',
+            'checked' => 'Bool#checked',
+            'disabled' => 'Bool#disabled',
+            'maxlength' => 'Number',
+            'name' => 'CDATA',
+            'readonly' => 'Bool#readonly',
+            'size' => 'Number',
+            'src' => 'URI#embedded',
+            'tabindex' => 'Number',
+            'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image',
+            'value' => 'CDATA',
+        ));
+        $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input();
+
+        $this->addElement('select', 'Formctrl', 'Required: optgroup | option', 'Common', array(
+            'disabled' => 'Bool#disabled',
+            'multiple' => 'Bool#multiple',
+            'name' => 'CDATA',
+            'size' => 'Number',
+            'tabindex' => 'Number',
+        ));
+
+        $this->addElement('option', false, 'Optional: #PCDATA', 'Common', array(
+            'disabled' => 'Bool#disabled',
+            'label' => 'Text',
+            'selected' => 'Bool#selected',
+            'value' => 'CDATA',
+        ));
+        // It's illegal for there to be more than one selected, but not
+        // be multiple. Also, no selected means undefined behavior. This might
+        // be difficult to implement; perhaps an injector, or a context variable.
+
+        $textarea = $this->addElement('textarea', 'Formctrl', 'Optional: #PCDATA', 'Common', array(
+            'accesskey' => 'Character',
+            'cols*' => 'Number',
+            'disabled' => 'Bool#disabled',
+            'name' => 'CDATA',
+            'readonly' => 'Bool#readonly',
+            'rows*' => 'Number',
+            'tabindex' => 'Number',
+        ));
+        $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea();
+
+        $button = $this->addElement('button', 'Formctrl', 'Optional: #PCDATA | Heading | List | Block | Inline', 'Common', array(
+            'accesskey' => 'Character',
+            'disabled' => 'Bool#disabled',
+            'name' => 'CDATA',
+            'tabindex' => 'Number',
+            'type' => 'Enum#button,submit,reset',
+            'value' => 'CDATA',
+        ));
+
+        // For exclusions, ideally we'd specify content sets, not literal elements
+        $button->excludes = $this->makeLookup(
+            'form', 'fieldset', // Form
+            'input', 'select', 'textarea', 'label', 'button', // Formctrl
+            'a', // as per HTML 4.01 spec, this is omitted by modularization
+            'isindex', 'iframe' // legacy items
+        );
+
+        // Extra exclusion: img usemap="" is not permitted within this element.
+        // We'll omit this for now, since we don't have any good way of
+        // indicating it yet.
+
+        // This is HIGHLY user-unfriendly; we need a custom child-def for this
+        $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common');
+
+        $label = $this->addElement('label', 'Formctrl', 'Optional: #PCDATA | Inline', 'Common', array(
+            'accesskey' => 'Character',
+            // 'for' => 'IDREF', // IDREF not implemented, cannot allow
+        ));
+        $label->excludes = array('label' => true);
+
+        $this->addElement('legend', false, 'Optional: #PCDATA | Inline', 'Common', array(
+            'accesskey' => 'Character',
+        ));
+
+        $this->addElement('optgroup', false, 'Required: option', 'Common', array(
+            'disabled' => 'Bool#disabled',
+            'label*' => 'Text',
+        ));
+
+        // Don't forget an injector for <isindex>. This one's a little complex
+        // because it maps to multiple elements.
+
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php
new file mode 100644
index 0000000..d7e9bdd
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Hypertext.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * XHTML 1.1 Hypertext Module, defines hypertext links. Core Module.
+ */
+class HTMLPurifier_HTMLModule_Hypertext extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Hypertext';
+
+    public function setup($config) {
+        $a = $this->addElement(
+            'a', 'Inline', 'Inline', 'Common',
+            array(
+                // 'accesskey' => 'Character',
+                // 'charset' => 'Charset',
+                'href' => 'URI',
+                // 'hreflang' => 'LanguageCode',
+                'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'),
+                'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'),
+                // 'tabindex' => 'Number',
+                // 'type' => 'ContentType',
+            )
+        );
+        $a->formatting = true;
+        $a->excludes = array('a' => true);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Iframe.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Iframe.php
new file mode 100644
index 0000000..287071e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Iframe.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * XHTML 1.1 Iframe Module provides inline frames.
+ *
+ * @note This module is not considered safe unless an Iframe
+ * whitelisting mechanism is specified.  Currently, the only
+ * such mechanism is %URL.SafeIframeRegexp
+ */
+class HTMLPurifier_HTMLModule_Iframe extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Iframe';
+    public $safe = false;
+
+    public function setup($config) {
+        if ($config->get('HTML.SafeIframe')) {
+            $this->safe = true;
+        }
+        $this->addElement(
+            'iframe', 'Inline', 'Flow', 'Common',
+            array(
+                'src' => 'URI#embedded',
+                'width' => 'Length',
+                'height' => 'Length',
+                'name' => 'ID',
+                'scrolling' => 'Enum#yes,no,auto',
+                'frameborder' => 'Enum#0,1',
+                'longdesc' => 'URI',
+                'marginheight' => 'Pixels',
+                'marginwidth' => 'Pixels',
+            )
+        );
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Image.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Image.php
new file mode 100644
index 0000000..948d435
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Image.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * XHTML 1.1 Image Module provides basic image embedding.
+ * @note There is specialized code for removing empty images in
+ *       HTMLPurifier_Strategy_RemoveForeignElements
+ */
+class HTMLPurifier_HTMLModule_Image extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Image';
+
+    public function setup($config) {
+        $max = $config->get('HTML.MaxImgLength');
+        $img = $this->addElement(
+            'img', 'Inline', 'Empty', 'Common',
+            array(
+                'alt*' => 'Text',
+                // According to the spec, it's Length, but percents can
+                // be abused, so we allow only Pixels.
+                'height' => 'Pixels#' . $max,
+                'width'  => 'Pixels#' . $max,
+                'longdesc' => 'URI',
+                'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded
+            )
+        );
+        if ($max === null || $config->get('HTML.Trusted')) {
+            $img->attr['height'] =
+            $img->attr['width'] = 'Length';
+        }
+
+        // kind of strange, but splitting things up would be inefficient
+        $img->attr_transform_pre[] =
+        $img->attr_transform_post[] =
+            new HTMLPurifier_AttrTransform_ImgRequired();
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php
new file mode 100644
index 0000000..f278eec
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Legacy.php
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * XHTML 1.1 Legacy module defines elements that were previously
+ * deprecated.
+ *
+ * @note Not all legacy elements have been implemented yet, which
+ *       is a bit of a reverse problem as compared to browsers! In
+ *       addition, this legacy module may implement a bit more than
+ *       mandated by XHTML 1.1.
+ *
+ * This module can be used in combination with TransformToStrict in order
+ * to transform as many deprecated elements as possible, but retain
+ * questionably deprecated elements that do not have good alternatives
+ * as well as transform elements that don't have an implementation.
+ * See docs/ref-strictness.txt for more details.
+ */
+
+class HTMLPurifier_HTMLModule_Legacy extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Legacy';
+
+    public function setup($config) {
+
+        $this->addElement('basefont', 'Inline', 'Empty', false, array(
+            'color' => 'Color',
+            'face' => 'Text', // extremely broad, we should
+            'size' => 'Text', // tighten it
+            'id' => 'ID'
+        ));
+        $this->addElement('center', 'Block', 'Flow', 'Common');
+        $this->addElement('dir', 'Block', 'Required: li', 'Common', array(
+            'compact' => 'Bool#compact'
+        ));
+        $this->addElement('font', 'Inline', 'Inline', array('Core', 'I18N'), array(
+            'color' => 'Color',
+            'face' => 'Text', // extremely broad, we should
+            'size' => 'Text', // tighten it
+        ));
+        $this->addElement('menu', 'Block', 'Required: li', 'Common', array(
+            'compact' => 'Bool#compact'
+        ));
+
+        $s = $this->addElement('s', 'Inline', 'Inline', 'Common');
+        $s->formatting = true;
+
+        $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common');
+        $strike->formatting = true;
+
+        $u = $this->addElement('u', 'Inline', 'Inline', 'Common');
+        $u->formatting = true;
+
+        // setup modifications to old elements
+
+        $align = 'Enum#left,right,center,justify';
+
+        $address = $this->addBlankElement('address');
+        $address->content_model = 'Inline | #PCDATA | p';
+        $address->content_model_type = 'optional';
+        $address->child = false;
+
+        $blockquote = $this->addBlankElement('blockquote');
+        $blockquote->content_model = 'Flow | #PCDATA';
+        $blockquote->content_model_type = 'optional';
+        $blockquote->child = false;
+
+        $br = $this->addBlankElement('br');
+        $br->attr['clear'] = 'Enum#left,all,right,none';
+
+        $caption = $this->addBlankElement('caption');
+        $caption->attr['align'] = 'Enum#top,bottom,left,right';
+
+        $div = $this->addBlankElement('div');
+        $div->attr['align'] = $align;
+
+        $dl = $this->addBlankElement('dl');
+        $dl->attr['compact'] = 'Bool#compact';
+
+        for ($i = 1; $i <= 6; $i++) {
+            $h = $this->addBlankElement("h$i");
+            $h->attr['align'] = $align;
+        }
+
+        $hr = $this->addBlankElement('hr');
+        $hr->attr['align'] = $align;
+        $hr->attr['noshade'] = 'Bool#noshade';
+        $hr->attr['size'] = 'Pixels';
+        $hr->attr['width'] = 'Length';
+
+        $img = $this->addBlankElement('img');
+        $img->attr['align'] = 'IAlign';
+        $img->attr['border'] = 'Pixels';
+        $img->attr['hspace'] = 'Pixels';
+        $img->attr['vspace'] = 'Pixels';
+
+        // figure out this integer business
+
+        $li = $this->addBlankElement('li');
+        $li->attr['value'] = new HTMLPurifier_AttrDef_Integer();
+        $li->attr['type']  = 'Enum#s:1,i,I,a,A,disc,square,circle';
+
+        $ol = $this->addBlankElement('ol');
+        $ol->attr['compact'] = 'Bool#compact';
+        $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer();
+        $ol->attr['type'] = 'Enum#s:1,i,I,a,A';
+
+        $p = $this->addBlankElement('p');
+        $p->attr['align'] = $align;
+
+        $pre = $this->addBlankElement('pre');
+        $pre->attr['width'] = 'Number';
+
+        // script omitted
+
+        $table = $this->addBlankElement('table');
+        $table->attr['align'] = 'Enum#left,center,right';
+        $table->attr['bgcolor'] = 'Color';
+
+        $tr = $this->addBlankElement('tr');
+        $tr->attr['bgcolor'] = 'Color';
+
+        $th = $this->addBlankElement('th');
+        $th->attr['bgcolor'] = 'Color';
+        $th->attr['height'] = 'Length';
+        $th->attr['nowrap'] = 'Bool#nowrap';
+        $th->attr['width'] = 'Length';
+
+        $td = $this->addBlankElement('td');
+        $td->attr['bgcolor'] = 'Color';
+        $td->attr['height'] = 'Length';
+        $td->attr['nowrap'] = 'Bool#nowrap';
+        $td->attr['width'] = 'Length';
+
+        $ul = $this->addBlankElement('ul');
+        $ul->attr['compact'] = 'Bool#compact';
+        $ul->attr['type'] = 'Enum#square,disc,circle';
+
+        // "safe" modifications to "unsafe" elements
+        // WARNING: If you want to add support for an unsafe, legacy
+        // attribute, make a new TrustedLegacy module with the trusted
+        // bit set appropriately
+
+        $form = $this->addBlankElement('form');
+        $form->content_model = 'Flow | #PCDATA';
+        $form->content_model_type = 'optional';
+        $form->attr['target'] = 'FrameTarget';
+
+        $input = $this->addBlankElement('input');
+        $input->attr['align'] = 'IAlign';
+
+        $legend = $this->addBlankElement('legend');
+        $legend->attr['align'] = 'LAlign';
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/List.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/List.php
new file mode 100644
index 0000000..79ccefa
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/List.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * XHTML 1.1 List Module, defines list-oriented elements. Core Module.
+ */
+class HTMLPurifier_HTMLModule_List extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'List';
+
+    // According to the abstract schema, the List content set is a fully formed
+    // one or more expr, but it invariably occurs in an optional declaration
+    // so we're not going to do that subtlety. It might cause trouble
+    // if a user defines "List" and expects that multiple lists are
+    // allowed to be specified, but then again, that's not very intuitive.
+    // Furthermore, the actual XML Schema may disagree. Regardless,
+    // we don't have support for such nested expressions without using
+    // the incredibly inefficient and draconic Custom ChildDef.
+
+    public $content_sets = array('Flow' => 'List');
+
+    public function setup($config) {
+        $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
+        $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common');
+        // XXX The wrap attribute is handled by MakeWellFormed.  This is all
+        // quite unsatisfactory, because we generated this
+        // *specifically* for lists, and now a big chunk of the handling
+        // is done properly by the List ChildDef.  So actually, we just
+        // want enough information to make autoclosing work properly,
+        // and then hand off the tricky stuff to the ChildDef.
+        $ol->wrap = 'li';
+        $ul->wrap = 'li';
+        $this->addElement('dl', 'List', 'Required: dt | dd', 'Common');
+
+        $this->addElement('li', false, 'Flow', 'Common');
+
+        $this->addElement('dd', false, 'Flow', 'Common');
+        $this->addElement('dt', false, 'Inline', 'Common');
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Name.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Name.php
new file mode 100644
index 0000000..3a1271a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Name.php
@@ -0,0 +1,21 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Name extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Name';
+
+    public function setup($config) {
+        $elements = array('a', 'applet', 'form', 'frame', 'iframe', 'img', 'map');
+        foreach ($elements as $name) {
+            $element = $this->addBlankElement($name);
+            $element->attr['name'] = 'CDATA';
+            if (!$config->get('HTML.Attr.Name.UseCDATA')) {
+                $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync();
+            }
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Nofollow.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Nofollow.php
new file mode 100644
index 0000000..3aa6654
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Nofollow.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Module adds the nofollow attribute transformation to a tags.  It
+ * is enabled by HTML.Nofollow
+ */
+class HTMLPurifier_HTMLModule_Nofollow extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Nofollow';
+
+    public function setup($config) {
+        $a = $this->addBlankElement('a');
+        $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow();
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php
new file mode 100644
index 0000000..5f1b14a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php
@@ -0,0 +1,14 @@
+<?php
+
+class HTMLPurifier_HTMLModule_NonXMLCommonAttributes extends HTMLPurifier_HTMLModule
+{
+    public $name = 'NonXMLCommonAttributes';
+
+    public $attr_collections = array(
+        'Lang' => array(
+            'lang' => 'LanguageCode',
+        )
+    );
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Object.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Object.php
new file mode 100644
index 0000000..193c101
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Object.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * XHTML 1.1 Object Module, defines elements for generic object inclusion
+ * @warning Users will commonly use <embed> to cater to legacy browsers: this
+ *      module does not allow this sort of behavior
+ */
+class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Object';
+    public $safe = false;
+
+    public function setup($config) {
+
+        $this->addElement('object', 'Inline', 'Optional: #PCDATA | Flow | param', 'Common',
+            array(
+                'archive' => 'URI',
+                'classid' => 'URI',
+                'codebase' => 'URI',
+                'codetype' => 'Text',
+                'data' => 'URI',
+                'declare' => 'Bool#declare',
+                'height' => 'Length',
+                'name' => 'CDATA',
+                'standby' => 'Text',
+                'tabindex' => 'Number',
+                'type' => 'ContentType',
+                'width' => 'Length'
+            )
+        );
+
+        $this->addElement('param', false, 'Empty', false,
+            array(
+                'id' => 'ID',
+                'name*' => 'Text',
+                'type' => 'Text',
+                'value' => 'Text',
+                'valuetype' => 'Enum#data,ref,object'
+           )
+        );
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php
new file mode 100644
index 0000000..8ff0b5e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Presentation.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * XHTML 1.1 Presentation Module, defines simple presentation-related
+ * markup. Text Extension Module.
+ * @note The official XML Schema and DTD specs further divide this into
+ *       two modules:
+ *          - Block Presentation (hr)
+ *          - Inline Presentation (b, big, i, small, sub, sup, tt)
+ *       We have chosen not to heed this distinction, as content_sets
+ *       provides satisfactory disambiguation.
+ */
+class HTMLPurifier_HTMLModule_Presentation extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Presentation';
+
+    public function setup($config) {
+        $this->addElement('hr',     'Block',  'Empty',  'Common');
+        $this->addElement('sub',    'Inline', 'Inline', 'Common');
+        $this->addElement('sup',    'Inline', 'Inline', 'Common');
+        $b = $this->addElement('b',      'Inline', 'Inline', 'Common');
+        $b->formatting = true;
+        $big = $this->addElement('big',    'Inline', 'Inline', 'Common');
+        $big->formatting = true;
+        $i = $this->addElement('i',      'Inline', 'Inline', 'Common');
+        $i->formatting = true;
+        $small = $this->addElement('small',  'Inline', 'Inline', 'Common');
+        $small->formatting = true;
+        $tt = $this->addElement('tt',     'Inline', 'Inline', 'Common');
+        $tt->formatting = true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Proprietary.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Proprietary.php
new file mode 100644
index 0000000..dd36a3d
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Proprietary.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Module defines proprietary tags and attributes in HTML.
+ * @warning If this module is enabled, standards-compliance is off!
+ */
+class HTMLPurifier_HTMLModule_Proprietary extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Proprietary';
+
+    public function setup($config) {
+
+        $this->addElement('marquee', 'Inline', 'Flow', 'Common',
+            array(
+                'direction' => 'Enum#left,right,up,down',
+                'behavior' => 'Enum#alternate',
+                'width' => 'Length',
+                'height' => 'Length',
+                'scrolldelay' => 'Number',
+                'scrollamount' => 'Number',
+                'loop' => 'Number',
+                'bgcolor' => 'Color',
+                'hspace' => 'Pixels',
+                'vspace' => 'Pixels',
+            )
+        );
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php
new file mode 100644
index 0000000..b26a0a3
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Ruby.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * XHTML 1.1 Ruby Annotation Module, defines elements that indicate
+ * short runs of text alongside base text for annotation or pronounciation.
+ */
+class HTMLPurifier_HTMLModule_Ruby extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Ruby';
+
+    public function setup($config) {
+        $this->addElement('ruby', 'Inline',
+            'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))',
+            'Common');
+        $this->addElement('rbc', false, 'Required: rb', 'Common');
+        $this->addElement('rtc', false, 'Required: rt', 'Common');
+        $rb = $this->addElement('rb', false, 'Inline', 'Common');
+        $rb->excludes = array('ruby' => true);
+        $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number'));
+        $rt->excludes = array('ruby' => true);
+        $this->addElement('rp', false, 'Optional: #PCDATA', 'Common');
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeEmbed.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeEmbed.php
new file mode 100644
index 0000000..9f3758a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeEmbed.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * A "safe" embed module. See SafeObject. This is a proprietary element.
+ */
+class HTMLPurifier_HTMLModule_SafeEmbed extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'SafeEmbed';
+
+    public function setup($config) {
+
+        $max = $config->get('HTML.MaxImgLength');
+        $embed = $this->addElement(
+            'embed', 'Inline', 'Empty', 'Common',
+            array(
+                'src*' => 'URI#embedded',
+                'type' => 'Enum#application/x-shockwave-flash',
+                'width' => 'Pixels#' . $max,
+                'height' => 'Pixels#' . $max,
+                'allowscriptaccess' => 'Enum#never',
+                'allownetworking' => 'Enum#internal',
+                'flashvars' => 'Text',
+                'wmode' => 'Enum#window,transparent,opaque',
+                'name' => 'ID',
+            )
+        );
+        $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed();
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeObject.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeObject.php
new file mode 100644
index 0000000..00da342
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeObject.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * A "safe" object module. In theory, objects permitted by this module will
+ * be safe, and untrusted users can be allowed to embed arbitrary flash objects
+ * (maybe other types too, but only Flash is supported as of right now).
+ * Highly experimental.
+ */
+class HTMLPurifier_HTMLModule_SafeObject extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'SafeObject';
+
+    public function setup($config) {
+
+        // These definitions are not intrinsically safe: the attribute transforms
+        // are a vital part of ensuring safety.
+
+        $max = $config->get('HTML.MaxImgLength');
+        $object = $this->addElement(
+            'object',
+            'Inline',
+            'Optional: param | Flow | #PCDATA',
+            'Common',
+            array(
+                // While technically not required by the spec, we're forcing
+                // it to this value.
+                'type'   => 'Enum#application/x-shockwave-flash',
+                'width'  => 'Pixels#' . $max,
+                'height' => 'Pixels#' . $max,
+                'data'   => 'URI#embedded',
+                'codebase' => new HTMLPurifier_AttrDef_Enum(array(
+                    'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0')),
+            )
+        );
+        $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject();
+
+        $param = $this->addElement('param', false, 'Empty', false,
+            array(
+                'id' => 'ID',
+                'name*' => 'Text',
+                'value' => 'Text'
+            )
+        );
+        $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam();
+        $this->info_injector[] = 'SafeObject';
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeScripting.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeScripting.php
new file mode 100644
index 0000000..e32a6b6
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/SafeScripting.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * A "safe" script module. No inline JS is allowed, and pointed to JS
+ * files must match whitelist.
+ */
+class HTMLPurifier_HTMLModule_SafeScripting extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'SafeScripting';
+
+    public function setup($config) {
+
+        // These definitions are not intrinsically safe: the attribute transforms
+        // are a vital part of ensuring safety.
+
+        $allowed = $config->get('HTML.SafeScripting');
+        $script = $this->addElement(
+            'script',
+            'Inline',
+            'Empty',
+            null,
+            array(
+                // While technically not required by the spec, we're forcing
+                // it to this value.
+                'type' => 'Enum#text/javascript',
+                'src*'  => new HTMLPurifier_AttrDef_Enum(array_keys($allowed))
+            )
+        );
+        $script->attr_transform_pre[] =
+        $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired();
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php
new file mode 100644
index 0000000..2ac0d80
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Scripting.php
@@ -0,0 +1,54 @@
+<?php
+
+/*
+
+WARNING: THIS MODULE IS EXTREMELY DANGEROUS AS IT ENABLES INLINE SCRIPTING
+INSIDE HTML PURIFIER DOCUMENTS. USE ONLY WITH TRUSTED USER INPUT!!!
+
+*/
+
+/**
+ * XHTML 1.1 Scripting module, defines elements that are used to contain
+ * information pertaining to executable scripts or the lack of support
+ * for executable scripts.
+ * @note This module does not contain inline scripting elements
+ */
+class HTMLPurifier_HTMLModule_Scripting extends HTMLPurifier_HTMLModule
+{
+    public $name = 'Scripting';
+    public $elements = array('script', 'noscript');
+    public $content_sets = array('Block' => 'script | noscript', 'Inline' => 'script | noscript');
+    public $safe = false;
+
+    public function setup($config) {
+        // TODO: create custom child-definition for noscript that
+        // auto-wraps stray #PCDATA in a similar manner to
+        // blockquote's custom definition (we would use it but
+        // blockquote's contents are optional while noscript's contents
+        // are required)
+
+        // TODO: convert this to new syntax, main problem is getting
+        // both content sets working
+
+        // In theory, this could be safe, but I don't see any reason to
+        // allow it.
+        $this->info['noscript'] = new HTMLPurifier_ElementDef();
+        $this->info['noscript']->attr = array( 0 => array('Common') );
+        $this->info['noscript']->content_model = 'Heading | List | Block';
+        $this->info['noscript']->content_model_type = 'required';
+
+        $this->info['script'] = new HTMLPurifier_ElementDef();
+        $this->info['script']->attr = array(
+            'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')),
+            'src'   => new HTMLPurifier_AttrDef_URI(true),
+            'type'  => new HTMLPurifier_AttrDef_Enum(array('text/javascript'))
+        );
+        $this->info['script']->content_model = '#PCDATA';
+        $this->info['script']->content_model_type = 'optional';
+        $this->info['script']->attr_transform_pre[] =
+        $this->info['script']->attr_transform_post[] =
+            new HTMLPurifier_AttrTransform_ScriptRequired();
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php
new file mode 100644
index 0000000..eb78464
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/StyleAttribute.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * XHTML 1.1 Edit Module, defines editing-related elements. Text Extension
+ * Module.
+ */
+class HTMLPurifier_HTMLModule_StyleAttribute extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'StyleAttribute';
+    public $attr_collections = array(
+        // The inclusion routine differs from the Abstract Modules but
+        // is in line with the DTD and XML Schemas.
+        'Style' => array('style' => false), // see constructor
+        'Core' => array(0 => array('Style'))
+    );
+
+    public function setup($config) {
+        $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS();
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php
new file mode 100644
index 0000000..45c42bb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tables.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * XHTML 1.1 Tables Module, fully defines accessible table elements.
+ */
+class HTMLPurifier_HTMLModule_Tables extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Tables';
+
+    public function setup($config) {
+
+        $this->addElement('caption', false, 'Inline', 'Common');
+
+        $this->addElement('table', 'Block',
+            new HTMLPurifier_ChildDef_Table(),  'Common',
+            array(
+                'border' => 'Pixels',
+                'cellpadding' => 'Length',
+                'cellspacing' => 'Length',
+                'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border',
+                'rules' => 'Enum#none,groups,rows,cols,all',
+                'summary' => 'Text',
+                'width' => 'Length'
+            )
+        );
+
+        // common attributes
+        $cell_align = array(
+            'align' => 'Enum#left,center,right,justify,char',
+            'charoff' => 'Length',
+            'valign' => 'Enum#top,middle,bottom,baseline',
+        );
+
+        $cell_t = array_merge(
+            array(
+                'abbr'    => 'Text',
+                'colspan' => 'Number',
+                'rowspan' => 'Number',
+                // Apparently, as of HTML5 this attribute only applies
+                // to 'th' elements.
+                'scope'   => 'Enum#row,col,rowgroup,colgroup',
+            ),
+            $cell_align
+        );
+        $this->addElement('td', false, 'Flow', 'Common', $cell_t);
+        $this->addElement('th', false, 'Flow', 'Common', $cell_t);
+
+        $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align);
+
+        $cell_col = array_merge(
+            array(
+                'span'  => 'Number',
+                'width' => 'MultiLength',
+            ),
+            $cell_align
+        );
+        $this->addElement('col',      false, 'Empty',         'Common', $cell_col);
+        $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col);
+
+        $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align);
+        $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align);
+        $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align);
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Target.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Target.php
new file mode 100644
index 0000000..2b844ec
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Target.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * XHTML 1.1 Target Module, defines target attribute in link elements.
+ */
+class HTMLPurifier_HTMLModule_Target extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Target';
+
+    public function setup($config) {
+        $elements = array('a');
+        foreach ($elements as $name) {
+            $e = $this->addBlankElement($name);
+            $e->attr = array(
+                'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget()
+            );
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/TargetBlank.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/TargetBlank.php
new file mode 100644
index 0000000..e1305ec
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/TargetBlank.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Module adds the target=blank attribute transformation to a tags.  It
+ * is enabled by HTML.TargetBlank
+ */
+class HTMLPurifier_HTMLModule_TargetBlank extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'TargetBlank';
+
+    public function setup($config) {
+        $a = $this->addBlankElement('a');
+        $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank();
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Text.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Text.php
new file mode 100644
index 0000000..ae77c71
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Text.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * XHTML 1.1 Text Module, defines basic text containers. Core Module.
+ * @note In the normative XML Schema specification, this module
+ *       is further abstracted into the following modules:
+ *          - Block Phrasal (address, blockquote, pre, h1, h2, h3, h4, h5, h6)
+ *          - Block Structural (div, p)
+ *          - Inline Phrasal (abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var)
+ *          - Inline Structural (br, span)
+ *       This module, functionally, does not distinguish between these
+ *       sub-modules, but the code is internally structured to reflect
+ *       these distinctions.
+ */
+class HTMLPurifier_HTMLModule_Text extends HTMLPurifier_HTMLModule
+{
+
+    public $name = 'Text';
+    public $content_sets = array(
+        'Flow' => 'Heading | Block | Inline'
+    );
+
+    public function setup($config) {
+
+        // Inline Phrasal -------------------------------------------------
+        $this->addElement('abbr',    'Inline', 'Inline', 'Common');
+        $this->addElement('acronym', 'Inline', 'Inline', 'Common');
+        $this->addElement('cite',    'Inline', 'Inline', 'Common');
+        $this->addElement('dfn',     'Inline', 'Inline', 'Common');
+        $this->addElement('kbd',     'Inline', 'Inline', 'Common');
+        $this->addElement('q',       'Inline', 'Inline', 'Common', array('cite' => 'URI'));
+        $this->addElement('samp',    'Inline', 'Inline', 'Common');
+        $this->addElement('var',     'Inline', 'Inline', 'Common');
+
+        $em = $this->addElement('em',      'Inline', 'Inline', 'Common');
+        $em->formatting = true;
+
+        $strong = $this->addElement('strong',  'Inline', 'Inline', 'Common');
+        $strong->formatting = true;
+
+        $code = $this->addElement('code',    'Inline', 'Inline', 'Common');
+        $code->formatting = true;
+
+        // Inline Structural ----------------------------------------------
+        $this->addElement('span', 'Inline', 'Inline', 'Common');
+        $this->addElement('br',   'Inline', 'Empty',  'Core');
+
+        // Block Phrasal --------------------------------------------------
+        $this->addElement('address',     'Block', 'Inline', 'Common');
+        $this->addElement('blockquote',  'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI') );
+        $pre = $this->addElement('pre', 'Block', 'Inline', 'Common');
+        $pre->excludes = $this->makeLookup(
+            'img', 'big', 'small', 'object', 'applet', 'font', 'basefont' );
+        $this->addElement('h1', 'Heading', 'Inline', 'Common');
+        $this->addElement('h2', 'Heading', 'Inline', 'Common');
+        $this->addElement('h3', 'Heading', 'Inline', 'Common');
+        $this->addElement('h4', 'Heading', 'Inline', 'Common');
+        $this->addElement('h5', 'Heading', 'Inline', 'Common');
+        $this->addElement('h6', 'Heading', 'Inline', 'Common');
+
+        // Block Structural -----------------------------------------------
+        $p = $this->addElement('p', 'Block', 'Inline', 'Common');
+        $p->autoclose = array_flip(array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul"));
+
+        $this->addElement('div', 'Block', 'Flow', 'Common');
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php
new file mode 100644
index 0000000..21783f1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * Abstract class for a set of proprietary modules that clean up (tidy)
+ * poorly written HTML.
+ * @todo Figure out how to protect some of these methods/properties
+ */
+class HTMLPurifier_HTMLModule_Tidy extends HTMLPurifier_HTMLModule
+{
+
+    /**
+     * List of supported levels. Index zero is a special case "no fixes"
+     * level.
+     */
+    public $levels = array(0 => 'none', 'light', 'medium', 'heavy');
+
+    /**
+     * Default level to place all fixes in. Disabled by default
+     */
+    public $defaultLevel = null;
+
+    /**
+     * Lists of fixes used by getFixesForLevel(). Format is:
+     *      HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2');
+     */
+    public $fixesForLevel = array(
+        'light'  => array(),
+        'medium' => array(),
+        'heavy'  => array()
+    );
+
+    /**
+     * Lazy load constructs the module by determining the necessary
+     * fixes to create and then delegating to the populate() function.
+     * @todo Wildcard matching and error reporting when an added or
+     *       subtracted fix has no effect.
+     */
+    public function setup($config) {
+
+        // create fixes, initialize fixesForLevel
+        $fixes = $this->makeFixes();
+        $this->makeFixesForLevel($fixes);
+
+        // figure out which fixes to use
+        $level = $config->get('HTML.TidyLevel');
+        $fixes_lookup = $this->getFixesForLevel($level);
+
+        // get custom fix declarations: these need namespace processing
+        $add_fixes    = $config->get('HTML.TidyAdd');
+        $remove_fixes = $config->get('HTML.TidyRemove');
+
+        foreach ($fixes as $name => $fix) {
+            // needs to be refactored a little to implement globbing
+            if (
+                isset($remove_fixes[$name]) ||
+                (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))
+            ) {
+                unset($fixes[$name]);
+            }
+        }
+
+        // populate this module with necessary fixes
+        $this->populate($fixes);
+
+    }
+
+    /**
+     * Retrieves all fixes per a level, returning fixes for that specific
+     * level as well as all levels below it.
+     * @param $level String level identifier, see $levels for valid values
+     * @return Lookup up table of fixes
+     */
+    public function getFixesForLevel($level) {
+        if ($level == $this->levels[0]) {
+            return array();
+        }
+        $activated_levels = array();
+        for ($i = 1, $c = count($this->levels); $i < $c; $i++) {
+            $activated_levels[] = $this->levels[$i];
+            if ($this->levels[$i] == $level) break;
+        }
+        if ($i == $c) {
+            trigger_error(
+                'Tidy level ' . htmlspecialchars($level) . ' not recognized',
+                E_USER_WARNING
+            );
+            return array();
+        }
+        $ret = array();
+        foreach ($activated_levels as $level) {
+            foreach ($this->fixesForLevel[$level] as $fix) {
+                $ret[$fix] = true;
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * Dynamically populates the $fixesForLevel member variable using
+     * the fixes array. It may be custom overloaded, used in conjunction
+     * with $defaultLevel, or not used at all.
+     */
+    public function makeFixesForLevel($fixes) {
+        if (!isset($this->defaultLevel)) return;
+        if (!isset($this->fixesForLevel[$this->defaultLevel])) {
+            trigger_error(
+                'Default level ' . $this->defaultLevel . ' does not exist',
+                E_USER_ERROR
+            );
+            return;
+        }
+        $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes);
+    }
+
+    /**
+     * Populates the module with transforms and other special-case code
+     * based on a list of fixes passed to it
+     * @param $lookup Lookup table of fixes to activate
+     */
+    public function populate($fixes) {
+        foreach ($fixes as $name => $fix) {
+            // determine what the fix is for
+            list($type, $params) = $this->getFixType($name);
+            switch ($type) {
+                case 'attr_transform_pre':
+                case 'attr_transform_post':
+                    $attr = $params['attr'];
+                    if (isset($params['element'])) {
+                        $element = $params['element'];
+                        if (empty($this->info[$element])) {
+                            $e = $this->addBlankElement($element);
+                        } else {
+                            $e = $this->info[$element];
+                        }
+                    } else {
+                        $type = "info_$type";
+                        $e = $this;
+                    }
+                    // PHP does some weird parsing when I do
+                    // $e->$type[$attr], so I have to assign a ref.
+                    $f =& $e->$type;
+                    $f[$attr] = $fix;
+                    break;
+                case 'tag_transform':
+                    $this->info_tag_transform[$params['element']] = $fix;
+                    break;
+                case 'child':
+                case 'content_model_type':
+                    $element = $params['element'];
+                    if (empty($this->info[$element])) {
+                        $e = $this->addBlankElement($element);
+                    } else {
+                        $e = $this->info[$element];
+                    }
+                    $e->$type = $fix;
+                    break;
+                default:
+                    trigger_error("Fix type $type not supported", E_USER_ERROR);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Parses a fix name and determines what kind of fix it is, as well
+     * as other information defined by the fix
+     * @param $name String name of fix
+     * @return array(string $fix_type, array $fix_parameters)
+     * @note $fix_parameters is type dependant, see populate() for usage
+     *       of these parameters
+     */
+    public function getFixType($name) {
+        // parse it
+        $property = $attr = null;
+        if (strpos($name, '#') !== false) list($name, $property) = explode('#', $name);
+        if (strpos($name, '@') !== false) list($name, $attr)     = explode('@', $name);
+
+        // figure out the parameters
+        $params = array();
+        if ($name !== '')    $params['element'] = $name;
+        if (!is_null($attr)) $params['attr'] = $attr;
+
+        // special case: attribute transform
+        if (!is_null($attr)) {
+            if (is_null($property)) $property = 'pre';
+            $type = 'attr_transform_' . $property;
+            return array($type, $params);
+        }
+
+        // special case: tag transform
+        if (is_null($property)) {
+            return array('tag_transform', $params);
+        }
+
+        return array($property, $params);
+
+    }
+
+    /**
+     * Defines all fixes the module will perform in a compact
+     * associative array of fix name to fix implementation.
+     */
+    public function makeFixes() {}
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Name.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Name.php
new file mode 100644
index 0000000..61ff85c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Name.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Name is deprecated, but allowed in strict doctypes, so onl
+ */
+class HTMLPurifier_HTMLModule_Tidy_Name extends HTMLPurifier_HTMLModule_Tidy
+{
+    public $name = 'Tidy_Name';
+    public $defaultLevel = 'heavy';
+    public function makeFixes() {
+
+        $r = array();
+
+        // @name for img, a -----------------------------------------------
+        // Technically, it's allowed even on strict, so we allow authors to use
+        // it. However, it's deprecated in future versions of XHTML.
+        $r['img at name'] =
+        $r['a at name'] = new HTMLPurifier_AttrTransform_Name();
+
+        return $r;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Proprietary.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Proprietary.php
new file mode 100644
index 0000000..14c15c4
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Proprietary.php
@@ -0,0 +1,24 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_Proprietary extends HTMLPurifier_HTMLModule_Tidy
+{
+
+    public $name = 'Tidy_Proprietary';
+    public $defaultLevel = 'light';
+
+    public function makeFixes() {
+        $r = array();
+        $r['table at background'] = new HTMLPurifier_AttrTransform_Background();
+        $r['td at background']    = new HTMLPurifier_AttrTransform_Background();
+        $r['th at background']    = new HTMLPurifier_AttrTransform_Background();
+        $r['tr at background']    = new HTMLPurifier_AttrTransform_Background();
+        $r['thead at background'] = new HTMLPurifier_AttrTransform_Background();
+        $r['tfoot at background'] = new HTMLPurifier_AttrTransform_Background();
+        $r['tbody at background'] = new HTMLPurifier_AttrTransform_Background();
+        $r['table at height']     = new HTMLPurifier_AttrTransform_Length('height');
+        return $r;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Strict.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Strict.php
new file mode 100644
index 0000000..c73dc3c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Strict.php
@@ -0,0 +1,21 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_Strict extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
+{
+    public $name = 'Tidy_Strict';
+    public $defaultLevel = 'light';
+
+    public function makeFixes() {
+        $r = parent::makeFixes();
+        $r['blockquote#content_model_type'] = 'strictblockquote';
+        return $r;
+    }
+
+    public $defines_child_def = true;
+    public function getChildDef($def) {
+        if ($def->content_model_type != 'strictblockquote') return parent::getChildDef($def);
+        return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Transitional.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Transitional.php
new file mode 100644
index 0000000..9960b1d
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/Transitional.php
@@ -0,0 +1,9 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_Transitional extends HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4
+{
+    public $name = 'Tidy_Transitional';
+    public $defaultLevel = 'heavy';
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/XHTML.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/XHTML.php
new file mode 100644
index 0000000..db5a378
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/XHTML.php
@@ -0,0 +1,17 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_XHTML extends HTMLPurifier_HTMLModule_Tidy
+{
+
+    public $name = 'Tidy_XHTML';
+    public $defaultLevel = 'medium';
+
+    public function makeFixes() {
+        $r = array();
+        $r['@lang'] = new HTMLPurifier_AttrTransform_Lang();
+        return $r;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php
new file mode 100644
index 0000000..02e9438
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php
@@ -0,0 +1,161 @@
+<?php
+
+class HTMLPurifier_HTMLModule_Tidy_XHTMLAndHTML4 extends HTMLPurifier_HTMLModule_Tidy
+{
+
+    public function makeFixes() {
+
+        $r = array();
+
+        // == deprecated tag transforms ===================================
+
+        $r['font']   = new HTMLPurifier_TagTransform_Font();
+        $r['menu']   = new HTMLPurifier_TagTransform_Simple('ul');
+        $r['dir']    = new HTMLPurifier_TagTransform_Simple('ul');
+        $r['center'] = new HTMLPurifier_TagTransform_Simple('div',  'text-align:center;');
+        $r['u']      = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:underline;');
+        $r['s']      = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
+        $r['strike'] = new HTMLPurifier_TagTransform_Simple('span', 'text-decoration:line-through;');
+
+        // == deprecated attribute transforms =============================
+
+        $r['caption at align'] =
+            new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
+                // we're following IE's behavior, not Firefox's, due
+                // to the fact that no one supports caption-side:right,
+                // W3C included (with CSS 2.1). This is a slightly
+                // unreasonable attribute!
+                'left'   => 'text-align:left;',
+                'right'  => 'text-align:right;',
+                'top'    => 'caption-side:top;',
+                'bottom' => 'caption-side:bottom;' // not supported by IE
+            ));
+
+        // @align for img -------------------------------------------------
+        $r['img at align'] =
+            new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
+                'left'   => 'float:left;',
+                'right'  => 'float:right;',
+                'top'    => 'vertical-align:top;',
+                'middle' => 'vertical-align:middle;',
+                'bottom' => 'vertical-align:baseline;',
+            ));
+
+        // @align for table -----------------------------------------------
+        $r['table at align'] =
+            new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
+                'left'   => 'float:left;',
+                'center' => 'margin-left:auto;margin-right:auto;',
+                'right'  => 'float:right;'
+            ));
+
+        // @align for hr -----------------------------------------------
+        $r['hr at align'] =
+            new HTMLPurifier_AttrTransform_EnumToCSS('align', array(
+                // we use both text-align and margin because these work
+                // for different browsers (IE and Firefox, respectively)
+                // and the melange makes for a pretty cross-compatible
+                // solution
+                'left'   => 'margin-left:0;margin-right:auto;text-align:left;',
+                'center' => 'margin-left:auto;margin-right:auto;text-align:center;',
+                'right'  => 'margin-left:auto;margin-right:0;text-align:right;'
+            ));
+
+        // @align for h1, h2, h3, h4, h5, h6, p, div ----------------------
+        // {{{
+            $align_lookup = array();
+            $align_values = array('left', 'right', 'center', 'justify');
+            foreach ($align_values as $v) $align_lookup[$v] = "text-align:$v;";
+        // }}}
+        $r['h1 at align'] =
+        $r['h2 at align'] =
+        $r['h3 at align'] =
+        $r['h4 at align'] =
+        $r['h5 at align'] =
+        $r['h6 at align'] =
+        $r['p at align']  =
+        $r['div at align'] =
+            new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup);
+
+        // @bgcolor for table, tr, td, th ---------------------------------
+        $r['table at bgcolor'] =
+        $r['td at bgcolor'] =
+        $r['th at bgcolor'] =
+            new HTMLPurifier_AttrTransform_BgColor();
+
+        // @border for img ------------------------------------------------
+        $r['img at border'] = new HTMLPurifier_AttrTransform_Border();
+
+        // @clear for br --------------------------------------------------
+        $r['br at clear'] =
+            new HTMLPurifier_AttrTransform_EnumToCSS('clear', array(
+                'left'  => 'clear:left;',
+                'right' => 'clear:right;',
+                'all'   => 'clear:both;',
+                'none'  => 'clear:none;',
+            ));
+
+        // @height for td, th ---------------------------------------------
+        $r['td at height'] =
+        $r['th at height'] =
+            new HTMLPurifier_AttrTransform_Length('height');
+
+        // @hspace for img ------------------------------------------------
+        $r['img at hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace');
+
+        // @noshade for hr ------------------------------------------------
+        // this transformation is not precise but often good enough.
+        // different browsers use different styles to designate noshade
+        $r['hr at noshade'] =
+            new HTMLPurifier_AttrTransform_BoolToCSS(
+                'noshade',
+                'color:#808080;background-color:#808080;border:0;'
+            );
+
+        // @nowrap for td, th ---------------------------------------------
+        $r['td at nowrap'] =
+        $r['th at nowrap'] =
+            new HTMLPurifier_AttrTransform_BoolToCSS(
+                'nowrap',
+                'white-space:nowrap;'
+            );
+
+        // @size for hr  --------------------------------------------------
+        $r['hr at size'] = new HTMLPurifier_AttrTransform_Length('size', 'height');
+
+        // @type for li, ol, ul -------------------------------------------
+        // {{{
+            $ul_types = array(
+                'disc'   => 'list-style-type:disc;',
+                'square' => 'list-style-type:square;',
+                'circle' => 'list-style-type:circle;'
+            );
+            $ol_types = array(
+                '1'   => 'list-style-type:decimal;',
+                'i'   => 'list-style-type:lower-roman;',
+                'I'   => 'list-style-type:upper-roman;',
+                'a'   => 'list-style-type:lower-alpha;',
+                'A'   => 'list-style-type:upper-alpha;'
+            );
+            $li_types = $ul_types + $ol_types;
+        // }}}
+
+        $r['ul at type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types);
+        $r['ol at type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true);
+        $r['li at type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true);
+
+        // @vspace for img ------------------------------------------------
+        $r['img at vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace');
+
+        // @width for hr, td, th ------------------------------------------
+        $r['td at width'] =
+        $r['th at width'] =
+        $r['hr at width'] = new HTMLPurifier_AttrTransform_Length('width');
+
+        return $r;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/XMLCommonAttributes.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/XMLCommonAttributes.php
new file mode 100644
index 0000000..9c0e031
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModule/XMLCommonAttributes.php
@@ -0,0 +1,14 @@
+<?php
+
+class HTMLPurifier_HTMLModule_XMLCommonAttributes extends HTMLPurifier_HTMLModule
+{
+    public $name = 'XMLCommonAttributes';
+
+    public $attr_collections = array(
+        'Lang' => array(
+            'xml:lang' => 'LanguageCode',
+        )
+    );
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/HTMLModuleManager.php b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModuleManager.php
new file mode 100644
index 0000000..2153086
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/HTMLModuleManager.php
@@ -0,0 +1,418 @@
+<?php
+
+class HTMLPurifier_HTMLModuleManager
+{
+
+    /**
+     * Instance of HTMLPurifier_DoctypeRegistry
+     */
+    public $doctypes;
+
+    /**
+     * Instance of current doctype
+     */
+    public $doctype;
+
+    /**
+     * Instance of HTMLPurifier_AttrTypes
+     */
+    public $attrTypes;
+
+    /**
+     * Active instances of modules for the specified doctype are
+     * indexed, by name, in this array.
+     */
+    public $modules = array();
+
+    /**
+     * Array of recognized HTMLPurifier_Module instances, indexed by
+     * module's class name. This array is usually lazy loaded, but a
+     * user can overload a module by pre-emptively registering it.
+     */
+    public $registeredModules = array();
+
+    /**
+     * List of extra modules that were added by the user using addModule().
+     * These get unconditionally merged into the current doctype, whatever
+     * it may be.
+     */
+    public $userModules = array();
+
+    /**
+     * Associative array of element name to list of modules that have
+     * definitions for the element; this array is dynamically filled.
+     */
+    public $elementLookup = array();
+
+    /** List of prefixes we should use for registering small names */
+    public $prefixes = array('HTMLPurifier_HTMLModule_');
+
+    public $contentSets;     /**< Instance of HTMLPurifier_ContentSets */
+    public $attrCollections; /**< Instance of HTMLPurifier_AttrCollections */
+
+    /** If set to true, unsafe elements and attributes will be allowed */
+    public $trusted = false;
+
+    public function __construct() {
+
+        // editable internal objects
+        $this->attrTypes = new HTMLPurifier_AttrTypes();
+        $this->doctypes  = new HTMLPurifier_DoctypeRegistry();
+
+        // setup basic modules
+        $common = array(
+            'CommonAttributes', 'Text', 'Hypertext', 'List',
+            'Presentation', 'Edit', 'Bdo', 'Tables', 'Image',
+            'StyleAttribute',
+            // Unsafe:
+            'Scripting', 'Object', 'Forms',
+            // Sorta legacy, but present in strict:
+            'Name',
+        );
+        $transitional = array('Legacy', 'Target', 'Iframe');
+        $xml = array('XMLCommonAttributes');
+        $non_xml = array('NonXMLCommonAttributes');
+
+        // setup basic doctypes
+        $this->doctypes->register(
+            'HTML 4.01 Transitional', false,
+            array_merge($common, $transitional, $non_xml),
+            array('Tidy_Transitional', 'Tidy_Proprietary'),
+            array(),
+            '-//W3C//DTD HTML 4.01 Transitional//EN',
+            'http://www.w3.org/TR/html4/loose.dtd'
+        );
+
+        $this->doctypes->register(
+            'HTML 4.01 Strict', false,
+            array_merge($common, $non_xml),
+            array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
+            array(),
+            '-//W3C//DTD HTML 4.01//EN',
+            'http://www.w3.org/TR/html4/strict.dtd'
+        );
+
+        $this->doctypes->register(
+            'XHTML 1.0 Transitional', true,
+            array_merge($common, $transitional, $xml, $non_xml),
+            array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'),
+            array(),
+            '-//W3C//DTD XHTML 1.0 Transitional//EN',
+            'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'
+        );
+
+        $this->doctypes->register(
+            'XHTML 1.0 Strict', true,
+            array_merge($common, $xml, $non_xml),
+            array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'),
+            array(),
+            '-//W3C//DTD XHTML 1.0 Strict//EN',
+            'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'
+        );
+
+        $this->doctypes->register(
+            'XHTML 1.1', true,
+            // Iframe is a real XHTML 1.1 module, despite being
+            // "transitional"!
+            array_merge($common, $xml, array('Ruby', 'Iframe')),
+            array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1
+            array(),
+            '-//W3C//DTD XHTML 1.1//EN',
+            'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
+        );
+
+    }
+
+    /**
+     * Registers a module to the recognized module list, useful for
+     * overloading pre-existing modules.
+     * @param $module Mixed: string module name, with or without
+     *                HTMLPurifier_HTMLModule prefix, or instance of
+     *                subclass of HTMLPurifier_HTMLModule.
+     * @param $overload Boolean whether or not to overload previous modules.
+     *                  If this is not set, and you do overload a module,
+     *                  HTML Purifier will complain with a warning.
+     * @note This function will not call autoload, you must instantiate
+     *       (and thus invoke) autoload outside the method.
+     * @note If a string is passed as a module name, different variants
+     *       will be tested in this order:
+     *          - Check for HTMLPurifier_HTMLModule_$name
+     *          - Check all prefixes with $name in order they were added
+     *          - Check for literal object name
+     *          - Throw fatal error
+     *       If your object name collides with an internal class, specify
+     *       your module manually. All modules must have been included
+     *       externally: registerModule will not perform inclusions for you!
+     */
+    public function registerModule($module, $overload = false) {
+        if (is_string($module)) {
+            // attempt to load the module
+            $original_module = $module;
+            $ok = false;
+            foreach ($this->prefixes as $prefix) {
+                $module = $prefix . $original_module;
+                if (class_exists($module)) {
+                    $ok = true;
+                    break;
+                }
+            }
+            if (!$ok) {
+                $module = $original_module;
+                if (!class_exists($module)) {
+                    trigger_error($original_module . ' module does not exist',
+                        E_USER_ERROR);
+                    return;
+                }
+            }
+            $module = new $module();
+        }
+        if (empty($module->name)) {
+            trigger_error('Module instance of ' . get_class($module) . ' must have name');
+            return;
+        }
+        if (!$overload && isset($this->registeredModules[$module->name])) {
+            trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING);
+        }
+        $this->registeredModules[$module->name] = $module;
+    }
+
+    /**
+     * Adds a module to the current doctype by first registering it,
+     * and then tacking it on to the active doctype
+     */
+    public function addModule($module) {
+        $this->registerModule($module);
+        if (is_object($module)) $module = $module->name;
+        $this->userModules[] = $module;
+    }
+
+    /**
+     * Adds a class prefix that registerModule() will use to resolve a
+     * string name to a concrete class
+     */
+    public function addPrefix($prefix) {
+        $this->prefixes[] = $prefix;
+    }
+
+    /**
+     * Performs processing on modules, after being called you may
+     * use getElement() and getElements()
+     * @param $config Instance of HTMLPurifier_Config
+     */
+    public function setup($config) {
+
+        $this->trusted = $config->get('HTML.Trusted');
+
+        // generate
+        $this->doctype = $this->doctypes->make($config);
+        $modules = $this->doctype->modules;
+
+        // take out the default modules that aren't allowed
+        $lookup = $config->get('HTML.AllowedModules');
+        $special_cases = $config->get('HTML.CoreModules');
+
+        if (is_array($lookup)) {
+            foreach ($modules as $k => $m) {
+                if (isset($special_cases[$m])) continue;
+                if (!isset($lookup[$m])) unset($modules[$k]);
+            }
+        }
+
+        // custom modules
+        if ($config->get('HTML.Proprietary')) {
+            $modules[] = 'Proprietary';
+        }
+        if ($config->get('HTML.SafeObject')) {
+            $modules[] = 'SafeObject';
+        }
+        if ($config->get('HTML.SafeEmbed')) {
+            $modules[] = 'SafeEmbed';
+        }
+        if ($config->get('HTML.SafeScripting') !== array()) {
+            $modules[] = 'SafeScripting';
+        }
+        if ($config->get('HTML.Nofollow')) {
+            $modules[] = 'Nofollow';
+        }
+        if ($config->get('HTML.TargetBlank')) {
+            $modules[] = 'TargetBlank';
+        }
+
+        // merge in custom modules
+        $modules = array_merge($modules, $this->userModules);
+
+        foreach ($modules as $module) {
+            $this->processModule($module);
+            $this->modules[$module]->setup($config);
+        }
+
+        foreach ($this->doctype->tidyModules as $module) {
+            $this->processModule($module);
+            $this->modules[$module]->setup($config);
+        }
+
+        // prepare any injectors
+        foreach ($this->modules as $module) {
+            $n = array();
+            foreach ($module->info_injector as $i => $injector) {
+                if (!is_object($injector)) {
+                    $class = "HTMLPurifier_Injector_$injector";
+                    $injector = new $class;
+                }
+                $n[$injector->name] = $injector;
+            }
+            $module->info_injector = $n;
+        }
+
+        // setup lookup table based on all valid modules
+        foreach ($this->modules as $module) {
+            foreach ($module->info as $name => $def) {
+                if (!isset($this->elementLookup[$name])) {
+                    $this->elementLookup[$name] = array();
+                }
+                $this->elementLookup[$name][] = $module->name;
+            }
+        }
+
+        // note the different choice
+        $this->contentSets = new HTMLPurifier_ContentSets(
+            // content set assembly deals with all possible modules,
+            // not just ones deemed to be "safe"
+            $this->modules
+        );
+        $this->attrCollections = new HTMLPurifier_AttrCollections(
+            $this->attrTypes,
+            // there is no way to directly disable a global attribute,
+            // but using AllowedAttributes or simply not including
+            // the module in your custom doctype should be sufficient
+            $this->modules
+        );
+    }
+
+    /**
+     * Takes a module and adds it to the active module collection,
+     * registering it if necessary.
+     */
+    public function processModule($module) {
+        if (!isset($this->registeredModules[$module]) || is_object($module)) {
+            $this->registerModule($module);
+        }
+        $this->modules[$module] = $this->registeredModules[$module];
+    }
+
+    /**
+     * Retrieves merged element definitions.
+     * @return Array of HTMLPurifier_ElementDef
+     */
+    public function getElements() {
+
+        $elements = array();
+        foreach ($this->modules as $module) {
+            if (!$this->trusted && !$module->safe) continue;
+            foreach ($module->info as $name => $v) {
+                if (isset($elements[$name])) continue;
+                $elements[$name] = $this->getElement($name);
+            }
+        }
+
+        // remove dud elements, this happens when an element that
+        // appeared to be safe actually wasn't
+        foreach ($elements as $n => $v) {
+            if ($v === false) unset($elements[$n]);
+        }
+
+        return $elements;
+
+    }
+
+    /**
+     * Retrieves a single merged element definition
+     * @param $name Name of element
+     * @param $trusted Boolean trusted overriding parameter: set to true
+     *                 if you want the full version of an element
+     * @return Merged HTMLPurifier_ElementDef
+     * @note You may notice that modules are getting iterated over twice (once
+     *       in getElements() and once here). This
+     *       is because
+     */
+    public function getElement($name, $trusted = null) {
+
+        if (!isset($this->elementLookup[$name])) {
+            return false;
+        }
+
+        // setup global state variables
+        $def = false;
+        if ($trusted === null) $trusted = $this->trusted;
+
+        // iterate through each module that has registered itself to this
+        // element
+        foreach($this->elementLookup[$name] as $module_name) {
+
+            $module = $this->modules[$module_name];
+
+            // refuse to create/merge from a module that is deemed unsafe--
+            // pretend the module doesn't exist--when trusted mode is not on.
+            if (!$trusted && !$module->safe) {
+                continue;
+            }
+
+            // clone is used because, ideally speaking, the original
+            // definition should not be modified. Usually, this will
+            // make no difference, but for consistency's sake
+            $new_def = clone $module->info[$name];
+
+            if (!$def && $new_def->standalone) {
+                $def = $new_def;
+            } elseif ($def) {
+                // This will occur even if $new_def is standalone. In practice,
+                // this will usually result in a full replacement.
+                $def->mergeIn($new_def);
+            } else {
+                // :TODO:
+                // non-standalone definitions that don't have a standalone
+                // to merge into could be deferred to the end
+                // HOWEVER, it is perfectly valid for a non-standalone
+                // definition to lack a standalone definition, even
+                // after all processing: this allows us to safely
+                // specify extra attributes for elements that may not be
+                // enabled all in one place.  In particular, this might
+                // be the case for trusted elements.  WARNING: care must
+                // be taken that the /extra/ definitions are all safe.
+                continue;
+            }
+
+            // attribute value expansions
+            $this->attrCollections->performInclusions($def->attr);
+            $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes);
+
+            // descendants_are_inline, for ChildDef_Chameleon
+            if (is_string($def->content_model) &&
+                strpos($def->content_model, 'Inline') !== false) {
+                if ($name != 'del' && $name != 'ins') {
+                    // this is for you, ins/del
+                    $def->descendants_are_inline = true;
+                }
+            }
+
+            $this->contentSets->generateChildDef($def, $module);
+        }
+
+        // This can occur if there is a blank definition, but no base to
+        // mix it in with
+        if (!$def) return false;
+
+        // add information on required attributes
+        foreach ($def->attr as $attr_name => $attr_def) {
+            if ($attr_def->required) {
+                $def->required_attr[] = $attr_name;
+            }
+        }
+
+        return $def;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/IDAccumulator.php b/application/libraries/htmlpurifier/HTMLPurifier/IDAccumulator.php
new file mode 100644
index 0000000..7321529
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/IDAccumulator.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * Component of HTMLPurifier_AttrContext that accumulates IDs to prevent dupes
+ * @note In Slashdot-speak, dupe means duplicate.
+ * @note The default constructor does not accept $config or $context objects:
+ *       use must use the static build() factory method to perform initialization.
+ */
+class HTMLPurifier_IDAccumulator
+{
+
+    /**
+     * Lookup table of IDs we've accumulated.
+     * @public
+     */
+    public $ids = array();
+
+    /**
+     * Builds an IDAccumulator, also initializing the default blacklist
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     * @return Fully initialized HTMLPurifier_IDAccumulator
+     */
+    public static function build($config, $context) {
+        $id_accumulator = new HTMLPurifier_IDAccumulator();
+        $id_accumulator->load($config->get('Attr.IDBlacklist'));
+        return $id_accumulator;
+    }
+
+    /**
+     * Add an ID to the lookup table.
+     * @param $id ID to be added.
+     * @return Bool status, true if success, false if there's a dupe
+     */
+    public function add($id) {
+        if (isset($this->ids[$id])) return false;
+        return $this->ids[$id] = true;
+    }
+
+    /**
+     * Load a list of IDs into the lookup table
+     * @param $array_of_ids Array of IDs to load
+     * @note This function doesn't care about duplicates
+     */
+    public function load($array_of_ids) {
+        foreach ($array_of_ids as $id) {
+            $this->ids[$id] = true;
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Injector.php b/application/libraries/htmlpurifier/HTMLPurifier/Injector.php
new file mode 100644
index 0000000..5922f81
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Injector.php
@@ -0,0 +1,239 @@
+<?php
+
+/**
+ * Injects tokens into the document while parsing for well-formedness.
+ * This enables "formatter-like" functionality such as auto-paragraphing,
+ * smiley-ification and linkification to take place.
+ *
+ * A note on how handlers create changes; this is done by assigning a new
+ * value to the $token reference. These values can take a variety of forms and
+ * are best described HTMLPurifier_Strategy_MakeWellFormed->processToken()
+ * documentation.
+ *
+ * @todo Allow injectors to request a re-run on their output. This
+ *       would help if an operation is recursive.
+ */
+abstract class HTMLPurifier_Injector
+{
+
+    /**
+     * Advisory name of injector, this is for friendly error messages
+     */
+    public $name;
+
+    /**
+     * Instance of HTMLPurifier_HTMLDefinition
+     */
+    protected $htmlDefinition;
+
+    /**
+     * Reference to CurrentNesting variable in Context. This is an array
+     * list of tokens that we are currently "inside"
+     */
+    protected $currentNesting;
+
+    /**
+     * Reference to InputTokens variable in Context. This is an array
+     * list of the input tokens that are being processed.
+     */
+    protected $inputTokens;
+
+    /**
+     * Reference to InputIndex variable in Context. This is an integer
+     * array index for $this->inputTokens that indicates what token
+     * is currently being processed.
+     */
+    protected $inputIndex;
+
+    /**
+     * Array of elements and attributes this injector creates and therefore
+     * need to be allowed by the definition. Takes form of
+     * array('element' => array('attr', 'attr2'), 'element2')
+     */
+    public $needed = array();
+
+    /**
+     * Index of inputTokens to rewind to.
+     */
+    protected $rewind = false;
+
+    /**
+     * Rewind to a spot to re-perform processing. This is useful if you
+     * deleted a node, and now need to see if this change affected any
+     * earlier nodes. Rewinding does not affect other injectors, and can
+     * result in infinite loops if not used carefully.
+     * @warning HTML Purifier will prevent you from fast-forwarding with this
+     *          function.
+     */
+    public function rewind($index) {
+        $this->rewind = $index;
+    }
+
+    /**
+     * Retrieves rewind, and then unsets it.
+     */
+    public function getRewind() {
+        $r = $this->rewind;
+        $this->rewind = false;
+        return $r;
+    }
+
+    /**
+     * Prepares the injector by giving it the config and context objects:
+     * this allows references to important variables to be made within
+     * the injector. This function also checks if the HTML environment
+     * will work with the Injector (see checkNeeded()).
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     * @return Boolean false if success, string of missing needed element/attribute if failure
+     */
+    public function prepare($config, $context) {
+        $this->htmlDefinition = $config->getHTMLDefinition();
+        // Even though this might fail, some unit tests ignore this and
+        // still test checkNeeded, so be careful. Maybe get rid of that
+        // dependency.
+        $result = $this->checkNeeded($config);
+        if ($result !== false) return $result;
+        $this->currentNesting =& $context->get('CurrentNesting');
+        $this->inputTokens    =& $context->get('InputTokens');
+        $this->inputIndex     =& $context->get('InputIndex');
+        return false;
+    }
+
+    /**
+     * This function checks if the HTML environment
+     * will work with the Injector: if p tags are not allowed, the
+     * Auto-Paragraphing injector should not be enabled.
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     * @return Boolean false if success, string of missing needed element/attribute if failure
+     */
+    public function checkNeeded($config) {
+        $def = $config->getHTMLDefinition();
+        foreach ($this->needed as $element => $attributes) {
+            if (is_int($element)) $element = $attributes;
+            if (!isset($def->info[$element])) return $element;
+            if (!is_array($attributes)) continue;
+            foreach ($attributes as $name) {
+                if (!isset($def->info[$element]->attr[$name])) return "$element.$name";
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Tests if the context node allows a certain element
+     * @param $name Name of element to test for
+     * @return True if element is allowed, false if it is not
+     */
+    public function allowsElement($name) {
+        if (!empty($this->currentNesting)) {
+            $parent_token = array_pop($this->currentNesting);
+            $this->currentNesting[] = $parent_token;
+            $parent = $this->htmlDefinition->info[$parent_token->name];
+        } else {
+            $parent = $this->htmlDefinition->info_parent_def;
+        }
+        if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) {
+            return false;
+        }
+        // check for exclusion
+        for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) {
+            $node = $this->currentNesting[$i];
+            $def  = $this->htmlDefinition->info[$node->name];
+            if (isset($def->excludes[$name])) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Iterator function, which starts with the next token and continues until
+     * you reach the end of the input tokens.
+     * @warning Please prevent previous references from interfering with this
+     *          functions by setting $i = null beforehand!
+     * @param &$i Current integer index variable for inputTokens
+     * @param &$current Current token variable. Do NOT use $token, as that variable is also a reference
+     */
+    protected function forward(&$i, &$current) {
+        if ($i === null) $i = $this->inputIndex + 1;
+        else $i++;
+        if (!isset($this->inputTokens[$i])) return false;
+        $current = $this->inputTokens[$i];
+        return true;
+    }
+
+    /**
+     * Similar to _forward, but accepts a third parameter $nesting (which
+     * should be initialized at 0) and stops when we hit the end tag
+     * for the node $this->inputIndex starts in.
+     */
+    protected function forwardUntilEndToken(&$i, &$current, &$nesting) {
+        $result = $this->forward($i, $current);
+        if (!$result) return false;
+        if ($nesting === null) $nesting = 0;
+        if     ($current instanceof HTMLPurifier_Token_Start) $nesting++;
+        elseif ($current instanceof HTMLPurifier_Token_End) {
+            if ($nesting <= 0) return false;
+            $nesting--;
+        }
+        return true;
+    }
+
+    /**
+     * Iterator function, starts with the previous token and continues until
+     * you reach the beginning of input tokens.
+     * @warning Please prevent previous references from interfering with this
+     *          functions by setting $i = null beforehand!
+     * @param &$i Current integer index variable for inputTokens
+     * @param &$current Current token variable. Do NOT use $token, as that variable is also a reference
+     */
+    protected function backward(&$i, &$current) {
+        if ($i === null) $i = $this->inputIndex - 1;
+        else $i--;
+        if ($i < 0) return false;
+        $current = $this->inputTokens[$i];
+        return true;
+    }
+
+    /**
+     * Initializes the iterator at the current position. Use in a do {} while;
+     * loop to force the _forward and _backward functions to start at the
+     * current location.
+     * @warning Please prevent previous references from interfering with this
+     *          functions by setting $i = null beforehand!
+     * @param &$i Current integer index variable for inputTokens
+     * @param &$current Current token variable. Do NOT use $token, as that variable is also a reference
+     */
+    protected function current(&$i, &$current) {
+        if ($i === null) $i = $this->inputIndex;
+        $current = $this->inputTokens[$i];
+    }
+
+    /**
+     * Handler that is called when a text token is processed
+     */
+    public function handleText(&$token) {}
+
+    /**
+     * Handler that is called when a start or empty token is processed
+     */
+    public function handleElement(&$token) {}
+
+    /**
+     * Handler that is called when an end token is processed
+     */
+    public function handleEnd(&$token) {
+        $this->notifyEnd($token);
+    }
+
+    /**
+     * Notifier that is called when an end token is processed
+     * @note This differs from handlers in that the token is read-only
+     * @deprecated
+     */
+    public function notifyEnd($token) {}
+
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Injector/AutoParagraph.php b/application/libraries/htmlpurifier/HTMLPurifier/Injector/AutoParagraph.php
new file mode 100644
index 0000000..afa7608
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Injector/AutoParagraph.php
@@ -0,0 +1,345 @@
+<?php
+
+/**
+ * Injector that auto paragraphs text in the root node based on
+ * double-spacing.
+ * @todo Ensure all states are unit tested, including variations as well.
+ * @todo Make a graph of the flow control for this Injector.
+ */
+class HTMLPurifier_Injector_AutoParagraph extends HTMLPurifier_Injector
+{
+
+    public $name = 'AutoParagraph';
+    public $needed = array('p');
+
+    private function _pStart() {
+        $par = new HTMLPurifier_Token_Start('p');
+        $par->armor['MakeWellFormed_TagClosedError'] = true;
+        return $par;
+    }
+
+    public function handleText(&$token) {
+        $text = $token->data;
+        // Does the current parent allow <p> tags?
+        if ($this->allowsElement('p')) {
+            if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) {
+                // Note that we have differing behavior when dealing with text
+                // in the anonymous root node, or a node inside the document.
+                // If the text as a double-newline, the treatment is the same;
+                // if it doesn't, see the next if-block if you're in the document.
+
+                $i = $nesting = null;
+                if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) {
+                    // State 1.1: ...    ^ (whitespace, then document end)
+                    //               ----
+                    // This is a degenerate case
+                } else {
+                    if (!$token->is_whitespace || $this->_isInline($current)) {
+                        // State 1.2: PAR1
+                        //            ----
+
+                        // State 1.3: PAR1\n\nPAR2
+                        //            ------------
+
+                        // State 1.4: <div>PAR1\n\nPAR2 (see State 2)
+                        //                 ------------
+                        $token = array($this->_pStart());
+                        $this->_splitText($text, $token);
+                    } else {
+                        // State 1.5: \n<hr />
+                        //            --
+                    }
+                }
+            } else {
+                // State 2:   <div>PAR1... (similar to 1.4)
+                //                 ----
+
+                // We're in an element that allows paragraph tags, but we're not
+                // sure if we're going to need them.
+                if ($this->_pLookAhead()) {
+                    // State 2.1: <div>PAR1<b>PAR1\n\nPAR2
+                    //                 ----
+                    // Note: This will always be the first child, since any
+                    // previous inline element would have triggered this very
+                    // same routine, and found the double newline. One possible
+                    // exception would be a comment.
+                    $token = array($this->_pStart(), $token);
+                } else {
+                    // State 2.2.1: <div>PAR1<div>
+                    //                   ----
+
+                    // State 2.2.2: <div>PAR1<b>PAR1</b></div>
+                    //                   ----
+                }
+            }
+        // Is the current parent a <p> tag?
+        } elseif (
+            !empty($this->currentNesting) &&
+            $this->currentNesting[count($this->currentNesting)-1]->name == 'p'
+        ) {
+            // State 3.1: ...<p>PAR1
+            //                  ----
+
+            // State 3.2: ...<p>PAR1\n\nPAR2
+            //                  ------------
+            $token = array();
+            $this->_splitText($text, $token);
+        // Abort!
+        } else {
+            // State 4.1: ...<b>PAR1
+            //                  ----
+
+            // State 4.2: ...<b>PAR1\n\nPAR2
+            //                  ------------
+        }
+    }
+
+    public function handleElement(&$token) {
+        // We don't have to check if we're already in a <p> tag for block
+        // tokens, because the tag would have been autoclosed by MakeWellFormed.
+        if ($this->allowsElement('p')) {
+            if (!empty($this->currentNesting)) {
+                if ($this->_isInline($token)) {
+                    // State 1: <div>...<b>
+                    //                  ---
+
+                    // Check if this token is adjacent to the parent token
+                    // (seek backwards until token isn't whitespace)
+                    $i = null;
+                    $this->backward($i, $prev);
+
+                    if (!$prev instanceof HTMLPurifier_Token_Start) {
+                        // Token wasn't adjacent
+
+                        if (
+                            $prev instanceof HTMLPurifier_Token_Text &&
+                            substr($prev->data, -2) === "\n\n"
+                        ) {
+                            // State 1.1.4: <div><p>PAR1</p>\n\n<b>
+                            //                                  ---
+
+                            // Quite frankly, this should be handled by splitText
+                            $token = array($this->_pStart(), $token);
+                        } else {
+                            // State 1.1.1: <div><p>PAR1</p><b>
+                            //                              ---
+
+                            // State 1.1.2: <div><br /><b>
+                            //                         ---
+
+                            // State 1.1.3: <div>PAR<b>
+                            //                      ---
+                        }
+
+                    } else {
+                        // State 1.2.1: <div><b>
+                        //                   ---
+
+                        // Lookahead to see if <p> is needed.
+                        if ($this->_pLookAhead()) {
+                            // State 1.3.1: <div><b>PAR1\n\nPAR2
+                            //                   ---
+                            $token = array($this->_pStart(), $token);
+                        } else {
+                            // State 1.3.2: <div><b>PAR1</b></div>
+                            //                   ---
+
+                            // State 1.3.3: <div><b>PAR1</b><div></div>\n\n</div>
+                            //                   ---
+                        }
+                    }
+                } else {
+                    // State 2.3: ...<div>
+                    //               -----
+                }
+            } else {
+                if ($this->_isInline($token)) {
+                    // State 3.1: <b>
+                    //            ---
+                    // This is where the {p} tag is inserted, not reflected in
+                    // inputTokens yet, however.
+                    $token = array($this->_pStart(), $token);
+                } else {
+                    // State 3.2: <div>
+                    //            -----
+                }
+
+                $i = null;
+                if ($this->backward($i, $prev)) {
+                    if (
+                        !$prev instanceof HTMLPurifier_Token_Text
+                    ) {
+                        // State 3.1.1: ...</p>{p}<b>
+                        //                        ---
+
+                        // State 3.2.1: ...</p><div>
+                        //                     -----
+
+                        if (!is_array($token)) $token = array($token);
+                        array_unshift($token, new HTMLPurifier_Token_Text("\n\n"));
+                    } else {
+                        // State 3.1.2: ...</p>\n\n{p}<b>
+                        //                            ---
+
+                        // State 3.2.2: ...</p>\n\n<div>
+                        //                         -----
+
+                        // Note: PAR<ELEM> cannot occur because PAR would have been
+                        // wrapped in <p> tags.
+                    }
+                }
+            }
+        } else {
+            // State 2.2: <ul><li>
+            //                ----
+
+            // State 2.4: <p><b>
+            //               ---
+        }
+    }
+
+    /**
+     * Splits up a text in paragraph tokens and appends them
+     * to the result stream that will replace the original
+     * @param $data String text data that will be processed
+     *    into paragraphs
+     * @param $result Reference to array of tokens that the
+     *    tags will be appended onto
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     */
+    private function _splitText($data, &$result) {
+        $raw_paragraphs = explode("\n\n", $data);
+        $paragraphs  = array(); // without empty paragraphs
+        $needs_start = false;
+        $needs_end   = false;
+
+        $c = count($raw_paragraphs);
+        if ($c == 1) {
+            // There were no double-newlines, abort quickly. In theory this
+            // should never happen.
+            $result[] = new HTMLPurifier_Token_Text($data);
+            return;
+        }
+        for ($i = 0; $i < $c; $i++) {
+            $par = $raw_paragraphs[$i];
+            if (trim($par) !== '') {
+                $paragraphs[] = $par;
+            } else {
+                if ($i == 0) {
+                    // Double newline at the front
+                    if (empty($result)) {
+                        // The empty result indicates that the AutoParagraph
+                        // injector did not add any start paragraph tokens.
+                        // This means that we have been in a paragraph for
+                        // a while, and the newline means we should start a new one.
+                        $result[] = new HTMLPurifier_Token_End('p');
+                        $result[] = new HTMLPurifier_Token_Text("\n\n");
+                        // However, the start token should only be added if
+                        // there is more processing to be done (i.e. there are
+                        // real paragraphs in here). If there are none, the
+                        // next start paragraph tag will be handled by the
+                        // next call to the injector
+                        $needs_start = true;
+                    } else {
+                        // We just started a new paragraph!
+                        // Reinstate a double-newline for presentation's sake, since
+                        // it was in the source code.
+                        array_unshift($result, new HTMLPurifier_Token_Text("\n\n"));
+                    }
+                } elseif ($i + 1 == $c) {
+                    // Double newline at the end
+                    // There should be a trailing </p> when we're finally done.
+                    $needs_end = true;
+                }
+            }
+        }
+
+        // Check if this was just a giant blob of whitespace. Move this earlier,
+        // perhaps?
+        if (empty($paragraphs)) {
+            return;
+        }
+
+        // Add the start tag indicated by \n\n at the beginning of $data
+        if ($needs_start) {
+            $result[] = $this->_pStart();
+        }
+
+        // Append the paragraphs onto the result
+        foreach ($paragraphs as $par) {
+            $result[] = new HTMLPurifier_Token_Text($par);
+            $result[] = new HTMLPurifier_Token_End('p');
+            $result[] = new HTMLPurifier_Token_Text("\n\n");
+            $result[] = $this->_pStart();
+        }
+
+        // Remove trailing start token; Injector will handle this later if
+        // it was indeed needed. This prevents from needing to do a lookahead,
+        // at the cost of a lookbehind later.
+        array_pop($result);
+
+        // If there is no need for an end tag, remove all of it and let
+        // MakeWellFormed close it later.
+        if (!$needs_end) {
+            array_pop($result); // removes \n\n
+            array_pop($result); // removes </p>
+        }
+
+    }
+
+    /**
+     * Returns true if passed token is inline (and, ergo, allowed in
+     * paragraph tags)
+     */
+    private function _isInline($token) {
+        return isset($this->htmlDefinition->info['p']->child->elements[$token->name]);
+    }
+
+    /**
+     * Looks ahead in the token list and determines whether or not we need
+     * to insert a <p> tag.
+     */
+    private function _pLookAhead() {
+        $this->current($i, $current);
+        if ($current instanceof HTMLPurifier_Token_Start) $nesting = 1;
+        else $nesting = 0;
+        $ok = false;
+        while ($this->forwardUntilEndToken($i, $current, $nesting)) {
+            $result = $this->_checkNeedsP($current);
+            if ($result !== null) {
+                $ok = $result;
+                break;
+            }
+        }
+        return $ok;
+    }
+
+    /**
+     * Determines if a particular token requires an earlier inline token
+     * to get a paragraph. This should be used with _forwardUntilEndToken
+     */
+    private function _checkNeedsP($current) {
+        if ($current instanceof HTMLPurifier_Token_Start){
+            if (!$this->_isInline($current)) {
+                // <div>PAR1<div>
+                //      ----
+                // Terminate early, since we hit a block element
+                return false;
+            }
+        } elseif ($current instanceof HTMLPurifier_Token_Text) {
+            if (strpos($current->data, "\n\n") !== false) {
+                // <div>PAR1<b>PAR1\n\nPAR2
+                //      ----
+                return true;
+            } else {
+                // <div>PAR1<b>PAR1...
+                //      ----
+            }
+        }
+        return null;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Injector/DisplayLinkURI.php b/application/libraries/htmlpurifier/HTMLPurifier/Injector/DisplayLinkURI.php
new file mode 100644
index 0000000..9dce9bd
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Injector/DisplayLinkURI.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Injector that displays the URL of an anchor instead of linking to it, in addition to showing the text of the link.
+ */
+class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector
+{
+
+    public $name = 'DisplayLinkURI';
+    public $needed = array('a');
+
+    public function handleElement(&$token) {
+    }
+
+    public function handleEnd(&$token) {
+        if (isset($token->start->attr['href'])){
+            $url = $token->start->attr['href'];
+            unset($token->start->attr['href']);
+            $token = array($token, new HTMLPurifier_Token_Text(" ($url)"));
+        } else {
+            // nothing to display
+        }
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Injector/Linkify.php b/application/libraries/htmlpurifier/HTMLPurifier/Injector/Linkify.php
new file mode 100644
index 0000000..296dac2
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Injector/Linkify.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Injector that converts http, https and ftp text URLs to actual links.
+ */
+class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector
+{
+
+    public $name = 'Linkify';
+    public $needed = array('a' => array('href'));
+
+    public function handleText(&$token) {
+        if (!$this->allowsElement('a')) return;
+
+        if (strpos($token->data, '://') === false) {
+            // our really quick heuristic failed, abort
+            // this may not work so well if we want to match things like
+            // "google.com", but then again, most people don't
+            return;
+        }
+
+        // there is/are URL(s). Let's split the string:
+        // Note: this regex is extremely permissive
+        $bits = preg_split('#((?:https?|ftp)://[^\s\'"<>()]+)#S', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
+
+        $token = array();
+
+        // $i = index
+        // $c = count
+        // $l = is link
+        for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
+            if (!$l) {
+                if ($bits[$i] === '') continue;
+                $token[] = new HTMLPurifier_Token_Text($bits[$i]);
+            } else {
+                $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i]));
+                $token[] = new HTMLPurifier_Token_Text($bits[$i]);
+                $token[] = new HTMLPurifier_Token_End('a');
+            }
+        }
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Injector/PurifierLinkify.php b/application/libraries/htmlpurifier/HTMLPurifier/Injector/PurifierLinkify.php
new file mode 100644
index 0000000..ad2455a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Injector/PurifierLinkify.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Injector that converts configuration directive syntax %Namespace.Directive
+ * to links
+ */
+class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector
+{
+
+    public $name = 'PurifierLinkify';
+    public $docURL;
+    public $needed = array('a' => array('href'));
+
+    public function prepare($config, $context) {
+        $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL');
+        return parent::prepare($config, $context);
+    }
+
+    public function handleText(&$token) {
+        if (!$this->allowsElement('a')) return;
+        if (strpos($token->data, '%') === false) return;
+
+        $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE);
+        $token = array();
+
+        // $i = index
+        // $c = count
+        // $l = is link
+        for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) {
+            if (!$l) {
+                if ($bits[$i] === '') continue;
+                $token[] = new HTMLPurifier_Token_Text($bits[$i]);
+            } else {
+                $token[] = new HTMLPurifier_Token_Start('a',
+                    array('href' => str_replace('%s', $bits[$i], $this->docURL)));
+                $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]);
+                $token[] = new HTMLPurifier_Token_End('a');
+            }
+        }
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Injector/RemoveEmpty.php b/application/libraries/htmlpurifier/HTMLPurifier/Injector/RemoveEmpty.php
new file mode 100644
index 0000000..423f079
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Injector/RemoveEmpty.php
@@ -0,0 +1,54 @@
+<?php
+
+class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector
+{
+
+    private $context, $config, $attrValidator, $removeNbsp, $removeNbspExceptions;
+
+    // TODO: make me configurable
+    private $_exclude = array('colgroup' => 1, 'th' => 1, 'td' => 1, 'iframe' => 1);
+
+    public function prepare($config, $context) {
+        parent::prepare($config, $context);
+        $this->config = $config;
+        $this->context = $context;
+        $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp');
+        $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions');
+        $this->attrValidator = new HTMLPurifier_AttrValidator();
+    }
+
+    public function handleElement(&$token) {
+        if (!$token instanceof HTMLPurifier_Token_Start) return;
+        $next = false;
+        for ($i = $this->inputIndex + 1, $c = count($this->inputTokens); $i < $c; $i++) {
+            $next = $this->inputTokens[$i];
+            if ($next instanceof HTMLPurifier_Token_Text) {
+                if ($next->is_whitespace) continue;
+                if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) {
+                    $plain = str_replace("\xC2\xA0", "", $next->data);
+                    $isWsOrNbsp = $plain === '' || ctype_space($plain);
+                    if ($isWsOrNbsp) continue;
+                }
+            }
+            break;
+        }
+        if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) {
+            if (isset($this->_exclude[$token->name])) return;
+            $this->attrValidator->validateToken($token, $this->config, $this->context);
+            $token->armor['ValidateAttributes'] = true;
+            if (isset($token->attr['id']) || isset($token->attr['name'])) return;
+            $token = $i - $this->inputIndex + 1;
+            for ($b = $this->inputIndex - 1; $b > 0; $b--) {
+                $prev = $this->inputTokens[$b];
+                if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) continue;
+                break;
+            }
+            // This is safe because we removed the token that triggered this.
+            $this->rewind($b - 1);
+            return;
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php b/application/libraries/htmlpurifier/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php
new file mode 100644
index 0000000..b213134
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * Injector that removes spans with no attributes
+ */
+class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector
+{
+    public $name = 'RemoveSpansWithoutAttributes';
+    public $needed = array('span');
+
+    private $attrValidator;
+
+    /**
+     * Used by AttrValidator
+     */
+    private $config;
+    private $context;
+
+    public function prepare($config, $context) {
+        $this->attrValidator = new HTMLPurifier_AttrValidator();
+        $this->config = $config;
+        $this->context = $context;
+        return parent::prepare($config, $context);
+    }
+
+    public function handleElement(&$token) {
+        if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) {
+            return;
+        }
+
+        // We need to validate the attributes now since this doesn't normally
+        // happen until after MakeWellFormed. If all the attributes are removed
+        // the span needs to be removed too.
+        $this->attrValidator->validateToken($token, $this->config, $this->context);
+        $token->armor['ValidateAttributes'] = true;
+
+        if (!empty($token->attr)) {
+            return;
+        }
+
+        $nesting = 0;
+        $spanContentTokens = array();
+        while ($this->forwardUntilEndToken($i, $current, $nesting)) {}
+
+        if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') {
+            // Mark closing span tag for deletion
+            $current->markForDeletion = true;
+            // Delete open span tag
+            $token = false;
+        }
+    }
+
+    public function handleEnd(&$token) {
+        if ($token->markForDeletion) {
+            $token = false;
+        }
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Injector/SafeObject.php b/application/libraries/htmlpurifier/HTMLPurifier/Injector/SafeObject.php
new file mode 100644
index 0000000..c1d8b04
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Injector/SafeObject.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * Adds important param elements to inside of object in order to make
+ * things safe.
+ */
+class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector
+{
+    public $name = 'SafeObject';
+    public $needed = array('object', 'param');
+
+    protected $objectStack = array();
+    protected $paramStack  = array();
+
+    // Keep this synchronized with AttrTransform/SafeParam.php
+    protected $addParam = array(
+        'allowScriptAccess' => 'never',
+        'allowNetworking' => 'internal',
+    );
+    protected $allowedParam = array(
+        'wmode' => true,
+        'movie' => true,
+        'flashvars' => true,
+        'src' => true,
+        'allowFullScreen' => true, // if omitted, assume to be 'false'
+    );
+
+    public function prepare($config, $context) {
+        parent::prepare($config, $context);
+    }
+
+    public function handleElement(&$token) {
+        if ($token->name == 'object') {
+            $this->objectStack[] = $token;
+            $this->paramStack[] = array();
+            $new = array($token);
+            foreach ($this->addParam as $name => $value) {
+                $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value));
+            }
+            $token = $new;
+        } elseif ($token->name == 'param') {
+            $nest = count($this->currentNesting) - 1;
+            if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') {
+                $i = count($this->objectStack) - 1;
+                if (!isset($token->attr['name'])) {
+                    $token = false;
+                    return;
+                }
+                $n = $token->attr['name'];
+                // We need this fix because YouTube doesn't supply a data
+                // attribute, which we need if a type is specified. This is
+                // *very* Flash specific.
+                if (!isset($this->objectStack[$i]->attr['data']) &&
+                    ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src')) {
+                    $this->objectStack[$i]->attr['data'] = $token->attr['value'];
+                }
+                // Check if the parameter is the correct value but has not
+                // already been added
+                if (
+                    !isset($this->paramStack[$i][$n]) &&
+                    isset($this->addParam[$n]) &&
+                    $token->attr['name'] === $this->addParam[$n]
+                ) {
+                    // keep token, and add to param stack
+                    $this->paramStack[$i][$n] = true;
+                } elseif (isset($this->allowedParam[$n])) {
+                    // keep token, don't do anything to it
+                    // (could possibly check for duplicates here)
+                } else {
+                    $token = false;
+                }
+            } else {
+                // not directly inside an object, DENY!
+                $token = false;
+            }
+        }
+    }
+
+    public function handleEnd(&$token) {
+        // This is the WRONG way of handling the object and param stacks;
+        // we should be inserting them directly on the relevant object tokens
+        // so that the global stack handling handles it.
+        if ($token->name == 'object') {
+            array_pop($this->objectStack);
+            array_pop($this->paramStack);
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Language.php b/application/libraries/htmlpurifier/HTMLPurifier/Language.php
new file mode 100644
index 0000000..3e2be03
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Language.php
@@ -0,0 +1,163 @@
+<?php
+
+/**
+ * Represents a language and defines localizable string formatting and
+ * other functions, as well as the localized messages for HTML Purifier.
+ */
+class HTMLPurifier_Language
+{
+
+    /**
+     * ISO 639 language code of language. Prefers shortest possible version
+     */
+    public $code = 'en';
+
+    /**
+     * Fallback language code
+     */
+    public $fallback = false;
+
+    /**
+     * Array of localizable messages
+     */
+    public $messages = array();
+
+    /**
+     * Array of localizable error codes
+     */
+    public $errorNames = array();
+
+    /**
+     * True if no message file was found for this language, so English
+     * is being used instead. Check this if you'd like to notify the
+     * user that they've used a non-supported language.
+     */
+    public $error = false;
+
+    /**
+     * Has the language object been loaded yet?
+     * @todo Make it private, fix usage in HTMLPurifier_LanguageTest
+     */
+    public $_loaded = false;
+
+    /**
+     * Instances of HTMLPurifier_Config and HTMLPurifier_Context
+     */
+    protected $config, $context;
+
+    public function __construct($config, $context) {
+        $this->config  = $config;
+        $this->context = $context;
+    }
+
+    /**
+     * Loads language object with necessary info from factory cache
+     * @note This is a lazy loader
+     */
+    public function load() {
+        if ($this->_loaded) return;
+        $factory = HTMLPurifier_LanguageFactory::instance();
+        $factory->loadLanguage($this->code);
+        foreach ($factory->keys as $key) {
+            $this->$key = $factory->cache[$this->code][$key];
+        }
+        $this->_loaded = true;
+    }
+
+    /**
+     * Retrieves a localised message.
+     * @param $key string identifier of message
+     * @return string localised message
+     */
+    public function getMessage($key) {
+        if (!$this->_loaded) $this->load();
+        if (!isset($this->messages[$key])) return "[$key]";
+        return $this->messages[$key];
+    }
+
+    /**
+     * Retrieves a localised error name.
+     * @param $int integer error number, corresponding to PHP's error
+     *             reporting
+     * @return string localised message
+     */
+    public function getErrorName($int) {
+        if (!$this->_loaded) $this->load();
+        if (!isset($this->errorNames[$int])) return "[Error: $int]";
+        return $this->errorNames[$int];
+    }
+
+    /**
+     * Converts an array list into a string readable representation
+     */
+    public function listify($array) {
+        $sep      = $this->getMessage('Item separator');
+        $sep_last = $this->getMessage('Item separator last');
+        $ret = '';
+        for ($i = 0, $c = count($array); $i < $c; $i++) {
+            if ($i == 0) {
+            } elseif ($i + 1 < $c) {
+                $ret .= $sep;
+            } else {
+                $ret .= $sep_last;
+            }
+            $ret .= $array[$i];
+        }
+        return $ret;
+    }
+
+    /**
+     * Formats a localised message with passed parameters
+     * @param $key string identifier of message
+     * @param $args Parameters to substitute in
+     * @return string localised message
+     * @todo Implement conditionals? Right now, some messages make
+     *     reference to line numbers, but those aren't always available
+     */
+    public function formatMessage($key, $args = array()) {
+        if (!$this->_loaded) $this->load();
+        if (!isset($this->messages[$key])) return "[$key]";
+        $raw = $this->messages[$key];
+        $subst = array();
+        $generator = false;
+        foreach ($args as $i => $value) {
+            if (is_object($value)) {
+                if ($value instanceof HTMLPurifier_Token) {
+                    // factor this out some time
+                    if (!$generator) $generator = $this->context->get('Generator');
+                    if (isset($value->name)) $subst['$'.$i.'.Name'] = $value->name;
+                    if (isset($value->data)) $subst['$'.$i.'.Data'] = $value->data;
+                    $subst['$'.$i.'.Compact'] =
+                    $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value);
+                    // a more complex algorithm for compact representation
+                    // could be introduced for all types of tokens. This
+                    // may need to be factored out into a dedicated class
+                    if (!empty($value->attr)) {
+                        $stripped_token = clone $value;
+                        $stripped_token->attr = array();
+                        $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token);
+                    }
+                    $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown';
+                }
+                continue;
+            } elseif (is_array($value)) {
+                $keys = array_keys($value);
+                if (array_keys($keys) === $keys) {
+                    // list
+                    $subst['$'.$i] = $this->listify($value);
+                } else {
+                    // associative array
+                    // no $i implementation yet, sorry
+                    $subst['$'.$i.'.Keys'] = $this->listify($keys);
+                    $subst['$'.$i.'.Values'] = $this->listify(array_values($value));
+                }
+                continue;
+            }
+            $subst['$' . $i] = $value;
+        }
+        return strtr($raw, $subst);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Language/classes/en-x-test.php b/application/libraries/htmlpurifier/HTMLPurifier/Language/classes/en-x-test.php
new file mode 100644
index 0000000..d52fcb7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Language/classes/en-x-test.php
@@ -0,0 +1,12 @@
+<?php
+
+// private class for unit testing
+
+class HTMLPurifier_Language_en_x_test extends HTMLPurifier_Language
+{
+
+
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en-x-test.php b/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en-x-test.php
new file mode 100644
index 0000000..1c046f3
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en-x-test.php
@@ -0,0 +1,11 @@
+<?php
+
+// private language message file for unit testing purposes
+
+$fallback = 'en';
+
+$messages = array(
+    'HTMLPurifier' => 'HTML Purifier X'
+);
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en-x-testmini.php b/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en-x-testmini.php
new file mode 100644
index 0000000..806c83f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en-x-testmini.php
@@ -0,0 +1,12 @@
+<?php
+
+// private language message file for unit testing purposes
+// this language file has no class associated with it
+
+$fallback = 'en';
+
+$messages = array(
+    'HTMLPurifier' => 'HTML Purifier XNone'
+);
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en.php b/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en.php
new file mode 100644
index 0000000..8d7b573
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Language/messages/en.php
@@ -0,0 +1,63 @@
+<?php
+
+$fallback = false;
+
+$messages = array(
+
+'HTMLPurifier' => 'HTML Purifier',
+
+// for unit testing purposes
+'LanguageFactoryTest: Pizza' => 'Pizza',
+'LanguageTest: List' => '$1',
+'LanguageTest: Hash' => '$1.Keys; $1.Values',
+
+'Item separator' => ', ',
+'Item separator last' => ' and ', // non-Harvard style
+
+'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.',
+'ErrorCollector: At line'   => ' at line $line',
+'ErrorCollector: Incidental errors'  => 'Incidental errors',
+
+'Lexer: Unclosed comment'      => 'Unclosed comment',
+'Lexer: Unescaped lt'          => 'Unescaped less-than sign (<) should be <',
+'Lexer: Missing gt'            => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped',
+'Lexer: Missing attribute key' => 'Attribute declaration has no key',
+'Lexer: Missing end quote'     => 'Attribute declaration has no end quote',
+'Lexer: Extracted body'        => 'Removed document metadata tags',
+
+'Strategy_RemoveForeignElements: Tag transform'              => '<$1> element transformed into $CurrentToken.Serialized',
+'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1',
+'Strategy_RemoveForeignElements: Foreign element to text'    => 'Unrecognized $CurrentToken.Serialized tag converted to text',
+'Strategy_RemoveForeignElements: Foreign element removed'    => 'Unrecognized $CurrentToken.Serialized tag removed',
+'Strategy_RemoveForeignElements: Comment removed'            => 'Comment containing "$CurrentToken.Data" removed',
+'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed',
+'Strategy_RemoveForeignElements: Token removed to end'       => 'Tags and text starting from $1 element where removed to end',
+'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed',
+'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens',
+
+'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed',
+'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text',
+'Strategy_MakeWellFormed: Tag auto closed'             => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact',
+'Strategy_MakeWellFormed: Tag carryover'               => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact',
+'Strategy_MakeWellFormed: Stray end tag removed'       => 'Stray $CurrentToken.Serialized tag removed',
+'Strategy_MakeWellFormed: Stray end tag to text'       => 'Stray $CurrentToken.Serialized tag converted to text',
+'Strategy_MakeWellFormed: Tag closed by element end'   => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized',
+'Strategy_MakeWellFormed: Tag closed by document end'  => '$1.Compact tag started on line $1.Line closed by end of document',
+
+'Strategy_FixNesting: Node removed'          => '$CurrentToken.Compact node removed',
+'Strategy_FixNesting: Node excluded'         => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element',
+'Strategy_FixNesting: Node reorganized'      => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model',
+'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed',
+
+'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys',
+'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed',
+
+);
+
+$errorNames = array(
+    E_ERROR   => 'Error',
+    E_WARNING => 'Warning',
+    E_NOTICE  => 'Notice'
+);
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/LanguageFactory.php b/application/libraries/htmlpurifier/HTMLPurifier/LanguageFactory.php
new file mode 100644
index 0000000..134ef8c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/LanguageFactory.php
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * Class responsible for generating HTMLPurifier_Language objects, managing
+ * caching and fallbacks.
+ * @note Thanks to MediaWiki for the general logic, although this version
+ *       has been entirely rewritten
+ * @todo Serialized cache for languages
+ */
+class HTMLPurifier_LanguageFactory
+{
+
+    /**
+     * Cache of language code information used to load HTMLPurifier_Language objects
+     * Structure is: $factory->cache[$language_code][$key] = $value
+     * @value array map
+     */
+    public $cache;
+
+    /**
+     * Valid keys in the HTMLPurifier_Language object. Designates which
+     * variables to slurp out of a message file.
+     * @value array list
+     */
+    public $keys = array('fallback', 'messages', 'errorNames');
+
+    /**
+     * Instance of HTMLPurifier_AttrDef_Lang to validate language codes
+     * @value object HTMLPurifier_AttrDef_Lang
+     */
+    protected $validator;
+
+    /**
+     * Cached copy of dirname(__FILE__), directory of current file without
+     * trailing slash
+     * @value string filename
+     */
+    protected $dir;
+
+    /**
+     * Keys whose contents are a hash map and can be merged
+     * @value array lookup
+     */
+    protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true);
+
+    /**
+     * Keys whose contents are a list and can be merged
+     * @value array lookup
+     */
+    protected $mergeable_keys_list = array();
+
+    /**
+     * Retrieve sole instance of the factory.
+     * @param $prototype Optional prototype to overload sole instance with,
+     *                   or bool true to reset to default factory.
+     */
+    public static function instance($prototype = null) {
+        static $instance = null;
+        if ($prototype !== null) {
+            $instance = $prototype;
+        } elseif ($instance === null || $prototype == true) {
+            $instance = new HTMLPurifier_LanguageFactory();
+            $instance->setup();
+        }
+        return $instance;
+    }
+
+    /**
+     * Sets up the singleton, much like a constructor
+     * @note Prevents people from getting this outside of the singleton
+     */
+    public function setup() {
+        $this->validator = new HTMLPurifier_AttrDef_Lang();
+        $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier';
+    }
+
+    /**
+     * Creates a language object, handles class fallbacks
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     * @param $code Code to override configuration with. Private parameter.
+     */
+    public function create($config, $context, $code = false) {
+
+        // validate language code
+        if ($code === false) {
+            $code = $this->validator->validate(
+              $config->get('Core.Language'), $config, $context
+            );
+        } else {
+            $code = $this->validator->validate($code, $config, $context);
+        }
+        if ($code === false) $code = 'en'; // malformed code becomes English
+
+        $pcode = str_replace('-', '_', $code); // make valid PHP classname
+        static $depth = 0; // recursion protection
+
+        if ($code == 'en') {
+            $lang = new HTMLPurifier_Language($config, $context);
+        } else {
+            $class = 'HTMLPurifier_Language_' . $pcode;
+            $file  = $this->dir . '/Language/classes/' . $code . '.php';
+            if (file_exists($file) || class_exists($class, false)) {
+                $lang = new $class($config, $context);
+            } else {
+                // Go fallback
+                $raw_fallback = $this->getFallbackFor($code);
+                $fallback = $raw_fallback ? $raw_fallback : 'en';
+                $depth++;
+                $lang = $this->create($config, $context, $fallback);
+                if (!$raw_fallback) {
+                    $lang->error = true;
+                }
+                $depth--;
+            }
+        }
+
+        $lang->code = $code;
+
+        return $lang;
+
+    }
+
+    /**
+     * Returns the fallback language for language
+     * @note Loads the original language into cache
+     * @param $code string language code
+     */
+    public function getFallbackFor($code) {
+        $this->loadLanguage($code);
+        return $this->cache[$code]['fallback'];
+    }
+
+    /**
+     * Loads language into the cache, handles message file and fallbacks
+     * @param $code string language code
+     */
+    public function loadLanguage($code) {
+        static $languages_seen = array(); // recursion guard
+
+        // abort if we've already loaded it
+        if (isset($this->cache[$code])) return;
+
+        // generate filename
+        $filename = $this->dir . '/Language/messages/' . $code . '.php';
+
+        // default fallback : may be overwritten by the ensuing include
+        $fallback = ($code != 'en') ? 'en' : false;
+
+        // load primary localisation
+        if (!file_exists($filename)) {
+            // skip the include: will rely solely on fallback
+            $filename = $this->dir . '/Language/messages/en.php';
+            $cache = array();
+        } else {
+            include $filename;
+            $cache = compact($this->keys);
+        }
+
+        // load fallback localisation
+        if (!empty($fallback)) {
+
+            // infinite recursion guard
+            if (isset($languages_seen[$code])) {
+                trigger_error('Circular fallback reference in language ' .
+                    $code, E_USER_ERROR);
+                $fallback = 'en';
+            }
+            $language_seen[$code] = true;
+
+            // load the fallback recursively
+            $this->loadLanguage($fallback);
+            $fallback_cache = $this->cache[$fallback];
+
+            // merge fallback with current language
+            foreach ( $this->keys as $key ) {
+                if (isset($cache[$key]) && isset($fallback_cache[$key])) {
+                    if (isset($this->mergeable_keys_map[$key])) {
+                        $cache[$key] = $cache[$key] + $fallback_cache[$key];
+                    } elseif (isset($this->mergeable_keys_list[$key])) {
+                        $cache[$key] = array_merge( $fallback_cache[$key], $cache[$key] );
+                    }
+                } else {
+                    $cache[$key] = $fallback_cache[$key];
+                }
+            }
+
+        }
+
+        // save to cache for later retrieval
+        $this->cache[$code] = $cache;
+
+        return;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Length.php b/application/libraries/htmlpurifier/HTMLPurifier/Length.php
new file mode 100644
index 0000000..8d2a46b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Length.php
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * Represents a measurable length, with a string numeric magnitude
+ * and a unit. This object is immutable.
+ */
+class HTMLPurifier_Length
+{
+
+    /**
+     * String numeric magnitude.
+     */
+    protected $n;
+
+    /**
+     * String unit. False is permitted if $n = 0.
+     */
+    protected $unit;
+
+    /**
+     * Whether or not this length is valid. Null if not calculated yet.
+     */
+    protected $isValid;
+
+    /**
+     * Lookup array of units recognized by CSS 2.1
+     */
+    protected static $allowedUnits = array(
+        'em' => true, 'ex' => true, 'px' => true, 'in' => true,
+        'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true
+    );
+
+    /**
+     * @param number $n Magnitude
+     * @param string $u Unit
+     */
+    public function __construct($n = '0', $u = false) {
+        $this->n = (string) $n;
+        $this->unit = $u !== false ? (string) $u : false;
+    }
+
+    /**
+     * @param string $s Unit string, like '2em' or '3.4in'
+     * @warning Does not perform validation.
+     */
+    static public function make($s) {
+        if ($s instanceof HTMLPurifier_Length) return $s;
+        $n_length = strspn($s, '1234567890.+-');
+        $n = substr($s, 0, $n_length);
+        $unit = substr($s, $n_length);
+        if ($unit === '') $unit = false;
+        return new HTMLPurifier_Length($n, $unit);
+    }
+
+    /**
+     * Validates the number and unit.
+     */
+    protected function validate() {
+        // Special case:
+        if ($this->n === '+0' || $this->n === '-0') $this->n = '0';
+        if ($this->n === '0' && $this->unit === false) return true;
+        if (!ctype_lower($this->unit)) $this->unit = strtolower($this->unit);
+        if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) return false;
+        // Hack:
+        $def = new HTMLPurifier_AttrDef_CSS_Number();
+        $result = $def->validate($this->n, false, false);
+        if ($result === false) return false;
+        $this->n = $result;
+        return true;
+    }
+
+    /**
+     * Returns string representation of number.
+     */
+    public function toString() {
+        if (!$this->isValid()) return false;
+        return $this->n . $this->unit;
+    }
+
+    /**
+     * Retrieves string numeric magnitude.
+     */
+    public function getN() {return $this->n;}
+
+    /**
+     * Retrieves string unit.
+     */
+    public function getUnit() {return $this->unit;}
+
+    /**
+     * Returns true if this length unit is valid.
+     */
+    public function isValid() {
+        if ($this->isValid === null) $this->isValid = $this->validate();
+        return $this->isValid;
+    }
+
+    /**
+     * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal.
+     * @warning If both values are too large or small, this calculation will
+     *          not work properly
+     */
+    public function compareTo($l) {
+        if ($l === false) return false;
+        if ($l->unit !== $this->unit) {
+            $converter = new HTMLPurifier_UnitConverter();
+            $l = $converter->convert($l, $this->unit);
+            if ($l === false) return false;
+        }
+        return $this->n - $l->n;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Lexer.php b/application/libraries/htmlpurifier/HTMLPurifier/Lexer.php
new file mode 100644
index 0000000..9bdbbbb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Lexer.php
@@ -0,0 +1,326 @@
+<?php
+
+/**
+ * Forgivingly lexes HTML (SGML-style) markup into tokens.
+ *
+ * A lexer parses a string of SGML-style markup and converts them into
+ * corresponding tokens.  It doesn't check for well-formedness, although its
+ * internal mechanism may make this automatic (such as the case of
+ * HTMLPurifier_Lexer_DOMLex).  There are several implementations to choose
+ * from.
+ *
+ * A lexer is HTML-oriented: it might work with XML, but it's not
+ * recommended, as we adhere to a subset of the specification for optimization
+ * reasons. This might change in the future. Also, most tokenizers are not
+ * expected to handle DTDs or PIs.
+ *
+ * This class should not be directly instantiated, but you may use create() to
+ * retrieve a default copy of the lexer.  Being a supertype, this class
+ * does not actually define any implementation, but offers commonly used
+ * convenience functions for subclasses.
+ *
+ * @note The unit tests will instantiate this class for testing purposes, as
+ *       many of the utility functions require a class to be instantiated.
+ *       This means that, even though this class is not runnable, it will
+ *       not be declared abstract.
+ *
+ * @par
+ *
+ * @note
+ * We use tokens rather than create a DOM representation because DOM would:
+ *
+ * @par
+ *  -# Require more processing and memory to create,
+ *  -# Is not streamable, and
+ *  -# Has the entire document structure (html and body not needed).
+ *
+ * @par
+ * However, DOM is helpful in that it makes it easy to move around nodes
+ * without a lot of lookaheads to see when a tag is closed. This is a
+ * limitation of the token system and some workarounds would be nice.
+ */
+class HTMLPurifier_Lexer
+{
+
+    /**
+     * Whether or not this lexer implements line-number/column-number tracking.
+     * If it does, set to true.
+     */
+    public $tracksLineNumbers = false;
+
+    // -- STATIC ----------------------------------------------------------
+
+    /**
+     * Retrieves or sets the default Lexer as a Prototype Factory.
+     *
+     * By default HTMLPurifier_Lexer_DOMLex will be returned. There are
+     * a few exceptions involving special features that only DirectLex
+     * implements.
+     *
+     * @note The behavior of this class has changed, rather than accepting
+     *       a prototype object, it now accepts a configuration object.
+     *       To specify your own prototype, set %Core.LexerImpl to it.
+     *       This change in behavior de-singletonizes the lexer object.
+     *
+     * @param $config Instance of HTMLPurifier_Config
+     * @return Concrete lexer.
+     */
+    public static function create($config) {
+
+        if (!($config instanceof HTMLPurifier_Config)) {
+            $lexer = $config;
+            trigger_error("Passing a prototype to
+              HTMLPurifier_Lexer::create() is deprecated, please instead
+              use %Core.LexerImpl", E_USER_WARNING);
+        } else {
+            $lexer = $config->get('Core.LexerImpl');
+        }
+
+        $needs_tracking =
+            $config->get('Core.MaintainLineNumbers') ||
+            $config->get('Core.CollectErrors');
+
+        $inst = null;
+        if (is_object($lexer)) {
+            $inst = $lexer;
+        } else {
+
+            if (is_null($lexer)) { do {
+                // auto-detection algorithm
+
+                if ($needs_tracking) {
+                    $lexer = 'DirectLex';
+                    break;
+                }
+
+                if (
+                    class_exists('DOMDocument') &&
+                    method_exists('DOMDocument', 'loadHTML') &&
+                    !extension_loaded('domxml')
+                ) {
+                    // check for DOM support, because while it's part of the
+                    // core, it can be disabled compile time. Also, the PECL
+                    // domxml extension overrides the default DOM, and is evil
+                    // and nasty and we shan't bother to support it
+                    $lexer = 'DOMLex';
+                } else {
+                    $lexer = 'DirectLex';
+                }
+
+            } while(0); } // do..while so we can break
+
+            // instantiate recognized string names
+            switch ($lexer) {
+                case 'DOMLex':
+                    $inst = new HTMLPurifier_Lexer_DOMLex();
+                    break;
+                case 'DirectLex':
+                    $inst = new HTMLPurifier_Lexer_DirectLex();
+                    break;
+                case 'PH5P':
+                    $inst = new HTMLPurifier_Lexer_PH5P();
+                    break;
+                default:
+                    throw new HTMLPurifier_Exception("Cannot instantiate unrecognized Lexer type " . htmlspecialchars($lexer));
+            }
+        }
+
+        if (!$inst) throw new HTMLPurifier_Exception('No lexer was instantiated');
+
+        // once PHP DOM implements native line numbers, or we
+        // hack out something using XSLT, remove this stipulation
+        if ($needs_tracking && !$inst->tracksLineNumbers) {
+            throw new HTMLPurifier_Exception('Cannot use lexer that does not support line numbers with Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)');
+        }
+
+        return $inst;
+
+    }
+
+    // -- CONVENIENCE MEMBERS ---------------------------------------------
+
+    public function __construct() {
+        $this->_entity_parser = new HTMLPurifier_EntityParser();
+    }
+
+    /**
+     * Most common entity to raw value conversion table for special entities.
+     */
+    protected $_special_entity2str =
+            array(
+                    '"' => '"',
+                    '&'  => '&',
+                    '<'   => '<',
+                    '>'   => '>',
+                    '''  => "'",
+                    ''' => "'",
+                    '&#x27;' => "'"
+            );
+
+    /**
+     * Parses special entities into the proper characters.
+     *
+     * This string will translate escaped versions of the special characters
+     * into the correct ones.
+     *
+     * @warning
+     * You should be able to treat the output of this function as
+     * completely parsed, but that's only because all other entities should
+     * have been handled previously in substituteNonSpecialEntities()
+     *
+     * @param $string String character data to be parsed.
+     * @returns Parsed character data.
+     */
+    public function parseData($string) {
+
+        // following functions require at least one character
+        if ($string === '') return '';
+
+        // subtracts amps that cannot possibly be escaped
+        $num_amp = substr_count($string, '&') - substr_count($string, '& ') -
+            ($string[strlen($string)-1] === '&' ? 1 : 0);
+
+        if (!$num_amp) return $string; // abort if no entities
+        $num_esc_amp = substr_count($string, '&');
+        $string = strtr($string, $this->_special_entity2str);
+
+        // code duplication for sake of optimization, see above
+        $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') -
+            ($string[strlen($string)-1] === '&' ? 1 : 0);
+
+        if ($num_amp_2 <= $num_esc_amp) return $string;
+
+        // hmm... now we have some uncommon entities. Use the callback.
+        $string = $this->_entity_parser->substituteSpecialEntities($string);
+        return $string;
+    }
+
+    /**
+     * Lexes an HTML string into tokens.
+     *
+     * @param $string String HTML.
+     * @return HTMLPurifier_Token array representation of HTML.
+     */
+    public function tokenizeHTML($string, $config, $context) {
+        trigger_error('Call to abstract class', E_USER_ERROR);
+    }
+
+    /**
+     * Translates CDATA sections into regular sections (through escaping).
+     *
+     * @param $string HTML string to process.
+     * @returns HTML with CDATA sections escaped.
+     */
+    protected static function escapeCDATA($string) {
+        return preg_replace_callback(
+            '/<!\[CDATA\[(.+?)\]\]>/s',
+            array('HTMLPurifier_Lexer', 'CDATACallback'),
+            $string
+        );
+    }
+
+    /**
+     * Special CDATA case that is especially convoluted for <script>
+     */
+    protected static function escapeCommentedCDATA($string) {
+        return preg_replace_callback(
+            '#<!--//--><!\[CDATA\[//><!--(.+?)//--><!\]\]>#s',
+            array('HTMLPurifier_Lexer', 'CDATACallback'),
+            $string
+        );
+    }
+
+    /**
+     * Special Internet Explorer conditional comments should be removed.
+     */
+    protected static function removeIEConditional($string) {
+        return preg_replace(
+            '#<!--\[if [^>]+\]>.*?<!\[endif\]-->#si', // probably should generalize for all strings
+            '',
+            $string
+        );
+    }
+
+    /**
+     * Callback function for escapeCDATA() that does the work.
+     *
+     * @warning Though this is public in order to let the callback happen,
+     *          calling it directly is not recommended.
+     * @params $matches PCRE matches array, with index 0 the entire match
+     *                  and 1 the inside of the CDATA section.
+     * @returns Escaped internals of the CDATA section.
+     */
+    protected static function CDATACallback($matches) {
+        // not exactly sure why the character set is needed, but whatever
+        return htmlspecialchars($matches[1], ENT_COMPAT, 'UTF-8');
+    }
+
+    /**
+     * Takes a piece of HTML and normalizes it by converting entities, fixing
+     * encoding, extracting bits, and other good stuff.
+     * @todo Consider making protected
+     */
+    public function normalize($html, $config, $context) {
+
+        // normalize newlines to \n
+        if ($config->get('Core.NormalizeNewlines')) {
+            $html = str_replace("\r\n", "\n", $html);
+            $html = str_replace("\r", "\n", $html);
+        }
+
+        if ($config->get('HTML.Trusted')) {
+            // escape convoluted CDATA
+            $html = $this->escapeCommentedCDATA($html);
+        }
+
+        // escape CDATA
+        $html = $this->escapeCDATA($html);
+
+        $html = $this->removeIEConditional($html);
+
+        // extract body from document if applicable
+        if ($config->get('Core.ConvertDocumentToFragment')) {
+            $e = false;
+            if ($config->get('Core.CollectErrors')) {
+                $e =& $context->get('ErrorCollector');
+            }
+            $new_html = $this->extractBody($html);
+            if ($e && $new_html != $html) {
+                $e->send(E_WARNING, 'Lexer: Extracted body');
+            }
+            $html = $new_html;
+        }
+
+        // expand entities that aren't the big five
+        $html = $this->_entity_parser->substituteNonSpecialEntities($html);
+
+        // clean into wellformed UTF-8 string for an SGML context: this has
+        // to be done after entity expansion because the entities sometimes
+        // represent non-SGML characters (horror, horror!)
+        $html = HTMLPurifier_Encoder::cleanUTF8($html);
+
+        // if processing instructions are to removed, remove them now
+        if ($config->get('Core.RemoveProcessingInstructions')) {
+            $html = preg_replace('#<\?.+?\?>#s', '', $html);
+        }
+
+        return $html;
+    }
+
+    /**
+     * Takes a string of HTML (fragment or document) and returns the content
+     * @todo Consider making protected
+     */
+    public function extractBody($html) {
+        $matches = array();
+        $result = preg_match('!<body[^>]*>(.*)</body>!is', $html, $matches);
+        if ($result) {
+            return $matches[1];
+        } else {
+            return $html;
+        }
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Lexer/DOMLex.php b/application/libraries/htmlpurifier/HTMLPurifier/Lexer/DOMLex.php
new file mode 100644
index 0000000..82f3774
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Lexer/DOMLex.php
@@ -0,0 +1,243 @@
+<?php
+
+/**
+ * Parser that uses PHP 5's DOM extension (part of the core).
+ *
+ * In PHP 5, the DOM XML extension was revamped into DOM and added to the core.
+ * It gives us a forgiving HTML parser, which we use to transform the HTML
+ * into a DOM, and then into the tokens.  It is blazingly fast (for large
+ * documents, it performs twenty times faster than
+ * HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5.
+ *
+ * @note Any empty elements will have empty tokens associated with them, even if
+ * this is prohibited by the spec. This is cannot be fixed until the spec
+ * comes into play.
+ *
+ * @note PHP's DOM extension does not actually parse any entities, we use
+ *       our own function to do that.
+ *
+ * @warning DOM tends to drop whitespace, which may wreak havoc on indenting.
+ *          If this is a huge problem, due to the fact that HTML is hand
+ *          edited and you are unable to get a parser cache that caches the
+ *          the output of HTML Purifier while keeping the original HTML lying
+ *          around, you may want to run Tidy on the resulting output or use
+ *          HTMLPurifier_DirectLex
+ */
+
+class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer
+{
+
+    private $factory;
+
+    public function __construct() {
+        // setup the factory
+        parent::__construct();
+        $this->factory = new HTMLPurifier_TokenFactory();
+    }
+
+    public function tokenizeHTML($html, $config, $context) {
+
+        $html = $this->normalize($html, $config, $context);
+
+        // attempt to armor stray angled brackets that cannot possibly
+        // form tags and thus are probably being used as emoticons
+        if ($config->get('Core.AggressivelyFixLt')) {
+            $char = '[^a-z!\/]';
+            $comment = "/<!--(.*?)(-->|\z)/is";
+            $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html);
+            do {
+                $old = $html;
+                $html = preg_replace("/<($char)/i", '<\\1', $html);
+            } while ($html !== $old);
+            $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments
+        }
+
+        // preprocess html, essential for UTF-8
+        $html = $this->wrapHTML($html, $config, $context);
+
+        $doc = new DOMDocument();
+        $doc->encoding = 'UTF-8'; // theoretically, the above has this covered
+
+        set_error_handler(array($this, 'muteErrorHandler'));
+        $doc->loadHTML($html);
+        restore_error_handler();
+
+        $tokens = array();
+        $this->tokenizeDOM(
+            $doc->getElementsByTagName('html')->item(0)-> // <html>
+                  getElementsByTagName('body')->item(0)-> //   <body>
+                  getElementsByTagName('div')->item(0)    //     <div>
+            , $tokens);
+        return $tokens;
+    }
+
+    /**
+     * Iterative function that tokenizes a node, putting it into an accumulator.
+     * To iterate is human, to recurse divine - L. Peter Deutsch
+     * @param $node     DOMNode to be tokenized.
+     * @param $tokens   Array-list of already tokenized tokens.
+     * @returns Tokens of node appended to previously passed tokens.
+     */
+    protected function tokenizeDOM($node, &$tokens) {
+
+        $level = 0;
+        $nodes = array($level => array($node));
+        $closingNodes = array();
+        do {
+            while (!empty($nodes[$level])) {
+                $node = array_shift($nodes[$level]); // FIFO
+                $collect = $level > 0 ? true : false;
+                $needEndingTag = $this->createStartNode($node, $tokens, $collect);
+                if ($needEndingTag) {
+                    $closingNodes[$level][] = $node;
+                }
+                if ($node->childNodes && $node->childNodes->length) {
+                    $level++;
+                    $nodes[$level] = array();
+                    foreach ($node->childNodes as $childNode) {
+                        array_push($nodes[$level], $childNode);
+                    }
+                }
+            }
+            $level--;
+            if ($level && isset($closingNodes[$level])) {
+                while($node = array_pop($closingNodes[$level])) {
+                    $this->createEndNode($node, $tokens);
+                }
+            }
+        } while ($level > 0);
+    }
+
+    /**
+     * @param $node  DOMNode to be tokenized.
+     * @param $tokens   Array-list of already tokenized tokens.
+     * @param $collect  Says whether or start and close are collected, set to
+     *                    false at first recursion because it's the implicit DIV
+     *                    tag you're dealing with.
+     * @returns bool if the token needs an endtoken
+     */
+    protected function createStartNode($node, &$tokens, $collect) {
+        // intercept non element nodes. WE MUST catch all of them,
+        // but we're not getting the character reference nodes because
+        // those should have been preprocessed
+        if ($node->nodeType === XML_TEXT_NODE) {
+            $tokens[] = $this->factory->createText($node->data);
+            return false;
+        } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) {
+            // undo libxml's special treatment of <script> and <style> tags
+            $last = end($tokens);
+            $data = $node->data;
+            // (note $node->tagname is already normalized)
+            if ($last instanceof HTMLPurifier_Token_Start && ($last->name == 'script' || $last->name == 'style')) {
+                $new_data = trim($data);
+                if (substr($new_data, 0, 4) === '<!--') {
+                    $data = substr($new_data, 4);
+                    if (substr($data, -3) === '-->') {
+                        $data = substr($data, 0, -3);
+                    } else {
+                        // Highly suspicious! Not sure what to do...
+                    }
+                }
+            }
+            $tokens[] = $this->factory->createText($this->parseData($data));
+            return false;
+        } elseif ($node->nodeType === XML_COMMENT_NODE) {
+            // this is code is only invoked for comments in script/style in versions
+            // of libxml pre-2.6.28 (regular comments, of course, are still
+            // handled regularly)
+            $tokens[] = $this->factory->createComment($node->data);
+            return false;
+        } elseif (
+            // not-well tested: there may be other nodes we have to grab
+            $node->nodeType !== XML_ELEMENT_NODE
+        ) {
+            return false;
+        }
+
+        $attr = $node->hasAttributes() ? $this->transformAttrToAssoc($node->attributes) : array();
+
+        // We still have to make sure that the element actually IS empty
+        if (!$node->childNodes->length) {
+            if ($collect) {
+                $tokens[] = $this->factory->createEmpty($node->tagName, $attr);
+            }
+            return false;
+        } else {
+            if ($collect) {
+                $tokens[] = $this->factory->createStart(
+                    $tag_name = $node->tagName, // somehow, it get's dropped
+                    $attr
+                );
+            }
+            return true;
+        }
+    }
+
+    protected function createEndNode($node, &$tokens) {
+        $tokens[] = $this->factory->createEnd($node->tagName);
+    }
+
+
+    /**
+     * Converts a DOMNamedNodeMap of DOMAttr objects into an assoc array.
+     *
+     * @param $attribute_list DOMNamedNodeMap of DOMAttr objects.
+     * @returns Associative array of attributes.
+     */
+    protected function transformAttrToAssoc($node_map) {
+        // NamedNodeMap is documented very well, so we're using undocumented
+        // features, namely, the fact that it implements Iterator and
+        // has a ->length attribute
+        if ($node_map->length === 0) return array();
+        $array = array();
+        foreach ($node_map as $attr) {
+            $array[$attr->name] = $attr->value;
+        }
+        return $array;
+    }
+
+    /**
+     * An error handler that mutes all errors
+     */
+    public function muteErrorHandler($errno, $errstr) {}
+
+    /**
+     * Callback function for undoing escaping of stray angled brackets
+     * in comments
+     */
+    public function callbackUndoCommentSubst($matches) {
+        return '<!--' . strtr($matches[1], array('&'=>'&','<'=>'<')) . $matches[2];
+    }
+
+    /**
+     * Callback function that entity-izes ampersands in comments so that
+     * callbackUndoCommentSubst doesn't clobber them
+     */
+    public function callbackArmorCommentEntities($matches) {
+        return '<!--' . str_replace('&', '&', $matches[1]) . $matches[2];
+    }
+
+    /**
+     * Wraps an HTML fragment in the necessary HTML
+     */
+    protected function wrapHTML($html, $config, $context) {
+        $def = $config->getDefinition('HTML');
+        $ret = '';
+
+        if (!empty($def->doctype->dtdPublic) || !empty($def->doctype->dtdSystem)) {
+            $ret .= '<!DOCTYPE html ';
+            if (!empty($def->doctype->dtdPublic)) $ret .= 'PUBLIC "' . $def->doctype->dtdPublic . '" ';
+            if (!empty($def->doctype->dtdSystem)) $ret .= '"' . $def->doctype->dtdSystem . '" ';
+            $ret .= '>';
+        }
+
+        $ret .= '<html><head>';
+        $ret .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />';
+        // No protection if $html contains a stray </div>!
+        $ret .= '</head><body><div>'.$html.'</div></body></html>';
+        return $ret;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Lexer/DirectLex.php b/application/libraries/htmlpurifier/HTMLPurifier/Lexer/DirectLex.php
new file mode 100644
index 0000000..456e6e1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Lexer/DirectLex.php
@@ -0,0 +1,490 @@
+<?php
+
+/**
+ * Our in-house implementation of a parser.
+ *
+ * A pure PHP parser, DirectLex has absolutely no dependencies, making
+ * it a reasonably good default for PHP4.  Written with efficiency in mind,
+ * it can be four times faster than HTMLPurifier_Lexer_PEARSax3, although it
+ * pales in comparison to HTMLPurifier_Lexer_DOMLex.
+ *
+ * @todo Reread XML spec and document differences.
+ */
+class HTMLPurifier_Lexer_DirectLex extends HTMLPurifier_Lexer
+{
+
+    public $tracksLineNumbers = true;
+
+    /**
+     * Whitespace characters for str(c)spn.
+     */
+    protected $_whitespace = "\x20\x09\x0D\x0A";
+
+    /**
+     * Callback function for script CDATA fudge
+     * @param $matches, in form of array(opening tag, contents, closing tag)
+     */
+    protected function scriptCallback($matches) {
+        return $matches[1] . htmlspecialchars($matches[2], ENT_COMPAT, 'UTF-8') . $matches[3];
+    }
+
+    public function tokenizeHTML($html, $config, $context) {
+
+        // special normalization for script tags without any armor
+        // our "armor" heurstic is a < sign any number of whitespaces after
+        // the first script tag
+        if ($config->get('HTML.Trusted')) {
+            $html = preg_replace_callback('#(<script[^>]*>)(\s*[^<].+?)(</script>)#si',
+                array($this, 'scriptCallback'), $html);
+        }
+
+        $html = $this->normalize($html, $config, $context);
+
+        $cursor = 0; // our location in the text
+        $inside_tag = false; // whether or not we're parsing the inside of a tag
+        $array = array(); // result array
+
+        // This is also treated to mean maintain *column* numbers too
+        $maintain_line_numbers = $config->get('Core.MaintainLineNumbers');
+
+        if ($maintain_line_numbers === null) {
+            // automatically determine line numbering by checking
+            // if error collection is on
+            $maintain_line_numbers = $config->get('Core.CollectErrors');
+        }
+
+        if ($maintain_line_numbers) {
+            $current_line = 1;
+            $current_col  = 0;
+            $length = strlen($html);
+        } else {
+            $current_line = false;
+            $current_col  = false;
+            $length = false;
+        }
+        $context->register('CurrentLine', $current_line);
+        $context->register('CurrentCol',  $current_col);
+        $nl = "\n";
+        // how often to manually recalculate. This will ALWAYS be right,
+        // but it's pretty wasteful. Set to 0 to turn off
+        $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval');
+
+        $e = false;
+        if ($config->get('Core.CollectErrors')) {
+            $e =& $context->get('ErrorCollector');
+        }
+
+        // for testing synchronization
+        $loops = 0;
+
+        while(++$loops) {
+
+            // $cursor is either at the start of a token, or inside of
+            // a tag (i.e. there was a < immediately before it), as indicated
+            // by $inside_tag
+
+            if ($maintain_line_numbers) {
+
+                // $rcursor, however, is always at the start of a token.
+                $rcursor = $cursor - (int) $inside_tag;
+
+                // Column number is cheap, so we calculate it every round.
+                // We're interested at the *end* of the newline string, so
+                // we need to add strlen($nl) == 1 to $nl_pos before subtracting it
+                // from our "rcursor" position.
+                $nl_pos = strrpos($html, $nl, $rcursor - $length);
+                $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1);
+
+                // recalculate lines
+                if (
+                    $synchronize_interval &&  // synchronization is on
+                    $cursor > 0 &&            // cursor is further than zero
+                    $loops % $synchronize_interval === 0 // time to synchronize!
+                ) {
+                    $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor);
+                }
+
+            }
+
+            $position_next_lt = strpos($html, '<', $cursor);
+            $position_next_gt = strpos($html, '>', $cursor);
+
+            // triggers on "<b>asdf</b>" but not "asdf <b></b>"
+            // special case to set up context
+            if ($position_next_lt === $cursor) {
+                $inside_tag = true;
+                $cursor++;
+            }
+
+            if (!$inside_tag && $position_next_lt !== false) {
+                // We are not inside tag and there still is another tag to parse
+                $token = new
+                    HTMLPurifier_Token_Text(
+                        $this->parseData(
+                            substr(
+                                $html, $cursor, $position_next_lt - $cursor
+                            )
+                        )
+                    );
+                if ($maintain_line_numbers) {
+                    $token->rawPosition($current_line, $current_col);
+                    $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor);
+                }
+                $array[] = $token;
+                $cursor  = $position_next_lt + 1;
+                $inside_tag = true;
+                continue;
+            } elseif (!$inside_tag) {
+                // We are not inside tag but there are no more tags
+                // If we're already at the end, break
+                if ($cursor === strlen($html)) break;
+                // Create Text of rest of string
+                $token = new
+                    HTMLPurifier_Token_Text(
+                        $this->parseData(
+                            substr(
+                                $html, $cursor
+                            )
+                        )
+                    );
+                if ($maintain_line_numbers) $token->rawPosition($current_line, $current_col);
+                $array[] = $token;
+                break;
+            } elseif ($inside_tag && $position_next_gt !== false) {
+                // We are in tag and it is well formed
+                // Grab the internals of the tag
+                $strlen_segment = $position_next_gt - $cursor;
+
+                if ($strlen_segment < 1) {
+                    // there's nothing to process!
+                    $token = new HTMLPurifier_Token_Text('<');
+                    $cursor++;
+                    continue;
+                }
+
+                $segment = substr($html, $cursor, $strlen_segment);
+
+                if ($segment === false) {
+                    // somehow, we attempted to access beyond the end of
+                    // the string, defense-in-depth, reported by Nate Abele
+                    break;
+                }
+
+                // Check if it's a comment
+                if (
+                    substr($segment, 0, 3) === '!--'
+                ) {
+                    // re-determine segment length, looking for -->
+                    $position_comment_end = strpos($html, '-->', $cursor);
+                    if ($position_comment_end === false) {
+                        // uh oh, we have a comment that extends to
+                        // infinity. Can't be helped: set comment
+                        // end position to end of string
+                        if ($e) $e->send(E_WARNING, 'Lexer: Unclosed comment');
+                        $position_comment_end = strlen($html);
+                        $end = true;
+                    } else {
+                        $end = false;
+                    }
+                    $strlen_segment = $position_comment_end - $cursor;
+                    $segment = substr($html, $cursor, $strlen_segment);
+                    $token = new
+                        HTMLPurifier_Token_Comment(
+                            substr(
+                                $segment, 3, $strlen_segment - 3
+                            )
+                        );
+                    if ($maintain_line_numbers) {
+                        $token->rawPosition($current_line, $current_col);
+                        $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment);
+                    }
+                    $array[] = $token;
+                    $cursor = $end ? $position_comment_end : $position_comment_end + 3;
+                    $inside_tag = false;
+                    continue;
+                }
+
+                // Check if it's an end tag
+                $is_end_tag = (strpos($segment,'/') === 0);
+                if ($is_end_tag) {
+                    $type = substr($segment, 1);
+                    $token = new HTMLPurifier_Token_End($type);
+                    if ($maintain_line_numbers) {
+                        $token->rawPosition($current_line, $current_col);
+                        $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
+                    }
+                    $array[] = $token;
+                    $inside_tag = false;
+                    $cursor = $position_next_gt + 1;
+                    continue;
+                }
+
+                // Check leading character is alnum, if not, we may
+                // have accidently grabbed an emoticon. Translate into
+                // text and go our merry way
+                if (!ctype_alpha($segment[0])) {
+                    // XML:  $segment[0] !== '_' && $segment[0] !== ':'
+                    if ($e) $e->send(E_NOTICE, 'Lexer: Unescaped lt');
+                    $token = new HTMLPurifier_Token_Text('<');
+                    if ($maintain_line_numbers) {
+                        $token->rawPosition($current_line, $current_col);
+                        $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
+                    }
+                    $array[] = $token;
+                    $inside_tag = false;
+                    continue;
+                }
+
+                // Check if it is explicitly self closing, if so, remove
+                // trailing slash. Remember, we could have a tag like <br>, so
+                // any later token processing scripts must convert improperly
+                // classified EmptyTags from StartTags.
+                $is_self_closing = (strrpos($segment,'/') === $strlen_segment-1);
+                if ($is_self_closing) {
+                    $strlen_segment--;
+                    $segment = substr($segment, 0, $strlen_segment);
+                }
+
+                // Check if there are any attributes
+                $position_first_space = strcspn($segment, $this->_whitespace);
+
+                if ($position_first_space >= $strlen_segment) {
+                    if ($is_self_closing) {
+                        $token = new HTMLPurifier_Token_Empty($segment);
+                    } else {
+                        $token = new HTMLPurifier_Token_Start($segment);
+                    }
+                    if ($maintain_line_numbers) {
+                        $token->rawPosition($current_line, $current_col);
+                        $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
+                    }
+                    $array[] = $token;
+                    $inside_tag = false;
+                    $cursor = $position_next_gt + 1;
+                    continue;
+                }
+
+                // Grab out all the data
+                $type = substr($segment, 0, $position_first_space);
+                $attribute_string =
+                    trim(
+                        substr(
+                            $segment, $position_first_space
+                        )
+                    );
+                if ($attribute_string) {
+                    $attr = $this->parseAttributeString(
+                                    $attribute_string
+                                  , $config, $context
+                              );
+                } else {
+                    $attr = array();
+                }
+
+                if ($is_self_closing) {
+                    $token = new HTMLPurifier_Token_Empty($type, $attr);
+                } else {
+                    $token = new HTMLPurifier_Token_Start($type, $attr);
+                }
+                if ($maintain_line_numbers) {
+                    $token->rawPosition($current_line, $current_col);
+                    $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor);
+                }
+                $array[] = $token;
+                $cursor = $position_next_gt + 1;
+                $inside_tag = false;
+                continue;
+            } else {
+                // inside tag, but there's no ending > sign
+                if ($e) $e->send(E_WARNING, 'Lexer: Missing gt');
+                $token = new
+                    HTMLPurifier_Token_Text(
+                        '<' .
+                        $this->parseData(
+                            substr($html, $cursor)
+                        )
+                    );
+                if ($maintain_line_numbers) $token->rawPosition($current_line, $current_col);
+                // no cursor scroll? Hmm...
+                $array[] = $token;
+                break;
+            }
+            break;
+        }
+
+        $context->destroy('CurrentLine');
+        $context->destroy('CurrentCol');
+        return $array;
+    }
+
+    /**
+     * PHP 5.0.x compatible substr_count that implements offset and length
+     */
+    protected function substrCount($haystack, $needle, $offset, $length) {
+        static $oldVersion;
+        if ($oldVersion === null) {
+            $oldVersion = version_compare(PHP_VERSION, '5.1', '<');
+        }
+        if ($oldVersion) {
+            $haystack = substr($haystack, $offset, $length);
+            return substr_count($haystack, $needle);
+        } else {
+            return substr_count($haystack, $needle, $offset, $length);
+        }
+    }
+
+    /**
+     * Takes the inside of an HTML tag and makes an assoc array of attributes.
+     *
+     * @param $string Inside of tag excluding name.
+     * @returns Assoc array of attributes.
+     */
+    public function parseAttributeString($string, $config, $context) {
+        $string = (string) $string; // quick typecast
+
+        if ($string == '') return array(); // no attributes
+
+        $e = false;
+        if ($config->get('Core.CollectErrors')) {
+            $e =& $context->get('ErrorCollector');
+        }
+
+        // let's see if we can abort as quickly as possible
+        // one equal sign, no spaces => one attribute
+        $num_equal = substr_count($string, '=');
+        $has_space = strpos($string, ' ');
+        if ($num_equal === 0 && !$has_space) {
+            // bool attribute
+            return array($string => $string);
+        } elseif ($num_equal === 1 && !$has_space) {
+            // only one attribute
+            list($key, $quoted_value) = explode('=', $string);
+            $quoted_value = trim($quoted_value);
+            if (!$key) {
+                if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
+                return array();
+            }
+            if (!$quoted_value) return array($key => '');
+            $first_char = @$quoted_value[0];
+            $last_char  = @$quoted_value[strlen($quoted_value)-1];
+
+            $same_quote = ($first_char == $last_char);
+            $open_quote = ($first_char == '"' || $first_char == "'");
+
+            if ( $same_quote && $open_quote) {
+                // well behaved
+                $value = substr($quoted_value, 1, strlen($quoted_value) - 2);
+            } else {
+                // not well behaved
+                if ($open_quote) {
+                    if ($e) $e->send(E_ERROR, 'Lexer: Missing end quote');
+                    $value = substr($quoted_value, 1);
+                } else {
+                    $value = $quoted_value;
+                }
+            }
+            if ($value === false) $value = '';
+            return array($key => $this->parseData($value));
+        }
+
+        // setup loop environment
+        $array  = array(); // return assoc array of attributes
+        $cursor = 0; // current position in string (moves forward)
+        $size   = strlen($string); // size of the string (stays the same)
+
+        // if we have unquoted attributes, the parser expects a terminating
+        // space, so let's guarantee that there's always a terminating space.
+        $string .= ' ';
+
+        while(true) {
+
+            if ($cursor >= $size) {
+                break;
+            }
+
+            $cursor += ($value = strspn($string, $this->_whitespace, $cursor));
+            // grab the key
+
+            $key_begin = $cursor; //we're currently at the start of the key
+
+            // scroll past all characters that are the key (not whitespace or =)
+            $cursor += strcspn($string, $this->_whitespace . '=', $cursor);
+
+            $key_end = $cursor; // now at the end of the key
+
+            $key = substr($string, $key_begin, $key_end - $key_begin);
+
+            if (!$key) {
+                if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
+                $cursor += strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop
+                continue; // empty key
+            }
+
+            // scroll past all whitespace
+            $cursor += strspn($string, $this->_whitespace, $cursor);
+
+            if ($cursor >= $size) {
+                $array[$key] = $key;
+                break;
+            }
+
+            // if the next character is an equal sign, we've got a regular
+            // pair, otherwise, it's a bool attribute
+            $first_char = @$string[$cursor];
+
+            if ($first_char == '=') {
+                // key="value"
+
+                $cursor++;
+                $cursor += strspn($string, $this->_whitespace, $cursor);
+
+                if ($cursor === false) {
+                    $array[$key] = '';
+                    break;
+                }
+
+                // we might be in front of a quote right now
+
+                $char = @$string[$cursor];
+
+                if ($char == '"' || $char == "'") {
+                    // it's quoted, end bound is $char
+                    $cursor++;
+                    $value_begin = $cursor;
+                    $cursor = strpos($string, $char, $cursor);
+                    $value_end = $cursor;
+                } else {
+                    // it's not quoted, end bound is whitespace
+                    $value_begin = $cursor;
+                    $cursor += strcspn($string, $this->_whitespace, $cursor);
+                    $value_end = $cursor;
+                }
+
+                // we reached a premature end
+                if ($cursor === false) {
+                    $cursor = $size;
+                    $value_end = $cursor;
+                }
+
+                $value = substr($string, $value_begin, $value_end - $value_begin);
+                if ($value === false) $value = '';
+                $array[$key] = $this->parseData($value);
+                $cursor++;
+
+            } else {
+                // boolattr
+                if ($key !== '') {
+                    $array[$key] = $key;
+                } else {
+                    // purely theoretical
+                    if ($e) $e->send(E_ERROR, 'Lexer: Missing attribute key');
+                }
+
+            }
+        }
+        return $array;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Lexer/PH5P.php b/application/libraries/htmlpurifier/HTMLPurifier/Lexer/PH5P.php
new file mode 100644
index 0000000..faf00b8
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Lexer/PH5P.php
@@ -0,0 +1,3904 @@
+<?php
+
+/**
+ * Experimental HTML5-based parser using Jeroen van der Meer's PH5P library.
+ * Occupies space in the HTML5 pseudo-namespace, which may cause conflicts.
+ * 
+ * @note
+ *    Recent changes to PHP's DOM extension have resulted in some fatal
+ *    error conditions with the original version of PH5P. Pending changes,
+ *    this lexer will punt to DirectLex if DOM throughs an exception.
+ */
+
+class HTMLPurifier_Lexer_PH5P extends HTMLPurifier_Lexer_DOMLex {
+    
+    public function tokenizeHTML($html, $config, $context) {
+        $new_html = $this->normalize($html, $config, $context);
+        $new_html = $this->wrapHTML($new_html, $config, $context);
+        try {
+            $parser = new HTML5($new_html);
+            $doc = $parser->save();
+        } catch (DOMException $e) {
+            // Uh oh, it failed. Punt to DirectLex.
+            $lexer = new HTMLPurifier_Lexer_DirectLex();
+            $context->register('PH5PError', $e); // save the error, so we can detect it
+            return $lexer->tokenizeHTML($html, $config, $context); // use original HTML
+        }
+        $tokens = array();
+        $this->tokenizeDOM(
+            $doc->getElementsByTagName('html')->item(0)-> // <html>
+                  getElementsByTagName('body')->item(0)-> //   <body>
+                  getElementsByTagName('div')->item(0)    //     <div>
+            , $tokens);
+        return $tokens;
+    }
+    
+}
+
+/*
+
+Copyright 2007 Jeroen van der Meer <http://jero.net/> 
+
+Permission is hereby granted, free of charge, to any person obtaining a 
+copy of this software and associated documentation files (the 
+"Software"), to deal in the Software without restriction, including 
+without limitation the rights to use, copy, modify, merge, publish, 
+distribute, sublicense, and/or sell copies of the Software, and to 
+permit persons to whom the Software is furnished to do so, subject to 
+the following conditions: 
+
+The above copyright notice and this permission notice shall be included 
+in all copies or substantial portions of the Software. 
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
+
+*/
+
+class HTML5 {
+    private $data;
+    private $char;
+    private $EOF;
+    private $state;
+    private $tree;
+    private $token;
+    private $content_model;
+    private $escape = false;
+    private $entities = array('AElig;','AElig','AMP;','AMP','Aacute;','Aacute',
+    'Acirc;','Acirc','Agrave;','Agrave','Alpha;','Aring;','Aring','Atilde;',
+    'Atilde','Auml;','Auml','Beta;','COPY;','COPY','Ccedil;','Ccedil','Chi;',
+    'Dagger;','Delta;','ETH;','ETH','Eacute;','Eacute','Ecirc;','Ecirc','Egrave;',
+    'Egrave','Epsilon;','Eta;','Euml;','Euml','GT;','GT','Gamma;','Iacute;',
+    'Iacute','Icirc;','Icirc','Igrave;','Igrave','Iota;','Iuml;','Iuml','Kappa;',
+    'LT;','LT','Lambda;','Mu;','Ntilde;','Ntilde','Nu;','OElig;','Oacute;',
+    'Oacute','Ocirc;','Ocirc','Ograve;','Ograve','Omega;','Omicron;','Oslash;',
+    'Oslash','Otilde;','Otilde','Ouml;','Ouml','Phi;','Pi;','Prime;','Psi;',
+    'QUOT;','QUOT','REG;','REG','Rho;','Scaron;','Sigma;','THORN;','THORN',
+    'TRADE;','Tau;','Theta;','Uacute;','Uacute','Ucirc;','Ucirc','Ugrave;',
+    'Ugrave','Upsilon;','Uuml;','Uuml','Xi;','Yacute;','Yacute','Yuml;','Zeta;',
+    'aacute;','aacute','acirc;','acirc','acute;','acute','aelig;','aelig',
+    'agrave;','agrave','alefsym;','alpha;','amp;','amp','and;','ang;','apos;',
+    'aring;','aring','asymp;','atilde;','atilde','auml;','auml','bdquo;','beta;',
+    'brvbar;','brvbar','bull;','cap;','ccedil;','ccedil','cedil;','cedil',
+    'cent;','cent','chi;','circ;','clubs;','cong;','copy;','copy','crarr;',
+    'cup;','curren;','curren','dArr;','dagger;','darr;','deg;','deg','delta;',
+    'diams;','divide;','divide','eacute;','eacute','ecirc;','ecirc','egrave;',
+    'egrave','empty;','emsp;','ensp;','epsilon;','equiv;','eta;','eth;','eth',
+    'euml;','euml','euro;','exist;','fnof;','forall;','frac12;','frac12',
+    'frac14;','frac14','frac34;','frac34','frasl;','gamma;','ge;','gt;','gt',
+    'hArr;','harr;','hearts;','hellip;','iacute;','iacute','icirc;','icirc',
+    'iexcl;','iexcl','igrave;','igrave','image;','infin;','int;','iota;',
+    'iquest;','iquest','isin;','iuml;','iuml','kappa;','lArr;','lambda;','lang;',
+    'laquo;','laquo','larr;','lceil;','ldquo;','le;','lfloor;','lowast;','loz;',
+    'lrm;','lsaquo;','lsquo;','lt;','lt','macr;','macr','mdash;','micro;','micro',
+    'middot;','middot','minus;','mu;','nabla;','nbsp;','nbsp','ndash;','ne;',
+    'ni;','not;','not','notin;','nsub;','ntilde;','ntilde','nu;','oacute;',
+    'oacute','ocirc;','ocirc','oelig;','ograve;','ograve','oline;','omega;',
+    'omicron;','oplus;','or;','ordf;','ordf','ordm;','ordm','oslash;','oslash',
+    'otilde;','otilde','otimes;','ouml;','ouml','para;','para','part;','permil;',
+    'perp;','phi;','pi;','piv;','plusmn;','plusmn','pound;','pound','prime;',
+    'prod;','prop;','psi;','quot;','quot','rArr;','radic;','rang;','raquo;',
+    'raquo','rarr;','rceil;','rdquo;','real;','reg;','reg','rfloor;','rho;',
+    'rlm;','rsaquo;','rsquo;','sbquo;','scaron;','sdot;','sect;','sect','shy;',
+    'shy','sigma;','sigmaf;','sim;','spades;','sub;','sube;','sum;','sup1;',
+    'sup1','sup2;','sup2','sup3;','sup3','sup;','supe;','szlig;','szlig','tau;',
+    'there4;','theta;','thetasym;','thinsp;','thorn;','thorn','tilde;','times;',
+    'times','trade;','uArr;','uacute;','uacute','uarr;','ucirc;','ucirc',
+    'ugrave;','ugrave','uml;','uml','upsih;','upsilon;','uuml;','uuml','weierp;',
+    'xi;','yacute;','yacute','yen;','yen','yuml;','yuml','zeta;','zwj;','zwnj;');
+
+    const PCDATA    = 0;
+    const RCDATA    = 1;
+    const CDATA     = 2;
+    const PLAINTEXT = 3;
+
+    const DOCTYPE  = 0;
+    const STARTTAG = 1;
+    const ENDTAG   = 2;
+    const COMMENT  = 3;
+    const CHARACTR = 4;
+    const EOF      = 5;
+
+    public function __construct($data) {
+
+        $this->data = $data;
+        $this->char = -1;
+        $this->EOF  = strlen($data);
+        $this->tree = new HTML5TreeConstructer;
+        $this->content_model = self::PCDATA;
+
+        $this->state = 'data';
+
+        while($this->state !== null) {
+            $this->{$this->state.'State'}();
+        }
+    }
+
+    public function save() {
+        return $this->tree->save();
+    }
+
+    private function char() {
+        return ($this->char < $this->EOF)
+            ? $this->data[$this->char]
+            : false;
+    }
+
+    private function character($s, $l = 0) {
+        if($s + $l < $this->EOF) {
+            if($l === 0) {
+                return $this->data[$s];
+            } else {
+                return substr($this->data, $s, $l);
+            }
+        }
+    }
+
+    private function characters($char_class, $start) {
+        return preg_replace('#^(['.$char_class.']+).*#s', '\\1', substr($this->data, $start));
+    }
+
+    private function dataState() {
+        // Consume the next input character
+        $this->char++;
+        $char = $this->char();
+
+        if($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) {
+            /* U+0026 AMPERSAND (&)
+            When the content model flag is set to one of the PCDATA or RCDATA
+            states: switch to the entity data state. Otherwise: treat it as per
+            the "anything else"    entry below. */
+            $this->state = 'entityData';
+
+        } elseif($char === '-') {
+            /* If the content model flag is set to either the RCDATA state or
+            the CDATA state, and the escape flag is false, and there are at
+            least three characters before this one in the input stream, and the
+            last four characters in the input stream, including this one, are
+            U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS,
+            and U+002D HYPHEN-MINUS ("<!--"), then set the escape flag to true. */
+            if(($this->content_model === self::RCDATA || $this->content_model ===
+            self::CDATA) && $this->escape === false &&
+            $this->char >= 3 && $this->character($this->char - 4, 4) === '<!--') {
+                $this->escape = true;
+            }
+
+            /* In any case, emit the input character as a character token. Stay
+            in the data state. */
+            $this->emitToken(array(
+                'type' => self::CHARACTR,
+                'data' => $char
+            ));
+
+        /* U+003C LESS-THAN SIGN (<) */
+        } elseif($char === '<' && ($this->content_model === self::PCDATA ||
+        (($this->content_model === self::RCDATA ||
+        $this->content_model === self::CDATA) && $this->escape === false))) {
+            /* When the content model flag is set to the PCDATA state: switch
+            to the tag open state.
+
+            When the content model flag is set to either the RCDATA state or
+            the CDATA state and the escape flag is false: switch to the tag
+            open state.
+
+            Otherwise: treat it as per the "anything else" entry below. */
+            $this->state = 'tagOpen';
+
+        /* U+003E GREATER-THAN SIGN (>) */
+        } elseif($char === '>') {
+            /* If the content model flag is set to either the RCDATA state or
+            the CDATA state, and the escape flag is true, and the last three
+            characters in the input stream including this one are U+002D
+            HYPHEN-MINUS, U+002D HYPHEN-MINUS, U+003E GREATER-THAN SIGN ("-->"),
+            set the escape flag to false. */
+            if(($this->content_model === self::RCDATA ||
+            $this->content_model === self::CDATA) && $this->escape === true &&
+            $this->character($this->char, 3) === '-->') {
+                $this->escape = false;
+            }
+
+            /* In any case, emit the input character as a character token.
+            Stay in the data state. */
+            $this->emitToken(array(
+                'type' => self::CHARACTR,
+                'data' => $char
+            ));
+
+        } elseif($this->char === $this->EOF) {
+            /* EOF
+            Emit an end-of-file token. */
+            $this->EOF();
+
+        } elseif($this->content_model === self::PLAINTEXT) {
+            /* When the content model flag is set to the PLAINTEXT state
+            THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of
+            the text and emit it as a character token. */
+            $this->emitToken(array(
+                'type' => self::CHARACTR,
+                'data' => substr($this->data, $this->char)
+            ));
+
+            $this->EOF();
+
+        } else {
+            /* Anything else
+            THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that
+            otherwise would also be treated as a character token and emit it
+            as a single character token. Stay in the data state. */
+            $len  = strcspn($this->data, '<&', $this->char);
+            $char = substr($this->data, $this->char, $len);
+            $this->char += $len - 1;
+
+            $this->emitToken(array(
+                'type' => self::CHARACTR,
+                'data' => $char
+            ));
+
+            $this->state = 'data';
+        }
+    }
+
+    private function entityDataState() {
+        // Attempt to consume an entity.
+        $entity = $this->entity();
+
+        // If nothing is returned, emit a U+0026 AMPERSAND character token.
+        // Otherwise, emit the character token that was returned.
+        $char = (!$entity) ? '&' : $entity;
+        $this->emitToken(array(
+            'type' => self::CHARACTR,
+            'data' => $char
+        ));
+
+        // Finally, switch to the data state.
+        $this->state = 'data';
+    }
+
+    private function tagOpenState() {
+        switch($this->content_model) {
+            case self::RCDATA:
+            case self::CDATA:
+                /* If the next input character is a U+002F SOLIDUS (/) character,
+                consume it and switch to the close tag open state. If the next
+                input character is not a U+002F SOLIDUS (/) character, emit a
+                U+003C LESS-THAN SIGN character token and switch to the data
+                state to process the next input character. */
+                if($this->character($this->char + 1) === '/') {
+                    $this->char++;
+                    $this->state = 'closeTagOpen';
+
+                } else {
+                    $this->emitToken(array(
+                        'type' => self::CHARACTR,
+                        'data' => '<'
+                    ));
+
+                    $this->state = 'data';
+                }
+            break;
+
+            case self::PCDATA:
+                // If the content model flag is set to the PCDATA state
+                // Consume the next input character:
+                $this->char++;
+                $char = $this->char();
+
+                if($char === '!') {
+                    /* U+0021 EXCLAMATION MARK (!)
+                    Switch to the markup declaration open state. */
+                    $this->state = 'markupDeclarationOpen';
+
+                } elseif($char === '/') {
+                    /* U+002F SOLIDUS (/)
+                    Switch to the close tag open state. */
+                    $this->state = 'closeTagOpen';
+
+                } elseif(preg_match('/^[A-Za-z]$/', $char)) {
+                    /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
+                    Create a new start tag token, set its tag name to the lowercase
+                    version of the input character (add 0x0020 to the character's code
+                    point), then switch to the tag name state. (Don't emit the token
+                    yet; further details will be filled in before it is emitted.) */
+                    $this->token = array(
+                        'name'  => strtolower($char),
+                        'type'  => self::STARTTAG,
+                        'attr'  => array()
+                    );
+
+                    $this->state = 'tagName';
+
+                } elseif($char === '>') {
+                    /* U+003E GREATER-THAN SIGN (>)
+                    Parse error. Emit a U+003C LESS-THAN SIGN character token and a
+                    U+003E GREATER-THAN SIGN character token. Switch to the data state. */
+                    $this->emitToken(array(
+                        'type' => self::CHARACTR,
+                        'data' => '<>'
+                    ));
+
+                    $this->state = 'data';
+
+                } elseif($char === '?') {
+                    /* U+003F QUESTION MARK (?)
+                    Parse error. Switch to the bogus comment state. */
+                    $this->state = 'bogusComment';
+
+                } else {
+                    /* Anything else
+                    Parse error. Emit a U+003C LESS-THAN SIGN character token and
+                    reconsume the current input character in the data state. */
+                    $this->emitToken(array(
+                        'type' => self::CHARACTR,
+                        'data' => '<'
+                    ));
+
+                    $this->char--;
+                    $this->state = 'data';
+                }
+            break;
+        }
+    }
+
+    private function closeTagOpenState() {
+        $next_node = strtolower($this->characters('A-Za-z', $this->char + 1));
+        $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName;
+
+        if(($this->content_model === self::RCDATA || $this->content_model === self::CDATA) &&
+        (!$the_same || ($the_same && (!preg_match('/[\t\n\x0b\x0c >\/]/',
+        $this->character($this->char + 1 + strlen($next_node))) || $this->EOF === $this->char)))) {
+            /* If the content model flag is set to the RCDATA or CDATA states then
+            examine the next few characters. If they do not match the tag name of
+            the last start tag token emitted (case insensitively), or if they do but
+            they are not immediately followed by one of the following characters:
+                * U+0009 CHARACTER TABULATION
+                * U+000A LINE FEED (LF)
+                * U+000B LINE TABULATION
+                * U+000C FORM FEED (FF)
+                * U+0020 SPACE
+                * U+003E GREATER-THAN SIGN (>)
+                * U+002F SOLIDUS (/)
+                * EOF
+            ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character
+            token, a U+002F SOLIDUS character token, and switch to the data state
+            to process the next input character. */
+            $this->emitToken(array(
+                'type' => self::CHARACTR,
+                'data' => '</'
+            ));
+
+            $this->state = 'data';
+
+        } else {
+            /* Otherwise, if the content model flag is set to the PCDATA state,
+            or if the next few characters do match that tag name, consume the
+            next input character: */
+            $this->char++;
+            $char = $this->char();
+
+            if(preg_match('/^[A-Za-z]$/', $char)) {
+                /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
+                Create a new end tag token, set its tag name to the lowercase version
+                of the input character (add 0x0020 to the character's code point), then
+                switch to the tag name state. (Don't emit the token yet; further details
+                will be filled in before it is emitted.) */
+                $this->token = array(
+                    'name'  => strtolower($char),
+                    'type'  => self::ENDTAG
+                );
+
+                $this->state = 'tagName';
+
+            } elseif($char === '>') {
+                /* U+003E GREATER-THAN SIGN (>)
+                Parse error. Switch to the data state. */
+                $this->state = 'data';
+
+            } elseif($this->char === $this->EOF) {
+                /* EOF
+                Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F
+                SOLIDUS character token. Reconsume the EOF character in the data state. */
+                $this->emitToken(array(
+                    'type' => self::CHARACTR,
+                    'data' => '</'
+                ));
+
+                $this->char--;
+                $this->state = 'data';
+
+            } else {
+                /* Parse error. Switch to the bogus comment state. */
+                $this->state = 'bogusComment';
+            }
+        }
+    }
+
+    private function tagNameState() {
+        // Consume the next input character:
+        $this->char++;
+        $char = $this->character($this->char);
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            /* U+0009 CHARACTER TABULATION
+            U+000A LINE FEED (LF)
+            U+000B LINE TABULATION
+            U+000C FORM FEED (FF)
+            U+0020 SPACE
+            Switch to the before attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } elseif($char === '>') {
+            /* U+003E GREATER-THAN SIGN (>)
+            Emit the current tag token. Switch to the data state. */
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } elseif($this->char === $this->EOF) {
+            /* EOF
+            Parse error. Emit the current tag token. Reconsume the EOF
+            character in the data state. */
+            $this->emitToken($this->token);
+
+            $this->char--;
+            $this->state = 'data';
+
+        } elseif($char === '/') {
+            /* U+002F SOLIDUS (/)
+            Parse error unless this is a permitted slash. Switch to the before
+            attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } else {
+            /* Anything else
+            Append the current input character to the current tag token's tag name.
+            Stay in the tag name state. */
+            $this->token['name'] .= strtolower($char);
+            $this->state = 'tagName';
+        }
+    }
+
+    private function beforeAttributeNameState() {
+        // Consume the next input character:
+        $this->char++;
+        $char = $this->character($this->char);
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            /* U+0009 CHARACTER TABULATION
+            U+000A LINE FEED (LF)
+            U+000B LINE TABULATION
+            U+000C FORM FEED (FF)
+            U+0020 SPACE
+            Stay in the before attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } elseif($char === '>') {
+            /* U+003E GREATER-THAN SIGN (>)
+            Emit the current tag token. Switch to the data state. */
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } elseif($char === '/') {
+            /* U+002F SOLIDUS (/)
+            Parse error unless this is a permitted slash. Stay in the before
+            attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } elseif($this->char === $this->EOF) {
+            /* EOF
+            Parse error. Emit the current tag token. Reconsume the EOF
+            character in the data state. */
+            $this->emitToken($this->token);
+
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            /* Anything else
+            Start a new attribute in the current tag token. Set that attribute's
+            name to the current input character, and its value to the empty string.
+            Switch to the attribute name state. */
+            $this->token['attr'][] = array(
+                'name'  => strtolower($char),
+                'value' => null
+            );
+
+            $this->state = 'attributeName';
+        }
+    }
+
+    private function attributeNameState() {
+        // Consume the next input character:
+        $this->char++;
+        $char = $this->character($this->char);
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            /* U+0009 CHARACTER TABULATION
+            U+000A LINE FEED (LF)
+            U+000B LINE TABULATION
+            U+000C FORM FEED (FF)
+            U+0020 SPACE
+            Stay in the before attribute name state. */
+            $this->state = 'afterAttributeName';
+
+        } elseif($char === '=') {
+            /* U+003D EQUALS SIGN (=)
+            Switch to the before attribute value state. */
+            $this->state = 'beforeAttributeValue';
+
+        } elseif($char === '>') {
+            /* U+003E GREATER-THAN SIGN (>)
+            Emit the current tag token. Switch to the data state. */
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } elseif($char === '/' && $this->character($this->char + 1) !== '>') {
+            /* U+002F SOLIDUS (/)
+            Parse error unless this is a permitted slash. Switch to the before
+            attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } elseif($this->char === $this->EOF) {
+            /* EOF
+            Parse error. Emit the current tag token. Reconsume the EOF
+            character in the data state. */
+            $this->emitToken($this->token);
+
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            /* Anything else
+            Append the current input character to the current attribute's name.
+            Stay in the attribute name state. */
+            $last = count($this->token['attr']) - 1;
+            $this->token['attr'][$last]['name'] .= strtolower($char);
+
+            $this->state = 'attributeName';
+        }
+    }
+
+    private function afterAttributeNameState() {
+        // Consume the next input character:
+        $this->char++;
+        $char = $this->character($this->char);
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            /* U+0009 CHARACTER TABULATION
+            U+000A LINE FEED (LF)
+            U+000B LINE TABULATION
+            U+000C FORM FEED (FF)
+            U+0020 SPACE
+            Stay in the after attribute name state. */
+            $this->state = 'afterAttributeName';
+
+        } elseif($char === '=') {
+            /* U+003D EQUALS SIGN (=)
+            Switch to the before attribute value state. */
+            $this->state = 'beforeAttributeValue';
+
+        } elseif($char === '>') {
+            /* U+003E GREATER-THAN SIGN (>)
+            Emit the current tag token. Switch to the data state. */
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } elseif($char === '/' && $this->character($this->char + 1) !== '>') {
+            /* U+002F SOLIDUS (/)
+            Parse error unless this is a permitted slash. Switch to the
+            before attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } elseif($this->char === $this->EOF) {
+            /* EOF
+            Parse error. Emit the current tag token. Reconsume the EOF
+            character in the data state. */
+            $this->emitToken($this->token);
+
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            /* Anything else
+            Start a new attribute in the current tag token. Set that attribute's
+            name to the current input character, and its value to the empty string.
+            Switch to the attribute name state. */
+            $this->token['attr'][] = array(
+                'name'  => strtolower($char),
+                'value' => null
+            );
+
+            $this->state = 'attributeName';
+        }
+    }
+
+    private function beforeAttributeValueState() {
+        // Consume the next input character:
+        $this->char++;
+        $char = $this->character($this->char);
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            /* U+0009 CHARACTER TABULATION
+            U+000A LINE FEED (LF)
+            U+000B LINE TABULATION
+            U+000C FORM FEED (FF)
+            U+0020 SPACE
+            Stay in the before attribute value state. */
+            $this->state = 'beforeAttributeValue';
+
+        } elseif($char === '"') {
+            /* U+0022 QUOTATION MARK (")
+            Switch to the attribute value (double-quoted) state. */
+            $this->state = 'attributeValueDoubleQuoted';
+
+        } elseif($char === '&') {
+            /* U+0026 AMPERSAND (&)
+            Switch to the attribute value (unquoted) state and reconsume
+            this input character. */
+            $this->char--;
+            $this->state = 'attributeValueUnquoted';
+
+        } elseif($char === '\'') {
+            /* U+0027 APOSTROPHE (')
+            Switch to the attribute value (single-quoted) state. */
+            $this->state = 'attributeValueSingleQuoted';
+
+        } elseif($char === '>') {
+            /* U+003E GREATER-THAN SIGN (>)
+            Emit the current tag token. Switch to the data state. */
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } else {
+            /* Anything else
+            Append the current input character to the current attribute's value.
+            Switch to the attribute value (unquoted) state. */
+            $last = count($this->token['attr']) - 1;
+            $this->token['attr'][$last]['value'] .= $char;
+
+            $this->state = 'attributeValueUnquoted';
+        }
+    }
+
+    private function attributeValueDoubleQuotedState() {
+        // Consume the next input character:
+        $this->char++;
+        $char = $this->character($this->char);
+
+        if($char === '"') {
+            /* U+0022 QUOTATION MARK (")
+            Switch to the before attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } elseif($char === '&') {
+            /* U+0026 AMPERSAND (&)
+            Switch to the entity in attribute value state. */
+            $this->entityInAttributeValueState('double');
+
+        } elseif($this->char === $this->EOF) {
+            /* EOF
+            Parse error. Emit the current tag token. Reconsume the character
+            in the data state. */
+            $this->emitToken($this->token);
+
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            /* Anything else
+            Append the current input character to the current attribute's value.
+            Stay in the attribute value (double-quoted) state. */
+            $last = count($this->token['attr']) - 1;
+            $this->token['attr'][$last]['value'] .= $char;
+
+            $this->state = 'attributeValueDoubleQuoted';
+        }
+    }
+
+    private function attributeValueSingleQuotedState() {
+        // Consume the next input character:
+        $this->char++;
+        $char = $this->character($this->char);
+
+        if($char === '\'') {
+            /* U+0022 QUOTATION MARK (')
+            Switch to the before attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } elseif($char === '&') {
+            /* U+0026 AMPERSAND (&)
+            Switch to the entity in attribute value state. */
+            $this->entityInAttributeValueState('single');
+
+        } elseif($this->char === $this->EOF) {
+            /* EOF
+            Parse error. Emit the current tag token. Reconsume the character
+            in the data state. */
+            $this->emitToken($this->token);
+
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            /* Anything else
+            Append the current input character to the current attribute's value.
+            Stay in the attribute value (single-quoted) state. */
+            $last = count($this->token['attr']) - 1;
+            $this->token['attr'][$last]['value'] .= $char;
+
+            $this->state = 'attributeValueSingleQuoted';
+        }
+    }
+
+    private function attributeValueUnquotedState() {
+        // Consume the next input character:
+        $this->char++;
+        $char = $this->character($this->char);
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            /* U+0009 CHARACTER TABULATION
+            U+000A LINE FEED (LF)
+            U+000B LINE TABULATION
+            U+000C FORM FEED (FF)
+            U+0020 SPACE
+            Switch to the before attribute name state. */
+            $this->state = 'beforeAttributeName';
+
+        } elseif($char === '&') {
+            /* U+0026 AMPERSAND (&)
+            Switch to the entity in attribute value state. */
+            $this->entityInAttributeValueState();
+
+        } elseif($char === '>') {
+            /* U+003E GREATER-THAN SIGN (>)
+            Emit the current tag token. Switch to the data state. */
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } else {
+            /* Anything else
+            Append the current input character to the current attribute's value.
+            Stay in the attribute value (unquoted) state. */
+            $last = count($this->token['attr']) - 1;
+            $this->token['attr'][$last]['value'] .= $char;
+
+            $this->state = 'attributeValueUnquoted';
+        }
+    }
+
+    private function entityInAttributeValueState() {
+        // Attempt to consume an entity.
+        $entity = $this->entity();
+
+        // If nothing is returned, append a U+0026 AMPERSAND character to the
+        // current attribute's value. Otherwise, emit the character token that
+        // was returned.
+        $char = (!$entity)
+            ? '&'
+            : $entity;
+
+        $last = count($this->token['attr']) - 1;
+        $this->token['attr'][$last]['value'] .= $char;
+    }
+
+    private function bogusCommentState() {
+        /* Consume every character up to the first U+003E GREATER-THAN SIGN
+        character (>) or the end of the file (EOF), whichever comes first. Emit
+        a comment token whose data is the concatenation of all the characters
+        starting from and including the character that caused the state machine
+        to switch into the bogus comment state, up to and including the last
+        consumed character before the U+003E character, if any, or up to the
+        end of the file otherwise. (If the comment was started by the end of
+        the file (EOF), the token is empty.) */
+        $data = $this->characters('^>', $this->char);
+        $this->emitToken(array(
+            'data' => $data,
+            'type' => self::COMMENT
+        ));
+
+        $this->char += strlen($data);
+
+        /* Switch to the data state. */
+        $this->state = 'data';
+
+        /* If the end of the file was reached, reconsume the EOF character. */
+        if($this->char === $this->EOF) {
+            $this->char = $this->EOF - 1;
+        }
+    }
+
+    private function markupDeclarationOpenState() {
+        /* If the next two characters are both U+002D HYPHEN-MINUS (-)
+        characters, consume those two characters, create a comment token whose
+        data is the empty string, and switch to the comment state. */
+        if($this->character($this->char + 1, 2) === '--') {
+            $this->char += 2;
+            $this->state = 'comment';
+            $this->token = array(
+                'data' => null,
+                'type' => self::COMMENT
+            );
+
+        /* Otherwise if the next seven chacacters are a case-insensitive match
+        for the word "DOCTYPE", then consume those characters and switch to the
+        DOCTYPE state. */
+        } elseif(strtolower($this->character($this->char + 1, 7)) === 'doctype') {
+            $this->char += 7;
+            $this->state = 'doctype';
+
+        /* Otherwise, is is a parse error. Switch to the bogus comment state.
+        The next character that is consumed, if any, is the first character
+        that will be in the comment. */
+        } else {
+            $this->char++;
+            $this->state = 'bogusComment';
+        }
+    }
+
+    private function commentState() {
+        /* Consume the next input character: */
+        $this->char++;
+        $char = $this->char();
+
+        /* U+002D HYPHEN-MINUS (-) */
+        if($char === '-') {
+            /* Switch to the comment dash state  */
+            $this->state = 'commentDash';
+
+        /* EOF */
+        } elseif($this->char === $this->EOF) {
+            /* Parse error. Emit the comment token. Reconsume the EOF character
+            in the data state. */
+            $this->emitToken($this->token);
+            $this->char--;
+            $this->state = 'data';
+
+        /* Anything else */
+        } else {
+            /* Append the input character to the comment token's data. Stay in
+            the comment state. */
+            $this->token['data'] .= $char;
+        }
+    }
+
+    private function commentDashState() {
+        /* Consume the next input character: */
+        $this->char++;
+        $char = $this->char();
+
+        /* U+002D HYPHEN-MINUS (-) */
+        if($char === '-') {
+            /* Switch to the comment end state  */
+            $this->state = 'commentEnd';
+
+        /* EOF */
+        } elseif($this->char === $this->EOF) {
+            /* Parse error. Emit the comment token. Reconsume the EOF character
+            in the data state. */
+            $this->emitToken($this->token);
+            $this->char--;
+            $this->state = 'data';
+
+        /* Anything else */
+        } else {
+            /* Append a U+002D HYPHEN-MINUS (-) character and the input
+            character to the comment token's data. Switch to the comment state. */
+            $this->token['data'] .= '-'.$char;
+            $this->state = 'comment';
+        }
+    }
+
+    private function commentEndState() {
+        /* Consume the next input character: */
+        $this->char++;
+        $char = $this->char();
+
+        if($char === '>') {
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } elseif($char === '-') {
+            $this->token['data'] .= '-';
+
+        } elseif($this->char === $this->EOF) {
+            $this->emitToken($this->token);
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            $this->token['data'] .= '--'.$char;
+            $this->state = 'comment';
+        }
+    }
+
+    private function doctypeState() {
+        /* Consume the next input character: */
+        $this->char++;
+        $char = $this->char();
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            $this->state = 'beforeDoctypeName';
+
+        } else {
+            $this->char--;
+            $this->state = 'beforeDoctypeName';
+        }
+    }
+
+    private function beforeDoctypeNameState() {
+        /* Consume the next input character: */
+        $this->char++;
+        $char = $this->char();
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            // Stay in the before DOCTYPE name state.
+
+        } elseif(preg_match('/^[a-z]$/', $char)) {
+            $this->token = array(
+                'name' => strtoupper($char),
+                'type' => self::DOCTYPE,
+                'error' => true
+            );
+
+            $this->state = 'doctypeName';
+
+        } elseif($char === '>') {
+            $this->emitToken(array(
+                'name' => null,
+                'type' => self::DOCTYPE,
+                'error' => true
+            ));
+
+            $this->state = 'data';
+
+        } elseif($this->char === $this->EOF) {
+            $this->emitToken(array(
+                'name' => null,
+                'type' => self::DOCTYPE,
+                'error' => true
+            ));
+
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            $this->token = array(
+                'name' => $char,
+                'type' => self::DOCTYPE,
+                'error' => true
+            );
+
+            $this->state = 'doctypeName';
+        }
+    }
+
+    private function doctypeNameState() {
+        /* Consume the next input character: */
+        $this->char++;
+        $char = $this->char();
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            $this->state = 'AfterDoctypeName';
+
+        } elseif($char === '>') {
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } elseif(preg_match('/^[a-z]$/', $char)) {
+            $this->token['name'] .= strtoupper($char);
+
+        } elseif($this->char === $this->EOF) {
+            $this->emitToken($this->token);
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            $this->token['name'] .= $char;
+        }
+
+        $this->token['error'] = ($this->token['name'] === 'HTML')
+            ? false
+            : true;
+    }
+
+    private function afterDoctypeNameState() {
+        /* Consume the next input character: */
+        $this->char++;
+        $char = $this->char();
+
+        if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) {
+            // Stay in the DOCTYPE name state.
+
+        } elseif($char === '>') {
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } elseif($this->char === $this->EOF) {
+            $this->emitToken($this->token);
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            $this->token['error'] = true;
+            $this->state = 'bogusDoctype';
+        }
+    }
+
+    private function bogusDoctypeState() {
+        /* Consume the next input character: */
+        $this->char++;
+        $char = $this->char();
+
+        if($char === '>') {
+            $this->emitToken($this->token);
+            $this->state = 'data';
+
+        } elseif($this->char === $this->EOF) {
+            $this->emitToken($this->token);
+            $this->char--;
+            $this->state = 'data';
+
+        } else {
+            // Stay in the bogus DOCTYPE state.
+        }
+    }
+
+    private function entity() {
+        $start = $this->char;
+
+        // This section defines how to consume an entity. This definition is
+        // used when parsing entities in text and in attributes.
+
+        // The behaviour depends on the identity of the next character (the
+        // one immediately after the U+0026 AMPERSAND character): 
+
+        switch($this->character($this->char + 1)) {
+            // U+0023 NUMBER SIGN (#)
+            case '#':
+
+                // The behaviour further depends on the character after the
+                // U+0023 NUMBER SIGN:
+                switch($this->character($this->char + 1)) {
+                    // U+0078 LATIN SMALL LETTER X
+                    // U+0058 LATIN CAPITAL LETTER X
+                    case 'x':
+                    case 'X':
+                        // Follow the steps below, but using the range of
+                        // characters U+0030 DIGIT ZERO through to U+0039 DIGIT
+                        // NINE, U+0061 LATIN SMALL LETTER A through to U+0066
+                        // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER
+                        // A, through to U+0046 LATIN CAPITAL LETTER F (in other
+                        // words, 0-9, A-F, a-f).
+                        $char = 1;
+                        $char_class = '0-9A-Fa-f';
+                    break;
+
+                    // Anything else
+                    default:
+                        // Follow the steps below, but using the range of
+                        // characters U+0030 DIGIT ZERO through to U+0039 DIGIT
+                        // NINE (i.e. just 0-9).
+                        $char = 0;
+                        $char_class = '0-9';
+                    break;
+                }
+
+                // Consume as many characters as match the range of characters
+                // given above.
+                $this->char++;
+                $e_name = $this->characters($char_class, $this->char + $char + 1);
+                $entity = $this->character($start, $this->char);
+                $cond = strlen($e_name) > 0;
+
+                // The rest of the parsing happens bellow.
+            break;
+
+            // Anything else
+            default:
+                // Consume the maximum number of characters possible, with the
+                // consumed characters case-sensitively matching one of the
+                // identifiers in the first column of the entities table.
+                $e_name = $this->characters('0-9A-Za-z;', $this->char + 1);
+                $len = strlen($e_name);
+
+                for($c = 1; $c <= $len; $c++) {
+                    $id = substr($e_name, 0, $c);
+                    $this->char++;
+
+                    if(in_array($id, $this->entities)) {
+                        if ($e_name[$c-1] !== ';') {
+                            if ($c < $len && $e_name[$c] == ';') {
+                                $this->char++; // consume extra semicolon
+                            }
+                        }
+                        $entity = $id;
+                        break;
+                    }
+                }
+
+                $cond = isset($entity);
+                // The rest of the parsing happens bellow.
+            break;
+        }
+
+        if(!$cond) {
+            // If no match can be made, then this is a parse error. No
+            // characters are consumed, and nothing is returned.
+            $this->char = $start;
+            return false;
+        }
+
+        // Return a character token for the character corresponding to the
+        // entity name (as given by the second column of the entities table).
+        return html_entity_decode('&'.$entity.';', ENT_QUOTES, 'UTF-8');
+    }
+
+    private function emitToken($token) {
+        $emit = $this->tree->emitToken($token);
+
+        if(is_int($emit)) {
+            $this->content_model = $emit;
+
+        } elseif($token['type'] === self::ENDTAG) {
+            $this->content_model = self::PCDATA;
+        }
+    }
+
+    private function EOF() {
+        $this->state = null;
+        $this->tree->emitToken(array(
+            'type' => self::EOF
+        ));
+    }
+}
+
+class HTML5TreeConstructer {
+    public $stack = array();
+
+    private $phase;
+    private $mode;
+    private $dom;
+    private $foster_parent = null;
+    private $a_formatting  = array();
+
+    private $head_pointer = null;
+    private $form_pointer = null;
+
+    private $scoping = array('button','caption','html','marquee','object','table','td','th');
+    private $formatting = array('a','b','big','em','font','i','nobr','s','small','strike','strong','tt','u');
+    private $special = array('address','area','base','basefont','bgsound',
+    'blockquote','body','br','center','col','colgroup','dd','dir','div','dl',
+    'dt','embed','fieldset','form','frame','frameset','h1','h2','h3','h4','h5',
+    'h6','head','hr','iframe','image','img','input','isindex','li','link',
+    'listing','menu','meta','noembed','noframes','noscript','ol','optgroup',
+    'option','p','param','plaintext','pre','script','select','spacer','style',
+    'tbody','textarea','tfoot','thead','title','tr','ul','wbr');
+
+    // The different phases.
+    const INIT_PHASE = 0;
+    const ROOT_PHASE = 1;
+    const MAIN_PHASE = 2;
+    const END_PHASE  = 3;
+
+    // The different insertion modes for the main phase.
+    const BEFOR_HEAD = 0;
+    const IN_HEAD    = 1;
+    const AFTER_HEAD = 2;
+    const IN_BODY    = 3;
+    const IN_TABLE   = 4;
+    const IN_CAPTION = 5;
+    const IN_CGROUP  = 6;
+    const IN_TBODY   = 7;
+    const IN_ROW     = 8;
+    const IN_CELL    = 9;
+    const IN_SELECT  = 10;
+    const AFTER_BODY = 11;
+    const IN_FRAME   = 12;
+    const AFTR_FRAME = 13;
+
+    // The different types of elements.
+    const SPECIAL    = 0;
+    const SCOPING    = 1;
+    const FORMATTING = 2;
+    const PHRASING   = 3;
+
+    const MARKER     = 0;
+
+    public function __construct() {
+        $this->phase = self::INIT_PHASE;
+        $this->mode = self::BEFOR_HEAD;
+        $this->dom = new DOMDocument;
+
+        $this->dom->encoding = 'UTF-8';
+        $this->dom->preserveWhiteSpace = true;
+        $this->dom->substituteEntities = true;
+        $this->dom->strictErrorChecking = false;
+    }
+
+    // Process tag tokens
+    public function emitToken($token) {
+        switch($this->phase) {
+            case self::INIT_PHASE: return $this->initPhase($token); break;
+            case self::ROOT_PHASE: return $this->rootElementPhase($token); break;
+            case self::MAIN_PHASE: return $this->mainPhase($token); break;
+            case self::END_PHASE : return $this->trailingEndPhase($token); break;
+        }
+    }
+
+    private function initPhase($token) {
+        /* Initially, the tree construction stage must handle each token
+        emitted from the tokenisation stage as follows: */
+
+        /* A DOCTYPE token that is marked as being in error
+        A comment token
+        A start tag token
+        An end tag token
+        A character token that is not one of one of U+0009 CHARACTER TABULATION,
+            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+            or U+0020 SPACE
+        An end-of-file token */
+        if((isset($token['error']) && $token['error']) ||
+        $token['type'] === HTML5::COMMENT ||
+        $token['type'] === HTML5::STARTTAG ||
+        $token['type'] === HTML5::ENDTAG ||
+        $token['type'] === HTML5::EOF ||
+        ($token['type'] === HTML5::CHARACTR && isset($token['data']) &&
+        !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']))) {
+            /* This specification does not define how to handle this case. In
+            particular, user agents may ignore the entirety of this specification
+            altogether for such documents, and instead invoke special parse modes
+            with a greater emphasis on backwards compatibility. */
+
+            $this->phase = self::ROOT_PHASE;
+            return $this->rootElementPhase($token);
+
+        /* A DOCTYPE token marked as being correct */
+        } elseif(isset($token['error']) && !$token['error']) {
+            /* Append a DocumentType node to the Document  node, with the name
+            attribute set to the name given in the DOCTYPE token (which will be
+            "HTML"), and the other attributes specific to DocumentType objects
+            set to null, empty lists, or the empty string as appropriate. */
+            $doctype = new DOMDocumentType(null, null, 'HTML');
+
+            /* Then, switch to the root element phase of the tree construction
+            stage. */
+            $this->phase = self::ROOT_PHASE;
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE */
+        } elseif(isset($token['data']) && preg_match('/^[\t\n\x0b\x0c ]+$/',
+        $token['data'])) {
+            /* Append that character  to the Document node. */
+            $text = $this->dom->createTextNode($token['data']);
+            $this->dom->appendChild($text);
+        }
+    }
+
+    private function rootElementPhase($token) {
+        /* After the initial phase, as each token is emitted from the tokenisation
+        stage, it must be processed as described in this section. */
+
+        /* A DOCTYPE token */
+        if($token['type'] === HTML5::DOCTYPE) {
+            // Parse error. Ignore the token.
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the Document object with the data
+            attribute set to the data given in the comment token. */
+            $comment = $this->dom->createComment($token['data']);
+            $this->dom->appendChild($comment);
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE */
+        } elseif($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Append that character  to the Document node. */
+            $text = $this->dom->createTextNode($token['data']);
+            $this->dom->appendChild($text);
+
+        /* A character token that is not one of U+0009 CHARACTER TABULATION,
+            U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED
+            (FF), or U+0020 SPACE
+        A start tag token
+        An end tag token
+        An end-of-file token */
+        } elseif(($token['type'] === HTML5::CHARACTR &&
+        !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) ||
+        $token['type'] === HTML5::STARTTAG ||
+        $token['type'] === HTML5::ENDTAG ||
+        $token['type'] === HTML5::EOF) {
+            /* Create an HTMLElement node with the tag name html, in the HTML
+            namespace. Append it to the Document object. Switch to the main
+            phase and reprocess the current token. */
+            $html = $this->dom->createElement('html');
+            $this->dom->appendChild($html);
+            $this->stack[] = $html;
+
+            $this->phase = self::MAIN_PHASE;
+            return $this->mainPhase($token);
+        }
+    }
+
+    private function mainPhase($token) {
+        /* Tokens in the main phase must be handled as follows: */
+
+        /* A DOCTYPE token */
+        if($token['type'] === HTML5::DOCTYPE) {
+            // Parse error. Ignore the token.
+
+        /* A start tag token with the tag name "html" */
+        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') {
+            /* If this start tag token was not the first start tag token, then
+            it is a parse error. */
+
+            /* For each attribute on the token, check to see if the attribute
+            is already present on the top element of the stack of open elements.
+            If it is not, add the attribute and its corresponding value to that
+            element. */
+            foreach($token['attr'] as $attr) {
+                if(!$this->stack[0]->hasAttribute($attr['name'])) {
+                    $this->stack[0]->setAttribute($attr['name'], $attr['value']);
+                }
+            }
+
+        /* An end-of-file token */
+        } elseif($token['type'] === HTML5::EOF) {
+            /* Generate implied end tags. */
+            $this->generateImpliedEndTags();
+
+        /* Anything else. */
+        } else {
+            /* Depends on the insertion mode: */
+            switch($this->mode) {
+                case self::BEFOR_HEAD: return $this->beforeHead($token); break;
+                case self::IN_HEAD:    return $this->inHead($token); break;
+                case self::AFTER_HEAD: return $this->afterHead($token); break;
+                case self::IN_BODY:    return $this->inBody($token); break;
+                case self::IN_TABLE:   return $this->inTable($token); break;
+                case self::IN_CAPTION: return $this->inCaption($token); break;
+                case self::IN_CGROUP:  return $this->inColumnGroup($token); break;
+                case self::IN_TBODY:   return $this->inTableBody($token); break;
+                case self::IN_ROW:     return $this->inRow($token); break;
+                case self::IN_CELL:    return $this->inCell($token); break;
+                case self::IN_SELECT:  return $this->inSelect($token); break;
+                case self::AFTER_BODY: return $this->afterBody($token); break;
+                case self::IN_FRAME:   return $this->inFrameset($token); break;
+                case self::AFTR_FRAME: return $this->afterFrameset($token); break;
+                case self::END_PHASE:  return $this->trailingEndPhase($token); break;
+            }
+        }
+    }
+
+    private function beforeHead($token) {
+        /* Handle the token as follows: */
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE */
+        if($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Append the character to the current node. */
+            $this->insertText($token['data']);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the current node with the data attribute
+            set to the data given in the comment token. */
+            $this->insertComment($token['data']);
+
+        /* A start tag token with the tag name "head" */
+        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') {
+            /* Create an element for the token, append the new element to the
+            current node and push it onto the stack of open elements. */
+            $element = $this->insertElement($token);
+
+            /* Set the head element pointer to this new element node. */
+            $this->head_pointer = $element;
+
+            /* Change the insertion mode to "in head". */
+            $this->mode = self::IN_HEAD;
+
+        /* A start tag token whose tag name is one of: "base", "link", "meta",
+        "script", "style", "title". Or an end tag with the tag name "html".
+        Or a character token that is not one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE. Or any other start tag token */
+        } elseif($token['type'] === HTML5::STARTTAG ||
+        ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') ||
+        ($token['type'] === HTML5::CHARACTR && !preg_match('/^[\t\n\x0b\x0c ]$/',
+        $token['data']))) {
+            /* Act as if a start tag token with the tag name "head" and no
+            attributes had been seen, then reprocess the current token. */
+            $this->beforeHead(array(
+                'name' => 'head',
+                'type' => HTML5::STARTTAG,
+                'attr' => array()
+            ));
+
+            return $this->inHead($token);
+
+        /* Any other end tag */
+        } elseif($token['type'] === HTML5::ENDTAG) {
+            /* Parse error. Ignore the token. */
+        }
+    }
+
+    private function inHead($token) {
+        /* Handle the token as follows: */
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE.
+
+        THIS DIFFERS FROM THE SPEC: If the current node is either a title, style
+        or script element, append the character to the current node regardless
+        of its content. */
+        if(($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || (
+        $token['type'] === HTML5::CHARACTR && in_array(end($this->stack)->nodeName,
+        array('title', 'style', 'script')))) {
+            /* Append the character to the current node. */
+            $this->insertText($token['data']);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the current node with the data attribute
+            set to the data given in the comment token. */
+            $this->insertComment($token['data']);
+
+        } elseif($token['type'] === HTML5::ENDTAG &&
+        in_array($token['name'], array('title', 'style', 'script'))) {
+            array_pop($this->stack);
+            return HTML5::PCDATA;
+
+        /* A start tag with the tag name "title" */
+        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') {
+            /* Create an element for the token and append the new element to the
+            node pointed to by the head element pointer, or, if that is null
+            (innerHTML case), to the current node. */
+            if($this->head_pointer !== null) {
+                $element = $this->insertElement($token, false);
+                $this->head_pointer->appendChild($element);
+
+            } else {
+                $element = $this->insertElement($token);
+            }
+
+            /* Switch the tokeniser's content model flag  to the RCDATA state. */
+            return HTML5::RCDATA;
+
+        /* A start tag with the tag name "style" */
+        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') {
+            /* Create an element for the token and append the new element to the
+            node pointed to by the head element pointer, or, if that is null
+            (innerHTML case), to the current node. */
+            if($this->head_pointer !== null) {
+                $element = $this->insertElement($token, false);
+                $this->head_pointer->appendChild($element);
+
+            } else {
+                $this->insertElement($token);
+            }
+
+            /* Switch the tokeniser's content model flag  to the CDATA state. */
+            return HTML5::CDATA;
+
+        /* A start tag with the tag name "script" */
+        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') {
+            /* Create an element for the token. */
+            $element = $this->insertElement($token, false);
+            $this->head_pointer->appendChild($element);
+
+            /* Switch the tokeniser's content model flag  to the CDATA state. */
+            return HTML5::CDATA;
+
+        /* A start tag with the tag name "base", "link", or "meta" */
+        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
+        array('base', 'link', 'meta'))) {
+            /* Create an element for the token and append the new element to the
+            node pointed to by the head element pointer, or, if that is null
+            (innerHTML case), to the current node. */
+            if($this->head_pointer !== null) {
+                $element = $this->insertElement($token, false);
+                $this->head_pointer->appendChild($element);
+                array_pop($this->stack);
+
+            } else {
+                $this->insertElement($token);
+            }
+
+        /* An end tag with the tag name "head" */
+        } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') {
+            /* If the current node is a head element, pop the current node off
+            the stack of open elements. */
+            if($this->head_pointer->isSameNode(end($this->stack))) {
+                array_pop($this->stack);
+
+            /* Otherwise, this is a parse error. */
+            } else {
+                // k
+            }
+
+            /* Change the insertion mode to "after head". */
+            $this->mode = self::AFTER_HEAD;
+
+        /* A start tag with the tag name "head" or an end tag except "html". */
+        } elseif(($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') ||
+        ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html')) {
+            // Parse error. Ignore the token.
+
+        /* Anything else */
+        } else {
+            /* If the current node is a head element, act as if an end tag
+            token with the tag name "head" had been seen. */
+            if($this->head_pointer->isSameNode(end($this->stack))) {
+                $this->inHead(array(
+                    'name' => 'head',
+                    'type' => HTML5::ENDTAG
+                ));
+
+            /* Otherwise, change the insertion mode to "after head". */
+            } else {
+                $this->mode = self::AFTER_HEAD;
+            }
+
+            /* Then, reprocess the current token. */
+            return $this->afterHead($token);
+        }
+    }
+
+    private function afterHead($token) {
+        /* Handle the token as follows: */
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE */
+        if($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Append the character to the current node. */
+            $this->insertText($token['data']);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the current node with the data attribute
+            set to the data given in the comment token. */
+            $this->insertComment($token['data']);
+
+        /* A start tag token with the tag name "body" */
+        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') {
+            /* Insert a body element for the token. */
+            $this->insertElement($token);
+
+            /* Change the insertion mode to "in body". */
+            $this->mode = self::IN_BODY;
+
+        /* A start tag token with the tag name "frameset" */
+        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') {
+            /* Insert a frameset element for the token. */
+            $this->insertElement($token);
+
+            /* Change the insertion mode to "in frameset". */
+            $this->mode = self::IN_FRAME;
+
+        /* A start tag token whose tag name is one of: "base", "link", "meta",
+        "script", "style", "title" */
+        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
+        array('base', 'link', 'meta', 'script', 'style', 'title'))) {
+            /* Parse error. Switch the insertion mode back to "in head" and
+            reprocess the token. */
+            $this->mode = self::IN_HEAD;
+            return $this->inHead($token);
+
+        /* Anything else */
+        } else {
+            /* Act as if a start tag token with the tag name "body" and no
+            attributes had been seen, and then reprocess the current token. */
+            $this->afterHead(array(
+                'name' => 'body',
+                'type' => HTML5::STARTTAG,
+                'attr' => array()
+            ));
+
+            return $this->inBody($token);
+        }
+    }
+
+    private function inBody($token) {
+        /* Handle the token as follows: */
+
+        switch($token['type']) {
+            /* A character token */
+            case HTML5::CHARACTR:
+                /* Reconstruct the active formatting elements, if any. */
+                $this->reconstructActiveFormattingElements();
+
+                /* Append the token's character to the current node. */
+                $this->insertText($token['data']);
+            break;
+
+            /* A comment token */
+            case HTML5::COMMENT:
+                /* Append a Comment node to the current node with the data
+                attribute set to the data given in the comment token. */
+                $this->insertComment($token['data']);
+            break;
+
+            case HTML5::STARTTAG:
+            switch($token['name']) {
+                /* A start tag token whose tag name is one of: "script",
+                "style" */
+                case 'script': case 'style':
+                    /* Process the token as if the insertion mode had been "in
+                    head". */
+                    return $this->inHead($token);
+                break;
+
+                /* A start tag token whose tag name is one of: "base", "link",
+                "meta", "title" */
+                case 'base': case 'link': case 'meta': case 'title':
+                    /* Parse error. Process the token as if the insertion mode
+                    had    been "in head". */
+                    return $this->inHead($token);
+                break;
+
+                /* A start tag token with the tag name "body" */
+                case 'body':
+                    /* Parse error. If the second element on the stack of open
+                    elements is not a body element, or, if the stack of open
+                    elements has only one node on it, then ignore the token.
+                    (innerHTML case) */
+                    if(count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') {
+                        // Ignore
+
+                    /* Otherwise, for each attribute on the token, check to see
+                    if the attribute is already present on the body element (the
+                    second element)    on the stack of open elements. If it is not,
+                    add the attribute and its corresponding value to that
+                    element. */
+                    } else {
+                        foreach($token['attr'] as $attr) {
+                            if(!$this->stack[1]->hasAttribute($attr['name'])) {
+                                $this->stack[1]->setAttribute($attr['name'], $attr['value']);
+                            }
+                        }
+                    }
+                break;
+
+                /* A start tag whose tag name is one of: "address",
+                "blockquote", "center", "dir", "div", "dl", "fieldset",
+                "listing", "menu", "ol", "p", "ul" */
+                case 'address': case 'blockquote': case 'center': case 'dir':
+                case 'div': case 'dl': case 'fieldset': case 'listing':
+                case 'menu': case 'ol': case 'p': case 'ul':
+                    /* If the stack of open elements has a p element in scope,
+                    then act as if an end tag with the tag name p had been
+                    seen. */
+                    if($this->elementInScope('p')) {
+                        $this->emitToken(array(
+                            'name' => 'p',
+                            'type' => HTML5::ENDTAG
+                        ));
+                    }
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+                break;
+
+                /* A start tag whose tag name is "form" */
+                case 'form':
+                    /* If the form element pointer is not null, ignore the
+                    token with a parse error. */
+                    if($this->form_pointer !== null) {
+                        // Ignore.
+
+                    /* Otherwise: */
+                    } else {
+                        /* If the stack of open elements has a p element in
+                        scope, then act as if an end tag with the tag name p
+                        had been seen. */
+                        if($this->elementInScope('p')) {
+                            $this->emitToken(array(
+                                'name' => 'p',
+                                'type' => HTML5::ENDTAG
+                            ));
+                        }
+
+                        /* Insert an HTML element for the token, and set the
+                        form element pointer to point to the element created. */
+                        $element = $this->insertElement($token);
+                        $this->form_pointer = $element;
+                    }
+                break;
+
+                /* A start tag whose tag name is "li", "dd" or "dt" */
+                case 'li': case 'dd': case 'dt':
+                    /* If the stack of open elements has a p  element in scope,
+                    then act as if an end tag with the tag name p had been
+                    seen. */
+                    if($this->elementInScope('p')) {
+                        $this->emitToken(array(
+                            'name' => 'p',
+                            'type' => HTML5::ENDTAG
+                        ));
+                    }
+
+                    $stack_length = count($this->stack) - 1;
+
+                    for($n = $stack_length; 0 <= $n; $n--) {
+                        /* 1. Initialise node to be the current node (the
+                        bottommost node of the stack). */
+                        $stop = false;
+                        $node = $this->stack[$n];
+                        $cat  = $this->getElementCategory($node->tagName);
+
+                        /* 2. If node is an li, dd or dt element, then pop all
+                        the    nodes from the current node up to node, including
+                        node, then stop this algorithm. */
+                        if($token['name'] === $node->tagName ||    ($token['name'] !== 'li'
+                        && ($node->tagName === 'dd' || $node->tagName === 'dt'))) {
+                            for($x = $stack_length; $x >= $n ; $x--) {
+                                array_pop($this->stack);
+                            }
+
+                            break;
+                        }
+
+                        /* 3. If node is not in the formatting category, and is
+                        not    in the phrasing category, and is not an address or
+                        div element, then stop this algorithm. */
+                        if($cat !== self::FORMATTING && $cat !== self::PHRASING &&
+                        $node->tagName !== 'address' && $node->tagName !== 'div') {
+                            break;
+                        }
+                    }
+
+                    /* Finally, insert an HTML element with the same tag
+                    name as the    token's. */
+                    $this->insertElement($token);
+                break;
+
+                /* A start tag token whose tag name is "plaintext" */
+                case 'plaintext':
+                    /* If the stack of open elements has a p  element in scope,
+                    then act as if an end tag with the tag name p had been
+                    seen. */
+                    if($this->elementInScope('p')) {
+                        $this->emitToken(array(
+                            'name' => 'p',
+                            'type' => HTML5::ENDTAG
+                        ));
+                    }
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+
+                    return HTML5::PLAINTEXT;
+                break;
+
+                /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4",
+                "h5", "h6" */
+                case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
+                    /* If the stack of open elements has a p  element in scope,
+                    then act as if an end tag with the tag name p had been seen. */
+                    if($this->elementInScope('p')) {
+                        $this->emitToken(array(
+                            'name' => 'p',
+                            'type' => HTML5::ENDTAG
+                        ));
+                    }
+
+                    /* If the stack of open elements has in scope an element whose
+                    tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
+                    this is a parse error; pop elements from the stack until an
+                    element with one of those tag names has been popped from the
+                    stack. */
+                    while($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) {
+                        array_pop($this->stack);
+                    }
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+                break;
+
+                /* A start tag whose tag name is "a" */
+                case 'a':
+                    /* If the list of active formatting elements contains
+                    an element whose tag name is "a" between the end of the
+                    list and the last marker on the list (or the start of
+                    the list if there is no marker on the list), then this
+                    is a parse error; act as if an end tag with the tag name
+                    "a" had been seen, then remove that element from the list
+                    of active formatting elements and the stack of open
+                    elements if the end tag didn't already remove it (it
+                    might not have if the element is not in table scope). */
+                    $leng = count($this->a_formatting);
+
+                    for($n = $leng - 1; $n >= 0; $n--) {
+                        if($this->a_formatting[$n] === self::MARKER) {
+                            break;
+
+                        } elseif($this->a_formatting[$n]->nodeName === 'a') {
+                            $this->emitToken(array(
+                                'name' => 'a',
+                                'type' => HTML5::ENDTAG
+                            ));
+                            break;
+                        }
+                    }
+
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    /* Insert an HTML element for the token. */
+                    $el = $this->insertElement($token);
+
+                    /* Add that element to the list of active formatting
+                    elements. */
+                    $this->a_formatting[] = $el;
+                break;
+
+                /* A start tag whose tag name is one of: "b", "big", "em", "font",
+                "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
+                case 'b': case 'big': case 'em': case 'font': case 'i':
+                case 'nobr': case 's': case 'small': case 'strike':
+                case 'strong': case 'tt': case 'u':
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    /* Insert an HTML element for the token. */
+                    $el = $this->insertElement($token);
+
+                    /* Add that element to the list of active formatting
+                    elements. */
+                    $this->a_formatting[] = $el;
+                break;
+
+                /* A start tag token whose tag name is "button" */
+                case 'button':
+                    /* If the stack of open elements has a button element in scope,
+                    then this is a parse error; act as if an end tag with the tag
+                    name "button" had been seen, then reprocess the token. (We don't
+                    do that. Unnecessary.) */
+                    if($this->elementInScope('button')) {
+                        $this->inBody(array(
+                            'name' => 'button',
+                            'type' => HTML5::ENDTAG
+                        ));
+                    }
+
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+
+                    /* Insert a marker at the end of the list of active
+                    formatting elements. */
+                    $this->a_formatting[] = self::MARKER;
+                break;
+
+                /* A start tag token whose tag name is one of: "marquee", "object" */
+                case 'marquee': case 'object':
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+
+                    /* Insert a marker at the end of the list of active
+                    formatting elements. */
+                    $this->a_formatting[] = self::MARKER;
+                break;
+
+                /* A start tag token whose tag name is "xmp" */
+                case 'xmp':
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+
+                    /* Switch the content model flag to the CDATA state. */
+                    return HTML5::CDATA;
+                break;
+
+                /* A start tag whose tag name is "table" */
+                case 'table':
+                    /* If the stack of open elements has a p element in scope,
+                    then act as if an end tag with the tag name p had been seen. */
+                    if($this->elementInScope('p')) {
+                        $this->emitToken(array(
+                            'name' => 'p',
+                            'type' => HTML5::ENDTAG
+                        ));
+                    }
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+
+                    /* Change the insertion mode to "in table". */
+                    $this->mode = self::IN_TABLE;
+                break;
+
+                /* A start tag whose tag name is one of: "area", "basefont",
+                "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */
+                case 'area': case 'basefont': case 'bgsound': case 'br':
+                case 'embed': case 'img': case 'param': case 'spacer':
+                case 'wbr':
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+
+                    /* Immediately pop the current node off the stack of open elements. */
+                    array_pop($this->stack);
+                break;
+
+                /* A start tag whose tag name is "hr" */
+                case 'hr':
+                    /* If the stack of open elements has a p element in scope,
+                    then act as if an end tag with the tag name p had been seen. */
+                    if($this->elementInScope('p')) {
+                        $this->emitToken(array(
+                            'name' => 'p',
+                            'type' => HTML5::ENDTAG
+                        ));
+                    }
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+
+                    /* Immediately pop the current node off the stack of open elements. */
+                    array_pop($this->stack);
+                break;
+
+                /* A start tag whose tag name is "image" */
+                case 'image':
+                    /* Parse error. Change the token's tag name to "img" and
+                    reprocess it. (Don't ask.) */
+                    $token['name'] = 'img';
+                    return $this->inBody($token);
+                break;
+
+                /* A start tag whose tag name is "input" */
+                case 'input':
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    /* Insert an input element for the token. */
+                    $element = $this->insertElement($token, false);
+
+                    /* If the form element pointer is not null, then associate the
+                    input element with the form element pointed to by the form
+                    element pointer. */
+                    $this->form_pointer !== null
+                        ? $this->form_pointer->appendChild($element)
+                        : end($this->stack)->appendChild($element);
+
+                    /* Pop that input element off the stack of open elements. */
+                    array_pop($this->stack);
+                break;
+
+                /* A start tag whose tag name is "isindex" */
+                case 'isindex':
+                    /* Parse error. */
+                    // w/e
+
+                    /* If the form element pointer is not null,
+                    then ignore the token. */
+                    if($this->form_pointer === null) {
+                        /* Act as if a start tag token with the tag name "form" had
+                        been seen. */
+                        $this->inBody(array(
+                            'name' => 'body',
+                            'type' => HTML5::STARTTAG,
+                            'attr' => array()
+                        ));
+
+                        /* Act as if a start tag token with the tag name "hr" had
+                        been seen. */
+                        $this->inBody(array(
+                            'name' => 'hr',
+                            'type' => HTML5::STARTTAG,
+                            'attr' => array()
+                        ));
+
+                        /* Act as if a start tag token with the tag name "p" had
+                        been seen. */
+                        $this->inBody(array(
+                            'name' => 'p',
+                            'type' => HTML5::STARTTAG,
+                            'attr' => array()
+                        ));
+
+                        /* Act as if a start tag token with the tag name "label"
+                        had been seen. */
+                        $this->inBody(array(
+                            'name' => 'label',
+                            'type' => HTML5::STARTTAG,
+                            'attr' => array()
+                        ));
+
+                        /* Act as if a stream of character tokens had been seen. */
+                        $this->insertText('This is a searchable index. '.
+                        'Insert your search keywords here: ');
+
+                        /* Act as if a start tag token with the tag name "input"
+                        had been seen, with all the attributes from the "isindex"
+                        token, except with the "name" attribute set to the value
+                        "isindex" (ignoring any explicit "name" attribute). */
+                        $attr = $token['attr'];
+                        $attr[] = array('name' => 'name', 'value' => 'isindex');
+
+                        $this->inBody(array(
+                            'name' => 'input',
+                            'type' => HTML5::STARTTAG,
+                            'attr' => $attr
+                        ));
+
+                        /* Act as if a stream of character tokens had been seen
+                        (see below for what they should say). */
+                        $this->insertText('This is a searchable index. '.
+                        'Insert your search keywords here: ');
+
+                        /* Act as if an end tag token with the tag name "label"
+                        had been seen. */
+                        $this->inBody(array(
+                            'name' => 'label',
+                            'type' => HTML5::ENDTAG
+                        ));
+
+                        /* Act as if an end tag token with the tag name "p" had
+                        been seen. */
+                        $this->inBody(array(
+                            'name' => 'p',
+                            'type' => HTML5::ENDTAG
+                        ));
+
+                        /* Act as if a start tag token with the tag name "hr" had
+                        been seen. */
+                        $this->inBody(array(
+                            'name' => 'hr',
+                            'type' => HTML5::ENDTAG
+                        ));
+
+                        /* Act as if an end tag token with the tag name "form" had
+                        been seen. */
+                        $this->inBody(array(
+                            'name' => 'form',
+                            'type' => HTML5::ENDTAG
+                        ));
+                    }
+                break;
+
+                /* A start tag whose tag name is "textarea" */
+                case 'textarea':
+                    $this->insertElement($token);
+
+                    /* Switch the tokeniser's content model flag to the
+                    RCDATA state. */
+                    return HTML5::RCDATA;
+                break;
+
+                /* A start tag whose tag name is one of: "iframe", "noembed",
+                "noframes" */
+                case 'iframe': case 'noembed': case 'noframes':
+                    $this->insertElement($token);
+
+                    /* Switch the tokeniser's content model flag to the CDATA state. */
+                    return HTML5::CDATA;
+                break;
+
+                /* A start tag whose tag name is "select" */
+                case 'select':
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    /* Insert an HTML element for the token. */
+                    $this->insertElement($token);
+
+                    /* Change the insertion mode to "in select". */
+                    $this->mode = self::IN_SELECT;
+                break;
+
+                /* A start or end tag whose tag name is one of: "caption", "col",
+                "colgroup", "frame", "frameset", "head", "option", "optgroup",
+                "tbody", "td", "tfoot", "th", "thead", "tr". */
+                case 'caption': case 'col': case 'colgroup': case 'frame':
+                case 'frameset': case 'head': case 'option': case 'optgroup':
+                case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead':
+                case 'tr':
+                    // Parse error. Ignore the token.
+                break;
+
+                /* A start or end tag whose tag name is one of: "event-source",
+                "section", "nav", "article", "aside", "header", "footer",
+                "datagrid", "command" */
+                case 'event-source': case 'section': case 'nav': case 'article':
+                case 'aside': case 'header': case 'footer': case 'datagrid':
+                case 'command':
+                    // Work in progress!
+                break;
+
+                /* A start tag token not covered by the previous entries */
+                default:
+                    /* Reconstruct the active formatting elements, if any. */
+                    $this->reconstructActiveFormattingElements();
+
+                    $this->insertElement($token, true, true);
+                break;
+            }
+            break;
+
+            case HTML5::ENDTAG:
+            switch($token['name']) {
+                /* An end tag with the tag name "body" */
+                case 'body':
+                    /* If the second element in the stack of open elements is
+                    not a body element, this is a parse error. Ignore the token.
+                    (innerHTML case) */
+                    if(count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') {
+                        // Ignore.
+
+                    /* If the current node is not the body element, then this
+                    is a parse error. */
+                    } elseif(end($this->stack)->nodeName !== 'body') {
+                        // Parse error.
+                    }
+
+                    /* Change the insertion mode to "after body". */
+                    $this->mode = self::AFTER_BODY;
+                break;
+
+                /* An end tag with the tag name "html" */
+                case 'html':
+                    /* Act as if an end tag with tag name "body" had been seen,
+                    then, if that token wasn't ignored, reprocess the current
+                    token. */
+                    $this->inBody(array(
+                        'name' => 'body',
+                        'type' => HTML5::ENDTAG
+                    ));
+
+                    return $this->afterBody($token);
+                break;
+
+                /* An end tag whose tag name is one of: "address", "blockquote",
+                "center", "dir", "div", "dl", "fieldset", "listing", "menu",
+                "ol", "pre", "ul" */
+                case 'address': case 'blockquote': case 'center': case 'dir':
+                case 'div': case 'dl': case 'fieldset': case 'listing':
+                case 'menu': case 'ol': case 'pre': case 'ul':
+                    /* If the stack of open elements has an element in scope
+                    with the same tag name as that of the token, then generate
+                    implied end tags. */
+                    if($this->elementInScope($token['name'])) {
+                        $this->generateImpliedEndTags();
+
+                        /* Now, if the current node is not an element with
+                        the same tag name as that of the token, then this
+                        is a parse error. */
+                        // w/e
+
+                        /* If the stack of open elements has an element in
+                        scope with the same tag name as that of the token,
+                        then pop elements from this stack until an element
+                        with that tag name has been popped from the stack. */
+                        for($n = count($this->stack) - 1; $n >= 0; $n--) {
+                            if($this->stack[$n]->nodeName === $token['name']) {
+                                $n = -1;
+                            }
+
+                            array_pop($this->stack);
+                        }
+                    }
+                break;
+
+                /* An end tag whose tag name is "form" */
+                case 'form':
+                    /* If the stack of open elements has an element in scope
+                    with the same tag name as that of the token, then generate
+                    implied    end tags. */
+                    if($this->elementInScope($token['name'])) {
+                        $this->generateImpliedEndTags();
+
+                    } 
+
+                    if(end($this->stack)->nodeName !== $token['name']) {
+                        /* Now, if the current node is not an element with the
+                        same tag name as that of the token, then this is a parse
+                        error. */
+                        // w/e
+
+                    } else {
+                        /* Otherwise, if the current node is an element with
+                        the same tag name as that of the token pop that element
+                        from the stack. */
+                        array_pop($this->stack);
+                    }
+
+                    /* In any case, set the form element pointer to null. */
+                    $this->form_pointer = null;
+                break;
+
+                /* An end tag whose tag name is "p" */
+                case 'p':
+                    /* If the stack of open elements has a p element in scope,
+                    then generate implied end tags, except for p elements. */
+                    if($this->elementInScope('p')) {
+                        $this->generateImpliedEndTags(array('p'));
+
+                        /* If the current node is not a p element, then this is
+                        a parse error. */
+                        // k
+
+                        /* If the stack of open elements has a p element in
+                        scope, then pop elements from this stack until the stack
+                        no longer has a p element in scope. */
+                        for($n = count($this->stack) - 1; $n >= 0; $n--) {
+                            if($this->elementInScope('p')) {
+                                array_pop($this->stack);
+
+                            } else {
+                                break;
+                            }
+                        }
+                    }
+                break;
+
+                /* An end tag whose tag name is "dd", "dt", or "li" */
+                case 'dd': case 'dt': case 'li':
+                    /* If the stack of open elements has an element in scope
+                    whose tag name matches the tag name of the token, then
+                    generate implied end tags, except for elements with the
+                    same tag name as the token. */
+                    if($this->elementInScope($token['name'])) {
+                        $this->generateImpliedEndTags(array($token['name']));
+
+                        /* If the current node is not an element with the same
+                        tag name as the token, then this is a parse error. */
+                        // w/e
+
+                        /* If the stack of open elements has an element in scope
+                        whose tag name matches the tag name of the token, then
+                        pop elements from this stack until an element with that
+                        tag name has been popped from the stack. */
+                        for($n = count($this->stack) - 1; $n >= 0; $n--) {
+                            if($this->stack[$n]->nodeName === $token['name']) {
+                                $n = -1;
+                            }
+
+                            array_pop($this->stack);
+                        }
+                    }
+                break;
+
+                /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4",
+                "h5", "h6" */
+                case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6':
+                    $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6');
+
+                    /* If the stack of open elements has in scope an element whose
+                    tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then
+                    generate implied end tags. */
+                    if($this->elementInScope($elements)) {
+                        $this->generateImpliedEndTags();
+
+                        /* Now, if the current node is not an element with the same
+                        tag name as that of the token, then this is a parse error. */
+                        // w/e
+
+                        /* If the stack of open elements has in scope an element
+                        whose tag name is one of "h1", "h2", "h3", "h4", "h5", or
+                        "h6", then pop elements from the stack until an element
+                        with one of those tag names has been popped from the stack. */
+                        while($this->elementInScope($elements)) {
+                            array_pop($this->stack);
+                        }
+                    }
+                break;
+
+                /* An end tag whose tag name is one of: "a", "b", "big", "em",
+                "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */
+                case 'a': case 'b': case 'big': case 'em': case 'font':
+                case 'i': case 'nobr': case 's': case 'small': case 'strike':
+                case 'strong': case 'tt': case 'u':
+                    /* 1. Let the formatting element be the last element in
+                    the list of active formatting elements that:
+                        * is between the end of the list and the last scope
+                        marker in the list, if any, or the start of the list
+                        otherwise, and
+                        * has the same tag name as the token.
+                    */
+                    while(true) {
+                        for($a = count($this->a_formatting) - 1; $a >= 0; $a--) {
+                            if($this->a_formatting[$a] === self::MARKER) {
+                                break;
+
+                            } elseif($this->a_formatting[$a]->tagName === $token['name']) {
+                                $formatting_element = $this->a_formatting[$a];
+                                $in_stack = in_array($formatting_element, $this->stack, true);
+                                $fe_af_pos = $a;
+                                break;
+                            }
+                        }
+
+                        /* If there is no such node, or, if that node is
+                        also in the stack of open elements but the element
+                        is not in scope, then this is a parse error. Abort
+                        these steps. The token is ignored. */
+                        if(!isset($formatting_element) || ($in_stack &&
+                        !$this->elementInScope($token['name']))) {
+                            break;
+
+                        /* Otherwise, if there is such a node, but that node
+                        is not in the stack of open elements, then this is a
+                        parse error; remove the element from the list, and
+                        abort these steps. */
+                        } elseif(isset($formatting_element) && !$in_stack) {
+                            unset($this->a_formatting[$fe_af_pos]);
+                            $this->a_formatting = array_merge($this->a_formatting);
+                            break;
+                        }
+
+                        /* 2. Let the furthest block be the topmost node in the
+                        stack of open elements that is lower in the stack
+                        than the formatting element, and is not an element in
+                        the phrasing or formatting categories. There might
+                        not be one. */
+                        $fe_s_pos = array_search($formatting_element, $this->stack, true);
+                        $length = count($this->stack);
+
+                        for($s = $fe_s_pos + 1; $s < $length; $s++) {
+                            $category = $this->getElementCategory($this->stack[$s]->nodeName);
+
+                            if($category !== self::PHRASING && $category !== self::FORMATTING) {
+                                $furthest_block = $this->stack[$s];
+                            }
+                        }
+
+                        /* 3. If there is no furthest block, then the UA must
+                        skip the subsequent steps and instead just pop all
+                        the nodes from the bottom of the stack of open
+                        elements, from the current node up to the formatting
+                        element, and remove the formatting element from the
+                        list of active formatting elements. */
+                        if(!isset($furthest_block)) {
+                            for($n = $length - 1; $n >= $fe_s_pos; $n--) {
+                                array_pop($this->stack);
+                            }
+
+                            unset($this->a_formatting[$fe_af_pos]);
+                            $this->a_formatting = array_merge($this->a_formatting);
+                            break;
+                        }
+
+                        /* 4. Let the common ancestor be the element
+                        immediately above the formatting element in the stack
+                        of open elements. */
+                        $common_ancestor = $this->stack[$fe_s_pos - 1];
+
+                        /* 5. If the furthest block has a parent node, then
+                        remove the furthest block from its parent node. */
+                        if($furthest_block->parentNode !== null) {
+                            $furthest_block->parentNode->removeChild($furthest_block);
+                        }
+
+                        /* 6. Let a bookmark note the position of the
+                        formatting element in the list of active formatting
+                        elements relative to the elements on either side
+                        of it in the list. */
+                        $bookmark = $fe_af_pos;
+
+                        /* 7. Let node and last node  be the furthest block.
+                        Follow these steps: */
+                        $node = $furthest_block;
+                        $last_node = $furthest_block;
+
+                        while(true) {
+                            for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) {
+                                /* 7.1 Let node be the element immediately
+                                prior to node in the stack of open elements. */
+                                $node = $this->stack[$n];
+
+                                /* 7.2 If node is not in the list of active
+                                formatting elements, then remove node from
+                                the stack of open elements and then go back
+                                to step 1. */
+                                if(!in_array($node, $this->a_formatting, true)) {
+                                    unset($this->stack[$n]);
+                                    $this->stack = array_merge($this->stack);
+
+                                } else {
+                                    break;
+                                }
+                            }
+
+                            /* 7.3 Otherwise, if node is the formatting
+                            element, then go to the next step in the overall
+                            algorithm. */
+                            if($node === $formatting_element) {
+                                break;
+
+                            /* 7.4 Otherwise, if last node is the furthest
+                            block, then move the aforementioned bookmark to
+                            be immediately after the node in the list of
+                            active formatting elements. */
+                            } elseif($last_node === $furthest_block) {
+                                $bookmark = array_search($node, $this->a_formatting, true) + 1;
+                            }
+
+                            /* 7.5 If node has any children, perform a
+                            shallow clone of node, replace the entry for
+                            node in the list of active formatting elements
+                            with an entry for the clone, replace the entry
+                            for node in the stack of open elements with an
+                            entry for the clone, and let node be the clone. */
+                            if($node->hasChildNodes()) {
+                                $clone = $node->cloneNode();
+                                $s_pos = array_search($node, $this->stack, true);
+                                $a_pos = array_search($node, $this->a_formatting, true);
+
+                                $this->stack[$s_pos] = $clone;
+                                $this->a_formatting[$a_pos] = $clone;
+                                $node = $clone;
+                            }
+
+                            /* 7.6 Insert last node into node, first removing
+                            it from its previous parent node if any. */
+                            if($last_node->parentNode !== null) {
+                                $last_node->parentNode->removeChild($last_node);
+                            }
+
+                            $node->appendChild($last_node);
+
+                            /* 7.7 Let last node be node. */
+                            $last_node = $node;
+                        }
+
+                        /* 8. Insert whatever last node ended up being in
+                        the previous step into the common ancestor node,
+                        first removing it from its previous parent node if
+                        any. */
+                        if($last_node->parentNode !== null) {
+                            $last_node->parentNode->removeChild($last_node);
+                        }
+
+                        $common_ancestor->appendChild($last_node);
+
+                        /* 9. Perform a shallow clone of the formatting
+                        element. */
+                        $clone = $formatting_element->cloneNode();
+
+                        /* 10. Take all of the child nodes of the furthest
+                        block and append them to the clone created in the
+                        last step. */
+                        while($furthest_block->hasChildNodes()) {
+                            $child = $furthest_block->firstChild;
+                            $furthest_block->removeChild($child);
+                            $clone->appendChild($child);
+                        }
+
+                        /* 11. Append that clone to the furthest block. */
+                        $furthest_block->appendChild($clone);
+
+                        /* 12. Remove the formatting element from the list
+                        of active formatting elements, and insert the clone
+                        into the list of active formatting elements at the
+                        position of the aforementioned bookmark. */
+                        $fe_af_pos = array_search($formatting_element, $this->a_formatting, true);
+                        unset($this->a_formatting[$fe_af_pos]);
+                        $this->a_formatting = array_merge($this->a_formatting);
+
+                        $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1);
+                        $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting));
+                        $this->a_formatting = array_merge($af_part1, array($clone), $af_part2);
+
+                        /* 13. Remove the formatting element from the stack
+                        of open elements, and insert the clone into the stack
+                        of open elements immediately after (i.e. in a more
+                        deeply nested position than) the position of the
+                        furthest block in that stack. */
+                        $fe_s_pos = array_search($formatting_element, $this->stack, true);
+                        $fb_s_pos = array_search($furthest_block, $this->stack, true);
+                        unset($this->stack[$fe_s_pos]);
+
+                        $s_part1 = array_slice($this->stack, 0, $fb_s_pos);
+                        $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack));
+                        $this->stack = array_merge($s_part1, array($clone), $s_part2);
+
+                        /* 14. Jump back to step 1 in this series of steps. */
+                        unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block);
+                    }
+                break;
+
+                /* An end tag token whose tag name is one of: "button",
+                "marquee", "object" */
+                case 'button': case 'marquee': case 'object':
+                    /* If the stack of open elements has an element in scope whose
+                    tag name matches the tag name of the token, then generate implied
+                    tags. */
+                    if($this->elementInScope($token['name'])) {
+                        $this->generateImpliedEndTags();
+
+                        /* Now, if the current node is not an element with the same
+                        tag name as the token, then this is a parse error. */
+                        // k
+
+                        /* Now, if the stack of open elements has an element in scope
+                        whose tag name matches the tag name of the token, then pop
+                        elements from the stack until that element has been popped from
+                        the stack, and clear the list of active formatting elements up
+                        to the last marker. */
+                        for($n = count($this->stack) - 1; $n >= 0; $n--) {
+                            if($this->stack[$n]->nodeName === $token['name']) {
+                                $n = -1;
+                            }
+
+                            array_pop($this->stack);
+                        }
+
+                        $marker = end(array_keys($this->a_formatting, self::MARKER, true));
+
+                        for($n = count($this->a_formatting) - 1; $n > $marker; $n--) {
+                            array_pop($this->a_formatting);
+                        }
+                    }
+                break;
+
+                /* Or an end tag whose tag name is one of: "area", "basefont",
+                "bgsound", "br", "embed", "hr", "iframe", "image", "img",
+                "input", "isindex", "noembed", "noframes", "param", "select",
+                "spacer", "table", "textarea", "wbr" */
+                case 'area': case 'basefont': case 'bgsound': case 'br':
+                case 'embed': case 'hr': case 'iframe': case 'image':
+                case 'img': case 'input': case 'isindex': case 'noembed':
+                case 'noframes': case 'param': case 'select': case 'spacer':
+                case 'table': case 'textarea': case 'wbr':
+                    // Parse error. Ignore the token.
+                break;
+
+                /* An end tag token not covered by the previous entries */
+                default:
+                    for($n = count($this->stack) - 1; $n >= 0; $n--) {
+                        /* Initialise node to be the current node (the bottommost
+                        node of the stack). */
+                        $node = end($this->stack);
+
+                        /* If node has the same tag name as the end tag token,
+                        then: */
+                        if($token['name'] === $node->nodeName) {
+                            /* Generate implied end tags. */
+                            $this->generateImpliedEndTags();
+
+                            /* If the tag name of the end tag token does not
+                            match the tag name of the current node, this is a
+                            parse error. */
+                            // k
+
+                            /* Pop all the nodes from the current node up to
+                            node, including node, then stop this algorithm. */
+                            for($x = count($this->stack) - $n; $x >= $n; $x--) {
+                                array_pop($this->stack);
+                            }
+                                    
+                        } else {
+                            $category = $this->getElementCategory($node);
+
+                            if($category !== self::SPECIAL && $category !== self::SCOPING) {
+                                /* Otherwise, if node is in neither the formatting
+                                category nor the phrasing category, then this is a
+                                parse error. Stop this algorithm. The end tag token
+                                is ignored. */
+                                return false;
+                            }
+                        }
+                    }
+                break;
+            }
+            break;
+        }
+    }
+
+    private function inTable($token) {
+        $clear = array('html', 'table');
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE */
+        if($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Append the character to the current node. */
+            $text = $this->dom->createTextNode($token['data']);
+            end($this->stack)->appendChild($text);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the current node with the data
+            attribute set to the data given in the comment token. */
+            $comment = $this->dom->createComment($token['data']);
+            end($this->stack)->appendChild($comment);
+
+        /* A start tag whose tag name is "caption" */
+        } elseif($token['type'] === HTML5::STARTTAG &&
+        $token['name'] === 'caption') {
+            /* Clear the stack back to a table context. */
+            $this->clearStackToTableContext($clear);
+
+            /* Insert a marker at the end of the list of active
+            formatting elements. */
+            $this->a_formatting[] = self::MARKER;
+
+            /* Insert an HTML element for the token, then switch the
+            insertion mode to "in caption". */
+            $this->insertElement($token);
+            $this->mode = self::IN_CAPTION;
+
+        /* A start tag whose tag name is "colgroup" */
+        } elseif($token['type'] === HTML5::STARTTAG &&
+        $token['name'] === 'colgroup') {
+            /* Clear the stack back to a table context. */
+            $this->clearStackToTableContext($clear);
+
+            /* Insert an HTML element for the token, then switch the
+            insertion mode to "in column group". */
+            $this->insertElement($token);
+            $this->mode = self::IN_CGROUP;
+
+        /* A start tag whose tag name is "col" */
+        } elseif($token['type'] === HTML5::STARTTAG &&
+        $token['name'] === 'col') {
+            $this->inTable(array(
+                'name' => 'colgroup',
+                'type' => HTML5::STARTTAG,
+                'attr' => array()
+            ));
+
+            $this->inColumnGroup($token);
+
+        /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */
+        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
+        array('tbody', 'tfoot', 'thead'))) {
+            /* Clear the stack back to a table context. */
+            $this->clearStackToTableContext($clear);
+
+            /* Insert an HTML element for the token, then switch the insertion
+            mode to "in table body". */
+            $this->insertElement($token);
+            $this->mode = self::IN_TBODY;
+
+        /* A start tag whose tag name is one of: "td", "th", "tr" */
+        } elseif($token['type'] === HTML5::STARTTAG &&
+        in_array($token['name'], array('td', 'th', 'tr'))) {
+            /* Act as if a start tag token with the tag name "tbody" had been
+            seen, then reprocess the current token. */
+            $this->inTable(array(
+                'name' => 'tbody',
+                'type' => HTML5::STARTTAG,
+                'attr' => array()
+            ));
+
+            return $this->inTableBody($token);
+
+        /* A start tag whose tag name is "table" */
+        } elseif($token['type'] === HTML5::STARTTAG &&
+        $token['name'] === 'table') {
+            /* Parse error. Act as if an end tag token with the tag name "table"
+            had been seen, then, if that token wasn't ignored, reprocess the
+            current token. */
+            $this->inTable(array(
+                'name' => 'table',
+                'type' => HTML5::ENDTAG
+            ));
+
+            return $this->mainPhase($token);
+
+        /* An end tag whose tag name is "table" */
+        } elseif($token['type'] === HTML5::ENDTAG &&
+        $token['name'] === 'table') {
+            /* If the stack of open elements does not have an element in table
+            scope with the same tag name as the token, this is a parse error.
+            Ignore the token. (innerHTML case) */
+            if(!$this->elementInScope($token['name'], true)) {
+                return false;
+
+            /* Otherwise: */
+            } else {
+                /* Generate implied end tags. */
+                $this->generateImpliedEndTags();
+
+                /* Now, if the current node is not a table element, then this
+                is a parse error. */
+                // w/e
+
+                /* Pop elements from this stack until a table element has been
+                popped from the stack. */
+                while(true) {
+                    $current = end($this->stack)->nodeName;
+                    array_pop($this->stack);
+
+                    if($current === 'table') {
+                        break;
+                    }
+                }
+
+                /* Reset the insertion mode appropriately. */
+                $this->resetInsertionMode();
+            }
+
+        /* An end tag whose tag name is one of: "body", "caption", "col",
+        "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
+        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
+        array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td',
+        'tfoot', 'th', 'thead', 'tr'))) {
+            // Parse error. Ignore the token.
+
+        /* Anything else */
+        } else {
+            /* Parse error. Process the token as if the insertion mode was "in
+            body", with the following exception: */
+
+            /* If the current node is a table, tbody, tfoot, thead, or tr
+            element, then, whenever a node would be inserted into the current
+            node, it must instead be inserted into the foster parent element. */
+            if(in_array(end($this->stack)->nodeName,
+            array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
+                /* The foster parent element is the parent element of the last
+                table element in the stack of open elements, if there is a
+                table element and it has such a parent element. If there is no
+                table element in the stack of open elements (innerHTML case),
+                then the foster parent element is the first element in the
+                stack of open elements (the html  element). Otherwise, if there
+                is a table element in the stack of open elements, but the last
+                table element in the stack of open elements has no parent, or
+                its parent node is not an element, then the foster parent
+                element is the element before the last table element in the
+                stack of open elements. */
+                for($n = count($this->stack) - 1; $n >= 0; $n--) {
+                    if($this->stack[$n]->nodeName === 'table') {
+                        $table = $this->stack[$n];
+                        break;
+                    }
+                }
+
+                if(isset($table) && $table->parentNode !== null) {
+                    $this->foster_parent = $table->parentNode;
+
+                } elseif(!isset($table)) {
+                    $this->foster_parent = $this->stack[0];
+
+                } elseif(isset($table) && ($table->parentNode === null ||
+                $table->parentNode->nodeType !== XML_ELEMENT_NODE)) {
+                    $this->foster_parent = $this->stack[$n - 1];
+                }
+            }
+
+            $this->inBody($token);
+        }
+    }
+
+    private function inCaption($token) {
+        /* An end tag whose tag name is "caption" */
+        if($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') {
+            /* If the stack of open elements does not have an element in table
+            scope with the same tag name as the token, this is a parse error.
+            Ignore the token. (innerHTML case) */
+            if(!$this->elementInScope($token['name'], true)) {
+                // Ignore
+
+            /* Otherwise: */
+            } else {
+                /* Generate implied end tags. */
+                $this->generateImpliedEndTags();
+
+                /* Now, if the current node is not a caption element, then this
+                is a parse error. */
+                // w/e
+
+                /* Pop elements from this stack until a caption element has
+                been popped from the stack. */
+                while(true) {
+                    $node = end($this->stack)->nodeName;
+                    array_pop($this->stack);
+
+                    if($node === 'caption') {
+                        break;
+                    }
+                }
+
+                /* Clear the list of active formatting elements up to the last
+                marker. */
+                $this->clearTheActiveFormattingElementsUpToTheLastMarker();
+
+                /* Switch the insertion mode to "in table". */
+                $this->mode = self::IN_TABLE;
+            }
+
+        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+        "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag
+        name is "table" */
+        } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'],
+        array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
+        'thead', 'tr'))) || ($token['type'] === HTML5::ENDTAG &&
+        $token['name'] === 'table')) {
+            /* Parse error. Act as if an end tag with the tag name "caption"
+            had been seen, then, if that token wasn't ignored, reprocess the
+            current token. */
+            $this->inCaption(array(
+                'name' => 'caption',
+                'type' => HTML5::ENDTAG
+            ));
+
+            return $this->inTable($token);
+
+        /* An end tag whose tag name is one of: "body", "col", "colgroup",
+        "html", "tbody", "td", "tfoot", "th", "thead", "tr" */
+        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
+        array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th',
+        'thead', 'tr'))) {
+            // Parse error. Ignore the token.
+
+        /* Anything else */
+        } else {
+            /* Process the token as if the insertion mode was "in body". */
+            $this->inBody($token);
+        }
+    }
+
+    private function inColumnGroup($token) {
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE */
+        if($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Append the character to the current node. */
+            $text = $this->dom->createTextNode($token['data']);
+            end($this->stack)->appendChild($text);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the current node with the data
+            attribute set to the data given in the comment token. */
+            $comment = $this->dom->createComment($token['data']);
+            end($this->stack)->appendChild($comment);
+
+        /* A start tag whose tag name is "col" */
+        } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') {
+            /* Insert a col element for the token. Immediately pop the current
+            node off the stack of open elements. */
+            $this->insertElement($token);
+            array_pop($this->stack);
+
+        /* An end tag whose tag name is "colgroup" */
+        } elseif($token['type'] === HTML5::ENDTAG &&
+        $token['name'] === 'colgroup') {
+            /* If the current node is the root html element, then this is a
+            parse error, ignore the token. (innerHTML case) */
+            if(end($this->stack)->nodeName === 'html') {
+                // Ignore
+
+            /* Otherwise, pop the current node (which will be a colgroup
+            element) from the stack of open elements. Switch the insertion
+            mode to "in table". */
+            } else {
+                array_pop($this->stack);
+                $this->mode = self::IN_TABLE;
+            }
+
+        /* An end tag whose tag name is "col" */
+        } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') {
+            /* Parse error. Ignore the token. */
+
+        /* Anything else */
+        } else {
+            /* Act as if an end tag with the tag name "colgroup" had been seen,
+            and then, if that token wasn't ignored, reprocess the current token. */
+            $this->inColumnGroup(array(
+                'name' => 'colgroup',
+                'type' => HTML5::ENDTAG
+            ));
+
+            return $this->inTable($token);
+        }
+    }
+
+    private function inTableBody($token) {
+        $clear = array('tbody', 'tfoot', 'thead', 'html');
+
+        /* A start tag whose tag name is "tr" */
+        if($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') {
+            /* Clear the stack back to a table body context. */
+            $this->clearStackToTableContext($clear);
+
+            /* Insert a tr element for the token, then switch the insertion
+            mode to "in row". */
+            $this->insertElement($token);
+            $this->mode = self::IN_ROW;
+
+        /* A start tag whose tag name is one of: "th", "td" */
+        } elseif($token['type'] === HTML5::STARTTAG &&
+        ($token['name'] === 'th' ||    $token['name'] === 'td')) {
+            /* Parse error. Act as if a start tag with the tag name "tr" had
+            been seen, then reprocess the current token. */
+            $this->inTableBody(array(
+                'name' => 'tr',
+                'type' => HTML5::STARTTAG,
+                'attr' => array()
+            ));
+
+            return $this->inRow($token);
+
+        /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
+        } elseif($token['type'] === HTML5::ENDTAG &&
+        in_array($token['name'], array('tbody', 'tfoot', 'thead'))) {
+            /* If the stack of open elements does not have an element in table
+            scope with the same tag name as the token, this is a parse error.
+            Ignore the token. */
+            if(!$this->elementInScope($token['name'], true)) {
+                // Ignore
+
+            /* Otherwise: */
+            } else {
+                /* Clear the stack back to a table body context. */
+                $this->clearStackToTableContext($clear);
+
+                /* Pop the current node from the stack of open elements. Switch
+                the insertion mode to "in table". */
+                array_pop($this->stack);
+                $this->mode = self::IN_TABLE;
+            }
+
+        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+        "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */
+        } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'],
+        array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead'))) ||
+        ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table')) {
+            /* If the stack of open elements does not have a tbody, thead, or
+            tfoot element in table scope, this is a parse error. Ignore the
+            token. (innerHTML case) */
+            if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) {
+                // Ignore.
+
+            /* Otherwise: */
+            } else {
+                /* Clear the stack back to a table body context. */
+                $this->clearStackToTableContext($clear);
+
+                /* Act as if an end tag with the same tag name as the current
+                node ("tbody", "tfoot", or "thead") had been seen, then
+                reprocess the current token. */
+                $this->inTableBody(array(
+                    'name' => end($this->stack)->nodeName,
+                    'type' => HTML5::ENDTAG
+                ));
+
+                return $this->mainPhase($token);
+            }
+
+        /* An end tag whose tag name is one of: "body", "caption", "col",
+        "colgroup", "html", "td", "th", "tr" */
+        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
+        array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) {
+            /* Parse error. Ignore the token. */
+
+        /* Anything else */
+        } else {
+            /* Process the token as if the insertion mode was "in table". */
+            $this->inTable($token);
+        }
+    }
+
+    private function inRow($token) {
+        $clear = array('tr', 'html');
+
+        /* A start tag whose tag name is one of: "th", "td" */
+        if($token['type'] === HTML5::STARTTAG &&
+        ($token['name'] === 'th' || $token['name'] === 'td')) {
+            /* Clear the stack back to a table row context. */
+            $this->clearStackToTableContext($clear);
+
+            /* Insert an HTML element for the token, then switch the insertion
+            mode to "in cell". */
+            $this->insertElement($token);
+            $this->mode = self::IN_CELL;
+
+            /* Insert a marker at the end of the list of active formatting
+            elements. */
+            $this->a_formatting[] = self::MARKER;
+
+        /* An end tag whose tag name is "tr" */
+        } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') {
+            /* If the stack of open elements does not have an element in table
+            scope with the same tag name as the token, this is a parse error.
+            Ignore the token. (innerHTML case) */
+            if(!$this->elementInScope($token['name'], true)) {
+                // Ignore.
+
+            /* Otherwise: */
+            } else {
+                /* Clear the stack back to a table row context. */
+                $this->clearStackToTableContext($clear);
+
+                /* Pop the current node (which will be a tr element) from the
+                stack of open elements. Switch the insertion mode to "in table
+                body". */
+                array_pop($this->stack);
+                $this->mode = self::IN_TBODY;
+            }
+
+        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+        "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */
+        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
+        array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) {
+            /* Act as if an end tag with the tag name "tr" had been seen, then,
+            if that token wasn't ignored, reprocess the current token. */
+            $this->inRow(array(
+                'name' => 'tr',
+                'type' => HTML5::ENDTAG
+            ));
+
+            return $this->inCell($token);
+
+        /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */
+        } elseif($token['type'] === HTML5::ENDTAG &&
+        in_array($token['name'], array('tbody', 'tfoot', 'thead'))) {
+            /* If the stack of open elements does not have an element in table
+            scope with the same tag name as the token, this is a parse error.
+            Ignore the token. */
+            if(!$this->elementInScope($token['name'], true)) {
+                // Ignore.
+
+            /* Otherwise: */
+            } else {
+                /* Otherwise, act as if an end tag with the tag name "tr" had
+                been seen, then reprocess the current token. */
+                $this->inRow(array(
+                    'name' => 'tr',
+                    'type' => HTML5::ENDTAG
+                ));
+
+                return $this->inCell($token);
+            }
+
+        /* An end tag whose tag name is one of: "body", "caption", "col",
+        "colgroup", "html", "td", "th" */
+        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
+        array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) {
+            /* Parse error. Ignore the token. */
+
+        /* Anything else */
+        } else {
+            /* Process the token as if the insertion mode was "in table". */
+            $this->inTable($token);
+        }
+    }
+
+    private function inCell($token) {
+        /* An end tag whose tag name is one of: "td", "th" */
+        if($token['type'] === HTML5::ENDTAG &&
+        ($token['name'] === 'td' || $token['name'] === 'th')) {
+            /* If the stack of open elements does not have an element in table
+            scope with the same tag name as that of the token, then this is a
+            parse error and the token must be ignored. */
+            if(!$this->elementInScope($token['name'], true)) {
+                // Ignore.
+
+            /* Otherwise: */
+            } else {
+                /* Generate implied end tags, except for elements with the same
+                tag name as the token. */
+                $this->generateImpliedEndTags(array($token['name']));
+
+                /* Now, if the current node is not an element with the same tag
+                name as the token, then this is a parse error. */
+                // k
+
+                /* Pop elements from this stack until an element with the same
+                tag name as the token has been popped from the stack. */
+                while(true) {
+                    $node = end($this->stack)->nodeName;
+                    array_pop($this->stack);
+
+                    if($node === $token['name']) {
+                        break;
+                    }
+                }
+
+                /* Clear the list of active formatting elements up to the last
+                marker. */
+                $this->clearTheActiveFormattingElementsUpToTheLastMarker();
+
+                /* Switch the insertion mode to "in row". (The current node
+                will be a tr element at this point.) */
+                $this->mode = self::IN_ROW;
+            }
+
+        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+        "tbody", "td", "tfoot", "th", "thead", "tr" */
+        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
+        array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
+        'thead', 'tr'))) {
+            /* If the stack of open elements does not have a td or th element
+            in table scope, then this is a parse error; ignore the token.
+            (innerHTML case) */
+            if(!$this->elementInScope(array('td', 'th'), true)) {
+                // Ignore.
+
+            /* Otherwise, close the cell (see below) and reprocess the current
+            token. */
+            } else {
+                $this->closeCell();
+                return $this->inRow($token);
+            }
+
+        /* A start tag whose tag name is one of: "caption", "col", "colgroup",
+        "tbody", "td", "tfoot", "th", "thead", "tr" */
+        } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'],
+        array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th',
+        'thead', 'tr'))) {
+            /* If the stack of open elements does not have a td or th element
+            in table scope, then this is a parse error; ignore the token.
+            (innerHTML case) */
+            if(!$this->elementInScope(array('td', 'th'), true)) {
+                // Ignore.
+
+            /* Otherwise, close the cell (see below) and reprocess the current
+            token. */
+            } else {
+                $this->closeCell();
+                return $this->inRow($token);
+            }
+
+        /* An end tag whose tag name is one of: "body", "caption", "col",
+        "colgroup", "html" */
+        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
+        array('body', 'caption', 'col', 'colgroup', 'html'))) {
+            /* Parse error. Ignore the token. */
+
+        /* An end tag whose tag name is one of: "table", "tbody", "tfoot",
+        "thead", "tr" */
+        } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'],
+        array('table', 'tbody', 'tfoot', 'thead', 'tr'))) {
+            /* If the stack of open elements does not have an element in table
+            scope with the same tag name as that of the token (which can only
+            happen for "tbody", "tfoot" and "thead", or, in the innerHTML case),
+            then this is a parse error and the token must be ignored. */
+            if(!$this->elementInScope($token['name'], true)) {
+                // Ignore.
+
+            /* Otherwise, close the cell (see below) and reprocess the current
+            token. */
+            } else {
+                $this->closeCell();
+                return $this->inRow($token);
+            }
+
+        /* Anything else */
+        } else {
+            /* Process the token as if the insertion mode was "in body". */
+            $this->inBody($token);
+        }
+    }
+
+    private function inSelect($token) {
+        /* Handle the token as follows: */
+
+        /* A character token */
+        if($token['type'] === HTML5::CHARACTR) {
+            /* Append the token's character to the current node. */
+            $this->insertText($token['data']);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the current node with the data
+            attribute set to the data given in the comment token. */
+            $this->insertComment($token['data']);
+
+        /* A start tag token whose tag name is "option" */
+        } elseif($token['type'] === HTML5::STARTTAG &&
+        $token['name'] === 'option') {
+            /* If the current node is an option element, act as if an end tag
+            with the tag name "option" had been seen. */
+            if(end($this->stack)->nodeName === 'option') {
+                $this->inSelect(array(
+                    'name' => 'option',
+                    'type' => HTML5::ENDTAG
+                ));
+            }
+
+            /* Insert an HTML element for the token. */
+            $this->insertElement($token);
+
+        /* A start tag token whose tag name is "optgroup" */
+        } elseif($token['type'] === HTML5::STARTTAG &&
+        $token['name'] === 'optgroup') {
+            /* If the current node is an option element, act as if an end tag
+            with the tag name "option" had been seen. */
+            if(end($this->stack)->nodeName === 'option') {
+                $this->inSelect(array(
+                    'name' => 'option',
+                    'type' => HTML5::ENDTAG
+                ));
+            }
+
+            /* If the current node is an optgroup element, act as if an end tag
+            with the tag name "optgroup" had been seen. */
+            if(end($this->stack)->nodeName === 'optgroup') {
+                $this->inSelect(array(
+                    'name' => 'optgroup',
+                    'type' => HTML5::ENDTAG
+                ));
+            }
+
+            /* Insert an HTML element for the token. */
+            $this->insertElement($token);
+
+        /* An end tag token whose tag name is "optgroup" */
+        } elseif($token['type'] === HTML5::ENDTAG &&
+        $token['name'] === 'optgroup') {
+            /* First, if the current node is an option element, and the node
+            immediately before it in the stack of open elements is an optgroup
+            element, then act as if an end tag with the tag name "option" had
+            been seen. */
+            $elements_in_stack = count($this->stack);
+
+            if($this->stack[$elements_in_stack - 1]->nodeName === 'option' &&
+            $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup') {
+                $this->inSelect(array(
+                    'name' => 'option',
+                    'type' => HTML5::ENDTAG
+                ));
+            }
+
+            /* If the current node is an optgroup element, then pop that node
+            from the stack of open elements. Otherwise, this is a parse error,
+            ignore the token. */
+            if($this->stack[$elements_in_stack - 1] === 'optgroup') {
+                array_pop($this->stack);
+            }
+
+        /* An end tag token whose tag name is "option" */
+        } elseif($token['type'] === HTML5::ENDTAG &&
+        $token['name'] === 'option') {
+            /* If the current node is an option element, then pop that node
+            from the stack of open elements. Otherwise, this is a parse error,
+            ignore the token. */
+            if(end($this->stack)->nodeName === 'option') {
+                array_pop($this->stack);
+            }
+
+        /* An end tag whose tag name is "select" */
+        } elseif($token['type'] === HTML5::ENDTAG &&
+        $token['name'] === 'select') {
+            /* If the stack of open elements does not have an element in table
+            scope with the same tag name as the token, this is a parse error.
+            Ignore the token. (innerHTML case) */
+            if(!$this->elementInScope($token['name'], true)) {
+                // w/e
+
+            /* Otherwise: */
+            } else {
+                /* Pop elements from the stack of open elements until a select
+                element has been popped from the stack. */
+                while(true) {
+                    $current = end($this->stack)->nodeName;
+                    array_pop($this->stack);
+
+                    if($current === 'select') {
+                        break;
+                    }
+                }
+
+                /* Reset the insertion mode appropriately. */
+                $this->resetInsertionMode();
+            }
+
+        /* A start tag whose tag name is "select" */
+        } elseif($token['name'] === 'select' &&
+        $token['type'] === HTML5::STARTTAG) {
+            /* Parse error. Act as if the token had been an end tag with the
+            tag name "select" instead. */
+            $this->inSelect(array(
+                'name' => 'select',
+                'type' => HTML5::ENDTAG
+            ));
+
+        /* An end tag whose tag name is one of: "caption", "table", "tbody",
+        "tfoot", "thead", "tr", "td", "th" */
+        } elseif(in_array($token['name'], array('caption', 'table', 'tbody',
+        'tfoot', 'thead', 'tr', 'td', 'th')) && $token['type'] === HTML5::ENDTAG) {
+            /* Parse error. */
+            // w/e
+
+            /* If the stack of open elements has an element in table scope with
+            the same tag name as that of the token, then act as if an end tag
+            with the tag name "select" had been seen, and reprocess the token.
+            Otherwise, ignore the token. */
+            if($this->elementInScope($token['name'], true)) {
+                $this->inSelect(array(
+                    'name' => 'select',
+                    'type' => HTML5::ENDTAG
+                ));
+
+                $this->mainPhase($token);
+            }
+
+        /* Anything else */
+        } else {
+            /* Parse error. Ignore the token. */
+        }
+    }
+
+    private function afterBody($token) {
+        /* Handle the token as follows: */
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE */
+        if($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Process the token as it would be processed if the insertion mode
+            was "in body". */
+            $this->inBody($token);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the first element in the stack of open
+            elements (the html element), with the data attribute set to the
+            data given in the comment token. */
+            $comment = $this->dom->createComment($token['data']);
+            $this->stack[0]->appendChild($comment);
+
+        /* An end tag with the tag name "html" */
+        } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') {
+            /* If the parser was originally created in order to handle the
+            setting of an element's innerHTML attribute, this is a parse error;
+            ignore the token. (The element will be an html element in this
+            case.) (innerHTML case) */
+
+            /* Otherwise, switch to the trailing end phase. */
+            $this->phase = self::END_PHASE;
+
+        /* Anything else */
+        } else {
+            /* Parse error. Set the insertion mode to "in body" and reprocess
+            the token. */
+            $this->mode = self::IN_BODY;
+            return $this->inBody($token);
+        }
+    }
+
+    private function inFrameset($token) {
+        /* Handle the token as follows: */
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
+        if($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Append the character to the current node. */
+            $this->insertText($token['data']);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the current node with the data
+            attribute set to the data given in the comment token. */
+            $this->insertComment($token['data']);
+
+        /* A start tag with the tag name "frameset" */
+        } elseif($token['name'] === 'frameset' &&
+        $token['type'] === HTML5::STARTTAG) {
+            $this->insertElement($token);
+
+        /* An end tag with the tag name "frameset" */
+        } elseif($token['name'] === 'frameset' &&
+        $token['type'] === HTML5::ENDTAG) {
+            /* If the current node is the root html element, then this is a
+            parse error; ignore the token. (innerHTML case) */
+            if(end($this->stack)->nodeName === 'html') {
+                // Ignore
+
+            } else {
+                /* Otherwise, pop the current node from the stack of open
+                elements. */
+                array_pop($this->stack);
+
+                /* If the parser was not originally created in order to handle
+                the setting of an element's innerHTML attribute (innerHTML case),
+                and the current node is no longer a frameset element, then change
+                the insertion mode to "after frameset". */
+                $this->mode = self::AFTR_FRAME;
+            }
+
+        /* A start tag with the tag name "frame" */
+        } elseif($token['name'] === 'frame' &&
+        $token['type'] === HTML5::STARTTAG) {
+            /* Insert an HTML element for the token. */
+            $this->insertElement($token);
+
+            /* Immediately pop the current node off the stack of open elements. */
+            array_pop($this->stack);
+
+        /* A start tag with the tag name "noframes" */
+        } elseif($token['name'] === 'noframes' &&
+        $token['type'] === HTML5::STARTTAG) {
+            /* Process the token as if the insertion mode had been "in body". */
+            $this->inBody($token);
+
+        /* Anything else */
+        } else {
+            /* Parse error. Ignore the token. */
+        }
+    }
+
+    private function afterFrameset($token) {
+        /* Handle the token as follows: */
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */
+        if($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Append the character to the current node. */
+            $this->insertText($token['data']);
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the current node with the data
+            attribute set to the data given in the comment token. */
+            $this->insertComment($token['data']);
+
+        /* An end tag with the tag name "html" */
+        } elseif($token['name'] === 'html' &&
+        $token['type'] === HTML5::ENDTAG) {
+            /* Switch to the trailing end phase. */
+            $this->phase = self::END_PHASE;
+
+        /* A start tag with the tag name "noframes" */
+        } elseif($token['name'] === 'noframes' &&
+        $token['type'] === HTML5::STARTTAG) {
+            /* Process the token as if the insertion mode had been "in body". */
+            $this->inBody($token);
+
+        /* Anything else */
+        } else {
+            /* Parse error. Ignore the token. */
+        }
+    }
+
+    private function trailingEndPhase($token) {
+        /* After the main phase, as each token is emitted from the tokenisation
+        stage, it must be processed as described in this section. */
+
+        /* A DOCTYPE token */
+        if($token['type'] === HTML5::DOCTYPE) {
+            // Parse error. Ignore the token.
+
+        /* A comment token */
+        } elseif($token['type'] === HTML5::COMMENT) {
+            /* Append a Comment node to the Document object with the data
+            attribute set to the data given in the comment token. */
+            $comment = $this->dom->createComment($token['data']);
+            $this->dom->appendChild($comment);
+
+        /* A character token that is one of one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE */
+        } elseif($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) {
+            /* Process the token as it would be processed in the main phase. */
+            $this->mainPhase($token);
+
+        /* A character token that is not one of U+0009 CHARACTER TABULATION,
+        U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF),
+        or U+0020 SPACE. Or a start tag token. Or an end tag token. */
+        } elseif(($token['type'] === HTML5::CHARACTR &&
+        preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) ||
+        $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG) {
+            /* Parse error. Switch back to the main phase and reprocess the
+            token. */
+            $this->phase = self::MAIN_PHASE;
+            return $this->mainPhase($token);
+
+        /* An end-of-file token */
+        } elseif($token['type'] === HTML5::EOF) {
+            /* OMG DONE!! */
+        }
+    }
+
+    private function insertElement($token, $append = true, $check = false) {
+        // Proprietary workaround for libxml2's limitations with tag names
+        if ($check) {
+            // Slightly modified HTML5 tag-name modification,
+            // removing anything that's not an ASCII letter, digit, or hyphen
+            $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']);
+            // Remove leading hyphens and numbers
+            $token['name'] = ltrim($token['name'], '-0..9');
+            // In theory, this should ever be needed, but just in case
+            if ($token['name'] === '') $token['name'] = 'span'; // arbitrary generic choice
+        }
+        
+        $el = $this->dom->createElement($token['name']);
+
+        foreach($token['attr'] as $attr) {
+            if(!$el->hasAttribute($attr['name'])) {
+                $el->setAttribute($attr['name'], $attr['value']);
+            }
+        }
+
+        $this->appendToRealParent($el);
+        $this->stack[] = $el;
+
+        return $el;
+    }
+
+    private function insertText($data) {
+        $text = $this->dom->createTextNode($data);
+        $this->appendToRealParent($text);
+    }
+
+    private function insertComment($data) {
+        $comment = $this->dom->createComment($data);
+        $this->appendToRealParent($comment);
+    }
+
+    private function appendToRealParent($node) {
+        if($this->foster_parent === null) {
+            end($this->stack)->appendChild($node);
+
+        } elseif($this->foster_parent !== null) {
+            /* If the foster parent element is the parent element of the
+            last table element in the stack of open elements, then the new
+            node must be inserted immediately before the last table element
+            in the stack of open elements in the foster parent element;
+            otherwise, the new node must be appended to the foster parent
+            element. */
+            for($n = count($this->stack) - 1; $n >= 0; $n--) {
+                if($this->stack[$n]->nodeName === 'table' &&
+                $this->stack[$n]->parentNode !== null) {
+                    $table = $this->stack[$n];
+                    break;
+                }
+            }
+
+            if(isset($table) && $this->foster_parent->isSameNode($table->parentNode))
+                $this->foster_parent->insertBefore($node, $table);
+            else
+                $this->foster_parent->appendChild($node);
+
+            $this->foster_parent = null;
+        }
+    }
+
+    private function elementInScope($el, $table = false) {
+        if(is_array($el)) {
+            foreach($el as $element) {
+                if($this->elementInScope($element, $table)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        $leng = count($this->stack);
+
+        for($n = 0; $n < $leng; $n++) {
+            /* 1. Initialise node to be the current node (the bottommost node of
+            the stack). */
+            $node = $this->stack[$leng - 1 - $n];
+
+            if($node->tagName === $el) {
+                /* 2. If node is the target node, terminate in a match state. */
+                return true;
+
+            } elseif($node->tagName === 'table') {
+                /* 3. Otherwise, if node is a table element, terminate in a failure
+                state. */
+                return false;
+
+            } elseif($table === true && in_array($node->tagName, array('caption', 'td',
+            'th', 'button', 'marquee', 'object'))) {
+                /* 4. Otherwise, if the algorithm is the "has an element in scope"
+                variant (rather than the "has an element in table scope" variant),
+                and node is one of the following, terminate in a failure state. */
+                return false;
+
+            } elseif($node === $node->ownerDocument->documentElement) {
+                /* 5. Otherwise, if node is an html element (root element), terminate
+                in a failure state. (This can only happen if the node is the topmost
+                node of the    stack of open elements, and prevents the next step from
+                being invoked if there are no more elements in the stack.) */
+                return false;
+            }
+
+            /* Otherwise, set node to the previous entry in the stack of open
+            elements and return to step 2. (This will never fail, since the loop
+            will always terminate in the previous step if the top of the stack
+            is reached.) */
+        }
+    }
+
+    private function reconstructActiveFormattingElements() {
+        /* 1. If there are no entries in the list of active formatting elements,
+        then there is nothing to reconstruct; stop this algorithm. */
+        $formatting_elements = count($this->a_formatting);
+
+        if($formatting_elements === 0) {
+            return false;
+        }
+
+        /* 3. Let entry be the last (most recently added) element in the list
+        of active formatting elements. */
+        $entry = end($this->a_formatting);
+
+        /* 2. If the last (most recently added) entry in the list of active
+        formatting elements is a marker, or if it is an element that is in the
+        stack of open elements, then there is nothing to reconstruct; stop this
+        algorithm. */
+        if($entry === self::MARKER || in_array($entry, $this->stack, true)) {
+            return false;
+        }
+
+        for($a = $formatting_elements - 1; $a >= 0; true) {
+            /* 4. If there are no entries before entry in the list of active
+            formatting elements, then jump to step 8. */
+            if($a === 0) {
+                $step_seven = false;
+                break;
+            }
+
+            /* 5. Let entry be the entry one earlier than entry in the list of
+            active formatting elements. */
+            $a--;
+            $entry = $this->a_formatting[$a];
+
+            /* 6. If entry is neither a marker nor an element that is also in
+            thetack of open elements, go to step 4. */
+            if($entry === self::MARKER || in_array($entry, $this->stack, true)) {
+                break;
+            }
+        }
+
+        while(true) {
+            /* 7. Let entry be the element one later than entry in the list of
+            active formatting elements. */
+            if(isset($step_seven) && $step_seven === true) {
+                $a++;
+                $entry = $this->a_formatting[$a];
+            }
+
+            /* 8. Perform a shallow clone of the element entry to obtain clone. */
+            $clone = $entry->cloneNode();
+
+            /* 9. Append clone to the current node and push it onto the stack
+            of open elements  so that it is the new current node. */
+            end($this->stack)->appendChild($clone);
+            $this->stack[] = $clone;
+
+            /* 10. Replace the entry for entry in the list with an entry for
+            clone. */
+            $this->a_formatting[$a] = $clone;
+
+            /* 11. If the entry for clone in the list of active formatting
+            elements is not the last entry in the list, return to step 7. */
+            if(end($this->a_formatting) !== $clone) {
+                $step_seven = true;
+            } else {
+                break;
+            }
+        }
+    }
+
+    private function clearTheActiveFormattingElementsUpToTheLastMarker() {
+        /* When the steps below require the UA to clear the list of active
+        formatting elements up to the last marker, the UA must perform the
+        following steps: */
+
+        while(true) {
+            /* 1. Let entry be the last (most recently added) entry in the list
+            of active formatting elements. */
+            $entry = end($this->a_formatting);
+
+            /* 2. Remove entry from the list of active formatting elements. */
+            array_pop($this->a_formatting);
+
+            /* 3. If entry was a marker, then stop the algorithm at this point.
+            The list has been cleared up to the last marker. */
+            if($entry === self::MARKER) {
+                break;
+            }
+        }
+    }
+
+    private function generateImpliedEndTags($exclude = array()) {
+        /* When the steps below require the UA to generate implied end tags,
+        then, if the current node is a dd element, a dt element, an li element,
+        a p element, a td element, a th  element, or a tr element, the UA must
+        act as if an end tag with the respective tag name had been seen and
+        then generate implied end tags again. */
+        $node = end($this->stack);
+        $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude);
+
+        while(in_array(end($this->stack)->nodeName, $elements)) {
+            array_pop($this->stack);
+        }
+    }
+
+    private function getElementCategory($node) {
+        $name = $node->tagName;
+        if(in_array($name, $this->special))
+            return self::SPECIAL;
+
+        elseif(in_array($name, $this->scoping))
+            return self::SCOPING;
+
+        elseif(in_array($name, $this->formatting))
+            return self::FORMATTING;
+
+        else
+            return self::PHRASING;
+    }
+
+    private function clearStackToTableContext($elements) {
+        /* When the steps above require the UA to clear the stack back to a
+        table context, it means that the UA must, while the current node is not
+        a table element or an html element, pop elements from the stack of open
+        elements. If this causes any elements to be popped from the stack, then
+        this is a parse error. */
+        while(true) {
+            $node = end($this->stack)->nodeName;
+
+            if(in_array($node, $elements)) {
+                break;
+            } else {
+                array_pop($this->stack);
+            }
+        }
+    }
+
+    private function resetInsertionMode() {
+        /* 1. Let last be false. */
+        $last = false;
+        $leng = count($this->stack);
+
+        for($n = $leng - 1; $n >= 0; $n--) {
+            /* 2. Let node be the last node in the stack of open elements. */
+            $node = $this->stack[$n];
+
+            /* 3. If node is the first node in the stack of open elements, then
+            set last to true. If the element whose innerHTML  attribute is being
+            set is neither a td  element nor a th element, then set node to the
+            element whose innerHTML  attribute is being set. (innerHTML  case) */
+            if($this->stack[0]->isSameNode($node)) {
+                $last = true;
+            }
+
+            /* 4. If node is a select element, then switch the insertion mode to
+            "in select" and abort these steps. (innerHTML case) */
+            if($node->nodeName === 'select') {
+                $this->mode = self::IN_SELECT;
+                break;
+
+            /* 5. If node is a td or th element, then switch the insertion mode
+            to "in cell" and abort these steps. */
+            } elseif($node->nodeName === 'td' || $node->nodeName === 'th') {
+                $this->mode = self::IN_CELL;
+                break;
+
+            /* 6. If node is a tr element, then switch the insertion mode to
+            "in    row" and abort these steps. */
+            } elseif($node->nodeName === 'tr') {
+                $this->mode = self::IN_ROW;
+                break;
+
+            /* 7. If node is a tbody, thead, or tfoot element, then switch the
+            insertion mode to "in table body" and abort these steps. */
+            } elseif(in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) {
+                $this->mode = self::IN_TBODY;
+                break;
+
+            /* 8. If node is a caption element, then switch the insertion mode
+            to "in caption" and abort these steps. */
+            } elseif($node->nodeName === 'caption') {
+                $this->mode = self::IN_CAPTION;
+                break;
+
+            /* 9. If node is a colgroup element, then switch the insertion mode
+            to "in column group" and abort these steps. (innerHTML case) */
+            } elseif($node->nodeName === 'colgroup') {
+                $this->mode = self::IN_CGROUP;
+                break;
+
+            /* 10. If node is a table element, then switch the insertion mode
+            to "in table" and abort these steps. */
+            } elseif($node->nodeName === 'table') {
+                $this->mode = self::IN_TABLE;
+                break;
+
+            /* 11. If node is a head element, then switch the insertion mode
+            to "in body" ("in body"! not "in head"!) and abort these steps.
+            (innerHTML case) */
+            } elseif($node->nodeName === 'head') {
+                $this->mode = self::IN_BODY;
+                break;
+
+            /* 12. If node is a body element, then switch the insertion mode to
+            "in body" and abort these steps. */
+            } elseif($node->nodeName === 'body') {
+                $this->mode = self::IN_BODY;
+                break;
+
+            /* 13. If node is a frameset element, then switch the insertion
+            mode to "in frameset" and abort these steps. (innerHTML case) */
+            } elseif($node->nodeName === 'frameset') {
+                $this->mode = self::IN_FRAME;
+                break;
+
+            /* 14. If node is an html element, then: if the head element
+            pointer is null, switch the insertion mode to "before head",
+            otherwise, switch the insertion mode to "after head". In either
+            case, abort these steps. (innerHTML case) */
+            } elseif($node->nodeName === 'html') {
+                $this->mode = ($this->head_pointer === null)
+                    ? self::BEFOR_HEAD
+                    : self::AFTER_HEAD;
+
+                break;
+
+            /* 15. If last is true, then set the insertion mode to "in body"
+            and    abort these steps. (innerHTML case) */
+            } elseif($last) {
+                $this->mode = self::IN_BODY;
+                break;
+            }
+        }
+    }
+
+    private function closeCell() {
+        /* If the stack of open elements has a td or th element in table scope,
+        then act as if an end tag token with that tag name had been seen. */
+        foreach(array('td', 'th') as $cell) {
+            if($this->elementInScope($cell, true)) {
+                $this->inCell(array(
+                    'name' => $cell,
+                    'type' => HTML5::ENDTAG
+                ));
+
+                break;
+            }
+        }
+    }
+
+    public function save() {
+        return $this->dom;
+    }
+}
+?>
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/PercentEncoder.php b/application/libraries/htmlpurifier/HTMLPurifier/PercentEncoder.php
new file mode 100644
index 0000000..a43c44f
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/PercentEncoder.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * Class that handles operations involving percent-encoding in URIs.
+ *
+ * @warning
+ *      Be careful when reusing instances of PercentEncoder. The object
+ *      you use for normalize() SHOULD NOT be used for encode(), or
+ *      vice-versa.
+ */
+class HTMLPurifier_PercentEncoder
+{
+
+    /**
+     * Reserved characters to preserve when using encode().
+     */
+    protected $preserve = array();
+
+    /**
+     * String of characters that should be preserved while using encode().
+     */
+    public function __construct($preserve = false) {
+        // unreserved letters, ought to const-ify
+        for ($i = 48; $i <= 57;  $i++) $this->preserve[$i] = true; // digits
+        for ($i = 65; $i <= 90;  $i++) $this->preserve[$i] = true; // upper-case
+        for ($i = 97; $i <= 122; $i++) $this->preserve[$i] = true; // lower-case
+        $this->preserve[45] = true; // Dash         -
+        $this->preserve[46] = true; // Period       .
+        $this->preserve[95] = true; // Underscore   _
+        $this->preserve[126]= true; // Tilde        ~
+
+        // extra letters not to escape
+        if ($preserve !== false) {
+            for ($i = 0, $c = strlen($preserve); $i < $c; $i++) {
+                $this->preserve[ord($preserve[$i])] = true;
+            }
+        }
+    }
+
+    /**
+     * Our replacement for urlencode, it encodes all non-reserved characters,
+     * as well as any extra characters that were instructed to be preserved.
+     * @note
+     *      Assumes that the string has already been normalized, making any
+     *      and all percent escape sequences valid. Percents will not be
+     *      re-escaped, regardless of their status in $preserve
+     * @param $string String to be encoded
+     * @return Encoded string.
+     */
+    public function encode($string) {
+        $ret = '';
+        for ($i = 0, $c = strlen($string); $i < $c; $i++) {
+            if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])]) ) {
+                $ret .= '%' . sprintf('%02X', $int);
+            } else {
+                $ret .= $string[$i];
+            }
+        }
+        return $ret;
+    }
+
+    /**
+     * Fix up percent-encoding by decoding unreserved characters and normalizing.
+     * @warning This function is affected by $preserve, even though the
+     *          usual desired behavior is for this not to preserve those
+     *          characters. Be careful when reusing instances of PercentEncoder!
+     * @param $string String to normalize
+     */
+    public function normalize($string) {
+        if ($string == '') return '';
+        $parts = explode('%', $string);
+        $ret = array_shift($parts);
+        foreach ($parts as $part) {
+            $length = strlen($part);
+            if ($length < 2) {
+                $ret .= '%25' . $part;
+                continue;
+            }
+            $encoding = substr($part, 0, 2);
+            $text     = substr($part, 2);
+            if (!ctype_xdigit($encoding)) {
+                $ret .= '%25' . $part;
+                continue;
+            }
+            $int = hexdec($encoding);
+            if (isset($this->preserve[$int])) {
+                $ret .= chr($int) . $text;
+                continue;
+            }
+            $encoding = strtoupper($encoding);
+            $ret .= '%' . $encoding . $text;
+        }
+        return $ret;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Printer.php b/application/libraries/htmlpurifier/HTMLPurifier/Printer.php
new file mode 100644
index 0000000..e7eb82e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Printer.php
@@ -0,0 +1,176 @@
+<?php
+
+// OUT OF DATE, NEEDS UPDATING!
+// USE XMLWRITER!
+
+class HTMLPurifier_Printer
+{
+
+    /**
+     * Instance of HTMLPurifier_Generator for HTML generation convenience funcs
+     */
+    protected $generator;
+
+    /**
+     * Instance of HTMLPurifier_Config, for easy access
+     */
+    protected $config;
+
+    /**
+     * Initialize $generator.
+     */
+    public function __construct() {
+    }
+
+    /**
+     * Give generator necessary configuration if possible
+     */
+    public function prepareGenerator($config) {
+        $all = $config->getAll();
+        $context = new HTMLPurifier_Context();
+        $this->generator = new HTMLPurifier_Generator($config, $context);
+    }
+
+    /**
+     * Main function that renders object or aspect of that object
+     * @note Parameters vary depending on printer
+     */
+    // function render() {}
+
+    /**
+     * Returns a start tag
+     * @param $tag Tag name
+     * @param $attr Attribute array
+     */
+    protected function start($tag, $attr = array()) {
+        return $this->generator->generateFromToken(
+                    new HTMLPurifier_Token_Start($tag, $attr ? $attr : array())
+               );
+    }
+
+    /**
+     * Returns an end teg
+     * @param $tag Tag name
+     */
+    protected function end($tag) {
+        return $this->generator->generateFromToken(
+                    new HTMLPurifier_Token_End($tag)
+               );
+    }
+
+    /**
+     * Prints a complete element with content inside
+     * @param $tag Tag name
+     * @param $contents Element contents
+     * @param $attr Tag attributes
+     * @param $escape Bool whether or not to escape contents
+     */
+    protected function element($tag, $contents, $attr = array(), $escape = true) {
+        return $this->start($tag, $attr) .
+               ($escape ? $this->escape($contents) : $contents) .
+               $this->end($tag);
+    }
+
+    protected function elementEmpty($tag, $attr = array()) {
+        return $this->generator->generateFromToken(
+            new HTMLPurifier_Token_Empty($tag, $attr)
+        );
+    }
+
+    protected function text($text) {
+        return $this->generator->generateFromToken(
+            new HTMLPurifier_Token_Text($text)
+        );
+    }
+
+    /**
+     * Prints a simple key/value row in a table.
+     * @param $name Key
+     * @param $value Value
+     */
+    protected function row($name, $value) {
+        if (is_bool($value)) $value = $value ? 'On' : 'Off';
+        return
+            $this->start('tr') . "\n" .
+                $this->element('th', $name) . "\n" .
+                $this->element('td', $value) . "\n" .
+            $this->end('tr')
+        ;
+    }
+
+    /**
+     * Escapes a string for HTML output.
+     * @param $string String to escape
+     */
+    protected function escape($string) {
+        $string = HTMLPurifier_Encoder::cleanUTF8($string);
+        $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
+        return $string;
+    }
+
+    /**
+     * Takes a list of strings and turns them into a single list
+     * @param $array List of strings
+     * @param $polite Bool whether or not to add an end before the last
+     */
+    protected function listify($array, $polite = false) {
+        if (empty($array)) return 'None';
+        $ret = '';
+        $i = count($array);
+        foreach ($array as $value) {
+            $i--;
+            $ret .= $value;
+            if ($i > 0 && !($polite && $i == 1)) $ret .= ', ';
+            if ($polite && $i == 1) $ret .= 'and ';
+        }
+        return $ret;
+    }
+
+    /**
+     * Retrieves the class of an object without prefixes, as well as metadata
+     * @param $obj Object to determine class of
+     * @param $prefix Further prefix to remove
+     */
+    protected function getClass($obj, $sec_prefix = '') {
+        static $five = null;
+        if ($five === null) $five = version_compare(PHP_VERSION, '5', '>=');
+        $prefix = 'HTMLPurifier_' . $sec_prefix;
+        if (!$five) $prefix = strtolower($prefix);
+        $class = str_replace($prefix, '', get_class($obj));
+        $lclass = strtolower($class);
+        $class .= '(';
+        switch ($lclass) {
+            case 'enum':
+                $values = array();
+                foreach ($obj->valid_values as $value => $bool) {
+                    $values[] = $value;
+                }
+                $class .= implode(', ', $values);
+                break;
+            case 'css_composite':
+                $values = array();
+                foreach ($obj->defs as $def) {
+                    $values[] = $this->getClass($def, $sec_prefix);
+                }
+                $class .= implode(', ', $values);
+                break;
+            case 'css_multiple':
+                $class .= $this->getClass($obj->single, $sec_prefix) . ', ';
+                $class .= $obj->max;
+                break;
+            case 'css_denyelementdecorator':
+                $class .= $this->getClass($obj->def, $sec_prefix) . ', ';
+                $class .= $obj->element;
+                break;
+            case 'css_importantdecorator':
+                $class .= $this->getClass($obj->def, $sec_prefix);
+                if ($obj->allow) $class .= ', !important';
+                break;
+        }
+        $class .= ')';
+        return $class;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Printer/CSSDefinition.php b/application/libraries/htmlpurifier/HTMLPurifier/Printer/CSSDefinition.php
new file mode 100644
index 0000000..81f9865
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Printer/CSSDefinition.php
@@ -0,0 +1,38 @@
+<?php
+
+class HTMLPurifier_Printer_CSSDefinition extends HTMLPurifier_Printer
+{
+
+    protected $def;
+
+    public function render($config) {
+        $this->def = $config->getCSSDefinition();
+        $ret = '';
+
+        $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer'));
+        $ret .= $this->start('table');
+
+        $ret .= $this->element('caption', 'Properties ($info)');
+
+        $ret .= $this->start('thead');
+        $ret .= $this->start('tr');
+        $ret .= $this->element('th', 'Property', array('class' => 'heavy'));
+        $ret .= $this->element('th', 'Definition', array('class' => 'heavy', 'style' => 'width:auto;'));
+        $ret .= $this->end('tr');
+        $ret .= $this->end('thead');
+
+        ksort($this->def->info);
+        foreach ($this->def->info as $property => $obj) {
+            $name = $this->getClass($obj, 'AttrDef_');
+            $ret .= $this->row($property, $name);
+        }
+
+        $ret .= $this->end('table');
+        $ret .= $this->end('div');
+
+        return $ret;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.css b/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.css
new file mode 100644
index 0000000..3ff1a88
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.css
@@ -0,0 +1,10 @@
+
+.hp-config {}
+
+.hp-config tbody th {text-align:right; padding-right:0.5em;}
+.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;}
+.hp-config .namespace th {text-align:center;}
+.hp-config .verbose {display:none;}
+.hp-config .controls {text-align:center;}
+
+/* vim: et sw=4 sts=4 */
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.js b/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.js
new file mode 100644
index 0000000..cba00c9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.js
@@ -0,0 +1,5 @@
+function toggleWriteability(id_of_patient, checked) {
+    document.getElementById(id_of_patient).disabled = checked;
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php b/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php
new file mode 100644
index 0000000..02aa656
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Printer/ConfigForm.php
@@ -0,0 +1,368 @@
+<?php
+
+/**
+ * @todo Rewrite to use Interchange objects
+ */
+class HTMLPurifier_Printer_ConfigForm extends HTMLPurifier_Printer
+{
+
+    /**
+     * Printers for specific fields
+     */
+    protected $fields = array();
+
+    /**
+     * Documentation URL, can have fragment tagged on end
+     */
+    protected $docURL;
+
+    /**
+     * Name of form element to stuff config in
+     */
+    protected $name;
+
+    /**
+     * Whether or not to compress directive names, clipping them off
+     * after a certain amount of letters. False to disable or integer letters
+     * before clipping.
+     */
+    protected $compress = false;
+
+    /**
+     * @param $name Form element name for directives to be stuffed into
+     * @param $doc_url String documentation URL, will have fragment tagged on
+     * @param $compress Integer max length before compressing a directive name, set to false to turn off
+     */
+    public function __construct(
+        $name, $doc_url = null, $compress = false
+    ) {
+        parent::__construct();
+        $this->docURL = $doc_url;
+        $this->name   = $name;
+        $this->compress = $compress;
+        // initialize sub-printers
+        $this->fields[0]    = new HTMLPurifier_Printer_ConfigForm_default();
+        $this->fields[HTMLPurifier_VarParser::BOOL]       = new HTMLPurifier_Printer_ConfigForm_bool();
+    }
+
+    /**
+     * Sets default column and row size for textareas in sub-printers
+     * @param $cols Integer columns of textarea, null to use default
+     * @param $rows Integer rows of textarea, null to use default
+     */
+    public function setTextareaDimensions($cols = null, $rows = null) {
+        if ($cols) $this->fields['default']->cols = $cols;
+        if ($rows) $this->fields['default']->rows = $rows;
+    }
+
+    /**
+     * Retrieves styling, in case it is not accessible by webserver
+     */
+    public static function getCSS() {
+        return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css');
+    }
+
+    /**
+     * Retrieves JavaScript, in case it is not accessible by webserver
+     */
+    public static function getJavaScript() {
+        return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js');
+    }
+
+    /**
+     * Returns HTML output for a configuration form
+     * @param $config Configuration object of current form state, or an array
+     *        where [0] has an HTML namespace and [1] is being rendered.
+     * @param $allowed Optional namespace(s) and directives to restrict form to.
+     */
+    public function render($config, $allowed = true, $render_controls = true) {
+        if (is_array($config) && isset($config[0])) {
+            $gen_config = $config[0];
+            $config = $config[1];
+        } else {
+            $gen_config = $config;
+        }
+
+        $this->config = $config;
+        $this->genConfig = $gen_config;
+        $this->prepareGenerator($gen_config);
+
+        $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def);
+        $all = array();
+        foreach ($allowed as $key) {
+            list($ns, $directive) = $key;
+            $all[$ns][$directive] = $config->get($ns .'.'. $directive);
+        }
+
+        $ret = '';
+        $ret .= $this->start('table', array('class' => 'hp-config'));
+        $ret .= $this->start('thead');
+        $ret .= $this->start('tr');
+            $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive'));
+            $ret .= $this->element('th', 'Value', array('class' => 'hp-value'));
+        $ret .= $this->end('tr');
+        $ret .= $this->end('thead');
+        foreach ($all as $ns => $directives) {
+            $ret .= $this->renderNamespace($ns, $directives);
+        }
+        if ($render_controls) {
+             $ret .= $this->start('tbody');
+             $ret .= $this->start('tr');
+                 $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls'));
+                     $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit'));
+                     $ret .= '[<a href="?">Reset</a>]';
+                 $ret .= $this->end('td');
+             $ret .= $this->end('tr');
+             $ret .= $this->end('tbody');
+        }
+        $ret .= $this->end('table');
+        return $ret;
+    }
+
+    /**
+     * Renders a single namespace
+     * @param $ns String namespace name
+     * @param $directive Associative array of directives to values
+     */
+    protected function renderNamespace($ns, $directives) {
+        $ret = '';
+        $ret .= $this->start('tbody', array('class' => 'namespace'));
+        $ret .= $this->start('tr');
+            $ret .= $this->element('th', $ns, array('colspan' => 2));
+        $ret .= $this->end('tr');
+        $ret .= $this->end('tbody');
+        $ret .= $this->start('tbody');
+        foreach ($directives as $directive => $value) {
+            $ret .= $this->start('tr');
+            $ret .= $this->start('th');
+            if ($this->docURL) {
+                $url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL);
+                $ret .= $this->start('a', array('href' => $url));
+            }
+                $attr = array('for' => "{$this->name}:$ns.$directive");
+
+                // crop directive name if it's too long
+                if (!$this->compress || (strlen($directive) < $this->compress)) {
+                    $directive_disp = $directive;
+                } else {
+                    $directive_disp = substr($directive, 0, $this->compress - 2) . '...';
+                    $attr['title'] = $directive;
+                }
+
+                $ret .= $this->element(
+                    'label',
+                    $directive_disp,
+                    // component printers must create an element with this id
+                    $attr
+                );
+            if ($this->docURL) $ret .= $this->end('a');
+            $ret .= $this->end('th');
+
+            $ret .= $this->start('td');
+                $def = $this->config->def->info["$ns.$directive"];
+                if (is_int($def)) {
+                    $allow_null = $def < 0;
+                    $type = abs($def);
+                } else {
+                    $type = $def->type;
+                    $allow_null = isset($def->allow_null);
+                }
+                if (!isset($this->fields[$type])) $type = 0; // default
+                $type_obj = $this->fields[$type];
+                if ($allow_null) {
+                    $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj);
+                }
+                $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config));
+            $ret .= $this->end('td');
+            $ret .= $this->end('tr');
+        }
+        $ret .= $this->end('tbody');
+        return $ret;
+    }
+
+}
+
+/**
+ * Printer decorator for directives that accept null
+ */
+class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer {
+    /**
+     * Printer being decorated
+     */
+    protected $obj;
+    /**
+     * @param $obj Printer to decorate
+     */
+    public function __construct($obj) {
+        parent::__construct();
+        $this->obj = $obj;
+    }
+    public function render($ns, $directive, $value, $name, $config) {
+        if (is_array($config) && isset($config[0])) {
+            $gen_config = $config[0];
+            $config = $config[1];
+        } else {
+            $gen_config = $config;
+        }
+        $this->prepareGenerator($gen_config);
+
+        $ret = '';
+        $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive"));
+        $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
+        $ret .= $this->text(' Null/Disabled');
+        $ret .= $this->end('label');
+        $attr = array(
+            'type' => 'checkbox',
+            'value' => '1',
+            'class' => 'null-toggle',
+            'name' => "$name"."[Null_$ns.$directive]",
+            'id' => "$name:Null_$ns.$directive",
+            'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!!
+        );
+        if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) {
+            // modify inline javascript slightly
+            $attr['onclick'] = "toggleWriteability('$name:Yes_$ns.$directive',checked);toggleWriteability('$name:No_$ns.$directive',checked)";
+        }
+        if ($value === null) $attr['checked'] = 'checked';
+        $ret .= $this->elementEmpty('input', $attr);
+        $ret .= $this->text(' or ');
+        $ret .= $this->elementEmpty('br');
+        $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config));
+        return $ret;
+    }
+}
+
+/**
+ * Swiss-army knife configuration form field printer
+ */
+class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer {
+    public $cols = 18;
+    public $rows = 5;
+    public function render($ns, $directive, $value, $name, $config) {
+        if (is_array($config) && isset($config[0])) {
+            $gen_config = $config[0];
+            $config = $config[1];
+        } else {
+            $gen_config = $config;
+        }
+        $this->prepareGenerator($gen_config);
+        // this should probably be split up a little
+        $ret = '';
+        $def = $config->def->info["$ns.$directive"];
+        if (is_int($def)) {
+            $type = abs($def);
+        } else {
+            $type = $def->type;
+        }
+        if (is_array($value)) {
+            switch ($type) {
+                case HTMLPurifier_VarParser::LOOKUP:
+                    $array = $value;
+                    $value = array();
+                    foreach ($array as $val => $b) {
+                        $value[] = $val;
+                    }
+                case HTMLPurifier_VarParser::ALIST:
+                    $value = implode(PHP_EOL, $value);
+                    break;
+                case HTMLPurifier_VarParser::HASH:
+                    $nvalue = '';
+                    foreach ($value as $i => $v) {
+                        $nvalue .= "$i:$v" . PHP_EOL;
+                    }
+                    $value = $nvalue;
+                    break;
+                default:
+                    $value = '';
+            }
+        }
+        if ($type === HTMLPurifier_VarParser::MIXED) {
+            return 'Not supported';
+            $value = serialize($value);
+        }
+        $attr = array(
+            'name' => "$name"."[$ns.$directive]",
+            'id' => "$name:$ns.$directive"
+        );
+        if ($value === null) $attr['disabled'] = 'disabled';
+        if (isset($def->allowed)) {
+            $ret .= $this->start('select', $attr);
+            foreach ($def->allowed as $val => $b) {
+                $attr = array();
+                if ($value == $val) $attr['selected'] = 'selected';
+                $ret .= $this->element('option', $val, $attr);
+            }
+            $ret .= $this->end('select');
+        } elseif (
+            $type === HTMLPurifier_VarParser::TEXT ||
+            $type === HTMLPurifier_VarParser::ITEXT ||
+            $type === HTMLPurifier_VarParser::ALIST ||
+            $type === HTMLPurifier_VarParser::HASH ||
+            $type === HTMLPurifier_VarParser::LOOKUP
+        ) {
+            $attr['cols'] = $this->cols;
+            $attr['rows'] = $this->rows;
+            $ret .= $this->start('textarea', $attr);
+            $ret .= $this->text($value);
+            $ret .= $this->end('textarea');
+        } else {
+            $attr['value'] = $value;
+            $attr['type'] = 'text';
+            $ret .= $this->elementEmpty('input', $attr);
+        }
+        return $ret;
+    }
+}
+
+/**
+ * Bool form field printer
+ */
+class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer {
+    public function render($ns, $directive, $value, $name, $config) {
+        if (is_array($config) && isset($config[0])) {
+            $gen_config = $config[0];
+            $config = $config[1];
+        } else {
+            $gen_config = $config;
+        }
+        $this->prepareGenerator($gen_config);
+        $ret = '';
+        $ret .= $this->start('div', array('id' => "$name:$ns.$directive"));
+
+        $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive"));
+        $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
+        $ret .= $this->text(' Yes');
+        $ret .= $this->end('label');
+
+        $attr = array(
+            'type' => 'radio',
+            'name' => "$name"."[$ns.$directive]",
+            'id' => "$name:Yes_$ns.$directive",
+            'value' => '1'
+        );
+        if ($value === true) $attr['checked'] = 'checked';
+        if ($value === null) $attr['disabled'] = 'disabled';
+        $ret .= $this->elementEmpty('input', $attr);
+
+        $ret .= $this->start('label', array('for' => "$name:No_$ns.$directive"));
+        $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose'));
+        $ret .= $this->text(' No');
+        $ret .= $this->end('label');
+
+        $attr = array(
+            'type' => 'radio',
+            'name' => "$name"."[$ns.$directive]",
+            'id' => "$name:No_$ns.$directive",
+            'value' => '0'
+        );
+        if ($value === false) $attr['checked'] = 'checked';
+        if ($value === null) $attr['disabled'] = 'disabled';
+        $ret .= $this->elementEmpty('input', $attr);
+
+        $ret .= $this->end('div');
+
+        return $ret;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Printer/HTMLDefinition.php b/application/libraries/htmlpurifier/HTMLPurifier/Printer/HTMLDefinition.php
new file mode 100644
index 0000000..8a8f126
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Printer/HTMLDefinition.php
@@ -0,0 +1,272 @@
+<?php
+
+class HTMLPurifier_Printer_HTMLDefinition extends HTMLPurifier_Printer
+{
+
+    /**
+     * Instance of HTMLPurifier_HTMLDefinition, for easy access
+     */
+    protected $def;
+
+    public function render($config) {
+        $ret = '';
+        $this->config =& $config;
+
+        $this->def = $config->getHTMLDefinition();
+
+        $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer'));
+
+        $ret .= $this->renderDoctype();
+        $ret .= $this->renderEnvironment();
+        $ret .= $this->renderContentSets();
+        $ret .= $this->renderInfo();
+
+        $ret .= $this->end('div');
+
+        return $ret;
+    }
+
+    /**
+     * Renders the Doctype table
+     */
+    protected function renderDoctype() {
+        $doctype = $this->def->doctype;
+        $ret = '';
+        $ret .= $this->start('table');
+        $ret .= $this->element('caption', 'Doctype');
+        $ret .= $this->row('Name', $doctype->name);
+        $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No');
+        $ret .= $this->row('Default Modules', implode($doctype->modules, ', '));
+        $ret .= $this->row('Default Tidy Modules', implode($doctype->tidyModules, ', '));
+        $ret .= $this->end('table');
+        return $ret;
+    }
+
+
+    /**
+     * Renders environment table, which is miscellaneous info
+     */
+    protected function renderEnvironment() {
+        $def = $this->def;
+
+        $ret = '';
+
+        $ret .= $this->start('table');
+        $ret .= $this->element('caption', 'Environment');
+
+        $ret .= $this->row('Parent of fragment', $def->info_parent);
+        $ret .= $this->renderChildren($def->info_parent_def->child);
+        $ret .= $this->row('Block wrap name', $def->info_block_wrapper);
+
+        $ret .= $this->start('tr');
+            $ret .= $this->element('th', 'Global attributes');
+            $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr),0,0);
+        $ret .= $this->end('tr');
+
+        $ret .= $this->start('tr');
+            $ret .= $this->element('th', 'Tag transforms');
+            $list = array();
+            foreach ($def->info_tag_transform as $old => $new) {
+                $new = $this->getClass($new, 'TagTransform_');
+                $list[] = "<$old> with $new";
+            }
+            $ret .= $this->element('td', $this->listify($list));
+        $ret .= $this->end('tr');
+
+        $ret .= $this->start('tr');
+            $ret .= $this->element('th', 'Pre-AttrTransform');
+            $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre));
+        $ret .= $this->end('tr');
+
+        $ret .= $this->start('tr');
+            $ret .= $this->element('th', 'Post-AttrTransform');
+            $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post));
+        $ret .= $this->end('tr');
+
+        $ret .= $this->end('table');
+        return $ret;
+    }
+
+    /**
+     * Renders the Content Sets table
+     */
+    protected function renderContentSets() {
+        $ret = '';
+        $ret .= $this->start('table');
+        $ret .= $this->element('caption', 'Content Sets');
+        foreach ($this->def->info_content_sets as $name => $lookup) {
+            $ret .= $this->heavyHeader($name);
+            $ret .= $this->start('tr');
+            $ret .= $this->element('td', $this->listifyTagLookup($lookup));
+            $ret .= $this->end('tr');
+        }
+        $ret .= $this->end('table');
+        return $ret;
+    }
+
+    /**
+     * Renders the Elements ($info) table
+     */
+    protected function renderInfo() {
+        $ret = '';
+        $ret .= $this->start('table');
+        $ret .= $this->element('caption', 'Elements ($info)');
+        ksort($this->def->info);
+        $ret .= $this->heavyHeader('Allowed tags', 2);
+        $ret .= $this->start('tr');
+        $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2));
+        $ret .= $this->end('tr');
+        foreach ($this->def->info as $name => $def) {
+            $ret .= $this->start('tr');
+                $ret .= $this->element('th', "<$name>", array('class'=>'heavy', 'colspan' => 2));
+            $ret .= $this->end('tr');
+            $ret .= $this->start('tr');
+                $ret .= $this->element('th', 'Inline content');
+                $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No');
+            $ret .= $this->end('tr');
+            if (!empty($def->excludes)) {
+                $ret .= $this->start('tr');
+                    $ret .= $this->element('th', 'Excludes');
+                    $ret .= $this->element('td', $this->listifyTagLookup($def->excludes));
+                $ret .= $this->end('tr');
+            }
+            if (!empty($def->attr_transform_pre)) {
+                $ret .= $this->start('tr');
+                    $ret .= $this->element('th', 'Pre-AttrTransform');
+                    $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre));
+                $ret .= $this->end('tr');
+            }
+            if (!empty($def->attr_transform_post)) {
+                $ret .= $this->start('tr');
+                    $ret .= $this->element('th', 'Post-AttrTransform');
+                    $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post));
+                $ret .= $this->end('tr');
+            }
+            if (!empty($def->auto_close)) {
+                $ret .= $this->start('tr');
+                    $ret .= $this->element('th', 'Auto closed by');
+                    $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close));
+                $ret .= $this->end('tr');
+            }
+            $ret .= $this->start('tr');
+                $ret .= $this->element('th', 'Allowed attributes');
+                $ret .= $this->element('td',$this->listifyAttr($def->attr), array(), 0);
+            $ret .= $this->end('tr');
+
+            if (!empty($def->required_attr)) {
+                $ret .= $this->row('Required attributes', $this->listify($def->required_attr));
+            }
+
+            $ret .= $this->renderChildren($def->child);
+        }
+        $ret .= $this->end('table');
+        return $ret;
+    }
+
+    /**
+     * Renders a row describing the allowed children of an element
+     * @param $def HTMLPurifier_ChildDef of pertinent element
+     */
+    protected function renderChildren($def) {
+        $context = new HTMLPurifier_Context();
+        $ret = '';
+        $ret .= $this->start('tr');
+            $elements = array();
+            $attr = array();
+            if (isset($def->elements)) {
+                if ($def->type == 'strictblockquote') {
+                    $def->validateChildren(array(), $this->config, $context);
+                }
+                $elements = $def->elements;
+            }
+            if ($def->type == 'chameleon') {
+                $attr['rowspan'] = 2;
+            } elseif ($def->type == 'empty') {
+                $elements = array();
+            } elseif ($def->type == 'table') {
+                $elements = array_flip(array('col', 'caption', 'colgroup', 'thead',
+                    'tfoot', 'tbody', 'tr'));
+            }
+            $ret .= $this->element('th', 'Allowed children', $attr);
+
+            if ($def->type == 'chameleon') {
+
+                $ret .= $this->element('td',
+                    '<em>Block</em>: ' .
+                    $this->escape($this->listifyTagLookup($def->block->elements)),0,0);
+                $ret .= $this->end('tr');
+                $ret .= $this->start('tr');
+                $ret .= $this->element('td',
+                    '<em>Inline</em>: ' .
+                    $this->escape($this->listifyTagLookup($def->inline->elements)),0,0);
+
+            } elseif ($def->type == 'custom') {
+
+                $ret .= $this->element('td', '<em>'.ucfirst($def->type).'</em>: ' .
+                    $def->dtd_regex);
+
+            } else {
+                $ret .= $this->element('td',
+                    '<em>'.ucfirst($def->type).'</em>: ' .
+                    $this->escape($this->listifyTagLookup($elements)),0,0);
+            }
+        $ret .= $this->end('tr');
+        return $ret;
+    }
+
+    /**
+     * Listifies a tag lookup table.
+     * @param $array Tag lookup array in form of array('tagname' => true)
+     */
+    protected function listifyTagLookup($array) {
+        ksort($array);
+        $list = array();
+        foreach ($array as $name => $discard) {
+            if ($name !== '#PCDATA' && !isset($this->def->info[$name])) continue;
+            $list[] = $name;
+        }
+        return $this->listify($list);
+    }
+
+    /**
+     * Listifies a list of objects by retrieving class names and internal state
+     * @param $array List of objects
+     * @todo Also add information about internal state
+     */
+    protected function listifyObjectList($array) {
+        ksort($array);
+        $list = array();
+        foreach ($array as $discard => $obj) {
+            $list[] = $this->getClass($obj, 'AttrTransform_');
+        }
+        return $this->listify($list);
+    }
+
+    /**
+     * Listifies a hash of attributes to AttrDef classes
+     * @param $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef)
+     */
+    protected function listifyAttr($array) {
+        ksort($array);
+        $list = array();
+        foreach ($array as $name => $obj) {
+            if ($obj === false) continue;
+            $list[] = "$name = <i>" . $this->getClass($obj, 'AttrDef_') . '</i>';
+        }
+        return $this->listify($list);
+    }
+
+    /**
+     * Creates a heavy header row
+     */
+    protected function heavyHeader($text, $num = 1) {
+        $ret = '';
+        $ret .= $this->start('tr');
+        $ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy'));
+        $ret .= $this->end('tr');
+        return $ret;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/PropertyList.php b/application/libraries/htmlpurifier/HTMLPurifier/PropertyList.php
new file mode 100644
index 0000000..2b99fb7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/PropertyList.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * Generic property list implementation
+ */
+class HTMLPurifier_PropertyList
+{
+    /**
+     * Internal data-structure for properties
+     */
+    protected $data = array();
+
+    /**
+     * Parent plist
+     */
+    protected $parent;
+
+    protected $cache;
+
+    public function __construct($parent = null) {
+        $this->parent = $parent;
+    }
+
+    /**
+     * Recursively retrieves the value for a key
+     */
+    public function get($name) {
+        if ($this->has($name)) return $this->data[$name];
+        // possible performance bottleneck, convert to iterative if necessary
+        if ($this->parent) return $this->parent->get($name);
+        throw new HTMLPurifier_Exception("Key '$name' not found");
+    }
+
+    /**
+     * Sets the value of a key, for this plist
+     */
+    public function set($name, $value) {
+        $this->data[$name] = $value;
+    }
+
+    /**
+     * Returns true if a given key exists
+     */
+    public function has($name) {
+        return array_key_exists($name, $this->data);
+    }
+
+    /**
+     * Resets a value to the value of it's parent, usually the default. If
+     * no value is specified, the entire plist is reset.
+     */
+    public function reset($name = null) {
+        if ($name == null) $this->data = array();
+        else unset($this->data[$name]);
+    }
+
+    /**
+     * Squashes this property list and all of its property lists into a single
+     * array, and returns the array. This value is cached by default.
+     * @param $force If true, ignores the cache and regenerates the array.
+     */
+    public function squash($force = false) {
+        if ($this->cache !== null && !$force) return $this->cache;
+        if ($this->parent) {
+            return $this->cache = array_merge($this->parent->squash($force), $this->data);
+        } else {
+            return $this->cache = $this->data;
+        }
+    }
+
+    /**
+     * Returns the parent plist.
+     */
+    public function getParent() {
+        return $this->parent;
+    }
+
+    /**
+     * Sets the parent plist.
+     */
+    public function setParent($plist) {
+        $this->parent = $plist;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/PropertyListIterator.php b/application/libraries/htmlpurifier/HTMLPurifier/PropertyListIterator.php
new file mode 100644
index 0000000..8f25044
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/PropertyListIterator.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Property list iterator. Do not instantiate this class directly.
+ */
+class HTMLPurifier_PropertyListIterator extends FilterIterator
+{
+
+    protected $l;
+    protected $filter;
+
+    /**
+     * @param $data Array of data to iterate over
+     * @param $filter Optional prefix to only allow values of
+     */
+    public function __construct(Iterator $iterator, $filter = null) {
+        parent::__construct($iterator);
+        $this->l = strlen($filter);
+        $this->filter = $filter;
+    }
+
+    public function accept() {
+        $key = $this->getInnerIterator()->key();
+        if( strncmp($key, $this->filter, $this->l) !== 0 ) {
+            return false;
+        }
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Strategy.php b/application/libraries/htmlpurifier/HTMLPurifier/Strategy.php
new file mode 100644
index 0000000..2462865
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Strategy.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Supertype for classes that define a strategy for modifying/purifying tokens.
+ *
+ * While HTMLPurifier's core purpose is fixing HTML into something proper,
+ * strategies provide plug points for extra configuration or even extra
+ * features, such as custom tags, custom parsing of text, etc.
+ */
+
+
+abstract class HTMLPurifier_Strategy
+{
+
+    /**
+     * Executes the strategy on the tokens.
+     *
+     * @param $tokens Array of HTMLPurifier_Token objects to be operated on.
+     * @param $config Configuration options
+     * @returns Processed array of token objects.
+     */
+    abstract public function execute($tokens, $config, $context);
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Strategy/Composite.php b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/Composite.php
new file mode 100644
index 0000000..92aefd3
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/Composite.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Composite strategy that runs multiple strategies on tokens.
+ */
+abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy
+{
+
+    /**
+     * List of strategies to run tokens through.
+     */
+    protected $strategies = array();
+
+    public function execute($tokens, $config, $context) {
+        foreach ($this->strategies as $strategy) {
+            $tokens = $strategy->execute($tokens, $config, $context);
+        }
+        return $tokens;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Strategy/Core.php b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/Core.php
new file mode 100644
index 0000000..d90e158
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/Core.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * Core strategy composed of the big four strategies.
+ */
+class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite
+{
+
+    public function __construct() {
+        $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements();
+        $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed();
+        $this->strategies[] = new HTMLPurifier_Strategy_FixNesting();
+        $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes();
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Strategy/FixNesting.php b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/FixNesting.php
new file mode 100644
index 0000000..d1588b9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/FixNesting.php
@@ -0,0 +1,346 @@
+<?php
+
+/**
+ * Takes a well formed list of tokens and fixes their nesting.
+ *
+ * HTML elements dictate which elements are allowed to be their children,
+ * for example, you can't have a p tag in a span tag.  Other elements have
+ * much more rigorous definitions: tables, for instance, require a specific
+ * order for their elements.  There are also constraints not expressible by
+ * document type definitions, such as the chameleon nature of ins/del
+ * tags and global child exclusions.
+ *
+ * The first major objective of this strategy is to iterate through all the
+ * nodes (not tokens) of the list of tokens and determine whether or not
+ * their children conform to the element's definition.  If they do not, the
+ * child definition may optionally supply an amended list of elements that
+ * is valid or require that the entire node be deleted (and the previous
+ * node rescanned).
+ *
+ * The second objective is to ensure that explicitly excluded elements of
+ * an element do not appear in its children.  Code that accomplishes this
+ * task is pervasive through the strategy, though the two are distinct tasks
+ * and could, theoretically, be seperated (although it's not recommended).
+ *
+ * @note Whether or not unrecognized children are silently dropped or
+ *       translated into text depends on the child definitions.
+ *
+ * @todo Enable nodes to be bubbled out of the structure.
+ *
+ * @warning This algorithm (though it may be hard to see) proceeds from
+ *          a top-down fashion.  Thus, parents are processed before
+ *          children.  This is easy to implement and has a nice effiency
+ *          benefit, in that if a node is removed, we never waste any
+ *          time processing it, but it also means that if a child
+ *          changes in a non-encapsulated way (e.g. it is removed), we
+ *          need to go back and reprocess the parent to see if those
+ *          changes resulted in problems for the parent.  See
+ *          [BACKTRACK] for an example of this.  In the current
+ *          implementation, this backtracking can only be triggered when
+ *          a node is removed and if that node was the sole node, the
+ *          parent would need to be removed.  As such, it is easy to see
+ *          that backtracking only incurs constant overhead.  If more
+ *          sophisticated backtracking is implemented, care must be
+ *          taken to avoid nontermination or exponential blowup.
+ */
+
+class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy
+{
+
+    public function execute($tokens, $config, $context) {
+        //####################################################################//
+        // Pre-processing
+
+        // get a copy of the HTML definition
+        $definition = $config->getHTMLDefinition();
+
+        $excludes_enabled = !$config->get('Core.DisableExcludes');
+
+        // insert implicit "parent" node, will be removed at end.
+        // DEFINITION CALL
+        $parent_name = $definition->info_parent;
+        array_unshift($tokens, new HTMLPurifier_Token_Start($parent_name));
+        $tokens[] = new HTMLPurifier_Token_End($parent_name);
+
+        // setup the context variable 'IsInline', for chameleon processing
+        // is 'false' when we are not inline, 'true' when it must always
+        // be inline, and an integer when it is inline for a certain
+        // branch of the document tree
+        $is_inline = $definition->info_parent_def->descendants_are_inline;
+        $context->register('IsInline', $is_inline);
+
+        // setup error collector
+        $e =& $context->get('ErrorCollector', true);
+
+        //####################################################################//
+        // Loop initialization
+
+        // stack that contains the indexes of all parents,
+        // $stack[count($stack)-1] being the current parent
+        $stack = array();
+
+        // stack that contains all elements that are excluded
+        // it is organized by parent elements, similar to $stack,
+        // but it is only populated when an element with exclusions is
+        // processed, i.e. there won't be empty exclusions.
+        $exclude_stack = array();
+
+        // variable that contains the start token while we are processing
+        // nodes. This enables error reporting to do its job
+        $start_token = false;
+        $context->register('CurrentToken', $start_token);
+
+        //####################################################################//
+        // Loop
+
+        // iterate through all start nodes. Determining the start node
+        // is complicated so it has been omitted from the loop construct
+        for ($i = 0, $size = count($tokens) ; $i < $size; ) {
+
+            //################################################################//
+            // Gather information on children
+
+            // child token accumulator
+            $child_tokens = array();
+
+            // scroll to the end of this node, report number, and collect
+            // all children
+            for ($j = $i, $depth = 0; ; $j++) {
+                if ($tokens[$j] instanceof HTMLPurifier_Token_Start) {
+                    $depth++;
+                    // skip token assignment on first iteration, this is the
+                    // token we currently are on
+                    if ($depth == 1) continue;
+                } elseif ($tokens[$j] instanceof HTMLPurifier_Token_End) {
+                    $depth--;
+                    // skip token assignment on last iteration, this is the
+                    // end token of the token we're currently on
+                    if ($depth == 0) break;
+                }
+                $child_tokens[] = $tokens[$j];
+            }
+
+            // $i is index of start token
+            // $j is index of end token
+
+            $start_token = $tokens[$i]; // to make token available via CurrentToken
+
+            //################################################################//
+            // Gather information on parent
+
+            // calculate parent information
+            if ($count = count($stack)) {
+                $parent_index = $stack[$count-1];
+                $parent_name  = $tokens[$parent_index]->name;
+                if ($parent_index == 0) {
+                    $parent_def   = $definition->info_parent_def;
+                } else {
+                    $parent_def   = $definition->info[$parent_name];
+                }
+            } else {
+                // processing as if the parent were the "root" node
+                // unknown info, it won't be used anyway, in the future,
+                // we may want to enforce one element only (this is
+                // necessary for HTML Purifier to clean entire documents
+                $parent_index = $parent_name = $parent_def = null;
+            }
+
+            // calculate context
+            if ($is_inline === false) {
+                // check if conditions make it inline
+                if (!empty($parent_def) && $parent_def->descendants_are_inline) {
+                    $is_inline = $count - 1;
+                }
+            } else {
+                // check if we're out of inline
+                if ($count === $is_inline) {
+                    $is_inline = false;
+                }
+            }
+
+            //################################################################//
+            // Determine whether element is explicitly excluded SGML-style
+
+            // determine whether or not element is excluded by checking all
+            // parent exclusions. The array should not be very large, two
+            // elements at most.
+            $excluded = false;
+            if (!empty($exclude_stack) && $excludes_enabled) {
+                foreach ($exclude_stack as $lookup) {
+                    if (isset($lookup[$tokens[$i]->name])) {
+                        $excluded = true;
+                        // no need to continue processing
+                        break;
+                    }
+                }
+            }
+
+            //################################################################//
+            // Perform child validation
+
+            if ($excluded) {
+                // there is an exclusion, remove the entire node
+                $result = false;
+                $excludes = array(); // not used, but good to initialize anyway
+            } else {
+                // DEFINITION CALL
+                if ($i === 0) {
+                    // special processing for the first node
+                    $def = $definition->info_parent_def;
+                } else {
+                    $def = $definition->info[$tokens[$i]->name];
+
+                }
+
+                if (!empty($def->child)) {
+                    // have DTD child def validate children
+                    $result = $def->child->validateChildren(
+                        $child_tokens, $config, $context);
+                } else {
+                    // weird, no child definition, get rid of everything
+                    $result = false;
+                }
+
+                // determine whether or not this element has any exclusions
+                $excludes = $def->excludes;
+            }
+
+            // $result is now a bool or array
+
+            //################################################################//
+            // Process result by interpreting $result
+
+            if ($result === true || $child_tokens === $result) {
+                // leave the node as is
+
+                // register start token as a parental node start
+                $stack[] = $i;
+
+                // register exclusions if there are any
+                if (!empty($excludes)) $exclude_stack[] = $excludes;
+
+                // move cursor to next possible start node
+                $i++;
+
+            } elseif($result === false) {
+                // remove entire node
+
+                if ($e) {
+                    if ($excluded) {
+                        $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded');
+                    } else {
+                        $e->send(E_ERROR, 'Strategy_FixNesting: Node removed');
+                    }
+                }
+
+                // calculate length of inner tokens and current tokens
+                $length = $j - $i + 1;
+
+                // perform removal
+                array_splice($tokens, $i, $length);
+
+                // update size
+                $size -= $length;
+
+                // there is no start token to register,
+                // current node is now the next possible start node
+                // unless it turns out that we need to do a double-check
+
+                // this is a rought heuristic that covers 100% of HTML's
+                // cases and 99% of all other cases. A child definition
+                // that would be tricked by this would be something like:
+                // ( | a b c) where it's all or nothing. Fortunately,
+                // our current implementation claims that that case would
+                // not allow empty, even if it did
+                if (!$parent_def->child->allow_empty) {
+                    // we need to do a double-check [BACKTRACK]
+                    $i = $parent_index;
+                    array_pop($stack);
+                }
+
+                // PROJECTED OPTIMIZATION: Process all children elements before
+                // reprocessing parent node.
+
+            } else {
+                // replace node with $result
+
+                // calculate length of inner tokens
+                $length = $j - $i - 1;
+
+                if ($e) {
+                    if (empty($result) && $length) {
+                        $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed');
+                    } else {
+                        $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized');
+                    }
+                }
+
+                // perform replacement
+                array_splice($tokens, $i + 1, $length, $result);
+
+                // update size
+                $size -= $length;
+                $size += count($result);
+
+                // register start token as a parental node start
+                $stack[] = $i;
+
+                // register exclusions if there are any
+                if (!empty($excludes)) $exclude_stack[] = $excludes;
+
+                // move cursor to next possible start node
+                $i++;
+
+            }
+
+            //################################################################//
+            // Scroll to next start node
+
+            // We assume, at this point, that $i is the index of the token
+            // that is the first possible new start point for a node.
+
+            // Test if the token indeed is a start tag, if not, move forward
+            // and test again.
+            $size = count($tokens);
+            while ($i < $size and !$tokens[$i] instanceof HTMLPurifier_Token_Start) {
+                if ($tokens[$i] instanceof HTMLPurifier_Token_End) {
+                    // pop a token index off the stack if we ended a node
+                    array_pop($stack);
+                    // pop an exclusion lookup off exclusion stack if
+                    // we ended node and that node had exclusions
+                    if ($i == 0 || $i == $size - 1) {
+                        // use specialized var if it's the super-parent
+                        $s_excludes = $definition->info_parent_def->excludes;
+                    } else {
+                        $s_excludes = $definition->info[$tokens[$i]->name]->excludes;
+                    }
+                    if ($s_excludes) {
+                        array_pop($exclude_stack);
+                    }
+                }
+                $i++;
+            }
+
+        }
+
+        //####################################################################//
+        // Post-processing
+
+        // remove implicit parent tokens at the beginning and end
+        array_shift($tokens);
+        array_pop($tokens);
+
+        // remove context variables
+        $context->destroy('IsInline');
+        $context->destroy('CurrentToken');
+
+        //####################################################################//
+        // Return
+
+        return $tokens;
+
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Strategy/MakeWellFormed.php b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/MakeWellFormed.php
new file mode 100644
index 0000000..c7aa1bb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/MakeWellFormed.php
@@ -0,0 +1,532 @@
+<?php
+
+/**
+ * Takes tokens makes them well-formed (balance end tags, etc.)
+ *
+ * Specification of the armor attributes this strategy uses:
+ *
+ *      - MakeWellFormed_TagClosedError: This armor field is used to
+ *        suppress tag closed errors for certain tokens [TagClosedSuppress],
+ *        in particular, if a tag was generated automatically by HTML
+ *        Purifier, we may rely on our infrastructure to close it for us
+ *        and shouldn't report an error to the user [TagClosedAuto].
+ */
+class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy
+{
+
+    /**
+     * Array stream of tokens being processed.
+     */
+    protected $tokens;
+
+    /**
+     * Current index in $tokens.
+     */
+    protected $t;
+
+    /**
+     * Current nesting of elements.
+     */
+    protected $stack;
+
+    /**
+     * Injectors active in this stream processing.
+     */
+    protected $injectors;
+
+    /**
+     * Current instance of HTMLPurifier_Config.
+     */
+    protected $config;
+
+    /**
+     * Current instance of HTMLPurifier_Context.
+     */
+    protected $context;
+
+    public function execute($tokens, $config, $context) {
+
+        $definition = $config->getHTMLDefinition();
+
+        // local variables
+        $generator = new HTMLPurifier_Generator($config, $context);
+        $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
+        // used for autoclose early abortion
+        $global_parent_allowed_elements = array();
+        if (isset($definition->info[$definition->info_parent])) {
+            // may be unset under testing circumstances
+            $global_parent_allowed_elements = $definition->info[$definition->info_parent]->child->getAllowedElements($config);
+        }
+        $e = $context->get('ErrorCollector', true);
+        $t = false; // token index
+        $i = false; // injector index
+        $token      = false; // the current token
+        $reprocess  = false; // whether or not to reprocess the same token
+        $stack = array();
+
+        // member variables
+        $this->stack   =& $stack;
+        $this->t       =& $t;
+        $this->tokens  =& $tokens;
+        $this->config  = $config;
+        $this->context = $context;
+
+        // context variables
+        $context->register('CurrentNesting', $stack);
+        $context->register('InputIndex',     $t);
+        $context->register('InputTokens',    $tokens);
+        $context->register('CurrentToken',   $token);
+
+        // -- begin INJECTOR --
+
+        $this->injectors = array();
+
+        $injectors = $config->getBatch('AutoFormat');
+        $def_injectors = $definition->info_injector;
+        $custom_injectors = $injectors['Custom'];
+        unset($injectors['Custom']); // special case
+        foreach ($injectors as $injector => $b) {
+            // XXX: Fix with a legitimate lookup table of enabled filters
+            if (strpos($injector, '.') !== false) continue;
+            $injector = "HTMLPurifier_Injector_$injector";
+            if (!$b) continue;
+            $this->injectors[] = new $injector;
+        }
+        foreach ($def_injectors as $injector) {
+            // assumed to be objects
+            $this->injectors[] = $injector;
+        }
+        foreach ($custom_injectors as $injector) {
+            if (!$injector) continue;
+            if (is_string($injector)) {
+                $injector = "HTMLPurifier_Injector_$injector";
+                $injector = new $injector;
+            }
+            $this->injectors[] = $injector;
+        }
+
+        // give the injectors references to the definition and context
+        // variables for performance reasons
+        foreach ($this->injectors as $ix => $injector) {
+            $error = $injector->prepare($config, $context);
+            if (!$error) continue;
+            array_splice($this->injectors, $ix, 1); // rm the injector
+            trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING);
+        }
+
+        // -- end INJECTOR --
+
+        // a note on reprocessing:
+        //      In order to reduce code duplication, whenever some code needs
+        //      to make HTML changes in order to make things "correct", the
+        //      new HTML gets sent through the purifier, regardless of its
+        //      status. This means that if we add a start token, because it
+        //      was totally necessary, we don't have to update nesting; we just
+        //      punt ($reprocess = true; continue;) and it does that for us.
+
+        // isset is in loop because $tokens size changes during loop exec
+        for (
+            $t = 0;
+            $t == 0 || isset($tokens[$t - 1]);
+            // only increment if we don't need to reprocess
+            $reprocess ? $reprocess = false : $t++
+        ) {
+
+            // check for a rewind
+            if (is_int($i) && $i >= 0) {
+                // possibility: disable rewinding if the current token has a
+                // rewind set on it already. This would offer protection from
+                // infinite loop, but might hinder some advanced rewinding.
+                $rewind_to = $this->injectors[$i]->getRewind();
+                if (is_int($rewind_to) && $rewind_to < $t) {
+                    if ($rewind_to < 0) $rewind_to = 0;
+                    while ($t > $rewind_to) {
+                        $t--;
+                        $prev = $tokens[$t];
+                        // indicate that other injectors should not process this token,
+                        // but we need to reprocess it
+                        unset($prev->skip[$i]);
+                        $prev->rewind = $i;
+                        if ($prev instanceof HTMLPurifier_Token_Start) array_pop($this->stack);
+                        elseif ($prev instanceof HTMLPurifier_Token_End) $this->stack[] = $prev->start;
+                    }
+                }
+                $i = false;
+            }
+
+            // handle case of document end
+            if (!isset($tokens[$t])) {
+                // kill processing if stack is empty
+                if (empty($this->stack)) break;
+
+                // peek
+                $top_nesting = array_pop($this->stack);
+                $this->stack[] = $top_nesting;
+
+                // send error [TagClosedSuppress]
+                if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) {
+                    $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting);
+                }
+
+                // append, don't splice, since this is the end
+                $tokens[] = new HTMLPurifier_Token_End($top_nesting->name);
+
+                // punt!
+                $reprocess = true;
+                continue;
+            }
+
+            $token = $tokens[$t];
+
+            //echo '<br>'; printTokens($tokens, $t); printTokens($this->stack);
+            //flush();
+
+            // quick-check: if it's not a tag, no need to process
+            if (empty($token->is_tag)) {
+                if ($token instanceof HTMLPurifier_Token_Text) {
+                    foreach ($this->injectors as $i => $injector) {
+                        if (isset($token->skip[$i])) continue;
+                        if ($token->rewind !== null && $token->rewind !== $i) continue;
+                        $injector->handleText($token);
+                        $this->processToken($token, $i);
+                        $reprocess = true;
+                        break;
+                    }
+                }
+                // another possibility is a comment
+                continue;
+            }
+
+            if (isset($definition->info[$token->name])) {
+                $type = $definition->info[$token->name]->child->type;
+            } else {
+                $type = false; // Type is unknown, treat accordingly
+            }
+
+            // quick tag checks: anything that's *not* an end tag
+            $ok = false;
+            if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) {
+                // claims to be a start tag but is empty
+                $token = new HTMLPurifier_Token_Empty($token->name, $token->attr, $token->line, $token->col, $token->armor);
+                $ok = true;
+            } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) {
+                // claims to be empty but really is a start tag
+                $this->swap(new HTMLPurifier_Token_End($token->name));
+                $this->insertBefore(new HTMLPurifier_Token_Start($token->name, $token->attr, $token->line, $token->col, $token->armor));
+                // punt (since we had to modify the input stream in a non-trivial way)
+                $reprocess = true;
+                continue;
+            } elseif ($token instanceof HTMLPurifier_Token_Empty) {
+                // real empty token
+                $ok = true;
+            } elseif ($token instanceof HTMLPurifier_Token_Start) {
+                // start tag
+
+                // ...unless they also have to close their parent
+                if (!empty($this->stack)) {
+
+                    // Performance note: you might think that it's rather
+                    // inefficient, recalculating the autoclose information
+                    // for every tag that a token closes (since when we
+                    // do an autoclose, we push a new token into the
+                    // stream and then /process/ that, before
+                    // re-processing this token.)  But this is
+                    // necessary, because an injector can make an
+                    // arbitrary transformations to the autoclosing
+                    // tokens we introduce, so things may have changed
+                    // in the meantime.  Also, doing the inefficient thing is
+                    // "easy" to reason about (for certain perverse definitions
+                    // of "easy")
+
+                    $parent = array_pop($this->stack);
+                    $this->stack[] = $parent;
+
+                    if (isset($definition->info[$parent->name])) {
+                        $elements = $definition->info[$parent->name]->child->getAllowedElements($config);
+                        $autoclose = !isset($elements[$token->name]);
+                    } else {
+                        $autoclose = false;
+                    }
+
+                    if ($autoclose && $definition->info[$token->name]->wrap) {
+                        // Check if an element can be wrapped by another 
+                        // element to make it valid in a context (for 
+                        // example, <ul><ul> needs a <li> in between)
+                        $wrapname = $definition->info[$token->name]->wrap;
+                        $wrapdef = $definition->info[$wrapname];
+                        $elements = $wrapdef->child->getAllowedElements($config);
+                        $parent_elements = $definition->info[$parent->name]->child->getAllowedElements($config);
+                        if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) {
+                            $newtoken = new HTMLPurifier_Token_Start($wrapname);
+                            $this->insertBefore($newtoken);
+                            $reprocess = true;
+                            continue;
+                        }
+                    }
+
+                    $carryover = false;
+                    if ($autoclose && $definition->info[$parent->name]->formatting) {
+                        $carryover = true;
+                    }
+
+                    if ($autoclose) {
+                        // check if this autoclose is doomed to fail
+                        // (this rechecks $parent, which his harmless)
+                        $autoclose_ok = isset($global_parent_allowed_elements[$token->name]);
+                        if (!$autoclose_ok) {
+                            foreach ($this->stack as $ancestor) {
+                                $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config);
+                                if (isset($elements[$token->name])) {
+                                    $autoclose_ok = true;
+                                    break;
+                                }
+                                if ($definition->info[$token->name]->wrap) {
+                                    $wrapname = $definition->info[$token->name]->wrap;
+                                    $wrapdef = $definition->info[$wrapname];
+                                    $wrap_elements = $wrapdef->child->getAllowedElements($config);
+                                    if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) {
+                                        $autoclose_ok = true;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                        if ($autoclose_ok) {
+                            // errors need to be updated
+                            $new_token = new HTMLPurifier_Token_End($parent->name);
+                            $new_token->start = $parent;
+                            if ($carryover) {
+                                $element = clone $parent;
+                                // [TagClosedAuto]
+                                $element->armor['MakeWellFormed_TagClosedError'] = true;
+                                $element->carryover = true;
+                                $this->processToken(array($new_token, $token, $element));
+                            } else {
+                                $this->insertBefore($new_token);
+                            }
+                            // [TagClosedSuppress]
+                            if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) {
+                                if (!$carryover) {
+                                    $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent);
+                                } else {
+                                    $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent);
+                                }
+                            }
+                        } else {
+                            $this->remove();
+                        }
+                        $reprocess = true;
+                        continue;
+                    }
+
+                }
+                $ok = true;
+            }
+
+            if ($ok) {
+                foreach ($this->injectors as $i => $injector) {
+                    if (isset($token->skip[$i])) continue;
+                    if ($token->rewind !== null && $token->rewind !== $i) continue;
+                    $injector->handleElement($token);
+                    $this->processToken($token, $i);
+                    $reprocess = true;
+                    break;
+                }
+                if (!$reprocess) {
+                    // ah, nothing interesting happened; do normal processing
+                    $this->swap($token);
+                    if ($token instanceof HTMLPurifier_Token_Start) {
+                        $this->stack[] = $token;
+                    } elseif ($token instanceof HTMLPurifier_Token_End) {
+                        throw new HTMLPurifier_Exception('Improper handling of end tag in start code; possible error in MakeWellFormed');
+                    }
+                }
+                continue;
+            }
+
+            // sanity check: we should be dealing with a closing tag
+            if (!$token instanceof HTMLPurifier_Token_End) {
+                throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier');
+            }
+
+            // make sure that we have something open
+            if (empty($this->stack)) {
+                if ($escape_invalid_tags) {
+                    if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text');
+                    $this->swap(new HTMLPurifier_Token_Text(
+                        $generator->generateFromToken($token)
+                    ));
+                } else {
+                    $this->remove();
+                    if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed');
+                }
+                $reprocess = true;
+                continue;
+            }
+
+            // first, check for the simplest case: everything closes neatly.
+            // Eventually, everything passes through here; if there are problems
+            // we modify the input stream accordingly and then punt, so that
+            // the tokens get processed again.
+            $current_parent = array_pop($this->stack);
+            if ($current_parent->name == $token->name) {
+                $token->start = $current_parent;
+                foreach ($this->injectors as $i => $injector) {
+                    if (isset($token->skip[$i])) continue;
+                    if ($token->rewind !== null && $token->rewind !== $i) continue;
+                    $injector->handleEnd($token);
+                    $this->processToken($token, $i);
+                    $this->stack[] = $current_parent;
+                    $reprocess = true;
+                    break;
+                }
+                continue;
+            }
+
+            // okay, so we're trying to close the wrong tag
+
+            // undo the pop previous pop
+            $this->stack[] = $current_parent;
+
+            // scroll back the entire nest, trying to find our tag.
+            // (feature could be to specify how far you'd like to go)
+            $size = count($this->stack);
+            // -2 because -1 is the last element, but we already checked that
+            $skipped_tags = false;
+            for ($j = $size - 2; $j >= 0; $j--) {
+                if ($this->stack[$j]->name == $token->name) {
+                    $skipped_tags = array_slice($this->stack, $j);
+                    break;
+                }
+            }
+
+            // we didn't find the tag, so remove
+            if ($skipped_tags === false) {
+                if ($escape_invalid_tags) {
+                    $this->swap(new HTMLPurifier_Token_Text(
+                        $generator->generateFromToken($token)
+                    ));
+                    if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text');
+                } else {
+                    $this->remove();
+                    if ($e) $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed');
+                }
+                $reprocess = true;
+                continue;
+            }
+
+            // do errors, in REVERSE $j order: a,b,c with </a></b></c>
+            $c = count($skipped_tags);
+            if ($e) {
+                for ($j = $c - 1; $j > 0; $j--) {
+                    // notice we exclude $j == 0, i.e. the current ending tag, from
+                    // the errors... [TagClosedSuppress]
+                    if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) {
+                        $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]);
+                    }
+                }
+            }
+
+            // insert tags, in FORWARD $j order: c,b,a with </a></b></c>
+            $replace = array($token);
+            for ($j = 1; $j < $c; $j++) {
+                // ...as well as from the insertions
+                $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name);
+                $new_token->start = $skipped_tags[$j];
+                array_unshift($replace, $new_token);
+                if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) {
+                    // [TagClosedAuto]
+                    $element = clone $skipped_tags[$j];
+                    $element->carryover = true;
+                    $element->armor['MakeWellFormed_TagClosedError'] = true;
+                    $replace[] = $element;
+                }
+            }
+            $this->processToken($replace);
+            $reprocess = true;
+            continue;
+        }
+
+        $context->destroy('CurrentNesting');
+        $context->destroy('InputTokens');
+        $context->destroy('InputIndex');
+        $context->destroy('CurrentToken');
+
+        unset($this->injectors, $this->stack, $this->tokens, $this->t);
+        return $tokens;
+    }
+
+    /**
+     * Processes arbitrary token values for complicated substitution patterns.
+     * In general:
+     *
+     * If $token is an array, it is a list of tokens to substitute for the
+     * current token. These tokens then get individually processed. If there
+     * is a leading integer in the list, that integer determines how many
+     * tokens from the stream should be removed.
+     *
+     * If $token is a regular token, it is swapped with the current token.
+     *
+     * If $token is false, the current token is deleted.
+     *
+     * If $token is an integer, that number of tokens (with the first token
+     * being the current one) will be deleted.
+     *
+     * @param $token Token substitution value
+     * @param $injector Injector that performed the substitution; default is if
+     *        this is not an injector related operation.
+     */
+    protected function processToken($token, $injector = -1) {
+
+        // normalize forms of token
+        if (is_object($token)) $token = array(1, $token);
+        if (is_int($token))    $token = array($token);
+        if ($token === false)  $token = array(1);
+        if (!is_array($token)) throw new HTMLPurifier_Exception('Invalid token type from injector');
+        if (!is_int($token[0])) array_unshift($token, 1);
+        if ($token[0] === 0) throw new HTMLPurifier_Exception('Deleting zero tokens is not valid');
+
+        // $token is now an array with the following form:
+        // array(number nodes to delete, new node 1, new node 2, ...)
+
+        $delete = array_shift($token);
+        $old = array_splice($this->tokens, $this->t, $delete, $token);
+
+        if ($injector > -1) {
+            // determine appropriate skips
+            $oldskip = isset($old[0]) ? $old[0]->skip : array();
+            foreach ($token as $object) {
+                $object->skip = $oldskip;
+                $object->skip[$injector] = true;
+            }
+        }
+
+    }
+
+    /**
+     * Inserts a token before the current token. Cursor now points to
+     * this token.  You must reprocess after this.
+     */
+    private function insertBefore($token) {
+        array_splice($this->tokens, $this->t, 0, array($token));
+    }
+
+    /**
+     * Removes current token. Cursor now points to new token occupying previously
+     * occupied space.  You must reprocess after this.
+     */
+    private function remove() {
+        array_splice($this->tokens, $this->t, 1);
+    }
+
+    /**
+     * Swap current token with new token. Cursor points to new token (no
+     * change).  You must reprocess after this.
+     */
+    private function swap($token) {
+        $this->tokens[$this->t] = $token;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Strategy/RemoveForeignElements.php b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/RemoveForeignElements.php
new file mode 100644
index 0000000..bccaf14
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/RemoveForeignElements.php
@@ -0,0 +1,188 @@
+<?php
+
+/**
+ * Removes all unrecognized tags from the list of tokens.
+ *
+ * This strategy iterates through all the tokens and removes unrecognized
+ * tokens. If a token is not recognized but a TagTransform is defined for
+ * that element, the element will be transformed accordingly.
+ */
+
+class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy
+{
+
+    public function execute($tokens, $config, $context) {
+        $definition = $config->getHTMLDefinition();
+        $generator = new HTMLPurifier_Generator($config, $context);
+        $result = array();
+
+        $escape_invalid_tags = $config->get('Core.EscapeInvalidTags');
+        $remove_invalid_img  = $config->get('Core.RemoveInvalidImg');
+
+        // currently only used to determine if comments should be kept
+        $trusted = $config->get('HTML.Trusted');
+        $comment_lookup = $config->get('HTML.AllowedComments');
+        $comment_regexp = $config->get('HTML.AllowedCommentsRegexp');
+        $check_comments = $comment_lookup !== array() || $comment_regexp !== null;
+
+        $remove_script_contents = $config->get('Core.RemoveScriptContents');
+        $hidden_elements     = $config->get('Core.HiddenElements');
+
+        // remove script contents compatibility
+        if ($remove_script_contents === true) {
+            $hidden_elements['script'] = true;
+        } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) {
+            unset($hidden_elements['script']);
+        }
+
+        $attr_validator = new HTMLPurifier_AttrValidator();
+
+        // removes tokens until it reaches a closing tag with its value
+        $remove_until = false;
+
+        // converts comments into text tokens when this is equal to a tag name
+        $textify_comments = false;
+
+        $token = false;
+        $context->register('CurrentToken', $token);
+
+        $e = false;
+        if ($config->get('Core.CollectErrors')) {
+            $e =& $context->get('ErrorCollector');
+        }
+
+        foreach($tokens as $token) {
+            if ($remove_until) {
+                if (empty($token->is_tag) || $token->name !== $remove_until) {
+                    continue;
+                }
+            }
+            if (!empty( $token->is_tag )) {
+                // DEFINITION CALL
+
+                // before any processing, try to transform the element
+                if (
+                    isset($definition->info_tag_transform[$token->name])
+                ) {
+                    $original_name = $token->name;
+                    // there is a transformation for this tag
+                    // DEFINITION CALL
+                    $token = $definition->
+                                info_tag_transform[$token->name]->
+                                    transform($token, $config, $context);
+                    if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name);
+                }
+
+                if (isset($definition->info[$token->name])) {
+
+                    // mostly everything's good, but
+                    // we need to make sure required attributes are in order
+                    if (
+                        ($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) &&
+                        $definition->info[$token->name]->required_attr &&
+                        ($token->name != 'img' || $remove_invalid_img) // ensure config option still works
+                    ) {
+                        $attr_validator->validateToken($token, $config, $context);
+                        $ok = true;
+                        foreach ($definition->info[$token->name]->required_attr as $name) {
+                            if (!isset($token->attr[$name])) {
+                                $ok = false;
+                                break;
+                            }
+                        }
+                        if (!$ok) {
+                            if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', $name);
+                            continue;
+                        }
+                        $token->armor['ValidateAttributes'] = true;
+                    }
+
+                    if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) {
+                        $textify_comments = $token->name;
+                    } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) {
+                        $textify_comments = false;
+                    }
+
+                } elseif ($escape_invalid_tags) {
+                    // invalid tag, generate HTML representation and insert in
+                    if ($e) $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text');
+                    $token = new HTMLPurifier_Token_Text(
+                        $generator->generateFromToken($token)
+                    );
+                } else {
+                    // check if we need to destroy all of the tag's children
+                    // CAN BE GENERICIZED
+                    if (isset($hidden_elements[$token->name])) {
+                        if ($token instanceof HTMLPurifier_Token_Start) {
+                            $remove_until = $token->name;
+                        } elseif ($token instanceof HTMLPurifier_Token_Empty) {
+                            // do nothing: we're still looking
+                        } else {
+                            $remove_until = false;
+                        }
+                        if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed');
+                    } else {
+                        if ($e) $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed');
+                    }
+                    continue;
+                }
+            } elseif ($token instanceof HTMLPurifier_Token_Comment) {
+                // textify comments in script tags when they are allowed
+                if ($textify_comments !== false) {
+                    $data = $token->data;
+                    $token = new HTMLPurifier_Token_Text($data);
+                } elseif ($trusted || $check_comments) {
+                    // always cleanup comments
+                    $trailing_hyphen = false;
+                    if ($e) {
+                        // perform check whether or not there's a trailing hyphen
+                        if (substr($token->data, -1) == '-') {
+                            $trailing_hyphen = true;
+                        }
+                    }
+                    $token->data = rtrim($token->data, '-');
+                    $found_double_hyphen = false;
+                    while (strpos($token->data, '--') !== false) {
+                        $found_double_hyphen = true;
+                        $token->data = str_replace('--', '-', $token->data);
+                    }
+                    if ($trusted || !empty($comment_lookup[trim($token->data)]) || ($comment_regexp !== NULL && preg_match($comment_regexp, trim($token->data)))) {
+                        // OK good
+                        if ($e) {
+                            if ($trailing_hyphen) {
+                                $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed');
+                            }
+                            if ($found_double_hyphen) {
+                                $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed');
+                            }
+                        }
+                    } else {
+                        if ($e) {
+                            $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
+                        }
+                        continue;
+                    }
+                } else {
+                    // strip comments
+                    if ($e) $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed');
+                    continue;
+                }
+            } elseif ($token instanceof HTMLPurifier_Token_Text) {
+            } else {
+                continue;
+            }
+            $result[] = $token;
+        }
+        if ($remove_until && $e) {
+            // we removed tokens until the end, throw error
+            $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until);
+        }
+
+        $context->destroy('CurrentToken');
+
+        return $result;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Strategy/ValidateAttributes.php b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/ValidateAttributes.php
new file mode 100644
index 0000000..c3328a9
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Strategy/ValidateAttributes.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Validate all attributes in the tokens.
+ */
+
+class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy
+{
+
+    public function execute($tokens, $config, $context) {
+
+        // setup validator
+        $validator = new HTMLPurifier_AttrValidator();
+
+        $token = false;
+        $context->register('CurrentToken', $token);
+
+        foreach ($tokens as $key => $token) {
+
+            // only process tokens that have attributes,
+            //   namely start and empty tags
+            if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) continue;
+
+            // skip tokens that are armored
+            if (!empty($token->armor['ValidateAttributes'])) continue;
+
+            // note that we have no facilities here for removing tokens
+            $validator->validateToken($token, $config, $context);
+
+            $tokens[$key] = $token; // for PHP 4
+        }
+        $context->destroy('CurrentToken');
+
+        return $tokens;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/StringHash.php b/application/libraries/htmlpurifier/HTMLPurifier/StringHash.php
new file mode 100644
index 0000000..62085c5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/StringHash.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * This is in almost every respect equivalent to an array except
+ * that it keeps track of which keys were accessed.
+ *
+ * @warning For the sake of backwards compatibility with early versions
+ *     of PHP 5, you must not use the $hash[$key] syntax; if you do
+ *     our version of offsetGet is never called.
+ */
+class HTMLPurifier_StringHash extends ArrayObject
+{
+    protected $accessed = array();
+
+    /**
+     * Retrieves a value, and logs the access.
+     */
+    public function offsetGet($index) {
+        $this->accessed[$index] = true;
+        return parent::offsetGet($index);
+    }
+
+    /**
+     * Returns a lookup array of all array indexes that have been accessed.
+     * @return Array in form array($index => true).
+     */
+    public function getAccessed() {
+        return $this->accessed;
+    }
+
+    /**
+     * Resets the access array.
+     */
+    public function resetAccessed() {
+        $this->accessed = array();
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/StringHashParser.php b/application/libraries/htmlpurifier/HTMLPurifier/StringHashParser.php
new file mode 100644
index 0000000..f3e70c7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/StringHashParser.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * Parses string hash files. File format is as such:
+ *
+ *      DefaultKeyValue
+ *      KEY: Value
+ *      KEY2: Value2
+ *      --MULTILINE-KEY--
+ *      Multiline
+ *      value.
+ *
+ * Which would output something similar to:
+ *
+ *      array(
+ *          'ID' => 'DefaultKeyValue',
+ *          'KEY' => 'Value',
+ *          'KEY2' => 'Value2',
+ *          'MULTILINE-KEY' => "Multiline\nvalue.\n",
+ *      )
+ *
+ * We use this as an easy to use file-format for configuration schema
+ * files, but the class itself is usage agnostic.
+ *
+ * You can use ---- to forcibly terminate parsing of a single string-hash;
+ * this marker is used in multi string-hashes to delimit boundaries.
+ */
+class HTMLPurifier_StringHashParser
+{
+
+    public $default = 'ID';
+
+    /**
+     * Parses a file that contains a single string-hash.
+     */
+    public function parseFile($file) {
+        if (!file_exists($file)) return false;
+        $fh = fopen($file, 'r');
+        if (!$fh) return false;
+        $ret = $this->parseHandle($fh);
+        fclose($fh);
+        return $ret;
+    }
+
+    /**
+     * Parses a file that contains multiple string-hashes delimited by '----'
+     */
+    public function parseMultiFile($file) {
+        if (!file_exists($file)) return false;
+        $ret = array();
+        $fh = fopen($file, 'r');
+        if (!$fh) return false;
+        while (!feof($fh)) {
+            $ret[] = $this->parseHandle($fh);
+        }
+        fclose($fh);
+        return $ret;
+    }
+
+    /**
+     * Internal parser that acepts a file handle.
+     * @note While it's possible to simulate in-memory parsing by using
+     *       custom stream wrappers, if such a use-case arises we should
+     *       factor out the file handle into its own class.
+     * @param $fh File handle with pointer at start of valid string-hash
+     *            block.
+     */
+    protected function parseHandle($fh) {
+        $state   = false;
+        $single  = false;
+        $ret     = array();
+        do {
+            $line = fgets($fh);
+            if ($line === false) break;
+            $line = rtrim($line, "\n\r");
+            if (!$state && $line === '') continue;
+            if ($line === '----') break;
+            if (strncmp('--#', $line, 3) === 0) {
+                // Comment
+                continue;
+            } elseif (strncmp('--', $line, 2) === 0) {
+                // Multiline declaration
+                $state = trim($line, '- ');
+                if (!isset($ret[$state])) $ret[$state] = '';
+                continue;
+            } elseif (!$state) {
+                $single = true;
+                if (strpos($line, ':') !== false) {
+                    // Single-line declaration
+                    list($state, $line) = explode(':', $line, 2);
+                    $line = trim($line);
+                } else {
+                    // Use default declaration
+                    $state  = $this->default;
+                }
+            }
+            if ($single) {
+                $ret[$state] = $line;
+                $single = false;
+                $state  = false;
+            } else {
+                $ret[$state] .= "$line\n";
+            }
+        } while (!feof($fh));
+        return $ret;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/TagTransform.php b/application/libraries/htmlpurifier/HTMLPurifier/TagTransform.php
new file mode 100644
index 0000000..210a447
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/TagTransform.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Defines a mutation of an obsolete tag into a valid tag.
+ */
+abstract class HTMLPurifier_TagTransform
+{
+
+    /**
+     * Tag name to transform the tag to.
+     */
+    public $transform_to;
+
+    /**
+     * Transforms the obsolete tag into the valid tag.
+     * @param $tag Tag to be transformed.
+     * @param $config Mandatory HTMLPurifier_Config object
+     * @param $context Mandatory HTMLPurifier_Context object
+     */
+    abstract public function transform($tag, $config, $context);
+
+    /**
+     * Prepends CSS properties to the style attribute, creating the
+     * attribute if it doesn't exist.
+     * @warning Copied over from AttrTransform, be sure to keep in sync
+     * @param $attr Attribute array to process (passed by reference)
+     * @param $css CSS to prepend
+     */
+    protected function prependCSS(&$attr, $css) {
+        $attr['style'] = isset($attr['style']) ? $attr['style'] : '';
+        $attr['style'] = $css . $attr['style'];
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/TagTransform/Font.php b/application/libraries/htmlpurifier/HTMLPurifier/TagTransform/Font.php
new file mode 100644
index 0000000..9db2db7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/TagTransform/Font.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * Transforms FONT tags to the proper form (SPAN with CSS styling)
+ *
+ * This transformation takes the three proprietary attributes of FONT and
+ * transforms them into their corresponding CSS attributes.  These are color,
+ * face, and size.
+ *
+ * @note Size is an interesting case because it doesn't map cleanly to CSS.
+ *       Thanks to
+ *       http://style.cleverchimp.com/font_size_intervals/altintervals.html
+ *       for reasonable mappings.
+ * @warning This doesn't work completely correctly; specifically, this
+ *          TagTransform operates before well-formedness is enforced, so
+ *          the "active formatting elements" algorithm doesn't get applied.
+ */
+class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform
+{
+
+    public $transform_to = 'span';
+
+    protected $_size_lookup = array(
+        '0' => 'xx-small',
+        '1' => 'xx-small',
+        '2' => 'small',
+        '3' => 'medium',
+        '4' => 'large',
+        '5' => 'x-large',
+        '6' => 'xx-large',
+        '7' => '300%',
+        '-1' => 'smaller',
+        '-2' => '60%',
+        '+1' => 'larger',
+        '+2' => '150%',
+        '+3' => '200%',
+        '+4' => '300%'
+    );
+
+    public function transform($tag, $config, $context) {
+
+        if ($tag instanceof HTMLPurifier_Token_End) {
+            $new_tag = clone $tag;
+            $new_tag->name = $this->transform_to;
+            return $new_tag;
+        }
+
+        $attr = $tag->attr;
+        $prepend_style = '';
+
+        // handle color transform
+        if (isset($attr['color'])) {
+            $prepend_style .= 'color:' . $attr['color'] . ';';
+            unset($attr['color']);
+        }
+
+        // handle face transform
+        if (isset($attr['face'])) {
+            $prepend_style .= 'font-family:' . $attr['face'] . ';';
+            unset($attr['face']);
+        }
+
+        // handle size transform
+        if (isset($attr['size'])) {
+            // normalize large numbers
+            if ($attr['size'] !== '') {
+                if ($attr['size']{0} == '+' || $attr['size']{0} == '-') {
+                    $size = (int) $attr['size'];
+                    if ($size < -2) $attr['size'] = '-2';
+                    if ($size > 4)  $attr['size'] = '+4';
+                } else {
+                    $size = (int) $attr['size'];
+                    if ($size > 7) $attr['size'] = '7';
+                }
+            }
+            if (isset($this->_size_lookup[$attr['size']])) {
+                $prepend_style .= 'font-size:' .
+                  $this->_size_lookup[$attr['size']] . ';';
+            }
+            unset($attr['size']);
+        }
+
+        if ($prepend_style) {
+            $attr['style'] = isset($attr['style']) ?
+                $prepend_style . $attr['style'] :
+                $prepend_style;
+        }
+
+        $new_tag = clone $tag;
+        $new_tag->name = $this->transform_to;
+        $new_tag->attr = $attr;
+
+        return $new_tag;
+
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/TagTransform/Simple.php b/application/libraries/htmlpurifier/HTMLPurifier/TagTransform/Simple.php
new file mode 100644
index 0000000..0e36130
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/TagTransform/Simple.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Simple transformation, just change tag name to something else,
+ * and possibly add some styling. This will cover most of the deprecated
+ * tag cases.
+ */
+class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform
+{
+
+    protected $style;
+
+    /**
+     * @param $transform_to Tag name to transform to.
+     * @param $style CSS style to add to the tag
+     */
+    public function __construct($transform_to, $style = null) {
+        $this->transform_to = $transform_to;
+        $this->style = $style;
+    }
+
+    public function transform($tag, $config, $context) {
+        $new_tag = clone $tag;
+        $new_tag->name = $this->transform_to;
+        if (!is_null($this->style) &&
+            ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty)
+        ) {
+            $this->prependCSS($new_tag->attr, $this->style);
+        }
+        return $new_tag;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Token.php b/application/libraries/htmlpurifier/HTMLPurifier/Token.php
new file mode 100644
index 0000000..7900e6c
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Token.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * Abstract base token class that all others inherit from.
+ */
+class HTMLPurifier_Token {
+    public $line; /**< Line number node was on in source document. Null if unknown. */
+    public $col;  /**< Column of line node was on in source document. Null if unknown. */
+
+    /**
+     * Lookup array of processing that this token is exempt from.
+     * Currently, valid values are "ValidateAttributes" and
+     * "MakeWellFormed_TagClosedError"
+     */
+    public $armor = array();
+
+    /**
+     * Used during MakeWellFormed.
+     */
+    public $skip;
+    public $rewind;
+    public $carryover;
+
+    public function __get($n) {
+      if ($n === 'type') {
+        trigger_error('Deprecated type property called; use instanceof', E_USER_NOTICE);
+        switch (get_class($this)) {
+          case 'HTMLPurifier_Token_Start':      return 'start';
+          case 'HTMLPurifier_Token_Empty':      return 'empty';
+          case 'HTMLPurifier_Token_End':        return 'end';
+          case 'HTMLPurifier_Token_Text':       return 'text';
+          case 'HTMLPurifier_Token_Comment':    return 'comment';
+          default: return null;
+        }
+      }
+    }
+
+    /**
+     * Sets the position of the token in the source document.
+     */
+    public function position($l = null, $c = null) {
+        $this->line = $l;
+        $this->col  = $c;
+    }
+
+    /**
+     * Convenience function for DirectLex settings line/col position.
+     */
+    public function rawPosition($l, $c) {
+        if ($c === -1) $l++;
+        $this->line = $l;
+        $this->col  = $c;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Token/Comment.php b/application/libraries/htmlpurifier/HTMLPurifier/Token/Comment.php
new file mode 100644
index 0000000..dc6bdca
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Token/Comment.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Concrete comment token class. Generally will be ignored.
+ */
+class HTMLPurifier_Token_Comment extends HTMLPurifier_Token
+{
+    public $data; /**< Character data within comment. */
+    public $is_whitespace = true;
+    /**
+     * Transparent constructor.
+     *
+     * @param $data String comment data.
+     */
+    public function __construct($data, $line = null, $col = null) {
+        $this->data = $data;
+        $this->line = $line;
+        $this->col  = $col;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Token/Empty.php b/application/libraries/htmlpurifier/HTMLPurifier/Token/Empty.php
new file mode 100644
index 0000000..2a82b47
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Token/Empty.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Concrete empty token class.
+ */
+class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag
+{
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Token/End.php b/application/libraries/htmlpurifier/HTMLPurifier/Token/End.php
new file mode 100644
index 0000000..353e79d
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Token/End.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Concrete end token class.
+ *
+ * @warning This class accepts attributes even though end tags cannot. This
+ * is for optimization reasons, as under normal circumstances, the Lexers
+ * do not pass attributes.
+ */
+class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag
+{
+    /**
+     * Token that started this node. Added by MakeWellFormed. Please
+     * do not edit this!
+     */
+    public $start;
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Token/Start.php b/application/libraries/htmlpurifier/HTMLPurifier/Token/Start.php
new file mode 100644
index 0000000..e0e14fc
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Token/Start.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Concrete start token class.
+ */
+class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag
+{
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Token/Tag.php b/application/libraries/htmlpurifier/HTMLPurifier/Token/Tag.php
new file mode 100644
index 0000000..f4d8f64
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Token/Tag.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * Abstract class of a tag token (start, end or empty), and its behavior.
+ */
+class HTMLPurifier_Token_Tag extends HTMLPurifier_Token
+{
+    /**
+     * Static bool marker that indicates the class is a tag.
+     *
+     * This allows us to check objects with <tt>!empty($obj->is_tag)</tt>
+     * without having to use a function call <tt>is_a()</tt>.
+     */
+    public $is_tag = true;
+
+    /**
+     * The lower-case name of the tag, like 'a', 'b' or 'blockquote'.
+     *
+     * @note Strictly speaking, XML tags are case sensitive, so we shouldn't
+     * be lower-casing them, but these tokens cater to HTML tags, which are
+     * insensitive.
+     */
+    public $name;
+
+    /**
+     * Associative array of the tag's attributes.
+     */
+    public $attr = array();
+
+    /**
+     * Non-overloaded constructor, which lower-cases passed tag name.
+     *
+     * @param $name String name.
+     * @param $attr Associative array of attributes.
+     */
+    public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) {
+        $this->name = ctype_lower($name) ? $name : strtolower($name);
+        foreach ($attr as $key => $value) {
+            // normalization only necessary when key is not lowercase
+            if (!ctype_lower($key)) {
+                $new_key = strtolower($key);
+                if (!isset($attr[$new_key])) {
+                    $attr[$new_key] = $attr[$key];
+                }
+                if ($new_key !== $key) {
+                    unset($attr[$key]);
+                }
+            }
+        }
+        $this->attr = $attr;
+        $this->line = $line;
+        $this->col  = $col;
+        $this->armor = $armor;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/Token/Text.php b/application/libraries/htmlpurifier/HTMLPurifier/Token/Text.php
new file mode 100644
index 0000000..82efd82
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/Token/Text.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Concrete text token class.
+ *
+ * Text tokens comprise of regular parsed character data (PCDATA) and raw
+ * character data (from the CDATA sections). Internally, their
+ * data is parsed with all entities expanded. Surprisingly, the text token
+ * does have a "tag name" called #PCDATA, which is how the DTD represents it
+ * in permissible child nodes.
+ */
+class HTMLPurifier_Token_Text extends HTMLPurifier_Token
+{
+
+    public $name = '#PCDATA'; /**< PCDATA tag name compatible with DTD. */
+    public $data; /**< Parsed character data of text. */
+    public $is_whitespace; /**< Bool indicating if node is whitespace. */
+
+    /**
+     * Constructor, accepts data and determines if it is whitespace.
+     *
+     * @param $data String parsed character data.
+     */
+    public function __construct($data, $line = null, $col = null) {
+        $this->data = $data;
+        $this->is_whitespace = ctype_space($data);
+        $this->line = $line;
+        $this->col  = $col;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/TokenFactory.php b/application/libraries/htmlpurifier/HTMLPurifier/TokenFactory.php
new file mode 100644
index 0000000..7cf48fb
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/TokenFactory.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Factory for token generation.
+ *
+ * @note Doing some benchmarking indicates that the new operator is much
+ *       slower than the clone operator (even discounting the cost of the
+ *       constructor).  This class is for that optimization.
+ *       Other then that, there's not much point as we don't
+ *       maintain parallel HTMLPurifier_Token hierarchies (the main reason why
+ *       you'd want to use an abstract factory).
+ * @todo Port DirectLex to use this
+ */
+class HTMLPurifier_TokenFactory
+{
+
+    /**
+     * Prototypes that will be cloned.
+     * @private
+     */
+    // p stands for prototype
+    private $p_start, $p_end, $p_empty, $p_text, $p_comment;
+
+    /**
+     * Generates blank prototypes for cloning.
+     */
+    public function __construct() {
+        $this->p_start  = new HTMLPurifier_Token_Start('', array());
+        $this->p_end    = new HTMLPurifier_Token_End('');
+        $this->p_empty  = new HTMLPurifier_Token_Empty('', array());
+        $this->p_text   = new HTMLPurifier_Token_Text('');
+        $this->p_comment= new HTMLPurifier_Token_Comment('');
+    }
+
+    /**
+     * Creates a HTMLPurifier_Token_Start.
+     * @param $name Tag name
+     * @param $attr Associative array of attributes
+     * @return Generated HTMLPurifier_Token_Start
+     */
+    public function createStart($name, $attr = array()) {
+        $p = clone $this->p_start;
+        $p->__construct($name, $attr);
+        return $p;
+    }
+
+    /**
+     * Creates a HTMLPurifier_Token_End.
+     * @param $name Tag name
+     * @return Generated HTMLPurifier_Token_End
+     */
+    public function createEnd($name) {
+        $p = clone $this->p_end;
+        $p->__construct($name);
+        return $p;
+    }
+
+    /**
+     * Creates a HTMLPurifier_Token_Empty.
+     * @param $name Tag name
+     * @param $attr Associative array of attributes
+     * @return Generated HTMLPurifier_Token_Empty
+     */
+    public function createEmpty($name, $attr = array()) {
+        $p = clone $this->p_empty;
+        $p->__construct($name, $attr);
+        return $p;
+    }
+
+    /**
+     * Creates a HTMLPurifier_Token_Text.
+     * @param $data Data of text token
+     * @return Generated HTMLPurifier_Token_Text
+     */
+    public function createText($data) {
+        $p = clone $this->p_text;
+        $p->__construct($data);
+        return $p;
+    }
+
+    /**
+     * Creates a HTMLPurifier_Token_Comment.
+     * @param $data Data of comment token
+     * @return Generated HTMLPurifier_Token_Comment
+     */
+    public function createComment($data) {
+        $p = clone $this->p_comment;
+        $p->__construct($data);
+        return $p;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URI.php b/application/libraries/htmlpurifier/HTMLPurifier/URI.php
new file mode 100644
index 0000000..f158ef5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URI.php
@@ -0,0 +1,242 @@
+<?php
+
+/**
+ * HTML Purifier's internal representation of a URI.
+ * @note
+ *      Internal data-structures are completely escaped. If the data needs
+ *      to be used in a non-URI context (which is very unlikely), be sure
+ *      to decode it first. The URI may not necessarily be well-formed until
+ *      validate() is called.
+ */
+class HTMLPurifier_URI
+{
+
+    public $scheme, $userinfo, $host, $port, $path, $query, $fragment;
+
+    /**
+     * @note Automatically normalizes scheme and port
+     */
+    public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment) {
+        $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
+        $this->userinfo = $userinfo;
+        $this->host = $host;
+        $this->port = is_null($port) ? $port : (int) $port;
+        $this->path = $path;
+        $this->query = $query;
+        $this->fragment = $fragment;
+    }
+
+    /**
+     * Retrieves a scheme object corresponding to the URI's scheme/default
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     * @return Scheme object appropriate for validating this URI
+     */
+    public function getSchemeObj($config, $context) {
+        $registry = HTMLPurifier_URISchemeRegistry::instance();
+        if ($this->scheme !== null) {
+            $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
+            if (!$scheme_obj) return false; // invalid scheme, clean it out
+        } else {
+            // no scheme: retrieve the default one
+            $def = $config->getDefinition('URI');
+            $scheme_obj = $def->getDefaultScheme($config, $context);
+            if (!$scheme_obj) {
+                // something funky happened to the default scheme object
+                trigger_error(
+                    'Default scheme object "' . $def->defaultScheme . '" was not readable',
+                    E_USER_WARNING
+                );
+                return false;
+            }
+        }
+        return $scheme_obj;
+    }
+
+    /**
+     * Generic validation method applicable for all schemes. May modify
+     * this URI in order to get it into a compliant form.
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     * @return True if validation/filtering succeeds, false if failure
+     */
+    public function validate($config, $context) {
+
+        // ABNF definitions from RFC 3986
+        $chars_sub_delims = '!$&\'()*+,;=';
+        $chars_gen_delims = ':/?#[]@';
+        $chars_pchar = $chars_sub_delims . ':@';
+
+        // validate host
+        if (!is_null($this->host)) {
+            $host_def = new HTMLPurifier_AttrDef_URI_Host();
+            $this->host = $host_def->validate($this->host, $config, $context);
+            if ($this->host === false) $this->host = null;
+        }
+
+        // validate scheme
+        // NOTE: It's not appropriate to check whether or not this
+        // scheme is in our registry, since a URIFilter may convert a
+        // URI that we don't allow into one we do.  So instead, we just
+        // check if the scheme can be dropped because there is no host
+        // and it is our default scheme.
+        if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
+            // support for relative paths is pretty abysmal when the
+            // scheme is present, so axe it when possible
+            $def = $config->getDefinition('URI');
+            if ($def->defaultScheme === $this->scheme) {
+                $this->scheme = null;
+            }
+        }
+
+        // validate username
+        if (!is_null($this->userinfo)) {
+            $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
+            $this->userinfo = $encoder->encode($this->userinfo);
+        }
+
+        // validate port
+        if (!is_null($this->port)) {
+            if ($this->port < 1 || $this->port > 65535) $this->port = null;
+        }
+
+        // validate path
+        $path_parts = array();
+        $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
+        if (!is_null($this->host)) { // this catches $this->host === ''
+            // path-abempty (hier and relative)
+            // http://www.example.com/my/path
+            // //www.example.com/my/path (looks odd, but works, and
+            //                            recognized by most browsers)
+            // (this set is valid or invalid on a scheme by scheme
+            // basis, so we'll deal with it later)
+            // file:///my/path
+            // ///my/path
+            $this->path = $segments_encoder->encode($this->path);
+        } elseif ($this->path !== '') {
+            if ($this->path[0] === '/') {
+                // path-absolute (hier and relative)
+                // http:/my/path
+                // /my/path
+                if (strlen($this->path) >= 2 && $this->path[1] === '/') {
+                    // This could happen if both the host gets stripped
+                    // out
+                    // http://my/path
+                    // //my/path
+                    $this->path = '';
+                } else {
+                    $this->path = $segments_encoder->encode($this->path);
+                }
+            } elseif (!is_null($this->scheme)) {
+                // path-rootless (hier)
+                // http:my/path
+                // Short circuit evaluation means we don't need to check nz
+                $this->path = $segments_encoder->encode($this->path);
+            } else {
+                // path-noscheme (relative)
+                // my/path
+                // (once again, not checking nz)
+                $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
+                $c = strpos($this->path, '/');
+                if ($c !== false) {
+                    $this->path =
+                        $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
+                        $segments_encoder->encode(substr($this->path, $c));
+                } else {
+                    $this->path = $segment_nc_encoder->encode($this->path);
+                }
+            }
+        } else {
+            // path-empty (hier and relative)
+            $this->path = ''; // just to be safe
+        }
+
+        // qf = query and fragment
+        $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
+
+        if (!is_null($this->query)) {
+            $this->query = $qf_encoder->encode($this->query);
+        }
+
+        if (!is_null($this->fragment)) {
+            $this->fragment = $qf_encoder->encode($this->fragment);
+        }
+
+        return true;
+
+    }
+
+    /**
+     * Convert URI back to string
+     * @return String URI appropriate for output
+     */
+    public function toString() {
+        // reconstruct authority
+        $authority = null;
+        // there is a rendering difference between a null authority
+        // (http:foo-bar) and an empty string authority
+        // (http:///foo-bar).
+        if (!is_null($this->host)) {
+            $authority = '';
+            if(!is_null($this->userinfo)) $authority .= $this->userinfo . '@';
+            $authority .= $this->host;
+            if(!is_null($this->port))     $authority .= ':' . $this->port;
+        }
+
+        // Reconstruct the result
+        // One might wonder about parsing quirks from browsers after
+        // this reconstruction.  Unfortunately, parsing behavior depends
+        // on what *scheme* was employed (file:///foo is handled *very*
+        // differently than http:///foo), so unfortunately we have to
+        // defer to the schemes to do the right thing.
+        $result = '';
+        if (!is_null($this->scheme))    $result .= $this->scheme . ':';
+        if (!is_null($authority))       $result .=  '//' . $authority;
+        $result .= $this->path;
+        if (!is_null($this->query))     $result .= '?' . $this->query;
+        if (!is_null($this->fragment))  $result .= '#' . $this->fragment;
+
+        return $result;
+    }
+
+    /**
+     * Returns true if this URL might be considered a 'local' URL given
+     * the current context.  This is true when the host is null, or
+     * when it matches the host supplied to the configuration.
+     *
+     * Note that this does not do any scheme checking, so it is mostly
+     * only appropriate for metadata that doesn't care about protocol
+     * security.  isBenign is probably what you actually want.
+     */
+    public function isLocal($config, $context) {
+        if ($this->host === null) return true;
+        $uri_def = $config->getDefinition('URI');
+        if ($uri_def->host === $this->host) return true;
+        return false;
+    }
+
+    /**
+     * Returns true if this URL should be considered a 'benign' URL,
+     * that is:
+     *
+     *      - It is a local URL (isLocal), and
+     *      - It has a equal or better level of security
+     */
+    public function isBenign($config, $context) {
+        if (!$this->isLocal($config, $context)) return false;
+
+        $scheme_obj = $this->getSchemeObj($config, $context);
+        if (!$scheme_obj) return false; // conservative approach
+
+        $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
+        if ($current_scheme_obj->secure) {
+            if (!$scheme_obj->secure) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIDefinition.php b/application/libraries/htmlpurifier/HTMLPurifier/URIDefinition.php
new file mode 100644
index 0000000..4dbde80
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIDefinition.php
@@ -0,0 +1,103 @@
+<?php
+
+class HTMLPurifier_URIDefinition extends HTMLPurifier_Definition
+{
+
+    public $type = 'URI';
+    protected $filters = array();
+    protected $postFilters = array();
+    protected $registeredFilters = array();
+
+    /**
+     * HTMLPurifier_URI object of the base specified at %URI.Base
+     */
+    public $base;
+
+    /**
+     * String host to consider "home" base, derived off of $base
+     */
+    public $host;
+
+    /**
+     * Name of default scheme based on %URI.DefaultScheme and %URI.Base
+     */
+    public $defaultScheme;
+
+    public function __construct() {
+        $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternal());
+        $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources());
+        $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources());
+        $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist());
+        $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe());
+        $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute());
+        $this->registerFilter(new HTMLPurifier_URIFilter_Munge());
+    }
+
+    public function registerFilter($filter) {
+        $this->registeredFilters[$filter->name] = $filter;
+    }
+
+    public function addFilter($filter, $config) {
+        $r = $filter->prepare($config);
+        if ($r === false) return; // null is ok, for backwards compat
+        if ($filter->post) {
+            $this->postFilters[$filter->name] = $filter;
+        } else {
+            $this->filters[$filter->name] = $filter;
+        }
+    }
+
+    protected function doSetup($config) {
+        $this->setupMemberVariables($config);
+        $this->setupFilters($config);
+    }
+
+    protected function setupFilters($config) {
+        foreach ($this->registeredFilters as $name => $filter) {
+            if ($filter->always_load) {
+                $this->addFilter($filter, $config);
+            } else {
+                $conf = $config->get('URI.' . $name);
+                if ($conf !== false && $conf !== null) {
+                    $this->addFilter($filter, $config);
+                }
+            }
+        }
+        unset($this->registeredFilters);
+    }
+
+    protected function setupMemberVariables($config) {
+        $this->host = $config->get('URI.Host');
+        $base_uri = $config->get('URI.Base');
+        if (!is_null($base_uri)) {
+            $parser = new HTMLPurifier_URIParser();
+            $this->base = $parser->parse($base_uri);
+            $this->defaultScheme = $this->base->scheme;
+            if (is_null($this->host)) $this->host = $this->base->host;
+        }
+        if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme');
+    }
+
+    public function getDefaultScheme($config, $context) {
+        return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context);
+    }
+
+    public function filter(&$uri, $config, $context) {
+        foreach ($this->filters as $name => $f) {
+            $result = $f->filter($uri, $config, $context);
+            if (!$result) return false;
+        }
+        return true;
+    }
+
+    public function postFilter(&$uri, $config, $context) {
+        foreach ($this->postFilters as $name => $f) {
+            $result = $f->filter($uri, $config, $context);
+            if (!$result) return false;
+        }
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIFilter.php b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter.php
new file mode 100644
index 0000000..6a1b0b0
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Chainable filters for custom URI processing.
+ *
+ * These filters can perform custom actions on a URI filter object,
+ * including transformation or blacklisting.  A filter named Foo
+ * must have a corresponding configuration directive %URI.Foo,
+ * unless always_load is specified to be true.
+ *
+ * The following contexts may be available while URIFilters are being
+ * processed:
+ *
+ *      - EmbeddedURI: true if URI is an embedded resource that will
+ *        be loaded automatically on page load
+ *      - CurrentToken: a reference to the token that is currently
+ *        being processed
+ *      - CurrentAttr: the name of the attribute that is currently being
+ *        processed
+ *      - CurrentCSSProperty: the name of the CSS property that is
+ *        currently being processed (if applicable)
+ *
+ * @warning This filter is called before scheme object validation occurs.
+ *          Make sure, if you require a specific scheme object, you
+ *          you check that it exists. This allows filters to convert
+ *          proprietary URI schemes into regular ones.
+ */
+abstract class HTMLPurifier_URIFilter
+{
+
+    /**
+     * Unique identifier of filter
+     */
+    public $name;
+
+    /**
+     * True if this filter should be run after scheme validation.
+     */
+    public $post = false;
+
+    /**
+     * True if this filter should always be loaded (this permits
+     * a filter to be named Foo without the corresponding %URI.Foo
+     * directive existing.)
+     */
+    public $always_load = false;
+
+    /**
+     * Performs initialization for the filter.  If the filter returns
+     * false, this means that it shouldn't be considered active.
+     */
+    public function prepare($config) {return true;}
+
+    /**
+     * Filter a URI object
+     * @param $uri Reference to URI object variable
+     * @param $config Instance of HTMLPurifier_Config
+     * @param $context Instance of HTMLPurifier_Context
+     * @return bool Whether or not to continue processing: false indicates
+     *         URL is no good, true indicates continue processing. Note that
+     *         all changes are committed directly on the URI object
+     */
+    abstract public function filter(&$uri, $config, $context);
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableExternal.php b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableExternal.php
new file mode 100644
index 0000000..d8a39a5
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableExternal.php
@@ -0,0 +1,23 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter
+{
+    public $name = 'DisableExternal';
+    protected $ourHostParts = false;
+    public function prepare($config) {
+        $our_host = $config->getDefinition('URI')->host;
+        if ($our_host !== null) $this->ourHostParts = array_reverse(explode('.', $our_host));
+    }
+    public function filter(&$uri, $config, $context) {
+        if (is_null($uri->host)) return true;
+        if ($this->ourHostParts === false) return false;
+        $host_parts = array_reverse(explode('.', $uri->host));
+        foreach ($this->ourHostParts as $i => $x) {
+            if (!isset($host_parts[$i])) return false;
+            if ($host_parts[$i] != $this->ourHostParts[$i]) return false;
+        }
+        return true;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableExternalResources.php b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableExternalResources.php
new file mode 100644
index 0000000..881abc4
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableExternalResources.php
@@ -0,0 +1,12 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal
+{
+    public $name = 'DisableExternalResources';
+    public function filter(&$uri, $config, $context) {
+        if (!$context->get('EmbeddedURI', true)) return true;
+        return parent::filter($uri, $config, $context);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableResources.php b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableResources.php
new file mode 100644
index 0000000..67538c7
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/DisableResources.php
@@ -0,0 +1,11 @@
+<?php
+
+class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter
+{
+    public $name = 'DisableResources';
+    public function filter(&$uri, $config, $context) {
+        return !$context->get('EmbeddedURI', true);
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/HostBlacklist.php b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/HostBlacklist.php
new file mode 100644
index 0000000..55fde3b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/HostBlacklist.php
@@ -0,0 +1,25 @@
+<?php
+
+// It's not clear to me whether or not Punycode means that hostnames
+// do not have canonical forms anymore. As far as I can tell, it's
+// not a problem (punycoding should be identity when no Unicode
+// points are involved), but I'm not 100% sure
+class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter
+{
+    public $name = 'HostBlacklist';
+    protected $blacklist = array();
+    public function prepare($config) {
+        $this->blacklist = $config->get('URI.HostBlacklist');
+        return true;
+    }
+    public function filter(&$uri, $config, $context) {
+        foreach($this->blacklist as $blacklisted_host_fragment) {
+            if (strpos($uri->host, $blacklisted_host_fragment) !== false) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/MakeAbsolute.php b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/MakeAbsolute.php
new file mode 100644
index 0000000..f46ab26
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/MakeAbsolute.php
@@ -0,0 +1,114 @@
+<?php
+
+// does not support network paths
+
+class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter
+{
+    public $name = 'MakeAbsolute';
+    protected $base;
+    protected $basePathStack = array();
+    public function prepare($config) {
+        $def = $config->getDefinition('URI');
+        $this->base = $def->base;
+        if (is_null($this->base)) {
+            trigger_error('URI.MakeAbsolute is being ignored due to lack of value for URI.Base configuration', E_USER_WARNING);
+            return false;
+        }
+        $this->base->fragment = null; // fragment is invalid for base URI
+        $stack = explode('/', $this->base->path);
+        array_pop($stack); // discard last segment
+        $stack = $this->_collapseStack($stack); // do pre-parsing
+        $this->basePathStack = $stack;
+        return true;
+    }
+    public function filter(&$uri, $config, $context) {
+        if (is_null($this->base)) return true; // abort early
+        if (
+            $uri->path === '' && is_null($uri->scheme) &&
+            is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)
+        ) {
+            // reference to current document
+            $uri = clone $this->base;
+            return true;
+        }
+        if (!is_null($uri->scheme)) {
+            // absolute URI already: don't change
+            if (!is_null($uri->host)) return true;
+            $scheme_obj = $uri->getSchemeObj($config, $context);
+            if (!$scheme_obj) {
+                // scheme not recognized
+                return false;
+            }
+            if (!$scheme_obj->hierarchical) {
+                // non-hierarchal URI with explicit scheme, don't change
+                return true;
+            }
+            // special case: had a scheme but always is hierarchical and had no authority
+        }
+        if (!is_null($uri->host)) {
+            // network path, don't bother
+            return true;
+        }
+        if ($uri->path === '') {
+            $uri->path = $this->base->path;
+        } elseif ($uri->path[0] !== '/') {
+            // relative path, needs more complicated processing
+            $stack = explode('/', $uri->path);
+            $new_stack = array_merge($this->basePathStack, $stack);
+            if ($new_stack[0] !== '' && !is_null($this->base->host)) {
+                array_unshift($new_stack, '');
+            }
+            $new_stack = $this->_collapseStack($new_stack);
+            $uri->path = implode('/', $new_stack);
+        } else {
+            // absolute path, but still we should collapse
+            $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path)));
+        }
+        // re-combine
+        $uri->scheme = $this->base->scheme;
+        if (is_null($uri->userinfo)) $uri->userinfo = $this->base->userinfo;
+        if (is_null($uri->host))     $uri->host     = $this->base->host;
+        if (is_null($uri->port))     $uri->port     = $this->base->port;
+        return true;
+    }
+
+    /**
+     * Resolve dots and double-dots in a path stack
+     */
+    private function _collapseStack($stack) {
+        $result = array();
+        $is_folder = false;
+        for ($i = 0; isset($stack[$i]); $i++) {
+            $is_folder = false;
+            // absorb an internally duplicated slash
+            if ($stack[$i] == '' && $i && isset($stack[$i+1])) continue;
+            if ($stack[$i] == '..') {
+                if (!empty($result)) {
+                    $segment = array_pop($result);
+                    if ($segment === '' && empty($result)) {
+                        // error case: attempted to back out too far:
+                        // restore the leading slash
+                        $result[] = '';
+                    } elseif ($segment === '..') {
+                        $result[] = '..'; // cannot remove .. with ..
+                    }
+                } else {
+                    // relative path, preserve the double-dots
+                    $result[] = '..';
+                }
+                $is_folder = true;
+                continue;
+            }
+            if ($stack[$i] == '.') {
+                // silently absorb
+                $is_folder = true;
+                continue;
+            }
+            $result[] = $stack[$i];
+        }
+        if ($is_folder) $result[] = '';
+        return $result;
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/Munge.php b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/Munge.php
new file mode 100644
index 0000000..de695df
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/Munge.php
@@ -0,0 +1,53 @@
+<?php
+
+class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter
+{
+    public $name = 'Munge';
+    public $post = true;
+    private $target, $parser, $doEmbed, $secretKey;
+
+    protected $replace = array();
+
+    public function prepare($config) {
+        $this->target    = $config->get('URI.' . $this->name);
+        $this->parser    = new HTMLPurifier_URIParser();
+        $this->doEmbed   = $config->get('URI.MungeResources');
+        $this->secretKey = $config->get('URI.MungeSecretKey');
+        return true;
+    }
+    public function filter(&$uri, $config, $context) {
+        if ($context->get('EmbeddedURI', true) && !$this->doEmbed) return true;
+
+        $scheme_obj = $uri->getSchemeObj($config, $context);
+        if (!$scheme_obj) return true; // ignore unknown schemes, maybe another postfilter did it
+        if (!$scheme_obj->browsable) return true; // ignore non-browseable schemes, since we can't munge those in a reasonable way
+        if ($uri->isBenign($config, $context)) return true; // don't redirect if a benign URL
+
+        $this->makeReplace($uri, $config, $context);
+        $this->replace = array_map('rawurlencode', $this->replace);
+
+        $new_uri = strtr($this->target, $this->replace);
+        $new_uri = $this->parser->parse($new_uri);
+        // don't redirect if the target host is the same as the
+        // starting host
+        if ($uri->host === $new_uri->host) return true;
+        $uri = $new_uri; // overwrite
+        return true;
+    }
+
+    protected function makeReplace($uri, $config, $context) {
+        $string = $uri->toString();
+        // always available
+        $this->replace['%s'] = $string;
+        $this->replace['%r'] = $context->get('EmbeddedURI', true);
+        $token = $context->get('CurrentToken', true);
+        $this->replace['%n'] = $token ? $token->name : null;
+        $this->replace['%m'] = $context->get('CurrentAttr', true);
+        $this->replace['%p'] = $context->get('CurrentCSSProperty', true);
+        // not always available
+        if ($this->secretKey) $this->replace['%t'] = sha1($this->secretKey . ':' . $string);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/SafeIframe.php b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/SafeIframe.php
new file mode 100644
index 0000000..284bb13
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIFilter/SafeIframe.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * Implements safety checks for safe iframes.
+ *
+ * @warning This filter is *critical* for ensuring that %HTML.SafeIframe
+ * works safely.
+ */
+class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter
+{
+    public $name = 'SafeIframe';
+    public $always_load = true;
+    protected $regexp = NULL;
+    // XXX: The not so good bit about how this is all setup now is we
+    // can't check HTML.SafeIframe in the 'prepare' step: we have to
+    // defer till the actual filtering.
+    public function prepare($config) {
+        $this->regexp = $config->get('URI.SafeIframeRegexp');
+        return true;
+    }
+    public function filter(&$uri, $config, $context) {
+        // check if filter not applicable
+        if (!$config->get('HTML.SafeIframe')) return true;
+        // check if the filter should actually trigger
+        if (!$context->get('EmbeddedURI', true)) return true;
+        $token = $context->get('CurrentToken', true);
+        if (!($token && $token->name == 'iframe')) return true;
+        // check if we actually have some whitelists enabled
+        if ($this->regexp === null) return false;
+        // actually check the whitelists
+        return preg_match($this->regexp, $uri->toString());
+    }
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIParser.php b/application/libraries/htmlpurifier/HTMLPurifier/URIParser.php
new file mode 100644
index 0000000..7179e4a
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIParser.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Parses a URI into the components and fragment identifier as specified
+ * by RFC 3986.
+ */
+class HTMLPurifier_URIParser
+{
+
+    /**
+     * Instance of HTMLPurifier_PercentEncoder to do normalization with.
+     */
+    protected $percentEncoder;
+
+    public function __construct() {
+        $this->percentEncoder = new HTMLPurifier_PercentEncoder();
+    }
+
+    /**
+     * Parses a URI.
+     * @param $uri string URI to parse
+     * @return HTMLPurifier_URI representation of URI. This representation has
+     *         not been validated yet and may not conform to RFC.
+     */
+    public function parse($uri) {
+
+        $uri = $this->percentEncoder->normalize($uri);
+
+        // Regexp is as per Appendix B.
+        // Note that ["<>] are an addition to the RFC's recommended
+        // characters, because they represent external delimeters.
+        $r_URI = '!'.
+            '(([^:/?#"<>]+):)?'. // 2. Scheme
+            '(//([^/?#"<>]*))?'. // 4. Authority
+            '([^?#"<>]*)'.       // 5. Path
+            '(\?([^#"<>]*))?'.   // 7. Query
+            '(#([^"<>]*))?'.     // 8. Fragment
+            '!';
+
+        $matches = array();
+        $result = preg_match($r_URI, $uri, $matches);
+
+        if (!$result) return false; // *really* invalid URI
+
+        // seperate out parts
+        $scheme     = !empty($matches[1]) ? $matches[2] : null;
+        $authority  = !empty($matches[3]) ? $matches[4] : null;
+        $path       = $matches[5]; // always present, can be empty
+        $query      = !empty($matches[6]) ? $matches[7] : null;
+        $fragment   = !empty($matches[8]) ? $matches[9] : null;
+
+        // further parse authority
+        if ($authority !== null) {
+            $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/";
+            $matches = array();
+            preg_match($r_authority, $authority, $matches);
+            $userinfo   = !empty($matches[1]) ? $matches[2] : null;
+            $host       = !empty($matches[3]) ? $matches[3] : '';
+            $port       = !empty($matches[4]) ? (int) $matches[5] : null;
+        } else {
+            $port = $host = $userinfo = null;
+        }
+
+        return new HTMLPurifier_URI(
+            $scheme, $userinfo, $host, $port, $path, $query, $fragment);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme.php
new file mode 100644
index 0000000..7be9581
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * Validator for the components of a URI for a specific scheme
+ */
+abstract class HTMLPurifier_URIScheme
+{
+
+    /**
+     * Scheme's default port (integer).  If an explicit port number is
+     * specified that coincides with the default port, it will be
+     * elided.
+     */
+    public $default_port = null;
+
+    /**
+     * Whether or not URIs of this schem are locatable by a browser
+     * http and ftp are accessible, while mailto and news are not.
+     */
+    public $browsable = false;
+
+    /**
+     * Whether or not data transmitted over this scheme is encrypted.
+     * https is secure, http is not.
+     */
+    public $secure = false;
+
+    /**
+     * Whether or not the URI always uses <hier_part>, resolves edge cases
+     * with making relative URIs absolute
+     */
+    public $hierarchical = false;
+
+    /**
+     * Whether or not the URI may omit a hostname when the scheme is
+     * explicitly specified, ala file:///path/to/file. As of writing,
+     * 'file' is the only scheme that browsers support his properly.
+     */
+    public $may_omit_host = false;
+
+    /**
+     * Validates the components of a URI for a specific scheme.
+     * @param $uri Reference to a HTMLPurifier_URI object
+     * @param $config HTMLPurifier_Config object
+     * @param $context HTMLPurifier_Context object
+     * @return Bool success or failure
+     */
+    public abstract function doValidate(&$uri, $config, $context);
+
+    /**
+     * Public interface for validating components of a URI.  Performs a
+     * bunch of default actions. Don't overload this method.
+     * @param $uri Reference to a HTMLPurifier_URI object
+     * @param $config HTMLPurifier_Config object
+     * @param $context HTMLPurifier_Context object
+     * @return Bool success or failure
+     */
+    public function validate(&$uri, $config, $context) {
+        if ($this->default_port == $uri->port) $uri->port = null;
+        // kludge: browsers do funny things when the scheme but not the
+        // authority is set
+        if (!$this->may_omit_host &&
+            // if the scheme is present, a missing host is always in error
+            (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) ||
+            // if the scheme is not present, a *blank* host is in error,
+            // since this translates into '///path' which most browsers
+            // interpret as being 'http://path'.
+             (is_null($uri->scheme) && $uri->host === '')
+        ) {
+            do {
+                if (is_null($uri->scheme)) {
+                    if (substr($uri->path, 0, 2) != '//') {
+                        $uri->host = null;
+                        break;
+                    }
+                    // URI is '////path', so we cannot nullify the
+                    // host to preserve semantics.  Try expanding the
+                    // hostname instead (fall through)
+                }
+                // first see if we can manually insert a hostname
+                $host = $config->get('URI.Host');
+                if (!is_null($host)) {
+                    $uri->host = $host;
+                } else {
+                    // we can't do anything sensible, reject the URL.
+                    return false;
+                }
+            } while (false);
+        }
+        return $this->doValidate($uri, $config, $context);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/data.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/data.php
new file mode 100644
index 0000000..ab56a3e
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/data.php
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * Implements data: URI for base64 encoded images supported by GD.
+ */
+class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme {
+
+    public $browsable = true;
+    public $allowed_types = array(
+        // you better write validation code for other types if you
+        // decide to allow them
+        'image/jpeg' => true,
+        'image/gif' => true,
+        'image/png' => true,
+        );
+    // this is actually irrelevant since we only write out the path
+    // component
+    public $may_omit_host = true;
+
+    public function doValidate(&$uri, $config, $context) {
+        $result = explode(',', $uri->path, 2);
+        $is_base64 = false;
+        $charset = null;
+        $content_type = null;
+        if (count($result) == 2) {
+            list($metadata, $data) = $result;
+            // do some legwork on the metadata
+            $metas = explode(';', $metadata);
+            while(!empty($metas)) {
+                $cur = array_shift($metas);
+                if ($cur == 'base64') {
+                    $is_base64 = true;
+                    break;
+                }
+                if (substr($cur, 0, 8) == 'charset=') {
+                    // doesn't match if there are arbitrary spaces, but
+                    // whatever dude
+                    if ($charset !== null) continue; // garbage
+                    $charset = substr($cur, 8); // not used
+                } else {
+                    if ($content_type !== null) continue; // garbage
+                    $content_type = $cur;
+                }
+            }
+        } else {
+            $data = $result[0];
+        }
+        if ($content_type !== null && empty($this->allowed_types[$content_type])) {
+            return false;
+        }
+        if ($charset !== null) {
+            // error; we don't allow plaintext stuff
+            $charset = null;
+        }
+        $data = rawurldecode($data);
+        if ($is_base64) {
+            $raw_data = base64_decode($data);
+        } else {
+            $raw_data = $data;
+        }
+        // XXX probably want to refactor this into a general mechanism
+        // for filtering arbitrary content types
+        $file = tempnam("/tmp", "");
+        file_put_contents($file, $raw_data);
+        if (function_exists('exif_imagetype')) {
+            $image_code = exif_imagetype($file);
+            unlink($file);
+        } elseif (function_exists('getimagesize')) {
+            set_error_handler(array($this, 'muteErrorHandler'));
+            $info = getimagesize($file);
+            restore_error_handler();
+            unlink($file);
+            if ($info == false) return false;
+            $image_code = $info[2];
+        } else {
+            trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR);
+        }
+        $real_content_type = image_type_to_mime_type($image_code);
+        if ($real_content_type != $content_type) {
+            // we're nice guys; if the content type is something else we
+            // support, change it over
+            if (empty($this->allowed_types[$real_content_type])) return false;
+            $content_type = $real_content_type;
+        }
+        // ok, it's kosher, rewrite what we need
+        $uri->userinfo = null;
+        $uri->host = null;
+        $uri->port = null;
+        $uri->fragment = null;
+        $uri->query = null;
+        $uri->path = "$content_type;base64," . base64_encode($raw_data);
+        return true;
+    }
+
+    public function muteErrorHandler($errno, $errstr) {}
+
+}
+
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/file.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/file.php
new file mode 100644
index 0000000..d74a3f1
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/file.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Validates file as defined by RFC 1630 and RFC 1738.
+ */
+class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme {
+
+    // Generally file:// URLs are not accessible from most
+    // machines, so placing them as an img src is incorrect.
+    public $browsable = false;
+
+    // Basically the *only* URI scheme for which this is true, since
+    // accessing files on the local machine is very common.  In fact,
+    // browsers on some operating systems don't understand the
+    // authority, though I hear it is used on Windows to refer to
+    // network shares.
+    public $may_omit_host = true;
+
+    public function doValidate(&$uri, $config, $context) {
+        // Authentication method is not supported
+        $uri->userinfo = null;
+        // file:// makes no provisions for accessing the resource
+        $uri->port     = null;
+        // While it seems to work on Firefox, the querystring has
+        // no possible effect and is thus stripped.
+        $uri->query    = null;
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/ftp.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/ftp.php
new file mode 100644
index 0000000..0fb2abf
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/ftp.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738.
+ */
+class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme {
+
+    public $default_port = 21;
+    public $browsable = true; // usually
+    public $hierarchical = true;
+
+    public function doValidate(&$uri, $config, $context) {
+        $uri->query    = null;
+
+        // typecode check
+        $semicolon_pos = strrpos($uri->path, ';'); // reverse
+        if ($semicolon_pos !== false) {
+            $type = substr($uri->path, $semicolon_pos + 1); // no semicolon
+            $uri->path = substr($uri->path, 0, $semicolon_pos);
+            $type_ret = '';
+            if (strpos($type, '=') !== false) {
+                // figure out whether or not the declaration is correct
+                list($key, $typecode) = explode('=', $type, 2);
+                if ($key !== 'type') {
+                    // invalid key, tack it back on encoded
+                    $uri->path .= '%3B' . $type;
+                } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') {
+                    $type_ret = ";type=$typecode";
+                }
+            } else {
+                $uri->path .= '%3B' . $type;
+            }
+            $uri->path = str_replace(';', '%3B', $uri->path);
+            $uri->path .= $type_ret;
+        }
+
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/http.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/http.php
new file mode 100644
index 0000000..959b8da
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/http.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Validates http (HyperText Transfer Protocol) as defined by RFC 2616
+ */
+class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme {
+
+    public $default_port = 80;
+    public $browsable = true;
+    public $hierarchical = true;
+
+    public function doValidate(&$uri, $config, $context) {
+        $uri->userinfo = null;
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/https.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/https.php
new file mode 100644
index 0000000..159c287
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/https.php
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * Validates https (Secure HTTP) according to http scheme.
+ */
+class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http {
+
+    public $default_port = 443;
+    public $secure = true;
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/mailto.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/mailto.php
new file mode 100644
index 0000000..9db4cb2
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/mailto.php
@@ -0,0 +1,27 @@
+<?php
+
+// VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the
+// email is valid, but be careful!
+
+/**
+ * Validates mailto (for E-mail) according to RFC 2368
+ * @todo Validate the email address
+ * @todo Filter allowed query parameters
+ */
+
+class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme {
+
+    public $browsable = false;
+    public $may_omit_host = true;
+
+    public function doValidate(&$uri, $config, $context) {
+        $uri->userinfo = null;
+        $uri->host     = null;
+        $uri->port     = null;
+        // we need to validate path against RFC 2368's addr-spec
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/news.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/news.php
new file mode 100644
index 0000000..84a6748
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/news.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Validates news (Usenet) as defined by generic RFC 1738
+ */
+class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme {
+
+    public $browsable = false;
+    public $may_omit_host = true;
+
+    public function doValidate(&$uri, $config, $context) {
+        $uri->userinfo = null;
+        $uri->host     = null;
+        $uri->port     = null;
+        $uri->query    = null;
+        // typecode check needed on path
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/nntp.php b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/nntp.php
new file mode 100644
index 0000000..4ccea0d
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URIScheme/nntp.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738
+ */
+class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme {
+
+    public $default_port = 119;
+    public $browsable = false;
+
+    public function doValidate(&$uri, $config, $context) {
+        $uri->userinfo = null;
+        $uri->query    = null;
+        return true;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/URISchemeRegistry.php b/application/libraries/htmlpurifier/HTMLPurifier/URISchemeRegistry.php
new file mode 100644
index 0000000..576bf7b
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/URISchemeRegistry.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Registry for retrieving specific URI scheme validator objects.
+ */
+class HTMLPurifier_URISchemeRegistry
+{
+
+    /**
+     * Retrieve sole instance of the registry.
+     * @param $prototype Optional prototype to overload sole instance with,
+     *                   or bool true to reset to default registry.
+     * @note Pass a registry object $prototype with a compatible interface and
+     *       the function will copy it and return it all further times.
+     */
+    public static function instance($prototype = null) {
+        static $instance = null;
+        if ($prototype !== null) {
+            $instance = $prototype;
+        } elseif ($instance === null || $prototype == true) {
+            $instance = new HTMLPurifier_URISchemeRegistry();
+        }
+        return $instance;
+    }
+
+    /**
+     * Cache of retrieved schemes.
+     */
+    protected $schemes = array();
+
+    /**
+     * Retrieves a scheme validator object
+     * @param $scheme String scheme name like http or mailto
+     * @param $config HTMLPurifier_Config object
+     * @param $config HTMLPurifier_Context object
+     */
+    public function getScheme($scheme, $config, $context) {
+        if (!$config) $config = HTMLPurifier_Config::createDefault();
+
+        // important, otherwise attacker could include arbitrary file
+        $allowed_schemes = $config->get('URI.AllowedSchemes');
+        if (!$config->get('URI.OverrideAllowedSchemes') &&
+            !isset($allowed_schemes[$scheme])
+        ) {
+            return;
+        }
+
+        if (isset($this->schemes[$scheme])) return $this->schemes[$scheme];
+        if (!isset($allowed_schemes[$scheme])) return;
+
+        $class = 'HTMLPurifier_URIScheme_' . $scheme;
+        if (!class_exists($class)) return;
+        $this->schemes[$scheme] = new $class();
+        return $this->schemes[$scheme];
+    }
+
+    /**
+     * Registers a custom scheme to the cache, bypassing reflection.
+     * @param $scheme Scheme name
+     * @param $scheme_obj HTMLPurifier_URIScheme object
+     */
+    public function register($scheme, $scheme_obj) {
+        $this->schemes[$scheme] = $scheme_obj;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/UnitConverter.php b/application/libraries/htmlpurifier/HTMLPurifier/UnitConverter.php
new file mode 100644
index 0000000..545d426
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/UnitConverter.php
@@ -0,0 +1,254 @@
+<?php
+
+/**
+ * Class for converting between different unit-lengths as specified by
+ * CSS.
+ */
+class HTMLPurifier_UnitConverter
+{
+
+    const ENGLISH = 1;
+    const METRIC = 2;
+    const DIGITAL = 3;
+
+    /**
+     * Units information array. Units are grouped into measuring systems
+     * (English, Metric), and are assigned an integer representing
+     * the conversion factor between that unit and the smallest unit in
+     * the system. Numeric indexes are actually magical constants that
+     * encode conversion data from one system to the next, with a O(n^2)
+     * constraint on memory (this is generally not a problem, since
+     * the number of measuring systems is small.)
+     */
+    protected static $units = array(
+        self::ENGLISH => array(
+            'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary
+            'pt' => 4,
+            'pc' => 48,
+            'in' => 288,
+            self::METRIC => array('pt', '0.352777778', 'mm'),
+        ),
+        self::METRIC => array(
+            'mm' => 1,
+            'cm' => 10,
+            self::ENGLISH => array('mm', '2.83464567', 'pt'),
+        ),
+    );
+
+    /**
+     * Minimum bcmath precision for output.
+     */
+    protected $outputPrecision;
+
+    /**
+     * Bcmath precision for internal calculations.
+     */
+    protected $internalPrecision;
+
+    /**
+     * Whether or not BCMath is available
+     */
+    private $bcmath;
+
+    public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false) {
+        $this->outputPrecision = $output_precision;
+        $this->internalPrecision = $internal_precision;
+        $this->bcmath = !$force_no_bcmath && function_exists('bcmul');
+    }
+
+    /**
+     * Converts a length object of one unit into another unit.
+     * @param HTMLPurifier_Length $length
+     *      Instance of HTMLPurifier_Length to convert. You must validate()
+     *      it before passing it here!
+     * @param string $to_unit
+     *      Unit to convert to.
+     * @note
+     *      About precision: This conversion function pays very special
+     *      attention to the incoming precision of values and attempts
+     *      to maintain a number of significant figure. Results are
+     *      fairly accurate up to nine digits. Some caveats:
+     *          - If a number is zero-padded as a result of this significant
+     *            figure tracking, the zeroes will be eliminated.
+     *          - If a number contains less than four sigfigs ($outputPrecision)
+     *            and this causes some decimals to be excluded, those
+     *            decimals will be added on.
+     */
+    public function convert($length, $to_unit) {
+
+        if (!$length->isValid()) return false;
+
+        $n    = $length->getN();
+        $unit = $length->getUnit();
+
+        if ($n === '0' || $unit === false) {
+            return new HTMLPurifier_Length('0', false);
+        }
+
+        $state = $dest_state = false;
+        foreach (self::$units as $k => $x) {
+            if (isset($x[$unit])) $state = $k;
+            if (isset($x[$to_unit])) $dest_state = $k;
+        }
+        if (!$state || !$dest_state) return false;
+
+        // Some calculations about the initial precision of the number;
+        // this will be useful when we need to do final rounding.
+        $sigfigs = $this->getSigFigs($n);
+        if ($sigfigs < $this->outputPrecision) $sigfigs = $this->outputPrecision;
+
+        // BCMath's internal precision deals only with decimals. Use
+        // our default if the initial number has no decimals, or increase
+        // it by how ever many decimals, thus, the number of guard digits
+        // will always be greater than or equal to internalPrecision.
+        $log = (int) floor(log(abs($n), 10));
+        $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision
+
+        for ($i = 0; $i < 2; $i++) {
+
+            // Determine what unit IN THIS SYSTEM we need to convert to
+            if ($dest_state === $state) {
+                // Simple conversion
+                $dest_unit = $to_unit;
+            } else {
+                // Convert to the smallest unit, pending a system shift
+                $dest_unit = self::$units[$state][$dest_state][0];
+            }
+
+            // Do the conversion if necessary
+            if ($dest_unit !== $unit) {
+                $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp);
+                $n = $this->mul($n, $factor, $cp);
+                $unit = $dest_unit;
+            }
+
+            // Output was zero, so bail out early. Shouldn't ever happen.
+            if ($n === '') {
+                $n = '0';
+                $unit = $to_unit;
+                break;
+            }
+
+            // It was a simple conversion, so bail out
+            if ($dest_state === $state) {
+                break;
+            }
+
+            if ($i !== 0) {
+                // Conversion failed! Apparently, the system we forwarded
+                // to didn't have this unit. This should never happen!
+                return false;
+            }
+
+            // Pre-condition: $i == 0
+
+            // Perform conversion to next system of units
+            $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp);
+            $unit = self::$units[$state][$dest_state][2];
+            $state = $dest_state;
+
+            // One more loop around to convert the unit in the new system.
+
+        }
+
+        // Post-condition: $unit == $to_unit
+        if ($unit !== $to_unit) return false;
+
+        // Useful for debugging:
+        //echo "<pre>n";
+        //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n</pre>\n";
+
+        $n = $this->round($n, $sigfigs);
+        if (strpos($n, '.') !== false) $n = rtrim($n, '0');
+        $n = rtrim($n, '.');
+
+        return new HTMLPurifier_Length($n, $unit);
+    }
+
+    /**
+     * Returns the number of significant figures in a string number.
+     * @param string $n Decimal number
+     * @return int number of sigfigs
+     */
+    public function getSigFigs($n) {
+        $n = ltrim($n, '0+-');
+        $dp = strpos($n, '.'); // decimal position
+        if ($dp === false) {
+            $sigfigs = strlen(rtrim($n, '0'));
+        } else {
+            $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character
+            if ($dp !== 0) $sigfigs--;
+        }
+        return $sigfigs;
+    }
+
+    /**
+     * Adds two numbers, using arbitrary precision when available.
+     */
+    private function add($s1, $s2, $scale) {
+        if ($this->bcmath) return bcadd($s1, $s2, $scale);
+        else return $this->scale($s1 + $s2, $scale);
+    }
+
+    /**
+     * Multiples two numbers, using arbitrary precision when available.
+     */
+    private function mul($s1, $s2, $scale) {
+        if ($this->bcmath) return bcmul($s1, $s2, $scale);
+        else return $this->scale($s1 * $s2, $scale);
+    }
+
+    /**
+     * Divides two numbers, using arbitrary precision when available.
+     */
+    private function div($s1, $s2, $scale) {
+        if ($this->bcmath) return bcdiv($s1, $s2, $scale);
+        else return $this->scale($s1 / $s2, $scale);
+    }
+
+    /**
+     * Rounds a number according to the number of sigfigs it should have,
+     * using arbitrary precision when available.
+     */
+    private function round($n, $sigfigs) {
+        $new_log = (int) floor(log(abs($n), 10)); // Number of digits left of decimal - 1
+        $rp = $sigfigs - $new_log - 1; // Number of decimal places needed
+        $neg = $n < 0 ? '-' : ''; // Negative sign
+        if ($this->bcmath) {
+            if ($rp >= 0) {
+                $n = bcadd($n, $neg . '0.' .  str_repeat('0', $rp) . '5', $rp + 1);
+                $n = bcdiv($n, '1', $rp);
+            } else {
+                // This algorithm partially depends on the standardized
+                // form of numbers that comes out of bcmath.
+                $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0);
+                $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1);
+            }
+            return $n;
+        } else {
+            return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1);
+        }
+    }
+
+    /**
+     * Scales a float to $scale digits right of decimal point, like BCMath.
+     */
+    private function scale($r, $scale) {
+        if ($scale < 0) {
+            // The f sprintf type doesn't support negative numbers, so we
+            // need to cludge things manually. First get the string.
+            $r = sprintf('%.0f', (float) $r);
+            // Due to floating point precision loss, $r will more than likely
+            // look something like 4652999999999.9234. We grab one more digit
+            // than we need to precise from $r and then use that to round
+            // appropriately.
+            $precise = (string) round(substr($r, 0, strlen($r) + $scale), -1);
+            // Now we return it, truncating the zero that was rounded off.
+            return substr($precise, 0, -1) . str_repeat('0', -$scale + 1);
+        }
+        return sprintf('%.' . $scale . 'f', (float) $r);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/VarParser.php b/application/libraries/htmlpurifier/HTMLPurifier/VarParser.php
new file mode 100644
index 0000000..68e72ae
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/VarParser.php
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * Parses string representations into their corresponding native PHP
+ * variable type. The base implementation does a simple type-check.
+ */
+class HTMLPurifier_VarParser
+{
+
+    const STRING    = 1;
+    const ISTRING   = 2;
+    const TEXT      = 3;
+    const ITEXT     = 4;
+    const INT       = 5;
+    const FLOAT     = 6;
+    const BOOL      = 7;
+    const LOOKUP    = 8;
+    const ALIST     = 9;
+    const HASH      = 10;
+    const MIXED     = 11;
+
+    /**
+     * Lookup table of allowed types. Mainly for backwards compatibility, but
+     * also convenient for transforming string type names to the integer constants.
+     */
+    static public $types = array(
+        'string'    => self::STRING,
+        'istring'   => self::ISTRING,
+        'text'      => self::TEXT,
+        'itext'     => self::ITEXT,
+        'int'       => self::INT,
+        'float'     => self::FLOAT,
+        'bool'      => self::BOOL,
+        'lookup'    => self::LOOKUP,
+        'list'      => self::ALIST,
+        'hash'      => self::HASH,
+        'mixed'     => self::MIXED
+    );
+
+    /**
+     * Lookup table of types that are string, and can have aliases or
+     * allowed value lists.
+     */
+    static public $stringTypes = array(
+        self::STRING    => true,
+        self::ISTRING   => true,
+        self::TEXT      => true,
+        self::ITEXT     => true,
+    );
+
+    /**
+     * Validate a variable according to type. Throws
+     * HTMLPurifier_VarParserException if invalid.
+     * It may return NULL as a valid type if $allow_null is true.
+     *
+     * @param $var Variable to validate
+     * @param $type Type of variable, see HTMLPurifier_VarParser->types
+     * @param $allow_null Whether or not to permit null as a value
+     * @return Validated and type-coerced variable
+     */
+    final public function parse($var, $type, $allow_null = false) {
+        if (is_string($type)) {
+            if (!isset(HTMLPurifier_VarParser::$types[$type])) {
+                throw new HTMLPurifier_VarParserException("Invalid type '$type'");
+            } else {
+                $type = HTMLPurifier_VarParser::$types[$type];
+            }
+        }
+        $var = $this->parseImplementation($var, $type, $allow_null);
+        if ($allow_null && $var === null) return null;
+        // These are basic checks, to make sure nothing horribly wrong
+        // happened in our implementations.
+        switch ($type) {
+            case (self::STRING):
+            case (self::ISTRING):
+            case (self::TEXT):
+            case (self::ITEXT):
+                if (!is_string($var)) break;
+                if ($type == self::ISTRING || $type == self::ITEXT) $var = strtolower($var);
+                return $var;
+            case (self::INT):
+                if (!is_int($var)) break;
+                return $var;
+            case (self::FLOAT):
+                if (!is_float($var)) break;
+                return $var;
+            case (self::BOOL):
+                if (!is_bool($var)) break;
+                return $var;
+            case (self::LOOKUP):
+            case (self::ALIST):
+            case (self::HASH):
+                if (!is_array($var)) break;
+                if ($type === self::LOOKUP) {
+                    foreach ($var as $k) if ($k !== true) $this->error('Lookup table contains value other than true');
+                } elseif ($type === self::ALIST) {
+                    $keys = array_keys($var);
+                    if (array_keys($keys) !== $keys) $this->error('Indices for list are not uniform');
+                }
+                return $var;
+            case (self::MIXED):
+                return $var;
+            default:
+                $this->errorInconsistent(get_class($this), $type);
+        }
+        $this->errorGeneric($var, $type);
+    }
+
+    /**
+     * Actually implements the parsing. Base implementation is to not
+     * do anything to $var. Subclasses should overload this!
+     */
+    protected function parseImplementation($var, $type, $allow_null) {
+        return $var;
+    }
+
+    /**
+     * Throws an exception.
+     */
+    protected function error($msg) {
+        throw new HTMLPurifier_VarParserException($msg);
+    }
+
+    /**
+     * Throws an inconsistency exception.
+     * @note This should not ever be called. It would be called if we
+     *       extend the allowed values of HTMLPurifier_VarParser without
+     *       updating subclasses.
+     */
+    protected function errorInconsistent($class, $type) {
+        throw new HTMLPurifier_Exception("Inconsistency in $class: ".HTMLPurifier_VarParser::getTypeName($type)." not implemented");
+    }
+
+    /**
+     * Generic error for if a type didn't work.
+     */
+    protected function errorGeneric($var, $type) {
+        $vtype = gettype($var);
+        $this->error("Expected type ".HTMLPurifier_VarParser::getTypeName($type).", got $vtype");
+    }
+
+    static public function getTypeName($type) {
+        static $lookup;
+        if (!$lookup) {
+            // Lazy load the alternative lookup table
+            $lookup = array_flip(HTMLPurifier_VarParser::$types);
+        }
+        if (!isset($lookup[$type])) return 'unknown';
+        return $lookup[$type];
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/VarParser/Flexible.php b/application/libraries/htmlpurifier/HTMLPurifier/VarParser/Flexible.php
new file mode 100644
index 0000000..21b8767
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/VarParser/Flexible.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * Performs safe variable parsing based on types which can be used by
+ * users. This may not be able to represent all possible data inputs,
+ * however.
+ */
+class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser
+{
+
+    protected function parseImplementation($var, $type, $allow_null) {
+        if ($allow_null && $var === null) return null;
+        switch ($type) {
+            // Note: if code "breaks" from the switch, it triggers a generic
+            // exception to be thrown. Specific errors can be specifically
+            // done here.
+            case self::MIXED :
+            case self::ISTRING :
+            case self::STRING :
+            case self::TEXT :
+            case self::ITEXT :
+                return $var;
+            case self::INT :
+                if (is_string($var) && ctype_digit($var)) $var = (int) $var;
+                return $var;
+            case self::FLOAT :
+                if ((is_string($var) && is_numeric($var)) || is_int($var)) $var = (float) $var;
+                return $var;
+            case self::BOOL :
+                if (is_int($var) && ($var === 0 || $var === 1)) {
+                    $var = (bool) $var;
+                } elseif (is_string($var)) {
+                    if ($var == 'on' || $var == 'true' || $var == '1') {
+                        $var = true;
+                    } elseif ($var == 'off' || $var == 'false' || $var == '0') {
+                        $var = false;
+                    } else {
+                        throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type");
+                    }
+                }
+                return $var;
+            case self::ALIST :
+            case self::HASH :
+            case self::LOOKUP :
+                if (is_string($var)) {
+                    // special case: technically, this is an array with
+                    // a single empty string item, but having an empty
+                    // array is more intuitive
+                    if ($var == '') return array();
+                    if (strpos($var, "\n") === false && strpos($var, "\r") === false) {
+                        // simplistic string to array method that only works
+                        // for simple lists of tag names or alphanumeric characters
+                        $var = explode(',',$var);
+                    } else {
+                        $var = preg_split('/(,|[\n\r]+)/', $var);
+                    }
+                    // remove spaces
+                    foreach ($var as $i => $j) $var[$i] = trim($j);
+                    if ($type === self::HASH) {
+                        // key:value,key2:value2
+                        $nvar = array();
+                        foreach ($var as $keypair) {
+                            $c = explode(':', $keypair, 2);
+                            if (!isset($c[1])) continue;
+                            $nvar[trim($c[0])] = trim($c[1]);
+                        }
+                        $var = $nvar;
+                    }
+                }
+                if (!is_array($var)) break;
+                $keys = array_keys($var);
+                if ($keys === array_keys($keys)) {
+                    if ($type == self::ALIST) return $var;
+                    elseif ($type == self::LOOKUP) {
+                        $new = array();
+                        foreach ($var as $key) {
+                            $new[$key] = true;
+                        }
+                        return $new;
+                    } else break;
+                }
+                if ($type === self::ALIST) {
+                    trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING);
+                    return array_values($var);
+                }
+                if ($type === self::LOOKUP) {
+                    foreach ($var as $key => $value) {
+                        if ($value !== true) {
+                            trigger_error("Lookup array has non-true value at key '$key'; maybe your input array was not indexed numerically", E_USER_WARNING);
+                        }
+                        $var[$key] = true;
+                    }
+                }
+                return $var;
+            default:
+                $this->errorInconsistent(__CLASS__, $type);
+        }
+        $this->errorGeneric($var, $type);
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/VarParser/Native.php b/application/libraries/htmlpurifier/HTMLPurifier/VarParser/Native.php
new file mode 100644
index 0000000..b02a6de
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/VarParser/Native.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * This variable parser uses PHP's internal code engine. Because it does
+ * this, it can represent all inputs; however, it is dangerous and cannot
+ * be used by users.
+ */
+class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser
+{
+
+    protected function parseImplementation($var, $type, $allow_null) {
+        return $this->evalExpression($var);
+    }
+
+    protected function evalExpression($expr) {
+        $var = null;
+        $result = eval("\$var = $expr;");
+        if ($result === false) {
+            throw new HTMLPurifier_VarParserException("Fatal error in evaluated code");
+        }
+        return $var;
+    }
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/HTMLPurifier/VarParserException.php b/application/libraries/htmlpurifier/HTMLPurifier/VarParserException.php
new file mode 100644
index 0000000..5df3414
--- /dev/null
+++ b/application/libraries/htmlpurifier/HTMLPurifier/VarParserException.php
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * Exception type for HTMLPurifier_VarParser
+ */
+class HTMLPurifier_VarParserException extends HTMLPurifier_Exception
+{
+
+}
+
+// vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/INSTALL b/application/libraries/htmlpurifier/INSTALL
new file mode 100644
index 0000000..677c04a
--- /dev/null
+++ b/application/libraries/htmlpurifier/INSTALL
@@ -0,0 +1,374 @@
+
+Install
+    How to install HTML Purifier
+
+HTML Purifier is designed to run out of the box, so actually using the
+library is extremely easy.  (Although... if you were looking for a
+step-by-step installation GUI, you've downloaded the wrong software!)
+
+While the impatient can get going immediately with some of the sample
+code at the bottom of this library, it's well worth reading this entire
+document--most of the other documentation assumes that you are familiar
+with these contents.
+
+
+---------------------------------------------------------------------------
+1.  Compatibility
+
+HTML Purifier is PHP 5 only, and is actively tested from PHP 5.0.5 and
+up. It has no core dependencies with other libraries. PHP
+4 support was deprecated on December 31, 2007 with HTML Purifier 3.0.0.
+HTML Purifier is not compatible with zend.ze1_compatibility_mode.
+
+These optional extensions can enhance the capabilities of HTML Purifier:
+
+    * iconv  : Converts text to and from non-UTF-8 encodings
+    * bcmath : Used for unit conversion and imagecrash protection
+    * tidy   : Used for pretty-printing HTML
+
+These optional libraries can enhance the capabilities of HTML Purifier:
+
+    * CSSTidy : Clean CSS stylesheets using %Core.ExtractStyleBlocks
+    * Net_IDNA2 (PEAR) : IRI support using %Core.EnableIDNA
+
+---------------------------------------------------------------------------
+2.  Reconnaissance
+
+A big plus of HTML Purifier is its inerrant support of standards, so
+your web-pages should be standards-compliant.  (They should also use
+semantic markup, but that's another issue altogether, one HTML Purifier
+cannot fix without reading your mind.)
+
+HTML Purifier can process these doctypes:
+
+* XHTML 1.0 Transitional (default)
+* XHTML 1.0 Strict
+* HTML 4.01 Transitional
+* HTML 4.01 Strict
+* XHTML 1.1
+
+...and these character encodings:
+
+* UTF-8 (default)
+* Any encoding iconv supports (with crippled internationalization support)
+
+These defaults reflect what my choices would be if I were authoring an
+HTML document, however, what you choose depends on the nature of your
+codebase.  If you don't know what doctype you are using, you can determine
+the doctype from this identifier at the top of your source code:
+
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+...and the character encoding from this code:
+
+    <meta http-equiv="Content-type" content="text/html;charset=ENCODING">
+
+If the character encoding declaration is missing, STOP NOW, and
+read 'docs/enduser-utf8.html' (web accessible at
+http://htmlpurifier.org/docs/enduser-utf8.html).  In fact, even if it is
+present, read this document anyway, as many websites specify their
+document's character encoding incorrectly.
+
+
+---------------------------------------------------------------------------
+3.  Including the library
+
+The procedure is quite simple:
+
+    require_once '/path/to/library/HTMLPurifier.auto.php';
+
+This will setup an autoloader, so the library's files are only included
+when you use them.
+
+Only the contents in the library/ folder are necessary, so you can remove
+everything else when using HTML Purifier in a production environment.
+
+If you installed HTML Purifier via PEAR, all you need to do is:
+
+    require_once 'HTMLPurifier.auto.php';
+
+Please note that the usual PEAR practice of including just the classes you
+want will not work with HTML Purifier's autoloading scheme.
+
+Advanced users, read on; other users can skip to section 4.
+
+Autoload compatibility
+----------------------
+
+    HTML Purifier attempts to be as smart as possible when registering an
+    autoloader, but there are some cases where you will need to change
+    your own code to accomodate HTML Purifier. These are those cases:
+
+    PHP VERSION IS LESS THAN 5.1.2, AND YOU'VE DEFINED __autoload
+        Because spl_autoload_register() doesn't exist in early versions
+        of PHP 5, HTML Purifier has no way of adding itself to the autoload
+        stack. Modify your __autoload function to test
+        HTMLPurifier_Bootstrap::autoload($class)
+
+        For example, suppose your autoload function looks like this:
+
+            function __autoload($class) {
+                require str_replace('_', '/', $class) . '.php';
+                return true;
+            }
+
+        A modified version with HTML Purifier would look like this:
+
+            function __autoload($class) {
+                if (HTMLPurifier_Bootstrap::autoload($class)) return true;
+                require str_replace('_', '/', $class) . '.php';
+                return true;
+            }
+
+        Note that there *is* some custom behavior in our autoloader; the
+        original autoloader in our example would work for 99% of the time,
+        but would fail when including language files.
+
+    AN __autoload FUNCTION IS DECLARED AFTER OUR AUTOLOADER IS REGISTERED
+        spl_autoload_register() has the curious behavior of disabling
+        the existing __autoload() handler. Users need to explicitly
+        spl_autoload_register('__autoload'). Because we use SPL when it
+        is available, __autoload() will ALWAYS be disabled. If __autoload()
+        is declared before HTML Purifier is loaded, this is not a problem:
+        HTML Purifier will register the function for you. But if it is
+        declared afterwards, it will mysteriously not work. This
+        snippet of code (after your autoloader is defined) will fix it:
+
+            spl_autoload_register('__autoload')
+
+    Users should also be on guard if they use a version of PHP previous
+    to 5.1.2 without an autoloader--HTML Purifier will define __autoload()
+    for you, which can collide with an autoloader that was added by *you*
+    later.
+
+
+For better performance
+----------------------
+
+    Opcode caches, which greatly speed up PHP initialization for scripts
+    with large amounts of code (HTML Purifier included), don't like
+    autoloaders. We offer an include file that includes all of HTML Purifier's
+    files in one go in an opcode cache friendly manner:
+
+        // If /path/to/library isn't already in your include path, uncomment
+        // the below line:
+        // require '/path/to/library/HTMLPurifier.path.php';
+
+        require 'HTMLPurifier.includes.php';
+
+    Optional components still need to be included--you'll know if you try to
+    use a feature and you get a class doesn't exists error! The autoloader
+    can be used in conjunction with this approach to catch classes that are
+    missing. Simply add this afterwards:
+
+        require 'HTMLPurifier.autoload.php';
+
+Standalone version
+------------------
+
+    HTML Purifier has a standalone distribution; you can also generate
+    a standalone file from the full version by running the script
+    maintenance/generate-standalone.php . The standalone version has the
+    benefit of having most of its code in one file, so parsing is much
+    faster and the library is easier to manage.
+
+    If HTMLPurifier.standalone.php exists in the library directory, you
+    can use it like this:
+
+        require '/path/to/HTMLPurifier.standalone.php';
+
+    This is equivalent to including HTMLPurifier.includes.php, except that
+    the contents of standalone/ will be added to your path. To override this
+    behavior, specify a new HTMLPURIFIER_PREFIX where standalone files can
+    be found (usually, this will be one directory up, the "true" library
+    directory in full distributions). Don't forget to set your path too!
+
+    The autoloader can be added to the end to ensure the classes are
+    loaded when necessary; otherwise you can manually include them.
+    To use the autoloader, use this:
+
+        require 'HTMLPurifier.autoload.php';
+
+For advanced users
+------------------
+
+    HTMLPurifier.auto.php performs a number of operations that can be done
+    individually. These are:
+
+        HTMLPurifier.path.php
+            Puts /path/to/library in the include path. For high performance,
+            this should be done in php.ini.
+
+        HTMLPurifier.autoload.php
+            Registers our autoload handler HTMLPurifier_Bootstrap::autoload($class).
+
+    You can do these operations by yourself--in fact, you must modify your own
+    autoload handler if you are using a version of PHP earlier than PHP 5.1.2
+    (See "Autoload compatibility" above).
+
+
+---------------------------------------------------------------------------
+4. Configuration
+
+HTML Purifier is designed to run out-of-the-box, but occasionally HTML
+Purifier needs to be told what to do.  If you answer no to any of these
+questions, read on; otherwise, you can skip to the next section (or, if you're
+into configuring things just for the heck of it, skip to 4.3).
+
+* Am I using UTF-8?
+* Am I using XHTML 1.0 Transitional?
+
+If you answered no to any of these questions, instantiate a configuration
+object and read on:
+
+    $config = HTMLPurifier_Config::createDefault();
+
+
+4.1. Setting a different character encoding
+
+You really shouldn't use any other encoding except UTF-8, especially if you
+plan to support multilingual websites (read section three for more details).
+However, switching to UTF-8 is not always immediately feasible, so we can
+adapt.
+
+HTML Purifier uses iconv to support other character encodings, as such,
+any encoding that iconv supports <http://www.gnu.org/software/libiconv/>
+HTML Purifier supports with this code:
+
+    $config->set('Core.Encoding', /* put your encoding here */);
+
+An example usage for Latin-1 websites (the most common encoding for English
+websites):
+
+    $config->set('Core.Encoding', 'ISO-8859-1');
+
+Note that HTML Purifier's support for non-Unicode encodings is crippled by the
+fact that any character not supported by that encoding will be silently
+dropped, EVEN if it is ampersand escaped.  If you want to work around
+this, you are welcome to read docs/enduser-utf8.html for a fix,
+but please be cognizant of the issues the "solution" creates (for this
+reason, I do not include the solution in this document).
+
+
+4.2. Setting a different doctype
+
+For those of you using HTML 4.01 Transitional, you can disable
+XHTML output like this:
+
+    $config->set('HTML.Doctype', 'HTML 4.01 Transitional');
+
+Other supported doctypes include:
+
+    * HTML 4.01 Strict
+    * HTML 4.01 Transitional
+    * XHTML 1.0 Strict
+    * XHTML 1.0 Transitional
+    * XHTML 1.1
+
+
+4.3. Other settings
+
+There are more configuration directives which can be read about
+here: <http://htmlpurifier.org/live/configdoc/plain.html>  They're a bit boring,
+but they can help out for those of you who like to exert maximum control over
+your code.  Some of the more interesting ones are configurable at the
+demo <http://htmlpurifier.org/demo.php> and are well worth looking into
+for your own system.
+
+For example, you can fine tune allowed elements and attributes, convert
+relative URLs to absolute ones, and even autoparagraph input text! These
+are, respectively, %HTML.Allowed, %URI.MakeAbsolute and %URI.Base, and
+%AutoFormat.AutoParagraph. The %Namespace.Directive naming convention
+translates to:
+
+    $config->set('Namespace.Directive', $value);
+
+E.g.
+
+    $config->set('HTML.Allowed', 'p,b,a[href],i');
+    $config->set('URI.Base', 'http://www.example.com');
+    $config->set('URI.MakeAbsolute', true);
+    $config->set('AutoFormat.AutoParagraph', true);
+
+
+---------------------------------------------------------------------------
+5. Caching
+
+HTML Purifier generates some cache files (generally one or two) to speed up
+its execution. For maximum performance, make sure that
+library/HTMLPurifier/DefinitionCache/Serializer is writeable by the webserver.
+
+If you are in the library/ folder of HTML Purifier, you can set the
+appropriate permissions using:
+
+    chmod -R 0755 HTMLPurifier/DefinitionCache/Serializer
+
+If the above command doesn't work, you may need to assign write permissions
+to all. This may be necessary if your webserver runs as nobody, but is
+not recommended since it means any other user can write files in the
+directory. Use:
+
+    chmod -R 0777 HTMLPurifier/DefinitionCache/Serializer
+
+You can also chmod files via your FTP client; this option
+is usually accessible by right clicking the corresponding directory and
+then selecting "chmod" or "file permissions".
+
+Starting with 2.0.1, HTML Purifier will generate friendly error messages
+that will tell you exactly what you have to chmod the directory to, if in doubt,
+follow its advice.
+
+If you are unable or unwilling to give write permissions to the cache
+directory, you can either disable the cache (and suffer a performance
+hit):
+
+    $config->set('Core.DefinitionCache', null);
+
+Or move the cache directory somewhere else (no trailing slash):
+
+    $config->set('Cache.SerializerPath', '/home/user/absolute/path');
+
+
+---------------------------------------------------------------------------
+6.   Using the code
+
+The interface is mind-numbingly simple:
+
+    $purifier = new HTMLPurifier($config);
+    $clean_html = $purifier->purify( $dirty_html );
+
+That's it!  For more examples, check out docs/examples/ (they aren't very
+different though).  Also, docs/enduser-slow.html gives advice on what to
+do if HTML Purifier is slowing down your application.
+
+
+---------------------------------------------------------------------------
+7.   Quick install
+
+First, make sure library/HTMLPurifier/DefinitionCache/Serializer is
+writable by the webserver (see Section 5: Caching above for details).
+If your website is in UTF-8 and XHTML Transitional, use this code:
+
+<?php
+    require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php';
+
+    $config = HTMLPurifier_Config::createDefault();
+    $purifier = new HTMLPurifier($config);
+    $clean_html = $purifier->purify($dirty_html);
+?>
+
+If your website is in a different encoding or doctype, use this code:
+
+<?php
+    require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php';
+
+    $config = HTMLPurifier_Config::createDefault();
+    $config->set('Core.Encoding', 'ISO-8859-1'); // replace with your encoding
+    $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); // replace with your doctype
+    $purifier = new HTMLPurifier($config);
+
+    $clean_html = $purifier->purify($dirty_html);
+?>
+
+    vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/LICENSE b/application/libraries/htmlpurifier/LICENSE
new file mode 100644
index 0000000..8c88a20
--- /dev/null
+++ b/application/libraries/htmlpurifier/LICENSE
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+    vim: et sw=4 sts=4
diff --git a/application/libraries/htmlpurifier/NEWS b/application/libraries/htmlpurifier/NEWS
new file mode 100644
index 0000000..2b2f0e6
--- /dev/null
+++ b/application/libraries/htmlpurifier/NEWS
@@ -0,0 +1,1056 @@
+NEWS ( CHANGELOG and HISTORY )                                     HTMLPurifier
+|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
+
+= KEY ====================
+    # Breaks back-compat
+    ! Feature
+    - Bugfix
+      + Sub-comment
+    . Internal change
+==========================
+
+4.5.0, released 2013-02-17
+# Fix bug where stacked attribute transforms clobber each other;
+  this also means it's no longer possible to override attribute
+  transforms in later modules.  No internal code was using this
+  but this may break some clients.
+# We now use SHA-1 to identify cached definitions, instead of MD5.
+! Support display:inline-block
+! Support for more white-space CSS values.
+! Permit underscores in font families
+! Support for page-break-* CSS3 properties when proprietary properties
+  are enabled.
+! New directive %Core.EnableExcludes; can be set to 'false' to turn off
+  SGML excludes checking.  If HTML Purifier is removing too much text
+  and you don't care about full standards compliance, try setting this to
+  'false'.
+- Use prepend for SPL autoloading on PHP 5.3 and later.
+- Fix bug with nofollow transform when pre-existing rel exists.
+- Fix bug where background:url() always gets lower-cased
+  (but not background-image:url())
+- Fix bug with non lower-case color names in HTML
+- Fix bug where data URI validation doesn't remove temporary files.
+  Thanks Javier Marín Ros <javiermarinros at gmail.com> for reporting.
+- Don't remove certain empty tags on RemoveEmpty.
+
+4.4.0, released 2012-01-18
+# Removed PEARSax3 handler.
+# URI.Munge now munges URIs inside the same host that go from https
+  to http.  Reported by Neike Taika-Tessaro.
+# Core.EscapeNonASCIICharacters now always transforms entities to
+  entities, even if target encoding is UTF-8.
+# Tighten up selector validation in ExtractStyleBlocks.
+  Non-syntactically valid selectors are now rejected, along with
+  some of the more obscure ones such as attribute selectors, the
+  :lang pseudoselector, and anything not in CSS2.1.  Furthermore,
+  ID and class selectors now work properly with the relevant
+  configuration attributes.  Also, mute errors when parsing CSS
+  with CSS Tidy.  Reported by Mario Heiderich and Norman Hippert.
+! Added support for 'scope' attribute on tables.
+! Added %HTML.TargetBlank, which adds target="blank" to all outgoing links.
+! Properly handle sub-lists directly nested inside of lists in
+  a standards compliant way, by moving them into the preceding <li>
+! Added %HTML.AllowedComments and %HTML.AllowedCommentsRegexp for
+  limited allowed comments in untrusted situations.
+! Implement iframes, and allow them to be used in untrusted mode with
+  %HTML.SafeIframe and %URI.SafeIframeRegexp.  Thanks Bradley M. Froehle
+  <brad.froehle at gmail.com> for submitting an initial version of the patch.
+! The Forms module now works properly for transitional doctypes.
+! Added support for internationalized domain names. You need the PEAR
+  Net_IDNA2 module to be in your path; if it is installed, ensure the
+  class can be loaded and then set %Core.EnableIDNA to true.
+- Color keywords are now case insensitive.  Thanks Yzmir Ramirez
+  <yramirez-htmlpurifier at adicio.com> for reporting.
+- Explicitly initialize anonModule variable to null.
+- Do not duplicate nofollow if already present.  Thanks 178
+  for reporting.
+- Do not add nofollow if hostname matches our current host.  Thanks 178
+  for reporting, and Neike Taika-Tessaro for helping diagnose.
+- Do not unset parser variable; this fixes intermittent serialization
+  problems.  Thanks Neike Taika-Tessaro for reporting, bill
+  <10010tiger at gmail.com> for diagnosing.
+- Fix iconv truncation bug, where non-UTF-8 target encodings see
+  output truncated after around 8000 characters.  Thanks Jörg Ludwig
+  <joerg.ludwig at iserv.eu> for reporting.
+- Fix broken table content model for XHTML1.1 (and also earlier
+  versions, although the W3C validator doesn't catch those violations).
+  Thanks GlitchMr <glitch.mr at gmail.com> for reporting.
+
+4.3.0, released 2011-03-27
+# Fixed broken caching of customized raw definitions, but requires an
+  API change.  The old API still works but will emit a warning,
+  see http://htmlpurifier.org/docs/enduser-customize.html#optimized
+  for how to upgrade your code.
+# Protect against Internet Explorer innerHTML behavior by specially
+  treating attributes with backticks but no angled brackets, quotes or
+  spaces.  This constitutes a slight semantic change, which can be
+  reverted using %Output.FixInnerHTML.  Reported by Neike Taika-Tessaro
+  and Mario Heiderich.
+# Protect against cssText/innerHTML by restricting allowed characters
+  used in fonts further than mandated by the specification and encoding
+  some extra special characters in URLs.  Reported by Neike
+  Taika-Tessaro and Mario Heiderich.
+! Added %HTML.Nofollow to add rel="nofollow" to external links.
+! More types of SPL autoloaders allowed on later versions of PHP.
+! Implementations for position, top, left, right, bottom, z-index
+  when %CSS.Trusted is on.
+! Add %Cache.SerializerPermissions option for custom serializer
+  directory/file permissions
+! Fix longstanding bug in Flash support for non-IE browsers, and
+  allow more wmode attributes.
+! Add %CSS.AllowedFonts to restrict permissible font names.
+- Switch to an iterative traversal of the DOM, which prevents us
+  from running out of stack space for deeply nested documents.
+  Thanks Maxim Krizhanovsky for contributing a patch.
+- Make removal of conditional IE comments ungreedy; thanks Bernd
+  for reporting.
+- Escape CDATA before removing Internet Explorer comments.
+- Fix removal of id attributes under certain conditions by ensuring
+  armor attributes are preserved when recreating tags.
+- Check if schema.ser was corrupted.
+- Check if zend.ze1_compatibility_mode is on, and error out if it is.
+  This safety check is only done for HTMLPurifier.auto.php; if you
+  are using standalone or the specialized includes files, you're
+  expected to know what you're doing.
+- Stop repeatedly writing the cache file after I'm done customizing a
+  raw definition.  Reported by ajh.
+- Switch to using require_once in the Bootstrap to work around bad
+  interaction with Zend Debugger and APC.  Reported by Antonio Parraga.
+- Fix URI handling when hostname is missing but scheme is present.
+  Reported by Neike Taika-Tessaro.
+- Fix missing numeric entities on DirectLex; thanks Neike Taika-Tessaro
+  for reporting.
+- Fix harmless notice from indexing into empty string.  Thanks Matthijs
+  Kooijman <matthijs at stdin.nl> for reporting.
+- Don't autoclose no parent elements are able to support the element
+  that triggered the autoclose.  In particular fixes strange behavior
+  of stray <li> tags.  Thanks pkuliga at gmail.com for reporting and
+  Neike Taika-Tessaro <pinkgothic at gmail.com> for debugging assistance.
+
+4.2.0, released 2010-09-15
+! Added %Core.RemoveProcessingInstructions, which lets you remove
+  <? ... ?> statements.
+! Added %URI.DisableResources functionality; the directive originally
+  did nothing.  Thanks David Rothstein for reporting.
+! Add documentation about configuration directive types.
+! Add %CSS.ForbiddenProperties configuration directive.
+! Add %HTML.FlashAllowFullScreen to permit embedded Flash objects
+  to utilize full-screen mode.
+! Add optional support for the <code>file</code> URI scheme, enable
+  by explicitly setting %URI.AllowedSchemes.
+! Add %Core.NormalizeNewlines options to allow turning off newline
+  normalization.
+- Fix improper handling of Internet Explorer conditional comments
+  by parser.  Thanks zmonteca for reporting.
+- Fix missing attributes bug when running on Mac Snow Leopard and APC.
+  Thanks sidepodcast for the fix.
+- Warn if an element is allowed, but an attribute it requires is
+  not allowed.
+
+4.1.1, released 2010-05-31
+- Fix undefined index warnings in maintenance scripts.
+- Fix bug in DirectLex for parsing elements with a single attribute
+  with entities.
+- Rewrite CSS output logic for font-family and url().  Thanks Mario
+  Heiderich <mario.heiderich at googlemail.com> for reporting and Takeshi
+  Terada <t-terada at violet.plala.or.jp> for suggesting the fix.
+- Emit an error for CollectErrors if a body is extracted
+- Fix bug where in background-position for center keyword handling.
+- Fix infinite loop when a wrapper element is inserted in a context
+  where it's not allowed.  Thanks Lars <lars at renoz.dk> for reporting.
+- Remove +x bit and shebang from index.php; only supported mode is to
+  explicitly call it with php.
+- Make test script less chatty when log_errors is on.
+
+4.1.0, released 2010-04-26
+! Support proprietary height attribute on table element
+! Support YouTube slideshows that contain /cp/ in their URL.
+! Support for data: URI scheme; not enabled by default, add it using
+  %URI.AllowedSchemes
+! Support flashvars when using %HTML.SafeObject and %HTML.SafeEmbed.
+! Support for Internet Explorer compatibility with %HTML.SafeObject
+  using %Output.FlashCompat.
+! Handle <ol><ol> properly, by inserting the necessary <li> tag.
+- Always quote the insides of url(...) in CSS.
+
+4.0.0, released 2009-07-07
+# APIs for ConfigSchema subsystem have substantially changed. See
+  docs/dev-config-bcbreaks.txt for details; in essence, anything that
+  had both namespace and directive now have a single unified key.
+# Some configuration directives were renamed, specifically:
+    %AutoFormatParam.PurifierLinkifyDocURL -> %AutoFormat.PurifierLinkify.DocURL
+    %FilterParam.ExtractStyleBlocksEscaping -> %Filter.ExtractStyleBlocks.Escaping
+    %FilterParam.ExtractStyleBlocksScope -> %Filter.ExtractStyleBlocks.Scope
+    %FilterParam.ExtractStyleBlocksTidyImpl -> %Filter.ExtractStyleBlocks.TidyImpl
+  As usual, the old directive names will still work, but will throw E_NOTICE
+  errors.
+# The allowed values for class have been relaxed to allow all of CDATA for
+  doctypes that are not XHTML 1.1 or XHTML 2.0.  For old behavior, set
+  %Attr.ClassUseCDATA to false.
+# Instead of appending the content model to an old content model, a blank
+  element will replace the old content model.  You can use #SUPER to get
+  the old content model.
+! More robust support for name="" and id=""
+! HTMLPurifier_Config::inherit($config) allows you to inherit one
+  configuration, and have changes to that configuration be propagated
+  to all of its children.
+! Implement %HTML.Attr.Name.UseCDATA, which relaxes validation rules on
+  the name attribute when set. Use with care. Thanks Ian Cook for
+  sponsoring.
+! Implement %AutoFormat.RemoveEmpty.RemoveNbsp, which removes empty
+  tags that contain non-breaking spaces as well other whitespace. You
+  can also modify which tags should have   maintained with
+  %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.
+! Implement %Attr.AllowedClasses, which allows administrators to restrict
+  classes users can use to a specified finite set of classes, and
+  %Attr.ForbiddenClasses, which is the logical inverse.
+! You can now maintain your own configuration schema directories by
+  creating a config-schema.php file or passing an extra argument. Check
+  docs/dev-config-schema.html for more details.
+! Added HTMLPurifier_Config->serialize() method, which lets you save away
+  your configuration in a compact serial file, which you can unserialize
+  and use directly without having to go through the overhead of setup.
+- Fix bug where URIDefinition would not get cleared if it's directives got
+  changed.
+- Fix fatal error in HTMLPurifier_Encoder on certain platforms (probably NetBSD 5.0)
+- Fix bug in Linkify autoformatter involving <a><span>http://foo</span></a>
+- Make %URI.Munge not apply to links that have the same host as your host.
+- Prevent stray </body> tag from truncating output, if a second </body>
+  is present.
+. Created script maintenance/rename-config.php for renaming a configuration
+  directive while maintaining its alias.  This script does not change source code.
+. Implement namespace locking for definition construction, to prevent
+  bugs where a directive is used for definition construction but is not
+  used to construct the cache hash.
+
+3.3.0, released 2009-02-16
+! Implement CSS property 'overflow' when %CSS.AllowTricky is true.
+! Implement generic property list classess
+- Fix bug with testEncodingSupportsASCII() algorithm when iconv() implementation
+  does not do the "right thing" with characters not supported in the output
+  set.
+- Spellcheck UTF-8: The Secret To Character Encoding
+- Fix improper removal of the contents of elements with only whitespace. Thanks
+  Eric Wald for reporting.
+- Fix broken test suite in versions of PHP without spl_autoload_register()
+- Fix degenerate case with YouTube filter involving double hyphens.
+  Thanks Pierre Attar for reporting.
+- Fix YouTube rendering problem on certain versions of Firefox.
+- Fix CSSDefinition Printer problems with decorators
+- Add text parameter to unit tests, forces text output
+. Add verbose mode to command line test runner, use (--verbose)
+. Turn on unit tests for UnitConverter
+. Fix missing version number in configuration %Attr.DefaultImageAlt (added 3.2.0)
+. Fix newline errors that caused spurious failures when CRLF HTML Purifier was
+  tested on Linux.
+. Removed trailing whitespace from all text files, see
+  remote-trailing-whitespace.php maintenance script.
+. Convert configuration to use property list backend.
+
+3.2.0, released 2008-10-31
+# Using %Core.CollectErrors forces line number/column tracking on, whereas
+  previously you could theoretically turn it off.
+# HTMLPurifier_Injector->notifyEnd() is formally deprecated. Please
+  use handleEnd() instead.
+! %Output.AttrSort for when you need your attributes in alphabetical order to
+  deal with a bug in FCKEditor. Requested by frank farmer.
+! Enable HTML comments when %HTML.Trusted is on. Requested by Waldo Jaquith.
+! Proper support for name attribute. It is now allowed and equivalent to the id
+  attribute in a and img tags, and is only converted to id when %HTML.TidyLevel
+  is heavy (for all doctypes).
+! %AutoFormat.RemoveEmpty to remove some empty tags from documents. Please don't
+  use on hand-written HTML.
+! Add error-cases for unsupported elements in MakeWellFormed. This enables
+  the strategy to be used, standalone, on untrusted input.
+! %Core.AggressivelyFixLt is on by default. This causes more sensible
+  processing of left angled brackets in smileys and other whatnot.
+! Test scripts now have a 'type' parameter, which lets you say 'htmlpurifier',
+  'phpt', 'vtest', etc. in order to only execute those tests. This supercedes
+  the --only-phpt parameter, although for backwards-compatibility the flag
+  will still work.
+! AutoParagraph auto-formatter will now preserve double-newlines upon output.
+  Users who are not performing inbound filtering, this may seem a little
+  useless, but as a bonus, the test suite and handling of edge cases is also
+  improved.
+! Experimental implementation of forms for %HTML.Trusted
+! Track column numbers when maintain line numbers is on
+! Proprietary 'background' attribute on table-related elements converted into
+  corresponding CSS.  Thanks Fusemail for sponsoring this feature!
+! Add forward(), forwardUntilEndToken(), backward() and current() to Injector
+  supertype.
+! HTMLPurifier_Injector->handleEnd() permits modification to end tokens. The
+  time of operation varies slightly from notifyEnd() as *all* end tokens are
+  processed by the injector before they are subject to the well-formedness rules.
+! %Attr.DefaultImageAlt allows overriding default behavior of setting alt to
+  basename of image when not present.
+! %AutoFormat.DisplayLinkURI neuters <a> tags into plain text URLs.
+- Fix two bugs in %URI.MakeAbsolute; one involving empty paths in base URLs,
+  the other involving an undefined $is_folder error.
+- Throw error when %Core.Encoding is set to a spurious value. Previously,
+  this errored silently and returned false.
+- Redirected stderr to stdout for flush error output.
+- %URI.DisableExternal will now use the host in %URI.Base if %URI.Host is not
+  available.
+- Do not re-munge URL if the output URL has the same host as the input URL.
+  Requested by Chris.
+- Fix error in documentation regarding %Filter.ExtractStyleBlocks
+- Prevent <![CDATA[<body></body>]]> from triggering %Core.ConvertDocumentToFragment
+- Fix bug with inline elements in blockquotes conflicting with strict doctype
+- Detect if HTML support is disabled for DOM by checking for loadHTML() method.
+- Fix bug where dots and double-dots in absolute URLs without hostname were
+  not collapsed by URIFilter_MakeAbsolute.
+- Fix bug with anonymous modules operating on SafeEmbed or SafeObject elements
+  by reordering their addition.
+- Will now throw exception on many error conditions during lexer creation; also
+  throw an exception when MaintainLineNumbers is true, but a non-tracksLineNumbers
+  is being used.
+- Detect if domxml extension is loaded, and use DirectLEx accordingly.
+- Improve handling of big numbers with floating point arithmetic in UnitConverter.
+  Reported by David Morton.
+. Strategy_MakeWellFormed now operates in-place, saving memory and allowing
+  for more interesting filter-backtracking
+. New HTMLPurifier_Injector->rewind() functionality, allows injectors to rewind
+  index to reprocess tokens.
+. StringHashParser now allows for multiline sections with "empty" content;
+  previously the section would remain undefined.
+. Added --quick option to multitest.php, which tests only the most recent
+  release for each series.
+. Added --distro option to multitest.php, which accepts either 'normal' or
+  'standalone'. This supercedes --exclude-normal and --exclude-standalone
+
+3.1.1, released 2008-06-19
+# %URI.Munge now, by default, does not munge resources (for example, <img src="">)
+  In order to enable this again, please set %URI.MungeResources to true.
+! More robust imagecrash protection with height/width CSS with %CSS.MaxImgLength,
+  and height/width HTML with %HTML.MaxImgLength.
+! %URI.MungeSecretKey for secure URI munging. Thanks Chris
+  for sponsoring this feature. Check out the corresponding documentation
+  for details. (Att Nightly testers: The API for this feature changed before
+  the general release. Namely, rename your directives %URI.SecureMungeSecretKey =>
+  %URI.MungeSecretKey and and %URI.SecureMunge => %URI.Munge)
+! Implemented post URI filtering. Set member variable $post to true to set
+  a URIFilter as such.
+! Allow modules to define injectors via $info_injector. Injectors are
+  automatically disabled if injector's needed elements are not found.
+! Support for "safe" objects added, use %HTML.SafeObject and %HTML.SafeEmbed.
+  Thanks Chris for sponsoring. If you've been using ad hoc code from the
+  forums, PLEASE use this instead.
+! Added substitutions for %e, %n, %a and %p in %URI.Munge (in order,
+  embedded, tag name, attribute name, CSS property name). See %URI.Munge
+  for more details. Requested by Jochem Blok.
+- Disable percent height/width attributes for img.
+- AttrValidator operations are now atomic; updates to attributes are not
+  manifest in token until end of operations. This prevents naughty internal
+  code from directly modifying CurrentToken when they're not supposed to.
+  This semantics change was requested by frank farmer.
+- Percent encoding checks enabled for URI query and fragment
+- Fix stray backslashes in font-family; CSS Unicode character escapes are
+  now properly resolved (although *only* in font-family). Thanks Takeshi Terada
+  for reporting.
+- Improve parseCDATA algorithm to take into account newline normalization
+- Account for browser confusion between Yen character and backslash in
+  Shift_JIS encoding. This fix generalizes to any other encoding which is not
+  a strict superset of printable ASCII. Thanks Takeshi Terada for reporting.
+- Fix missing configuration parameter in Generator calls. Thanks vs for the
+  partial patch.
+- Improved adherence to Unicode by checking for non-character codepoints.
+  Thanks Geoffrey Sneddon for reporting. This may result in degraded
+  performance for extremely large inputs.
+- Allow CSS property-value pair ''text-decoration: none''. Thanks Jochem Blok
+  for reporting.
+. Added HTMLPurifier_UnitConverter and HTMLPurifier_Length for convenient
+  handling of CSS-style lengths. HTMLPurifier_AttrDef_CSS_Length now uses
+  this class.
+. API of HTMLPurifier_AttrDef_CSS_Length changed from __construct($disable_negative)
+  to __construct($min, $max). __construct(true) is equivalent to
+  __construct('0').
+. Added HTMLPurifier_AttrDef_Switch class
+. Rename HTMLPurifier_HTMLModule_Tidy->construct() to setup() and bubble method
+  up inheritance hierarchy to HTMLPurifier_HTMLModule. All HTMLModules
+  get this called with the configuration object.  All modules now
+  use this rather than __construct(), although legacy code using constructors
+  will still work--the new format, however, lets modules access the
+  configuration object for HTML namespace dependant tweaks.
+. AttrDef_HTML_Pixels now takes a single construction parameter, pixels.
+. ConfigSchema data-structure heavily optimized; on average it uses a third
+  the memory it did previously. The interface has changed accordingly,
+  consult changes to HTMLPurifier_Config for details.
+. Variable parsing types now are magic integers instead of strings
+. Added benchmark for ConfigSchema
+. HTMLPurifier_Generator requires $config and $context parameters. If you
+  don't know what they should be, use HTMLPurifier_Config::createDefault()
+  and new HTMLPurifier_Context().
+. Printers now properly distinguish between output configuration, and
+  target configuration. This is not applicable to scripts using
+  the Printers for HTML Purifier related tasks.
+. HTML/CSS Printers must be primed with prepareGenerator($gen_config), otherwise
+  fatal errors will ensue.
+. URIFilter->prepare can return false in order to abort loading of the filter
+. Factory for AttrDef_URI implemented, URI#embedded to indicate URI that embeds
+  an external resource.
+. %URI.Munge functionality factored out into a post-filter class.
+. Added CurrentCSSProperty context variable during CSS validation
+
+3.1.0, released 2008-05-18
+# Unnecessary references to objects (vestiges of PHP4) removed from method
+  signatures.  The following methods do not need references when assigning from
+  them and will result in E_STRICT errors if you try:
+    + HTMLPurifier_Config->get*Definition() [* = HTML, CSS]
+    + HTMLPurifier_ConfigSchema::instance()
+    + HTMLPurifier_DefinitionCacheFactory::instance()
+    + HTMLPurifier_DefinitionCacheFactory->create()
+    + HTMLPurifier_DoctypeRegistry->register()
+    + HTMLPurifier_DoctypeRegistry->get()
+    + HTMLPurifier_HTMLModule->addElement()
+    + HTMLPurifier_HTMLModule->addBlankElement()
+    + HTMLPurifier_LanguageFactory::instance()
+# Printer_ConfigForm's get*() functions were static-ified
+# %HTML.ForbiddenAttributes requires attribute declarations to be in the
+  form of tag at attr, NOT tag.attr (which will throw an error and won't do
+  anything). This is for forwards compatibility with XML; you'd do best
+  to migrate an %HTML.AllowedAttributes directives to this syntax too.
+! Allow index to be false for config from form creation
+! Added HTMLPurifier::VERSION constant
+! Commas, not dashes, used for serializer IDs. This change is forwards-compatible
+  and allows for version numbers like "3.1.0-dev".
+! %HTML.Allowed deals gracefully with whitespace anywhere, anytime!
+! HTML Purifier's URI handling is a lot more robust, with much stricter
+  validation checks and better percent encoding handling. Thanks Gareth Heyes
+  for indicating security vulnerabilities from lax percent encoding.
+! Bootstrap autoloader deals more robustly with classes that don't exist,
+  preventing class_exists($class, true) from barfing.
+- InterchangeBuilder now alphabetizes its lists
+- Validation error in configdoc output fixed
+- Iconv and other encoding errors muted even with custom error handlers that
+  do not honor error_reporting
+- Add protection against imagecrash attack with CSS height/width
+- HTMLPurifier::instance() created for consistency, is equivalent to getInstance()
+- Fixed and revamped broken ConfigForm smoketest
+- Bug with bool/null fields in Printer_ConfigForm fixed
+- Bug with global forbidden attributes fixed
+- Improved error messages for allowed and forbidden HTML elements and attributes
+- Missing (or null) in configdoc documentation restored
+- If DOM throws and exception during parsing with PH5P (occurs in newer versions
+  of DOM), HTML Purifier punts to DirectLex
+- Fatal error with unserialization of ScriptRequired
+- Created directories are now chmod'ed properly
+- Fixed bug with fallback languages in LanguageFactory
+- Standalone testing setup properly with autoload
+. Out-of-date documentation revised
+. UTF-8 encoding check optimization as suggested by Diego
+. HTMLPurifier_Error removed in favor of exceptions
+. More copy() function removed; should use clone instead
+. More extensive unit tests for HTMLDefinition
+. assertPurification moved to central harness
+. HTMLPurifier_Generator accepts $config and $context parameters during
+  instantiation, not runtime
+. Double-quotes outside of attribute values are now unescaped
+
+3.1.0rc1, released 2008-04-22
+# Autoload support added. Internal require_once's removed in favor of an
+  explicit require list or autoloading. To use HTML Purifier,
+  you must now either use HTMLPurifier.auto.php
+  or HTMLPurifier.includes.php; setting the include path and including
+  HTMLPurifier.php is insufficient--in such cases include HTMLPurifier.autoload.php
+  as well to register our autoload handler (or modify your autoload function
+  to check HTMLPurifier_Bootstrap::getPath($class)). You can also use
+  HTMLPurifier.safe-includes.php for a less performance friendly but more
+  user-friendly library load.
+# HTMLPurifier_ConfigSchema static functions are officially deprecated. Schema
+  information is stored in the ConfigSchema directory, and the
+  maintenance/generate-schema-cache.php generates the schema.ser file, which
+  is now instantiated. Support for userland schema changes coming soon!
+# HTMLPurifier_Config will now throw E_USER_NOTICE when you use a directive
+  alias; to get rid of these errors just modify your configuration to use
+  the new directive name.
+# HTMLPurifier->addFilter is deprecated; built-in filters can now be
+  enabled using %Filter.$filter_name or by setting your own filters using
+  %Filter.Custom
+# Directive-level safety properties superceded in favor of module-level
+  safety. Internal method HTMLModule->addElement() has changed, although
+  the externally visible HTMLDefinition->addElement has *not* changed.
+! Extra utility classes for testing and non-library operations can
+  be found in extras/. Specifically, these are FSTools and ConfigDoc.
+  You may find a use for these in your own project, but right now they
+  are highly experimental and volatile.
+! Integration with PHPT allows for automated smoketests
+! Limited support for proprietary HTML elements, namely <marquee>, sponsored
+  by Chris. You can enable them with %HTML.Proprietary if your client
+  demands them.
+! Support for !important CSS cascade modifier. By default, this will be stripped
+  from CSS, but you can enable it using %CSS.AllowImportant
+! Support for display and visibility CSS properties added, set %CSS.AllowTricky
+  to true to use them.
+! HTML Purifier now has its own Exception hierarchy under HTMLPurifier_Exception.
+  Developer error (not enduser error) can cause these to be triggered.
+! Experimental kses() wrapper introduced with HTMLPurifier.kses.php
+! Finally %CSS.AllowedProperties for tweaking allowed CSS properties without
+  mucking around with HTMLPurifier_CSSDefinition
+! ConfigDoc output has been enhanced with version and deprecation info.
+! %HTML.ForbiddenAttributes and %HTML.ForbiddenElements implemented.
+- Autoclose now operates iteratively, i.e. <span><span><div> now has
+  both span tags closed.
+- Various HTMLPurifier_Config convenience functions now accept another parameter
+  $schema which defines what HTMLPurifier_ConfigSchema to use besides the
+  global default.
+- Fix bug with trusted script handling in libxml versions later than 2.6.28.
+- Fix bug in ExtractStyleBlocks with comments in style tags
+- Fix bug in comment parsing for DirectLex
+- Flush output now displayed when in command line mode for unit tester
+- Fix bug with rgb(0, 1, 2) color syntax with spaces inside shorthand syntax
+- HTMLPurifier_HTMLDefinition->addAttribute can now be called multiple times
+  on the same element without emitting errors.
+- Fixed fatal error in PH5P lexer with invalid tag names
+. Plugins now get their own changelogs according to project conventions.
+. Convert tokens to use instanceof, reducing memory footprint and
+  improving comparison speed.
+. Dry runs now supported in SimpleTest; testing facilities improved
+. Bootstrap class added for handling autoloading functionality
+. Implemented recursive glob at FSTools->globr
+. ConfigSchema now has instance methods for all corresponding define*
+  static methods.
+. A couple of new historical maintenance scripts were added.
+. HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php split into two files
+. tests/index.php can now be run from any directory.
+. HTMLPurifier_Token subclasses split into seperate files
+. HTMLPURIFIER_PREFIX now is defined in Bootstrap.php, NOT HTMLPurifier.php
+. HTMLPURIFIER_PREFIX can now be defined outside of HTML Purifier
+. New --php=php flag added, allows PHP executable to be specified (command
+  line only!)
+. htmlpurifier_add_test() preferred method to translate test files in to
+  classes, because it handles PHPT files too.
+. Debugger class is deprecated and will be removed soon.
+. Command line argument parsing for testing scripts revamped, now --opt value
+  format is supported.
+. Smoketests now cleanup after magic quotes
+. Generator now can output comments (however, comments are still stripped
+  from HTML Purifier output)
+. HTMLPurifier_ConfigSchema->validate() deprecated in favor of
+  HTMLPurifier_VarParser->parse()
+. Integers auto-cast into float type by VarParser.
+. HTMLPURIFIER_STRICT removed; no validation is performed on runtime, only
+  during cache generation
+. Reordered script calls in maintenance/flush.php
+. Command line scripts now honor exit codes
+. When --flush fails in unit testers, abort tests and print message
+. Improved documentation in docs/dev-flush.html about the maintenance scripts
+. copy() methods removed in favor of clone keyword
+
+3.0.0, released 2008-01-06
+# HTML Purifier is PHP 5 only! The 2.1.x branch will be maintained
+  until PHP 4 is completely deprecated, but no new features will be added
+  to it.
+  + Visibility declarations added
+  + Constructor methods renamed to __construct()
+  + PHP4 reference cruft removed (in progress)
+! CSS properties are now case-insensitive
+! DefinitionCacheFactory now can register new implementations
+! New HTMLPurifier_Filter_ExtractStyleBlocks for extracting <style> from
+  documents and cleaning their contents up. Requires the CSSTidy library
+  <http://csstidy.sourceforge.net/>. You can access the blocks with the
+  'StyleBlocks' Context variable ($purifier->context->get('StyleBlocks')).
+  The output CSS can also be "scoped" for a specific element, use:
+  %Filter.ExtractStyleBlocksScope
+! Experimental support for some proprietary CSS attributes allowed:
+  opacity (and all of the browser-specific equivalents) and scrollbar colors.
+  Enable by setting %CSS.Proprietary to true.
+- Colors missing # but in hex form will be corrected
+- CSS Number algorithm improved
+- Unit testing and multi-testing now on steroids: command lines,
+  XML output, and other goodies now added.
+. Unit tests for Injector improved
+. New classes:
+  + HTMLPurifier_AttrDef_CSS_AlphaValue
+  + HTMLPurifier_AttrDef_CSS_Filter
+. Multitest now has a file docblock
+
+2.1.3, released 2007-11-05
+! tests/multitest.php allows you to test multiple versions by running
+  tests/index.php through multiple interpreters using `phpv` shell
+  script (you must provide this script!)
+- Fixed poor include ordering for Email URI AttrDefs, causes fatal errors
+  on some systems.
+- Injector algorithm further refined: off-by-one error regarding skip
+  counts for dormant injectors fixed
+- Corrective blockquote definition now enabled for HTML 4.01 Strict
+- Fatal error when <img> tag (or any other element with required attributes)
+  has 'id' attribute fixed, thanks NykO18 for reporting
+- Fix warning emitted when a non-supported URI scheme is passed to the
+  MakeAbsolute URIFilter, thanks NykO18 (again)
+- Further refine AutoParagraph injector. Behavior inside of elements
+  allowing paragraph tags clarified: only inline content delimeted by
+  double newlines (not block elements) are paragraphed.
+- Buggy treatment of end tags of elements that have required attributes
+  fixed (does not manifest on default tag-set)
+- Spurious internal content reorganization error suppressed
+- HTMLDefinition->addElement now returns a reference to the created
+  element object, as implied by the documentation
+- Phorum mod's HTML Purifier help message expanded (unreleased elsewhere)
+- Fix a theoretical class of infinite loops from DirectLex reported
+  by Nate Abele
+- Work around unnecessary DOMElement type-cast in PH5P that caused errors
+  in PHP 5.1
+- Work around PHP 4 SimpleTest lack-of-error complaining for one-time-only
+  HTMLDefinition errors, this may indicate problems with error-collecting
+  facilities in PHP 5
+- Make ErrorCollectorEMock work in both PHP 4 and PHP 5
+- Make PH5P work with PHP 5.0 by removing unnecessary array parameter typedef
+. %Core.AcceptFullDocuments renamed to %Core.ConvertDocumentToFragment
+  to better communicate its purpose
+. Error unit tests can now specify the expectation of no errors. Future
+  iterations of the harness will be extremely strict about what errors
+  are allowed
+. Extend Injector hooks to allow for more powerful injector routines
+. HTMLDefinition->addBlankElement created, as according to the HTMLModule
+  method
+. Doxygen configuration file updated, with minor improvements
+. Test runner now checks for similarly named files in conf/ directory too.
+. Minor cosmetic change to flush-definition-cache.php: trailing newline is
+  outputted
+. Maintenance script for generating PH5P patch added, original PH5P source
+  file also added under version control
+. Full unit test runner script title made more descriptive with PHP version
+. Updated INSTALL file to state that 4.3.7 is the earliest version we
+  are actively testing
+
+2.1.2, released 2007-09-03
+! Implemented Object module for trusted users
+! Implemented experimental HTML5 parsing mode using PH5P. To use, add
+  this to your code:
+        require_once 'HTMLPurifier/Lexer/PH5P.php';
+        $config->set('Core', 'LexerImpl', 'PH5P');
+  Note that this Lexer introduces some classes not in the HTMLPurifier
+  namespace.  Also, this is PHP5 only.
+! CSS property border-spacing implemented
+- Fix non-visible parsing error in DirectLex with empty tags that have
+  slashes inside attribute values.
+- Fix typo in CSS definition: border-collapse:seperate; was incorrectly
+  accepted as valid CSS. Usually non-visible, because this styling is the
+  default for tables in most browsers. Thanks Brett Zamir for pointing
+  this out.
+- Fix validation errors in configuration form
+- Hammer out a bunch of edge-case bugs in the standalone distribution
+- Inclusion reflection removed from URISchemeRegistry; you must manually
+  include any new schema files you wish to use
+- Numerous typo fixes in documentation thanks to Brett Zamir
+. Unit test refactoring for one logical test per test function
+. Config and context parameters in ComplexHarness deprecated: instead, edit
+  the $config and $context member variables
+. HTML wrapper in DOMLex now takes DTD identifiers into account; doesn't
+  really make a difference, but is good for completeness sake
+. merge-library.php script refactored for greater code reusability and
+  PHP4 compatibility
+
+2.1.1, released 2007-08-04
+- Fix show-stopper bug in %URI.MakeAbsolute functionality
+- Fix PHP4 syntax error in standalone version
+. Add prefix directory to include path for standalone, this prevents
+  other installations from clobbering the standalone's URI schemes
+. Single test methods can be invoked by prefixing with __only
+
+2.1.0, released 2007-08-02
+# flush-htmldefinition-cache.php superseded in favor of a generic
+  flush-definition-cache.php script, you can clear a specific cache
+  by passing its name as a parameter to the script
+! Phorum mod implemented for HTML Purifier
+! With %Core.AggressivelyFixLt, <3 and similar emoticons no longer
+  trigger HTML removal in PHP5 (DOMLex). This directive is not necessary
+  for PHP4 (DirectLex).
+! Standalone file now available, which greatly reduces the amount of
+  includes (although there are still a few files that reside in the
+  standalone folder)
+! Relative URIs can now be transformed into their absolute equivalents
+  using %URI.Base and %URI.MakeAbsolute
+! Ruby implemented for XHTML 1.1
+! You can now define custom URI filtering behavior, see enduser-uri-filter.html
+  for more details
+! UTF-8 font names now supported in CSS
+- AutoFormatters emit friendly error messages if tags or attributes they
+  need are not allowed
+- ConfigForm's compactification of directive names is now configurable
+- AutoParagraph autoformatter algorithm refined after field-testing
+- XHTML 1.1 now applies XHTML 1.0 Strict cleanup routines, namely
+  blockquote wrapping
+- Contents of <style> tags removed by default when tags are removed
+. HTMLPurifier_Config->getSerial() implemented, this is extremely useful
+  for output cache invalidation
+. ConfigForm printer now can retrieve CSS and JS files as strings, in
+  case HTML Purifier's directory is not publically accessible
+. Introduce new text/itext configuration directive values: these represent
+  longer strings that would be more appropriately edited with a textarea
+. Allow newlines to act as separators for lists, hashes, lookups and
+  %HTML.Allowed
+. ConfigForm generates textareas instead of text inputs for lists, hashes,
+  lookups, text and itext fields
+. Hidden element content removal genericized: %Core.HiddenElements can
+  be used to customize this behavior, by default <script> and <style> are
+  hidden
+. Added HTMLPURIFIER_PREFIX constant, should be used instead of dirname(__FILE__)
+. Custom ChildDef added to default include list
+. URIScheme reflection improved: will not attempt to include file if class
+  already exists. May clobber autoload, so I need to keep an eye on it
+. ConfigSchema heavily optimized, will only collect information and validate
+  definitions when HTMLPURIFIER_SCHEMA_STRICT is true.
+. AttrDef_URI unit tests and implementation refactored
+. benchmarks/ directory now protected from public view with .htaccess file;
+  run the tests via command line
+. URI scheme is munged off if there is no authority and the scheme is the
+  default one
+. All unit tests inherit from HTMLPurifier_Harness, not UnitTestCase
+. Interface for URIScheme changed
+. Generic URI object to hold components of URI added, most systems involved
+  in URI validation have been migrated to use it
+. Custom filtering for URIs factored out to URIDefinition interface for
+  maximum extensibility
+
+2.0.1, released 2007-06-27
+! Tag auto-closing now based on a ChildDef heuristic rather than a
+  manually set auto_close array; some behavior may change
+! Experimental AutoFormat functionality added: auto-paragraph and
+  linkify your HTML input by setting %AutoFormat.AutoParagraph and
+  %AutoFormat.Linkify to true
+! Newlines normalized internally, and then converted back to the
+  value of PHP_EOL. If this is not desired, set your newline format
+  using %Output.Newline.
+! Beta error collection, messages are implemented for the most generic
+  cases involving Lexing or Strategies
+- Clean up special case code for <script> tags
+- Reorder includes for DefinitionCache decorators, fixes a possible
+  missing class error
+- Fixed bug where manually modified definitions were not saved via cache
+  (mostly harmless, except for the fact that it would be a little slower)
+- Configuration objects with different serials do not clobber each
+  others when revision numbers are unequal
+- Improve Serializer DefinitionCache directory permissions checks
+- DefinitionCache no longer throws errors when it encounters old
+  serial files that do not conform to the current style
+- Stray xmlns attributes removed from configuration documentation
+- configForm.php smoketest no longer has XSS vulnerability due to
+  unescaped print_r output
+- Printer adheres to configuration's directives on output format
+- Fix improperly named form field in ConfigForm printer
+. Rewire some test-cases to swallow errors rather than expect them
+. HTMLDefinition printer updated with some of the new attributes
+. DefinitionCache keys reordered to reflect precedence: version number,
+  hash, then revision number
+. %Core.DefinitionCache renamed to %Cache.DefinitionImpl
+. Interlinking in configuration documentation added using
+  Injector_PurifierLinkify
+. Directives now keep track of aliases to themselves
+. Error collector now requires a severity to be passed, use PHP's internal
+  error constants for this
+. HTMLPurifier_Config::getAllowedDirectivesForForm implemented, allows
+  much easier selective embedding of configuration values
+. Doctype objects now accept public and system DTD identifiers
+. %HTML.Doctype is now constrained by specific values, to specify a custom
+  doctype use new %HTML.CustomDoctype
+. ConfigForm truncates long directives to keep the form small, and does
+  not re-output namespaces
+
+2.0.0, released 2007-06-20
+# Completely refactored HTMLModuleManager, decentralizing safety
+  information
+# Transform modules changed to Tidy modules, which offer more flexibility
+  and better modularization
+# Configuration object now finalizes itself when a read operation is
+  performed on it, ensuring that its internal state stays consistent.
+  To revert this behavior, you can set the $autoFinalize member variable
+  off, but it's not recommended.
+# New compact syntax for AttrDef objects that can be used to instantiate
+  new objects via make()
+# Definitions (esp. HTMLDefinition) are now cached for a significant
+  performance boost. You can disable caching by setting %Core.DefinitionCache
+  to null. You CANNOT edit raw definitions without setting the corresponding
+  DefinitionID directive (%HTML.DefinitionID for HTMLDefinition).
+# Contents between <script> tags are now completely removed if <script>
+  is not allowed
+# Prototype-declarations for Lexer removed in favor of configuration
+  determination of Lexer implementations.
+! HTML Purifier now works in PHP 4.3.2.
+! Configuration form-editing API makes tweaking HTMLPurifier_Config a
+  breeze!
+! Configuration directives that accept hashes now allow new string
+  format: key1:value1,key2:value2
+! ConfigDoc now factored into OOP design
+! All deprecated elements now natively supported
+! Implement TinyMCE styled whitelist specification format in
+  %HTML.Allowed
+! Config object gives more friendly error messages when things go wrong
+! Advanced API implemented: easy functions for creating elements (addElement)
+  and attributes (addAttribute) on HTMLDefinition
+! Add native support for required attributes
+- Deprecated and removed EnableRedundantUTF8Cleaning. It didn't even work!
+- DOMLex will not emit errors when a custom error handler that does not
+  honor error_reporting is used
+- StrictBlockquote child definition refrains from wrapping whitespace
+  in tags now.
+- Bug resulting from tag transforms to non-allowed elements fixed
+- ChildDef_Custom's regex generation has been improved, removing several
+  false positives
+. Unit test for ElementDef created, ElementDef behavior modified to
+  be more flexible
+. Added convenience functions for HTMLModule constructors
+. AttrTypes now has accessor functions that should be used instead
+  of directly manipulating info
+. TagTransform_Center deprecated in favor of generic TagTransform_Simple
+. Add extra protection in AttrDef_URI against phantom Schemes
+. Doctype object added to HTMLDefinition which describes certain aspects
+  of the operational document type
+. Lexer is now pre-emptively included, with a conditional include for the
+  PHP5 only version.
+. HTMLDefinition and CSSDefinition have a common parent class: Definition.
+. DirectLex can now track line-numbers
+. Preliminary error collector is in place, although no code actually reports
+  errors yet
+. Factor out most of ValidateAttributes to new AttrValidator class
+
+1.6.1, released 2007-05-05
+! Support for more deprecated attributes via transformations:
+  + hspace and vspace in img
+  + size and noshade in hr
+  + nowrap in td
+  + clear in br
+  + align in caption, table, img and hr
+  + type in ul, ol and li
+! DirectLex now preserves text in which a < bracket is followed by
+  a non-alphanumeric character. This means that certain emoticons
+  are now preserved.
+! %Core.RemoveInvalidImg is now operational, when set to false invalid
+  images will hang around with an empty src
+! target attribute in a tag supported, use %Attr.AllowedFrameTargets
+  to enable
+! CSS property white-space now allows nowrap (supported in all modern
+  browsers) but not others (which have spotty browser implementations)
+! XHTML 1.1 mode now sort-of works without any fatal errors, and
+  lang is now moved over to xml:lang.
+! Attribute transformation smoketest available at smoketests/attrTransform.php
+! Transformation of font's size attribute now handles super-large numbers
+- Possibly fatal bug with __autoload() fixed in module manager
+- Invert HTMLModuleManager->addModule() processing order to check
+  prefixes first and then the literal module
+- Empty strings get converted to empty arrays instead of arrays with
+  an empty string in them.
+- Merging in attribute lists now works.
+. Demo script removed: it has been added to the website's repository
+. Basic.php script modified to work out of the box
+. Refactor AttrTransform classes to reduce duplication
+. AttrTransform_TextAlign axed in favor of a more general
+  AttrTransform_EnumToCSS, refer to HTMLModule/TransformToStrict.php to
+  see how the new equivalent is implemented
+. Unit tests now use exclusively assertIdentical
+
+1.6.0, released 2007-04-01
+! Support for most common deprecated attributes via transformations:
+  + bgcolor in td, th, tr and table
+  + border in img
+  + name in a and img
+  + width in td, th and hr
+  + height in td, th
+! Support for CSS attribute 'height' added
+! Support for rel and rev attributes in a tags added, use %Attr.AllowedRel
+  and %Attr.AllowedRev to activate
+- You can define ID blacklists using regular expressions via
+  %Attr.IDBlacklistRegexp
+- Error messages are emitted when you attempt to "allow" elements or
+  attributes that HTML Purifier does not support
+- Fix segfault in unit test. The problem is not very reproduceable and
+  I don't know what causes it, but a six line patch fixed it.
+
+1.5.0, released 2007-03-23
+! Added a rudimentary I18N and L10N system modeled off MediaWiki. It
+  doesn't actually do anything yet, but keep your eyes peeled.
+! docs/enduser-utf8.html explains how to use UTF-8 and HTML Purifier
+! Newly structured HTMLDefinition modeled off of XHTML 1.1 modules.
+  I am loathe to release beta quality APIs, but this is exactly that;
+  don't use the internal interfaces if you're not willing to do migration
+  later on.
+- Allow 'x' subtag in language codes
+- Fixed buggy chameleon-support for ins and del
+. Added support for IDREF attributes (i.e. for)
+. Renamed HTMLPurifier_AttrDef_Class to HTMLPurifier_AttrDef_Nmtokens
+. Removed context variable ParentType, replaced with IsInline, which
+  is false when you're not inline and an integer of the parent that
+  caused you to become inline when you are (so possibly zero)
+. Removed ElementDef->type in favor of ElementDef->descendants_are_inline
+  and HTMLDefinition->content_sets
+. StrictBlockquote now reports what elements its supposed to allow,
+  rather than what it does allow
+. Removed HTMLDefinition->info_flow_elements in favor of
+  HTMLDefinition->content_sets['Flow']
+. Removed redundant "exclusionary" definitions from DTD roster
+. StrictBlockquote now requires a construction parameter as if it
+  were an Required ChildDef, this is the "real" set of allowed elements
+. AttrDef partitioned into HTML, CSS and URI segments
+. Modify Youtube filter regexp to be multiline
+. Require both PHP5 and DOM extension in order to use DOMLex, fixes
+  some edge cases where a DOMDocument class exists in a PHP4 environment
+  due to DOM XML extension.
+
+1.4.1, released 2007-01-21
+! docs/enduser-youtube.html updated according to new functionality
+- YouTube IDs can have underscores and dashes
+
+1.4.0, released 2007-01-21
+! Implemented list-style-image, URIs now allowed in list-style
+! Implemented background-image, background-repeat, background-attachment
+  and background-position CSS properties. Shorthand property background
+  supports all of these properties.
+! Configuration documentation looks nicer
+! Added %Core.EscapeNonASCIICharacters to workaround loss of Unicode
+  characters while %Core.Encoding is set to a non-UTF-8 encoding.
+! Support for configuration directive aliases added
+! Config object can now be instantiated from ini files
+! YouTube preservation code added to the core, with two lines of code
+  you can add it as a filter to your code. See smoketests/preserveYouTube.php
+  for sample code.
+! Moved SLOW to docs/enduser-slow.html and added code examples
+- Replaced version check with functionality check for DOM (thanks Stephen
+  Khoo)
+. Added smoketest 'all.php', which loads all other smoketests via frames
+. Implemented AttrDef_CSSURI for url(http://google.com) style declarations
+. Added convenient single test selector form on test runner
+
+1.3.2, released 2006-12-25
+! HTMLPurifier object now accepts configuration arrays, no need to manually
+  instantiate a configuration object
+! Context object now accessible to outside
+! Added enduser-youtube.html, explains how to embed YouTube videos. See
+  also corresponding smoketest preserveYouTube.php.
+! Added purifyArray(), which takes a list of HTML and purifies it all
+! Added static member variable $version to HTML Purifier with PHP-compatible
+  version number string.
+- Fixed fatal error thrown by upper-cased language attributes
+- printDefinition.php: added labels, added better clarification
+. HTMLPurifier_Config::create() added, takes mixed variable and converts into
+  a HTMLPurifier_Config object.
+
+1.3.1, released 2006-12-06
+! Added HTMLPurifier.func.php stub for a convenient function to call the library
+- Fixed bug in RemoveInvalidImg code that caused all images to be dropped
+  (thanks to .mario for reporting this)
+. Standardized all attribute handling variables to attr, made it plural
+
+1.3.0, released 2006-11-26
+# Invalid images are now removed, rather than replaced with a dud
+  <img src="" alt="Invalid image" />. Previous behavior can be restored
+  with new directive %Core.RemoveInvalidImg set to false.
+! (X)HTML Strict now supported
+  + Transparently handles inline elements in block context (blockquote)
+! Added GET method to demo for easier validation, added 50kb max input size
+! New directive %HTML.BlockWrapper, for block-ifying inline elements
+! New directive %HTML.Parent, allows you to only allow inline content
+! New directives %HTML.AllowedElements and %HTML.AllowedAttributes to let
+  users narrow the set of allowed tags
+! <li value="4"> and <ul start="2"> now allowed in loose mode
+! New directives %URI.DisableExternalResources and %URI.DisableResources
+! New directive %Attr.DisableURI, which eliminates all hyperlinking
+! New directive %URI.Munge, munges URI so you can use some sort of redirector
+  service to avoid PageRank leaks or warn users that they are exiting your site.
+! Added spiffy new smoketest printDefinition.php, which lets you twiddle with
+  the configuration settings and see how the internal rules are affected.
+! New directive %URI.HostBlacklist for blocking links to bad hosts.
+  xssAttacks.php smoketest updated accordingly.
+- Added missing type to ChildDef_Chameleon
+- Remove Tidy option from demo if there is not Tidy available
+. ChildDef_Required guards against empty tags
+. Lookup table HTMLDefinition->info_flow_elements added
+. Added peace-of-mind variable initialization to Strategy_FixNesting
+. Added HTMLPurifier->info_parent_def, parent child processing made special
+. Added internal documents briefly summarizing future progression of HTML
+. HTMLPurifier_Config->getBatch($namespace) added
+. More lenient casting to bool from string in HTMLPurifier_ConfigSchema
+. Refactored ChildDef classes into their own files
+
+1.2.0, released 2006-11-19
+# ID attributes now disabled by default. New directives:
+  + %HTML.EnableAttrID - restores old behavior by allowing IDs
+  + %Attr.IDPrefix - %Attr.IDBlacklist alternative that munges all user IDs
+    so that they don't collide with your IDs
+  + %Attr.IDPrefixLocal - Same as above, but for when there are multiple
+    instances of user content on the page
+  + Profuse documentation on how to use these available in docs/enduser-id.txt
+! Added MODx plugin <http://modxcms.com/forums/index.php/topic,6604.0.html>
+! Added percent encoding normalization
+! XSS attacks smoketest given facelift
+! Configuration documentation now has table of contents
+! Added %URI.DisableExternal, which prevents links to external websites.  You
+  can also use %URI.Host to permit absolute linking to subdomains
+! Non-accessible resources (ex. mailto) blocked from embedded URIs (img src)
+- Type variable in HTMLDefinition was not being set properly, fixed
+- Documentation updated
+  + TODO added request Phalanger
+  + TODO added request Native compression
+  + TODO added request Remove redundant tags
+  + TODO added possible plaintext formatter for HTML Purifier documentation
+  + Updated ConfigDoc TODO
+  + Improved inline comments in AttrDef/Class.php, AttrDef/CSS.php
+    and AttrDef/Host.php
+  + Revamped documentation into HTML, along with misc updates
+- HTMLPurifier_Context doesn't throw a variable reference error if you attempt
+  to retrieve a non-existent variable
+. Switched to purify()-wide Context object registry
+. Refactored unit tests to minimize duplication
+. XSS attack sheet updated
+. configdoc.xml now has xml:space attached to default value nodes
+. Allow configuration directives to permit null values
+. Cleaned up test-cases to remove unnecessary swallowErrors()
+
+1.1.2, released 2006-09-30
+! Add HTMLPurifier.auto.php stub file that configures include_path
+- Documentation updated
+  + INSTALL document rewritten
+  + TODO added semi-lossy conversion
+  + API Doxygen docs' file exclusions updated
+  + Added notes on HTML versus XML attribute whitespace handling
+  + Noted that HTMLPurifier_ChildDef_Custom isn't being used
+  + Noted that config object's definitions are cached versions
+- Fixed lack of attribute parsing in HTMLPurifier_Lexer_PEARSax3
+- ftp:// URIs now have their typecodes checked
+- Hooked up HTMLPurifier_ChildDef_Custom's unit tests (they weren't being run)
+. Line endings standardized throughout project (svn:eol-style standardized)
+. Refactored parseData() to general Lexer class
+. Tester named "HTML Purifier" not "HTMLPurifier"
+
+1.1.1, released 2006-09-24
+! Configuration option to optionally Tidy up output for indentation to make up
+  for dropped whitespace by DOMLex (pretty-printing for the entire application
+  should be done by a page-wide Tidy)
+- Various documentation updates
+- Fixed parse error in configuration documentation script
+- Fixed fatal error in benchmark scripts, slightly augmented
+- As far as possible, whitespace is preserved in-between table children
+- Sample test-settings.php file included
+
+1.1.0, released 2006-09-16
+! Directive documentation generation using XSLT
+! XHTML can now be turned off, output becomes <br>
+- Made URI validator more forgiving: will ignore leading and trailing
+  quotes, apostrophes and less than or greater than signs.
+- Enforce alphanumeric namespace and directive names for configuration.
+- Table child definition made more flexible, will fix up poorly ordered elements
+. Renamed ConfigDef to ConfigSchema
+
+1.0.1, released 2006-09-04
+- Fixed slight bug in DOMLex attribute parsing
+- Fixed rejection of case-insensitive configuration values when there is a
+  set of allowed values.  This manifested in %Core.Encoding.
+- Fixed rejection of inline style declarations that had lots of extra
+  space in them.  This manifested in TinyMCE.
+
+1.0.0, released 2006-09-01
+! Shorthand CSS properties implemented: font, border, background, list-style
+! Basic color keywords translated into hexadecimal values
+! Table CSS properties implemented
+! Support for charsets other than UTF-8 (defined by iconv)
+! Malformed UTF-8 and non-SGML character detection and cleaning implemented
+- Fixed broken numeric entity conversion
+- API documentation completed
+. (HTML|CSS)Definition de-singleton-ized
+
+1.0.0beta, released 2006-08-16
+! First public release, most functionality implemented. Notable omissions are:
+  + Shorthand CSS properties
+  + Table CSS properties
+  + Deprecated attribute transformations
+
+    vim: et sw=4 sts=4
diff --git a/application/libraries/jwysiwyg/common.php b/application/libraries/jwysiwyg/common.php
new file mode 100644
index 0000000..0561540
--- /dev/null
+++ b/application/libraries/jwysiwyg/common.php
@@ -0,0 +1,250 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * PHP handler for jWYSIWYG file uploader.
+ *
+ * By Alec Gorge <alecgorge at gmail.com>
+ */
+
+class Config {
+	public static $instance;
+
+	public static function getInstance() {
+		if(!(self::$instance instanceof Config)) {
+			self::$instance = new Config();
+		}
+		return self::$instance;
+	}
+
+	private $valid_extensions = array();
+	private $capabilities = array();
+	private $root_dir = "";
+	private $pub_dir = "";
+	private $baseUrl = '';
+
+	public function setValidExtensions(array $extensions) {
+		$this->valid_extensions = $extensions;
+	}
+
+	public function setBaseUrl($url) {
+		if ($url !== '/') {
+			$url = rtrim($url, '/');
+		}
+
+		$this->baseUrl = $url;
+	}
+
+	public function getBaseUrl() {
+		return $this->baseUrl;
+	}
+
+	public function setRootDir($dir) {
+		$dir = str_replace("\\", '/', realpath($dir));
+
+		if ($dir !== '/') {
+			$dir = rtrim($dir, '/');
+		}
+
+		$this->root_dir = $dir;
+	}
+
+	public function getRootDir() {
+		return $this->root_dir;
+	}
+
+	public function setPubDir($dir) {
+		$this->pub_dir = $this->normalizeDir($dir);
+	}
+
+	public function getPubDir() {
+		return $this->pub_dir;
+	}
+
+	public function setCapabilities(array $capabilities) {
+		$this->capabilities = $capabilities;
+	}
+
+	public function getCapabilities() {
+		return $this->capabilities;
+	}
+
+	public function getValidExtensions() {
+		return $this->valid_extensions;
+	}
+
+	public function extensionIsValid ($ext) {
+		return !(array_search(trim($ext, "."), $this->getValidExtensions()) === false);
+	}
+
+	public function addValidExtension($extension) {
+		if(is_string($extension)) {
+			$this->valid_extensions[] = $extension;
+			return;
+		}
+		throw new Exception("Invalid argument. String expected.");
+	}
+
+	public function normalizeDir($dir) {
+		$dir = str_replace("\\", "/", $dir);
+		if($dir != "/") {
+			$dir = rtrim($dir, "/")."/";
+		}
+		return $dir;
+	}
+
+	public function cleanFile ($f) {
+		return str_replace(array(
+			"../",
+			"..\\"
+		), array("", ""), $f);
+	}
+}
+
+Config::getInstance()->setValidExtensions($accepted_extensions);
+Config::getInstance()->setCapabilities($capabilities);
+Config::getInstance()->setRootDir($uploads_dir);
+Config::getInstance()->setPubDir($uploads_access_dir);
+Config::getInstance()->setBaseUrl($base_url);
+
+function json_response ($json, $exit = true) {
+	if(!is_string($json)) {
+		$json = json_encode($json);
+	}
+	header("Content-type: application/json; charset=utf-8");
+	header("Cache-Control: no-cache, must-revalidate");
+	echo $json;
+
+	if($exit) {
+		exit();
+	}
+}
+
+class ResponseRouter {
+	public static $instance;
+	public static $Status401 = "401 Unauthorized";
+	public static $Status404 = "404 Not Found";
+
+	public static function getInstance() {
+		if(!(self::$instance instanceof ResponseRouter)) {
+			self::$instance = new ResponseRouter();
+		}
+		return self::$instance;
+	}
+
+	private $config;
+	private $clean_base_filename;
+	private $handlers = array();
+
+	public function __construct () {
+		$this->config = Config::getInstance();
+		$t_var_pass_by_ref = explode("?", $_SERVER['REQUEST_URI']);
+		$this->clean_base_filename = array_shift($t_var_pass_by_ref);
+	}
+
+	public function run () {
+		if (array_key_exists('action', $_GET)) {
+			if ($this->handlers[$_GET['action']]) {
+				$this->handle($this->handlers[$_GET['action']]);
+				return true;
+			}
+		} else if (array_key_exists('action', $_POST)) {
+			if ($this->handlers[$_POST['action']]) {
+				$this->handle($this->handlers[$_POST['action']]);
+				return true;
+			}
+		}
+
+		$this->handle($this->handlers["401"]);
+	}
+
+	public function getConfig () {
+		return $this->config;
+	}
+
+	public function getBaseFile () {
+		return $this->clean_base_filename;
+	}
+
+	private function handle(ResponseHandler $obj) {
+		if($obj->getStatusNumber($this) != 200) {
+			header($_SERVER["SERVER_PROTOCOL"]." ".$obj->getStatus($this));
+
+			// FastCGI, blecch
+			header("Status: ".$obj->getStatus($this));
+			$_SERVER['REDIRECT_STATUS'] = $obj->getStatusNumber($this);
+			// end FastCGI
+		}
+		$ret = $obj->getResponse($this);
+		if($ret instanceof ResponseHandler) {
+			$this->handle($ret);
+		}
+		else {
+			json_response($ret);
+		}
+	}
+
+	public function setHandler($name, ResponseHandler $call) {
+		$this->handlers[$name] = $call;
+	}
+
+	public function normalizeDir($dir) {
+		$dir = str_replace("\\", "/", $dir);
+		if($dir != "/") {
+			$dir = rtrim($dir, "/")."/";
+		}
+		return $dir;
+	}
+
+	public function cleanFile ($f) {
+		return str_replace(array(
+			"../",
+			"..\\"
+		), array("", ""), $f);
+	}
+}
+
+abstract class ResponseHandler {
+	public function getStatus($router) {
+		return "200 OK";
+	}
+	public function getStatusNumber($router) {
+		return 200;
+	}
+	public abstract function getResponse($router);
+}
+
+class Response404 extends ResponseHandler {
+	public function getStatus($router) {
+		return ResponseRouter::$Status404;
+	}
+
+	public function getStatusNumber($router) {
+		return 404;
+	}
+
+	public function getResponse($router) {
+		return array(
+			"success" => false,
+			"error" => "Not found"
+		);
+	}
+}
+ResponseRouter::getInstance()->setHandler("404", new Response404());
+
+class Response401 extends ResponseHandler {
+	public function getStatus($router) {
+		return ResponseRouter::$Status401;
+	}
+
+	public function getStatusNumber($router) {
+		return 401;
+	}
+
+	public function getResponse($router) {
+		return array(
+			"success" => false,
+			"error" => "Not authorized."
+		);
+	}
+}
+ResponseRouter::getInstance()->setHandler("401", new Response401());
diff --git a/application/libraries/jwysiwyg/handlers.php b/application/libraries/jwysiwyg/handlers.php
new file mode 100644
index 0000000..9ed4350
--- /dev/null
+++ b/application/libraries/jwysiwyg/handlers.php
@@ -0,0 +1,230 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * PHP handler for jWYSIWYG file uploader.
+ *
+ * By Alec Gorge <alecgorge at gmail.com>
+ */
+class AuthHandler extends ResponseHandler {
+	private $authorized = false;
+
+	public function __construct () {
+		$this->authorized = (array_key_exists('auth', $_GET) && $_GET['auth'] === 'jwysiwyg');
+	}
+
+	public function getStatus ($router) {
+		return $this->authorized ? "" : ResponseRouter::$Status401;
+	}
+
+	public function getStatusNumber ($router) {
+		return $this->authorized ? 200 : 401;
+	}
+
+	public function getResponse ($router) {
+		$cap = $router->getConfig()->getCapabilities();
+
+		$r = array();
+		$r['baseUrl'] = $router->getConfig()->getBaseUrl();
+		foreach($cap as $k => $v) {
+			// var_dump($router->getBaseFile());
+			$r[$k] = array(
+				"handler" => $router->getBaseFile(),
+				"enabled" => $v
+			);
+
+			if($k == "upload") {
+				$r[$k]["accept_ext"] = $router->getConfig()->getValidExtensions();
+			}
+		}
+		return array(
+			"success" => true,
+			"data" => $r
+		);
+	}
+}
+ResponseRouter::getInstance()->setHandler("auth", new AuthHandler());
+
+class ListHandler extends ResponseHandler {
+	private function remove_path($file, $path) {
+		$path = rtrim($path, '/');
+		if (strpos($file, $path) !== false) {
+			return substr($file, strlen($path));
+		}
+	}
+
+	public function getResponse ($router) {
+		$data = array(
+			"directories" => array(),
+			"files" => array()
+		);
+		$dir = $router->normalizeDir($_GET['dir']);
+
+		if(!file_exists($router->getConfig()->getRootDir().$dir)) {
+			return new Response404();
+		}
+		foreach(new DirectoryIterator($router->getConfig()->getRootDir().$dir) as $info) {
+			if($info->isDot()) continue;
+			if($info->isFile()) {
+				$data["files"][$info->getFilename()] = $this->remove_path($info->getPathname(), $router->getConfig()->getPubDir());
+			}
+			else if($info->isDir()) {
+				$data["directories"][$info->getFilename()] = $this->remove_path($info->getPathname(), $router->getConfig()->getPubDir());
+			}
+		}
+
+		return array(
+			"success" => true,
+			"data" => $data
+		);
+	}
+}
+ResponseRouter::getInstance()->setHandler("list", new ListHandler());
+
+class RenameHandler extends ResponseHandler {
+	public function getResponse ($router) {
+		$dir = $router->normalizeDir($_GET['dir']);
+		$root = $router->getConfig()->getRootDir();
+		$file = $_GET['file'] == "null" ? "" : $router->cleanFile($_GET['file']);
+
+		if(!file_exists($root.$dir)) {
+			return new Response404();
+		}
+
+		if(rename($root.$dir.$file, $root.$dir.$router->cleanFile($_GET['newName']))) {
+			return array(
+				"success" => true,
+				"data" => "message .. "
+			);
+		}
+		else {
+			return array(
+				"success" => false,
+				"error" => "Couldn't rename the file."
+			);
+		}
+	}
+}
+ResponseRouter::getInstance()->setHandler("rename", new RenameHandler());
+
+class RemoveHandler extends ResponseHandler {
+	public function getResponse ($router) {
+		$dir = $router->normalizeDir($_GET['dir']);
+		$root = $router->getConfig()->getRootDir();
+		$file = $router->cleanFile($_GET['file']);
+
+		if (!file_exists($root.$dir.$file)) {
+			return new Response404();
+		} else if (!is_writable($root.$dir.$file)) {
+			return array(
+				'success' => false,
+				'error' => 'Don\'t have permission'
+			);
+		}
+
+		$is_removed = false;
+		if (is_dir($root.$dir.$file)) {
+			// :TODO: recursive remove
+			$is_removed = rmdir($root.$dir.$file);
+		} else {
+			$is_removed = unlink($root.$dir.$file);
+		}
+		if ($is_removed) {
+			return array(
+				"success" => true,
+				"data" => "message .. "
+			);
+		} else {
+			return array(
+				"success" => false,
+				"error" => "Couldn't delete the file."
+			);
+		}
+	}
+}
+ResponseRouter::getInstance()->setHandler("remove", new RemoveHandler());
+
+class MkdirHandler extends ResponseHandler {
+	public function getResponse ($router) {
+		$dir = $router->normalizeDir($_GET['dir']);
+		$root = $router->getConfig()->getRootDir();
+		$newName = $router->cleanFile($_GET['newName']);
+
+		if(!file_exists($root.$dir)) {
+			return new Response404();
+		}
+		if(mkdir($root.$dir.$newName)) {
+			return array(
+				"success" => true,
+				"data" => "Made: ".$root.$dir.$newName
+			);
+		}
+		else {
+			return array(
+				"success" => false,
+				"error" => "Couldn't make directory."
+			);
+		}
+	}
+}
+ResponseRouter::getInstance()->setHandler("mkdir", new MkdirHandler());
+
+class MoveHandler extends ResponseHandler {
+	public function getResponse ($router) {
+		$dir = $router->normalizeDir($_GET['dir']);
+		$root = $router->getConfig()->getRootDir();
+		$newPath = $router->cleanFile($_GET['newPath']);
+		$file = $router->cleanFile($_GET['file']);
+
+		if(!file_exists($root.$dir.$file)) {
+			return new Response404();
+		}
+		if(rename($root.$dir.$file, $root.$newPath)) {
+			return array(
+				"success" => true,
+				"data" => "message .. "
+			);
+		}
+		else {
+			return array(
+				"success" => false,
+				"error" => "Couldn't move file."
+			);
+		}
+	}
+}
+ResponseRouter::getInstance()->setHandler("move", new MoveHandler());
+
+class UploadHandler extends ResponseHandler {
+	public function getResponse ($router) {
+		$dir = $router->normalizeDir($_POST['dir']);
+		$root = $router->getConfig()->getRootDir();
+		$dst = $root . $dir . $_POST['newName'];
+
+		if (file_exists($dst)) {
+			return array(
+				'success' => false,
+				'error' => sprintf('Destination file "%s" exists.', $dir . $_POST['newName'])
+			);
+		}
+
+		if (!is_uploaded_file($_FILES['handle']['tmp_name'])) {
+			return array(
+				'success' => false,
+				'error' => 'Couldn\'t upload file.'
+			);
+		}
+
+		if (!move_uploaded_file($_FILES['handle']['tmp_name'], $dst)) {
+			return array(
+				'success' => false,
+				'error' => 'Couldn\'t upload file.'
+			);
+		}
+
+		return array(
+			'success' => true,
+			'data' => 'File upload successful.'
+		);
+	}
+}
+ResponseRouter::getInstance()->setHandler("upload", new UploadHandler());
diff --git a/application/logs/.gitignore b/application/logs/.gitignore
new file mode 100755
index 0000000..e69de29
diff --git a/application/models/actions.php b/application/models/actions.php
new file mode 100644
index 0000000..3e78c8e
--- /dev/null
+++ b/application/models/actions.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Actions
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Actions_Model extends ORM
+{
+	// Database table name
+	protected $table_name = 'actions';
+
+	protected $primary_key = 'action_id';
+}
diff --git a/application/models/actions_log.php b/application/models/actions_log.php
new file mode 100644
index 0000000..812f0f3
--- /dev/null
+++ b/application/models/actions_log.php
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model to record "actions" actions
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Actions_Log_Model extends ORM
+{	
+	// Database table name
+	protected $table_name = 'actions_log';
+}
diff --git a/application/models/alert.php b/application/models/alert.php
new file mode 100644
index 0000000..8f8ee31
--- /dev/null
+++ b/application/models/alert.php
@@ -0,0 +1,220 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Alerts
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Alert_Model extends ORM {
+	
+	/**
+	 * Many-to-one relationship definition
+	 * @var array
+	 */
+	protected $belongs_to = array('user');
+	
+	/**
+	 * One-to-many relationship definition
+	 * @var array
+	 */
+	protected $has_many = array('incident' => 'alert_sent', 'category' => 'alert_category');
+    
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'alert';
+
+	/**
+	 * Ignored columns - alert_mobile & alert_email will be replaced with alert_recipient
+	 * These are columns not contained in the Model itself
+	 * @var array
+	 */
+	protected $ignored_columns = array('alert_mobile', 'alert_email'); 	
+
+	/**
+	 * Method that provides the functionality of the magic method, __set, without the overhead
+	 * of having to instantiate a Reflection class to realize it, and provides for object chaining
+	 * 
+	 * @param string $column
+	 * @param mixed $value
+	 * @return Alert_Model
+	 */
+	public function set($column, $value)
+	{
+		// CALL the magic method __set, with the parameters provided
+		$this->__set($column, $value);
+
+		// RETURN $this for a fluent interface
+		return $this;
+
+	} // END function set
+    
+    
+	/**
+	 * Method that allows for the use of the set method, en masse
+	 * 
+	 * @param array $params
+	 * @return Alert_Model
+	 */
+	public function assign(array $params = array())
+	{
+		// ITERATE through all of the column/value pairs provided ...
+		foreach ($params as $column => $value)
+		{
+			// CALL the set method with the column/value pair
+			$this->set($column, $value);
+		}
+        
+		// RETURN $this for a fluent interface
+		return $this;
+
+	} // END function assign
+
+
+	/**
+	 * Model Validation
+	 * 
+	 * @param array $array values to check
+	 * @param boolean $save save[Optional] the record when validation succeeds
+	 * @return bool TRUE when validation succeeds, FALSE otherwise
+	 */
+	public function validate(array & $post, $save = FALSE)
+	{
+		// Initialise the validation library and setup some rules
+		$post = Validation::factory($post)
+			->pre_filter('trim')
+			->add_rules('alert_mobile', 'numeric', 'length[6,20]')
+			->add_rules('alert_email', 'email', 'length[3,64]')
+			->add_rules('alert_lat', 'required', 'between[-90,90]')
+			->add_rules('alert_lon', 'required', 'between[-180,180]')
+			->add_rules('alert_radius','required','in_array[1,5,10,20,50,100]');
+				
+		// TODO Callbacks to check for duplicate alert subscription - same
+		// subscriber for the same lat/lon
+		//$post->add_callbacks('alert_mobile', array($this, '_mobile_check'));
+		//$post->add_callbacks('alert_email', array($this, '_email_check'));
+
+		// Check if a recipient mobile phone no. or email address has been
+		// specified	
+		if (empty($post->alert_mobile) AND empty($post->alert_email))
+		{
+			$post->add_rules('alert_recipient', 'required');
+		}
+
+
+		return parent::validate($post, $save);
+		
+	} // END function validate
+
+
+    /**
+     * Callback tests if a mobile number exists in the database for this alert
+	 * @param   mixed mobile number to check
+	 * @return  boolean
+     */
+    public function _mobile_check(Validation $post)
+    {
+		// If add->rules validation found any errors, get me out of here!
+        if (array_key_exists('alert_mobile', $post->errors()) 
+            OR array_key_exists('alert_lat', $post->errors()) 
+            OR array_key_exists('alert_lon', $post->errors()))
+            return;
+
+        if ($post->alert_mobile AND (bool) $this->db
+			->where(array(
+				'alert_type' => 1,
+				'alert_recipient' => $post->alert_mobile,
+				'alert_lat' => $post->alert_lat,
+				'alert_lon' => $post->alert_lon
+				))
+			->count_records($this->table_name) )
+		{
+			$post->add_error( 'alert_mobile', 'mobile_check');
+		}
+    } // END function _mobile_check
+
+	
+	/**
+	 * Callback tests if an email accounts exists in the database for this alert
+	 * @param   mixed mobile number to check
+	 * @return  boolean
+	 */
+	public function _email_check(Validation $post)
+	{
+		// If add->rules validation found any errors, get me out of here!
+		if (array_key_exists('alert_email', $post->errors()) 
+			OR array_key_exists('alert_lat', $post->errors()) 
+			OR array_key_exists('alert_lon', $post->errors()))
+			return;
+
+		if ( $post->alert_email AND (bool) $this->db
+			->where(array(
+					'alert_type' => 2,
+					'alert_recipient' => $post->alert_email,
+					'alert_lat' => $post->alert_lat,
+					'alert_lon' => $post->alert_lon
+				))
+			->count_records($this->table_name) )
+		{
+			$post->add_error('alert_email', 'email_check');
+		}
+	} // END function _email_check
+
+	
+	/**
+	 * Checks if the alert subscription in @param $alert_code exists
+	 *
+	 * @param string $alert_code
+	 * @return bool TRUE if the alert code exists, FALSE otherwise
+	 */
+	public static function alert_code_exists($alert_code)
+	{
+		return (ORM::factory('alert')
+					->where('alert_code', $alert_code)
+					->count_all() > 0
+				);
+	}
+	
+	/**
+	 * Removes the alert code in @param $alert_code from the list of alerts
+	 *
+	 * @param string $alert_code
+	 * @return bool TRUE if succeeds, FALSE otherwise
+	 */
+	public static function unsubscribe($alert_code)
+	{
+		// Fetch the alerts with the specified code
+		$alerts = ORM::factory('alert')
+			->where('alert_code', $alert_code)
+			->find();
+		
+		// Check if the alert exists
+		if ($alerts->loaded)
+		{
+			// Delete all categories linked to the alert
+			ORM::factory('alert_category')->where('alert_id', $alerts->id)->delete_all();
+			
+			// Delete the alert
+			$alerts->delete();
+
+			// Success!
+			return TRUE;
+		}
+		else
+		{
+			// Alert code not found. FAIL
+			return FALSE;
+		}
+	}
+
+} // END class Alert_Model
diff --git a/application/models/alert_category.php b/application/models/alert_category.php
new file mode 100644
index 0000000..8011a95
--- /dev/null
+++ b/application/models/alert_category.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Categories for each Incident
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Alert_Category_Model extends ORM
+{
+	protected $belongs_to = array('alert', 'category');
+	
+	// Database table name
+	protected $table_name = 'alert_category';
+}
diff --git a/application/models/alert_sent.php b/application/models/alert_sent.php
new file mode 100644
index 0000000..ea93206
--- /dev/null
+++ b/application/models/alert_sent.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Alerts that have been sent
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Alert_Sent_Model extends ORM
+{
+	protected $belongs_to = array('alert', 'incident');
+	
+	// Database table name
+	protected $table_name = 'alert_sent';
+}
diff --git a/application/models/api_banned.php b/application/models/api_banned.php
new file mode 100644
index 0000000..ac31666
--- /dev/null
+++ b/application/models/api_banned.php
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model to record api banned actions
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Api_Banned_Model extends ORM
+{	
+	// Database table name
+	protected $table_name = 'api_banned';
+}
diff --git a/application/models/api_log.php b/application/models/api_log.php
new file mode 100644
index 0000000..9e6b400
--- /dev/null
+++ b/application/models/api_log.php
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model to record api actions
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Api_Log_Model extends ORM
+{	
+	// Database table name
+	protected $table_name = 'api_log';
+}
diff --git a/application/models/api_settings.php b/application/models/api_settings.php
new file mode 100644
index 0000000..4a92729
--- /dev/null
+++ b/application/models/api_settings.php
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') or die('No direct script access');
+
+/**
+ * Model for API Settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Api_Settings_Model extends ORM
+{
+    // Database table name
+    protected $table_name = 'api_settings';
+}
diff --git a/application/models/badge.php b/application/models/badge.php
new file mode 100644
index 0000000..cf970f7
--- /dev/null
+++ b/application/models/badge.php
@@ -0,0 +1,97 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Badge
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Badge_Model extends ORM
+{
+	// Database table name
+	protected $table_name = 'badge';
+
+	protected $primary_key = 'id';
+
+	protected $has_one = array('media');
+
+	protected $has_and_belongs_to_many = array('users');
+
+	/**
+	 * Returns all details for all badges, including images and user information
+	 * @return array
+	 */
+	public static function badges()
+	{
+		$badges = ORM::factory('badge')->find_all();
+		$arr = array();
+		foreach($badges as $badge){
+			$arr[$badge->id] = array('id'=>$badge->id,
+									'name'=>$badge->name,
+									'description'=>$badge->description,
+									'img'=>url::convert_uploaded_to_abs($badge->media->media_link),
+									'img_m'=>url::convert_uploaded_to_abs($badge->media->media_medium),
+									'img_t'=>url::convert_uploaded_to_abs($badge->media->media_thumb),
+									'users'=>array());
+			foreach($badge->users as $user)
+			{
+				$arr[$badge->id]['users'][$user->id] = $user->username;
+			}
+
+			asort($arr[$badge->id]['users']);
+		}
+
+		return $arr;
+	}
+
+	/**
+	 * Returns a simple array of badge names with badge id as the array key
+	 * @return array
+	 */
+	public static function badge_names()
+	{
+		$badges = ORM::factory('badge')->select_list('id','name');
+		return $badges;
+	}
+
+	/**
+	 * Returns an array of badges for a specific user
+	 * @return array
+	 */
+	public static function users_badges($user_id)
+	{
+		// Get assigned badge ids
+		$assigned_badges = ORM::factory('badge_user')->where(array('user_id'=>$user_id))->find_all();
+		$assigned = array();
+		foreach($assigned_badges as $assigned_badge)
+		{
+			$assigned[] = $assigned_badge->badge_id;
+		}
+		
+		$arr = array();
+		if(count($assigned) > 0)
+		{
+			// Get badges with those ids
+			$badges = ORM::factory('badge')->in('id', $assigned)->find_all();
+			foreach($badges as $badge)
+			{
+				$arr[$badge->id] = array('id'=>$badge->id,
+										'name'=>$badge->name,
+										'description'=>$badge->description,
+										'img'=>url::convert_uploaded_to_abs($badge->media->media_link),
+										'img_m'=>url::convert_uploaded_to_abs($badge->media->media_medium),
+										'img_t'=>url::convert_uploaded_to_abs($badge->media->media_thumb));
+			}
+		}
+
+		return $arr;
+	}
+}
diff --git a/application/models/badge_user.php b/application/models/badge_user.php
new file mode 100644
index 0000000..4ff182e
--- /dev/null
+++ b/application/models/badge_user.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Model for Badges of each User
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ * $Id: $
+ */
+class Badge_User_Model extends ORM {
+
+	protected $belongs_to = array('badge','users');
+
+	protected $table_name = 'badge_users';
+	
+	protected $sorting = array('badge_id' => 'asc');
+}
diff --git a/application/models/category.php b/application/models/category.php
new file mode 100644
index 0000000..d5ed748
--- /dev/null
+++ b/application/models/category.php
@@ -0,0 +1,289 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Categories of reported Incidents
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Category_Model extends ORM_Tree {	
+	/**
+	 * One-to-many relationship definition
+	 * @var array
+	 */
+	protected $has_many = array('incident' => 'incident_category', 'feed_item' => 'feed_item_category', 'category_lang');
+	
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'category';
+	
+	/**
+	 * Name of the child table for this model - recursive
+	 * @var string
+	 */ 
+	protected $children = "category";
+	
+	/**
+	 * Default sort order
+	 * @var array
+	 */
+	protected $sorting = array("category_position" => "asc");
+	
+	protected static $categories;
+	
+	/**
+	 * Validates and optionally saves a category record from an array
+	 *
+	 * @param array $array Values to check
+	 * @param bool $save Saves the record when validation succeeds
+	 * @return bool
+	 */
+	public function validate(array & $array, $save = FALSE)
+	{
+		// Set up validation
+		$array = Validation::factory($array)
+					->pre_filter('trim', TRUE)
+					->add_rules('parent_id','required', 'numeric')
+					->add_rules('category_title','required', 'length[3,80]')
+					->add_rules('category_description','required')
+					->add_rules('category_color','required', 'length[6,6]');
+		
+		
+		// When creating a new category
+		if ( empty($this->id) )
+		{
+			// Set locale to current language
+			$this->locale = Kohana::config('locale.language.0');
+		}
+		
+		// Validation checks where parent_id > 0
+		if ($array->parent_id > 0)
+		{
+			$this_parent = self::factory('category')->find($array->parent_id);
+			
+			// If parent category is trusted/special
+			if ($this_parent->category_trusted == 1)
+			{
+				Kohana::log('error', 'The parent id is a trusted category!');
+				$array->add_error('parent_id', 'parent_trusted');
+			}
+			
+			// When editing a category	
+			if ( ! empty($this->id))
+			{
+				$this_cat = self::factory('category')->find($this->id);
+				
+				// If this category is trusted/special, don't subcategorize
+				if ($this_cat->category_trusted)
+				{
+					Kohana::log('error', 'This is a special category');
+					$array->add_error('parent_id', 'special');
+				}
+				
+				// If parent category is trusted/special
+				if ($this_parent->category_trusted == 1)
+				{
+					Kohana::log('error', 'The parent id is a trusted category!');
+					$array->add_error('parent_id', 'parent_trusted');
+				}
+				
+				// If subcategories exist
+				$children = self::factory('category')->where('parent_id',$this->id)->count_all();
+				if ($children > 0 )
+				{
+					Kohana::log('error', 'This category has subcategories');
+					$array->add_error('parent_id', 'already_parent');
+				}
+				
+				// If parent and category id are the same
+				if ($this->id == $array->parent_id)
+				{
+					// Error
+					Kohana::log('error', 'The parent id and category id are the same!');
+					$array->add_error('parent_id', 'same');
+				}	
+			}
+			else
+			{
+				// Ensure parent_id exists in the DB
+				$array->add_callbacks('parent_id', 'Category_Model::is_valid_category');
+			}
+		}
+		
+		// Pass on validation to parent and return
+		return parent::validate($array, $save);
+	}
+	
+	/**
+	 * Gets the list of categories from the database as an array
+	 *
+	 * @param int $category_id Database id of the category
+	 * @param string $local Localization to use
+	 * @return array
+	 */
+	public static function categories($category_id = NULL)
+	{
+		if (! isset(self::$categories))
+		{
+			$categories = ORM::factory('category')->find_all();
+			
+			self::$categories = array();
+			foreach($categories as $category)
+			{
+				self::$categories[$category->id]['category_id'] = $category->id;
+				self::$categories[$category->id]['category_title'] = $category->category_title;
+				self::$categories[$category->id]['category_description'] = $category->category_description;
+				self::$categories[$category->id]['category_color'] = $category->category_color;
+				self::$categories[$category->id]['category_image'] = $category->category_image;
+				self::$categories[$category->id]['category_image_thumb'] = $category->category_image_thumb;
+			}
+		}
+		
+		if ($category_id)
+		{
+			return isset(self::$categories[$category_id]) ? array($category_id => self::$categories[$category_id]) : FALSE;
+		}
+		
+		return self::$categories;
+	}
+
+	/**
+	 * Checks if the specified category ID is of type INT and exists in the database
+	 *
+	 * @param	int	$category_id Database id of the category to be looked up
+	 * @return	bool
+	 */
+	public static function is_valid_category($category_id)
+	{
+		// Hiding errors/warnings here because child categories are seeing category_id as an obj and this fails poorly
+		return ( ! is_object($category_id) AND intval($category_id) > 0)
+				? self::factory('category', intval($category_id))->loaded
+				: FALSE;
+	}
+	
+	/**
+	 * Given a parent id, gets the immediate children for the category, else gets the list
+	 * of all categories with parent id 0
+	 *
+	 * @param int $parent_id
+	 * @return ORM_Iterator
+	 */
+	public static function get_categories($parent_id = FALSE, $exclude_trusted = TRUE, $exclude_hidden = TRUE)
+	{
+		
+		$where = array();
+		//a little hack to work around the way ORM handles table prefixes
+		//it seems that when you use "JOIN table_name as table_alias ON other_table.id = table_alias.id"
+		//with a database that uses prefixes, that ORM adds the prefix to "table_alias" so that you get
+		//"JOIN table_name as table_alias ON other_table.id = prefix_table_alias.id"
+		//so I added the the table prefix to the ORM code.
+		//This should be properly fixed by the good people at Kohana. Might even be fixed in Kohana 3.x
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		$categories = ORM::factory('category')
+			->join('category AS '.$table_prefix.'c_parent','category.parent_id','c_parent.id','LEFT')
+				->orderby('category.parent_id', 'ASC')
+				->orderby('category.category_position', 'ASC')
+				->orderby('category.category_title', 'ASC');
+		
+		// Check if the parent is specified, if FALSE get everything
+		if ($parent_id !== FALSE)
+		{
+			// top level
+			if ($parent_id === 0)
+			{
+				$categories->where('category.parent_id', 0);
+			}
+			// valid parent
+			elseif (self::is_valid_category($parent_id))
+			{
+				$categories->where('category.parent_id', $parent_id);
+			}
+			// invalid parent
+			else
+			{
+				// Force no results, but still returning an ORM_Iterator
+				$categories->where('1 = 0');
+			}
+		}
+			
+		// Make sure the category is visible
+		if ($exclude_hidden)
+		{
+			$categories->where('category.category_visible', '1');
+			$categories->where('(c_parent.category_visible = 1 OR c_parent.id IS NULL)');
+		}
+		
+		// Exclude trusted reports
+		if ($exclude_trusted)
+		{
+			$categories->where('category.category_trusted !=', '1');
+		}
+		
+		return $categories->find_all();
+	}
+	
+	/**
+	 * Extend the default ORM save to also update matching Category_Lang record if it exits
+	 */
+	public function save()
+	{
+		parent::save();
+		
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		$this->db->query('UPDATE `'.$table_prefix.'category_lang` SET category_title = ?, category_description = ? WHERE category_id = ? AND locale = ?',
+			$this->category_title, $this->category_description, $this->id, $this->locale
+		);
+	}
+	
+	/**
+	 * Extend the default ORM delete to remove related records
+	 */
+	public function delete()
+	{
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		// Delete category_lang entries
+		ORM::factory('category_lang')
+			->where('category_id', $this->id)
+			->delete_all();
+		
+		// Update subcategories tied to this category and make them top level
+		$this->db->query(
+			'UPDATE `'.$table_prefix.'category` SET parent_id = 0 WHERE parent_id = :category_id',
+			array(':category_id' => $this->id)
+		);
+		
+		// Unapprove all reports tied to this category only (not to multiple categories)
+		$this->db->query(
+			'UPDATE `'.$table_prefix.'incident`
+				SET incident_active = 0
+				WHERE
+					id IN (SELECT incident_id FROM `'.$table_prefix.'incident_category` WHERE category_id = :category_id)
+				AND
+					id NOT IN (SELECT DISTINCT incident_id FROM `'.$table_prefix.'incident_category` WHERE category_id != :category_id)
+			',
+			array(':category_id' => $this->id)
+		);
+
+		// Delete all incident_category entries
+		$result = ORM::factory('incident_category')
+					->where('category_id',$this->id)
+					->delete_all();
+		
+		// @todo Delete the category image
+		
+		parent::delete();
+	}
+
+	
+}
diff --git a/application/models/category_lang.php b/application/models/category_lang.php
new file mode 100644
index 0000000..e944c08
--- /dev/null
+++ b/application/models/category_lang.php
@@ -0,0 +1,134 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Localization of Categories
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Category_Lang_Model extends ORM
+{
+	protected $belongs_to = array('category');
+
+	protected $primary_key = 'id';
+
+	// Database table name
+	protected $table_name = 'category_lang';
+	
+	// Array of all category_langs
+	protected static $category_langs;
+
+	/**
+	 * Get array of category lang entries
+	 * @param int category id
+	 */
+	static function category_langs($category_id = FALSE)
+	{
+		// If we haven't already, grab all category_lang entries at once
+		// We often list all categories at once so its more efficient to just get them all.
+		if (! isset(self::$category_langs))
+		{
+			$category_langs = ORM::factory('category_lang')->find_all();
+			self::$category_langs = array();
+			
+			foreach($category_langs as $category_lang) {
+				self::$category_langs[$category_lang->category_id][$category_lang->locale]['id'] = $category_lang->id;
+				self::$category_langs[$category_lang->category_id][$category_lang->locale]['category_title'] = $category_lang->category_title;
+				self::$category_langs[$category_lang->category_id][$category_lang->locale]['category_description'] = $category_lang->category_description;
+			}
+		}
+
+		
+		// Not sure we need to bother with this
+		if ($category_id)
+		{
+			if (isset(self::$category_langs[$category_id]))
+			{
+				return array($category_id => self::$category_langs[$category_id]);
+			}
+			else
+			{
+				return array();
+			}
+		}
+
+		return self::$category_langs;
+	}
+
+	/**
+	 * Return category title in specified language
+	 * If not locale specified return default
+	 * @param int category id
+	 * @param string Locale string
+	 */
+	static function category_title($category_id, $locale = FALSE)
+	{
+		// Use default locale from settings if not specified
+		if (! $locale)
+		{
+			$locale = Kohana::config('locale.language.0');
+		}
+		
+		// Use self::category_langs() to grab all category_lang entries
+		// This function is often in a loop so only query once
+		$cat_langs = self::category_langs();
+		
+		// Return translated title if its not blank
+		if (isset($cat_langs[$category_id][$locale]) AND ! empty($cat_langs[$category_id][$locale]['category_title']))
+		{
+			return $cat_langs[$category_id][$locale]['category_title'];
+		}
+		
+		// If we didn't find one, grab the default title
+		$categories = Category_Model::categories();
+		if (isset($categories[$category_id]['category_title']))
+		{
+			return $categories[$category_id]['category_title'];
+		}
+		
+		return FALSE;
+	}
+
+	/**
+	 * Return category description in specified language
+	 * If not locale specified return default
+	 * @param int category id
+	 * @param string Locale string
+	 */
+	static function category_description($category_id, $locale = FALSE)
+	{
+		// Use default locale from settings if not specified
+		if (! $locale)
+		{
+			$locale = Kohana::config('locale.language.0');
+		}
+		
+		// Use self::category_langs() to grab all category_lang entries
+		// This function is often in a loop so only query once
+		$cat_langs = self::category_langs();
+		
+		// Return translated title if its not blank
+		if (isset($cat_langs[$category_id][$locale]) AND ! empty($cat_langs[$category_id][$locale]['category_description']))
+		{
+			return $cat_langs[$category_id][$locale]['category_description'];
+		}
+		
+		// If we didn't find one, grab the default title
+		$categories = Category_Model::categories();
+		if (isset($categories[$category_id]['category_description']))
+		{
+			return $categories[$category_id]['category_description'];
+		}
+		
+		return FALSE;
+	}
+
+}
diff --git a/application/models/city.php b/application/models/city.php
new file mode 100644
index 0000000..d6c13d1
--- /dev/null
+++ b/application/models/city.php
@@ -0,0 +1,31 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Cities where incidents occurred
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class City_Model extends ORM
+{
+	protected $belongs_to = array('country');
+	
+	// Database table name
+	protected $table_name = 'city';
+
+	/**
+	 * Gets all the cities
+	 */
+	public static function get_all()
+	{
+		return ORM::factory('city')->orderby('city', 'asc')->find_all();
+	}
+}
diff --git a/application/models/cluster.php b/application/models/cluster.php
new file mode 100644
index 0000000..b5481ac
--- /dev/null
+++ b/application/models/cluster.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Clusters
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Cluster_Model extends ORM
+{
+	protected $has_many = array('incident' => 'cluster_incident');
+	
+	// Database table name
+	protected $table_name = 'cluster';
+}
diff --git a/application/models/cluster_incident.php b/application/models/cluster_incident.php
new file mode 100644
index 0000000..7b7e341
--- /dev/null
+++ b/application/models/cluster_incident.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Cluster for each Incident
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Cluster_Incident_Model extends ORM
+{
+	protected $belongs_to = array('cluster', 'incident');
+	
+	// Database table name
+	protected $table_name = 'cluster_incident';
+}
diff --git a/application/models/comment.php b/application/models/comment.php
new file mode 100644
index 0000000..d03f3af
--- /dev/null
+++ b/application/models/comment.php
@@ -0,0 +1,14 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Comments Table Model
+*/
+
+class Comment_Model extends ORM
+{
+	protected $has_many = array('rating');
+	protected $belongs_to = array('incident','user');
+	
+	// Database table name
+	protected $table_name = 'comment';
+}
diff --git a/application/models/country.php b/application/models/country.php
new file mode 100644
index 0000000..6c98e21
--- /dev/null
+++ b/application/models/country.php
@@ -0,0 +1,104 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Countries where incidents occured
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Country_Model extends ORM
+{
+	/**
+	 * Many-to-one relationship definition
+	 *
+	 * @var array
+	 */
+	protected $belongs_to = array('location');
+	
+	/**
+	 * One-to-many relationship definition
+	 * @var array
+	 */
+	protected $has_many = array('city');
+	
+	/**
+	 * Database table name
+	 *
+	 * @var string
+	 */
+	protected $table_name = 'country';
+	
+	/**
+	 * Given a country name, returns a country model object reference. Country names
+	 * are unique so no two countries should have the same name
+	 *
+	 * @param string $country_name The name of the country
+	 * @return mixed ORM reference if country exists, FALSE otherwise
+	 */
+	public static function get_country_by_name($country_name)
+	{
+		// Find the country with the specified name
+		$country = self::factory('country')->where('country', $country_name)->find();
+		
+		// Return
+		return ($country->loaded)? $country : NULL;
+
+	}
+
+	/**
+	 * Given a country code, returns a country model object reference. Country codes are unique and ISO based
+	 *
+	 * @param string $country_code The two letter country code
+	 * @return mixed ORM reference if country exists, FALSE otherwise
+	 */
+	public static function get_country_by_code($country_code)
+	{
+		// Find the country with the specified name
+		$country = self::factory('country')->where('iso', $country_code)->find();
+
+		// Return
+		return ($country->loaded)? $country : NULL;
+
+	}
+	
+	/**
+	 * Returns a key=>value array of the list of countries in the database
+	 * ordered by the country name
+	 *
+	 * @return array
+	 */
+	public static function get_countries_list()
+	{
+		$countries = array();
+		foreach (ORM::factory('country')->orderby('country')->find_all() as $country)
+		{
+			// Check the length of the country name before adding it to the list
+			$country_name = strlen($country->country) > 35
+				? substr($country->country, 0, 35) . "..."
+				: $country->country;
+				
+			$countries[$country->id] = $country_name;
+		}
+		
+		return $countries;
+	}
+
+	/**
+	 * Gets all the cities for country
+	 */
+	public function get_cities()
+	{
+		return ORM::factory('city')
+		    ->where('country_id', $this->id)
+		    ->orderby('city', 'asc')
+		    ->find_all();
+	}
+}
diff --git a/application/models/externalapp.php b/application/models/externalapp.php
new file mode 100644
index 0000000..e83a046
--- /dev/null
+++ b/application/models/externalapp.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for External Apps Settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Externalapp_Model extends ORM
+{
+	protected $table_name = 'externalapp';
+	
+	protected $primary_key = 'id';
+}
diff --git a/application/models/feed.php b/application/models/feed.php
new file mode 100755
index 0000000..cd1602f
--- /dev/null
+++ b/application/models/feed.php
@@ -0,0 +1,61 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for RSS Feeds
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Feed_Model extends ORM
+{
+	/**
+	 * One-to-many relationship definition
+	 * @var array
+	 */
+	protected $has_many = array('feed_item');
+	
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'feed';
+	
+	/**
+	 * Validates and optionally saves a new feed record from an array
+	 *
+	 * @param array $array Values to check
+	 * @param bool $save Saves the record when validation succeeds
+	 * @return bool
+	 */
+	public function validate(array & $array, $save = FALSE)
+	{
+		// Instantiate validation
+		$array = Validation::factory($array)
+				->pre_filter('trim', TRUE)
+				->add_rules('feed_name','required', 'length[3,70]')
+				->add_rules('feed_url','required', 'url');
+		
+		return parent::validate($array, $save);
+	}
+	
+	/**
+	 * Checks if the specified feed exists in the database
+	 *
+	 * @param int $feed_id Database record ID of the feed to check
+	 * @return bool
+	 */
+	public static function is_valid_feed($feed_id)
+	{
+		return (intval($feed_id) > 0)
+			? self::factory('feed', intval($feed_id))->loaded
+			: FALSE;
+	}
+}
diff --git a/application/models/feed_item.php b/application/models/feed_item.php
new file mode 100644
index 0000000..e6a25ef
--- /dev/null
+++ b/application/models/feed_item.php
@@ -0,0 +1,20 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Feed Items Table Model
+*/
+
+class Feed_Item_Model extends ORM
+{
+	protected $belongs_to = array('feed','location');
+	
+	// Database table name
+	protected $table_name = 'feed_item';
+	
+	// HT: New code
+	protected $has_many = array(
+			'category' => 'feed_item_category',
+	);
+	// HT: End of new code
+	
+}
diff --git a/application/models/feed_item_category.php b/application/models/feed_item_category.php
new file mode 100755
index 0000000..bcdab03
--- /dev/null
+++ b/application/models/feed_item_category.php
@@ -0,0 +1,48 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+// HT: New model
+/**
+* Model for Categories for each Feed Item
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Feed_Item_Category_Model extends ORM
+{
+	protected $belongs_to = array('feed_item', 'category');
+	
+	// Database table name
+	protected $table_name = 'feed_item_category';
+	
+	/**
+	 * Assigns a category id to an feed item if it hasn't already been assigned
+	 * @param int $feed_id feed to assign the category to
+	 * @param int $category_id category id of the category you want to assign to the feed
+	 * @return array
+	 */
+	public static function assign_category_to_feed($feed_id,$category_id)
+	{
+		
+		// Check to see if it is already added to that category
+		//    If it's not, add it.
+		
+		$feed_item_category = ORM::factory('feed_item_category')->where(array('feed_item_id'=>$feed_id,'category_id'=>$category_id))->find_all();
+		
+		if( ! $feed_item_category->count() )
+		{
+			$new_feed_item_category = ORM::factory('feed_item_category');
+			$new_feed_item_category->category_id = $category_id;
+			$new_feed_item_category->feed_item_id = $feed_id;
+			$new_feed_item_category->save();
+		}
+		
+		return true;
+	}
+}
diff --git a/application/models/form.php b/application/models/form.php
new file mode 100644
index 0000000..21bea8d
--- /dev/null
+++ b/application/models/form.php
@@ -0,0 +1,53 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Forms
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Form_Model extends ORM
+{
+	/**
+	 * One-to-many relationship definition
+	 * @var array
+	 */
+	protected $has_many = array('form_field');
+	
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'form';
+	
+	/**
+	 * Given the database id, checks if a form record exists in the database
+	 *
+	 * @param int $form_id Database id of the form
+	 * @return bool
+	 */
+	public static function is_valid_form($form_id)
+	{
+		return (intval($form_id) > 0)? ORM::factory('form', $form_id)->loaded : FALSE;
+	}
+	
+	/**
+	 * Deletes the a form and all its associated data
+	 */
+	public function delete()
+	{
+		// Delete all fields associated with this form
+		ORM::factory('form_field')->where('form_id', $this->id)->delete_all();
+		
+		// Delete the field
+		parent::delete();
+	}
+}
diff --git a/application/models/form_field.php b/application/models/form_field.php
new file mode 100755
index 0000000..9cca3e1
--- /dev/null
+++ b/application/models/form_field.php
@@ -0,0 +1,140 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Form Fields
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Form_Field_Model extends ORM {
+	
+	/**
+	 * Many-to-one relationship definition
+	 * @var array
+	 */
+	protected $belongs_to = array('form');
+	
+	/**
+	 * One-to-many relationship definition
+	 * @var array
+	 */
+	protected $has_many = array('form_response', 'form_field_options');
+	
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'form_field';
+	
+	/**
+	 * Validates and optionally saves a form field record from an array
+	 *
+	 * @param array $array Values to check
+	 * @param bool $save Save the record when validation suceeds
+	 * @return bool
+	 */
+	public function validate(array & $array, $save = FALSE)
+	{
+		// Setup validation
+		$array = Validation::factory($array)
+					->pre_filter('trim', TRUE)
+					->add_rules('form_id','required', 'numeric', array('Form_Model', 'is_valid_form'))
+					->add_rules('field_type','required', 'numeric')
+					->add_rules('field_name','required', 'length[1,1000]')
+					->add_rules('field_required','required', 'between[0,1]')
+					->add_rules('field_ispublic_visible','required', 'numeric')
+					->add_rules('field_ispublic_submit','required', 'numeric');
+	
+		// Get the field type
+		$array->field_isdate = ($array->field_type == 3)? 1 : 0;
+		
+		// If this is a new form field, make sure no duplicate name is provided
+		if ($array->field_id == '')
+		{
+			$field_name = ORM::factory('form_field')
+						->where('field_name', $array->field_name)
+						->where('form_id', $array->form_id)
+						->count_all();
+			if ($field_name > 0)
+			{
+				$array->add_error('field_name', 'duplicate');
+			}
+		}
+		// Ensure that checkboxes and radio buttons have a default value
+		if ($array->field_type == 5 OR $array->field_type == 6 OR $array->field_type == 7)
+		{
+			$array->add_rules('field_default', 'required', 'standard_text');
+		}
+				
+		// Check if field width and height have been specified	
+		if ( ! empty($array->field_width))
+		{
+			$array->add_rules('field_width', 'between[0,300]');
+		}
+
+		if ( ! empty($array->field_height))
+		{
+			$array->add_rules('field_height', 'between[0,50]');
+		}
+
+		if ( ! empty($array->field_default))
+		{
+			$array->add_rules('field_default', 'length[1,10000]');	
+		}
+		
+		// If date field, and default value is not empty, add date validation rules
+		if ( ! empty($array->field_default) AND !empty($array->field_isdate))
+		{
+			$array->add_rules('field_default', array('valid', 'date_mmddyyyy'));
+		}
+
+		// Return
+		return parent::validate($array, $save);
+	}
+	
+	/**
+	 * Given the database id, checks if a form field record exists in the datbase
+	 *
+	 * @param int $field_id Database id of the form field record
+	 * @return bool
+	 */
+	public static function is_valid_form_field($field_id)
+	{
+		return (intval($field_id) > 0)? ORM::factory('form_field', $field_id)->loaded : FALSE;
+	}
+	
+	/**
+	 * Deletes the a form field and all its associated data
+	 */
+	public function delete()
+	{
+		// Delete all responses associated with this field
+		ORM::factory('form_response')->where('form_field_id', $this->id)->delete_all();
+		// Delete all responses associated with this field
+		ORM::factory('form_field_option')->where('form_field_id', $this->id)->delete_all();
+		
+		// Update other fields position
+		$fields = ORM::factory('form_field')
+			->where(array(
+				'form_id' => $this->form_id, 
+				'id != ' => $this->id, 
+				'field_position > ' => $this->field_position))
+			->find_all();
+		foreach($fields as $field)
+		{
+			$field->field_position = $field->field_position - 1;
+			$field->save();
+		}
+		
+		// Delete the field
+		parent::delete();
+	}
+}
diff --git a/application/models/form_field_option.php b/application/models/form_field_option.php
new file mode 100644
index 0000000..f17e941
--- /dev/null
+++ b/application/models/form_field_option.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Form Field Options
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Form_Field_Option_Model extends ORM
+{
+	protected $belongs_to = array('form_field');
+	
+	// Database table name
+	protected $table_name = 'form_field_option';
+}
diff --git a/application/models/form_response.php b/application/models/form_response.php
new file mode 100644
index 0000000..366d23d
--- /dev/null
+++ b/application/models/form_response.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Form Responses
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Form_Response_Model extends ORM
+{
+	protected $belongs_to = array('form_field');
+	
+	// Database table name
+	protected $table_name = 'form_response';
+}
diff --git a/application/models/geometry.php b/application/models/geometry.php
new file mode 100644
index 0000000..ce3f48f
--- /dev/null
+++ b/application/models/geometry.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Geometry
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Geometry_Model extends ORM
+{
+	protected $belongs_to = array('incident');
+	
+	// Database table name
+	protected $table_name = 'geometry';
+}
diff --git a/application/models/incident.php b/application/models/incident.php
new file mode 100644
index 0000000..da7c6bf
--- /dev/null
+++ b/application/models/incident.php
@@ -0,0 +1,591 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for reported Incidents
+ *
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Incident_Model extends ORM {
+	/**
+	 * One-to-may relationship definition
+	 * @var array
+	 */
+	protected $has_many = array(
+		'category' => 'incident_category',
+		'media',
+		'verify',
+		'comment',
+		'rating',
+		'alert' => 'alert_sent',
+		'incident_lang',
+		'form_response',
+		'cluster' => 'cluster_incident',
+		'geometry'
+	);
+
+	/**
+	 * One-to-one relationship definition
+	 * @var array
+	 */
+	protected $has_one = array(
+		'location',
+		'incident_person',
+		'user',
+		'message',
+		'twitter',
+		'form'
+	);
+
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'incident';
+
+	/**
+	 * Prevents cached items from being reloaded
+	 * @var bool
+	 */
+	protected $reload_on_wakeup   = FALSE;
+
+	/**
+	 * Gets a list of all visible categories
+	 * @todo Move this to the category model
+	 * @return array
+	 */
+	public static function get_active_categories()
+	{
+		// Get all active categories
+		$categories = array();
+		foreach
+		(
+			ORM::factory('category')
+			    ->where('category_visible', '1')
+			    ->find_all() as $category)
+		{
+			// Create a list of all categories
+			$categories[$category->id] = array(
+				$category->category_title, 
+				$category->category_color
+			);
+		}
+		return $categories;
+	}
+
+	/**
+	 * Get the total number of reports
+	 *
+	 * @param boolean $approved - Only count approved reports if true
+	 * @return int
+	 */
+	public static function get_total_reports($approved = FALSE)
+	{
+		return ($approved)
+			? ORM::factory('incident')->where('incident_active', '1')->count_all()
+			: ORM::factory('incident')->count_all();
+	}
+
+	/**
+	 * Get the total number of verified or unverified reports
+	 *
+	 * @param boolean $verified - Only count verified reports if true, unverified if false
+	 * @return int
+	 */
+	public static function get_total_reports_by_verified($verified = FALSE)
+	{
+		return ($verified)
+			? ORM::factory('incident')->where('incident_verified', '1')->where('incident_active', '1')->count_all()
+			: ORM::factory('incident')->where('incident_verified', '0')->where('incident_active', '1')->count_all();
+	}
+
+	/**
+	 * Get the earliest report date
+	 *
+	 * @param boolean $approved - Oldest approved report timestamp if true (oldest overall if false)
+	 * @return string
+	 */
+	public static function get_oldest_report_timestamp($approved = TRUE)
+	{
+		$result = ($approved)
+			? ORM::factory('incident')->where('incident_active', '1')->orderby(array('incident_date'=>'ASC'))->find_all(1,0)
+			: ORM::factory('incident')->where('incident_active', '0')->orderby(array('incident_date'=>'ASC'))->find_all(1,0);
+
+		foreach($result as $report)
+		{
+			return strtotime($report->incident_date);
+		}
+	}
+
+	/**
+	 * Get the latest report date
+	 * @return string
+	 */
+	public static function get_latest_report_timestamp($approved = TRUE)
+	{
+		$result = ($approved)
+			? ORM::factory('incident')->where('incident_active', '1')->orderby(array('incident_date'=>'DESC'))->find_all(1,0)
+			: ORM::factory('incident')->where('incident_active', '0')->orderby(array('incident_date'=>'DESC'))->find_all(1,0);
+
+		foreach($result as $report)
+		{
+			return strtotime($report->incident_date);
+		}
+	}
+
+	/**
+	 * Get the number of reports by date for dashboard chart
+	 *
+	 * @param int $range No. of days in the past
+	 * @param int $user_id
+	 * @return array
+	 */
+	public static function get_number_reports_by_date($range = NULL, $user_id = NULL)
+	{
+		// Table Prefix
+		$table_prefix = Kohana::config('database.default.table_prefix');
+
+		// Database instance
+		$db = new Database();
+
+		$params = array();
+		
+		$db->select(
+				'COUNT(id) as count',
+				'DATE(incident_date) as date',
+				'MONTH(incident_date) as month',
+				'DAY(incident_date) as day',
+				'YEAR(incident_date) as year'
+			)
+			->from('incident')
+			->groupby('date')
+			->orderby('incident_date', 'ASC');
+		
+		if (!empty($user_id))
+		{
+			$db->where('user_id', $user_id);
+		}
+		
+		if (!empty($range))
+		{
+			// Use Database_Expression to sanitize range param
+			$range_expr = new Database_Expression('incident_date  >= DATE_SUB(CURDATE(), INTERVAL :range DAY)', array(':range' => (int)$range));
+			$db->where(
+				$range_expr->compile()
+			);
+		}
+		$query = $db->get();
+		$result = $query->result_array(FALSE);
+
+		$array = array();
+		foreach ($result AS $row)
+		{
+			$timestamp = mktime(0, 0, 0, $row['month'], $row['day'], $row['year']) * 1000;
+			$array["$timestamp"] = $row['count'];
+		}
+
+		return $array;
+	}
+
+	/**
+	 * Gets a list of dates of all approved incidents
+	 *
+	 * @return array
+	 */
+	public static function get_incident_dates()
+	{
+		//$incidents = ORM::factory('incident')->where('incident_active',1)->incident_date->find_all();
+		$incidents = ORM::factory('incident')->where('incident_active',1)->select_list('id', 'incident_date');
+		$array = array();
+		foreach ($incidents as $id => $incident_date)
+		{
+			$array[] = $incident_date;
+		}
+		return $array;
+	}
+
+	/**
+	 * Checks if a specified incident id is numeric and exists in the database
+	 *
+	 * @param int $incident_id ID of the incident to be looked up
+	 * @param bool $approved Whether to include un-approved reports
+	 * @return bool
+	 */
+	public static function is_valid_incident($incident_id, $approved = TRUE)
+	{
+		$where = ($approved == TRUE) ? array("incident_active" => "1") : array("id >" => 0);
+		return (intval($incident_id) > 0)
+			? ORM::factory('incident')->where($where)->find(intval($incident_id))->loaded
+			: FALSE;
+	}
+
+	/**
+	 * Gets the reports that match the conditions specified in the $where parameter
+	 * The conditions must relate to columns in the incident, location, incident_category
+	 * category and media tables
+	 *
+	 * @param array $where List of conditions to apply to the query
+	 * @param mixed $limit No. of records to fetch or an instance of Pagination
+	 * @param string $order_field Column by which to order the records
+	 * @param string $sort How to order the records - only ASC or DESC are allowed
+	 * @return Database_Result
+	 */
+	public static function get_incidents($where = array(), $limit = NULL, $order_field = NULL, $sort = NULL, $count = FALSE)
+	{
+		// Get the table prefix
+		$table_prefix = Kohana::config('database.default.table_prefix');
+
+		// To store radius parameters
+		$radius = array();
+		$having_clause = "";
+		if (array_key_exists('radius', $where))
+		{
+			// Grab the radius parameter
+			$radius = $where['radius'];
+
+			// Delete radius parameter from the list of predicates
+			unset ($where['radius']);
+		}
+
+		// Query
+		// Normal query
+		if (! $count)
+		{
+			$sql = 'SELECT DISTINCT i.id incident_id, i.incident_title, i.incident_description, i.incident_date, i.incident_mode, i.incident_active, '
+				. 'i.incident_verified, i.location_id, l.country_id, l.location_name, l.latitude, l.longitude ';
+		}
+		// Count query
+		else
+		{
+			$sql = 'SELECT COUNT(DISTINCT i.id) as report_count ';
+		}
+		
+		// Check if all the parameters exist
+		if (count($radius) > 0 AND array_key_exists('latitude', $radius) AND array_key_exists('longitude', $radius)
+			AND array_key_exists('distance', $radius))
+		{
+
+			// Calculate the distance of each point from the starting point using Spherical Law of Cosines
+			// 60 = nautical miles per degree of latitude, 1.1515 miles in every nautical mile, 1.609344 km = 1 km
+			// more details about the math here: http://sgowtham.net/ramblings/2009/08/04/php-calculating-distance-between-two-locations-given-their-gps-coordinates/
+			$sql .= ", ((ACOS(SIN(%s * PI() / 180) * SIN(l.`latitude` * PI() / 180) + COS(%s * PI() / 180) * "
+				. "	COS(l.`latitude` * PI() / 180) * COS((%s - l.`longitude`) * PI() / 180)) * 180 / PI()) * 60 * 1.1515 * 1.609344) AS distance ";
+
+			$sql = sprintf($sql, $radius['latitude'], $radius['latitude'], $radius['longitude']);
+
+			// Set the "HAVING" clause
+			$having_clause = "HAVING distance <= ".intval($radius['distance'])." ";
+		}
+
+		$sql .=  'FROM '.$table_prefix.'incident i '
+			. 'LEFT JOIN '.$table_prefix.'location l ON (i.location_id = l.id) '
+			. 'LEFT JOIN '.$table_prefix.'incident_category ic ON (ic.incident_id = i.id) '
+			. 'LEFT JOIN '.$table_prefix.'category c ON (ic.category_id = c.id) ';
+		
+		// Check if the all reports flag has been specified
+		if (array_key_exists('all_reports', $where) AND $where['all_reports'] == TRUE)
+		{
+			unset ($where['all_reports']);
+			$sql .= 'WHERE 1=1 ';
+		}
+		else
+		{
+			$sql .= 'WHERE i.incident_active = 1 ';
+		}
+
+		// Check for the additional conditions for the query
+		if ( ! empty($where) AND count($where) > 0)
+		{
+			foreach ($where as $predicate)
+			{
+				$sql .= 'AND '.$predicate.' ';
+			}
+		}
+
+		// Might need "GROUP BY i.id" do avoid dupes
+		
+		// Add the having clause
+		$sql .= $having_clause;
+
+		// Check for the order field and sort parameters
+		if ( ! empty($order_field) AND ! empty($sort) AND (strtoupper($sort) == 'ASC' OR strtoupper($sort) == 'DESC'))
+		{
+			$sql .= 'ORDER BY '.$order_field.' '.$sort.' ';
+		}
+		else
+		{
+			$sql .= 'ORDER BY i.incident_date DESC ';
+		}
+
+		// Check if the record limit has been specified
+		if ( ! empty($limit) AND is_int($limit) AND intval($limit) > 0)
+		{
+			$sql .= 'LIMIT 0, '.$limit;
+		}
+		elseif ( ! empty($limit) AND $limit instanceof Pagination_Core)
+		{
+			$sql .= 'LIMIT '.$limit->sql_offset.', '.$limit->items_per_page;
+		}
+
+		// Event to alter SQL
+		Event::run('ushahidi_filter.get_incidents_sql', $sql);
+
+		// Kohana::log('debug', $sql);
+		return Database::instance()->query($sql);
+	}
+
+	/**
+	 * Gets the comments for an incident
+	 * @param int $incident_id Database ID of the incident
+	 * @return mixed FALSE if the incident id is non-existent, ORM_Iterator if it exists
+	 */
+	public static function get_comments($incident_id)
+	{
+		if (self::is_valid_incident($incident_id))
+		{
+			$where = array(
+				'comment.incident_id' => $incident_id,
+				'comment_active' => '1',
+				'comment_spam' => '0'
+			);
+
+			// Fetch the comments
+			return ORM::factory('comment')
+					->where($where)
+					->orderby('comment_date', 'asc')
+					->find_all();
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Given an incident, gets the list of incidents within a specified radius
+	 *
+	 * @param int $incident_id Database ID of the incident to be used to fetch the neighbours
+	 * @param int $distance Radius within which to fetch the neighbouring incidents
+	 * @param int $num_neigbours Number of neigbouring incidents to fetch
+	 * @return mixed FALSE is the parameters are invalid, Result otherwise
+	 */
+	public static function get_neighbouring_incidents($incident_id, $order_by_distance = FALSE, $distance = 0, $num_neighbours = 100)
+	{
+		if (self::is_valid_incident($incident_id))
+		{
+			// Get the table prefix
+			$table_prefix = Kohana::config('database.default.table_prefix');
+
+			$incident_id = (intval($incident_id));
+
+			// Get the location object and extract the latitude and longitude
+			$location = self::factory('incident', $incident_id)->location;
+			$latitude = $location->latitude;
+			$longitude = $location->longitude;
+
+			// Garbage collection
+			unset ($location);
+
+			// Query to fetch the neighbour
+			// 60 = nautical miles per degree of latitude, 1.1515 miles in every nautical mile, 1.609344 km = 1 km
+			// more details about the math here: http://sgowtham.net/ramblings/2009/08/04/php-calculating-distance-between-two-locations-given-their-gps-coordinates/
+			$sql = "SELECT DISTINCT i.*, l.`latitude`, l.`longitude`, l.location_name, "
+				. "((ACOS(SIN( :lat * PI() / 180) * SIN(l.`latitude` * PI() / 180) + COS( :lat * PI() / 180) * "
+				. "	COS(l.`latitude` * PI() / 180) * COS(( :lon - l.`longitude`) * PI() / 180)) * 180 / PI()) * 60 * 1.1515 * 1.609344) AS distance "
+				. "FROM `".$table_prefix."incident` AS i "
+				. "INNER JOIN `".$table_prefix."location` AS l ON (l.`id` = i.`location_id`) "
+				. "WHERE i.incident_active = 1 "
+				. "AND i.id <> :incidentid ";
+
+			// Check if the distance has been specified
+			if (intval($distance) > 0)
+			{
+				$sql .= "HAVING distance <= :distance ";
+			}
+
+			// If the order by distance parameter is TRUE
+			if ($order_by_distance)
+			{
+				$sql .= "ORDER BY distance ASC ";
+			}
+			else
+			{
+				$sql .= "ORDER BY i.`incident_date` DESC ";
+			}
+
+			// Has the no. of neigbours been specified
+			if (intval($num_neighbours) > 0)
+			{
+				$sql .= "LIMIT :limit";
+			}
+		
+			// Event to alter SQL
+			Event::run('ushahidi_filter.get_neighbouring_incidents_sql', $sql);
+
+			// Fetch records and return
+			return Database::instance()->query($sql,
+				array(':lat' => $latitude, ':lon' => $longitude, ':incidentid' => $incident_id, ':limit' => (int)$num_neighbours, ':distance' => (int)$distance)
+			);
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Sets approval of an incident
+	 * @param int $incident_id
+	 * @param int $val Set to 1 or 0 for approved or not approved
+	 * @return bool
+	 */
+	public static function set_approve($incident_id,$val)
+	{
+		$incident = ORM::factory('incident',$incident_id);
+		$incident->incident_active = $val;
+		return $incident->save();
+	}
+
+	/**
+	 * Sets incident as verified or not
+	 * @param int $incident_id
+	 * @param int $val Set to 1 or 0 for verified or not verified
+	 * @return bool
+	 */
+	public static function set_verification($incident_id,$val)
+	{
+		$incident = ORM::factory('incident',$incident_id);
+		$incident->incident_verified = $val;
+		return $incident->save();
+	}
+
+	/**
+	 * Overrides the default delete method for the ORM.
+	 * Deletes all other content related to the incident - performs
+	 * an SQL destroy
+	 */
+	public function delete()
+	{
+		// Delete Location
+		ORM::factory('location')
+			->where('id', $this->location_id)
+			->delete_all();
+
+		// Delete Categories
+		ORM::factory('incident_category')
+		    ->where('incident_id', $this->id)
+		    ->delete_all();
+
+		// Delete Translations
+		ORM::factory('incident_lang')
+		    ->where('incident_id', $this->id)
+		    ->delete_all();
+
+		// Delete Photos From Directory
+		$photos = ORM::factory('media')
+				      ->where('incident_id', $this->id)
+				      ->where('media_type', 1)
+				      ->find_all();
+		
+		foreach ($photos as $photo)
+		{
+			Media_Model::delete_photo($photo->id);
+		}
+
+		// Delete Media
+		ORM::factory('media')
+		    ->where('incident_id', $this->id)
+		    ->delete_all();
+
+		// Delete Sender
+		ORM::factory('incident_person')
+		    ->where('incident_id', $this->id)
+		    ->delete_all();
+
+		// Delete relationship to SMS message
+		$updatemessage = ORM::factory('message')
+						     ->where('incident_id', $this->id)
+						     ->find();
+
+		if ($updatemessage->loaded)
+		{
+			$updatemessage->incident_id = 0;
+			$updatemessage->save();
+		}
+
+		// Delete Comments
+		ORM::factory('comment')
+			->where('incident_id', $this->id)
+			->delete_all();
+			
+		// Delete ratings
+		ORM::factory('rating')
+			->where('incident_id', $this->id)
+			->delete_all();
+
+		// Delete form responses
+		ORM::factory('form_response')
+			->where('incident_id', $this->id)
+			->delete_all();
+
+		$incident_id = $this->id;
+
+		// Action::report_delete - Deleted a Report
+		Event::run('ushahidi_action.report_delete', $incident_id);
+
+		parent::delete();
+	}
+
+	/**
+	 * Get url of this incident
+	 * @return string
+	 **/
+	public function url()
+	{
+		return self::get_url($this);
+	}
+	
+	/**
+	 * Get url for the incident object passed
+	 * @param object|int
+	 * @return string
+	 **/
+	public static function get_url($incident)
+	{
+		if (is_object($incident))
+		{
+			$id = isset($incident->incident_id) ? $incident->incident_id : $incident->id;
+		}
+		elseif (intval($incident) > 0)
+		{
+			$id = intval($incident);
+		}
+		else
+		{
+			return false;
+		}
+		
+		return url::site('reports/view/'.$id);
+	}
+
+	/**
+	 * Overrides the default save method for the ORM.
+	 * 
+	 */
+	public function save()
+	{
+		// Fire an event on every save
+		Event::run('ushahidi_action.report_save', $this);
+		
+		parent::save();
+	}
+
+}
\ No newline at end of file
diff --git a/application/models/incident_category.php b/application/models/incident_category.php
new file mode 100644
index 0000000..e757113
--- /dev/null
+++ b/application/models/incident_category.php
@@ -0,0 +1,48 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Categories for each Incident
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Incident_Category_Model extends ORM
+{
+	protected $belongs_to = array('incident', 'category');
+	
+	// Database table name
+	protected $table_name = 'incident_category';
+	
+	/**
+	 * Assigns a category id to an incident if it hasn't already been assigned
+	 * @param int $incident_id incident to assign the category to
+	 * @param int $category_id category id of the category you want to assign to the incident
+	 * @return array
+	 */
+	public static function assign_category_to_incident($incident_id,$category_id)
+	{	
+		
+		// Check to see if it is already added to that category
+		//    If it's not, add it.
+		
+		$incident_category = ORM::factory('incident_category')->where(array('incident_id'=>$incident_id,'category_id'=>$category_id))->find_all();
+		
+		if( ! $incident_category->count() )
+		{
+			$new_incident_category = ORM::factory('incident_category');
+			$new_incident_category->category_id = $category_id;
+			$new_incident_category->incident_id = $incident_id;
+			$new_incident_category->save();
+		}
+		
+		return true;
+	}
+}
diff --git a/application/models/incident_lang.php b/application/models/incident_lang.php
new file mode 100644
index 0000000..fc19113
--- /dev/null
+++ b/application/models/incident_lang.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Localizations for each Incident
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Incident_Lang_Model extends ORM
+{
+	protected $belongs_to = array('incident');
+	
+	// Database table name
+	protected $table_name = 'incident_lang';
+
+}
diff --git a/application/models/incident_person.php b/application/models/incident_person.php
new file mode 100644
index 0000000..e38037a
--- /dev/null
+++ b/application/models/incident_person.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for people who reported each Incident
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Incident_Person_Model extends ORM
+{
+	protected $belongs_to = array('location', 'incident');
+	
+	// Database table name
+	protected $table_name = 'incident_person';
+}
diff --git a/application/models/layer.php b/application/models/layer.php
new file mode 100644
index 0000000..26507a6
--- /dev/null
+++ b/application/models/layer.php
@@ -0,0 +1,90 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Layers
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Layer_Model extends ORM
+{
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'layer';
+	
+	/** 
+	 * Validates and optionally saves a new layer record from an array
+	 * 
+	 * @param array $array Values to check
+	 * @param bool $save Saves the record when validation succeeds
+	 * @return bool
+	 */
+	public function validate(array & $array, $save = FALSE)
+	{
+		// Set up validation
+		$array = Validation::factory($array)
+				->pre_filter('trim')
+				->add_rules('layer_name','required', 'length[3,80]')
+				->add_rules('layer_color','required', 'length[6,6]');
+		
+		// Add callbacks for the layer url and layer file
+		$array->add_callbacks('layer_url', array($this, 'layer_url_file_check'));
+		$array->add_callbacks('layer_file', array($this, 'layer_url_file_check'));
+		
+		// Pass validation to parent and return
+		return parent::validate($array, $save);
+	}
+	
+	/**
+	 * Performs validation checks on the layer url and layer file - Checks that at least
+	 * one of them has been specified using the applicable validation rules
+	 *
+	 * @param Validation $array Validation object containing the field names to be checked
+	 */
+	public function layer_url_file_check(Validation $array)
+	{
+		// Ensure at least a layer URL or layer file has been specified
+		if (empty($array->layer_url) AND empty($array->layer_file) AND empty($array->layer_file_old))
+		{
+			$array->add_error('layer_url', 'atleast');
+		}
+		
+		// Add validation rule for the layer URL if specified
+		if ( ! empty($array->layer_url) AND (empty($array->layer_file) OR empty($array->layer_file_old)))
+		{
+			if (! valid::url($array->layer_url))
+			{
+				$array->add_error('layer_url', 'url');
+			}
+		}
+		
+		// Check if both the layer URL and the layer file have been specified
+		if ( ! empty($array->layer_url) AND ( ! empty($array->layer_file_old) OR ! empty($array->layer_file)))
+		{
+			$array->add_error('layer_url', 'both');
+		}
+	}
+	
+	/**
+	 * Checks if the specified layer id is a valid integer and exists in the database
+	 *
+	 * @param int $layer_id 
+	 * @return bool
+	 */
+	public static function is_valid_layer($layer_id)
+	{
+		return (intval($layer_id) > 0)
+				? self::factory('layer', intval($layer_id))->loaded
+				: FALSE;
+	}
+}
diff --git a/application/models/level.php b/application/models/level.php
new file mode 100644
index 0000000..be359f2
--- /dev/null
+++ b/application/models/level.php
@@ -0,0 +1,73 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Reporter Levels
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Level_Model extends ORM
+{	
+	/**
+	 * One-many relationship definition
+	 * @var array
+	 */
+	protected $has_many = array('reporter');
+	
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'level';
+	
+	/**
+	 * Validates and optionally saves a new level record from an array
+	 *
+	 * @param array $array Values to check
+	 * @param save $save Saves the level record when validation succeeds
+	 * @return bool
+	 */
+	public function validate(array & $array, $save = FALSE)
+	{
+		// Setup validation
+		$array = Validation::factory($array)
+					->pre_filter('trim')
+					->add_rules('level_title','required', 'length[3,80]')
+					->add_rules('level_description','required')
+					->add_rules('level_weight','required');
+		
+		// Pass validation to parent and return
+		return parent::validate($array, $save);
+	}
+	
+	/**
+	 * Gets the levels as a key => value array
+	 *
+	 * @return array
+	 */
+	public static function get_array() 
+	{
+		return self::factory('level')->select_list('id', 'level_title');
+	}
+	
+	/**
+	 * Checks if the specified level id is valid and exists in the database
+	 *
+	 * @param int $level_id Level to be verified
+	 * @return bool
+	 */
+	public static function is_valid_level($level_id)
+	{
+		return (preg_match('/[1-9](\d*)/', $level_id) > 0)
+			? self::factory('level', $level_id)->loaded
+			: FALSE;
+	}
+}
diff --git a/application/models/location.php b/application/models/location.php
new file mode 100644
index 0000000..4a84ea1
--- /dev/null
+++ b/application/models/location.php
@@ -0,0 +1,48 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Locations
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Location_Model extends ORM
+{
+	/**
+	 * One-to-many relationship definition
+	 * @var array
+	 */
+	protected $has_many = array('incident', 'media', 'incident_person', 'feed_item', 'reporter');
+	
+	/**
+	 * Many-to-one relationship definition
+	 * @var array
+	 */
+	protected $has_one = array('country');
+	
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'location';
+	
+	/**
+	 * Checks if a location id exists in the database
+	 * @param int $location_id Database ID of the the location
+	 * @return bool
+	 */
+	public static function is_valid_location($location_id)
+	{
+		return (intval($location_id) > 0)
+			? ORM::factory('location', intval($location_id))->loaded
+			: FALSE;
+	}
+}
diff --git a/application/models/media.php b/application/models/media.php
new file mode 100644
index 0000000..72897ac
--- /dev/null
+++ b/application/models/media.php
@@ -0,0 +1,65 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Media files: photos, videos of incidents or locations
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Media_Model extends ORM
+{
+	protected $belongs_to = array('location', 'incident', 'message', 'badge');
+	
+	// Database table name
+	protected $table_name = 'media';
+
+	/**
+	 * Delete Photo
+	 * @param int $id The unique id of the photo to be deleted
+	 */
+	public static function delete_photo($id)
+	{
+		$photo = ORM::factory('media', $id);
+		$photo_large = $photo->media_link;
+		$photo_medium = $photo->media_medium;
+		$photo_thumb = $photo->media_thumb;
+
+		if (!empty($photo_large) AND file_exists(Kohana::config('upload.directory', TRUE).$photo_large))
+		{
+			unlink(Kohana::config('upload.directory', TRUE).$photo_large);
+		}
+		elseif (Kohana::config("cdn.cdn_store_dynamic_content") AND valid::url($photo_large))
+		{
+			cdn::delete($photo_large);
+		}
+
+		if (!empty($photo_medium) AND file_exists(Kohana::config('upload.directory', TRUE).$photo_medium))
+		{
+			unlink(Kohana::config('upload.directory', TRUE).$photo_medium);
+		}
+		elseif (Kohana::config("cdn.cdn_store_dynamic_content") AND valid::url($photo_medium))
+		{
+			cdn::delete($photo_medium);
+		}
+
+		if (!empty($photo_thumb) AND file_exists(Kohana::config('upload.directory', TRUE).$photo_thumb))
+		{
+			unlink(Kohana::config('upload.directory', TRUE).$photo_thumb);
+		}
+		elseif (Kohana::config("cdn.cdn_store_dynamic_content") AND valid::url($photo_thumb))
+		{
+			cdn::delete($photo_thumb);
+		}
+
+		// Finally Remove from DB
+		$photo->delete();
+	}
+}
diff --git a/application/models/message.php b/application/models/message.php
new file mode 100644
index 0000000..9a12fe7
--- /dev/null
+++ b/application/models/message.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Messages
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Message_Model extends ORM
+{
+	protected $belongs_to = array('incident','reporter');
+	protected $has_many = array('media');
+	
+	// Database table name
+	protected $table_name = 'message';
+}
diff --git a/application/models/openid.php b/application/models/openid.php
new file mode 100644
index 0000000..f2d2115
--- /dev/null
+++ b/application/models/openid.php
@@ -0,0 +1,13 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* OpenID Table Model
+*/
+
+class Openid_Model extends ORM
+{
+	protected $belongs_to = array('user');
+	
+	// Database table name
+	protected $table_name = 'openid';
+}
diff --git a/application/models/page.php b/application/models/page.php
new file mode 100644
index 0000000..9e3179e
--- /dev/null
+++ b/application/models/page.php
@@ -0,0 +1,62 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Pages
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Page_Model extends ORM
+{
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'page';
+	
+	/**
+	 * Validates and optionally saves a new page record from an array
+	 *
+	 * @param array $array
+	 * @param $save bool
+	 * @return bool
+	 */
+	public function validate(array & $array, $save = FALSE)
+	{
+		// Setup validation
+		$array = Validation::factory($array)
+					->pre_filter('trim', TRUE)
+					->add_rules('page_title','required', 'length[3,150]')
+					->add_rules('page_tab', 'required')
+					->add_rules('page_description','required');
+		
+		if (empty($array->page_tab))
+		{
+			$array->page_tab = $array->page_title;
+		}
+				
+		// Pass validation to parent and return
+		return parent::validate($array, $save);
+	}
+	
+	/**
+	 * Checks if a page id is non-zero and exists in the database
+	 *
+	 * @param int $page_id
+	 * @return bool
+	 */
+	public static function is_valid_page($page_id)
+	{
+		return (preg_match('/^[1-9](\d*)$/', $page_id) > 0)
+			? self::factory('page', $page_id)->loaded
+			: FALSE;
+	}
+}
diff --git a/application/models/permission.php b/application/models/permission.php
new file mode 100644
index 0000000..680d513
--- /dev/null
+++ b/application/models/permission.php
@@ -0,0 +1,31 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class Permission_Model extends ORM {
+	
+	protected $has_and_belongs_to_many = array('roles');
+	
+	public function delete()
+	{
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		// Remove records referencing this permission
+		// Have to use db->query() since we don't have an ORM model for permissions_roles
+		$this->db->query('DELETE FROM '.$table_prefix.'permissions_roles WHERE permission_id = ?',$this->id);
+
+		parent::delete();
+	}
+	
+	/**
+	 * Allows finding permissions by name.
+	 */
+	public function unique_key($id)
+	{
+		if ( ! empty($id) AND is_string($id) AND ! ctype_digit($id))
+		{
+			return 'name';
+		}
+
+		return parent::unique_key($id);
+	}
+
+} // End Permission Model
\ No newline at end of file
diff --git a/application/models/plugin.php b/application/models/plugin.php
new file mode 100644
index 0000000..59407c8
--- /dev/null
+++ b/application/models/plugin.php
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Plugins
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Plugin_Model extends ORM
+{
+	// Database table name
+	protected $table_name = 'plugin';
+}
diff --git a/application/models/private_message.php b/application/models/private_message.php
new file mode 100644
index 0000000..a5d5373
--- /dev/null
+++ b/application/models/private_message.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Private Messages
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Private_Message_Model extends ORM
+{
+	protected $belongs_to = array('user_id');
+	
+	// Database table name
+	protected $table_name = 'private_message';
+}
diff --git a/application/models/rating.php b/application/models/rating.php
new file mode 100644
index 0000000..f57ae94
--- /dev/null
+++ b/application/models/rating.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Ratings of comments and incidents
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Rating_Model extends ORM
+{
+	protected $belongs_to = array('incident', 'comment', 'user');
+	
+	// Database table name
+	protected $table_name = 'rating';
+}
diff --git a/application/models/reporter.php b/application/models/reporter.php
new file mode 100644
index 0000000..37680f5
--- /dev/null
+++ b/application/models/reporter.php
@@ -0,0 +1,35 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Reporters
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Reporter_Model extends ORM
+{
+	protected $belongs_to = array('service','level','location');
+	protected $has_many = array('incident','message');
+	
+	// Database table name
+	protected $table_name = 'reporter';
+	
+	// Create a Reporter if they do not already exist
+	function add($reporter_attrs)
+	{
+		if (count($this->where('service_id', $reporter_attrs['service_id'])->
+		                 where('service_account', $reporter_attrs['service_account'])->
+		                 find_all()) == 0)
+		{
+			$this->db->insert('reporter', $reporter_attrs);
+		}
+	}
+}
diff --git a/application/models/role.php b/application/models/role.php
new file mode 100644
index 0000000..df067cd
--- /dev/null
+++ b/application/models/role.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class Role_Model extends Auth_Role_Model {
+
+	protected $has_and_belongs_to_many = array('permissions', 'users');
+	
+	public function delete()
+	{
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		// Remove assigned users
+		// Have to use db->query() since we don't have an ORM model for roles_users
+		$this->db->query('DELETE FROM '.$table_prefix.'roles_users WHERE role_id = ?',$this->id);
+		
+		// Remove assigned permissions
+		// Have to use db->query() since we don't have an ORM model for permissions_roles
+		$this->db->query('DELETE FROM '.$table_prefix.'permissions_roles WHERE role_id = ?',$this->id);
+
+		parent::delete();
+	}
+
+} // End Role Model
\ No newline at end of file
diff --git a/application/models/scheduler.php b/application/models/scheduler.php
new file mode 100644
index 0000000..62ff7e8
--- /dev/null
+++ b/application/models/scheduler.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Scheduler
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Scheduler_Model extends ORM
+{
+	protected $has_many = array('scheduler_log');
+	
+	// Database table name
+	protected $table_name = 'scheduler';
+}
diff --git a/application/models/scheduler_log.php b/application/models/scheduler_log.php
new file mode 100644
index 0000000..6b7bad7
--- /dev/null
+++ b/application/models/scheduler_log.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model to record scheduler actions
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Scheduler_Log_Model extends ORM
+{
+	protected $belongs_to = array('scheduler');
+	
+	// Database table name
+	protected $table_name = 'scheduler_log';
+}
diff --git a/application/models/service.php b/application/models/service.php
new file mode 100644
index 0000000..d71104b
--- /dev/null
+++ b/application/models/service.php
@@ -0,0 +1,41 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Services
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Service_Model extends ORM
+{
+	protected $has_many = array('reporter');
+	//protected $has_many = array('incident');
+	
+	// Database table name
+	protected $table_name = 'service';
+	
+	function find_by_id($service_id) {
+		return ORM::factory('service', $service_id);
+	}
+	
+	function name() {
+		return $this->service_name;
+	}
+	
+	/**
+	 * Gets the list of services as an array
+	 * @return array
+	 */
+	public static function get_array() 
+	{
+    	return ORM::factory('service')->select_list('id', 'service_name');
+	}
+}
diff --git a/application/models/settings.php b/application/models/settings.php
new file mode 100644
index 0000000..e9b70db
--- /dev/null
+++ b/application/models/settings.php
@@ -0,0 +1,214 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Settings
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Settings_Model extends ORM {
+
+	/**
+	 * Database table name
+	 * @var string
+	 */
+	protected $table_name = 'settings';
+	
+	// Prevents cached items from being reloaded
+	protected $reload_on_wakeup   = FALSE;
+
+	/**
+	 * Check if settings table is still using old schema
+	 * @return bool
+	 */
+	protected static function new_schema($force = FALSE)
+	{
+		return array_key_exists('key', ORM::factory('settings')->reload_columns($force)->table_columns);
+	}
+	
+	/**
+	 * Given the setting identifier, returns its value. If the identifier
+	 * is non-existed, a NULL value is returned
+	 *
+	 * @param string $key UniqueID of the settings item
+	 *
+	 * @return string
+	 */
+	public static function get_setting($key)
+	{
+		if (self::new_schema())
+		{
+			$setting = ORM::factory('settings')->where('key', $key)->find();
+			return ($setting->loaded) ? $setting->value : NULL;
+		}
+		else
+		{
+			$setting = Database::instance()->query('SELECT * FROM `'.Kohana::config('database.default.table_prefix').'settings` LIMIT 1', $key)->current();
+			return isset($setting->$key) ? $setting->$key : NULL;
+		}
+	}
+
+	/**
+	 * Convenience method for the settings ORM when not loaded
+	 * with a specific settings value
+	 * @return string
+	 */
+	public function get($key)
+	{
+		return self::get_setting($key);
+	}
+
+	/**
+	 * Convenience method to save a single setting value
+	 *
+	 * @param string key Unique ID of the setting
+	 * @param string value Value for the setting item
+	 */
+	public static function save_setting($key, $value)
+	{
+		if (self::new_schema())
+		{
+			$setting = ORM::factory('settings')->where('key', $key)->find();
+			
+			$setting->key = $key;
+			$setting->value = $value;
+			$setting->save();
+		}
+		else
+		{
+			try
+			{
+				$settings = ORM::factory('settings', 1);
+				$settings->$key = $value;
+				$settings->save();
+			}
+			// Catch errors from missing settings and log
+			catch (Exception $e)
+			{
+				Kohana::log('alert',(string)$e);
+			}
+		}
+	}
+
+	/**
+	 * Returns a key=>value array of the unique setting identifier
+	 * and its corresponding value
+	 *
+	 * @return array
+	 */
+	public static function get_array()
+	{
+		if (self::new_schema())
+		{
+			$all_settings = ORM::factory('settings')->find_all();
+			$settings = array();
+			foreach ($all_settings as $setting)
+			{
+				$settings[$setting->key] = $setting->value;
+			}
+		}
+		else
+		{
+			$settings = ORM::factory('settings', 1)->as_array();
+		}
+
+		return $settings;
+	}
+
+	/**
+	 * Given a validation object, updates the settings table
+	 * with the values assigned to its properties
+	 *
+	 * @param Validation $settings Validation object
+	 */
+	public static function save_all(Validation $settings)
+	{
+		// For old schema throw error
+		if (! self::new_schema())
+			throw new Kohana_User_Exception('Settings database schema out of date');
+		
+		// Get all the settings
+		$all_settings = self::get_array();
+
+		// Settings update query
+		$query = sprintf("UPDATE `%ssettings` SET `value` = CASE `key` ", 
+		    Kohana::config('database.default.table_prefix'));
+
+		// Used for building the query clauses for the final query
+		$values = array();
+		$keys = array();
+		
+		// Modification date
+		$settings['date_modify'] = date("Y-m-d H:i:s",time());
+		
+		// List of value to skip
+		$skip = array('api_live');
+		$value_expr = new Database_Expression("WHEN :key THEN :value ");
+		foreach ($settings as $key => $value)
+		{
+			// If an item has been marked for skipping or is a 
+			// non-existent setting, skip current iteration
+			if (in_array($key, $skip) OR empty($key) OR ! array_key_exists($key, $all_settings))
+				continue;
+
+			// Check for the timezone
+			if ($key === 'timezone' AND $value == 0)
+			{
+				$value = NULL;
+			}
+			
+			$value_expr->param(':key', $key);
+			$value_expr->param(':value', $value);
+
+			$keys[] = $key;
+			$values[] = $value_expr->compile();
+		}
+		
+		// Construct the final query
+		$query .= implode(" ", $values)."END WHERE `key` IN :keys";
+
+		// Performa batch update
+		Database::instance()->query($query, array(':keys' => $keys));
+	}
+
+
+	/**
+	 * Given an array of settings identifiers (unique values in the 'key' column),
+	 * returns a key => value array of the identifiers with their corresponding values
+	 * An exception is thrown if the parameter is not an array or is an empty
+	 * array
+	 *
+	 * @param    array $keys 
+	 * @return   array
+	 */
+	public function get_settings($keys)
+	{
+		// For old schema just return everything
+		if (! self::new_schema())
+			return Settings::get_array();
+		
+		if ( ! is_array($keys) OR empty($keys))
+			throw new Kohana_Exception("Invalid parameters");
+
+		$selected_settings = ORM::factory('settings')
+			->in('key', $keys)
+			->find_all();
+
+		$settings = array();
+		foreach ($selected_settings as $setting)
+		{
+			$settings[$setting->key] = $setting->value;
+		}
+
+		return $settings;
+
+	}
+}
diff --git a/application/models/stats.php b/application/models/stats.php
new file mode 100644
index 0000000..1863fe1
--- /dev/null
+++ b/application/models/stats.php
@@ -0,0 +1,541 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Model for Statistics
+ *
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Stats_Model extends ORM {
+
+	static $time_out = 1;
+
+	/**
+	 * Generates the JavaScript for stats tracking
+	 */
+	public static function get_javascript()
+	{
+		// Make sure cURL is installed
+		if ( ! function_exists('curl_exec'))
+		{
+			throw new Kohana_Exception('footer.cURL_not_installed');
+			return false;
+		}
+
+		// Get the stat id
+		$stat_id = Settings_Model::get_setting('stat_id');
+
+		// If stats isn't set, ignore this
+		if ($stat_id == 0)
+			return '';
+
+		$cache = Cache::instance();
+		$tag = $cache->get(Kohana::config('settings.subdomain').'_piwiktag');
+
+		if ( ! $tag)
+		{ // Cache is Empty so Re-Cache
+
+			// Grabbing the URL to update stats URL, Name, Reports, etc on the stats server
+			$additional_query = '';
+			if (isset($_SERVER["HTTP_HOST"]))
+			{
+				// Grab the site domain from the config and trim any whitespaces
+				$site_domain = trim(Kohana::config('config.site_domain'));
+				$slashornoslash = '';
+				if (empty($site_domain) OR $site_domain{0} != '/')
+				{
+					$slashornoslash = '/';
+				}
+
+				// URL
+				$val = url::base();
+				$additional_query = '&val='.base64_encode($val);
+
+				// Site Name
+				$site_name = html::escape(Kohana::config('settings.site_name'));
+				$additional_query .= '&sitename='.base64_encode($site_name);
+
+				// Version
+				$version = Kohana::config('settings.ushahidi_version');
+				$additional_query .= '&version='.base64_encode($version);
+
+				// Report Count
+				$number_reports = ORM::factory("incident")->where("incident_active", 1)->count_all();
+				$additional_query .= '&reports='.base64_encode($number_reports);
+
+				// Latitude
+				$latitude = Kohana::config('settings.default_lat');
+				$additional_query .= '&lat='.base64_encode($latitude);
+
+				// Longitude
+				$longitude = Kohana::config('settings.default_lon');
+				$additional_query .= '&lon='.base64_encode($longitude);
+			}
+
+			$url = Kohana::config('config.external_site_protocol').'://tracker.ushahidi.com/dev.px.php?task=tc&siteid='.$stat_id.$additional_query;
+			$request = new HttpClient($url);
+			$buffer = $request->execute();
+
+			try
+			{
+				// This works because the tracking code is only wrapped in one tag
+				$tag = (string) @simplexml_load_string($buffer);
+			}
+			catch (Exception $e)
+			{
+				// In case the xml was malformed for whatever reason, we will just guess what the tag should be here
+				$tag = <<< STATSCOLLECTOR
+					<!-- Stats Collector -->
+					<script type="text/javascript">
+					setTimeout(function() {
+						var statsCollector = document.createElement('img');
+						    statsCollector.src = document.location.protocol + "//tracker.ushahidi.com/piwik/piwik.php?idsite={$stat_id}&rec=1";
+						    statsCollector.style.cssText = "width: 1px; height: 1px; opacity: 0.1;";
+
+						document.body.appendChild(statsCollector);
+					}, 100);
+					</script>
+					<!-- End Stats Collector -->
+STATSCOLLECTOR;
+			}
+
+			// Reset Cache Here
+			$cache->set(Kohana::config('settings.subdomain').'_piwiktag', $tag, array('piwiktag'), 86400); // 1 Day
+		}
+
+		return $tag;
+
+	}
+
+	/*
+	*	range will be ignored if dp1 and dp2 are set
+	*	dp1 and dp2 format is YYYY-MM-DD
+	*/
+	public static function get_hit_stats($range=30, $dp1=NULL, $dp2=NULL)
+	{
+		// Get ID for stats
+		$stat_id = Settings_Model::get_setting('stat_id');
+		$stat_key = Settings_Model::get_setting('stat_key');
+
+		$twodates = '';
+		if ($dp1 !== NULL AND $dp2 !== NULL)
+		{
+			$twodates = '&twodates='.urlencode($dp1.','.$dp2);
+		}
+
+		$stat_url = Kohana::config('config.external_site_protocol').'://tracker.ushahidi.com/px.php?stat_key='.$stat_key
+		    .'&task=stats&siteid='.urlencode($stat_id).'&period=day&range='.urlencode($range).$twodates;
+
+		// Ignore errors since we are error checking later
+
+		$response = @simplexml_load_string(self::_curl_req($stat_url));
+
+		// If we encounter an error, return false
+		if
+		(
+			isset($response->result->error[0]) OR
+			isset($response->error[0]) OR
+			! isset($response->visits->result)
+		)
+		{
+			Kohana::log('error', "Error on stats request");
+			return false;
+		}
+
+		foreach ($response->visits->result as $res)
+		{
+			$dt = $res['date'];
+			$y = substr($dt,0,4);
+			$m = substr($dt,5,2);
+			$d = substr($dt,8,2);
+			$timestamp = mktime(0,0,0,$m,$d,$y)*1000;
+
+			if (isset($res->nb_visits))
+			{
+				$data['visits'][ (string) $timestamp] = (string) $res->nb_visits;
+			}
+			else
+			{
+				$data['visits'][ (string) $timestamp] = '0';
+			}
+
+			if (isset($res->nb_uniq_visitors))
+			{
+				$data['uniques'][ (string) $timestamp] = (string) $res->nb_uniq_visitors;
+			}
+			else
+			{
+				$data['uniques'][ (string) $timestamp] = '0';
+			}
+
+			if (isset($res->nb_actions))
+			{
+				$data['pageviews'][ (string) $timestamp] = (string) $res->nb_actions;
+			}
+			else
+			{
+				$data['pageviews'][ (string) $timestamp] = '0';
+			}
+		}
+
+		return $data;
+	}
+
+	static function get_hit_countries($range=30, $dp1=NULL, $dp2=NULL)
+	{
+		$stat_id = Settings_Model::get_setting('stat_id');
+		$stat_key = Settings_Model::get_setting('stat_key');
+
+		$twodates = '';
+		if ($dp1 !== NULL AND $dp2 !== NULL)
+		{
+			$twodates = '&twodates='.urlencode($dp1.','.$dp2);
+		}
+
+		$stat_url = Kohana::config('config.external_site_protocol').'://tracker.ushahidi.com/px.php?stat_key='.$stat_key
+		    .'&task=stats&siteid='.urlencode($stat_id).'&period=day&range='.urlencode($range).$twodates;
+
+		// Ignore errors since we are error checking later
+
+		$response = @simplexml_load_string(self::_curl_req($stat_url));
+
+		// If we encounter an error, return false
+		if
+		(
+			isset($response->result->error[0]) OR
+			isset($response->error[0]) OR
+			! isset($response->countries->result)
+		)
+		{
+			Kohana::log('error', "Error on stats request");
+			return false;
+		}
+
+		$data = array();
+		foreach ($response->countries->result as $res)
+		{
+			$date = (string) $res['date'];
+			foreach ($res->row as $row)
+			{
+				$code = (string) $row->code;
+				$data[$date][$code]['label'] = (string) $row->label;
+				$data[$date][$code]['uniques'] = (string) $row->nb_uniq_visitors;
+				$logo = (string) $row->logo;
+				$data[$date][$code]['logo'] = Kohana::config('core.site_protocol').'://tracker.ushahidi.com/piwik/'.$logo;
+			}
+		}
+
+		return $data;
+
+	}
+
+	/*
+	* get an array of report counts
+	* @param approved - Only count approved reports if true
+	* @param by_time - Format array with timestamp as the key if true
+	* @param range - Number of days back from today to pull reports from. Will end up defaulting to 100000 days to get them all.
+	* @param dp1 - Arbitrary date range. Low date. YYYY-MM-DD
+	* @param dp2 - Arbitrary date range. High date. YYYY-MM-DD
+	*/
+	static function get_report_stats($approved=FALSE, $by_time=FALSE, $range=NULL, $dp1=NULL, $dp2=NULL, $line_chart_data=FALSE)
+	{
+		if ($range === NULL)
+		{
+			$range = 100000;
+		}
+
+		if ($dp1 === NULL)
+		{
+			$dp1 = 0;
+		}
+
+		if ($dp2 === NULL)
+		{
+			$dp2 = '3000-01-01';
+		}
+
+		// Set up the range calculation
+		$time = time() - ($range*86400);
+		$range_date = date('Y-m-d', $time);
+
+		// Only grab approved
+		if ($approved)
+		{
+			$reports = ORM::factory('incident')
+			    ->where('incident_active', '1')
+			    ->where('incident_date >=', $dp1)
+			    ->where('incident_date <=',$dp2)
+			    ->where('incident_date >', $range_date)
+			    ->find_all();
+		}
+		else
+		{
+			$reports = ORM::factory('incident')
+			    ->where('incident_date >=', $dp1)
+			    ->where('incident_date <=', $dp2)
+			    ->where('incident_date >', $range_date)
+			    ->find_all();
+		}
+
+		$reports_categories = ORM::factory('incident_category')->find_all();
+
+		// Initialize arrays so we don't error out
+		$report_data = array();
+		$verified_counts = array();
+		$approved_counts = array();
+		$all = array();
+		$earliest_timestamp = 32503680000; // Year 3000 in epoch so we can catch everything less than this.
+		$latest_timestamp = 0;
+
+		// Gather some data into an array on incident reports
+		$num_reports = 0;
+		foreach ($reports as $report)
+		{
+			$timestamp = (string) strtotime(substr($report->incident_date,0,10));
+			$report_data[$report->id] = array(
+				'date'=>$timestamp,
+				'mode'=>$report->incident_mode,
+				'active'=>$report->incident_active,
+				'verified'=>$report->incident_verified
+			);
+
+			if ($timestamp < $earliest_timestamp)
+			{
+				$earliest_timestamp = $timestamp;
+			}
+
+			if ($timestamp > $latest_timestamp)
+			{
+				$latest_timestamp = $timestamp;
+			}
+
+			if ( ! isset($verified_counts['verified'][$timestamp]))
+			{
+				$verified_counts['verified'][$timestamp] = 0;
+				$verified_counts['unverified'][$timestamp] = 0;
+				$approved_counts['approved'][$timestamp] = 0;
+				$approved_counts['unapproved'][$timestamp] = 0;
+				$all[$timestamp] = 0;
+			}
+
+			$all[$timestamp]++;
+
+			if ($report->incident_verified == 1)
+			{
+				$verified_counts['verified'][$timestamp]++;
+			}
+			else
+			{
+				$verified_counts['unverified'][$timestamp]++;
+			}
+
+			if ($report->incident_active == 1)
+			{
+				$approved_counts['approved'][$timestamp]++;
+			}
+			else
+			{
+				$approved_counts['unapproved'][$timestamp]++;
+			}
+			$num_reports++;
+		}
+
+		$category_counts = array();
+		$lowest_date = 9999999999; // Really far in the future.
+		$highest_date = 0;
+		foreach ($reports_categories as $report)
+		{
+			// If this report category doesn't have any reports (in case we are only
+			//  looking at approved reports), move on to the next one.
+			if ( ! isset($report_data[$report->incident_id]))
+				continue;
+
+			$c_id = $report->category_id;
+			$timestamp = $report_data[$report->incident_id]['date'];
+
+			if ($timestamp < $lowest_date)
+			{
+				$lowest_date = $timestamp;
+			}
+
+			if ($timestamp > $highest_date)
+			{
+				$highest_date = $timestamp;
+			}
+
+			if ( ! isset($category_counts[$c_id][$timestamp]))
+			{
+				$category_counts[$c_id][$timestamp] = 0;
+			}
+
+			$category_counts[$c_id][$timestamp]++;
+		}
+
+		// Populate date range
+		$date_range = array();
+		$add_date = $lowest_date;
+		while ($add_date <= $highest_date)
+		{
+			$date_range[] = $add_date;
+			$add_date += 86400;
+		}
+
+		// Zero out days that don't have a count
+		foreach ($category_counts as & $arr)
+		{
+			foreach ($date_range as $timestamp)
+			{
+				if ( ! isset($arr[$timestamp]))
+				{
+					$arr[$timestamp] = 0;
+				}
+
+				if ( ! isset($verified_counts['verified'][$timestamp]))
+				{
+					$verified_counts['verified'][$timestamp] = 0;
+				}
+
+				if ( ! isset($verified_counts['unverified'][$timestamp]))
+				{
+					$verified_counts['unverified'][$timestamp] = 0;
+				}
+
+				if ( ! isset($approved_counts['approved'][$timestamp]))
+				{
+					$approved_counts['approved'][$timestamp] = 0;
+				}
+
+				if ( ! isset($approved_counts['unapproved'][$timestamp]))
+				{
+					$approved_counts['unapproved'][$timestamp] = 0;
+				}
+
+				if ( ! isset($all[$timestamp]))
+				{
+					$all[$timestamp] = 0;
+				}
+
+			}
+			// keep dates in order
+			ksort($arr);
+			ksort($verified_counts['verified']);
+			ksort($verified_counts['unverified']);
+			ksort($approved_counts['approved']);
+			ksort($approved_counts['unapproved']);
+			ksort($all);
+
+		}
+
+		// Add all our data sets to the array we are returning
+		$data['category_counts'] = $category_counts;
+		$data['verified_counts'] = $verified_counts;
+		$data['approved_counts'] = $approved_counts;
+		$data['all']['all'] = $all;
+
+		// I'm just tacking this on here. However, we could improve performance
+		//   by implementing the code above but I just don't have the time
+		//   to mess with it.
+		if ($by_time)
+		{
+			// Reorder the array. Is there a built in PHP function that can do this?
+			$new_data = array();
+			foreach ($data as $main_key => $data_array)
+			{
+				foreach ($data_array as $key => $counts)
+				{
+
+					if ($line_chart_data == FALSE)
+					{
+						foreach ($counts as $timestamp => $count)
+						{
+							$new_data[$main_key][$timestamp][$key] = $count;
+						}
+					}
+					else
+					{
+						foreach ($counts as $timestamp => $count)
+						{
+							$timestamp_key = (string) ($timestamp*1000);
+							if ( ! isset($new_data[$main_key][$timestamp_key]))
+							{
+								$new_data[$main_key][$timestamp_key] = 0;
+							}
+							$new_data[$main_key][$timestamp_key] += $count;
+						}
+					}
+				}
+			}
+
+			$data = $new_data;
+
+		}
+
+		if ($line_chart_data == FALSE)
+		{
+			$data['total_reports'] = $num_reports;
+			$data['total_categories'] = count($category_counts);
+			$data['earliest_report_time'] = $earliest_timestamp;
+			$data['latest_report_time'] = $latest_timestamp;
+		}
+
+		return $data;
+	}
+
+	/**
+	 * Creates a new site in centralized stat tracker
+	 * @param sitename - name of the instance
+	 * @param url - base url
+	 */
+	public function create_site( $sitename, $url)
+	{
+		$stat_url = Kohana::config('config.external_site_protocol').'://tracker.ushahidi.com/px.php?task=cs&sitename='.urlencode($sitename).'&url='.urlencode($url);
+
+		// Ignore errors since we are error checking later
+
+		$xml = simplexml_load_string(self::_curl_req($stat_url));
+
+		if ($xml === false)
+		{
+			return false;
+		}
+
+		$stat_id = (string) $xml->id[0];
+		$stat_key = (string) $xml->key[0];
+
+		if ($stat_id > 0)
+		{
+			Settings_Model::save_setting('stat_id', $stat_id);
+			Settings_Model::save_setting('stat_key', $stat_key);
+			return $stat_id;
+		}
+
+		return false;
+	}
+
+	/**
+	 * Helper function to send a cURL request
+	 * @param url - URL for cURL to hit
+	 */
+	public function _curl_req($url)
+	{
+		$request = new HttpClient($url);
+		$buffer = $request->execute();
+
+		if ($buffer === FALSE) 
+		{
+			throw new Kohana_Exception($request->get_error_msg());
+		}
+
+		return $buffer;
+	}
+
+}
diff --git a/application/models/twitter.php b/application/models/twitter.php
new file mode 100644
index 0000000..081cc39
--- /dev/null
+++ b/application/models/twitter.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Twitter
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Twitter_Model extends ORM
+{	
+	protected $belongs_to = array('incident');
+	
+	// Database table name
+	protected $table_name = 'twitter';
+}
diff --git a/application/models/user.php b/application/models/user.php
new file mode 100644
index 0000000..6ef52ab
--- /dev/null
+++ b/application/models/user.php
@@ -0,0 +1,381 @@
+<?php
+/**
+ * Model for users for the Auth Module
+ *
+ * $Id: user.php 3352 2008-08-18 09:43:56BST atomless $
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class User_Model extends Auth_User_Model {
+
+	protected $has_many = array('alert', 'comment', 'openid', 'private_message', 'rating');
+	
+	/**
+	 * Creates a basic user and assigns to login and member roles
+	 * 
+	 * @param   string  email
+	 * @param   string  password
+	 * @param   string  riverid user id
+	 * @return  object  ORM object from saving the user
+	 */
+	public static function create_user($email, $password, $riverid=FALSE, $name=FALSE)
+	{
+		$user = ORM::factory('user');
+
+		$user->email = $email;
+		$user->username = User_Model::random_username();
+		$user->password = $password;
+
+		if (! empty($name))
+		{
+			$user->name = $name;
+		}
+		else
+		{
+			$user->needinfo = 1;
+		}
+
+		$user->riverid = ( $riverid == false ) ? '' : $riverid;
+
+		// Add New Roles if:
+		//    1. We don't require admin to approve users (will be added when admin approves)
+		//    2. We don't require users to first confirm their email address (will be added
+		//       when user confirms if the admin doesn't have to first approve the user)
+		if (Kohana::config('settings.manually_approve_users') == 0
+			AND Kohana::config('settings.require_email_confirmation') == 0)
+		{
+			$user->add(ORM::factory('role', 'login'));
+			$user->add(ORM::factory('role', 'member'));
+		}
+
+		return $user->save();
+	}
+
+	/**
+	 * Gets the email address of a user
+	 * @return string
+	 */
+	public static function get_email($user_id)
+	{
+		$user = ORM::factory('user')->find($user_id);
+		return $user->email;
+	}
+
+	/**
+	 * Returns data for a user based on username
+	 * @return object
+	 */
+	public static function get_user_by_username($username)
+	{
+		$user = ORM::factory('user')->where(array('username'=>$username))->find();
+		return $user;
+	}
+
+	/**
+	 * Returns data for a user based on email
+	 * @return object
+	 */
+	public static function get_user_by_email($email)
+	{
+		$user = ORM::factory('user')->where(array('email'=>$email))->find();
+		return $user;
+	}
+
+	/**
+	 * Returns data for a user based on user id
+	 * @return object
+	 */
+	public static function get_user_by_id($user_id)
+	{
+		$user = ORM::factory('user')->where(array('id'=>$user_id))->find();
+		return $user;
+	}
+
+	/**
+	 * Returns data for a user based on river id
+	 * @return object
+	 */
+	public static function get_user_by_river_id($river_id)
+	{
+		$user = ORM::factory('user')->where(array('riverid'=>$river_id))->find();
+		return $user;
+	}
+
+	/**
+	 * Returns all users with public profiles
+	 * @return object
+	 */
+	public static function get_public_users()
+	{
+		$users = ORM::factory('user')
+			->where(array('public_profile'=>1)) // Only show public profiles
+			->notlike(array('username'=>'@')) // We only want to show profiles that don't have email addresses as usernames
+			->find_all();
+		return $users;
+	}
+
+	/**
+	 * Custom validation for this model - complements the default validate()
+	 *
+	 * @param   array  array to validate
+	 * @param   Auth   instance of Auth class; used for testing purposes
+	 * @return bool TRUE if validation succeeds, FALSE otherwise
+	 */
+	public static function custom_validate(array & $post, Auth $auth = NULL)
+	{
+		// Initalize validation
+		$post = Validation::factory($post)
+				->pre_filter('trim', TRUE);
+		
+		if ($auth === NULL)
+		{
+			$auth = new Auth;
+		}
+
+		$post->add_rules('username','required','length[3,100]', 'alpha_numeric');
+		$post->add_rules('name','required','length[3,100]');
+        $post->add_rules('email','required','email','length[4,64]');
+
+		// If user id is not specified, check if the username already exists
+		if (empty($post->user_id))
+		{
+			$post->add_callbacks('username', array('User_Model', 'unique_value_exists'));
+			$post->add_callbacks('email', array('User_Model', 'unique_value_exists'));
+		}
+		
+		// Make sure we have a value for password length to avoid PHP error for missing length[] function
+		$password_length = Kohana::config('auth.password_length');
+		$password_length = ( ! empty($password_length)) ? $password_length : '1,127';
+
+		// Only check for the password if the user id has been specified and we are passing a pw
+		if (isset($post->user_id) AND isset($post->password))
+		{
+			$post->add_rules('password','required', 'length['.$password_length.']');
+		}
+
+		// If Password field is not blank and is being passed
+		if ( isset($post->password) AND
+			(! empty($post->password) OR (empty($post->password) AND ! empty($post->password_again))))
+		{
+			$post->add_rules('password','required','length['.$password_length.']', 'matches[password_again]');
+		}
+
+		$post->add_rules('role','required','length[3,30]', 'alpha_numeric');
+		$post->add_rules('notify','between[0,1]');
+
+		if ( ! $auth->logged_in('superadmin'))
+		{
+			$post->add_callbacks('role', array('User_Model', 'prevent_superadmin_modification'));
+		}
+
+		// Additional validation checks
+		Event::run('ushahidi_action.user_submit_admin', $post);
+
+		// Return
+		return $post->validate();
+	}
+
+	/**
+	 * Checks if a password is correct
+	 *
+	 * @param   int  user id
+	 * @param   string   password to check
+	 * @return bool TRUE if the password matches, FALSE otherwise
+	 */
+	public static function check_password($user_id,$password,$force_standard_method=FALSE)
+	{
+		$user = ORM::factory('user',$user_id);
+
+		// RiverID or Standard method?
+		if (kohana::config('riverid.enable') == TRUE
+        	AND ! empty($user->riverid)
+        	AND ! $force_standard_method)
+		{
+			// RiverID
+			$riverid = new RiverID;
+			$riverid->email = $user->email;
+			$riverid->password = $password;
+			if ($riverid->checkpassword() != FALSE)
+			{
+				return TRUE;
+			}
+			else
+			{
+				// TODO: Maybe return the error message?
+				return FALSE;
+			}
+		}
+		else
+		{
+			// Standard Local
+			$auth = Auth::instance();
+			return $auth->check_password($user_id,$password);
+		}
+	}
+
+	/**
+	 * Checks if the value in the specified field exists in database
+	 */
+	public static function unique_value_exists(Validation $post, $field)
+	{
+		$exists = (bool) ORM::factory('user')->where($field, $post[$field])->count_all();
+		if ($exists)
+		{
+			$post->add_error($field, 'exists');
+		}
+	}
+
+	/**
+	 * Ensures that only a superadmin can modify superadmin users, or upgrade a user to superadmin
+	 * @note this assumes the currently logged-in user isn't a superadmin
+	 */
+	public static function prevent_superadmin_modification(Validation $post, $field)
+	{
+		if ($post[$field] == 'superadmin')
+		{
+			$post->add_error($field, 'superadmin_modify');
+		}
+	}
+
+	/*
+	* Creates a random int value for a username that isn't already represented in the database
+	*/
+	public function random_username()
+	{
+		while ($random = mt_rand(1000,mt_getrandmax()))
+		{
+			$find_username = ORM::factory('user')->where('username',$random)->count_all();
+			if ($find_username == 0)
+			{
+				return $random;
+			}
+		}
+
+		return FALSE;
+	}
+
+
+	/**
+	 * Overrides the default delete method for the ORM.
+	 * Deletes roles associated with the user before user is removed from DB.
+	 */
+	public function delete()
+	{
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		// Remove assigned roles
+		// Have to use db->query() since we don't have an ORM model for roles_users
+		$this->db->query('DELETE FROM `'.$table_prefix.'roles_users` WHERE user_id = ?',$this->id);
+		
+		// Remove assigned badges
+		$this->db->query('DELETE FROM `'.$table_prefix.'badge_users` WHERE user_id = ?',$this->id);
+
+		// Delete alerts
+		ORM::factory('alert')
+		    ->where('user_id', $this->id)
+		    ->delete_all();
+		
+		// Delete user_token
+		ORM::factory('user_token')
+		    ->where('user_id', $this->id)
+		    ->delete_all();
+		
+		// Delete openid
+		ORM::factory('openid')
+		    ->where('user_id', $this->id)
+		    ->delete_all();
+
+		parent::delete();
+	}
+	
+	/**
+	 * Check if user has specified permission
+	 * @param $permission String permission name
+	 **/
+	public function has_permission($permission)
+	{
+		// Cache superadmin role to avoid repeating query
+		static $superadmin;
+		if (!isset($superadmin)) $superadmin = ORM::factory('role','superadmin');
+		
+		// Special case - superadmin ALWAYS has all permissions
+		if ($this->has($superadmin))
+		{
+			return TRUE;
+		}
+		
+		foreach ($this->roles as $user_role)
+		{
+			if ($user_role->has(ORM::factory('permission',$permission)))
+			{
+				return TRUE;
+			}
+		}
+		
+		return FALSE;
+	}
+	
+	/**
+	 * Get user's dashboard
+	 */
+	public function dashboard()
+	{
+		if ($this->has_permission('admin_ui'))
+			return 'admin';
+		
+		if ($this->has_permission('member_ui'))
+			return 'members';
+		
+		// Just in case someone has a login only role
+		if ($this->has(ORM::factory('role','login')))
+			return '';
+		
+		// Send anyone else to login
+		return 'login';
+	}
+	
+	/**
+	 * Get a new forgotten password challenge token for this user
+	 * @param string $salt Optional salt for token generation (use this)
+	 * @return string
+	 */
+	public function forgot_password_token()
+	{
+		return $this->_forgot_password_token();
+	}
+
+	/**
+	 * Check to see if forgotten password token is valid
+	 * @param string $token token to check
+	 * @return boolean is token valid
+	 **/
+	public function check_forgot_password_token($token)
+	{
+		$salt = substr($token, 0, 32);
+		return $this->_forgot_password_token($salt) == $token;
+	}
+
+	/**
+	 * Generate a forgotten password challenge token for this user
+	 * @param string $salt Optional salt for token generation (only use this for checking a token in URL)
+	 * @return string token
+	 */
+	private function _forgot_password_token($salt = FALSE)
+	{
+		// Hashed datq consists of email and the last_login field
+		// So as soon as the user logs in again, the reset link expires automatically.
+		$salt = $salt ? $salt : text::random('alnum', 32); // Limited charset to keep it URL friendly
+		$key = Kohana::config('settings.forgot_password_secret');
+		return $salt . hash_hmac('sha1', $this->last_login . $this->email, $salt . $key);
+	}
+
+} // End User_Model
diff --git a/application/models/verify.php b/application/models/verify.php
new file mode 100644
index 0000000..2c8a069
--- /dev/null
+++ b/application/models/verify.php
@@ -0,0 +1,24 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Verifications of incidents
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Verify_Model extends ORM
+{
+	protected $belongs_to = array('incident', 'user');
+	
+	// Database table name
+	protected $table_name = 'verified';
+
+}
diff --git a/application/views/admin/addons/addons_js.php b/application/views/admin/addons/addons_js.php
new file mode 100644
index 0000000..6d60cab
--- /dev/null
+++ b/application/views/admin/addons/addons_js.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Plugins js file.
+ *
+ * Handles javascript stuff related  to comments function
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Plugins_JS
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+
+function pluginAction ( action, pluginAction, plugin_id )
+{
+	var statusMessage;
+	if( !isChecked( "plugin" ) && plugin_id=='' )
+	{ 
+		alert('Please select at least one plugin.');
+	} else {
+		var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + pluginAction + '?')
+		if (answer){
+			// Set Submit Type
+			$("#action").attr("value", action);
+		
+			if (plugin_id != '') 
+			{
+				// Submit Form For Single Item
+				$("#plugin_single").attr("value", plugin_id);
+				$("#pluginMain").submit();
+			}
+			else
+			{
+				// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+				$("#plugin_single").attr("value", "000");
+				// Submit Form For Multiple Items
+				$("#pluginMain").submit();
+			}
+	
+		} else {
+			return false;
+		}
+	}
+}
\ No newline at end of file
diff --git a/application/views/admin/addons/plugin_settings.php b/application/views/admin/addons/plugin_settings.php
new file mode 100644
index 0000000..c5ae9b2
--- /dev/null
+++ b/application/views/admin/addons/plugin_settings.php
@@ -0,0 +1,70 @@
+<?php 
+/**
+ * Plugin Settings View
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2><?php echo Kohana::lang('ui_admin.addons'); ?> 
+					<a href="<?php echo url::site() . 'admin/addons/plugins' . '" class="active">' . Kohana::lang('ui_main.plugins') . '</a>' ?>
+					<a href="<?php echo url::site() . 'admin/addons/themes' . '">' . Kohana::lang('ui_main.themes') . '</a>' ?>
+				</h2>
+				
+				<?php print form::open(NULL, array('name'=>'plugin_settings')); ?>
+				<div class="report-form">
+					<?php
+					if (isset($form_error) AND $form_error)
+					{
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if (isset($form_saved) AND $form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>			
+				
+					<div class="head">
+						<h3><?php echo (isset($title)) ? $title : Kohana::lang('ui_admin.settings');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>				
+					
+					<div class="settings_holder">
+						<?php
+						
+						echo $settings_form;
+						
+						?>
+					</div>
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
\ No newline at end of file
diff --git a/application/views/admin/addons/plugins.php b/application/views/admin/addons/plugins.php
new file mode 100644
index 0000000..38b1595
--- /dev/null
+++ b/application/views/admin/addons/plugins.php
@@ -0,0 +1,156 @@
+<?php 
+/**
+ * Plugins view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Plugin Settings View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2><?php echo $title; ?> 
+					<a href="<?php echo url::site() . 'admin/addons/plugins' . '" class="active">' . Kohana::lang('ui_main.plugins') . '</a>' ?>
+					<a href="<?php echo url::site() . 'admin/addons/themes' . '">' . Kohana::lang('ui_main.themes') . '</a>' ?>
+				</h2>
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="?status=0" <?php if ($status !='i' && $status !='a') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.show_all');?></a></li>
+						<li><a href="?status=i" <?php if ($status == 'i') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.inactive');?></a></li>
+						<li><a href="?status=a" <?php if ($status == 'a') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.active');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><a href="#" onclick="pluginAction('a','ACTIVATE', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.activate'));?></a></li>
+							<li><a href="#" onclick="pluginAction('i','DEACTIVATE', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.deactivate'));?></a></li>
+						</ul>
+					</div>
+				</div>
+				<?php
+				if ($form_error)
+				{
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved)
+				{
+				?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3><?php echo Kohana::lang('ui_admin.plugins'); ?> <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide"><?php echo Kohana::lang('ui_main.hide_this_message');?></a></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<?php print form::open(NULL, array('id' => 'pluginMain', 'name' => 'pluginMain')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<input type="hidden" name="plugin_id[]" id="plugin_single" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1"><input id="checkallplugins" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'plugin_id[]' )" /></th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.plugins');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.version');?></th>
+									<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										 
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr>
+										<td colspan="4" class="col">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+								<?php	
+								}
+
+								foreach ($plugins as $plugin)
+								{
+									$plugin_id = $plugin->id;
+									$plugin_active = $plugin->plugin_active;
+									
+									// Retrieve Plugin Header Information from readme.txt
+									$defaults = array(
+										"name" => "",
+										"description" => "",
+										"website" => "",
+										"author" => "",
+										"version" => "",
+									);
+									$plugin_meta = addon::meta_data($plugin->plugin_name, 'plugin', $defaults);
+									
+									// Do we have a settings page?
+									$settings = plugin::find_settings($plugin->plugin_name);
+									?>
+									<tr <?php if ($plugin_active)
+									{
+										echo " class=\"addon_active\" ";
+									}?>>
+										<td class="col-1"><input name="plugin_id[]" id="plugin" value="<?php echo $plugin_id; ?>" type="checkbox" class="check-box"/></td>
+										<td class="col-2">
+											<div class="post">
+												<h4>
+												<?php echo $plugin_meta["name"]; ?><?php
+												if ($plugin_active AND $settings)
+												{
+													echo "   [<a href=\"".url::site($settings)."\">".Kohana::lang('ui_admin.settings')."</a>]";
+												}
+												?></h4>
+												<p><?php echo $plugin_meta["description"]; ?></p>
+											</div>
+											<ul class="info">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.author');?>: <strong><?php echo $plugin_meta["author"]; ?></strong></li>
+												<li><?php echo Kohana::lang('ui_main.plugin_url');?>: <strong><?php echo $plugin_meta["website"]; ?></strong></li>
+											</ul>
+										</td>
+										<td class="col-3"><?php echo $plugin_meta["version"]; ?></td>
+										<td class="col-4">
+											<ul>
+												<li class="none-separator"><?php
+												if ($plugin_active)
+												{
+													?><a href="#" class="status_no" onclick="pluginAction('i','DEACTIVATE', '<?php echo $plugin_id; ?>');"><?php echo Kohana::lang('ui_main.deactivate');?></a><?php
+												}
+												else
+												{
+													?><a href="#" class="status_yes" onclick="pluginAction('a','ACTIVATE', '<?php echo $plugin_id; ?>');"><?php echo Kohana::lang('ui_main.activate');?></a><?php
+												}
+												?></li>
+											</ul>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+						<p class="more_addons"><a href="http://community.ushahidi.com/plugins"><?php echo Kohana::lang('ui_admin.get_more_plugins'); ?></a></p>
+					</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/addons/themes.php b/application/views/admin/addons/themes.php
new file mode 100644
index 0000000..6c6298d
--- /dev/null
+++ b/application/views/admin/addons/themes.php
@@ -0,0 +1,101 @@
+<?php 
+/**
+ * Themes view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Themes View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2><?php echo $title; ?>
+					<a href="<?php echo url::site() . 'admin/addons' . '">' . Kohana::lang('ui_main.plugins') . '</a>' ?>
+					<a href="<?php echo url::site() . 'admin/addons/themes' . '" class="active">' . Kohana::lang('ui_main.themes') . '</a>' ?>
+				</h2>
+				<?php print form::open(); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>				
+					<div class="head">
+						<h3><?php echo Kohana::lang('ui_main.theme_settings');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->
+					<div class="sms_holder">
+						<?php
+						$i = 1; // Start at 2 because the default theme isn't in this array
+						foreach ($themes as $theme_key => $theme)
+						{
+							?>
+							<div class="theme_holder">
+								<div class="theme_screenshot"><?php
+									if (!empty($theme['Screenshot']))
+									{
+										echo "<img src=\"".url::site("themes/".$theme_key."/".$theme['Screenshot'])."\" width=240 height=150 border=0>";
+									}
+								?></div>
+								<strong><?php echo $theme['Theme Name']." by ".$theme['Author']; ?></strong><BR />
+								<?php echo $theme['Description'] ?><BR />
+								<strong><u><?php echo Kohana::lang('ui_main.version');?></u></strong>: <?php echo $theme['Version'] ?><BR />
+								<strong><u><?php echo Kohana::lang('ui_main.demo');?></u></strong>: <?php echo $theme['Demo'] ?><BR />
+								<strong><u><?php echo Kohana::lang('ui_main.contact');?></u></strong>: <?php echo $theme['Author Email'] ?><BR />
+								<strong><u><?php echo Kohana::lang('ui_main.location');?></u></strong>: <i>/themes/<?php echo $theme_key ?>/</i>
+								<label class="theme_select" style="display: block;">
+									<input type="radio" name="site_style" value="<?php echo $theme_key ?>" <?php
+									if ($theme_key == $form['site_style'])
+									{
+										echo "checked = \"checked\"";
+									}
+									?> /><?php echo Kohana::lang('ui_main.select_theme');?>
+									
+								</label>
+							</div>
+							<?php
+							// Make sure the themes don't get bunched up
+							if($i % 3 == 0) {
+								?><div style="clear:both;"></div><?php
+							}
+							$i++;
+						}						
+						?>
+						<div style="clear:both;"></div>
+						<p class="more_addons"><a href="http://community.ushahidi.com/themes"><?php echo Kohana::lang('ui_admin.get_more_themes'); ?></a></p>
+					</div>
+		
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/comments/comments_js.php b/application/views/admin/comments/comments_js.php
new file mode 100644
index 0000000..69739dc
--- /dev/null
+++ b/application/views/admin/comments/comments_js.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Comments js file.
+ *
+ * Handles javascript stuff related  to comments function
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+
+	// Ajax Submission
+	function commentAction ( action, confirmAction, comment_id )
+	{
+		var statusMessage;
+		if( !isChecked( "comment" ) && comment_id=='' )
+		{ 
+			alert('Please select at least one comment.');
+		} else {
+			var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+			if (answer){
+				// Set Submit Type
+				$("#action").attr("value", action);
+			
+				if (comment_id != '') 
+				{
+					// Submit Form For Single Item
+					$("#comment_single").attr("value", comment_id);
+					$("#commentMain").submit();
+				}
+				else
+				{
+					// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+					$("#comment_single").attr("value", "000");
+					// Submit Form For Multiple Items
+					$("#commentMain").submit();
+				}
+		
+			} else {
+				return false;
+			}
+		}
+	}
+
+
diff --git a/application/views/admin/comments/main.php b/application/views/admin/comments/main.php
new file mode 100644
index 0000000..4f3845e
--- /dev/null
+++ b/application/views/admin/comments/main.php
@@ -0,0 +1,169 @@
+<?php 
+/**
+ * Comments view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::reports_subtabs("comments"); ?>
+				</h2>
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="?status=0" <?php if ($status != 'a' && $status !='p' && $status !='s') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.show_all');?></a></li>
+						<li><a href="?status=p" <?php if ($status == 'p') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.pending');?></a></li>
+						<li><a href="?status=a" <?php if ($status == 'a') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.approved');?></a></li>
+						<li><a href="?status=s" <?php if ($status == 's') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.spam');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><a href="#" onclick="commentAction('a','APPROVE', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.approve'));?></a></li>
+							<li><a href="#" onclick="commentAction('u','UNAPPROVE', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.disapprove'));?></a></li>
+							<li><a href="#" onclick="commentAction('s','MARK AS SPAM', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.spam'));?></a></li>
+							<li><a href="#" onclick="commentAction('n','MARK AS NOT SPAM', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.not_spam'));?></a></li>
+							<li><a href="#" onclick="commentAction('d','DELETE', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete'));?></a></li>
+							<?php 
+							if ($status == 's')
+							{
+								?>
+								<li><a href="#" onclick="commentAction('x','DELETE ALL SPAM', '000');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_spam'));?></a></li>
+								<?php
+							}
+							?>
+						</ul>
+					</div>
+				</div>
+				<?php
+				if ($form_error)
+				{
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved)
+				{
+				?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3><?php echo Kohana::lang('ui_admin.comments'); ?> <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide"><?php echo Kohana::lang('ui_main.hide_this_message');?></a></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<?php print form::open(NULL, array('id' => 'commentMain', 'name' => 'commentMain')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<input type="hidden" name="comment_id[]" id="comment_single" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1"><input id="checkallcomments" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'comment_id[]' )" /></th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.comment_details');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+									<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										<?php echo $pagination; ?>
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr>
+										<td colspan="4" class="col">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+								<?php
+								}
+								foreach ($comments as $comment)
+								{
+									$comment_id = $comment->id;
+									$comment_author = $comment->comment_author;
+									$comment_description = $comment->comment_description;
+									$comment_email = $comment->comment_email;
+									$comment_ip = $comment->comment_ip;
+									$comment_active = $comment->comment_active;
+									$comment_spam = $comment->comment_spam;
+									$comment_date = date('Y-m-d H:i', strtotime($comment->comment_date));
+
+									$incident_id = $comment->incident->id;
+									$incident_title = $comment->incident->incident_title;
+									?>
+									<tr>
+										<td class="col-1"><input name="comment_id[]" id="comment" value="<?php echo $comment_id; ?>" type="checkbox" class="check-box"/></td>
+										<td class="col-2">
+											<div class="post">
+												<h4><a href="<?php echo url::site('reports/view/' . $incident_id); ?>"><?php echo html::specialchars($comment_author); ?></a></h4>
+												<?php
+												if ($incident_title != "")
+												{
+													?><div class="comment_incident"><?php echo Kohana::lang('ui_main.in_response_to');?>: <strong><a href="<?php echo url::site('admin/reports/edit/' . $incident_id); ?>"><?php echo html::escape($incident_title); ?></a></strong></div><?php
+												}
+												?>
+												<p><?php echo html::specialchars($comment_description); ?></p>
+											</div>
+											<ul class="info">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.email');?>: <strong><?php echo $comment_email; ?></strong></li>
+												<li><?php echo Kohana::lang('ui_main.ip_address');?>: <strong><?php echo $comment_ip; ?></strong></li>
+											</ul>
+										</td>
+										<td class="col-3"><?php echo $comment_date; ?></td>
+										<td class="col-4">
+											<ul>
+												<li class="none-separator"><?php
+												if ($comment_active)
+												{
+													?><a href="#" class="status_yes" onclick="commentAction('u','UNAPPROVE', '<?php echo $comment_id; ?>');"><?php echo Kohana::lang('ui_main.approved');?></a><?php
+												}
+												else
+												{
+													?><a href="#" class="status_no" onclick="commentAction('a','APPROVE', '<?php echo $comment_id; ?>');"><?php echo Kohana::lang('ui_main.approve');?></a><?php
+												}
+												?></li>
+												<li><?php
+												if ($comment_spam)
+												{
+													?><a href="#" class="status_yes" onclick="commentAction('n','MARK AS NOT SPAM', '<?php echo $comment_id; ?>');"><?php echo Kohana::lang('ui_main.not_spam');?></a><?php
+												}
+												else
+												{
+													?><a href="#" class="status_no" onclick="commentAction('s','MARK AS SPAM', '<?php echo $comment_id; ?>');"><?php echo Kohana::lang('ui_main.spam');?></a><?php
+												}
+												?></li>
+												<li><a href="#" class="del" onclick="commentAction('d','DELETE', '<?php echo $comment_id; ?>');"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+											</ul>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/current_version.php b/application/views/admin/current_version.php
new file mode 100644
index 0000000..4f219d1
--- /dev/null
+++ b/application/views/admin/current_version.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Current Version View File.
+ *
+ * Used to render the HTML for the ajax call to find out if this Ushahidi instance is running the latest version
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     John Etherton <john at ethertontech.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<?php
+if ((Kohana::config('config.enable_auto_upgrader') == TRUE))
+{
+	if (( !empty($version)) AND (url::current() != "admin/upgrade"))
+	{ ?>
+		<div id="current-version" class="update-info">
+			<?php echo Kohana::lang('ui_admin.ushahidi');?> <?php echo $version; ?>
+			<?php echo Kohana::lang('ui_admin.version_available');?>
+			<a href="<?php echo url::site() ?>admin/upgrade" title="upgrade ushahidi"><?php echo Kohana::lang('ui_admin.update_link');?></a>
+		</div>
+	<?php
+	}
+} ?>
diff --git a/application/views/admin/dashboard/main.php b/application/views/admin/dashboard/main.php
new file mode 100644
index 0000000..00cbfac
--- /dev/null
+++ b/application/views/admin/dashboard/main.php
@@ -0,0 +1,195 @@
+<?php 
+/**
+ * Dashboard view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2><?php echo $title; ?></h2>
+				
+				<div id="need_to_upgrade" style="display:none;"></div>
+				<?php echo $version_sync; ?>
+				<?php echo $security_info; ?>
+				
+				<!-- column -->
+				<div class="column">
+					
+					<!-- box -->
+					<div class="box">
+						<h3><?php echo Kohana::lang('ui_main.reports_timeline');?></h3>
+						<ul class="inf" style="margin-bottom:10px;">
+							<li class="none-separator"><?php echo Kohana::lang('ui_main.view');?>:<a href="<?php print url::site() ?>admin/dashboard/?range=1"><?php echo Kohana::lang('ui_main.today');?></a></li>
+							<li><a href="<?php print url::site() ?>admin/dashboard/?range=31"><?php echo Kohana::lang('ui_main.past_month');?></a></li>
+							<li><a href="<?php print url::site() ?>admin/dashboard/?range=365"><?php echo Kohana::lang('ui_main.past_year');?></a></li>
+							<li><a href="<?php print url::site() ?>admin/dashboard/?range=0"><?php echo Kohana::lang('ui_main.all');?></a></li>
+						</ul>
+						<div class="chart-holder" style="clear:both;padding-left:5px;">
+							<?php echo $report_chart; ?>
+							<?php if($failure != ''){ ?>
+								<div class="red-box" style="width:400px;">
+									<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+									<ul><li><?php echo $failure; ?></li></ul>
+								</div>
+							<?php } ?>
+						</div>
+					</div>
+					
+					<!-- info-container -->
+					<div class="info-container">
+						<div class="i-c-head">
+							<h3><?php echo Kohana::lang('ui_main.recent_reports');?></h3>
+							<ul>
+								<li class="none-separator"><a href="<?php echo url::site() . 'admin/reports' ?>"><?php echo Kohana::lang('ui_main.view_all');?></a></li>
+								<li><a href="<?php echo url::site(); ?>feed" class="rss-icon"><?php echo Kohana::lang('ui_main.rss');?></a></li>
+							</ul>
+						</div>
+						<?php
+						if ($reports_total == 0)
+						{
+						?>
+						<div class="post">
+							<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+						</div>
+						<?php	
+						}
+						foreach ($incidents as $incident)
+						{
+							$incident_id = $incident->id;
+							$incident_title = html::escape($incident->incident_title);
+							$incident_description = text::limit_chars(html::escape($incident->incident_description), 150, '...');
+							$incident_date = $incident->incident_date;
+							$incident_date = date('g:i A', strtotime($incident->incident_date));
+							$incident_mode = $incident->incident_mode;	// Mode of submission... WEB/SMS/EMAIL?
+							
+							if ($incident_mode == 1)
+							{
+								$submit_mode = "mail";
+							}
+							elseif ($incident_mode == 2)
+							{
+								$submit_mode = "sms";
+							}
+							elseif ($incident_mode == 3)
+							{
+								$submit_mode = "mail";
+							}
+							elseif ($incident_mode == 4)
+							{
+								$submit_mode = "twitter";
+							}
+							
+							// Incident Status
+							$incident_approved = $incident->incident_active;
+							if ($incident_approved == '1')
+							{
+								$incident_approved = "ok";
+							}
+							else
+							{
+								$incident_approved = "none";
+							}
+							
+							$incident_verified = $incident->incident_verified;
+							if ($incident_verified == '1')
+							{
+								$incident_verified = "ok";
+							}
+							else
+							{
+								$incident_verified = "none";
+							}
+							?>
+							<div class="post">
+								<ul class="post-info">
+									<li><a href="#" class="<?php echo $incident_approved; ?>"><?php echo utf8::strtoupper(Kohana::lang('ui_main.approved'));?>:</a></li>
+									<li><a href="#" class="<?php echo $incident_verified ?>"><?php echo utf8::strtoupper(Kohana::lang('ui_main.verified'));?>:</a></li>
+									<li class="last"><a href="#" class="<?php echo $submit_mode; ?>"><?php echo utf8::strtoupper(Kohana::lang('ui_main.source'));?>:</a></li>
+								</ul>
+								<h4><strong><?php echo $incident_date; ?></strong><a href="<?php echo url::site() . 'admin/reports/edit/' . $incident_id; ?>"><?php echo $incident_title; ?></a></h4>
+								<p><?php echo $incident_description; ?></p>
+							</div>
+							<?php
+						}
+						?>
+						<a href="<?php echo url::site() . 'admin/reports' ?>" class="view-all"><?php echo Kohana::lang('ui_main.view_all_reports');?></a>
+					</div>
+				</div>
+				<div class="column-1">
+					<!-- box -->
+					<div class="box">
+						<h3><?php echo Kohana::lang('ui_main.quick_stats');?></h3>
+						<ul class="nav-list">
+							<li>
+								<a href="<?php echo url::site() . 'admin/reports' ?>" class="reports"><?php echo Kohana::lang('ui_main.reports');?></a>
+								<strong><?php echo number_format($reports_total); ?></strong>
+								<ul>
+									<li><a href="<?php echo url::site() . 'admin/reports?status=a' ?>"><?php echo Kohana::lang('ui_main.not_approved');?></a><strong>(<?php echo $reports_unapproved; ?>)</strong></li>
+									
+								</ul>
+							</li>
+							<li>
+								<a href="<?php echo url::site() . 'admin/manage' ?>" class="categories"><?php echo Kohana::lang('ui_main.categories');?></a>
+								<strong><?php echo number_format($categories); ?></strong>
+							</li>
+							<li>
+								<span class="locations"><?php echo Kohana::lang('ui_main.locations');?></span>
+								<strong><?php echo $locations; ?></strong>
+							</li>
+							<li>
+								<a href="<?php echo url::site() . 'admin/manage/feeds' ?>" class="media"><?php echo Kohana::lang('ui_main.news_feeds');?></a>
+								<strong><?php echo number_format($incoming_media); ?></strong>
+							</li>
+							<li>
+								<a href="<?php echo url::site() . 'admin/messages' ?>" class="messages"><?php echo Kohana::lang('ui_main.messages');?></a>
+								<strong><?php echo number_format($message_count); ?></strong>
+								<ul>
+									<?php
+									foreach ($message_services as $service) {
+										echo "<li><a href=\"".url::site() . 'admin/messages/index/'.$service['id']."\">".$service['name']."</a><strong>(".$service['count'].")</strong></li>";
+									}
+									?>
+								</ul>
+							</li>
+						</ul>
+					</div>
+					<!-- info-container -->
+					<div class="info-container">
+						<div class="i-c-head">
+							<h3><?php echo Kohana::lang('ui_main.news_feeds');?></h3>
+							<ul>
+								<li class="none-separator"><a href="<?php echo url::site() . 'admin/manage/feeds' ?>"><?php echo Kohana::lang('ui_main.view_all');?></a></li>
+								<li><a href="<?php echo url::site(); ?>feeds" class="rss-icon"><?php echo Kohana::lang('ui_main.rss');?></a></li>
+							</ul>
+						</div>
+						<?php
+						foreach ($feeds as $feed)
+						{
+							$feed_id = $feed->id;
+							$feed_title = $feed->item_title;
+							$feed_description = text::limit_chars(html::escape($feed->item_description), 150, '...', True);
+							$feed_link = $feed->item_link;
+							$feed_date = date('M j Y', strtotime($feed->item_date));
+							$feed_source = "NEWS";
+							?>
+							<div class="post">
+								<h4><a href="<?php echo $feed_link; ?>" target="_blank"><?php echo $feed_title ?></a></h4>
+								<em class="date"><?php echo $feed_source; ?> - <?php echo $feed_date; ?></em>
+								<p><?php echo $feed_description; ?></p>
+							</div>
+							<?php
+						}
+						?>
+						<a href="<?php echo url::site() . 'admin/manage/feeds' ?>" class="view-all"><?php echo Kohana::lang('ui_main.view_all');?> <?php echo Kohana::lang('ui_main.incoming_media');?></a>
+					</div>
+				</div>
+			</div>
+
diff --git a/application/views/admin/layout.php b/application/views/admin/layout.php
new file mode 100644
index 0000000..ebdde59
--- /dev/null
+++ b/application/views/admin/layout.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * Layout for the admin interface.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - https://github.com/ushahidi/Ushahidi_Web
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<meta http-equiv="X-UA-Compatible" content="IE=7" />
+	<title><?php echo html::specialchars($site_name) ?></title>
+	<?php
+	// Action::header_scripts_admin - Additional Inline Scripts
+	Event::run('ushahidi_action.header_scripts_admin');
+	?>
+	<script type="text/javascript" charset="utf-8">
+		<?php if ($form_error): ?>
+			$(document).ready(function() { $("#addedit").show(); });
+		<?php endif; ?>
+	</script>
+
+	<?php echo $header_block; ?>
+</head>
+<body>
+
+	<?php
+		echo $header_nav;
+
+		// Action::admin_header_top_left - Admin Header Menu
+		Event::run('ushahidi_action.admin_header_top_left');
+
+		// Action::admin_secondary_header_bar - Admin Secondary Menu
+		Event::run('ushahidi_action.admin_secondary_header_bar');
+	?>
+
+	<div class="holder">
+		<!-- header -->
+		<div id="header">
+
+			<!-- info-nav -->
+			<div class="info-nav">
+				<h3><?php echo Kohana::lang('ui_admin.get_help');?></h3>
+				<ul>
+					<li ><a href="http://wiki.ushahidi.com/"><?php echo Kohana::lang('ui_admin.wiki');?></a></li>
+					<li><a href="http://ushahidi.com/community_resources/"><?php echo Kohana::lang('ui_admin.faqs');?></a></li>
+					<li><a href="http://forums.ushahidi.com/"><?php echo Kohana::lang('ui_admin.forum');?></a></li>
+				</ul>
+				
+				<!-- languages -->
+				<?php echo $languages; ?>
+				<!-- / languages -->
+				<div class="info-search">
+					<?php echo form::open('admin/reports', array('method' => 'get', 'id' => 'info-search')); ?>
+					<input type="text" name="k" class="info-keyword" value=""> 
+					<a href="javascript:info_search();" class="btn">
+						<?php echo Kohana::lang('ui_admin.search_reports');?>
+					</a>
+					<?php echo form::close(); ?>
+				</div>
+				<div style="clear:both;"></div>
+				<div class="info-buttons">
+					<a class="button" href="<?php echo url::site().'admin/manage/publiclisting'; ?>">
+						<?php echo Kohana::lang('ui_admin.manage_public_listing'); ?>
+					</a>
+				</div>
+			</div>
+			<!-- title -->
+			<h1><?php echo $site_name ?></h1>
+			<!-- nav-holder -->
+			<div class="nav-holder">
+				<!-- main-nav -->
+				<ul class="main-nav">
+					<?php foreach ($main_tabs as $page => $tab_name): ?>
+						<li>
+							<a href="<?php echo url::site(); ?>admin/<?php echo $page; ?>" <?php if($this_page==$page) echo 'class="active"' ;?>>
+								<?php echo $tab_name; ?>
+							</a>
+						</li>
+					<?php endforeach; ?>
+				</ul>
+				<!-- sub-nav -->
+				<ul class="sub-nav">
+					<?php foreach ($main_right_tabs as $page => $tab_name): ?>
+						<li>
+							<a href="<?php echo url::site(); ?>admin/<?php echo $page; ?>" <?php if($this_page==$page) echo 'class="active"' ;?>>
+								<?php echo $tab_name; ?>
+							</a>
+						</li>
+					<?php endforeach; ?>
+				</ul>
+			</div>
+		</div>
+		<!-- content -->
+		<div id="content">
+			<div class="bg">
+				<?php print $content; ?>
+			</div>
+		</div>
+	</div>
+	<div id="footer">
+		<div class="holder">
+			<strong>
+				<a href="http://www.ushahidi.com" target="_blank" title="Ushahidi Platform" alt="Ushahidi Platform">
+                	<sup><?php echo Kohana::config('settings.ushahidi_version');?></sup>
+            	</a>
+			</strong>
+		</div>
+	</div>
+<?php echo $footer_block; ?>
+</body>
+</html>
diff --git a/application/views/admin/manage/actions/actions_js.php b/application/views/admin/manage/actions/actions_js.php
new file mode 100644
index 0000000..d1fc020
--- /dev/null
+++ b/application/views/admin/manage/actions/actions_js.php
@@ -0,0 +1,519 @@
+<?php
+/**
+ * Actions JS file.
+ *
+ * Handles javascript stuff related to editing actions
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+var map;
+var thisLayer;
+var proj_4326 = new OpenLayers.Projection('EPSG:4326');
+var proj_900913 = new OpenLayers.Projection('EPSG:900913');
+var vlayer;
+var highlightCtrl;
+var selectedFeatures = [];
+
+$(document).ready(function() {
+	// Now initialize the map
+	var options = {
+	units: "dd"
+	, numZoomLevels: 18
+	, controls:[],
+	projection: proj_900913,
+	'displayProjection': proj_4326,
+	eventListeners: {
+			"zoomend": incidentZoom
+	   },
+				maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
+				maxResolution: 156543.0339
+	};
+	map = new OpenLayers.Map('divMap', options);
+
+	<?php echo map::layers_js(FALSE); ?>
+	map.addLayers(<?php echo map::layers_array(FALSE); ?>);
+
+	map.addControl(new OpenLayers.Control.Navigation());
+	map.addControl(new OpenLayers.Control.Zoom());
+	map.addControl(new OpenLayers.Control.MousePosition({
+		formatOutput: Ushahidi.convertLongLat
+	}));
+	map.addControl(new OpenLayers.Control.ScaleLine());
+	map.addControl(new OpenLayers.Control.Scale('mapScale'));
+	map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+	// Vector/Drawing Layer Styles
+	style1 = new OpenLayers.Style({
+		pointRadius: "8",
+		fillColor: "#ffcc66",
+		fillOpacity: "0.7",
+		strokeColor: "#CC0000",
+		strokeWidth: 2.5,
+		graphicZIndex: 1,
+		externalGraphic: "<?php echo url::file_loc('img').'media/img/openlayers/marker.png' ;?>",
+		graphicOpacity: 1,
+		graphicWidth: 21,
+		graphicHeight: 25,
+		graphicXOffset: -14,
+		graphicYOffset: -27
+	});
+	style2 = new OpenLayers.Style({
+		pointRadius: "8",
+		fillColor: "#30E900",
+		fillOpacity: "0.7",
+		strokeColor: "#197700",
+		strokeWidth: 2.5,
+		graphicZIndex: 1,
+		externalGraphic: "<?php echo url::file_loc('img').'media/img/openlayers/marker-green.png' ;?>",
+		graphicOpacity: 1,
+		graphicWidth: 21,
+		graphicHeight: 25,
+		graphicXOffset: -14,
+		graphicYOffset: -27
+	});
+	style3 = new OpenLayers.Style({
+		pointRadius: "8",
+		fillColor: "#30E900",
+		fillOpacity: "0.7",
+		strokeColor: "#197700",
+		strokeWidth: 2.5,
+		graphicZIndex: 1
+	});
+
+	var vlayerStyles = new OpenLayers.StyleMap({
+		"default": style1,
+		"select": style2,
+		"temporary": style3
+	});
+
+	// Create Vector/Drawing layer
+	vlayer = new OpenLayers.Layer.Vector( "Editable", {
+		styleMap: vlayerStyles,
+		rendererOptions: {zIndexing: true}
+	});
+	map.addLayer(vlayer);
+
+	// Drag Control
+	var drag = new OpenLayers.Control.DragFeature(vlayer, {
+		onStart: startDrag,
+		onDrag: doDrag,
+		onComplete: endDrag
+	});
+	map.addControl(drag);
+
+	// Vector Layer Events
+	vlayer.events.on({
+		beforefeaturesadded: function(event) {
+			// Nothing here.
+		},
+		featuresadded: function(event) {
+			refreshFeatures(event);
+		},
+		featuremodified: function(event) {
+			refreshFeatures(event);
+		},
+		featuresremoved: function(event) {
+			refreshFeatures(event);
+		}
+	});
+
+	// Vector Layer Highlight Features
+	highlightCtrl = new OpenLayers.Control.SelectFeature(vlayer, {
+	    hover: true,
+	    highlightOnly: true,
+	    renderIntent: "temporary"
+	});
+	map.addControl(highlightCtrl);
+
+	// Insert Saved Geometries
+	wkt = new OpenLayers.Format.WKT();
+	<?php
+	if (count($geometries))
+	{
+		foreach ($geometries as $geometry)
+		{
+			$geometry = json_decode($geometry);
+			echo "wktFeature = wkt.read('$geometry->geometry');\n";
+			echo "wktFeature.geometry.transform(proj_4326,proj_900913);\n";
+			echo "wktFeature.label = '$geometry->label';\n";
+			echo "wktFeature.comment = '$geometry->comment';\n";
+			echo "wktFeature.color = '$geometry->color';\n";
+			echo "wktFeature.strokewidth = '$geometry->strokewidth';\n";
+			echo "vlayer.addFeatures(wktFeature);\n";
+			echo "var color = '$geometry->color';if (color) {updateFeature(wktFeature, color, '');};";
+			echo "var strokewidth = '$geometry->strokewidth';if (strokewidth) {updateFeature(wktFeature, '', strokewidth);};";
+		}
+	}
+	?>
+
+	// create a lat/lon object
+	var startPoint = new OpenLayers.LonLat(<?php echo $longitude; ?>, <?php echo $latitude; ?>);
+	startPoint.transform(proj_4326, map.getProjectionObject());
+
+	// display the map centered on a latitude and longitude (Google zoom levels)
+	map.setCenter(startPoint, <?php echo ($incident_zoom) ? $incident_zoom : $default_zoom; ?>);
+
+	// Create the Editing Toolbar
+	var container = document.getElementById("panel");
+	var panel = new OpenLayers.Control.EditingToolbar(
+		vlayer, {div: container}
+	);
+	map.addControl(panel);
+	panel.activateControl(panel.controls[0]);
+
+	drag.activate();
+	highlightCtrl.activate();
+
+
+	// Clear Map
+	$('.btn_clear').on('click', function () {
+		clear_everything();
+	});
+
+	function clear_everything(){
+		vlayer.removeFeatures(vlayer.features);
+		$('input[name="geometry[]"]').remove();
+		$("#latitude").val("");
+		$("#longitude").val("");
+		$('#geometry_lat').val("");
+		$('#geometry_lon').val("");
+		$('#geometryLabelerHolder').hide(400);
+	}
+
+	// GeoCode
+	$('.btn_find').on('click', function () {
+		geoCode();
+	});
+	$('#location_find').bind('keypress', function(e) {
+		var code = (e.keyCode ? e.keyCode : e.which);
+		if(code == 13) { //Enter keycode
+			geoCode();
+			return false;
+		}
+	});
+
+	// Event on Latitude/Longitude Typing Change
+	$('#latitude, #longitude').bind("change keyup", function() {
+		var newlat = $("#latitude").val();
+		var newlon = $("#longitude").val();
+		if (!isNaN(newlat) && !isNaN(newlon))
+		{
+			// Clear the map first
+			vlayer.removeFeatures(vlayer.features);
+			$('input[name="geometry[]"]').remove();
+
+			point = new OpenLayers.Geometry.Point(newlon, newlat);
+			OpenLayers.Projection.transform(point, proj_4326,proj_900913);
+
+			f = new OpenLayers.Feature.Vector(point);
+			vlayer.addFeatures(f);
+
+			// create a new lat/lon object
+			myPoint = new OpenLayers.LonLat(newlon, newlat);
+			myPoint.transform(proj_4326, map.getProjectionObject());
+
+			// display the map centered on a latitude and longitude
+			map.setCenter(myPoint, <?php echo $default_zoom; ?>);
+		}
+		else
+		{
+			alert('Invalid value!')
+		}
+	});
+
+
+
+
+	// Prevent Map Effects in the Geometry Labeler
+	$('#geometryLabelerHolder').click(function(evt) {
+		var e = evt ? evt : window.event;
+		OpenLayers.Event.stop(e);
+		return false;
+	});
+
+	$('#geometry_lat').click(function() {
+		$('#geometry_lat').focus();
+	}).bind("change keyup blur", function(){
+		for (f in selectedFeatures) {
+			selectedFeatures[f].lat = this.value;
+	    }
+		refreshFeatures();
+	});
+
+	$('#geometry_lon').click(function() {
+		$('#geometry_lon').focus();
+	}).bind("change keyup blur", function(){
+		for (f in selectedFeatures) {
+			selectedFeatures[f].lon = this.value;
+	    }
+		refreshFeatures();
+	});
+
+	// Event on Latitude/Longitude Typing Change
+	$('#geometry_lat, #geometry_lon').bind("change keyup", function() {
+		var newlat = $("#geometry_lat").val();
+		var newlon = $("#geometry_lon").val();
+		if (!isNaN(newlat) && !isNaN(newlon))
+		{
+			var lonlat = new OpenLayers.LonLat(newlon, newlat);
+			lonlat.transform(proj_4326,proj_900913);
+			for (f in selectedFeatures) {
+				selectedFeatures[f].geometry.x = lonlat.lon;
+				selectedFeatures[f].geometry.y = lonlat.lat;
+				selectedFeatures[f].lon = newlat;
+				selectedFeatures[f].lat = newlon;
+				vlayer.drawFeature(selectedFeatures[f]);
+		    }
+		}
+		else
+		{
+			alert('Invalid value!')
+		}
+	});
+
+	// Event on StrokeWidth Change
+	$('#geometry_strokewidth').bind("change keyup", function() {
+		if (parseFloat(this.value) && parseFloat(this.value) <?php echo '<='; ?> 8) {
+			for (f in selectedFeatures) {
+				selectedFeatures[f].strokewidth = this.value;
+				updateFeature(selectedFeatures[f], '', parseFloat(this.value));
+			}
+			refreshFeatures();
+		}
+	});
+
+	hide_map();
+	
+	
+	// Category treeview
+	$(".category-column").treeview({
+		persist: "location",
+		collapsed: true,
+		unique: false
+	});
+
+});
+
+/* Feature starting to move */
+function startDrag(feature, pixel) {
+    lastPixel = pixel;
+}
+
+/* Feature moving */
+function doDrag(feature, pixel) {
+    for (f in selectedFeatures) {
+        if (feature != selectedFeatures[f]) {
+            var res = map.getResolution();
+            selectedFeatures[f].geometry.move(res * (pixel.x - lastPixel.x), res * (lastPixel.y - pixel.y));
+            vlayer.drawFeature(selectedFeatures[f]);
+        }
+    }
+    lastPixel = pixel;
+}
+
+/* Feature stopped moving */
+function endDrag(feature, pixel) {
+    for (f in selectedFeatures) {
+        f.state = OpenLayers.State.UPDATE;
+    }
+	refreshFeatures();
+}
+
+function refreshFeatures(event) {
+	var geoCollection = new OpenLayers.Geometry.Collection;
+	$('input[name="geometry[]"]').remove();
+	for(i=0; i <?php echo '<'; ?> vlayer.features.length; i++) {
+		newFeature = vlayer.features[i].clone();
+		newFeature.geometry.transform(proj_900913,proj_4326);
+		geoCollection.addComponents(newFeature.geometry);
+		if (vlayer.features.length == 1 && vlayer.features[i].geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+			// If feature is a Single Point - save as lat/lon
+		} else {
+			// Otherwise, save geometry values
+			// Convert to Well Known Text
+			var format = new OpenLayers.Format.WKT();
+			var geometry = format.write(newFeature);
+
+			geometryAttributes = JSON.stringify({ geometry: geometry });
+			$('#actionsMain').append($('<input></input>').attr('name','geometry[]').attr('type','hidden').attr('value',geometryAttributes));
+		}
+	}
+}
+
+function incidentZoom(event) {
+	$("#incident_zoom").val(map.getZoom());
+}
+
+function updateFeature(feature, color, strokeWidth){
+	// create a symbolizer from exiting stylemap
+	var symbolizer = feature.layer.styleMap.createSymbolizer(feature);
+
+	// color available?
+	if (color) {
+		symbolizer['fillColor'] = "#"+color;
+		symbolizer['strokeColor'] = "#"+color;
+		symbolizer['fillOpacity'] = "0.7";
+	} else {
+		if ( typeof(feature.color) != 'undefined' && feature.color != '' ) {
+			symbolizer['fillColor'] = "#"+feature.color;
+			symbolizer['strokeColor'] = "#"+feature.color;
+			symbolizer['fillOpacity'] = "0.7";
+		}
+	}
+
+	// stroke available?
+	if (parseFloat(strokeWidth)) {
+		symbolizer['strokeWidth'] = parseFloat(strokeWidth);
+	} else if ( typeof(feature.strokewidth) != 'undefined' && feature.strokewidth !='' ) {
+		symbolizer['strokeWidth'] = feature.strokewidth;
+	} else {
+		symbolizer['strokeWidth'] = "2.5";
+	}
+
+	// set the unique style to the feature
+	feature.style = symbolizer;
+
+	// redraw the feature with its new style
+	feature.layer.drawFeature(feature);
+}
+
+function hide_map() {
+
+	$('#divMap').slideUp(function(){
+		$('#divMap').css({"height":"0px", "width": "0px"});
+		map.updateSize();
+		map.pan(0,1);
+	});
+}
+
+function show_map() {
+	$('#divMap').css({"height":"350px", "width": "900px"});
+	$('#divMap').slideDown(function(){
+		map.updateSize();
+		map.pan(0,1);
+
+	});
+}
+
+// Ajax Submission
+function actionsAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		$("#action_id").attr("value", id);
+		$("#action_switch_to").attr("value", action);
+		$("#actionListing").submit();
+	}
+}
+
+var update_field = function(k, v) {
+	var select = $("select[name='action_"+k+"']");
+	var input = $("input[name='action_"+k+"']");
+	// Is the value an array?
+	if( Object.prototype.toString.call( v ) === '[object Array]' ) {
+		input = $("input[name='action_"+k+"[]']");
+		var select = $("select[name='action_"+k+"[]']");
+	}
+	
+	// If theres a matching select
+	if (select.length > 0)
+	{
+		if( Object.prototype.toString.call( v ) === '[object Array]' ) {
+			options = $('option', select);
+			$.each(options, function(i, el) {
+				if (jQuery.inArray($(el).val(), v) != -1) $(el).attr('selected', true);
+			});
+		}
+		else
+		{
+			$('option[value='+v+']', select).attr('selected', true);
+		}
+	}
+	// If its a text input
+	else if (input.attr('type') == 'text')
+	{
+		input.val(v);
+	}
+	// For radios and checkboxes
+	else if (input.attr('type') == 'radio' ||
+		input.attr('type') == 'checkbox')
+	{
+		if( Object.prototype.toString.call( v ) === '[object Array]' ) {
+			$.each(input, function(i, el) {
+				if (jQuery.inArray($(el).val(), v) != -1) $(el).attr('checked', true);
+			});
+		}
+		else
+		{
+			$.each(input, function(i, el) {
+				if ($(el).val() == v) $(el).attr('checked', true);
+			});
+		}
+	}
+};
+
+function actionEdit(id, trigger, qualifiers, response, responseVars)
+{
+	$('form#actionsMain').trigger('reset');
+	// Reset date picker
+	$.each($('#action_specific_days_calendar').dpGetSelected(), function(i, date) {
+		$('#action_specific_days_calendar').dpSetSelected(new Date(date).asString(), false);
+	});
+	hide_advanced_options();
+	hide_response_advanced_options();
+	
+	$("form#actionsMain input[name=id]").val(id);
+	$("#action_trigger option[value='"+trigger+"']").attr('selected',true).trigger('change');
+	$("#action_response option[value='"+response+"']").attr('selected',true).trigger('change');
+	
+	jQuery.each(qualifiers, update_field);
+	jQuery.each(responseVars, update_field);
+	
+	// Special cases
+	if (qualifiers.between_times == 1)
+	{
+		qualifiers.between_times_1 = qualifiers.between_times_1 / 60;
+		var min_1 = qualifiers.between_times_1 % 60;
+		var hr_1 = (qualifiers.between_times_1 - min_1) / 60;
+		update_field("between_times_hour_1", hr_1);
+		update_field("between_times_minute_1", min_1);
+
+		qualifiers.between_times_2 = qualifiers.between_times_2 / 60;
+		var min_2 = qualifiers.between_times_2 % 60;
+		var hr_2 = (qualifiers.between_times_2 - min_2) / 60;
+		update_field("between_times_hour_2", hr_2);
+		update_field("between_times_minute_2", min_2);
+	}
+	
+	// Set selected days in date picker
+	if (qualifiers.specific_days != undefined)
+	{
+		$.each(qualifiers.specific_days, function(i, date) {
+			$('#action_specific_days_calendar').dpSetSelected(new Date(date).asString());
+		});
+	}
+	
+	// Load geometries
+	vlayer.removeAllFeatures();
+	if (qualifiers.geometry != undefined)
+	{
+		wkt = new OpenLayers.Format.WKT();
+		$.each(qualifiers.geometry, function (i, geom) {
+			wktFeature = wkt.read(geom.geometry);
+			wktFeature.geometry.transform(proj_4326,proj_900913);
+			vlayer.addFeatures(wktFeature);
+		});
+	}
+	// Trigger change event
+	$('.action_location').change();
+}
diff --git a/application/views/admin/manage/actions/main.php b/application/views/admin/manage/actions/main.php
new file mode 100644
index 0000000..7b264d5
--- /dev/null
+++ b/application/views/admin/manage/actions/main.php
@@ -0,0 +1,598 @@
+<?php
+/**
+ * Actions view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+<script type="text/javascript">
+$(document).ready(function() {
+
+	// ----- TRIGGERS & QUALIFIERS ------
+
+	var advanced_fields = <?php echo json_encode($trigger_advanced_options); ?>;
+	var advanced_option_areas = <?php echo json_encode($advanced_option_areas); ?>;
+	var response_advanced_fields = <?php echo json_encode($response_advanced_options); ?>;
+	var response_advanced_option_areas = <?php echo json_encode($response_advanced_option_areas); ?>;
+	var trigger_allowed_responses = <?php echo json_encode($trigger_allowed_responses); ?>;
+	var response_options = <?php echo json_encode($response_options); ?>;
+
+	// ----- ACTIONS & TRIGGERS
+
+	$('#action_trigger').change(function() {
+		// When the trigger is selected, load the advanced fields.
+
+		hide_advanced_options();
+		hide_response_advanced_options();
+		hide_response();
+
+		for(i=0; i<advanced_option_areas.length; i++) {
+			if(jQuery.inArray(advanced_option_areas[i], advanced_fields[$('#action_trigger').val()]) != -1){
+				// From here we need to enable the extra form fields
+				$('#action_form_'+advanced_option_areas[i]).slideDown();
+			}
+		}
+
+		// Set response for action
+		build_response_select($('#action_trigger').val());
+		show_response();
+
+	});
+
+	hide_advanced_options = function (){
+		for(i=0; i<advanced_option_areas.length; i++) {
+			$('#action_form_'+advanced_option_areas[i]).slideUp();
+		}
+	}
+	hide_advanced_options();
+
+	// ----- RESPONSES
+
+	$('#action_response').change(function() {
+		// When the trigger is selected, load the advanced fields.
+
+		hide_response_advanced_options();
+
+		for(i=0; i<response_advanced_option_areas.length; i++) {
+			if(jQuery.inArray(response_advanced_option_areas[i], response_advanced_fields[$('#action_response').val()]) != -1){
+				// From here we need to enable the extra form fields
+				$('#action_form_'+response_advanced_option_areas[i]).slideDown();
+			}
+		}
+	});
+
+	hide_response_advanced_options = function (){
+		for(i=0; i<response_advanced_option_areas.length; i++) {
+			$('#action_form_'+response_advanced_option_areas[i]).slideUp();
+		}
+	}
+	hide_response_advanced_options();
+
+	function hide_response(){
+		$('#action_form_response').slideUp();
+	}
+	hide_response();
+
+	function show_response(){
+		$('#action_form_response').slideDown();
+		hide_trigger_select_messages();
+	}
+
+	function build_response_select(trigger){
+		var selected = '';
+		$('#action_response').html('');
+		$.each(trigger_allowed_responses[trigger], function(k, response_key) {
+
+			selected = '';
+			if(response_key == 'log_it') {
+				selected = 'selected';
+			}
+
+			$('#action_response').append('<option value="'+response_key+'" '+selected+'>'+response_options[response_key]+'</option>');
+		});
+	}
+
+	function hide_trigger_select_messages(){
+		$('#trigger_first_response').hide();
+		$('#trigger_first_qualifiers').hide();
+	}
+
+	function show_trigger_select_messages(){
+		$('#trigger_first_response').show();
+		$('#trigger_first_qualifiers').show();
+	}
+
+	var selected_specific_days = new Array();
+	$('#action_specific_days_calendar')
+		.datePicker(
+			{
+				startDate:'2000/01/01', // date obviously in the past
+				inline:true,
+				selectMultiple:true
+			}
+		)
+		.bind(
+			'dateSelected',
+			function(e, selectedDate, $td, state)
+			{
+				//console.log('You ' + (state ? '' : 'un') + 'selected ' + selectedDate);
+				if (state){
+					// selected
+					selected_specific_days.push(selectedDate.asString());
+				} else {
+					// unselected, remove from array
+					selected_specific_days = jQuery.grep(selected_specific_days, function (a) { return a != selectedDate.asString(); });
+				}
+				$("#action_specific_days").attr("value", selected_specific_days.join(','));
+			}
+		);
+
+});
+</script>
+
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("actions"); ?>
+				</h2>
+				<div style="width:100%;background-color:#FFD8D9;padding:4px 0px;"><img src="<?php echo url::file_loc('img'); ?>media/img/experimental.png" alt="<?php echo Kohana::lang('ui_admin.experimental');?>" style="position:relative;float:left;padding-left:250px;padding-right:5px;"/>This is an experimental feature. The Ushahidi and Crowdmap Teams cannot be <br/>responsible for any mishaps, bugs or quirks that show up when using Actions.</div>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							echo (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo $form_action; ?>!</h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<div class="report-form">
+					<?php echo form::open('/admin/manage/actions/changestate',array('id' => 'actionListing', 'name' => 'actionListing')); ?>
+						<input type="hidden" name="action_id" id="action_id" value="">
+						<input type="hidden" name="action_switch_to" id="action_switch_to" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1" style="width:125px;"><?php echo Kohana::lang('ui_admin.triggers'); ?></th>
+										<th class="col-2" style="width:275px;"><?php echo Kohana::lang('ui_admin.qualifiers');?></th>
+										<th class="col-3" style="width:125px;"><?php echo Kohana::lang('ui_admin.response');?></th>
+										<th class="col-4" style="width:275px;text-align:left;"><?php echo Kohana::lang('ui_admin.actions');?></th>
+										<th class="col-5" style="width:100px;"><?php echo Kohana::lang('ui_admin.state');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="5">
+											<!--TODO: Pagination-->
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr>
+											<td colspan="5" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results'); ?></h3>
+											</td>
+										</tr>
+									<?php
+									}
+
+									foreach ($actions as $action)
+									{
+										$action_id = $action->action_id;
+										$trigger = $action->action;
+										$qualifiers = unserialize($action->qualifiers);
+										$response = $action->response;
+										$response_vars = unserialize($action->response_vars);
+										$active = $action->active;
+
+										$qualifier_string = '';
+										foreach($qualifiers as $qkey => $qval)
+										{
+
+											// Show username
+											if($qkey == 'user') $qval = $user_options[$qval];
+
+											// Don't show geometry variable because we show it with location
+											if($qkey == 'geometry') continue;
+
+											// between_times doesn't actually show anything
+											if($qkey == 'between_times') continue;
+
+											// Convert seconds to something easier to digest
+											if($qkey == 'between_times_1' OR $qkey == 'between_times_2')
+											{
+												$time = $qval;
+												if($time == 0)
+												{
+													$hours = '00';
+													$minutes = '00';
+												}else{
+													$total_mins = $qval / 60;
+													$minutes = $total_mins % 60;
+													$hours = ($total_mins - $minutes) / 60;
+												}
+												$qval = sprintf('%02d:%02d <small>(%d)</small>', $hours, $minutes, $site_timezone);
+											}
+
+											// Make sure we show the right language for days of the week
+											if($qkey == 'days_of_the_week')
+											{
+												foreach($qval as $key => $day){
+													$qval[$key] = $days[$day];
+												}
+											}
+
+											// Make sure we show the right language for days of the week
+											if($qkey == 'specific_days')
+											{
+												foreach($qval as $key => $day){
+													$qval[$key] = date('Y/m/d', $day);
+												}
+												// Actually update the original (before it gets json encoded)
+												$qualifiers[$qkey] = $qval;
+											}
+
+											// If there's nothing there, don't show it
+											if($qval === '' OR $qval === 0 OR $qval === '0') continue;
+
+											// If it's really long and not an exempted key, chop off the end
+											if( is_string($qval) AND strlen($qval) > 150
+												AND $qkey != 'location' AND $qkey != 'days_of_the_week')
+											{
+												$qval = substr($qval,0,150).'…';
+											}
+
+											// If it's a specific location, show the polygon on a static map
+											if ($qkey == 'location' AND $qval == 'specific') {
+												// TODO: Find some more intuitive way to illustrate where this is.
+												//$qval = print_r($qualifiers['geometry'],true);;
+												$qval = 'Geofenced<br/>';
+												$qval .= '<img src="'.Kohana::config('core.site_protocol').'://maps.googleapis.com/maps/api/staticmap?size=275x200';
+												
+												$wkt = new Wkt();
+																							
+												foreach ($qualifiers['geometry'] as $geom_key => $geom)
+												{
+													$geom = json_decode($geom);
+													// Decode in qualifiers array too, so it gets passed to edit as an array
+													$qualifiers['geometry'][$geom_key] = $geom;
+
+													// Decode polygon with WKT
+													$polygon = $wkt->read($geom->geometry);
+													$coordinates = $polygon->getCoordinates();
+													WKT::collapse_points($coordinates, 0);
+
+													// for polygons
+													if (is_array($coordinates))
+													{
+														$qval .= "&path=color:0xff0000ff|weight:2|fillcolor:0xFFFF0033|";
+														$qval .= implode('|', WKT::flatten($coordinates));
+													}
+													// for points
+													else
+													{
+														$qval .= '&markers='.$coordinates;
+													}
+												}
+												$qval .= '&sensor=false" />';
+											} else {
+
+												// If it's not a location, break the array into a string
+												if (is_array($qval))
+												{
+													$qval = implode(', ',$qval);
+												}
+
+											}
+
+											$qualifier_string .= '<strong>'.$qkey.'</strong>: '.$qval.'<br/>';
+										}
+
+										$response_string ='';
+										foreach($response_vars as $rkey => $rval){
+
+											$display_val = $rval;
+											if(is_array($rval))
+											{
+												$display_val = implode(',',$rval);
+											}elseif($rkey == 'email_send_address' AND $rval = '1'){
+												$display_val = '<em>'.Kohana::lang('ui_admin.triggering_user').'</em>';
+											}
+
+											$response_string .= '<strong>'.$rkey.'</strong>: '.$display_val.'<br/>';
+										}
+
+										?>
+										<tr>
+											<td class="col-1" style="width:125px;font-weight:bold;">
+												<?php echo $trigger_options[$trigger]; ?>
+											</td>
+											<td class="col-2" style="width:275px;">
+												<?php echo $qualifier_string; ?>
+											</td>
+											<td class="col-3" style="width:125px;">
+												<?php echo $response_options[$response]; ?>
+											</td>
+											<td class="col-4" style="width:250px;border-right:0px;">
+												<?php echo $response_string; ?>
+											</td>
+											<td class="col" style="width:125px;border-left:0px;">
+
+												<?php if($active) {?>
+													<?php echo Kohana::lang('ui_admin.currently_active'); ?><br/><a href="javascript:actionsAction('0','DEACTIVATE',<?php echo rawurlencode($action_id);?>)" class="status_yes"><?php echo Kohana::lang('ui_main.deactivate'); ?></a>
+												<?php } else {?>
+													<?php echo Kohana::lang('ui_admin.currently_inactive'); ?><br/><a href="javascript:actionsAction('1','ACTIVATE',<?php echo rawurlencode($action_id);?>)" class="status_no"><?php echo Kohana::lang('ui_main.activate'); ?></a>
+												<?php } ?>
+												<br />
+												<a href='javascript:actionEdit(<?php echo json_encode($action_id); ?>,<?php echo json_encode($trigger); ?>,<?php echo json_encode($qualifiers); ?>,<?php echo json_encode($response); ?>,<?php echo json_encode($response_vars); ?>)'><?php echo Kohana::lang('ui_main.edit'); ?></a>
+												<br />
+												<a href="javascript:actionsAction('de','DELETE',<?php echo (int)$action_id;?>)" class="del"><?php echo Kohana::lang('ui_main.delete'); ?></a>
+
+											</td>
+										</tr>
+										<?php
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php echo form::close(); ?>
+				</div>
+
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active"><?php echo Kohana::lang('ui_main.add_edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="content-tab">
+						<?php echo form::open(NULL,array('id' => 'actionsMain', 'name' => 'actionsMain')); ?>
+
+						<div id="divMap" style="width:900px;height:350px;border:0px;">
+							<div id="geometryLabelerHolder" class="olControlNoSelect">
+								<div id="geometryLabeler">
+									<span id="geometry[]"><?php echo form::input('geometry[]'); ?></span>
+								</div>
+								<div id="geometryLabelerClose"></div>
+							</div>
+							<a href="#" class="btn_clear" style="float:right;padding:25px;"><?php echo utf8::strtoupper(Kohana::lang('ui_main.clear_map'));?></a>
+						</div>
+
+						<script type="text/javascript">
+						$(document).ready(function() {
+							// Close map to start
+							//hide_map();
+
+							$('.action_location').change(function(){
+								// Check value.
+								if($(this).val() == 'specific'){
+									// Open map
+									show_map();
+								}else{
+									// Close map (since it's 'anywhere')
+									hide_map();
+								}
+							});
+
+						});
+						</script>
+
+						<input type="hidden" id="action_id" name="action_id" value="" />
+						<input type="hidden" name="form_action" id="form_action" value="a"/>
+
+						<div style="float:right;padding:25px 25px 0 0;text-align:right;">
+							<?php echo Kohana::lang('ui_admin.server_time').' '.date("m/d/Y H:i:s",time()).' ('.$site_timezone.')'; ?><br/>
+							<a href="<?php echo url::site(); ?>admin/settings/site"><small><?php echo Kohana::lang('ui_admin.modify_timezone'); ?></small></a>
+						</div>
+
+						<h3><?php echo Kohana::lang('ui_admin.trigger'); ?></h3>
+
+						<div id="actions_qualifier_section">
+
+							<div class="tab_form_item" id="action_form_trigger">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.actions.trigger"); ?>"><?php echo Kohana::lang('ui_admin.trigger'); ?>:</a></h4>
+								<?php echo form::dropdown('action_trigger', $trigger_options); ?>
+							</div>
+
+						</div>
+
+						<div style="clear:both"></div>
+
+						<div id="actions_qualifier_section" style="padding-top:10px;">
+
+							<h3><?php echo Kohana::lang('ui_admin.qualifiers'); ?></h3>
+
+							<div class="tab_form_item" id="trigger_first_qualifiers"><?php echo Kohana::lang('ui_admin.select_trigger_before_qualifiers'); ?></div>
+
+							<div class="tab_form_item" id="action_form_user" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.user")); ?>"><?php echo Kohana::lang('ui_admin.user'); ?>:</a></h4>
+								<?php echo form::dropdown('action_user', $user_options, 0); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_location" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.location")); ?>"><?php echo Kohana::lang('ui_main.location'); ?>:</a></h4>
+								<?php echo form::radio('action_location', 'anywhere', TRUE, ' class="action_location"').' '.Kohana::lang('ui_admin.anywhere'); ?><br/>
+								<?php echo form::radio('action_location', 'specific', FALSE, ' class="action_location"').' '.Kohana::lang('ui_admin.specific_area'); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_keyword" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.keywords")); ?>"><?php echo Kohana::lang('ui_admin.keywords'); ?>:</a></h4>
+								<?php echo form::input('action_keyword',''); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_from" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.from")); ?>"><?php echo Kohana::lang('ui_admin.from'); ?>:</a></h4>
+								<?php echo form::input('action_from',''); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_feed_id" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.feed_id")); ?>"><?php echo Kohana::lang('ui_main.feed'); ?>:</a></h4>
+								<ul>
+								<?php
+									foreach ($feeds as $id => $feed)
+									{
+										echo "<li><label>".form::checkbox('action_feed_id[]',$id)." $feed</label></li>";
+									}
+								?>
+								</ul>
+							</div>
+
+							<div class="tab_form_item" id="action_form_category" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.category")); ?>"><?php echo Kohana::lang('ui_main.category'); ?>:</a></h4>
+								<?php
+									// categories, selected_categories, form field name, number of columns
+									echo category::form_tree('action_category', array(), 1, FALSE, TRUE);
+								?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_on_specific_count" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.on_specific_count")); ?>"><?php echo Kohana::lang('ui_admin.on_specific_count');?>:</a></h4>
+								<?php echo Kohana::lang('ui_admin.count').' '.form::input('action_on_specific_count','',' style="width:25px;"'); ?><br/>
+								<?php echo form::radio('action_on_specific_count_collective', '0', TRUE).' '.Kohana::lang('ui_admin.triggering_user'); ?><br/>
+								<?php echo form::radio('action_on_specific_count_collective', '1', FALSE).' '.Kohana::lang('ui_admin.entire_collective'); ?>
+
+							</div>
+
+							<div class="tab_form_item" id="action_form_days_of_the_week" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.days_of_the_week")); ?>"><?php echo Kohana::lang('ui_admin.days_of_the_week');?>:</a></h4>
+								<?php
+									echo form::dropdown(array('name' => 'action_days_of_the_week[]', 'multiple' => 'multiple', 'size' => 7), $days);
+								?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_between_times" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.between_times")); ?>"><?php echo Kohana::lang('ui_admin.between_times');?>:</a></h4>
+								<?php
+									$hours = range(0,24);
+									foreach($hours as $hour_key => $hour){
+										if($hour < 10) $hours[$hour_key] = '0'.$hour;
+									}
+
+									$minutes = range(0,59);
+									foreach($minutes as $minute_key => $minute){
+										if($minute < 10) $minutes[$minute_key] = '0'.$minute;
+									}
+								?>
+								<?php echo form::dropdown('action_between_times_hour_1', $hours); ?> : <?php echo form::dropdown('action_between_times_minute_1', $minutes); ?>
+								<center><?php echo Kohana::lang('ui_main.and');?></center>
+								<?php echo form::dropdown('action_between_times_hour_2', $hours); ?> : <?php echo form::dropdown('action_between_times_minute_2', $minutes); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_specific_days" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.specific_days")); ?>"><?php echo Kohana::lang('ui_admin.specific_days');?>:</a></h4>
+								<div id="action_specific_days_calendar" class="action_specific_days_calendar"></div>
+								<input type="hidden" name="action_specific_days" id="action_specific_days" value=""  />
+							</div>
+
+						</div>
+
+						<div style="clear:both"></div>
+
+						<div id="actions_response_section" style="padding-top:10px;">
+
+							<h3><?php echo Kohana::lang('ui_admin.response'); ?></h3>
+
+							<div class="tab_form_item" id="trigger_first_response"><?php echo Kohana::lang('ui_admin.select_trigger_before_response'); ?></div>
+
+							<div class="tab_form_item" id="action_form_response" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.response")); ?>"><?php echo Kohana::lang('ui_admin.response'); ?>:</a></h4>
+								<?php
+									// This dropdown is special since it will write all options and then be
+									//   changed as soon as an action trigger is selected. It does this
+									//   so the advanced options for responses will show up properly.
+									echo form::dropdown('action_response', $response_options, 'log_it');
+								?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_email_send_address" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.send_to")); ?>"><?php echo Kohana::lang('ui_admin.send_to');?>:</a></h4>
+								<?php echo form::radio('action_email_send_address', '0', TRUE).' '.Kohana::lang('ui_admin.triggering_user'); ?><br/>
+								<?php echo form::radio('action_email_send_address', '1', FALSE); ?>
+								<?php echo form::input('action_email_send_address_specific',''); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_email_subject" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.email_subject")); ?>"><?php echo Kohana::lang('ui_admin.subject');?>:</a></h4>
+								<?php echo form::input('action_email_subject',''); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_email_body" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.email_body")); ?>"><?php echo Kohana::lang('ui_admin.body');?>:</a></h4>
+								<?php echo form::textarea('action_email_body',''); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_add_category" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.add_to_category")); ?>"><?php echo Kohana::lang('ui_admin.add_to_category'); ?>:</a></h4>
+								<?php
+									// categories, selected_categories, form field name, number of columns
+									echo category::form_tree('action_add_category', array(), 1, FALSE, TRUE);
+								?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_report_title" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.report_title")); ?>"><?php echo Kohana::lang('ui_admin.report_title');?>:</a></h4>
+								<?php echo form::input('action_report_title',''); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_verify" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.verify")); ?>"><?php echo Kohana::lang('ui_admin.mark_as');?>:</a></h4>
+								<?php echo form::radio('action_verify', '0', TRUE).' '.Kohana::lang('ui_main.unverified'); ?><br/>
+								<?php echo form::radio('action_verify', '1', FALSE).' '.Kohana::lang('ui_main.verified'); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_approve" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.approve")); ?>"><?php echo Kohana::lang('ui_admin.mark_as');?>:</a></h4>
+								<?php echo form::radio('action_approve', '0', TRUE).' '.Kohana::lang('ui_main.disapprove'); ?><br/>
+								<?php echo form::radio('action_approve', '1', FALSE).' '.Kohana::lang('ui_main.approve'); ?>
+							</div>
+
+							<div class="tab_form_item" id="action_form_badge" style="margin-right:75px;">
+								<h4><a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang("tooltips.actions.assign_badge")); ?>"><?php echo Kohana::lang('ui_admin.assign_badge'); ?>:</a></h4>
+								<?php
+									echo form::dropdown('action_badge', $badges);
+								?>
+							</div>
+
+						</div>
+
+						<div style="clear:both"></div>
+
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php echo form::hidden('id', 0); ?>
+						<?php echo form::close(); ?>
+					</div>
+				</div>
+			</div>
diff --git a/application/views/admin/manage/alerts/alerts_js.php b/application/views/admin/manage/alerts/alerts_js.php
new file mode 100644
index 0000000..7640b54
--- /dev/null
+++ b/application/views/admin/manage/alerts/alerts_js.php
@@ -0,0 +1,52 @@
+/**
+ * Alerts js file.
+ * 
+ * Handles javascript stuff related to alerts function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+
+		// Ajax Submission
+		function alertAction ( action, confirmAction, alert_id )
+		{
+			var statusMessage;
+			if( !isChecked( "alert" ) && alert_id=='' )
+			{ 
+				alert('Please select at least one alert.');
+			} else {
+				var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+				if (answer){
+					
+					// Set Submit Type
+					$("#alertMain #action").attr("value", action);
+					
+					if (alert_id != '') 
+					{
+						// Submit Form For Single Item
+						$("#alert_single").attr("value", alert_id);
+						$("#alertMain").submit();
+					}
+					else
+					{
+						// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+						$("#alert_single").attr("value", "000");
+						
+						// Submit Form For Multiple Items
+						$("#alertMain").submit();
+					}
+				
+				} else {
+					return false;
+				}
+			}
+		}
\ No newline at end of file
diff --git a/application/views/admin/manage/alerts/main.php b/application/views/admin/manage/alerts/main.php
new file mode 100644
index 0000000..127a770
--- /dev/null
+++ b/application/views/admin/manage/alerts/main.php
@@ -0,0 +1,134 @@
+<?php 
+/**
+ * Alerts view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("alerts"); ?>
+				</h2>				
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="<?php echo url::site()."admin/manage/alerts/"; ?>" <?php if ($type == '0' OR empty($type) ) echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.show_all');?></a></li>
+						<li><a href="<?php echo url::site()."admin/manage/alerts/index/"; ?>?type=1" <?php if ($type == '1') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.sms');?></a></li>
+						<li><a href="<?php echo url::site()."admin/manage/alerts/index/"; ?>?type=2" <?php if ($type == '2') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.email');?></a></li>
+					</ul>
+					
+					<!-- tab -->
+					<div class="tab">
+						<?php print form::open(NULL,array('method'=>'get', 'id' => 'alertSearch', 'name' => 'alertSearch')); ?>
+							<input type="hidden" name="action" id="action" value="s"/>
+							<input type="hidden" name="type" value="<?php echo $type; ?>"/>
+							<ul>
+								<li>
+									<a href="#" onclick="alertAction('d','<?php echo strtoupper(Kohana::lang('ui_main.delete')); ?>', '');">
+									<?php echo strtoupper(Kohana::lang('ui_main.delete'));?></a>
+								</li>
+								<li style="float:right;">
+									<?php print form::input('ak', $keyword, ' class="text" style="float:left;height:20px;"'); ?>
+									<a href="#" onclick="javascript:alertSearch.submit();">
+									<?php echo Kohana::lang('ui_main.search');?></a>
+								</li>
+							</ul>
+						<?php print form::close(); ?>
+					</div>
+				</div>
+				<?php if ($form_error): ?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php endif; ?>
+				
+				<?php if ($form_saved): ?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.alert_has_been');?> <?php echo $form_action; ?>!</h3>
+					</div>
+				<?php endif; ?>
+				
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'alertMain', 'name' => 'alertMain')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="alert_id[]" id="alert_single" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1"><input id="checkallalerts" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'alert_id[]' )" /></th>
+										<th class="col-2"><?php echo Kohana::lang('ui_admin.alerts');?></th>
+										<th class="col-3"><?php echo Kohana::lang('ui_main.sent');?></th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4"><?php echo $pagination; ?></td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php if ($total_items == 0): ?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php endif; ?>
+									<?php
+									foreach ($alerts as $alert)
+									{?>
+										<tr>
+											<td class="col-1"><input name="alert_id[]" id="alert" value="<?php echo $alert->id; ?>" type="checkbox" class="check-box"/></td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo $alert->alert_recipient; ?></h4>
+												</div>
+												<ul class="info">
+													<li class="none-separator">
+														<?php echo Kohana::lang('ui_main.location');?>: 
+														<strong><?php echo $alert->alert_lat.','.$alert->alert_lon; ?></strong>
+													</li>
+													<li class="none-separator">
+														<?php echo Kohana::lang('ui_main.radius');?>: 
+														<strong><?php echo $alert->alert_radius; ?></strong>
+													</li>
+												</ul>
+											</td>
+											<td><?php echo $alert->alert_sent->count(); ?></td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="javascript:alertAction('d','DELETE','<?php echo(rawurlencode($alert->id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+												</ul>
+											</td>
+										</tr>
+										<?php
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+			</div>
diff --git a/application/views/admin/manage/badges/badges_js.php b/application/views/admin/manage/badges/badges_js.php
new file mode 100644
index 0000000..6aba972
--- /dev/null
+++ b/application/views/admin/manage/badges/badges_js.php
@@ -0,0 +1,56 @@
+/**
+ * Badges js file.
+ *
+ * Handles javascript stuff related to badges in the admin panel.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Badges Javascript
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+function badgeAction ( action, confirmAction, badge_id )
+{
+	var answer = confirm(<?php echo json_encode(Kohana::lang('ui_admin.are_you_sure_you_want_to')); ?> + ' ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#badge_id").attr("value", badge_id);
+		// Set Submit Type
+		$(".js_action").attr("value", action);
+
+		// Assign
+		if(action == 'b'){
+			$("#assign_user").attr("value", $("#assign_user_"+badge_id).val());
+		}
+
+		// Revoke
+		if(action == 'r'){
+			$("#revoke_user").attr("value", $("#revoke_user_"+badge_id).val());
+		}
+
+		// Submit Form
+		$("#badgeListing").submit();
+	}
+}
+
+$(document).ready(function() {
+
+	$('.badge_selection').click(function() {
+
+		// Set form field value
+		$('#selected_badge').val(this.id);
+		
+		// Re-add transparency to every element
+		$('.badge_selection').addClass('transparent-25');
+
+		// Remove transparency from selected element
+		$(this).removeClass('transparent-25');
+
+	});
+
+});
diff --git a/application/views/admin/manage/badges/main.php b/application/views/admin/manage/badges/main.php
new file mode 100644
index 0000000..bedc53c
--- /dev/null
+++ b/application/views/admin/manage/badges/main.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ * Badges view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Badges View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("badges"); ?>
+				</h2>
+
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.category_has_been');?> <?php echo $form_action; ?>!</h3>
+					</div>
+				<?php
+				}
+				?>
+
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active" onclick="show_addedit(true)"><?php echo Kohana::lang('ui_main.add_edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab" id="addedit" style="display:none">
+						<?php print form::open(NULL,array('enctype' => 'multipart/form-data', 'id' => 'badgeMain', 'name' => 'badgeMain')); ?>
+						<input type="hidden" id="id" name="id" value="<?php echo $form['id']; ?>" />
+						<input type="hidden" name="action" id="action" value="a"/>
+
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.name');?>:</strong><br />
+							<?php print form::input('name', $form['name'], ' class="text"'); ?>
+						</div>
+
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.description');?>:</strong><br />
+							<?php print form::input('description', $form['description'], ' class="text"'); ?>
+						</div>
+						
+						<div class="tab_form_item" style="clear:both;">
+							
+							<strong><?php echo Kohana::lang('ui_main.badge_select');?>:</strong><br />
+							<input type="hidden" name="selected_badge" value="" id="selected_badge"  />
+							<?php
+								foreach($badge_packs as $pack_name => $pack)
+								{
+									echo '<h4>'.$pack_name.' '.Kohana::lang('ui_main.badge_pack').'</h4>';
+									foreach($pack as $badge_filename)
+									{
+										$badge_url = url::base().'media/img/badge_packs/'.$pack_name.'/'.$badge_filename;
+										$encoded_badge = base64_encode($pack_name.'/'.$badge_filename);
+										echo '<img src="'.$badge_url.'" id="badge_'.$encoded_badge.'" class="badge_selection transparent-25" />'."\n";
+									}
+								}
+							?>
+						</div>
+
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.badge_image_upload_your_own');?>:</strong><br />
+							<?php echo form::upload('image', '', ''); ?>
+						</div>
+
+						<div style="clear:both"></div>
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>
+					</div>
+				</div>
+
+				<!-- badge-table -->
+				<div>
+
+					<?php print form::open(NULL,array('id' => 'badgeListing', 'name' => 'badgeListing')); ?>
+						<input type="hidden" name="action" id="action" class="js_action" value="" />
+						<input type="hidden" name="badge_id" id="badge_id" value="" />
+						<input type="hidden" name="assign_user" id="assign_user" value="" />
+						<input type="hidden" name="revoke_user" id="revoke_user" value="" />
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr class="nodrag">
+										<th class="col-1"> </th>
+										<th class="col-2" style="width:80px;"><?php echo Kohana::lang('ui_main.badges');?></th>
+										<th class="col-3" style="width:600px;"> </th>
+										<th class="col-4" style="width:120px;"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr class="nodrag">
+											<td colspan="4" class="col" id="row1">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+								<?php
+									}
+									$i = 1;
+									foreach ($badges as $badge)
+									{
+								?>
+										<tr>
+											<td class="col-1"> </td>
+											<td class="col-2" style="width:80px;">
+												<img src="<?php echo $badge['img_m']; ?>" alt="<?php echo Kohana::lang('ui_main.badge').' '.$badge['id'];?>" width="80" height="80" />
+											</td>
+											<td class="col-3" style="width:600px;font-weight:normal;">
+												<strong><?php echo $badge['name']; ?></strong>
+												<br/><?php echo $badge['description']; ?>
+												<br/><?php echo Kohana::lang('ui_admin.assignments'); ?>: <?php echo count($badge['users']);?>
+
+												<br/><?php echo form::dropdown('assign_user_'.$badge['id'], array_diff($users, $badge['users']), 'standard'); ?>
+													<a href="javascript:badgeAction('b','<?php echo utf8::strtoupper(html::escape(Kohana::lang('ui_admin.assign')));?>','<?php echo rawurlencode($badge['id']); ?>')"><?php echo Kohana::lang('ui_admin.assign');?></a>
+													<?php if(count($badge['users']) > 0) { ?>
+														     
+														<?php echo form::dropdown('revoke_user_'.$badge['id'], $badge['users'], 'standard'); ?>
+														<a href="javascript:badgeAction('r','<?php echo utf8::strtoupper(html::escape(Kohana::lang('ui_admin.revoke')));?>','<?php echo rawurlencode($badge['id']); ?>')"><?php echo Kohana::lang('ui_admin.revoke');?></a>
+													<?php } ?>
+
+											</td>
+											<td class="col-4" style="width:120px;">
+
+												<ul>
+													<li><a href="javascript:badgeAction('d','<?php echo utf8::strtoupper(html::escape(Kohana::lang('ui_admin.delete_badge')));?>','<?php echo rawurlencode($badge['id']); ?>')" class="del"><?php echo Kohana::lang('ui_admin.delete_badge');?></a></li>
+												</ul>
+
+											</td>
+										</tr>
+								<?php
+									}
+								?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+
+			</div>
diff --git a/application/views/admin/manage/blocks/blocks_js.php b/application/views/admin/manage/blocks/blocks_js.php
new file mode 100644
index 0000000..2def3aa
--- /dev/null
+++ b/application/views/admin/manage/blocks/blocks_js.php
@@ -0,0 +1,55 @@
+/**
+ * Blocks js file.
+ * 
+ * Handles javascript stuff related to category function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Blocks Javascript
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+$(document).ready(function() {
+    // Initialise the table
+	$("#blockSort").tableDnD({
+		dragHandle: "col-drag-handle",
+		onDragClass: "col-drag",
+		onDrop: function(table, row) {
+			var rows = table.tBodies[0].rows;
+			var blocksArray = [];
+			for (var i=0; i<rows.length; i++) {
+				blocksArray[i] = rows[i].id;
+			}
+			var blocks = blocksArray.join(',');
+			$.post("<?php echo url::site() . 'admin/manage/blocks/sort' ?>", { blocks: blocks },
+				function(data){
+					$("#blockSort"+" tbody tr td").effect("highlight", {}, 500);
+			});
+		}
+	});
+	
+	$("#blockSort tr").hover(function() {
+		$(this.cells[0]).addClass('col-show-handle');
+	}, function() {
+		$(this.cells[0]).removeClass('col-show-handle');
+	});
+});
+
+function blockAction ( action, confirmAction, block )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#block").attr("value", block);
+		// Set Submit Type
+		$("#action").attr("value", action);
+		// Submit Form
+		$("#blockListing").submit();
+	}
+}
\ No newline at end of file
diff --git a/application/views/admin/manage/blocks/main.php b/application/views/admin/manage/blocks/main.php
new file mode 100644
index 0000000..949beec
--- /dev/null
+++ b/application/views/admin/manage/blocks/main.php
@@ -0,0 +1,122 @@
+<?php 
+/**
+ * Blocks view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Blocks View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("blocks"); ?>
+				</h2>
+				
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.category_has_been');?> <?php echo $form_action; ?>!</h3>
+					</div>
+				<?php
+				}
+				?>
+				
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'blockListing',
+					 	'name' => 'blockListing')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="block" id="block" value="">
+						<div class="table-holder">
+							<table class="table" id="blockSort">
+								<thead>
+									<tr class="nodrag">
+										<th class="col-1"> </th>
+										<th class="col-2"><?php echo Kohana::lang('ui_admin.blocks');?></th>
+										<th class="col-3"> </th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr class="nodrag">
+											<td colspan="4" class="col" id="row1">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php	
+									}
+									$i = 1;
+									foreach ($sorted_blocks as $key)
+									{
+										$block = $registered_blocks[$key];
+										$block_name = $block['name'];
+										$block_description = $block['description'];
+										
+										$block_visible = FALSE;
+										if (in_array($key, $active_blocks))
+										{
+											$block_visible = TRUE;
+										}
+										?>
+										<tr id="<?php echo $key; ?>"<?php if ( ! $block_visible) echo " class=\"nodrag nodrop\"" ?>>
+											<td class="col-1 <?php if ( $block_visible) echo "col-drag-handle" ?>"> </td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo $block_name; ?></h4>
+													<p><?php echo $block_description; ?></p>
+												</div>
+											</td>
+											<td class="col-3"> </td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator">
+													<?php if ($block_visible) { ?>
+													<a href="javascript:blockAction('d','HIDE','<?php echo(rawurlencode($key)); ?>')"<?php echo " class=\"status_yes\"" ?>><?php echo Kohana::lang('ui_main.visible');?></a>
+													<?php } else { ?>
+													<a href="javascript:blockAction('a','SHOW','<?php echo(rawurlencode($key)); ?>')"<?php echo " class=\"status_yes\"" ?>><?php echo Kohana::lang('ui_main.hidden');?></a>
+													<?php } ?>
+													</li>
+												</ul>
+											</td>
+										</tr>
+										<?php
+										$i++;									
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+
+			</div>
diff --git a/application/views/admin/manage/categories/categories_js.php b/application/views/admin/manage/categories/categories_js.php
new file mode 100644
index 0000000..4316041
--- /dev/null
+++ b/application/views/admin/manage/categories/categories_js.php
@@ -0,0 +1,102 @@
+/**
+ * Categories js file.
+ * 
+ * Handles javascript stuff related to category function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+$(document).ready(function() {
+    // Initialise the table
+	$("#categorySort").tableDnD({
+		dragHandle: "col-drag-handle",
+		onDragClass: "col-drag",
+		onDrop: function(table, row) {
+			var rows = table.tBodies[0].rows;
+			var categoriesArray = [];
+			for (var i=0; i<rows.length; i++) {
+				categoriesArray[i] = rows[i].id;
+			}
+			var categories = categoriesArray.join(',');
+			$.post("<?php echo url::site() . 'admin/manage/category_sort/' ?>", { categories: categories },
+				function(data){
+					if (data == "ERROR") {
+						alert("Invalid Placement!!\n You cannot place a subcategory on top of a category.");
+					} else {
+						$("#categorySort"+" tbody tr td").effect("highlight", {}, 500);
+					}
+			});
+		}
+	});
+	
+	$("#categorySort tr").hover(function() {
+		$(this.cells[0]).addClass('col-show-handle');
+	}, function() {
+		$(this.cells[0]).removeClass('col-show-handle');
+	});
+
+	$('a#category_translations').click(function() {
+		$('.category_translations_form_fields').toggle(400);
+		return false;
+	});
+	
+	$('#category_color').ColorPicker({
+		onSubmit: function(hsb, hex, rgb) {
+			$('#category_color').val(hex);
+		},
+		onChange: function(hsb, hex, rgb) {
+			$('#category_color').val(hex);
+		},
+		onBeforeShow: function () {
+			$(this).ColorPickerSetColor(this.value);
+		}
+	})
+	.bind('keyup', function(){
+		$(this).ColorPickerSetColor(this.value);
+	});
+});
+
+// Categories JS
+function fillFields(event)
+{
+	params = event.data;
+	show_addedit();
+	$("#category_id").attr("value", params.category_id);
+	$("#parent_id").attr("value", params.parent_id);
+	$("#category_title").attr("value", params.category_title);
+	$("#category_description").attr("value", params.category_description);
+	$("#category_color").attr("value", params.category_color);
+	$(".category_lang").show();
+	$(".category_lang_"+params.locale).hide();
+	$.each(params.category_langs, function (lang_key, value) {
+		$("#category_title_"+lang_key).attr("value",value['category_title']);
+		$("#category_description_"+lang_key).attr("value",value['category_description']);
+		if (value['category_title'] != '')
+		{
+			$('.category_translations_form_fields').show();
+		}
+	});
+}
+
+// Ajax Submission
+function catAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#category_id_action").attr("value", id);
+		// Set Submit Type
+		$("#category_action").attr("value", action);
+		// Submit Form
+		$("#catListing").submit();
+	}
+}
\ No newline at end of file
diff --git a/application/views/admin/manage/categories/main.php b/application/views/admin/manage/categories/main.php
new file mode 100644
index 0000000..41896c3
--- /dev/null
+++ b/application/views/admin/manage/categories/main.php
@@ -0,0 +1,294 @@
+<?php 
+/**
+ * Categories view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("categories"); ?>
+				</h2>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.category_has_been');?> <?php echo $form_action; ?>!</h3>
+					</div>
+				<?php
+				}
+				?>
+				
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active" onclick="show_addedit(true)"><?php echo Kohana::lang('ui_main.add_edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab" id="addedit" style="display:none">
+						<?php print form::open(NULL,array('enctype' => 'multipart/form-data', 
+							'id' => 'catMain', 'name' => 'catMain')); ?>
+						<input type="hidden" id="category_id" name="category_id" value="<?php echo $form['category_id']; ?>" />
+						<input type="hidden" name="action" id="action" value="a"/>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.category_name');?>:</strong><br />
+							<?php print form::input('category_title', $form['category_title'], ' class="text"'); ?><br/>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.description');?>:</strong><br />
+							<?php print form::input('category_description', $form['category_description'], ' class="text category_description"'); ?>
+						</div>
+						<div style="clear:both"></div>
+
+						<div style="clear: left; width: 100%;">
+							<a href="#" id="category_translations" class="category_translations" style="clear:both;">Category Translations</a>
+							<div style="clear:both;"></div>
+							<div class="category_translations_form_fields">
+								<?php
+									foreach($locale_array as $lang_key => $lang_name) {
+										echo '<div class="category_lang category_lang_'.$lang_key.'">';
+										echo '<div class="category_lang_name"><strong>'.$lang_name.':</strong></div>';
+										echo '<div class="tab_form_item">'.form::input('category_title_lang['.$lang_key.']', $form['category_title_'.$lang_key], ' class="text" id="category_title_'.$lang_key.'"').'</div>';
+										echo '<div class="tab_form_item">'.form::input('category_description_lang['.$lang_key.']', $form['category_description_'.$lang_key], ' class="text category_description" id="category_description_'.$lang_key.'"').'</div>';
+										echo '</div>';
+									}
+								?>
+
+							</div>
+						</div>
+						<div style="clear:both"></div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_admin.color');?>:</strong><br />
+							<?php print form::input('category_color', $form['category_color'], ' class="text"'); ?>
+							
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.parent_category');?>:</strong><br />
+							<?php print form::dropdown('parent_id', $parents_array, '0'); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.image_icon');?>:</strong><br />
+							<?php
+
+								// I removed $category_image from the second parameter to fix bug #161
+								print form::upload('category_image', '', '');
+							?>
+						</div>
+						<div style="clear:both"></div>
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>			
+					</div>
+				</div>
+				
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'catListing',
+					 	'name' => 'catListing')); ?>
+						<input type="hidden" name="action" id="category_action" value="">
+						<input type="hidden" name="category_id" id="category_id_action" value="">
+						<div class="table-holder">
+							<table class="table" id="categorySort">
+								<thead>
+									<tr class="nodrag">
+										<th class="col-1"> </th>
+										<th class="col-2"><?php echo Kohana::lang('ui_main.category');?></th>
+										<th class="col-3"><?php echo Kohana::lang('ui_main.color');?></th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot nodrag">
+										<td colspan="4">
+											<?php echo $pagination; ?>
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr class="nodrag">
+											<td colspan="4" class="col" id="row1">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php	
+									}
+									foreach ($categories as $category)
+									{
+										$category_id = $category->id;
+										$parent_id = $category->parent_id;
+										$category_title = Category_Lang_Model::category_title($category_id);
+										$category_description = substr(Category_Lang_Model::category_description($category_id), 0, 150);
+										$category_color = $category->category_color;
+										$category_image = ($category->category_image != NULL) ? url::convert_uploaded_to_abs($category->category_image) : NULL;
+										$category_visible = $category->category_visible;
+										$category_trusted = $category->category_trusted;
+										
+										$fillFields = array();
+										$fillFields['category_id'] = $category->id;
+										$fillFields['parent_id'] = $category->parent_id;
+										$fillFields['category_title'] = $category->category_title;
+										$fillFields['category_description'] = $category->category_description;
+										$fillFields['category_color'] = $category->category_color;
+										$fillFields['category_image'] = $category->category_image;
+										$fillFields['locale'] = $category->locale;
+										$fillFields['category_langs'] = array();
+										foreach($category->category_lang as $category_lang) {
+											$fillFields['category_langs'][$category_lang->locale] = array(
+												'category_title' => $category_lang->category_title,
+												'category_description' => $category_lang->category_description
+											);
+										}
+										
+										?>
+										<tr id="<?php echo $category_id; ?>">
+											<td class="col-1 col-drag-handle"> </td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo html::escape($category_title); ?></h4>
+													<p><?php echo html::escape($category_description); ?></p>
+												</div>
+											</td>
+											<td class="col-3">
+											<?php if (!empty($category_image))
+											{
+												echo "<img src=\"".$category_image."\">";
+												echo " [<a href=\"javascript:catAction('i','DELETE ICON','".rawurlencode($category_id)."')\">".Kohana::lang('ui_main.delete')."</a>]";
+											}
+											else
+											{
+												echo "<span class=\"swatch\" style=\"background-color: #".$category_color.";\"> </span>";
+											}
+											?>
+											</td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="#add" id="edit-cat-<?php echo($category_id); ?>"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+													<script type="text/javascript">
+														$('#edit-cat-<?php echo($category_id); ?>').click(<?php echo json_encode($fillFields); ?>, fillFields);
+													</script>
+													<li class="none-separator">
+														<a class="status_yes" href="javascript:catAction('v','SHOW/HIDE','<?php echo(rawurlencode($category_id)); ?>')"><?php if ($category_visible) { echo Kohana::lang('ui_main.visible'); } else { echo Kohana::lang('ui_main.hidden'); }?></a>
+													</li>
+													<li>
+														<a href="javascript:catAction('d','DELETE','<?php echo(rawurlencode($category_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a>
+													</li>
+												</ul>
+												
+												<?php if($category_trusted == 1) { ?>
+												<div class="right">
+													<?php if($category_id == '4') { ?>
+													<a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang('ui_admin.special_category_explanation'));?>"><strong><?php echo Kohana::lang('ui_admin.special_category');?></strong></a>
+													<?php } else {?>
+														<a href="#" class="tooltip" title="<?php echo html::escape(Kohana::lang('ui_admin.none_category_explanation')); ?>"><strong><?php echo Kohana::lang('ui_admin.special_category');?></strong></a>
+													<?php } ?>
+												</div>
+												<?php } ?>
+												
+											</td>
+										</tr>
+										<?php
+										
+										// Get All Category Children
+										foreach ( $category->children as $child)
+										{
+											$category_id = $child->id;
+											$parent_id = $child->parent_id;
+											$category_title = Category_Lang_Model::category_title($category_id);
+											$category_description = substr(Category_Lang_Model::category_description($category_id), 0, 150);
+											$category_color = $child->category_color;
+											$category_image = ($child->category_image != NULL) ? url::convert_uploaded_to_abs($child->category_image) : NULL;
+											$category_visible = $child->category_visible;
+
+											$fillFields = array();
+											$fillFields['category_id'] = $child->id;
+											$fillFields['parent_id'] = $child->parent_id;
+											$fillFields['category_title'] = $child->category_title;
+											$fillFields['category_description'] = $child->category_description;
+											$fillFields['category_color'] = $child->category_color;
+											$fillFields['category_image'] = $child->category_image;
+											$fillFields['locale'] = $category->locale;
+											$fillFields['category_langs'] = array();
+											foreach($child->category_lang as $category_lang) {
+												$fillFields['category_langs'][$category_lang->locale] = array(
+													'category_title' => $category_lang->category_title,
+													'category_description' => $category_lang->category_description
+												);
+											}
+
+											?>
+											<tr id="<?php echo $category_id; ?>">
+												<td class="col-1 col-drag-handle"> </td>
+												<td class="col-2_sub">
+													<div class="post">
+														<h4><?php echo html::escape($category_title); ?></h4>
+														<p><?php echo html::escape($category_description); ?>...</p>
+													</div>
+												</td>
+												<td class="col-3">
+												<?php if (!empty($category_image))
+												{
+													echo "<img src=\"".$category_image."\">";
+													echo " [<a href=\"javascript:catAction('i','DELETE ICON','".rawurlencode($category_id)."')\">delete</a>]";
+												}
+												else
+												{
+													echo "<span class=\"swatch\" style=\"background-color: #".$category_color.";\"> </span>";
+												}
+												?>
+												</td>
+												<td class="col-4">
+													<ul>
+														<li class="none-separator"><a href="#add" id="edit-cat-<?php echo($category_id); ?>"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+														<script type="text/javascript">
+															$('#edit-cat-<?php echo($category_id); ?>').click(<?php echo json_encode($fillFields); ?>, fillFields);
+														</script>
+														<li class="none-separator"><a class="status_yes" href="javascript:catAction('v','SHOW/HIDE','<?php echo(rawurlencode($category_id)); ?>')"><?php if ($category_visible) { echo Kohana::lang('ui_main.visible'); } else { echo Kohana::lang('ui_main.hidden'); }?></a></li>
+	<li><a href="javascript:catAction('d','DELETE','<?php echo(rawurlencode($category_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+													</ul>
+												</td>
+											</tr>
+											<?php
+										}										
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+
+			</div>
diff --git a/application/views/admin/manage/feeds/feeds_js.php b/application/views/admin/manage/feeds/feeds_js.php
new file mode 100644
index 0000000..a71860b
--- /dev/null
+++ b/application/views/admin/manage/feeds/feeds_js.php
@@ -0,0 +1,53 @@
+/**
+ * Feeds js file.
+ *
+ * Handles javascript stuff related to feeds function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+// Categories JS
+function fillFields(id, feed_name, feed_url,
+ feed_visible )
+{
+	$("#feed_id").attr("value", decodeURIComponent(id));
+	$("#feed_name").attr("value", decodeURIComponent(feed_name));
+	$("#feed_url").attr("value", decodeURIComponent(feed_url));
+	$("#feed_visible").attr("value", decodeURIComponent(feed_visible));
+	
+}
+
+// Form Submission
+function feedAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#feed_id_action").attr("value", id);
+		// Set Submit Type
+		$("#action").attr("value", action);		
+		// Submit Form
+		$("#feedListing").submit();			
+	
+	} 
+//	else{
+//		return false;
+//	}
+}
+
+// Ajax Refresh Feeds
+function refreshFeeds()
+{
+	$('#feeds_loading').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+	$("#action").attr("value", 'r');		
+	// Submit Form
+	$("#feedListing").submit();
+}
diff --git a/application/views/admin/manage/feeds/items.php b/application/views/admin/manage/feeds/items.php
new file mode 100644
index 0000000..247fd39
--- /dev/null
+++ b/application/views/admin/manage/feeds/items.php
@@ -0,0 +1,142 @@
+<?php 
+/**
+ * Messages view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("feeds"); ?>
+				</h2>
+			
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="<?php echo url::site() . 'admin/manage/feeds' ?>"><?php echo Kohana::lang('ui_main.feeds');?></a></li>
+						<li><a href="<?php echo url::site() . 'admin/manage/feeds_items' ?>" class="active"><?php echo Kohana::lang('ui_main.feed_items');?></a></li>
+						
+					</ul>
+				
+					<!-- tab -->
+					<div class="tab">
+						<ul><li><a href="#" onclick="feedAction('d','<?php echo utf8::strtoupper(Kohana::lang('ui_main.delete')); ?>', '');">
+							<?php echo Kohana::lang('ui_main.delete');?></a>
+						</li></ul>
+					</div>
+				</div>
+				
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3><?php echo Kohana::lang('ui_main.messages');?> <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide"><?php echo Kohana::lang('ui_main.hide_this_message');?></a></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<?php print form::open(NULL, array('id' => 'feedListing', 'name' => 'feedListing')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<input type="hidden" name="item_id[]"  id="item_id_action"  value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1"><input id="checkallincidents" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'item_id[]' )" /></th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.item_details');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+									<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										<?php echo $pagination; ?>
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr>
+										<td colspan="4" class="col">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+								<?php	
+								}
+								foreach ($feed_items as $item)
+								{
+									$item_id = $item->id;
+									$item_title = $item->item_title;
+									$item_description = $item->item_description;
+									$item_link = $item->item_link;
+									$item_date = date('Y-m-d', strtotime($item->item_date));
+									
+									$feed_name = $item->feed->feed_name;
+									
+									$location_id = $item->location_id;
+									$incident_id = $item->incident_id;
+									?>
+									<tr>
+										<td class="col-1"><input name="item_id[]" value="<?php echo $item_id; ?>" type="checkbox" class="check-box"/></td>
+										<td class="col-2">
+											<div class="post">
+												<h4><?php echo $item_title; ?></h4>
+												<p><a href="javascript:preview('feed_preview_<?php echo $item_id?>')"><?php echo Kohana::lang('ui_main.preview_item');?></a></p>
+												<div id="feed_preview_<?php echo $item_id?>" style="display:none;">
+													<?php echo $item_description; ?>
+												</div>
+											</div>
+											<ul class="info">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.feed');?>: <strong><a href="<?php echo $item_link; ?>"><?php echo $feed_name; ?></a></strong>
+												<li><?php echo Kohana::lang('ui_main.geolocation_available');?>?: <strong><?php echo ($location_id) ? utf8::strtoupper(Kohana::lang('ui_main.yes')) : utf8::strtoupper(Kohana::lang('ui_main.no'));?></strong></li>
+											</ul>
+										</td>
+										<td class="col-3"><?php echo $item_date; ?></td>
+										<td class="col-4">
+											<ul>
+												<?php
+												if ($incident_id != 0) {
+													echo "<li class=\"none-separator\"><a href=\"". url::site() . 'admin/reports/edit/' . $incident_id ."\" class=\"status_yes\"><strong>".Kohana::lang('ui_main.view_report')."</strong></a></li>";
+												}
+												else
+												{
+													echo "<li class=\"none-separator\"><a href=\"".url::site().'admin/reports/edit?fid='.$item_id."\">".Kohana::lang('ui_main.create_report')."?</a></li>";
+												}
+												?>
+											<li><a href="javascript:feedAction('d','DELETE','<?php echo(rawurlencode($item_id)); ?>');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete'));?></a></li>
+											</ul>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/manage/feeds/items_js.php b/application/views/admin/manage/feeds/items_js.php
new file mode 100644
index 0000000..21a56da
--- /dev/null
+++ b/application/views/admin/manage/feeds/items_js.php
@@ -0,0 +1,67 @@
+
+// Preview Feed Item
+function preview ( id ){
+	if (id) {
+		$('#' + id).toggle(400);
+	}
+}
+/**
+ * Feeds_delete js file.
+ *
+ * Handles javascript stuff related to feeds_delete function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+// Categories JS
+function fillFields(id )
+{
+	$("#feed_id").attr("value", decodeURIComponent(id));
+	
+}
+
+// Form Submission
+function feedAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#feed_id_action").attr("value", id);
+		if (id != '') 
+		{
+			$("input[name=\"item_id[]\"]").attr('checked',false);
+			// Submit Form For Single Item
+			$("#item_id_action").attr("value", id);
+		}
+		else
+		{
+			// Set Item ID to 0
+			$("#item_id_action").attr("value", 0);
+		}
+		// Set Submit Type
+		$("#action").attr("value", action);		
+		// Submit Form
+		$("#feedListing").submit();			
+	
+	} 
+//	else{
+//		return false;
+//	}
+}
+
+// Ajax Refresh Feeds
+function refreshFeeds()
+{
+	$('#feeds_loading').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+	$("#action").attr("value", 'r');		
+	// Submit Form
+	$("#feedListing").submit();
+}
diff --git a/application/views/admin/manage/feeds/main.php b/application/views/admin/manage/feeds/main.php
new file mode 100644
index 0000000..4286b75
--- /dev/null
+++ b/application/views/admin/manage/feeds/main.php
@@ -0,0 +1,148 @@
+<?php 
+/**
+ * Feeds view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("feeds"); ?>
+				</h2>				
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="<?php echo url::site() . 'admin/manage/feeds' ?>" class="active"><?php echo Kohana::lang('ui_main.feeds');?></a></li>
+						<li><a href="<?php echo url::site() . 'admin/manage/feeds_items' ?>"><?php echo Kohana::lang('ui_main.feed_items');?></a></li>
+					</ul>
+					
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><a href="javascript:refreshFeeds();"><?php echo utf8::strtoupper(Kohana::lang('ui_main.refresh_news_feeds'));?></a></li><span id="feeds_loading"></span>
+						</ul>
+					</div>
+				</div>
+				<?php if ($form_error): ?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php endif; ?>
+				
+				<?php if ($form_saved): ?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.feed_has_been');?> <?php echo $form_action; ?>!</h3>
+					</div>
+				<?php endif; ?>
+				
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'feedListing', 'name' => 'feedListing')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="feed_id" id="feed_id_action" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1"> </th>
+										<th class="col-2"><?php echo Kohana::lang('ui_main.feed');?></th>
+										<th class="col-3"><?php echo Kohana::lang('ui_main.items');?></th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4"><?php echo $pagination; ?></td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php if ($total_items == 0): ?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php endif; ?>
+									<?php
+									foreach ($feeds as $feed)
+									{
+										$feed_id = $feed->id;
+										$feed_name = $feed->feed_name;
+										$feed_url = $feed->feed_url;
+										$feed_active = $feed->feed_active;
+										$feed_count = ORM::factory('feed_item')->where('feed_id',$feed->id)->count_all();
+									?>
+										<tr>
+											<td class="col-1"> </td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo $feed_name; ?>   [<a href="<?php echo url::site() . 'admin/manage/feeds_items/'.$feed_id ?>"><?php echo Kohana::lang('ui_main.view_items');?></a>]</h4>
+													<p><?php echo $feed_url; ?></p>
+												</div>
+											</td>
+											<td><?php echo $feed_count; ?></td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="#add" onClick="fillFields('<?php echo(rawurlencode($feed_id)); ?>','<?php echo(rawurlencode($feed_name)); ?>','<?php echo(rawurlencode($feed_url)); ?>')"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+													<li class="none-separator"><a class="status_yes" href="javascript:feedAction('v','SHOW/HIDE','<?php echo(rawurlencode($feed_id)); ?>')"><?php if ($feed_active) { echo Kohana::lang('ui_main.visible'); } else { echo Kohana::lang('ui_main.hidden'); } ?></a></li>
+													<li><a href="javascript:feedAction('d','DELETE','<?php echo(rawurlencode($feed_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+												</ul>
+											</td>
+										</tr>
+										<?php
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+				
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active"><?php echo Kohana::lang('ui_main.add_edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<?php print form::open(NULL,array('id' => 'feedMain', 'name' => 'feedMain')); ?>
+						<input type="hidden" id="feed_id"  name="feed_id" value="" />
+						<input type="hidden" id="feed_active" name="feed_active" vaule="" />
+						<input type="hidden" name="action" id="action" value="a"/>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.feed_name');?>:</strong><br />
+							<?php print form::input('feed_name', '', ' class="text"'); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.feed_url');?>:</strong><br />
+							<?php print form::input('feed_url', '', ' class="text long"'); ?>
+						</div>						
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>			
+					</div>
+				</div>
+			</div>
diff --git a/application/views/admin/manage/forms/forms_js.php b/application/views/admin/manage/forms/forms_js.php
new file mode 100644
index 0000000..fa16137
--- /dev/null
+++ b/application/views/admin/manage/forms/forms_js.php
@@ -0,0 +1,151 @@
+/**
+ * Forms js file.
+ *
+ * Handles javascript stuff related to forms function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+
+$(document).ready(function() {
+	form_id = '<?php echo $form_id; ?>';
+	if (form_id) {
+		showForm('formDiv_' + form_id);
+		$('#tr_' + form_id).effect("highlight", {}, 3000);
+	};
+});
+
+function fillFields(id, form_title, form_description,
+ form_visible )
+{
+	show_addedit();
+	$("#form_id").attr("value", decodeURIComponent(id));
+	$("#form_title").attr("value", decodeURIComponent(form_title));
+	$("#form_description").attr("value", decodeURIComponent(form_description));
+	$("#form_active").attr("value", decodeURIComponent(form_active));
+	
+}
+
+// Form Submission
+function formAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?');
+		
+	if (answer){
+		// Set Submit Type
+		$("#action_" + id).attr("value", action);		
+		
+		// Submit Form
+		$("#form_action_" + id).submit();			
+	
+	} 
+}
+
+// Show Function
+function showForm(id)
+{
+	if (id) {
+		$('#' + id).toggle(400);
+	}
+}
+
+// Toggle Add New Field
+function addNewForm(id)
+{
+	if (id) {
+		showForm('formadd_' + id);
+		showFormSelected('1',id,'',false);
+	}
+}
+
+// Ajax Call to Display 'Add New Field Form'
+function showFormSelected(id, form_id, field_id, select_disable)
+{
+	if (id) {
+		$('#form_result_' + form_id).html('');
+		$('#form_result_' + form_id).hide();
+		$('#form_fields_' + form_id).hide(300);
+		$('#form_field_' + form_id +' [name=field_type]').val(id);
+		$.post("<?php echo url::site() . 'admin/manage/forms/selector' ?>", { selector_id: id, form_id: form_id, field_id: field_id },
+			function(data){
+				if (data.status == 'success'){
+					$('#form_fields_' + form_id).html('');
+					$('#form_fields_' + form_id).show(300);
+					$('#form_fields_' + form_id).html(data.message);
+					$('#form_field_' + form_id +' [name=field_name]').focus();
+				}
+		  	}, "json");
+	};	
+}
+
+// Modify Individual Form Fields
+function fieldAction( action, confirmAction, field_id, form_id, field_type )
+{
+	$('#form_fields_current_' + form_id).css({
+	"background-image" : "url('<?php echo url::file_loc('img')."media/img/loading_g2.gif"; ?>')",
+	"background-position" : "center center",
+	"background-repeat" : "no-repeat"
+	});
+	
+	switch(action)
+	{
+	case 'e':
+		$('#formadd_' + form_id).show(400);
+		showFormSelected(field_type, form_id, field_id, true);
+		$('#form_fields_current_' + form_id).css({
+		"background-image" : "none"
+		});
+		break;
+	case 'd':
+		var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?');
+		if (answer){
+			$.post("<?php echo url::site() . 'admin/manage/forms/field_delete' ?>", { form_id: form_id, field_id: field_id },
+				function(data){
+					if (data.status == 'success'){
+						$('#form_fields_current_' + form_id).html('');
+						$('#form_fields_current_' + form_id).html(decodeURIComponent(data.response));
+						//$('#form_fields_current_' + form_id).effect("highlight", {}, 2000);
+						$('#form_fields_current_' + form_id).css({
+						"background-image" : "none"
+						});
+					}
+			  	}, "json");
+		}
+		break;
+	case 'mu':
+		$.post("<?php echo url::site() . 'admin/manage/forms/field_move' ?>", { form_id: form_id, field_id: field_id, field_position: 'u' },
+			function(data){
+				if (data.status == 'success'){
+					$('#form_fields_current_' + form_id).html('');
+					$('#form_fields_current_' + form_id).html(decodeURIComponent(data.response));
+					//$('#form_fields_current_' + form_id).effect("highlight", {}, 2000);
+					$('#form_fields_current_' + form_id).css({
+					"background-image" : "none"
+					});
+				}
+		  	}, "json");
+		break;
+	case 'md':
+		$.post("<?php echo url::site() . 'admin/manage/forms/field_move' ?>", { form_id: form_id, field_id: field_id, field_position: 'd' },
+			function(data){
+				if (data.status == 'success'){
+					$('#form_fields_current_' + form_id).html('');
+					$('#form_fields_current_' + form_id).html(decodeURIComponent(data.response));
+					//$('#form_fields_current_' + form_id).effect("highlight", {}, 2000);
+					$('#form_fields_current_' + form_id).css({
+					"background-image" : "none"
+					});
+				}
+		  	}, "json");
+		break;
+	}
+}
diff --git a/application/views/admin/manage/forms/main.php b/application/views/admin/manage/forms/main.php
new file mode 100644
index 0000000..a0e36de
--- /dev/null
+++ b/application/views/admin/manage/forms/main.php
@@ -0,0 +1,215 @@
+<?php 
+/**
+ * Forms view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>	
+		<div class="bg">
+			<h2>
+				<?php admin::manage_subtabs("forms"); ?>
+			</h2>
+			<?php
+			if ($form_error) {
+			?>
+				<!-- red-box -->
+				<div class="red-box">
+					<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+					<ul>
+					<?php
+					foreach ($errors as $error_item => $error_description)
+					{
+						// print "<li>" . $error_description . "</li>";
+						print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+					}
+					?>
+					</ul>
+				</div>
+			<?php
+			}
+
+			if ($form_saved) {
+			?>
+				<!-- green-box -->
+				<div class="green-box">
+					<h3><?php echo Kohana::lang('ui_main.form_has_been');?> <?php echo $form_action; ?>!</h3>
+				</div>
+			<?php
+			}
+			?>
+			
+			<!-- tabs -->
+			<div class="tabs">
+				<!-- tabset -->
+				<a name="add"></a>
+				<ul class="tabset">
+					<li><a href="#" class="active" onclick="show_addedit(true)"><?php echo Kohana::lang('ui_main.add_edit');?></a></li>
+				</ul>
+				<!-- tab -->
+				<div class="tab" id="addedit" style="display:none">
+					<?php print form::open(NULL,array('id' => 'formMain',
+					 	'name' => 'formMain')); ?>
+					<input type="hidden" id="form_id" 
+						name="form_id" value="<?php echo $form['form_id']; ?>" />
+					<input type="hidden" id="form_active" 
+						name="form_active" value="" />
+					<input type="hidden" name="action" 
+						id="action" value="a"/>
+					<div class="tab_form_item">
+						<strong><?php echo Kohana::lang('ui_main.form_title');?>:</strong><br />
+						<?php print form::input('form_title', $form['form_title'], 
+						' class="text"'); ?>
+					</div>
+					<div class="tab_form_item">
+						<strong><?php echo Kohana::lang('ui_main.form_description');?>:</strong><br />
+						<?php print form::input('form_description', $form['form_description'], ' class="text long"'); ?>
+					</div>						
+					<div class="tab_form_item">
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+					</div>
+					<?php print form::close(); ?>			
+				</div>
+			</div>
+							
+			<!-- report-table -->
+			<div class="report-form">				
+				<div class="table-holder">
+					<table class="table">
+						<thead>
+							<tr>
+								<th class="col-1"> </th>
+								<th class="col-2"><?php echo Kohana::lang('ui_main.form');?></th>
+								<th class="col-3"> </th>
+								<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+							</tr>
+						</thead>
+						<tfoot>
+							<tr class="foot">
+								<td colspan="4">
+									<?php echo $pagination; ?>
+								</td>
+							</tr>
+						</tfoot>
+						<tbody>
+							<?php
+							if ($total_items == 0)
+							{
+							?>
+								<tr>
+									<td colspan="4" class="col">
+										<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+									</td>
+								</tr>
+							<?php	
+							}
+							foreach ($forms as $form)
+							{
+								$form_id = $form->id;
+								$form_title = $form->form_title;
+								$form_description = $form->form_description;
+								$form_active = $form->form_active;
+								
+								$fields = ORM::factory('form_field')
+									->where('form_id', $form_id)
+									->orderby('field_position', 'asc')
+									->orderby('id', 'asc')
+									->find_all();
+
+								$form_fields = form::open(NULL, array('method' => 'get'));
+								foreach ($fields as $field)
+								{
+									$field_id = $field->id;
+									$field_name = $field->field_name;
+									$field_default = $field->field_default;
+									$field_required = $field->field_required;
+									$field_width = $field->field_width;
+									$field_height = $field->field_height;
+									$field_maxlength = $field->field_maxlength;
+									$field_position = $field->field_position;
+									$field_type = $field->field_type;
+									$field_isdate = $field->field_isdate;
+
+									$form_fields .= "<div class=\"forms_fields_item\">";
+									$form_fields .= "	<strong>".$field_name.":</strong><br />";
+									if ($field_type == 1)
+									{
+										$form_fields .= form::input("custom_".$field_id, '', '');
+									}
+									elseif ($field_type == 2)
+									{
+										$form_fields .= form::textarea("custom_".$field_id, '');
+									}
+									if ($field_isdate == 1) 
+									{
+										$form_fields .= " <a href=\"#\"><img src = \"".url::file_loc('img')."media/img/icon-calendar.gif\"  align=\"middle\" border=\"0\"></a>";
+									}
+									$form_fields .= "	<div class=\"forms_fields_edit\">
+									<a href=\"javascript:fieldAction('e','EDIT',".$field_id.",".$form_id.",".$field_type.");\">EDIT</a> | 
+									<a href=\"javascript:fieldAction('d','DELETE',".$field_id.",".$form_id.",".$field_type.");\">DELETE</a> | 
+									<a href=\"javascript:fieldAction('mu','MOVE',".$field_id.",".$form_id.",".$field_type.");\">MOVE UP</a> | 
+									<a href=\"javascript:fieldAction('md','MOVE',".$field_id.",".$form_id.",".$field_type.");\">MOVE DOWN</a></div>";
+									$form_fields .= "</div>";
+								}
+								$form_fields .= form::close();
+								?>
+								<?php print form::open(NULL,array('id' => 'form_action_' . $form_id,
+								 	'name' => 'form_action_' . $form_id )); ?>
+									<input type="hidden" name="action" id="action_<?php echo $form_id;?>" value="">
+									<input type="hidden" name="form_id" value="<?php echo $form_id;?>">
+									<tr id="tr_<?php echo $form_id; ?>">
+										<td class="col-1"> </td>
+										<td class="col-2">
+											<div class="post">
+												<h4><?php echo $form_title; ?>   [<a href="javascript:showForm('formDiv_<?php echo $form_id; ?>')"><?php echo Kohana::lang('ui_main.edit_form_fields');?></a>]</h4>
+												<p><?php echo $form_description; ?></p>
+											</div>
+										</td>
+										<td> </td>
+										<td class="col-4">
+											<ul>
+												<li class="none-separator"><a href="#add" onClick="fillFields('<?php echo(rawurlencode($form_id)); ?>','<?php echo(rawurlencode($form_title)); ?>','<?php echo(rawurlencode($form_description)); ?>')">Edit</a></li>
+												<li class="none-separator"><a href="javascript:formAction('h','SHOW/HIDE','<?php echo(rawurlencode($form_id)); ?>')"<?php if ($form_active) echo " class=\"status_yes\"" ?>><?php echo Kohana::lang('ui_main.active');?></a></li>
+												<li><a href="javascript:formAction('d','DELETE','<?php echo(rawurlencode($form_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+											</ul>
+										</td>
+									</tr>
+								<?php print form::close(); ?>
+								<tr style="margin:0;padding:0;border-width:0;">
+									<td colspan="4" style="margin:0;padding:0;border-width:0;">
+										<div id="formDiv_<?php echo $form_id; ?>" class="forms_fields">
+											<a href="javascript:addNewForm('<?php echo $form_id; ?>')" class="new-form_field">Add New Field</a>
+											<div id="formadd_<?php echo $form_id; ?>" class="forms_fields_add">
+												<div class="tab">
+													<div>
+														<?php echo form::open(url::site() . 'admin/manage/forms/field_add', array('id' => 'form_field_'.$form_id,
+																'name' => 'form_field_'.$form_id)); ?>
+															<strong><?php echo Kohana::lang('ui_main.select_field_type');?>:</strong>
+															<?php print form::dropdown('field_type',$form_field_types, '', ' onchange="showFormSelected(this.options[this.selectedIndex].value, \''.$form_id.'\', \'\', \'\')"'); ?>
+															<div id="form_fields_<?php echo $form_id; ?>" class="forms_fields_new"></div>
+														<?php echo form::close(); ?>
+													</div>		
+												</div>
+											</div>
+											<div id="form_fields_current_<?php echo $form_id; ?>" class="forms_fields_current">
+												<?php echo $form_fields; ?>
+											</div>
+										</div>
+									</td>
+								</tr>
+								<?php
+							}
+							?>
+						</tbody>
+					</table>
+				</div>
+			</div>
+
+		</div>
diff --git a/application/views/admin/manage/layers/layers_js.php b/application/views/admin/manage/layers/layers_js.php
new file mode 100644
index 0000000..27d0566
--- /dev/null
+++ b/application/views/admin/manage/layers/layers_js.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Layers js file.
+ * 
+ * Handles javascript stuff related to layers controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Layers JS View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+// Layers JS
+function fillFields(id, layer_name, layer_url, layer_color, layer_file_old)
+{
+	$("#layer_id").attr("value", decodeURIComponent(id));
+	$("#layer_name").attr("value", decodeURIComponent(layer_name));
+	$("#layer_url").attr("value", decodeURIComponent(layer_url));
+	$("#layer_color").attr("value", decodeURIComponent(layer_color));
+	$("#layer_file_old").attr("value", decodeURIComponent(layer_file_old));
+}
+
+function layerAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#layer_id_action").attr("value", id);
+		// Set Submit Type
+		$("#action").attr("value", action);		
+		// Submit Form
+		$("#layerListing").submit();
+	}
+}
\ No newline at end of file
diff --git a/application/views/admin/manage/layers/main.php b/application/views/admin/manage/layers/main.php
new file mode 100644
index 0000000..0b5c947
--- /dev/null
+++ b/application/views/admin/manage/layers/main.php
@@ -0,0 +1,196 @@
+<?php 
+/**
+ * Layers view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Layers view
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("layers"); ?>
+				</h2>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.layer_has_been');?> <?php echo $form_action; ?>!</h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'layerListing',
+					 	'name' => 'layerListing')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="layer_id" id="layer_id_action" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1"> </th>
+										<th class="col-2"><?php echo Kohana::lang('ui_main.layers');?></th>
+										<th class="col-3"><?php echo Kohana::lang('ui_main.color');?></th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4">
+											<?php echo $pagination; ?>
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php	
+									}
+									foreach ($layers as $layer)
+									{
+										$layer_id = $layer->id;
+										$layer_name = $layer->layer_name;
+										$layer_color = $layer->layer_color;
+										$layer_url = $layer->layer_url;
+										$layer_file = $layer->layer_file;
+										$layer_visible = $layer->layer_visible;
+										?>
+										<tr>
+											<td class="col-1"> </td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo $layer_name; ?></h4>
+												</div>
+												<ul class="info">
+													<?php
+													if($layer_file)
+													{
+														?><li class="none-separator"><?php echo Kohana::lang('ui_main.kml_kmz_file');?>: <p><strong><?php echo $layer_file; ?></strong></p>
+														 [<a href="javascript:layerAction('i','DELETE FILE','<?php echo rawurlencode($layer_id);?>')">Delete</a>]</li>
+														<?php
+													}
+													?>
+												</ul>
+												<ul class="links">
+													<?php
+													if($layer_url)
+													{
+														?><li class="none-separator"><?php echo Kohana::lang('ui_main.kml_url');?>: <p><strong><?php echo text::auto_link($layer_url); ?></strong></p></li><?php
+													}
+													?>
+												</ul>
+											</td>
+											<td class="col-3">
+											<?php echo "<img src=\"".url::base()."swatch/?c=".$layer_color."&w=30&h=30\">"; ?>
+											</td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="#add" onClick="fillFields('<?php echo(rawurlencode($layer_id)); ?>','<?php echo(rawurlencode($layer_name)); ?>','<?php echo(rawurlencode($layer_url)); ?>','<?php echo(rawurlencode($layer_color)); ?>','<?php echo(rawurlencode($layer_file)); ?>')"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+													<li class="none-separator"><a class="status_yes" href="javascript:layerAction('v','SHOW/HIDE','<?php echo(rawurlencode($layer_id)); ?>')"><?php if ($layer_visible) { echo Kohana::lang('ui_main.visible'); } else { echo Kohana::lang('ui_main.hidden'); }?></a></li>
+<li><a href="javascript:layerAction('d','DELETE','<?php echo(rawurlencode($layer_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+												</ul>
+											</td>
+										</tr>
+										<?php									
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+				
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active"><?php echo Kohana::lang('ui_main.add_edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<?php print form::open(NULL,array('enctype' => 'multipart/form-data', 
+							'id' => 'layerMain', 'name' => 'layerMain')); ?>
+						<input type="hidden" id="layer_id" 
+							name="layer_id" value="" />
+						<input type="hidden" name="action" 
+							id="action" value="a"/>
+						<input type="hidden" name="layer_file_old" 
+							id="layer_file_old" value=""/>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.layer_name');?>:</strong><br />
+							<?php print form::input('layer_name', '', ' class="text"'); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.layer_url');?>:</strong><br />
+							<?php print form::input('layer_url', '', ' class="text long"'); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.color');?>:</strong><br />
+							<?php print form::input('layer_color', '', ' class="text"'); ?>
+							<script type="text/javascript" charset="utf-8">
+								$(document).ready(function() {
+									$('#layer_color').ColorPicker({
+										onSubmit: function(hsb, hex, rgb) {
+											$('#layer_color').val(hex);
+										},
+										onChange: function(hsb, hex, rgb) {
+											$('#layer_color').val(hex);
+										},
+										onBeforeShow: function () {
+											$(this).ColorPickerSetColor(this.value);
+										}
+									})
+									.bind('keyup', function(){
+										$(this).ColorPickerSetColor(this.value);
+									});
+								});
+							</script>
+						</div>
+						<div style="clear:both"></div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.kml_kmz_upload');?>:</strong><br />
+							<?php print form::upload('layer_file', '', ''); ?>
+						</div>
+						<div style="clear:both"></div>
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>			
+					</div>
+				</div>
+			</div>
diff --git a/application/views/admin/manage/pages/main.php b/application/views/admin/manage/pages/main.php
new file mode 100644
index 0000000..5cd3ff8
--- /dev/null
+++ b/application/views/admin/manage/pages/main.php
@@ -0,0 +1,156 @@
+<?php 
+/**
+ * Pages view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Pages View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("pages"); ?>
+				</h2>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.page_has_been');?> <?php echo $form_action; ?></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'pageListing',
+					 	'name' => 'pageListing')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="page_id" id="page_id_action" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1"> </th>
+										<th class="col-2"><?php echo Kohana::lang('ui_main.page');?></th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4">
+											<?php echo $pagination; ?>
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php	
+									} 
+									foreach ($pages as $page)
+									{
+										$page_id = $page->id;
+										$page_title = $page->page_title;
+										$page_tab = $page->page_tab;
+										$page_description = $page->page_description;
+										$page_active = $page->page_active;
+										?>
+										<tr>
+											<td class="col-1"> </td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo html::escape($page_title); ?></h4>
+													<p><?php echo text::limit_chars(html::strip_tags($page_description), "100", "..."); ?></p>
+												</div>
+											</td>
+											
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="#add" onClick="fillFields(
+	'<?php echo(rawurlencode($page_id)); ?>',
+	'<?php echo(base64_encode($page_title)); ?>',
+	'<?php echo(base64_encode($page_tab)); ?>',
+	'<?php echo(base64_encode($page_description)); ?>')"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+	<li class="none-separator"><a class="status_yes" href="javascript:pageAction('v','SHOW/HIDE','<?php echo(rawurlencode($page_id)); ?>')"><?php if ($page_active) { echo Kohana::lang('ui_main.visible'); }else{ echo Kohana::lang('ui_main.hidden'); }?></a></li>
+													<li><a href="javascript:pageAction('d','DELETE','<?php echo(rawurlencode($page_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+												</ul>
+											</td>
+										</tr>
+										<?php
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+				
+				<div class="tabs">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active"><?php echo Kohana::lang('ui_main.add_edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<?php print form::open(NULL,array('enctype' => 'multipart/form-data', 'id' => 'pageMain',
+						 	'name' => 'pageMain')); ?>					
+						<input type="hidden" id="page_id" 
+							name="page_id" value="<?php echo $form['page_id']; ?>" />
+						<input type="hidden" name="action" 
+							id="action" value="a"/>							
+						<div class="tab_form_item2">
+							<strong><?php echo Kohana::lang('ui_main.page_title');?>:</strong><br />
+							<?php print form::input('page_title', $form['page_title'], ' class="text long"'); ?>
+						</div>
+						<div class="tab_form_item2">
+							<strong><?php echo Kohana::lang('ui_main.page_tab_name');?>:</strong><br />
+							<?php print form::input('page_tab', $form['page_tab'], ' class="text long"'); ?>
+						</div>
+						<div class="tab_form_item2">
+							<strong><?php echo Kohana::lang('ui_main.page_description');?>:</strong><br />
+							<?php print form::textarea('page_description',$form['page_description'],' rows="12" cols="120" ','id = page_description'); ?>
+						</div>
+						<?php
+							// Action::page_form_admin - Runs just after the page description
+							Event::run('ushahidi_action.page_form_admin');
+						?>
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>
+					</div>
+				</div>
+				
+			</div>
diff --git a/application/views/admin/manage/pages/pages_js.php b/application/views/admin/manage/pages/pages_js.php
new file mode 100644
index 0000000..5197d83
--- /dev/null
+++ b/application/views/admin/manage/pages/pages_js.php
@@ -0,0 +1,154 @@
+<?php
+/**
+ * Pages js file.
+ *
+ * Handles javascript stuff related to pages function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Pages Javascript
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+// Pages JS
+
+
+function fillFields(id, page_title, page_tab,
+ page_description )
+{
+	$("#page_id").attr("value", decodeURIComponent(id));
+	page_title = decodeURIComponent(escape($.base64.decode(page_title)));
+	$("#page_title").attr("value", decodeURIComponent(page_title));
+	page_tab = decodeURIComponent(escape($.base64.decode(page_tab)));
+	$("#page_tab").attr("value", decodeURIComponent(page_tab));
+	page_description = decodeURIComponent(escape($.base64.decode(page_description)));
+	$("#page_description").attr("value", decodeURIComponent(page_description));
+	$("#page_description").wysiwyg("setContent",decodeURIComponent(page_description));
+}
+
+// Ajax Submission
+function pageAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#page_id_action").attr("value", id);
+		// Set Submit Type
+		$("#action").attr("value", action);		
+		// Submit Form
+		$("#pageListing").submit();
+	}
+}
+
+//Load jwysiwyg editor
+var hb_full ;
+$(document).ready(function(){
+	hb_full = $("#page_description").wysiwyg({
+		resizeOptions: {},
+		
+		controls: {
+			bold: { visible : true, groupIndex: 0 },
+			italic: { visible : true, groupIndex: 0 },
+			underline: { visible: false },
+			strikeThrough: { visible: false },
+			
+			justifyLeft:{ visible: true },
+			justifyCenter: { visible: true },
+			justifyRight: { visible: true },
+			justifyFull: { visible: false },
+			
+			subscript: { visible: false },
+			superscript: { visible: false },
+			
+			undo: { visible: false },
+			redo: { visible: false },
+			
+			insertOrderedList    : { visible : true },
+			insertUnorderedList  : { visible : true },
+			insertHorizontalRule : { visible : false },
+			
+			h1: {
+				visible: true,
+				className: 'h1',
+				command: ($.browser.msie || $.browser.safari) ? 'formatBlock' : 'heading',
+				arguments: ($.browser.msie || $.browser.safari) ? '<h1>' : 'h1',
+				tags: ['h1'],
+				tooltip: 'Header 1',
+				groupIndex: 7
+			},
+			h2: {
+				visible: true,
+				className: 'h2',
+				command: ($.browser.msie || $.browser.safari) ? 'formatBlock' : 'heading',
+				arguments: ($.browser.msie || $.browser.safari) ? '<h2>' : 'h2',
+				tags: ['h2'],
+				tooltip: 'Header 2',
+				groupIndex: 7
+			},
+			h3: {
+				visible: true,
+				className: 'h3',
+				command: ($.browser.msie || $.browser.safari) ? 'formatBlock' : 'heading',
+				arguments: ($.browser.msie || $.browser.safari) ? '<h3>' : 'h3',
+				tags: ['h3'],
+				tooltip: 'Header 3',
+				groupIndex: 7
+			},
+			h4: {
+				visible: true,
+				className: 'h4',
+				command: ($.browser.msie || $.browser.safari) ? 'formatBlock' : 'heading',
+				arguments: ($.browser.msie || $.browser.safari) ? '<h4>' : 'h4',
+				tags: ['h4'],
+				tooltip: 'Header 4',
+				groupIndex: 7
+			},
+			h5: {
+				visible: true,
+				className: 'h5',
+				command: ($.browser.msie || $.browser.safari) ? 'formatBlock' : 'heading',
+				arguments: ($.browser.msie || $.browser.safari) ? '<h5>' : 'h5',
+				tags: ['h5'],
+				tooltip: 'Header 5',
+				groupIndex: 7
+			},
+			paragraph: { visible: true },
+			
+			fileManager: { 
+				visible: false,
+				groupIndex: 12,
+				tooltip: "File Manager",
+				exec: function () {
+					$.wysiwyg.fileManager.init(this, function (file) {
+						file ? alert(file) : alert("No file selected.");
+					});
+				}
+			},
+			
+			cut   : { visible : false },
+			copy  : { visible : false },
+			paste : { visible : false },
+			
+			html: { visible: true },
+			code: { visible: false },
+			fullscreen: {
+				groupIndex: 12,
+				visible: true,
+				exec: function () {
+					if ($.wysiwyg.fullscreen) {
+						$.wysiwyg.fullscreen.init(this);
+					}
+				},
+				tooltip: "Fullscreen"
+			}
+		},
+	});
+	$.wysiwyg.fileManager.setAjaxHandler("<?php echo url::site('admin/jwysiwyg/filemanager') ?>");
+});
+
diff --git a/application/views/admin/manage/publiclisting.php b/application/views/admin/manage/publiclisting.php
new file mode 100644
index 0000000..8e71250
--- /dev/null
+++ b/application/views/admin/manage/publiclisting.php
@@ -0,0 +1,11 @@
+<div class="bg">
+
+	<h2>
+		<?php admin::manage_subtabs("publiclisting"); ?>
+	</h2>
+
+	<iframe src ="<?php echo Kohana::config('core.site_protocol'); ?>://tracker.ushahidi.com/list/manage.php?id=<?php echo $encoded_stat_id; ?>&key=<?php echo $encoded_stat_key; ?>&lat=<?php echo $lat; ?>&lon=<?php echo $lon; ?>" width="100%" height="700" border="0" style="border:0px;">
+	<p>Error: Your browser does not support iframes.</p>
+	</iframe>
+
+</div>
\ No newline at end of file
diff --git a/application/views/admin/manage/scheduler/log.php b/application/views/admin/manage/scheduler/log.php
new file mode 100644
index 0000000..a3b002d
--- /dev/null
+++ b/application/views/admin/manage/scheduler/log.php
@@ -0,0 +1,96 @@
+<?php 
+/**
+ * Scheduler view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+				<?php admin::manage_subtabs("scheduler"); ?>
+				</h2>
+				
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="<?php echo url::site() . 'admin/manage/scheduler' ?>"><?php echo Kohana::lang('ui_main.scheduler');?></a></li>
+						<li><a href="<?php echo url::site() . 'admin/manage/scheduler/log' ?>" class="active"><?php echo Kohana::lang('ui_main.scheduler_log');?></a></li>
+					</ul>
+
+					<!-- tab -->
+					<div class="tab">
+						 
+					</div>
+				</div>
+				
+
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'schedulerListing',
+					 	'name' => 'schedulerListing')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="scheduler_id" id="scheduler_id_action" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1"> </th>
+										<th class="col-2"><?php echo Kohana::lang('ui_main.schedule');?></th>
+										<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+										<th class="col-4"> </th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4">
+											<?php echo $pagination; ?>
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php	
+									}
+									foreach ($scheduler_logs as $log)
+									{
+										$scheduler_name = $log->scheduler->scheduler_name;
+										$scheduler_date = date("F j, Y, g:i a", $log->scheduler_date);
+										?>
+										<tr>
+											<td class="col-1"> </td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo $scheduler_name; ?></h4>
+												</div>
+											</td>
+											<td class="col-3" nowrap="nowrap"><?php echo $scheduler_date; ?></td>
+											<td class="col-4"> 
+												
+											</td>
+										</tr>
+										<?php
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+			</div>
diff --git a/application/views/admin/manage/scheduler/main.php b/application/views/admin/manage/scheduler/main.php
new file mode 100644
index 0000000..18d34dd
--- /dev/null
+++ b/application/views/admin/manage/scheduler/main.php
@@ -0,0 +1,225 @@
+<?php 
+/**
+ * Scheduler view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("scheduler"); ?>
+				</h2>
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="<?php echo url::site() . 'admin/manage/scheduler' ?>" class="active"><?php echo Kohana::lang('ui_main.scheduler');?></a></li>
+						<li><a href="<?php echo url::site() . 'admin/manage/scheduler/log' ?>"><?php echo Kohana::lang('ui_main.scheduler_log');?></a></li>
+					</ul>
+
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><a href="<?php echo url::site(); ?>admin/manage/scheduler/?run_scheduler=1"><?php echo utf8::strtoupper(Kohana::lang('ui_main.force_run_scheduler'));?></a></li>
+						</ul>
+					</div>
+				</div>
+				
+				
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.schedule');?> <?php echo $form_action; ?>!</h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'schedulerListing',
+					 	'name' => 'schedulerListing')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="scheduler_id" id="scheduler_id_action" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1"> </th>
+										<th class="col-2"><?php echo Kohana::lang('ui_main.schedule');?></th>
+										<th class="col-3"> </th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4">
+											<?php echo $pagination; ?>
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php	
+									}
+									foreach ($schedules as $schedule)
+									{
+										$scheduler_id = $schedule->id;
+										$scheduler_name = $schedule->scheduler_name;
+										$scheduler_weekday = $scheduler_weekday_text = $schedule->scheduler_weekday;
+										$scheduler_day = $scheduler_day_text = $schedule->scheduler_day;
+										$scheduler_hour = $scheduler_hour_text = $schedule->scheduler_hour;
+										$scheduler_minute = $scheduler_minute_text = $schedule->scheduler_minute;
+										$scheduler_active = $schedule->scheduler_active;
+										
+										$days[0] = 'Sunday'; 
+										$days[1] = 'Monday'; 
+										$days[2] = 'Tuesday'; 
+										$days[3] = 'Wednesday'; 
+										$days[4] = 'Thursday'; 
+										$days[5] = 'Friday'; 
+										$days[6] = 'Saturday';
+										if ($scheduler_weekday <= -1) 
+										{ // Ran every day?
+											$scheduler_weekday_text = "ALL";
+										}
+										else
+										{
+											$scheduler_weekday_text = $days[$scheduler_weekday];
+										}
+										
+										if ($scheduler_day <= -1) 
+										{ // Ran every day?
+											$scheduler_day_text = "ALL";
+										}
+
+										if ($scheduler_hour <= -1) 
+										{ // Ran every hour?
+											$scheduler_hour_text = "ALL";
+										}
+
+										if ($scheduler_minute <= -1) 
+										{ // Ran every minute?
+											$scheduler_minute_text = "ALL";
+										}
+										?>
+										<tr>
+											<td class="col-1"> </td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo $scheduler_name; ?></h4>
+													<p>Schedule:</p>
+												</div>
+												<ul class="info">
+													<li class="none-separator"><?php echo Kohana::lang('ui_main.scheduler_weekday');?>: <strong><?php echo $scheduler_weekday_text; ?></strong></li>
+													<li><?php echo Kohana::lang('ui_main.scheduler_day');?>: <strong><?php echo $scheduler_day_text; ?></strong></li>
+													<li><?php echo Kohana::lang('ui_main.scheduler_hour');?>: <strong><?php echo $scheduler_hour_text; ?></strong></li>
+													<li><?php echo Kohana::lang('ui_main.scheduler_minute');?>: <strong><?php echo $scheduler_minute_text; ?></strong>   <a href="http://en.wikipedia.org/wiki/Cron" target="_blank">?</a></li>
+												</ul>
+											</td>
+											<td class="col-3"> </td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="#add" onClick="fillFields('<?php echo(rawurlencode($scheduler_id)); ?>','<?php echo(rawurlencode($scheduler_name)); ?>','<?php echo $scheduler_weekday; ?>','<?php echo $scheduler_day; ?>','<?php echo $scheduler_hour; ?>','<?php echo $scheduler_minute; ?>')">Edit</a></li>
+													<li class="none-separator"><a href="javascript:schedulerAction('v','ACTIVATE/DEACTIVATE','<?php echo(rawurlencode($scheduler_id)); ?>')"<?php if ($scheduler_active) echo " class=\"status_yes\"" ?>><?php echo Kohana::lang('ui_main.active');?></a></li>
+												</ul>
+											</td>
+										</tr>
+										<?php
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+				
+				<!-- tabs -->
+				<div class="tabs" id="add_edit_form" style="display:none;">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<?php print form::open(NULL,array('id' => 'schedulerMain',
+						 	'name' => 'schedulerMain')); ?>
+						<input type="hidden" id="scheduler_id" 
+							name="scheduler_id" value="" />
+						<input type="hidden" id="scheduler_active" 
+							name="scheduler_active" vaule="" />
+						<input type="hidden" name="action" 
+							id="action" value=""/>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.scheduler');?>:</strong><br />
+							<?php print form::input('scheduler_name', '', ' class="text" disabled="disabled" '); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.scheduler_weekday');?>:</strong><br />
+							<?php print form::dropdown('scheduler_weekday', $weekday_array, '0'); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.scheduler_day');?>:</strong><br />
+							<?php print form::dropdown('scheduler_day', $day_array, '0'); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.scheduler_hour');?>:</strong><br />
+							<?php print form::dropdown('scheduler_hour', $hour_array, '0'); ?>
+						</div>												
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.scheduler_minute');?>:</strong><br />
+							<?php print form::dropdown('scheduler_minute', $minute_array, '0'); ?>
+						</div>						
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>			
+						<div style="clear:both"></div>
+						<div class="tab_form_item">
+							<strong>Examples:</strong><Br />
+							Every Minute: ALL | ALL | ALL | ALL <br />
+							Every Hour: ALL | ALL | ALL | 0 <br />
+							Midnight Every Day: ALL | ALL | 0 | 0 <br />
+							Once A Week on Monday: Monday | ALL | 0 | 0 <br />
+							Every 1st of the Month: ALL | 1 | 0 | 0 <br /><br />
+							<a href="http://en.wikipedia.org/wiki/Cron" target="_blank">More about running CRON Tasks</a>
+						</div>
+					</div>
+				</div>
+			</div>
diff --git a/application/views/admin/manage/scheduler/scheduler_js.php b/application/views/admin/manage/scheduler/scheduler_js.php
new file mode 100644
index 0000000..a90fdc5
--- /dev/null
+++ b/application/views/admin/manage/scheduler/scheduler_js.php
@@ -0,0 +1,27 @@
+function fillFields(id, scheduler_name, scheduler_weekday, 
+	scheduler_day, scheduler_hour, scheduler_minute)
+{
+	$('#add_edit_form').show();
+	$("#scheduler_id").attr("value", decodeURIComponent(id));
+	$("#scheduler_name").attr("value", decodeURIComponent(scheduler_name));
+	$("#scheduler_weekday").attr("value", decodeURIComponent(scheduler_weekday));
+	$("#scheduler_day").attr("value", decodeURIComponent(scheduler_day));
+	$("#scheduler_hour").attr("value", decodeURIComponent(scheduler_hour));
+	$("#scheduler_minute").attr("value", decodeURIComponent(scheduler_minute));
+}
+
+
+// Ajax Submission
+function schedulerAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#scheduler_id_action").attr("value", id);
+		// Set Submit Type
+		$("#action").attr("value", action);		
+		// Submit Form
+		$("#schedulerListing").submit();
+	}
+}
\ No newline at end of file
diff --git a/application/views/admin/messages/main.php b/application/views/admin/messages/main.php
new file mode 100644
index 0000000..944599f
--- /dev/null
+++ b/application/views/admin/messages/main.php
@@ -0,0 +1,251 @@
+<?php 
+/**
+ * Messages view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module	   API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::messages_subtabs($service_id); ?>
+				</h2>
+
+<?php
+	Event::run('ushahidi_action.admin_messages_custom_layout');
+	// Kill the rest of the page if this event has been utilized by a plugin
+	if( ! Event::has_run('ushahidi_action.admin_messages_custom_layout')){
+?>
+
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="<?php echo url::site()."admin/messages/index/".$service_id; ?>?type=1" <?php if ($type == '1') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.inbox');?></a></li>
+						<?php
+						if ($service_id == 1)
+						{
+							?><li><a href="<?php echo url::site()."admin/messages/index/".$service_id; ?>?type=2" <?php if ($type == '2') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.outbox');?></a></li><?php
+						}
+						?>
+						<?php if ($type == '1')
+						{ ?>
+							<li>      </li>
+							<li><a href="?type=<?php echo $type ?>&level=0" <?php if ($level == '0') echo "class=\"active2\""; ?>><?php echo Kohana::lang('ui_main.all');?> (<?php echo $count_all; ?>)</a></li>
+							<li><a href="?type=<?php echo $type ?>&level=4" <?php if ($level == '4') echo "class=\"active2\""; ?>><?php echo Kohana::lang('ui_main.trusted'); ?> (<?php echo $count_trusted; ?>)</a></li>
+							<li><a href="?type=<?php echo $type ?>&level=2" <?php if ($level == '2') echo "class=\"active2\""; ?>><?php echo Kohana::lang('ui_main.spam');?> (<?php echo $count_spam; ?>)</a></li>
+						<?php } ?>
+						<li>      </li>
+						<li><a href="<?php echo
+						url::site()."admin/messages/reporters/index/".$service_id; ?>">Reporters(<?php echo $count_reporters; ?>)</a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><a href="#" onClick="messagesAction('d', 'DELETE', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete'));?></a></li>
+							<li><a href="#" onClick="messagesAction('s', 'SPAM', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.spam'));?></a></li>
+							<li><a href="#" onClick="messagesAction('n', 'NOT SPAM', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.not_spam'));?></a></li>
+						</ul>
+					</div>
+				</div>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3><?php echo Kohana::lang('ui_main.messages');?> <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide">hide this message</a></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<?php print form::open(NULL, array('id' => 'messageMain', 'name' => 'messageMain')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<input type="hidden" name="level"  id="level"  value="">
+					<input type="hidden" name="message_id[]" id="message_single" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1"><input id="checkall" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'message_id[]' )" /></th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.message_details');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+									<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										<?php echo $pagination; ?>
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr>
+										<td colspan="4" class="col">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+								<?php	
+								}
+								foreach ($messages as $message)
+								{
+									$message_id = $message->id;
+									$message_from = html::strip_tags($message->reporter->service_account);
+									$message_to = html::strip_tags($message->message_to);
+									$incident_id = $message->incident_id;
+									$message_description = text::auto_link(html::strip_tags($message->message));
+									$message_detail = nl2br(text::auto_link(html::strip_tags($message->message_detail)));
+									$message_date = date('Y-m-d  H:i', strtotime($message->message_date));
+									$message_type = $message->message_type;
+									$message_level = $message->message_level;
+									$latitude = $message->latitude;
+									$longitude = $message->longitude;
+
+									$level_id = $message->reporter->level_id;
+									?>
+									<tr <?php if ($message_level == "99") {
+										echo " class=\"spam_tr\"";
+									} ?>>
+										<td class="col-1"><input name="message_id[]" id="message" value="<?php echo $message_id; ?>" type="checkbox" class="check-box"/></td>
+										<td class="col-2">
+											<div class="post">
+												<p><?php echo $message_description; ?></p>
+												<?php
+												if ($message_detail OR $message->media->count() > 0)
+												{
+													?>
+													<p><a href="javascript:preview('message_preview_<?php echo $message_id?>')"><?php echo Kohana::lang('ui_main.preview_message');?></a></p>
+													<div id="message_preview_<?php echo $message_id?>" style="display:none;">
+														<?php echo $message_detail; ?>
+
+														<?php
+														// Retrieve Attachments if any
+														foreach($message->media as $photo) 
+														{
+															if ($photo->media_type == 1)
+															{
+																print "<div class=\"attachment_thumbs\" id=\"photo_". $photo->id ."\">";
+
+																$thumb = $photo->media_thumb;
+																$photo_link = $photo->media_link;
+																$prefix = url::base().Kohana::config('upload.relative_directory');
+																print "<a class='photothumb' rel='lightbox-group".$message_id."' href='$prefix/$photo_link'>";
+																print "<img src=\"$prefix/$thumb\" border=\"0\" >";
+																print "</a>";
+																print "</div>";
+															}
+														}
+														?>
+													</div>
+													<?php
+												}
+												// Action::message_extra_admin	- Message Additional/Extra Stuff
+												Event::run('ushahidi_action.message_extra_admin', $message_id);
+												Event::run('ushahidi_action.message_from_admin', $message_from);
+												?>
+
+												<?php if($reply_to == TRUE) { ?>
+
+												<?php
+												if ($service_id == 1 && $message_type == 1)
+												{
+													?>
+													<div id="replies">
+
+													</div>
+													<a href="javascript:showReply('reply_<?php echo $message_id; ?>')" class="more">+<?php echo Kohana::lang('ui_main.reply');?></a>
+													<div id="reply_<?php echo $message_id; ?>" class="reply">
+														<?php print form::open(url::site() . 'admin/messages/send/',array('id' => 'newreply_' . $message_id,
+															'name' => 'newreply_' . $message_id)); ?>
+														<div class="reply_can"><a href="javascript:cannedReply('1', 'message_<?php echo $message_id; ?>')">+<?php echo Kohana::lang('ui_main.request_location');?></a>   <a href="javascript:cannedReply('2', 'message_<?php echo $message_id; ?>')">+<?php echo Kohana::lang('ui_main.request_information');?></a></div>
+														<div id="replyerror_<?php echo $message_id; ?>" class="reply_error"></div>
+														<div class="reply_input"><?php print form::input('message_' .  $message_id, '', ' class="text long2" onkeyup="limitChars(this.id, \'160\', \'replyleft_' . $message_id . '\')" '); ?></div>
+														<div class="reply_input"><a href="javascript:sendMessage('<?php echo $message_id; ?>' , 'sending_<?php echo $message_id; ?>')" title="Submit Message"><img src="<?php echo url::file_loc('img'); ?>media/img/admin/btn-send.gif" alt="Submit" border="0" /></a></div>
+														<div class="reply_input" id="sending_<?php echo $message_id; ?>"></div>
+														<div style="clear:both"></div>
+														<?php print form::close(); ?>
+														<div id="replyleft_<?php echo $message_id; ?>" class="replychars"></div>
+													</div>
+													<?php
+												}
+												?>
+
+											<?php } ?>
+											</div>
+											<ul class="info">
+												<?php
+												if ($message_type == 2)
+												{
+													?><li class="none-separator"><?php echo Kohana::lang('ui_admin.to');?>: <strong><?php echo $message_to; ?></strong><?php
+												}
+												else
+												{
+													?><li class="none-separator"><?php echo Kohana::lang('ui_admin.from');?>: <a href="<?php echo url::site()."admin/messages/reporters/index/".$service_id."?k=".urlencode($message_from);?>"><strong class="reporters_<?php echo $level_id?>"><?php echo $message_from; ?></strong></a><?php
+												}
+
+												if ($latitude != NULL AND $longitude != NULL)
+												{
+													?><li class="none-separator"> @ <?php echo $latitude; ?>,<?php echo $longitude; ?></li><?php
+												}
+												?>
+											</ul>
+										</td>
+										<td class="col-3"><?php echo $message_date; ?></td>
+										<td class="col-4">
+											<ul>
+												<?php
+												if ($incident_id != 0 && $message_type != 2) {
+													echo "<li class=\"none-separator\"><a href=\"". url::site() . 'admin/reports/edit/' . $incident_id ."\" class=\"status_yes\"><strong>".Kohana::lang('ui_admin.view_report')."</strong></a></li>";
+												}
+												elseif ($message_type != 2)
+												{
+													echo "<li class=\"none-separator\"><a href=\"". url::site() . 'admin/reports/edit?mid=' . $message_id ."\">".Kohana::lang('ui_admin.create_report')."?</a></li>";
+												}
+												?>
+												<li><a href="javascript:messagesAction('d','DELETE','<?php echo(rawurlencode($message_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+											</ul>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+				<div class="tabs">
+					<div class="tab">
+						<ul>
+							<li><a href="#" onClick="messagesAction('d', 'DELETE', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete'));?></a></li>
+							<li><a href="#" onClick="messagesAction('s', 'SPAM', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.spam'));?></a></li>
+							<li><a href="#" onClick="messagesAction('n', 'NOT SPAM', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.not_spam'));?></a></li>
+						</ul>
+					</div>
+				</div>
+			</div>
+
+<?php
+	}
+?>
diff --git a/application/views/admin/messages/messages_js.php b/application/views/admin/messages/messages_js.php
new file mode 100644
index 0000000..61b2d37
--- /dev/null
+++ b/application/views/admin/messages/messages_js.php
@@ -0,0 +1,111 @@
+/**
+ * Messages js file.
+ *
+ * Handles javascript stuff related to messages function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+		
+		function limitChars(textid, limit, infodiv)
+		{
+			var text = $('#'+textid).val();	
+			var textlength = text.length;
+			if(textlength > limit)
+			{
+				$('#' + infodiv).html('You cannot write more then '+limit+' characters!');
+				$('#'+textid).val(text.substr(0,limit));
+				return false;
+			}
+			else
+			{
+				$('#' + infodiv).html('You have '+ (limit - textlength) +' characters left.');
+				return true;
+			}
+		}
+		
+		function showReply(id)
+		{
+			if (id) {
+				$('#' + id).toggle(400);
+			}
+		}
+		
+		function sendMessage(id, loader)
+		{
+			$('#' + loader).html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+			$.post("<?php echo url::site() . 'admin/messages/send/' ?>", { to_id: id, message: $("#message_" + id).attr("value") },
+				function(data){
+					if (data.status == 'sent'){
+						$('#reply_' + id).hide();
+					} else {
+						$('#replyerror_' + id).show();
+						$('#replyerror_' + id).html(data.message);
+						alert('ERROR!');
+					}
+					$('#' + loader).html('');
+			  	}, "json");
+		}
+		
+		function cannedReply(id, field)
+		{
+			var autoreply;
+			$("#" + field).attr("value", "");
+			if (id == 1) {
+				autoreply = "Thank you for sending a message to Ushahidi. What is the closest town or city for your last message?";
+			}else if (id == 2) {
+				autoreply = "Thank you for sending a message to Ushahidi. Can you send more information on the incident?"
+			};
+			$("#" + field).attr("value", autoreply);		
+		}
+
+		function messagesAction ( action, confirmAction, message_id )
+		{
+			var statusMessage;
+			if( !isChecked( "message" ) && message_id=='' )
+			{ 
+				alert('Please select at least one message.');
+			} else {
+				var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+				if (answer){
+
+					// Set Submit Type
+					$("#action").attr("value", action);
+
+					if (message_id != '') 
+					{
+						// Submit Form For Single Item
+						$("#message_single").attr("value", message_id);
+						$("#messageMain").submit();
+					}
+					else
+					{
+						// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+						$("#message_single").attr("value", "000");
+
+						// Submit Form For Multiple Items
+						$("#messageMain").submit();
+					}
+
+				} else {
+				//	return false;
+				}
+			}
+		}
+		
+		// Preview Message
+		function preview ( id ){
+			if (id) {
+				$('#' + id).toggle(400);
+			}
+		}
+		
diff --git a/application/views/admin/messages/messages_twitter.php b/application/views/admin/messages/messages_twitter.php
new file mode 100644
index 0000000..28cd955
--- /dev/null
+++ b/application/views/admin/messages/messages_twitter.php
@@ -0,0 +1,137 @@
+<?php 
+/**
+ * Twitter view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2><?php echo $title; ?> <a href="<?php print url::site() ?>admin/messages">SMS</a> <a href="<?php print url::site() ?>admin/messages/twitter"><?php echo Kohana::lang('ui_main.twitter');?></a> <a href="<?php print url::site() ?>admin/messages/laconica"><?php echo Kohana::lang('ui_main.laconica');?></a> </h2>
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="?type=1" <?php if ($type == '1') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.inbox');?></a></li>
+						<li><a href="?type=2" <?php if ($type == '2') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.outbox');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><!-- <a href="#" onClick="submitIds()">DELETE</a> --> <a href="#"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_disabled'));?></a></li>
+						</ul>
+					</div>
+				</div>
+				<?php 
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3><?php echo Kohana::lang('ui_main.messages');?> <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide"><?php echo Kohana::lang('ui_main.hide_this_message');?></a></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<?php  
+					print form::open(NULL, array('id' => 'messagesMain', 'name' => 'messagesMain')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1"><input id="checkallincidents" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'message_id[]' )" /></th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.message_details');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+									<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										<?php echo $pagination; ?>
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr>
+										<td colspan="4" class="col">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+								<?php	
+								}
+								
+								foreach ($tweets as $tweet)
+								{
+									$tweet_id = $tweet->id;
+									$tweet_from = $tweet->tweet_from;
+									$tweet_hashtag = $tweet->tweet_hashtag;
+									$incident_id = $tweet->incident_id;
+									$tweet_link = $tweet->tweet_link;
+									$tweet_description = $tweet->tweet;
+									$tweet_date = date('Y-m-d', strtotime($tweet->tweet_date));
+									?>
+									<tr>
+										<td class="col-1"><input name="message_id[]" id="message_id" value="<?php echo $tweet_id; ?>" type="checkbox" class="check-box"/></td>
+										<td class="col-2">
+											<div class="post">
+												<p><?php echo $tweet_description; ?></p>
+											</div>
+											<ul class="info">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.from');?>: <strong><a href="<?php echo $tweet_link; ?>" target="_blank"><?php echo $tweet_from; ?></a></strong>
+												<?php
+												if($tweet_hashtag == ''){ //if this was a direct report
+													echo "<li class=\"none-separator\"><strong>". utf8::strtoupper(Kohana::lang('ui_main.direct_report'))."</strong>";
+												}else{ //if this was found using a hashtag search
+													echo "<li class=\"none-separator\">". Kohana::lang('ui_main.hashtag').": <strong>#".$tweet_hashtag."</strong>";
+												}
+												?>
+											</ul>
+										</td>
+										<td class="col-3"><?php echo $tweet_date; ?></td>
+										<td class="col-4">
+											<ul>
+												<?php
+												if ($incident_id != 0) {
+													echo "<li class=\"none-separator\"><a href=\"". url::site() . 'admin/reports/edit/' . $incident_id ."\" class=\"status_yes\"><strong>".Kohana::lang('ui_main.view_report')."</strong></a></li>";
+												}
+												else
+												{
+													echo "<li class=\"none-separator\"><a href=\"". url::site() . 'admin/reports/edit?tid=' . $tweet_id ."\">".Kohana::lang('ui_main.create_report')."?</a></li>";
+												}
+												?>
+												<li>
+                                                <!-- <a href="<?php echo url::site().'admin/messages/delete/'.$tweet_id ?>" onclick="return confirm("<?php echo Kohana::lang('ui_main.action_confirm');?>")" class="del"><?php echo Kohana::lang('ui_main.delete');?></a> --></li>
+											</ul>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/profile.php b/application/views/admin/profile.php
new file mode 100644
index 0000000..8784e9f
--- /dev/null
+++ b/application/views/admin/profile.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * Site view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2><?php echo Kohana::lang('ui_admin.my_profile');?></h2>
+				<?php print form::open(); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.profile_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>
+					<div class="head">
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->
+					<div class="sms_holder">
+
+						<?php Event::run('ui_admin.profile_shown'); ?>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_password"); ?>"><?php echo Kohana::lang('ui_main.current_password'); ?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+							<?php print form::password('current_password', '', ' class="text"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_name"); ?>"><?php echo Kohana::lang('ui_main.full_name');?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+							<?php print form::input('name', $form['name'], ' class="text long2"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_email"); ?>"><?php echo Kohana::lang('ui_main.email');?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+							<?php print form::input('email', $form['email'], ' class="text long2"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_new_password"); ?>"><?php echo Kohana::lang('ui_main.new_password');?></a></h4>
+							<?php print form::password('new_password', $form['new_password'], ' class="text"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><?php echo Kohana::lang('ui_main.password_again');?></h4>
+							<?php print form::password('password_again', $form['password_again'], ' class="text"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_notify"); ?>"><?php echo Kohana::lang('ui_main.receive_notifications');?>?</a></h4>
+							<?php print form::dropdown('notify', $yesno_array, $form['notify']); ?>
+						</div>
+
+					</div>
+
+					<div class="simple_border"></div>
+
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/reporters/main.php b/application/views/admin/reporters/main.php
new file mode 100644
index 0000000..e60b72b
--- /dev/null
+++ b/application/views/admin/reporters/main.php
@@ -0,0 +1,247 @@
+		<div class="bg">
+				<h2>
+					<?php admin::messages_subtabs($service_id); ?>
+				</h2>
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="<?php echo url::site()."admin/messages/index/".$service_id; ?>?type=1"><?php echo Kohana::lang('ui_main.inbox');?></a></li>
+						<?php
+						if ($service_id == 1)
+						{
+							?><li><a href="<?php echo url::site()."admin/messages/index/".$service_id; ?>?type=2"><?php echo Kohana::lang('ui_main.outbox');?></a></li><?php
+						}
+						?>
+						<li><a href="<?php echo url::site()."admin/messages/reporters/?s=".$service_id; ?>" class="active">Reporters (<?php echo $total_items; ?>)</a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><a href="#" onClick="reportersAction('d', 'DELETE', '', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete'));?></a></li>
+							<?php foreach($levels as $level) { ?>
+								<li><a href="#" onClick="reportersAction('l', 'Mark As <?php echo $level->level_title?>', '', <?php echo $level->id?>)" class="reporters_tab_<?php echo $level->id;?>"><?php echo $level->level_title?></a></li>
+							<?php } ?>
+						</ul>
+					</div>
+				</div>				
+				
+				
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tab -->
+					<div class="tab">
+						<?php print form::open(NULL,array('method' => 'get',
+						 	'id' => 'searchReporters')); ?>
+						<div class="tab_form_item">
+							<?php print form::input('k', $keyword, ' class="text long"'); ?>
+						</div>
+						<div class="tab_form_item">
+							<?php print form::dropdown('s', $search_type_array, $search_type); ?>
+						</div>				
+						<div class="tab_form_item">
+							<a href="#" onclick="submitSearch()"><strong><?php echo Kohana::lang('ui_admin.search');?></strong></a>
+						</div>
+						<?php print form::close(); ?>			
+					</div>
+				</div>
+				
+				<div class="tabs" id="addedit" style="display:none">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#add" class="active"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab reporters">
+						<?php print form::open(NULL,array('id' => 'reporterEdit',
+						 	'name' => 'reporterEdit')); ?>
+						<input type="hidden" name="action" 
+							id="action" value="a"/>
+						<input type="hidden" id="reporter_id" 
+							name="reporter_id[]" value="<?php echo $form['reporter_id']; ?>" />
+						<input type="hidden" id="service_account" name="service_account" value="">
+						<input type="hidden" id="service_name" name="service_name" value="">
+						<input type="hidden" id="location_id" name="location_id" value="">
+						<div style="clear:both;"></div>							
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.reporter');?>:</strong><br />
+							<h3 id="reporter_account"><?php echo $form['service_account']; ?></h3>
+						</div>
+						<div style="clear:both;"></div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.service');?>:</strong><br />
+							<h3 id="reporter_service"><?php echo $form['service_name']; ?></h3>
+						</div>
+						<div style="clear:both;"></div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.reporter_level');?>:</strong><br />
+							<?php print form::dropdown('level_id', $level_array, $form['level_id']); ?>
+						</div>
+						<div style="clear:both;"></div>
+						<div id="reporter_location">
+							<h3>Give this Reporter A Location <span>(Giving the reporter a location will allow their reports to be mapped immediately if they are trusted)</span></h3>
+							<div class="tab_form_item">
+								<strong><?php echo Kohana::lang('ui_main.location');?>:</strong><br />
+								<?php print form::input('location_name', $form['location_name'], ' class="text"'); ?>
+							</div>
+							<div class="tab_form_item">
+								<strong><?php echo Kohana::lang('ui_main.latitude');?>:</strong><br />
+								<?php print form::input('latitude', $form['latitude'], ' class="text"'); ?>
+							</div>
+							<div class="tab_form_item">
+								<strong><?php echo Kohana::lang('ui_main.longitude');?>:</strong><br />
+								<?php print form::input('longitude', $form['longitude'], ' class="text"'); ?>
+							</div>
+							<div style="clear:both;"></div>
+							<div class="tab_form_item">
+								<strong><?php echo Kohana::lang('ui_main.location');?>:</strong><br />
+								<div id="ReporterMap"></div>
+							</div>
+							<div style="clear:both;"></div>
+						</div>
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>
+					</div>
+				</div>				
+							
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.reporter_has_been');?> <?php echo $form_action; ?></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'reporterMain',
+					 	'name' => 'reporterMain')); ?>
+						<input type="hidden" name="action" id="reporter_action" value="">
+						<input type="hidden" name="reporter_id[]" id="reporter_single" value="">
+						<input type="hidden" name="level_id" id="level_id_main" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1"><input id="checkall" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'reporter_id[]' )" /></th>
+										<th class="col-2"><?php echo Kohana::lang('ui_main.reporter');?></th>
+										<th class="col-3"><?php echo Kohana::lang('ui_main.service');?></th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4">
+											<?php echo $pagination; ?>
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php	
+									} 
+									foreach ($reporters as $reporter)
+									{
+										$reporter_id = $reporter->id;   
+								        $service_id = $reporter->service_id;
+										$level_id = $reporter->level_id;
+								        $service = new Service_Model($service_id);
+								        $service_name = $service->service_name;
+									$service_account = $reporter->service_account;
+											if ($keyword)
+											{
+												$service_account = str_ireplace($keyword,
+													"<span class=\"highlight\">$keyword</span>", $service_account);
+											}
+											
+							    		// Get Location Information
+										$location_id = "";
+										$location_name = "";
+										$latitude = "";
+										$longitude = "";
+										$location = $reporter->location;
+										if ($location->loaded)
+										{
+											$location_id = $location->id;
+											$location_name = $location->location_name;
+											$latitude = $location->latitude;
+											$longitude = $location->longitude;
+										}
+										
+										// Get Message Information
+										$message_count = $reporter->message->count();
+										
+										// Get Reporter Level
+										$reporter_level = $level_array[$level_id];
+										?>
+										<tr>
+											<td class="col-1"><input name="reporter_id[]" id="reporter" value="<?php echo $reporter_id; ?>" type="checkbox" class="check-box"/></td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo $service_account; ?> <span>[<a href="<?php echo url::site()."admin/messages/index/".$service_id."?rid=".$reporter_id;?>">View Messages</a>]</span></h4>
+												</div>
+												<ul class="info">
+													<li class="none-separator"><?php echo Kohana::lang('ui_main.messages');?>: <strong><?php echo $message_count; ?></strong></li>
+													<li class="none-separator">Reporter Level: <strong class="reporters_<?php echo $level_id?>"><?php echo $reporter_level; ?></strong></li>
+												</ul>
+											</td>
+											<td class="col-3">
+												<div>
+													<?php echo $service_name; ?>
+												</div>
+											</td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="#add" onClick="fillFields(
+	'<?php echo(rawurlencode($reporter_id)); ?>',
+	'<?php echo(rawurlencode($level_id)); ?>',
+	'<?php echo(rawurlencode($service_name)); ?>',
+	'<?php echo(rawurlencode($reporter->service_account)); ?>',
+	'<?php echo(rawurlencode($location_id)); ?>',
+	'<?php echo(rawurlencode($location_name)); ?>',	
+	'<?php echo(rawurlencode($latitude)); ?>',
+	'<?php echo(rawurlencode($longitude)); ?>')"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+													<li><a href="javascript:reportersAction('d','DELETE','<?php echo(rawurlencode($reporter_id)); ?>', '')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+												</ul>
+											</td>
+										</tr>
+										<?php
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+				
+			</div>
diff --git a/application/views/admin/reporters/reporters_js.php b/application/views/admin/reporters/reporters_js.php
new file mode 100644
index 0000000..7f0c39c
--- /dev/null
+++ b/application/views/admin/reporters/reporters_js.php
@@ -0,0 +1,211 @@
+<?php
+/**
+ * Reporter js file.
+ *
+ * Handles javascript stuff related to reporter function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Reporters Javascript
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+// Reporter JS
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+$().ready(function() {
+	<?php
+	if ($form_error)
+	{
+		?>
+		$('#add_edit_form').show();
+		showMap();
+		<?php
+	}?>
+});
+
+function fillFields(id, level_id, service_name, service_account, location_id, location_name, latitude, longitude)
+{
+	show_addedit();
+	$('#add_edit_form').show();
+	$("#reporter_id").attr("value", decodeURIComponent(id));
+	$("#level_id").attr("value", decodeURIComponent(level_id));
+	$("#service_name").attr("value", decodeURIComponent(service_name));
+	$("#reporter_service").text(decodeURIComponent(service_name));
+	$("#service_account").attr("value", decodeURIComponent(service_account));
+	$("#reporter_account").text(decodeURIComponent(service_account));
+	$("#location_id").attr("value", decodeURIComponent(location_id));
+	$("#location_name").attr("value", decodeURIComponent(location_name));
+	$("#latitude").attr("value", decodeURIComponent(latitude));
+	$("#longitude").attr("value", decodeURIComponent(longitude));
+	showMap();
+}
+
+// Ajax Submission
+function reporterAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Reporter ID
+		$("#rptr_id_action").attr("value", id);
+		// Set Submit Type
+		$("#action").attr("value", action);		
+		// Submit Form
+		$("#rptrListing").submit();
+	}
+}
+
+function submitSearch()
+{
+	$("#searchReporters").submit();
+}
+
+function reportersAction ( action, confirmAction, reporter_id, level_id )
+{
+	var statusMessage;
+	if( !isChecked( "reporter" ) && reporter_id=='' )
+	{ 
+		alert('Please select at least one reporter.');
+	} else {
+		var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+		if (answer){
+			
+			// Set Submit Type
+			$("#reporter_action").attr("value", action);
+			
+			// Set Level ID
+			$("#level_id_main").attr("value", level_id);
+			
+			if (reporter_id != '') 
+			{
+				// Submit Form For Single Item
+				$("#reporter_single").attr("value", reporter_id);
+				$("#reporterMain").submit();
+			}
+			else
+			{
+				// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+				$("#reporter_single").attr("value", "000");
+				
+				// Submit Form For Multiple Items
+				$("#reporterMain").submit();
+			}
+		
+		} else {
+			return false;
+		}
+	}
+}
+
+var map;
+var thisLayer;
+var proj_4326 = new OpenLayers.Projection('EPSG:4326');
+var proj_900913 = new OpenLayers.Projection('EPSG:900913');
+var markers;
+
+function showMap()
+{
+	$("#ReporterMap").html('');
+	
+	if (markers) {
+		markers.destroy();
+		markers = null;
+	}
+	
+	// Now initialise the map
+	var options = {
+	units: "dd"
+	, numZoomLevels: 18
+	, controls:[],
+	projection: proj_900913,
+	'displayProjection': proj_4326,
+	maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
+	maxResolution: 156543.0339
+	};
+	
+	map = new OpenLayers.Map('ReporterMap', options);
+	
+	<?php echo map::layers_js(FALSE); ?>
+	map.addLayers(<?php echo map::layers_array(FALSE); ?>);
+	
+	map.addControl(new OpenLayers.Control.Navigation());
+	map.addControl(new OpenLayers.Control.Zoom());
+	map.addControl(new OpenLayers.Control.MousePosition({
+		formatOutput: Ushahidi.convertLongLat	
+	}));
+	map.addControl(new OpenLayers.Control.LayerSwitcher());
+	
+	// Create the markers layer
+	markers = new OpenLayers.Layer.Markers("Markers");
+	map.addLayer(markers);
+	
+	// create a lat/lon object
+	var latitude, longitude;
+	if ($("#latitude").val() != "" && $("#longitude").val() != "") {
+		latitude = $("#latitude").val();
+		longitude = $("#longitude").val();
+	} else {
+		latitude = "<?php echo $latitude; ?>";
+		longitude = "<?php echo $longitude; ?>";
+	}
+	var myPoint = new OpenLayers.LonLat(longitude, latitude);
+	myPoint.transform(proj_4326, map.getProjectionObject());
+	
+	// create a marker positioned at a lon/lat
+	var marker = new OpenLayers.Marker(myPoint);
+	markers.addMarker(marker);
+	
+	// display the map centered on a latitude and longitude (Google zoom levels)
+	map.setCenter(myPoint, <?php echo $default_zoom; ?>);
+	
+	// Detect Map Clicks
+	map.events.register("click", map, function(e){
+		var lonlat = map.getLonLatFromViewPortPx(e.xy);
+		var lonlat2 = map.getLonLatFromViewPortPx(e.xy);
+	    m = new OpenLayers.Marker(lonlat);
+		markers.clearMarkers();
+    	markers.addMarker(m);
+		
+		lonlat2.transform(proj_900913,proj_4326);	
+		// Update form values (jQuery)
+		$("#latitude").attr("value", lonlat2.lat);
+		$("#longitude").attr("value", lonlat2.lon);
+	});
+	
+	// Event on Latitude/Longitude Typing Change
+	$('#latitude, #longitude').bind("change keyup", function() {
+		var newlat = $("#latitude").val();
+		var newlon = $("#longitude").val();
+		if (!isNaN(newlat) && !isNaN(newlon))
+		{
+			var lonlat = new OpenLayers.LonLat(newlon, newlat);
+			lonlat.transform(proj_4326,proj_900913);
+			m = new OpenLayers.Marker(lonlat);
+			markers.clearMarkers();
+	    	markers.addMarker(m);
+			map.setCenter(lonlat, <?php echo $default_zoom; ?>);
+		}
+		else
+		{
+			alert('Invalid value!')
+		}
+	});
+
+	// GeoCode
+	$('.btn_find').on('click', function () {
+		geoCode();
+	});
+
+	$('#location_find').bind('keypress', function(e) {
+		var code = (e.keyCode ? e.keyCode : e.which);
+		if(code == 13) { //Enter keycode
+			geoCode();
+			return false;
+		}
+	});
+}
\ No newline at end of file
diff --git a/application/views/admin/reports/delete_all.php b/application/views/admin/reports/delete_all.php
new file mode 100644
index 0000000..c505879
--- /dev/null
+++ b/application/views/admin/reports/delete_all.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Reports download view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+<div class="bg">
+	<h2>
+		<?php admin::reports_subtabs("delete-all"); ?>
+	</h2>
+
+	<!-- report-form -->
+	<div class="report-form">
+		<!-- column -->
+		<div class="deleteall_container">
+			<h1><?php echo Kohana::lang('ui_admin.delete_all')?></h1>
+
+			<?php if($report_count > 0): ?>
+				<?php print form::open(NULL, array('id' => 'reportForm', 'name' => 'reportForm')); ?>
+
+				<p><?php echo Kohana::lang('ui_admin.delete_all_instructions')?></p>
+				<p><?php echo Kohana::lang('ui_admin.delete_all_backup')?></p>
+
+				<?php print form::hidden('confirm_delete_all','1'); ?>
+				<input type="submit" value="<?php echo Kohana::lang('ui_admin.delete_all_button', $report_count)?>" />
+				<?php print form::close(); ?>
+			<?php else: ?>
+				<p><?php echo Kohana::lang('ui_admin.delete_all_no_reports')?></p>
+			<?php endif; ?>
+		</div>
+	</div>
+</div>
diff --git a/application/views/admin/reports/delete_all_js.php b/application/views/admin/reports/delete_all_js.php
new file mode 100644
index 0000000..b86a243
--- /dev/null
+++ b/application/views/admin/reports/delete_all_js.php
@@ -0,0 +1,21 @@
+/**
+ * Delete all js file.
+ *
+ * Handles javascript stuff related to the delete all function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+		$(document).ready(function() {
+			$(document.getElementById("reportForm")).submit(function () {
+				return window.confirm("<?php echo addslashes(Kohana::lang('ui_admin.delete_all_confirm')); ?>");
+			});
+		});
\ No newline at end of file
diff --git a/application/views/admin/reports/download.php b/application/views/admin/reports/download.php
new file mode 100755
index 0000000..e10439f
--- /dev/null
+++ b/application/views/admin/reports/download.php
@@ -0,0 +1,111 @@
+<?php 
+/**
+ * Reports download view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2>
+		<?php admin::reports_subtabs("download"); ?>
+	</h2>
+	<!-- report-form -->
+	<div class="report-form">
+		<?php
+		if ($form_error) {
+		?>
+			<!-- red-box -->
+			<div class="red-box">
+				<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+				<ul>
+				<?php
+				foreach ($errors as $error_item => $error_description)
+				{
+					// print "<li>" . $error_description . "</li>";
+					print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+				}
+				?>
+				</ul>
+			</div>
+		<?php
+		}
+		?>
+		<!-- column -->
+		<div class="download_container">
+			<?php print form::open(NULL, array('id' => 'reportForm', 'name' => 'reportForm')); ?>
+			<p style="font-weight: bold; color:#00699b; display: block;padding-bottom: 5px;"><?php echo Kohana::lang('ui_admin.select_download_format'); ?></p>
+			<div id="form_error_format"></div>
+			<p>
+				<span><?php print form::radio('format','csv', TRUE); ?><?php echo Kohana::lang('ui_admin.csv')?></span>
+				<span><?php print form::radio('format','xml', FALSE); ?><?php echo Kohana::lang('ui_admin.xml') ?></span>
+			</p>
+			<span style="font-weight: bold; color: #00699b; display: block; padding-bottom: 5px;"><?php echo Kohana::lang('ui_main.choose_data_points');?>:</span>
+			<table class="data_points">
+				<tr>
+					<td colspan="2">
+						<input type="checkbox" id="data_all" name="data_all" onclick="CheckAll(this.id)"  checked="checked" /><strong><?php echo utf8::strtoupper(Kohana::lang('ui_main.select_all'));?></strong>
+						<div id="form_error1"></div>
+					</td>
+				</tr>
+				<tr>
+					<td><?php print form::checkbox('data_active[]', '1', in_array(1, $form['data_active'])); ?><?php echo Kohana::lang('ui_main.approved_reports');?></td>
+					<td><?php print form::checkbox('data_verified[]', '1', in_array(1, $form['data_verified'])); ?><?php echo Kohana::lang('ui_main.verified_reports');?></td>
+				</tr>
+				<tr>
+					<td><?php print form::checkbox('data_active[]', '0', in_array(0, $form['data_active'])); ?><?php echo Kohana::lang('ui_main.reports');?> <?php echo Kohana::lang('ui_main.awaiting_approval');?></td>
+					<td><?php print form::checkbox('data_verified[]', '0', in_array(0, $form['data_verified'])); ?><?php echo Kohana::lang('ui_main.reports');?> <?php echo Kohana::lang('ui_main.awaiting_verification');?></td>
+				</tr>
+				<tr>
+					<td colspan="2">
+						<div class="date-box">
+							<h4><?php echo Kohana::lang('ui_admin.from_date');?>: <span><?php echo Kohana::lang('ui_main.date_format');?></span></h4>
+							<?php print form::input('from_date', $form['from_date'], ' class="text"'); ?>											    
+						</div>
+						<div class="date-box">
+							<h4><?php echo Kohana::lang('ui_admin.to_date');?>: <span><?php echo Kohana::lang('ui_main.date_format');?></span></h4>
+							<?php print form::input('to_date', $form['to_date'], ' class="text"'); ?>											    
+						</div>
+						<div id="form_error2"></div>
+					</td>
+				</tr>
+			</table>
+			<span  style="font-weight: bold; color:#00699b; display: block;padding-bottom: 5px;"><?php echo Kohana::lang('ui_main.additional_data');?>:</span>
+			<table class="data_points">
+				<tr>
+					<td colspan="2">
+						<input type="checkbox" id="data_include_all" name="data_include_all" onclick="CheckAll(this.id)" checked="checked"/><strong><?php echo utf8::strtoupper(Kohana::lang('ui_main.select_all'));?></strong>
+						<div id="form_error1"></div>
+					</td>
+				</tr>
+
+				<tr>
+					<td><?php print form::checkbox('data_include[]', '2', in_array(2, $form['data_include'])); ?><?php echo Kohana::lang('ui_main.include_description');?></td>
+					<td><?php print form::checkbox('data_include[]', '1', in_array(1, $form['data_include'])); ?><?php echo Kohana::lang('ui_main.include_location_information');?></td>
+				</tr>
+				<tr>
+				<td><?php print form::checkbox('data_include[]', '3', in_array(3, $form['data_include'])); ?><?php echo Kohana::lang('ui_main.include_categories');?></td>
+					<td><?php print form::checkbox('data_include[]','4',in_array(4, $form['data_include'])); ?><?php echo Kohana::lang('ui_main.include_latitude');?></td>
+				</tr>
+				
+				<tr>
+					<td><?php print form::checkbox('data_include[]','6',in_array(6, $form['data_include'])); ?><?php echo Kohana::lang('ui_main.include_custom_fields');?></td>
+					<td><?php print form::checkbox('data_include[]','5',in_array(5, $form['data_include'])); ?><?php echo Kohana::lang('ui_main.include_longitude');?></td>
+				</tr>
+				<tr>
+					<td><?php print form::checkbox('data_include[]','7',in_array(7, $form['data_include'])); ?><?php echo Kohana::lang('ui_main.include_personal_info');?></td>
+					<td></td>
+				</tr>
+			</table>
+			<input id="save_only" type="submit" value="<?php echo utf8::strtoupper(Kohana::lang('ui_main.download'));?>" class="save-rep-btn" />
+			<?php print form::close(); ?>
+		</div>
+	</div>
+</div>
diff --git a/application/views/admin/reports/download_js.php b/application/views/admin/reports/download_js.php
new file mode 100644
index 0000000..f2321f7
--- /dev/null
+++ b/application/views/admin/reports/download_js.php
@@ -0,0 +1,109 @@
+/**
+ * Download reports js file.
+ *
+ * Handles javascript stuff related to download reports function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+		$(document).ready(function() {
+			$("#from_date").datepicker({ 
+				showOn: "both", 
+				buttonImage: "<?php echo $calendar_img; ?>", 
+				buttonImageOnly: true 
+			});
+			
+			$("#to_date").datepicker({ 
+				showOn: "both", 
+				buttonImage: "<?php echo $calendar_img; ?>", 
+				buttonImageOnly: true 
+			});
+			
+			
+			$("#reportForm").validate({
+				rules: {
+					format: {
+						required: true
+					},
+					"data_active[]": {
+						required: true,
+						range: [0,1]
+					},
+					"data_verified[]": {
+						required: true,
+						range: [0,1]
+					},
+					"data_include[]": {
+						range: [1,5]
+					},
+					from_date: {
+						date: true
+					},
+					to_date: {
+						date: true
+					}
+				},
+				messages: {
+					format: {
+						required: "<?php echo addslashes(Kohana::lang('report.format.required')); ?>"
+					},
+					"data_verified[]": {
+						required: "<?php echo addslashes(Kohana::lang('report.data_verified.required'));?>",
+						range: "<?php echo addslashes(Kohana::lang('report.data_verified.between'));?>"
+					},
+					"data_active[]": {
+						required: "<?php echo addslashes(Kohana::lang('report.data_active.required'));?>",
+						range: "<?php echo addslashes(Kohana::lang('report.data_active.between'));?>"
+					},
+					"data_include[]": {
+						range: "<?php echo addslashes(Kohana::lang('report.data_include.between'));?>"
+					},
+					from_date: {
+						date: "<?php echo addslashes(Kohana::lang('report.from_date.date_mmddyyyy'));?>"
+					},
+					to_date: {
+						date: "<?php echo addslashes(Kohana::lang('report.to_date.date_mmddyyyy'));?>"
+					}
+				},
+				errorPlacement: function(error, element) {
+					if (element.attr("name") == "data_point" || element.attr("name") == "data_include")
+					{
+						error.appendTo("#form_error1");
+					}
+					else if (element.attr("name") == "from_date" || element.attr("name") == "to_date")
+					{
+						error.appendTo("#form_error2");
+					}
+					else if (element.attr("name") == "format" )
+					{
+						error.appendTo("#form_error_format");
+					}
+					else
+					{
+						error.insertAfter(element);
+					}
+				}
+			});			
+		});
+		
+		// Check All / Check None
+		function CheckAll( id )
+		{
+			if (id == 'data_all')
+			{
+				$("td > input:checkbox[name='data_verified[]']").attr('checked', $('#' + id).is(':checked'));
+				$("td > input:checkbox[name='data_active[]']").attr('checked', $('#' + id).is(':checked'));
+			}
+			else if (id == 'data_include_all')
+			{
+				$("td > input:checkbox[name='data_include[]']").attr('checked', $('#' + id).is(':checked'));
+			}
+		}
diff --git a/application/views/admin/reports/edit.php b/application/views/admin/reports/edit.php
new file mode 100755
index 0000000..144a4fb
--- /dev/null
+++ b/application/views/admin/reports/edit.php
@@ -0,0 +1,520 @@
+<?php 
+/**
+ * Reports edit view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::reports_subtabs("edit"); ?>
+				</h2>
+				<?php print form::open(NULL, array('enctype' => 'multipart/form-data', 'id' => 'reportForm', 'name' => 'reportForm')); ?>
+					<input type="hidden" name="save" id="save" value="">
+					<input type="hidden" name="location_id" id="location_id" value="<?php print $form['location_id']; ?>">
+					<input type="hidden" name="incident_zoom" id="incident_zoom" value="<?php print $form['incident_zoom']; ?>">
+					<input type="hidden" name="country_name" id="country_name" value="<?php echo $form['country_name'];?>" />
+					<!-- report-form -->
+					<div class="report-form">
+						<?php
+						if ($form_error) {
+						?>
+							<!-- red-box -->
+							<div class="red-box">
+								<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+								<ul>
+								<?php
+								foreach ($errors as $error_item => $error_description)
+								{
+									print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+								}
+								?>
+								</ul>
+							</div>
+						<?php
+						}
+
+						if ($form_saved) {
+						?>
+							<!-- green-box -->
+							<div class="green-box">
+								<h3><?php echo Kohana::lang('ui_main.report_saved');?></h3>
+							</div>
+						<?php
+						}
+						?>
+						<div class="head">
+							<h3><?php echo $id ? Kohana::lang('ui_main.edit_report') : Kohana::lang('ui_main.new_report'); ?></h3>
+							<div class="btns" style="float:right;">
+								<ul>
+									<li><a href="#" class="btn_save"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_report'));?></a></li>
+									<li><a href="#" class="btn_save_close"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_close'));?></a></li>
+									<li><a href="#" class="btn_save_add_new"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_add_new'));?></a></li>
+									<li><a href="<?php echo url::site('admin/reports');?>" class="btns_red"><?php echo utf8::strtoupper(Kohana::lang('ui_main.cancel'));?></a>   </li>
+									<?php if ($id) {?>
+									<li><a href="<?php echo $previous_url;?>" class="btns_gray">« <?php echo utf8::strtoupper(Kohana::lang('ui_main.previous'));?></a></li>
+									<li><a href="<?php echo $next_url;?>" class="btns_gray"><?php echo utf8::strtoupper(Kohana::lang('ui_main.next'));?> »</a></li>
+									<?php } ?>
+								</ul>
+							</div>
+						</div>
+						<!-- f-col -->
+						<div class="f-col">
+							<?php
+							// Action::report_pre_form_admin - Runs right before report form is rendered
+							Event::run('ushahidi_action.report_pre_form_admin', $id);
+							?>
+							<?php if ($show_messages) { ?>
+							<div class="row">
+								<h4 style="margin:0;padding:0;"><a href="#" id="messages_toggle" class="show-messages"><?php echo Kohana::lang('ui_main.show_messages');?></a> </h4>
+								<!--messages table goes here-->
+			                    <div id="show_messages">
+									<?php
+									foreach ($all_messages as $message) {
+										echo "<div class=\"message\">";
+										echo "<strong><u>" . $message->message_from . "</u></strong> - ";
+										echo $message->message;
+										echo "</div>";
+									}
+									?>
+								</div>
+							</div>
+							<?php } ?>
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.form');?> <span>(<?php echo Kohana::lang('ui_main.select_form_type');?>)</span></h4>
+								<span class="sel-holder">
+									<?php print form::dropdown('form_id', $forms, $form['form_id'],
+										' onchange="formSwitch(this.options[this.selectedIndex].value, \''.$id.'\')"') ?>
+								</span>
+								<div id="form_loader" style="float:left;"></div>
+							</div>
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.title');?> <span class="required">*</span></h4>
+								<?php print form::input('incident_title', $form['incident_title'], ' class="text title"'); ?>
+							</div>
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.description');?> <span><?php echo Kohana::lang('ui_main.include_detail');?>.</span> <span class="required">*</span></h4>
+								<span class="allowed-html"><?php echo html::allowed_html(); ?></span>
+								<?php print form::textarea('incident_description', $form['incident_description'], ' rows="12" cols="40"') ?>
+							</div>
+
+							<?php
+							// Action::report_form_admin - Runs just after the report description
+							Event::run('ushahidi_action.report_form_admin', $id);
+							?>
+
+							<?php
+							if (!($id))
+							{ // Use default date for new report
+								?>
+								<div class="row" id="datetime_default">
+									<h4><a href="#" id="date_toggle" class="new-cat"><?php echo Kohana::lang('ui_main.modify_date');?></a><?php echo Kohana::lang('ui_main.modify_date');?>: 
+									<?php echo Kohana::lang('ui_main.today_at').' '.$form['incident_hour']
+										.":".$form['incident_minute']." ".$form['incident_ampm']; ?></h4>
+								</div>
+								<?php
+							}
+							?>
+							<div class="row <?php
+								if (!($id))
+								{ // Hide date editor for new report
+									echo "hide";
+								}?> " id="datetime_edit">
+								<div class="date-box">
+									<h4><?php echo Kohana::lang('ui_main.date');?> <span><?php echo Kohana::lang('ui_main.date_format');?></span></h4>
+									<?php print form::input('incident_date', $form['incident_date'], ' class="text"'); ?>								
+									<?php print $date_picker_js; ?>				    
+								</div>
+								<div class="time">
+									<h4><?php echo Kohana::lang('ui_main.time');?> <span>(<?php echo Kohana::lang('ui_main.approximate');?>)</span></h4>
+									<?php
+									print '<span class="sel-holder">' .
+								    form::dropdown('incident_hour', $hour_array,
+									$form['incident_hour']) . '</span>';
+									
+									print '<span class="dots">:</span>';
+									
+									print '<span class="sel-holder">' .
+									form::dropdown('incident_minute',
+									$minute_array, $form['incident_minute']) .
+									'</span>';
+									print '<span class="dots">:</span>';
+									
+									print '<span class="sel-holder">' .
+									form::dropdown('incident_ampm', $ampm_array,
+									$form['incident_ampm']) . '</span>';
+									?>
+								</div>
+							</div>
+							<div class="row">
+							<?php Event::run('ushahidi_action.report_form_admin_after_time', $id); ?>
+							</div>
+							<div class="row">
+								<h4><a href="#" id="category_toggle" class="new-cat"><?php echo Kohana::lang('ui_main.new_category');?></a><?php echo Kohana::lang('ui_main.categories');?> 
+								<span><?php echo Kohana::lang('ui_main.select_multiple');?>.</span>  <span class="required">*</span></h4>
+								<?php print $new_category_toggle_js; ?>
+								<!--category_add form goes here-->
+			                    <div id="category_add" class="category_add">
+			                        <?php
+			                        print '<p>'.Kohana::lang('ui_main.add_new_category').'<hr/></p>';
+                                    print form::label(array("id"=>"category_name_label", "for"=>"category_name"), Kohana::lang('ui_main.name'));
+                                    print '<br/>';
+                                    print form::input('category_name', $new_categories_form['category_name'], 'class=""');
+                                    print '<br/>';
+									// HT: Parent category on report edit
+									print form::label(array("id"=>"parent_id_label", "for"=>"parent_id"), Kohana::lang('ui_main.parent_category'));
+									print '<br/>';
+									print form::dropdown('category_parent_id', $new_categories_form['category_parent_array'], $new_categories_form['parent_id'], 'class=""');
+									print '<br/>';
+									// HT: End of Parent category on report edit
+                                    print form::label(array("id"=>"description_label", "for"=>"description"), Kohana::lang('ui_main.description'));
+                                    print '<br/>';
+                                    print form::input('category_description', $new_categories_form['category_description'], 'class=""');
+                                    print '<br/>';
+                                    print form::label(array("id"=>"color_label", "for"=>"color"), Kohana::lang('ui_main.color'));
+                                    print '<br/>';
+                                    print form::input('category_color', $new_categories_form['category_color'], 'class=""');
+                                    print $color_picker_js;
+                                    print '<br/>';
+                                    print '<span>';
+                                    print '<a href="#" id="add_new_category">'.Kohana::lang('ui_main.add').'</a>';
+                                    print '</span>';
+                                    ?> 
+                                </div>
+
+			                    <div class="report_category">
+                        	    <?php
+									$selected_categories = array();
+									if (!empty($form['incident_category']) && is_array($form['incident_category'])) {
+										$selected_categories = $form['incident_category'];
+									}
+									$columns = 2;
+									echo category::form_tree('incident_category', $selected_categories, $columns, FALSE, TRUE);
+								?>
+           						</div>
+							</div>
+
+						<?php echo $custom_forms; ?>
+
+						</div>
+						<!-- f-col-1 -->
+						<div class="f-col-1">
+							<div class="incident-location">
+								<h4><?php echo Kohana::lang('ui_main.incident_location');?></h4>
+								<div class="location-info">
+									<span><?php echo Kohana::lang('ui_main.latitude');?>:</span>
+									<?php print form::input('latitude', $form['latitude'], ' class="text"'); ?>
+									<span><?php echo Kohana::lang('ui_main.longitude');?>:</span>
+									<?php print form::input('longitude', $form['longitude'], ' class="text"'); ?>
+								</div>
+								<ul class="map-toggles">
+									<li><a href="#" class="smaller-map"><?php echo Kohana::lang('ui_main.smaller_map'); ?></a></li>
+									<li style="display:block;"><a href="#" class="wider-map"><?php echo Kohana::lang('ui_main.wider_map'); ?></a></li>
+									<li><a href="#" class="taller-map"><?php echo Kohana::lang('ui_main.taller_map'); ?></a></li>
+									<li><a href="#" class="shorter-map"><?php echo Kohana::lang('ui_main.shorter_map'); ?></a></li>
+								</ul>
+								<div id="divMap" class="map_holder_reports">
+									<div id="geometryLabelerHolder" class="olControlNoSelect">
+										<div id="geometryLabeler">
+											<div id="geometryLabelComment">
+												<span id="geometryLabel"><label><?php echo Kohana::lang('ui_main.geometry_label');?>:</label> <?php print form::input('geometry_label', '', ' class="lbl_text"'); ?></span>
+												<span id="geometryComment"><label><?php echo Kohana::lang('ui_main.geometry_comments');?>:</label> <?php print form::input('geometry_comment', '', ' class="lbl_text2"'); ?></span>
+											</div>
+											<div>
+												<span id="geometryColor"><label><?php echo Kohana::lang('ui_main.geometry_color');?>:</label> <?php print form::input('geometry_color', '', ' class="lbl_text"'); ?></span>
+												<span id="geometryStrokewidth"><label><?php echo Kohana::lang('ui_main.geometry_strokewidth');?>:</label> <?php print form::dropdown('geometry_strokewidth', $stroke_width_array, ''); ?></span>
+												<span id="geometryLat"><label><?php echo Kohana::lang('ui_main.latitude');?>:</label> <?php print form::input('geometry_lat', '', ' class="lbl_text"'); ?></span>
+												<span id="geometryLon"><label><?php echo Kohana::lang('ui_main.longitude');?>:</label> <?php print form::input('geometry_lon', '', ' class="lbl_text"'); ?></span>
+											</div>
+										</div>
+										<div id="geometryLabelerClose"></div>
+									</div>
+								</div>
+							</div>
+							<div class="incident-find-location">
+								<div id="panel" class="olControlEditingToolbar"></div>
+								<div class="btns" style="float:left;">
+									<ul style="padding:4px;">
+										<li><a href="#" class="btn_del_last"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_last'));?></a></li>
+										<li><a href="#" class="btn_del_sel"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_selected'));?></a></li>
+										<li><a href="#" class="btn_clear"><?php echo utf8::strtoupper(Kohana::lang('ui_main.clear_map'));?></a></li>
+									</ul>
+								</div>
+								<div style="clear:both;"></div>
+								<?php print form::input('location_find', '', ' title="'.Kohana::lang('ui_main.location_example').'" class="findtext"'); ?>
+								<div class="btns" style="float:left;">
+									<ul>
+										<li><a href="#" class="btn_find"><?php echo utf8::strtoupper(Kohana::lang('ui_main.find_location'));?></a></li>
+									</ul>
+								</div>
+								<div id="find_loading" class="incident-find-loading"></div>
+								<div style="clear:both;"><?php echo Kohana::lang('ui_main.pinpoint_location');?>.</div>
+							</div>
+							<?php Event::run('ushahidi_action.report_form_admin_location', $id); ?>
+							<div class="row">
+								<div class="town">
+									<h4><?php echo Kohana::lang('ui_main.reports_location_name');?>  <span class="required">*</span><br /><span><?php echo Kohana::lang('ui_main.detailed_location_example');?></span></h4>
+									<?php print form::input('location_name', $form['location_name'], ' class="text long"'); ?>
+								</div>
+							</div>
+				
+				
+							<!-- News Fields -->
+							<div class="row link-row">
+								<h4><?php echo Kohana::lang('ui_main.reports_news');?></h4>
+							</div>
+							<div id="divNews">
+								<?php
+								$this_div = "divNews";
+								$this_field = "incident_news";
+								$this_startid = "news_id";
+								$this_field_type = "text";
+					
+								if (empty($form[$this_field]))
+								{
+									$i = 1;
+									print "<div class=\"row link-row\">";
+									print form::input($this_field . '[]', '', ' class="text long"');
+									print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+									print "</div>";
+								}
+								else
+								{
+									$i = 0;
+									foreach ($form[$this_field] as $value) {									
+										print "<div ";
+										if ($i != 0) {
+											print "class=\"row link-row second\" id=\"" . $this_field . "_" . $i . "\">\n";
+										}
+										else
+										{
+											print "class=\"row link-row\" id=\"$i\">\n";
+										}
+										print form::input($this_field . '[]', $value, ' class="text long"');
+										print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+										if ($i != 0)
+										{
+											print "<a href=\"#\" class=\"rem\"  onClick='removeFormField(\"#" . $this_field . "_" . $i . "\"); return false;'>remove</a>";
+										}
+										print "</div>\n";
+										$i++;
+									}
+								}
+								print "<input type=\"hidden\" name=\"$this_startid\" value=\"$i\" id=\"$this_startid\">";
+								?>
+							</div>
+
+
+							<!-- Video Fields -->
+							<div class="row link-row">
+								<h4><?php echo Kohana::lang('ui_main.external_video_link');?></h4>
+							</div>
+							<div id="divVideo">
+								<?php
+								$this_div = "divVideo";
+								$this_field = "incident_video";
+								$this_startid = "video_id";
+								$this_field_type = "text";
+					
+								if (empty($form[$this_field]))
+								{
+									$i = 1;
+									print "<div class=\"row link-row\">";
+									print form::input($this_field . '[]', '', ' class="text long"');
+									print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+									print "</div>";
+								}
+								else
+								{
+									$i = 0;
+									foreach ($form[$this_field] as $value) {									
+										print "<div ";
+										if ($i != 0) {
+											print "class=\"row link-row second\" id=\"" . $this_field . "_" . $i . "\">\n";
+										}
+										else
+										{
+											print "class=\"row link-row\" id=\"$i\">\n";
+										}
+										print form::input($this_field . '[]', $value, ' class="text long"');
+										print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+										if ($i != 0)
+										{
+											print "<a href=\"#\" class=\"rem\"  onClick='removeFormField(\"#" . $this_field . "_" . $i . "\"); return false;'>remove</a>";
+										}
+										print "</div>\n";
+										$i++;
+									}
+								}
+								print "<input type=\"hidden\" name=\"$this_startid\" value=\"$i\" id=\"$this_startid\">";
+								?>
+							</div>
+							
+							<?php Event::run('ushahidi_action.report_form_admin_after_video_link', $id); ?>
+
+							<!-- Photo Fields -->
+							<div class="row link-row">
+								<h4><?php echo Kohana::lang('ui_main.reports_photos');?></h4>
+								<?php								
+    								if ($incident_media)
+                        			{
+                        				// Retrieve Media
+                        				foreach($incident_media as $photo) 
+                        				{
+                        					if ($photo->media_type == 1)
+                        					{
+                        						$thumb = url::convert_uploaded_to_abs($photo->media_thumb);
+                        						$large_photo = url::convert_uploaded_to_abs($photo->media_link);
+                        						?>
+                        						<div class="report_thumbs" id="photo_<?php echo $photo->id; ?>">
+	                        						<a class="photothumb" rel="lightbox-group1" href="<?php echo $large_photo; ?>">
+	                        						<img src="<?php echo $thumb; ?>" />
+	                        						</a>
+													  
+													<a href="#" onClick="deletePhoto('<?php echo $photo->id; ?>', 'photo_<?php echo $photo->id; ?>'); return false;" ><?php echo Kohana::lang('ui_main.delete'); ?></a>
+                        						</div>
+                        						<?php
+                        					}
+                        				}
+                        			}
+			                    ?>
+							</div>
+							<div id="divPhoto">
+								<?php
+								$this_div = "divPhoto";
+								$this_field = "incident_photo";
+								$this_startid = "photo_id";
+								$this_field_type = "file";
+					
+								if (empty($form[$this_field]['name'][0]))
+								{
+									$i = 1;
+									print "<div class=\"row link-row\">";
+									print form::upload($this_field . '[]', '', ' class="text long"');
+									print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+									print "</div>";
+								}
+								else
+								{
+									$i = 0;
+									foreach ($form[$this_field]['name'] as $value) 
+									{
+										print "<div ";
+										if ($i != 0) {
+											print "class=\"row link-row second\" id=\"" . $this_field . "_" . $i . "\">\n";
+										}
+										else
+										{
+											print "class=\"row link-row\" id=\"$i\">\n";
+										}
+										// print "\"<strong>" . $value . "</strong>\"" . "<BR />";
+										print form::upload($this_field . '[]', $value, ' class="text long"');
+										print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+										if ($i != 0)
+										{
+											print "<a href=\"#\" class=\"rem\"  onClick='removeFormField(\"#".$this_field."_".$i."\"); return false;'>remove</a>";
+										}
+										print "</div>\n";
+										$i++;
+									}
+								}
+								print "<input type=\"hidden\" name=\"$this_startid\" value=\"$i\" id=\"$this_startid\">";
+								?>
+							</div>
+						</div>
+						<!-- f-col-bottom -->
+						<div class="f-col-bottom-container">
+							<div class="f-col-bottom">
+								<div class="row">
+									<h4><?php echo Kohana::lang('ui_main.personal_information');?></span></h4>
+									<label>
+										<span><?php echo Kohana::lang('ui_main.first_name');?></span>
+										<?php print form::input('person_first', $form['person_first'], ' class="text"'); ?>
+									</label>
+									<label>
+										<span><?php echo Kohana::lang('ui_main.last_name');?></span>
+										<?php print form::input('person_last', $form['person_last'], ' class="text"'); ?>
+									</label>
+								</div>
+								<div class="row">
+									<label>
+										<span><?php echo Kohana::lang('ui_main.email_address');?></span>
+										<?php print form::input('person_email', $form['person_email'], ' class="text"'); ?>
+									</label>
+								</div>
+							</div>
+							<!-- f-col-bottom-1 -->
+							<div class="f-col-bottom-1">
+								<h4><?php echo Kohana::lang('ui_main.information_evaluation');?></h4>
+								<div class="row">
+									<div class="f-col-bottom-1-col"><?php echo Kohana::lang('ui_main.approve_this_report');?>?</div>
+									<?php if (Auth::instance()->has_permission('reports_approve')): ?>
+									<input type="radio" name="incident_active" value="1"
+									<?php if ($form['incident_active'] == 1)
+									{
+										echo " checked=\"checked\" ";
+									}?>> <?php echo Kohana::lang('ui_main.yes');?>
+									<input type="radio" name="incident_active" value="0"
+									<?php if ($form['incident_active'] == 0)
+									{
+										echo " checked=\"checked\" ";
+									}?>> <?php echo Kohana::lang('ui_main.no');?>
+									<?php else: ?>
+										<?php echo $form['incident_active'] ? Kohana::lang('ui_main.yes') : Kohana::lang('ui_main.no');?>
+									<?php endif; ?>
+								</div>
+								<div class="row">
+									<div class="f-col-bottom-1-col"><?php echo Kohana::lang('ui_main.verify_this_report');?>?</div>
+									<?php if (Auth::instance()->has_permission('reports_verify')): ?>
+									<input type="radio" name="incident_verified" value="1"
+									<?php if ($form['incident_verified'] == 1)
+									{
+										echo " checked=\"checked\" ";
+									}?>> <?php echo Kohana::lang('ui_main.yes');?>
+									<input type="radio" name="incident_verified" value="0"
+									<?php if ($form['incident_verified'] == 0)
+									{
+										echo " checked=\"checked\" ";
+									}?>> <?php echo Kohana::lang('ui_main.no');?>									
+									<?php else: ?>
+										<?php echo $form['incident_verified'] ? Kohana::lang('ui_main.yes') : Kohana::lang('ui_main.no');?>
+									<?php endif; ?>
+								</div>
+							</div>
+							<div style="clear:both;"></div>
+						</div>
+						<div class="btns">
+							<ul>
+								<li><a href="#" class="btn_save"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_report'));?></a></li>
+								<li><a href="#" class="btn_save_close"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_close'));?></a></li>
+									<li><a href="#" class="btn_save_add_new"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_add_new'));?></a></li>
+								<?php 
+								if($id)
+								{
+									echo "<li><a href=\"#\" class=\"btn_delete btns_red\">".utf8::strtoupper(Kohana::lang('ui_main.delete_report'))."</a></li>";
+								}
+								?>
+								<li><a href="<?php echo url::site().'admin/reports/';?>" class="btns_red"><?php echo utf8::strtoupper(Kohana::lang('ui_main.cancel'));?></a></li>
+							</ul>
+						</div>						
+					</div>
+				<?php print form::close(); ?>
+				<?php
+				if($id)
+				{
+					// Hidden Form to Perform the Delete function
+					print form::open(url::site().'admin/reports/', array('id' => 'reportMain', 'name' => 'reportMain'));
+					$array=array('action'=>'d','incident_id[]'=>$id);
+					print form::hidden($array);
+					print form::close();
+				}
+				?>
+			</div>
diff --git a/application/views/admin/reports/main.php b/application/views/admin/reports/main.php
new file mode 100644
index 0000000..0b577e9
--- /dev/null
+++ b/application/views/admin/reports/main.php
@@ -0,0 +1,357 @@
+<?php
+/**
+ * Reports view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::reports_subtabs("view"); ?>
+				</h2>
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li>
+							<a href="?status=0" <?php if ($status != 'a' AND $status !='v' AND $status != 'o' AND  $status != 'search') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.show_all');?></a>
+						</li>
+						<li><a href="?status=a" <?php if ($status == 'a') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.awaiting_approval');?></a></li>
+						<li><a href="?status=v" <?php if ($status == 'v') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.awaiting_verification');?></a></li>
+						<li>
+							<a href="?status=o" <?php if ($status == 'o') echo "class=\"active\""; ?>>
+								<?php echo Kohana::lang('ui_main.uncategorized_reports'); ?>
+							</a>
+						</li>
+						<li class="right">
+							<a href="?status=search" class="search <?php if ($status == 'search') echo "active"; ?>">
+								<?php echo Kohana::lang('ui_main.search'); ?>
+							</a>
+						</li>
+					</ul>
+					<!-- tab -->
+					<div class="tab action-tab active">
+						<ul>
+							<?php if (Auth::instance()->has_permission('reports_approve')): ?>
+							<li><a href="#" onclick="reportAction('a','<?php echo utf8::strtoupper(Kohana::lang('ui_main.approve')); ?>', '');">
+								<?php echo Kohana::lang('ui_main.approve');?></a>
+							</li>
+							<li><a href="#" onclick="reportAction('u','<?php echo utf8::strtoupper(Kohana::lang('ui_main.disapprove')); ?>', '');">
+								<?php echo Kohana::lang('ui_main.disapprove');?></a>
+							</li>
+							<?php endif; ?>
+							<?php if (Auth::instance()->has_permission('reports_verify')): ?>
+							<li><a href="#" onclick="reportAction('v','<?php echo utf8::strtoupper(Kohana::lang('ui_admin.verify_unverify')); ?>', '');">
+								<?php echo Kohana::lang('ui_admin.verify_unverify');?></a>
+							</li>
+							<?php endif; ?>
+							<?php if (Auth::instance()->has_permission('reports_edit')): ?>
+							<li><a href="#" onclick="reportAction('d','<?php echo utf8::strtoupper(Kohana::lang('ui_main.delete')); ?>', '');">
+								<?php echo Kohana::lang('ui_main.delete');?></a>
+							</li>
+							<?php endif; ?>
+						</ul>
+						
+						<div class="sort_by">
+							<?php print form::open(NULL, array('method' => 'get', 'class' => 'sort-form')); ?>
+							<?php echo Kohana::lang('ui_main.sort_by'); ?>
+							<?php echo form::dropdown('order', array(
+								'date' => Kohana::lang('ui_admin.report_date'),
+								'id' => Kohana::lang('ui_main.id'),
+								'datemodify' => Kohana::lang('ui_admin.date_modified'),
+								'dateadd' => Kohana::lang('ui_admin.date_added'),
+								'title' => Kohana::lang('ui_admin.report_title'),
+							), $order_field); 
+							echo form::input(array(
+									'type'  => 'hidden',
+									'name'  => 'sort',
+									'value' => $sort,
+									'class' => 'sort-field'
+								));
+							echo form::hidden('status', $status);
+							echo form::close(); ?>
+						</div>
+					</div>
+					
+					<div class="content-tab search-tab">
+						<?php echo $search_form; ?>
+					</div>
+				</div>
+				<?php if ($form_error): ?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+							<?php
+								foreach ($errors as $error_item => $error_description)
+								{
+									print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+								}
+							?>
+						</ul>
+					</div>
+				<?php endif; ?>
+				
+				<?php if ($form_saved): ?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3><?php echo Kohana::lang('ui_main.reports');?> 
+							<?php echo $form_action; ?> 
+							<a href="#" id="hideMessage" class="hide"><?php echo Kohana::lang('ui_main.hide_this_message'); ?></a>
+						</h3>
+					</div>
+				<?php endif; ?>
+				
+				<!-- report-table -->
+				<?php print form::open(NULL, array('id' => 'reportMain', 'name' => 'reportMain')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<input type="hidden" name="incident_id[]" id="incident_single" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1">
+										<input id="checkallincidents" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'incident_id[]' )" />
+									</th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.report_details');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+									<th class="col-4">
+										<a class="sort sort-<?php echo $sort; ?>" title="<?php echo ($sort == 'ASC') ? Kohana::lang('ui_main.ascending') : Kohana::lang('ui_main.descending'); ?>" href="#"></a>
+										<?php echo Kohana::lang('ui_main.actions');?>
+									</th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										<?php echo $pagination; ?>
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+							<?php if ($total_items == 0): ?>
+								<tr>
+									<td colspan="4" class="col">
+										<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+									</td>
+								</tr>
+							<?php endif; ?>
+							<?php
+								foreach ($incidents as $incident)
+								{
+									$incident_id = $incident->incident_id;
+									$incident_title = html::escape($incident->incident_title);
+									$incident_description = text::limit_chars(html::strip_tags($incident->incident_description), 150, "...", true);
+									$incident_date = $incident->incident_date;
+									$incident_date = date('Y-m-d', strtotime($incident->incident_date));
+									
+									// Mode of submission... WEB/SMS/EMAIL?
+									$incident_mode = $incident->incident_mode;
+									
+									// Get the incident ORM
+									$incident_orm = ORM::factory('incident', $incident_id);
+									
+									// Get the person submitting the report
+									$incident_person = $incident_orm->incident_person;
+									$submit_by = NULL;
+									
+									//XXX incident_Mode will be discontinued in favour of $service_id
+									if ($incident_mode == 1)	// Submitted via WEB
+									{
+										$submit_mode = "WEB";
+										// Who submitted the report?
+										if ($incident_person->loaded)
+										{
+											// Report was submitted by a visitor
+											$submit_by = $incident_person->person_first . " " . $incident_person->person_last;
+										}
+										
+										// If $submit_by is empty, check for user id
+										if (trim($submit_by) == "" AND $incident_orm->user_id)	// Report Was Submitted By authenticated user
+										{
+											$submit_by = $incident_orm->user->name;
+										}
+									}
+									elseif ($incident_mode == 2) 	// Submitted via SMS
+									{
+										$submit_mode = "SMS";
+										$submit_by = $incident_orm->message->message_from;
+									}
+									elseif ($incident_mode == 3) 	// Submitted via Email
+									{
+										$submit_mode = "EMAIL";
+										$submit_by = $incident_orm->message->message_from;
+									}
+									elseif ($incident_mode == 4) 	// Submitted via Twitter
+									{
+										$submit_mode = "TWITTER";
+										$submit_by = $incident_orm->message->message_from;
+									}
+									
+									// If $submit_by is still empty, set it to Unknown
+									if (trim($submit_by) == "")
+									{
+										$submit_by = Kohana::lang('ui_admin.unknown');
+									}
+									
+									// Incident location
+									$incident_location = $incident->location_id ? $incident->location_name : Kohana::lang('ui_main.none');
+									
+									// Retrieve Incident Categories
+									$incident_category = "";
+									if ($incident_orm->incident_category->count() > 0)
+									{
+										foreach ($incident_orm->incident_category as $category)
+										{
+											$incident_category .= Category_Lang_Model::category_title($category->category_id) ."  ";
+										}
+									}
+									else
+									{
+										$incident_category .= Kohana::lang('ui_main.none');
+									}
+									
+									// Incident Status
+									$incident_approved = $incident->incident_active;
+									$incident_verified = $incident->incident_verified;
+									
+									// Get Edit Log
+									$edit_count = $incident_orm->verify->count();
+									$edit_css = ($edit_count == 0) ? "post-edit-log-gray" : "post-edit-log-blue";
+									
+									$edit_log  = "<div class=\"".$edit_css."\">"
+										. "<a href=\"javascript:showLog('edit_log_".$incident_id."')\">".Kohana::lang('ui_admin.edit_log').":</a> (".$edit_count.")</div>"
+										. "<div id=\"edit_log_".$incident_id."\" class=\"post-edit-log\"><ul>";
+									
+									foreach ($incident_orm->verify as $verify)
+									{
+										$edit_log .= "<li>".Kohana::lang('ui_admin.edited_by')." ".html::specialchars($verify->user->name)." : ".$verify->verified_date."</li>";
+									}
+									$edit_log .= "</ul></div>";
+
+									// Get Any Translations
+									$i = 1;
+									$incident_translation  = "<div class=\"post-trans-new\">"
+											. "<a href=\"" . url::site('admin/reports/translate/?iid='.$incident_id) ."\">"
+											. utf8::strtoupper(Kohana::lang('ui_main.add_translation')).":</a></div>";
+											
+									foreach ($incident_orm->incident_lang as $translation)
+									{
+										$incident_translation .= "<div class=\"post-trans\">"
+											. Kohana::lang('ui_main.translation'). $i . ": "
+											. "<a href=\"" . url::site('admin/reports/translate/'. $translation->id .'/?iid=' . $incident_id). "\">"
+											. text::limit_chars($translation->incident_title, 150, "...", TRUE)
+											. "</a>"
+											. "</div>";
+									}
+									?>
+									<tr>
+										<td class="col-1">
+											<input name="incident_id[]" id="incident" value="<?php echo $incident_id; ?>" type="checkbox" class="check-box"/>
+										</td>
+										<td class="col-2">
+											<div class="post">
+												<div class="incident-id"><a href="<?php echo url::site() . 'admin/reports/edit/' . $incident_id; ?>" class="more">#<?php echo $incident_id; ?></a></div>
+												<h4>
+													<a href="<?php echo url::site() . 'admin/reports/edit/' . $incident_id; ?>" class="more">
+														<?php echo $incident_title; ?>
+													</a>
+												</h4>
+												<p><?php echo $incident_description; ?>... 
+													<a href="<?php echo url::site('admin/reports/edit/' . $incident_id); ?>" class="more">
+														<?php echo Kohana::lang('ui_main.more');?>
+													</a>
+												</p>
+											</div>
+											<ul class="info">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.location');?>: 
+													<strong><?php echo html::specialchars($incident_location); ?></strong>
+												</li>
+												<li><?php echo Kohana::lang('ui_main.submitted_by', array($submit_by, $submit_mode));?>
+												</li>
+											</ul>
+											<ul class="links">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.categories');?>:
+													<strong><?php echo $incident_category;?></strong>
+												</li>
+											</ul>
+											<?php
+											echo $edit_log;
+											
+											// Action::report_extra_admin - Add items to the report list in admin
+											Event::run('ushahidi_action.report_extra_admin', $incident);
+											?>
+										</td>
+										<td class="col-3"><?php echo $incident_date; ?></td>
+										<td class="col-4">
+											<ul>
+												<?php if (Auth::instance()->has_permission('reports_approve')): ?>
+												<li class="none-separator">
+													<?php if ($incident_approved): ?>
+													<a href="#" class="status_yes" onclick="reportAction('u','UNAPPROVE', '<?php echo $incident_id; ?>');">
+														<?php echo Kohana::lang('ui_main.approve');?>
+													</a>
+													<?php  else: ?>
+													<a href="#" onclick="reportAction('a','APPROVE', '<?php echo $incident_id; ?>');">
+														<?php echo Kohana::lang('ui_main.approve');?>
+													</a>
+													<?php endif; ?>	
+												</li>
+												<?php endif; ?>
+												<?php if (Auth::instance()->has_permission('reports_verify')): ?>
+												<li>
+													<?php if ($incident_verified): ?>
+													<a href="#" class="status_yes" 
+													    onclick="reportAction('v','VERIFY', '<?php echo $incident_id; ?>');"><?php echo Kohana::lang('ui_main.verify');?>
+													</a>
+													<?php else: ?>
+													<a href="#" onclick="reportAction('v','VERIFY', '<?php echo $incident_id; ?>');">
+														<?php echo Kohana::lang('ui_main.verify');?>
+													</a>
+													<?php endif; ?>
+												</li>
+												<?php endif; ?>
+												<?php if (Auth::instance()->has_permission('reports_edit')): ?>
+												<li>
+													<a href="#" class="del" onclick="reportAction('d','DELETE', '<?php echo $incident_id; ?>');">
+														<?php echo Kohana::lang('ui_main.delete');?>
+													</a>
+												</li>
+												<?php endif; ?>
+											</ul>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+				<div class="tabs">
+					<div class="tab">
+						<ul>
+						<li><a href="#" onclick="reportAction('a','<?php echo utf8::strtoupper(Kohana::lang('ui_main.approve')); ?>', '');">
+							<?php echo Kohana::lang('ui_main.approve');?></a>
+						</li>
+						<li><a href="#" onclick="reportAction('u','<?php echo utf8::strtoupper(Kohana::lang('ui_main.disapprove')); ?>', '');">
+							<?php echo Kohana::lang('ui_main.disapprove');?></a>
+						</li>
+						<li><a href="#" onclick="reportAction('v','<?php echo utf8::strtoupper(Kohana::lang('ui_admin.verify_unverify')); ?>', '');">
+							<?php echo Kohana::lang('ui_admin.verify_unverify');?></a>
+						</li>
+						<li><a href="#" onclick="reportAction('d','<?php echo utf8::strtoupper(Kohana::lang('ui_main.delete')); ?>', '');">
+							<?php echo Kohana::lang('ui_main.delete');?></a>
+						</li>
+						</ul>
+					</div>
+				</div>
+			</div>
diff --git a/application/views/admin/reports/reports_extra.php b/application/views/admin/reports/reports_extra.php
new file mode 100644
index 0000000..bca5473
--- /dev/null
+++ b/application/views/admin/reports/reports_extra.php
@@ -0,0 +1,164 @@
+<?php 
+/**
+ * Reports extra view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2><?php echo $title; ?><span>(<?php echo $total_items; ?>)</span><a href="reports/edit"><?php echo Kohana::lang('ui_main.create_report');?></a><a href="reports/extra"><?php echo Kohana::lang('ui_main.additional_reports');?></a></h2>
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="?status=0" <?php if ($status != 'a' && $status !='v') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.show_all');?></a></li>
+						<li><a href="?status=a" <?php if ($status == 'a') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.awaiting_approval');?></a></li>
+						<li><a href="?status=v" <?php if ($status == 'v') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.awaiting_verification');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><a href="#" onclick="reportAction('a','APPROVE');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.appove'));?></a></li>
+							<li><a href="#" onclick="reportAction('u','UNAPPROVE');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.disapprove'));?></a></li>
+							<li><a href="#" onclick="reportAction('v','VERIFY');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.verify'));?></a></li>
+							<li><a href="#" onclick="reportAction('d','DELETE');"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete'));?></a></li>
+						</ul>
+					</div>
+				</div>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3>Reports <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide"><?php echo Kohana::lang('ui_main.hide_this_message');?></a></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<?php print form::open(NULL, array('id' => 'reportMain', 'name' => 'reportMain')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1"><input id="checkallincidents" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'incident_id[]' )" /></th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.report_details');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+									<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										<?php echo $pagination; ?>
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr>
+										<td colspan="4" class="col">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+								<?php	
+								}
+								foreach ($incidents as $incident)
+								{
+									$incident_id = $incident->id;
+									$incident_title = $incident->incident_title;
+									$incident_description = substr($incident->incident_description, 0, 150);
+									$incident_date = $incident->incident_date;
+									$incident_date = date('Y-m-d', strtotime($incident->incident_date));
+									$incident_mode = $incident->incident_mode;	// Mode of submission... WEB/SMS/EMAIL?
+									
+									if ($incident_mode == 1)
+									{
+										$submit_mode = "WEB";
+										// Who submitted the report?
+										if ($incident->incident_person->id)
+										{
+											// Report was submitted by a visitor
+											$submit_by = $incident->incident_person->person_first . " " . $incident->incident_person->person_last;
+										}
+										else
+										{
+											if ($incident->user_id)					// Report Was Submitted By Administrator
+											{
+												$submit_by = $incident->user->name;
+											}
+											else
+											{
+												$submit_by = Kohana::lang('ui_admin.unknown');
+											}
+										}
+									}
+									
+									$incident_location = $incident->location->location_name;
+									$incident_country = $incident->location->country->country;
+
+									// Retrieve Incident Categories
+									$incident_category = "";
+									foreach($incident->incident_category as $category) 
+									{ 
+										$incident_category .= "<a href=\"#\">" . $category->category->category_title . "</a>  ";
+									}
+									
+									// Incident Status
+									$incident_approved = $incident->incident_active;
+									$incident_verified = $incident->incident_verified;
+									?>
+									<tr>
+										<td class="col-1"><input name="incident_id[]" id="incident" value="<?php echo $incident_id; ?>" type="checkbox" class="check-box"/></td>
+										<td class="col-2">
+											<div class="post">
+												<h4><a href="<?php echo url::site() . 'admin/reports/edit/' . $incident_id; ?>" class="more"><?php echo $incident_title; ?></a></h4>
+												<p><?php echo $incident_description; ?>... <a href="<?php echo url::site() . 'admin/reports/edit/' . $incident_id; ?>" class="more"><?php echo Kohana::lang('ui_main.more');?></a></p>
+											</div>
+											<ul class="info">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.location');?>: <strong><?php echo $incident_location; ?></strong>, <strong><?php echo Kohana::lang('ui_main.example_country');?></strong></li>
+												<li><?php echo Kohana::lang('ui_main.submitted_by');?> <strong><?php echo $submit_by; ?></strong> via <strong><?php echo $submit_mode; ?></strong></li>
+											</ul>
+											<ul class="links">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.categories');?>:<?php echo $incident_category; ?></li>
+											</ul>
+										</td>
+										<td class="col-3"><?php echo $incident_date; ?></td>
+										<td class="col-4">
+											<ul>
+												<li class="none-separator"><a href="#"<?php if ($incident_approved) echo " class=\"status_yes\"" ?> onclick="reportAction('a','APPROVE');"><?php echo Kohana::lang('ui_main.approve');?></a></li>
+												<li><a href="#"<?php if ($incident_verified) echo " class=\"status_yes\"" ?> onclick="reportAction('v','VERIFY');"><?php echo Kohana::lang('ui_main.verify');?></a></li>
+												<li><a href="#" class="del" onclick="reportAction('d','DELETE');"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+											</ul>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+			</div>
\ No newline at end of file
diff --git a/application/views/admin/reports/reports_js.php b/application/views/admin/reports/reports_js.php
new file mode 100644
index 0000000..5812e94
--- /dev/null
+++ b/application/views/admin/reports/reports_js.php
@@ -0,0 +1,211 @@
+/**
+ * Main reports js file.
+ * 
+ * Handles javascript stuff related to reports function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+
+		// Ajax Submission
+		function reportAction ( action, confirmAction, incident_id )
+		{
+			var statusMessage;
+			if( !isChecked( "incident" ) && incident_id=='' )
+			{ 
+				alert('Please select at least one report.');
+			} else {
+				var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+				if (answer){
+					
+					// Set Submit Type
+					$("#action").attr("value", action);
+
+					var $incident_single = $(document.getElementById("incident_single"))
+					var $mainform = $(document.getElementById("reportMain"));
+					
+					if (incident_id != '') 
+					{
+						// Submit Form For Single Item
+						$incident_single.attr("value", incident_id);
+
+						//prevent checked reports from receiving actoin if action is triggered from a single report
+						$mainform.find("input:checkbox").attr("checked", false);
+					}
+					else
+					{
+						// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+						$incident_single.attr("value", "000");
+					}
+
+					$mainform.submit();
+				}
+			}
+
+			return false;						
+		}
+		
+		function showLog(id)
+		{
+			$('#' + id).toggle(400);
+		}
+
+$(function () {
+	// Handle sort/order fields
+	$("select#order").change(function() { $('.sort-form').submit(); });
+	$(".sort-ASC").click(function() {
+		$('.sort-field').val('DESC');
+		$('.sort-form').submit();
+		return false;
+	});
+	$(".sort-DESC").click(function() {
+		$('.sort-field').val('ASC');
+		$('.sort-form').submit();
+		return false;
+	});
+
+	// Handle search tab
+	$(".tabset .search").click(function() {
+		if ($('.search-tab').hasClass('active'))
+		{
+			$(".search-tab").removeClass('active').slideUp(300, function() { $(".action-tab").slideDown().addClass('active'); });
+			$(".tabset .search").removeClass('active');
+		}
+		else
+		{
+			$(".action-tab").removeClass('active').slideUp(300, function() { 
+				$(".search-tab").slideDown().addClass('active');
+			
+				// Check if the map has already been created
+				if (mapLoaded == false)
+				{
+					initMap();
+				}
+			});
+			$(".tabset .search").addClass('active');
+		}
+		
+		return false;
+	});
+	
+	// Category treeview
+	$(".category-column").treeview({
+	  persist: "location",
+	  collapsed: true,
+	  unique: false
+	});
+});
+	
+	
+// Map reference
+var map = null;
+var latitude = <?php echo isset($_GET['start_loc'][0]) ? floatval($_GET['start_loc'][0]) : Kohana::config('settings.default_lat') ?>;
+var longitude = <?php echo isset($_GET['start_loc'][1]) ? floatval($_GET['start_loc'][1]) : Kohana::config('settings.default_lon'); ?>;
+var zoom = 8;
+
+var mapLoaded = false;
+
+var initMap = function(){
+		// OpenLayers uses IE's VML for vector graphics
+		// We need to wait for IE's engine to finish loading all namespaces (document.namespaces) for VML.
+		// jQuery.ready is executing too soon for IE to complete it's loading process.
+		
+		<?php echo map::layers_js(FALSE); ?>
+		var mapConfig = {
+
+			// Map center
+			center: {
+				latitude: latitude,
+				longitude: longitude
+			},
+
+			// Zoom level
+			zoom: zoom,
+
+			// Base layers
+			baseLayers: <?php echo map::layers_array(FALSE); ?>
+		};
+
+		map = new Ushahidi.Map('divMap', mapConfig);
+		map.addRadiusLayer({
+			latitude: latitude,
+			longitude: longitude
+		});
+
+		// Subscribe to makerpositionchanged event
+		map.register("markerpositionchanged", function(coords){
+			$(".search_lat").val(coords.latitude);
+			$(".search_lon").val(coords.longitude);
+		});
+		
+		// Alerts Slider
+		$("select#alert_radius").change(
+			function(e, ui) {
+				var newRadius = $("#alert_radius").val();
+				
+				// Convert to Meters
+				radius = newRadius * 1000;	
+				
+				// Redraw Circle
+				map.updateRadius({radius: radius});
+			}
+		);
+		
+		mapLoaded = true;
+};
+
+$(function () {
+		$('.btn_find').on('click', function () {
+			geoCode();
+		});
+
+		$('#location_find').bind('keypress', function(e) {
+			var code = (e.keyCode ? e.keyCode : e.which);
+			if(code == 13) { //Enter keycode
+				geoCode();
+				return false;
+			}
+		});
+});
+
+
+/**
+ * Google GeoCoder
+ */
+function geoCode() {
+	$('#find_loading').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+	address = $("#location_find").val();
+	$.post("<?php echo url::site(); ?>reports/geocode/", { address: address },
+		function(data){
+			if (data.status == 'success') {
+
+				map.updateRadius({
+					longitude: data.longitude,
+					latitude: data.latitude
+				});
+			
+				// Update form values
+				$("#alert_lat").val(data.latitude);
+				$("#alert_lon").val(data.longitude);
+			} else {
+				// Alert message to be displayed
+				var alertMessage = address + " not found!\n\n***************************\n" + 
+				    "Enter more details like city, town, country\nor find a city or town " +
+				    "close by and zoom in\nto find your precise location";
+
+				alert(alertMessage)
+			}
+			$('#find_loading').html('');
+		}, "json");
+	return false;
+}
+
diff --git a/application/views/admin/reports/search_form.php b/application/views/admin/reports/search_form.php
new file mode 100644
index 0000000..fdb7fbc
--- /dev/null
+++ b/application/views/admin/reports/search_form.php
@@ -0,0 +1,82 @@
+<?php echo form::open(NULL, array('method' => 'get', 'class' => 'report-search-form')); ?>
+<h4><?php echo Kohana::lang('ui_main.filter_reports_by'); ?></h4>
+<?php
+	$search = array();
+?>
+<div class="row category-row">
+<?php
+	// Category
+	echo form::label('c', Kohana::lang('ui_main.category'));
+	echo category::form_tree('c', $categories, 1, TRUE, TRUE);
+?>
+</div>
+<div class="row location-row">
+<?php
+	// Location
+	echo form::label('location_filter', Kohana::lang('ui_main.location'));
+	echo form::checkbox('location_filter',1,$location_filter);
+	echo $alert_radius_view;
+?>
+</div>
+<div class="row">
+<?php
+	// date range
+	echo form::label('from', Kohana::lang('ui_main.from'), ' class="fixw"');
+	echo form::input('from',date('M d, Y', $date_from));
+	echo form::label('to', Kohana::lang('ui_main.to'), ' class="wrapped"');
+	echo form::input('to',date('M d, Y', $date_to));
+?>
+</div>
+<div class="row">
+<?php
+	// Type/mode
+	echo form::label('mode', Kohana::lang('ui_main.type'), ' class="fixw"');
+	echo "<label class='wrapped'>".form::checkbox('mode[]',1, in_array(1, $mode)) . Kohana::lang('ui_main.web') . "</label>";
+	echo "<label class='wrapped'>".form::checkbox('mode[]',2, in_array(2, $mode)) . Kohana::lang('ui_main.sms') . "</label>";
+	echo "<label class='wrapped'>".form::checkbox('mode[]',3, in_array(3, $mode)) . Kohana::lang('ui_main.email') . "</label>";
+	echo "<label class='wrapped'>".form::checkbox('mode[]',4, in_array(4, $mode)) . Kohana::lang('ui_main.twitter') . "</label>";
+?>
+</div>
+<div class="row">
+<?php
+	// Media
+	echo form::label('m', Kohana::lang('ui_main.media'), ' class="fixw"');
+	echo "<label class='wrapped'>".form::checkbox('m[]',1, in_array(1, $media)) . Kohana::lang('ui_main.photos') . "</label>";
+	echo "<label class='wrapped'>".form::checkbox('m[]',2, in_array(2, $media)) . Kohana::lang('ui_main.video') . "</label>";
+	echo "<label class='wrapped'>".form::checkbox('m[]',4, in_array(4, $media)) . Kohana::lang('ui_main.reports_news') . "</label>";
+?>
+</div>
+<div class="row">
+<?php
+	// Verification
+	echo form::label('a', Kohana::lang('ui_main.approved'), ' class="fixw"');
+	echo "<label class='wrapped'>".form::radio('a',1, $approved == 1) . Kohana::lang('ui_main.yes') . "</label>";
+	echo "<label class='wrapped'>".form::radio('a',0, $approved == 0) . Kohana::lang('ui_main.no') . "</label>";
+	echo "<label class='wrapped'>".form::radio('a','all', $approved == 'all') . Kohana::lang('ui_main.all') . "</label>";
+?>
+</div>
+<div class="row">
+<?php
+	// Approved
+	echo form::label('v', Kohana::lang('ui_main.verified'), ' class="fixw"');
+	echo "<label class='wrapped'>".form::radio('v',1, $verified == 1) . Kohana::lang('ui_main.yes') . "</label>";
+	echo "<label class='wrapped'>".form::radio('v',0, $verified == 0) . Kohana::lang('ui_main.no') . "</label>";
+	echo "<label class='wrapped'>".form::radio('v','all', $verified == 'all') . Kohana::lang('ui_main.all') . "</label>";
+?>
+</div>
+<div class="row">
+<?php
+	// Text
+	echo form::label('k', Kohana::lang('ui_main.keywords'), ' class="fixw"');
+	echo form::input('k',$keywords);
+?>
+</div>
+<div class="row">
+<?php
+	echo form::hidden('status', 'search');
+	echo form::input(array('type' => 'hidden', 'name' => 'start_loc[0]', 'value' => floatval($start_loc[0]), 'class' => 'search_lat'));
+	echo form::input(array('type' => 'hidden', 'name' => 'start_loc[1]', 'value' => floatval($start_loc[1]), 'class' => 'search_lon'));
+	echo form::submit('submit', Kohana::lang('ui_main.search'));
+?>
+</div>
+<?php echo form::close(); ?>
\ No newline at end of file
diff --git a/application/views/admin/reports/upload.php b/application/views/admin/reports/upload.php
new file mode 100644
index 0000000..d77b25b
--- /dev/null
+++ b/application/views/admin/reports/upload.php
@@ -0,0 +1,81 @@
+<?php 
+/**
+ * Reports upload view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<div class="bg">
+	<h2>
+		<?php admin::reports_subtabs("upload"); ?>
+	</h2>
+	<!-- report-form -->
+	<div class="report-form">
+		<?php
+		if ($form_error) {
+		?>
+			<!-- red-box -->
+			<div class="red-box">
+				<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+				<ul>
+                <?php
+				foreach ($errors as $error_item => $error_description)
+				{
+					print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+				}
+				?>
+				</ul>
+			</div>
+		<?php
+		}
+		?>
+		<!-- column -->
+		<div class="upload_container">
+		<p><?php echo Kohana::lang('ui_main.upload_reports_detail_1');?>.</p>
+		<h3><?php echo Kohana::lang('ui_main.please_note');?></h3>
+		<ul>
+			<li><?php echo Kohana::lang('ui_main.upload_reports_detail_2');?>.</li>
+			<li><?php echo Kohana::lang('ui_main.upload_reports_detail_3');?>.</li>
+			<li><?php echo Kohana::lang('ui_main.upload_reports_detail_4');?></li>
+			<li><?php echo Kohana::lang('ui_main.upload_reports_detail_4b');?></li>
+		</ul>
+		<p><strong><?php echo Kohana::lang('ui_main.upload_reports_detail_4c')?></strong></p>
+		<ul>
+			<li><?php echo Kohana::lang('ui_main.upload_reports_detail_4d')?></li>
+			<li><?php echo Kohana::lang('ui_main.upload_reports_custom_forms')?>
+				<ol>
+					<li><?php echo Kohana::lang('ui_main.upload_reports_detail_4e')?></li>
+					<li><?php echo Kohana::lang('ui_main.upload_reports_detail_4f')?></li>
+					<li><?php echo Kohana::lang('ui_main.upload_reports_detail_4g')?></li>
+				<ol>
+			</li>
+		</ul>
+		<h3><?php echo Kohana::lang('ui_main.upload_guide')?></h3>
+		<ul>
+			<li><a href="https://wiki.ushahidi.com/display/WIKI/XML+Upload+Guide"><?php echo Kohana::lang('ui_main.upload_docs_1')?></a></li>
+			<li><a href="https://wiki.ushahidi.com/display/WIKI/CSV+Upload+Guide"><?php echo Kohana::lang('ui_main.upload_docs_2')?></a></li>
+		</ul>
+			<p>
+				<?php echo Kohana::lang('ui_main.upload_reports_detail_5');?>:
+				<p>
+					<?php echo Kohana::lang('ui_main.upload_reports_detail_6');?><br />
+					<?php echo Kohana::lang('ui_main.upload_reports_detail_7');?><br />
+					
+				</p>
+			</p>
+			<?php print form::open(NULL, array('id' => 'uploadForm', 'name' => 'uploadForm', 'enctype' => 'multipart/form-data')); ?>
+            <p><b><?php echo Kohana::lang('ui_main.upload_file');?></b> <?php echo form::upload(array('name' => 'uploadfile'), 'path/to/local/file'); ?></p>
+			<button type="submit"><?php echo Kohana::lang('ui_main.upload');?></button>
+			<?php print form::close(); ?>
+		</div>
+	</div>
+</div>
diff --git a/application/views/admin/reports/upload_success.php b/application/views/admin/reports/upload_success.php
new file mode 100644
index 0000000..99d8a9c
--- /dev/null
+++ b/application/views/admin/reports/upload_success.php
@@ -0,0 +1,40 @@
+<?php 
+/**
+ * Reports upload success view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<div class="bg">
+	<h2>
+		<?php admin::reports_subtabs("upload"); ?>
+	</h2>
+	<!-- report-form -->
+	<div class="report-form">
+
+		<div class = "green-box">
+			<h3><?php echo Kohana::lang('ui_main.upload_successful');?></h3>
+		</div>
+		<div class="upload_container">
+   			<p><?php echo Kohana::lang('ui_main.successfuly_imported');?> <?php echo $imported; ?> of <?php echo $rowcount; ?> <?php echo Kohana::lang('ui_main.reports');?>.</p>
+
+
+		<?php if(count($notices)){  ?>  
+			<h3><?php echo Kohana::lang('ui_main.notices');?></h3>	
+			<ul>
+			<?php foreach($notices as $notice)  { ?>
+				<li><?php echo $notice ?></li>
+				<?php } }?>
+			</ul>
+		</div>
+	</div>
+</div>
\ No newline at end of file
diff --git a/application/views/admin/security_info.php b/application/views/admin/security_info.php
new file mode 100644
index 0000000..4a16a44
--- /dev/null
+++ b/application/views/admin/security_info.php
@@ -0,0 +1,50 @@
+<?php
+/**
+ * Security Info View File.
+ *
+ * Used to render the HTML for security misconfigurations
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Robbie Mackay <rm at robbiemackay.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<?php 
+if ( Kohana::config('config.enable_security_info') == TRUE)
+{ ?>
+	<?php
+	$warnings = array();
+	if (Kohana::config('encryption.default.key') == "USHAHIDI-INSECURE" || Kohana::config('encryption.default.key') == "K0H at NA+PHP_7hE-SW!FtFraM3w0R|<")
+	{ 
+		$warnings[] = "<li>". Kohana::lang('ui_admin.security_info_encryption_key'). "</li>";
+	}
+	if (Kohana::config('config.site_protocol') == "http")
+	{
+		$warnings[] = "<li>". Kohana::lang('ui_admin.security_info_https'). "</li>";
+	}
+	if (file_exists(DOCROOT.DIRECTORY_SEPARATOR.'installer'))
+	{
+		$warnings[] = "<li>" .Kohana::lang('ui_admin.installer_info'). "</li>";
+	}
+	
+	if (count($warnings) > 0) 
+	{ ?>
+	<div id="security-info" class="update-info">
+	<h4>Security Warning:</h4>
+	<ul>
+		<?php
+			echo implode("\n",$warnings);
+		?>
+	</ul>
+	<?php echo Kohana::lang('ui_admin.security_info_instructions'); ?> <a href="http://wiki.ushahidi.com/display/WIKI/Securing+your+Ushahidi+deployment">Securing your Ushahidi deployment</a>
+	</div>
+	<?php
+	}
+} ?>
\ No newline at end of file
diff --git a/application/views/admin/settings/api/api_js.php b/application/views/admin/settings/api/api_js.php
new file mode 100644
index 0000000..0981d8e
--- /dev/null
+++ b/application/views/admin/settings/api/api_js.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * API settings js file.
+ *
+ * Handles javascript stuff related  to api log function
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+
+    // Ajax submission
+    function apiSettingsAction(action, confirmAction)
+    {
+        // Display confirm dialog
+        var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?');
+
+        if (answer) {
+            $("#action").attr("value", action);
+
+            if (action == 's') // Save action therefore submit form
+            {
+                $("#apiSettingsMain").submit();
+            }
+            else // Cancel form submission
+            {
+                $("#apiSettingsMain").cancel();
+            }
+        }
+        else
+        {
+            return false;
+        }
+    }
\ No newline at end of file
diff --git a/application/views/admin/settings/api/banned.php b/application/views/admin/settings/api/banned.php
new file mode 100644
index 0000000..0459799
--- /dev/null
+++ b/application/views/admin/settings/api/banned.php
@@ -0,0 +1,116 @@
+<?php 
+/**
+ * API banned view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+            <div class="bg">
+                <h2>
+                    <?php admin::settings_subtabs("api"); ?>
+                </h2>
+
+            	<!-- tabs -->
+            	<div class="tabs">
+                    <!-- tabset -->
+                    <ul class="tabset">
+                        <li><a href="<?php echo url::site() ?>admin/settings/api" ><?php echo Kohana::lang('ui_admin.api_settings'); ?></a></li>
+                        <li><a href="<?php echo url::site() ?>admin/settings/api/log"><?php echo Kohana::lang('ui_admin.api_logs');?></a></li>
+                        <li>
+                            <a href="<?php echo url::site() ?>admin/settings/api/banned" <?php if ($this_page == 'apibanned') echo "class=\"active\""?>>
+                                <?php echo Kohana::lang('ui_admin.api_banned'); ?>
+                            </a>
+                        </li>
+                    </ul>
+                    <!-- /tabset -->
+
+            		<!-- tab -->
+            		<div class="tab">
+            			<ul>
+            				<li><a href="#" onclick="apiBannedAction('d','UNBAN', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_admin.api_unban')); ?></a></li>
+            				<li><a href="#" onclick="apiBannedAction('x','UNBAN ALL ', '000');"><?php echo utf8::strtoupper(Kohana::lang('ui_admin.api_unban_all')); ?></a></li>
+            			</ul>
+            		</div>
+            		<!-- /tab -->
+            	</div> 
+            	<!-- /tabs -->
+	
+            	<?php if ($form_error): ?>
+            		<!-- red-box -->
+            		<div class="red-box">
+            			<h3>Error!</h3>
+            			<ul>Please verify that you have checked an item</ul>
+            		</div>
+            	<?php endif; ?>
+
+                <?php if ($form_saved): ?>
+            		<!-- green-box -->
+            		<div class="green-box" id="submitStatus">
+            			<h3>API <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide">hide this message</a></h3>
+            		</div>
+            	<?php endif; ?>
+            	
+            	<!-- report-table -->
+            	<?php print form::open(NULL, array('id' => 'apiBannedMain', 'name' => 'apiBannedMain')); ?>
+            		<input type="hidden" name="action" id="action" value="">
+            		<input type="hidden" name="api_banned_id[]" id="api_banned_single" value="">
+            		<div class="table-holder">
+            			<table class="table">
+            				<thead>
+            					<tr>
+            						<th class="col-1">
+            						<input id="checkallapibanned" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'api_banned_id[]' )" /></th>
+            						<th class="col-3">IP Address</th>
+            						<th class="col-3">DateTime</th>
+            						<th class="col-4">Actions</th>
+            					</tr>
+            				</thead>
+            				<tfoot>
+            					<tr class="foot">
+            						<td colspan="4">
+            							<?php echo $pagination; ?>
+            						</td>
+            					</tr>
+            				</tfoot>
+            			<tbody>
+            			<?php if ($total_items == 0): ?>
+            			    <tr>
+            					<td colspan="4" class="col">
+            						<h3>No Results To Display!</h3>
+            					</td>
+            				</tr>
+            			<?php endif; ?>	
+            				
+            				<?php	
+            					foreach ($api_bans as $api_ban)
+            					{
+            						$api_ban_id = $api_ban->id;
+            						$banned_ipaddress = $api_ban->banned_ipaddress;
+            						$banned_date = $api_ban->banned_date;						
+            					?>
+            						<tr>
+            							<td class="col-1"><input name="api_banned_id[]" id="api_banned" value="<?php echo $api_ban_id; ?>" type="checkbox" class="check-box"/></td>
+            							<td class="col-3"><?php echo $banned_ipaddress;?></td>
+            							<td class="col-3"><?php echo $banned_date;?></td>
+            							<td class="col-4">
+            								<ul>	
+            								 <li><a href="#" class="del" onclick="apiBannedAction('d','UNBAN', '<?php echo $api_ban_id; ?>');">Unban</a></li>
+            								</ul>
+            							</td>
+            						</tr>
+            					<?php
+            					}
+            				?>
+            			</tbody>
+            		</table>
+            	</div>
+            	<?php print form::close(); ?>
+            </div>
\ No newline at end of file
diff --git a/application/views/admin/settings/api/banned_js.php b/application/views/admin/settings/api/banned_js.php
new file mode 100644
index 0000000..125437d
--- /dev/null
+++ b/application/views/admin/settings/api/banned_js.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * API banned logs js file.
+ *
+ * Handles javascript stuff related  to api banned function
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+
+	// Ajax Submission
+	function apiBannedAction ( action, confirmAction, api_banned_id )
+	{
+		var statusMessage;
+		if( !isChecked( "api_banned" ) && api_banned_id =='' )
+		{ 
+			alert('Please select at least one banned ip.');
+		} else {
+			var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+			if (answer){
+				// Set Submit Type
+				$("#action").attr("value", action);
+			
+				if (api_banned_id != '') 
+				{
+					// Submit Form For Single Item
+					$("#api_banned_single").attr("value", api_banned_id);
+					$("#apiBannedMain").submit();
+				}
+				else
+				{
+					// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+					$("#api_banned_single").attr("value", "000");
+					
+					// Submit Form For Multiple Items
+					$("#apiBannedMain").submit();
+				}
+		
+			} else {
+				return false;
+			}
+		}
+	}
\ No newline at end of file
diff --git a/application/views/admin/settings/api/logs.php b/application/views/admin/settings/api/logs.php
new file mode 100644
index 0000000..2929716
--- /dev/null
+++ b/application/views/admin/settings/api/logs.php
@@ -0,0 +1,143 @@
+<?php 
+/**
+ * API log view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+            <div class="bg">
+                <h2>
+                    <?php admin::settings_subtabs("api"); ?>
+                </h2>
+
+            	<!-- tabs -->
+            	<div class="tabs">
+                    <!-- tabset -->
+                    <ul class="tabset">
+                        <li><a href="<?php echo url::site() ?>admin/settings/api" ><?php echo Kohana::lang('ui_admin.api_settings'); ?></a></li>
+                        <li><a href="<?php echo url::site() ?>admin/settings/api/log" <?php if ($this_page == 'apilogs') echo "class=\"active\""?>><?php echo Kohana::lang('ui_admin.api_logs');?></a></li>
+                        <li><a href="<?php echo url::site() ?>admin/settings/api/banned"><?php echo Kohana::lang('ui_admin.api_banned'); ?></a></li>
+                    </ul>
+                    <!-- /tabset -->
+
+            		<!-- tab -->
+            		<div class="tab">
+            			<ul>
+            				<li><a href="#" onclick="apiLogAction('d','DELETE', '');"><?php echo utf8::strtoupper(Kohana::lang('ui_admin.delete_action')) ;?></a></li>
+            				<li><a href="#" onclick="apiLogAction('x','DELETE ALL ', '000');"><?php echo utf8::strtoupper(Kohana::lang('ui_admin.delete_all')) ;?></a></li>
+            			</ul>
+            		</div>
+            	</div>
+            	<!-- /tabs -->
+            	
+            	<?php if ($form_error) : ?>
+            		<!-- red-box -->
+            		<div class="red-box">
+            			<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+            			<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+            		</div>
+            	<?php endif; ?>
+            	
+                <?php if ($form_saved): ?>
+            		<!-- green-box -->
+            		<div class="green-box" id="submitStatus">
+            			<h3><?php echo Kohana::lang('ui_admin.api_logs');?> <?php echo $form_action; ?> 
+            			    <a href="#" id="hideMessage" class="hide"><?php echo Kohana::lang('ui_main.hide_this_message');?></a>
+            			</h3>
+            		</div>
+            	<?php endif; ?>
+            	
+            	<!-- report-table -->
+            	<?php print form::open(NULL, array('id' => 'apiLogMain', 'name' => 'apiLogMain')); ?>
+            		<input type="hidden" name="action" id="action" value="" />
+            		<input type="hidden" name="api_log_id[]" id="api_log_single" value="" />
+            		<input type="hidden" name="api_log_ipaddress" id="api_log_ipaddress" value="" />
+            		<div class="table-holder">
+            			<table class="table">
+            				<thead>
+            					<tr>
+            						<th class="col-1">
+            						<input id="checkallapilogs" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'api_log_id[]' )" /></th>
+            						<th class="col-2"><?php echo Kohana::lang('ui_admin.parameters_used');?></th>
+            						<th class="col-3"><?php echo Kohana::lang('ui_admin.task_performed');?></th>
+            						<th class="col-3"><?php echo Kohana::lang('ui_admin.total_records');?></th>
+            						<th class="col-3"><?php echo Kohana::lang('ui_admin.ip_address');?></th>
+            						<th class="col-3"><?php echo Kohana::lang('ui_admin.date_time');?></th>
+            						<th class="col-4"><?php echo Kohana::lang('ui_admin.actions');?></th>
+            					</tr>
+            				</thead>
+            				<tfoot>
+            					<tr class="foot">
+            						<td colspan="7">
+            							<?php echo $pagination; ?>
+            						</td>
+            					</tr>
+            				</tfoot>
+            			<tbody>
+            				<?php if ($total_items == 0): ?>
+            					<tr>
+            						<td colspan="7" class="col">
+            							<h3><?php echo Kohana::lang('ui_admin.no_result_display_msg');?></h3>
+            						</td>
+            					</tr>
+            				<?php endif; ?>
+            				
+            				<?php
+            					foreach ($api_logs as $api_log)
+            					{
+            						$api_log_id = $api_log->id;
+            						$api_task = $api_log->api_task;
+            						$api_parameters = $api_log->api_parameters;
+            						$api_records = $api_log->api_records;
+            						$api_ipaddress = $api_log->api_ipaddress;
+            						$api_date = $api_log->api_date;						
+            				    ?>
+            						<tr>
+            							<td class="col-1">
+            							    <input name="api_log_id[]" id="api_log" value="<?php echo $api_log_id; ?>" type="checkbox" class="check-box"/>
+            							</td>
+            							<td class="col-2">
+            							    <?php echo (is_array(@unserialize($api_parameters)))
+            							        ? implode(",", at unserialize($api_parameters))
+            							        : @unserialize($api_parameters); 
+            							    ?>
+            							</td>
+            							<td class="col-3"><?php echo $api_task;?></td>
+            							<td class="col-3"><?php echo $api_records;?></td>
+            							<td class="col-3"><?php echo $api_ipaddress;?></td>
+            							<td class="col-3"><?php echo $api_date; ?></td>
+            							<td class="col-4">
+            								<ul>
+            								    <li>
+            								        <?php if ( ! isset($api_log->ban_id)): ?>
+            								        <a href="#" class="del" onclick="apiLogAction('b', 'BAN', '<?php echo $api_log_id; ?>');">
+            								            <?php echo utf8::strtoupper(Kohana::lang('ui_admin.banip_action')); ?>
+            								        </a>
+            								        <?php else: ?>
+            								            <span><?php echo utf8::strtoupper(Kohana::lang('ui_admin.banip_action')); ?></span>
+            								        <?php endif; ?>
+            								    </li>
+            								    <li>
+            								        <a href="#" class="del" onclick="apiLogAction('d','DELETE', '<?php echo $api_log_id; ?>');">
+            								            <?php echo utf8::strtoupper(Kohana::lang('ui_admin.delete_action')) ;?>
+            								        </a>
+            								    </li>
+            								</ul>
+            							</td>
+            						</tr>
+            					<?php
+            					}
+            				?>
+            			</tbody>
+            		</table>
+            	</div>
+            	<?php print form::close(); ?>
+            </div>
\ No newline at end of file
diff --git a/application/views/admin/settings/api/logs_js.php b/application/views/admin/settings/api/logs_js.php
new file mode 100644
index 0000000..4dba3d6
--- /dev/null
+++ b/application/views/admin/settings/api/logs_js.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * API logs js file.
+ *
+ * Handles javascript stuff related  to api log function
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<?php require SYSPATH.'../application/views/admin/utils_js.php' ?>
+
+	// Ajax Submission
+	function apiLogAction ( action, confirmAction, api_log_id )
+	{
+		var statusMessage;
+		if( !isChecked( "api_log" ) && api_log_id=='' )
+		{ 
+			alert('Please select at least one api log.');
+		} else {
+			var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+			if (answer){
+				// Set Submit Type
+				$("#action").attr("value", action);
+			
+				if (api_log_id != '') 
+				{
+					// Submit Form For Single Item
+					$("#api_log_single").attr("value", api_log_id);
+					$("#apiLogMain").submit();
+				}
+				else
+				{
+					// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+					$("#api_log_single").attr("value", "000");
+					
+					// Submit Form For Multiple Items
+					$("#apiLogMain").submit();
+				}
+		
+			} else {
+				return false;
+			}
+		}
+	}
\ No newline at end of file
diff --git a/application/views/admin/settings/api/main.php b/application/views/admin/settings/api/main.php
new file mode 100644
index 0000000..c0df364
--- /dev/null
+++ b/application/views/admin/settings/api/main.php
@@ -0,0 +1,99 @@
+<?php
+/**
+ * API settings view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+            <div class="bg">
+                <h2>
+                    <?php admin::settings_subtabs("api"); ?>
+                </h2>
+                <!-- tabs -->
+                <div class="tabs">
+                    <!-- tabset -->
+                    <ul class="tabset">
+                        <li><a href="<?php echo url::site() . 'admin/settings/api' ?>" class="active"><?php echo Kohana::lang('ui_admin.api_settings'); ?></a></li>
+                        <li><a href="<?php echo url::site() . 'admin/settings/api/log' ?>"><?php echo Kohana::lang('ui_admin.api_logs');?></a></li>
+                        <li><a href="<?php echo url::site() . 'admin/settings/api/banned'?>"><?php echo Kohana::lang('ui_admin.api_banned'); ?></a></li>
+                    </ul>
+                    <!-- /tabset -->
+                    
+                    <!-- tab -->
+                    <div class="tab">
+                        <ul>
+                            <li><a href="#" onclick="apiSettingsAction('s', 'SAVE');"><?php echo utf8::strtoupper(Kohana::lang('ui_admin.save_settings')); ?></a></li>
+                            <li><a href="#" onclick-"apiSettingsAction('c', 'CANCEL');"><?php echo utf8::strtoupper(Kohana::lang('ui_admin.cancel')); ?></a></li>
+                        </ul>
+                    </div>
+                    <!-- /tab -->
+                </div>
+                <!-- /tabs -->
+                
+                <?php print form::open(NULL, array('id'=>'apiSettingsMain', 'name'=>'apiSettingsMain')); ?>
+                    <input type="hidden" name="action" id="action" value="" />
+                <div class="report-form">
+                                        
+                    <?php if ($form_error): ?>
+                    <!-- red box-->
+                    <div class="red-box">
+                        <h3><?php echo Kohana::lang('ui_main.error'); ?></h3>
+                        <ul>
+                            <?php 
+                                foreach ($errors as $error_item=>$error_description)
+                                {
+                                    print (!$error_description)? '' : "<li>".$error_description."</li>";
+                                }
+                            ?>
+                        </ul>
+                    </div>
+                    <?php endif; ?>
+                    
+                    <?php if ($form_saved): ?>
+                        <!-- green box -->
+                        <div class="green-box">
+                            <h3><?php echo Kohana::lang('ui_main.configuration_saved'); ?></h3>
+                        </div>
+                    <?php endif; ?>
+                    
+                    <!--column-->
+                    <div class="sms_holder">
+                        <div class="row">
+                            <h4>
+                                <a href="#" class="tooltip" title="<?php echo Kohana::lang('tooltips.settings_api_default_record_limit'); ?>">
+                                    <?php echo Kohana::lang('settings.api.default_record_limit'); ?>
+                                </a>
+                            </h4>
+                            <?php print form::input('api_default_record_limit', $form['api_default_record_limit'], ' class="text"'); ?>
+                        </div>
+                        <div class="row">
+                            <h4>
+                                <a href="#" class="tooltip" title="<?php echo Kohana::lang('tooltips.settings_api_max_record_limit'); ?>">
+                                    <?php echo Kohana::lang('settings.api.maximum_record_limit'); ?>
+                                </a>
+                            </h4>
+                            <?php print form::input('api_max_record_limit', $form['api_max_record_limit'], ' class="text"'); ?>
+                        </div>
+                        <div class="row">
+                            <h4>
+                                <a href="#" class="tooltip" title="<?php echo Kohana::lang('tooltips.settings_api_max_requests_per_ip'); ?>">
+                                    <?php echo Kohana::lang('settings.api.maximum_requests_per_ip_address'); ?>
+                                </a>
+                            </h4>
+                            
+                            <?php print form::input('api_max_requests_per_ip_address', $form['api_max_requests_per_ip_address'], ' class="text"'); ?>
+                            <strong> <?php echo Kohana::lang('ui_main.per'); ?> </strong>
+                            <?php print form::dropdown('api_max_requests_quota_basis', $max_requests_quota_array, $form['api_max_requests_quota_basis']); ?>
+                        </div>
+                    </div>                    
+                </div>
+                <?php print form::close(); ?>
+            </div>
\ No newline at end of file
diff --git a/application/views/admin/settings/cleanurl.php b/application/views/admin/settings/cleanurl.php
new file mode 100644
index 0000000..dd25d5f
--- /dev/null
+++ b/application/views/admin/settings/cleanurl.php
@@ -0,0 +1,77 @@
+<?php 
+/**
+ * Clean URLs view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::settings_subtabs("cleanurl"); ?>
+				</h2>
+				<?php print form::open(NULL, array('id' => 'cleanurlForm', 'name' => 'cleanurlForm','action'=> url::site().'admin/settings/cleanurl')); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>				
+					<div class="head">
+						<h3><?php echo Kohana::lang('settings.cleanurl.title');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->		
+					<div class="sms_holder">
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.cleanurl.enable_clean_url');?>?</h4>
+								<?php if(!$is_clean_url_enabled) { ?>
+								<?php print form::dropdown(array('name'=>'enable_clean_url','disabled' =>'true'), $yesno_array, '0'); ?>
+								<p>
+								<?php echo Kohana::lang('settings.cleanurl.clean_url_disabled');?>
+								</p>
+								<?php } else {?>
+								<?php print form::dropdown('enable_clean_url', $yesno_array, $form['enable_clean_url']); ?>
+								<p>
+								<?php echo Kohana::lang('settings.cleanurl.clean_url_enabled');?>
+								</p>
+								<?php } ?>
+						</div>						
+						
+					</div>
+		
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/settings/email.php b/application/views/admin/settings/email.php
new file mode 100644
index 0000000..31dc4a0
--- /dev/null
+++ b/application/views/admin/settings/email.php
@@ -0,0 +1,129 @@
+<?php 
+/**
+ * Email view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::settings_subtabs("email"); ?>
+				</h2>
+				<?php print form::open(); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>				
+					<div class="head">
+						<h3><?php echo Kohana::lang('ui_main.email_configuration');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->		
+					<div class="sms_holder">
+						<?php
+						if ( ! $form_error
+							AND ! empty($form['email_username'])
+							AND ! empty($form['email_username'])
+							AND ! empty($form['email_password'])
+							AND ! empty($form['email_port'])
+							AND ! empty($form['email_host'])
+							AND ! empty($form['email_servertype'])
+						)
+						{
+						?>
+							<div class="test_settings">
+								<div class="tab">
+									<ul>
+										<li><a href="javascript:emailTest();"><?php echo utf8::strtoupper(Kohana::lang('settings.test_settings'));?></a></li>
+										<li id="test_loading"></li>
+										<li id="test_status"></li>
+									</ul>
+								</div>
+							</div>
+						<?php
+						}
+						?>
+						<?php echo Kohana::lang('ui_main.email_settings_comment_00');?> <a href="<?php echo url::site()."admin/settings/site";?>"><?php echo Kohana::lang('ui_main.site_email_address');?></a> 
+						(<strong> <?php echo Kohana::config('settings.site_email');?></strong>), <?php echo Kohana::lang('ui_main.email_settings_comment_0');?>.
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_server_username"); ?>"><?php echo Kohana::lang('ui_main.email_server_username');?></a></h4>
+							<?php print form::input('email_username', $form['email_username'], ' class="text long2"'); ?>
+						</div>
+						<span>
+							<?php echo Kohana::lang('ui_main.email_settings_comment_1');?>
+						</span>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_server_password"); ?>"><?php echo Kohana::lang('ui_main.email_server_password');?></a></h4>
+							<?php print form::password('email_password', $form['email_password'], ' class="text long2"'); ?>							
+						</div>
+						<span>
+							<?php echo Kohana::lang('ui_main.email_settings_comment_2');?>
+						</span>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_server_port"); ?>"><?php echo Kohana::lang('ui_main.email_server_port');?></a></h4>
+							<?php print form::input('email_port', $form['email_port'], ' class="text long2"'); ?>
+						</div>
+						<span>
+							<?php echo Kohana::lang('ui_main.email_settings_comment_3');?>
+						</span>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_server_host"); ?>"><?php echo Kohana::lang('ui_main.email_server_host');?></a></h4>
+							<?php print form::input('email_host', $form['email_host'], ' class="text long2"'); ?>
+						</div>
+						<span>
+							<?php echo Kohana::lang('ui_main.email_settings_comment_4');?>
+						</span>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_server_type"); ?>"><?php echo Kohana::lang('ui_main.email_server_type');?></a></h4>
+							<?php print form::input('email_servertype', $form['email_servertype'], ' class="text long2"'); ?>								 
+						</div>
+						<span>
+							<?php echo Kohana::lang('ui_main.email_settings_comment_5');?>
+						</span>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_server_ssl_support"); ?>"><?php echo Kohana::lang('ui_main.email_server_ssl_support');?></a></h4>
+								<?php print form::dropdown('email_ssl', $email_ssl_array, $form['email_ssl']); ?>
+						</div>
+						<span>
+							<?php echo Kohana::lang('ui_main.email_settings_comment_6');?>
+						</span>
+					</div>
+		
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/settings/email_js.php b/application/views/admin/settings/email_js.php
new file mode 100644
index 0000000..57b3e9e
--- /dev/null
+++ b/application/views/admin/settings/email_js.php
@@ -0,0 +1,13 @@
+function emailTest() {
+	$('#test_loading').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+	$('#test_status').html('');
+	$.post("<?php echo url::site() . 'admin/settings/test_email/' ?>", {  },
+		function(data){
+			if (data.status == 'success'){
+				$('#test_status').html(data.message);
+			} else {
+				$('#test_status').html(data.message);
+			}
+			$('#test_loading').html('');
+	  	}, "json");
+}
\ No newline at end of file
diff --git a/application/views/admin/settings/externalapps/externalapps_js.php b/application/views/admin/settings/externalapps/externalapps_js.php
new file mode 100644
index 0000000..6eac283
--- /dev/null
+++ b/application/views/admin/settings/externalapps/externalapps_js.php
@@ -0,0 +1,32 @@
+/**
+ * External Apps js file.
+ *
+ * Handles javascript stuff related to badges in the admin panel.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     External Apps Javascript
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+function appAction ( action, confirmAction, externalapp_id )
+{
+	var answer = confirm(<?php echo json_encode(Kohana::lang('ui_admin.are_you_sure_you_want_to')); ?> +' '+ confirmAction + '?')
+	if (answer){
+
+		// Set External App ID
+		$("#id").attr("value", externalapp_id);
+
+		// Set Submit Type
+		$("#action").attr("value", action);
+
+		// Submit Form
+		$("#externalappMain").submit();
+
+	}
+}
diff --git a/application/views/admin/settings/externalapps/main.php b/application/views/admin/settings/externalapps/main.php
new file mode 100644
index 0000000..5dc4c9a
--- /dev/null
+++ b/application/views/admin/settings/externalapps/main.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * External Apps view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     External Apps View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::settings_subtabs("externalapps"); ?>
+				</h2>
+
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.saved');?>!</h3>
+					</div>
+				<?php
+				}
+				?>
+
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active" onclick="show_addedit(true)"><?php echo Kohana::lang('ui_main.add');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab" id="addedit" style="display:none">
+						<?php print form::open(NULL,array('enctype' => 'multipart/form-data', 'id' => 'externalappMain', 'name' => 'externalappMain')); ?>
+						<input type="hidden" id="id" name="id" value="<?php echo $form['id']; ?>" />
+						<input type="hidden" name="action" id="action" value="a"/>
+
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.name');?>:</strong><br />
+							<?php print form::input('name', $form['name'], ' class="text"'); ?>
+						</div>
+
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.url');?>:</strong><br />
+							<?php print form::input('url', $form['url'], ' class="text"'); ?>
+						</div>
+
+						<div style="clear:both"></div>
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>
+					</div>
+				</div>
+
+				<!-- externalapp-table -->
+				<div>
+
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr class="nodrag">
+									<th class="col-1"> </th>
+									<th class="col-2" style="width:80px;"><?php echo Kohana::lang('ui_main.external_apps');?></th>
+									<th class="col-3" style="width:600px;"> </th>
+									<th class="col-4" style="width:120px;"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr class="nodrag">
+										<td colspan="4" class="col" id="row1">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+							<?php
+								}
+								$i = 1;
+								foreach ($externalapps as $app)
+								{
+							?>
+									<tr>
+										<td class="col-1"> </td>
+										<td class="col-2" style="width:80px;">
+											<?php
+											// TODO: Put image here once it's in
+											?>
+										</td>
+										<td class="col-3" style="width:600px;font-weight:normal;">
+											<strong><?php echo $app->name; ?></strong>
+											<br/><a href="<?php echo $app->url; ?>"><?php echo $app->url; ?></a>
+
+										</td>
+										<td class="col-4" style="width:120px;">
+
+											<ul>
+												<li><a href="javascript:appAction('d','<?php echo utf8::strtoupper(html::escape(Kohana::lang('ui_main.remove').' '.$app->name));?>','<?php echo rawurlencode($app->id); ?>')" class="del"><?php echo Kohana::lang('ui_main.remove');?></a></li>
+											</ul>
+
+										</td>
+									</tr>
+							<?php
+								}
+							?>
+							</tbody>
+						</table>
+					</div>
+				</div>
+
+			</div>
diff --git a/application/views/admin/settings/facebook/main.php b/application/views/admin/settings/facebook/main.php
new file mode 100644
index 0000000..7fe8877
--- /dev/null
+++ b/application/views/admin/settings/facebook/main.php
@@ -0,0 +1,77 @@
+<?php 
+/**
+ * Facebook Settings view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Facebook Settings View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+
+				<h2>
+					<?php admin::settings_subtabs("facebook"); ?>
+				</h2>
+				<?php print form::open(); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								// print "<li>" . $error_description . "</li>";
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>				
+					<div class="head">
+						<h3><?php echo Kohana::lang('settings.facebook.title');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->
+		
+					<div class="sms_holder">
+						<div class="row" style="margin-top:20px;">
+							<h4><?php echo Kohana::lang('settings.facebook.description');?>:<BR /><a href="http://www.facebook.com/developers/" target="_blank">http://www.facebook.com/developers/</a></h4>
+							<?php echo Kohana::lang('settings.facebook.description_2');?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.facebook.app_id');?>:</h4>
+							<?php print form::input('facebook_appid', $form['facebook_appid'], ' class="text title_2"'); ?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.facebook.app_secret');?>:</h4>
+							<?php print form::input('facebook_appsecret', $form['facebook_appsecret'], ' class="text title_2"'); ?>
+						</div>
+					</div>
+		
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/settings/https.php b/application/views/admin/settings/https.php
new file mode 100644
index 0000000..65fdc7c
--- /dev/null
+++ b/application/views/admin/settings/https.php
@@ -0,0 +1,77 @@
+<?php 
+/**
+ * SSL view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Settings Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::settings_subtabs("https"); ?>
+				</h2>
+				<?php print form::open(NULL, array('id' => 'httpsForm', 'name' => 'httpsForm','action'=> url::site().'admin/settings/https')); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>				
+					<div class="head">
+						<h3><?php echo Kohana::lang('settings.https.title');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->		
+					<div class="sms_holder">
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.https.enable_https');?>?</h4>
+								<?php if ( ! $is_https_capable): ?>
+								<?php print form::dropdown(array('name'=>'enable_https','disabled' =>'true'), $yesno_array, '0'); ?>
+								<p>
+								<?php echo Kohana::lang('settings.https.https_disabled');?>
+								</p>
+								<?php else: ?>
+								<?php print form::dropdown('enable_https', $yesno_array, $form['enable_https']); ?>
+								<p>
+								<?php echo Kohana::lang('settings.https.https_enabled');?>
+								</p>
+								<?php endif; ?>
+						</div>						
+						
+					</div>
+		
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/settings/main.php b/application/views/admin/settings/main.php
new file mode 100644
index 0000000..6b1604b
--- /dev/null
+++ b/application/views/admin/settings/main.php
@@ -0,0 +1,233 @@
+<?php 
+/**
+ * Settings view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::settings_subtabs("map"); ?>
+				</h2>
+				<?php print form::open(NULL, array('enctype' => 'multipart/form-data')); ?>
+					<div class="report-form">
+					<?php if ($form_error): ?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php endif; ?>
+						
+					<?php if ($form_saved): ?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved'); ?></h3>
+						</div>
+					<?php endif; ?>
+						<div class="head">
+							<h3><?php echo Kohana::lang('settings.map_settings');?></h3>
+							<input type="submit" value="<?php echo Kohana::lang('ui_admin.save_settings'); ?>" class="save-rep-btn" />
+						</div>
+						<!-- column -->
+						<div class="l-column">
+							<div class="has_border_first">
+								<div class="row">
+									<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_default_location"); ?>"><?php echo Kohana::lang('settings.default_location');?></a></h4>
+									<p class="bold_desc"><?php echo Kohana::lang('settings.select_default_location');?>.</p>
+									<span class="my-sel-holder">
+										<?php print form::dropdown('default_country',$countries,$form['default_country']); ?>
+									</span>
+									
+									<div id="retrieve_cities">
+										<a href="javascript:retrieveCities()" id="retrieve"><?php echo Kohana::lang('settings.download_city_list');?></a>
+										<span id="cities_loading"></span>
+										<div id="city_count"></div>
+									</div>
+									<div>
+										<?php echo Kohana::lang('settings.multiple_countries');?><br />
+										<input type="radio" name="multi_country" value="1"
+										<?php if ($form['multi_country'] == 1)
+										{
+											echo " checked=\"checked\" ";
+										}?>> <?php echo Kohana::lang('ui_main.yes');?>
+										<input type="radio" name="multi_country" value="0"
+										<?php if ($form['multi_country'] != 1)
+										{
+											echo " checked=\"checked\" ";
+										}?>> <?php echo Kohana::lang('ui_main.no');?>
+										
+									</div>
+								</div>
+							</div>
+							<div class="has_border">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_map_timeline");?>"><?php echo Kohana::lang('settings.map_timeline');?></a></h4>
+								<input type="radio" name="enable_timeline" value="1"
+								<?php if ($form['enable_timeline'] == 1)
+								{
+									echo " checked=\"checked\" ";
+								}?>> <?php echo Kohana::lang('ui_main.yes');?>
+								<input type="radio" name="enable_timeline" value="0"
+								<?php if ($form['enable_timeline'] !=1)
+								{
+									echo " checked=\"checked\" ";
+								}?>> <?php echo Kohana::lang('ui_main.no');?> 
+								<!--  HT: Timeline Graph style setting -->
+								<h4><?php echo Kohana::lang('settings.graph_style');?></h4>
+								<input type="radio" name="timeline_graph" value="line"
+								<?php if ($form['timeline_graph'] != 'bar')
+								{
+									echo " checked=\"checked\" ";
+								}?>> <?php echo Kohana::lang('settings.line');?>
+								<input type="radio" name="timeline_graph" value="bar"
+								<?php if ($form['timeline_graph'] == 'bar')
+								{
+									echo " checked=\"checked\" ";
+								}?>> <?php echo Kohana::lang('settings.bar');?>
+								<h4><?php echo Kohana::lang('settings.timeline_point_label');?></h4>
+								<input type="radio" name="timeline_point_label" value="1"
+								<?php if ($form['timeline_point_label'] == 1)
+								{
+									echo " checked=\"checked\" ";
+								}?>> <?php echo Kohana::lang('ui_main.yes');?>
+								<input type="radio" name="timeline_point_label" value="0"
+								<?php if ($form['timeline_point_label'] != 1)
+								{
+									echo " checked=\"checked\" ";
+								}?>> <?php echo Kohana::lang('ui_main.no');?>
+								<!--  HT: End of Timeline Graph style setting -->
+							</div>
+
+							<div class="has_border">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_map_provider"); ?>"><?php echo Kohana::lang('settings.map_provider.name');?></a></h4>
+								<p class="bold_desc"><?php echo Kohana::lang('settings.map_provider.info');?></p>
+								<span class="blue_span"><?php echo Kohana::lang('ui_main.step');?> 1: </span>
+								<span class="dark_span"><?php echo Kohana::lang('settings.map_provider.choose');?></span><br />
+								<div class="c_push">
+									<span class="my-sel-holder">
+										<?php										
+										print form::dropdown('default_map',$map_array,$form['default_map']);
+										?>
+									</span>
+								</div>
+	
+								<div id="api_key_div" <?php if (strpos($form['default_map'],'bing') === FALSE) echo "style=\"display:none\""; ?>>
+									<span class="blue_span"><?php echo Kohana::lang('ui_main.step');?> 2: </span>
+									<span class="dark_span"><?php echo Kohana::lang('settings.map_provider.get_api');?></span><br />
+									
+									<div class="c_push api_key_div">
+										<a href="https://www.bingmapsportal.com/" id="api_link" title="Get API Key">
+											<img src="<?php echo url::file_loc('img'); ?>media/img/admin/btn-get-api-key.gif" border="0" alt="Get API Key">
+										</a>
+									</div>
+									
+									<div class="api_key_div">
+										<span class="blue_span"><?php echo Kohana::lang('ui_main.step');?> 3: </span>
+										<span class="dark_span"><?php echo Kohana::lang('settings.map_provider.enter_api');?></span><br />
+										<div class="c_push">
+											<?php print form::input('api_live', $form['api_live'], ' class="text"'); ?>
+										</div>
+									</div>
+								</div>
+							</div>
+							
+							
+							<div class="has_border">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_allow_clustering"); ?>"><?php echo Kohana::lang('settings.site.allow_clustering');?></a></h4>
+								<div class="c_push">
+									<input type="radio" name="allow_clustering" value="1"
+									<?php if ($form['allow_clustering'] == 1)
+									{
+										echo " checked=\"checked\" ";
+									}?>> <?php echo Kohana::lang('ui_main.yes');?>
+									<input type="radio" name="allow_clustering" value="0"
+									<?php if ($form['allow_clustering'] !=1)
+									{
+										echo " checked=\"checked\" ";
+									}?>> <?php echo Kohana::lang('ui_main.no');?> 
+								</div>
+								
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_default_category_colors"); ?>"><?php echo Kohana::lang('settings.site.default_category_colors');?></a></h4>
+								<div class="c_push">
+								<?php print form::input('default_map_all', $form['default_map_all'], ' class="text"'); ?>
+								</div>
+								<script type="text/javascript" charset="utf-8">
+									$(document).ready(function() {
+										$('#default_map_all').ColorPicker({
+											onSubmit: function(hsb, hex, rgb) {
+												$('#default_map_all').val(hex);
+											},
+											onChange: function(hsb, hex, rgb) {
+												$('#default_map_all').val(hex);
+											},
+											onBeforeShow: function () {
+												$(this).ColorPickerSetColor(this.value);
+											}
+										})
+										.bind('keyup', function(){
+											$(this).ColorPickerSetColor(this.value);
+										});
+									});
+								</script>
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_default_category_icons"); ?>"><?php echo Kohana::lang('settings.site.default_category_icons');?></a></h4>
+								<div class="c_push">
+									<?php if($default_map_all_icon_m != NULL) { ?>
+										<img src="<?php echo $default_map_all_icon_m; ?>" alt="<?php Kohana::lang('settings.site.default_category_icons'); ?>" /><br/>
+									<?php } ?>
+									<?php echo form::upload('default_map_all_icon', '', ''); ?> (<= 250k)
+									<br/>
+									<?php
+										echo form::checkbox('delete_default_map_all_icon', '1');
+										echo form::label('delete_default_map_all_icon', Kohana::lang("settings.site.delete_default_map_all_icon"));
+		
+									?>
+								</div>
+							</div>
+							<input type="submit" value="<?php echo Kohana::lang('ui_admin.save_settings'); ?>" class="cancel-btn" />
+						</div>
+						<div class="r-column">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_configure_map"); ?>"><?php echo Kohana::lang('settings.configure_map');?></a></h4>
+
+							<div style="width: 279px; float: left; margin-top: 10px;">
+								<span class="bold_span"><?php echo Kohana::lang('settings.default_zoom_level');?></span>
+								<div class="slider_container">
+									<?php print form::dropdown('default_zoom',$default_zoom_array,$form['default_zoom']); ?>
+								</div>
+							</div>
+							<div style="width: 279px; height: 90px; float: left; margin-top: 10px;">
+								<span class="bold_span"><?php echo Kohana::lang('settings.default_map_view');?></span>
+								<span class="my-sel-holder"><select><option><?php echo Kohana::lang('ui_main.map');?></option></select></span>
+								<div class="location-info">
+									<span><?php echo Kohana::lang('ui_main.latitude');?>:</span>
+									<?php print form::input('default_lat', $form['default_lat'], ' readonly="readonly" class="text"'); ?>
+									<span><?php echo Kohana::lang('ui_main.longitude');?>:</span>
+									<?php print form::input('default_lon', $form['default_lon'], ' readonly="readonly" class="text"'); ?>
+								</div>
+							</div>
+							<div style="clear:both;"></div>
+							<h4><?php echo Kohana::lang('ui_main.preview');?></h4>
+							<p class="bold_desc"><?php echo Kohana::lang('settings.set_location');?>.</p>
+
+							<div id="map_holder">
+								<div id="map" class="mapstraction"></div>    
+							</div>
+							<div style="margin-top:25px" id="map_loaded"></div>
+						</div>
+					</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/settings/settings_js.php b/application/views/admin/settings/settings_js.php
new file mode 100644
index 0000000..d6fd670
--- /dev/null
+++ b/application/views/admin/settings/settings_js.php
@@ -0,0 +1,145 @@
+/**
+ * JavaScript for the map settings page
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://github.com/ushahidi/Ushahidi_Web
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+$(document).ready(function() {
+	
+	var default_map = '<?php print $default_map ?>';
+	var all_maps = <?php echo $all_maps_json; ?>;
+	
+	// Current zoom level - updated on each zoom updated
+	var currentZoom = <?php echo $default_zoom; ?>;
+	
+	<?php echo map::layers_js(TRUE); ?>
+	var mapConfig = {
+		zoom: currentZoom,
+		center: {
+			latitude: <?php echo $default_lat; ?>,
+			longitude: <?php echo $default_lon; ?>
+		},
+		mapControls: [
+		    new OpenLayers.Control.Navigation({ dragPanOptions: { enableKinetic: true } }),
+		    new OpenLayers.Control.Zoom(),
+		    new OpenLayers.Control.MousePosition({
+				formatOutput: Ushahidi.convertLongLat
+			})
+		],
+		
+		// Base layers
+		baseLayers: <?php echo map::layers_array(TRUE); ?>,
+		
+		// Detect map zoom events
+		detectMapZoom: true
+	};
+	
+	var map = new Ushahidi.Map('map', mapConfig);
+	map.addLayer(Ushahidi.DEFAULT);
+	
+	// Marker position changed - for the map center
+	map.register("markerpositionchanged", function(coords){
+		$("#default_lat").attr("value", coords.latitude);
+		$("#default_lon").attr("value", coords.longitude);
+	});
+	
+	// Zoom changed
+	map.register("zoomchanged", function(zoom) {
+		$("select#default_zoom").val(zoom);
+		$("select#default_zoom").trigger("click");
+	});
+	
+	// Zoom slider detection
+	$('select#default_zoom').selectToUISlider({
+		labels: 5,
+		sliderOptions: {
+			change:function(e, ui) {
+				var new_zoom = parseInt($("#default_zoom").val());
+				if (currentZoom !== new_zoom) {
+					currentZoom = new_zoom;
+					$('#zoom_level').html('"' + new_zoom + '"');
+					map.trigger("zoomchanged", new_zoom);
+				}
+			}
+		}
+	}).hide();
+		
+	// Detect country dropdown change, then zoom to selected country
+	$('#default_country').change(function(){
+		address = $('#default_country :selected').text();
+		
+		var geocoder = new google.maps.Geocoder();
+		
+		if (geocoder) {
+			geocoder.geocode({ 'address': address },
+				function(results, status) {
+					if (status == google.maps.GeocoderStatus.ZERO_RESULTS) {
+						alert(address + " not found");
+					} else if (status == google.maps.GeocoderStatus.OK) {
+						
+						// Get the lat/lon from the result; accuracy of the map center is off 
+						// because the lookup address does not include the name of the capital city
+						var point = results[0].geometry.location;
+						map.trigger("mapcenterchanged", {latitude: point.lat(), longitude: point.lng()});
+					}
+			});
+		}
+	});
+
+	// detect map provider dropdown change
+	$('#default_map').change(function(){
+		if ($(this).val() == "" || $(this).val() == null)
+			return;
+		
+		var selected_key = $(this).val();
+		
+		selected_map = all_maps[selected_key];
+
+		if (selected_map.api_signup) {
+			$('#api_link').attr('href', selected_map.api_signup);
+			$('#api_link').attr('target', '_blank');
+		} else {
+			$('#api_link').attr('href', 'javascript:alert(\'Your current selection does not require an API key!\')');
+			$('#api_link').attr('target', '_top');
+		}
+		
+		if (selected_key.indexOf('bing') !== -1) {
+			$("#api_key_div").show();
+		} else {
+			$("#api_key_div").hide();
+		}
+		
+		// Trigger baselayer changed event
+		map.trigger("baselayerchanged", selected_map.title);
+	});
+		
+});
+
+// Retrieve Cities From Geonames DB (Ajax)
+function retrieveCities() {
+	var selected = $("#default_country option[selected]");
+	country = selected.val();
+	if (!country || country =='') {
+		alert('Please select a country from the dropdown');
+	} else {
+		$('#cities_loading').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+		$.getJSON("<?php echo url::site() . 'admin/settings/update_cities/' ?>" + country,
+			function(data){
+				if (data.status == 'success'){
+					$('#city_count').show();
+					$('#city_count').html(data.response);
+					$('#cities_loading').html('');
+				} else	{
+					alert(data.response);
+				}
+				$('#cities_loading').html('');
+		  	}, "json");
+	}
+}
diff --git a/application/views/admin/settings/site.php b/application/views/admin/settings/site.php
new file mode 100755
index 0000000..7d06558
--- /dev/null
+++ b/application/views/admin/settings/site.php
@@ -0,0 +1,237 @@
+<?php
+/**
+ * Site view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::settings_subtabs("site"); ?>
+
+				</h2>
+				<?php print form::open(NULL,array('enctype' => 'multipart/form-data', 'id' => 'siteForm', 'name' => 'siteForm')); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>
+					<div class="head">
+						<h3><?php echo Kohana::lang('settings.site.title');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->
+					<div class="sms_holder">
+						<div id="need_to_upgrade" style="display:none;"></div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_site_name"); ?>"><?php echo Kohana::lang('settings.site.name');?></a></h4>
+							<?php print form::input('site_name', $form['site_name'], ' class="text long2" maxlength="250"'); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_site_tagline"); ?>"><?php echo Kohana::lang('settings.site.tagline');?></a></h4>
+							<?php print form::input('site_tagline', $form['site_tagline'], ' class="text long2" maxlength="250"'); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_banner"); ?>"><?php echo Kohana::lang('settings.site.banner');?></a></h4>
+							<?php if($banner_m != NULL) { ?>
+								<img src="<?php echo $banner_m; ?>" alt="<?php Kohana::lang('settings.site.banner'); ?>" /><br/>
+							<?php } ?>
+							<?php echo form::upload('banner_image', '', ''); ?> (<= 250k)
+							<br/>
+							<?php
+								echo form::checkbox('delete_banner_image', '1');
+								echo form::label('delete_banner_image', Kohana::lang("settings.site.delete_banner_image"));
+
+							?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_site_email"); ?>"><?php echo Kohana::lang('settings.site.email_site');?></a>
+							<br /><?php echo Kohana::lang('settings.site.email_notice');?></h4>
+							<?php print form::input('site_email', $form['site_email'], ' class="text long2"'); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_site_message"); ?>"><?php echo Kohana::lang('settings.site.message');?></a></h4>
+							<?php print form::textarea('site_message', $form['site_message'], ' style="height:40px;"'); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_site_copyright_statement"); ?>"><?php echo Kohana::lang('settings.site.copyright_statement');?></a></h4>
+							<?php print form::textarea('site_copyright_statement', $form['site_copyright_statement'], ' style="height:40px;"'); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_site_submit_report_message"); ?>"><?php echo Kohana::lang('settings.site.submit_report_message');?></a></h4>
+							<?php print form::textarea('site_submit_report_message', $form['site_submit_report_message'], ' style="height:40px;"'); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_locale"); ?>"><?php echo Kohana::lang('settings.site.language');?></a> (Locale)</h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('site_language', $locales_array, $form['site_language']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_site_timezone"); ?>"><?php echo Kohana::lang('settings.site.timezone');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('site_timezone',$site_timezone_array, $form['site_timezone']); ?>
+							</span>
+							<div style="clear:both;"></div>
+							<small><?php echo Kohana::lang('ui_admin.server_time').' '.date("m/d/Y H:i:s",time()).' ('.$form['site_timezone'].')'; ?></small>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_display_contact"); ?>"><?php echo Kohana::lang('settings.site.display_contact_page');?></a></h4>
+							<?php print form::dropdown('site_contact_page', $yesno_array, $form['site_contact_page']); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_display_items_per_page"); ?>"><?php echo Kohana::lang('settings.site.items_per_page');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('items_per_page', $items_per_page_array, $form['items_per_page']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_display_items_per_page_admin"); ?>"><?php echo Kohana::lang('settings.site.items_per_page_admin');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('items_per_page_admin', $items_per_page_array, $form['items_per_page_admin']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_blocks_per_row"); ?>"><?php echo Kohana::lang('settings.site.blocks_per_row');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('blocks_per_row', $blocks_per_row_array, $form['blocks_per_row']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_allow_reports"); ?>"><?php echo Kohana::lang('settings.site.allow_reports');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('allow_reports', $yesno_array, $form['allow_reports']); ?>
+							</span>
+						</div>
+						 <!--Adding max file upload setting-->
+						<div class="row">
+						 <h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_max_upload_size"); ?>"><?php echo Kohana::lang('settings.site.max_upload_size');?></a></h4>
+						 	<span class="sel-holder">
+						 		<?php print form::input('max_upload_size', $form['max_upload_size'], ' class="text long2"', 'id = max_upload_size'); ?>
+						 	</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_allow_alerts"); ?>"><?php echo Kohana::lang('settings.site.allow_alerts');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('allow_alerts', $yesno_array, $form['allow_alerts']); ?>
+							</span>
+						</div>
+						<div class="row" id="alerts_selector">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_alert_email"); ?>"><?php echo Kohana::lang('settings.site.email_alerts');?></a></h4>
+							<?php print form::input('alerts_email', $form['alerts_email'], ' class="text long2"', 'id = alert_email'); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_allow_comments"); ?>"><?php echo Kohana::lang('settings.site.allow_comments');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('allow_comments', $comments_array, $form['allow_comments']); ?>
+							</span>
+						</div>
+						<!--  HT: Number of alert days setting -->
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_alert_days"); ?>"><?php echo Kohana::lang('settings.site.alert_days');?></a>
+							<br /><?php echo Kohana::lang('settings.site.alert_days_notice');?></h4>
+							<?php print form::input('alert_days', $form['alert_days'], ' class="text long2"'); ?>
+						</div>
+						<!--  HT: End of Number of alert days setting -->
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_allow_feed"); ?>"><?php echo Kohana::lang('settings.site.allow_feed');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('allow_feed', $yesno_array, $form['allow_feed']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_allow_feed_category"); ?>"><?php echo Kohana::lang('settings.site.allow_feed_category');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('allow_feed_category', $yesno_array, $form['allow_feed_category']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.feed_geolocation_user"); ?>"><?php echo Kohana::lang('settings.site.feed_geolocation_user');?></a></h4>
+							<?php print form::input('feed_geolocation_user', $form['feed_geolocation_user'], ' class="text long2"', 'id = alert_email'); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_share_site_stats"); ?>"><?php echo Kohana::lang('settings.site.share_site_stats');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('allow_stat_sharing', $yesno_array, $form['allow_stat_sharing']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_cache_pages"); ?>"><?php echo Kohana::lang('settings.site.cache_pages');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('cache_pages', $yesno_array, $form['cache_pages']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_cache_pages_lifetime"); ?>"><?php echo Kohana::lang('settings.site.cache_pages_lifetime');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('cache_pages_lifetime', $cache_pages_lifetime_array, $form['cache_pages_lifetime']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_private_deployment"); ?>"><?php echo Kohana::lang('settings.site.private_deployment');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('private_deployment', $yesno_array, $form['private_deployment']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_manually_approve_users"); ?>"><?php echo Kohana::lang('settings.site.manually_approve_users');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('manually_approve_users', $yesno_array, $form['manually_approve_users']); ?>
+							</span>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_require_email_confirmation"); ?>"><?php echo Kohana::lang('settings.site.require_email_confirmation');?></a></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('require_email_confirmation', $yesno_array, $form['require_email_confirmation']); ?>
+							</span>
+						</div>
+						<div class="row">
+						<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_google_analytics"); ?>"><?php echo Kohana::lang('settings.site.google_analytics');?></a></h4>
+							<?php echo Kohana::lang('settings.site.google_analytics_example');?>   
+							<?php print form::input('google_analytics', $form['google_analytics'], ' class="text"'); ?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.site.api_akismet');?></h4>
+							<?php echo Kohana::lang('settings.site.kismet_notice');?>.
+							<?php print form::input('api_akismet', $form['api_akismet'], ' class="text"'); ?>
+						</div>
+					</div>
+
+					<div class="simple_border"></div>
+
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/settings/site_js.php b/application/views/admin/settings/site_js.php
new file mode 100644
index 0000000..9f11467
--- /dev/null
+++ b/application/views/admin/settings/site_js.php
@@ -0,0 +1,71 @@
+<?php
+/**
+ * site settings js file.
+ *
+ * Handles javascript stuff related to stats function.
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     John Etherton <john at ethertontech.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Settings View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+
+// Check for a new version of the Ushahidi Software
+jQuery(document).ready(function() {
+
+	// Prevent an HTTP call if auto upgrading isn't enabled
+	<?php if (Kohana::config('config.enable_auto_upgrader') == TRUE): ?>
+
+	// Check if we need to upgrade this deployment of Ushahidi
+	// if we're on the dashbboard, check for a new version
+	jQuery.get("<?php echo url::site().'admin/upgrade/check_current_version' ?>", function(data){
+			jQuery('#need_to_upgrade').html(data);
+			jQuery('#need_to_upgrade').removeAttr("style");
+		});
+
+	<?php endif; ?>
+
+	showhide();
+
+	// onChange event handler for the alerts dropdown
+	$("#allow_alerts").change(function() {
+		showhide();
+	});
+		
+});
+
+
+function showhide() {
+	var allow_alerts = $("#allow_alerts").val();
+	if (parseInt(allow_alerts) == 1) {   
+		// Show the alerts email textbox
+		$("#alerts_selector").show('slow'); 
+
+		$("#siteForm").validate({
+			rules: {
+				alerts_email: {
+					required: true,
+					email: true
+				}
+			},
+			messages: {
+				alerts_email: {
+					required: "Please enter an alerts email address",
+					email: "Please enter a valid email address"
+				}
+			}
+		});
+	} else {
+		// Hide the alerts email textbox
+		$("#alerts_selector").remove("rules").hide('slow');
+		
+		$("#siteForm").unbind("submit");
+	}
+}
\ No newline at end of file
diff --git a/application/views/admin/settings/sms.php b/application/views/admin/settings/sms.php
new file mode 100644
index 0000000..429a140
--- /dev/null
+++ b/application/views/admin/settings/sms.php
@@ -0,0 +1,94 @@
+<?php 
+/**
+ * Sms view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+
+				<h2>
+					<?php admin::settings_subtabs("sms"); ?>
+				</h2>
+				<?php print form::open(); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								// print "<li>" . $error_description . "</li>";
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>				
+					<div class="head">
+						<h3><?php echo Kohana::lang('settings.sms.title');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->
+		
+					<div class="sms_holder">
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="Text Messages Sent From the System Will Use the Selected Provider. ***Provider text messaging rates may apply!">Default Sending Provider</a> <span>Provider text messaging rates may apply</span></h4>
+							<span class="sel-holder">
+								<?php print form::dropdown('sms_provider', $sms_provider_array, $form['sms_provider']); ?>
+							</span>
+						</div>
+
+						<div class="row" style="margin-top:20px;">
+							<h4>Enter all the phone numbers that users can use to send text messages into your system below.</h4>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('ui_main.phone');?> 1: <span><?php echo Kohana::lang('settings.sms.flsms_text_2');?></span></h4>
+							<?php print form::input('sms_no1', $form['sms_no1'], ' class="text title_2"'); ?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('ui_main.phone');?> 2: <span><?php echo Kohana::lang('settings.sms.flsms_text_2');?></span></h4>
+							<?php print form::input('sms_no2', $form['sms_no2'], ' class="text title_2"'); ?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('ui_main.phone');?> 3: <span><?php echo Kohana::lang('settings.sms.flsms_text_2');?></span></h4>
+							<?php print form::input('sms_no3', $form['sms_no3'], ' class="text title_2"'); ?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('ui_main.alert_url');?></h4>
+							<?php print form::dropdown('sms_alert_url', $alert_url_array, $form['sms_alert_url']); ?>
+						</div>
+						<span>
+							<?php echo Kohana::lang('settings.sms.alert_url_text');?>
+						</span>
+					</div>
+		
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/settings/twitter/main.php b/application/views/admin/settings/twitter/main.php
new file mode 100644
index 0000000..fa71325
--- /dev/null
+++ b/application/views/admin/settings/twitter/main.php
@@ -0,0 +1,115 @@
+<?php 
+/**
+ * Twitter Settings view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Facebook Settings View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+
+				<h2>
+					<?php admin::settings_subtabs("twitter"); ?>
+				</h2>
+				<?php print form::open(); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								// print "<li>" . $error_description . "</li>";
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.configuration_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>				
+					<div class="head">
+						<h3><?php echo Kohana::lang('settings.twitter.title');?></h3>
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->
+		
+					<div class="sms_holder">
+						<?php
+						if ( ! $form_error
+							AND ! empty($form['twitter_api_key'])
+							AND ! empty($form['twitter_api_key_secret'])
+							AND ! empty($form['twitter_token'])
+							AND ! empty($form['twitter_token_secret'])
+						)
+						{
+						?>
+							<div class="test_settings">
+								<div class="tab">
+									<ul>
+										<li><a
+										href="javascript:twitterTest();"><?php echo utf8::strtoupper(Kohana::lang('settings.test_settings'));?></a></li>
+										<li id="test_loading"></li>
+										<li id="test_status"></li>
+									</ul>
+								</div>
+							</div>
+						<?php
+						}
+						?>
+
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.twitter.description');?>:<br><a href="https://twitter.com/oauth_clients/" target="_blank">https://twitter.com/oauth_clients/</a></h4>
+							<h4>For instructions see <a
+							href="https://wiki.ushahidi.com/display/WIKI/Configuring+Twitter+on+a+deployment/"target="_blank">https://wiki.ushahidi.com/display/WIKI/Configuring+Twitter+on+a+deployment</a></h4>
+							<h4><?php echo Kohana::lang('settings.twitter.api_key');?>:</h4>
+							<?php print form::input('twitter_api_key', $form['twitter_api_key'], ' class="text long2"'); ?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.twitter.api_key_secret');?>:</h4>
+							<?php print form::input('twitter_api_key_secret',$form['twitter_api_key_secret'],'class="text long2"'); ?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.twitter.token');?>:</h4>
+							<?php print form::input('twitter_token', $form['twitter_token'], ' class="text long2"'); ?>
+						</div>
+						<div class="row">
+							<h4><?php echo Kohana::lang('settings.twitter.token_secret');?>:</h4>
+							<?php print form::input('twitter_token_secret',$form['twitter_token_secret'],'class="text long2"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.settings_twitter_configuration"); ?>"><?php echo Kohana::lang('settings.site.twitter_configuration');?></a></h4>
+							<div class="row">
+								<?php echo Kohana::lang('settings.site.twitter_hashtags');?>
+								<?php print form::input('twitter_hashtags', $form['twitter_hashtags'], ' class="text"'); ?>
+							</div>
+						</div>
+					</div>
+		
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/settings/twitter/twitter_js.php b/application/views/admin/settings/twitter/twitter_js.php
new file mode 100644
index 0000000..ea277ce
--- /dev/null
+++ b/application/views/admin/settings/twitter/twitter_js.php
@@ -0,0 +1,13 @@
+function twitterTest() {
+	$('#test_loading').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+	$('#test_status').html('');
+	$.post("<?php echo url::site() . 'admin/settings/test_twitter/' ?>", {  },
+		function(data){
+			if (data.status == 'success'){
+				$('#test_status').html(data.message);
+			} else {
+				$('#test_status').html(data.message);
+			}
+			$('#test_loading').html('');
+	  	}, "json");
+}
diff --git a/application/views/admin/stats/country.php b/application/views/admin/stats/country.php
new file mode 100644
index 0000000..2563139
--- /dev/null
+++ b/application/views/admin/stats/country.php
@@ -0,0 +1,103 @@
+<?php 
+/**
+ * Feedback view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2><?php echo $title; ?> <a href="<?php print url::site() ?>admin/stats/hits"><?php echo Kohana::lang('stats.visitor_summary');?></a> <a href="<?php print url::site() ?>admin/stats/country"><?php echo Kohana::lang('stats.country_breakdown');?></a> <a href="<?php print url::site() ?>admin/stats/reports"><?php echo Kohana::lang('stats.report_stats');?></a> <a href="<?php print url::site() ?>admin/stats/impact"><?php echo Kohana::lang('stats.category_impact');?></a> <a href="<?php print u [...]
+
+	<div class="content-wrap">
+	<h3><?php echo Kohana::lang('stats.country_breakdown');?></h3>
+	
+	<div id="time-period-selector">
+		<p>
+			<?php echo form::open('admin/stats/country/', array('method' => 'get', 'style' => "display: inline;")); ?>
+				<?php echo Kohana::lang('stats.choose_date_range');?>: <a href="<?php print url::site() ?>admin/stats/country/?range=30"><?php echo Kohana::lang('stats.time_range_1');?></a> <a href="<?php print url::site() ?>admin/stats/country/?range=90"><?php echo Kohana::lang('stats.time_range_2');?></a> <a href="<?php print url::site() ?>admin/stats/country/?range=180"><?php echo Kohana::lang('stats.time_range_3');?></a>  <a href="<?php print url::site() ?>admin/stats/country/"><?php echo Kohana [...]
+				<input type="text" class="dp" name="dp1" id="dp1" value="<?php echo $dp1; ?>" />  -   
+				<input type="text" class="dp" name="dp2" id="dp2" value="<?php echo $dp2; ?>" /> 
+				<input type="hidden" name="range" value="<?php echo $range; ?>" />
+				<input type="submit" value="Go →" class="button" />
+			<?php echo form::close(); ?>
+		</p>
+	</div>
+
+	<div class="chart-holder" style="height:220px;text-align:center;">
+	<img src="<?php echo $visitor_map; ?>" />
+	<?php if($failure != ''){ ?>
+		<div class="red-box">
+			<h3><?php echo Kohana::lang('stats.error');?></h3>
+			<ul><li><?php echo $failure; ?></li></ul>
+		</div>
+	<?php } ?>
+	</div>
+
+	<div class="stats-wrapper clearfix">
+		<div class="statistic first">
+			<h4><?php echo Kohana::lang('stats.countries');?></h4>
+			<p><?php echo $num_countries; ?></p>
+		</div>
+		<div class="statistic">
+			<h4><?php echo Kohana::lang('stats.unique_visitors');?></h4>
+			<p><?php echo $uniques; ?></p>
+		</div>
+		<div class="statistic">
+			<h4><?php echo Kohana::lang('stats.visits');?></h4>
+			<p><?php echo $visits; ?></p>
+		</div>
+		<div class="statistic">
+			<h4><?php echo Kohana::lang('stats.pageviews');?></h4>
+			<p><?php echo $pageviews; ?></p>
+		</div>
+	</div>
+
+	<!-- Left Column -->
+	<div class="two-col tc-left">
+		<div class="tabs">
+			<!-- tabset -->
+			<ul class="tabset">
+				<li><a class="active"><?php echo Kohana::lang('stats.unique_visitors');?></a></li>
+			</ul>
+			<div class="tab-boxes">
+
+				<div class="tab-box active-tab" id="unique-visitors">
+					<table class="table-graph generic-data">
+						<tr>
+							<th class="gdItem"><?php echo Kohana::lang('stats.country');?></th>
+							<th class="gdDesc"><?php echo Kohana::lang('stats.unique_visitors');?></th>
+						</tr>
+						<?php
+						foreach($countries as $name => $data){
+							?>
+							<tr>
+							<td class="gdItem"><img class="flag" src="<?php echo $data['icon']; ?>"/> <?php echo $name; ?></td>
+							<td class="gdDesc"><?php echo $data['count']; ?></td>
+							</tr>
+							<?php
+						}
+						?>
+					</table>
+				</div>
+
+			</div>
+		</div>
+	</div>
+
+	<!-- Right Column -->
+	<div class="two-col tc-right">
+		<!-- Nothing here yet -->
+	</div>
+	<div style="clear:both;"></div>
+	<br /><br />
+	</div>
+
+</div>
\ No newline at end of file
diff --git a/application/views/admin/stats/hits.php b/application/views/admin/stats/hits.php
new file mode 100644
index 0000000..5948ad9
--- /dev/null
+++ b/application/views/admin/stats/hits.php
@@ -0,0 +1,116 @@
+<?php 
+/**
+ * Feedback view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2><?php echo $title; ?> <a href="<?php print url::site() ?>admin/stats/hits"><?php echo Kohana::lang('stats.visitor_summary');?></a> <a href="<?php print url::site() ?>admin/stats/country"><?php echo Kohana::lang('stats.country_breakdown');?></a> <a href="<?php print url::site() ?>admin/stats/reports"><?php echo Kohana::lang('stats.report_stats');?></a> <a href="<?php print url::site() ?>admin/stats/impact"><?php echo Kohana::lang('stats.category_impact');?></a> <a href="<?php print u [...]
+	
+	<div class="content-wrap">
+	<h3><?php echo Kohana::lang('stats.visitor_summary');?></h3>
+	
+	<div id="time-period-selector">
+		<p>
+			<?php echo form::open('admin/stats/hits/', array('method' => 'get', 'style' => "display: inline;")); ?>
+				<?php echo Kohana::lang('stats.choose_date_range');?>: <a href="<?php print url::site() ?>admin/stats/hits/?range=30"><?php echo Kohana::lang('stats.time_range_1');?></a> <a href="<?php print url::site() ?>admin/stats/hits/?range=90"><?php echo Kohana::lang('stats.time_range_2');?></a> <a href="<?php print url::site() ?>admin/stats/hits/?range=180"><?php echo Kohana::lang('stats.time_range_3');?></a> <a href="<?php print url::site() ?>admin/stats/hits/"><?php echo Kohana::lang('stats [...]
+				<input type="text" class="dp" name="dp1" id="dp1" value="<?php echo $dp1; ?>" />  -   
+				<input type="text" class="dp" name="dp2" id="dp2" value="<?php echo $dp2; ?>" /> 
+				<input type="hidden" name="range" value="<?php echo $range; ?>" />
+				<input type="hidden" name="active_tab" value="<?php echo $active_tab; ?>" /> 
+				<input type="submit" value="Go →" class="button" />
+			<?php echo form::close(); ?>
+		</p>
+	</div>
+
+	<div class="chart-holder">
+		<?php echo $traffic_chart; ?>
+		<?php if($failure != ''){ ?>
+			<div class="red-box">
+				<h3><?php echo Kohana::lang('stats.error');?></h3>
+				<ul><li><?php echo $failure; ?></li></ul>
+			</div>
+		<?php } ?>
+	</div>
+
+	<div class="stats-wrapper clearfix">
+		<div class="statistic first">
+			<h4><?php echo Kohana::lang('stats.unique_visitors');?></h4>
+			<p><?php echo $uniques; ?></p>
+		</div>
+		<div class="statistic">
+			<h4><?php echo Kohana::lang('stats.visits');?></h4>
+			<p><?php echo $visits; ?></p>
+		</div>
+		<div class="statistic">
+			<h4><?php echo Kohana::lang('stats.pageviews');?></h4>
+			<p><?php echo $pageviews; ?></p>
+		</div>
+	</div>
+
+	<!-- Left Column -->
+	<div class="two-col tc-left">
+		<div class="tabs">
+			<!-- tabset -->
+			<ul class="tabset">
+				<li><a <?php if($active_tab == 'uniques') echo 'class="active"'; ?> href="<?php print url::site() ?>admin/stats/hits/?range=<?php echo $range; ?>&dp1=<?php echo $dp1; ?>&dp2=<?php echo $dp2; ?>&active_tab=uniques"><?php echo Kohana::lang('stats.unique_visitors');?></a></li>
+				<li><a <?php if($active_tab == 'visits') echo 'class="active"'; ?> href="<?php print url::site() ?>admin/stats/hits/?range=<?php echo $range; ?>&dp1=<?php echo $dp1; ?>&dp2=<?php echo $dp2; ?>&active_tab=visits"><?php echo Kohana::lang('stats.visits');?></a></li>
+				<li><a <?php if($active_tab == 'pageviews') echo 'class="active"'; ?> href="<?php print url::site() ?>admin/stats/hits/?range=<?php echo $range; ?>&dp1=<?php echo $dp1; ?>&dp2=<?php echo $dp2; ?>&active_tab=pageviews"><?php echo Kohana::lang('stats.pageviews');?></a></li>
+			</ul>
+
+			<div class="tab-boxes">
+			<?php
+			$labels = array();
+			foreach($raw_data as $label => $data_array) {
+				$activetabcss = '';
+				if($label == $active_tab) $activetabcss = 'active-tab';
+				?>
+				<div class="tab-box <?php echo $activetabcss; ?>" id="<?php echo $label; ?>">
+				<table class="table-graph horizontal-bar">
+				<?php
+				$data_array = array_reverse($data_array,true);
+				foreach($data_array as $timestamp => $count) {
+					$date = date('M jS, Y',($timestamp/1000));
+					$percentage = 0;
+					if($$label != 0) $percentage = round((($count / $$label) * 100),1);
+					?>
+					<tr>
+						<td class="hbItem"><?php echo $date; ?></td>
+						<td class="hbDesc"><span style="width:<?php echo $percentage; ?>%" class="stat-bar"> </span><span class="stat-percentage"><?php echo $percentage; ?>% (<?php echo $count; ?>)</span></td>
+					</tr>
+					<?php
+				}
+				?>
+				</table>
+				</div>
+				<?php
+			}
+			?>
+			</div>
+
+		</div>
+	</div>
+
+	<!-- Right Column -->
+	<div class="two-col tc-right glossary">
+		<h4><?php echo Kohana::lang('stats.glossary');?></h4>
+		<div class="terms">
+			<p><strong><?php echo Kohana::lang('stats.unique_visitors');?>:</strong> <?php echo Kohana::lang('stats.unique_visitors_description');?>.</p>
+			<p><strong><?php echo Kohana::lang('stats.visits');?>:</strong> <?php echo Kohana::lang('stats.visits_description');?>.</p>
+			<p><strong><?php echo Kohana::lang('stats.pageviews');?>:</strong> <?php echo Kohana::lang('stats.pageviews_description');?>.</p>
+		</div>
+	</div>
+	<div style="clear:both;"></div>
+	<br /><br />
+
+	</div>
+</div>
diff --git a/application/views/admin/stats/impact.php b/application/views/admin/stats/impact.php
new file mode 100644
index 0000000..a7c194c
--- /dev/null
+++ b/application/views/admin/stats/impact.php
@@ -0,0 +1,61 @@
+<?php 
+/**
+ * Feedback view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2><?php echo $title; ?> <a href="<?php print url::site() ?>admin/stats/hits"><?php echo Kohana::lang('stats.visitor_summary');?></a> <a href="<?php print url::site() ?>admin/stats/country"><?php echo Kohana::lang('stats.country_breakdown');?></a> <a href="<?php print url::site() ?>admin/stats/reports"><?php echo Kohana::lang('stats.report_stats');?></a> <a href="<?php print url::site() ?>admin/stats/impact"><?php echo Kohana::lang('stats.category_impact');?></a> <a href="<?php print u [...]
+	
+	<div class="content-wrap clearfix">
+        <h3><?php echo Kohana::lang('stats.category_impact');?></h3>
+        
+        <div id="time-period-selector">
+			<p>
+				<?php echo form::open('admin/stats/impact/', array('method' => 'get', 'style' => "display: inline;")); ?>
+					<?php echo Kohana::lang('stats.choose_date_range');?>: <a href="<?php print url::site() ?>admin/stats/impact/?range=30"><?php echo Kohana::lang('stats.time_range_1');?></a> <a href="<?php print url::site() ?>admin/stats/impact/?range=90"><?php echo Kohana::lang('stats.time_range_2');?></a> <a href="<?php print url::site() ?>admin/stats/impact/?range=180"><?php echo Kohana::lang('stats.time_range_3');?></a> <a href="<?php print url::site() ?>admin/stats/impact/"><?php echo Kohana::la [...]
+					<input type="text" class="dp" name="dp1" id="dp1" value="<?php echo $dp1; ?>" />  -   
+					<input type="text" class="dp" name="dp2" id="dp2" value="<?php echo $dp2; ?>" />
+					<input type="hidden" name="range" value="<?php echo $range; ?>" />
+					<input type="submit" value="Go →" class="button" />
+				<?php echo form::close(); ?>
+			</p>
+		</div>
+        
+        <!-- Left Column -->
+        <div class="two-col tc-left reports-charts">
+        <p><?php echo Kohana::lang('stats.category_impact_description');?>.</p>
+            <div id="impact_info2" class="impact_hidden">
+                <div id="impact_legend2"> </div>
+                <div id="impact_message2"><?php echo Kohana::lang('stats.legend');?></div>
+            </div>
+            <div id="impact_placeholder"></div>
+            <div id="impact_chart"></div>
+            
+        </div>
+        
+        <!-- Right Column -->
+        <div class="two-col tc-right stats-sidebar">
+        	<div class="stats-wrapper clearfix">
+                <div class="statistic first">
+                    <h4><?php echo Kohana::lang('stats.reports');?></h4>
+                    <p><?php echo $num_reports; ?></p>
+                </div>
+                <div class="statistic">
+                    <h4><?php echo Kohana::lang('stats.categories');?></h4>
+                    <p><?php echo $num_categories; ?></p>
+                </div>
+            </div>
+            <div style="clear:both;"></div>
+		</div>
+</div>
+
diff --git a/application/views/admin/stats/main.php b/application/views/admin/stats/main.php
new file mode 100644
index 0000000..d00a121
--- /dev/null
+++ b/application/views/admin/stats/main.php
@@ -0,0 +1,38 @@
+<?php 
+/**
+ * Feedback view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2><?php echo $title; ?> 
+		<a href="<?php print url::site() ?>admin/stats/hits"><?php echo Kohana::lang('stats.hit_summary');?></a> 
+		<a href="<?php print url::site() ?>admin/stats/country"><?php echo Kohana::lang('stats.country_breakdown');?></a> 
+		<a href="<?php print url::site() ?>admin/stats/reports"><?php echo Kohana::lang('stats.report_stats');?></a> 
+		<a href="<?php print url::site() ?>admin/stats/impact"><?php echo Kohana::lang('stats.category_impact');?></a>
+	</h2>
+	
+	<div>
+		<?php
+			if($stat_id == 0){ // No stat account created
+		?>
+				<h1 style="text-align:center"><?php echo Kohana::lang('stats.stats_not_setup');?></h1>
+		<?php
+			}else{
+		?>
+		<?php echo Kohana::lang('stats.description');?>.
+		<?php
+			}
+		?>
+	</div>
+	
+</div>
diff --git a/application/views/admin/stats/punchcard.php b/application/views/admin/stats/punchcard.php
new file mode 100644
index 0000000..69aeb01
--- /dev/null
+++ b/application/views/admin/stats/punchcard.php
@@ -0,0 +1,37 @@
+<?php 
+/**
+ * Feedback view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2><?php echo $title; ?> <a href="<?php print url::site() ?>admin/stats/hits"><?php echo Kohana::lang('stats.visitor_summary');?></a> <a href="<?php print url::site() ?>admin/stats/country"><?php echo Kohana::lang('stats.country_breakdown');?></a> <a href="<?php print url::site() ?>admin/stats/reports"><?php echo Kohana::lang('stats.report_stats');?></a> <a href="<?php print url::site() ?>admin/stats/impact"><?php echo Kohana::lang('stats.category_impact');?></a> <a href="<?php print u [...]
+
+	<div class="content-wrap">
+	<h3><?php echo Kohana::lang('stats.report_punchcard');?></h3>
+	
+	<div>
+		<br/><br/>
+		<img src="<?php echo $chart_url; ?>" alt="Report Punchcard" />
+	</div>
+	
+
+
+	<!-- Right Column -->
+	<div class="two-col tc-right">
+		<!-- Nothing here yet -->
+	</div>
+	<div style="clear:both;"></div>
+	<br /><br />
+	</div>
+
+</div>
\ No newline at end of file
diff --git a/application/views/admin/stats/reports.php b/application/views/admin/stats/reports.php
new file mode 100644
index 0000000..c0b13a8
--- /dev/null
+++ b/application/views/admin/stats/reports.php
@@ -0,0 +1,116 @@
+<?php 
+/**
+ * Feedback view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2><?php echo $title; ?> <a href="<?php print url::site() ?>admin/stats/hits"><?php echo Kohana::lang('stats.visitor_summary');?></a> <a href="<?php print url::site() ?>admin/stats/country"><?php echo Kohana::lang('stats.country_breakdown');?></a> <a href="<?php print url::site() ?>admin/stats/reports"><?php echo Kohana::lang('stats.report_stats');?></a> <a href="<?php print url::site() ?>admin/stats/impact"><?php echo Kohana::lang('stats.category_impact');?></a> <a href="<?php print u [...]
+    
+    <div class="content-wrap clearfix">
+        <h3><?php echo Kohana::lang('stats.reports_statistics');?></h3>
+        
+        <div id="time-period-selector">
+			<p>
+				<?php echo form::open('admin/stats/reports/', array('method' => 'get', 'style' => "display: inline;")); ?>
+					<?php echo Kohana::lang('stats.choose_date_range');?>: <a href="<?php print url::site() ?>admin/stats/reports/?range=30"><?php echo Kohana::lang('stats.time_range_1');?></a> <a href="<?php print url::site() ?>admin/stats/reports/?range=90"><?php echo Kohana::lang('stats.time_range_2');?></a> <a href="<?php print url::site() ?>admin/stats/reports/?range=180"><?php echo Kohana::lang('stats.time_range_3');?></a> <a href="<?php print url::site() ?>admin/stats/reports/"><?php echo Kohana [...]
+					<input type="text" class="dp" name="dp1" id="dp1" value="<?php echo $dp1; ?>" />  -   
+					<input type="text" class="dp" name="dp2" id="dp2" value="<?php echo $dp2; ?>" /> 
+					<input type="hidden" name="range" value="<?php echo $range; ?>" />
+					<input type="submit" value="Go →" class="button" />
+				<?php echo form::close(); ?>
+			</p>
+		</div>
+		
+        <!-- Left Column -->
+        <div class="two-col tc-left reports-charts">
+        	
+            <h4><?php echo Kohana::lang('stats.reports_categories');?></h4>
+            <p>
+            	<div style="float:left;"><?php echo $reports_chart; ?></div>
+            	<div style="float:left;">
+            		<table>
+            			<?php
+            			foreach($reports_per_cat as $category_id => $count){
+	            			?>
+	            			<tr>
+	            				<td><div id="little-color-box" style="background-color:#<?php echo $category_data[$category_id]['category_color']; ?>"> </div></td>
+	            				<td><?php echo $category_data[$category_id]['category_title']; ?></td>
+	            				<td style="padding-left:25px;"><?php echo $count; ?></td>
+	            			</tr>
+	            			<?php
+            			}
+            			?>
+            		</table>
+            	</div>
+            	<div style="clear:both;"></div>
+            </p>
+
+            <h4><?php echo Kohana::lang('stats.reports_status');?></h4>
+            <p>
+
+            	<div style="float:left;"><?php echo $report_status_chart_ver; ?></div>
+				<div style="float:left;">
+					<table>
+						<tr>
+							<td><div id="little-color-box" style="background-color:#0E7800"> </div></td>
+							<td><?php echo Kohana::lang('stats.verified');?></td>
+							<td style="padding-left:25px;"><?php echo $verified; ?></td>
+						</tr>
+						<tr>
+							<td><div id="little-color-box" style="background-color:#FFCF00"> </div></td>
+							<td><?php echo Kohana::lang('stats.unverified');?></td>
+							<td style="padding-left:25px;"><?php echo $unverified; ?></td>
+						</tr>
+					</table>
+				</div>
+            
+				<div style="float:left;margin-left:100px;"><?php echo $report_status_chart_app; ?></div>
+				<div style="float:left;">
+					<table>
+						<tr>
+							<td><div id="little-color-box" style="background-color:#0E7800"> </div></td>
+							<td><?php echo Kohana::lang('stats.approved');?></td>
+							<td style="padding-left:25px;"><?php echo $approved; ?></td>
+						</tr>
+						<tr>
+							<td><div id="little-color-box" style="background-color:#FFCF00"> </div></td>
+							<td><?php echo Kohana::lang('stats.unapproved');?></td>
+							<td style="padding-left:25px;"><?php echo $unapproved; ?></td>
+						</tr>
+					</table>
+				</div>
+				<div style="clear:both;"></div>
+
+            </p> 
+        </div>
+
+        <!-- Right Column -->
+        <div class="two-col tc-right stats-sidebar">
+        	<div class="stats-wrapper clearfix">
+                <div class="statistic first">
+                    <h4><?php echo Kohana::lang('stats.reports');?></h4>
+                    <p><?php echo $num_reports; ?></p>
+                </div>
+                <div class="statistic">
+                    <h4><?php echo Kohana::lang('stats.categories');?></h4>
+                    <p><?php echo $num_categories; ?></p>
+                </div>
+                
+            </div>
+            <div style="clear:both;"></div>
+            
+        </div>
+    </div>
+
+</div>
+
diff --git a/application/views/admin/stats/stats_js.php b/application/views/admin/stats/stats_js.php
new file mode 100644
index 0000000..afefdf5
--- /dev/null
+++ b/application/views/admin/stats/stats_js.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Stats js file.
+ *
+ * Handles javascript stuff related to stats function.
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+// Date Picker JS
+jQuery(document).ready(function() {
+	jQuery("#dp1").datepicker({ 
+	    showOn: "both",
+		dateFormat: "yy-mm-dd",
+	    buttonImage: "<?php echo url::file_loc('img'); ?>media/img/icon-calendar.gif", 
+	    buttonImageOnly: true 
+	});
+	
+	jQuery("#dp2").datepicker({ 
+	    showOn: "both",
+		dateFormat: "yy-mm-dd",
+	    buttonImage: "<?php echo url::file_loc('img'); ?>media/img/icon-calendar.gif", 
+	    buttonImageOnly: true 
+	});
+	
+	
+<?php
+	// Prevent an HTTP call if auto upgrading isn't enabled and
+	//   make sure we are looking at the dashboard
+	if (Kohana::config('config.enable_auto_upgrader') == TRUE
+		AND Router::$controller == 'dashboard'){
+?>
+
+// Check for a new version of the Ushahidi Software
+jQuery(document).ready(function() {
+	// Check if we need to upgrade this deployment of Ushahidi
+	//   if we're on the dashboard, check for a new version
+	jQuery.get("<?php echo url::site().'admin/upgrade/check_current_version' ?>", function(data){
+			jQuery('#need_to_upgrade').html(data);
+			jQuery('#need_to_upgrade').removeAttr("style");
+		});
+		
+});
+
+<?php
+	}
+?>
+	
+	
+});
\ No newline at end of file
diff --git a/application/views/admin/upgrade/upgrade.php b/application/views/admin/upgrade/upgrade.php
new file mode 100644
index 0000000..de556ad
--- /dev/null
+++ b/application/views/admin/upgrade/upgrade.php
@@ -0,0 +1,90 @@
+<?php 
+/**
+ * Upgrade overview view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module	   API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2><?php echo $title; ?></h2>
+	<?php if( $release_version == Kohana::config('settings.ushahidi_version') ) { ?>
+	<div class="settings_holder">
+		<h4><?php print Kohana::lang('upgrade.upgrade_status_info'); ?></h4>
+		<p><?php print"You do not need to upgrade."?></p>
+	</div>
+	<?php } ?>
+	<?php if( $release_version > Kohana::config('settings.ushahidi_version') ) { ?>
+	<div class="head">
+		<h4 class="version"><?php print Kohana::lang('upgrade.upgrade_title_text', array($current_version, $current_db_version, $environment)); ?></h4>
+	</div>
+	<div class="head">
+		<h4><?php print Kohana::lang('upgrade.upgrade_available') ?></h4>
+	</div>
+	<div class="settings_holder">
+		<strong><u><?php print Kohana::lang('upgrade.ushahidi_release_version', array($release_version)); ?></u></strong>
+		<?php if (isset($critical)) echo "(<strong style=\"color:#FF0000\">".Kohana::lang('ui_admin.critical_upgrade')."</strong>)";?>
+		<?php if (is_array($changelogs)) { ?><br />
+		<?php echo Kohana::lang('upgrade.upgrade_db_version', array($release_db_version)); ?>
+		<ul>
+			<?php foreach ( $changelogs as $changelog ) { ?>
+			<li><?php print $changelog ?></li>
+			<?php } ?>
+		</ul>
+		<?php } ?>
+	</div>
+	
+	<div class="head">
+		<h4><?php print Kohana::lang('upgrade.upgrade_automatic'); ?> (<?php print Kohana::lang('upgrade.beta'); ?>)</h4>
+	</div>
+	<div class="settings_holder">
+		<?php print form::open(NULL, array('id' => 'upgradeMain', 'name' => 'upgradeMain')); ?>
+			<p>
+				<?php print form::label('chk_db_backup_box', Kohana::lang('upgrade.upgrade_db_text_5'));?>
+				<?php print form::checkbox('chk_db_backup_box', '1', 1);?>
+			</p>
+			<input type="button" id="upgrade" name="button" value="<?php echo Kohana::lang('upgrade.upgrade_continue_btn_text');?>" class="login_btn" onClick="showFTP();" />
+			<div class="report-form ftp-settings" id="ftp_settings">
+				<div class="row">
+				<h4 style="padding-top:0;"><?php print Kohana::lang('upgrade.upgrade_ftp_text'); ?></h4>
+				</div>
+				<div class="row">
+					<h4><?php print Kohana::lang('upgrade.upgrade_ftp_hostname'); ?></h4>
+					<?php print form::input('ftp_server', $ftp_server, ' class="text title_2"'); ?>
+				</div>
+				<div class="row">
+					<h4><?php print Kohana::lang('upgrade.upgrade_ftp_username'); ?></h4>
+					<?php print form::input('ftp_user_name', $ftp_user_name, ' class="text title_2"'); ?>
+				</div>
+				<div class="row">
+					<h4><?php print Kohana::lang('upgrade.upgrade_ftp_password'); ?></h4>
+					<?php print form::password('ftp_user_pass', "", ' class="text title_2"'); ?>
+				</div>
+				<div class="row" style="clear:both;margin-top:10px;">
+					<input type="submit" id="upgrade" name="submit" value="<?php echo Kohana::lang('upgrade.upgrade_continue_btn_text');?>" class="login_btn" />
+				</div>
+			</div>
+		<?php print form::close();?>
+	</div>
+	
+	<div class="head">
+		<h4><?php print Kohana::lang('upgrade.upgrade_manual');?></h4>
+	</div>
+	<div class="settings_holder">
+		<p><?php echo Kohana::lang('upgrade.upgrade_text_1');?>.</p>
+		<p><?php echo Kohana::lang('upgrade.upgrade_text_2');?></p>
+		<?php if(isset($download)) { $url = "<a href=\"$download\">$download</a></dd>"?>
+
+		<p><?php echo Kohana::lang('upgrade.upgrade_text_3')." ".$url;?></p><?php } ?>
+
+		<p><?php echo Kohana::lang('upgrade.upgrade_text_4');?></p>
+	</div>	
+	<?php } ?>
+</div>
diff --git a/application/views/admin/upgrade/upgrade_database.php b/application/views/admin/upgrade/upgrade_database.php
new file mode 100644
index 0000000..e05bda6
--- /dev/null
+++ b/application/views/admin/upgrade/upgrade_database.php
@@ -0,0 +1,42 @@
+<h2><?php print Kohana::lang('upgrade.upgrade_db_title'); ?></h2>
+<div class="table-holder">
+	<?php if ($form_error): ?>
+		<!-- red-box -->
+		<div class="red-box">
+			<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+			<ul>
+				<?php
+					foreach ($errors as $error_item => $error_description)
+					{
+						print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+					}
+				?>
+			</ul>
+		</div>
+	<?php elseif ($form_saved): ?>
+		<!-- green-box -->
+		<div class="green-box" id="submitStatus" style="margin: 0;">
+			<h3><?php echo $message; ?></h3>
+			<div class="btns"><ul><li><a href="<?php echo url::site('admin'); ?>"><?php echo Kohana::lang('upgrade.upgrade_continue_btn_text');?></a></li></ul></div>
+		</div>
+		
+	<?php elseif ( Kohana::config('version.ushahidi_db_version') > Kohana::config('settings.db_version') ): ?>
+		<p><?php print Kohana::lang('upgrade.upgrade_db_info', Kohana::config('version.ushahidi_db_version') ); ?></p>
+		<?php print form::open(NULL, array('id' => 'upgrade-db', 'name' => 'upgrade-db')); ?>
+		<p>
+			<?php print form::label('chk_db_backup_box', Kohana::lang('upgrade.upgrade_db_text_5'));?>
+			<?php print form::checkbox('chk_db_backup_box', '1', 1);?>
+		</p>
+		<p>
+			<?php print form::submit('submit', Kohana::lang('upgrade.upgrade_db_btn_text')); ?>
+		</p>
+		<?php print form::close(); ?>
+	<?php else: ?>
+		<p><?php print Kohana::lang('upgrade.upgrade_db_up_to_date'); ?></p>
+	<?php endif; ?>
+	<small><?php print Kohana::lang('upgrade.upgrade_title_text', array($current_version, $current_db_version, $environment)); ?></small>
+</div>
+<style>
+	/* Ugly hack to hide nav */
+	.nav-holder, .info-nav {display:none;}
+</style>
\ No newline at end of file
diff --git a/application/views/admin/upgrade/upgrade_js.php b/application/views/admin/upgrade/upgrade_js.php
new file mode 100644
index 0000000..741313b
--- /dev/null
+++ b/application/views/admin/upgrade/upgrade_js.php
@@ -0,0 +1,20 @@
+/**
+ * Upgrade js file.
+ *
+ * Handles javascript stuff related to upgrade functions.
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+// Ajax Submission
+function showFTP()
+{
+	$("#ftp_settings").toggle(400);
+}
\ No newline at end of file
diff --git a/application/views/admin/upgrade/upgrade_status.php b/application/views/admin/upgrade/upgrade_status.php
new file mode 100644
index 0000000..e816c16
--- /dev/null
+++ b/application/views/admin/upgrade/upgrade_status.php
@@ -0,0 +1,26 @@
+<?php 
+/**
+ * Upgrade status view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<div class="bg">
+	<h2><?php echo $title; ?></h2>
+	
+	<div class="head">
+		<h4><?php echo Kohana::lang('upgrade.upgrading');?>...</h4>
+	</div>
+	<div class="settings_holder">
+		<div id="upgrade_log"></div>
+	</div>
+</div>
diff --git a/application/views/admin/upgrade/upgrade_status_js.php b/application/views/admin/upgrade/upgrade_status_js.php
new file mode 100644
index 0000000..3e7b79f
--- /dev/null
+++ b/application/views/admin/upgrade/upgrade_status_js.php
@@ -0,0 +1,34 @@
+var i=0;
+var backup = <?php echo ($backup) ? 1 : 0; ?>;
+$(document).ready(function() {
+	upgrade_error = false;
+	for (i=0; i < 8; i++) {
+		if (backup == 0 && i == 4){
+			i = i + 1;
+		} else if (backup == 1 && i == 5) {
+			i = i + 1;
+		}
+		$.ajax({
+			url: "<?php echo url::site()."admin/upgrade/status/";?>"+i,
+			async: false,
+			dataType: "json",
+			success: function(data) {
+				if (data.status == 'success'){
+					$('#upgrade_log').append("<div class=\"upgrade_log_message log_success\">"+data.message+"</div>");
+				} else if (data.status == 'error') {
+					$('#upgrade_log').append("<div class=\"upgrade_log_message log_error\">"+data.message+" <a href=\"<?php echo $log_file; ?>\" target=\"_blank\"><?php echo Kohana::lang('upgrade.log_file'); ?></a></div>");
+					upgrade_error = true;
+				} else {
+					upgrade_error = true;
+				}
+			},
+			error: function(data) {
+				upgrade_error = true;
+			}
+		});
+			
+		if (upgrade_error) {
+			break;
+		}
+	}
+});
\ No newline at end of file
diff --git a/application/views/admin/upgrade/upgrade_table.php b/application/views/admin/upgrade/upgrade_table.php
new file mode 100644
index 0000000..23f1bfe
--- /dev/null
+++ b/application/views/admin/upgrade/upgrade_table.php
@@ -0,0 +1,33 @@
+<?php 
+/**
+ * Upgrade table upgrade view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div id="ushahidi_install_container" class="advanced">
+<div class="bg">
+	<h2><?php echo $title; ?></h2>
+	<div class="table-holder">
+   		<p>I have detected that you have an old database version.</p>
+       	<p>So, I'm going to upgrade your database from version <?php print $db_version; ?> to the newest database.</p>
+       	<p>Click on the "Upgrade" button and just chilax as I perform the magic.</p>
+       	<p>Oh, also if you want me to backup your database, just thick the check button below and I will do that for you in a breeze.</p>
+       	<?php print form::open(NULL, array('id' => 'upgradeDb', 'name' => 'upgradeDb')); ?>
+       	<p>
+       		<?php print form::label('chk_db_backup_box', 'Backup database?(<strong style="color:#FF0000;"> Highly recommended. </strong>)');?>
+		   	<?php print form::checkbox('chk_db_backup_box', '1');?>
+		</p>	
+        <p><input type="submit" id="upgrade" name="submit" value="<?php echo Kohana::lang('upgrade.upgrade_db_btn_text');?>" class="login_btn" /></p>                
+        <?php print form::close();?>
+	</div>
+</div>
+</div>
diff --git a/application/views/admin/users/edit.php b/application/views/admin/users/edit.php
new file mode 100644
index 0000000..84cf2e6
--- /dev/null
+++ b/application/views/admin/users/edit.php
@@ -0,0 +1,148 @@
+<?php
+/**
+ * Edit User
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Edit User View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::user_subtabs("users_edit", $display_roles); ?>
+				</h2>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.profile_saved');?></h3>
+					</div>
+				<?php
+				}
+				?>
+				<?php print form::open(); ?>
+				<div class="report-form">
+					<div class="head">
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->
+					<div class="sms_holder">
+						<div class="l-column inverse">
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.full_name');?> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+								<?php print form::input('name', $form['name'], ' class="text long2"'); ?>
+							</div>
+
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.email');?> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+								<?php print form::input('email', $form['email'], ' class="text long2"'); ?>
+							</div>
+
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.role');?></h4>
+								<?php
+								if ($user AND $user->loaded AND $user->id == 1)
+								{
+									print form::dropdown('role', $role_array, $form['role'], ' readonly="readonly"');
+								}
+								else
+								{
+									print form::dropdown('role', $role_array, $form['role']);
+								}
+								?>
+							</div>
+
+							<div class="row">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_public_url"); ?>"><?php echo Kohana::lang('ui_main.public_profile_url');?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+							<span style="float:left;"><?php echo url::site().'profile/user/'; ?></span>
+								<?php print form::input('username', $form['username'], ' class="text short3"'); ?>
+							</div>
+
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.receive_notifications');?>?</h4>
+								<?php print form::dropdown('notify', $yesno_array, $form['notify']); ?>
+							</div>
+
+							<?php if ($user_id == FALSE) { ?>
+
+							<div class="row">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_new_users_password"); ?>"><?php echo Kohana::lang('ui_main.password'); ?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+								<?php print form::password('password', '', ' class="text"'); ?>
+							</div>
+
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.password_again');?></h4>
+								<?php print form::password('password_again', $form['password_again'], ' class="text"'); ?>
+							</div>
+
+							<?php }elseif(kohana::config('riverid.enable') == FALSE){ ?>
+
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_admin.new_password');?></h4>
+								<?php print form::password('new_password', '', ' class="text long2"'); ?>
+							</div>
+						</div>
+
+						<?php
+						if ($user AND $user->loaded)
+						{
+						?>
+						<div class="r-column inverse">
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_admin.user_no_logins');?></h4>
+								<p class="bold_desc"><?php echo $user->logins; ?></p>
+							</div>
+
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_admin.user_last_login');?></h4>
+								<p class="bold_desc"><?php echo date("m/d/Y g:ia", $user->last_login); ?> <?php echo date_default_timezone_get(); ?></p>
+							</div>
+
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_admin.user_confirmed_account');?></h4>
+								<p class="bold_desc"><?php echo Kohana::lang('ui_admin.' . ($user->confirmed ? "yes" : "no"));?></p>
+							</div>
+
+						</div>
+						<?php
+						}
+						?>
+
+						<?php } ?>
+
+                        <?php
+                        // users_form_admin - add content to users from
+                        Event::run('ushahidi_action.users_form_admin', $id);
+                        ?>
+					</div>
+
+					<div class="simple_border"></div>
+
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/admin/users/main.php b/application/views/admin/users/main.php
new file mode 100644
index 0000000..acd4d8f
--- /dev/null
+++ b/application/views/admin/users/main.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Users view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::user_subtabs("users", $display_roles); ?>
+				</h2>
+				<!-- report-table -->
+				<div class="report-form">
+					<!-- report-table -->
+					<?php print form::open(NULL,array('id' => 'userMain', 'name' => 'userMain')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="user_id_action" id="user_id_action" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1">
+											 
+										</th>
+										<th class="col-2">
+											<?php echo Kohana::lang('ui_admin.header_user'); ?>
+										</th>
+
+										<?php
+											//Add header item to users listing page
+											Event::run('ushahidi_action.users_listing_header');
+										?>
+
+										<th class="col-2">
+											<?php echo Kohana::lang('ui_admin.header_role');?>
+										</th>
+										<th class="col-4">
+											<?php echo Kohana::lang('ui_admin.header_actions')?>
+										</th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4">
+											<?php echo $pagination; ?>
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3>
+													<?php echo Kohana::lang('ui_admin.no_result_display_msg');?>
+												</h3>
+											</td>
+										</tr>
+									<?php
+									}
+									foreach ($users as $user)
+									{
+										$user_id = $user->id;
+										$username = $user->username;
+										$password = $user->password;
+										$name = $user->name;
+										$email = $user->email;
+
+										// Show the highest role, defaulting to "none"
+										$role = Kohana::lang('ui_main.none');
+										foreach ($user->roles as $user_role) {
+											$role = $user_role->name;
+										}
+										?>
+										<tr>
+
+											<td class="col-1">
+												 
+											</td>
+											<td class="col-2">
+												<div class="post">
+													<h4>
+														<a href="<?php echo url::site() . 'admin/users/edit/' . $user_id; ?>">
+															<?php echo html::specialchars($name); ?>
+														</a>
+													</h4>
+												</div>
+												<ul class="info">
+													<li class="none-separator">
+														<?php echo Kohana::lang('ui_main.email');?>: <strong><?php echo html::specialchars($email); ?></strong>
+													</li>
+												</ul>
+											</td>
+																						<?php
+												//Add item to users listing page
+												Event::run('ushahidi_action.users_listing_item',$user_id);
+											?>
+
+											<td class="col-3">
+												<?php echo utf8::strtoupper($role); ?>
+											</td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="<?php echo url::site() . 'admin/users/edit/' . $user_id; ?>">
+														<?php echo Kohana::lang('ui_admin.edit_action');?>
+														</a></li>
+		<?php if($user_id != 1) { ?>
+		<li><a href="javascript:userAction('d','DELETE','<?php echo(rawurlencode($user_id)); ?>')" class="del">
+														<?php echo Kohana::lang('ui_admin.delete_action');?>
+														</a></li>
+		<?php } ?>
+												</ul>
+											</td>
+										</tr>
+										<?php
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+			</div>
diff --git a/application/views/admin/users/roles.php b/application/views/admin/users/roles.php
new file mode 100755
index 0000000..c9a08da
--- /dev/null
+++ b/application/views/admin/users/roles.php
@@ -0,0 +1,182 @@
+<?php 
+/**
+ * Roles view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Roles View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+		<div class="bg">
+			<h2>
+				<?php admin::user_subtabs("roles", $display_roles); ?>
+			</h2>
+
+			<?php if ($form_error): ?>
+				<!-- red-box -->
+				<div class="red-box">
+					<h3><?php echo Kohana::lang('ui_admin.error_msg');?></h3>
+					<ul>
+					<?php foreach ($errors as $error_item => $error_description) { ?>
+						<?php if ($error_description): ?>
+						<li><?php echo $error_description; ?></li>
+						<?php endif; ?>
+					<?php } ?>
+					</ul>
+				</div>
+			<?php endif; ?>
+			<?php if ($form_saved): ?>
+				<!-- green-box -->
+				<div class="green-box">
+					<h3>
+						<?php echo $form_action; ?>!
+					</h3>
+				</div>
+			<?php endif; ?>
+				
+			<!-- tabs -->
+			<div class="tabs">
+				<a name="add"></a>
+				<!-- tabset -->
+				<ul class="tabset">
+					<li>
+						<a href="#" class="active" onclick="show_addedit(true)">
+							<?php echo Kohana::lang('ui_admin.header_add_edit'); ?>
+						</a>
+					</li>
+				</ul>
+				<!-- tab -->
+				<div class="tab" id="addedit" style="display: none">
+					<?php print form::open(NULL, array('id' => 'rolesMain','name' => 'rolesMain')); ?>
+					<input type="hidden" name="action" id="action" value="a"/>
+					<input type="hidden" id="role_id" name="role_id" value=""/>
+					<div class="tab_form_item">
+						<strong><?php echo Kohana::lang('ui_main.name');?>:</strong><br />
+						<?php print form::input('name', '', ' class="text"'); ?>
+					</div>
+					<div class="tab_form_item">
+						<strong><?php echo Kohana::lang('ui_main.description');?>:</strong><br />
+						<?php print form::input('description', '', ' class="text long"'); ?>
+					</div>
+					<div class="tab_form_item">
+						<strong>
+							<a href="#" class="tooltip" style="background-position-y:0px" title="<?php echo Kohana::lang("tooltips.settings_access_level"); ?>">
+								<?php echo  Kohana::lang('ui_admin.access_level'); ?>: </a></h4>
+						<?php print form::input('access_level','', ' class="text"'); ?>
+					</div>
+					<div style="clear:both;"></div>
+					<div class="tab_form_item">
+					<?php
+					$i = 0;
+					foreach ($permissions as $permission_id => $permission_name)
+					{
+						echo "<div style=\"width:200px;float:left;margin-bottom:3px;\">";
+						echo form::checkbox('permissions[]', $permission_id, FALSE, "id='permission_$permission_name'");
+						echo form::label("permission_$permission_name", Kohana::lang('permissions.'.$permission_name));
+						echo "     ";
+						echo "</div>";
+						$i++;
+					}
+					?>
+					</div>
+					<div style="clear:both;"></div>
+					<div class="tab_form_item">
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<?php print form::close(); ?>			
+				</div>
+			</div>			
+			
+			
+			<!-- report-table -->
+			<div class="report-form">
+				<?php print form::open(NULL,array('id' => 'roleListing', 'name' => 'roleListing')); ?>
+					<input type="hidden" name="action" id="role_action_main" value="">
+					<input type="hidden" name="role_id" id="role_id_main" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1">
+										 
+									</th>
+									<th class="col-2">
+										<?php echo Kohana::lang('ui_admin.header_role'); ?>
+									</th>
+									<th class="col-2">
+										 
+									</th>
+									<th class="col-4">
+										<?php echo Kohana::lang('ui_admin.header_actions')?>
+									</th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										 
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								foreach ($roles as $role)
+								{
+									$role_id = $role->id;
+									$name = $role->name;
+									$description = $role->description;
+									$access_level = $role->access_level;
+									$role_permissions = array();
+									foreach($role->permissions as $perm)
+									{
+										$role_permissions[] = $perm->name;
+									}
+									?>
+									<tr>
+										
+										<td class="col-1">
+											 
+										</td>
+										<td class="col-2">
+											<div class="post">
+												<h4><?php echo utf8::strtoupper($name); ?></h4>
+												<p><?php echo $description; ?></p>
+											</div>
+										</td>
+										<td class="col-3"> </td>
+										<td class="col-4">
+											<?php if($role_id == 1 OR $role_id == 3 OR $role_id == 4) { echo " ";
+											
+											} else {?>
+											<ul>
+												<li class="none-separator"><a href="#" 
+													onClick="fillFields(
+													'<?php echo(rawurlencode($role_id)); ?>',
+													'<?php echo(rawurlencode($name)); ?>',
+													'<?php echo(rawurlencode($description)); ?>',
+													'<?php echo(rawurlencode($access_level)); ?>',
+													[<?php echo("'".implode("','",$role_permissions)."'");?>])">
+													<?php echo Kohana::lang('ui_admin.edit_action');?>
+													</a></li>
+	<li><a href="javascript:rolesAction('d','DELETE','<?php echo(rawurlencode($role_id)); ?>')" class="del">
+													<?php echo Kohana::lang('ui_admin.delete_action');?>
+													</a></li>
+											</ul>
+											<?php } ?>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+			</div>
+		</div>
diff --git a/application/views/admin/users/roles_js.php b/application/views/admin/users/roles_js.php
new file mode 100755
index 0000000..b203e49
--- /dev/null
+++ b/application/views/admin/users/roles_js.php
@@ -0,0 +1,38 @@
+// Populates the fields in the add/edit form
+function fillFields(id, name, description, access_level, permissions) {
+
+	show_addedit();
+	$("#role_id").attr("value", decodeURIComponent(id));
+	$("#name").attr("value", decodeURIComponent(name));
+	$("#description").attr("value", decodeURIComponent(description));
+	$("#access_level").attr("value", decodeURIComponent(access_level));
+
+	for (i = 0; i < permissions.length; i++)
+	{
+		$("#permission_"+permissions[i]).attr("checked", "checked")
+	}
+}
+
+// Ajax Submission
+function rolesAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction)
+	if (answer){
+		// Set Role ID
+		$("#role_id_main").attr("value", id);
+		// Set Submit Type
+		$("#role_action_main").attr("value", action);		
+		// Submit Form
+		$("#roleListing").submit();
+	}
+}
+
+function B( objAny ){
+	// Test argument for true / false,
+	if (objAny == 1) {
+		return(true);
+	} else {
+		return(false);
+	}
+}
diff --git a/application/views/admin/users/users_js.php b/application/views/admin/users/users_js.php
new file mode 100644
index 0000000..20aceb7
--- /dev/null
+++ b/application/views/admin/users/users_js.php
@@ -0,0 +1,42 @@
+/**
+ * Users js file.
+ *
+ * Handles javascript stuff related to users function
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+		// Users JS
+		function fillFields(id, username, name, role, email)
+		{
+			$("#user_id").attr("value", decodeURIComponent(id));
+			$("#username").attr("value", decodeURIComponent(username));
+			$("#name").attr("value", decodeURIComponent(name));
+			$('#role').attr("value", decodeURIComponent( role ) );
+			$('#email').attr("value", decodeURIComponent( email ) );
+			
+		}
+		
+		// Form Submission
+		function userAction ( action, confirmAction, id )
+		{
+			var statusMessage;
+			var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' 
+				+ confirmAction + ' user with ID: ' + id + '?')
+			if (answer){
+				// Set User ID
+				$("#user_id_action").attr("value", id);
+				// Set Submit Type
+				$("#action").attr("value", action);		
+				// Submit Form
+				$("#userMain").submit();			
+
+			}
+		}
\ No newline at end of file
diff --git a/application/views/admin/utils_js.php b/application/views/admin/utils_js.php
new file mode 100644
index 0000000..c7bdfd2
--- /dev/null
+++ b/application/views/admin/utils_js.php
@@ -0,0 +1,44 @@
+/**
+ * Form Utilities js file
+ *
+ * Common form functions for admin dashboard pages.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+ 				
+$(document).ready(function()
+{		
+	$(".hide").click(function () {
+		$("#submitStatus").hide();
+		return false;
+	});
+});
+
+// Check All / Check None
+function CheckAll( id, name )
+{
+	// TODO use the given name in the jQuery selector
+	//$("INPUT[name='" + name + "'][type='checkbox']").attr('checked', $('#' + id).is(':checked'));
+	$("td > input:checkbox").attr('checked', $('#' + id).is(':checked'));
+}
+
+//check if a checkbox has been ticked.
+function isChecked( id )
+{
+	//var checked = $("input[id="+id+"]:checked").length
+	var checked = $("td > input:checked").length
+	
+	if( checked == 0 )
+	return false
+	
+	else 
+	return true
+}
diff --git a/application/views/admin/version_sync.php b/application/views/admin/version_sync.php
new file mode 100644
index 0000000..ad627bb
--- /dev/null
+++ b/application/views/admin/version_sync.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * Version Sync View File.
+ *
+ * Used to render the HTML for warning is db or software version are mismatches
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Robbie Mackay <rm at robbiemackay.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<?php 
+if ( Kohana::config('config.enable_ver_sync_warning') == TRUE)
+{
+	if ( ( url::current() != "admin/upgrade") AND (Kohana::config('version.ushahidi_version') != Kohana::config('settings.ushahidi_version')))
+	{ ?>
+	<div id="version-sync-software" class="update-info">
+		<?php echo Kohana::lang('upgrade.upgrade_warning_software_version'); ?><br />
+		version.php: <?php echo Kohana::config('version.ushahidi_version') ?>   <?php echo Kohana::lang('upgrade.upgrade_database'); ?>  <?php echo Kohana::config('settings.ushahidi_version') ?>
+	</div>
+	<?php
+	}
+	if (( url::current() != "admin/upgrade") AND (Kohana::config('version.ushahidi_db_version') != Kohana::config('settings.db_version')))
+	{ ?>
+	<div id="version-sync-db" class="update-info">
+		<?php echo Kohana::lang('upgrade.upgrade_warning_db_version'); ?><br />
+		version.php: <?php echo Kohana::config('version.ushahidi_db_version') ?>   <?php echo Kohana::lang('upgrade.upgrade_database'); ?>  <?php echo Kohana::config('settings.db_version') ?>
+	</div>
+<?php
+	}
+} ?>
\ No newline at end of file
diff --git a/application/views/error.php b/application/views/error.php
new file mode 100644
index 0000000..bc19d72
--- /dev/null
+++ b/application/views/error.php
@@ -0,0 +1,15 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title><?php echo $title ?></title>
+<?php echo html::stylesheet('media/css/error'); ?>
+</head>
+
+<body>
+<div id="error">
+	<h1><?php echo $title ?></h1>
+	<?php echo $content ?>   
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/application/views/help_view_js.php b/application/views/help_view_js.php
new file mode 100644
index 0000000..39f9b2e
--- /dev/null
+++ b/application/views/help_view_js.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Help view js file.
+ * 
+ * Handles javascript stuff related to help view function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+		jQuery(function() {
+			/*
+			Send Message JS
+			*/			
+			// Ajax Validation
+			$("#sendMessage").validate({
+				rules: {
+					name: {
+						required: true,
+						minlength: 3
+					},
+					email: {
+						email: true
+					},
+					phone: {
+						minlength: 3
+					},
+					message: {
+						required: true,
+						minlength: 3
+					},
+					captcha: {
+						required: true
+					}
+				},
+				messages: {
+					name: {
+						required: "Please enter your Name",
+						minlength: "Your Name must consist of at least 3 characters"
+					},
+					email: {
+						required: "Please enter an Email Address",
+						email: "Please enter a valid Email Address"
+					},
+					phone: {
+						minlength: "Your Phone number is invalid"
+					},
+					message: {
+						required: "Please enter a Comment",
+						minlength: "Your Comment must be at least 3 characters long"
+					},
+					captcha: {
+						required: "Please enter the Security Code"
+					}
+				}
+			});
+		});
\ No newline at end of file
diff --git a/application/views/layout.php b/application/views/layout.php
new file mode 100644
index 0000000..6fb5527
--- /dev/null
+++ b/application/views/layout.php
@@ -0,0 +1,3 @@
+<?php echo $header; ?>
+<?php echo $content; ?>
+<?php echo $footer; ?>
\ No newline at end of file
diff --git a/application/views/map_common_js.php b/application/views/map_common_js.php
new file mode 100644
index 0000000..b775097
--- /dev/null
+++ b/application/views/map_common_js.php
@@ -0,0 +1,261 @@
+<?php
+/**
+ * Common mapping functions
+ */
+?>
+
+	// Projections
+	var proj_4326 = new OpenLayers.Projection('EPSG:4326');
+	var proj_900913 = new OpenLayers.Projection('EPSG:900913');
+	
+	/**
+	 * Creates an returns a map object
+	 * @param targetElement ID of the element to be used for creating the map
+	 * @param options Options to be used for creating the map
+	 */
+	function createMap(targetElement, lat, lon, zoomLevel, options, controls)
+	{
+		if (typeof targetElement == 'undefined' 
+		    || $("#"+targetElement) == null) {
+			return;
+		}
+		
+		if (typeof(options) == 'undefined' || options == null) {
+			// Create the default options
+			options = {
+				units: "dd",
+				numZoomLevels: 18,
+				theme: false,
+				controls: [],
+				projection: proj_900913,
+				'displayProjection': proj_4326,
+				maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 
+				                                 20037508.34, 20037508.34),
+				maxResolution: 156543.0339
+			};
+		}
+
+		// Create the map object
+		var map = new OpenLayers.Map(targetElement, options);
+		
+		<?php echo map::layers_js(FALSE); ?>
+		
+		// Add the default layers
+		map.addLayers(<?php echo map::layers_array(FALSE); ?>);
+
+		// Add controls
+		if (typeof controls == "undefined" || controls == null) {
+			// Set the controls for the map options
+			map.addControls([
+				new OpenLayers.Control.Navigation({ dragPanOptions: { enableKinetic: true } }),
+				new OpenLayers.Control.Zoom(),
+				new OpenLayers.Control.Attribution(),
+				new OpenLayers.Control.MousePosition({
+					formatOutput: Ushahidi.convertLongLat
+				}),
+				new OpenLayers.Control.LayerSwitcher()
+			]);
+		} else if (controls.length > 0) {
+			map.addControls(controls);
+		}
+
+		
+		// Check for the zoom level
+		var zoom = (typeof zoomLevel == 'undefined' || zoomLevel < 1)
+		    ? 9 : zoomLevel;
+		
+		// Create a lat/lon object and center the map
+		var myPoint = new OpenLayers.LonLat(lon, lat);
+		myPoint.transform(proj_4326, proj_900913);
+		
+		// Display the map centered on a latitude and longitude
+		map.setCenter(myPoint, zoom);
+		
+		// Return
+		return map;
+	}
+		
+	/**
+	 * Creates a radius layer and adds it on the map object
+	 */
+	function addRadiusLayer(map, lat, lon, radius)
+	{
+		if (typeof map == 'undefined' 
+		    || typeof lat == 'undefined' || typeof lon == 'undefined') {
+			return;
+		}
+		
+		if (typeof radius == 'undefined' || radius > 0)
+		{
+			// Set the radius to a default value
+			radius = 20000;
+		}
+		
+		// Create the Circle/Radius layer
+		var radiusLayer = new OpenLayers.Layer.Vector("Radius Layer");
+				
+		// Create the markers layer
+		markers = new OpenLayers.Layer.Markers("Markers");
+		map.addLayers([radiusLayer, markers]);
+		
+		// Create a marker positioned at the map center
+		var myPoint = new OpenLayers.LonLat(lon, lat);
+		
+		myPoint.transform(proj_4326, proj_900913);
+		var marker = new OpenLayers.Marker(myPoint);
+		
+		markers.addMarker(marker);
+		
+		return radiusLayer;
+	}
+	
+	/**
+	 * Draw circle around point
+	 */
+	function drawCircle(map, lat, lon, radius)
+	{
+		if (typeof map == 'undefined' || typeof map != 'object') return;
+		if (typeof radius == 'undefined') {
+			radius = 20000;
+		}
+		
+		var radiusLayer;
+		radiusLayers = map.getLayersByName("Radius Layer");
+		for (var i=0; i<radiusLayers.length; i++)
+		{
+			if (radiusLayers[i].name == "Radius Layer")
+			{
+				radiusLayer = radiusLayers[i];
+				break;
+			}
+		}
+		
+		radiusLayer.destroyFeatures();
+		
+		var circOrigin = new OpenLayers.Geometry.Point(lon, lat);
+		circOrigin.transform(proj_4326, proj_900913);
+		
+		var circStyle = OpenLayers.Util.extend({}, 
+		    OpenLayers.Feature.Vector.style["default"]);
+		var circleFeature = new OpenLayers.Feature.Vector(
+			OpenLayers.Geometry.Polygon.createRegularPolygon(circOrigin,
+			    radius, 40, 0),
+			null,
+			circStyle
+		);
+		
+		radiusLayer.addFeatures( [circleFeature] );
+	}
+	
+	/**
+	 * Registers feature selection events on the map
+	 */
+	function addFeatureSelectionEvents(map, layer) {
+		var selectedFeature = null;
+		selectControl = new OpenLayers.Control.SelectFeature(layer);
+		map.addControl(selectControl);
+		selectControl.activate();
+		layer.events.on({
+			"featureselected": onFeatureSelect,
+			"featureunselected": onFeatureUnselect
+		});
+  }
+
+  	/**
+	 * Display popup when feature selected
+     */
+	function onFeatureSelect(event) {
+		selectedFeature = event.feature;
+	    zoom_point = event.feature.geometry.getBounds().getCenterLonLat();
+	    lon = zoom_point.lon;
+	    lat = zoom_point.lat;
+    
+	    var thumb = "";
+	    if ( typeof(event.feature.attributes.thumb) != 'undefined' && 
+	      event.feature.attributes.thumb != '')
+	    {
+	      thumb = "<div class=\"infowindow_image\"><a href='"+event.feature.attributes.link+"'>";
+	      thumb += "<img src=\""+event.feature.attributes.thumb+"\" height=\"59\" width=\"89\" /></a></div>";
+	    }
+
+	    var content = "<div class=\"infowindow\">" + thumb +
+	        "<div class=\"infowindow_content\">"+
+	        "<div class=\"infowindow_list\">"+event.feature.attributes.name+"</div>\n" +
+	        "<div class=\"infowindow_meta\">";
+
+	    if ( typeof(event.feature.attributes.link) != 'undefined' &&
+	      event.feature.attributes.link != '')
+	    {
+	      content += "<a href='"+event.feature.attributes.link+"'>" +
+	          "<?php echo Kohana::lang('ui_main.more_information');?></a><br/>";
+	    }
+    
+	    content += "<a href='javascript:zoomToSelectedFeature("+ lon + ","+ lat +",1)'>";
+	    content += "<?php echo Kohana::lang('ui_main.zoom_in');?></a>";
+	    content += "  |  ";
+	    content += "<a href='javascript:zoomToSelectedFeature("+ lon + ","+ lat +",-1)'>";
+	    content += "<?php echo Kohana::lang('ui_main.zoom_out');?></a></div>";
+	    content += "</div><div style=\"clear:both;\"></div></div>";		
+
+	    if (content.search("<?php echo '<'; ?>script") != -1) {
+	      content = "Content contained Javascript! Escaped content " +
+	          "below.<br />" + content.replace(/<?php echo '<'; ?>/g, "<");
+	    }
+          
+	    // Destroy existing popups before opening a new one
+	    if (event.feature.popup != null) {
+	      map.removePopup(event.feature.popup);
+	    }
+    
+	    popup = new OpenLayers.Popup.FramedCloud("chicken", 
+	      event.feature.geometry.getBounds().getCenterLonLat(),
+	      new OpenLayers.Size(100,100),
+	      content,
+	      null, true, onPopupClose);
+
+	    event.feature.popup = popup;
+	    map.addPopup(popup);
+	    popup.show();
+	}
+
+	/**
+     * Destroy Popup Layer
+     */
+	function onFeatureUnselect(event) {
+		// Safety check
+		if (event.feature.popup != null) {
+			map.removePopup(event.feature.popup);
+			event.feature.popup.destroy();
+			event.feature.popup = null;
+		}
+	}
+
+	/**
+	 * Close Popup
+	 */
+	function onPopupClose(event) {
+		selectControl.unselect(selectedFeature);
+		selectedFeature = null;
+	};
+	
+	/**
+	 * Zoom to Selected Feature from within Popup
+	 */
+	function zoomToSelectedFeature(lon, lat, zoomfactor) {
+		var lonlat = new OpenLayers.LonLat(lon,lat);
+
+		// Get Current Zoom
+		currZoom = map.getZoom();
+		
+		// New Zoom
+		newZoom = currZoom + zoomfactor;
+		
+		// Center and Zoom
+		map.setCenter(lonlat, newZoom);
+
+		// Remove Popups
+		for (var i=0; i<?php echo '<'; ?>map.popups.length; ++i) {
+			map.removePopup(map.popups[i]);
+		}
+    	onPopupClose(true);
+	}
diff --git a/application/views/members/alerts.php b/application/views/members/alerts.php
new file mode 100644
index 0000000..0070c8b
--- /dev/null
+++ b/application/views/members/alerts.php
@@ -0,0 +1,126 @@
+<?php 
+/**
+ * Alerts view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Private Messages View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<div class="bg">
+	<h2>
+		<?php members::alerts_subtabs("view"); ?>
+	</h2>
+
+	<!-- tabs -->
+	<div class="tabs">
+		<!-- tabset -->
+		<ul class="tabset">
+			<li><a href="<?php echo url::site()."members/alerts/"; ?>" <?php if ($type == '0' OR empty($type) ) echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.show_all');?></a></li>
+			<li><a href="<?php echo url::site()."members/alerts/index/"; ?>?type=1" <?php if ($type == '1') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.sms');?></a></li>
+			<li><a href="<?php echo url::site()."members/alerts/index/"; ?>?type=2" <?php if ($type == '2') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.email');?></a></li>
+		</ul>
+		<!-- tab -->
+		<div class="tab">
+			<ul>
+				<li><a href="#" onClick="alertsAction('d', 'DELETE', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete'));?></a></li>
+			</ul>
+		</div>
+	</div>
+	<?php if ($form_error): ?>
+		<!-- red-box -->
+		<div class="red-box">
+			<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+			<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+		</div>
+	<?php endif; ?>
+
+	<?php if ($form_saved): ?>
+		<!-- green-box -->
+		<div class="green-box" id="submitStatus">
+			<h3><?php echo Kohana::lang('ui_main.alert_has_been');?> <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide">hide this message</a></h3>
+		</div>
+	<?php endif;  ?>
+
+	<!-- report-table -->
+	<?php print form::open(NULL, array('id' => 'alertsMain', 'name' => 'alertsMain')); ?>
+		<input type="hidden" name="action" id="action" value="">
+		<input type="hidden" name="level"  id="level"  value="">
+		<input type="hidden" name="alert_id[]" id="alert_single" value="">
+		<div class="table-holder">
+			<table class="table">
+				<thead>
+					<tr>
+						<th class="col-1"><input id="checkall" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'alert_id[]' )" /></th>
+						<th class="col-2"><?php echo Kohana::lang('ui_admin.alerts');?></th>
+						<th class="col-3"><?php echo Kohana::lang('ui_admin.alerts_received');?></th>
+						<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+					</tr>
+				</thead>
+				<tfoot>
+					<tr class="foot">
+						<td colspan="4">
+							<?php echo $pagination; ?>
+						</td>
+					</tr>
+				</tfoot>
+				<tbody>
+				<?php if ($total_items == 0): ?>
+					<tr>
+						<td colspan="4" class="col">
+							<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+						</td>
+					</tr>
+				<?php endif; ?>
+
+				<?php
+				foreach ($alerts as $alert):
+					$alert_id = $alert->id;
+					$alert_type = $alert->alert_type;
+					$alert_lat = $alert->alert_lat;
+					$alert_lon = $alert->alert_lon;
+					$alert_radius = $alert->alert_radius;
+						
+					$alert_count = $alert->alert_sent->count();
+					$categories = $alert->alert_category;
+				?>
+					<tr>
+						<td class="col-1"><input name="alert_id[]" id="alert" value="<?php echo $alert_id; ?>" type="checkbox" class="check-box"/></td>
+						<td class="col-2">
+							<div class="post">
+								<p><?php echo $alert_lat.", ".$alert_lon; ?></p>
+								<p><a href="javascript:showAlert('alert_preview_<?php echo $alert_id?>', '<?php echo $alert_lon?>', '<?php echo $alert_lat?>', '<?php echo $alert_radius?>')"><?php echo Kohana::lang('ui_admin.preview');?></a></p>
+								<div id="alert_preview_<?php echo $alert_id?>" class="preview_div">
+									<div id="alert_preview_<?php echo $alert_id?>_map" class="checkin_map"></div>
+								</div>
+							</div>
+							<ul class="info">
+								<li class="none-separator">Radius: <strong><?php echo $alert_radius; ?></strong></li>
+								
+								<li>Categories: <strong><?php 
+								foreach ($categories as $alert_category)
+								{
+									echo $alert_category->category->category_title.", ";
+								}
+								?></strong></li>
+							</ul>
+						</td>
+						<td class="col-3"><?php echo $alert_count; ?></td>
+						<td class="col-4">
+							<ul>
+								<li class="none-separator"><a href="javascript:alertsAction('d','DELETE','<?php echo(rawurlencode($alert_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+							</ul>
+						</td>
+					</tr>
+				<?php endforeach; ?>
+				</tbody>
+			</table>
+		</div>
+	<?php print form::close(); ?>
+</div>
\ No newline at end of file
diff --git a/application/views/members/alerts_js.php b/application/views/members/alerts_js.php
new file mode 100644
index 0000000..f0d0234
--- /dev/null
+++ b/application/views/members/alerts_js.php
@@ -0,0 +1,77 @@
+<?php require APPPATH.'views/admin/utils_js.php' ?>
+var map;
+
+function showAlert(id, lon, lat, radius) {
+	if (id) {
+		if ($('#' + id).css('display') == 'none') {
+			$('#' + id).show(400);
+			showMap(id, lon, lat, radius);
+		}
+		else
+		{
+			$('#' + id).hide(400);
+			if (map)
+			{
+				map.destroy();
+				$('#' + id + '_map').html();
+			}
+		}
+	}
+}
+
+function showMap(id, lon, lat, radius) {
+	<?php echo map::layers_js(FALSE); ?>
+	var mapConfig = {
+
+		// Map center
+		center: {
+			latitude: lat,
+			longitude: lon
+		},
+
+		// Zoom level
+		zoom: <?php echo Kohana::config('settings.default_zoom'); ?>,
+
+		// Base layers
+		baseLayers: <?php echo map::layers_array(FALSE); ?>
+	};
+
+	// Initialize the map
+	map = new Ushahidi.Map(id + '_map', mapConfig);
+
+	// Add the radius layer
+	map.addRadiusLayer({latitude: lat, longitude: lon});
+}
+
+function alertsAction (action, confirmAction, alert_id) {
+	var statusMessage;
+	if( !isChecked( "alert" ) && alert_id=='' )
+	{
+		alert('Please select at least one alert.');
+	} else {
+		var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+		if (answer){
+
+			// Set Submit Type
+			$("#action").attr("value", action);
+
+			if (alert_id != '')
+			{
+				// Submit Form For Single Item
+				$("#alert_single").attr("value", alert_id);
+				$("#alertsMain").submit();
+			}
+			else
+			{
+				// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+				$("#alert_single").attr("value", "000");
+
+				// Submit Form For Multiple Items
+				$("#alertsMain").submit();
+			}
+
+		} else {
+		//	return false;
+		}
+	}
+}
diff --git a/application/views/members/dashboard.php b/application/views/members/dashboard.php
new file mode 100644
index 0000000..90e0a5b
--- /dev/null
+++ b/application/views/members/dashboard.php
@@ -0,0 +1,276 @@
+<?php
+/**
+ * Dashboard view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2><?php echo $title; ?></h2>
+				<!-- column -->
+				<div class="column">
+
+					<!-- welcome box -->
+					<?php if($hidden_welcome_fields['needinfo']) { ?>
+					<div class="box">
+						<h3><?php echo Kohana::lang("ui_main.more_information"); ?></h3>
+						<?php echo form::open('/members/profile/'); ?>
+
+						<div class="welcome-form">
+
+							<?php echo form::hidden($hidden_welcome_fields); ?>
+
+							<div class="row" style="padding-top:10px;">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_name"); ?>"><?php echo Kohana::lang('ui_main.full_name');?></a></h4>
+								<?php echo form::input('name', $user->name, ' class="text long2"'); ?>
+							</div>
+
+							<div class="row" style="padding-top:10px;">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_public_url"); ?>"><?php echo Kohana::lang('ui_main.public_profile_url');?></a></h4>
+								<span style="float:left;"><?php echo url::site().'profile/user/'; ?></span>
+								<?php echo form::input('username', $user->username, ' class="text short2"'); ?>
+							</div>
+							
+							<div class="row">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_password"); ?>"><?php echo Kohana::lang('ui_main.current_password'); ?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+								<?php print form::password('current_password', '', ' class="text"'); ?>
+							</div>
+
+							<div class="row" style="padding-top:10px;">
+								<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_public"); ?>"><?php echo Kohana::lang('ui_main.public_profile');?>:</a></h4>
+								<?php
+									echo form::label('profile_public', Kohana::lang('ui_main.on').': ');
+									echo form::radio('public_profile', '1', $profile_public, 'id="profile_public"').'    ';
+									echo form::label('profile_private', Kohana::lang('ui_main.off').': ');
+									echo form::radio('public_profile', '0', $profile_private, 'id="profile_private"').'<br />';
+								?>
+							</div>
+
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+
+						</div>
+
+						<?php echo form::close(); ?>
+					</div>
+					<?php } ?>
+
+					<!-- box -->
+					<div class="box">
+						<h3><?php echo Kohana::lang('ui_main.reports');?> <?php echo Kohana::lang('ui_main.reports_timeline');?></h3>
+						<ul class="inf" style="margin-bottom:10px;">
+							<li class="none-separator"><?php echo Kohana::lang('ui_main.view');?>:<a href="<?php echo url::site() ?>members/dashboard/?range=1"><?php echo Kohana::lang('ui_main.today');?></a></li>
+							<li><a href="<?php echo url::site() ?>members/dashboard/?range=31"><?php echo Kohana::lang('ui_main.past_month');?></a></li>
+							<li><a href="<?php echo url::site() ?>members/dashboard/?range=365"><?php echo Kohana::lang('ui_main.past_year');?></a></li>
+							<li><a href="<?php echo url::site() ?>members/dashboard/?range=0"><?php echo Kohana::lang('ui_main.all');?></a></li>
+						</ul>
+						<div class="chart-holder" style="clear:both;padding-left:5px;">
+							<?php echo $report_chart; ?>
+							<?php if($failure != ''){ ?>
+								<div class="red-box" style="width:400px;">
+									<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+									<ul><li><?php echo $failure; ?></li></ul>
+								</div>
+							<?php } ?>
+						</div>
+					</div>
+
+					<!-- info-container -->
+					<div class="info-container">
+						<div class="i-c-head">
+							<h3><?php echo Kohana::lang('ui_main.recent_reports');?></h3>
+							<ul>
+								<li class="none-separator"><a href="<?php echo url::site() . 'members/reports' ?>"><?php echo Kohana::lang('ui_main.view_all');?></a></li>
+								<li><a href="#" class="rss-icon"><?php echo Kohana::lang('ui_main.rss');?></a></li>
+							</ul>
+						</div>
+						<?php
+						if ($reports_total == 0)
+						{
+						?>
+						<div class="post">
+							<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+						</div>
+						<?php
+						}
+						foreach ($incidents as $incident)
+						{
+							$incident_id = $incident->id;
+							$incident_title = $incident->incident_title;
+							$incident_description = text::limit_chars($incident->incident_description, 150, '...');
+							$incident_date = $incident->incident_date;
+							$incident_date = date('g:i A', strtotime($incident->incident_date));
+							$incident_mode = $incident->incident_mode;	// Mode of submission... WEB/SMS/EMAIL?
+
+							if ($incident_mode == 1)
+							{
+								$submit_mode = "mail";
+							}
+							elseif ($incident_mode == 2)
+							{
+								$submit_mode = "sms";
+							}
+							elseif ($incident_mode == 3)
+							{
+								$submit_mode = "mail";
+							}
+							elseif ($incident_mode == 4)
+							{
+								$submit_mode = "twitter";
+							}
+
+							// Incident Status
+							$incident_approved = $incident->incident_active;
+							if ($incident_approved == '1')
+							{
+								$incident_approved = "ok";
+							}
+							else
+							{
+								$incident_approved = "none";
+							}
+
+							$incident_verified = $incident->incident_verified;
+							if ($incident_verified == '1')
+							{
+								$incident_verified = "ok";
+							}
+							else
+							{
+								$incident_verified = "none";
+							}
+							?>
+							<div class="post">
+								<ul class="post-info">
+									<li><a href="#" class="<?php echo $incident_approved; ?>"><?php echo utf8::strtoupper(Kohana::lang('ui_main.approved'));?>:</a></li>
+									<li><a href="#" class="<?php echo $incident_verified ?>"><?php echo utf8::strtoupper(Kohana::lang('ui_main.verified'));?>:</a></li>
+									<li class="last"><a href="#" class="<?php echo $submit_mode; ?>"><?php echo utf8::strtoupper(Kohana::lang('ui_main.source'));?>:</a></li>
+								</ul>
+								<h4><strong><?php echo $incident_date; ?></strong><a href="<?php echo url::site() . 'members/reports/edit/' . $incident_id; ?>"><?php echo $incident_title; ?></a></h4>
+								<p><?php echo $incident_description; ?></p>
+							</div>
+							<?php
+						}
+						?>
+						<a href="<?php echo url::site() . 'members/reports' ?>" class="view-all"><?php echo Kohana::lang('ui_main.view_all_reports');?></a>
+					</div>
+				</div>
+				<div class="column-1">
+					<!-- box -->
+					<div class="box">
+						<h3><?php echo Kohana::lang('ui_admin.my_profile');?></h3>
+						<ul class="inf" style="margin-bottom:10px;">
+							<li class="none-separator"><a href="<?php echo url::site() ?>members/profile"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+						</ul>
+						<div class="member_profile">
+							<div class="member_photo"><img src="<?php echo members::gravatar($user->email); ?>" width="80" /></div>
+							<div class="member_info">
+								<div class="member_info_row"><span class="member_info_label"><?php echo Kohana::lang('ui_admin.name');?>:</span> <?php echo html::specialchars($user->name); ?></div>
+
+								<?php if(count($user->openid) > 0) { ?>
+								<div class="member_info_row"><span class="member_info_label"><?php echo Kohana::lang('ui_admin.openids');?></span>:
+									<ul>
+										<?php
+										foreach ($user->openid as $openid)
+										{
+											$openid_server = parse_url($openid->openid_server);
+											echo "<li>".$openid->openid_email." (".$openid_server["host"].")</li>";
+										}
+										?>
+									</ul>
+								</div>
+								<?php } ?>
+
+								<?php
+									if (isset($user->username) AND // Only show if it's set
+										($user->username != '' OR $user->username != NULL) AND // Don't show if the user hasn't set a username
+										(valid::email($user->username) == false) AND // Don't show if it's a valid email address because it won't work
+										($user->public_profile == 1) // Only show if they've set their profile to be public
+									)
+									{
+								?>
+								<div class="member_info_row"><span class="member_info_label"><?php echo Kohana::lang('ui_main.public_profile_url');?></span>:
+									<br/><a href="<?php echo url::site().'profile/user/'.$user->username; ?>"><?php echo url::site().'profile/user/'.$user->username; ?></a>
+								</div>
+								<?php
+									}
+								?>
+
+								<div class="member_info_row"><span class="member_info_label"><?php echo Kohana::lang('ui_main.profile_color');?></span>:
+									<span style="background-color:#<?php echo $user->color; ?>;width:150px;height:10px;display:inline-block;"></span>
+								</div>
+
+								<!-- NOTE: Not calculating reputation yet
+								<div class="member_info_row"><span class="member_info_label"><?php echo Kohana::lang('ui_admin.reputation');?>:</span> <span class="member_reputation"><?php echo $reputation; ?></span></div> -->
+
+							</div>
+						</div>
+					</div>
+
+					<!-- badge box -->
+					<div class="box">
+
+						<h3><?php echo Kohana::lang('ui_main.badges');?></h3>
+						<div style="clear:both;"></div>
+						<div style="text-align:center;">
+						<?php
+							if(count($badges) > 0) {
+								foreach($badges as $badge) {
+						?>
+
+								<div class="badge">
+									<center><img src="<?php echo $badge['img_m']; ?>" alt="<?php echo Kohana::lang('ui_main.badge').' '.$badge['id'];?>" width="80" height="80" style="margin:5px;" /></center>
+									<br/><strong><?php echo $badge['name']; ?></strong>
+								</div>
+
+						<?php
+								}
+							}else{
+								echo Kohana::lang('ui_main.sorry_no_badges');
+							}
+						?>
+						</div>
+						<div style="clear:both;"></div>
+
+					</div>
+
+					<!-- box -->
+					<div class="box">
+						<h3><?php echo Kohana::lang('ui_main.quick_stats');?></h3>
+						<ul class="nav-list">
+							<li>
+								<a href="<?php echo url::site() . 'members/reports' ?>" class="reports"><?php echo Kohana::lang('ui_admin.my_reports');?></a>
+								<strong><?php echo number_format($reports_total); ?></strong>
+								<ul>
+									<li><a href="<?php echo url::site() . 'members/reports?status=a' ?>"><?php echo Kohana::lang('ui_main.not_approved');?></a><strong>(<?php echo $reports_unapproved; ?>)</strong></li>
+
+								</ul>
+							</li>
+							<li>
+								<a href="<?php echo url::site() . 'members/alerts' ?>" class="alerts"><?php echo Kohana::lang('ui_admin.my_alerts');?></a>
+								<strong><?php echo $alerts; ?></strong>
+							</li>
+							<li>
+								<a href="#" class="votes"><?php echo Kohana::lang('ui_admin.my_votes');?></a>
+								<strong><?php echo $votes; ?></strong>
+								<ul>
+									<li><a href="#"><?php echo Kohana::lang('ui_admin.my_votes_up');?></a><strong>(<?php echo $votes_up; ?>)</strong></li>
+									<li><a href="#"><?php echo Kohana::lang('ui_admin.my_votes_down');?></a><strong>(<?php echo $votes_down; ?>)</strong></li>
+								</ul>
+							</li>
+							<li>
+								<a href="<?php echo url::site() . 'members/private' ?>" class="messages"><?php echo Kohana::lang('ui_admin.private_messages');?></a>
+								<strong><?php echo "0"; ?></strong>
+							</li>
+						</ul>
+					</div>
+				</div>
+			</div>
+
diff --git a/application/views/members/layout.php b/application/views/members/layout.php
new file mode 100644
index 0000000..d550ac1
--- /dev/null
+++ b/application/views/members/layout.php
@@ -0,0 +1,79 @@
+<?php 
+/**
+ * Layout for the members interface.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Member View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<meta http-equiv="X-UA-Compatible" content="IE=7" />
+	<title><?php echo html::specialchars($site_name) ?></title>
+	<?php
+	// Action::header_scripts_member - Additional Inline Scripts
+	Event::run('ushahidi_action.header_scripts_member');
+	?>
+	<script type="text/javascript" charset="utf-8">
+		<?php if ($form_error): ?>
+			$(document).ready(function() { $("#addedit").show(); });
+		<?php endif; ?>
+	</script>
+
+	<?php echo $header_block; ?>
+</head>
+<body>
+
+	<?php echo $header_nav; ?>
+
+	<div class="holder">
+		<!-- header -->
+		<div id="header">
+
+			<!-- info-nav -->
+			<div class="info-nav">
+				<ul>
+					<li><a href="http://forums.ushahidi.com/"><?php echo Kohana::lang('ui_admin.forum');?></a></li>
+				</ul>
+				<div class="info-search"><?php echo form::open('members/reports', array('id' => 'info-search', 'method' => 'get')); ?><input type="text" name="k" class="info-keyword" value=""> <a href="javascript:info_search();" class="btn"><?php echo Kohana::lang('ui_admin.search');?></a><?php echo form::close(); ?></div>
+				<div style="clear:both"></div>
+			</div>
+			<!-- title -->
+			<h1><?php echo $site_name ?></h1>
+			<!-- nav-holder -->
+			<div class="nav-holder">
+				<!-- main-nav -->
+				<ul class="main-nav">
+					<?php foreach($main_tabs as $page => $tab_name): ?>
+						<li><a href="<?php echo url::site(); ?>members/<?php echo $page; ?>" <?php if($this_page==$page) echo 'class="active"' ;?>><?php echo $tab_name; ?></a></li>
+					<?php endforeach; ?>
+				</ul>
+			</div>
+		</div>
+		<!-- content -->
+		<div id="content">
+			<div class="bg">
+				<?php print $content; ?>
+			</div>
+		</div>
+	</div>
+	<div id="footer">
+		<div class="holder">
+			<strong>
+				<a href="http://www.ushahidi.com" target="_blank" title="Ushahidi Engine" alt="Ushahidi Engine">
+                	<sup><?php echo Kohana::config('settings.ushahidi_version');?></sup>
+            	</a>
+			</strong>
+		</div>
+	</div>
+</body>
+</html>
diff --git a/application/views/members/private.php b/application/views/members/private.php
new file mode 100644
index 0000000..4f0e88b
--- /dev/null
+++ b/application/views/members/private.php
@@ -0,0 +1,173 @@
+<?php 
+/**
+ * Private Messages view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Private Messages View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php members::private_subtabs("view"); ?>
+				</h2>
+
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="<?php echo url::site()."members/private/index/"; ?>?type=1" <?php if ($type == '1') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.inbox');?></a></li>
+						<li><a href="<?php echo url::site()."members/private/index/"; ?>?type=2" <?php if ($type == '2') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.outbox');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<?php
+						if ($type != '2')
+						{
+							?>
+							<ul>
+								<li><a href="#" onClick="messagesAction('d', 'DELETE', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete'));?></a></li>
+								<li><a href="#" onClick="messagesAction('r', 'MARK READ', '')"><?php echo utf8::strtoupper(Kohana::lang('ui_main.mark_read'));?></a></li>
+							</ul>
+							<?php
+						}
+						?>
+					</div>
+				</div>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3><?php echo Kohana::lang('ui_main.messages');?> <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide">hide this message</a></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<?php print form::open(NULL, array('id' => 'messageMain', 'name' => 'messageMain')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<input type="hidden" name="level"  id="level"  value="">
+					<input type="hidden" name="message_id[]" id="message_single" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1"><input id="checkall" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'message_id[]' )" /></th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.message_details');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+									<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										<?php echo $pagination; ?>
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr>
+										<td colspan="4" class="col">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+								<?php	
+								}
+								foreach ($messages as $priv_message)
+								{
+									$message_id = $priv_message->id;
+									$parent_id = ($priv_message->parent_id) ? $priv_message->parent_id : $message_id;
+									if ($priv_message->user_id == $user_id)
+									{
+										$message_from_user = ORM::factory("user", $priv_message->from_user_id);
+									}
+									else
+									{
+										$message_from_user = ORM::factory("user", $priv_message->user_id);
+									}
+									
+									if ($message_from_user->loaded)
+									{
+										$message_from = $message_from_user->name;
+									}
+									else
+									{
+										$message_from = Kohana::lang('ui_admin.unknown');
+									}
+									$subject = $priv_message->private_subject;
+									$message = text::auto_link($priv_message->private_message);
+									$message_preview = text::limit_chars(html::strip_tags($message), 150, "...", true);
+									$message_date = date('Y-m-d', strtotime($priv_message->private_message_date));
+									$message_new = $priv_message->private_message_new;
+									?>
+									<tr>
+										<td class="col-1"><input name="message_id[]" id="message" value="<?php echo $message_id; ?>" type="checkbox" class="check-box"/></td>
+										<td class="col-2">
+											<div class="post">
+												<p><?php echo $subject; ?></p>
+												<p><a href="javascript:preview('message_preview_<?php echo $message_id?>')"><?php echo Kohana::lang('ui_main.preview_message');?></a></p>
+												<div id="message_preview_<?php echo $message_id?>" class="preview_div">
+													<?php echo $message; ?>
+												</div>
+											</div>
+											<ul class="info">
+												<?php
+												if ($type == 2)
+												{
+													?><li class="none-separator">To: <strong><?php echo $message_from; ?></strong><?php
+												}
+												else
+												{
+													?><li class="none-separator">From: <strong><a href="<?php echo  url::site()."members/private/send?to=".urlencode($message_from)."&p=".$parent_id ;?>"><?php echo $message_from; ?></a></strong><?php
+												}
+												?>
+											</ul>
+										</td>
+										<td class="col-3"><?php echo $message_date; ?></td>
+										<td class="col-4">
+											<?php
+											if ($type != '2')
+											{
+												?>
+												<ul>
+													<li class="none-separator"><a href="javascript:messagesAction('r','MARK READ','<?php echo(rawurlencode($message_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.mark_read');?></a></li>
+													<li><a href="javascript:messagesAction('d','DELETE','<?php echo(rawurlencode($message_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+												</ul>
+												<?php
+											}
+											else
+											{
+												echo " ";
+											}
+											?>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+			</div>
\ No newline at end of file
diff --git a/application/views/members/private_js.php b/application/views/members/private_js.php
new file mode 100644
index 0000000..153356a
--- /dev/null
+++ b/application/views/members/private_js.php
@@ -0,0 +1,41 @@
+<?php require APPPATH.'views/admin/utils_js.php' ?>
+function messagesAction ( action, confirmAction, message_id )
+{
+	var statusMessage;
+	if( !isChecked( "message" ) && message_id=='' )
+	{ 
+		alert('Please select at least one message.');
+	} else {
+		var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+		if (answer){
+
+			// Set Submit Type
+			$("#action").attr("value", action);
+
+			if (message_id != '') 
+			{
+				// Submit Form For Single Item
+				$("#message_single").attr("value", message_id);
+				$("#messageMain").submit();
+			}
+			else
+			{
+				// Set Hidden form item to 000 so that it doesn't return server side error for blank value
+				$("#message_single").attr("value", "000");
+
+				// Submit Form For Multiple Items
+				$("#messageMain").submit();
+			}
+
+		} else {
+		//	return false;
+		}
+	}
+}
+
+// Preview Message
+function preview ( id ){
+	if (id) {
+		$('#' + id).toggle(400);
+	}
+}
\ No newline at end of file
diff --git a/application/views/members/private_send.php b/application/views/members/private_send.php
new file mode 100644
index 0000000..3590abd
--- /dev/null
+++ b/application/views/members/private_send.php
@@ -0,0 +1,74 @@
+<?php 
+/**
+ * New Private Message
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Private Message New
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php members::private_subtabs("new"); ?>
+				</h2>
+				<?php print form::open(); ?>
+				<input type="hidden" name="parent_id" value="<?php echo $form['parent_id']; ?>">
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_admin.private_sent');?></h3>
+						</div>
+					<?php
+					}
+					?>				
+					<div class="head">
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.send');?>" />
+					</div>
+					<!-- column -->		
+					<div class="sms_holder">
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.private_to"); ?>"><?php echo Kohana::lang('ui_admin.private_to');?></a></h4>
+							<?php print form::input('private_to', $form['private_to'], ' class="text long2" '); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.private_subject"); ?>"><?php echo Kohana::lang('ui_admin.private_subject');?></a></h4>
+							<?php print form::input('private_subject', $form['private_subject'], ' class="text long2" '); ?>
+						</div>
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.private_message"); ?>"><?php echo Kohana::lang('ui_admin.private_message');?></a></h4>
+							<?php print form::textarea('private_message', $form['private_message'], ' rows="6" cols="40" class="textarea long" '); ?>
+						</div>
+					</div>
+		
+					<div class="simple_border"></div>
+		
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.send');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/members/private_send_js.php b/application/views/members/private_send_js.php
new file mode 100644
index 0000000..0d3ff45
--- /dev/null
+++ b/application/views/members/private_send_js.php
@@ -0,0 +1,14 @@
+function formatItem(row) {
+	return row[0];
+}
+function formatResult(row) {
+	return row[0].replace(/(<.+?>)/gi, '');
+}
+jQuery(document).ready(function() {
+	$("#private_to").autocomplete('<?php echo url::site()."members/private/get_user/"; ?>', {
+		multiple: true,
+		matchContains: true,
+		formatItem: formatItem,
+		formatResult: formatResult
+	});
+});
\ No newline at end of file
diff --git a/application/views/members/profile.php b/application/views/members/profile.php
new file mode 100644
index 0000000..3a29b40
--- /dev/null
+++ b/application/views/members/profile.php
@@ -0,0 +1,136 @@
+<?php
+/**
+ * Site view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2><?php echo Kohana::lang('ui_admin.my_profile');?></h2>
+				<?php print form::open(); ?>
+				<div class="report-form">
+					<?php
+					if ($form_error) {
+					?>
+						<!-- red-box -->
+						<div class="red-box">
+							<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+							<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+							</ul>
+						</div>
+					<?php
+					}
+
+					if ($form_saved) {
+					?>
+						<!-- green-box -->
+						<div class="green-box">
+							<h3><?php echo Kohana::lang('ui_main.profile_saved');?></h3>
+						</div>
+					<?php
+					}
+					?>
+					<div class="head">
+						<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+					</div>
+					<!-- column -->
+					<div class="sms_holder">
+
+						<?php Event::run('ui_admin.profile_shown'); ?>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_password"); ?>"><?php echo Kohana::lang('ui_main.current_password'); ?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+							<?php print form::password('current_password', '', ' class="text"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_name"); ?>"><?php echo Kohana::lang('ui_main.full_name');?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+							<?php print form::input('name', $form['name'], ' class="text long2"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_email"); ?>"><?php echo Kohana::lang('ui_main.email');?></a> <span class="required"><?php echo Kohana::lang('ui_main.required'); ?></span></h4>
+							<?php print form::input('email', $form['email'], ' class="text long2"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_new_password"); ?>"><?php echo Kohana::lang('ui_main.new_password');?></a></h4>
+							<?php print form::password('new_password', $form['new_password'], ' class="text"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><?php echo Kohana::lang('ui_main.password_again');?></h4>
+							<?php print form::password('password_again', $form['password_again'], ' class="text"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_notify"); ?>"><?php echo Kohana::lang('ui_main.receive_notifications');?>?</a></h4>
+							<?php print form::dropdown('notify', $yesno_array, $form['notify']); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_public_url"); ?>"><?php echo Kohana::lang('ui_main.public_profile_url');?></a></h4>
+							<span style="float:left;"><?php echo url::site().'profile/user/'; ?></span>
+							<?php print form::input('username', $form['username'], ' class="text short3"'); ?>
+						</div>
+
+						<div class="row">
+							<h4><a href="#" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_public"); ?>"><?php echo Kohana::lang('ui_main.public_profile');?>:</a></h4>
+							<?php
+								print form::label('profile_public', Kohana::lang('ui_main.on').': ');
+								print form::radio('public_profile', '1', $profile_public, 'id="profile_public"').'    ';
+								print form::label('profile_private', Kohana::lang('ui_main.off').': ');
+								print form::radio('public_profile', '0', $profile_private, 'id="profile_private"').'<br />';
+							?>
+						</div>
+
+						<div class="row">
+							<h4><a href="http://www.gravatar.com/" target="_blank" class="tooltip" title="<?php echo Kohana::lang("tooltips.change_picture"); ?>"><?php echo Kohana::lang('ui_main.change_picture');?></a></h4>
+							<a href="http://www.gravatar.com/" target="_blank"><img src="<?php echo members::gravatar($form['email']); ?>" width="80" border="0" /></a>
+						</div>
+
+						<div class="row">
+							<h4><a href="http://www.gravatar.com/" target="_blank" class="tooltip" title="<?php echo Kohana::lang("tooltips.profile_color"); ?>"><?php echo Kohana::lang('ui_main.profile_color');?></a></h4>
+							<?php print form::input('color', $form['color'], ' class="text"'); ?>
+							<script type="text/javascript" charset="utf-8">
+								$(document).ready(function() {
+									$('#color').ColorPicker({
+										onSubmit: function(hsb, hex, rgb) {
+											$('#color').val(hex);
+										},
+										onChange: function(hsb, hex, rgb) {
+											$('#color').val(hex);
+										},
+										onBeforeShow: function () {
+											$(this).ColorPickerSetColor(this.value);
+										}
+									})
+									.bind('keyup', function(){
+										$(this).ColorPickerSetColor(this.value);
+									});
+								});
+							</script>
+						</div>
+
+					</div>
+
+					<div class="simple_border"></div>
+
+					<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_admin.save_settings');?>" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/members/reports.php b/application/views/members/reports.php
new file mode 100644
index 0000000..6a41ecd
--- /dev/null
+++ b/application/views/members/reports.php
@@ -0,0 +1,178 @@
+<?php
+/**
+ * Reports view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php members::reports_subtabs("view"); ?>
+				</h2>
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<ul class="tabset">
+						<li><a href="?status=0" <?php if ($status != 'a' && $status !='v') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.show_all');?></a></li>
+						<li><a href="?status=a" <?php if ($status == 'a') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.awaiting_approval');?></a></li>
+						<li><a href="?status=v" <?php if ($status == 'v') echo "class=\"active\""; ?>><?php echo Kohana::lang('ui_main.awaiting_verification');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<ul>
+							<li><a href="#" onclick="reportAction('d','DELETE', '');"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+						</ul>
+					</div>
+				</div>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul><?php echo Kohana::lang('ui_main.select_one');?></ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box" id="submitStatus">
+						<h3><?php echo Kohana::lang('ui_main.reports');?> <?php echo $form_action; ?> <a href="#" id="hideMessage" class="hide">hide this message</a></h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<?php print form::open(NULL, array('id' => 'reportMain', 'name' => 'reportMain')); ?>
+					<input type="hidden" name="action" id="action" value="">
+					<input type="hidden" name="incident_id[]" id="incident_single" value="">
+					<div class="table-holder">
+						<table class="table">
+							<thead>
+								<tr>
+									<th class="col-1"><input id="checkallincidents" type="checkbox" class="check-box" onclick="CheckAll( this.id, 'incident_id[]' )" /></th>
+									<th class="col-2"><?php echo Kohana::lang('ui_main.report_details');?></th>
+									<th class="col-3"><?php echo Kohana::lang('ui_main.date');?></th>
+									<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+								</tr>
+							</thead>
+							<tfoot>
+								<tr class="foot">
+									<td colspan="4">
+										<?php echo $pagination; ?>
+									</td>
+								</tr>
+							</tfoot>
+							<tbody>
+								<?php
+								if ($total_items == 0)
+								{
+								?>
+									<tr>
+										<td colspan="4" class="col">
+											<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+										</td>
+									</tr>
+								<?php
+								}
+								foreach ($incidents as $incident)
+								{
+									$incident_id = $incident->id;
+									$incident_title = html::escape($incident->incident_title);
+									$incident_description = text::limit_chars(html::strip_tags($incident->incident_description), 150, "...", true);
+									$incident_date = $incident->incident_date;
+									$incident_date = date('Y-m-d', strtotime($incident->incident_date));
+									$incident_mode = $incident->incident_mode;	// Mode of submission... WEB/SMS/EMAIL?
+
+									//XXX incident_Mode will be discontinued in favour of $service_id
+									if ($incident_mode == 1)	// Submitted via WEB
+									{
+										$submit_mode = "WEB";
+										// Who submitted the report?
+										if ($incident->incident_person->id)
+										{
+											// Report was submitted by a visitor
+											$submit_by = $incident->incident_person->person_first . " " . $incident->incident_person->person_last;
+										}
+										else
+										{
+											if ($incident->user_id)					// Report Was Submitted By Administrator
+											{
+												$submit_by = $incident->user->name;
+											}
+											else
+											{
+												$submit_by = Kohana::lang('ui_admin.unknown');
+											}
+										}
+									}
+									elseif ($incident_mode == 2) 	// Submitted via SMS
+									{
+										$submit_mode = "SMS";
+										$submit_by = $incident->message->message_from;
+									}
+									elseif ($incident_mode == 3) 	// Submitted via Email
+									{
+										$submit_mode = "EMAIL";
+										$submit_by = $incident->message->message_from;
+									}
+									elseif ($incident_mode == 4) 	// Submitted via Twitter
+									{
+										$submit_mode = "TWITTER";
+										$submit_by = $incident->message->message_from;
+									}
+
+									$incident_location = $locations[$incident->location_id];
+									$country_id = $country_ids[$incident->location_id]['country_id'];
+
+									// Retrieve Incident Categories
+									$incident_category = "";
+									foreach($incident->incident_category as $category)
+									{
+										$incident_category .= "<a href=\"#\">" . $category->category->category_title . "</a>  ";
+									}
+									?>
+									<tr>
+										<td class="col-1"><input name="incident_id[]" id="incident" value="<?php echo $incident_id; ?>" type="checkbox" class="check-box"/></td>
+										<td class="col-2">
+											<div class="post">
+												<h4><a href="<?php echo url::site() . 'members/reports/edit/' . $incident_id; ?>" class="more"><?php echo $incident_title; ?></a></h4>
+												<p><?php echo $incident_description; ?>... <a href="<?php echo url::site() . 'members/reports/edit/' . $incident_id; ?>" class="more"><?php echo Kohana::lang('ui_main.more');?></a></p>
+											</div>
+											<ul class="info">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.location');?>: <strong><?php echo html::specialchars($incident_location); ?></strong>,<strong><?php if ($country_id !=0) { echo $countries[$country_id];}?></strong></li>
+												<li><?php echo Kohana::lang('ui_main.submitted_by');?> <strong><?php echo html::specialchars($submit_by); ?></strong> via <strong><?php echo html::specialchars($submit_mode); ?></strong></li>
+											</ul>
+											<ul class="links">
+												<li class="none-separator"><?php echo Kohana::lang('ui_main.categories');?>:<?php echo $incident_category; ?></li>
+											</ul>
+											<?php
+											// Action::report_extra_admin - Add items to the report list in admin
+											Event::run('ushahidi_action.report_extra_members', $incident);
+											?>
+										</td>
+										<td class="col-3"><?php echo $incident_date; ?></td>
+										<td class="col-4">
+											<ul>
+												<li class="none-separator"><a href="#" class="del" onclick="reportAction('d','DELETE', '<?php echo $incident_id; ?>');"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+											</ul>
+										</td>
+									</tr>
+									<?php
+								}
+								?>
+							</tbody>
+						</table>
+					</div>
+				<?php print form::close(); ?>
+			</div>
diff --git a/application/views/members/reports_edit.php b/application/views/members/reports_edit.php
new file mode 100644
index 0000000..ec55219
--- /dev/null
+++ b/application/views/members/reports_edit.php
@@ -0,0 +1,437 @@
+<?php 
+/**
+ * Reports edit view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php members::reports_subtabs("edit"); ?>
+				</h2>
+				<?php print form::open(NULL, array('enctype' => 'multipart/form-data', 'id' => 'reportForm', 'name' => 'reportForm')); ?>
+					<input type="hidden" name="save" id="save" value="">
+					<input type="hidden" name="location_id" id="location_id" value="<?php print $form['location_id']; ?>">
+					<input type="hidden" name="incident_zoom" id="incident_zoom" value="<?php print $form['incident_zoom']; ?>">
+					<input type="hidden" name="country_name" id="country_name" value="<?php echo $form['country_name'];?>" />
+					<!-- report-form -->
+					<div class="report-form">
+						<?php if ($form_error): ?>
+							<!-- red-box -->
+							<div class="red-box">
+								<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+								<ul>
+								<?php
+								foreach ($errors as $error_item => $error_description)
+								{
+									print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+								}
+								?>
+								</ul>
+							</div>
+						<?php endif; ?>
+
+						<?php if ($form_saved): ?>
+							<!-- green-box -->
+							<div class="green-box">
+								<h3><?php echo Kohana::lang('ui_main.report_saved');?></h3>
+							</div>
+						<?php endif; ?>
+						
+						<div class="head">
+							<h3><?php echo $id ? Kohana::lang('ui_main.edit_report') : Kohana::lang('ui_main.new_report'); ?></h3>
+							<div class="btns" style="float:right;">
+								<ul>
+									<li><a href="#" class="btn_save"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_report'));?></a></li>
+									<li><a href="#" class="btn_save_close"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_close'));?></a></li>
+									<li><a href="<?php echo url::site().'members/reports/';?>" class="btns_red"><?php echo utf8::strtoupper(Kohana::lang('ui_main.cancel'));?></a>   </li>
+									<?php if ($id): ?>
+									<li><a href="<?php echo $previous_url;?>" class="btns_gray">« <?php echo utf8::strtoupper(Kohana::lang('ui_main.previous'));?></a></li>
+									<li><a href="<?php echo $next_url;?>" class="btns_gray"><?php echo utf8::strtoupper(Kohana::lang('ui_main.next'));?> »</a></li>
+									<?php endif; ?>
+								</ul>
+							</div>
+						</div>
+						
+						<!-- f-col -->
+						<div class="f-col">
+							<?php
+							// Action::report_pre_form_admin - Runs right before report form is rendered
+							Event::run('ushahidi_action.report_pre_form_messages', $id);
+							?>
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.form');?> <span>(<?php echo Kohana::lang('ui_main.select_form_type');?>)</span></h4>
+								<span class="sel-holder">
+									<?php print form::dropdown('form_id', $forms, $form['form_id'],
+										' onchange="formSwitch(this.options[this.selectedIndex].value, \''.$id.'\')"') ?>
+								</span>
+								<div id="form_loader" style="float:left;"></div>
+							</div>
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.title');?></h4>
+								<?php print form::input('incident_title', $form['incident_title'], ' class="text title"'); ?>
+							</div>
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.description');?> <span><?php echo Kohana::lang('ui_main.include_detail');?>.</span></h4>
+								<span class="allowed-html"><?php echo html::allowed_html(); ?></span>
+								<?php print form::textarea('incident_description', $form['incident_description'], ' rows="12" cols="40"'); ?>
+							</div>
+
+							<?php
+							// Action::report_form_admin - Runs just after the report description
+							Event::run('ushahidi_action.report_form_admin', $id);
+							?>
+
+							<?php if (!($id)): ?>
+								<div class="row" id="datetime_default">
+									<h4><a href="#" id="date_toggle" class="new-cat">
+									<?php echo Kohana::lang('ui_main.modify_date');?></a><?php echo Kohana::lang('ui_main.modify_date');?>: 
+									<?php echo Kohana::lang('ui_main.today_at').' '.$form['incident_hour']
+										.":".$form['incident_minute']." ".$form['incident_ampm']; ?>
+									</h4>
+								</div>
+							<?php endif; ?>
+							<div class="row <?php if (!($id)) echo "hide"; ?> " id="datetime_edit">
+								<div class="date-box">
+									<h4><?php echo Kohana::lang('ui_main.date');?> <span><?php echo Kohana::lang('ui_main.date_format');?></span></h4>
+									<?php print form::input('incident_date', $form['incident_date'], ' class="text"'); ?>								
+									<?php print $date_picker_js; ?>				    
+								</div>
+								<div class="time">
+									<h4><?php echo Kohana::lang('ui_main.time');?> <span>(<?php echo Kohana::lang('ui_main.approximate');?>)</span></h4>
+									<?php
+									print '<span class="sel-holder">' .
+								    form::dropdown('incident_hour', $hour_array,
+									$form['incident_hour']) . '</span>';
+									
+									print '<span class="dots">:</span>';
+									
+									print '<span class="sel-holder">' .
+									form::dropdown('incident_minute',
+									$minute_array, $form['incident_minute']) .
+									'</span>';
+									print '<span class="dots">:</span>';
+									
+									print '<span class="sel-holder">' .
+									form::dropdown('incident_ampm', $ampm_array,
+									$form['incident_ampm']) . '</span>';
+									?>
+								</div>
+							</div>
+							<div class="row">
+								<h4><?php echo Kohana::lang('ui_main.categories');?> 
+								<span><?php echo Kohana::lang('ui_main.select_multiple');?>.</span></h4>
+			                    <div class="report_category">
+                        	    <?php
+									$selected_categories = array();
+									if (!empty($form['incident_category']) && is_array($form['incident_category'])) {
+										$selected_categories = $form['incident_category'];
+									}
+									$columns = 2;
+									echo category::form_tree('incident_category', $selected_categories, $columns);
+								?>
+             					</div>
+							</div>
+							
+						<?php echo $custom_forms; ?>
+						
+						</div>
+						<!-- f-col-1 -->
+						<div class="f-col-1">
+							<div class="incident-location">
+								<h4><?php echo Kohana::lang('ui_main.incident_location');?></h4>
+								<div class="location-info">
+									<span><?php echo Kohana::lang('ui_main.latitude');?>:</span>
+									<?php print form::input('latitude', $form['latitude'], ' class="text"'); ?>
+									<span><?php echo Kohana::lang('ui_main.longitude');?>:</span>
+									<?php print form::input('longitude', $form['longitude'], ' class="text"'); ?>
+								</div>
+								<ul class="map-toggles">
+						          <li><a href="#" class="smaller-map">Smaller map</a></li>
+						          <li style="display:block;"><a href="#" class="wider-map">Wider map</a></li>
+						          <li><a href="#" class="taller-map">Taller map</a></li>
+						          <li><a href="#" class="shorter-map">Shorter Map</a></li>
+						        </ul>
+								<div id="divMap" class="map_holder_reports">
+									<div id="geometryLabelerHolder" class="olControlNoSelect">
+										<div id="geometryLabeler">
+											<div id="geometryLabelComment">
+												<span id="geometryLabel">
+													<label><?php echo Kohana::lang('ui_main.geometry_label');?>:</label> 
+													<?php print form::input('geometry_label', '', ' class="lbl_text"'); ?>
+												</span>
+												<span id="geometryComment">
+													<label><?php echo Kohana::lang('ui_main.geometry_comments');?>:</label> 
+													<?php print form::input('geometry_comment', '', ' class="lbl_text2"'); ?>
+												</span>
+											</div>
+											<div>
+												<span id="geometryColor">
+													<label><?php echo Kohana::lang('ui_main.geometry_color');?>:</label> 
+													<?php print form::input('geometry_color', '', ' class="lbl_text"'); ?>
+												</span>
+												<span id="geometryStrokewidth">
+													<label><?php echo Kohana::lang('ui_main.geometry_strokewidth');?>:</label> 
+													<?php print form::dropdown('geometry_strokewidth', $stroke_width_array, ''); ?>
+												</span>
+												<span id="geometryLat">
+													<label><?php echo Kohana::lang('ui_main.latitude');?>:</label> 
+													<?php print form::input('geometry_lat', '', ' class="lbl_text"'); ?>
+												</span>
+												<span id="geometryLon">
+													<label><?php echo Kohana::lang('ui_main.longitude');?>:</label> 
+													<?php print form::input('geometry_lon', '', ' class="lbl_text"'); ?>
+												</span>
+											</div>
+										</div>
+										<div id="geometryLabelerClose"></div>
+									</div>
+								</div>
+							</div>
+							<div class="incident-find-location">
+								<div id="panel" class="olControlEditingToolbar"></div>
+								<div class="btns" style="float:left;">
+									<ul style="padding:4px;">
+										<li><a href="#" class="btn_del_last"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_last'));?></a></li>
+										<li><a href="#" class="btn_del_sel"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_selected'));?></a></li>
+										<li><a href="#" class="btn_clear"><?php echo utf8::strtoupper(Kohana::lang('ui_main.clear_map'));?></a></li>
+									</ul>
+								</div>
+								<div style="clear:both;"></div>
+								<?php print form::input('location_find', '', ' title="'.Kohana::lang('ui_main.location_example').'" class="findtext"'); ?>
+								<div class="btns" style="float:left;">
+									<ul>
+										<li><a href="#" class="btn_find"><?php echo utf8::strtoupper(Kohana::lang('ui_main.find_location'));?></a></li>
+									</ul>
+								</div>
+								<div id="find_loading" class="incident-find-loading"></div>
+								<div style="clear:both;"><?php echo Kohana::lang('ui_main.pinpoint_location');?>.</div>
+							</div>
+							<div class="row">
+								<div class="town">
+									<h4><?php echo Kohana::lang('ui_main.reports_location_name');?> <br /><span><?php echo Kohana::lang('ui_main.detailed_location_example');?></span></h4>
+									<?php print form::input('location_name', $form['location_name'], ' class="text long"'); ?>
+								</div>
+							</div>
+				
+				
+							<!-- News Fields -->
+							<div class="row link-row">
+								<h4><?php echo Kohana::lang('ui_main.reports_news');?></h4>
+							</div>
+							<div id="divNews">
+								<?php
+								$this_div = "divNews";
+								$this_field = "incident_news";
+								$this_startid = "news_id";
+								$this_field_type = "text";
+					
+								if (empty($form[$this_field]))
+								{
+									$i = 1;
+									print "<div class=\"row link-row\">";
+									print form::input($this_field . '[]', '', ' class="text long"');
+									print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+									print "</div>";
+								}
+								else
+								{
+									$i = 0;
+									foreach ($form[$this_field] as $value) {									
+										print "<div ";
+										if ($i != 0) {
+											print "class=\"row link-row second\" id=\"" . $this_field . "_" . $i . "\">\n";
+										}
+										else
+										{
+											print "class=\"row link-row\" id=\"$i\">\n";
+										}
+										print form::input($this_field . '[]', $value, ' class="text long"');
+										print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+										if ($i != 0)
+										{
+											print "<a href=\"#\" class=\"rem\"  onClick='removeFormField(\"#" . $this_field . "_" . $i . "\"); return false;'>remove</a>";
+										}
+										print "</div>\n";
+										$i++;
+									}
+								}
+								print "<input type=\"hidden\" name=\"$this_startid\" value=\"$i\" id=\"$this_startid\">";
+								?>
+							</div>
+
+
+							<!-- Video Fields -->
+							<div class="row link-row">
+								<h4><?php echo Kohana::lang('ui_main.reports_video');?></h4>
+							</div>
+							<div id="divVideo">
+								<?php
+								$this_div = "divVideo";
+								$this_field = "incident_video";
+								$this_startid = "video_id";
+								$this_field_type = "text";
+					
+								if (empty($form[$this_field]))
+								{
+									$i = 1;
+									print "<div class=\"row link-row\">";
+									print form::input($this_field . '[]', '', ' class="text long"');
+									print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+									print "</div>";
+								}
+								else
+								{
+									$i = 0;
+									foreach ($form[$this_field] as $value) {									
+										print "<div ";
+										if ($i != 0) {
+											print "class=\"row link-row second\" id=\"" . $this_field . "_" . $i . "\">\n";
+										}
+										else
+										{
+											print "class=\"row link-row\" id=\"$i\">\n";
+										}
+										print form::input($this_field . '[]', $value, ' class="text long"');
+										print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+										if ($i != 0)
+										{
+											print "<a href=\"#\" class=\"rem\"  onClick='removeFormField(\"#" . $this_field . "_" . $i . "\"); return false;'>remove</a>";
+										}
+										print "</div>\n";
+										$i++;
+									}
+								}
+								print "<input type=\"hidden\" name=\"$this_startid\" value=\"$i\" id=\"$this_startid\">";
+								?>
+							</div>
+				
+				
+							<!-- Photo Fields -->
+							<div class="row link-row">
+								<h4><?php echo Kohana::lang('ui_main.reports_photos');?></h4>
+								<?php								
+    								if ($incident != "0")
+                        			{
+                        				// Retrieve Media
+                        				foreach($incident->media as $photo) 
+                        				{
+                        					if ($photo->media_type == 1)
+                        					{
+                        						print "<div class=\"report_thumbs\" id=\"photo_". $photo->id ."\">";
+
+                        						$thumb = $photo->media_thumb;
+                        						$photo_link = $photo->media_link;
+												$prefix = url::base().Kohana::config('upload.relative_directory');
+                        						print "<a class='photothumb' rel='lightbox-group1' href='$prefix/$photo_link'>";
+                        						print "<img src=\"$prefix/$thumb\" >";
+                        						print "</a>";
+
+                        						print "  <a href=\"#\" onClick=\"deletePhoto('".$photo->id."', 'photo_".$photo->id."'); return false;\" >".Kohana::lang('ui_main.delete')."</a>";
+                        						print "</div>";
+                        					}
+                        				}
+                        			}
+			                    ?>
+							</div>
+							<div id="divPhoto">
+								<?php
+								$this_div = "divPhoto";
+								$this_field = "incident_photo";
+								$this_startid = "photo_id";
+								$this_field_type = "file";
+					
+								if (empty($form[$this_field]['name'][0]))
+								{
+									$i = 1;
+									print "<div class=\"row link-row\">";
+									print form::upload($this_field . '[]', '', ' class="text long"');
+									print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+									print "</div>";
+								}
+								else
+								{
+									$i = 0;
+									foreach ($form[$this_field]['name'] as $value) 
+									{
+										print "<div ";
+										if ($i != 0) {
+											print "class=\"row link-row second\" id=\"" . $this_field . "_" . $i . "\">\n";
+										}
+										else
+										{
+											print "class=\"row link-row\" id=\"$i\">\n";
+										}
+										// print "\"<strong>" . $value . "</strong>\"" . "<BR />";
+										print form::upload($this_field . '[]', $value, ' class="text long"');
+										print "<a href=\"#\" class=\"add\" onClick=\"addFormField('$this_div','$this_field','$this_startid','$this_field_type'); return false;\">add</a>";
+										if ($i != 0)
+										{
+											print "<a href=\"#\" class=\"rem\"  onClick='removeFormField(\"#".$this_field."_".$i."\"); return false;'>remove</a>";
+										}
+										print "</div>\n";
+										$i++;
+									}
+								}
+								print "<input type=\"hidden\" name=\"$this_startid\" value=\"$i\" id=\"$this_startid\">";
+								?>
+							</div>
+						</div>
+						<!-- f-col-bottom -->
+						<div class="f-col-bottom-container">
+							<div class="f-col-bottom">
+								<div class="row">
+									<h4><?php echo Kohana::lang('ui_main.personal_information');?></span></h4>
+									<label>
+										<span><?php echo Kohana::lang('ui_main.first_name');?></span>
+										<?php print form::input('person_first', $form['person_first'], ' class="text"'); ?>
+									</label>
+									<label>
+										<span><?php echo Kohana::lang('ui_main.last_name');?></span>
+										<?php print form::input('person_last', $form['person_last'], ' class="text"'); ?>
+									</label>
+								</div>
+								<div class="row">
+									<label>
+										<span><?php echo Kohana::lang('ui_main.email_address');?></span>
+										<?php print form::input('person_email', $form['person_email'], ' class="text"'); ?>
+									</label>
+								</div>
+							</div>
+							<div style="clear:both;"></div>
+						</div>
+						<div class="btns">
+							<ul>
+								<li><a href="#" class="btn_save"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_report'));?></a></li>
+								<li><a href="#" class="btn_save_close"><?php echo utf8::strtoupper(Kohana::lang('ui_main.save_close'));?></a></li>
+								<?php if ($id): ?>
+									<li><a href="#" class="btn_delete btns_red"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_report')); ?></a></li>
+								<?php endif; ?>
+								<li>
+									<a href="<?php echo url::site().'members/reports/';?>" class="btns_red">
+										<?php echo utf8::strtoupper(Kohana::lang('ui_main.cancel'));?>
+									</a>
+								</li>
+							</ul>
+						</div>						
+					</div>
+				<?php print form::close(); ?>
+				<?php
+				if($id)
+				{
+					// Hidden Form to Perform the Delete function
+					print form::open(url::site().'members/reports/', array('id' => 'reportMain', 'name' => 'reportMain'));
+					$array=array('action'=>'d','incident_id[]'=>$id);
+					print form::hidden($array);
+					print form::close();
+				}
+				?>
+			</div>
diff --git a/application/views/new_password.php b/application/views/new_password.php
new file mode 100644
index 0000000..73fe5dc
--- /dev/null
+++ b/application/views/new_password.php
@@ -0,0 +1,44 @@
+<?php 
+/**
+ * Reset password view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title><?php echo Kohana::lang('ui_main.reset_password');?></title>
+<link href="<?php echo url::file_loc('css'); ?>media/css/login.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+<div id="ushahidi_login_container">
+    <div id="ushahidi_login_logo"><img src="<?php echo url::file_loc('img'); ?>media/img/admin/logo_login.gif" width="400" height="80" /></div>
+    <div id="ushahidi_login">
+      <table width="100%" border="0" cellspacing="3" cellpadding="4" background="" id="ushahidi_loginbox">
+			<tr>
+				<td align="left">
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.password_reset_confirm'); ?></h3>
+						<br />
+						<a href="<?php echo url::site().'login'?>"><?php echo Kohana::lang('ui_main.login');?></a>
+					</div>
+				</td>
+			</tr>
+      </table>
+  </div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/application/views/pagination/classic.php b/application/views/pagination/classic.php
new file mode 100644
index 0000000..02c4bcd
--- /dev/null
+++ b/application/views/pagination/classic.php
@@ -0,0 +1,51 @@
+<?php 
+/**
+ * pagination view.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+/**
+ * Ushahidi pagination style
+ * 
+ * @preview  Pages: 1 … 4 5 6 7 8 … 15
+ */
+?>
+
+<ul class="pager">
+
+	<li class="first"><?php echo $total_pages . " " . Kohana::lang('ui_main.pages') ?></li>
+
+	<?php if ($current_page > 10): ?>
+		<li><a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a></li>
+		<?php if ($current_page != 11) echo '…' ?>
+	<?php endif ?>
+
+
+	<?php for ($i = $current_page - 9, $stop = $current_page + 10; $i < $stop; ++$i): ?>
+
+		<?php if ($i < 1 OR $i > $total_pages) continue ?>
+
+		<?php if ($current_page == $i): ?>
+			<li><a href="#" class="active"><?php echo $i ?></a></li>
+		<?php else: ?>
+			<li><a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a></li>
+		<?php endif ?>
+
+	<?php endfor ?>
+
+
+	<?php if ($current_page <= $total_pages - 10): ?>
+		<?php if ($current_page != $total_pages - 10) echo '…' ?>
+		<li><a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a></li>
+	<?php endif ?>
+
+</ul>
\ No newline at end of file
diff --git a/application/views/reset_password.php b/application/views/reset_password.php
new file mode 100644
index 0000000..f702148
--- /dev/null
+++ b/application/views/reset_password.php
@@ -0,0 +1,79 @@
+<?php 
+/**
+ * Reset password view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title><?php echo Kohana::lang('ui_main.reset_password');?></title>
+<link href="<?php echo url::file_loc('css'); ?>media/css/login.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body>
+<div id="ushahidi_login_container">
+    <div id="ushahidi_login_logo"><img src="<?php echo url::file_loc('img'); ?>media/img/admin/logo_login.gif" width="400" height="80" /></div>
+    <div id="ushahidi_login">
+      <table width="100%" border="0" cellspacing="3" cellpadding="4" background="" id="ushahidi_loginbox">
+      <?php print form::open(NULL,array('id' => 'frm_reset',
+			'name' => 'frm_reset',
+			'method' => 'post', 
+			'style' => 'line-height: 100%; margin-top: 0; margin-bottom: 0' )); ?>     
+			<?php
+			 if ($form_error) { ?>
+            <tr>
+              	<td align="left" class="login_error">
+				<?php
+				foreach ($errors as $error_item => $error_description)
+				{
+					if ($error_description)
+					{
+						echo '• '.$error_description.'<br />';
+					}
+				}
+				?>
+				</td>
+            </tr>
+			<?php }
+			if ($password_reset) { ?>
+			<tr>
+				<td align="left">
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.password_reset_confirm'); ?></h3>
+						<br />
+						<a href="<?php echo url::site().'login'?>"><?php echo Kohana::lang('ui_main.login');?></a>
+					</div>
+				</td>
+			</tr>
+			<?php } else { ?>
+            <tr>
+              <td><strong><?php echo Kohana::lang('ui_main.registered_email')?></strong><br />
+              <?php print form::input('resetemail', '', 
+						' class="login_text"'); ?></td>
+            </tr>
+            <tr>
+              <td><input type="submit" id="resetemail" name="submit" value="Reset password" class="login_btn" />
+              <br /><br />
+              <a href="<?php echo url::site().'login'?>">Login</a>
+              </td>
+            </tr>
+            <?php } ?>
+        <?php print form::close(); ?>
+      </table>
+  </div>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/application/views/reset_password_js.php b/application/views/reset_password_js.php
new file mode 100644
index 0000000..d3cface
--- /dev/null
+++ b/application/views/reset_password_js.php
@@ -0,0 +1,22 @@
+/**
+ * Reset password js file.
+ *
+ * Handles javascript stuff related to reset password function.
+ * 
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Alerts Controller  
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+	function fillFields(email)
+	{
+		$('#email').attr("value", decodeURIComponent( email ) );
+			
+	}
+
diff --git a/application/views/riverid.php b/application/views/riverid.php
new file mode 100644
index 0000000..b09cbb9
--- /dev/null
+++ b/application/views/riverid.php
@@ -0,0 +1 @@
+<?php echo $json; ?>
\ No newline at end of file
diff --git a/debian/changelog b/debian/changelog
deleted file mode 100644
index 6fbb9c1..0000000
--- a/debian/changelog
+++ /dev/null
@@ -1,5 +0,0 @@
-ushahidi (2.7.4-1) UNRELEASED; urgency=low
-
-  * Initial release (Closes: #<bug>)
-
- -- DMPT <debian-med-packaging at lists.alioth.debian.org>  Thu, 24 May 2012 14:30:13 +0200
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec63514..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
deleted file mode 100644
index 470554e..0000000
--- a/debian/control
+++ /dev/null
@@ -1,22 +0,0 @@
-Source: ushahidi
-Section: science
-Priority: optional
-Maintainer: Debian Med Packaging Team <debian-med-packaging at lists.alioth.debian.org>
-Uploaders: Andreas Tille <tille at debian.org>
-Build-Depends: debhelper (>= 9)
-Standards-Version: 3.9.6
-Vcs-Browser: http://anonscm.debian.org/viewvc/debian-med/trunk/packages/ushahidi/trunk/
-Vcs-Svn: svn://anonscm.debian.org/debian-med/trunk/packages/ushahidi/trunk/
-Homepage: http://www.ushahidi.com/
-
-Package: ushahidi
-Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}
-Description: web platform for information collection
- Ushahidi is a platform that allows information collection,
- visualization and interactive mapping, allowing anyone to submit
- information through text messaging using a mobile phone, email or web
- form.
- .
- It can be used to monitor epidemic diseases, measuring the impact of
- natural disasters, uncovering corruption, and empowering peace makers.
diff --git a/debian/copyright b/debian/copyright
deleted file mode 100644
index 993a9e7..0000000
--- a/debian/copyright
+++ /dev/null
@@ -1,15 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: Ushahidi
-Source: https://github.com/ushahidi/Ushahidi_Web/releases
-
-Files: *
-Copyright: © 2009-2014 <upstream>
-License: LGPL-3+
-
-Files: debian/*
-Copyright: © 2014 maintainername <maintainer at e.mail>
-License: LGPL-3+
-
-License: LGPL-3+
- On Debian systems you can find the full text of the GNU Lesser General
- Public License version 3 under /usr/share/common-licenses/LGPL-3
diff --git a/debian/rules b/debian/rules
deleted file mode 100755
index 58ec00b..0000000
--- a/debian/rules
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/make -f
-
-# DH_VERBOSE := 1
-
-# some helpful variables - uncomment them if needed
-# shamelessly stolen from http://jmtd.net/log/awk/
-#DEBVERS        := $(shell dpkg-parsechangelog | awk '/^Version:/ {print $$2}')
-#VERSION        := $(shell echo '$(DEBVERS)' | sed -e 's/^[0-9]*://' -e 's/-.*//')
-#DEBFLAVOR      := $(shell dpkg-parsechangelog | awk '/^Distribution:/ {print $$2}')
-#DEBPKGNAME     := $(shell dpkg-parsechangelog | awk '/^Source:/ {print $$2}')
-#DEBIAN_BRANCH  := $(shell awk 'BEGIN{FS="[= ]+"} /debian-branch/ {print $$2}' debian/gbp.conf)
-#GIT_TAG        := $(subst ~,_,$(VERSION))
-
-# alternatively to manually set those variables you can
-#  include /usr/share/cdbs/1/rules/buildvars.mk
-# and use what is set there.  Any hint whether dh might set variables in
-# a similar manner are welcome.
-
-%:
-	dh $@
-
-#get-orig-source:
-#	. debian/get-orig-source
diff --git a/debian/source/format b/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/debian/watch b/debian/watch
deleted file mode 100644
index 91b051a..0000000
--- a/debian/watch
+++ /dev/null
@@ -1,3 +0,0 @@
-version=3
-
-https://github.com/ushahidi/Ushahidi_Web/releases .*/archive/(\d[\d.-]+)\.(?:tar(?:\.gz|\.bz2)?|tgz)
diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..4b1d379
Binary files /dev/null and b/favicon.ico differ
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..4b558ba
--- /dev/null
+++ b/index.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ * This file acts as the "front controller" to your application. You can
+ * configure your application, modules, and system directories here.
+ * PHP error_reporting level may also be changed.
+ *
+ * @see http://kohanaphp.com
+ */
+
+/**
+ * This checks to see if the site is in maintenance mode. Put your site
+ * in maintenance mode by putting a 'maintenance.php' file in the root
+ * directory of your site. (Same directory as this index.php file)
+ *
+ */
+if (file_exists('maintenance.php'))
+{
+	header("Status: 503 Service Temporarily Unavailable");
+	die(file_get_contents('maintenance.php'));
+}
+
+/**
+ * Define the website environment status. When this flag is set to TRUE, some
+ * module demonstration controllers will result in 404 errors. For more information
+ * about this option, read the documentation about deploying Kohana.
+ *
+ * @see http://docs.kohanaphp.com/installation/deployment
+ */
+define('IN_PRODUCTION', TRUE);
+
+/**
+ * Website application directory. This directory should contain your application
+ * configuration, controllers, models, views, and other resources.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_application = 'application';
+
+/**
+ * Kohana modules directory. This directory should contain all the modules used
+ * by your application. Modules are enabled and disabled by the application
+ * configuration file.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_modules = 'modules';
+
+/**
+ * Kohana system directory. This directory should contain the core/ directory,
+ * and the resources you included in your download of Kohana.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_system = 'system';
+
+/**
+ * Themes directory.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_themes = 'themes';
+
+/**
+ * Plugin directory.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_plugins = 'plugins';
+
+/**
+ * Media directory.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_media = 'media';
+
+/**
+ * Test to make sure that Kohana is running on PHP 5.2 or newer. Once you are
+ * sure that your environment is compatible with Kohana, you can comment this
+ * line out. When running an application on a new server, uncomment this line
+ * to check the PHP version quickly.
+ */
+version_compare(PHP_VERSION, '5.2', '<') and exit('Kohana requires PHP 5.2 or newer.');
+
+/**
+ * Set the error reporting level. Unless you have a special need, E_ALL is a
+ * good level for error reporting.
+ */
+error_reporting(E_ALL & ~E_STRICT);
+
+/**
+ * Turning off display_errors will effectively disable Kohana error display
+ * and logging. You can turn off Kohana errors in application/config/config.php
+ */
+ini_set('display_errors', TRUE);
+
+/**
+ * If you rename all of your .php files to a different extension, set the new
+ * extension here. This option can left to .php, even if this file has a
+ * different extension.
+ */
+define('EXT', '.php');
+
+//
+// DO NOT EDIT BELOW THIS LINE, UNLESS YOU FULLY UNDERSTAND THE IMPLICATIONS.
+// ----------------------------------------------------------------------------
+// $Id: index.php 3168 2008-07-21 01:34:36Z Shadowhand $
+//
+
+// Define the front controller name and docroot
+define('DOCROOT', getcwd().DIRECTORY_SEPARATOR);
+define('KOHANA',  basename(__FILE__));
+
+// If the front controller is a symlink, change to the real docroot
+is_link(KOHANA) and chdir(dirname(realpath(__FILE__)));
+
+// Define application and system paths
+define('APPPATH', str_replace('\\', '/', realpath($kohana_application)).'/');
+define('THEMEPATH', str_replace('\\', '/', realpath($kohana_themes)).'/');
+define('PLUGINPATH', str_replace('\\', '/', realpath($kohana_plugins)).'/');
+define('MODPATH', str_replace('\\', '/', realpath($kohana_modules)).'/');
+define('SYSPATH', str_replace('\\', '/', realpath($kohana_system)).'/');
+define('MEDIAPATH', str_replace('\\', '/', realpath($kohana_media)).'/');
+
+// Clean up
+unset($kohana_application, $kohana_themes, $kohana_plugins, $kohana_modules, $kohana_system, $kohana_media);
+
+if ( ! IN_PRODUCTION)
+{
+	// Check APPPATH
+	if ( ! (is_dir(APPPATH) AND is_file(APPPATH.'config/config'.EXT)))
+	{
+		die
+		(
+			'<div style="width:80%;margin:50px auto;text-align:center;">'.
+				'<h3>Application Directory Not Found</h3>'.
+				'<p>The <code>$kohana_application</code> directory does not exist.</p>'.
+				'<p>Set <code>$kohana_application</code> in <tt>'.KOHANA.'</tt> to a valid directory and refresh the page.</p>'.
+			'</div>'
+		);
+	}
+
+	// Check SYSPATH
+	if ( ! (is_dir(SYSPATH) AND is_file(SYSPATH.'core/Bootstrap'.EXT)))
+	{
+		die
+		(
+			'<div style="width:80%;margin:50px auto;text-align:center;">'.
+				'<h3>System Directory Not Found</h3>'.
+				'<p>The <code>$kohana_system</code> directory does not exist.</p>'.
+				'<p>Set <code>$kohana_system</code> in <tt>'.KOHANA.'</tt> to a valid directory and refresh the page.</p>'.
+			'</div>'
+		);
+	}
+}
+
+// 
+// Check if the application has been installed
+// -------------------------------------------
+// This has to be done before bootstrapping the Kohana framework
+// 
+// Does the installer directory exist?
+if (file_exists(DOCROOT.DIRECTORY_SEPARATOR.'installer'))
+{
+	if ( ! file_exists(APPPATH.'config'.DIRECTORY_SEPARATOR.'database.php'))
+	{
+		// Redirect to the installer
+		header("Location: ./installer");
+		exit();
+	}
+}
+
+// Initialize.
+require SYSPATH.'core/Bootstrap'.EXT;
diff --git a/installer/index.php b/installer/index.php
new file mode 100755
index 0000000..d417b37
--- /dev/null
+++ b/installer/index.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * This file acts as the bootstrap for the installer.
+ * It forwards all HTTP requests to the Install_Wizard class
+ *
+ * @copyright Ushahidi Inc <http://www.ushahidi.com>
+ */
+
+// Define absolute paths for the root and installation directories
+define('ROOT_DIR', realpath(dirname(dirname(__FILE__))).DIRECTORY_SEPARATOR);
+define('INSTALLER_DIR', ROOT_DIR.'installer'.DIRECTORY_SEPARATOR);
+define('INSTALLER_PAGES', INSTALLER_DIR.'pages'.DIRECTORY_SEPARATOR);
+
+require INSTALLER_DIR . 'wizard.php';
+require INSTALLER_DIR . 'utils.php';
+
+// Bootstrap the installer
+Installer_Wizard::init();
+
+if ($_POST)
+{
+	if  (isset($_POST['previous']))
+	{
+		Installer_Wizard::previous();
+	}
+	else
+	{
+		// Show the next step
+		Installer_Wizard::next();
+	}
+}
+
+?>
\ No newline at end of file
diff --git a/installer/mod_rewrite/.htaccess b/installer/mod_rewrite/.htaccess
new file mode 100644
index 0000000..30b3886
--- /dev/null
+++ b/installer/mod_rewrite/.htaccess
@@ -0,0 +1,2 @@
+RewriteEngine On
+RewriteRule ^.*$ test.php
\ No newline at end of file
diff --git a/installer/mod_rewrite/test.php b/installer/mod_rewrite/test.php
new file mode 100644
index 0000000..151421c
--- /dev/null
+++ b/installer/mod_rewrite/test.php
@@ -0,0 +1,3 @@
+<?php
+print 'mod_rewrite works!';
+?>
\ No newline at end of file
diff --git a/installer/pages/adminpassword.php b/installer/pages/adminpassword.php
new file mode 100644
index 0000000..0eae114
--- /dev/null
+++ b/installer/pages/adminpassword.php
@@ -0,0 +1,62 @@
+<body>
+	<div id="ushahidi_install_container" class="advanced">
+		<div id="ushahidi_login_logo"><img src="../media/img/admin/logo_login.gif" /></div>
+		<div id="ushahidi_login">
+			<ol class="progress-meter clearfix">
+				<li><span>Database</span></li>
+				<li><span>General</span></li>
+		
+				<?php if ($install_mode === 'advanced'): ?>
+				<li><span>Mail Server</span></li>
+				<li><span>Map</span></li>
+				<?php endif; ?>
+		
+				<li class="active"><span>Admin Password</span></li>
+				<li class="last"><span>Finished</span></li>
+			</ol>
+
+		    <form method="POST" name="frm_install" style="line-height: 100%; margin-top: 0; margin-bottom: 0;">  
+				<?php if (isset($errors)): ?>
+		   		<div class="feedback error"><a class="btn-close" href="#">x</a>
+		       		<p>Listed below is a summary of the errors we encountered:</p>
+		   				<ul id="error-list">
+						<?php foreach ($errors as $error): ?>
+							<li><?php print $error; ?></li>
+						<?php endforeach; ?>
+						</ul>
+					</p>
+				</div>
+				<?php endif; ?>
+				
+				<div class="feedback info">
+				 	<p>You can make use of this <a href="http://strongpasswordgenerator.com/">strong password generator</a>. Please note that the only allowable symbols in your password are the # and @symbol.</p> 
+				</div> 
+				<table class="form-table fields">
+					<tbody>
+						<!-- The design of the password form -->
+						<tr>
+							<th scope="row"><label for="email">Admin Email</label></th>
+							<td><input value="<?php print $form['email']; ?>" size="25" name="email" /></td>
+							<td>The administrative user will log in with this email address.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="password">Password</label></th>
+							<td><input type="password" value="" size="25" name="password" /></td>
+							<td>Please type in your preferred password for your deployment's administrative user.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="confirm_password">Confirm Password</label></th>
+							<td><input type="password" value="" size="25" name="confirm_password"></td>
+							<td>Please re-type your password for confirmation.</td>
+						</tr>
+					</tbody>
+				</table>
+				<div class="actions clearfix">
+					<div class="next"><input type="submit" name="continue" value="Continue →" class="button" /></div>
+					<div class="prev"><input type="submit" name="previous" value="← Previous" class="button" /></div>
+				</div>
+			</form>
+		</div>
+	</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/installer/pages/database.php b/installer/pages/database.php
new file mode 100644
index 0000000..e976c7c
--- /dev/null
+++ b/installer/pages/database.php
@@ -0,0 +1,76 @@
+<body>
+	<div id="ushahidi_install_container" class="advanced">
+		<div id="ushahidi_login_logo"><img src="../media/img/admin/logo_login.gif" /></div>
+		<div id="ushahidi_login">
+			<ol class="progress-meter clearfix">
+				<li class="active"><span>Database</span></li>
+				<li class=""><span>General</span></li>
+		
+				<?php if ($install_mode === 'advanced'): ?>
+				<li class=""><span>Mail Server</span></li>
+				<li class=""><span>Map</span></li>
+				<?php endif; ?>
+		
+				<li class=""><span>Admin Password</span></li>
+				<li class="last"><span>Finished</span></li>
+			</ol>
+
+		    <form method="POST" name="frm_install" style="line-height: 100%; margin-top: 0; margin-bottom: 0;">  
+				<?php if (isset($errors)): ?>
+		   		<div class="feedback error"><a class="btn-close" href="#">x</a>
+		       		<p>Listed below is a summary of the errors we encountered:</p>
+		   				<ul id="error-list">
+						<?php foreach ($errors as $error): ?>
+							<li><?php print $error; ?></li>
+						<?php endforeach; ?>
+						</ul>
+					</p>
+				</div>
+				<?php endif; ?>
+				<table class="form-table fields">
+					<tbody>
+						<tr>
+							<th scope="row"><label for="database">Database Name</label></th>
+							<td><input type="text" value="<?php print $form['database']; ?>" size="25" id="db_name" name="database"/></td>
+							<td>The name of the database you want to run Ushahidi in. </td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="username">User Name</label></th>
+							<td><input type="text" value="<?php print $form['username']; ?>" size="25" id="username" name="username"/></td>
+							<td>Your database username.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="password">Password</label></th>
+							<td><input type="password" value="<?php print $form['password']; ?>" size="25" id="password" name="password"/></td>
+							<td>Your database password.</td>
+						</tr>
+						
+						<tr>
+							<th scope="row"><label for="host">Database Host</label></th>
+							<td><input type="text" value="<?php print $form['host']; ?>" size="25" id="host" name="host"/></td>
+							<td>If you are running Ushahidi on your own computer, this will more than likely be "localhost". If you are running Ushahidi from a web server, you'll get your host information from your web hosting provider.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="table_prefix">Table Prefix</label></th>
+							<td><input type="text" size="25" value="<?php print $form['table_prefix']; ?>" id="table_prefix" name="table_prefix"/></td>
+							<td>Normally you won't change the table prefix.	 However, If you want to run multiple Ushahidi installations from a single database you can do that by changing the prefix here.</td>
+						</tr>
+						<input type="hidden" name="connection" />
+						<input type="hidden" name="permission" />
+						<input type="hidden" name="load_db_tpl" />
+						<input type="hidden" name="load_htaccess_file" />
+						<input type="hidden" name="config_perm" />
+						<input type="hidden" name="htaccess_perm" />
+					</tbody>
+				</table>
+
+				
+				<div class="actions clearfix">
+					<div class="next"><input type="submit" name="continue" value="Continue →" class="button" /></div>
+					<div class="prev"><!-- <input type="submit" name="previous" value="← Previous" class="button" /> --></div>
+				</div>
+			</form>
+		</div>
+	<div>
+</body>
+</html>
\ No newline at end of file
diff --git a/installer/pages/email.php b/installer/pages/email.php
new file mode 100644
index 0000000..544089f
--- /dev/null
+++ b/installer/pages/email.php
@@ -0,0 +1,90 @@
+<body>
+	<div id="ushahidi_install_container" class="advanced">
+		<div id="ushahidi_login_logo"><img src="../media/img/admin/logo_login.gif" /></div>
+		<div id="ushahidi_login">
+			<ol class="progress-meter clearfix">
+				<li><span>Database</span></li>
+				<li><span>General</span></li>
+		
+				<?php if ($install_mode === 'advanced'): ?>
+				<li class="active"><span>Mail Server</span></li>
+				<li><span>Map</span></li>
+				<?php endif; ?>
+		
+				<li><span>Admin Password</span></li>
+				<li class="last"><span>Finished</span></li>
+			</ol>
+
+		    <form method="POST" name="frm_install" style="line-height: 100%; margin-top: 0; margin-bottom: 0;">  
+				<?php if (isset($errors)): ?>
+		   		<div class="feedback error"><a class="btn-close" href="#">x</a>
+		       		<p>Listed below is a summary of the errors we encountered:</p>
+		   				<ul id="error-list">
+						<?php foreach ($errors as $error): ?>
+							<li><?php print $error; ?></li>
+						<?php endforeach; ?>
+						</ul>
+					</p>
+				</div>
+				<?php endif; ?>
+				
+				<table class="form-table fields">
+					<tbody>
+						<tr>
+							<th scope="row"><label for="alerts_email">Site Alert Email Address</label></th>
+							<td><input type="text" value="<?php print $form['alerts_email']; ?>" size="25" name="alerts_email"/></td>
+							<td>When your site visitors sign up for email alerts, they will recieve emails from this address. This email address does not have to be the same as the Site Email Address.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="email_host">Mail Server Host</label></th>
+							<td><input type="text" value="<?php print $form['email_host']; ?>" size="25" name="email_host"/></td>
+							<td>Examples: mail.yourwebsite.com, imap.gmail.com, pop.gmail.com.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="email_username">Mail Server Username</label></th>
+							<td><input type="text" value="<?php print $form['email_username']; ?>" size="25" name="email_username"/></td>
+							<td>If you're using Gmail, Hotmail, or Yahoo Mail, enter a full email address as a username.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="email_password">Mail Server Password </label></th>
+							<td><input type="password" value="<?php print $form['email_password']; ?>" size="25" name="email_password"/></td>
+							<td>The password you normally use to login in to your email.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="email_port">Mail Server Port</label></th>
+							<td><input type="text" value="<?php print $form['email_port']; ?>" size="25" name="email_port"/></td>
+							<td>Common Ports: 25, 110, 995 (Gmail POP3 SSL), 993 (Gmail IMAP SSL) .</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="select_mail_server_type">Mail Server Type</label></th>
+							<td>
+								<select name="select_mail_server_type">
+									<option value="imap" selected="selected">IMAP</option>
+									<option value="pop">POP</option>
+								</select>
+							</td>
+							<td>Internet Message Access Protocol (IMAP) or Post Office Protocol (POP). <a href="http://www1.umn.edu/adcs/guides/email/imapvspop.html" target="_blank">What's the difference?</a></td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="email_ssl">Enable or disable SSL</label></th>
+							<td>
+								<select name="email_ssl">
+									<option value="0" selected="selected">Disable</option>
+									<option value="1">Enable</option>
+								</select>
+							</td>
+							<td>Some mail servers give you the option of using <abbr title="Secure Sockets Layer">SSL</abbr> when making a connection. Using SSL is recommended as it gives you an added level of security.</td>
+						</tr>
+					</tbody>
+				</table>
+				
+				<div class="actions clearfix">
+					<div class="next"><input type="submit" name="continue" value="Continue →" class="button" /></div>
+					<div class="prev"><input type="submit" name="previous" value="← Previous" class="button" /></div>
+				</div>
+
+			</form>
+		</div>
+	<div>
+</body>
+</html>
\ No newline at end of file
diff --git a/installer/pages/finish.php b/installer/pages/finish.php
new file mode 100644
index 0000000..03698bd
--- /dev/null
+++ b/installer/pages/finish.php
@@ -0,0 +1,34 @@
+<body>
+	<div id="ushahidi_install_container" class="advanced">
+		<div id="ushahidi_login_logo"><img src="../media/img/admin/logo_login.gif" /></div>
+		<div id="ushahidi_login">
+			<ol class="progress-meter clearfix">
+				<li><span>Database</span></li>
+				<li><span>General</span></li>
+				<?php if ($install_mode === 'advanced'): ?>
+				<li><span>Mail Server</span></li>
+				<li><span>Map</span></li>
+				<?php endif; ?>
+				<li><span>Admin Password</span></li>
+				<li class="active last"><span>Finished</span></li>
+			</ol>
+			<form method="POST" name="frm_install" style="line-height: 100%; margin-top: 0; margin-bottom: 0;">
+				<div class="feedback success">
+					<h2>Installation Successful!</h2>
+				</div>
+				<p>To login, go to <a href="<?php echo $base_url; ?>admin" target="_blank">
+				<?php echo $base_url."admin"; ?></a> and use the following credentials:<br /><br />
+				<strong>Login Email:</strong> <?php echo $admin_email; ?><br />
+				<strong>Password:</strong> (not shown)</p>
+				<p><strong>Other next steps...</strong></p>
+				<ul>
+					<li><a href="<?php echo $base_url; ?>" target="_blank">View your website</a></li>
+					<li><a href="<?php echo $base_url; ?>admin/reports/edit" target="_blank">Upload report data</a></li>
+					<li><a href="<?php echo $base_url; ?>admin/settings" target="_blank">Configure your map</a></li>
+					<li><a href="<?php echo $base_url; ?>admin/settings/sms" target="_blank">Setup your SMS server</a></li>
+				</ul>
+			</form>
+		</div>
+	</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/installer/pages/general.php b/installer/pages/general.php
new file mode 100644
index 0000000..6925d8d
--- /dev/null
+++ b/installer/pages/general.php
@@ -0,0 +1,88 @@
+<body>
+	<div id="ushahidi_install_container" class="advanced">
+		<div id="ushahidi_login_logo"><img src="../media/img/admin/logo_login.gif" /></div>
+		<div id="ushahidi_login">
+			<ol class="progress-meter clearfix">
+				<li><span>Database</span></li>
+				<li class="active"><span>General</span></li>
+				<?php if ($install_mode === 'advanced'): ?>
+				<li><span>Mail Server</span></li>
+				<li><span>Map</span></li>
+				<?php endif; ?>
+				<li><span>Admin Password</span></li>
+				<li class="last"><span>Finished</span></li>
+			</ol>
+
+			<form method="POST" name="frm_install" style="line-height: 100%; margin-top: 0; margin-bottom: 0;">
+				<?php if (isset($errors)): ?>
+		   		<div class="feedback error"><a class="btn-close" href="#">x</a>
+		       		<p>Listed below is a summary of the errors we encountered:</p>
+		   				<ul id="error-list">
+						<?php foreach ($errors as $error): ?>
+							<li><?php print $error; ?></li>
+						<?php endforeach; ?>
+						</ul>
+					</p>
+				</div>
+				<?php endif; ?>
+				
+				<table class="form-table fields">
+					<tbody>
+						<tr>
+							<th scope="row"><label for="site_name">Site Name</label></th>
+							<td><input type="text" value="<?php print $form['site_name']; ?>" size="25" id="site_name" name="site_name"/></td>
+							<td>The name of your site.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="site_tagline">Site Tagline.</label></th>
+							<td><input type="text" value="<?php print $form['site_tagline']; ?>" size="25" id="site_tagline" name="site_tagline"/></td>
+							<td>Your tagline </td>
+						</tr>
+						 <tr>
+							<th scope="row"><label for="site_language">Default Language (Locale)</label></th>
+							<td>
+								<select name="site_language">
+									<option value="en_US" selected="selected">English (US)</option>
+									<option value="fr_FR">Français</option>
+								</select>
+							</td>
+							<td>Each Ushahidi deployment comes with a set of built in language translations. You can also <a href="https://wiki.ushahidi.com/display/WIKI/Localization" target="_blank">add your own</a>.</td>
+						</tr>
+						<tr>
+							<th scope="row"><label for="site_email">Site Email Address</label></th>
+							<td><input type="text" value="<?php print $form['site_email']; ?>" size="25" id="site_email" name="site_email"/></td>
+							<td>Site wide email communication will be funneled through this address.</td>
+						</tr>
+					   	<tr>
+							<th scope="row"><label for="enable_clean_url">Enable Clean URLs</label></th>
+							<?php if ( ! $enable_clean_urls): ?>
+							<td>
+								<select name="enable_clean_urls" disabled="true">
+									<option value="1" >Yes</option>
+									<option value="0" selected="selected">No</option>
+								</select>
+							</td>
+							<td>It looks like your server is not configured to handle clean URLs. You will need to change the configuration of your server before you can enable clean URLs. See more info on how to enable clean URLs at this forum <a href="http://forums.ushahidi.com/topic/server-configuration-for-apache-mod-rewrite" target="_blank">post</a> </td>		
+							<?php else: ?>
+							<td>
+								<select name="enable_clean_urls">
+									<option value="1" selected="selected">Yes</option>
+									<option value="0">No</option>
+								</select>
+							</td>
+							<td>This option makes Ushahidi to be accessed via "clean" URLs without "index.php" in the URL.</td>
+							<?php endif; ?>
+						</tr>
+					</tbody>
+				</table>
+				<div class="actions clearfix">
+					<div class="next"><input type="submit" name="continue" value="Continue →" class="button" /></div>
+					<div class="prev"><input type="submit" name="previous" value="← Previous" class="button" /></div>
+				</div>
+
+			</form>
+
+		</div>
+	</div>
+</body>
+</html>
diff --git a/installer/pages/header.php b/installer/pages/header.php
new file mode 100644
index 0000000..f39ff7b
--- /dev/null
+++ b/installer/pages/header.php
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title>Ushahidi Web Installer</title>
+<link href="../media/css/installer.css" rel="stylesheet" type="text/css" />
+</head>
+<script src="../media/js/jquery.js" type="text/javascript" charset="utf-8"></script>
+<script src="../media/js/login.js" type="text/javascript" charset="utf-8"></script>
+</head>
diff --git a/installer/pages/main.php b/installer/pages/main.php
new file mode 100644
index 0000000..ae207d9
--- /dev/null
+++ b/installer/pages/main.php
@@ -0,0 +1,25 @@
+<body>
+	<div id="ushahidi_install_container">
+	    <div id="ushahidi_login_logo"><img src="../media/img/admin/logo_login.gif" /></div>
+	    <div id="ushahidi_login" class="clearfix">
+    	
+		<form method="POST">
+		<p>Welcome to the Ushahidi server install process.  Choose which type of installation you would like to use below:</p>
+		
+		<a class="two-col-box tc-left btn-box" id="install-box-basic">
+			<span class="btn-box-title">BASIC INSTALLATION</span>
+			<span class="btn-box-content">Simple and fast.  All you need is your website's root directory and your database information.  Choose this option if you want to get up and running quickly, and you can always configure everything else later.</span>
+			<input type="submit" name="install_mode_basic" class="button install-button-basic" value="Proceed with basic →"/>
+		</a>
+		<a class="two-col-box tc-right btn-box" id="install-box-advanced">
+			<span class="btn-box-title">ADVANCED INSTALLATION</span>
+			<span class="btn-box-content">Get all the basic settings completed through this 5-step process.  This includes server, map, site name and contact details.</span>
+			<input type="submit" name="install_mode_advanced" class="button install-button-advanced" value="Proceed with advanced →"/>
+			<br /><br /><br />
+		</a>
+		</form>
+        	
+		</div>
+	</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/installer/pages/map.php b/installer/pages/map.php
new file mode 100644
index 0000000..155691d
--- /dev/null
+++ b/installer/pages/map.php
@@ -0,0 +1,85 @@
+<body>
+	<div id="ushahidi_install_container" class="advanced">
+		<div id="ushahidi_login_logo"><img src="../media/img/admin/logo_login.gif" /></div>
+		<div id="ushahidi_login">
+			<ol class="progress-meter clearfix">
+				<li><span>Database</span></li>
+				<li><span>General</span></li>
+				<?php if ($install_mode === 'advanced'): ?>
+				<li><span>Mail Server</span></li>
+				<li class="active"><span>Map</span></li>
+				<?php endif; ?>
+				<li><span>Admin Password</span></li>
+				<li class="last"><span>Finished</span></li>
+			</ol>
+
+			<form method="POST" name="frm_install" action="" style="line-height: 100%; margin-top: 0; margin-bottom: 0;">
+				<?php if (isset($errors)): ?>
+		   		<div class="feedback error"><a class="btn-close" href="#">x</a>
+		       		<p>Listed below is a summary of the errors we encountered:</p>
+		   				<ul id="error-list">
+						<?php foreach ($errors as $error): ?>
+							<li><?php print $error; ?></li>
+						<?php endforeach; ?>
+						</ul>
+					</p>
+				</div>
+				<?php endif; ?>
+				
+				<table class="form-table fields">
+					<tbody>
+						<tr>
+							<th scope="row"><label for="default_map">Map Provider</label></th>
+							<td>
+								<select name="default_map" id="default_map">
+									<option selected="selected">--Select Map Provider</option>
+									<option value="osm_mapnik" url="http://www.openstreetmap.org/user/new">OpenStreetMap</option>
+									<option value="google_normal" url="https://developers.google.com/maps/signup">Google</option>
+									<option value="bing_road" url="https://www.bingmapsportal.com/">Bing Maps</option>
+								</select>								 
+							</td>
+							<td>Ushahidi works equally well with any of these three mapping providers: Google, Bing or OpenStreetMap. Choose the one that has the most detail in your area.</td>
+						</tr>
+						<tr id="api_key_row">
+							<th scope="row"><label id="map_provider_label" for="api_key"><span>Google</span> API Key</label></th>
+							<td><input type="text" value="" size="25" name="api_key"/></td>
+							<td>Anyone can get an api key. <a id="api-link" href="http://code.google.com/apis/maps/signup.html" target="_blank">Get yours now</a> (<span id="map-provider-title">Google</span>).
+							</td>
+						</tr> 
+					</tbody>
+				</table>
+				
+				<div class="actions clearfix">
+					<div class="next"><input type="submit" name="continue" value="Continue →" class="button" /></div>
+					<div class="prev"><input type="submit" name="previous" value="← Previous" class="button" /></div>
+				</div>
+			</form>
+		</div>
+	</div>
+<body>
+<script type="text/javascript">
+	jQuery(function(){
+		$("#api_key_row").hide();
+		
+		$("#default_map").change(function(){
+			if ($(this).val() == "" || $(this).val() == null)
+				return;
+				
+			if ($(this).val() == 'bing_road') {
+				var providerTitle = $(":selected", this).text();
+				var labelHTML = "<span>"+providerTitle+"</span> API Key";
+				$("#api_key_row").show();
+				$("#map_provider_label", "#api_key_row").html(labelHTML);
+				
+				// Get the url
+				$("#api-link").attr({href: $(":selected", this).attr("url")});
+				$("#map-provider-title").html(providerTitle);
+
+			} else {
+
+				$("#api_key_row").hide();
+			}
+		});
+	});
+</script>
+</html>
diff --git a/installer/pages/requirements.php b/installer/pages/requirements.php
new file mode 100644
index 0000000..c39f59c
--- /dev/null
+++ b/installer/pages/requirements.php
@@ -0,0 +1,106 @@
+<body>
+	<div id="ushahidi_install_container">
+	    <div id="ushahidi_login_logo"><img src="../media/img/admin/logo_login.gif" /></div>
+	    <div id="ushahidi_login" class="clearfix">
+	    <form method="POST" name="frm_install" style="line-height: 100%; margin-top: 0; margin-bottom: 0;">  
+		
+		<?php if (isset($errors)): ?>
+	   		<div class="feedback error"><a class="btn-close" href="#">x</a>
+	       		<p>Listed below is a summary of the errors we encountered:</p>
+	   				<ul id="error-list">
+					<?php foreach ($errors as $error): ?>
+						<li><?php print $error; ?></li>
+					<?php endforeach; ?>
+					</ul>
+				</p>
+			</div>
+		<?php endif; ?>
+		
+		<?php if ($install_mode === 'basic'): ?>
+	    <div class="feedback info"> 
+	    	<p>Before you get started, you will need to make sure the following files and folders are writable by your webserver. This involves changing file permissions.</p>
+	            <ul>
+	                <li>application/config</li>
+	                <li>application/cache</li>
+	                <li>application/logs</li>
+	                <li>media/uploads</li>
+	                <li>.htaccess</li>
+	            </ul> 
+
+	        <p>Here are instructions for changing file permissions:</p>
+	        <ul>
+	            <li><a href="http://www.washington.edu/computing/unix/permissions.html" target="_blank">Unix/Linux</a></li>
+	            <li><a href="http://support.microsoft.com/kb/308419" target="_blank">Windows</a></li>
+	        </ul>
+	    </div>
+
+		<p>For the installation process, please have the following bits of information on hand.</p>	
+		<div class="two-col tc-left">
+	        <h3>Database <a href="http://wiki.ushahidi.com/doku.php?id=a_brief_word_on_databases" target="_blank">what's this?</a></h3>
+	        <ol>	
+	            <li>Database name</li>
+	            <li>Database username</li>
+	            <li>Database password</li>
+	            <li>Database host</li>
+
+	        </ol>
+	    </div>
+	    <div class="two-col tc-right">
+	        <h3>General</h3>
+	        <ol>	
+	            <li>Site name & tagline</li>
+	            <li>Site Email Address</li>
+	        </ol>
+	    </div>
+	    <div style="clear:both"></div>
+		<p>
+			<input type="submit" name="previous" class="button" value="← Go back"/>  
+			<input type="submit" name="continue" value="Let's get started!" class="button"  /></p>
+		</div>
+		
+		<?php elseif ($install_mode === 'advanced'): ?>
+			<p>Before you get started, you will need to make sure the following files and folders are writable by your webserver. This involves changing file permissions.</p>
+			<div class="two-col tc-left">
+		        <h3>Database <a href="http://wiki.ushahidi.com/doku.php?id=a_brief_word_on_databases" target="_blank">what's this?</a></h3>
+		        <ol>	
+		            <li>Database name</li>
+		            <li>Database username</li>
+		            <li>Database password</li>
+		            <li>Database host</li>
+		        </ol>
+
+		        <h3>General</h3>
+		        <ol>	
+		            <li>Site name & tagline</li>
+		            <li>Site Email Address</li>
+		        </ol>
+		    </div>
+		    <div class="two-col tc-right last">
+		    	<h3>Mail Server</h3>
+		        <ol>
+		            <li>Site alert email address</li>
+		            <li>Mail Server Username</li>
+		            <li>Mail Server Password</li>
+		            <li>Mail Server Port</li>
+		            <li>Mail Server Host</li>
+		            <li>Mail Server Type</li>
+		        </ol>
+		        <h3>Map</h3>
+		        <ol>	
+		            <li>Map Provider</li>
+		            <li>API Key</li>
+		        </ol>
+			</div>	
+
+			
+			<div class="actions clearfix">
+				<div class="next"><input type="submit" name="continue" value="Continue →" class="button" /></div>
+				<div class="prev"><input type="submit" name="previous" value="← Previous" class="button" /></div>
+			</div>
+			</div>
+
+		<?php endif; ?>
+		<form>
+	<div>
+<body>
+<html>
\ No newline at end of file
diff --git a/installer/utils.php b/installer/utils.php
new file mode 100644
index 0000000..ca47d73
--- /dev/null
+++ b/installer/utils.php
@@ -0,0 +1,132 @@
+<?php defined('INSTALLER_DIR') or die('No direct script access');
+
+/**
+ * Installation utilities class
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - https://github.com/ushahidi/Ushahidi_Web
+ * @subpackage Installer
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Installer_Utils {
+
+	/**
+	 * Generates a random alpha-numeric string
+	 *
+	 * @return string
+	 */
+	public static function get_random_str($length = 24)
+	{
+		// Characters to be used for random string generatino
+		$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+[]{};:,./?`~';
+		
+		// Split the pool into an array of characters
+		$pool = str_split($pool, 1);
+		
+		$max = count($pool) - 1;
+		
+		$str = '';
+		for ($i=0; $i < $length; $i++)
+		{
+			$str .= $pool[mt_rand(0, $max)];
+		}
+		
+		// Ensure the string has at least one digit and one letter
+		if (ctype_alpha($str))
+		{
+			// Add a random digit at a randomly chosen position
+			$str[mt_rand(0, $length - 1)] = chr(mt_rand(23, 61));
+		}
+		elseif (ctype_digit($str))
+		{
+			// Add a random character at a randomly chosen position
+			$str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90));
+		}
+		
+		return $str;
+	}
+	
+	/**
+	 * Generates a salt pattern
+	 *
+	 * @return string
+	 */
+	public static function get_salt_pattern()
+	{
+		$salt_pattern = array();
+		for ($i = 0; $i < 10; $i++)
+		{
+			// Generate an offset
+			$offset = mt_rand(1, 40);
+			
+			// Ensure all the offsets are unique
+			while (in_array($offset, $salt_pattern))
+			{
+				$offset = mt_rand(1, 40);
+			}
+			
+			$salt_pattern[] = $offset;
+		}
+		
+		// Sort the values in ascending order
+		sort($salt_pattern, SORT_NUMERIC);
+		
+		return $salt_pattern;
+	}
+
+	/**
+	 * Creates a hashed password from a plaintext password, inserting salt
+	 * based on the configured salt pattern.
+	 *
+	 * @param   string  plaintext password
+	 * @param   string  salt for password hash
+	 * @return  string  hashed password string
+	 */
+	public static function hash_password($password, $salt_pattern)
+	{
+		$salt = substr(self::hash(uniqid(NULL, TRUE)), 0, count($salt_pattern));
+				
+		// Password hash that the salt will be inserted into
+		$hash = self::hash($salt.$password);
+
+		// Change salt to an array
+		$salt = str_split($salt, 1);
+
+		// Returned password
+		$password = '';
+
+		// Used to calculate the length of splits
+		$last_offset = 0;
+
+		foreach ($salt_pattern as $offset)
+		{
+			// Split a new part of the hash off
+			$part = substr($hash, 0, $offset - $last_offset);
+
+			// Cut the current part out of the hash
+			$hash = substr($hash, $offset - $last_offset);
+
+			// Add the part to the password, appending the salt character
+			$password .= $part.array_shift($salt);
+
+			// Set the last offset to the current offset
+			$last_offset = $offset;
+		}
+
+		// Return the password, with the remaining hash appended
+		return $password.$hash;
+	}
+	
+	public static function hash($str)
+	{
+		return hash("sha1", $str);
+	}
+	
+}
+?>
\ No newline at end of file
diff --git a/installer/wizard.php b/installer/wizard.php
new file mode 100644
index 0000000..8c78cf4
--- /dev/null
+++ b/installer/wizard.php
@@ -0,0 +1,1129 @@
+<?php
+/**
+ * Installatation Wizard
+ *
+ * This class manages the installation process. It tracks the
+ * status of the installation and allows a user to resume from
+ * where they left off. All install stages are submitted for processing
+ * via HTTP POST. 
+ *
+ * The config files are created and/or modified at the end of the installation
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - https://github.com/ushahidi/Ushahidi_Web
+ * @subpackage Installer
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Installer_Wizard {
+	
+	/**
+	 * Configuration directory for the application
+	 */
+	private static $_app_config_dir = '';
+	
+	/**
+	 * Installer data
+	 * @var array
+	 */
+	private static $_data = array();
+	
+	/**
+	 * @var array
+	 */
+	private static $_errors = array();
+		
+	/**
+	 * Database connection
+	 * @var mixed
+	 */
+	private static $_connection = FALSE;
+	
+	/**
+	 * Required extensions
+	 * @var array
+	 */
+	private static $_extensions = array(
+		// Extensions required for UTF-8 functions
+		'pcre',
+		'iconv',
+		'mbstring',
+		
+		// Database access
+		'mysql',
+		
+		// cURL for remote site access
+		'curl',
+		
+		// IMAP extension
+		//'imap',
+		
+		// GD library for imaging
+		'gd',
+		
+		// Encryption
+		'mcrypt',
+		
+		// Misc
+		'spl'
+	);
+	
+	/**
+	 * Directories and files that should be writeable by the installer
+	 * and application
+	 * @var array
+	 */
+	private static $_filesystem = array(
+		// Cache directory
+		'cache' => 'application/cache',
+
+		// Logs directory
+		'logs' => 'application/logs',
+
+		// Config directory
+		'config' => 'application/config',
+
+		// Uploads directory
+		'uploads' => 'media/uploads',
+
+		// .htaccess file
+		'htaccess' => '.htaccess'
+	);
+
+	/**
+	 * Installation stages for each mode (basic & advanced)
+	 * Precedence of the stages matters
+	 * @var array
+	 */
+	private static $_install_stages = array(
+		// Basic install stages
+		'basic' => array(
+			'requirements',
+			'database',
+			'general',
+			'adminpassword',
+			'finish'
+		),
+
+		// Advanced install stages
+		'advanced' => array(
+			'requirements',
+			'database',
+			'general',
+			'email',
+			'map',
+			'adminpassword',
+			'finish'
+		)
+	);
+	
+	/**
+	 * Form data for the install phases
+	 * @var array
+	 */
+	private static $_forms = array(
+		// Database form
+		'database' => array(
+			'base_path' => '',
+			'database' => '',
+			'username' => '',
+			'password' => '',
+			'host' => '',
+			'table_prefix' => ''
+		),
+		
+		// General site settings
+		'general' => array(
+			'site_name' => '',
+			'site_tagline' => '',
+			'site_language' => '',
+			'site_email' => '',
+			'enable_clean_urls' => ''
+		),
+		
+		// Email
+		'email' => array(
+			'alerts_email' => '',
+			'email_host' => '',
+			'email_username' => '',
+			'email_password' => '',
+			'email_port' => ''
+		),
+		
+		// Admin password
+		'adminpassword' => array(
+			'email' => '',
+			'password' => '',
+			'confirm_password' => ''
+		)
+	);
+
+
+	/**
+	 * Bootstraps the installer
+	 */
+	public static function init()
+	{
+		self::$_app_config_dir = ROOT_DIR.'application'.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR;
+		
+		// Initialize the session
+		session_start();
+
+		self::$_data = & $_SESSION;
+		
+		// Check if the application has already been installed
+		if (self::is_installed())
+		{
+			session_destroy();
+
+			session_unset();
+			
+			header("Location:../");
+			
+			// For security: Make sure we don't return the rest of the page
+			exit();
+		}
+
+		// 
+		// TODO: Expire the session after 30 minutes
+		// and implement mechanisms to prevent attacks on sessions
+		// 
+
+		// Check if installation has started or if a current stage exists
+		if ( ! isset(self::$_data['started']) OR ! isset(self::$_data['current_stage']))
+		{
+			self::$_data['started'] = TRUE;
+			
+			// Get the site protocol
+			$protocol = (isset($_SERVER['HTTPS']) OR (isset($_SERVER['HTTPS']) AND $_SERVER['HTTPS'] === 'on'))
+			    ? 'https'
+			    : 'http';
+						
+			// Site protocol
+			self::$_data['site_protocol'] = $protocol;
+			
+			// Build the full URI
+			$request_uri = $_SERVER['REQUEST_URI'];
+			
+			// Get the installation directory
+			$site_domain = substr(substr($request_uri, 0, stripos($request_uri, '/installer/')) ,1);
+			
+			if (strpos($site_domain, "/") !== 0)
+			{
+				$site_domain = "/" . $site_domain;
+			}
+			
+			// Server port
+			$port = ! in_array($_SERVER['SERVER_PORT'], array("80", "443"))
+			    ? ':'.$_SERVER['SERVER_PORT'] 
+			    : '';
+
+			// Build out the base URL
+			$base_url = $protocol.'://'.$_SERVER['SERVER_NAME'].$port.$site_domain;
+
+			// Add a trailing slash to the base URL
+			if (substr($base_url, -1) !== "/")
+			{
+				$base_url .= "/";
+			}
+			
+			self::$_data['site_domain'] = $site_domain;
+			self::$_data['base_url'] = $base_url;
+
+			if (isset(self::$_data['current_stage']))
+			{
+				unset(self::$_data['current_stage']);
+			}
+		
+			// Show the welcome page
+			self::render_install_page('main');
+		}
+		else
+		{
+			self::render_install_page(self::$_data['current_stage']);
+		}
+		
+	}
+	
+		
+	/**
+	 * Executes the callback function of the current step of the installtion
+	 * process and loads the page for the next stage. The callback function
+	 * for the current step should halt script execution if the validation
+	 * checks fail
+	 */
+	public static function next()
+	{		
+		// Check for the install mode
+		if (isset($_POST['install_mode_basic']))
+		{
+			self::$_data['install_mode'] = 'basic';
+		}
+		elseif (isset($_POST['install_mode_advanced']))
+		{
+			self::$_data['install_mode'] = 'advanced';
+		}
+		
+		// Get the installer mode
+		$install_mode = self::$_data['install_mode'];
+		
+		// Get the current stage name
+		$stage_name = array_key_exists('current_stage', self::$_data)
+		    ? self::$_data['current_stage']
+		    : NULL;
+
+		if (empty($stage_name))
+		{
+			// We're in the first stage
+			$stage_name = self::$_install_stages[$install_mode][0];
+			self::$_data['current_stage'] = $stage_name;
+		}
+		else
+		{
+			// Run the current stage before advancing to the next one
+			$callback_func = sprintf("install_%s", $stage_name);
+			
+			if ( ! self::$callback_func())
+			{
+				self::render_install_page($stage_name);
+				exit;
+			}
+
+			// Set the next stage to be executed
+			$stage_position = self::_get_install_position($install_mode, $stage_name) + 1;
+			self::$_data['current_stage'] = self::$_install_stages[$install_mode][$stage_position];
+		}
+
+		// If we're in the last stage, create/modify all the configuration files
+		if (self::_is_last_stage())
+		{
+			// Create database.php
+			self::_create_database_config();
+
+			// Modify the .htaccess and config.php
+			self::_set_base_path();
+
+			// Modify encrypt.php and auth.php
+			self::_set_security_params();
+
+			// Check for errors
+			if (count(self::$_errors) > 0)
+			{
+				// Go the the previous 
+				self::previous(FALSE);
+			}
+		}
+		
+		// Show the page for the next stage
+		self::render_install_page(self::$_data['current_stage']);
+	}
+	
+	/**
+	 * Loads the page for the previous installation step.
+	 */
+	public static function previous($render = TRUE)
+	{
+		if ( ! array_key_exists('current_stage', self::$_data))
+		{
+			self::render_install_page('main');
+			return;
+		}
+		$install_mode = self::$_data['install_mode'];
+		$current_position = self::_get_install_position($install_mode, self::$_data['current_stage']);
+
+		$page = 'main';
+		if ($current_position > 0)
+		{
+			$current_position -= 1;
+			
+			$page = self::$_install_stages[$install_mode][$current_position];
+			self::$_data['current_stage'] = $page;
+		}
+		else
+		{
+			unset(self::$_data['current_stage']);
+		}
+		
+		// Render?
+		if ($render)
+		{
+			self::render_install_page($page);
+		}
+	}
+	
+	/**
+	 * Given the installation mode and stage name, gets the index
+	 * of the install stage
+	 *
+	 * @param string $mode
+	 * @param string $stage_name
+	 * @return int
+	 */
+	private static function _get_install_position($mode, $stage_name)
+	{
+		$stages = self::$_install_stages[$mode];
+		$stage_pos = 0;
+
+		for ($i = 0; $i < count($stages); $i++)
+		{
+			if ($stages[$i] === $stage_name)
+			{
+				$stage_pos = $i;
+				break;
+			}
+		}
+		
+		return $stage_pos;
+	}
+	
+	/**
+	 * Checks if the installer is in the last installation stage
+	 * @return bool
+	 */
+	private static function _is_last_stage()
+	{
+		if ( ! array_key_exists('current_stage', self::$_data))
+			return FALSE;
+
+		$current = self::$_data['current_stage'];
+		$mode = self::$_data['install_mode'];
+		
+		$position = self::_get_install_position($mode, $current);
+		
+		return count(self::$_install_stages[$mode]) === ($position + 1);
+	}
+
+	/**
+	 * Verifies whether all the extensions in $_extensions
+	 * have been loaded.
+	 *
+	 * @return bool 
+	 */
+	private static function _verify_extensions()
+	{
+		foreach (self::$_extensions as $extension)
+		{
+			if ( ! extension_loaded($extension))
+			{
+				self::$_errors[] = sprintf("The <code>%s</code> extension is disabled", $extension);
+			}
+		}
+		
+		return count(self::$_errors) == 0;
+	}
+	
+	
+	/**
+	 * Verifies that the entries in $_filesystem are writeable 
+	 * by the installer and the application
+	 *
+	 * @return bool 
+	 */
+	private static function _verify_permissions()
+	{
+		// Check if the filesytem object are writeable
+		foreach (self::$_filesystem as $key => $item)
+		{
+			$item = preg_replace("/\//", DIRECTORY_SEPARATOR, $item);
+			$full_path = ROOT_DIR.$item;
+			if ( ! is_writable($full_path))
+			{
+				$type = is_dir($full_path) ? 'directory' : 'file';
+				self::$_errors[] = sprintf("<code>%s</code> %s is not writable", $item, $type);
+			}
+		}
+		
+		return count(self::$_errors) == 0;
+	}
+	
+	/**
+	 * Checks whether the application has already been installed
+	 * The presence of database.php in application/config halts the
+	 * installation process and redirects to the landing page
+	 *
+	 * @return bool
+	 */
+	public static function is_installed()
+	{
+		// Absolute file name of the DB config file
+		$db_config_file = self::$_app_config_dir.'database.php';
+		return file_exists($db_config_file);
+	}
+	
+	/**
+	 * Helper function for display the page for the various
+	 * installation phases. The name of the page must be of a file
+	 * that exists in the {installer}/pages directory
+	 *
+	 * @param $page_name string Name of the page to be displayed
+	 */
+	public static function render_install_page($page_name)
+	{
+		// Clear buffer
+		if (ob_get_length() > 0)
+		{
+			ob_end_clean();
+		}
+		
+		ob_start();
+		
+		// Full path of the page to be rendered
+		$page_path = INSTALLER_PAGES . $page_name . '.php';
+		
+		include INSTALLER_PAGES . 'header.php';
+		
+		// If the page doesn't exist, show the main page
+		if (file_exists($page_path))
+		{
+			$install_data = self::$_data;
+			
+			// Remove database info
+			if (array_key_exists('database', $install_data))
+			{
+				unset($install_data['database']);
+			}
+			
+			// Check for errors
+			if (count(self::$_errors) > 0)
+			{
+				$install_data['errors'] = self::$_errors;
+			}
+		
+			// Check if there are any forms for the current page
+			if (array_key_exists($page_name, self::$_forms))
+			{
+				$form = self::$_forms[$page_name];
+				if ($_POST)
+				{
+					foreach ($_POST as $field => $value)
+					{
+						if (array_key_exists($field, $form))
+						{
+							$form[$field] = $value;
+						}
+					}
+				}
+
+				$install_data['form'] = $form;
+			}
+
+			extract($install_data);
+		}
+		else
+		{
+			// Restart the installation
+			unset (self::$_data['current_stage']);
+			$page_path =  INSTALLER_PAGES . 'main.php';
+		}
+		
+		include $page_path;
+		
+		if (self::_is_last_stage())
+		{
+			// Destroy session data
+			session_destroy();
+
+			// Unset $_SESSION variable for the runtime
+			session_unset();
+		}
+		
+		$content = ob_get_contents();
+		ob_clean();
+		print $content;
+	}
+	
+	/**
+	 * Checks whether a set of parameters have values in the payload. The optional
+	 * items are skipped
+	 *
+	 * @param array $params    Parameter keys to be checked for in the payload
+	 * @param array $payload   Input data 
+	 * @param array $optional  Parameters to be exempted from the validation check
+	 *
+	 * @return bool
+	 */
+	private static function _validate_params($params, & $payload, $optional = NULL)
+	{
+		foreach ($params as $param)
+		{
+			if ( ! empty($optional) AND in_array($param, $optional))
+				continue;
+			
+			if (empty($payload[$param]))
+			{
+				self::$_errors[] = sprintf("The <code>%s</code> parameter has not been specified", $param);
+			}
+			else
+			{
+				// Input sanitization
+				$payload[$param] = strip_tags($payload[$param]);
+			}
+		}
+
+		return count(self::$_errors) === 0;
+	}
+
+
+	/**
+	 * Verifies whether the installation requirements
+	 * have been met
+	 *
+	 * @return bool
+	 */
+	public static function install_requirements()
+	{
+		// Verify the extensions
+		self::_verify_extensions();
+
+		// Verify the permissions
+		self::_verify_permissions();
+		
+		// Check for errors
+		if (count(self::$_errors) == 0)
+		{
+			// Check if clean urls are enabled
+			self::$_data['enable_clean_urls'] = self::_clean_urls_enabled();
+			
+			return TRUE;
+		}
+		
+		return FALSE;
+	}
+	
+	/**
+	 * Sets up the database and creates the database.php
+	 * config file
+	 *
+	 * @return bool
+	 */
+	public static function install_database()
+	{
+		// Verify the host, username and password have been specified
+		$params = array('host', 'username', 'password', 'database');
+		
+		if ( ! self::_validate_params($params, $_POST))
+			return FALSE;
+			
+		// Extract the POST data
+		extract($_POST);
+
+		// Store the database info
+		self::$_data['database'] = array(
+			'host' => $host,
+			'user' => $username,
+			'pass' => $password,
+			'database' => $database,
+			'table_prefix' => $table_prefix
+		);
+
+		// Set up the database schema + objects
+		self::_database_connect();
+		
+		if ( ! self::$_connection)
+		{
+			self::$_errors[] = sprintf("Database connection error: %s", mysql_error());
+			
+			return FALSE;
+		}
+
+		// Get the schema DDL script
+		$schema_ddl = file_get_contents(ROOT_DIR.'sql'.DIRECTORY_SEPARATOR.'ushahidi.sql');
+	
+		// Add table prefix
+		if ( ! empty($table_prefix))
+		{
+			$find = array(
+				'CREATE TABLE IF NOT EXISTS `',
+				'INSERT INTO `',
+				'INSERT IGNORE INTO `',
+				'ALTER TABLE `',
+				'UPDATE `',
+				'FROM `',
+				'LOCK TABLES `',
+				'DROP TABLE IF EXISTS `',
+				'RENAME TABLE `',
+				' TO `',
+			);
+			
+			$replace = array(
+				'CREATE TABLE IF NOT EXISTS `'.$table_prefix,
+				'INSERT INTO `'.$table_prefix,
+				'INSERT IGNORE INTO `'.$table_prefix,
+				'ALTER TABLE `'.$table_prefix,
+				'UPDATE `'.$table_prefix,
+				'FROM `'.$table_prefix,
+				'LOCK TABLES `'.$table_prefix,
+				'DROP TABLE IF EXISTS `'.$table_prefix,
+				'RENAME TABLE `'.$table_prefix,
+				' TO `'.$table_prefix,
+			);
+		
+			$schema_ddl = str_replace($find, $replace, $schema_ddl);
+		}
+	
+		// Run the script
+		foreach (explode(";", $schema_ddl) as $query)
+		{
+			if ( ! self::_execute_query($query))
+			{
+				break;
+			}
+		}
+				
+		return count(self::$_errors) == 0;
+	}
+	
+	/**
+	 * Connects to the database
+	 */
+	private static function _database_connect()
+	{
+		$params = self::$_data['database'];
+		
+		self::$_connection = mysql_connect($params['host'], $params['user'], 
+		    $params['pass'], TRUE);
+		
+		if ( ! self::$_connection)
+		{
+			self::$_errors[] = sprintf("Connection error: <strong>%s</strong>", mysql_error());
+
+			return FALSE;
+		}
+		
+		$database_name = $params['database'];
+		
+		if ( ! mysql_select_db($database_name))
+		{
+			if (self::_execute_query(sprintf("CREATE DATABASE %s", self::_escape_str($database_name))))
+			{
+				mysql_select_db($database_name, self::$_connection);
+			}
+			else
+			{
+				self::$_errors[] = sprintf("Error creating database: %s", mysql_error());
+			}
+		}
+				
+	}
+	
+	/**
+	 * Executes a query against the database
+	 * @param string $query
+	 */
+	private static function _execute_query($query)
+	{
+		if (self::$_connection === FALSE)
+		{
+			self::_database_connect();
+		}
+		
+		if (strlen(trim($query)) > 0)
+		{
+			if ( ! mysql_query($query))
+			{
+				self::$_errors[] = sprintf("Encountered error <strong>%s</strong> when executing query <code>%s</code>", 
+				    mysql_error(), $query);
+			
+				return FALSE;
+			}
+		}
+		
+		return TRUE;
+	}
+	
+	/**
+	 * Escape string for the database
+	 * @param string $query
+	 */
+	private static function _escape_str($string)
+	{
+		if (self::$_connection === FALSE)
+		{
+			self::_database_connect();
+		}
+		
+		return mysql_real_escape_string($string);
+	}
+	
+	/**
+	 * Creates the database configuration file - database.php
+	 */
+	private static function _create_database_config()
+	{
+		$template_file_name = self::$_app_config_dir.'database.template.php';
+		
+		$output_file_name = self::$_app_config_dir.'database.php';
+		
+		// Create the new config file
+		if (($output_file = @fopen($output_file_name, 'w')) !== FALSE)
+		{
+			if (($template_file = file($template_file_name)) !== FALSE)
+			{
+				$config_params = self::$_data['database'];
+
+				foreach ($template_file as $line_no => $line)
+				{
+					foreach ($config_params as $config => $value)
+					{
+						$search = sprintf("/'%s' =>.*/i", $config);
+						if (preg_match($search, $line, $matches))
+						{
+							$replace = sprintf("'%s' => '%s',", $config, addslashes($value));
+							$line = preg_replace("/".$matches[0]."/i", $replace, $line);
+							break;
+						}
+					}
+					fwrite($output_file, $line);
+				}
+			}
+			fclose($output_file);
+		}
+		else
+		{
+			self::$_errors[] = "Error creating the database config file. Check permissions";
+		}
+		
+		return count(self::$_errors) == 0;
+	}
+	
+	/**
+	 * Updates the .htaccess and config.php files with the base path
+	 */
+	private static function _set_base_path()
+	{
+		// TODO Set the site_domain, site_protocol and install_check parameters
+		$params = array(
+			'site_domain' => self::$_data['site_domain'],
+			'site_protocol' => self::$_data['site_protocol'],
+			'installer_check' => FALSE
+		);
+		
+		// Clean URLs enabled, set the 'index_page' config to ''
+		// else it remains as 'index.php'
+		if (self::$_data['enable_clean_urls'])
+		{
+			$params = array_merge($params, array(
+				'index_page' => ''
+			));
+		}
+
+		// .htaccess
+		$htaccess_file = ROOT_DIR.'.htaccess';
+		$htaccess = file($htaccess_file);
+		if (($fp = @fopen($htaccess_file, 'w')) !== FALSE)
+		{
+			foreach ($htaccess as $line_no => $line)
+			{
+				if (preg_match("/RewriteBase.*/i", $line, $matches))
+				{
+					$replace = sprintf("RewriteBase %s", self::$_data['site_domain']);
+					$line = str_replace($matches[0], $replace, $line);
+				}
+				fwrite($fp, $line);
+			}
+			fclose($fp);
+		}
+		else
+		{
+			self::$_errors[] = "Permission error. Could not open <code>.htaccess</code> for writing";
+		}
+		
+		// config.php
+		$config_file = self::$_app_config_dir.'config.php';
+		
+		// Load the template file
+		$config_template = file(self::$_app_config_dir.'config.template.php');
+		
+		if (($fp = @fopen($config_file, 'w')) !== FALSE)
+		{
+			foreach ($config_template as $line_no => $line)
+			{
+				foreach ($params as $param => $value)
+				{
+					if (preg_match("/config\['".$param."'\].*/i", $line, $matches))
+					{
+						// Type check for the param values
+						if (is_bool($value))
+						{
+							// Get the string represenation of the boolean value
+							// without quotes
+							$value = ($value) ? 'TRUE' : 'FALSE';
+						}
+						else
+						{
+							// Quote the value
+							$value = "'".$value."'";
+						}
+
+						$replace = sprintf("config['%s'] = %s;", $param, $value);
+
+						$line = str_replace($matches[0], $replace, $line);
+						break;
+					}
+				}
+				fwrite($fp, $line);
+			}
+			fclose($fp);
+		}
+		else
+		{
+			self::$_errors[] = "Permission error. Could not open <code>config.php</code> for writing";
+		}
+	}
+
+	/**
+	 * Updates the auth.php and encrypt.php files with
+	 * the salt pattern and encryption keys
+	 */
+	private static function _set_security_params()
+	{
+		// Auth.php
+		$auth_file_name = self::$_app_config_dir.'auth.php';
+		
+		// Load the template
+		$auth_template = file(self::$_app_config_dir.'auth.template.php');
+
+		if (($fp = fopen($auth_file_name, 'w')) !== FALSE)
+		{
+			// Get the salt pattern
+			$salt_pattern = self::$_data['salt_pattern'];
+			foreach ($auth_template as $line_no => $line)
+			{
+				if (preg_match("/config\['salt_pattern'\].*/i", $line, $matches))
+				{
+					$replace = sprintf("config['salt_pattern'] = '%s';", implode(", ", $salt_pattern));
+					$line = str_replace($matches[0], $replace, $line);
+				}
+				fwrite($fp, $line);
+			}
+			fclose($fp);
+		}
+		else
+		{
+			self::$_errors[] = "Permission error. Unable to write to <code>auth.php</code>";
+		}
+		
+		// encryption.php
+		$crypto_key = Installer_Utils::get_random_str(32);
+		
+		$encrypt_file_name = self::$_app_config_dir.'encryption.php';
+		
+		// Load the template file
+		$encryption_template = file(self::$_app_config_dir.'encryption.template.php');
+		
+		if (($fp = @fopen($encrypt_file_name, 'w')) !== FALSE)
+		{
+			foreach ($encryption_template as $line_no => $line)
+			{
+				if (preg_match("/config\['default'\]\['key'\].*/i", $line, $matches))
+				{
+					$replace = sprintf("config['default']['key'] = '%s';", $crypto_key);
+					$line = str_replace($matches[0], $replace, $line);
+				}
+				fwrite($fp, $line);
+			}
+			fclose($fp);
+		}
+		else
+		{
+			self::$_errors[] = "Permission error. Unable to write to <code>encryption.php</code>";
+		}
+	}
+	
+
+	/**
+	 * Setup the general site settings
+	 * @return bool
+	 */
+	public static function install_general()
+	{
+		$params = array_keys(self::$_forms['general']);
+		$optional = array('site_email', 'enable_clean_urls');
+		
+		if ( ! self::_validate_params($params, $_POST, $optional))
+			return FALSE;
+		
+		// Clean URLs
+		if (isset($_POST['enable_clean_urls']))
+		{
+			self::$_data['enable_clean_urls'] = (bool) $_POST['enable_clean_urls'];
+		}
+		
+		// Validate email
+		if ( ! empty($_POST['site_email']))
+		{
+			if ((filter_var($_POST['site_email'], FILTER_VALIDATE_EMAIL)) === FALSE)
+			{
+				self::$_errors[] = sprintf("Invalid email address: <strong>%s</strong>", $_POST['site_email']);
+				return FALSE;
+			}
+		}
+		
+		return self::_update_settings($params);
+	}
+	
+	/**
+	 * Set up the email settings
+	 * @return bool
+	 */
+	public static function install_email()
+	{
+		$params=  array_keys(self::$_forms['email']);
+		if ( ! self::_validate_params($params, $_POST))
+			return FALSE;
+		
+		return self::_update_settings($params);
+	}
+
+	/**
+	 * Internal utility method to update the settings table
+	 * @return bool
+	 */
+	private static function _update_settings($params)
+	{
+		$query = "UPDATE `".self::$_data['database']['table_prefix']."settings` SET `value` = CASE `key` ";
+		
+		// Update the site settings
+		$settings_keys = array();
+		foreach ($params as $param)
+		{
+			if ( ! isset($_POST[$param]))
+				continue;
+			$settings_keys[] = self::_escape_str($param);
+			$query .= sprintf("WHEN '%s' THEN '%s' ", self::_escape_str($param), self::_escape_str($_POST[$param]));
+		}
+		
+		$settings_keys = "'".implode("','", $settings_keys)."'";
+		$query .= sprintf('END WHERE `key` IN (%s)', $settings_keys);
+		
+		return self::_execute_query($query);
+		
+	}
+	
+	/**
+	 * Setup the map configuration
+	 */
+	public static function install_map()
+	{
+		extract($_POST);
+		$api_key_mapping = array('bing_road' => 'api_live');
+		
+		$exempt_providers = array(
+			'google_normal',
+			'osm_mapnik'
+		);
+		
+		if (empty($default_map))
+		{
+			self::$_errors[] = "You have not selected a provider";
+			return FALSE;
+		}
+		
+		// Build the update query
+		$table_prefix = self::$_data['database']['table_prefix'];
+		
+		// Running as 2 seperate updates because the CASE method is messy and pointless with just 2 keys anyway
+		$query = sprintf("UPDATE `%ssettings` SET `value` = '%s' WHERE `key` = 'default_map'", $table_prefix, self::_escape_str($default_map) );
+		
+		$result = TRUE;
+		// Check for BingMaps API Key
+		if ( ! in_array($default_map, $exempt_providers))
+		{
+			// Check for the API key
+			if ( ! empty($api_key))
+			{
+				$setting_id = $api_key_mapping[$default_map];
+				
+				$query = sprintf("UPDATE `%ssettings` SET `value` = '%s' WHERE `key` = '%s' ", $table_prefix, self::_escape_str($api_key), self::_escape_str($setting_id) );
+				$result = self::_execute_query($query) AND $result;
+			}
+			else
+			{
+				self::$_errors[] = "The selected map provider requires an API key";
+				return FALSE;
+			}
+		}
+		$result = self::_execute_query($query) AND $result;
+		
+		return $result;
+		
+	}
+	
+	
+	/**
+	 * Save the admin password
+	 */
+	public static function install_adminpassword()
+	{
+		$params = array_keys(self::$_forms['adminpassword']);
+		if ( ! self::_validate_params($params, $_POST))
+			return FALSE;
+		
+		extract($_POST);
+		
+		if (filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE)
+		{
+			self::$_errors[] = sprintf("Invalid email address: <strong>%s</strong>", $email);
+			return FALSE;
+		}
+		
+		// Do the passwords match?
+		if (hash("sha1", $password) !== hash("sha1", $confirm_password))
+		{
+			self::$_errors[] = "The passwords do not match";
+			return FALSE;
+		}
+
+		// Get the table prefix
+		$table_prefix = self::$_data['database']['table_prefix'];
+		
+		// Get the salt pattern from the auth.php
+		$salt_pattern = Installer_Utils::get_salt_pattern();
+		self::$_data['salt_pattern'] = $salt_pattern;
+		
+		// Generate the password hash
+		$password_hash = Installer_Utils::hash_password($password, $salt_pattern);
+				
+		// Update the admin user password
+		$query = sprintf("UPDATE `%susers` SET `email` = '%s', `password` = '%s' WHERE `username` = 'admin'", 
+			$table_prefix, self::_escape_str($email), self::_escape_str($password_hash));
+
+		self::$_data['admin_email'] = $email;
+		 
+		return self::_execute_query($query);
+	}
+
+	/**
+	 * Checks if clean URLS are enabled
+	 *
+	 * @return bool
+	 */
+	private static function _clean_urls_enabled()
+	{
+		$url = self::$_data['base_url']."installer/mod_rewrite/";
+		$curl_handle = curl_init();
+
+		curl_setopt($curl_handle, CURLOPT_URL, $url);
+		curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, TRUE);
+		curl_exec($curl_handle);
+
+		$return_code = curl_getinfo($curl_handle,CURLINFO_HTTP_CODE);
+		curl_close($curl_handle);
+
+		if ($return_code ==  404 OR $return_code ==  403)
+		{
+			return FALSE;
+		}
+		else
+		{
+			return TRUE;
+		}
+	}
+		
+}
+
+?>
diff --git a/maintenance_off.php b/maintenance_off.php
new file mode 100644
index 0000000..b144bf8
--- /dev/null
+++ b/maintenance_off.php
@@ -0,0 +1 @@
+This website is currently undergoing maintenance. Please try again later.
\ No newline at end of file
diff --git a/media/css/admin.css b/media/css/admin.css
new file mode 100755
index 0000000..46b250d
--- /dev/null
+++ b/media/css/admin.css
@@ -0,0 +1,2641 @@
+body{
+	margin:0;
+	font:12px/16px "Trebuchet MS",sans-serif;
+	color:#555;
+	min-width:960px;
+	padding:0;
+}
+form{
+	margin:0;
+	padding:0;
+}
+.holder{
+	width:960px;
+	margin:0 auto;
+}
+h1, h2 ,h3, h4{margin:0;}
+h1{
+	padding:16px 0 0 2px;
+	color:#000;
+	font:28px/30px Georgia, "Times New Roman", Times, serif;
+}
+h2{
+	padding:12px 18px 0;
+	font-size:20px;
+	height:28px;
+	font-weight:normal;
+	color:#9b0000;
+	border:1px solid #fff;
+	background:#f2f7fa;
+}
+h2 span{
+	padding:0 15px 0 0;
+	font-size:14px;
+	line-height:14px;
+}
+h2 a{
+	font-size:14px;
+	line-height:16px;
+	color:#00699b;
+	padding:0 10px 0;
+}
+h2 a.active{
+	font-size:18px;
+	font-weight: bold;
+	padding-right:5px;
+}
+h2 span a, h2 span a:visited{
+	font-size:12px;
+	padding:0;
+	color:#666;
+}	
+h3{
+	padding:0 12px;
+	font-size:18px;
+	line-height:20px;
+	color:#00699b;
+}
+h4 sup a{
+	font-size: 11px;
+}
+a { color:#00699b;}
+a:hover{ text-decoration:none;}
+.none-separator { background:none !important;}
+/*-- header --*/
+#header{
+	width:100%;
+	clear:both;
+	overflow:hidden;
+}
+/*-- top --*/
+.top {
+	width:100%;
+	overflow:hidden;
+	clear:both;
+	height:37px;
+}
+.top strong{
+	padding:13px 0 0;
+	font-size:12px;
+	color:#fff;
+	float:left;
+}
+.top ul{
+	margin:0;
+	padding:9px 7px 0 0;
+	float:right;
+}
+.top ul li {
+	font-size:12px;
+	color:#fff;
+	padding:0 3px 0 10px;
+	background:url(../img/admin/top-separator.gif) no-repeat 0 8px;
+	display:inline;
+	list-style:none;
+}
+.top ul li.none-separator { padding-left:0;}
+.top ul li a{
+	color:#ffa9a9;
+}
+.top span {
+	color:#C90000;
+	background-color:#FFE8E7;
+	padding:0 4px 0 4px;
+}
+/*-- info-nav --*/
+.info-nav {
+	margin:16px 7px 0 0;
+	display:inline;
+	float:right;
+}
+.info-nav h3{
+	font-weight:normal;
+	font-size:12px;
+	color:#7b7b7b;
+	padding:0;
+	line-height:19px;
+}
+.info-nav ul{
+	margin:0;
+	padding:6px 0 9px;
+	border-top:1px solid #898989;
+	border-bottom:1px solid #898989;
+	clear:both;
+	float:left;
+	line-height:25px;
+}
+.info-nav ul li{
+	padding:0 7px 0 10px;
+	display:inline;
+	list-style:none;
+}
+.info-nav ul li a{
+	color:#a20000;
+}
+.info-nav .info-search{
+	margin:0;
+	padding:6px 0 9px 20px;
+	border-top:1px solid #898989;
+	border-bottom:1px solid #898989;
+	float:left;
+	line-height:25px;
+}
+.info-nav .info-search a{
+	padding:2px 5px;
+	color:#5c5c5c;
+	background:#f2f7fa;
+	text-decoration:none;
+	border:1px solid #d1d1d1;
+	vertical-align: middle;
+	line-height:23px;
+}
+.info-nav .info-search .info-keyword{
+	vertical-align: middle;
+	width:100px;
+}
+
+.info-nav .language-box{
+	margin:0;
+	padding:6px 0 9px 20px;
+	border-top:1px solid #898989;
+	border-bottom:1px solid #898989;
+	float:left;
+	line-height:25px;
+}
+.info-nav .language-box select{
+	width: 160px;
+	margin: 0;
+	padding: 1px;
+	font-size: 11px;
+	color: #00789F;
+}
+
+.info-buttons { clear:both; margin:10px 0 0 0; text-align:right; }
+
+/*-- nav-holder --*/
+.nav-holder {
+	padding:18px 0 0;
+	width:100%;
+	clear:both;
+	overflow:hidden;
+}
+
+/*-- main-nav --*/
+ul.main-nav{
+	margin:0;
+	padding:0;
+	float:left;
+}
+ul.main-nav li{
+	padding:0 5px 0 0;
+	list-style:none;
+	float:left;
+}
+ul.main-nav li a{
+	height:25px;
+	float:left;
+	padding:12px 15px 0;
+	font-size:17px;
+	color:#555;
+	text-decoration:none;
+}
+
+ul.main-nav li a:hover{background:#f5f5f5;}
+ul.main-nav li a.active{ background:#eee !important;}
+ul.sub-nav {
+	margin:0;
+	padding:13px 2px 0 0;
+	float:right;
+}
+/*-- sub-nav --*/
+ul.sub-nav  li{
+	padding:0 5px 0 7px;
+	display:inline;
+	list-style:none;
+}
+ul.sub-nav  li a{
+	text-decoration:none;
+	font-size:14px;
+	color:#898989;
+}
+ul.sub-nav  li a:hover{ text-decoration:underline;}
+
+/*-- content --*/
+#content{
+	background:url(../img/admin/content-bg.gif) repeat-x;
+	width:940px;
+	padding:10px 10px 0;
+	overflow:hidden;
+	clear:both;
+}
+.bg{
+	width:940px;
+	background:#fff;
+	clear:both;
+	overflow:hidden;
+	padding-bottom:29px;
+}
+/*-- column --*/
+.column{
+	padding:0 10px 0 18px;
+	width:442px;
+	float:left;
+}
+/*-- box --*/
+.box {
+	margin:20px 0 0;
+	padding:0 0 20px;
+	border:4px solid #f5f5f5;
+	clear:both;
+	overflow:hidden;
+}
+.box h3{
+	padding-top:9px;
+	float:left;
+}
+ul.inf {
+	margin:0;
+	padding:11px 0 0;
+	float:right;
+}
+ul.inf li{
+	background:url(../img/admin/separator.gif) no-repeat 0 4px;
+	padding:0 7px 0 5px;
+	color:#898989;
+	float:left;
+	list-style:none;
+}
+ul.inf li a{
+	margin:0 0 0 5px;
+}
+.box img{
+	clear:both;
+	padding:18px 0 0 12px;
+	display:block;
+}
+.graph-holder{
+	width:410px;
+	height:315px;
+	margin:35px 0 0 10px;
+}
+/*-- info-container --*/
+.info-container {
+	margin:20px 0 0;
+	width:100%;
+	clear:both;
+	overflow:hidden;
+}
+.i-c-head {
+	background:#f2f7fa;
+	width:100%;
+	clear:both;
+	overflow:hidden;
+	margin:0 0 -10px;
+}
+.info-container h3{
+	float:left;
+	padding:6px 15px 8px;
+	color:#555;
+}
+.info-container .i-c-head ul{
+	float:right;
+	margin:0;
+	padding:11px 4px 0 0;
+}
+.info-container .i-c-head ul li{
+	padding:0 10px 0 12px;
+	background:url(../img/admin/separator.gif) no-repeat 0 3px;
+	float:left;
+	list-style:none;
+}
+.info-container .i-c-head ul li a.rss-icon{
+	margin:3px 0 0;
+	display:block;
+	width:12px;
+	height:12px;
+	text-indent:-3000px;
+	background:url(../img/admin/icon-rss.gif) no-repeat;
+	overflow:hidden;
+}
+.post {
+	/* border-bottom:2px solid #ddd; */
+	margin:10px 0 0 14px;
+	padding:0 0 7px;
+	width:419px;
+	clear:both;
+	overflow:hidden;
+}
+.post-edit-log-blue{
+	padding:3px;
+	background-color:#F2F7FA;
+	border:1px solid #A8CADE;
+}
+.post-edit-log-gray{
+	padding:3px;
+	background-color:#eee;
+	border:1px solid #ccc;
+}
+.post-edit-log-red a, .post-edit-log-gray a{
+	color:#666;
+}
+.post-edit-log{
+	display:none;
+	padding:5px;
+}
+.post-trans-new{
+	margin:5px 0 2px 20px;
+	font-size:80%;
+}
+.post-trans-new a, .post-trans-new a:visited{
+	color:#666;
+}
+.post-trans{
+	margin:5px 0 0 20px;
+	padding:2px;
+	font-size:90%;
+	background-color:#eee;
+	border:1px dotted #666;
+}
+ul.post-info {
+	display:inline;
+	margin:9px 0 0 12px;
+	padding:1px 3px 4px 8px;
+	border:1px solid #ddd;
+	float:right;
+	width:67px;
+}
+ul.post-info li{
+	width:67px;
+	float:left;
+	padding:2px 0 1px 0;
+	font-size:10px;
+	border-bottom:1px solid #ddd;
+	list-style:none;
+}
+ul.post-info li.last{ border:none;}
+ul.post-info li a{
+	width:65px;
+	height:1%;
+	display:block;
+	color:#555;
+	text-decoration:none;
+}
+
+ul.post-info li a.ok{ background:url(../img/admin/icon-ok.gif) no-repeat 100% 3px;}
+ul.post-info li a.none{ background:url(../img/admin/icon-none.gif) no-repeat 100% 3px;}
+ul.post-info li a.mail{ background:url(../img/admin/icon-mail.gif) no-repeat 100% 3px;}
+ul.post-info li a.phone{ background:url(../img/admin/icon-phone.gif) no-repeat 100% 2px;}
+ul.post-info li a.sms{ background:url(../img/admin/icon-phone.gif) no-repeat 100% 2px;}
+ul.post-info li a.twitter{ background:url(../img/admin/icon-twitter.gif) no-repeat 100% 2px;}
+.post  h4{
+	padding:9px 0 0;
+	font-weight:normal;
+	font-size:12px;
+}
+.post  h4 strong{
+	padding:0 5px 0 0;
+	font-weight:bold;
+	font-size:10px;
+	color:#9b0000;
+}
+.post p{
+	margin:0;
+	padding:2px 0 4px;
+}
+a.view-all {
+	margin:10px 8px 0 0;
+	display:inline;
+	float:right;
+}
+/*-- column-1 --*/
+.column-1{
+	padding:0 18px 0 10px;
+	float:right;
+	width:442px;
+}
+ul.nav-list {
+	width:395px;
+	margin:0;
+	padding:0 0 21px 20px;
+	clear:both;
+	overflow:hidden;
+}
+ul.nav-list li{
+	padding:18px 0 19px;
+	background:url(../img/admin/dots.gif) repeat-x 0 100%;
+	float:left;
+	width:395px;
+	list-style:none;
+}
+ul.nav-list li a{
+	padding:0 0 5px 40px;
+	float:left;
+	font-size:18px;
+	line-height:20px;
+	color:#9b0000;
+}
+ul.nav-list li a.reports {background:url(../img/admin/report-icon.gif) no-repeat 0 0;}
+ul.nav-list li a.categories { background:url(../img/admin/category-icon.gif) no-repeat 0 5px;}
+ul.nav-list li a.locations { background:url(../img/admin/locations-icon.gif) no-repeat 0 2px;}
+ul.nav-list li a.media { background:url(../img/admin/media-icon.gif) no-repeat 0 2px;}
+ul.nav-list li a.messages { background:url(../img/admin/messages-icon.gif) no-repeat 0 2px;}
+ul.nav-list li a.alerts { background:url(../img/admin/alerts-icon.png) no-repeat 0 2px;}
+ul.nav-list li a.votes { background:url(../img/admin/votes-icon.png) no-repeat 0 2px;}
+ul.nav-list li a.checkins { background:url(../img/admin/checkins-icon.png) no-repeat 0 2px;}
+ul.nav-list li span.locations {
+	background:url(../img/admin/locations-icon.gif) no-repeat 0 2px;
+	padding:0 0 5px 40px;
+	float:left;
+	font-size:18px;
+	line-height:20px;
+	color:#9b0000;
+}
+ul.nav-list li strong{
+	float:right;
+	font-weight:normal;
+	line-height:22px;
+	font-size:20px;
+	color:#555;
+}
+ul.nav-list li ul{
+	height:1%;
+	clear:both;
+	overflow:hidden;
+	margin:0;
+	padding:6px 0 0;
+}
+ul.nav-list li li{
+	width:334px;
+	padding:4px 0 0 61px;
+	background:none;
+}
+ul.nav-list li li a{
+	padding:0;
+	text-decoration:none;
+	color:#cb0000;
+	font-size:16px;
+	line-height:20px;
+}
+ul.nav-list li li strong{
+	font-size:16px;
+	color:#898989;
+	line-height:18px;
+}
+.post em.date{
+	font-size:10px;
+	color:#9b0000;
+}
+/*-- footer --*/
+#footer{
+	background:#202020 url(../img/admin/footer-bg.jpg) repeat-x;
+	border-top:4px solid #cb0000;
+	border-bottom:4px solid #cb0000;
+	height:213px;
+	width:100%;
+	overflow:hidden;
+	clear:both;
+}
+#footer strong{ display:block;}
+#footer a{
+        
+	margin:34px 0 0 60px;
+	background:url(../img/admin/logo.gif) no-repeat;
+	width:257px;
+	height:39px;
+	display:block;
+	overflow:hidden;
+	text-indent:-3000px;
+        text-align : right;
+        text-decoration : none;
+}
+
+#footer sup {
+    color : #231f20;
+}
+
+/*------------------- inner ---------------------*/
+/*-- tabs --*/
+.tabs {
+	margin:19px 20px 0;
+	width:900px;
+	clear:both;
+	overflow:hidden;
+}
+/*-- tabset --*/
+ul.tabset{
+	width:100%;
+	clear:both;
+	overflow:hidden;
+	margin:0;
+	padding:0;
+}
+ul.tabset li{
+	padding:0 1px 0 0;
+	float:left;
+	list-style:none;
+}
+ul.tabset li a{
+	text-decoration:none;
+	height:25px;
+	font-weight:bold;
+	padding:5px 15px 0;
+	line-height:20px;
+	float:left;
+	color:#999;
+	font-size:16px;
+}
+ul.tabset li a.active{
+	color:#636363;
+	background:#eee !important;
+}
+ul.tabset li a.active2{
+	color:#fff;
+	background:#C0C0C0 !important;
+}
+ul.tabset li a:hover{
+	color:#636363;
+	background:#f5f5f5;
+}
+ul.tabset li.right {
+	float: right;
+}
+/*-- tabs --*/
+/* Adding content tabs for where we just want a tab, but not the tab button style lists */
+.tab, .content-tab {
+	background:#eee;
+	width:100%;
+	clear:both;
+	padding:0 0 8px;
+	overflow:hidden;
+}
+.tab ul{
+	margin:0;
+	padding:9px 10px 0;
+	float:left;
+	clear:left;
+	overflow:hidden;
+}
+.tab ul li{
+	padding:0 6px;
+	float:left;
+	list-style:none;
+}
+.tab ul li a{
+	padding:0 9px;
+	color:#5c5c5c;
+	background:#f2f7fa;
+	float:left;
+	line-height:24px;
+	text-decoration:none;
+	border:1px solid #d1d1d1;
+}
+.tab ul li a:hover{ text-decoration:underline;}
+.tab .tab_form_item,
+.content-tab .tab_form_item {
+	float:left;
+	margin:10px 10px 0 10px;
+}
+.tab .tab_form_item2{
+	margin:10px 10px 0 10px;
+}
+/*-- tabs --*/
+/*-- btns --*/
+.btns ul{
+	margin:0;
+	padding:9px 10px 0;
+	clear:both;
+	overflow:hidden;
+}
+.btns ul li{
+	padding:0 4px;
+	float:left;
+	list-style:none;
+}
+.btns ul li a{
+	padding:0 9px;
+	color:#5c5c5c;
+	background:#f2f7fa;
+	float:left;
+	line-height:24px;
+	text-decoration:none;
+	border:1px solid #d1d1d1;
+}
+.btns ul li a.btns_red{
+	background:#FFE9EC;
+}
+.btns ul li a.btns_gray{
+	background:#eee;
+}
+.btns ul li a:hover{ text-decoration:underline;}
+/*-- btns --*/
+
+.green-box {
+	margin:15px 21px 0 22px;
+	padding:9px 0 8px;
+	background:#d8f1d8;
+	border:2px solid #a7d1a7;
+	width:893px;
+	clear:both;
+	overflow:hidden;
+}
+.red-box {
+	margin:15px 21px 0 22px;
+	padding:9px 0 8px;
+	background:#FFD8D9;
+	border:2px solid #990000;
+	width:893px;
+	clear:both;
+	overflow:hidden;
+}
+.green-box h3, .red-box h3{
+	padding:0 0 0 15px; 
+	font-size:14px;
+	color:#555;
+}
+a.hide {
+	font-weight:normal;
+	font-size:11px;
+	color:#b75f5f;
+	padding:0 0 0 43px;
+	background:url(../img/admin/separator-1.gif) no-repeat 17px 3px;
+}
+table{
+	margin:0;
+	padding:0;
+	width:100%;
+	border-collapse:collapse;
+}
+table td{
+	margin:0;
+	padding:0;
+	text-align:left;
+	vertical-align:top;
+}
+/*-- report-table --*/
+.table-holder {
+	clear:both;
+	overflow:hidden;
+	margin:11px 20px 0;
+	padding:1px 1px 1px;
+	width:900px;
+}
+.table td{
+	padding:12px 0 10px;
+	border-bottom:2px solid #ddd;
+}
+.table tfoot td {
+	border-right:2px solid #bbb;
+	border-left:2px solid #bbb;
+	border-bottom:2px solid #bbb;
+}
+.table th{
+	height:30px;
+	color:#fff;
+	font-weight:bold;
+	font-size:14px;
+	background:#bbb;
+	margin:0;
+	padding:0;
+	text-align:left;
+}
+.table th.col-4{ text-align:right;}
+.table .col-1{
+	border-left:2px solid #bbb;
+	padding:12px 0 10px 10px;
+	width:25px;
+}
+.table .col-2{ width:539px;}
+.table .col-2 p{ width:539px;word-wrap: break-word;}
+.table .col-2_sub{
+	width:519px;
+	padding-left:30px;
+	background:url(../img/arrow-play.gif) no-repeat 13px 22px;
+}
+.table .col-3{
+	width:103px;
+}
+.table td.col-3 {
+	font-size:12px;
+	font-weight:bold;
+	color:#555;
+}
+.table .col-4{
+	border-right:2px solid #bbb;
+	padding:12px 18px 10px 0;
+	width:200px;
+}
+.table .col{
+	border-left:2px solid #bbb;
+	padding:12px 0 10px 10px;
+	border-right:2px solid #bbb;
+}
+.table .col-drag{
+	background-color:#ffffcc;
+}
+.table .col-show-handle{
+	background-image: url(../img/admin/drag.gif);
+	background-repeat: no-repeat;
+	background-position: center center;
+	cursor: move;
+}
+.table .post{
+	margin:0;
+	padding:0 0 8px;
+	width:492px;
+}
+.table .post h4{
+	padding:2px 0 0;
+	margin:0;
+	color:#00699b;
+	font-weight:bold;
+	font-size:12px;
+}
+.table .post p{
+	line-height:15px;
+	margin:0;
+	padding:3px 20px 0 0;
+}
+.table .post .incident-id{
+	float:right;
+}
+.table th {
+	padding-top:0 !important;
+	padding-bottom:0 !important;
+	border:2px solid #bbb !important;
+	border-bottom:2px solid #959595 !important;
+}
+ul.info,
+ul.links {
+	clear:both;
+	overflow:hidden;
+	margin:0;
+	padding:0 0 1px;
+}
+ul.info {padding-top:4px;}
+ul.info li,
+ul.links li{
+	line-height:12px;
+	font-size:10px;
+	color:#636363;
+	list-style:none;
+	float:left;
+}
+ul.info li {
+	padding:0 9px 0 10px;
+	background:url(../img/admin/separator-2.gif) no-repeat 0 3px;
+}
+ul.info li.none-separator{ padding-left:0;}
+ul.info li strong {
+	font-weight:normal;
+	color:#9b0000;
+}
+ul.links li a{
+	color:#669eb9;
+	text-decoration:none;
+	padding:0 0 0 3px;
+}
+ul.links li a:hover{
+	text-decoration:underline;
+}
+.table .col-4 ul{
+	float:right;
+	margin:0;
+	padding:0;
+}
+.table .col-4 ul li{
+	padding:0 10px 0 12px;
+	background:url(../img/admin/separator.gif) no-repeat 0 4px;
+	float:left;
+	list-style:none;
+}
+.table .col-4 ul li a.del, a.del { color:#b75f5f;}
+.table td .check-box { margin-top:2px !important;}
+
+/*-- tooltips --*/
+.tooltip{
+	cursor: help;
+	background:url(../img/tooltip.gif) no-repeat 100% 4px;
+	padding-right:10px;
+}
+a.tooltip{
+	text-decoration:none;
+	color:#333;
+}
+
+/*-- pager --*/
+ul.pager {
+	margin:0;
+	padding:0 0 0 17px;
+}
+ul.pager li{
+	display:inline;
+	margin:0 6px 0 0;
+	font-size:10px;
+	font-weight:bold;
+	color:#555;
+	border:1px solid #bbb;
+	float:left;
+	list-style:none;
+}
+ul.pager li.first {
+	padding:0 5px;
+}
+ul.pager li a{
+	text-decoration:none;
+	color:#555;
+	padding:0 5px;
+
+}
+ul.pager li a:hover,
+ul.pager li a.active {background:#f2f7fa;}
+/*------------------- inner-1 ---------------------*/
+/*-- report-form --*/
+.report-form,
+.head,
+.row{
+	width:100%;
+	clear:both;
+	overflow:hidden;
+}
+.head {
+	width:928px;
+	padding:19px 12px 4px 0;
+}
+.head h3{
+	padding:0 0 0 20px;
+	font-weight:normal;
+	font-size:22px;
+	line-height:24px;
+	float:left;
+}
+.head h4{
+	padding:0 0 0 20px;
+	font-weight:bold;
+	font-size:16px;
+	line-height:18px;
+	float:left;
+}
+	.head h4.version{
+		text-decoration:underline;
+		color:#37616F;
+	}
+.report-form input:focus, .report-form textarea:focus
+{
+	background: #EDF9EE;
+}
+.report-form input.text, .report-form input.file, .tab_form_item input.text{
+	display:inline;
+	margin:0;
+	padding:2px 5px;
+	float:left;
+	font:12px/14px Arial, Helvetica, sans-serif;
+	border:2px solid #d9d9d9;
+}
+.report-form input.title {
+	width:363px;
+	padding:10px 5px;
+}
+div.custom_div {
+	width: 100%;
+    padding: 5px;
+    border: 1px dotted #C0C2B0;
+    margin-bottom: 20px;
+}
+div.custom_div h2 {
+    font-size: 150%;
+    margin-bottom: 10px;
+    padding-top: 3px;
+    padding-bottom: 3px;
+    padding-left: 5px;
+    background: #E8E8E8;
+}
+.report-form input.custom_text {
+	width:363px;
+	margin-bottom: 10px;
+	float: none;
+}
+.report-form textarea{
+	width:368px;
+	height:260px;
+	margin:0;
+	padding:4px 0 4px 5px;
+	border:2px solid #d9d9d9;
+	float:left;
+}
+.report-form .texthint{
+	font-style:italic;
+	font-weight:normal;
+	color:red;
+	background-color:#FFFF99;
+}
+.sel-holder {
+	overflow:hidden;
+	border:2px solid #d9d9d9;
+	float:left;
+}
+.sel-holder select{
+	margin:-1px -1px;
+	float:left;
+}
+.nofloat, .nofloat select, .nofloat input.text{
+	clear:both;
+	float:none;
+}
+.report-form h4{
+	font-weight:bold;
+	color:#404040;
+	font-size:15px;
+	line-height:18px;
+	padding:16px 0 4px;
+}
+.report-form h4 span{
+	font-weight:normal;
+	font-size:10px;
+	line-height:12px;
+	color:#a1a1a1;
+}
+div.experimental{
+	float:left;
+	padding-left:20px;
+	font-weight:bold;
+	text-transform: uppercase;
+}
+.welcome-form{
+	padding:12px 0px 0px 12px;
+}
+/*-- f-col --*/
+.f-col {
+	padding:0 10px 14px 20px;
+	width:380px;
+	float:left;
+}
+.f-col-full {
+	padding:0 10px 14px 20px;
+}
+.date-box {
+	float:left;
+	width:190px;
+	padding:0 0 3px;
+}
+.date-box input.text { width:100px;}
+a.calendar {
+	text-indent:-3000px;
+	overflow:hidden;
+	margin:0 0 0 9px;
+	display:inline;
+	float:left;
+	width:22px;
+	height:21px;
+	background:url(../img/icon-calendar.gif) no-repeat;
+}
+.time {
+	width:188px;
+	float:left;
+	padding:0 0 3px;
+}
+.date-box h4,
+.time h4{ padding-top:13px;} 
+span.dots {
+	width:17px;
+	text-align:center;
+	float:left;
+	color:#404040;
+	font-size:18px;
+	font-weight:bold;
+}
+.time input.text {width:24px;}
+.time select{width:46px;}
+.time .sel-holder {
+	display:inline;
+}
+a.new-cat, a.show-messages {
+	text-decoration:underline;
+	font-size:10px;
+	padding:1px 0 0 18px;
+	background:url(../img/icon-plus.gif) no-repeat 0 4px;
+	float:right;
+}
+a.new-cat:hover, a.show-messages:hover{ text-decoration:none;}
+a.category_translations {
+	text-decoration:underline;
+	font-size:10px;
+	padding:5px 0 0 18px;
+	background:url(../img/icon-plus.gif) no-repeat 0 4px;
+	float:left;
+	margin: 10px 10px 0 10px;
+}
+a.category_translations:hover {text-decoration: none;}
+.category {
+	background:#f5f5f5;
+	width:377px;
+	clear:both;
+	overflow:hidden;
+	padding:0 0 18px;
+}
+.category ul{
+	width:167px;
+	float:left;
+	margin:0;
+	padding:11px 0 0;
+}
+.category ul li{
+	padding:0 0 7px 18px;
+	width:149px;
+	float:left;
+	overflow:hidden;
+	list-style:none;
+}
+.category ul li label{
+	color:#555;
+	font-size:11px;
+}
+.category_translations_form_fields{
+	display:none;
+	margin: 10px 10px 0 10px
+}
+.category_translations_form_fields .category_lang {
+	margin-top:10px;
+	clear: left;
+}
+.category_translations_form_fields .tab_form_item {
+	margin: 0 20px 0 0;
+}
+#addedit .category_description {
+	width: 250px;
+}
+/*Categories Tree*/
+.category ul ul { width: auto; float: none; }
+.category ul li.sub_category { padding-left: 47px; }
+.category ul li.sub_category_last { background-position: 16px -1766px; }
+.category ul li input.check-box { margin: 0 3px 0 0; }
+.category ul li.hover,
+.category ul li li:hover { background-color: #efefef; }
+ul.treeview li.lastCollapsable,
+ul.treeview li.lastExpandable { width:155px; }
+
+.check-box {
+	margin:0;
+	padding:0;
+	width:15px;
+	height:15px;
+	float:left;
+}
+.category ul li label span{
+	padding:0 0 0 5px;
+	float:left;
+}
+.category_add {
+	background:#f5f5f5;
+	width:377px;
+	clear:both;
+	overflow:hidden;
+	padding:0 0 18px;
+	display: none;
+}
+#show_messages{
+	display: none;
+	font-size:10px;
+	color:#666;
+	margin-bottom:5px;
+}
+#show_messages .message{
+	padding:2px 0 2px 0;
+	border-bottom:1px dotted #ccc;
+}
+.translation{
+	clear:both;
+	margin:10px 0 10px 20px;
+	background-color:#CBDACF;
+	padding:10px;
+	width:400px;
+}
+.translation h4{
+	margin:0 0 3px 0;
+	padding:0;
+}
+/*-- f-col --*/
+.f-col-1 {
+	padding:0 14px 14px 10px;
+	width:506px;
+	float:left;
+}
+.incident-location {
+	width:100%;
+	clear:both;
+	overflow:hidden;
+}
+.incident-location h4{
+	padding-top:15px;
+	clear:none;
+	float:left;
+}
+.incident-find-location {
+	margin-top:0;
+	margin-right:6px;
+	padding:0 9px 9px 9px;
+	background-color:#eee;
+	border:1px solid #ccc;
+	border-width:0 1px 1px 1px;
+	font-size:90%;
+	color:#666;
+}
+.incident-find-location input.findtext {
+	margin-top:9px;
+	padding:3px 3px 0 3px;
+	height:21px;
+	float:left;
+	font-size:14px;
+	font-weight:bold;
+	color:#666;
+	width:250px;
+	border:1px #ccc solid;
+}
+.incident-find-loading{
+	float:left;
+	height:24px;
+	margin-top:9px;
+}
+.location-info {
+	padding:11px 0 0;
+	width:290px;
+	float:right;
+}
+.location-info span,
+.location-info strong{
+	float:left;
+}
+.location-info span {
+	padding:3px 5px 0 7px;
+	color:#404040;
+	font-size:10px;
+}
+.location-info strong {
+	font-weight:normal;
+	font-size:10px;
+	padding:1px 5px;
+	width:74px;
+	border:1px solid #c6c5c2;
+	color:#b75f5f;
+}
+.location-info input.text{
+	width:70px;
+	color:#CC0000;
+	background-color:#FFFFCC;
+}
+.town {
+	float:left;
+	padding-bottom:8px;
+}
+.location {
+	float:left;
+	width:310px;
+	padding-bottom:8px;
+}
+.location select{ width:240px;}
+.town h4,
+.location h4{ padding-top:13px;}
+a.add,
+a.rem{
+	margin:3px 0 0 5px;
+	text-indent:-3000px;
+	overflow:hidden;
+	width:13px;
+	height:13px;
+	float:left;
+	background:url(../img/icon-plus.gif) no-repeat;
+}
+a.rem { background:url(../img/icon-minus.gif) no-repeat;}
+input.short {
+	width:50px;
+}
+input.short2 {
+	width:100px;
+}
+input.short3 {
+	width:150px;
+}
+input.long {
+	width:454px;
+}
+input.long2 {
+	width:300px;
+}
+.second { margin:13px 0 0 !important;}
+.link-row h4{ padding-top:11px;}
+/*-- f-col-bottom --*/
+.f-col-bottom-container {
+	clear:both;
+	border-top:2px solid #dcdcdc;
+	border-bottom:2px solid #dcdcdc;
+}
+.f-col-bottom {
+	overflow:hidden;
+	clear:both;
+	padding:0 10px 6px 20px;
+	width:423px;
+	float:left;
+}
+.f-col-bottom h4, .f-col-bottom-1 h4{
+	padding-top:9px;
+}
+.f-col-bottom  label{
+	padding:2px 17px 5px 0;
+	float:left;
+}
+.f-col-bottom  label span, .f-col-bottom-1 label span{
+	display:block;
+	font-size:12px;
+	font-weight:bold;
+	color:#636363;
+}
+.f-col-bottom  label input.text,
+.f-col-bottom-1 label input.text{ width:167px;}
+.f-col-bottom label input.email,
+.f-col-bottom-1 label input.email{ width:362px;}
+/*-- f-col-bottom-1 --*/
+.f-col-bottom-1 {
+	padding:0 14px 14px 20px;
+	width:451px;
+	float:left;
+	background-color:#eee;
+	border-left:2px dotted #dcdcdc;
+}
+.f-col-bottom-1 .row {
+	margin-top:10px;
+}
+.f-col-bottom-1-col{
+	font-size:12px;
+	font-weight:bold;
+	color:#636363;
+	float:left;
+	width:180px;
+}
+
+.save-rep-btn,
+.save-close-btn,
+.cancel-btn {
+	display:inline;
+	float:left;
+	margin:13px 12px 0 0;
+	padding:0 9px;
+	color:#5c5c5c;
+	background:#f2f7fa;
+	border:1px solid #d1d1d1;
+	text-transform: uppercase;
+	line-height: 24px;
+}
+.report-form .save-rep-btn { margin-left:20px;}
+.report-form .head .save-rep-btn,
+.report-form .head .cancel-btn {
+	margin:0 9px 0 0;
+	display:inline;
+	float:right;
+}
+
+.save-rep-btn:hover,
+.save-close-btn:hover,
+.cancel-btn:hover
+{
+	cursor: pointer;
+}
+.report-form .report_thumbs {
+	border-bottom:1px #ccc dotted;
+	margin:0 0 10px 0;
+}
+.report-form .report_thumbs img {
+	border:1px #ccc solid;
+	padding:3px;
+}
+.status_yes{
+	color:#009900;
+	font-weight:bold;
+	font-size:13px;
+	text-decoration:none;
+}
+.status_no{
+	color:#990000;
+	font-weight:bold;
+	font-size:13px;
+	text-decoration:none;
+}
+.comment_incident{
+	background-color:#eee;
+	border-left:3px #ccc solid;
+	padding:2px 4px;
+}
+.comment_incident a, .comment_incident a:visited{
+	color:#666;
+}
+
+/*-- Extended --*/
+.l-column{
+	padding: 0 0px 0 20px;
+	width: 280px;
+	float:left;
+}
+
+.l-column.inverse {
+	padding: 0 20px 0 0;
+	width: 558px;
+}
+
+.r-column{
+	padding: 0 30px 0;
+	width: 558px;
+	float:left;
+	border: 1px solid #dadada;
+}
+
+.r-column.inverse {
+	width: 220px;
+}
+
+
+p.bold_desc{
+	margin-top: 0px;
+	font-weight: bold;	
+}
+.my-sel-holder {
+	margin-bottom: 10px;
+	overflow:hidden;
+	border:2px solid #d9d9d9;
+	float:left;
+}
+.my-sel-holder select{
+	margin:-2px -1px;
+	float:left;
+}
+.has_border_first{
+	width: 280px;
+	margin: 0px 0 10px;
+	padding: 0px 0 10px;
+	float: left;
+	border-bottom:2px solid #d9d9d9;
+}
+.has_border{
+	width: 280px;
+	margin: 0px 0 10px;
+	padding: 0px 0 10px;
+	float: left;
+	border-bottom:2px solid #d9d9d9;
+}
+.has_border h4{margin-top: 0px; padding-top: 0px;}
+.map_holder_settings{
+	
+}
+.map_holder_reports{
+	width: 494px; 
+	height: 350px; 
+	float:left; 
+	margin:-1px; 
+	border:3px solid #c2c2c2;
+	position:relative;
+}
+.mini_map_holder{
+	margin: 0 0 20px 20px;	
+	float: left;
+}
+span.bold_span{
+	display: block;
+	color: #000;	
+}
+.mapstraction{
+	width:550px;
+	height:300px;
+	border:2px solid #acacac;
+}
+.mapstraction:hover{
+	border-color: #669eb9;
+}
+span.blue_span{
+	color: #1173a2;
+}
+span.dark_span{
+	color: #555555;
+}
+div.c_push{
+	width: 235px;
+	margin: 5px 0 5px;
+	padding-left: 45px;
+	display: block;
+	float: left;
+}
+#retrieve_cities{
+	clear:both;
+	background-color:#eee;
+	color:#333;
+	padding:5px;
+	margin:10px 15px 10px 0;
+}
+#retrieve_cities a, #retrieve_cities a:visited{
+	color:#333;
+}
+#cities_loading{
+	
+}
+#city_count{
+	margin:5px 0 0 0;
+	display:none;
+}
+div.sms_nav_holder{
+	width: 900px;
+	margin: 20px 0 0 20px;
+	float: left;
+}
+div.sms_nav_holder a{
+	padding: 10px;
+	display: block;
+	float: left;
+	text-decoration: none;	
+	color: #a0a0a0;
+}
+div.sms_nav_holder a:hover{background-color: #f0f0f0;}
+div.sms_nav_holder a.active{background-color: #eee; color: #6c6c6c; font-weight: bold;}
+div.sms_holder, div.settings_holder{
+	width: 860px;
+	padding: 15px;
+	margin-left: 20px;
+	border: 5px solid #f0f0f0;
+	float: left;
+}
+	* html div.sms_holder{
+		margin-left: 10px;
+	}
+	div.theme_holder{
+		width:243px;
+		margin-right:20px;
+		margin-bottom:20px;
+		border:1px solid #ccc;
+		float:left;
+		padding:10px;
+	}
+		div.theme_screenshot{
+			width:240px;
+			height:150px;
+			border:1px solid #000;
+			background-color:#ccc;
+			margin-bottom:10px;
+		}
+		.theme_select{
+			margin-top:10px;
+			padding:5px;
+			background-color:#eee;
+			border:1px solid #ccc;
+			font-weight:bold;
+			display: block;
+		}
+		div.theme_select:hover{
+			background-color:#FFFFCC;
+		}
+	div.test_settings{
+		margin-bottom:15px;
+	}
+		div.test_settings #test_loading{
+			line-height: 24px;
+		}
+		div.test_settings #test_status{
+			line-height: 24px;
+			font-size:16px;
+			font-weight:bold;
+		}
+		
+
+div.simple_border{
+	width: 900px;
+	height: 3px;
+	margin: 20px 0 10px 20px;
+	background-color: #f0f0f0;
+	display: block;
+	float: left;
+}
+h4.fix{
+	padding: 0px;
+	margin: 0px;
+}
+a.no_border img{border: none; text-decoration: none;}
+p.sync_key{
+	padding: 10px 5px 10px;
+	display: inline-block;
+	border-width: 3px 0 3px;
+	border-color: #969696;
+	border-style: solid;
+	font-weight: bold;
+	background-color: #fffee5;
+}
+p.sync_key span{
+	color: #9b0000;	
+}
+p.sync_key_2{
+	width: 210px;
+	padding: 20px;
+	display: inline-block;
+	border-width: 3px 0 3px;
+	border-color: #969696;
+	border-style: solid;
+	font-weight: bold;
+	font-size: 22px;
+	color: #9b0000;
+	background-color: #fffee5;
+}
+p.sync_key_3{
+	width: 210px;
+	padding: 20px;
+	display: inline-block;
+	border-width: 3px 0 3px;
+	border-color: #969696;
+	border-style: solid;
+	background-color: #fffee5;
+}
+table.my_table td{padding: 0 0 10px;}
+span.big_blue_span{
+	padding-top: 7px;
+	display: block;
+	font-weight: bold;
+	color: #00699b;
+}
+table.data_points{
+	width: 580px;
+	border-width: 3px 0 3px;
+	border-color: #669eb9;
+	border-style: solid;
+}
+
+	table.data_points td{
+		padding: 10px;
+		background-color: #f2f7fa;
+	}
+span.span_cl1{font-size: 15px; color:#4f4f4f; font-weight: bold; display: block; margin-bottom: 5px; text-transform: capitalize;}
+span.span_cl4{font-size: 12px; color:#00699b; font-weight: bold; display: block; margin: 10px 0 5px;}
+span.span_cl2{font-size: 12px; color:#7d7d7d;}
+span.span_cl3{font-size: 12px; color:#9b0000;}
+.dark_red{color:#9b0000;}
+span.dark_red_span{color:#9b0000; display: block; font-weight: bold; padding:0px; margin: 0px 0 -20px 0;}
+span.dark_red_span_2{color:#9b0000; display: block; font-weight: bold; padding:0px; margin: 0px 0 -10px 0;}
+div.download_container, div.deleteall_container {
+	width: 900px;
+	padding: 0 0 100px 0;
+	margin: 0 0 10px 20px;
+	float: left;
+	border-bottom: 2px solid #dcdcdc;
+}
+div.final_container{
+	width: 900px;
+	padding: 15px 0 15px 0;
+	margin: 0 0px 10px 20px;
+	float: left;
+	border-bottom: 2px solid #dcdcdc;
+}
+
+div.final_l{
+	width: 280px;
+	float: left;
+}
+
+div.final_r{
+	width: 580px;
+	float: left;
+}
+table.p_replace{
+	width: 250px;
+	margin-top: 15px;
+	border-width: 3px 0 3px;
+	border-color: #969696;
+	border-style: solid;
+	background-color: #fffee5;
+}
+table.p_replace td{
+	padding: 20px;
+}
+table.p_replace td.special{
+	width: 47px;
+	padding: 20px 20px 20px 0;
+	background-image:url(../images/bg-td-special.gif);
+	background-position: center left;
+	background-repeat: no-repeat;
+	vertical-align: middle;
+	text-align: center;
+}
+
+/*-- Messages --*/
+.reply{
+	display:none;
+	margin:5px 0 0 0;
+	padding:5px;
+	background-color:#eee;
+}
+#replies{
+	margin:0 0 10px 0;
+}
+.reply_can{
+	font-size:11px;
+	margin-bottom:3px;
+}
+.replychars{
+	font-size:11px;
+	color:#990000;
+}
+.reply_input{
+	float:left;
+	top:0;
+	margin-right:10px;
+}
+.reply_error{
+	background:#FFD8D9;
+	margin:0 0 3px 0;
+	padding:2px;
+	display:none;
+}
+.spam_tr {
+	background-color:#ffffcc;
+}
+
+/*-- Forms --*/
+.forms_fields{
+	display:none;
+	margin:0 20px 15px 20px;
+	padding:10px;
+	background-color:#eee;
+	border-bottom:2px solid #ccc;
+}
+.forms_fields_add{
+	display:none;
+	margin:10px 0 0 15px;
+}
+.forms_fields_new{
+	display:none;
+	border:1px dashed #ccc;
+	padding:10px 10px 0 10px;
+	margin-top:5px;
+	background-color:#fff;
+}
+a.new-form_field{
+	text-decoration:underline;
+	font-size:11px;
+	font-weight:bold;
+	padding:1px 0 0 18px;
+	background:url(../img/icon-plus.gif) no-repeat 0 2px;
+}
+.forms_item{
+	float:left;
+	margin:0 15px 10px 0;
+}
+.forms_fields_current{
+	margin:15px 0 0 0;
+	padding:5px 0 0 0;
+	border-top:1px solid #ccc;
+}
+.forms_fields_edit{
+	font-size:10px;
+	margin:5px 0 0 0;
+	padding-bottom:10px;
+	border-bottom:1px solid #ccc;
+}
+.forms_fields_loading{
+	float:left;
+	margin-right:15px;
+}
+.forms_fields_result{
+	display:none;
+	font-weight:bold;
+	background:#d8f1d8;
+	border:1px solid #a7d1a7;
+	padding:5px;
+	margin:0 0 10px 0;
+}
+.forms_fields_result_error{
+	display:none;
+	font-weight:bold;
+	background:#FFD8D9;
+	border:1px solid #990000;
+	padding:5px;
+	margin:0 0 10px 0;
+}
+.forms_fields_result ul, .forms_fields_result_error ul{
+	margin:0;
+	padding:0;
+}
+.forms_fields_item{
+	margin:0 0 20px 0;
+}
+.forms_fields_item textarea, #custom_forms textarea{
+	float:none;
+	height:50px;
+}
+.hide{
+	display:none;
+}
+
+.required,
+.report-form h4 .required {
+  color: #CC1830;
+  font-size: 15px;
+  font-weight: normal;
+}
+
+/*-- Color Picker --*/
+.jColorSelect {
+	overflow:hidden; /* for IE6 */
+	border:1px solid #fff;
+}
+.jColorSelect div{
+	background:url(../img/icon_check.gif) no-repeat;
+	float:left;
+	width:13px;
+	height:13px;
+	cursor:pointer;
+	overflow:hidden; /* for IE6 */
+	border:1px solid #fff;
+	margin:1px;
+}
+.jColorSelect .check {
+	background-position:-13px 0;
+	cursor:default;
+}
+
+/*-- Sharing --*/
+.sharing_siteinfo{
+	border:1px solid #ccc;
+	padding:10px;
+	background-color:#ffffcc;
+	font-size:95%;
+}
+.sharing_siteinfo span{
+	padding:2px 5px;
+	background-color:#fff;
+	color:#990000;
+	font-weight:bold;
+	font-size:110%;
+}
+.sharing_dispinfo{
+	border:1px solid #ccc;
+	margin-top:5px;
+	padding:1px 5px 4px 5px;
+	background-color:#eee;
+}
+/*-- Admin Map InfoWindow --*/
+.infowindow{
+	margin:0 15px 0 0;
+}
+.infowindow h1{
+	margin:0;
+	padding:0;
+	font:16px bold Arial, Helvetica, sans-serif;
+}
+
+
+/*-----Accessible Slider CSS-----*/
+.slider_container { background: #fff; margin-right: 5px; position: relative; padding: 15px; float: left; border:1px #ccc solid; width:230px; }
+.ui-slider {clear: both; margin-bottom:20px;}
+.ui-slider  {
+	text-decoration: none !important;
+}
+.ui-slider .ui-slider-handle  {
+	overflow: visible !important;
+}
+.ui-slider .ui-slider-tooltip {
+	display: none;
+}
+.ui-slider .screenReaderContext {
+	position: absolute;
+	width: 0;
+	height: 0;
+	overflow: hidden;
+	left: -999999999px;
+}
+.ui-slider .ui-state-active .ui-slider-tooltip, .ui-slider .ui-state-focus .ui-slider-tooltip, .ui-slider .ui-state-hover .ui-slider-tooltip {
+	display: block;
+	position: absolute;
+	bottom: 2.5em;
+	text-align: center;
+	padding: .3em .2em .4em;
+	font-size: .9em;
+	width: 8em;
+	margin-left: -3.7em;
+}
+.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down, .ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down-inner {
+	position: absolute; 
+	display: block;
+	width:0; 
+	height:0; 
+	border-bottom-width: 0;
+	background: none;
+}
+.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down {	
+	border-left: 7px dashed transparent; 
+	border-right: 7px dashed transparent;
+	border-top-width: 8px;
+	bottom:	-8px;
+	right: auto;
+	left: 50%;
+	margin-left: -7px;
+}
+.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down-inner {
+	border-left: 6px dashed transparent; 
+	border-right: 6px dashed transparent;
+	border-top: 7px solid #fff;
+	bottom:	auto;
+	top: -9px;
+	left: -6px;
+}
+.ui-slider a {
+	text-decoration: none;
+}
+.ui-slider ol, .ui-slider li, .ui-slider dl, .ui-slider dd, .ui-slider dt {
+	list-style: none;
+	margin: 0;
+	padding: 0;
+}
+.ui-slider ol, .ui-slider dl {
+	position: relative;
+	top: 1.3em;
+	width: 100%;
+}
+.ui-slider dt {
+	top: 1.5em;
+	position: absolute;
+	padding-top: .2em;
+	text-align: center;
+	border-bottom: 1px dotted #ddd;
+	height: .7em;
+	color: #999;
+}
+.ui-slider dt span {
+	background: #fff;
+	padding: 0 .5em;
+}
+.ui-slider li, .ui-slider dd {
+	position: absolute;
+	overflow: visible;
+	color: #666;
+}
+.ui-slider span.ui-slider-label {
+	position: absolute;
+}
+.ui-slider li span.ui-slider-label, .ui-slider dd span.ui-slider-label {
+	display: none;
+}
+.ui-slider li span.ui-slider-label-show, .ui-slider dd span.ui-slider-label-show {
+	display: block;
+}
+.ui-slider span.ui-slider-tic {
+	position: absolute;
+	left: 0;
+	height: .8em;
+	top: -1.3em;
+}
+.ui-slider li span.ui-widget-content, .ui-slider dd span.ui-widget-content {
+	border-right: 0;
+	border-left-width: 1px;
+	border-left-style: solid;
+	border-top: 0;
+	border-bottom: 0;
+}
+.ui-slider .first .ui-slider-tic, .ui-slider .last .ui-slider-tic {
+	display: none;
+}
+
+
+/*-- Form JS Errors --*/
+.error{
+	color:red;
+	font-style: italic;
+	width: auto;
+	display: block;
+}
+
+
+/*-- Settings Map Tabs --*/
+UL.mapNavigation {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+UL.mapNavigation LI {
+    display: inline;
+}
+
+UL.mapNavigation LI A {
+    padding: 3px 5px;
+    background-color: #ccc;
+    color: #000;
+    text-decoration: none;
+}
+
+UL.mapNavigation LI A.selected,
+UL.mapNavigation LI A:hover {
+    background-color: #333;
+    color: #fff;
+    padding-top: 10px;
+}
+
+UL.mapNavigation LI A:focus {
+    outline: 0;
+}
+
+.upload_container {
+    padding : 20px;
+}
+
+/* update info */
+.update-info {
+    line-height: 29px;
+    font-size: 12px;
+    background : #FFFEEB;
+    border-bottom-width: 1px;
+    border-bottom-style: solid;
+    border-top-width: 1px;
+    border-top-style: solid;
+    padding: 5px;
+}
+
+.update-info a {
+    font-size: 1.1em;
+}
+
+/* raphael impact chart */
+#impact_chart {
+	width: 100%;
+	_width: 940px;
+	height: 500px;
+	overflow: auto;
+	clear: left;
+}
+.impact_hidden {
+	display: none;
+}
+#impact_legend,
+#impact_legend2 {
+	-moz-border-radius: 2px;
+	-webkit-border-radius: 2px;
+	width: 1em;
+	height: 1em;
+	border: solid 1px #000;
+	margin-right: .3em;
+	line-height: 1;
+}
+#impact_info, 
+#impact_info2 {
+	float: left;
+	clear: left;
+}
+#impact_info div,
+#impact_info2 div {
+	float: left;
+}
+#impact_placeholder {
+	clear: left;
+	color: #999;
+}
+
+/*--Stats Page--*/
+
+.content-wrap { padding:0 18px; } 
+	.content-wrap h3 { float:left; padding:0; }
+	.content-wrap .tabs { margin-left:0;}
+
+/*Two column layout */
+.two-col { float:left; }
+	.tc-left { width:570px; margin:0 10px 0 0;}
+	.tc-right { }
+	
+/* Chart Holders */
+.chart-holder { height:300px; }
+.reports-charts { width:664px; margin-right:36px; }
+
+/* Time Period Selection*/
+#time-period-selector { text-align:right; margin-bottom:18px; }
+	input.dp { width:100px; margin:0 10px; font-weight:bold; font-size:10px; color:#5c5c5c;  }
+	#time-period-selector a { padding:0 5px; }
+
+/* Stats Summary */
+.stats-wrapper { border:1px solid #eee; border-top:2px solid #898989; }
+.stats-summary { border-collapse: collapse; }
+	.statistic { border-right:1px solid #eee; padding:5px 8px; width:100px; height:50px; float:left;	 }
+		 
+	.statistic h4 { font-size:14px;  margin:0 0 0px; font-weight:normal; }
+	.statistic p { font-size:30px; font-weight:bold; margin:0; line-height:30px; }
+	.first { border-left:none; }
+	
+	/* Stats Sidebar... used on the Reports Statistics & Impact Reports screens */
+	.stats-sidebar { width:118px; }
+	.stats-sidebar .statistic { border-right:none; border-bottom:1px solid #eee; }
+
+
+/* ProtoChart Styles */
+	/* Reports Pie Chart */
+	#protochart_reports { width:600px; }
+		#protochart_reports .ProtoChart-legend {}
+			#protochart_reports td.legendColorBox {}
+			#protochart_reports td.legendLabel {}
+
+	
+
+
+/*General Graph/Table Styles */
+
+.table-graph { border-collapse: collapse; }
+
+	/* Horizontal Bar Graph */
+	.horizontal-bar {  }
+	
+		.horizontal-bar span { display:block; float:left;  }
+		.horizontal-bar td { border-bottom:1px solid #eee; padding:5px; }
+		
+		.hbItem { width:125px; border-right:1px solid #eee; }
+		td.hbDesc { padding-left:0; }	
+			.stat-bar { background-color:#09F; margin-right:5px;  }
+	
+	/* Generic Data Table */
+	.generic-data {  }
+	
+		.generic-data td,
+		.generic-data th { border-bottom:1px solid #eee; padding:3px; text-align:left; }
+		
+		.gdItem { width:175px; border-right:1px solid #eee; }
+		.gdDesc { }
+		
+		/*flag*/
+		img.flag { margin-right:3px;}
+	
+/* Tabs */
+.tab-box { display:none; border:3px solid #eee; padding:10px 15px; }
+	.active-tab { display:block; }
+
+
+/*Hover state for various stats*/
+.table-graph tr:hover,
+.statistic:hover{ background:#FFFFEF; }
+
+.glossary { width:320px; margin:10px 0 0; } 
+	.glossary h4 { font-size:12px; font-weight:bold; line-height:16px; color:#00699B; margin:0 0 3px 0; }
+	.terms { border-top:3px solid #669EB9; border-bottom:3px solid #669EB9; background:#F2F7FA; padding:0px 18px; }
+
+.two-col .tabs { width:auto; }
+
+/* Color box used for legend on reports stats page */
+#little-color-box {
+	float:left;
+	width:15px;
+	height:10px;
+	background-color:#CCCCCC; /* This should be overridden in the HTML */
+	margin: 4px 8px 4px 0;
+}
+
+/* Clearfix */
+.clearfix:after {
+	content: ".";
+	display: block;
+	clear: both;
+	visibility: hidden;
+	line-height: 0;
+	height: 0;
+}
+ 
+.clearfix {
+	display: inline-block;
+}
+ 
+html[xmlns] .clearfix {
+	display: block;
+}
+ 
+* html .clearfix {
+	height: 1%;
+}
+
+/* Reporters */
+.reporters h3{
+	font-weight:bold;
+	color:#990000;
+	font-size:14px;
+	padding:0;
+	margin:0;
+}
+h4 span.highlight{
+	background-color:#ffffcc;
+	font-size: 15px;
+	font-weight:bold;
+	color:#000;
+}
+#reporter_location{
+	clear:both;
+	padding:10px;
+	margin:10px;
+	border:1px solid #ccc;
+}
+	#reporter_location h3{
+		color:#666;
+	}
+		#reporter_location h3 span{
+			font-weight:normal;
+			font-size:11px;
+		}
+	#reporter_location #ReporterMap{
+		height:250px;
+		width:600px;
+		border:1px solid #666;
+	}
+	/* Trusted Reporter */
+	ul.info li strong.reporters_4{
+		background-color:#005366;
+		color:#fff;
+		padding:2px;
+	}
+	.tab ul li a.reporters_tab_4{
+		background-color:#005366;
+		color:#fff;
+	}
+	/* Trusted + Verify Reporter */
+	ul.info li strong.reporters_5{
+		background-color:#339900;
+		color:#fff;
+		padding:2px;
+	}
+	.tab ul li a.reporters_tab_5{
+		background-color:#339900;
+		color:#fff;
+	}
+	
+.attachment_thumbs{
+	float:left;
+	margin:0 10px 10px 0;
+	border:1px solid #ccc;
+}
+	.attachment_thumbs img{
+		border-width:0;
+	}
+	
+.preview_div{
+	margin:5px 0 0 0;
+	border:1px dotted #ccc;
+	padding:10px;
+	display:none;
+}	
+	
+/* Upgrade Process */
+#upgrade_log .upgrade_log_message{
+	font-size:14px;
+	font-weight:bold;
+	border:2px solid #ccc;
+	background-color:#eee;
+	padding:8px;
+	margin-bottom:5px;
+}
+	#upgrade_log .log_success{
+		background-color:#d8f1d8;
+		border-color: #a7d1a7;
+	}
+	#upgrade_log .log_error{
+		background-color:#FFD8D9;
+		border-color: #990000;
+	}
+	.ftp-settings{
+		display:none;
+		width:600px;
+		margin-top:10px;
+		padding:10px;
+		background-color:#ffffcc;
+	}
+	
+/* Plugins/Addons/Themes */
+tr.addon_active td{
+	background-color:#ffffcc;
+}
+
+/* floaties */
+.right {
+	float:right;
+}
+.left {
+	float:left;
+}
+
+/* Map Toggles */
+ul.map-toggles { overflow:auto; clear:both; margin:3px 0 0 0; padding:0; }
+ul.map-toggles li { display:none; float:right; list-style-type: none; }
+
+ul.map-toggles li a, 
+ul.map-toggles li a:active { display:inline-block; padding:3px 5px 3px 17px; outline:none; }
+
+	a.wider-map		{ background:transparent url(../img/admin/icon_sprite.png) -4px -202px no-repeat;  }
+	a.taller-map	{ background:transparent url(../img/admin/icon_sprite.png) -4px -221px no-repeat;  }
+	a.shorter-map	{ background:transparent url(../img/admin/icon_sprite.png) -4px -241px no-repeat;  }
+	a.smaller-map	{ background:transparent url(../img/admin/icon_sprite.png) -4px -182px no-repeat;  }
+	
+/* Map Labeler */	
+#geometryLabelerHolder{
+	position:absolute;
+	bottom:0;
+	display:none;
+	width:100%;
+	background-color:darkblue;
+	opacity:0.8;
+	z-index:5000;
+}
+	#geometryLabeler{
+		padding:10px;
+	}
+		#geometryLabeler label{
+			font-weight:bold;
+			color:#fff;
+			margin-right:5px;
+		}
+		#geometryLabeler .lbl_text{
+			width:80px;
+			margin-right:15px;
+		}
+		#geometryLabeler .lbl_text2{
+			width:180px;
+		}
+		#geometryLabeler #geometryLabelComment{
+			padding-bottom:7px;
+			margin-bottom:7px;
+			border-bottom:1px dotted #fff;
+		}
+		#geometryLat, #geometryLon{
+			display:none;
+		}
+		#geometryLabelerClose{
+			z-index:5001;
+			position:absolute;
+			top:2px;
+			right:0;
+			width:18px;
+			height:18px;
+			background: url("../../img/openlayers/layer-switcher-minimize.png") no-repeat;
+			cursor: pointer;
+		}
+		
+		
+/* Members */
+.member_profile{
+	clear:both;
+}
+	.member_photo{
+		float:left;
+		margin:0 15px 0 15px;
+	}
+	.member_photo img{
+		border:2px solid #f5f5f5;
+		padding:3px;
+	}
+	.member_info{
+		float:left;
+		width:295px;
+		font-size:110%;
+	}
+	.member_info_row{
+		margin-bottom:3px;
+		padding:3px;
+		border-bottom:1px dotted #ccc;
+	}
+		.member_info_row:hover{
+			background-color:#ffffcc;
+		}
+		.member_info_label{
+			font-weight:bold;
+		}
+		.member_info_row ul {
+			margin:0;
+			padding:0;
+			margin-left: 10px;
+			padding-left: 15px;
+		}
+			.member_info_row ul li{
+				margin:2px 0 2px 0;
+			}
+			
+		.member_reputation{
+			font-size:120%;
+			font-weight:bold;
+			color:#888;
+			background-color:#eee;
+			padding:3px;
+		}
+.checkin_map{
+	margin-top:10px;
+	width:460px;
+	height:300px;
+	border: 2px solid #ccc;
+}
+
+/*
+Button Styles
+*/
+a.button 
+/* button,
+input[type="submit"],
+input[type="reset"],
+input[type="button"] */ {
+	background: #eee; /* Old browsers */
+	
+  border: 1px solid #aaa;
+  border-top: 1px solid #ccc;
+  border-left: 1px solid #ccc;
+  padding: 2px 10px;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+  border-radius: 3px;
+  color: #444;
+  display: inline-block;
+  font-size: 10px;
+  font-weight: bold;
+  text-decoration: none;
+  text-shadow: 0 1px rgba(255, 255, 255, .75);
+  cursor: pointer;
+  margin-bottom: 20px;
+  line-height: 21px;
+  font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; }
+  
+a.button:hover
+/* button:hover,
+input[type="submit"]:hover,
+input[type="reset"]:hover,
+input[type="button"]:hover */ {
+	color: #222;
+	background: #eee; /* Old browsers */
+	
+  border: 1px solid #888;
+  border-top: 1px solid #aaa;
+  border-left: 1px solid #aaa; }
+  
+.badge {
+	float:left;
+	width:100px;
+	text-align:center;
+	background-color:#E9E9E9;
+	-moz-border-radius:5px;
+	border-radius:5px;
+	padding:5px;
+	margin:0px 5px;
+}
+
+/* jquery datePicker CSS */
+
+
+
+table.jCalendar {
+	border: 1px solid #000;
+	background: #aaa;
+    border-collapse: separate;
+    border-spacing: 2px;
+}
+table.jCalendar th {
+	background: #333;
+	color: #fff;
+	font-weight: bold;
+	padding: 3px 5px;
+}
+
+table.jCalendar td {
+	background: #ccc;
+	color: #000;
+	padding: 3px 5px;
+	text-align: center;
+}
+table.jCalendar td.other-month {
+	background: #ddd;
+	color: #aaa;
+}
+table.jCalendar td.today {
+	background: #666;
+	color: #fff;
+}
+table.jCalendar td.selected {
+	background: #f66;
+	color: #fff;
+}
+table.jCalendar td.selected.dp-hover {
+	background: #f33;
+	color: #fff;
+}
+table.jCalendar td.dp-hover,
+table.jCalendar tr.activeWeekHover td {
+	background: #fff;
+	color: #000;
+}
+table.jCalendar tr.selectedWeek td {
+	background: #f66;
+	color: #fff;
+}
+table.jCalendar td.disabled, table.jCalendar td.disabled.dp-hover {
+	background: #bbb;
+	color: #888;
+}
+table.jCalendar td.unselectable,
+table.jCalendar td.unselectable:hover,
+table.jCalendar td.unselectable.dp-hover {
+	background: #bbb;
+	color: #888;
+}
+
+/* For the popup */
+
+/* NOTE - you will probably want to style a.dp-choose-date - see how I did it in demo.css */
+
+div.dp-popup {
+	position: relative;
+	background: #ccc;
+	font-size: 10px;
+	font-family: arial, sans-serif;
+	padding: 2px;
+	width: 171px;
+	line-height: 1.2em;
+}
+div#dp-popup {
+	position: absolute;
+	z-index: 199;
+}
+div.dp-popup h2 {
+	font-size: 12px;
+	text-align: center;
+	margin: 2px 0;
+	padding: 0;
+}
+a#dp-close {
+	font-size: 11px;
+	padding: 4px 0;
+	text-align: center;
+	display: block;
+}
+a#dp-close:hover {
+	text-decoration: underline;
+}
+div.dp-popup a {
+	color: #000;
+	text-decoration: none;
+	padding: 3px 2px 0;
+}
+div.dp-popup div.dp-nav-prev {
+	position: absolute;
+	top: 2px;
+	left: 4px;
+	width: 100px;
+}
+div.dp-popup div.dp-nav-prev a {
+	float: left;
+}
+/* Opera needs the rules to be this specific otherwise it doesn't change the cursor back to pointer after you have disabled and re-enabled a link */
+div.dp-popup div.dp-nav-prev a, div.dp-popup div.dp-nav-next a {
+	cursor: pointer;
+}
+div.dp-popup div.dp-nav-prev a.disabled, div.dp-popup div.dp-nav-next a.disabled {
+	cursor: default;
+}
+div.dp-popup div.dp-nav-next {
+	position: absolute;
+	top: 2px;
+	right: 4px;
+	width: 100px;
+}
+div.dp-popup div.dp-nav-next a {
+	float: right;
+}
+div.dp-popup a.disabled {
+	cursor: default;
+	color: #aaa;
+}
+div.dp-popup td {
+	cursor: pointer;
+}
+div.dp-popup td.disabled {
+	cursor: default;
+}
+/* end datepicker css */
+
+.transparent-25 {
+  /* IE 8 */
+  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=25)";
+
+  /* IE 5-7 */
+  filter: alpha(opacity=25);
+
+  /* Netscape */
+  -moz-opacity: 0.25;
+
+  /* Safari 1.x */
+  -khtml-opacity: 0.25;
+
+  /* Good browsers */
+  opacity: 0.25;
+}
+.transparent-50 {
+  /* IE 8 */
+  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
+
+  /* IE 5-7 */
+  filter: alpha(opacity=50);
+
+  /* Netscape */
+  -moz-opacity: 0.5;
+
+  /* Safari 1.x */
+  -khtml-opacity: 0.5;
+
+  /* Good browsers */
+  opacity: 0.5;
+}
+.transparent-75 {
+  /* IE 8 */
+  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=75)";
+
+  /* IE 5-7 */
+  filter: alpha(opacity=75);
+
+  /* Netscape */
+  -moz-opacity: 0.75;
+
+  /* Safari 1.x */
+  -khtml-opacity: 0.75;
+
+  /* Good browsers */
+  opacity: 0.75;
+}
+
+/* Swatch style */
+.swatch {
+	width: 30px;
+	height: 30px;
+	display: inline-block;
+}
+/* Reports sort/order fields */
+.sort_by {
+	float: right;
+	margin: 0;
+	padding: 9px 10px 0;
+}
+.sort {
+	width: 18px;
+	height: 9px;
+	display: block;
+	float: right;
+	margin: 4px 0 0 6px;
+}
+	.sort-ASC {
+		background: url(../img/admin/drag.gif) 0 0 no-repeat;
+	}
+	.sort-DESC {
+		background: url(../img/admin/drag.gif) 0 -8px no-repeat;
+	}
+
+/* Report search form */
+.search-tab {
+	padding: 1.2em;
+	display: none;
+}
+	.search-tab .row {
+		margin-bottom: 1em;
+	}
+		.search-tab .category-row, .search-tab .location-row {
+			float: left;
+			clear: none;
+			padding-right: 0.5em;
+		}
+		.search-tab .category-row { width: 300px; }
+		.search-tab .location-row { width: 515px; }
+	.search-tab label.fixw {
+		width: 100px;
+		float: left;
+	}
+	.search-tab label.wrapped {
+		padding-right: 5px;
+	}
+	.search-tab .map {
+		overflow: hidden;
+		padding: 10px;
+		margin: 10px 0 0 0;
+	}
+	.search-tab .report-find-location {
+		padding: 9px 18px 18px 18px;
+		font-size: 80%;
+	}
+
+.report-find-location {
+	margin-right:1px;
+	padding:0 9px 9px 9px;
+	background-color:#eee;
+	font-size:90%;
+	color:#666;
+}
+.report-find-location input.findtext {
+	margin-top:9px;
+	padding:5px 3px 0 3px;
+	height:24px;
+	float:left;
+	font-size:14px;
+	font-weight:bold;
+	color:#666;
+	width:250px;
+	border:1px #ccc solid;
+}
+.report-find-loading {
+	float:left;
+	height:31px;
+	margin:9px 0 0 3px;
+}
+.allowed-html {
+	font-weight: normal;
+	font-size: 10px;
+	line-height: 12px;
+	color: #a1a1a1;
+}
diff --git a/media/css/colorpicker.css b/media/css/colorpicker.css
new file mode 100644
index 0000000..dd8d37c
--- /dev/null
+++ b/media/css/colorpicker.css
@@ -0,0 +1,162 @@
+.colorpicker {
+	width: 356px;
+	height: 176px;
+	overflow: hidden;
+	position: absolute;
+	background: url(../img/colorpicker/colorpicker_background.png);
+	font-family: Arial, Helvetica, sans-serif;
+	display: none;
+	z-index: 1010;
+}
+.colorpicker_color {
+	width: 150px;
+	height: 150px;
+	left: 14px;
+	top: 13px;
+	position: absolute;
+	background: #f00;
+	overflow: hidden;
+	cursor: crosshair;
+}
+.colorpicker_color div {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 150px;
+	height: 150px;
+	background: url(../img/colorpicker/colorpicker_overlay.png);
+}
+.colorpicker_color div div {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 11px;
+	height: 11px;
+	overflow: hidden;
+	background: url(../img/colorpicker/colorpicker_select.gif);
+	margin: -5px 0 0 -5px;
+}
+.colorpicker_hue {
+	position: absolute;
+	top: 13px;
+	left: 171px;
+	width: 35px;
+	height: 150px;
+	cursor: n-resize;
+}
+.colorpicker_hue div {
+	position: absolute;
+	width: 35px;
+	height: 9px;
+	overflow: hidden;
+	background: url(../img/colorpicker/colorpicker_indic.gif) left top;
+	margin: -4px 0 0 0;
+	left: 0px;
+}
+.colorpicker_new_color {
+	position: absolute;
+	width: 60px;
+	height: 30px;
+	left: 213px;
+	top: 13px;
+	background: #f00;
+}
+.colorpicker_current_color {
+	position: absolute;
+	width: 60px;
+	height: 30px;
+	left: 283px;
+	top: 13px;
+	background: #f00;
+}
+.colorpicker input {
+	background-color: transparent;
+	border: 1px solid transparent;
+	position: absolute;
+	font-size: 10px;
+	font-family: Arial, Helvetica, sans-serif;
+	color: #898989;
+	top: 4px;
+	right: 11px;
+	text-align: right;
+	margin: 0;
+	padding: 0;
+	height: 11px;
+}
+.colorpicker_hex {
+	position: absolute;
+	width: 72px;
+	height: 22px;
+	background: url(../img/colorpicker/colorpicker_hex.png) top;
+	left: 212px;
+	top: 142px;
+}
+.colorpicker_hex input {
+	right: 6px;
+}
+.colorpicker_field {
+	height: 22px;
+	width: 62px;
+	background-position: top;
+	position: absolute;
+}
+.colorpicker_field span {
+	position: absolute;
+	width: 12px;
+	height: 22px;
+	overflow: hidden;
+	top: 0;
+	right: 0;
+	cursor: n-resize;
+}
+.colorpicker_rgb_r {
+	background-image: url(../img/colorpicker/colorpicker_rgb_r.png);
+	top: 52px;
+	left: 212px;
+}
+.colorpicker_rgb_g {
+	background-image: url(../img/colorpicker/colorpicker_rgb_g.png);
+	top: 82px;
+	left: 212px;
+}
+.colorpicker_rgb_b {
+	background-image: url(../img/colorpicker/colorpicker_rgb_b.png);
+	top: 112px;
+	left: 212px;
+}
+.colorpicker_hsb_h {
+	background-image: url(../img/colorpicker/colorpicker_hsb_h.png);
+	top: 52px;
+	left: 282px;
+}
+.colorpicker_hsb_s {
+	background-image: url(../img/colorpicker/colorpicker_hsb_s.png);
+	top: 82px;
+	left: 282px;
+}
+.colorpicker_hsb_b {
+	background-image: url(../img/colorpicker/colorpicker_hsb_b.png);
+	top: 112px;
+	left: 282px;
+}
+.colorpicker_submit {
+	position: absolute;
+	width: 22px;
+	height: 22px;
+	background: url(../img/colorpicker/colorpicker_submit.png) top;
+	left: 322px;
+	top: 142px;
+	overflow: hidden;
+}
+.colorpicker_focus {
+	background-position: center;
+}
+.colorpicker_hex.colorpicker_focus {
+	background-position: bottom;
+}
+.colorpicker_submit.colorpicker_focus {
+	background-position: bottom;
+}
+.colorpicker_slider {
+	background-position: bottom;
+}
diff --git a/media/css/error.css b/media/css/error.css
new file mode 100644
index 0000000..26f5465
--- /dev/null
+++ b/media/css/error.css
@@ -0,0 +1,174 @@
+html,body{height:100%}
+body {
+	font-size:12px;
+	margin:0;
+	font-family:Verdana, Arial, Helvetica, sans-serif;
+	text-align:center;
+	background-image:url(../img/bkg_login.gif);
+}
+body td {
+	font-family:Verdana, Arial, Helvetica, sans-serif;
+	font-size:12px;
+}
+a {
+	color:#990000;
+	text-decoration:underline;
+}
+a:hover {
+	text-decoration:none;
+}
+
+div#ushahidi_login_logo {text-align:center; padding:50px 0 20px 0;}
+div#ushahidi_login_logo img { width:400px; height:80px;  }
+div#framework_error { background:#fff; font-family:sans-serif; color:#111; font-size:14px; line-height:130%;border:3px solid #ccc;text-align:left;padding: 20px; }
+div#framework_error h3 { color:#fff; font-size:16px; padding:8px 6px; margin:0 0 8px; background:#666; text-align:center; }
+div#framework_error a { color:#228; text-decoration:none; }
+div#framework_error a:hover { text-decoration:underline; }
+div#framework_error strong { color:#900; }
+div#framework_error p { margin:0; padding:4px 6px 10px; }
+div#framework_error tt,
+div#framework_error pre,
+div#framework_error code { font-family:monospace; padding:2px 4px; font-size:12px; color:#333;
+	white-space:pre-wrap; /* CSS 2.1 */
+	white-space:-moz-pre-wrap; /* For Mozilla */
+	word-wrap:break-word; /* For IE5.5+ */
+}
+div#framework_error tt { font-style:italic; }
+div#framework_error tt:before { content:">"; color:#aaa; }
+div#framework_error code tt:before { content:""; }
+div#framework_error pre,
+div#framework_error code { background:#eaeee5; border:solid 0 #D6D8D1; border-width:0 1px 1px 0; }
+div#framework_error .block { display:block; text-align:left; }
+div#framework_error .ushahidi_bugs { text-align:center; font-size:13px; font-weight:bold; }
+div#framework_error div#bug_form { display:none; padding:20px; font-size:80%; }
+div#framework_error div#bug_form .bug_form_desc { font-size:12px;}
+div#framework_error .stats { padding:4px; background: #eee; border-top:solid 1px #ccc; text-align:center; font-size:10px; color:#888; }
+div#framework_error .backtrace { margin:0; padding:0 6px; list-style:none; line-height:12px; }
+
+/* form */
+form {
+	margin:0;
+	padding:0;
+}
+.label{
+	font-weight:bold;
+	color:#333;
+	text-align:right;
+}
+label.error{
+	font-weight:normal;
+	color:red;
+	text-align:left;
+	display:block;
+}
+.text,
+.textarea,
+.select,
+.file{
+	color:#990000;
+	font-size:120%;
+	font-weight:bold;
+}
+.text:focus,
+.textarea:focus,
+.select:focus,
+.file:focus{
+	background-color:#ffffcc;
+}
+.text {
+	padding:3px 7px 3px 7px;
+	border:1px #ccc solid;
+}
+.textarea {
+	padding:7px 7px 3px 7px;
+	border:1px #ccc solid;
+	font-family:"Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;
+}
+.select{
+	overflow:hidden;
+}
+.long{
+	width:90%;
+}
+.long2{
+	width:80%;
+	float:left;
+	margin-bottom:10px;
+}
+.short{
+	width:100px;
+}
+.environ{
+	font-size:12px;
+	font-weight:normal;
+	color:#333;
+	background-color:#eee;
+}
+.environ:focus{
+	background-color:#eee;
+}
+.action_btn {
+	margin-top:5px;
+	font-size:17px;
+	font-weight:bold;
+	color:#FFFFFF;
+	height:40px;
+	background-color:#666666;
+	border-top-color:#999999;
+	border-bottom-color:#333333;
+	border-left-color:#999999;
+	border-right-color:#333333;
+	border-width:3px;
+	width: 190px;
+}
+/* error/success */
+.green-box {
+	margin:10px 0 10px 0;
+	padding:9px 0 8px;
+	background:#d8f1d8;
+	border:2px solid #a7d1a7;
+	clear:both;
+	overflow:hidden;
+}
+.red-box {
+	margin:10px 0 10px 0;
+	padding:9px 0 8px;
+	background:#FFD8D9;
+	border:2px solid #990000;
+	clear:both;
+	overflow:hidden;
+}
+.green-box h3, .red-box h3{
+	margin:0;
+	padding:0 0 0 15px; 
+	font-size:14px;
+	color:#555;
+}
+
+/********* 404 ERROR PAGE *********/
+#error {
+	width:500px;
+	height:400px;
+	position:absolute;
+	top:50%;
+	left:50%;
+	margin:-200px auto auto -250px;
+	border:3px dotted #666;
+	text-align:left;
+	padding: 20px;
+	background-color: #FFFFCC;
+	background-image:url(../img/icon_alert_big.gif);
+	background-position:20px 20px;
+	background-repeat:no-repeat;
+}
+#error h1{
+	height:120px;
+	margin:50px 0 0 100px;
+	font-size:30px;
+	font-weight:bold;
+	color:#333333;
+	font-family:Arial, Helvetica, sans-serif;
+	line-height:32px;
+	padding-left:30px;
+}
+/********* /404 ERROR PAGE *********/
\ No newline at end of file
diff --git a/media/css/global.css b/media/css/global.css
new file mode 100644
index 0000000..257d5e1
--- /dev/null
+++ b/media/css/global.css
@@ -0,0 +1,185 @@
+/* ---- Header Nav Bar ---- */
+
+#header_nav {
+	height:30px;
+	background-color: #585858;
+	border-bottom: 1px solid #FFFFFF;
+	color:#CCCCCC;
+	padding:0;
+	margin:0;
+	width:100%;
+	
+	-moz-box-shadow: 1px 1px 1px #CCC;
+	-webkit-box-shadow: 1px 1px 1px #CCC;
+	box-shadow: 1px 1px 1px #CCC;
+}
+
+#header_nav a {
+	color:#CCCCCC !important;
+	text-decoration:none;
+	display:inline-block;
+	height:30px;
+	padding: 0px 10px 0px 10px;
+}
+
+#header_nav a:hover {
+	background-color:#CCCCCC;
+	color:#000000 !important;
+	text-decoration:none;
+}
+
+#header_nav ul#header_nav_left a:hover {
+	background-color:transparent;
+	color:#CCCCCC !important;
+}
+
+#header_nav img {
+	border: 1px solid #CCCCCC;
+	vertical-align:middle;
+}
+
+#header_nav img {
+	border: 0;
+	vertical-align:middle;
+	margin:0;
+}
+
+
+#header_nav_left {
+	float: left;
+	height: 30px;
+	padding:0 100px;
+	margin:0;
+}
+
+#header_nav_left li {
+	list-style-type: none;
+	display: inline;
+}
+
+#header_nav_right {
+	padding:0 100px;
+	margin:0;
+}
+
+#header_nav_right li {
+	list-style-type: none;
+}
+
+#header_nav_right li.header_nav_user a {
+	padding: 4px 5px 0 5px;
+	height: 26px;
+}
+
+#header_nav_right li.header_nav_has_dropdown a span.header_nav_icon {
+	float: right;
+	width: 14px;
+	height: 14px;
+	background-position: center -24px;
+	margin: 11px 5px 0;
+}
+
+#header_nav_right li .header_nav_dropdown {
+	position:absolute;
+	z-index: 10;
+	right: 100px;
+	text-align: left;
+	border: 1px solid #FFFFFF;
+	border-top: 0px;
+	background: #585858;
+	padding: 5px 0;
+	float:right;
+	top:30px;
+	
+	-moz-box-shadow: 1px 1px 1px #CCC;
+	-webkit-box-shadow: 1px 1px 1px #CCC;
+	-o-box-shadow: 1px 1px 1px #CCC;
+	
+	box-shadow: 1px 1px 1px #CCC;
+}
+
+#header_nav_right li .header_nav_dropdown ul {
+	margin: 0;
+}
+
+#header_nav_right li .header_nav_dropdown #userpass_form ul {
+	padding-bottom: 7px;
+	margin-bottom: 7px;
+	border-bottom: 1px solid #fff;
+}
+
+#header_nav .header_nav_user {
+	float:right;
+	clear:right;
+}
+
+#header_nav_left h1 {
+	font-size:100%;
+	font-weight:normal;
+	margin:0;
+	padding:0;
+}
+
+#header_nav .left_bar, header .right_bar {
+	float: left;
+	height: 30px;
+	background-color:red;
+}
+
+#header_nav .center {
+	float: left;
+	margin: 0;
+}
+
+#header_nav .logo {
+	vertical-align:middle;
+}
+
+#header_nav .logo a {
+	display: block;
+	width: 25px;
+	height: 25px;
+	-webkit-transition: all .2s ease;
+	-moz-transition: all .2s ease;
+	-o-transition: all .2s ease;
+	transition: all .2s ease;
+	padding-right: 5px;
+	margin: 0px 15px;
+}
+
+#header_nav .logo a:hover {
+	opacity: 0.7;
+	background-color: transparent;
+}
+
+#header_nav .nodisplay, .label {
+	display: none;
+}
+
+#header_nav label {
+	display: block;
+	font-weight: bold;
+	padding: 5px 0px 0px 15px;
+}
+
+#header_nav input {
+	display: block;
+	margin: 0px 15px;
+}
+
+#header_nav input.header_nav_login_btn {
+	margin-top: 8px;
+}
+
+#header_nav .bignext {
+	font-size:20px;
+}
+
+body {
+	margin:0;
+	padding:0;
+}
+
+form#header_nav_userforgot_form {
+	display:none;
+}
diff --git a/media/css/ie6.css b/media/css/ie6.css
new file mode 100644
index 0000000..de49ce6
--- /dev/null
+++ b/media/css/ie6.css
@@ -0,0 +1,10 @@
+body{
+	width:expression(document.documentElement.clientWidth < 960 ? "960px" : "auto");
+}
+
+#footer .footer-info ul li {
+	background-image: expression( (this===this.parentNode.childNodes[0]) ? "none" : "auto");
+	padding-left: expression( (this===this.parentNode.childNodes[0]) ? "0" : "auto");
+}
+.small-block .block-bg ul li,
+.incident-name ul li {border: expression( (this===this.parentNode.childNodes[0]) ? "none" : "auto");}
diff --git a/media/css/installer.css b/media/css/installer.css
new file mode 100644
index 0000000..5145419
--- /dev/null
+++ b/media/css/installer.css
@@ -0,0 +1,210 @@
+/*****************************
+ USHAHIDI STYLES
+ v1.0
+ David Kobia - 07/08/2008
+*****************************/
+
+* { margin: 0; padding: 0; }
+body {
+	background-image:url(../img/bkg_login.gif);
+}
+body, td {
+	font: 13px "Lucida Grande", "Lucida Sans Unicode", Tahoma, Verdana, sans-serif;
+}
+
+
+h1, h2 { margin-bottom:18px; }
+
+h3 { margin-bottom:8px; } 
+	h3 a { font-size:10px; font-weight:normal; }
+a:link,
+a:visited {
+	color:#003399;
+}
+a:visited {
+	color:#0066CC;
+}
+a:hover,
+a:focus,
+a:active {
+	text-decoration:none;
+	color:#003399;
+}
+
+
+
+p, ul, ol { margin-bottom:18px; }
+
+ul, ol { margin-left:18px; }
+ul li, ol li { padding:0 0 5px;  }
+.last { margin-bottom:0; }
+
+ul, ol { list-style-position:inside; }
+
+dl {}
+	dt {}
+	dd {}
+
+/* LOGIN BOX */
+/*****************************/
+#ushahidi_login_container{
+	width:400px;
+	margin:50px auto;
+}
+#ushahidi_login_logo {
+	text-align:center;
+	margin-bottom:20px;
+}
+#ushahidi_login_logo img { width:400px; height:80px;  }
+
+#ushahidi_login {
+	border:3px solid #cb0000;
+	text-align:left;
+	padding: 20px;
+	background-color: #ddd;
+}
+#ushahidi_loginbox td{
+	padding:5px;
+	vertical-align:top;
+}
+
+/* Installer Container	 */
+/*****************************/
+#ushahidi_install_container { 
+    width:800px;
+    margin:50px auto;
+}
+
+/* Content Area */
+/*****************************/
+.two-col { padding:20px 30px; width:306px; float:left;  }
+.two-col-box { 
+	-moz-background-clip:border;
+	-moz-background-inline-policy:continuous;
+	-moz-background-origin:padding;
+	/*-moz-border-radius-bottomleft:4px;
+	-moz-border-radius-bottomright:4px;
+	-moz-border-radius-topleft:4px;
+	-moz-border-radius-topright:4px;*/
+	-moz-box-shadow:0 1px 3px #999999;
+	border-top:1px solid #FFFFFF;
+	background:#F5F5F5;
+	padding:20px 30px;
+	width:306px;
+	float:left;
+}
+
+	a.btn-box { display:block; font-size:inherit; cursor:pointer; text-decoration:none; }
+		a.btn-box span { display:block; text-decoration:none; }
+		a.btn-box:hover span { text-decoration:none; }
+		span.btn-box-title 		{ margin-bottom:0px; font-size:20px; font-weight:bold; color:#000; }
+		span.btn-box-content	{ margin-bottom:18px; color:#000; }
+		span.btn-action			{ color:#000; }
+	
+	a.btn-box:hover { background:#e0f2ff; }
+		a.btn-box:hover	span.btn-action	 { text-decoration:underline; }
+		
+	a.btn-box:active { outline:none; }
+
+	.tc-left { margin-right:20px; }
+	.tc-right { }
+
+
+
+	/* BUTTONS*/
+	/*****************************/
+	a.button,
+	input.button {
+		/*-moz-border-radius-bottomleft:10px;
+		-moz-border-radius-bottomright:10px;
+		-moz-border-radius-topleft:10px;
+		-moz-border-radius-topright:10px;*/
+		background:#F5F5F5;
+		color:#464646;
+		border:1px solid #F5F5F5;
+		cursor:pointer;
+		font-size:14px !important;
+		line-height:16px;
+		padding:6px 12px;
+		text-decoration:none;
+	}
+	a.button:hover,
+	input.button:hover { color:#000; border:1px solid #a1a1a1; background:#e0f2ff; }
+
+	/* Progress Meter*/
+	/*****************************/
+	ol.progress-meter { padding:16px 0 19px 15px; margin-left:0; background:transparent url(../img/install_bg-progress-bar.gif) top repeat-x; list-style-position:inside; }
+	ol.progress-meter li { margin:0 28px 0 0px; padding:3px 0 5px 7px; float:left; color:#777; font-size:14px; background:transparent url(../img/install_bg-progress-item-number.gif) 0px 0px no-repeat; }
+	ol.progress-meter li.active { color:#000;  }
+	ol.progress-meter li.active span { text-decoration:underline; font-weight:bold; padding:0 0 35px; background:transparent url(../img/install_bg-progress-active.gif) bottom center no-repeat; }
+	ol.progress-meter li:hover {}
+	ol.progress-meter li.last { margin-right:0px; }
+	ol.progress-meter li span {}
+	
+	
+	
+/*****************************/
+
+
+
+/* FORM */
+/*****************************/
+
+.form-table { border-collapse: collapse; margin: 1em 0; width: 100%; }
+
+.form-table td { margin-bottom: 9px; padding: 10px; border-bottom: 4px solid #eee; font-size: 12px; }
+.fields th { font-size: 13px; text-align: left; line-height:18px; padding: 16px 10px 10px 10px; border-bottom: 4px solid #eee; width: 140px; vertical-align: middle;}
+
+.form-table tr { background: #DDDDDD; }
+	.fields tr:hover { background:#EFEFEF; }
+	.fields tr.hover { background:#e0f2ff; }
+
+.form-table code { line-height: 18px; font-size: 18px; }
+
+.form-table p {	margin: 4px 0 0 0;font-size: 11px; }
+
+.form-table input { line-height: 20px; font-size: 15px; padding: 2px; }
+
+.next { text-align:right; border:none; float: right; }
+.prev { text-align:left; border:none; float: left; }
+
+	/* System Messages */
+	/*****************************/
+	.feedback { padding:10px 10px 10px 48px; color:#4D4D4D; margin-bottom:10px; }
+	
+	.feedback p, 
+	.feedback ul { margin-bottom:10px }
+	
+	.feedback h1,
+	.feedback h2 { margin-top:5px; }
+		.error { background:#F6D273 url(../img/ico-warning.png) no-repeat 10px 10px; border:1px solid #F6B200; }
+		.success { background:#C4DF9B url(../img/ico-check.png) no-repeat 10px 10px; border:1px solid #A5BC83; }
+		.info { background:#DEEDFF url(../img/ico-info.png) no-repeat 10px 10px; border:1px solid #A1CCFF; }
+		.info-light { border-left:2px solid #A1CCFF;  background:#DEEDFf; margin:0 0 18px 40px; padding:10px 14px; }
+		
+	a.btn-close { color:#004A80;display:block;float:right;line-height:12px;padding:0 3px 2px;text-decoration:none; margin:1px 1px 0 0; }
+	a.btn-close:hover { border:1px solid; margin:0; }
+
+
+
+
+.clearfix:after {
+	content: ".";
+	display: block;
+	clear: both;
+	visibility: hidden;
+	line-height: 0;
+	height: 0;
+}
+ 
+.clearfix {
+	display: inline-block;
+}
+ 
+html[xmlns] .clearfix {
+	display: block;
+}
+ 
+* html .clearfix {
+	height: 1%;
+}
\ No newline at end of file
diff --git a/media/css/jquery-ui-themeroller.css b/media/css/jquery-ui-themeroller.css
new file mode 100644
index 0000000..23deb49
--- /dev/null
+++ b/media/css/jquery-ui-themeroller.css
@@ -0,0 +1,406 @@
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+*/
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute; left: -99999999px; }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
+.ui-helper-clearfix { display: inline-block; }
+/* required comment for clearfix to work in Opera \*/
+* html .ui-helper-clearfix { height:1%; }
+.ui-helper-clearfix { display:block; }
+/* end clearfix */
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+
+/*
+* jQuery UI CSS Framework
+* Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=4px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=aaaaaa&fcHeader=222222&iconColorHeader=222222&bgColorContent=ffffff&bgTextureContent=01_flat.png&bgImgOpacityContent=75&borderColorContent=aaaaaa&fcContent=222222&iconColorContent=222222&bgColorDefault=e6e6e6&bgTextureDefault=02_glass.p [...]
+*/
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa; background: #ffffff url(../img/themeroller/ui-bg_flat_75_ffffff_40x100.png) 50% 50% repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #aaaaaa; background: #cccccc url(../img/themeroller/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #222222; font-weight: bold; }
+.ui-widget-header a { color: #222222; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default { border: 1px solid #d3d3d3; background: #e6e6e6 url(../img/themeroller/ui-bg_glass_75_e6e6e6_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #555555; outline: none; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555; text-decoration: none; outline: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus { border: 1px solid #999999; background: #dadada url(../img/themeroller/ui-bg_glass_75_dadada_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; outline: none; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121; text-decoration: none; outline: none; }
+.ui-state-active, .ui-widget-content .ui-state-active { border: 1px solid #aaaaaa; background: #ffffff url(../img/themeroller/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; font-weight: normal; color: #212121; outline: none; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121; outline: none; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight {border: 1px solid #fcefa1; background: #fbf9ee url(../img/themeroller/ui-bg_glass_55_fbf9ee_1x400.png) 50% 50% repeat-x; color: #363636; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a { color: #363636; }
+.ui-state-error, .ui-widget-content .ui-state-error {border: 1px solid #cd0a0a; background: #fef1ec url(../img/themeroller/ui-bg_glass_95_fef1ec_1x400.png) 50% 50% repeat-x; color: #cd0a0a; }
+.ui-state-error a, .ui-widget-content .ui-state-error a { color: #cd0a0a; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text { color: #cd0a0a; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(../img/themeroller/ui-icons_222222_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(../img/themeroller/ui-icons_222222_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(../img/themeroller/ui-icons_222222_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(../img/themeroller/ui-icons_888888_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(../img/themeroller/ui-icons_454545_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(../img/themeroller/ui-icons_454545_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(../img/themeroller/ui-icons_2e83ff_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(../img/themeroller/ui-icons_cd0a0a_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-tl { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; }
+.ui-corner-tr { -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
+.ui-corner-bl { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
+.ui-corner-br { -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-top { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; }
+.ui-corner-bottom { -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-right {  -moz-border-radius-topright: 4px; -webkit-border-top-right-radius: 4px; -moz-border-radius-bottomright: 4px; -webkit-border-bottom-right-radius: 4px; }
+.ui-corner-left { -moz-border-radius-topleft: 4px; -webkit-border-top-left-radius: 4px; -moz-border-radius-bottomleft: 4px; -webkit-border-bottom-left-radius: 4px; }
+.ui-corner-all { -moz-border-radius: 4px; -webkit-border-radius: 4px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(../img/themeroller/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: -8px 0 0 -8px; padding: 8px; background: #aaaaaa url(../img/themeroller/ui-bg_flat_0_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); -moz-border-radius: 8px; -webkit-border-radius: 8px; }/* Accordion
+----------------------------------*/
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; right: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; }
+.ui-accordion .ui-accordion-content-active { display: block; }/* Datepicker
+----------------------------------*/
+.ui-datepicker { width: 17em; padding: .2em .2em 0; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px;  }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month, 
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0;  }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+    display: none; /*sorry for IE5*/
+    display/**/: block; /*sorry for IE5*/
+    position: absolute; /*must have*/
+    z-index: -1; /*must have*/
+    filter: mask(); /*must have*/
+    top: -4px; /*must have*/
+    left: -4px; /*must have*/
+    width: 200px; /*must have*/
+    height: 200px; /*must have*/
+}/* Dialog
+----------------------------------*/
+.ui-dialog { position: relative; padding: .2em; width: 300px; }
+.ui-dialog .ui-dialog-titlebar { padding: .5em .3em .3em 1em; position: relative;  }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 0 .2em; } 
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane button { float: right; margin: .5em .4em .5em 0; cursor: pointer; padding: .2em .6em .3em .6em; line-height: 1.4em; width:auto; overflow:visible; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/* Progressbar
+----------------------------------*/
+.ui-progressbar { height:2em; text-align: left; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }/* Resizable
+----------------------------------*/
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/* Slider
+----------------------------------*/
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/* Tabs
+----------------------------------*/
+.ui-tabs { padding: .2em; zoom: 1; }
+.ui-tabs .ui-tabs-nav { list-style: none; position: relative; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { position: relative; float: left; border-bottom-width: 0 !important; margin: 0 .2em -1px 0; padding: 0; }
+.ui-tabs .ui-tabs-nav li a { float: left; text-decoration: none; padding: .5em 1em; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { padding-bottom: 1px; border-bottom-width: 0; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { padding: 1em 1.4em; display: block; border-width: 0; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
diff --git a/media/css/jquery.autocomplete.css b/media/css/jquery.autocomplete.css
new file mode 100644
index 0000000..ac5d911
--- /dev/null
+++ b/media/css/jquery.autocomplete.css
@@ -0,0 +1,48 @@
+.ac_results {
+	padding: 0px;
+	border: 1px solid black;
+	background-color: white;
+	overflow: hidden;
+	z-index: 99999;
+}
+
+.ac_results ul {
+	width: 100%;
+	list-style-position: outside;
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+.ac_results li {
+	margin: 0px;
+	padding: 2px 5px;
+	cursor: default;
+	display: block;
+	/* 
+	if width will be 100% horizontal scrollbar will apear 
+	when scroll mode will be used
+	*/
+	/*width: 100%;*/
+	font: menu;
+	font-size: 12px;
+	/* 
+	it is very important, if line-height not setted or setted 
+	in relative units scroll will be broken in firefox
+	*/
+	line-height: 16px;
+	overflow: hidden;
+}
+
+.ac_loading {
+	background: white url('../img/indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+	background-color: #eee;
+}
+
+.ac_over {
+	background-color: #0A246A;
+	color: white;
+}
diff --git a/media/css/jquery.hovertip-1.0.css b/media/css/jquery.hovertip-1.0.css
new file mode 100644
index 0000000..f0b9229
--- /dev/null
+++ b/media/css/jquery.hovertip-1.0.css
@@ -0,0 +1,15 @@
+.hovertip {
+    position: absolute;
+    padding: 10px 13px;
+    z-index: 1010;
+    max-width: 300px;
+
+    color: #303030;
+    background-color: #f5f5b5;
+    border: 1px solid #DECA7E;
+
+    font-family: sans-serif;
+    font-size: 12px;
+    line-height: 18px;
+    text-align: left;
+}
diff --git a/media/css/jquery.jqplot.min.css b/media/css/jquery.jqplot.min.css
new file mode 100644
index 0000000..de15fff
--- /dev/null
+++ b/media/css/jquery.jqplot.min.css
@@ -0,0 +1 @@
+.jqplot-target{position:relative;color:#666;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:1em;}.jqplot-axis{font-size:.75em;}.jqplot-xaxis{margin-top:10px;}.jqplot-x2axis{margin-bottom:10px;}.jqplot-yaxis{margin-right:10px;}.jqplot-y2axis,.jqplot-y3axis,.jqplot-y4axis,.jqplot-y5axis,.jqplot-y6axis,.jqplot-y7axis,.jqplot-y8axis,.jqplot-y9axis,.jqplot-yMidAxis{margin-left:10px;margin-right:10px;}.jqplot-axis-tick,.jqplot-xaxis-tick,.jqplot-yaxis-tick,.jqplot-x2axis-tick,. [...]
\ No newline at end of file
diff --git a/media/css/jquery.treeview.css b/media/css/jquery.treeview.css
new file mode 100644
index 0000000..c6b752e
--- /dev/null
+++ b/media/css/jquery.treeview.css
@@ -0,0 +1,70 @@
+.treeview, .treeview ul { 
+	padding: 0;
+	margin: 0;
+	list-style: none;
+}
+
+.treeview ul {
+	background-color: white;
+	margin-top: 4px;
+}
+
+.treeview .hitarea {
+	background: url(../img/treeview-default.gif) -64px -25px no-repeat;
+	height: 16px;
+	width: 16px;
+	margin-left: -16px;
+	float: left;
+	cursor: pointer;
+}
+/* fix for IE6 */
+* html .hitarea {
+	display: inline;
+	float:none;
+}
+
+.treeview li { 
+	margin: 0;
+	padding: 3px 0pt 3px 16px;
+}
+
+.treeview a.selected {
+	background-color: #eee;
+}
+
+#treecontrol { margin: 1em 0; display: none; }
+
+.treeview .hover { color: red; cursor: pointer; }
+
+.treeview li { background: url(../img/treeview-default-line.gif) 0 0 no-repeat; padding:5px 0 5px 16px;}
+.treeview li li:hover { background-color:#f3f3f3;}
+
+.treeview li.collapsable, .treeview li.expandable { background-position: 0 -176px; }
+
+.treeview .expandable-hitarea { background-position: -80px -3px; }
+
+.treeview li.last { background-position: 0 -1766px }
+.treeview li.lastCollapsable, .treeview li.lastExpandable { background-image: url(../img/treeview-default.gif); }  
+.treeview li.lastCollapsable { background-position: 0 -111px }
+.treeview li.lastExpandable { background-position: -32px -67px }
+
+.treeview div.lastCollapsable-hitarea, .treeview div.lastExpandable-hitarea { background-position: 0; }
+
+.treeview-red li { background-image: url(../img/treeview-red-line.gif); }
+.treeview-red .hitarea, .treeview-red li.lastCollapsable, .treeview-red li.lastExpandable { background-image: url(../img/treeview-red.gif); } 
+
+.treeview-black li { background-image: url(../img/treeview-black-line.gif); }
+.treeview-black .hitarea, .treeview-black li.lastCollapsable, .treeview-black li.lastExpandable { background-image: url(../img/treeview-black.gif); }  
+
+.treeview-gray li { background-image: url(../img/treeview-gray-line.gif); }
+.treeview-gray .hitarea, .treeview-gray li.lastCollapsable, .treeview-gray li.lastExpandable { background-image: url(../img/treeview-gray.gif); } 
+
+.treeview-famfamfam li { background-image: url(../img/treeview-famfamfam-line.gif); }
+.treeview-famfamfam .hitarea, .treeview-famfamfam li.lastCollapsable, .treeview-famfamfam li.lastExpandable { background-image: url(../img/treeview-famfamfam.gif); } 
+
+
+.filetree li { padding: 3px 0 2px 16px; }
+.filetree span.folder, .filetree span.file { padding: 1px 0 1px 16px; display: block; }
+.filetree span.folder { background: url(../img/folder.gif) 0 0 no-repeat; }
+.filetree li.expandable span.folder { background: url(../img/folder-closed.gif) 0 0 no-repeat; }
+.filetree span.file { background: url(../img/file.gif) 0 0 no-repeat; }
diff --git a/media/css/login.css b/media/css/login.css
new file mode 100644
index 0000000..3e181ba
--- /dev/null
+++ b/media/css/login.css
@@ -0,0 +1,283 @@
+/*****************************
+ USHAHIDI STYLES
+ v1.0
+ David Kobia - 07/08/2008
+*****************************/
+
+* { margin: 0; padding: 0; }
+body {
+	background-image:url(../img/bkg_login.gif);
+}
+body, td {
+	font: 13px "Lucida Grande", "Lucida Sans Unicode", Tahoma, Verdana, sans-serif;
+}
+
+
+h1 { margin-bottom:18px; }
+h2 { margin-bottom:15px; }
+
+h3 { margin-bottom:8px; } 
+	h3 a { font-size:10px; font-weight:normal; }
+a:link,
+a:visited {
+	color:#003399;
+}
+a:visited {
+	color:#0066CC;
+}
+a:hover,
+a:focus,
+a:active {
+	text-decoration:none;
+	color:#003399;
+}
+
+
+
+p, ul, ol { margin-bottom:18px; }
+
+ul, ol { margin-left:18px; }
+ul li, ol li { padding:0 0 5px;  }
+.last { margin-bottom:0; }
+
+ul, ol { list-style-position:inside; }
+
+dl {}
+	dt {}
+	dd {}
+
+/* LOGIN BOX */
+/*****************************/
+#ushahidi_login_container {
+	width:400px;
+	margin:50px auto;
+}
+
+#ushahidi_site_name {
+	border:3px solid #999;
+	text-align:center;
+	padding:20px;
+	background-color: #ddd;
+	margin-bottom:20px;
+}
+
+#ushahidi_site_name h1 {
+	padding:0px;
+	margin:0px;
+}
+
+#ushahidi_site_name span {
+	font-style:italic;
+}
+
+#ushahidi_login {
+	border:3px solid #999;
+	text-align:left;
+	padding: 20px;
+	background-color: #ddd;
+}
+#ushahidi_loginbox td{
+	padding:5px;
+	vertical-align:top;
+}
+#password_reset_change_form {
+	border:3px solid #999;
+	text-align:left;
+	padding: 20px;
+	background-color: #ddd;
+	float:left;
+	width:704px;
+	margin-bottom:20px;
+}
+#openid_login_container{
+	margin:50px auto;
+	/*width:626px;*/
+	width:750px;
+}
+#openid_login {
+	border:3px solid #999;
+	padding: 20px;
+	background-color: #ddd;
+	float:left;
+	width:340px;
+	text-align:center;
+}
+#create_account {
+	border:3px solid #999;
+	text-align:left;
+	padding: 20px;
+	background-color: #ddd;
+	float:right;
+	width:300px;
+}
+.login_text{
+	padding:3px 7px 3px 7px;
+	border:1px #ccc solid;
+	width:180px;
+}
+
+/* Installer Container	 */
+/*****************************/
+#ushahidi_install_container { 
+    width:800px;
+    margin:50px auto;
+}
+
+/* Content Area */
+/*****************************/
+.two-col { padding:20px 30px; width:306px; float:left;  }
+.two-col-box { 
+	-moz-background-clip:border;
+	-moz-background-inline-policy:continuous;
+	-moz-background-origin:padding;
+	/*-moz-border-radius-bottomleft:4px;
+	-moz-border-radius-bottomright:4px;
+	-moz-border-radius-topleft:4px;
+	-moz-border-radius-topright:4px;*/
+	-moz-box-shadow:0 1px 3px #999999;
+	border-top:1px solid #FFFFFF;
+	background:#F5F5F5;
+	padding:20px 30px;
+	width:306px;
+	float:left;
+}
+
+	a.btn-box { display:block; font-size:inherit; cursor:pointer; text-decoration:none; }
+		a.btn-box span { display:block; text-decoration:none; }
+		a.btn-box:hover span { text-decoration:none; }
+		span.btn-box-title 		{ margin-bottom:0px; font-size:20px; font-weight:bold; color:#000; }
+		span.btn-box-content	{ margin-bottom:18px; color:#000; }
+		span.btn-action			{ color:#000; }
+	
+	a.btn-box:hover { background:#e0f2ff; }
+		a.btn-box:hover	span.btn-action	 { text-decoration:underline; }
+		
+	a.btn-box:active { outline:none; }
+
+	.tc-left { margin-right:20px; }
+	.tc-right { }
+
+
+
+	/* BUTTONS*/
+	/*****************************/
+	a.button,
+	input.button {
+		/*-moz-border-radius-bottomleft:10px;
+		-moz-border-radius-bottomright:10px;
+		-moz-border-radius-topleft:10px;
+		-moz-border-radius-topright:10px;*/
+		background:#F5F5F5;
+		color:#464646;
+		border:1px solid #F5F5F5;
+		cursor:pointer;
+		font-size:14px !important;
+		line-height:16px;
+		padding:6px 12px;
+		text-decoration:none;
+	}
+	a.button:hover,
+	input.button:hover { color:#000; border:1px solid #a1a1a1; background:#e0f2ff; }
+
+	/* Progress Meter*/
+	/*****************************/
+	ol.progress-meter { padding:16px 0 19px 15px; margin-left:0; background:transparent url(../img/install_bg-progress-bar.gif) top repeat-x; list-style-position:inside; }
+	ol.progress-meter li { margin:0 28px 0 0px; padding:3px 0 5px 7px; float:left; color:#777; font-size:14px; background:transparent url(../img/install_bg-progress-item-number.gif) 0px 0px no-repeat; }
+	ol.progress-meter li.active { color:#000;  }
+	ol.progress-meter li.active span { text-decoration:underline; font-weight:bold; padding:0 0 35px; background:transparent url(../img/install_bg-progress-active.gif) bottom center no-repeat; }
+	ol.progress-meter li:hover {}
+	ol.progress-meter li.last { margin-right:0px; }
+	ol.progress-meter li span {}
+	
+	
+	
+/*****************************/
+
+
+
+/* FORM */
+/*****************************/
+
+.form-table { border-collapse: collapse; margin: 1em 0; width: 100%; }
+
+.form-table td { margin-bottom: 9px; padding: 10px; border-bottom: 4px solid #eee; font-size: 12px; }
+	.form-table td.next { text-align:left; border:none; }
+	.form-table td.prev { text-align:right; border:none; }
+.fields th { font-size: 13px; text-align: left; line-height:18px; padding: 16px 10px 10px 10px; border-bottom: 4px solid #eee; width: 140px; vertical-align: middle;}
+
+.form-table tr { background: #DDDDDD; }
+	.fields tr:hover { background:#EFEFEF; }
+	.fields tr.hover { background:#e0f2ff; }
+
+.form-table code { line-height: 18px; font-size: 18px; }
+
+.form-table p {	margin: 4px 0 0 0;font-size: 11px; }
+
+.form-table input { line-height: 20px; font-size: 15px; padding: 2px; }
+
+
+	/* System Messages */
+	/*****************************/
+	.feedback { padding:10px 10px 10px 48px; color:#4D4D4D; margin-bottom:10px; }
+	
+	.feedback p, 
+	.feedback ul { margin-bottom:10px }
+	
+	.feedback h1,
+	.feedback h2 { margin-top:5px; }
+		.error { background:#F6D273 url(../img/ico-warning.png) no-repeat 10px 10px; border:1px solid #F6B200; }
+		.success { background:#C4DF9B url(../img/ico-check.png) no-repeat 10px 10px; border:1px solid #A5BC83; }
+		.info { background:#DEEDFF url(../img/ico-info.png) no-repeat 10px 10px; border:1px solid #A1CCFF; }
+		.info-light { border-left:2px solid #A1CCFF;  background:#DEEDFf; margin:0 0 18px 40px; padding:10px 14px; }
+		
+	a.btn-close { color:#004A80;display:block;float:right;line-height:12px;padding:0 3px 2px;text-decoration:none; margin:1px 1px 0 0; }
+	a.btn-close:hover { border:1px solid; margin:0; }
+
+/* Error */
+/*****************************/
+.login_error,
+.login_success{
+	margin-bottom:15px;
+	padding:10px;
+	background-color:#eee;
+	color:#990000;
+	font-weight:bold;
+	border:3px solid #999;
+}
+.login_success{
+	color:#336600;
+}
+
+.clearfix:after {
+	content: ".";
+	display: block;
+	clear: both;
+	visibility: hidden;
+	line-height: 0;
+	height: 0;
+}
+ 
+.clearfix {
+	display: inline-block;
+}
+ 
+html[xmlns] .clearfix {
+	display: block;
+}
+ 
+* html .clearfix {
+	height: 1%;
+}
+
+.signin_select{
+	display:none;
+	margin-bottom:15px;
+	padding:10px;
+	background-color:#eee;
+}
+
+#resend_confirm_email, #signin_forgot, #signin_new { margin-top:10px; }
+
+.hidden{
+	display:none;
+}
\ No newline at end of file
diff --git a/media/css/openid.css b/media/css/openid.css
new file mode 100644
index 0000000..42f7a32
--- /dev/null
+++ b/media/css/openid.css
@@ -0,0 +1,87 @@
+/*
+	Simple OpenID Plugin
+	http://code.google.com/p/openid-selector/
+	
+	This code is licenced under the New BSD License.
+*/
+
+#openid_form {
+	width: 580px;
+}
+
+#openid_form legend {
+	font-weight: bold;
+}
+
+#openid_choice {
+	display: none;
+}
+
+#openid_input_area {
+	clear: both;
+	padding: 10px;
+}
+
+#openid_btns, #openid_btns br {
+	clear: both;
+}
+
+#openid_highlight {
+	padding: 3px;
+	background-color: #FFFCC9;
+	float: left;
+}
+
+.openid_large_btn {
+	width: 100px;
+	height: 60px;
+/* fix for IE 6 only: http://en.wikipedia.org/wiki/CSS_filter#Underscore_hack */
+	_width: 102px;
+	_height: 62px;
+
+	border: 1px solid #DDD;
+	margin: 3px;
+	float: left;
+}
+
+.openid_small_btn {
+	width: 24px;
+	height: 24px;
+/* fix for IE 6 only: http://en.wikipedia.org/wiki/CSS_filter#Underscore_hack */
+	_width: 26px;
+	_height: 26px;
+
+	border: 1px solid #DDD;
+	margin: 3px;
+	float: left;
+}
+
+a.openid_large_btn:focus {
+	outline: none;
+}
+
+a.openid_large_btn:focus {
+	-moz-outline-style: none;
+}
+
+.openid_selected {
+	border: 4px solid #DDD;
+}
+#openid_form fieldset{
+	border-width:0;
+}
+	#openid_form fieldset legend{
+		padding:5px 5px 5px 0;
+		font-size:110%;
+	}
+	
+#openid_signup{
+	margin-top:20px;
+}
+#openid_error{
+	margin-bottom:15px;
+	padding:10px;
+	background-color:#fff;
+	text-align:center;
+	font-weight:bold;
+}
\ No newline at end of file
diff --git a/media/css/openlayers.css b/media/css/openlayers.css
new file mode 100644
index 0000000..0f87a33
--- /dev/null
+++ b/media/css/openlayers.css
@@ -0,0 +1,585 @@
+.infowindow{
+	margin:20px 0 0 0;
+	position:relative;
+	padding:0;
+}
+.infowindow .infowindow_content{
+	width:250px;
+	float:left;
+}
+.infowindow h2{
+	margin:0 0 8px 0;
+	padding:0;
+	font-weight:bold;
+	font-size:14px;
+}
+.infowindow .infowindow_list{
+	margin-bottom:10px;
+	font-size:14px;
+	font-weight:bold;
+}
+.infowindow .infowindow_image{
+	float:left;
+	margin-right:10px;
+}
+	.infowindow .infowindow_image img{
+		border:1px solid #3764aa;
+	}
+.infowindow ul{
+	font-weight:bold;
+	margin:0 0 0 1em;
+	padding:0;
+}
+.infowindow li{
+	list-style-type: square;
+}
+.infowindow a, .infowindow a:visited{
+	color:#3764aa;
+}
+.infowindow a:hover{
+	text-decoration:none;
+}
+.infowindow a:hover{
+	text-decoration:underline;
+}
+.infowindow .infowindow_meta{
+	font-size:90%;
+	clear:both;
+}
+.infowindow_meta a, .infowindow_meta a:visited{
+	color:#000;
+}
+
+/* Openlayers style.css */
+div.olMap {
+    z-index: 0;
+    padding: 0 !important;
+    margin: 0 !important;
+    cursor: default;
+}
+
+div.olMapViewport {
+    text-align: left;
+}
+
+div.olLayerDiv {
+   -moz-user-select: none;
+   -khtml-user-select: none;
+}
+
+.olLayerGoogleCopyright {
+    left: 2px;
+    bottom: 2px;
+}
+.olLayerGoogleV3.olLayerGoogleCopyright {
+    right: auto !important;
+}
+.olLayerGooglePoweredBy {
+    left: 2px;
+    bottom: 15px;
+}
+.olLayerGoogleV3.olLayerGooglePoweredBy {
+    bottom: 15px !important;
+}
+.olControlAttribution {
+    font-size: smaller;
+    right: 3px;
+    bottom: 4.5em;
+    position: absolute;
+    display: block;
+}
+.olControlScale {
+    right: 3px;
+    bottom: 3em;
+    display: block;
+    position: absolute;
+    font-size: smaller;
+}
+.olControlScaleLine {
+   display: block;
+   position: absolute;
+   left: 10px;
+   bottom: 15px;
+   font-size: xx-small;
+}
+.olControlScaleLineBottom {
+   border: solid 2px black;
+   border-bottom: none;
+   margin-top:-2px;
+   text-align: center;
+}
+.olControlScaleLineTop {
+   border: solid 2px black;
+   border-top: none;
+   text-align: center;
+}
+
+.olControlPermalink {
+    right: 3px;
+    bottom: 1.5em;
+    display: block;
+    position: absolute;
+    font-size: smaller;
+}
+
+div.olControlMousePosition {
+    bottom: 0;
+    right: 3px;
+    display: block;
+    position: absolute;
+    font-family: Arial;
+    font-size: smaller;
+}
+
+.olControlOverviewMapContainer {
+    position: absolute;
+    bottom: 0;
+    right: 0;
+}
+
+.olControlOverviewMapElement {
+    padding: 10px 18px 10px 10px;
+    background-color: #00008B;
+    -moz-border-radius: 1em 0 0 0;
+}
+
+.olControlOverviewMapMinimizeButton,
+.olControlOverviewMapMaximizeButton {
+    height: 18px;
+    width: 18px;
+    right: 0;
+    bottom: 80px;
+    cursor: pointer;
+}
+
+.olControlOverviewMapExtentRectangle {
+    overflow: hidden;
+    background-image: url("../img/openlayers/blank.gif");
+    cursor: move;
+    border: 2px dotted red;
+}
+.olControlOverviewMapRectReplacement {
+    overflow: hidden;
+    cursor: move;
+    background-image: url("../img/openlayers/overview_replacement.gif");
+    background-repeat: no-repeat;
+    background-position: center;
+}
+
+.olLayerGeoRSSDescription {
+    float:left;
+    width:100%;
+    overflow:auto;
+    font-size:1.0em;
+}
+.olLayerGeoRSSClose {
+    float:right;
+    color:gray;
+    font-size:1.2em;
+    margin-right:6px;
+    font-family:sans-serif;
+}
+.olLayerGeoRSSTitle {
+    float:left;font-size:1.2em;
+}
+
+.olPopupContent {
+    padding:5px;
+    overflow: auto;
+}
+
+.olControlNavigationHistory {
+   background-image: url("../img/openlayers/navigation_history.png");
+   background-repeat: no-repeat;
+   width:  24px;
+   height: 24px;
+
+}
+.olControlNavigationHistoryPreviousItemActive {
+  background-position: 0 0;
+}
+.olControlNavigationHistoryPreviousItemInactive {
+   background-position: 0 -24px;
+}
+.olControlNavigationHistoryNextItemActive {
+   background-position: -24px 0;
+}
+.olControlNavigationHistoryNextItemInactive {
+   background-position: -24px -24px;
+}
+
+div.olControlSaveFeaturesItemActive {
+    background-image: url(../img/openlayers/save_features_on.png);
+    background-repeat: no-repeat;
+    background-position: 0 1px;
+}
+div.olControlSaveFeaturesItemInactive {
+    background-image: url(../img/openlayers/save_features_off.png);
+    background-repeat: no-repeat;
+    background-position: 0 1px;
+}
+
+.olHandlerBoxZoomBox {
+    border: 2px solid red;
+    position: absolute;
+    background-color: white;
+    opacity: 0.50;
+    font-size: 1px;
+    filter: alpha(opacity=50);
+}
+.olHandlerBoxSelectFeature {
+    border: 2px solid blue;
+    position: absolute;
+    background-color: white;
+    opacity: 0.50;
+    font-size: 1px;
+    filter: alpha(opacity=50);
+}
+
+.olControlPanPanel {
+    top: 10px;
+    left: 5px;
+}
+
+.olControlPanPanel div {
+    background-image: url(../img/openlayers/pan-panel.png);
+    height: 18px;
+    width: 18px;
+    cursor: pointer;
+    position: absolute;
+}
+
+.olControlPanPanel .olControlPanNorthItemInactive {
+    top: 0;
+    left: 9px;
+    background-position: 0 0;
+}
+.olControlPanPanel .olControlPanSouthItemInactive {
+    top: 36px;
+    left: 9px;
+    background-position: 18px 0;
+}
+.olControlPanPanel .olControlPanWestItemInactive {
+    position: absolute;
+    top: 18px;
+    left: 0;
+    background-position: 0 18px;
+}
+.olControlPanPanel .olControlPanEastItemInactive {
+    top: 18px;
+    left: 18px;
+    background-position: 18px 18px;
+}
+
+.olControlZoomPanel {
+    top: 71px;
+    left: 14px;
+}
+
+.olControlZoomPanel div {
+    background-image: url(../img/openlayers/zoom-panel.png);
+    position: absolute;
+    height: 18px;
+    width: 18px;
+    cursor: pointer;
+}
+
+.olControlZoomPanel .olControlZoomInItemInactive {
+    top: 0;
+    left: 0;
+    background-position: 0 0;
+}
+
+.olControlZoomPanel .olControlZoomToMaxExtentItemInactive {
+    top: 18px;
+    left: 0;
+    background-position: 0 -18px;
+}
+
+.olControlZoomPanel .olControlZoomOutItemInactive {
+    top: 36px;
+    left: 0;
+    background-position: 0 18px;
+}
+
+/*
+ * When a potential text is bigger than the image it move the image
+ * with some headers (closes #3154)
+ */
+.olControlPanZoomBar div {
+    font-size: 1px;
+}
+
+.olPopupCloseBox {
+  background: url("../img/openlayers/close.gif") no-repeat;
+  cursor: pointer;
+}
+
+.olFramedCloudPopupContent {
+    padding: 5px;
+    overflow: auto;
+}
+
+.olControlNoSelect {
+ -moz-user-select: none;
+ -khtml-user-select: none;
+}
+
+.olImageLoadError {
+    background-color: pink;
+    opacity: 0.5;
+    filter: alpha(opacity=50); /* IE */
+}
+
+/**
+ * Cursor styles
+ */
+
+.olCursorWait {
+    cursor: wait;
+}
+.olDragDown {
+    cursor: move;
+}
+.olDrawBox {
+    cursor: crosshair;
+}
+.olControlDragFeatureOver {
+    cursor: move;
+}
+.olControlDragFeatureActive.olControlDragFeatureOver.olDragDown {
+    cursor: -moz-grabbing;
+}
+
+/**
+ * Layer switcher
+ */
+.olControlLayerSwitcher {
+    position: absolute;
+    top: 25px;
+    right: 0;
+    width: 20em;
+    font-family: sans-serif;
+    font-weight: bold;
+    margin-top: 3px;
+    margin-left: 3px;
+    margin-bottom: 3px;
+    font-size: smaller;
+    color: white;
+    background-color: transparent;
+}
+
+.olControlLayerSwitcher .layersDiv {
+    padding-top: 5px;
+    padding-left: 10px;
+    padding-bottom: 5px;
+    padding-right: 10px;
+    background-color: darkblue;
+}
+
+.olControlLayerSwitcher .layersDiv .baseLbl,
+.olControlLayerSwitcher .layersDiv .dataLbl {
+    margin-top: 3px;
+    margin-left: 3px;
+    margin-bottom: 3px;
+}
+
+.olControlLayerSwitcher .layersDiv .baseLayersDiv,
+.olControlLayerSwitcher .layersDiv .dataLayersDiv {
+    padding-left: 10px;
+}
+
+.olControlLayerSwitcher .maximizeDiv,
+.olControlLayerSwitcher .minimizeDiv {
+    width: 18px;
+    height: 18px;
+    top: 5px;
+    right: 0;
+    cursor: pointer;
+}
+
+.olBingAttribution {
+    color: #DDD;
+}
+.olBingAttribution.road {
+    color: #333;
+}
+
+.olGoogleAttribution.hybrid, .olGoogleAttribution.satellite {
+    color: #EEE;
+}
+.olGoogleAttribution {
+    color: #333;
+}
+span.olGoogleAttribution a {
+    color: #77C;
+}
+span.olGoogleAttribution.hybrid a, span.olGoogleAttribution.satellite a {
+    color: #EEE;
+}
+
+/**
+ * Editing and navigation icons.
+ * (using the editing_tool_bar.png sprint image)
+ */
+.olControlNavToolbar ,
+.olControlEditingToolbar {
+    margin: 5px 5px 0 0;
+}
+.olControlNavToolbar div,
+.olControlEditingToolbar div {
+    background-image: url("../img/openlayers/editing_tool_bar.png");
+    background-repeat: no-repeat;
+    margin: 0 0 5px 5px;
+    width: 24px;
+    height: 22px;
+    cursor: pointer
+}
+/* positions */
+.olControlEditingToolbar {
+    right: 0;
+    top: 0;
+}
+.olControlNavToolbar {
+    top: 295px;
+    left: 9px;
+}
+/* layouts */
+.olControlEditingToolbar div {
+    float: right;
+}
+/* individual controls */
+.olControlNavToolbar .olControlNavigationItemInactive,
+.olControlEditingToolbar .olControlNavigationItemInactive {
+    background-position: -103px -1px;
+}
+.olControlNavToolbar .olControlNavigationItemActive ,
+.olControlEditingToolbar .olControlNavigationItemActive  {
+    background-position: -103px -24px;
+}
+.olControlNavToolbar .olControlZoomBoxItemInactive {
+    background-position: -128px -1px;
+}
+.olControlNavToolbar .olControlZoomBoxItemActive  {
+    background-position: -128px -24px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePointItemInactive {
+    background-position: -77px -1px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePointItemActive {
+    background-position: -77px -24px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePathItemInactive {
+    background-position: -51px -1px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePathItemActive {
+    background-position: -51px -24px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive{
+    background-position: -26px -1px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePolygonItemActive {
+    background-position: -26px -24px;
+}
+
+div.olControlZoom {
+    position: absolute;
+    top: 8px;
+    left: 8px;
+    background: rgba(255,255,255,0.4);
+    border-radius: 4px;
+    padding: 2px;
+}
+div.olControlZoom a {
+    display: block;
+    margin: 1px;
+    padding: 0;
+    color: white;
+    font-size: 18px;
+    font-family: 'Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif;
+    font-weight: bold;
+    text-decoration: none;
+    text-align: center;
+    height: 22px;
+    width:22px;
+    line-height: 19px;
+    background: #130085; /* fallback for IE - IE6 requires background shorthand*/
+    background: rgba(0, 60, 136, 0.5);
+    filter: alpha(opacity=80);
+}
+div.olControlZoom a:hover {
+    background: #130085; /* fallback for IE */
+    background: rgba(0, 60, 136, 0.7);
+    filter: alpha(opacity=100);
+}
+ at media only screen and (max-width: 600px) {
+    div.olControlZoom a:hover {
+        background: rgba(0, 60, 136, 0.5);
+    }
+}
+a.olControlZoomIn {
+    border-radius: 4px 4px 0 0;
+}
+a.olControlZoomOut {
+    border-radius: 0 0 4px 4px;
+}
+
+
+/**
+ * Animations
+ */
+
+.olLayerGrid .olTileImage {
+    -webkit-transition: opacity 0.2s linear;
+    -moz-transition: opacity 0.2s linear;
+    -o-transition: opacity 0.2s linear;
+    transition: opacity 0.2s linear;
+}
+
+/* Openlayers google.css */
+.olLayerGoogleCopyright {
+    right: 3px;
+    bottom: 2px;
+    left: auto;  
+}
+.olLayerGoogleV3.olLayerGoogleCopyright {
+    bottom: 0px;
+    right: 0px !important;
+}
+.olLayerGooglePoweredBy {
+    left: 2px;
+    bottom: 2px;   
+}
+.olLayerGoogleV3.olLayerGooglePoweredBy {
+    bottom: 0px !important;
+}
+
+/* Ushahidi specific overrides */
+div.olControlMousePosition {
+    top: 2px;
+}
+.olFramedCloudPopupContent {
+    padding: 3px;
+}
+.olPopupCloseBox {
+  margin-right:-5px;
+  margin-top:-7px;
+}
+.olControlLayerSwitcher .layersDiv {
+    -webkit-border-radius: 8px 0 0 8px;
+    -moz-border-radius: 8px 0 0 8px;
+    -o-border-radius: 8px 0 0 8px;
+    border-radius: 8px 0 0 8px;
+}
+.olControlEditingToolbar  {
+    float:left;
+}
+.olControlAttribution {
+	bottom: 0.5em;
+}
+.olVectorLayerDiv {
+    -webkit-transition: opacity 0.3s linear;
+    -moz-transition: opacity 0.3s linear;
+    -o-transition: opacity 0.3s linear;
+    transition: opacity 0.3s linear;
+}
diff --git a/media/css/picbox/closebutton.png b/media/css/picbox/closebutton.png
new file mode 100644
index 0000000..6b47ead
Binary files /dev/null and b/media/css/picbox/closebutton.png differ
diff --git a/media/css/picbox/loading.gif b/media/css/picbox/loading.gif
new file mode 100644
index 0000000..35218b3
Binary files /dev/null and b/media/css/picbox/loading.gif differ
diff --git a/media/css/picbox/navbtns.png b/media/css/picbox/navbtns.png
new file mode 100644
index 0000000..9dbe25a
Binary files /dev/null and b/media/css/picbox/navbtns.png differ
diff --git a/media/css/picbox/picbox.css b/media/css/picbox/picbox.css
new file mode 100644
index 0000000..63055aa
--- /dev/null
+++ b/media/css/picbox/picbox.css
@@ -0,0 +1,108 @@
+/* PICBOX */
+#pbOverlay, #pbImage, #pbBottom, #pbCloseBtn {
+	position: fixed;
+	z-index: 9999;
+}
+
+#pbOverlay {
+	left: 0;
+	top: 0;
+	width: 100%;
+	height: 100%;
+    background: #000;
+	cursor: pointer;
+}
+
+#pbCloseBtn {
+	top: 0;
+	right: 0;
+	display: block;
+	width: 50px;
+	height: 50px;
+	background: url(closebutton.png) no-repeat top left;
+}
+
+.pbLoading {
+	background: #000 url(loading.gif) no-repeat center !important;
+}
+
+#pbImage {
+	border: none;
+	background: #000 no-repeat;
+	-moz-box-shadow: 0 0 40px #000;
+	-webkit-box-shadow: 0 0 40px #000;
+	box-shadow: 0 0 40px #000;
+	cursor: move;
+}
+
+#pbBottom {
+	font-family: Verdana, Arial, Geneva, Helvetica, sans-serif;
+	font-size: 10px;
+	text-align: center;
+	color: #EEE;
+	line-height: 1.4em;
+	left: 50%;
+	bottom: 10px;
+	width: 400px;
+	margin-left: -200px; /* half the width */ 
+}
+
+#pbCaption {
+	font-weight: bold;
+	padding: 4px;
+	display: inline-block;
+	*display: inline; /* IE 7 */
+	background-color: rgba(0,0,0,0.7);
+	filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=0, StartColorStr=#B0000000, EndColorStr=#B0000000);
+	zoom: 1;
+}
+
+#pbNav {
+	display: block;
+	width: 200px;
+	margin: auto;
+	margin-top: 5px;
+}
+
+#pbPrevBtn, #pbNextBtn, #pbZoomBtn  {
+	display: inline-block;
+	width: 70px;
+	height: 65px;
+	outline: none;
+	text-indent: -9000px;
+	background: url(navbtns.png) no-repeat;
+}
+
+#pbPrevBtn {
+	background-position: -70px 0;
+	float: left;
+}
+
+#pbPrevBtn.pbgreyed {
+	background-position: 0 0;
+}
+
+#pbNextBtn {
+	background-position: -320px 0;
+	float: right;
+}
+
+#pbNextBtn.pbgreyed {
+	background-position: -390px;
+}
+
+#pbZoomBtn {
+	width: 60px;
+	background-position: -140px 0;
+	float: left;
+}
+
+#pbZoomBtn.pbgreyed { 
+	background-position: -200px 0;
+}
+
+#pbZoomBtn.pbzoomed {
+	background-position: -260px 0;
+}
+
+/* END PICBOX */
\ No newline at end of file
diff --git a/media/css/readme.css b/media/css/readme.css
new file mode 100644
index 0000000..9a634f0
--- /dev/null
+++ b/media/css/readme.css
@@ -0,0 +1,40 @@
+body {
+        font-family: "Lucida Grande", Verdana, Arial, "Bitstream Vera Sans", sans-serif;
+	margin: 2em auto 0 auto;
+	width: 700px;
+	padding: 1em 2em;
+	-moz-border-radius: 11px;
+	-khtml-border-radius: 11px;
+	-webkit-border-radius: 11px;
+	border-radius: 11px;
+	border: 1px solid #dfdfdf;
+    }
+    h1 {
+	clear: both;
+	font: 24px Georgia, "Times New Roman", Times, serif;
+	margin: 5px 0 0 -4px;
+	padding: 0;
+	padding-bottom: 7px;
+}
+
+h2 { font-size: 16px; padding-top:7px; }
+ p, li {
+	padding-bottom: 2px;
+	font-size: 12px;
+	line-height: 18px;
+}
+code { font-size: 11px; font-weight:bold; }
+
+ul, ol { padding: 5px 5px 5px 22px; margin-bottom:10px; }
+
+#ushahidi_readme {
+	border:3px solid #cb0000;
+	text-align:left;
+	padding: 20px;
+	background-color: #ddd;
+}
+
+#ushahidi_readme_container {
+	width: 600px;
+	margin:10px auto;
+}
\ No newline at end of file
diff --git a/media/img/admin/alerts-icon.png b/media/img/admin/alerts-icon.png
new file mode 100755
index 0000000..6dbcc2c
Binary files /dev/null and b/media/img/admin/alerts-icon.png differ
diff --git a/media/img/admin/body-bg.gif b/media/img/admin/body-bg.gif
new file mode 100644
index 0000000..b7fd680
Binary files /dev/null and b/media/img/admin/body-bg.gif differ
diff --git a/media/img/admin/border.gif b/media/img/admin/border.gif
new file mode 100644
index 0000000..3e7c056
Binary files /dev/null and b/media/img/admin/border.gif differ
diff --git a/media/img/admin/btn-cancel.gif b/media/img/admin/btn-cancel.gif
new file mode 100644
index 0000000..8dc564d
Binary files /dev/null and b/media/img/admin/btn-cancel.gif differ
diff --git a/media/img/admin/btn-download.gif b/media/img/admin/btn-download.gif
new file mode 100644
index 0000000..cea205d
Binary files /dev/null and b/media/img/admin/btn-download.gif differ
diff --git a/media/img/admin/btn-get-api-key.gif b/media/img/admin/btn-get-api-key.gif
new file mode 100644
index 0000000..04881fd
Binary files /dev/null and b/media/img/admin/btn-get-api-key.gif differ
diff --git a/media/img/admin/btn-save-and-close.gif b/media/img/admin/btn-save-and-close.gif
new file mode 100644
index 0000000..7f16d21
Binary files /dev/null and b/media/img/admin/btn-save-and-close.gif differ
diff --git a/media/img/admin/btn-save-report.gif b/media/img/admin/btn-save-report.gif
new file mode 100644
index 0000000..1fdacff
Binary files /dev/null and b/media/img/admin/btn-save-report.gif differ
diff --git a/media/img/admin/btn-save-settings.gif b/media/img/admin/btn-save-settings.gif
new file mode 100644
index 0000000..a833256
Binary files /dev/null and b/media/img/admin/btn-save-settings.gif differ
diff --git a/media/img/admin/btn-save.gif b/media/img/admin/btn-save.gif
new file mode 100644
index 0000000..e54168c
Binary files /dev/null and b/media/img/admin/btn-save.gif differ
diff --git a/media/img/admin/btn-send.gif b/media/img/admin/btn-send.gif
new file mode 100644
index 0000000..25d3ff9
Binary files /dev/null and b/media/img/admin/btn-send.gif differ
diff --git a/media/img/admin/category-icon.gif b/media/img/admin/category-icon.gif
new file mode 100644
index 0000000..2622581
Binary files /dev/null and b/media/img/admin/category-icon.gif differ
diff --git a/media/img/admin/checkins-icon.png b/media/img/admin/checkins-icon.png
new file mode 100755
index 0000000..a2deff9
Binary files /dev/null and b/media/img/admin/checkins-icon.png differ
diff --git a/media/img/admin/content-bg.gif b/media/img/admin/content-bg.gif
new file mode 100644
index 0000000..c1b7454
Binary files /dev/null and b/media/img/admin/content-bg.gif differ
diff --git a/media/img/admin/dots.gif b/media/img/admin/dots.gif
new file mode 100644
index 0000000..d2e4d97
Binary files /dev/null and b/media/img/admin/dots.gif differ
diff --git a/media/img/admin/download_frontline_engine.gif b/media/img/admin/download_frontline_engine.gif
new file mode 100644
index 0000000..785d8f6
Binary files /dev/null and b/media/img/admin/download_frontline_engine.gif differ
diff --git a/media/img/admin/drag.gif b/media/img/admin/drag.gif
new file mode 100644
index 0000000..d7880e1
Binary files /dev/null and b/media/img/admin/drag.gif differ
diff --git a/media/img/admin/footer-bg.jpg b/media/img/admin/footer-bg.jpg
new file mode 100644
index 0000000..4d0b221
Binary files /dev/null and b/media/img/admin/footer-bg.jpg differ
diff --git a/media/img/admin/icon-mail.gif b/media/img/admin/icon-mail.gif
new file mode 100644
index 0000000..c87d84b
Binary files /dev/null and b/media/img/admin/icon-mail.gif differ
diff --git a/media/img/admin/icon-none.gif b/media/img/admin/icon-none.gif
new file mode 100644
index 0000000..d8881ac
Binary files /dev/null and b/media/img/admin/icon-none.gif differ
diff --git a/media/img/admin/icon-ok.gif b/media/img/admin/icon-ok.gif
new file mode 100644
index 0000000..6cacc2e
Binary files /dev/null and b/media/img/admin/icon-ok.gif differ
diff --git a/media/img/admin/icon-phone.gif b/media/img/admin/icon-phone.gif
new file mode 100644
index 0000000..e2e39b3
Binary files /dev/null and b/media/img/admin/icon-phone.gif differ
diff --git a/media/img/admin/icon-rss.gif b/media/img/admin/icon-rss.gif
new file mode 100644
index 0000000..56c2aa0
Binary files /dev/null and b/media/img/admin/icon-rss.gif differ
diff --git a/media/img/admin/icon-twitter.gif b/media/img/admin/icon-twitter.gif
new file mode 100644
index 0000000..ccc8847
Binary files /dev/null and b/media/img/admin/icon-twitter.gif differ
diff --git a/media/img/admin/icon-zip.gif b/media/img/admin/icon-zip.gif
new file mode 100644
index 0000000..b1e2492
Binary files /dev/null and b/media/img/admin/icon-zip.gif differ
diff --git a/media/img/admin/icon_sprite.png b/media/img/admin/icon_sprite.png
new file mode 100644
index 0000000..f665c12
Binary files /dev/null and b/media/img/admin/icon_sprite.png differ
diff --git a/media/img/admin/img-graph.gif b/media/img/admin/img-graph.gif
new file mode 100644
index 0000000..f546d6f
Binary files /dev/null and b/media/img/admin/img-graph.gif differ
diff --git a/media/img/admin/img-map-zoom.gif b/media/img/admin/img-map-zoom.gif
new file mode 100644
index 0000000..698bc16
Binary files /dev/null and b/media/img/admin/img-map-zoom.gif differ
diff --git a/media/img/admin/img-map.gif b/media/img/admin/img-map.gif
new file mode 100644
index 0000000..5d35f20
Binary files /dev/null and b/media/img/admin/img-map.gif differ
diff --git a/media/img/admin/locations-icon.gif b/media/img/admin/locations-icon.gif
new file mode 100644
index 0000000..970e819
Binary files /dev/null and b/media/img/admin/locations-icon.gif differ
diff --git a/media/img/admin/logo.gif b/media/img/admin/logo.gif
new file mode 100644
index 0000000..ed26706
Binary files /dev/null and b/media/img/admin/logo.gif differ
diff --git a/media/img/admin/logo_header.gif b/media/img/admin/logo_header.gif
new file mode 100644
index 0000000..7ff1a2d
Binary files /dev/null and b/media/img/admin/logo_header.gif differ
diff --git a/media/img/admin/logo_login.gif b/media/img/admin/logo_login.gif
new file mode 100644
index 0000000..a3f2630
Binary files /dev/null and b/media/img/admin/logo_login.gif differ
diff --git a/media/img/admin/media-icon.gif b/media/img/admin/media-icon.gif
new file mode 100644
index 0000000..a0ef2f9
Binary files /dev/null and b/media/img/admin/media-icon.gif differ
diff --git a/media/img/admin/messages-icon.gif b/media/img/admin/messages-icon.gif
new file mode 100644
index 0000000..f56090e
Binary files /dev/null and b/media/img/admin/messages-icon.gif differ
diff --git a/media/img/admin/nairobi_google.gif b/media/img/admin/nairobi_google.gif
new file mode 100644
index 0000000..8783d8a
Binary files /dev/null and b/media/img/admin/nairobi_google.gif differ
diff --git a/media/img/admin/nairobi_msn.gif b/media/img/admin/nairobi_msn.gif
new file mode 100644
index 0000000..29af6b1
Binary files /dev/null and b/media/img/admin/nairobi_msn.gif differ
diff --git a/media/img/admin/nairobi_osm.gif b/media/img/admin/nairobi_osm.gif
new file mode 100644
index 0000000..82dcce6
Binary files /dev/null and b/media/img/admin/nairobi_osm.gif differ
diff --git a/media/img/admin/nairobi_yahoo.gif b/media/img/admin/nairobi_yahoo.gif
new file mode 100644
index 0000000..dc8a530
Binary files /dev/null and b/media/img/admin/nairobi_yahoo.gif differ
diff --git a/media/img/admin/none.gif b/media/img/admin/none.gif
new file mode 100644
index 0000000..35d42e8
Binary files /dev/null and b/media/img/admin/none.gif differ
diff --git a/media/img/admin/report-icon.gif b/media/img/admin/report-icon.gif
new file mode 100644
index 0000000..17f78cc
Binary files /dev/null and b/media/img/admin/report-icon.gif differ
diff --git a/media/img/admin/separator-1.gif b/media/img/admin/separator-1.gif
new file mode 100644
index 0000000..551fae3
Binary files /dev/null and b/media/img/admin/separator-1.gif differ
diff --git a/media/img/admin/separator-2.gif b/media/img/admin/separator-2.gif
new file mode 100644
index 0000000..73c8753
Binary files /dev/null and b/media/img/admin/separator-2.gif differ
diff --git a/media/img/admin/separator.gif b/media/img/admin/separator.gif
new file mode 100644
index 0000000..14d9193
Binary files /dev/null and b/media/img/admin/separator.gif differ
diff --git a/media/img/admin/sharing_down.gif b/media/img/admin/sharing_down.gif
new file mode 100644
index 0000000..4d045b2
Binary files /dev/null and b/media/img/admin/sharing_down.gif differ
diff --git a/media/img/admin/sharing_down_gray.gif b/media/img/admin/sharing_down_gray.gif
new file mode 100644
index 0000000..5cbbc79
Binary files /dev/null and b/media/img/admin/sharing_down_gray.gif differ
diff --git a/media/img/admin/sharing_up.gif b/media/img/admin/sharing_up.gif
new file mode 100644
index 0000000..bc80328
Binary files /dev/null and b/media/img/admin/sharing_up.gif differ
diff --git a/media/img/admin/sharing_up_gray.gif b/media/img/admin/sharing_up_gray.gif
new file mode 100644
index 0000000..d2b1466
Binary files /dev/null and b/media/img/admin/sharing_up_gray.gif differ
diff --git a/media/img/admin/top-separator.gif b/media/img/admin/top-separator.gif
new file mode 100644
index 0000000..4748da2
Binary files /dev/null and b/media/img/admin/top-separator.gif differ
diff --git a/media/img/admin/votes-icon.png b/media/img/admin/votes-icon.png
new file mode 100755
index 0000000..005af38
Binary files /dev/null and b/media/img/admin/votes-icon.png differ
diff --git a/media/img/arrow-down.gif b/media/img/arrow-down.gif
new file mode 100644
index 0000000..1857377
Binary files /dev/null and b/media/img/arrow-down.gif differ
diff --git a/media/img/arrow-play.gif b/media/img/arrow-play.gif
new file mode 100644
index 0000000..70e0f6a
Binary files /dev/null and b/media/img/arrow-play.gif differ
diff --git a/media/img/arrow.gif b/media/img/arrow.gif
new file mode 100644
index 0000000..ce5d04c
Binary files /dev/null and b/media/img/arrow.gif differ
diff --git a/media/img/badge_packs/Locations/china.png b/media/img/badge_packs/Locations/china.png
new file mode 100644
index 0000000..806b597
Binary files /dev/null and b/media/img/badge_packs/Locations/china.png differ
diff --git a/media/img/badge_packs/Locations/france.png b/media/img/badge_packs/Locations/france.png
new file mode 100644
index 0000000..f054fc9
Binary files /dev/null and b/media/img/badge_packs/Locations/france.png differ
diff --git a/media/img/badge_packs/Locations/germany.png b/media/img/badge_packs/Locations/germany.png
new file mode 100644
index 0000000..6bd4b7a
Binary files /dev/null and b/media/img/badge_packs/Locations/germany.png differ
diff --git a/media/img/badge_packs/Locations/greece.png b/media/img/badge_packs/Locations/greece.png
new file mode 100644
index 0000000..aeb92d8
Binary files /dev/null and b/media/img/badge_packs/Locations/greece.png differ
diff --git a/media/img/badge_packs/Locations/italy.png b/media/img/badge_packs/Locations/italy.png
new file mode 100644
index 0000000..e0a48e7
Binary files /dev/null and b/media/img/badge_packs/Locations/italy.png differ
diff --git a/media/img/badge_packs/Locations/kenya.png b/media/img/badge_packs/Locations/kenya.png
new file mode 100644
index 0000000..25071dc
Binary files /dev/null and b/media/img/badge_packs/Locations/kenya.png differ
diff --git a/media/img/badge_packs/Locations/malaysia.png b/media/img/badge_packs/Locations/malaysia.png
new file mode 100644
index 0000000..d1bc6bf
Binary files /dev/null and b/media/img/badge_packs/Locations/malaysia.png differ
diff --git a/media/img/badge_packs/Locations/mexico.png b/media/img/badge_packs/Locations/mexico.png
new file mode 100644
index 0000000..f8f2dc5
Binary files /dev/null and b/media/img/badge_packs/Locations/mexico.png differ
diff --git a/media/img/badge_packs/Locations/spain.png b/media/img/badge_packs/Locations/spain.png
new file mode 100644
index 0000000..57258f7
Binary files /dev/null and b/media/img/badge_packs/Locations/spain.png differ
diff --git a/media/img/badge_packs/Locations/turkey.png b/media/img/badge_packs/Locations/turkey.png
new file mode 100644
index 0000000..0c9399e
Binary files /dev/null and b/media/img/badge_packs/Locations/turkey.png differ
diff --git a/media/img/badge_packs/Locations/unitedkingdom.png b/media/img/badge_packs/Locations/unitedkingdom.png
new file mode 100644
index 0000000..a0f33e9
Binary files /dev/null and b/media/img/badge_packs/Locations/unitedkingdom.png differ
diff --git a/media/img/badge_packs/Locations/usa.png b/media/img/badge_packs/Locations/usa.png
new file mode 100644
index 0000000..c6c0f4a
Binary files /dev/null and b/media/img/badge_packs/Locations/usa.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_1.png b/media/img/badge_packs/Ushahidi/badge_1.png
new file mode 100644
index 0000000..5b42d27
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_1.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_10.png b/media/img/badge_packs/Ushahidi/badge_10.png
new file mode 100644
index 0000000..3affa06
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_10.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_11.png b/media/img/badge_packs/Ushahidi/badge_11.png
new file mode 100644
index 0000000..ffd201c
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_11.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_12.png b/media/img/badge_packs/Ushahidi/badge_12.png
new file mode 100644
index 0000000..8638402
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_12.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_13.png b/media/img/badge_packs/Ushahidi/badge_13.png
new file mode 100644
index 0000000..99dd1b6
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_13.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_14.png b/media/img/badge_packs/Ushahidi/badge_14.png
new file mode 100644
index 0000000..1197753
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_14.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_15.png b/media/img/badge_packs/Ushahidi/badge_15.png
new file mode 100644
index 0000000..fc237e7
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_15.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_16.png b/media/img/badge_packs/Ushahidi/badge_16.png
new file mode 100644
index 0000000..22ad0fb
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_16.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_17.png b/media/img/badge_packs/Ushahidi/badge_17.png
new file mode 100644
index 0000000..a528b40
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_17.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_18.png b/media/img/badge_packs/Ushahidi/badge_18.png
new file mode 100644
index 0000000..3fcd370
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_18.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_19.png b/media/img/badge_packs/Ushahidi/badge_19.png
new file mode 100644
index 0000000..d39bee1
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_19.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_2.png b/media/img/badge_packs/Ushahidi/badge_2.png
new file mode 100644
index 0000000..373df43
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_2.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_20.png b/media/img/badge_packs/Ushahidi/badge_20.png
new file mode 100644
index 0000000..822f0b3
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_20.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_21.png b/media/img/badge_packs/Ushahidi/badge_21.png
new file mode 100644
index 0000000..4f41a93
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_21.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_22.png b/media/img/badge_packs/Ushahidi/badge_22.png
new file mode 100644
index 0000000..bf438ff
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_22.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_23.png b/media/img/badge_packs/Ushahidi/badge_23.png
new file mode 100644
index 0000000..d7aa8de
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_23.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_24.png b/media/img/badge_packs/Ushahidi/badge_24.png
new file mode 100644
index 0000000..e139a45
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_24.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_25.png b/media/img/badge_packs/Ushahidi/badge_25.png
new file mode 100644
index 0000000..3323212
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_25.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_26.png b/media/img/badge_packs/Ushahidi/badge_26.png
new file mode 100644
index 0000000..b4aecba
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_26.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_27.png b/media/img/badge_packs/Ushahidi/badge_27.png
new file mode 100644
index 0000000..8fd1c76
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_27.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_3.png b/media/img/badge_packs/Ushahidi/badge_3.png
new file mode 100644
index 0000000..940b248
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_3.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_4.png b/media/img/badge_packs/Ushahidi/badge_4.png
new file mode 100644
index 0000000..eed734f
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_4.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_5.png b/media/img/badge_packs/Ushahidi/badge_5.png
new file mode 100644
index 0000000..ad19d69
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_5.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_6.png b/media/img/badge_packs/Ushahidi/badge_6.png
new file mode 100644
index 0000000..cebd1f1
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_6.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_7.png b/media/img/badge_packs/Ushahidi/badge_7.png
new file mode 100644
index 0000000..c126590
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_7.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_8.png b/media/img/badge_packs/Ushahidi/badge_8.png
new file mode 100644
index 0000000..cdfd895
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_8.png differ
diff --git a/media/img/badge_packs/Ushahidi/badge_9.png b/media/img/badge_packs/Ushahidi/badge_9.png
new file mode 100644
index 0000000..112fc57
Binary files /dev/null and b/media/img/badge_packs/Ushahidi/badge_9.png differ
diff --git a/media/img/big-block-bg.gif b/media/img/big-block-bg.gif
new file mode 100644
index 0000000..4070917
Binary files /dev/null and b/media/img/big-block-bg.gif differ
diff --git a/media/img/big-block-bottom.gif b/media/img/big-block-bottom.gif
new file mode 100644
index 0000000..1b05867
Binary files /dev/null and b/media/img/big-block-bottom.gif differ
diff --git a/media/img/big-block-top.gif b/media/img/big-block-top.gif
new file mode 100644
index 0000000..146db40
Binary files /dev/null and b/media/img/big-block-top.gif differ
diff --git a/media/img/big-map.gif b/media/img/big-map.gif
new file mode 100644
index 0000000..e2750e6
Binary files /dev/null and b/media/img/big-map.gif differ
diff --git a/media/img/bkg_login.gif b/media/img/bkg_login.gif
new file mode 100644
index 0000000..0d3ae2e
Binary files /dev/null and b/media/img/bkg_login.gif differ
diff --git a/media/img/bkg_login.jpg b/media/img/bkg_login.jpg
new file mode 100644
index 0000000..2fca4c7
Binary files /dev/null and b/media/img/bkg_login.jpg differ
diff --git a/media/img/btn-blue.gif b/media/img/btn-blue.gif
new file mode 100644
index 0000000..f953d26
Binary files /dev/null and b/media/img/btn-blue.gif differ
diff --git a/media/img/btn-gray.gif b/media/img/btn-gray.gif
new file mode 100644
index 0000000..6e33775
Binary files /dev/null and b/media/img/btn-gray.gif differ
diff --git a/media/img/btn-next.gif b/media/img/btn-next.gif
new file mode 100644
index 0000000..82af2c2
Binary files /dev/null and b/media/img/btn-next.gif differ
diff --git a/media/img/btn-prev.gif b/media/img/btn-prev.gif
new file mode 100644
index 0000000..1f88617
Binary files /dev/null and b/media/img/btn-prev.gif differ
diff --git a/media/img/btn_bkg_g.gif b/media/img/btn_bkg_g.gif
new file mode 100644
index 0000000..740ed0d
Binary files /dev/null and b/media/img/btn_bkg_g.gif differ
diff --git a/media/img/category-filter-list.gif b/media/img/category-filter-list.gif
new file mode 100644
index 0000000..476dad5
Binary files /dev/null and b/media/img/category-filter-list.gif differ
diff --git a/media/img/category-item-active.gif b/media/img/category-item-active.gif
new file mode 100644
index 0000000..7eb19ce
Binary files /dev/null and b/media/img/category-item-active.gif differ
diff --git a/media/img/color_icon.php b/media/img/color_icon.php
new file mode 100644
index 0000000..5094180
--- /dev/null
+++ b/media/img/color_icon.php
@@ -0,0 +1,48 @@
+<?php
+
+/*
+* Creates a square image with one color
+* Params:
+*   w - width, in pixels
+*   h - height, in pixels
+*   c - color, in hex (ex: FF0000)
+*/
+
+header("Content-type: image/png"); 
+
+$width = 100;
+$height = 100;
+$r = 0;
+$g = 0;
+$b = 255;
+//$alpha = 0;
+
+if(isset($_GET['w'])) $width = $_GET['w'];
+if(isset($_GET['h'])) $height = $_GET['h'];
+if(isset($_GET['c'])) {
+	$c = $_GET['c'];
+	$r = hexdec($c{0}.$c{1});
+	$g = hexdec($c{2}.$c{3});
+	$b = hexdec($c{4}.$c{5});
+}
+
+$im = imagecreatetruecolor($width, $height);
+
+$color = ImageColorAllocate($im, $r, $g, $b);
+$white = ImageColorAllocate($im, 255, 255, 255);
+
+imagefilltoborder($im, 0, 0, $white, $white);
+
+imagecolortransparent($im, $white);
+
+$cx = ceil(($width / 2));
+$cy = ceil(($height / 2));
+ImageFilledEllipse($im, $cx, $cy, ($width-1), ($height-1), $color);
+
+// send the new PNG image to the browser
+imagepng($im); 
+ 
+// destroy the reference pointer to the image in memory to free up resources
+imagedestroy($im); 
+
+?>
\ No newline at end of file
diff --git a/media/img/colorpicker/blank.gif b/media/img/colorpicker/blank.gif
new file mode 100644
index 0000000..75b945d
Binary files /dev/null and b/media/img/colorpicker/blank.gif differ
diff --git a/media/img/colorpicker/colorpicker_background.png b/media/img/colorpicker/colorpicker_background.png
new file mode 100644
index 0000000..8401572
Binary files /dev/null and b/media/img/colorpicker/colorpicker_background.png differ
diff --git a/media/img/colorpicker/colorpicker_hex.png b/media/img/colorpicker/colorpicker_hex.png
new file mode 100644
index 0000000..4e532d7
Binary files /dev/null and b/media/img/colorpicker/colorpicker_hex.png differ
diff --git a/media/img/colorpicker/colorpicker_hsb_b.png b/media/img/colorpicker/colorpicker_hsb_b.png
new file mode 100644
index 0000000..dfac595
Binary files /dev/null and b/media/img/colorpicker/colorpicker_hsb_b.png differ
diff --git a/media/img/colorpicker/colorpicker_hsb_h.png b/media/img/colorpicker/colorpicker_hsb_h.png
new file mode 100644
index 0000000..3977ed9
Binary files /dev/null and b/media/img/colorpicker/colorpicker_hsb_h.png differ
diff --git a/media/img/colorpicker/colorpicker_hsb_s.png b/media/img/colorpicker/colorpicker_hsb_s.png
new file mode 100644
index 0000000..a2a6997
Binary files /dev/null and b/media/img/colorpicker/colorpicker_hsb_s.png differ
diff --git a/media/img/colorpicker/colorpicker_indic.gif b/media/img/colorpicker/colorpicker_indic.gif
new file mode 100644
index 0000000..f9fa95e
Binary files /dev/null and b/media/img/colorpicker/colorpicker_indic.gif differ
diff --git a/media/img/colorpicker/colorpicker_overlay.png b/media/img/colorpicker/colorpicker_overlay.png
new file mode 100644
index 0000000..561cdd9
Binary files /dev/null and b/media/img/colorpicker/colorpicker_overlay.png differ
diff --git a/media/img/colorpicker/colorpicker_rgb_b.png b/media/img/colorpicker/colorpicker_rgb_b.png
new file mode 100644
index 0000000..dfac595
Binary files /dev/null and b/media/img/colorpicker/colorpicker_rgb_b.png differ
diff --git a/media/img/colorpicker/colorpicker_rgb_g.png b/media/img/colorpicker/colorpicker_rgb_g.png
new file mode 100644
index 0000000..72b3276
Binary files /dev/null and b/media/img/colorpicker/colorpicker_rgb_g.png differ
diff --git a/media/img/colorpicker/colorpicker_rgb_r.png b/media/img/colorpicker/colorpicker_rgb_r.png
new file mode 100644
index 0000000..4855fe0
Binary files /dev/null and b/media/img/colorpicker/colorpicker_rgb_r.png differ
diff --git a/media/img/colorpicker/colorpicker_select.gif b/media/img/colorpicker/colorpicker_select.gif
new file mode 100644
index 0000000..599f7f1
Binary files /dev/null and b/media/img/colorpicker/colorpicker_select.gif differ
diff --git a/media/img/colorpicker/colorpicker_submit.png b/media/img/colorpicker/colorpicker_submit.png
new file mode 100644
index 0000000..7f4c082
Binary files /dev/null and b/media/img/colorpicker/colorpicker_submit.png differ
diff --git a/media/img/colorpicker/custom_background.png b/media/img/colorpicker/custom_background.png
new file mode 100644
index 0000000..cf55ffd
Binary files /dev/null and b/media/img/colorpicker/custom_background.png differ
diff --git a/media/img/colorpicker/custom_hex.png b/media/img/colorpicker/custom_hex.png
new file mode 100644
index 0000000..888f444
Binary files /dev/null and b/media/img/colorpicker/custom_hex.png differ
diff --git a/media/img/colorpicker/custom_hsb_b.png b/media/img/colorpicker/custom_hsb_b.png
new file mode 100644
index 0000000..2f99dae
Binary files /dev/null and b/media/img/colorpicker/custom_hsb_b.png differ
diff --git a/media/img/colorpicker/custom_hsb_h.png b/media/img/colorpicker/custom_hsb_h.png
new file mode 100644
index 0000000..a217e92
Binary files /dev/null and b/media/img/colorpicker/custom_hsb_h.png differ
diff --git a/media/img/colorpicker/custom_hsb_s.png b/media/img/colorpicker/custom_hsb_s.png
new file mode 100644
index 0000000..7826b41
Binary files /dev/null and b/media/img/colorpicker/custom_hsb_s.png differ
diff --git a/media/img/colorpicker/custom_indic.gif b/media/img/colorpicker/custom_indic.gif
new file mode 100644
index 0000000..222fb94
Binary files /dev/null and b/media/img/colorpicker/custom_indic.gif differ
diff --git a/media/img/colorpicker/custom_rgb_b.png b/media/img/colorpicker/custom_rgb_b.png
new file mode 100644
index 0000000..80764e5
Binary files /dev/null and b/media/img/colorpicker/custom_rgb_b.png differ
diff --git a/media/img/colorpicker/custom_rgb_g.png b/media/img/colorpicker/custom_rgb_g.png
new file mode 100644
index 0000000..fc9778b
Binary files /dev/null and b/media/img/colorpicker/custom_rgb_g.png differ
diff --git a/media/img/colorpicker/custom_rgb_r.png b/media/img/colorpicker/custom_rgb_r.png
new file mode 100644
index 0000000..91b0cd4
Binary files /dev/null and b/media/img/colorpicker/custom_rgb_r.png differ
diff --git a/media/img/colorpicker/custom_submit.png b/media/img/colorpicker/custom_submit.png
new file mode 100644
index 0000000..cd202cd
Binary files /dev/null and b/media/img/colorpicker/custom_submit.png differ
diff --git a/media/img/colorpicker/select.png b/media/img/colorpicker/select.png
new file mode 100644
index 0000000..21213bf
Binary files /dev/null and b/media/img/colorpicker/select.png differ
diff --git a/media/img/colorpicker/select2.png b/media/img/colorpicker/select2.png
new file mode 100644
index 0000000..2cd2cab
Binary files /dev/null and b/media/img/colorpicker/select2.png differ
diff --git a/media/img/colorpicker/slider.png b/media/img/colorpicker/slider.png
new file mode 100644
index 0000000..8b03da9
Binary files /dev/null and b/media/img/colorpicker/slider.png differ
diff --git a/media/img/content-bottom.gif b/media/img/content-bottom.gif
new file mode 100644
index 0000000..aa7164f
Binary files /dev/null and b/media/img/content-bottom.gif differ
diff --git a/media/img/content-top.gif b/media/img/content-top.gif
new file mode 100644
index 0000000..bda7095
Binary files /dev/null and b/media/img/content-top.gif differ
diff --git a/media/img/down.png b/media/img/down.png
new file mode 100755
index 0000000..cb4bc45
Binary files /dev/null and b/media/img/down.png differ
diff --git a/media/img/experimental.png b/media/img/experimental.png
new file mode 100644
index 0000000..3da0cf7
Binary files /dev/null and b/media/img/experimental.png differ
diff --git a/media/img/f-logo.gif b/media/img/f-logo.gif
new file mode 100644
index 0000000..fbacd75
Binary files /dev/null and b/media/img/f-logo.gif differ
diff --git a/media/img/f-nav-separator.gif b/media/img/f-nav-separator.gif
new file mode 100644
index 0000000..0fa7654
Binary files /dev/null and b/media/img/f-nav-separator.gif differ
diff --git a/media/img/filter-item-l.gif b/media/img/filter-item-l.gif
new file mode 100644
index 0000000..9c81dbd
Binary files /dev/null and b/media/img/filter-item-l.gif differ
diff --git a/media/img/filter-item-r.gif b/media/img/filter-item-r.gif
new file mode 100644
index 0000000..88e9900
Binary files /dev/null and b/media/img/filter-item-r.gif differ
diff --git a/media/img/flags/de_DE.png b/media/img/flags/de_DE.png
new file mode 100644
index 0000000..ac4a977
Binary files /dev/null and b/media/img/flags/de_DE.png differ
diff --git a/media/img/flags/en_US.png b/media/img/flags/en_US.png
new file mode 100644
index 0000000..10f451f
Binary files /dev/null and b/media/img/flags/en_US.png differ
diff --git a/media/img/flags/es_ES.png b/media/img/flags/es_ES.png
new file mode 100644
index 0000000..c2de2d7
Binary files /dev/null and b/media/img/flags/es_ES.png differ
diff --git a/media/img/flags/fa_IR.png b/media/img/flags/fa_IR.png
new file mode 100644
index 0000000..c5fd136
Binary files /dev/null and b/media/img/flags/fa_IR.png differ
diff --git a/media/img/flags/fr_FR.png b/media/img/flags/fr_FR.png
new file mode 100644
index 0000000..8332c4e
Binary files /dev/null and b/media/img/flags/fr_FR.png differ
diff --git a/media/img/flags/it_IT.png b/media/img/flags/it_IT.png
new file mode 100644
index 0000000..89692f7
Binary files /dev/null and b/media/img/flags/it_IT.png differ
diff --git a/media/img/flags/nl_NL.png b/media/img/flags/nl_NL.png
new file mode 100644
index 0000000..fe44791
Binary files /dev/null and b/media/img/flags/nl_NL.png differ
diff --git a/media/img/flags/pl_PL.png b/media/img/flags/pl_PL.png
new file mode 100644
index 0000000..d413d01
Binary files /dev/null and b/media/img/flags/pl_PL.png differ
diff --git a/media/img/flags/pt_BR.png b/media/img/flags/pt_BR.png
new file mode 100644
index 0000000..9b1a553
Binary files /dev/null and b/media/img/flags/pt_BR.png differ
diff --git a/media/img/flags/pt_PT.png b/media/img/flags/pt_PT.png
new file mode 100644
index 0000000..ece7980
Binary files /dev/null and b/media/img/flags/pt_PT.png differ
diff --git a/media/img/flags/ru_RU.png b/media/img/flags/ru_RU.png
new file mode 100644
index 0000000..47da421
Binary files /dev/null and b/media/img/flags/ru_RU.png differ
diff --git a/media/img/footer-bg.jpg b/media/img/footer-bg.jpg
new file mode 100644
index 0000000..dd6e767
Binary files /dev/null and b/media/img/footer-bg.jpg differ
diff --git a/media/img/footer-bg2.jpg b/media/img/footer-bg2.jpg
new file mode 100644
index 0000000..143d08b
Binary files /dev/null and b/media/img/footer-bg2.jpg differ
diff --git a/media/img/footer-logo.png b/media/img/footer-logo.png
new file mode 100644
index 0000000..33b627b
Binary files /dev/null and b/media/img/footer-logo.png differ
diff --git a/media/img/gal-big.gif b/media/img/gal-big.gif
new file mode 100644
index 0000000..43df6aa
Binary files /dev/null and b/media/img/gal-big.gif differ
diff --git a/media/img/gal-small.gif b/media/img/gal-small.gif
new file mode 100644
index 0000000..f9527ed
Binary files /dev/null and b/media/img/gal-small.gif differ
diff --git a/media/img/google-map.jpg b/media/img/google-map.jpg
new file mode 100644
index 0000000..abca8cd
Binary files /dev/null and b/media/img/google-map.jpg differ
diff --git a/media/img/gray_down.png b/media/img/gray_down.png
new file mode 100644
index 0000000..266f95b
Binary files /dev/null and b/media/img/gray_down.png differ
diff --git a/media/img/gray_up.png b/media/img/gray_up.png
new file mode 100644
index 0000000..07a7196
Binary files /dev/null and b/media/img/gray_up.png differ
diff --git a/media/img/grey-box-bottom.gif b/media/img/grey-box-bottom.gif
new file mode 100644
index 0000000..6860d81
Binary files /dev/null and b/media/img/grey-box-bottom.gif differ
diff --git a/media/img/grey-box-top.gif b/media/img/grey-box-top.gif
new file mode 100644
index 0000000..216d994
Binary files /dev/null and b/media/img/grey-box-top.gif differ
diff --git a/media/img/grey-btn-l.gif b/media/img/grey-btn-l.gif
new file mode 100644
index 0000000..d6c961c
Binary files /dev/null and b/media/img/grey-btn-l.gif differ
diff --git a/media/img/grey-btn-r.gif b/media/img/grey-btn-r.gif
new file mode 100644
index 0000000..208bb23
Binary files /dev/null and b/media/img/grey-btn-r.gif differ
diff --git a/media/img/ico-01.png b/media/img/ico-01.png
new file mode 100644
index 0000000..514145c
Binary files /dev/null and b/media/img/ico-01.png differ
diff --git a/media/img/ico-02.png b/media/img/ico-02.png
new file mode 100644
index 0000000..f72cf20
Binary files /dev/null and b/media/img/ico-02.png differ
diff --git a/media/img/ico-03.png b/media/img/ico-03.png
new file mode 100644
index 0000000..3defd9c
Binary files /dev/null and b/media/img/ico-03.png differ
diff --git a/media/img/ico-04.png b/media/img/ico-04.png
new file mode 100644
index 0000000..dcf877f
Binary files /dev/null and b/media/img/ico-04.png differ
diff --git a/media/img/ico-05.png b/media/img/ico-05.png
new file mode 100644
index 0000000..a3aa868
Binary files /dev/null and b/media/img/ico-05.png differ
diff --git a/media/img/ico-06.png b/media/img/ico-06.png
new file mode 100644
index 0000000..ca36c19
Binary files /dev/null and b/media/img/ico-06.png differ
diff --git a/media/img/ico-07.png b/media/img/ico-07.png
new file mode 100644
index 0000000..d8abb73
Binary files /dev/null and b/media/img/ico-07.png differ
diff --git a/media/img/ico-08.png b/media/img/ico-08.png
new file mode 100644
index 0000000..3defd9c
Binary files /dev/null and b/media/img/ico-08.png differ
diff --git a/media/img/ico-09.png b/media/img/ico-09.png
new file mode 100644
index 0000000..e1ebf9d
Binary files /dev/null and b/media/img/ico-09.png differ
diff --git a/media/img/ico-10.png b/media/img/ico-10.png
new file mode 100644
index 0000000..f3b0875
Binary files /dev/null and b/media/img/ico-10.png differ
diff --git a/media/img/ico-info.png b/media/img/ico-info.png
new file mode 100644
index 0000000..bee8e6b
Binary files /dev/null and b/media/img/ico-info.png differ
diff --git a/media/img/ico-orange.gif b/media/img/ico-orange.gif
new file mode 100644
index 0000000..8a80965
Binary files /dev/null and b/media/img/ico-orange.gif differ
diff --git a/media/img/ico-red.gif b/media/img/ico-red.gif
new file mode 100644
index 0000000..35f8f94
Binary files /dev/null and b/media/img/ico-red.gif differ
diff --git a/media/img/ico-warning.png b/media/img/ico-warning.png
new file mode 100644
index 0000000..4e739b6
Binary files /dev/null and b/media/img/ico-warning.png differ
diff --git a/media/img/icon-calendar.gif b/media/img/icon-calendar.gif
new file mode 100644
index 0000000..f0f32f4
Binary files /dev/null and b/media/img/icon-calendar.gif differ
diff --git a/media/img/icon-feed.png b/media/img/icon-feed.png
new file mode 100644
index 0000000..1679ab0
Binary files /dev/null and b/media/img/icon-feed.png differ
diff --git a/media/img/icon-minus.gif b/media/img/icon-minus.gif
new file mode 100644
index 0000000..2d1476e
Binary files /dev/null and b/media/img/icon-minus.gif differ
diff --git a/media/img/icon-plus.gif b/media/img/icon-plus.gif
new file mode 100644
index 0000000..812c345
Binary files /dev/null and b/media/img/icon-plus.gif differ
diff --git a/media/img/icon-tick.png b/media/img/icon-tick.png
new file mode 100644
index 0000000..998842d
Binary files /dev/null and b/media/img/icon-tick.png differ
diff --git a/media/img/icon-warning.png b/media/img/icon-warning.png
new file mode 100644
index 0000000..7d8ebe9
Binary files /dev/null and b/media/img/icon-warning.png differ
diff --git a/media/img/icon_alert_big.gif b/media/img/icon_alert_big.gif
new file mode 100644
index 0000000..183f219
Binary files /dev/null and b/media/img/icon_alert_big.gif differ
diff --git a/media/img/icon_sprite.png b/media/img/icon_sprite.png
new file mode 100644
index 0000000..f665c12
Binary files /dev/null and b/media/img/icon_sprite.png differ
diff --git a/media/img/image.png b/media/img/image.png
new file mode 100644
index 0000000..8536d1a
Binary files /dev/null and b/media/img/image.png differ
diff --git a/media/img/incident-pointer.jpg b/media/img/incident-pointer.jpg
new file mode 100644
index 0000000..6fc5149
Binary files /dev/null and b/media/img/incident-pointer.jpg differ
diff --git a/media/img/index.html b/media/img/index.html
new file mode 100644
index 0000000..fa6d84e
--- /dev/null
+++ b/media/img/index.html
@@ -0,0 +1 @@
+<html><body bgcolor="#FFFFFF"></body></html>
\ No newline at end of file
diff --git a/media/img/indicator.gif b/media/img/indicator.gif
new file mode 100644
index 0000000..085ccae
Binary files /dev/null and b/media/img/indicator.gif differ
diff --git a/media/img/install_bg-progress-active.gif b/media/img/install_bg-progress-active.gif
new file mode 100644
index 0000000..db897b3
Binary files /dev/null and b/media/img/install_bg-progress-active.gif differ
diff --git a/media/img/install_bg-progress-bar.gif b/media/img/install_bg-progress-bar.gif
new file mode 100644
index 0000000..6657659
Binary files /dev/null and b/media/img/install_bg-progress-bar.gif differ
diff --git a/media/img/install_bg-progress-item-number.gif b/media/img/install_bg-progress-item-number.gif
new file mode 100644
index 0000000..62cbd7f
Binary files /dev/null and b/media/img/install_bg-progress-item-number.gif differ
diff --git a/media/img/lnk-info-l.gif b/media/img/lnk-info-l.gif
new file mode 100644
index 0000000..65f780c
Binary files /dev/null and b/media/img/lnk-info-l.gif differ
diff --git a/media/img/lnk-info-r.gif b/media/img/lnk-info-r.gif
new file mode 100644
index 0000000..3158599
Binary files /dev/null and b/media/img/lnk-info-r.gif differ
diff --git a/media/img/loading.gif b/media/img/loading.gif
new file mode 100644
index 0000000..03d3ee7
Binary files /dev/null and b/media/img/loading.gif differ
diff --git a/media/img/loading_g.gif b/media/img/loading_g.gif
new file mode 100644
index 0000000..e58b950
Binary files /dev/null and b/media/img/loading_g.gif differ
diff --git a/media/img/loading_g2.gif b/media/img/loading_g2.gif
new file mode 100644
index 0000000..ee7984c
Binary files /dev/null and b/media/img/loading_g2.gif differ
diff --git a/media/img/loading_y.gif b/media/img/loading_y.gif
new file mode 100644
index 0000000..137447e
Binary files /dev/null and b/media/img/loading_y.gif differ
diff --git a/media/img/map.gif b/media/img/map.gif
new file mode 100644
index 0000000..7a57333
Binary files /dev/null and b/media/img/map.gif differ
diff --git a/media/img/media-image.jpg b/media/img/media-image.jpg
new file mode 100644
index 0000000..ea30216
Binary files /dev/null and b/media/img/media-image.jpg differ
diff --git a/media/img/media-type-image.jpg b/media/img/media-type-image.jpg
new file mode 100644
index 0000000..27459a8
Binary files /dev/null and b/media/img/media-type-image.jpg differ
diff --git a/media/img/media-type-video.jpg b/media/img/media-type-video.jpg
new file mode 100644
index 0000000..c594628
Binary files /dev/null and b/media/img/media-type-video.jpg differ
diff --git a/media/img/nav-active.gif b/media/img/nav-active.gif
new file mode 100644
index 0000000..e888d11
Binary files /dev/null and b/media/img/nav-active.gif differ
diff --git a/media/img/nav-bg.gif b/media/img/nav-bg.gif
new file mode 100644
index 0000000..db0ac19
Binary files /dev/null and b/media/img/nav-bg.gif differ
diff --git a/media/img/nav-first.gif b/media/img/nav-first.gif
new file mode 100644
index 0000000..0da0a27
Binary files /dev/null and b/media/img/nav-first.gif differ
diff --git a/media/img/nav-last.gif b/media/img/nav-last.gif
new file mode 100644
index 0000000..c83ffee
Binary files /dev/null and b/media/img/nav-last.gif differ
diff --git a/media/img/nav-separator.gif b/media/img/nav-separator.gif
new file mode 100644
index 0000000..4dd2060
Binary files /dev/null and b/media/img/nav-separator.gif differ
diff --git a/media/img/nearby-incident-pointer.jpg b/media/img/nearby-incident-pointer.jpg
new file mode 100644
index 0000000..edc9e0b
Binary files /dev/null and b/media/img/nearby-incident-pointer.jpg differ
diff --git a/media/img/openid/openid-inputicon.gif b/media/img/openid/openid-inputicon.gif
new file mode 100644
index 0000000..cde836c
Binary files /dev/null and b/media/img/openid/openid-inputicon.gif differ
diff --git a/media/img/openid/openid-providers-en.png b/media/img/openid/openid-providers-en.png
new file mode 100755
index 0000000..34d08d3
Binary files /dev/null and b/media/img/openid/openid-providers-en.png differ
diff --git a/media/img/openid/openid-providers-ru.png b/media/img/openid/openid-providers-ru.png
new file mode 100755
index 0000000..a1750ca
Binary files /dev/null and b/media/img/openid/openid-providers-ru.png differ
diff --git a/media/img/openid/openid-providers-uk.png b/media/img/openid/openid-providers-uk.png
new file mode 100755
index 0000000..dd772a1
Binary files /dev/null and b/media/img/openid/openid-providers-uk.png differ
diff --git a/media/img/openlayers/404.png b/media/img/openlayers/404.png
new file mode 100644
index 0000000..f7baca1
Binary files /dev/null and b/media/img/openlayers/404.png differ
diff --git a/media/img/openlayers/LICENSE.txt b/media/img/openlayers/LICENSE.txt
new file mode 100755
index 0000000..e07f0bd
--- /dev/null
+++ b/media/img/openlayers/LICENSE.txt
@@ -0,0 +1,25 @@
+Copyright (c) 2010, Development Seed, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+- Neither the name of the Development Seed, Inc. nor the names of its
+  contributors may be used to endorse or promote products derived from this
+  software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/media/img/openlayers/add_point_off.png b/media/img/openlayers/add_point_off.png
new file mode 100644
index 0000000..aefd09c
Binary files /dev/null and b/media/img/openlayers/add_point_off.png differ
diff --git a/media/img/openlayers/add_point_on.png b/media/img/openlayers/add_point_on.png
new file mode 100644
index 0000000..1294a2c
Binary files /dev/null and b/media/img/openlayers/add_point_on.png differ
diff --git a/media/img/openlayers/blank.gif b/media/img/openlayers/blank.gif
new file mode 100644
index 0000000..4bcc753
Binary files /dev/null and b/media/img/openlayers/blank.gif differ
diff --git a/media/img/openlayers/close.gif b/media/img/openlayers/close.gif
new file mode 100644
index 0000000..b38d0d7
Binary files /dev/null and b/media/img/openlayers/close.gif differ
diff --git a/media/img/openlayers/cloud-popup-relative.png b/media/img/openlayers/cloud-popup-relative.png
new file mode 100755
index 0000000..c9fd4c4
Binary files /dev/null and b/media/img/openlayers/cloud-popup-relative.png differ
diff --git a/media/img/openlayers/drag-rectangle-off.png b/media/img/openlayers/drag-rectangle-off.png
new file mode 100644
index 0000000..382a81d
Binary files /dev/null and b/media/img/openlayers/drag-rectangle-off.png differ
diff --git a/media/img/openlayers/drag-rectangle-on.png b/media/img/openlayers/drag-rectangle-on.png
new file mode 100644
index 0000000..2ed2d5b
Binary files /dev/null and b/media/img/openlayers/drag-rectangle-on.png differ
diff --git a/media/img/openlayers/draw_line_off.png b/media/img/openlayers/draw_line_off.png
new file mode 100644
index 0000000..7f15612
Binary files /dev/null and b/media/img/openlayers/draw_line_off.png differ
diff --git a/media/img/openlayers/draw_line_on.png b/media/img/openlayers/draw_line_on.png
new file mode 100644
index 0000000..ba09186
Binary files /dev/null and b/media/img/openlayers/draw_line_on.png differ
diff --git a/media/img/openlayers/draw_point_off.png b/media/img/openlayers/draw_point_off.png
new file mode 100644
index 0000000..fde94bd
Binary files /dev/null and b/media/img/openlayers/draw_point_off.png differ
diff --git a/media/img/openlayers/draw_point_on.png b/media/img/openlayers/draw_point_on.png
new file mode 100644
index 0000000..8804221
Binary files /dev/null and b/media/img/openlayers/draw_point_on.png differ
diff --git a/media/img/openlayers/draw_polygon_off.png b/media/img/openlayers/draw_polygon_off.png
new file mode 100644
index 0000000..53ce9d7
Binary files /dev/null and b/media/img/openlayers/draw_polygon_off.png differ
diff --git a/media/img/openlayers/draw_polygon_on.png b/media/img/openlayers/draw_polygon_on.png
new file mode 100644
index 0000000..2a33376
Binary files /dev/null and b/media/img/openlayers/draw_polygon_on.png differ
diff --git a/media/img/openlayers/east-mini.png b/media/img/openlayers/east-mini.png
new file mode 100644
index 0000000..ecedc5e
Binary files /dev/null and b/media/img/openlayers/east-mini.png differ
diff --git a/media/img/openlayers/editing_tool_bar.png b/media/img/openlayers/editing_tool_bar.png
new file mode 100644
index 0000000..464340e
Binary files /dev/null and b/media/img/openlayers/editing_tool_bar.png differ
diff --git a/media/img/openlayers/label-maximize.png b/media/img/openlayers/label-maximize.png
new file mode 100644
index 0000000..2712c6d
Binary files /dev/null and b/media/img/openlayers/label-maximize.png differ
diff --git a/media/img/openlayers/layer-switcher-maximize.png b/media/img/openlayers/layer-switcher-maximize.png
new file mode 100644
index 0000000..f346086
Binary files /dev/null and b/media/img/openlayers/layer-switcher-maximize.png differ
diff --git a/media/img/openlayers/layer-switcher-minimize.png b/media/img/openlayers/layer-switcher-minimize.png
new file mode 100644
index 0000000..b4aab0b
Binary files /dev/null and b/media/img/openlayers/layer-switcher-minimize.png differ
diff --git a/media/img/openlayers/loading.gif b/media/img/openlayers/loading.gif
new file mode 100644
index 0000000..296340b
Binary files /dev/null and b/media/img/openlayers/loading.gif differ
diff --git a/media/img/openlayers/marker-blue.png b/media/img/openlayers/marker-blue.png
new file mode 100644
index 0000000..f5b4efc
Binary files /dev/null and b/media/img/openlayers/marker-blue.png differ
diff --git a/media/img/openlayers/marker-gold.png b/media/img/openlayers/marker-gold.png
new file mode 100644
index 0000000..0b62f96
Binary files /dev/null and b/media/img/openlayers/marker-gold.png differ
diff --git a/media/img/openlayers/marker-green.png b/media/img/openlayers/marker-green.png
new file mode 100644
index 0000000..c36b164
Binary files /dev/null and b/media/img/openlayers/marker-green.png differ
diff --git a/media/img/openlayers/marker.png b/media/img/openlayers/marker.png
new file mode 100644
index 0000000..ea3e59a
Binary files /dev/null and b/media/img/openlayers/marker.png differ
diff --git a/media/img/openlayers/measuring-stick-off.png b/media/img/openlayers/measuring-stick-off.png
new file mode 100644
index 0000000..efbf63f
Binary files /dev/null and b/media/img/openlayers/measuring-stick-off.png differ
diff --git a/media/img/openlayers/measuring-stick-on.png b/media/img/openlayers/measuring-stick-on.png
new file mode 100644
index 0000000..2d41c84
Binary files /dev/null and b/media/img/openlayers/measuring-stick-on.png differ
diff --git a/media/img/openlayers/move_feature_off.png b/media/img/openlayers/move_feature_off.png
new file mode 100644
index 0000000..9f588db
Binary files /dev/null and b/media/img/openlayers/move_feature_off.png differ
diff --git a/media/img/openlayers/move_feature_on.png b/media/img/openlayers/move_feature_on.png
new file mode 100644
index 0000000..072f066
Binary files /dev/null and b/media/img/openlayers/move_feature_on.png differ
diff --git a/media/img/openlayers/navigation_history.png b/media/img/openlayers/navigation_history.png
new file mode 100644
index 0000000..053d1e0
Binary files /dev/null and b/media/img/openlayers/navigation_history.png differ
diff --git a/media/img/openlayers/north-mini.png b/media/img/openlayers/north-mini.png
new file mode 100644
index 0000000..dfd7211
Binary files /dev/null and b/media/img/openlayers/north-mini.png differ
diff --git a/media/img/openlayers/overview_replacement.gif b/media/img/openlayers/overview_replacement.gif
new file mode 100644
index 0000000..a82cf5f
Binary files /dev/null and b/media/img/openlayers/overview_replacement.gif differ
diff --git a/media/img/openlayers/pan-panel-NOALPHA.png b/media/img/openlayers/pan-panel-NOALPHA.png
new file mode 100644
index 0000000..2740d8b
Binary files /dev/null and b/media/img/openlayers/pan-panel-NOALPHA.png differ
diff --git a/media/img/openlayers/pan-panel.png b/media/img/openlayers/pan-panel.png
new file mode 100644
index 0000000..9910121
Binary files /dev/null and b/media/img/openlayers/pan-panel.png differ
diff --git a/media/img/openlayers/pan_off.png b/media/img/openlayers/pan_off.png
new file mode 100644
index 0000000..30b2aed
Binary files /dev/null and b/media/img/openlayers/pan_off.png differ
diff --git a/media/img/openlayers/pan_on.png b/media/img/openlayers/pan_on.png
new file mode 100644
index 0000000..d73e7dd
Binary files /dev/null and b/media/img/openlayers/pan_on.png differ
diff --git a/media/img/openlayers/panning-hand-off.png b/media/img/openlayers/panning-hand-off.png
new file mode 100644
index 0000000..d1c593e
Binary files /dev/null and b/media/img/openlayers/panning-hand-off.png differ
diff --git a/media/img/openlayers/panning-hand-on.png b/media/img/openlayers/panning-hand-on.png
new file mode 100644
index 0000000..9b7e064
Binary files /dev/null and b/media/img/openlayers/panning-hand-on.png differ
diff --git a/media/img/openlayers/remove_point_off.png b/media/img/openlayers/remove_point_off.png
new file mode 100644
index 0000000..76c8606
Binary files /dev/null and b/media/img/openlayers/remove_point_off.png differ
diff --git a/media/img/openlayers/remove_point_on.png b/media/img/openlayers/remove_point_on.png
new file mode 100644
index 0000000..0ff28fc
Binary files /dev/null and b/media/img/openlayers/remove_point_on.png differ
diff --git a/media/img/openlayers/ruler.png b/media/img/openlayers/ruler.png
new file mode 100644
index 0000000..aa4883b
Binary files /dev/null and b/media/img/openlayers/ruler.png differ
diff --git a/media/img/openlayers/save_features_off.png b/media/img/openlayers/save_features_off.png
new file mode 100644
index 0000000..2bf2906
Binary files /dev/null and b/media/img/openlayers/save_features_off.png differ
diff --git a/media/img/openlayers/save_features_on.png b/media/img/openlayers/save_features_on.png
new file mode 100644
index 0000000..93c8f08
Binary files /dev/null and b/media/img/openlayers/save_features_on.png differ
diff --git a/media/img/openlayers/slider.png b/media/img/openlayers/slider.png
new file mode 100644
index 0000000..4335364
Binary files /dev/null and b/media/img/openlayers/slider.png differ
diff --git a/media/img/openlayers/south-mini.png b/media/img/openlayers/south-mini.png
new file mode 100644
index 0000000..2970875
Binary files /dev/null and b/media/img/openlayers/south-mini.png differ
diff --git a/media/img/openlayers/view_next_off.png b/media/img/openlayers/view_next_off.png
new file mode 100644
index 0000000..23c5ac1
Binary files /dev/null and b/media/img/openlayers/view_next_off.png differ
diff --git a/media/img/openlayers/view_next_on.png b/media/img/openlayers/view_next_on.png
new file mode 100644
index 0000000..e41fb7b
Binary files /dev/null and b/media/img/openlayers/view_next_on.png differ
diff --git a/media/img/openlayers/view_previous_off.png b/media/img/openlayers/view_previous_off.png
new file mode 100644
index 0000000..b9c230f
Binary files /dev/null and b/media/img/openlayers/view_previous_off.png differ
diff --git a/media/img/openlayers/view_previous_on.png b/media/img/openlayers/view_previous_on.png
new file mode 100644
index 0000000..c009c25
Binary files /dev/null and b/media/img/openlayers/view_previous_on.png differ
diff --git a/media/img/openlayers/west-mini.png b/media/img/openlayers/west-mini.png
new file mode 100644
index 0000000..363cd3d
Binary files /dev/null and b/media/img/openlayers/west-mini.png differ
diff --git a/media/img/openlayers/zoom-minus-mini.png b/media/img/openlayers/zoom-minus-mini.png
new file mode 100644
index 0000000..8f0d77f
Binary files /dev/null and b/media/img/openlayers/zoom-minus-mini.png differ
diff --git a/media/img/openlayers/zoom-panel-NOALPHA.png b/media/img/openlayers/zoom-panel-NOALPHA.png
new file mode 100644
index 0000000..cdde6fc
Binary files /dev/null and b/media/img/openlayers/zoom-panel-NOALPHA.png differ
diff --git a/media/img/openlayers/zoom-panel.png b/media/img/openlayers/zoom-panel.png
new file mode 100644
index 0000000..f2c7c51
Binary files /dev/null and b/media/img/openlayers/zoom-panel.png differ
diff --git a/media/img/openlayers/zoom-plus-mini.png b/media/img/openlayers/zoom-plus-mini.png
new file mode 100644
index 0000000..a73ab4e
Binary files /dev/null and b/media/img/openlayers/zoom-plus-mini.png differ
diff --git a/media/img/openlayers/zoom-world-mini.png b/media/img/openlayers/zoom-world-mini.png
new file mode 100644
index 0000000..aebf22d
Binary files /dev/null and b/media/img/openlayers/zoom-world-mini.png differ
diff --git a/media/img/openlayers/zoombar.png b/media/img/openlayers/zoombar.png
new file mode 100644
index 0000000..47110ab
Binary files /dev/null and b/media/img/openlayers/zoombar.png differ
diff --git a/media/img/pin_trans.png b/media/img/pin_trans.png
new file mode 100644
index 0000000..75d97cf
Binary files /dev/null and b/media/img/pin_trans.png differ
diff --git a/media/img/range-slider.jpg b/media/img/range-slider.jpg
new file mode 100644
index 0000000..1a00157
Binary files /dev/null and b/media/img/range-slider.jpg differ
diff --git a/media/img/red-btn-l.gif b/media/img/red-btn-l.gif
new file mode 100644
index 0000000..c3637a4
Binary files /dev/null and b/media/img/red-btn-l.gif differ
diff --git a/media/img/red-btn-r.gif b/media/img/red-btn-r.gif
new file mode 100644
index 0000000..fbebb89
Binary files /dev/null and b/media/img/red-btn-r.gif differ
diff --git a/media/img/report-map.jpg b/media/img/report-map.jpg
new file mode 100644
index 0000000..2990956
Binary files /dev/null and b/media/img/report-map.jpg differ
diff --git a/media/img/report-thumb-default.jpg b/media/img/report-thumb-default.jpg
new file mode 100644
index 0000000..9ab9465
Binary files /dev/null and b/media/img/report-thumb-default.jpg differ
diff --git a/media/img/slider-bg-1.png b/media/img/slider-bg-1.png
new file mode 100644
index 0000000..b7d806e
Binary files /dev/null and b/media/img/slider-bg-1.png differ
diff --git a/media/img/slider-bg-2.png b/media/img/slider-bg-2.png
new file mode 100644
index 0000000..8b24cf0
Binary files /dev/null and b/media/img/slider-bg-2.png differ
diff --git a/media/img/slider-handle-inactive.gif b/media/img/slider-handle-inactive.gif
new file mode 100644
index 0000000..b7aed25
Binary files /dev/null and b/media/img/slider-handle-inactive.gif differ
diff --git a/media/img/slider-handle.gif b/media/img/slider-handle.gif
new file mode 100644
index 0000000..9b89f26
Binary files /dev/null and b/media/img/slider-handle.gif differ
diff --git a/media/img/small-block-bg.gif b/media/img/small-block-bg.gif
new file mode 100644
index 0000000..97d5ca2
Binary files /dev/null and b/media/img/small-block-bg.gif differ
diff --git a/media/img/small-block-bottom.gif b/media/img/small-block-bottom.gif
new file mode 100644
index 0000000..f369d18
Binary files /dev/null and b/media/img/small-block-bottom.gif differ
diff --git a/media/img/small-block-top.gif b/media/img/small-block-top.gif
new file mode 100644
index 0000000..8d1beef
Binary files /dev/null and b/media/img/small-block-top.gif differ
diff --git a/media/img/small-map.gif b/media/img/small-map.gif
new file mode 100644
index 0000000..68e5ae2
Binary files /dev/null and b/media/img/small-map.gif differ
diff --git a/media/img/spacer.gif b/media/img/spacer.gif
new file mode 100644
index 0000000..de1b7be
Binary files /dev/null and b/media/img/spacer.gif differ
diff --git a/media/img/submit-incident.jpg b/media/img/submit-incident.jpg
new file mode 100644
index 0000000..5d44c93
Binary files /dev/null and b/media/img/submit-incident.jpg differ
diff --git a/media/img/themeroller/ui-bg_flat_0_aaaaaa_40x100.png b/media/img/themeroller/ui-bg_flat_0_aaaaaa_40x100.png
new file mode 100644
index 0000000..5b5dab2
Binary files /dev/null and b/media/img/themeroller/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/media/img/themeroller/ui-bg_flat_75_ffffff_40x100.png b/media/img/themeroller/ui-bg_flat_75_ffffff_40x100.png
new file mode 100644
index 0000000..ac8b229
Binary files /dev/null and b/media/img/themeroller/ui-bg_flat_75_ffffff_40x100.png differ
diff --git a/media/img/themeroller/ui-bg_glass_55_fbf9ee_1x400.png b/media/img/themeroller/ui-bg_glass_55_fbf9ee_1x400.png
new file mode 100644
index 0000000..ad3d634
Binary files /dev/null and b/media/img/themeroller/ui-bg_glass_55_fbf9ee_1x400.png differ
diff --git a/media/img/themeroller/ui-bg_glass_65_ffffff_1x400.png b/media/img/themeroller/ui-bg_glass_65_ffffff_1x400.png
new file mode 100644
index 0000000..42ccba2
Binary files /dev/null and b/media/img/themeroller/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/media/img/themeroller/ui-bg_glass_75_dadada_1x400.png b/media/img/themeroller/ui-bg_glass_75_dadada_1x400.png
new file mode 100644
index 0000000..5a46b47
Binary files /dev/null and b/media/img/themeroller/ui-bg_glass_75_dadada_1x400.png differ
diff --git a/media/img/themeroller/ui-bg_glass_75_e6e6e6_1x400.png b/media/img/themeroller/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100644
index 0000000..86c2baa
Binary files /dev/null and b/media/img/themeroller/ui-bg_glass_75_e6e6e6_1x400.png differ
diff --git a/media/img/themeroller/ui-bg_glass_95_fef1ec_1x400.png b/media/img/themeroller/ui-bg_glass_95_fef1ec_1x400.png
new file mode 100644
index 0000000..4443fdc
Binary files /dev/null and b/media/img/themeroller/ui-bg_glass_95_fef1ec_1x400.png differ
diff --git a/media/img/themeroller/ui-bg_highlight-soft_75_cccccc_1x100.png b/media/img/themeroller/ui-bg_highlight-soft_75_cccccc_1x100.png
new file mode 100644
index 0000000..7c9fa6c
Binary files /dev/null and b/media/img/themeroller/ui-bg_highlight-soft_75_cccccc_1x100.png differ
diff --git a/media/img/themeroller/ui-icons_222222_256x240.png b/media/img/themeroller/ui-icons_222222_256x240.png
new file mode 100644
index 0000000..ee039dc
Binary files /dev/null and b/media/img/themeroller/ui-icons_222222_256x240.png differ
diff --git a/media/img/themeroller/ui-icons_2e83ff_256x240.png b/media/img/themeroller/ui-icons_2e83ff_256x240.png
new file mode 100644
index 0000000..45e8928
Binary files /dev/null and b/media/img/themeroller/ui-icons_2e83ff_256x240.png differ
diff --git a/media/img/themeroller/ui-icons_454545_256x240.png b/media/img/themeroller/ui-icons_454545_256x240.png
new file mode 100644
index 0000000..7ec70d1
Binary files /dev/null and b/media/img/themeroller/ui-icons_454545_256x240.png differ
diff --git a/media/img/themeroller/ui-icons_888888_256x240.png b/media/img/themeroller/ui-icons_888888_256x240.png
new file mode 100644
index 0000000..5ba708c
Binary files /dev/null and b/media/img/themeroller/ui-icons_888888_256x240.png differ
diff --git a/media/img/themeroller/ui-icons_cd0a0a_256x240.png b/media/img/themeroller/ui-icons_cd0a0a_256x240.png
new file mode 100644
index 0000000..7930a55
Binary files /dev/null and b/media/img/themeroller/ui-icons_cd0a0a_256x240.png differ
diff --git a/media/img/thumb-down.jpg b/media/img/thumb-down.jpg
new file mode 100644
index 0000000..a5433ce
Binary files /dev/null and b/media/img/thumb-down.jpg differ
diff --git a/media/img/thumb-up.jpg b/media/img/thumb-up.jpg
new file mode 100644
index 0000000..dc80f50
Binary files /dev/null and b/media/img/thumb-up.jpg differ
diff --git a/media/img/thumbs.jpg b/media/img/thumbs.jpg
new file mode 100644
index 0000000..63bc84e
Binary files /dev/null and b/media/img/thumbs.jpg differ
diff --git a/media/img/tooltip.gif b/media/img/tooltip.gif
new file mode 100644
index 0000000..7f5eed4
Binary files /dev/null and b/media/img/tooltip.gif differ
diff --git a/media/img/treeview-default-line.gif b/media/img/treeview-default-line.gif
new file mode 100644
index 0000000..37114d3
Binary files /dev/null and b/media/img/treeview-default-line.gif differ
diff --git a/media/img/treeview-default.gif b/media/img/treeview-default.gif
new file mode 100644
index 0000000..a12ac52
Binary files /dev/null and b/media/img/treeview-default.gif differ
diff --git a/media/img/up.png b/media/img/up.png
new file mode 100755
index 0000000..0515a8a
Binary files /dev/null and b/media/img/up.png differ
diff --git a/media/img/video.png b/media/img/video.png
new file mode 100644
index 0000000..b0ce7bb
Binary files /dev/null and b/media/img/video.png differ
diff --git a/media/js/OpenLayers.js b/media/js/OpenLayers.js
new file mode 100644
index 0000000..669778d
--- /dev/null
+++ b/media/js/OpenLayers.js
@@ -0,0 +1,1443 @@
+/*
+
+  OpenLayers.js -- OpenLayers Map Viewer Library
+
+  Copyright (c) 2006-2013 by OpenLayers Contributors
+  Published under the 2-clause BSD license.
+  See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+  Includes compressed code under the following licenses:
+
+  (For uncompressed versions of the code used, please see the
+  OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission of Yahoo! Inc.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+var OpenLayers={VERSION_NUMBER:"Release 2.13.1",singleFile:!0,_getScriptLocation:function(){for(var a=/(^|(.*?\/))(OpenLayers[^\/]*?\.js)(\?|$)/,b=document.getElementsByTagName("script"),c,d="",e=0,f=b.length;e<f;e++)if(c=b[e].getAttribute("src"))if(c=c.match(a)){d=c[1];break}return function(){return d}}(),ImgPath:""};OpenLayers.Class=function(){var a=arguments.length,b=arguments[0],c=arguments[a-1],d="function"==typeof c.initialize?c.initialize:function(){b.prototype.initialize.apply(th [...]
+OpenLayers.inherit=function(a,b){var c=function(){};c.prototype=b.prototype;a.prototype=new c;var d,e,c=2;for(d=arguments.length;c<d;c++)e=arguments[c],"function"===typeof e&&(e=e.prototype),OpenLayers.Util.extend(a.prototype,e)};OpenLayers.Util=OpenLayers.Util||{};OpenLayers.Util.extend=function(a,b){a=a||{};if(b){for(var c in b){var d=b[c];void 0!==d&&(a[c]=d)}"function"==typeof window.Event&&b instanceof window.Event||(!b.hasOwnProperty||!b.hasOwnProperty("toString"))||(a.toString=b.t [...]
+f=f[g[h]]}"function"==typeof f&&(f=c?f.apply(null,c):f());return"undefined"==typeof f?"undefined":f})},tokenRegEx:/\$\{([\w.]+?)\}/g,numberRegEx:/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,isNumeric:function(a){return OpenLayers.String.numberRegEx.test(a)},numericIf:function(a,b){var c=a;!0===b&&(null!=a&&a.replace)&&(a=a.replace(/^\s*|\s*$/g,""));return OpenLayers.String.isNumeric(a)?parseFloat(a):c}};
+OpenLayers.Number={decimalSeparator:".",thousandsSeparator:",",limitSigDigs:function(a,b){var c=0;0<b&&(c=parseFloat(a.toPrecision(b)));return c},format:function(a,b,c,d){b="undefined"!=typeof b?b:0;c="undefined"!=typeof c?c:OpenLayers.Number.thousandsSeparator;d="undefined"!=typeof d?d:OpenLayers.Number.decimalSeparator;null!=b&&(a=parseFloat(a.toFixed(b)));var e=a.toString().split(".");1==e.length&&null==b&&(b=0);a=e[0];if(c)for(var f=/(-?[0-9]+)([0-9]{3})/;f.test(a);)a=a.replace(f,"$1 [...]
+0==b?b=a:(c=1<e.length?e[1]:"0",null!=b&&(c+=Array(b-c.length+1).join("0")),b=a+d+c);return b},zeroPad:function(a,b,c){for(a=a.toString(c||10);a.length<b;)a="0"+a;return a}};
+OpenLayers.Function={bind:function(a,b){var c=Array.prototype.slice.apply(arguments,[2]);return function(){var d=c.concat(Array.prototype.slice.apply(arguments,[0]));return a.apply(b,d)}},bindAsEventListener:function(a,b){return function(c){return a.call(b,c||window.event)}},False:function(){return!1},True:function(){return!0},Void:function(){}};
+OpenLayers.Array={filter:function(a,b,c){var d=[];if(Array.prototype.filter)d=a.filter(b,c);else{var e=a.length;if("function"!=typeof b)throw new TypeError;for(var f=0;f<e;f++)if(f in a){var g=a[f];b.call(c,g,f,a)&&d.push(g)}}return d}};OpenLayers.Bounds=OpenLayers.Class({left:null,bottom:null,right:null,top:null,centerLonLat:null,initialize:function(a,b,c,d){OpenLayers.Util.isArray(a)&&(d=a[3],c=a[2],b=a[1],a=a[0]);null!=a&&(this.left=OpenLayers.Util.toFloat(a));null!=b&&(this.bottom=Op [...]
+a&&(b=this.left==a.left&&this.right==a.right&&this.top==a.top&&this.bottom==a.bottom);return b},toString:function(){return[this.left,this.bottom,this.right,this.top].join()},toArray:function(a){return!0===a?[this.bottom,this.left,this.top,this.right]:[this.left,this.bottom,this.right,this.top]},toBBOX:function(a,b){null==a&&(a=6);var c=Math.pow(10,a),d=Math.round(this.left*c)/c,e=Math.round(this.bottom*c)/c,f=Math.round(this.right*c)/c,c=Math.round(this.top*c)/c;return!0===b?e+","+d+","+ [...]
+","+e+","+f+","+c},toGeometry:function(){return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(this.left,this.bottom),new OpenLayers.Geometry.Point(this.right,this.bottom),new OpenLayers.Geometry.Point(this.right,this.top),new OpenLayers.Geometry.Point(this.left,this.top)])])},getWidth:function(){return this.right-this.left},getHeight:function(){return this.top-this.bottom},getSize:function(){return new OpenLayers.Size(this.getWidth(),t [...]
+getCenterPixel:function(){return new OpenLayers.Pixel((this.left+this.right)/2,(this.bottom+this.top)/2)},getCenterLonLat:function(){this.centerLonLat||(this.centerLonLat=new OpenLayers.LonLat((this.left+this.right)/2,(this.bottom+this.top)/2));return this.centerLonLat},scale:function(a,b){null==b&&(b=this.getCenterLonLat());var c,d;"OpenLayers.LonLat"==b.CLASS_NAME?(c=b.lon,d=b.lat):(c=b.x,d=b.y);return new OpenLayers.Bounds((this.left-c)*a+c,(this.bottom-d)*a+d,(this.right-c)*a+c,(this [...]
+d)},add:function(a,b){if(null==a||null==b)throw new TypeError("Bounds.add cannot receive null values");return new OpenLayers.Bounds(this.left+a,this.bottom+b,this.right+a,this.top+b)},extend:function(a){if(a)switch(a.CLASS_NAME){case "OpenLayers.LonLat":this.extendXY(a.lon,a.lat);break;case "OpenLayers.Geometry.Point":this.extendXY(a.x,a.y);break;case "OpenLayers.Bounds":this.centerLonLat=null;if(null==this.left||a.left<this.left)this.left=a.left;if(null==this.bottom||a.bottom<this.botto [...]
+a.bottom;if(null==this.right||a.right>this.right)this.right=a.right;if(null==this.top||a.top>this.top)this.top=a.top}},extendXY:function(a,b){this.centerLonLat=null;if(null==this.left||a<this.left)this.left=a;if(null==this.bottom||b<this.bottom)this.bottom=b;if(null==this.right||a>this.right)this.right=a;if(null==this.top||b>this.top)this.top=b},containsLonLat:function(a,b){"boolean"===typeof b&&(b={inclusive:b});b=b||{};var c=this.contains(a.lon,a.lat,b.inclusive),d=b.worldBounds;d&&!c& [...]
+d=Math.round((a.lon-(d.left+d.right)/2)/c),c=this.containsLonLat({lon:a.lon-d*c,lat:a.lat},{inclusive:b.inclusive}));return c},containsPixel:function(a,b){return this.contains(a.x,a.y,b)},contains:function(a,b,c){null==c&&(c=!0);if(null==a||null==b)return!1;a=OpenLayers.Util.toFloat(a);b=OpenLayers.Util.toFloat(b);var d=!1;return d=c?a>=this.left&&a<=this.right&&b>=this.bottom&&b<=this.top:a>this.left&&a<this.right&&b>this.bottom&&b<this.top},intersectsBounds:function(a,b){"boolean"===ty [...]
+{inclusive:b});b=b||{};if(b.worldBounds){var c=this.wrapDateLine(b.worldBounds);a=a.wrapDateLine(b.worldBounds)}else c=this;null==b.inclusive&&(b.inclusive=!0);var d=!1,e=c.left==a.right||c.right==a.left||c.top==a.bottom||c.bottom==a.top;if(b.inclusive||!e)var d=a.top>=c.bottom&&a.top<=c.top||c.top>a.bottom&&c.top<a.top,e=a.left>=c.left&&a.left<=c.right||c.left>=a.left&&c.left<=a.right,f=a.right>=c.left&&a.right<=c.right||c.right>=a.left&&c.right<=a.right,d=(a.bottom>=c.bottom&&a.bottom< [...]
+a.bottom&&c.bottom<=a.top||d)&&(e||f);if(b.worldBounds&&!d){var g=b.worldBounds,e=g.getWidth(),f=!g.containsBounds(c),g=!g.containsBounds(a);f&&!g?(a=a.add(-e,0),d=c.intersectsBounds(a,{inclusive:b.inclusive})):g&&!f&&(c=c.add(-e,0),d=a.intersectsBounds(c,{inclusive:b.inclusive}))}return d},containsBounds:function(a,b,c){null==b&&(b=!1);null==c&&(c=!0);var d=this.contains(a.left,a.bottom,c),e=this.contains(a.right,a.bottom,c),f=this.contains(a.left,a.top,c);a=this.contains(a.right,a.top, [...]
+d||e||f||a:d&&e&&f&&a},determineQuadrant:function(a){var b="",c=this.getCenterLonLat(),b=b+(a.lat<c.lat?"b":"t");return b+=a.lon<c.lon?"l":"r"},transform:function(a,b){this.centerLonLat=null;var c=OpenLayers.Projection.transform({x:this.left,y:this.bottom},a,b),d=OpenLayers.Projection.transform({x:this.right,y:this.bottom},a,b),e=OpenLayers.Projection.transform({x:this.left,y:this.top},a,b),f=OpenLayers.Projection.transform({x:this.right,y:this.top},a,b);this.left=Math.min(c.x,e.x);this. [...]
+d.y);this.right=Math.max(d.x,f.x);this.top=Math.max(e.y,f.y);return this},wrapDateLine:function(a,b){b=b||{};var c=b.leftTolerance||0,d=b.rightTolerance||0,e=this.clone();if(a){for(var f=a.getWidth();e.left<a.left&&e.right-d<=a.left;)e=e.add(f,0);for(;e.left+c>=a.right&&e.right>a.right;)e=e.add(-f,0);c=e.left+c;c<a.right&&(c>a.left&&e.right-d>a.right)&&(e=e.add(-f,0))}return e},CLASS_NAME:"OpenLayers.Bounds"});
+OpenLayers.Bounds.fromString=function(a,b){var c=a.split(",");return OpenLayers.Bounds.fromArray(c,b)};OpenLayers.Bounds.fromArray=function(a,b){return!0===b?new OpenLayers.Bounds(a[1],a[0],a[3],a[2]):new OpenLayers.Bounds(a[0],a[1],a[2],a[3])};OpenLayers.Bounds.fromSize=function(a){return new OpenLayers.Bounds(0,a.h,a.w,0)};OpenLayers.Bounds.oppositeQuadrant=function(a){var b;b=""+("t"==a.charAt(0)?"b":"t");return b+="l"==a.charAt(1)?"r":"l"};OpenLayers.Element={visible:function(a){retu [...]
+addClass:function(a,b){OpenLayers.Element.hasClass(a,b)||(a.className+=(a.className?" ":"")+b);return a},removeClass:function(a,b){var c=a.className;c&&(a.className=OpenLayers.String.trim(c.replace(RegExp("(^|\\s+)"+b+"(\\s+|$)")," ")));return a},toggleClass:function(a,b){OpenLayers.Element.hasClass(a,b)?OpenLayers.Element.removeClass(a,b):OpenLayers.Element.addClass(a,b);return a},getStyle:function(a,b){a=OpenLayers.Util.getElement(a);var c=null;if(a&&a.style){c=a.style[OpenLayers.Strin [...]
+c||(document.defaultView&&document.defaultView.getComputedStyle?c=(c=document.defaultView.getComputedStyle(a,null))?c.getPropertyValue(b):null:a.currentStyle&&(c=a.currentStyle[OpenLayers.String.camelize(b)]));var d=["left","top","right","bottom"];window.opera&&(-1!=OpenLayers.Util.indexOf(d,b)&&"static"==OpenLayers.Element.getStyle(a,"position"))&&(c="auto")}return"auto"==c?null:c}};OpenLayers.LonLat=OpenLayers.Class({lon:0,lat:0,initialize:function(a,b){OpenLayers.Util.isArray(a)&&(b=a [...]
+OpenLayers.Util.toFloat(a),this.lat+OpenLayers.Util.toFloat(b))},equals:function(a){var b=!1;null!=a&&(b=this.lon==a.lon&&this.lat==a.lat||isNaN(this.lon)&&isNaN(this.lat)&&isNaN(a.lon)&&isNaN(a.lat));return b},transform:function(a,b){var c=OpenLayers.Projection.transform({x:this.lon,y:this.lat},a,b);this.lon=c.x;this.lat=c.y;return this},wrapDateLine:function(a){var b=this.clone();if(a){for(;b.lon<a.left;)b.lon+=a.getWidth();for(;b.lon>a.right;)b.lon-=a.getWidth()}return b},CLASS_NAME:" [...]
+OpenLayers.LonLat.fromString=function(a){a=a.split(",");return new OpenLayers.LonLat(a[0],a[1])};OpenLayers.LonLat.fromArray=function(a){var b=OpenLayers.Util.isArray(a);return new OpenLayers.LonLat(b&&a[0],b&&a[1])};OpenLayers.Pixel=OpenLayers.Class({x:0,y:0,initialize:function(a,b){this.x=parseFloat(a);this.y=parseFloat(b)},toString:function(){return"x="+this.x+",y="+this.y},clone:function(){return new OpenLayers.Pixel(this.x,this.y)},equals:function(a){var b=!1;null!=a&&(b=this.x==a.x [...]
+return new OpenLayers.Pixel(this.x+a,this.y+b)},offset:function(a){var b=this.clone();a&&(b=this.add(a.x,a.y));return b},CLASS_NAME:"OpenLayers.Pixel"});OpenLayers.Size=OpenLayers.Class({w:0,h:0,initialize:function(a,b){this.w=parseFloat(a);this.h=parseFloat(b)},toString:function(){return"w="+this.w+",h="+this.h},clone:function(){return new OpenLayers.Size(this.w,this.h)},equals:function(a){var b=!1;null!=a&&(b=this.w==a.w&&this.h==a.h||isNaN(this.w)&&isNaN(this.h)&&isNaN(a.w)&&isNaN(a.h [...]
+(function(){for(var a=document.getElementsByTagName("script"),b=0,c=a.length;b<c;++b)if(-1!=a[b].src.indexOf("firebug.js")&&console){OpenLayers.Util.extend(OpenLayers.Console,console);break}})();OpenLayers.Lang={code:null,defaultCode:"en",getCode:function(){OpenLayers.Lang.code||OpenLayers.Lang.setCode();return OpenLayers.Lang.code},setCode:function(a){var b;a||(a="msie"==OpenLayers.BROWSER_NAME?navigator.userLanguage:navigator.language);a=a.split("-");a[0]=a[0].toLowerCase();"object"==t [...]
+b=OpenLayers.Lang.defaultCode);OpenLayers.Lang.code=b},translate:function(a,b){var c=OpenLayers.Lang[OpenLayers.Lang.getCode()];(c=c&&c[a])||(c=a);b&&(c=OpenLayers.String.format(c,b));return c}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Util=OpenLayers.Util||{};OpenLayers.Util.getElement=function(){for(var a=[],b=0,c=arguments.length;b<c;b++){var d=arguments[b];"string"==typeof d&&(d=document.getElementById(d));if(1==arguments.length)return d;a.push(d)}return a};OpenLayers.Uti [...]
+OpenLayers.Util.indexOf=function(a,b){if("function"==typeof a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;c<d;c++)if(a[c]==b)return c;return-1};OpenLayers.Util.dotless=/\./g;
+OpenLayers.Util.modifyDOMElement=function(a,b,c,d,e,f,g,h){b&&(a.id=b.replace(OpenLayers.Util.dotless,"_"));c&&(a.style.left=c.x+"px",a.style.top=c.y+"px");d&&(a.style.width=d.w+"px",a.style.height=d.h+"px");e&&(a.style.position=e);f&&(a.style.border=f);g&&(a.style.overflow=g);0<=parseFloat(h)&&1>parseFloat(h)?(a.style.filter="alpha(opacity="+100*h+")",a.style.opacity=h):1==parseFloat(h)&&(a.style.filter="",a.style.opacity="")};
+OpenLayers.Util.createDiv=function(a,b,c,d,e,f,g,h){var k=document.createElement("div");d&&(k.style.backgroundImage="url("+d+")");a||(a=OpenLayers.Util.createUniqueID("OpenLayersDiv"));e||(e="absolute");OpenLayers.Util.modifyDOMElement(k,a,b,c,e,f,g,h);return k};
+OpenLayers.Util.createImage=function(a,b,c,d,e,f,g,h){var k=document.createElement("img");a||(a=OpenLayers.Util.createUniqueID("OpenLayersDiv"));e||(e="relative");OpenLayers.Util.modifyDOMElement(k,a,b,c,e,f,null,g);h&&(k.style.display="none",b=function(){k.style.display="";OpenLayers.Event.stopObservingElement(k)},OpenLayers.Event.observe(k,"load",b),OpenLayers.Event.observe(k,"error",b));k.style.alt=a;k.galleryImg="no";d&&(k.src=d);return k};OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;
+OpenLayers.Util.alphaHackNeeded=null;OpenLayers.Util.alphaHack=function(){if(null==OpenLayers.Util.alphaHackNeeded){var a=navigator.appVersion.split("MSIE"),a=parseFloat(a[1]),b=!1;try{b=!!document.body.filters}catch(c){}OpenLayers.Util.alphaHackNeeded=b&&5.5<=a&&7>a}return OpenLayers.Util.alphaHackNeeded};
+OpenLayers.Util.modifyAlphaImageDiv=function(a,b,c,d,e,f,g,h,k){OpenLayers.Util.modifyDOMElement(a,b,c,d,f,null,null,k);b=a.childNodes[0];e&&(b.src=e);OpenLayers.Util.modifyDOMElement(b,a.id+"_innerImage",null,d,"relative",g);OpenLayers.Util.alphaHack()&&("none"!=a.style.display&&(a.style.display="inline-block"),null==h&&(h="scale"),a.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+b.src+"', sizingMethod='"+h+"')",0<=parseFloat(a.style.opacity)&&1>parseFloat(a.sty [...]
+(a.style.filter+=" alpha(opacity="+100*a.style.opacity+")"),b.style.filter="alpha(opacity=0)")};OpenLayers.Util.createAlphaImageDiv=function(a,b,c,d,e,f,g,h,k){var l=OpenLayers.Util.createDiv();k=OpenLayers.Util.createImage(null,null,null,null,null,null,null,k);k.className="olAlphaImg";l.appendChild(k);OpenLayers.Util.modifyAlphaImageDiv(l,a,b,c,d,e,f,g,h);return l};OpenLayers.Util.upperCaseObject=function(a){var b={},c;for(c in a)b[c.toUpperCase()]=a[c];return b};
+OpenLayers.Util.applyDefaults=function(a,b){a=a||{};var c="function"==typeof window.Event&&b instanceof window.Event,d;for(d in b)if(void 0===a[d]||!c&&b.hasOwnProperty&&b.hasOwnProperty(d)&&!a.hasOwnProperty(d))a[d]=b[d];!c&&(b&&b.hasOwnProperty&&b.hasOwnProperty("toString")&&!a.hasOwnProperty("toString"))&&(a.toString=b.toString);return a};
+OpenLayers.Util.getParameterString=function(a){var b=[],c;for(c in a){var d=a[c];if(null!=d&&"function"!=typeof d){if("object"==typeof d&&d.constructor==Array){for(var e=[],f,g=0,h=d.length;g<h;g++)f=d[g],e.push(encodeURIComponent(null===f||void 0===f?"":f));d=e.join(",")}else d=encodeURIComponent(d);b.push(encodeURIComponent(c)+"="+d)}}return b.join("&")};OpenLayers.Util.urlAppend=function(a,b){var c=a;if(b)var d=(a+" ").split(/[?&]/),c=c+(" "===d.pop()?b:d.length?"&"+b:"?"+b);return c};
+OpenLayers.Util.getImagesLocation=function(){return OpenLayers.ImgPath||OpenLayers._getScriptLocation()+"img/"};OpenLayers.Util.getImageLocation=function(a){return OpenLayers.Util.getImagesLocation()+a};OpenLayers.Util.Try=function(){for(var a=null,b=0,c=arguments.length;b<c;b++){var d=arguments[b];try{a=d();break}catch(e){}}return a};
+OpenLayers.Util.getXmlNodeValue=function(a){var b=null;OpenLayers.Util.Try(function(){b=a.text;b||(b=a.textContent);b||(b=a.firstChild.nodeValue)},function(){b=a.textContent});return b};OpenLayers.Util.mouseLeft=function(a,b){for(var c=a.relatedTarget?a.relatedTarget:a.toElement;c!=b&&null!=c;)c=c.parentNode;return c!=b};OpenLayers.Util.DEFAULT_PRECISION=14;OpenLayers.Util.toFloat=function(a,b){null==b&&(b=OpenLayers.Util.DEFAULT_PRECISION);"number"!==typeof a&&(a=parseFloat(a));return 0 [...]
+OpenLayers.Util.rad=function(a){return a*Math.PI/180};OpenLayers.Util.deg=function(a){return 180*a/Math.PI};OpenLayers.Util.VincentyConstants={a:6378137,b:6356752.3142,f:1/298.257223563};
+OpenLayers.Util.distVincenty=function(a,b){for(var c=OpenLayers.Util.VincentyConstants,d=c.a,e=c.b,c=c.f,f=OpenLayers.Util.rad(b.lon-a.lon),g=Math.atan((1-c)*Math.tan(OpenLayers.Util.rad(a.lat))),h=Math.atan((1-c)*Math.tan(OpenLayers.Util.rad(b.lat))),k=Math.sin(g),g=Math.cos(g),l=Math.sin(h),h=Math.cos(h),m=f,n=2*Math.PI,p=20;1E-12<Math.abs(m-n)&&0<--p;){var q=Math.sin(m),r=Math.cos(m),s=Math.sqrt(h*q*h*q+(g*l-k*h*r)*(g*l-k*h*r));if(0==s)return 0;var r=k*l+g*h*r,t=Math.atan2(s,r),u=Math [...]
+q/s),v=Math.cos(u)*Math.cos(u),q=r-2*k*l/v,w=c/16*v*(4+c*(4-3*v)),n=m,m=f+(1-w)*c*Math.sin(u)*(t+w*s*(q+w*r*(-1+2*q*q)))}if(0==p)return NaN;d=v*(d*d-e*e)/(e*e);c=d/1024*(256+d*(-128+d*(74-47*d)));return(e*(1+d/16384*(4096+d*(-768+d*(320-175*d))))*(t-c*s*(q+c/4*(r*(-1+2*q*q)-c/6*q*(-3+4*s*s)*(-3+4*q*q))))).toFixed(3)/1E3};
+OpenLayers.Util.destinationVincenty=function(a,b,c){var d=OpenLayers.Util,e=d.VincentyConstants,f=e.a,g=e.b,h=e.f,e=a.lon;a=a.lat;var k=d.rad(b);b=Math.sin(k);k=Math.cos(k);a=(1-h)*Math.tan(d.rad(a));var l=1/Math.sqrt(1+a*a),m=a*l,n=Math.atan2(a,k);a=l*b;for(var p=1-a*a,f=p*(f*f-g*g)/(g*g),q=1+f/16384*(4096+f*(-768+f*(320-175*f))),r=f/1024*(256+f*(-128+f*(74-47*f))),f=c/(g*q),s=2*Math.PI;1E-12<Math.abs(f-s);)var t=Math.cos(2*n+f),u=Math.sin(f),v=Math.cos(f),w=r*u*(t+r/4*(v*(-1+2*t*t)-r/6 [...]
+u*u)*(-3+4*t*t))),s=f,f=c/(g*q)+w;c=m*u-l*v*k;g=Math.atan2(m*v+l*u*k,(1-h)*Math.sqrt(a*a+c*c));b=Math.atan2(u*b,l*v-m*u*k);k=h/16*p*(4+h*(4-3*p));t=b-(1-k)*h*a*(f+k*u*(t+k*v*(-1+2*t*t)));Math.atan2(a,-c);return new OpenLayers.LonLat(e+d.deg(t),d.deg(g))};
+OpenLayers.Util.getParameters=function(a,b){b=b||{};a=null===a||void 0===a?window.location.href:a;var c="";if(OpenLayers.String.contains(a,"?"))var d=a.indexOf("?")+1,c=OpenLayers.String.contains(a,"#")?a.indexOf("#"):a.length,c=a.substring(d,c);for(var d={},c=c.split(/[&;]/),e=0,f=c.length;e<f;++e){var g=c[e].split("=");if(g[0]){var h=g[0];try{h=decodeURIComponent(h)}catch(k){h=unescape(h)}g=(g[1]||"").replace(/\+/g," ");try{g=decodeURIComponent(g)}catch(l){g=unescape(g)}!1!==b.splitArg [...]
+1==g.length&&(g=g[0]);d[h]=g}}return d};OpenLayers.Util.lastSeqID=0;OpenLayers.Util.createUniqueID=function(a){a=null==a?"id_":a.replace(OpenLayers.Util.dotless,"_");OpenLayers.Util.lastSeqID+=1;return a+OpenLayers.Util.lastSeqID};OpenLayers.INCHES_PER_UNIT={inches:1,ft:12,mi:63360,m:39.37,km:39370,dd:4374754,yd:36};OpenLayers.INCHES_PER_UNIT["in"]=OpenLayers.INCHES_PER_UNIT.inches;OpenLayers.INCHES_PER_UNIT.degrees=OpenLayers.INCHES_PER_UNIT.dd;OpenLayers.INCHES_PER_UNIT.nmi=1852*OpenLa [...]
+OpenLayers.METERS_PER_INCH=0.0254000508001016;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{Inch:OpenLayers.INCHES_PER_UNIT.inches,Meter:1/OpenLayers.METERS_PER_INCH,Foot:0.3048006096012192/OpenLayers.METERS_PER_INCH,IFoot:0.3048/OpenLayers.METERS_PER_INCH,ClarkeFoot:0.3047972651151/OpenLayers.METERS_PER_INCH,SearsFoot:0.30479947153867626/OpenLayers.METERS_PER_INCH,GoldCoastFoot:0.3047997101815088/OpenLayers.METERS_PER_INCH,IInch:0.0254/OpenLayers.METERS_PER_INCH,MicroInch:2.54E-5/OpenLayers.METERS_PER_INCH,Mil:2.54E-8/OpenLaye [...]
+Centimeter:0.01/OpenLayers.METERS_PER_INCH,Kilometer:1E3/OpenLayers.METERS_PER_INCH,Yard:0.9144018288036576/OpenLayers.METERS_PER_INCH,SearsYard:0.914398414616029/OpenLayers.METERS_PER_INCH,IndianYard:0.9143985307444408/OpenLayers.METERS_PER_INCH,IndianYd37:0.91439523/OpenLayers.METERS_PER_INCH,IndianYd62:0.9143988/OpenLayers.METERS_PER_INCH,IndianYd75:0.9143985/OpenLayers.METERS_PER_INCH,IndianFoot:0.30479951/OpenLayers.METERS_PER_INCH,IndianFt37:0.30479841/OpenLayers.METERS_PER_INCH,In [...]
+OpenLayers.METERS_PER_INCH,IndianFt75:0.3047995/OpenLayers.METERS_PER_INCH,Mile:1609.3472186944373/OpenLayers.METERS_PER_INCH,IYard:0.9144/OpenLayers.METERS_PER_INCH,IMile:1609.344/OpenLayers.METERS_PER_INCH,NautM:1852/OpenLayers.METERS_PER_INCH,"Lat-66":110943.31648893273/OpenLayers.METERS_PER_INCH,"Lat-83":110946.25736872235/OpenLayers.METERS_PER_INCH,Decimeter:0.1/OpenLayers.METERS_PER_INCH,Millimeter:0.001/OpenLayers.METERS_PER_INCH,Dekameter:10/OpenLayers.METERS_PER_INCH,Decameter:1 [...]
+Hectometer:100/OpenLayers.METERS_PER_INCH,GermanMeter:1.0000135965/OpenLayers.METERS_PER_INCH,CaGrid:0.999738/OpenLayers.METERS_PER_INCH,ClarkeChain:20.1166194976/OpenLayers.METERS_PER_INCH,GunterChain:20.11684023368047/OpenLayers.METERS_PER_INCH,BenoitChain:20.116782494375872/OpenLayers.METERS_PER_INCH,SearsChain:20.11676512155/OpenLayers.METERS_PER_INCH,ClarkeLink:0.201166194976/OpenLayers.METERS_PER_INCH,GunterLink:0.2011684023368047/OpenLayers.METERS_PER_INCH,BenoitLink:0.20116782494 [...]
+SearsLink:0.2011676512155/OpenLayers.METERS_PER_INCH,Rod:5.02921005842012/OpenLayers.METERS_PER_INCH,IntnlChain:20.1168/OpenLayers.METERS_PER_INCH,IntnlLink:0.201168/OpenLayers.METERS_PER_INCH,Perch:5.02921005842012/OpenLayers.METERS_PER_INCH,Pole:5.02921005842012/OpenLayers.METERS_PER_INCH,Furlong:201.1684023368046/OpenLayers.METERS_PER_INCH,Rood:3.778266898/OpenLayers.METERS_PER_INCH,CapeFoot:0.3047972615/OpenLayers.METERS_PER_INCH,Brealey:375/OpenLayers.METERS_PER_INCH,ModAmFt:0.30481 [...]
+OpenLayers.METERS_PER_INCH,Fathom:1.8288/OpenLayers.METERS_PER_INCH,"NautM-UK":1853.184/OpenLayers.METERS_PER_INCH,"50kilometers":5E4/OpenLayers.METERS_PER_INCH,"150kilometers":15E4/OpenLayers.METERS_PER_INCH});
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{mm:OpenLayers.INCHES_PER_UNIT.Meter/1E3,cm:OpenLayers.INCHES_PER_UNIT.Meter/100,dm:100*OpenLayers.INCHES_PER_UNIT.Meter,km:1E3*OpenLayers.INCHES_PER_UNIT.Meter,kmi:OpenLayers.INCHES_PER_UNIT.nmi,fath:OpenLayers.INCHES_PER_UNIT.Fathom,ch:OpenLayers.INCHES_PER_UNIT.IntnlChain,link:OpenLayers.INCHES_PER_UNIT.IntnlLink,"us-in":OpenLayers.INCHES_PER_UNIT.inches,"us-ft":OpenLayers.INCHES_PER_UNIT.Foot,"us-yd":OpenLayers.INCHES_PER_UNIT.Yard,"u [...]
+"us-mi":OpenLayers.INCHES_PER_UNIT.Mile,"ind-yd":OpenLayers.INCHES_PER_UNIT.IndianYd37,"ind-ft":OpenLayers.INCHES_PER_UNIT.IndianFt37,"ind-ch":20.11669506/OpenLayers.METERS_PER_INCH});OpenLayers.DOTS_PER_INCH=72;OpenLayers.Util.normalizeScale=function(a){return 1<a?1/a:a};OpenLayers.Util.getResolutionFromScale=function(a,b){var c;a&&(null==b&&(b="degrees"),c=1/(OpenLayers.Util.normalizeScale(a)*OpenLayers.INCHES_PER_UNIT[b]*OpenLayers.DOTS_PER_INCH));return c};
+OpenLayers.Util.getScaleFromResolution=function(a,b){null==b&&(b="degrees");return a*OpenLayers.INCHES_PER_UNIT[b]*OpenLayers.DOTS_PER_INCH};
+OpenLayers.Util.pagePosition=function(a){var b=[0,0],c=OpenLayers.Util.getViewportElement();if(!a||a==window||a==c)return b;var d=OpenLayers.IS_GECKO&&document.getBoxObjectFor&&"absolute"==OpenLayers.Element.getStyle(a,"position")&&(""==a.style.top||""==a.style.left),e=null;if(a.getBoundingClientRect)a=a.getBoundingClientRect(),e=window.pageYOffset||c.scrollTop,b[0]=a.left+(window.pageXOffset||c.scrollLeft),b[1]=a.top+e;else if(document.getBoxObjectFor&&!d)a=document.getBoxObjectFor(a),c [...]
+b[0]=a.screenX-c.screenX,b[1]=a.screenY-c.screenY;else{b[0]=a.offsetLeft;b[1]=a.offsetTop;e=a.offsetParent;if(e!=a)for(;e;)b[0]+=e.offsetLeft,b[1]+=e.offsetTop,e=e.offsetParent;c=OpenLayers.BROWSER_NAME;if("opera"==c||"safari"==c&&"absolute"==OpenLayers.Element.getStyle(a,"position"))b[1]-=document.body.offsetTop;for(e=a.offsetParent;e&&e!=document.body;){b[0]-=e.scrollLeft;if("opera"!=c||"TR"!=e.tagName)b[1]-=e.scrollTop;e=e.offsetParent}}return b};
+OpenLayers.Util.getViewportElement=function(){var a=arguments.callee.viewportElement;void 0==a&&(a="msie"==OpenLayers.BROWSER_NAME&&"CSS1Compat"!=document.compatMode?document.body:document.documentElement,arguments.callee.viewportElement=a);return a};
+OpenLayers.Util.isEquivalentUrl=function(a,b,c){c=c||{};OpenLayers.Util.applyDefaults(c,{ignoreCase:!0,ignorePort80:!0,ignoreHash:!0,splitArgs:!1});a=OpenLayers.Util.createUrlObject(a,c);b=OpenLayers.Util.createUrlObject(b,c);for(var d in a)if("args"!==d&&a[d]!=b[d])return!1;for(d in a.args){if(a.args[d]!=b.args[d])return!1;delete b.args[d]}for(d in b.args)return!1;return!0};
+OpenLayers.Util.createUrlObject=function(a,b){b=b||{};if(!/^\w+:\/\//.test(a)){var c=window.location,d=c.port?":"+c.port:"",d=c.protocol+"//"+c.host.split(":").shift()+d;0===a.indexOf("/")?a=d+a:(c=c.pathname.split("/"),c.pop(),a=d+c.join("/")+"/"+a)}b.ignoreCase&&(a=a.toLowerCase());c=document.createElement("a");c.href=a;d={};d.host=c.host.split(":").shift();d.protocol=c.protocol;d.port=b.ignorePort80?"80"==c.port||"0"==c.port?"":c.port:""==c.port||"0"==c.port?"80":c.port;d.hash=b.ignor [...]
+c.hash?"":c.hash;var e=c.search;e||(e=a.indexOf("?"),e=-1!=e?a.substr(e):"");d.args=OpenLayers.Util.getParameters(e,{splitArgs:b.splitArgs});d.pathname="/"==c.pathname.charAt(0)?c.pathname:"/"+c.pathname;return d};OpenLayers.Util.removeTail=function(a){var b=null,b=a.indexOf("?"),c=a.indexOf("#");return b=-1==b?-1!=c?a.substr(0,c):a:-1!=c?a.substr(0,Math.min(b,c)):a.substr(0,b)};OpenLayers.IS_GECKO=function(){var a=navigator.userAgent.toLowerCase();return-1==a.indexOf("webkit")&&-1!=a.in [...]
+OpenLayers.CANVAS_SUPPORTED=function(){var a=document.createElement("canvas");return!(!a.getContext||!a.getContext("2d"))}();OpenLayers.BROWSER_NAME=function(){var a="",b=navigator.userAgent.toLowerCase();-1!=b.indexOf("opera")?a="opera":-1!=b.indexOf("msie")?a="msie":-1!=b.indexOf("safari")?a="safari":-1!=b.indexOf("mozilla")&&(a=-1!=b.indexOf("firefox")?"firefox":"mozilla");return a}();OpenLayers.Util.getBrowserName=function(){return OpenLayers.BROWSER_NAME};
+OpenLayers.Util.getRenderedDimensions=function(a,b,c){var d,e,f=document.createElement("div");f.style.visibility="hidden";for(var g=c&&c.containerElement?c.containerElement:document.body,h=!1,k=null,l=g;l&&"body"!=l.tagName.toLowerCase();){var m=OpenLayers.Element.getStyle(l,"position");if("absolute"==m){h=!0;break}else if(m&&"static"!=m)break;l=l.parentNode}!h||0!==g.clientHeight&&0!==g.clientWidth||(k=document.createElement("div"),k.style.visibility="hidden",k.style.position="absolute" [...]
+"visible",k.style.width=document.body.clientWidth+"px",k.style.height=document.body.clientHeight+"px",k.appendChild(f));f.style.position="absolute";b&&(b.w?(d=b.w,f.style.width=d+"px"):b.h&&(e=b.h,f.style.height=e+"px"));c&&c.displayClass&&(f.className=c.displayClass);b=document.createElement("div");b.innerHTML=a;b.style.overflow="visible";if(b.childNodes)for(a=0,c=b.childNodes.length;a<c;a++)b.childNodes[a].style&&(b.childNodes[a].style.overflow="visible");f.appendChild(b);k?g.appendChi [...]
+d||(d=parseInt(b.scrollWidth),f.style.width=d+"px");e||(e=parseInt(b.scrollHeight));f.removeChild(b);k?(k.removeChild(f),g.removeChild(k)):g.removeChild(f);return new OpenLayers.Size(d,e)};
+OpenLayers.Util.getScrollbarWidth=function(){var a=OpenLayers.Util._scrollbarWidth;if(null==a){var b=null,c=null,b=a=0,b=document.createElement("div");b.style.position="absolute";b.style.top="-1000px";b.style.left="-1000px";b.style.width="100px";b.style.height="50px";b.style.overflow="hidden";c=document.createElement("div");c.style.width="100%";c.style.height="200px";b.appendChild(c);document.body.appendChild(b);a=c.offsetWidth;b.style.overflow="scroll";b=c.offsetWidth;document.body.remo [...]
+OpenLayers.Util._scrollbarWidth=a-b;a=OpenLayers.Util._scrollbarWidth}return a};
+OpenLayers.Util.getFormattedLonLat=function(a,b,c){c||(c="dms");a=(a+540)%360-180;var d=Math.abs(a),e=Math.floor(d),f=d=(d-e)/(1/60),d=Math.floor(d),f=Math.round(10*((f-d)/(1/60))),f=f/10;60<=f&&(f-=60,d+=1,60<=d&&(d-=60,e+=1));10>e&&(e="0"+e);e+="\u00b0";0<=c.indexOf("dm")&&(10>d&&(d="0"+d),e+=d+"'",0<=c.indexOf("dms")&&(10>f&&(f="0"+f),e+=f+'"'));return e="lon"==b?e+(0>a?OpenLayers.i18n("W"):OpenLayers.i18n("E")):e+(0>a?OpenLayers.i18n("S"):OpenLayers.i18n("N"))};OpenLayers.Format=Open [...]
+"_"))},destroy:function(){this.events&&(this.eventListeners&&this.events.un(this.eventListeners),this.events.destroy(),this.events=null);this.eventListeners=null;this.handler&&(this.handler.destroy(),this.handler=null);if(this.handlers){for(var a in this.handlers)this.handlers.hasOwnProperty(a)&&"function"==typeof this.handlers[a].destroy&&this.handlers[a].destroy();this.handlers=null}this.map&&(this.map.removeControl(this),this.map=null);this.div=null},setMap:function(a){this.map=a;this [...]
+this.handler.setMap(a)},draw:function(a){null==this.div&&(this.div=OpenLayers.Util.createDiv(this.id),this.div.className=this.displayClass,this.allowSelection||(this.div.className+=" olControlNoSelect",this.div.setAttribute("unselectable","on",0),this.div.onselectstart=OpenLayers.Function.False),""!=this.title&&(this.div.title=this.title));null!=a&&(this.position=a.clone());this.moveTo(this.position);return this.div},moveTo:function(a){null!=a&&null!=this.div&&(this.div.style.left=a.x+"p [...]
+a.y+"px")},activate:function(){if(this.active)return!1;this.handler&&this.handler.activate();this.active=!0;this.map&&OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");this.events.triggerEvent("activate");return!0},deactivate:function(){return this.active?(this.handler&&this.handler.deactivate(),this.active=!1,this.map&&OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active"),this.events.triggerEvent("dea [...]
+!0):!1},CLASS_NAME:"OpenLayers.Control"});OpenLayers.Control.TYPE_BUTTON=1;OpenLayers.Control.TYPE_TOGGLE=2;OpenLayers.Control.TYPE_TOOL=3;OpenLayers.Event={observers:!1,KEY_SPACE:32,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(a){return a.target||a.srcElement},isSingleTouch:function(a){return a.touches&&1==a.touches.length},isMultiTouch:function(a){return a.touches&&1<a.touches.length},isLeftClick:functi [...]
+b){b||OpenLayers.Event.preventDefault(a);a.stopPropagation?a.stopPropagation():a.cancelBubble=!0},preventDefault:function(a){a.preventDefault?a.preventDefault():a.returnValue=!1},findElement:function(a,b){for(var c=OpenLayers.Event.element(a);c.parentNode&&(!c.tagName||c.tagName.toUpperCase()!=b.toUpperCase());)c=c.parentNode;return c},observe:function(a,b,c,d){a=OpenLayers.Util.getElement(a);d=d||!1;"keypress"==b&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||a.attachEvent)&&(b [...]
+this.observers||(this.observers={});if(!a._eventCacheID){var e="eventCacheID_";a.id&&(e=a.id+"_"+e);a._eventCacheID=OpenLayers.Util.createUniqueID(e)}e=a._eventCacheID;this.observers[e]||(this.observers[e]=[]);this.observers[e].push({element:a,name:b,observer:c,useCapture:d});a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},stopObservingElement:function(a){a=OpenLayers.Util.getElement(a)._eventCacheID;this._removeElementObservers(OpenLayers.Event.obser [...]
+_removeElementObservers:function(a){if(a)for(var b=a.length-1;0<=b;b--){var c=a[b];OpenLayers.Event.stopObserving.apply(this,[c.element,c.name,c.observer,c.useCapture])}},stopObserving:function(a,b,c,d){d=d||!1;a=OpenLayers.Util.getElement(a);var e=a._eventCacheID;"keypress"==b&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||a.detachEvent)&&(b="keydown");var f=!1,g=OpenLayers.Event.observers[e];if(g)for(var h=0;!f&&h<g.length;){var k=g[h];if(k.name==b&&k.observer==c&&k.useCapture [...]
+1);0==g.length&&delete OpenLayers.Event.observers[e];f=!0;break}h++}f&&(a.removeEventListener?a.removeEventListener(b,c,d):a&&a.detachEvent&&a.detachEvent("on"+b,c));return f},unloadCache:function(){if(OpenLayers.Event&&OpenLayers.Event.observers){for(var a in OpenLayers.Event.observers)OpenLayers.Event._removeElementObservers.apply(this,[OpenLayers.Event.observers[a]]);OpenLayers.Event.observers=!1}},CLASS_NAME:"OpenLayers.Event"};
+OpenLayers.Event.observe(window,"unload",OpenLayers.Event.unloadCache,!1);
+OpenLayers.Events=OpenLayers.Class({BROWSER_EVENTS:"mouseover mouseout mousedown mouseup mousemove click dblclick rightclick dblrightclick resize focus blur touchstart touchmove touchend keydown".split(" "),listeners:null,object:null,element:null,eventHandler:null,fallThrough:null,includeXY:!1,extensions:null,extensionCount:null,clearMouseListener:null,initialize:function(a,b,c,d,e){OpenLayers.Util.extend(this,e);this.object=a;this.fallThrough=d;this.listeners={};this.extensions={};this. [...]
+{};this._msTouches=[];null!=b&&this.attachToElement(b)},destroy:function(){for(var a in this.extensions)"boolean"!==typeof this.extensions[a]&&this.extensions[a].destroy();this.extensions=null;this.element&&(OpenLayers.Event.stopObservingElement(this.element),this.element.hasScrollEvent&&OpenLayers.Event.stopObserving(window,"scroll",this.clearMouseListener));this.eventHandler=this.fallThrough=this.object=this.listeners=this.element=null},addEventType:function(a){},attachToElement:functi [...]
+OpenLayers.Event.stopObservingElement(this.element):(this.eventHandler=OpenLayers.Function.bindAsEventListener(this.handleBrowserEvent,this),this.clearMouseListener=OpenLayers.Function.bind(this.clearMouseCache,this));this.element=a;for(var b=!!window.navigator.msMaxTouchPoints,c,d=0,e=this.BROWSER_EVENTS.length;d<e;d++)c=this.BROWSER_EVENTS[d],OpenLayers.Event.observe(a,c,this.eventHandler),b&&0===c.indexOf("touch")&&this.addMsTouchListener(a,c,this.eventHandler);OpenLayers.Event.observ [...]
+OpenLayers.Event.stop)},on:function(a){for(var b in a)"scope"!=b&&a.hasOwnProperty(b)&&this.register(b,a.scope,a[b])},register:function(a,b,c,d){a in OpenLayers.Events&&!this.extensions[a]&&(this.extensions[a]=new OpenLayers.Events[a](this));if(null!=c){null==b&&(b=this.object);var e=this.listeners[a];e||(e=[],this.listeners[a]=e,this.extensionCount[a]=0);b={obj:b,func:c};d?(e.splice(this.extensionCount[a],0,b),"object"===typeof d&&d.extension&&this.extensionCount[a]++):e.push(b)}},regis [...]
+b,c){this.register(a,b,c,!0)},un:function(a){for(var b in a)"scope"!=b&&a.hasOwnProperty(b)&&this.unregister(b,a.scope,a[b])},unregister:function(a,b,c){null==b&&(b=this.object);a=this.listeners[a];if(null!=a)for(var d=0,e=a.length;d<e;d++)if(a[d].obj==b&&a[d].func==c){a.splice(d,1);break}},remove:function(a){null!=this.listeners[a]&&(this.listeners[a]=[])},triggerEvent:function(a,b){var c=this.listeners[a];if(c&&0!=c.length){null==b&&(b={});b.object=this.object;b.element=this.element;b. [...]
+a);for(var c=c.slice(),d,e=0,f=c.length;e<f&&(d=c[e],d=d.func.apply(d.obj,[b]),void 0==d||!1!=d);e++);this.fallThrough||OpenLayers.Event.stop(b,!0);return d}},handleBrowserEvent:function(a){var b=a.type,c=this.listeners[b];if(c&&0!=c.length){if((c=a.touches)&&c[0]){for(var d=0,e=0,f=c.length,g,h=0;h<f;++h)g=this.getTouchClientXY(c[h]),d+=g.clientX,e+=g.clientY;a.clientX=d/f;a.clientY=e/f}this.includeXY&&(a.xy=this.getMousePosition(a));this.triggerEvent(b,a)}},getTouchClientXY:function(a) [...]
+window,c=b.pageXOffset,b=b.pageYOffset,d=a.clientX,e=a.clientY;if(0===a.pageY&&Math.floor(e)>Math.floor(a.pageY)||0===a.pageX&&Math.floor(d)>Math.floor(a.pageX))d-=c,e-=b;else if(e<a.pageY-b||d<a.pageX-c)d=a.pageX-c,e=a.pageY-b;a.olClientX=d;a.olClientY=e;return{clientX:d,clientY:e}},clearMouseCache:function(){this.element.scrolls=null;this.element.lefttop=null;this.element.offsets=null},getMousePosition:function(a){this.includeXY?this.element.hasScrollEvent||(OpenLayers.Event.observe(wi [...]
+this.clearMouseListener),this.element.hasScrollEvent=!0):this.clearMouseCache();if(!this.element.scrolls){var b=OpenLayers.Util.getViewportElement();this.element.scrolls=[window.pageXOffset||b.scrollLeft,window.pageYOffset||b.scrollTop]}this.element.lefttop||(this.element.lefttop=[document.documentElement.clientLeft||0,document.documentElement.clientTop||0]);this.element.offsets||(this.element.offsets=OpenLayers.Util.pagePosition(this.element));return new OpenLayers.Pixel(a.clientX+this. [...]
+this.element.offsets[0]-this.element.lefttop[0],a.clientY+this.element.scrolls[1]-this.element.offsets[1]-this.element.lefttop[1])},addMsTouchListener:function(a,b,c){function d(a){c(OpenLayers.Util.applyDefaults({stopPropagation:function(){for(var a=e.length-1;0<=a;--a)e[a].stopPropagation()},preventDefault:function(){for(var a=e.length-1;0<=a;--a)e[a].preventDefault()},type:b},a))}var e=this._msTouches;switch(b){case "touchstart":return this.addMsTouchListenerStart(a,b,d);case "touchen [...]
+b,d);case "touchmove":return this.addMsTouchListenerMove(a,b,d);default:throw"Unknown touch event type";}},addMsTouchListenerStart:function(a,b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerDown",function(a){for(var b=!1,g=0,h=d.length;g<h;++g)if(d[g].pointerId==a.pointerId){b=!0;break}b||d.push(a);a.touches=d.slice();c(a)});OpenLayers.Event.observe(a,"MSPointerUp",function(a){for(var b=0,c=d.length;b<c;++b)if(d[b].pointerId==a.pointerId){d.splice(b,1);break}})},addMsTouc [...]
+b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerMove",function(a){if(a.pointerType!=a.MSPOINTER_TYPE_MOUSE||0!=a.buttons)if(1!=d.length||d[0].pageX!=a.pageX||d[0].pageY!=a.pageY){for(var b=0,g=d.length;b<g;++b)if(d[b].pointerId==a.pointerId){d[b]=a;break}a.touches=d.slice();c(a)}})},addMsTouchListenerEnd:function(a,b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerUp",function(a){for(var b=0,g=d.length;b<g;++b)if(d[b].pointerId==a.pointerId){d.splice(b,1);br [...]
+d.slice();c(a)})},CLASS_NAME:"OpenLayers.Events"});OpenLayers.Events.buttonclick=OpenLayers.Class({target:null,events:"mousedown mouseup click dblclick touchstart touchmove touchend keydown".split(" "),startRegEx:/^mousedown|touchstart$/,cancelRegEx:/^touchmove$/,completeRegEx:/^mouseup|touchend$/,initialize:function(a){this.target=a;for(a=this.events.length-1;0<=a;--a)this.target.register(this.events[a],this,this.buttonClick,{extension:!0})},destroy:function(){for(var a=this.events.leng [...]
+delete this.target},getPressedButton:function(a){var b=3,c;do{if(OpenLayers.Element.hasClass(a,"olButton")){c=a;break}a=a.parentNode}while(0<--b&&a);return c},ignore:function(a){var b=3,c=!1;do{if("a"===a.nodeName.toLowerCase()){c=!0;break}a=a.parentNode}while(0<--b&&a);return c},buttonClick:function(a){var b=!0,c=OpenLayers.Event.element(a);if(c&&(OpenLayers.Event.isLeftClick(a)||!~a.type.indexOf("mouse")))if(c=this.getPressedButton(c)){if("keydown"===a.type)switch(a.keyCode){case OpenL [...]
+{buttonElement:c}),OpenLayers.Event.stop(a),b=!1}else if(this.startEvt){if(this.completeRegEx.test(a.type)){var b=OpenLayers.Util.pagePosition(c),d=OpenLayers.Util.getViewportElement(),e=window.pageYOffset||d.scrollTop;b[0]-=window.pageXOffset||d.scrollLeft;b[1]-=e;this.target.triggerEvent("buttonclick",{buttonElement:c,buttonXY:{x:this.startEvt.clientX-b[0],y:this.startEvt.clientY-b[1]}})}this.cancelRegEx.test(a.type)&&delete this.startEvt;OpenLayers.Event.stop(a);b=!1}this.startRegEx.t [...]
+(this.startEvt=a,OpenLayers.Event.stop(a),b=!1)}else b=!this.ignore(OpenLayers.Event.element(a)),delete this.startEvt;return b}});OpenLayers.Util=OpenLayers.Util||{};
+OpenLayers.Util.vendorPrefix=function(){function a(a){return a?a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()}).replace(/^ms-/,"-ms-"):null}function b(a,b){if(void 0===g[b]){var c,e=0,f=d.length,p="undefined"!==typeof a.cssText;for(g[b]=null;e<f;e++)if((c=d[e])?(p||(c=c.toLowerCase()),c=c+b.charAt(0).toUpperCase()+b.slice(1)):c=b,void 0!==a[c]){g[b]=c;break}}return g[b]}function c(a){return b(e,a)}var d=["","O","ms","Moz","Webkit"],e=document.createElement("div").style,f={},g [...]
+f[b]){var d=b.replace(/(-[\s\S])/g,function(a){return a.charAt(1).toUpperCase()}),d=c(d);f[b]=a(d)}return f[b]},js:b,style:c,cssCache:f,jsCache:g}}();OpenLayers.Animation=function(a){var b=OpenLayers.Util.vendorPrefix.js(a,"requestAnimationFrame"),c=!!b,d=function(){var c=a[b]||function(b,c){a.setTimeout(b,16)};return function(b,d){c.apply(a,[b,d])}}(),e=0,f={};return{isNative:c,requestFrame:d,start:function(a,b,c){b=0<b?b:Number.POSITIVE_INFINITY;var l=++e,m=+new Date;f[l]=function(){f[ [...]
+this.callbacks&&this.callbacks.start&&this.callbacks.start.call(this,this.begin);this.animationId=OpenLayers.Animation.start(OpenLayers.Function.bind(this.play,this))},stop:function(){this.playing&&(this.callbacks&&this.callbacks.done&&this.callbacks.done.call(this,this.finish),OpenLayers.Animation.stop(this.animationId),this.animationId=null,this.playing=!1)},play:function(){var a={},b;for(b in this.begin){var c=this.begin[b],d=this.finish[b];if(null==c||null==d||isNaN(c)||isNaN(d))thro [...]
+a[b]=this.easing.apply(this,[this.time,c,d-c,this.duration])}this.time++;this.callbacks&&this.callbacks.eachStep&&((new Date).getTime()-this.startTime)/this.time<=1E3/this.minFrameRate&&this.callbacks.eachStep.call(this,a);this.time>this.duration&&this.stop()},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(a,b,c,d){return c*a/d+b},easeOut:function(a,b,c,d){return c*a/d+b},easeInOut:function(a,b,c,d){return c*a/ [...]
+OpenLayers.Easing.Expo={easeIn:function(a,b,c,d){return 0==a?b:c*Math.pow(2,10*(a/d-1))+b},easeOut:function(a,b,c,d){return a==d?b+c:c*(-Math.pow(2,-10*a/d)+1)+b},easeInOut:function(a,b,c,d){return 0==a?b:a==d?b+c:1>(a/=d/2)?c/2*Math.pow(2,10*(a-1))+b:c/2*(-Math.pow(2,-10*--a)+2)+b},CLASS_NAME:"OpenLayers.Easing.Expo"};
+OpenLayers.Easing.Quad={easeIn:function(a,b,c,d){return c*(a/=d)*a+b},easeOut:function(a,b,c,d){return-c*(a/=d)*(a-2)+b},easeInOut:function(a,b,c,d){return 1>(a/=d/2)?c/2*a*a+b:-c/2*(--a*(a-2)-1)+b},CLASS_NAME:"OpenLayers.Easing.Quad"};OpenLayers.Projection=OpenLayers.Class({proj:null,projCode:null,titleRegEx:/\+title=[^\+]*/,initialize:function(a,b){OpenLayers.Util.extend(this,b);this.projCode=a;"object"==typeof Proj4js&&(this.proj=new Proj4js.Proj(a))},getCode:function(){return this.pr [...]
+typeof Proj4js&&this.proj.defData&&a.proj.defData?b=this.proj.defData.replace(this.titleRegEx,"")==a.proj.defData.replace(this.titleRegEx,""):a.getCode&&(b=this.getCode(),a=a.getCode(),b=b==a||!!OpenLayers.Projection.transforms[b]&&OpenLayers.Projection.transforms[b][a]===OpenLayers.Projection.nullTransform));return b},destroy:function(){delete this.proj;delete this.projCode},CLASS_NAME:"OpenLayers.Projection"});OpenLayers.Projection.transforms={};
+OpenLayers.Projection.defaults={"EPSG:4326":{units:"degrees",maxExtent:[-180,-90,180,90],yx:!0},"CRS:84":{units:"degrees",maxExtent:[-180,-90,180,90]},"EPSG:900913":{units:"m",maxExtent:[-2.003750834E7,-2.003750834E7,2.003750834E7,2.003750834E7]}};
+OpenLayers.Projection.addTransform=function(a,b,c){if(c===OpenLayers.Projection.nullTransform){var d=OpenLayers.Projection.defaults[a];d&&!OpenLayers.Projection.defaults[b]&&(OpenLayers.Projection.defaults[b]=d)}OpenLayers.Projection.transforms[a]||(OpenLayers.Projection.transforms[a]={});OpenLayers.Projection.transforms[a][b]=c};
+OpenLayers.Projection.transform=function(a,b,c){if(b&&c)if(b instanceof OpenLayers.Projection||(b=new OpenLayers.Projection(b)),c instanceof OpenLayers.Projection||(c=new OpenLayers.Projection(c)),b.proj&&c.proj)a=Proj4js.transform(b.proj,c.proj,a);else{b=b.getCode();c=c.getCode();var d=OpenLayers.Projection.transforms;if(d[b]&&d[b][c])d[b][c](a)}return a};OpenLayers.Projection.nullTransform=function(a){return a};
+(function(){function a(a){a.x=180*a.x/d;a.y=180/Math.PI*(2*Math.atan(Math.exp(a.y/d*Math.PI))-Math.PI/2);return a}function b(a){a.x=a.x*d/180;var b=Math.log(Math.tan((90+a.y)*Math.PI/360))/Math.PI*d;a.y=Math.max(-2.003750834E7,Math.min(b,2.003750834E7));return a}function c(c,d){var e=OpenLayers.Projection.addTransform,f=OpenLayers.Projection.nullTransform,g,p,q,r,s;g=0;for(p=d.length;g<p;++g)for(q=d[g],e(c,q,b),e(q,c,a),s=g+1;s<p;++s)r=d[s],e(q,r,f),e(r,q,f)}var d=2.003750834E7,e=["EPSG: [...]
+"EPSG:102113","EPSG:102100"],f=["CRS:84","urn:ogc:def:crs:EPSG:6.6:4326","EPSG:4326"],g;for(g=e.length-1;0<=g;--g)c(e[g],f);for(g=f.length-1;0<=g;--g)c(f[g],e)})();OpenLayers.Map=OpenLayers.Class({Z_INDEX_BASE:{BaseLayer:100,Overlay:325,Feature:725,Popup:750,Control:1E3},id:null,fractionalZoom:!1,events:null,allOverlays:!1,div:null,dragging:!1,size:null,viewPortDiv:null,layerContainerOrigin:null,layerContainerDiv:null,layers:null,controls:null,popups:null,baseLayer:null,center:null,resol [...]
+maxExtent:null,minExtent:null,restrictedExtent:null,numZoomLevels:16,theme:null,displayProjection:null,fallThrough:!1,autoUpdateSize:!0,eventListeners:null,panTween:null,panMethod:OpenLayers.Easing.Expo.easeOut,panDuration:50,zoomTween:null,zoomMethod:OpenLayers.Easing.Quad.easeOut,zoomDuration:20,paddingForPopups:null,layerContainerOriginPx:null,minPx:null,maxPx:null,initialize:function(a,b){1===arguments.length&&"object"===typeof a&&(a=(b=a)&&b.div);this.tileSize=new OpenLayers.Size(Op [...]
+OpenLayers.Map.TILE_HEIGHT);this.paddingForPopups=new OpenLayers.Bounds(15,15,15,15);this.theme=OpenLayers._getScriptLocation()+"theme/default/style.css";this.options=OpenLayers.Util.extend({},b);OpenLayers.Util.extend(this,b);OpenLayers.Util.applyDefaults(this,OpenLayers.Projection.defaults[this.projection instanceof OpenLayers.Projection?this.projection.projCode:this.projection]);!this.maxExtent||this.maxExtent instanceof OpenLayers.Bounds||(this.maxExtent=new OpenLayers.Bounds(this.ma [...]
+!this.minExtent||this.minExtent instanceof OpenLayers.Bounds||(this.minExtent=new OpenLayers.Bounds(this.minExtent));!this.restrictedExtent||this.restrictedExtent instanceof OpenLayers.Bounds||(this.restrictedExtent=new OpenLayers.Bounds(this.restrictedExtent));!this.center||this.center instanceof OpenLayers.LonLat||(this.center=new OpenLayers.LonLat(this.center));this.layers=[];this.id=OpenLayers.Util.createUniqueID("OpenLayers.Map_");this.div=OpenLayers.Util.getElement(a);this.div||(th [...]
+this.div.style.height="1px",this.div.style.width="1px");OpenLayers.Element.addClass(this.div,"olMap");var c=this.id+"_OpenLayers_ViewPort";this.viewPortDiv=OpenLayers.Util.createDiv(c,null,null,null,"relative",null,"hidden");this.viewPortDiv.style.width="100%";this.viewPortDiv.style.height="100%";this.viewPortDiv.className="olMapViewport";this.div.appendChild(this.viewPortDiv);this.events=new OpenLayers.Events(this,this.viewPortDiv,null,this.fallThrough,{includeXY:!0});OpenLayers.TileMan [...]
+this.tileManager&&(this.tileManager instanceof OpenLayers.TileManager||(this.tileManager=new OpenLayers.TileManager(this.tileManager)),this.tileManager.addMap(this));c=this.id+"_OpenLayers_Container";this.layerContainerDiv=OpenLayers.Util.createDiv(c);this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE.Popup-1;this.layerContainerOriginPx={x:0,y:0};this.applyTransform();this.viewPortDiv.appendChild(this.layerContainerDiv);this.updateSize();if(this.eventListeners instanceof Object)this.e [...]
+!0===this.autoUpdateSize&&(this.updateSizeDestroy=OpenLayers.Function.bind(this.updateSize,this),OpenLayers.Event.observe(window,"resize",this.updateSizeDestroy));if(this.theme){for(var c=!0,d=document.getElementsByTagName("link"),e=0,f=d.length;e<f;++e)if(OpenLayers.Util.isEquivalentUrl(d.item(e).href,this.theme)){c=!1;break}c&&(c=document.createElement("link"),c.setAttribute("rel","stylesheet"),c.setAttribute("type","text/css"),c.setAttribute("href",this.theme),document.getElementsByTa [...]
+this.controls&&(this.controls=[],null!=OpenLayers.Control&&(OpenLayers.Control.Navigation?this.controls.push(new OpenLayers.Control.Navigation):OpenLayers.Control.TouchNavigation&&this.controls.push(new OpenLayers.Control.TouchNavigation),OpenLayers.Control.Zoom?this.controls.push(new OpenLayers.Control.Zoom):OpenLayers.Control.PanZoom&&this.controls.push(new OpenLayers.Control.PanZoom),OpenLayers.Control.ArgParser&&this.controls.push(new OpenLayers.Control.ArgParser),OpenLayers.Control. [...]
+this.controls.push(new OpenLayers.Control.Attribution)));e=0;for(f=this.controls.length;e<f;e++)this.addControlToMap(this.controls[e]);this.popups=[];this.unloadDestroy=OpenLayers.Function.bind(this.destroy,this);OpenLayers.Event.observe(window,"unload",this.unloadDestroy);b&&b.layers&&(delete this.center,delete this.zoom,this.addLayers(b.layers),b.center&&!this.getCenter()&&this.setCenter(b.center,b.zoom));this.panMethod&&(this.panTween=new OpenLayers.Tween(this.panMethod));this.zoomMet [...]
+(this.zoomTween=new OpenLayers.Tween(this.zoomMethod))},getViewport:function(){return this.viewPortDiv},render:function(a){this.div=OpenLayers.Util.getElement(a);OpenLayers.Element.addClass(this.div,"olMap");this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);this.div.appendChild(this.viewPortDiv);this.updateSize()},unloadDestroy:null,updateSizeDestroy:null,destroy:function(){if(!this.unloadDestroy)return!1;this.panTween&&(this.panTween.stop(),this.panTween=null);this.zoomTween&&(t [...]
+this.zoomTween=null);OpenLayers.Event.stopObserving(window,"unload",this.unloadDestroy);this.unloadDestroy=null;this.updateSizeDestroy&&OpenLayers.Event.stopObserving(window,"resize",this.updateSizeDestroy);this.paddingForPopups=null;if(null!=this.controls){for(var a=this.controls.length-1;0<=a;--a)this.controls[a].destroy();this.controls=null}if(null!=this.layers){for(a=this.layers.length-1;0<=a;--a)this.layers[a].destroy(!1);this.layers=null}this.viewPortDiv&&this.viewPortDiv.parentNod [...]
+this.viewPortDiv=null;this.tileManager&&(this.tileManager.removeMap(this),this.tileManager=null);this.eventListeners&&(this.events.un(this.eventListeners),this.eventListeners=null);this.events.destroy();this.options=this.events=null},setOptions:function(a){var b=this.minPx&&a.restrictedExtent!=this.restrictedExtent;OpenLayers.Util.extend(this,a);b&&this.moveTo(this.getCachedCenter(),this.zoom,{forceZoomChange:!0})},getTileSize:function(){return this.tileSize},getBy:function(a,b,c){var d= [...]
+typeof c.test;return OpenLayers.Array.filter(this[a],function(a){return a[b]==c||d&&c.test(a[b])})},getLayersBy:function(a,b){return this.getBy("layers",a,b)},getLayersByName:function(a){return this.getLayersBy("name",a)},getLayersByClass:function(a){return this.getLayersBy("CLASS_NAME",a)},getControlsBy:function(a,b){return this.getBy("controls",a,b)},getControlsByClass:function(a){return this.getControlsBy("CLASS_NAME",a)},getLayer:function(a){for(var b=null,c=0,d=this.layers.length;c< [...]
+this.layers[c];if(e.id==a){b=e;break}}return b},setLayerZIndex:function(a,b){a.setZIndex(this.Z_INDEX_BASE[a.isBaseLayer?"BaseLayer":"Overlay"]+5*b)},resetLayersZIndex:function(){for(var a=0,b=this.layers.length;a<b;a++)this.setLayerZIndex(this.layers[a],a)},addLayer:function(a){for(var b=0,c=this.layers.length;b<c;b++)if(this.layers[b]==a)return!1;if(!1===this.events.triggerEvent("preaddlayer",{layer:a}))return!1;this.allOverlays&&(a.isBaseLayer=!1);a.div.className="olLayerDiv";a.div.st [...]
+"";this.setLayerZIndex(a,this.layers.length);a.isFixed?this.viewPortDiv.appendChild(a.div):this.layerContainerDiv.appendChild(a.div);this.layers.push(a);a.setMap(this);a.isBaseLayer||this.allOverlays&&!this.baseLayer?null==this.baseLayer?this.setBaseLayer(a):a.setVisibility(!1):a.redraw();this.events.triggerEvent("addlayer",{layer:a});a.events.triggerEvent("added",{map:this,layer:a});a.afterAdd();return!0},addLayers:function(a){for(var b=0,c=a.length;b<c;b++)this.addLayer(a[b])},removeLa [...]
+b){if(!1!==this.events.triggerEvent("preremovelayer",{layer:a})){null==b&&(b=!0);a.isFixed?this.viewPortDiv.removeChild(a.div):this.layerContainerDiv.removeChild(a.div);OpenLayers.Util.removeItem(this.layers,a);a.removeMap(this);a.map=null;if(this.baseLayer==a&&(this.baseLayer=null,b))for(var c=0,d=this.layers.length;c<d;c++){var e=this.layers[c];if(e.isBaseLayer||this.allOverlays){this.setBaseLayer(e);break}}this.resetLayersZIndex();this.events.triggerEvent("removelayer",{layer:a});a.ev [...]
+{map:this,layer:a})}},getNumLayers:function(){return this.layers.length},getLayerIndex:function(a){return OpenLayers.Util.indexOf(this.layers,a)},setLayerIndex:function(a,b){var c=this.getLayerIndex(a);0>b?b=0:b>this.layers.length&&(b=this.layers.length);if(c!=b){this.layers.splice(c,1);this.layers.splice(b,0,a);for(var c=0,d=this.layers.length;c<d;c++)this.setLayerZIndex(this.layers[c],c);this.events.triggerEvent("changelayer",{layer:a,property:"order"});this.allOverlays&&(0===b?this.se [...]
+this.baseLayer!==this.layers[0]&&this.setBaseLayer(this.layers[0]))}},raiseLayer:function(a,b){var c=this.getLayerIndex(a)+b;this.setLayerIndex(a,c)},setBaseLayer:function(a){if(a!=this.baseLayer&&-1!=OpenLayers.Util.indexOf(this.layers,a)){var b=this.getCachedCenter(),c=OpenLayers.Util.getResolutionFromScale(this.getScale(),a.units);null==this.baseLayer||this.allOverlays||this.baseLayer.setVisibility(!1);this.baseLayer=a;if(!this.allOverlays||this.baseLayer.visibility)this.baseLayer.set [...]
+!1===this.baseLayer.inRange&&this.baseLayer.redraw();null!=b&&(a=this.getZoomForResolution(c||this.resolution,!0),this.setCenter(b,a,!1,!0));this.events.triggerEvent("changebaselayer",{layer:this.baseLayer})}},addControl:function(a,b){this.controls.push(a);this.addControlToMap(a,b)},addControls:function(a,b){for(var c=1===arguments.length?[]:b,d=0,e=a.length;d<e;d++)this.addControl(a[d],c[d]?c[d]:null)},addControlToMap:function(a,b){a.outsideViewport=null!=a.div;this.displayProjection&&! [...]
+(a.displayProjection=this.displayProjection);a.setMap(this);var c=a.draw(b);c&&!a.outsideViewport&&(c.style.zIndex=this.Z_INDEX_BASE.Control+this.controls.length,this.viewPortDiv.appendChild(c));a.autoActivate&&a.activate()},getControl:function(a){for(var b=null,c=0,d=this.controls.length;c<d;c++){var e=this.controls[c];if(e.id==a){b=e;break}}return b},removeControl:function(a){a&&a==this.getControl(a.id)&&(a.div&&a.div.parentNode==this.viewPortDiv&&this.viewPortDiv.removeChild(a.div),Op [...]
+a))},addPopup:function(a,b){if(b)for(var c=this.popups.length-1;0<=c;--c)this.removePopup(this.popups[c]);a.map=this;this.popups.push(a);if(c=a.draw())c.style.zIndex=this.Z_INDEX_BASE.Popup+this.popups.length,this.layerContainerDiv.appendChild(c)},removePopup:function(a){OpenLayers.Util.removeItem(this.popups,a);if(a.div)try{this.layerContainerDiv.removeChild(a.div)}catch(b){}a.map=null},getSize:function(){var a=null;null!=this.size&&(a=this.size.clone());return a},updateSize:function(){ [...]
+if(a&&!isNaN(a.h)&&!isNaN(a.w)){this.events.clearMouseCache();var b=this.getSize();null==b&&(this.size=b=a);if(!a.equals(b)){this.size=a;a=0;for(b=this.layers.length;a<b;a++)this.layers[a].onMapResize();a=this.getCachedCenter();null!=this.baseLayer&&null!=a&&(b=this.getZoom(),this.zoom=null,this.setCenter(a,b))}}this.events.triggerEvent("updatesize")},getCurrentSize:function(){var a=new OpenLayers.Size(this.div.clientWidth,this.div.clientHeight);if(0==a.w&&0==a.h||isNaN(a.w)&&isNaN(a.h)) [...]
+a.h=this.div.offsetHeight;if(0==a.w&&0==a.h||isNaN(a.w)&&isNaN(a.h))a.w=parseInt(this.div.style.width),a.h=parseInt(this.div.style.height);return a},calculateBounds:function(a,b){var c=null;null==a&&(a=this.getCachedCenter());null==b&&(b=this.getResolution());if(null!=a&&null!=b)var c=this.size.w*b/2,d=this.size.h*b/2,c=new OpenLayers.Bounds(a.lon-c,a.lat-d,a.lon+c,a.lat+d);return c},getCenter:function(){var a=null,b=this.getCachedCenter();b&&(a=b.clone());return a},getCachedCenter:funct [...]
+this.size&&(this.center=this.getLonLatFromViewPortPx({x:this.size.w/2,y:this.size.h/2}));return this.center},getZoom:function(){return this.zoom},pan:function(a,b,c){c=OpenLayers.Util.applyDefaults(c,{animate:!0,dragging:!1});if(c.dragging)0==a&&0==b||this.moveByPx(a,b);else{var d=this.getViewPortPxFromLonLat(this.getCachedCenter());a=d.add(a,b);if(this.dragging||!a.equals(d))d=this.getLonLatFromViewPortPx(a),c.animate?this.panTo(d):(this.moveTo(d),this.dragging&&(this.dragging=!1,this.e [...]
+panTo:function(a){if(this.panTween&&this.getExtent().scale(this.panRatio).containsLonLat(a)){var b=this.getCachedCenter();if(!a.equals(b)){var b=this.getPixelFromLonLat(b),c=this.getPixelFromLonLat(a),d=0,e=0;this.panTween.start({x:0,y:0},{x:c.x-b.x,y:c.y-b.y},this.panDuration,{callbacks:{eachStep:OpenLayers.Function.bind(function(a){this.moveByPx(a.x-d,a.y-e);d=Math.round(a.x);e=Math.round(a.y)},this),done:OpenLayers.Function.bind(function(b){this.moveTo(a);this.dragging=!1;this.events. [...]
+this)}})}}else this.setCenter(a)},setCenter:function(a,b,c,d){this.panTween&&this.panTween.stop();this.zoomTween&&this.zoomTween.stop();this.moveTo(a,b,{dragging:c,forceZoomChange:d})},moveByPx:function(a,b){var c=this.size.w/2,d=this.size.h/2,e=c+a,f=d+b,g=this.baseLayer.wrapDateLine,h=0,k=0;this.restrictedExtent&&(h=c,k=d,g=!1);a=g||e<=this.maxPx.x-h&&e>=this.minPx.x+h?Math.round(a):0;b=f<=this.maxPx.y-k&&f>=this.minPx.y+k?Math.round(b):0;if(a||b){this.dragging||(this.dragging=!0,this. [...]
+this.center=null;a&&(this.layerContainerOriginPx.x-=a,this.minPx.x-=a,this.maxPx.x-=a);b&&(this.layerContainerOriginPx.y-=b,this.minPx.y-=b,this.maxPx.y-=b);this.applyTransform();d=0;for(e=this.layers.length;d<e;++d)c=this.layers[d],c.visibility&&(c===this.baseLayer||c.inRange)&&(c.moveByPx(a,b),c.events.triggerEvent("move"));this.events.triggerEvent("move")}},adjustZoom:function(a){if(this.baseLayer&&this.baseLayer.wrapDateLine){var b=this.baseLayer.resolutions,c=this.getMaxExtent().get [...]
+if(this.getResolutionForZoom(a)>c)if(this.fractionalZoom)a=this.getZoomForResolution(c);else for(var d=a|0,e=b.length;d<e;++d)if(b[d]<=c){a=d;break}}return a},getMinZoom:function(){return this.adjustZoom(0)},moveTo:function(a,b,c){null==a||a instanceof OpenLayers.LonLat||(a=new OpenLayers.LonLat(a));c||(c={});null!=b&&(b=parseFloat(b),this.fractionalZoom||(b=Math.round(b)));var d=b;b=this.adjustZoom(b);b!==d&&(a=this.getCenter());var d=c.dragging||this.dragging,e=c.forceZoomChange;this.g [...]
+this.isValidLonLat(a)||(a=this.maxExtent.getCenterLonLat(),this.center=a.clone());if(null!=this.restrictedExtent){null==a&&(a=this.center);null==b&&(b=this.getZoom());var f=this.getResolutionForZoom(b),f=this.calculateBounds(a,f);if(!this.restrictedExtent.containsBounds(f)){var g=this.restrictedExtent.getCenterLonLat();f.getWidth()>this.restrictedExtent.getWidth()?a=new OpenLayers.LonLat(g.lon,a.lat):f.left<this.restrictedExtent.left?a=a.add(this.restrictedExtent.left-f.left,0):f.right>t [...]
+(a=a.add(this.restrictedExtent.right-f.right,0));f.getHeight()>this.restrictedExtent.getHeight()?a=new OpenLayers.LonLat(a.lon,g.lat):f.bottom<this.restrictedExtent.bottom?a=a.add(0,this.restrictedExtent.bottom-f.bottom):f.top>this.restrictedExtent.top&&(a=a.add(0,this.restrictedExtent.top-f.top))}}e=e||this.isValidZoomLevel(b)&&b!=this.getZoom();f=this.isValidLonLat(a)&&!a.equals(this.center);if(e||f||d){d||this.events.triggerEvent("movestart",{zoomChanged:e});f&&(!e&&this.center&&this. [...]
+this.center=a.clone());a=e?this.getResolutionForZoom(b):this.getResolution();if(e||null==this.layerContainerOrigin){this.layerContainerOrigin=this.getCachedCenter();this.layerContainerOriginPx.x=0;this.layerContainerOriginPx.y=0;this.applyTransform();var f=this.getMaxExtent({restricted:!0}),h=f.getCenterLonLat(),g=this.center.lon-h.lon,h=h.lat-this.center.lat,k=Math.round(f.getWidth()/a),l=Math.round(f.getHeight()/a);this.minPx={x:(this.size.w-k)/2-g/a,y:(this.size.h-l)/2-h/a};this.maxPx [...]
+Math.round(f.getWidth()/a),y:this.minPx.y+Math.round(f.getHeight()/a)}}e&&(this.zoom=b,this.resolution=a);a=this.getExtent();this.baseLayer.visibility&&(this.baseLayer.moveTo(a,e,c.dragging),c.dragging||this.baseLayer.events.triggerEvent("moveend",{zoomChanged:e}));a=this.baseLayer.getExtent();for(b=this.layers.length-1;0<=b;--b)f=this.layers[b],f===this.baseLayer||f.isBaseLayer||(g=f.calculateInRange(),f.inRange!=g&&((f.inRange=g)||f.display(!1),this.events.triggerEvent("changelayer",{l [...]
+g&&f.visibility&&(f.moveTo(a,e,c.dragging),c.dragging||f.events.triggerEvent("moveend",{zoomChanged:e})));this.events.triggerEvent("move");d||this.events.triggerEvent("moveend");if(e){b=0;for(c=this.popups.length;b<c;b++)this.popups[b].updatePosition();this.events.triggerEvent("zoomend")}}},centerLayerContainer:function(a){var b=this.getViewPortPxFromLonLat(this.layerContainerOrigin),c=this.getViewPortPxFromLonLat(a);if(null!=b&&null!=c){var d=this.layerContainerOriginPx.x;a=this.layerCo [...]
+var e=Math.round(b.x-c.x),b=Math.round(b.y-c.y);this.applyTransform(this.layerContainerOriginPx.x=e,this.layerContainerOriginPx.y=b);d-=e;a-=b;this.minPx.x-=d;this.maxPx.x-=d;this.minPx.y-=a;this.maxPx.y-=a}},isValidZoomLevel:function(a){return null!=a&&0<=a&&a<this.getNumZoomLevels()},isValidLonLat:function(a){var b=!1;null!=a&&(b=this.getMaxExtent(),b=b.containsLonLat(a,{worldBounds:this.baseLayer.wrapDateLine&&b}));return b},getProjection:function(){var a=this.getProjectionObject();re [...]
+null},getProjectionObject:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.projection);return a},getMaxResolution:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.maxResolution);return a},getMaxExtent:function(a){var b=null;a&&a.restricted&&this.restrictedExtent?b=this.restrictedExtent:null!=this.baseLayer&&(b=this.baseLayer.maxExtent);return b},getNumZoomLevels:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.numZoomLevels);return a},getExtent:func [...]
+null;null!=this.baseLayer&&(a=this.baseLayer.getExtent());return a},getResolution:function(){var a=null;null!=this.baseLayer?a=this.baseLayer.getResolution():!0===this.allOverlays&&0<this.layers.length&&(a=this.layers[0].getResolution());return a},getUnits:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.units);return a},getScale:function(){var a=null;null!=this.baseLayer&&(a=this.getResolution(),a=OpenLayers.Util.getScaleFromResolution(a,this.baseLayer.units));return a},get [...]
+b){var c=null;null!=this.baseLayer&&(c=this.baseLayer.getZoomForExtent(a,b));return c},getResolutionForZoom:function(a){var b=null;this.baseLayer&&(b=this.baseLayer.getResolutionForZoom(a));return b},getZoomForResolution:function(a,b){var c=null;null!=this.baseLayer&&(c=this.baseLayer.getZoomForResolution(a,b));return c},zoomTo:function(a,b){var c=this;if(c.isValidZoomLevel(a))if(c.baseLayer.wrapDateLine&&(a=c.adjustZoom(a)),c.zoomTween){var d=c.getResolution(),e=c.getResolutionForZoom(a [...]
+d={scale:d/e};c.zoomTween.playing&&c.zoomTween.duration<3*c.zoomDuration?c.zoomTween.finish={scale:c.zoomTween.finish.scale*d.scale}:(b||(e=c.getSize(),b={x:e.w/2,y:e.h/2}),c.zoomTween.start(f,d,c.zoomDuration,{minFrameRate:50,callbacks:{eachStep:function(a){var d=c.layerContainerOriginPx;a=a.scale;c.applyTransform(d.x+((a-1)*(d.x-b.x)|0),d.y+((a-1)*(d.y-b.y)|0),a)},done:function(a){c.applyTransform();a=c.getResolution()/a.scale;var d=c.getZoomForResolution(a,!0);c.moveTo(c.getZoomTarget [...]
+a),d,!0)}}}))}else f=b?c.getZoomTargetCenter(b,c.getResolutionForZoom(a)):null,c.setCenter(f,a)},zoomIn:function(){this.zoomTo(this.getZoom()+1)},zoomOut:function(){this.zoomTo(this.getZoom()-1)},zoomToExtent:function(a,b){a instanceof OpenLayers.Bounds||(a=new OpenLayers.Bounds(a));var c=a.getCenterLonLat();if(this.baseLayer.wrapDateLine){c=this.getMaxExtent();for(a=a.clone();a.right<a.left;)a.right+=c.getWidth();c=a.getCenterLonLat().wrapDateLine(c)}this.setCenter(c,this.getZoomForExte [...]
+zoomToMaxExtent:function(a){a=this.getMaxExtent({restricted:a?a.restricted:!0});this.zoomToExtent(a)},zoomToScale:function(a,b){var c=OpenLayers.Util.getResolutionFromScale(a,this.baseLayer.units),d=this.size.w*c/2,c=this.size.h*c/2,e=this.getCachedCenter(),d=new OpenLayers.Bounds(e.lon-d,e.lat-c,e.lon+d,e.lat+c);this.zoomToExtent(d,b)},getLonLatFromViewPortPx:function(a){var b=null;null!=this.baseLayer&&(b=this.baseLayer.getLonLatFromViewPortPx(a));return b},getViewPortPxFromLonLat:func [...]
+null;null!=this.baseLayer&&(b=this.baseLayer.getViewPortPxFromLonLat(a));return b},getZoomTargetCenter:function(a,b){var c=null,d=this.getSize(),e=d.w/2-a.x,d=a.y-d.h/2,f=this.getLonLatFromPixel(a);f&&(c=new OpenLayers.LonLat(f.lon+e*b,f.lat+d*b));return c},getLonLatFromPixel:function(a){return this.getLonLatFromViewPortPx(a)},getPixelFromLonLat:function(a){a=this.getViewPortPxFromLonLat(a);a.x=Math.round(a.x);a.y=Math.round(a.y);return a},getGeodesicPixelSize:function(a){var b=a?this.ge [...]
+this.getCachedCenter()||new OpenLayers.LonLat(0,0),c=this.getResolution();a=b.add(-c/2,0);var d=b.add(c/2,0),e=b.add(0,-c/2),b=b.add(0,c/2),c=new OpenLayers.Projection("EPSG:4326"),f=this.getProjectionObject()||c;f.equals(c)||(a.transform(f,c),d.transform(f,c),e.transform(f,c),b.transform(f,c));return new OpenLayers.Size(OpenLayers.Util.distVincenty(a,d),OpenLayers.Util.distVincenty(e,b))},getViewPortPxFromLayerPx:function(a){var b=null;null!=a&&(b=a.add(this.layerContainerOriginPx.x,thi [...]
+return b},getLayerPxFromViewPortPx:function(a){var b=null;null!=a&&(b=a.add(-this.layerContainerOriginPx.x,-this.layerContainerOriginPx.y),isNaN(b.x)||isNaN(b.y))&&(b=null);return b},getLonLatFromLayerPx:function(a){a=this.getViewPortPxFromLayerPx(a);return this.getLonLatFromViewPortPx(a)},getLayerPxFromLonLat:function(a){a=this.getPixelFromLonLat(a);return this.getLayerPxFromViewPortPx(a)},applyTransform:function(a,b,c){c=c||1;var d=this.layerContainerOriginPx,e=1!==c;a=a||d.x;b=b||d.y; [...]
+g=this.applyTransform.transform,h=this.applyTransform.template;if(void 0===g&&(g=OpenLayers.Util.vendorPrefix.style("transform"),this.applyTransform.transform=g)){var k=OpenLayers.Element.getStyle(this.viewPortDiv,OpenLayers.Util.vendorPrefix.css("transform"));k&&"none"===k||(h=["translate3d(",",0) ","scale3d(",",1)"],f[g]=[h[0],"0,0",h[1]].join(""));h&&~f[g].indexOf(h[0])||(h=["translate(",") ","scale(",")"]);this.applyTransform.template=h}null===g||"translate3d("!==h[0]&&!0!==e?(f.left [...]
+b+"px",null!==g&&(f[g]="")):(!0===e&&"translate("===h[0]&&(a-=d.x,b-=d.y,f.left=d.x+"px",f.top=d.y+"px"),f[g]=[h[0],a,"px,",b,"px",h[1],h[2],c,",",c,h[3]].join(""))},CLASS_NAME:"OpenLayers.Map"});OpenLayers.Map.TILE_WIDTH=256;OpenLayers.Map.TILE_HEIGHT=256;OpenLayers.Handler=OpenLayers.Class({id:null,control:null,map:null,keyMask:null,active:!1,evt:null,touch:!1,initialize:function(a,b,c){OpenLayers.Util.extend(this,c);this.control=a;this.callbacks=b;(a=this.map||a.map)&&this.setMap(a);t [...]
+0)|(a.metaKey?OpenLayers.Handler.MOD_META:0))==this.keyMask},activate:function(){if(this.active)return!1;for(var a=OpenLayers.Events.prototype.BROWSER_EVENTS,b=0,c=a.length;b<c;b++)this[a[b]]&&this.register(a[b],this[a[b]]);return this.active=!0},deactivate:function(){if(!this.active)return!1;for(var a=OpenLayers.Events.prototype.BROWSER_EVENTS,b=0,c=a.length;b<c;b++)this[a[b]]&&this.unregister(a[b],this[a[b]]);this.active=this.touch=!1;return!0},startTouch:function(){if(!this.touch){thi [...]
+for(var a="mousedown mouseup mousemove click dblclick mouseout".split(" "),b=0,c=a.length;b<c;b++)this[a[b]]&&this.unregister(a[b],this[a[b]])}},callback:function(a,b){a&&this.callbacks[a]&&this.callbacks[a].apply(this.control,b)},register:function(a,b){this.map.events.registerPriority(a,this,b);this.map.events.registerPriority(a,this,this.setEvent)},unregister:function(a,b){this.map.events.unregister(a,this,b);this.map.events.unregister(a,this,this.setEvent)},setEvent:function(a){this.e [...]
+destroy:function(){this.deactivate();this.control=this.map=null},CLASS_NAME:"OpenLayers.Handler"});OpenLayers.Handler.MOD_NONE=0;OpenLayers.Handler.MOD_SHIFT=1;OpenLayers.Handler.MOD_CTRL=2;OpenLayers.Handler.MOD_ALT=4;OpenLayers.Handler.MOD_META=8;OpenLayers.Handler.Click=OpenLayers.Class(OpenLayers.Handler,{delay:300,single:!0,"double":!1,pixelTolerance:0,dblclickTolerance:13,stopSingle:!1,stopDouble:!1,timerId:null,down:null,last:null,first:null,rightclickTimerId:null,touchstart:funct [...]
+this.down=null);return!0},mousedown:function(a){this.down=this.getEventInfo(a);this.last=this.getEventInfo(a);return!0},mouseup:function(a){var b=!0;this.checkModifiers(a)&&(this.control.handleRightClicks&&OpenLayers.Event.isRightClick(a))&&(b=this.rightclick(a));return b},rightclick:function(a){if(this.passesTolerance(a)){if(null!=this.rightclickTimerId)return this.clearTimer(),this.callback("dblrightclick",[a]),!this.stopDouble;a=this["double"]?OpenLayers.Util.extend({},a):this.callbac [...]
+[a]);a=OpenLayers.Function.bind(this.delayedRightCall,this,a);this.rightclickTimerId=window.setTimeout(a,this.delay)}return!this.stopSingle},delayedRightCall:function(a){this.rightclickTimerId=null;a&&this.callback("rightclick",[a])},click:function(a){this.last||(this.last=this.getEventInfo(a));this.handleSingle(a);return!this.stopSingle},dblclick:function(a){this.handleDouble(a);return!this.stopDouble},handleDouble:function(a){this.passesDblclickTolerance(a)&&(this["double"]&&this.callb [...]
+[a]),this.clearTimer())},handleSingle:function(a){this.passesTolerance(a)&&(null!=this.timerId?(this.last.touches&&1===this.last.touches.length&&(this["double"]&&OpenLayers.Event.preventDefault(a),this.handleDouble(a)),this.last.touches&&2===this.last.touches.length||this.clearTimer()):(this.first=this.getEventInfo(a),a=this.single?OpenLayers.Util.extend({},a):null,this.queuePotentialClick(a)))},queuePotentialClick:function(a){this.timerId=window.setTimeout(OpenLayers.Function.bind(this. [...]
+this,a),this.delay)},passesTolerance:function(a){var b=!0;if(null!=this.pixelTolerance&&this.down&&this.down.xy&&(b=this.pixelTolerance>=this.down.xy.distanceTo(a.xy))&&this.touch&&this.down.touches.length===this.last.touches.length){a=0;for(var c=this.down.touches.length;a<c;++a)if(this.getTouchDistance(this.down.touches[a],this.last.touches[a])>this.pixelTolerance){b=!1;break}}return b},getTouchDistance:function(a,b){return Math.sqrt(Math.pow(a.clientX-b.clientX,2)+Math.pow(a.clientY-b [...]
+2))},passesDblclickTolerance:function(a){a=!0;this.down&&this.first&&(a=this.down.xy.distanceTo(this.first.xy)<=this.dblclickTolerance);return a},clearTimer:function(){null!=this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null);null!=this.rightclickTimerId&&(window.clearTimeout(this.rightclickTimerId),this.rightclickTimerId=null)},delayedCall:function(a){this.timerId=null;a&&this.callback("click",[a])},getEventInfo:function(a){var b;if(a.touches){var c=a.touches.length;b=Ar [...]
+e=0;e<c;e++)d=a.touches[e],b[e]={clientX:d.olClientX,clientY:d.olClientY}}return{xy:a.xy,touches:b}},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.clearTimer(),this.last=this.first=this.down=null,a=!0);return a},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:!1,stopDown:!0,dragging:!1,last:null,start:null,lastMoveEvt:null,oldOnselectstart:null,interval:0,timeoutId:null,docu [...]
+dragstart:function(a){var b=!0;this.dragging=!1;this.checkModifiers(a)&&(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))?(this.started=!0,this.last=this.start=a.xy,OpenLayers.Element.addClass(this.map.viewPortDiv,"olDragDown"),this.down(a),this.callback("down",[a.xy]),OpenLayers.Event.preventDefault(a),this.oldOnselectstart||(this.oldOnselectstart=document.onselectstart?document.onselectstart:OpenLayers.Function.True),document.onselectstart=OpenLayers.Function.False,b [...]
+(this.started=!1,this.last=this.start=null);return b},dragmove:function(a){this.lastMoveEvt=a;!this.started||(this.timeoutId||a.xy.x==this.last.x&&a.xy.y==this.last.y)||(!0===this.documentDrag&&this.documentEvents&&(a.element===document?(this.adjustXY(a),this.setEvent(a)):this.removeDocumentEvents()),0<this.interval&&(this.timeoutId=setTimeout(OpenLayers.Function.bind(this.removeTimeout,this),this.interval)),this.dragging=!0,this.move(a),this.callback("move",[a.xy]),this.oldOnselectstart [...]
+document.onselectstart,document.onselectstart=OpenLayers.Function.False),this.last=a.xy);return!0},dragend:function(a){if(this.started){!0===this.documentDrag&&this.documentEvents&&(this.adjustXY(a),this.removeDocumentEvents());var b=this.start!=this.last;this.dragging=this.started=!1;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.up(a);this.callback("up",[a.xy]);b&&this.callback("done",[a.xy]);document.onselectstart=this.oldOnselectstart}return!0},down:function(a [...]
+up:function(a){},out:function(a){},mousedown:function(a){return this.dragstart(a)},touchstart:function(a){this.startTouch();return this.dragstart(a)},mousemove:function(a){return this.dragmove(a)},touchmove:function(a){return this.dragmove(a)},removeTimeout:function(){this.timeoutId=null;this.dragging&&this.mousemove(this.lastMoveEvt)},mouseup:function(a){return this.dragend(a)},touchend:function(a){a.xy=this.last;return this.dragend(a)},mouseout:function(a){if(this.started&&OpenLayers.U [...]
+this.map.viewPortDiv))if(!0===this.documentDrag)this.addDocumentEvents();else{var b=this.start!=this.last;this.dragging=this.started=!1;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.out(a);this.callback("out",[]);b&&this.callback("done",[a.xy]);document.onselectstart&&(document.onselectstart=this.oldOnselectstart)}return!0},click:function(a){return this.start==this.last},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(th [...]
+!1,a=!0);return a},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.dragging=this.started=!1,this.last=this.start=null,a=!0,OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown"));return a},adjustXY:function(a){var b=OpenLayers.Util.pagePosition(this.map.viewPortDiv);a.xy.x-=b[0];a.xy.y-=b[1]},addDocumentEvents:function(){OpenLayers.Element.addClass(document.body,"olDragDown");this.documentEvents=!0;OpenLayers.Event.observe(do [...]
+this._docMove);OpenLayers.Event.observe(document,"mouseup",this._docUp)},removeDocumentEvents:function(){OpenLayers.Element.removeClass(document.body,"olDragDown");this.documentEvents=!1;OpenLayers.Event.stopObserving(document,"mousemove",this._docMove);OpenLayers.Event.stopObserving(document,"mouseup",this._docUp)},CLASS_NAME:"OpenLayers.Handler.Drag"});OpenLayers.Control.OverviewMap=OpenLayers.Class(OpenLayers.Control,{element:null,ovmap:null,size:{w:180,h:90},layers:null,minRectSize:1 [...]
+this.handlers.drag&&this.handlers.drag.destroy(),this.ovmap&&this.ovmap.viewPortDiv.removeChild(this.extentRectangle),this.extentRectangle=null,this.rectEvents&&(this.rectEvents.destroy(),this.rectEvents=null),this.ovmap&&(this.ovmap.destroy(),this.ovmap=null),this.element.removeChild(this.mapDiv),this.mapDiv=null,this.div.removeChild(this.element),this.element=null,this.maximizeDiv&&(this.div.removeChild(this.maximizeDiv),this.maximizeDiv=null),this.minimizeDiv&&(this.div.removeChild(th [...]
+this.minimizeDiv=null),this.map.events.un({buttonclick:this.onButtonClick,moveend:this.update,changebaselayer:this.baseLayerDraw,scope:this}),OpenLayers.Control.prototype.destroy.apply(this,arguments))},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(0===this.layers.length)if(this.map.baseLayer)this.layers=[this.map.baseLayer.clone()];else return this.map.events.register("changebaselayer",this,this.baseLayerDraw),this.div;this.element=document.createElement("di [...]
+this.displayClass+"Element";this.element.style.display="none";this.mapDiv=document.createElement("div");this.mapDiv.style.width=this.size.w+"px";this.mapDiv.style.height=this.size.h+"px";this.mapDiv.style.position="relative";this.mapDiv.style.overflow="hidden";this.mapDiv.id=OpenLayers.Util.createUniqueID("overviewMap");this.extentRectangle=document.createElement("div");this.extentRectangle.style.position="absolute";this.extentRectangle.style.zIndex=1E3;this.extentRectangle.className=thi [...]
+"ExtentRectangle";this.element.appendChild(this.mapDiv);this.div.appendChild(this.element);if(this.outsideViewport)this.element.style.display="";else{this.div.className+=" "+this.displayClass+"Container";var a=OpenLayers.Util.getImageLocation("layer-switcher-maximize.png");this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv(this.displayClass+"MaximizeButton",null,null,a,"absolute");this.maximizeDiv.style.display="none";this.maximizeDiv.className=this.displayClass+"MaximizeButton olButto [...]
+(this.maximizeDiv.title=this.maximizeTitle);this.div.appendChild(this.maximizeDiv);a=OpenLayers.Util.getImageLocation("layer-switcher-minimize.png");this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_minimizeDiv",null,null,a,"absolute");this.minimizeDiv.style.display="none";this.minimizeDiv.className=this.displayClass+"MinimizeButton olButton";this.minimizeTitle&&(this.minimizeDiv.title=this.minimizeTitle);this.div.appendChild(this.minimizeDiv);this.minimizeControl( [...]
+this.update();this.map.events.on({buttonclick:this.onButtonClick,moveend:this.update,scope:this});this.maximized&&this.maximizeControl();return this.div},baseLayerDraw:function(){this.draw();this.map.events.unregister("changebaselayer",this,this.baseLayerDraw)},rectDrag:function(a){var b=this.handlers.drag.last.x-a.x,c=this.handlers.drag.last.y-a.y;if(0!=b||0!=c){var d=this.rectPxBounds.top,e=this.rectPxBounds.left;a=Math.abs(this.rectPxBounds.getHeight());var f=this.rectPxBounds.getWidt [...]
+d-c),c=Math.min(c,this.ovmap.size.h-this.hComp-a),b=Math.max(0,e-b),b=Math.min(b,this.ovmap.size.w-this.wComp-f);this.setRectPxBounds(new OpenLayers.Bounds(b,c+a,b+f,c))}},mapDivClick:function(a){var b=this.rectPxBounds.getCenterPixel(),c=a.xy.x-b.x,d=a.xy.y-b.y,e=this.rectPxBounds.top,f=this.rectPxBounds.left;a=Math.abs(this.rectPxBounds.getHeight());b=this.rectPxBounds.getWidth();d=Math.max(0,e+d);d=Math.min(d,this.ovmap.size.h-a);c=Math.max(0,f+c);c=Math.min(c,this.ovmap.size.w-b);thi [...]
+d+a,c+b,d));this.updateMapToRect()},onButtonClick:function(a){a.buttonElement===this.minimizeDiv?this.minimizeControl():a.buttonElement===this.maximizeDiv&&this.maximizeControl()},maximizeControl:function(a){this.element.style.display="";this.showToggle(!1);null!=a&&OpenLayers.Event.stop(a)},minimizeControl:function(a){this.element.style.display="none";this.showToggle(!0);null!=a&&OpenLayers.Event.stop(a)},showToggle:function(a){this.maximizeDiv&&(this.maximizeDiv.style.display=a?"":"non [...]
+(this.minimizeDiv.style.display=a?"none":"")},update:function(){null==this.ovmap&&this.createMap();!this.autoPan&&this.isSuitableOverview()||this.updateOverview();this.updateRectToMap()},isSuitableOverview:function(){var a=this.map.getExtent(),b=this.map.getMaxExtent(),a=new OpenLayers.Bounds(Math.max(a.left,b.left),Math.max(a.bottom,b.bottom),Math.min(a.right,b.right),Math.min(a.top,b.top));this.ovmap.getProjection()!=this.map.getProjection()&&(a=a.transform(this.map.getProjectionObject [...]
+b=this.ovmap.getResolution()/this.map.getResolution();return b>this.minRatio&&b<=this.maxRatio&&this.ovmap.getExtent().containsBounds(a)},updateOverview:function(){var a=this.map.getResolution(),b=this.ovmap.getResolution(),c=b/a;c>this.maxRatio?b=this.minRatio*a:c<=this.minRatio&&(b=this.maxRatio*a);this.ovmap.getProjection()!=this.map.getProjection()?(a=this.map.center.clone(),a.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject())):a=this.map.center;this.ovmap.set [...]
+this.ovmap.getZoomForResolution(b*this.resolutionFactor));this.updateRectToMap()},createMap:function(){var a=OpenLayers.Util.extend({controls:[],maxResolution:"auto",fallThrough:!1},this.mapOptions);this.ovmap=new OpenLayers.Map(this.mapDiv,a);this.ovmap.viewPortDiv.appendChild(this.extentRectangle);OpenLayers.Event.stopObserving(window,"unload",this.ovmap.unloadDestroy);this.ovmap.addLayers(this.layers);this.ovmap.zoomToMaxExtent();this.wComp=(this.wComp=parseInt(OpenLayers.Element.getS [...]
+"border-left-width"))+parseInt(OpenLayers.Element.getStyle(this.extentRectangle,"border-right-width")))?this.wComp:2;this.hComp=(this.hComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,"border-top-width"))+parseInt(OpenLayers.Element.getStyle(this.extentRectangle,"border-bottom-width")))?this.hComp:2;this.handlers.drag=new OpenLayers.Handler.Drag(this,{move:this.rectDrag,done:this.updateMapToRect},{map:this.ovmap});this.handlers.click=new OpenLayers.Handler.Click(this,{click [...]
+{single:!0,"double":!1,stopSingle:!0,stopDouble:!0,pixelTolerance:1,map:this.ovmap});this.handlers.click.activate();this.rectEvents=new OpenLayers.Events(this,this.extentRectangle,null,!0);this.rectEvents.register("mouseover",this,function(a){this.handlers.drag.active||this.map.dragging||this.handlers.drag.activate()});this.rectEvents.register("mouseout",this,function(a){this.handlers.drag.dragging||this.handlers.drag.deactivate()});if(this.ovmap.getProjection()!=this.map.getProjection() [...]
+this.map.units||this.map.baseLayer.units,b=this.ovmap.getProjectionObject().getUnits()||this.ovmap.units||this.ovmap.baseLayer.units;this.resolutionFactor=a&&b?OpenLayers.INCHES_PER_UNIT[a]/OpenLayers.INCHES_PER_UNIT[b]:1}},updateRectToMap:function(){var a;a=this.ovmap.getProjection()!=this.map.getProjection()?this.map.getExtent().transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject()):this.map.getExtent();(a=this.getRectBoundsFromMapBounds(a))&&this.setRectPxBounds(a) [...]
+this.getMapBoundsFromRectBounds(this.rectPxBounds);this.ovmap.getProjection()!=this.map.getProjection()&&(a=a.transform(this.ovmap.getProjectionObject(),this.map.getProjectionObject()));this.map.panTo(a.getCenterLonLat())},setRectPxBounds:function(a){var b=Math.max(a.top,0),c=Math.max(a.left,0),d=Math.min(a.top+Math.abs(a.getHeight()),this.ovmap.size.h-this.hComp);a=Math.min(a.left+a.getWidth(),this.ovmap.size.w-this.wComp);var e=Math.max(a-c,0),f=Math.max(d-b,0);e<this.minRectSize||f<th [...]
+(this.extentRectangle.className=this.displayClass+this.minRectDisplayClass,e=c+e/2-this.minRectSize/2,this.extentRectangle.style.top=Math.round(b+f/2-this.minRectSize/2)+"px",this.extentRectangle.style.left=Math.round(e)+"px",this.extentRectangle.style.height=this.minRectSize+"px",this.extentRectangle.style.width=this.minRectSize+"px"):(this.extentRectangle.className=this.displayClass+"ExtentRectangle",this.extentRectangle.style.top=Math.round(b)+"px",this.extentRectangle.style.left=Math [...]
+"px",this.extentRectangle.style.height=Math.round(f)+"px",this.extentRectangle.style.width=Math.round(e)+"px");this.rectPxBounds=new OpenLayers.Bounds(Math.round(c),Math.round(d),Math.round(a),Math.round(b))},getRectBoundsFromMapBounds:function(a){var b=this.getOverviewPxFromLonLat({lon:a.left,lat:a.bottom});a=this.getOverviewPxFromLonLat({lon:a.right,lat:a.top});var c=null;b&&a&&(c=new OpenLayers.Bounds(b.x,b.y,a.x,a.y));return c},getMapBoundsFromRectBounds:function(a){var b=this.getLon [...]
+y:a.bottom});a=this.getLonLatFromOverviewPx({x:a.right,y:a.top});return new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat)},getLonLatFromOverviewPx:function(a){var b=this.ovmap.size,c=this.ovmap.getResolution(),d=this.ovmap.getExtent().getCenterLonLat();return{lon:d.lon+(a.x-b.w/2)*c,lat:d.lat-(a.y-b.h/2)*c}},getOverviewPxFromLonLat:function(a){var b=this.ovmap.getResolution(),c=this.ovmap.getExtent();if(c)return{x:Math.round(1/b*(a.lon-c.left)),y:Math.round(1/b*(c.top-a.lat))}},CLASS_NAME:" [...]
+numZoomLevels:null,minScale:null,maxScale:null,displayOutsideMaxExtent:!1,wrapDateLine:!1,metadata:null,initialize:function(a,b){this.metadata={};b=OpenLayers.Util.extend({},b);null!=this.alwaysInRange&&(b.alwaysInRange=this.alwaysInRange);this.addOptions(b);this.name=a;if(null==this.id&&(this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_"),this.div=OpenLayers.Util.createDiv(this.id),this.div.style.width="100%",this.div.style.height="100%",this.div.dir="ltr",this.events=new OpenLa [...]
+this.div),this.eventListeners instanceof Object))this.events.on(this.eventListeners)},destroy:function(a){null==a&&(a=!0);null!=this.map&&this.map.removeLayer(this,a);this.options=this.div=this.name=this.map=this.projection=null;this.events&&(this.eventListeners&&this.events.un(this.eventListeners),this.events.destroy());this.events=this.eventListeners=null},clone:function(a){null==a&&(a=new OpenLayers.Layer(this.name,this.getOptions()));OpenLayers.Util.applyDefaults(a,this);a.map=null;r [...]
+getOptions:function(){var a={},b;for(b in this.options)a[b]=this[b];return a},setName:function(a){a!=this.name&&(this.name=a,null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"name"}))},addOptions:function(a,b){null==this.options&&(this.options={});a&&("string"==typeof a.projection&&(a.projection=new OpenLayers.Projection(a.projection)),a.projection&&OpenLayers.Util.applyDefaults(a,OpenLayers.Projection.defaults[a.projection.getCode()]),!a.maxExtent||a.maxEx [...]
+OpenLayers.Bounds||(a.maxExtent=new OpenLayers.Bounds(a.maxExtent)),!a.minExtent||a.minExtent instanceof OpenLayers.Bounds||(a.minExtent=new OpenLayers.Bounds(a.minExtent)));OpenLayers.Util.extend(this.options,a);OpenLayers.Util.extend(this,a);this.projection&&this.projection.getUnits()&&(this.units=this.projection.getUnits());if(this.map){var c=this.map.getResolution(),d=this.RESOLUTION_PROPERTIES.concat(["projection","units","minExtent","maxExtent"]),e;for(e in a)if(a.hasOwnProperty(e) [...]
+e)){this.initResolutions();b&&this.map.baseLayer===this&&(this.map.setCenter(this.map.getCenter(),this.map.getZoomForResolution(c),!1,!0),this.map.events.triggerEvent("changebaselayer",{layer:this}));break}}},onMapResize:function(){},redraw:function(){var a=!1;if(this.map){this.inRange=this.calculateInRange();var b=this.getExtent();b&&(this.inRange&&this.visibility)&&(this.moveTo(b,!0,!1),this.events.triggerEvent("moveend",{zoomChanged:!0}),a=!0)}return a},moveTo:function(a,b,c){a=this.v [...]
+this.isBaseLayer||(a=a&&this.inRange);this.display(a)},moveByPx:function(a,b){},setMap:function(a){null==this.map&&(this.map=a,this.maxExtent=this.maxExtent||this.map.maxExtent,this.minExtent=this.minExtent||this.map.minExtent,this.projection=this.projection||this.map.projection,"string"==typeof this.projection&&(this.projection=new OpenLayers.Projection(this.projection)),this.units=this.projection.getUnits()||this.units||this.map.units,this.initResolutions(),this.isBaseLayer||(this.inRa [...]
+this.div.style.display=this.visibility&&this.inRange?"":"none"),this.setTileSize())},afterAdd:function(){},removeMap:function(a){},getImageSize:function(a){return this.imageSize||this.tileSize},setTileSize:function(a){this.tileSize=a=a?a:this.tileSize?this.tileSize:this.map.getTileSize();this.gutter&&(this.imageSize=new OpenLayers.Size(a.w+2*this.gutter,a.h+2*this.gutter))},getVisibility:function(){return this.visibility},setVisibility:function(a){a!=this.visibility&&(this.visibility=a,t [...]
+this.redraw(),null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"visibility"}),this.events.triggerEvent("visibilitychanged"))},display:function(a){a!=("none"!=this.div.style.display)&&(this.div.style.display=a&&this.calculateInRange()?"block":"none")},calculateInRange:function(){var a=!1;this.alwaysInRange?a=!0:this.map&&(a=this.map.getResolution(),a=a>=this.minResolution&&a<=this.maxResolution);return a},setIsBaseLayer:function(a){a!=this.isBaseLayer&&(this [...]
+a,null!=this.map&&this.map.events.triggerEvent("changebaselayer",{layer:this}))},initResolutions:function(){var a,b,c,d={},e=!0;a=0;for(b=this.RESOLUTION_PROPERTIES.length;a<b;a++)c=this.RESOLUTION_PROPERTIES[a],d[c]=this.options[c],e&&this.options[c]&&(e=!1);null==this.options.alwaysInRange&&(this.alwaysInRange=e);null==d.resolutions&&(d.resolutions=this.resolutionsFromScales(d.scales));null==d.resolutions&&(d.resolutions=this.calculateResolutions(d));if(null==d.resolutions){a=0;for(b=t [...]
+b;a++)c=this.RESOLUTION_PROPERTIES[a],d[c]=null!=this.options[c]?this.options[c]:this.map[c];null==d.resolutions&&(d.resolutions=this.resolutionsFromScales(d.scales));null==d.resolutions&&(d.resolutions=this.calculateResolutions(d))}var f;this.options.maxResolution&&"auto"!==this.options.maxResolution&&(f=this.options.maxResolution);this.options.minScale&&(f=OpenLayers.Util.getResolutionFromScale(this.options.minScale,this.units));var g;this.options.minResolution&&"auto"!==this.options.m [...]
+(g=this.options.minResolution);this.options.maxScale&&(g=OpenLayers.Util.getResolutionFromScale(this.options.maxScale,this.units));d.resolutions&&(d.resolutions.sort(function(a,b){return b-a}),f||(f=d.resolutions[0]),g||(g=d.resolutions[d.resolutions.length-1]));if(this.resolutions=d.resolutions){b=this.resolutions.length;this.scales=Array(b);for(a=0;a<b;a++)this.scales[a]=OpenLayers.Util.getScaleFromResolution(this.resolutions[a],this.units);this.numZoomLevels=b}if(this.minResolution=g) [...]
+OpenLayers.Util.getScaleFromResolution(g,this.units);if(this.maxResolution=f)this.minScale=OpenLayers.Util.getScaleFromResolution(f,this.units)},resolutionsFromScales:function(a){if(null!=a){var b,c,d;d=a.length;b=Array(d);for(c=0;c<d;c++)b[c]=OpenLayers.Util.getResolutionFromScale(a[c],this.units);return b}},calculateResolutions:function(a){var b,c,d=a.maxResolution;null!=a.minScale?d=OpenLayers.Util.getResolutionFromScale(a.minScale,this.units):"auto"==d&&null!=this.maxExtent&&(b=this. [...]
+c=this.maxExtent.getWidth()/b.w,b=this.maxExtent.getHeight()/b.h,d=Math.max(c,b));c=a.minResolution;null!=a.maxScale?c=OpenLayers.Util.getResolutionFromScale(a.maxScale,this.units):"auto"==a.minResolution&&null!=this.minExtent&&(b=this.map.getSize(),c=this.minExtent.getWidth()/b.w,b=this.minExtent.getHeight()/b.h,c=Math.max(c,b));"number"!==typeof d&&("number"!==typeof c&&null!=this.maxExtent)&&(d=this.map.getTileSize(),d=Math.max(this.maxExtent.getWidth()/d.w,this.maxExtent.getHeight()/ [...]
+a=a.numZoomLevels;"number"===typeof c&&"number"===typeof d&&void 0===a?a=Math.floor(Math.log(d/c)/Math.log(2))+1:void 0===a&&null!=b&&(a=b+1);if(!("number"!==typeof a||0>=a||"number"!==typeof d&&"number"!==typeof c)){b=Array(a);var e=2;"number"==typeof c&&"number"==typeof d&&(e=Math.pow(d/c,1/(a-1)));var f;if("number"===typeof d)for(f=0;f<a;f++)b[f]=d/Math.pow(e,f);else for(f=0;f<a;f++)b[a-1-f]=c*Math.pow(e,f);return b}},getResolution:function(){var a=this.map.getZoom();return this.getRe [...]
+getExtent:function(){return this.map.calculateBounds()},getZoomForExtent:function(a,b){var c=this.map.getSize(),c=Math.max(a.getWidth()/c.w,a.getHeight()/c.h);return this.getZoomForResolution(c,b)},getDataExtent:function(){},getResolutionForZoom:function(a){a=Math.max(0,Math.min(a,this.resolutions.length-1));if(this.map.fractionalZoom){var b=Math.floor(a),c=Math.ceil(a);a=this.resolutions[b]-(a-b)*(this.resolutions[b]-this.resolutions[c])}else a=this.resolutions[Math.round(a)];return a}, [...]
+b){var c,d;if(this.map.fractionalZoom){var e=0,f=this.resolutions[e],g=this.resolutions[this.resolutions.length-1],h;c=0;for(d=this.resolutions.length;c<d;++c)if(h=this.resolutions[c],h>=a&&(f=h,e=c),h<=a){g=h;break}c=f-g;c=0<c?e+(f-a)/c:e}else{f=Number.POSITIVE_INFINITY;c=0;for(d=this.resolutions.length;c<d;c++)if(b){e=Math.abs(this.resolutions[c]-a);if(e>f)break;f=e}else if(this.resolutions[c]<a)break;c=Math.max(0,c-1)}return c},getLonLatFromViewPortPx:function(a){var b=null,c=this.map [...]
+a&&c.minPx){var b=c.getResolution(),d=c.getMaxExtent({restricted:!0}),b=new OpenLayers.LonLat((a.x-c.minPx.x)*b+d.left,(c.minPx.y-a.y)*b+d.top);this.wrapDateLine&&(b=b.wrapDateLine(this.maxExtent))}return b},getViewPortPxFromLonLat:function(a,b){var c=null;null!=a&&(b=b||this.map.getResolution(),c=this.map.calculateBounds(null,b),c=new OpenLayers.Pixel(1/b*(a.lon-c.left),1/b*(c.top-a.lat)));return c},setOpacity:function(a){if(a!=this.opacity){this.opacity=a;for(var b=this.div.childNodes, [...]
+d;++c){var e=b[c].firstChild||b[c],f=b[c].lastChild;f&&"iframe"===f.nodeName.toLowerCase()&&(e=f.parentNode);OpenLayers.Util.modifyDOMElement(e,null,null,null,null,null,null,a)}null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"})}},getZIndex:function(){return this.div.style.zIndex},setZIndex:function(a){this.div.style.zIndex=a},adjustBounds:function(a){if(this.gutter){var b=this.gutter*this.map.getResolution();a=new OpenLayers.Bounds(a.left-b,a.bott [...]
+b,a.top+b)}this.wrapDateLine&&(b={rightTolerance:this.getResolution(),leftTolerance:this.getResolution()},a=a.wrapDateLine(this.maxExtent,b));return a},CLASS_NAME:"OpenLayers.Layer"});OpenLayers.Layer.SphericalMercator={getExtent:function(){var a=null;return a=this.sphericalMercator?this.map.calculateBounds():OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this)},getLonLatFromViewPortPx:function(a){return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this,arguments) [...]
+156543.03390625/Math.pow(2,a);this.units="m";this.projection=this.projection||"EPSG:900913"},forwardMercator:function(){var a=new OpenLayers.Projection("EPSG:4326"),b=new OpenLayers.Projection("EPSG:900913");return function(c,d){var e=OpenLayers.Projection.transform({x:c,y:d},a,b);return new OpenLayers.LonLat(e.x,e.y)}}(),inverseMercator:function(){var a=new OpenLayers.Projection("EPSG:4326"),b=new OpenLayers.Projection("EPSG:900913");return function(c,d){var e=OpenLayers.Projection.tran [...]
+y:d},b,a);return new OpenLayers.LonLat(e.x,e.y)}}()};OpenLayers.Layer.EventPane=OpenLayers.Class(OpenLayers.Layer,{smoothDragPan:!0,isBaseLayer:!0,isFixed:!0,pane:null,mapObject:null,initialize:function(a,b){OpenLayers.Layer.prototype.initialize.apply(this,arguments);null==this.pane&&(this.pane=OpenLayers.Util.createDiv(this.div.id+"_EventPane"))},destroy:function(){this.pane=this.mapObject=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},setMap:function(a){OpenLayers.Layer [...]
+parseInt(this.div.style.zIndex)+1;this.pane.style.display=this.div.style.display;this.pane.style.width="100%";this.pane.style.height="100%";"msie"==OpenLayers.BROWSER_NAME&&(this.pane.style.background="url("+OpenLayers.Util.getImageLocation("blank.gif")+")");this.isFixed?this.map.viewPortDiv.appendChild(this.pane):this.map.layerContainerDiv.appendChild(this.pane);this.loadMapObject();null==this.mapObject&&this.loadWarningMessage()},removeMap:function(a){this.pane&&this.pane.parentNode&&t [...]
+OpenLayers.Layer.prototype.removeMap.apply(this,arguments)},loadWarningMessage:function(){this.div.style.backgroundColor="darkblue";var a=this.map.getSize(),b=Math.min(a.w,300),c=Math.min(a.h,200),b=new OpenLayers.Size(b,c),a=(new OpenLayers.Pixel(a.w/2,a.h/2)).add(-b.w/2,-b.h/2),a=OpenLayers.Util.createDiv(this.name+"_warning",a,b,null,null,null,"auto");a.style.padding="7px";a.style.backgroundColor="yellow";a.innerHTML=this.getWarningHTML();this.div.appendChild(a)},getWarningHTML:functi [...]
+display:function(a){OpenLayers.Layer.prototype.display.apply(this,arguments);this.pane.style.display=this.div.style.display},setZIndex:function(a){OpenLayers.Layer.prototype.setZIndex.apply(this,arguments);this.pane.style.zIndex=parseInt(this.div.style.zIndex)+1},moveByPx:function(a,b){OpenLayers.Layer.prototype.moveByPx.apply(this,arguments);this.dragPanMapObject?this.dragPanMapObject(a,-b):this.moveTo(this.map.getCachedCenter())},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo [...]
+arguments);if(null!=this.mapObject){var d=this.map.getCenter(),e=this.map.getZoom();if(null!=d){var f=this.getMapObjectCenter(),f=this.getOLLonLatFromMapObjectLonLat(f),g=this.getMapObjectZoom(),g=this.getOLZoomFromMapObjectZoom(g);d.equals(f)&&e==g||(!b&&f&&this.dragPanMapObject&&this.smoothDragPan?(e=this.map.getViewPortPxFromLonLat(f),d=this.map.getViewPortPxFromLonLat(d),this.dragPanMapObject(d.x-e.x,e.y-d.y)):(d=this.getMapObjectLonLatFromOLLonLat(d),e=this.getMapObjectZoomFromOLZoo [...]
+e,c)))}}},getLonLatFromViewPortPx:function(a){var b=null;null!=this.mapObject&&null!=this.getMapObjectCenter()&&(a=this.getMapObjectPixelFromOLPixel(a),a=this.getMapObjectLonLatFromMapObjectPixel(a),b=this.getOLLonLatFromMapObjectLonLat(a));return b},getViewPortPxFromLonLat:function(a){var b=null;null!=this.mapObject&&null!=this.getMapObjectCenter()&&(a=this.getMapObjectLonLatFromOLLonLat(a),a=this.getMapObjectPixelFromMapObjectLonLat(a),b=this.getOLPixelFromMapObjectPixel(a));return b}, [...]
+null;null!=a&&(b=this.getLongitudeFromMapObjectLonLat(a),a=this.getLatitudeFromMapObjectLonLat(a),b=new OpenLayers.LonLat(b,a));return b},getMapObjectLonLatFromOLLonLat:function(a){var b=null;null!=a&&(b=this.getMapObjectLonLatFromLonLat(a.lon,a.lat));return b},getOLPixelFromMapObjectPixel:function(a){var b=null;null!=a&&(b=this.getXFromMapObjectPixel(a),a=this.getYFromMapObjectPixel(a),b=new OpenLayers.Pixel(b,a));return b},getMapObjectPixelFromOLPixel:function(a){var b=null;null!=a&&(b [...]
+a.y));return b},CLASS_NAME:"OpenLayers.Layer.EventPane"});OpenLayers.Layer.FixedZoomLevels=OpenLayers.Class({initialize:function(){},initResolutions:function(){for(var a=["minZoomLevel","maxZoomLevel","numZoomLevels"],b=0,c=a.length;b<c;b++){var d=a[b];this[d]=null!=this.options[d]?this.options[d]:this.map[d]}if(null==this.minZoomLevel||this.minZoomLevel<this.MIN_ZOOM_LEVEL)this.minZoomLevel=this.MIN_ZOOM_LEVEL;a=this.MAX_ZOOM_LEVEL-this.minZoomLevel+1;b=null==this.options.numZoomLevels& [...]
+this.maxZoomLevel-this.minZoomLevel+1:this.numZoomLevels;this.numZoomLevels=null!=b?Math.min(b,a):a;this.maxZoomLevel=this.minZoomLevel+this.numZoomLevels-1;if(null!=this.RESOLUTIONS){a=0;this.resolutions=[];for(b=this.minZoomLevel;b<=this.maxZoomLevel;b++)this.resolutions[a++]=this.RESOLUTIONS[b];this.maxResolution=this.resolutions[0];this.minResolution=this.resolutions[this.resolutions.length-1]}},getResolution:function(){if(null!=this.resolutions)return OpenLayers.Layer.prototype.getR [...]
+arguments);var a=null,b=this.map.getSize(),c=this.getExtent();null!=b&&null!=c&&(a=Math.max(c.getWidth()/b.w,c.getHeight()/b.h));return a},getExtent:function(){var a=this.map.getSize(),b=this.getLonLatFromViewPortPx({x:0,y:0}),a=this.getLonLatFromViewPortPx({x:a.w,y:a.h});return null!=b&&null!=a?new OpenLayers.Bounds(b.lon,a.lat,a.lon,b.lat):null},getZoomForResolution:function(a){if(null!=this.resolutions)return OpenLayers.Layer.prototype.getZoomForResolution.apply(this,arguments);var b= [...]
+[]);return this.getZoomForExtent(b)},getOLZoomFromMapObjectZoom:function(a){var b=null;null!=a&&(b=a-this.minZoomLevel,this.map.baseLayer!==this&&(b=this.map.baseLayer.getZoomForResolution(this.getResolutionForZoom(b))));return b},getMapObjectZoomFromOLZoom:function(a){var b=null;null!=a&&(b=a+this.minZoomLevel,this.map.baseLayer!==this&&(b=this.getZoomForResolution(this.map.baseLayer.getResolutionForZoom(b))));return b},CLASS_NAME:"OpenLayers.Layer.FixedZoomLevels"});OpenLayers.Layer.Go [...]
+type:null,wrapDateLine:!0,sphericalMercator:!1,version:null,initialize:function(a,b){b=b||{};b.version||(b.version="function"===typeof GMap2?"2":"3");var c=OpenLayers.Layer.Google["v"+b.version.replace(/\./g,"_")];if(c)OpenLayers.Util.applyDefaults(b,c);else throw"Unsupported Google Maps API version: "+b.version;OpenLayers.Util.applyDefaults(b,c.DEFAULTS);b.maxExtent&&(b.maxExtent=b.maxExtent.clone());OpenLayers.Layer.EventPane.prototype.initialize.apply(this,[a,b]);OpenLayers.Layer.Fixe [...]
+[a,b]);this.sphericalMercator&&(OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator),this.initMercatorParameters())},clone:function(){return new OpenLayers.Layer.Google(this.name,this.getOptions())},setVisibility:function(a){var b=null==this.opacity?1:this.opacity;OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this,arguments);this.setOpacity(b)},display:function(a){this._dragging||this.setGMapVisibility(a);OpenLayers.Layer.EventPane.prototype.display.apply(this,argum [...]
+b,c){this._dragging=c;OpenLayers.Layer.EventPane.prototype.moveTo.apply(this,arguments);delete this._dragging},setOpacity:function(a){a!==this.opacity&&(null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"}),this.opacity=a);if(this.getVisibility()){var b=this.getMapContainer();OpenLayers.Util.modifyDOMElement(b,null,null,null,null,null,null,a)}},destroy:function(){if(this.map){this.setGMapVisibility(!1);var a=OpenLayers.Layer.Google.cache[this.map.id] [...]
+this.removeGMapElements()}OpenLayers.Layer.EventPane.prototype.destroy.apply(this,arguments)},removeGMapElements:function(){var a=OpenLayers.Layer.Google.cache[this.map.id];if(a){var b=this.mapObject&&this.getMapContainer();b&&b.parentNode&&b.parentNode.removeChild(b);(b=a.termsOfUse)&&b.parentNode&&b.parentNode.removeChild(b);(a=a.poweredBy)&&a.parentNode&&a.parentNode.removeChild(a);this.mapObject&&(window.google&&google.maps&&google.maps.event&&google.maps.event.clearListeners)&&googl [...]
+"tilesloaded")}},removeMap:function(a){this.visibility&&this.mapObject&&this.setGMapVisibility(!1);var b=OpenLayers.Layer.Google.cache[a.id];b&&(1>=b.count?(this.removeGMapElements(),delete OpenLayers.Layer.Google.cache[a.id]):--b.count);delete this.termsOfUse;delete this.poweredBy;delete this.mapObject;delete this.dragObject;OpenLayers.Layer.EventPane.prototype.removeMap.apply(this,arguments)},getOLBoundsFromMapObjectBounds:function(a){var b=null;null!=a&&(b=a.getSouthWest(),a=a.getNort [...]
+(b=this.forwardMercator(b.lng(),b.lat()),a=this.forwardMercator(a.lng(),a.lat())):(b=new OpenLayers.LonLat(b.lng(),b.lat()),a=new OpenLayers.LonLat(a.lng(),a.lat())),b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat));return b},getWarningHTML:function(){return OpenLayers.i18n("googleWarning")},getMapObjectCenter:function(){return this.mapObject.getCenter()},getMapObjectZoom:function(){return this.mapObject.getZoom()},getLongitudeFromMapObjectLonLat:function(a){return this.sphericalMercator [...]
+a.lat()).lon:a.lng()},getLatitudeFromMapObjectLonLat:function(a){return this.sphericalMercator?this.forwardMercator(a.lng(),a.lat()).lat:a.lat()},getXFromMapObjectPixel:function(a){return a.x},getYFromMapObjectPixel:function(a){return a.y},CLASS_NAME:"OpenLayers.Layer.Google"});OpenLayers.Layer.Google.cache={};
+OpenLayers.Layer.Google.v2={termsOfUse:null,poweredBy:null,dragObject:null,loadMapObject:function(){this.type||(this.type=G_NORMAL_MAP);var a,b,c,d=OpenLayers.Layer.Google.cache[this.map.id];if(d)a=d.mapObject,b=d.termsOfUse,c=d.poweredBy,++d.count;else{var d=this.map.viewPortDiv,e=document.createElement("div");e.id=this.map.id+"_GMap2Container";e.style.position="absolute";e.style.width="100%";e.style.height="100%";d.appendChild(e);try{a=new GMap2(e),b=e.lastChild,d.appendChild(b),b.styl [...]
+"1100",b.style.right="",b.style.bottom="",b.className="olLayerGoogleCopyright",c=e.lastChild,d.appendChild(c),c.style.zIndex="1100",c.style.right="",c.style.bottom="",c.className="olLayerGooglePoweredBy gmnoprint"}catch(f){throw f;}OpenLayers.Layer.Google.cache[this.map.id]={mapObject:a,termsOfUse:b,poweredBy:c,count:1}}this.mapObject=a;this.termsOfUse=b;this.poweredBy=c;-1===OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),this.type)&&this.mapObject.addMapType(this.type);"function"= [...]
+this.dragObject=a.getDragObject():this.dragPanMapObject=null;!1===this.isBaseLayer&&this.setGMapVisibility("none"!==this.div.style.display)},onMapResize:function(){if(this.visibility&&this.mapObject.isLoaded())this.mapObject.checkResize();else{if(!this._resized)var a=this,b=GEvent.addListener(this.mapObject,"load",function(){GEvent.removeListener(b);delete a._resized;a.mapObject.checkResize();a.moveTo(a.map.getCenter(),a.map.getZoom())});this._resized=!0}},setGMapVisibility:function(a){v [...]
+if(b){var c=this.mapObject.getContainer();!0===a?(this.mapObject.setMapType(this.type),c.style.display="",this.termsOfUse.style.left="",this.termsOfUse.style.display="",this.poweredBy.style.display="",b.displayed=this.id):(b.displayed===this.id&&delete b.displayed,b.displayed||(c.style.display="none",this.termsOfUse.style.display="none",this.termsOfUse.style.left="-9999px",this.poweredBy.style.display="none"))}},getMapContainer:function(){return this.mapObject.getContainer()},getMapObjec [...]
+null;null!=a&&(b=this.sphericalMercator?this.inverseMercator(a.bottom,a.left):new OpenLayers.LonLat(a.bottom,a.left),a=this.sphericalMercator?this.inverseMercator(a.top,a.right):new OpenLayers.LonLat(a.top,a.right),b=new GLatLngBounds(new GLatLng(b.lat,b.lon),new GLatLng(a.lat,a.lon)));return b},setMapObjectCenter:function(a,b){this.mapObject.setCenter(a,b)},dragPanMapObject:function(a,b){this.dragObject.moveBy(new GSize(-a,b))},getMapObjectLonLatFromMapObjectPixel:function(a){return thi [...]
+getMapObjectPixelFromMapObjectLonLat:function(a){return this.mapObject.fromLatLngToContainerPixel(a)},getMapObjectZoomFromMapObjectBounds:function(a){return this.mapObject.getBoundsZoomLevel(a)},getMapObjectLonLatFromLonLat:function(a,b){var c;this.sphericalMercator?(c=this.inverseMercator(a,b),c=new GLatLng(c.lat,c.lon)):c=new GLatLng(b,a);return c},getMapObjectPixelFromXY:function(a,b){return new GPoint(a,b)}};OpenLayers.Format.XML=OpenLayers.Class(OpenLayers.Format,{namespaces:null,na [...]
+arguments)},setNamespace:function(a,b){this.namespaces[a]=b;this.namespaceAlias[b]=a},read:function(a){var b=a.indexOf("<");0<b&&(a=a.substring(b));b=OpenLayers.Util.Try(OpenLayers.Function.bind(function(){var b;b=window.ActiveXObject&&!this.xmldom?new ActiveXObject("Microsoft.XMLDOM"):this.xmldom;b.loadXML(a);return b},this),function(){return(new DOMParser).parseFromString(a,"text/xml")},function(){var b=new XMLHttpRequest;b.open("GET","data:text/xml;charset=utf-8,"+encodeURIComponent(a [...]
+b.overrideMimeType("text/xml");b.send(null);return b.responseXML});this.keepData&&(this.data=b);return b},write:function(a){if(this.xmldom)a=a.xml;else{var b=new XMLSerializer;if(1==a.nodeType){var c=document.implementation.createDocument("","",null);c.importNode&&(a=c.importNode(a,!0));c.appendChild(a);a=b.serializeToString(c)}else a=b.serializeToString(a)}return a},createElementNS:function(a,b){return this.xmldom?"string"==typeof a?this.xmldom.createNode(1,b,a):this.xmldom.createNode(1 [...]
+b)},createDocumentFragment:function(){return this.xmldom?this.xmldom.createDocumentFragment():document.createDocumentFragment()},createTextNode:function(a){"string"!==typeof a&&(a=String(a));return this.xmldom?this.xmldom.createTextNode(a):document.createTextNode(a)},getElementsByTagNameNS:function(a,b,c){var d=[];if(a.getElementsByTagNameNS)d=a.getElementsByTagNameNS(b,c);else{a=a.getElementsByTagName("*");for(var e,f,g=0,h=a.length;g<h;++g)if(e=a[g],f=e.prefix?e.prefix+":"+c:c,"*"==c|| [...]
+b&&b!=e.namespaceURI||d.push(e)}return d},getAttributeNodeNS:function(a,b,c){var d=null;if(a.getAttributeNodeNS)d=a.getAttributeNodeNS(b,c);else{a=a.attributes;for(var e,f,g=0,h=a.length;g<h;++g)if(e=a[g],e.namespaceURI==b&&(f=e.prefix?e.prefix+":"+c:c,f==e.nodeName)){d=e;break}}return d},getAttributeNS:function(a,b,c){var d="";if(a.getAttributeNS)d=a.getAttributeNS(b,c)||"";else if(a=this.getAttributeNodeNS(a,b,c))d=a.nodeValue;return d},getChildValue:function(a,b){var c=b||"";if(a)for( [...]
+d.nextSibling)switch(d.nodeType){case 3:case 4:c+=d.nodeValue}return c},isSimpleContent:function(a){var b=!0;for(a=a.firstChild;a;a=a.nextSibling)if(1===a.nodeType){b=!1;break}return b},contentType:function(a){var b=!1,c=!1,d=OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;for(a=a.firstChild;a;a=a.nextSibling){switch(a.nodeType){case 1:c=!0;break;case 8:break;default:b=!0}if(c&&b)break}if(c&&b)d=OpenLayers.Format.XML.CONTENT_TYPE.MIXED;else{if(c)return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX [...]
+hasAttributeNS:function(a,b,c){var d=!1;return d=a.hasAttributeNS?a.hasAttributeNS(b,c):!!this.getAttributeNodeNS(a,b,c)},setAttributeNS:function(a,b,c,d){if(a.setAttributeNS)a.setAttributeNS(b,c,d);else if(this.xmldom)b?(b=a.ownerDocument.createNode(2,c,b),b.nodeValue=d,a.setAttributeNode(b)):a.setAttribute(c,d);else throw"setAttributeNS not implemented";},createElementNSPlus:function(a,b){b=b||{};var c=b.uri||this.namespaces[b.prefix];c||(c=a.indexOf(":"),c=this.namespaces[a.substring( [...]
+(c=this.namespaces[this.defaultPrefix]);c=this.createElementNS(c,a);b.attributes&&this.setAttributes(c,b.attributes);var d=b.value;null!=d&&c.appendChild(this.createTextNode(d));return c},setAttributes:function(a,b){var c,d,e;for(e in b)null!=b[e]&&b[e].toString&&(c=b[e].toString(),d=this.namespaces[e.substring(0,e.indexOf(":"))]||null,this.setAttributeNS(a,d,e,c))},readNode:function(a,b){b||(b={});var c=this.readers[a.namespaceURI?this.namespaceAlias[a.namespaceURI]:this.defaultPrefix]; [...]
+a.localName||a.nodeName.split(":").pop();(c=c[d]||c["*"])&&c.apply(this,[a,b])}return b},readChildNodes:function(a,b){b||(b={});for(var c=a.childNodes,d,e=0,f=c.length;e<f;++e)d=c[e],1==d.nodeType&&this.readNode(d,b);return b},writeNode:function(a,b,c){var d,e=a.indexOf(":");0<e?(d=a.substring(0,e),a=a.substring(e+1)):d=c?this.namespaceAlias[c.namespaceURI]:this.defaultPrefix;b=this.writers[d][a].apply(this,[b]);c&&c.appendChild(b);return b},getChildEl:function(a,b,c){return a&&this.getT [...]
+b,c)},getNextEl:function(a,b,c){return a&&this.getThisOrNextEl(a.nextSibling,b,c)},getThisOrNextEl:function(a,b,c){a:for(;a;a=a.nextSibling)switch(a.nodeType){case 1:if(!(b&&b!==(a.localName||a.nodeName.split(":").pop())||c&&c!==a.namespaceURI))break a;a=null;break a;case 3:if(/^\s*$/.test(a.nodeValue))break;case 4:case 6:case 12:case 10:case 11:a=null;break a}return a||null},lookupNamespaceURI:function(a,b){var c=null;if(a)if(a.lookupNamespaceURI)c=a.lookupNamespaceURI(b);else a:switch( [...]
+a.namespaceURI&&a.prefix===b){c=a.namespaceURI;break a}if(c=a.attributes.length)for(var d,e=0;e<c;++e)if(d=a.attributes[e],"xmlns"===d.prefix&&d.name==="xmlns:"+b){c=d.value||null;break a}else if("xmlns"===d.name&&null===b){c=d.value||null;break a}c=this.lookupNamespaceURI(a.parentNode,b);break a;case 2:c=this.lookupNamespaceURI(a.ownerElement,b);break a;case 9:c=this.lookupNamespaceURI(a.documentElement,b);break a;case 6:case 12:case 10:case 11:break a;default:c=this.lookupNamespaceURI( [...]
+b)}return c},getXMLDoc:function(){OpenLayers.Format.XML.document||this.xmldom||(document.implementation&&document.implementation.createDocument?OpenLayers.Format.XML.document=document.implementation.createDocument("","",null):!this.xmldom&&window.ActiveXObject&&(this.xmldom=new ActiveXObject("Microsoft.XMLDOM")));return OpenLayers.Format.XML.document||this.xmldom},CLASS_NAME:"OpenLayers.Format.XML"});OpenLayers.Format.XML.CONTENT_TYPE={EMPTY:0,SIMPLE:1,COMPLEX:2,MIXED:3};
+OpenLayers.Format.XML.lookupNamespaceURI=OpenLayers.Function.bind(OpenLayers.Format.XML.prototype.lookupNamespaceURI,OpenLayers.Format.XML.prototype);OpenLayers.Format.XML.document=null;OpenLayers.Format.WFST=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Format.WFST.DEFAULTS);var b=OpenLayers.Format.WFST["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported WFST version: "+a.version;return new b(a)};OpenLayers.Format.WFST.DEFAULTS={version:"1.0.0"};OpenLayers.Feature=OpenL [...]
+(this.destroyMarker(this.marker),this.marker=null);null!=this.popup&&(this.destroyPopup(this.popup),this.popup=null)},onScreen:function(){var a=!1;null!=this.layer&&null!=this.layer.map&&(a=this.layer.map.getExtent().containsLonLat(this.lonlat));return a},createMarker:function(){null!=this.lonlat&&(this.marker=new OpenLayers.Marker(this.lonlat,this.data.icon));return this.marker},destroyMarker:function(){this.marker.destroy()},createPopup:function(a){null!=this.lonlat&&(this.popup||(this [...]
+this.popupClass:OpenLayers.Popup.Anchored)(this.id+"_popup",this.lonlat,this.data.popupSize,this.data.popupContentHTML,this.marker?this.marker.icon:null,a)),null!=this.data.overflow&&(this.popup.contentDiv.style.overflow=this.data.overflow),this.popup.feature=this);return this.popup},destroyPopup:function(){this.popup&&(this.popup.feature=null,this.popup.destroy(),this.popup=null)},CLASS_NAME:"OpenLayers.Feature"});OpenLayers.State={UNKNOWN:"Unknown",INSERT:"Insert",UPDATE:"Update",DELET [...]
+OpenLayers.Feature.Vector=OpenLayers.Class(OpenLayers.Feature,{fid:null,geometry:null,attributes:null,bounds:null,state:null,style:null,url:null,renderIntent:"default",modified:null,initialize:function(a,b,c){OpenLayers.Feature.prototype.initialize.apply(this,[null,null,b]);this.lonlat=null;this.geometry=a?a:null;this.state=null;this.attributes={};b&&(this.attributes=OpenLayers.Util.extend(this.attributes,b));this.style=c?c:null},destroy:function(){this.layer&&(this.layer.removeFeatures( [...]
+null);this.modified=this.geometry=null;OpenLayers.Feature.prototype.destroy.apply(this,arguments)},clone:function(){return new OpenLayers.Feature.Vector(this.geometry?this.geometry.clone():null,this.attributes,this.style)},onScreen:function(a){var b=!1;this.layer&&this.layer.map&&(b=this.layer.map.getExtent(),a?(a=this.geometry.getBounds(),b=b.intersectsBounds(a)):b=b.toGeometry().intersects(this.geometry));return b},getVisibility:function(){return!(this.style&&"none"==this.style.display [...]
+this.layer&&this.layer.styleMap&&"none"==this.layer.styleMap.createSymbolizer(this,this.renderIntent).display||this.layer&&!this.layer.getVisibility())},createMarker:function(){return null},destroyMarker:function(){},createPopup:function(){return null},atPoint:function(a,b,c){var d=!1;this.geometry&&(d=this.geometry.atPoint(a,b,c));return d},destroyPopup:function(){},move:function(a){if(this.layer&&this.geometry.move){a="OpenLayers.LonLat"==a.CLASS_NAME?this.layer.getViewPortPxFromLonLat [...]
+this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat()),c=this.layer.map.getResolution();this.geometry.move(c*(a.x-b.x),c*(b.y-a.y));this.layer.drawFeature(this);return b}},toState:function(a){if(a==OpenLayers.State.UPDATE)switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.DELETE:this.state=a}else if(a==OpenLayers.State.INSERT)switch(this.state){case OpenLayers.State.UNKNOWN:break;default:this.state=a}else if(a==OpenLayers.State.DELETE)switch(t [...]
+a}else a==OpenLayers.State.UNKNOWN&&(this.state=a)},CLASS_NAME:"OpenLayers.Feature.Vector"});
+OpenLayers.Feature.Vector.style={"default":{fillColor:"#ee9900",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#ee9900",strokeOpacity:1,strokeWidth:1,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},select:{fillColor:"blu [...]
+hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"blue",strokeOpacity:1,strokeWidth:2,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"pointer",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},temporary:{fillColor:"#66cccc",fillOpacity:0.2,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"# [...]
+strokeLinecap:"round",strokeWidth:2,strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},"delete":{display:"none"}};OpenLayers.Style=OpenLayers.Class({id:null,name:null,title:null,description:null,layerName:null,isDefault:!1,rules:null,context:null,defaultStyle:null,defaul [...]
+this.rules[a]=null;this.defaultStyle=this.rules=null},createSymbolizer:function(a){for(var b=this.defaultsPerSymbolizer?{}:this.createLiterals(OpenLayers.Util.extend({},this.defaultStyle),a),c=this.rules,d,e=[],f=!1,g=0,h=c.length;g<h;g++)d=c[g],d.evaluate(a)&&(d instanceof OpenLayers.Rule&&d.elseFilter?e.push(d):(f=!0,this.applySymbolizer(d,b,a)));if(!1==f&&0<e.length)for(f=!0,g=0,h=e.length;g<h;g++)this.applySymbolizer(e[g],b,a);0<c.length&&!1==f&&(b.display="none");null!=b.label&&"str [...]
+(b.label=String(b.label));return b},applySymbolizer:function(a,b,c){var d=c.geometry?this.getSymbolizerPrefix(c.geometry):OpenLayers.Style.SYMBOLIZER_PREFIXES[0];a=a.symbolizer[d]||a.symbolizer;!0===this.defaultsPerSymbolizer&&(d=this.defaultStyle,OpenLayers.Util.applyDefaults(a,{pointRadius:d.pointRadius}),!0!==a.stroke&&!0!==a.graphic||OpenLayers.Util.applyDefaults(a,{strokeWidth:d.strokeWidth,strokeColor:d.strokeColor,strokeOpacity:d.strokeOpacity,strokeDashstyle:d.strokeDashstyle,str [...]
+!0!==a.fill&&!0!==a.graphic||OpenLayers.Util.applyDefaults(a,{fillColor:d.fillColor,fillOpacity:d.fillOpacity}),!0===a.graphic&&OpenLayers.Util.applyDefaults(a,{pointRadius:this.defaultStyle.pointRadius,externalGraphic:this.defaultStyle.externalGraphic,graphicName:this.defaultStyle.graphicName,graphicOpacity:this.defaultStyle.graphicOpacity,graphicWidth:this.defaultStyle.graphicWidth,graphicHeight:this.defaultStyle.graphicHeight,graphicXOffset:this.defaultStyle.graphicXOffset,graphicYOff [...]
+return this.createLiterals(OpenLayers.Util.extend(b,a),c)},createLiterals:function(a,b){var c=OpenLayers.Util.extend({},b.attributes||b.data);OpenLayers.Util.extend(c,this.context);for(var d in this.propertyStyles)a[d]=OpenLayers.Style.createLiteral(a[d],c,b,d);return a},findPropertyStyles:function(){var a={};this.addPropertyStyles(a,this.defaultStyle);for(var b=this.rules,c,d,e=0,f=b.length;e<f;e++){c=b[e].symbolizer;for(var g in c)if(d=c[g],"object"==typeof d)this.addPropertyStyles(a,d [...]
+c);break}}return a},addPropertyStyles:function(a,b){var c,d;for(d in b)c=b[d],"string"==typeof c&&c.match(/\$\{\w+\}/)&&(a[d]=!0);return a},addRules:function(a){Array.prototype.push.apply(this.rules,a);this.propertyStyles=this.findPropertyStyles()},setDefaultStyle:function(a){this.defaultStyle=a;this.propertyStyles=this.findPropertyStyles()},getSymbolizerPrefix:function(a){for(var b=OpenLayers.Style.SYMBOLIZER_PREFIXES,c=0,d=b.length;c<d;c++)if(-1!=a.CLASS_NAME.indexOf(b[c]))return b[c]} [...]
+OpenLayers.Util.extend({},this);if(this.rules){a.rules=[];for(var b=0,c=this.rules.length;b<c;++b)a.rules.push(this.rules[b].clone())}a.context=this.context&&OpenLayers.Util.extend({},this.context);b=OpenLayers.Util.extend({},this.defaultStyle);return new OpenLayers.Style(b,a)},CLASS_NAME:"OpenLayers.Style"});OpenLayers.Style.createLiteral=function(a,b,c,d){"string"==typeof a&&-1!=a.indexOf("${")&&(a=OpenLayers.String.format(a,b,[c,d]),a=isNaN(a)||!a?a:parseFloat(a));return a};
+OpenLayers.Style.SYMBOLIZER_PREFIXES=["Point","Line","Polygon","Text","Raster"];OpenLayers.Filter=OpenLayers.Class({initialize:function(a){OpenLayers.Util.extend(this,a)},destroy:function(){},evaluate:function(a){return!0},clone:function(){return null},toString:function(){return OpenLayers.Format&&OpenLayers.Format.CQL?OpenLayers.Format.CQL.prototype.write(this):Object.prototype.toString.call(this)},CLASS_NAME:"OpenLayers.Filter"});OpenLayers.Filter.Spatial=OpenLayers.Class(OpenLayers.Fi [...]
+OpenLayers.Util.applyDefaults({value:this.value&&this.value.clone&&this.value.clone()},this);return new OpenLayers.Filter.Spatial(a)},CLASS_NAME:"OpenLayers.Filter.Spatial"});OpenLayers.Filter.Spatial.BBOX="BBOX";OpenLayers.Filter.Spatial.INTERSECTS="INTERSECTS";OpenLayers.Filter.Spatial.DWITHIN="DWITHIN";OpenLayers.Filter.Spatial.WITHIN="WITHIN";OpenLayers.Filter.Spatial.CONTAINS="CONTAINS";OpenLayers.Filter.FeatureId=OpenLayers.Class(OpenLayers.Filter,{fids:null,type:"FID",initialize:f [...]
+"wfs:Update";this.stateName[OpenLayers.State.DELETE]="wfs:Delete";OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},getSrsName:function(a,b){var c=b&&b.srsName;c||(c=a&&a.layer?a.layer.projection.getCode():this.srsName);return c},read:function(a,b){b=b||{};OpenLayers.Util.applyDefaults(b,{output:"features"});"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var c={};a&&this.readNode(a,c,!0);c.features&&"features [...]
+(c=c.features);return c},readers:{wfs:{FeatureCollection:function(a,b){b.features=[];this.readChildNodes(a,b)}}},write:function(a,b){var c=this.writeNode("wfs:Transaction",{features:a,options:b}),d=this.schemaLocationAttr();d&&this.setAttributeNS(c,this.namespaces.xsi,"xsi:schemaLocation",d);return OpenLayers.Format.XML.prototype.write.apply(this,[c])},writers:{wfs:{GetFeature:function(a){var b=this.createElementNSPlus("wfs:GetFeature",{attributes:{service:"WFS",version:this.version,hand [...]
+outputFormat:a&&a.outputFormat,maxFeatures:a&&a.maxFeatures,"xsi:schemaLocation":this.schemaLocationAttr(a)}});if("string"==typeof this.featureType)this.writeNode("Query",a,b);else for(var c=0,d=this.featureType.length;c<d;c++)a.featureType=this.featureType[c],this.writeNode("Query",a,b);return b},Transaction:function(a){a=a||{};var b=a.options||{},c=this.createElementNSPlus("wfs:Transaction",{attributes:{service:"WFS",version:this.version,handle:b.handle}}),d,e=a.features;if(e){!0===b.m [...]
+{"OpenLayers.Geometry.Point":"MultiPoint","OpenLayers.Geometry.LineString":!0===this.multiCurve?"MultiCurve":"MultiLineString","OpenLayers.Geometry.Polygon":!0===this.multiSurface?"MultiSurface":"MultiPolygon"});var f,g;a=0;for(d=e.length;a<d;++a)g=e[a],(f=this.stateName[g.state])&&this.writeNode(f,{feature:g,options:b},c);!0===b.multi&&this.setGeometryTypes()}if(b.nativeElements)for(a=0,d=b.nativeElements.length;a<d;++a)this.writeNode("wfs:Native",b.nativeElements[a],c);return c},Native [...]
+{attributes:{vendorId:a.vendorId,safeToIgnore:a.safeToIgnore},value:a.value})},Insert:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Insert",{attributes:{handle:a&&a.handle}});this.srsName=this.getSrsName(b);this.writeNode("feature:_typeName",b,a);return a},Update:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Update",{attributes:{handle:a&&a.handle,typeName:(this.featureNS?this.featurePrefix+":":"")+this.featureType}});this.featureNS&&a. [...]
+this.featurePrefix,this.featureNS);var c=b.modified;null===this.geometryName||c&&void 0===c.geometry||(this.srsName=this.getSrsName(b),this.writeNode("Property",{name:this.geometryName,value:b.geometry},a));for(var d in b.attributes)void 0===b.attributes[d]||c&&c.attributes&&(!c.attributes||void 0===c.attributes[d])||this.writeNode("Property",{name:d,value:b.attributes[d]},a);this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[b.fid]}),a);return a},Property:function(a){var [...]
+this.writeNode("Name",a.name,b);null!==a.value&&this.writeNode("Value",a.value,b);return b},Name:function(a){return this.createElementNSPlus("wfs:Name",{value:a})},Value:function(a){var b;a instanceof OpenLayers.Geometry?(b=this.createElementNSPlus("wfs:Value"),a=this.writeNode("feature:_geometry",a).firstChild,b.appendChild(a)):b=this.createElementNSPlus("wfs:Value",{value:a});return b},Delete:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Delete",{attributes:{h [...]
+a.handle,typeName:(this.featureNS?this.featurePrefix+":":"")+this.featureType}});this.featureNS&&a.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[b.fid]}),a);return a}}},schemaLocationAttr:function(a){a=OpenLayers.Util.extend({featurePrefix:this.featurePrefix,schema:this.schema},a);var b=OpenLayers.Util.extend({},this.schemaLocations);a.schema&&(b[a.featurePrefix]=a.schema);a=[];var c,d;for(d in b)(c=this.namesp [...]
+a.push(c+" "+b[d]);return a.join(" ")||void 0},setFilterProperty:function(a){if(a.filters)for(var b=0,c=a.filters.length;b<c;++b)OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this,a.filters[b]);else a instanceof OpenLayers.Filter.Spatial&&!a.property&&(a.property=this.geometryName)},CLASS_NAME:"OpenLayers.Format.WFST.v1"});OpenLayers.Format.OGCExceptionReport=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc"},regExes:{trimSpace:/^\s*|\s*$/g [...]
+b){b.exceptionReport={exceptions:[]};this.readChildNodes(a,b.exceptionReport)},ServiceException:function(a,b){var c={code:a.getAttribute("code"),locator:a.getAttribute("locator"),text:this.getChildValue(a)};b.exceptions.push(c)}}},CLASS_NAME:"OpenLayers.Format.OGCExceptionReport"});OpenLayers.Format.XML.VersionedOGC=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:null,version:null,profile:null,allowFallback:!1,name:null,stringifyOutput:!1,parser:null,initialize:function(a){OpenLay [...]
+a||this.defaultVersion;var b=this.profile?"_"+this.profile:"";if(!this.parser||this.parser.VERSION!=a){var c=OpenLayers.Format[this.name]["v"+a.replace(/\./g,"_")+b];if(!c&&(""!==b&&this.allowFallback&&(b="",c=OpenLayers.Format[this.name]["v"+a.replace(/\./g,"_")]),!c))throw"Can't find a "+this.name+" parser for version "+a+b;this.parser=new c(this.options)}return this.parser},write:function(a,b){var c=this.getVersion(null,b);this.parser=this.getParser(c);c=this.parser.write(a,b);return! [...]
+c:OpenLayers.Format.XML.prototype.write.apply(this,[c])},read:function(a,b){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var c=this.getVersion(a.documentElement);this.parser=this.getParser(c);var d=this.parser.read(a,b),e=this.parser.errorProperty||null;null!==e&&void 0===d[e]&&(e=new OpenLayers.Format.OGCExceptionReport,d.error=e.read(a));d.version=c;return d},CLASS_NAME:"OpenLayers.Format.XML.VersionedOGC"});OpenLayers.Filter.Logical=OpenLayers.Class(Ope [...]
+c;b++)if(!0==this.filters[b].evaluate(a))return!0;return!1;case OpenLayers.Filter.Logical.NOT:return!this.filters[0].evaluate(a)}},clone:function(){for(var a=[],b=0,c=this.filters.length;b<c;++b)a.push(this.filters[b].clone());return new OpenLayers.Filter.Logical({type:this.type,filters:a})},CLASS_NAME:"OpenLayers.Filter.Logical"});OpenLayers.Filter.Logical.AND="&&";OpenLayers.Filter.Logical.OR="||";OpenLayers.Filter.Logical.NOT="!";OpenLayers.Filter.Comparison=OpenLayers.Class(OpenLayer [...]
+b=this.matchCase||"string"!=typeof a||"string"!=typeof b?a==b:a.toUpperCase()==b.toUpperCase();break;case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:b=this.value;b=this.matchCase||"string"!=typeof a||"string"!=typeof b?a!=b:a.toUpperCase()!=b.toUpperCase();break;case OpenLayers.Filter.Comparison.LESS_THAN:b=a<this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN:b=a>this.value;break;case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:b=a<=this.value;break;case OpenLayers.Fil [...]
+a>=this.value;break;case OpenLayers.Filter.Comparison.BETWEEN:b=a>=this.lowerBoundary&&a<=this.upperBoundary;break;case OpenLayers.Filter.Comparison.LIKE:b=RegExp(this.value,"gi").test(a);break;case OpenLayers.Filter.Comparison.IS_NULL:b=null===a}return b},value2regex:function(a,b,c){if("."==a)throw Error("'.' is an unsupported wildCard character for OpenLayers.Filter.Comparison");a=a?a:"*";b=b?b:".";this.value=this.value.replace(RegExp("\\"+(c?c:"!")+"(.|$)","g"),"\\$1");this.value=this [...]
+b,"g"),".");this.value=this.value.replace(RegExp("\\"+a,"g"),".*");this.value=this.value.replace(RegExp("\\\\.\\*","g"),"\\"+a);return this.value=this.value.replace(RegExp("\\\\\\.","g"),"\\"+b)},regex2value:function(){var a=this.value,a=a.replace(/!/g,"!!"),a=a.replace(/(\\)?\\\./g,function(a,c){return c?a:"!."}),a=a.replace(/(\\)?\\\*/g,function(a,c){return c?a:"!*"}),a=a.replace(/\\\\/g,"\\");return a=a.replace(/\.\*/g,"*")},clone:function(){return OpenLayers.Util.extend(new OpenLayer [...]
+this)},CLASS_NAME:"OpenLayers.Filter.Comparison"});OpenLayers.Filter.Comparison.EQUAL_TO="==";OpenLayers.Filter.Comparison.NOT_EQUAL_TO="!=";OpenLayers.Filter.Comparison.LESS_THAN="<";OpenLayers.Filter.Comparison.GREATER_THAN=">";OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO="<=";OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO=">=";OpenLayers.Filter.Comparison.BETWEEN="..";OpenLayers.Filter.Comparison.LIKE="~";OpenLayers.Filter.Comparison.IS_NULL="NULL";OpenLayers.Format.Filte [...]
+2)+":"+OpenLayers.Number.zeroPad(a.getUTCSeconds(),2)+"."+OpenLayers.Number.zeroPad(a.getUTCMilliseconds(),3)+"Z"}}(),parse:function(a){var b;if((a=a.match(this.dateRegEx))&&(a[1]||a[7])){b=parseInt(a[1],10)||0;var c=parseInt(a[2],10)-1||0,d=parseInt(a[3],10)||1;b=new Date(Date.UTC(b,c,d));if(c=a[7]){var d=parseInt(a[4],10),e=parseInt(a[5],10),f=parseFloat(a[6]),g=f|0,f=Math.round(1E3*(f-g));b.setUTCHours(d,e,g,f);"Z"!==c&&(c=parseInt(c,10),a=parseInt(a[8],10)||0,a=-1E3*(60*60*c+60*a),b= [...]
+a))}}else b=new Date("invalid");return b}};OpenLayers.Format.Filter.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"ogc",schemaLocation:null,initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){var b={};this.readers.ogc.Filter.apply(this,[a,b]);return b.filter},readers:{ [...]
+c.nextSibling)switch(c.nodeType){case 1:a=this.readNode(c);a.property?b+="${"+a.property+"}":void 0!==a.value&&(b+=a.value);break;case 3:case 4:b+=c.nodeValue}return b},Filter:function(a,b){var c={fids:[],filters:[]};this.readChildNodes(a,c);0<c.fids.length?b.filter=new OpenLayers.Filter.FeatureId({fids:c.fids}):0<c.filters.length&&(b.filter=c.filters[0])},FeatureId:function(a,b){var c=a.getAttribute("fid");c&&b.fids.push(c)},And:function(a,b){var c=new OpenLayers.Filter.Logical({type:Op [...]
+this.readChildNodes(a,c);b.filters.push(c)},Or:function(a,b){var c=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.OR});this.readChildNodes(a,c);b.filters.push(c)},Not:function(a,b){var c=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.NOT});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLessThan:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsGreat [...]
+b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLessThanOrEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsGreaterThanOrEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO});this.readChildNodes(a,c);b [...]
+PropertyIsBetween:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.BETWEEN});this.readChildNodes(a,c);b.filters.push(c)},Literal:function(a,b){b.value=OpenLayers.String.numericIf(this.getChildValue(a),!0)},PropertyName:function(a,b){b.property=this.getChildValue(a)},LowerBoundary:function(a,b){b.lowerBoundary=OpenLayers.String.numericIf(this.readers.ogc._expression.call(this,a),!0)},UpperBoundary:function(a,b){b.upperBoundary=OpenLayers.String.numer [...]
+a),!0)},Intersects:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.INTERSECTS)},Within:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.WITHIN)},Contains:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.CONTAINS)},DWithin:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.DWITHIN)},Distance:function(a,b){b.distance=parseInt(this.getChildValue(a));b.distanceUnits=a.getAttribute("units")},Function:function(a,b){},PropertyIsNull:function(a,b){v [...]
+this.readChildNodes(a,c);b.filters.push(c)}}},readSpatial:function(a,b,c){c=new OpenLayers.Filter.Spatial({type:c});this.readChildNodes(a,c);c.value=c.components[0];delete c.components;b.filters.push(c)},encodeLiteral:function(a){a instanceof Date&&(a=OpenLayers.Date.toISOString(a));return a},writeOgcExpression:function(a,b){a instanceof OpenLayers.Filter.Function?this.writeNode("Function",a,b):this.writeNode("Literal",a,b);return b},write:function(a){return this.writers.ogc.Filter.apply [...]
+writers:{ogc:{Filter:function(a){var b=this.createElementNSPlus("ogc:Filter");this.writeNode(this.getFilterType(a),a,b);return b},_featureIds:function(a){for(var b=this.createDocumentFragment(),c=0,d=a.fids.length;c<d;++c)this.writeNode("ogc:FeatureId",a.fids[c],b);return b},FeatureId:function(a){return this.createElementNSPlus("ogc:FeatureId",{attributes:{fid:a}})},And:function(a){for(var b=this.createElementNSPlus("ogc:And"),c,d=0,e=a.filters.length;d<e;++d)c=a.filters[d],this.writeNod [...]
+c,b);return b},Or:function(a){for(var b=this.createElementNSPlus("ogc:Or"),c,d=0,e=a.filters.length;d<e;++d)c=a.filters[d],this.writeNode(this.getFilterType(c),c,b);return b},Not:function(a){var b=this.createElementNSPlus("ogc:Not");a=a.filters[0];this.writeNode(this.getFilterType(a),a,b);return b},PropertyIsLessThan:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLessThan");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsGreaterThan:fu [...]
+this.createElementNSPlus("ogc:PropertyIsGreaterThan");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsLessThanOrEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsGreaterThanOrEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a [...]
+return b},PropertyIsBetween:function(a){var b=this.createElementNSPlus("ogc:PropertyIsBetween");this.writeNode("PropertyName",a,b);this.writeNode("LowerBoundary",a,b);this.writeNode("UpperBoundary",a,b);return b},PropertyName:function(a){return this.createElementNSPlus("ogc:PropertyName",{value:a.property})},Literal:function(a){return this.createElementNSPlus("ogc:Literal",{value:(this.encodeLiteral||OpenLayers.Format.Filter.v1.prototype.encodeLiteral)(a)})},LowerBoundary:function(a){var [...]
+this.writeOgcExpression(a.lowerBoundary,b);return b},UpperBoundary:function(a){var b=this.createElementNSPlus("ogc:UpperBoundary");this.writeNode("Literal",a.upperBoundary,b);return b},INTERSECTS:function(a){return this.writeSpatial(a,"Intersects")},WITHIN:function(a){return this.writeSpatial(a,"Within")},CONTAINS:function(a){return this.writeSpatial(a,"Contains")},DWITHIN:function(a){var b=this.writeSpatial(a,"DWithin");this.writeNode("Distance",a,b);return b},Distance:function(a){retur [...]
+{attributes:{units:a.distanceUnits},value:a.distance})},Function:function(a){var b=this.createElementNSPlus("ogc:Function",{attributes:{name:a.name}});a=a.params;for(var c=0,d=a.length;c<d;c++)this.writeOgcExpression(a[c],b);return b},PropertyIsNull:function(a){var b=this.createElementNSPlus("ogc:PropertyIsNull");this.writeNode("PropertyName",a,b);return b}}},getFilterType:function(a){var b=this.filterMap[a.type];if(!b)throw"Filter writing not supported for rule type: "+a.type;return b}, [...]
+"||":"Or","!":"Not","==":"PropertyIsEqualTo","!=":"PropertyIsNotEqualTo","<":"PropertyIsLessThan",">":"PropertyIsGreaterThan","<=":"PropertyIsLessThanOrEqualTo",">=":"PropertyIsGreaterThanOrEqualTo","..":"PropertyIsBetween","~":"PropertyIsLike",NULL:"PropertyIsNull",BBOX:"BBOX",DWITHIN:"DWITHIN",WITHIN:"WITHIN",CONTAINS:"CONTAINS",INTERSECTS:"INTERSECTS",FID:"_featureIds"},CLASS_NAME:"OpenLayers.Format.Filter.v1"});OpenLayers.Geometry=OpenLayers.Class({id:null,parent:null,bounds:null,ini [...]
+return this.bounds},calculateBounds:function(){},distanceTo:function(a,b){},getVertices:function(a){},atPoint:function(a,b,c){var d=!1;null!=this.getBounds()&&null!=a&&(b=null!=b?b:0,c=null!=c?c:0,d=(new OpenLayers.Bounds(this.bounds.left-b,this.bounds.bottom-c,this.bounds.right+b,this.bounds.top+c)).containsLonLat(a));return d},getLength:function(){return 0},getArea:function(){return 0},getCentroid:function(){return null},toString:function(){return OpenLayers.Format&&OpenLayers.Format.W [...]
+Object.prototype.toString.call(this)},CLASS_NAME:"OpenLayers.Geometry"});OpenLayers.Geometry.fromWKT=function(a){var b;if(OpenLayers.Format&&OpenLayers.Format.WKT){var c=OpenLayers.Geometry.fromWKT.format;c||(c=new OpenLayers.Format.WKT,OpenLayers.Geometry.fromWKT.format=c);a=c.read(a);if(a instanceof OpenLayers.Feature.Vector)b=a.geometry;else if(OpenLayers.Util.isArray(a)){b=a.length;for(var c=Array(b),d=0;d<b;++d)c[d]=a[d].geometry;b=new OpenLayers.Geometry.Collection(c)}}return b};
+OpenLayers.Geometry.segmentsIntersect=function(a,b,c){var d=c&&c.point;c=c&&c.tolerance;var e=!1,f=a.x1-b.x1,g=a.y1-b.y1,h=a.x2-a.x1,k=a.y2-a.y1,l=b.y2-b.y1,m=b.x2-b.x1,n=l*h-m*k,l=m*g-l*f,g=h*g-k*f;0==n?0==l&&0==g&&(e=!0):(f=l/n,n=g/n,0<=f&&(1>=f&&0<=n&&1>=n)&&(d?(h=a.x1+f*h,n=a.y1+f*k,e=new OpenLayers.Geometry.Point(h,n)):e=!0));if(c)if(e){if(d)a:for(a=[a,b],b=0;2>b;++b)for(f=a[b],k=1;3>k;++k)if(h=f["x"+k],n=f["y"+k],d=Math.sqrt(Math.pow(h-e.x,2)+Math.pow(n-e.y,2)),d<c){e.x=h;e.y=n;bre [...]
+[a,b],b=0;2>b;++b)for(h=a[b],n=a[(b+1)%2],k=1;3>k;++k)if(f={x:h["x"+k],y:h["y"+k]},g=OpenLayers.Geometry.distanceToSegment(f,n),g.distance<c){e=d?new OpenLayers.Geometry.Point(f.x,f.y):!0;break a}return e};OpenLayers.Geometry.distanceToSegment=function(a,b){var c=OpenLayers.Geometry.distanceSquaredToSegment(a,b);c.distance=Math.sqrt(c.distance);return c};
+OpenLayers.Geometry.distanceSquaredToSegment=function(a,b){var c=a.x,d=a.y,e=b.x1,f=b.y1,g=b.x2,h=b.y2,k=g-e,l=h-f,m=(k*(c-e)+l*(d-f))/(Math.pow(k,2)+Math.pow(l,2));0>=m||(1<=m?(e=g,f=h):(e+=m*k,f+=m*l));return{distance:Math.pow(e-c,2)+Math.pow(f-d,2),x:e,y:f,along:m}};OpenLayers.Geometry.Point=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,initialize:function(a,b){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=parseFloat(a);this.y=parseFloat(b)},clone:functio [...]
+OpenLayers.Geometry.Point?(e=this.x,f=this.y,g=a.x,h=a.y,d=Math.sqrt(Math.pow(e-g,2)+Math.pow(f-h,2)),d=c?{x0:e,y0:f,x1:g,y1:h,distance:d}:d):(d=a.distanceTo(this,b),c&&(d={x0:d.x1,y0:d.y1,x1:d.x0,y1:d.y0,distance:d.distance}));return d},equals:function(a){var b=!1;null!=a&&(b=this.x==a.x&&this.y==a.y||isNaN(this.x)&&isNaN(this.y)&&isNaN(a.x)&&isNaN(a.y));return b},toShortString:function(){return this.x+", "+this.y},move:function(a,b){this.x+=a;this.y+=b;this.clearBounds()},rotate:functi [...]
+Math.PI/180;var c=this.distanceTo(b),d=a+Math.atan2(this.y-b.y,this.x-b.x);this.x=b.x+c*Math.cos(d);this.y=b.y+c*Math.sin(d);this.clearBounds()},getCentroid:function(){return new OpenLayers.Geometry.Point(this.x,this.y)},resize:function(a,b,c){this.x=b.x+a*(void 0==c?1:c)*(this.x-b.x);this.y=b.y+a*(this.y-b.y);this.clearBounds();return this},intersects:function(a){var b=!1;return b="OpenLayers.Geometry.Point"==a.CLASS_NAME?this.equals(a):a.intersects(this)},transform:function(a,b){a&&b&& [...]
+a,b),this.bounds=null);return this},getVertices:function(a){return[this]},CLASS_NAME:"OpenLayers.Geometry.Point"});OpenLayers.Geometry.Collection=OpenLayers.Class(OpenLayers.Geometry,{components:null,componentTypes:null,initialize:function(a){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.components=[];null!=a&&this.addComponents(a)},destroy:function(){this.components.length=0;this.components=null;OpenLayers.Geometry.prototype.destroy.apply(this,arguments)},clone:fun [...]
+OpenLayers.Util.applyDefaults(a,this);return a},getComponentsString:function(){for(var a=[],b=0,c=this.components.length;b<c;b++)a.push(this.components[b].toShortString());return a.join(",")},calculateBounds:function(){this.bounds=null;var a=new OpenLayers.Bounds,b=this.components;if(b)for(var c=0,d=b.length;c<d;c++)a.extend(b[c].getBounds());null!=a.left&&(null!=a.bottom&&null!=a.right&&null!=a.top)&&this.setBounds(a)},addComponents:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(va [...]
+c;b++)this.addComponent(a[b])},addComponent:function(a,b){var c=!1;if(a&&(null==this.componentTypes||-1<OpenLayers.Util.indexOf(this.componentTypes,a.CLASS_NAME))){if(null!=b&&b<this.components.length){var c=this.components.slice(0,b),d=this.components.slice(b,this.components.length);c.push(a);this.components=c.concat(d)}else this.components.push(a);a.parent=this;this.clearBounds();c=!0}return c},removeComponents:function(a){var b=!1;OpenLayers.Util.isArray(a)||(a=[a]);for(var c=a.length [...]
+this.removeComponent(a[c])||b;return b},removeComponent:function(a){OpenLayers.Util.removeItem(this.components,a);this.clearBounds();return!0},getLength:function(){for(var a=0,b=0,c=this.components.length;b<c;b++)a+=this.components[b].getLength();return a},getArea:function(){for(var a=0,b=0,c=this.components.length;b<c;b++)a+=this.components[b].getArea();return a},getGeodesicArea:function(a){for(var b=0,c=0,d=this.components.length;c<d;c++)b+=this.components[c].getGeodesicArea(a);return  [...]
+this.components[0].getCentroid();a=this.components.length;if(!a)return!1;for(var b=[],c=[],d=0,e=Number.MAX_VALUE,f,g=0;g<a;++g){f=this.components[g];var h=f.getArea();f=f.getCentroid(!0);isNaN(h)||(isNaN(f.x)||isNaN(f.y))||(b.push(h),d+=h,e=h<e&&0<h?h:e,c.push(f))}a=b.length;if(0===d){for(g=0;g<a;++g)b[g]=1;d=b.length}else{for(g=0;g<a;++g)b[g]/=e;d/=e}for(var k=e=0,g=0;g<a;++g)f=c[g],h=b[g],e+=f.x*h,k+=f.y*h;return new OpenLayers.Geometry.Point(e/d,k/d)},getGeodesicLength:function(a){fo [...]
+c=0,d=this.components.length;c<d;c++)b+=this.components[c].getGeodesicLength(a);return b},move:function(a,b){for(var c=0,d=this.components.length;c<d;c++)this.components[c].move(a,b)},rotate:function(a,b){for(var c=0,d=this.components.length;c<d;++c)this.components[c].rotate(a,b)},resize:function(a,b,c){for(var d=0;d<this.components.length;++d)this.components[d].resize(a,b,c);return this},distanceTo:function(a,b){for(var c=!(b&&!1===b.edge)&&b&&b.details,d,e,f,g=Number.POSITIVE_INFINITY, [...]
+k&&!(d=this.components[h].distanceTo(a,b),f=c?d.distance:d,f<g&&(g=f,e=d,0==g));++h);return e},equals:function(a){var b=!0;if(a&&a.CLASS_NAME&&this.CLASS_NAME==a.CLASS_NAME)if(OpenLayers.Util.isArray(a.components)&&a.components.length==this.components.length)for(var c=0,d=this.components.length;c<d;++c){if(!this.components[c].equals(a.components[c])){b=!1;break}}else b=!1;else b=!1;return b},transform:function(a,b){if(a&&b){for(var c=0,d=this.components.length;c<d;c++)this.components[c]. [...]
+b);this.bounds=null}return this},intersects:function(a){for(var b=!1,c=0,d=this.components.length;c<d&&!(b=a.intersects(this.components[c]));++c);return b},getVertices:function(a){for(var b=[],c=0,d=this.components.length;c<d;++c)Array.prototype.push.apply(b,this.components[c].getVertices(a));return b},CLASS_NAME:"OpenLayers.Geometry.Collection"});OpenLayers.Geometry.MultiPoint=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Point"],addPoint:function [...]
+f;e++)c=b.components[e-1],d=b.components[e],a+=OpenLayers.Util.distVincenty({lon:c.x,lat:c.y},{lon:d.x,lat:d.y});return 1E3*a},CLASS_NAME:"OpenLayers.Geometry.Curve"});OpenLayers.Geometry.LineString=OpenLayers.Class(OpenLayers.Geometry.Curve,{removeComponent:function(a){var b=this.components&&2<this.components.length;b&&OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);return b},intersects:function(a){var b=!1,c=a.CLASS_NAME;if("OpenLayers.Geometry.LineString [...]
+var e,f,g,h,k,l,m,n=0,p=d.length;a:for(;n<p;++n){c=d[n];e=c.x1;f=c.x2;g=c.y1;h=c.y2;var q=0,r=a.length;for(;q<r;++q){k=a[q];if(k.x1>f)break;if(!(k.x2<e||(l=k.y1,m=k.y2,Math.min(l,m)>Math.max(g,h)||Math.max(l,m)<Math.min(g,h)||!OpenLayers.Geometry.segmentsIntersect(c,k)))){b=!0;break a}}}}else b=a.intersects(this);return b},getSortedSegments:function(){for(var a=this.components.length-1,b=Array(a),c,d,e=0;e<a;++e)c=this.components[e],d=this.components[e+1],b[e]=c.x<d.x?{x1:c.x,y1:c.y,x2:d [...]
+{x1:d.x,y1:d.y,x2:c.x,y2:c.y};return b.sort(function(a,b){return a.x1-b.x1})},splitWithSegment:function(a,b){for(var c=!(b&&!1===b.edge),d=b&&b.tolerance,e=[],f=this.getVertices(),g=[],h=[],k=!1,l,m,n,p={point:!0,tolerance:d},q=null,r=0,s=f.length-2;r<=s;++r)if(d=f[r],g.push(d.clone()),l=f[r+1],m={x1:d.x,y1:d.y,x2:l.x,y2:l.y},m=OpenLayers.Geometry.segmentsIntersect(a,m,p),m instanceof OpenLayers.Geometry.Point&&((n=m.x===a.x1&&m.y===a.y1||m.x===a.x2&&m.y===a.y2||m.equals(d)||m.equals(l)? [...]
+1])||h.push(m.clone()),0===r&&m.equals(d)||m.equals(l)||(k=!0,m.equals(d)||g.push(m),e.push(new OpenLayers.Geometry.LineString(g)),g=[m.clone()]);k&&(g.push(l.clone()),e.push(new OpenLayers.Geometry.LineString(g)));if(0<h.length)var t=a.x1<a.x2?1:-1,u=a.y1<a.y2?1:-1,q={lines:e,points:h.sort(function(a,b){return t*a.x-t*b.x||u*a.y-u*b.y})};return q},split:function(a,b){var c=null,d=b&&b.mutual,e,f,g,h;if(a instanceof OpenLayers.Geometry.LineString){var k=this.getVertices(),l,m,n,p,q,r=[]; [...]
+0,t=k.length-2;s<=t;++s){l=k[s];m=k[s+1];n={x1:l.x,y1:l.y,x2:m.x,y2:m.y};h=h||[a];d&&r.push(l.clone());for(var u=0;u<h.length;++u)if(p=h[u].splitWithSegment(n,b))if(q=p.lines,0<q.length&&(q.unshift(u,1),Array.prototype.splice.apply(h,q),u+=q.length-2),d)for(var v=0,w=p.points.length;v<w;++v)q=p.points[v],q.equals(l)||(r.push(q),g.push(new OpenLayers.Geometry.LineString(r)),r=q.equals(m)?[]:[q.clone()])}d&&(0<g.length&&0<r.length)&&(r.push(m.clone()),g.push(new OpenLayers.Geometry.LineStr [...]
+a.splitWith(this,b);h&&1<h.length?f=!0:h=[];g&&1<g.length?e=!0:g=[];if(f||e)c=d?[g,h]:h;return c},splitWith:function(a,b){return a.split(this,b)},getVertices:function(a){return!0===a?[this.components[0],this.components[this.components.length-1]]:!1===a?this.components.slice(1,this.components.length-1):this.components.slice()},distanceTo:function(a,b){var c=!(b&&!1===b.edge)&&b&&b.details,d,e={},f=Number.POSITIVE_INFINITY;if(a instanceof OpenLayers.Geometry.Point){for(var g=this.getSorted [...]
+h=a.x,k=a.y,l,m=0,n=g.length;m<n;++m)if(l=g[m],d=OpenLayers.Geometry.distanceToSegment(a,l),d.distance<f){if(f=d.distance,e=d,0===f)break}else if(l.x2>h&&(k>l.y1&&k<l.y2||k<l.y1&&k>l.y2))break;e=c?{distance:e.distance,x0:e.x,y0:e.y,x1:h,y1:k}:e.distance}else if(a instanceof OpenLayers.Geometry.LineString){var g=this.getSortedSegments(),h=a.getSortedSegments(),p,q,r=h.length,s={point:!0},m=0,n=g.length;a:for(;m<n;++m){k=g[m];l=k.x1;q=k.y1;for(var t=0;t<r;++t)if(d=h[t],p=OpenLayers.Geometr [...]
+d,s)){f=0;e={distance:0,x0:p.x,y0:p.y,x1:p.x,y1:p.y};break a}else d=OpenLayers.Geometry.distanceToSegment({x:l,y:q},d),d.distance<f&&(f=d.distance,e={distance:f,x0:l,y0:q,x1:d.x,y1:d.y})}c||(e=e.distance);0!==f&&k&&(d=a.distanceTo(new OpenLayers.Geometry.Point(k.x2,k.y2),b),m=c?d.distance:d,m<f&&(e=c?{distance:f,x0:d.x1,y0:d.y1,x1:d.x0,y1:d.y0}:m))}else e=a.distanceTo(this,b),c&&(e={distance:e.distance,x0:e.x1,y0:e.y1,x1:e.x0,y1:e.y0});return e},simplify:function(a){if(this&&null!==this) [...]
+if(3>b.length)return this;var c=function(a,b,d,k){for(var l=0,m=0,n=b,p;n<d;n++){p=a[b];var q=a[d],r=a[n],r=Math.abs(0.5*(p.x*q.y+q.x*r.y+r.x*p.y-q.x*p.y-r.x*q.y-p.x*r.y));p=Math.sqrt(Math.pow(p.x-q.x,2)+Math.pow(p.y-q.y,2));p=2*(r/p);p>l&&(l=p,m=n)}l>k&&m!=b&&(e.push(m),c(a,b,m,k),c(a,m,d,k))},d=b.length-1,e=[];e.push(0);for(e.push(d);b[0].equals(b[d]);)d--,e.push(d);c(b,0,d,a);a=[];e.sort(function(a,b){return a-b});for(d=0;d<e.length;d++)a.push(b[e[d]]);return new OpenLayers.Geometry.L [...]
+CLASS_NAME:"OpenLayers.Geometry.LineString"});OpenLayers.Geometry.MultiLineString=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LineString"],split:function(a,b){for(var c=null,d=b&&b.mutual,e,f,g,h,k=[],l=[a],m=0,n=this.components.length;m<n;++m){f=this.components[m];g=!1;for(var p=0;p<l.length;++p)if(e=f.split(l[p],b)){if(d){g=e[0];for(var q=0,r=g.length;q<r;++q)0===q&&k.length?k[k.length-1].addComponent(g[q]):k.push(new OpenLayers.Geometry.MultiL [...]
+1);Array.prototype.splice.apply(l,e);break}}g||(k.length?k[k.length-1].addComponent(f.clone()):k=[new OpenLayers.Geometry.MultiLineString(f.clone())])}k&&1<k.length?g=!0:k=[];l&&1<l.length?h=!0:l=[];if(g||h)c=d?[k,l]:l;return c},splitWith:function(a,b){var c=null,d=b&&b.mutual,e,f,g,h,k,l;if(a instanceof OpenLayers.Geometry.LineString){l=[];k=[a];for(var m=0,n=this.components.length;m<n;++m){g=!1;f=this.components[m];for(var p=0;p<k.length;++p)if(e=k[p].split(f,b)){d&&(g=e[0],g.length&&( [...]
+1),Array.prototype.splice.apply(k,g),p+=g.length-2),e=e[1],0===e.length&&(e=[f.clone()]));g=0;for(var q=e.length;g<q;++g)0===g&&l.length?l[l.length-1].addComponent(e[g]):l.push(new OpenLayers.Geometry.MultiLineString([e[g]]));g=!0}g||(l.length?l[l.length-1].addComponent(f.clone()):l=[new OpenLayers.Geometry.MultiLineString([f.clone()])])}}else c=a.split(this);k&&1<k.length?h=!0:k=[];l&&1<l.length?g=!0:l=[];if(h||g)c=d?[k,l]:l;return c},CLASS_NAME:"OpenLayers.Geometry.MultiLineString"});O [...]
+arguments),OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[this.components[0]]));return b},move:function(a,b){for(var c=0,d=this.components.length;c<d-1;c++)this.components[c].move(a,b)},rotate:function(a,b){for(var c=0,d=this.components.length;c<d-1;++c)this.components[c].rotate(a,b)},resize:function(a,b,c){for(var d=0,e=this.components.length;d<e-1;++d)this.components[d].resize(a,b,c);return this},transform:function(a,b){if(a&&b){for(var c=0,d=this.components.length;c [...]
+b);this.bounds=null}return this},getCentroid:function(){if(this.components){var a=this.components.length;if(0<a&&2>=a)return this.components[0].clone();if(2<a){var b=0,c=0,d=this.components[0].x,e=this.components[0].y,f=-1*this.getArea();if(0!=f){for(var g=0;g<a-1;g++)var h=this.components[g],k=this.components[g+1],b=b+(h.x+k.x-2*d)*((h.x-d)*(k.y-e)-(k.x-d)*(h.y-e)),c=c+(h.y+k.y-2*e)*((h.x-d)*(k.y-e)-(k.x-d)*(h.y-e));b=d+b/(6*f);a=e+c/(6*f)}else{for(g=0;g<a-1;g++)b+=this.components[g].x, [...]
+b/=a-1;a=c/(a-1)}return new OpenLayers.Geometry.Point(b,a)}return null}},getArea:function(){var a=0;if(this.components&&2<this.components.length){for(var b=a=0,c=this.components.length;b<c-1;b++)var d=this.components[b],e=this.components[b+1],a=a+(d.x+e.x)*(e.y-d.y);a=-a/2}return a},getGeodesicArea:function(a){var b=this;if(a){var c=new OpenLayers.Projection("EPSG:4326");c.equals(a)||(b=this.clone().transform(a,c))}a=0;c=b.components&&b.components.length;if(2<c){for(var d,e,f=0;f<c-1;f++ [...]
+e=b.components[f+1],a+=OpenLayers.Util.rad(e.x-d.x)*(2+Math.sin(OpenLayers.Util.rad(d.y))+Math.sin(OpenLayers.Util.rad(e.y)));a=40680631590769*a/2}return a},containsPoint:function(a){var b=OpenLayers.Number.limitSigDigs,c=b(a.x,14);a=b(a.y,14);for(var d=this.components.length-1,e,f,g,h,k,l=0,m=0;m<d;++m)if(e=this.components[m],g=b(e.x,14),e=b(e.y,14),f=this.components[m+1],h=b(f.x,14),f=b(f.y,14),e==f){if(a==e&&(g<=h&&c>=g&&c<=h||g>=h&&c<=g&&c>=h)){l=-1;break}}else{k=b((a-f)*((h-g)/(f-e) [...]
+c&&(e<f&&a>=e&&a<=f||e>f&&a<=e&&a>=f)){l=-1;break}k<=c||g!=h&&(k<Math.min(g,h)||k>Math.max(g,h))||(e<f&&a>=e&&a<f||e>f&&a<e&&a>=f)&&++l}return-1==l?1:!!(l&1)},intersects:function(a){var b=!1;if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.containsPoint(a);else if("OpenLayers.Geometry.LineString"==a.CLASS_NAME)b=a.intersects(this);else if("OpenLayers.Geometry.LinearRing"==a.CLASS_NAME)b=OpenLayers.Geometry.LineString.prototype.intersects.apply(this,[a]);else for(var c=0,d=a.components [...]
+d&&!(b=a.components[c].intersects(this));++c);return b},getVertices:function(a){return!0===a?[]:this.components.slice(0,this.components.length-1)},CLASS_NAME:"OpenLayers.Geometry.LinearRing"});OpenLayers.Geometry.Polygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LinearRing"],getArea:function(){var a=0;if(this.components&&0<this.components.length)for(var a=a+Math.abs(this.components[0].getArea()),b=1,c=this.components.length;b<c;b++)a-=Math.abs( [...]
+d;c++)b-=Math.abs(this.components[c].getGeodesicArea(a));return b},containsPoint:function(a){var b=this.components.length,c=!1;if(0<b&&(c=this.components[0].containsPoint(a),1!==c&&c&&1<b))for(var d,e=1;e<b;++e)if(d=this.components[e].containsPoint(a)){c=1===d?1:!1;break}return c},intersects:function(a){var b=!1,c,d;if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.containsPoint(a);else if("OpenLayers.Geometry.LineString"==a.CLASS_NAME||"OpenLayers.Geometry.LinearRing"==a.CLASS_NAME){c [...]
+this.components.length;c<d&&!(b=a.intersects(this.components[c]));++c);if(!b)for(c=0,d=a.components.length;c<d&&!(b=this.containsPoint(a.components[c]));++c);}else for(c=0,d=a.components.length;c<d&&!(b=this.intersects(a.components[c]));++c);if(!b&&"OpenLayers.Geometry.Polygon"==a.CLASS_NAME){var e=this.components[0];c=0;for(d=e.components.length;c<d&&!(b=a.containsPoint(e.components[c]));++c);}return b},distanceTo:function(a,b){return b&&!1===b.edge&&this.intersects(a)?0:OpenLayers.Geom [...]
+[a,b])},CLASS_NAME:"OpenLayers.Geometry.Polygon"});OpenLayers.Geometry.Polygon.createRegularPolygon=function(a,b,c,d){var e=Math.PI*(1/c-0.5);d&&(e+=d/180*Math.PI);for(var f,g=[],h=0;h<c;++h)f=e+2*h*Math.PI/c,d=a.x+b*Math.cos(f),f=a.y+b*Math.sin(f),g.push(new OpenLayers.Geometry.Point(d,f));a=new OpenLayers.Geometry.LinearRing(g);return new OpenLayers.Geometry.Polygon([a])};OpenLayers.Geometry.MultiPolygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geome [...]
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a=this.getElementsByTagNameNS(a.documentElement,this.gmlns,this.featureName);for(var b=[],c=0;c<a.length;c++){var d=this.parseFeature(a[c]);d&&b.push(d)}return b},parseFeature:function(a){for(var b="MultiPolygon Polygon MultiLineString LineString MultiPoint Point Envelope".split(" "),c,d,e,f=0;f<b.length;++f)if(c=b[f],d=this.getElementsByTagNameNS(a,this.gmlns,c),0<d.length){if(e=this.parseGeometry[c.toLowerCase()])e=e.ap [...]
+[d[0]]),this.internalProjection&&this.externalProjection&&e.transform(this.externalProjection,this.internalProjection);else throw new TypeError("Unsupported geometry type: "+c);break}var g;c=this.getElementsByTagNameNS(a,this.gmlns,"Box");for(f=0;f<c.length;++f)b=c[f],d=this.parseGeometry.box.apply(this,[b]),b=b.parentNode,"boundedBy"===(b.localName||b.nodeName.split(":").pop())?g=d:e=d.toGeometry();var h;this.extractAttributes&&(h=this.parseAttributes(a));h=new OpenLayers.Feature.Vector [...]
+g;h.gml={featureType:a.firstChild.nodeName.split(":")[1],featureNS:a.firstChild.namespaceURI,featureNSPrefix:a.firstChild.prefix};a=a.firstChild;for(var k;a&&(1!=a.nodeType||!(k=a.getAttribute("fid")||a.getAttribute("id")));)a=a.nextSibling;h.fid=k;return h},parseGeometry:{point:function(a){var b,c;c=[];b=this.getElementsByTagNameNS(a,this.gmlns,"pos");0<b.length&&(c=b[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));0==c.length&&(b=this. [...]
+this.gmlns,"coordinates"),0<b.length&&(c=b[0].firstChild.nodeValue,c=c.replace(this.regExes.removeSpace,""),c=c.split(",")));0==c.length&&(b=this.getElementsByTagNameNS(a,this.gmlns,"coord"),0<b.length&&(a=this.getElementsByTagNameNS(b[0],this.gmlns,"X"),b=this.getElementsByTagNameNS(b[0],this.gmlns,"Y"),0<a.length&&0<b.length&&(c=[a[0].firstChild.nodeValue,b[0].firstChild.nodeValue])));2==c.length&&(c[2]=null);return this.xy?new OpenLayers.Geometry.Point(c[0],c[1],c[2]):new OpenLayers.G [...]
+c[0],c[2])},multipoint:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"Point");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.point.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiPoint(b)},linestring:function(a,b){var c,d;d=[];var e=[];c=this.getElementsByTagNameNS(a,this.gmlns,"posList");if(0<c.length){d=this.getChildValue(c[0]);d=d.replace(this.regExes.trimSpace,"");d=d.split(this.regExes.splitSpace);var f=parseInt(c[0].getAttribut [...]
+g,h,k;for(c=0;c<d.length/f;++c)g=c*f,h=d[g],k=d[g+1],g=2==f?null:d[g+2],this.xy?e.push(new OpenLayers.Geometry.Point(h,k,g)):e.push(new OpenLayers.Geometry.Point(k,h,g))}if(0==d.length&&(c=this.getElementsByTagNameNS(a,this.gmlns,"coordinates"),0<c.length))for(d=this.getChildValue(c[0]),d=d.replace(this.regExes.trimSpace,""),d=d.replace(this.regExes.trimComma,","),f=d.split(this.regExes.splitSpace),c=0;c<f.length;++c)d=f[c].split(","),2==d.length&&(d[2]=null),this.xy?e.push(new OpenLayer [...]
+d[1],d[2])):e.push(new OpenLayers.Geometry.Point(d[1],d[0],d[2]));d=null;0!=e.length&&(d=b?new OpenLayers.Geometry.LinearRing(e):new OpenLayers.Geometry.LineString(e));return d},multilinestring:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"LineString");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.linestring.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiLineString(b)},polygon:function(a){a=this.getElementsByTagNameNS(a,this.gmlns [...]
+var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.linestring.apply(this,[a[d],!0]))&&b.push(c);return new OpenLayers.Geometry.Polygon(b)},multipolygon:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"Polygon");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.polygon.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiPolygon(b)},envelope:function(a){var b=[],c,d,e=this.getElementsByTagNameNS(a,this.gmlns,"lowerCorner" [...]
+[];0<e.length&&(c=e[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));2==c.length&&(c[2]=null);var f=this.xy?new OpenLayers.Geometry.Point(c[0],c[1],c[2]):new OpenLayers.Geometry.Point(c[1],c[0],c[2])}a=this.getElementsByTagNameNS(a,this.gmlns,"upperCorner");if(0<a.length){c=[];0<a.length&&(c=a[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));2==c.length&&(c[2]=null);var g=this.xy?new OpenL [...]
+c[1],c[2]):new OpenLayers.Geometry.Point(c[1],c[0],c[2])}f&&g&&(b.push(new OpenLayers.Geometry.Point(f.x,f.y)),b.push(new OpenLayers.Geometry.Point(g.x,f.y)),b.push(new OpenLayers.Geometry.Point(g.x,g.y)),b.push(new OpenLayers.Geometry.Point(f.x,g.y)),b.push(new OpenLayers.Geometry.Point(f.x,f.y)),b=new OpenLayers.Geometry.LinearRing(b),d=new OpenLayers.Geometry.Polygon([b]));return d},box:function(a){var b=this.getElementsByTagNameNS(a,this.gmlns,"coordinates"),c=a=null;0<b.length&&(b=b [...]
+b=b.split(" "),2==b.length&&(a=b[0].split(","),c=b[1].split(",")));if(null!==a&&null!==c)return new OpenLayers.Bounds(parseFloat(a[0]),parseFloat(a[1]),parseFloat(c[0]),parseFloat(c[1]))}},parseAttributes:function(a){var b={};a=a.firstChild;for(var c,d,e;a;){if(1==a.nodeType){a=a.childNodes;for(c=0;c<a.length;++c)if(d=a[c],1==d.nodeType)if(e=d.childNodes,1==e.length){if(e=e[0],3==e.nodeType||4==e.nodeType)d=d.prefix?d.nodeName.split(":")[1]:d.nodeName,e=e.nodeValue.replace(this.regExes.t [...]
+""),b[d]=e}else b[d.nodeName.split(":").pop()]=null;break}a=a.nextSibling}return b},write:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=this.createElementNS("http://www.opengis.net/wfs","wfs:"+this.collectionName),c=0;c<a.length;c++)b.appendChild(this.createFeatureXML(a[c]));return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFeatureXML:function(a){var b=this.buildGeometryNode(a.geometry),c=this.createElementNS(this.featureNS,this.featurePrefix+":"+this.geomet [...]
+var b=this.createElementNS(this.gmlns,"gml:"+this.featureName),d=this.createElementNS(this.featureNS,this.featurePrefix+":"+this.layerName);d.setAttribute("fid",a.fid||a.id);d.appendChild(c);for(var e in a.attributes){var c=this.createTextNode(a.attributes[e]),f=e.substring(e.lastIndexOf(":")+1),f=this.createElementNS(this.featureNS,this.featurePrefix+":"+f);f.appendChild(c);d.appendChild(f)}b.appendChild(d);return b},buildGeometryNode:function(a){this.externalProjection&&this.internalPr [...]
+(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b=a.CLASS_NAME,b=b.substring(b.lastIndexOf(".")+1);return this.buildGeometry[b.toLowerCase()].apply(this,[a])},buildGeometry:{point:function(a){var b=this.createElementNS(this.gmlns,"gml:Point");b.appendChild(this.buildCoordinatesNode(a));return b},multipoint:function(a){var b=this.createElementNS(this.gmlns,"gml:MultiPoint");a=a.components;for(var c,d,e=0;e<a.length;e++)c=this.createElementNS(this.gmlns,"gml: [...]
+d=this.buildGeometry.point.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},linestring:function(a){var b=this.createElementNS(this.gmlns,"gml:LineString");b.appendChild(this.buildCoordinatesNode(a));return b},multilinestring:function(a){var b=this.createElementNS(this.gmlns,"gml:MultiLineString");a=a.components;for(var c,d,e=0;e<a.length;++e)c=this.createElementNS(this.gmlns,"gml:lineStringMember"),d=this.buildGeometry.linestring.apply(this,[a[e]]),c.appendChild(d),b.append [...]
+return b},linearring:function(a){var b=this.createElementNS(this.gmlns,"gml:LinearRing");b.appendChild(this.buildCoordinatesNode(a));return b},polygon:function(a){var b=this.createElementNS(this.gmlns,"gml:Polygon");a=a.components;for(var c,d,e=0;e<a.length;++e)c=0==e?"outerBoundaryIs":"innerBoundaryIs",c=this.createElementNS(this.gmlns,"gml:"+c),d=this.buildGeometry.linearring.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},multipolygon:function(a){var b=this.createElemen [...]
+"gml:MultiPolygon");a=a.components;for(var c,d,e=0;e<a.length;++e)c=this.createElementNS(this.gmlns,"gml:polygonMember"),d=this.buildGeometry.polygon.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},bounds:function(a){var b=this.createElementNS(this.gmlns,"gml:Box");b.appendChild(this.buildCoordinatesNode(a));return b}},buildCoordinatesNode:function(a){var b=this.createElementNS(this.gmlns,"gml:coordinates");b.setAttribute("decimal",".");b.setAttribute("cs",",");b.setAttrib [...]
+" ");var c=[];if(a instanceof OpenLayers.Bounds)c.push(a.left+","+a.bottom),c.push(a.right+","+a.top);else{a=a.components?a.components:[a];for(var d=0;d<a.length;d++)c.push(a[d].x+","+a[d].y)}c=this.createTextNode(c.join(" "));b.appendChild(c);return b},CLASS_NAME:"OpenLayers.Format.GML"});OpenLayers.Format.GML||(OpenLayers.Format.GML={});
+OpenLayers.Format.GML.Base=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs"},defaultPrefix:"gml",schemaLocation:null,featureType:null,featureNS:null,geometryName:"geometry",extractAttributes:!0,srsName:null,xy:!0,geometryTypes:null,singleFeatureType:null,regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*, [...]
+initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);this.setGeometryTypes();a&&a.featureNS&&this.setNamespace("feature",a.featureNS);this.singleFeatureType=!a||"string"===typeof a.featureType},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b=[];this.readNode(a,{features:b},!0);if(0==b.length){var c=this.getElementsByTagNameNS(a,this.namespaces.gml,"featureMember");if(c [...]
+0;for(var d=c.length;a<d;++a)this.readNode(c[a],{features:b},!0)}else c=this.getElementsByTagNameNS(a,this.namespaces.gml,"featureMembers"),c.length&&this.readNode(c[0],{features:b},!0)}return b},readNode:function(a,b,c){!0===c&&!0===this.autoConfig&&(this.featureType=null,delete this.namespaceAlias[this.featureNS],delete this.namespaces.feature,this.featureNS=null);this.featureNS||(a.prefix in this.namespaces||a.parentNode.namespaceURI!=this.namespaces.gml||!this.regExes.featureMember.t [...]
+(this.featureType=a.nodeName.split(":").pop(),this.setNamespace("feature",a.namespaceURI),this.featureNS=a.namespaceURI,this.autoConfig=!0);return OpenLayers.Format.XML.prototype.readNode.apply(this,[a,b])},readers:{gml:{_inherit:function(a,b,c){},featureMember:function(a,b){this.readChildNodes(a,b)},featureMembers:function(a,b){this.readChildNodes(a,b)},name:function(a,b){b.name=this.getChildValue(a)},boundedBy:function(a,b){var c={};this.readChildNodes(a,c);c.components&&0<c.components [...]
+(b.bounds=c.components[0])},Point:function(a,b){var c={points:[]};this.readChildNodes(a,c);b.components||(b.components=[]);b.components.push(c.points[0])},coordinates:function(a,b){for(var c=this.getChildValue(a).replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),c=c.split(this.regExes.splitSpace),d,e=c.length,f=Array(e),g=0;g<e;++g)d=c[g].split(","),f[g]=this.xy?new OpenLayers.Geometry.Point(d[0],d[1],d[2]):new OpenLayers.Geometry.Point(d[1],d[0],d[2]);b.points=f [...]
+b){var c={};this.readChildNodes(a,c);b.points||(b.points=[]);b.points.push(new OpenLayers.Geometry.Point(c.x,c.y,c.z))},X:function(a,b){b.x=this.getChildValue(a)},Y:function(a,b){b.y=this.getChildValue(a)},Z:function(a,b){b.z=this.getChildValue(a)},MultiPoint:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiPoint(c.components)]},pointMember:function(a,b){this.readChildNodes(a,b)},LineStri [...]
+b){var c={};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components||(b.components=[]);b.components.push(new OpenLayers.Geometry.LineString(c.points))},MultiLineString:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiLineString(c.components)]},lineStringMember:function(a,b){this.readChildNodes(a,b)},Polygon:function(a,b){var c={outer:null,inner:[]};this.readers [...]
+[a,c,b]);this.readChildNodes(a,c);c.inner.unshift(c.outer);b.components||(b.components=[]);b.components.push(new OpenLayers.Geometry.Polygon(c.inner))},LinearRing:function(a,b){var c={};this.readers.gml._inherit.apply(this,[a,c]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.LinearRing(c.points)]},MultiPolygon:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiPolygon(c.com [...]
+polygonMember:function(a,b){this.readChildNodes(a,b)},GeometryCollection:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.Collection(c.components)]},geometryMember:function(a,b){this.readChildNodes(a,b)}},feature:{"*":function(a,b){var c,d=a.localName||a.nodeName.split(":").pop();b.features?this.singleFeatureType||-1===OpenLayers.Util.indexOf(this.featureType,d)?d===this.featureType&&(c="_type [...]
+"_typeName":0==a.childNodes.length||1==a.childNodes.length&&3==a.firstChild.nodeType?this.extractAttributes&&(c="_attribute"):c="_geometry";c&&this.readers.feature[c].apply(this,[a,b])},_typeName:function(a,b){var c={components:[],attributes:{}};this.readChildNodes(a,c);c.name&&(c.attributes.name=c.name);var d=new OpenLayers.Feature.Vector(c.components[0],c.attributes);this.singleFeatureType||(d.type=a.nodeName.split(":").pop(),d.namespace=a.namespaceURI);var e=a.getAttribute("fid")||thi [...]
+this.namespaces.gml,"id");e&&(d.fid=e);this.internalProjection&&(this.externalProjection&&d.geometry)&&d.geometry.transform(this.externalProjection,this.internalProjection);c.bounds&&(d.bounds=c.bounds);b.features.push(d)},_geometry:function(a,b){this.geometryName||(this.geometryName=a.nodeName.split(":").pop());this.readChildNodes(a,b)},_attribute:function(a,b){var c=a.localName||a.nodeName.split(":").pop(),d=this.getChildValue(a);b.attributes[c]=d}},wfs:{FeatureCollection:function(a,b) [...]
+b)}}},write:function(a){var b;b=OpenLayers.Util.isArray(a)?"featureMembers":"featureMember";a=this.writeNode("gml:"+b,a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{gml:{featureMember:function(a){var b=this.createElementNSPlus("gml:featureMember");this.writeNode("feature:_typeName",a,b);return b},MultiPoint:function(a){var b=this.createElementNSPlus("gml:MultiPoint");a=a.compone [...]
+for(var c=0,d=a.length;c<d;++c)this.writeNode("pointMember",a[c],b);return b},pointMember:function(a){var b=this.createElementNSPlus("gml:pointMember");this.writeNode("Point",a,b);return b},MultiLineString:function(a){var b=this.createElementNSPlus("gml:MultiLineString");a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("lineStringMember",a[c],b);return b},lineStringMember:function(a){var b=this.createElementNSPlus("gml:lineStringMember");this.writeNode("LineString",a,b); [...]
+MultiPolygon:function(a){var b=this.createElementNSPlus("gml:MultiPolygon");a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("polygonMember",a[c],b);return b},polygonMember:function(a){var b=this.createElementNSPlus("gml:polygonMember");this.writeNode("Polygon",a,b);return b},GeometryCollection:function(a){for(var b=this.createElementNSPlus("gml:GeometryCollection"),c=0,d=a.components.length;c<d;++c)this.writeNode("geometryMember",a.components[c],b);return b},geometryMem [...]
+this.createElementNSPlus("gml:geometryMember");a=this.writeNode("feature:_geometry",a);b.appendChild(a.firstChild);return b}},feature:{_typeName:function(a){var b=this.createElementNSPlus("feature:"+this.featureType,{attributes:{fid:a.fid}});a.geometry&&this.writeNode("feature:_geometry",a.geometry,b);for(var c in a.attributes){var d=a.attributes[c];null!=d&&this.writeNode("feature:_attribute",{name:c,value:d},b)}return b},_geometry:function(a){this.externalProjection&&this.internalProje [...]
+a.clone().transform(this.internalProjection,this.externalProjection));var b=this.createElementNSPlus("feature:"+this.geometryName);a=this.writeNode("gml:"+this.geometryTypes[a.CLASS_NAME],a,b);this.srsName&&a.setAttribute("srsName",this.srsName);return b},_attribute:function(a){return this.createElementNSPlus("feature:"+a.name,{value:a.value})}},wfs:{FeatureCollection:function(a){for(var b=this.createElementNSPlus("wfs:FeatureCollection"),c=0,d=a.length;c<d;++c)this.writeNode("gml:featur [...]
+a[c],b);return b}}},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":"LineString","OpenLayers.Geometry.MultiLineString":"MultiLineString","OpenLayers.Geometry.Polygon":"Polygon","OpenLayers.Geometry.MultiPolygon":"MultiPolygon","OpenLayers.Geometry.Collection":"GeometryCollection"}},CLASS_NAME:"OpenLayers.Format.GML.Base"});OpenLayers.Format.GML.v3=OpenLayers.Class(OpenLayers [...]
+b){this.readChildNodes(a,b)},Curve:function(a,b){var c={points:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components||(b.components=[]);b.components.push(new OpenLayers.Geometry.LineString(c.points))},segments:function(a,b){this.readChildNodes(a,b)},LineStringSegment:function(a,b){var c={};this.readChildNodes(a,c);c.points&&Array.prototype.push.apply(b.points,c.points)},pos:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").spli [...]
+c=this.xy?new OpenLayers.Geometry.Point(c[0],c[1],c[2]):new OpenLayers.Geometry.Point(c[1],c[0],c[2]);b.points=[c]},posList:function(a,b){for(var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(this.regExes.splitSpace),d=b.srsDimension||parseInt(a.getAttribute("srsDimension")||a.getAttribute("dimension"),10)||2,e,f,g,h=Array(c.length/d),k=0,l=c.length;k<l;k+=d)e=c[k],f=c[k+1],g=2==d?void 0:c[k+2],h[k/d]=this.xy?new OpenLayers.Geometry.Point(e,f,g):new OpenLayers.Geometry [...]
+e,g);b.points=h},Surface:function(a,b){this.readChildNodes(a,b)},patches:function(a,b){this.readChildNodes(a,b)},PolygonPatch:function(a,b){this.readers.gml.Polygon.apply(this,[a,b])},exterior:function(a,b){var c={};this.readChildNodes(a,c);b.outer=c.components[0]},interior:function(a,b){var c={};this.readChildNodes(a,c);b.inner.push(c.components[0])},MultiCurve:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);0<c.components.lengt [...]
+[new OpenLayers.Geometry.MultiLineString(c.components)])},curveMember:function(a,b){this.readChildNodes(a,b)},MultiSurface:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);0<c.components.length&&(b.components=[new OpenLayers.Geometry.MultiPolygon(c.components)])},surfaceMember:function(a,b){this.readChildNodes(a,b)},surfaceMembers:function(a,b){this.readChildNodes(a,b)},pointMembers:function(a,b){this.readChildNodes(a,b)},lineStri [...]
+b){this.readChildNodes(a,b)},polygonMembers:function(a,b){this.readChildNodes(a,b)},geometryMembers:function(a,b){this.readChildNodes(a,b)},Envelope:function(a,b){var c={points:Array(2)};this.readChildNodes(a,c);b.components||(b.components=[]);var d=c.points[0],c=c.points[1];b.components.push(new OpenLayers.Bounds(d.x,d.y,c.x,c.y))},lowerCorner:function(a,b){var c={};this.readers.gml.pos.apply(this,[a,c]);b.points[0]=c.points[0]},upperCorner:function(a,b){var c={};this.readers.gml.pos.ap [...]
+[a,c]);b.points[1]=c.points[0]}},OpenLayers.Format.GML.Base.prototype.readers.gml),feature:OpenLayers.Format.GML.Base.prototype.readers.feature,wfs:OpenLayers.Format.GML.Base.prototype.readers.wfs},write:function(a){var b;b=OpenLayers.Util.isArray(a)?"featureMembers":"featureMember";a=this.writeNode("gml:"+b,a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{gml:OpenLayers.Util.appl [...]
+this.createElementNSPlus("gml:featureMembers"),c=0,d=a.length;c<d;++c)this.writeNode("feature:_typeName",a[c],b);return b},Point:function(a){var b=this.createElementNSPlus("gml:Point");this.writeNode("pos",a,b);return b},pos:function(a){return this.createElementNSPlus("gml:pos",{value:this.xy?a.x+" "+a.y:a.y+" "+a.x})},LineString:function(a){var b=this.createElementNSPlus("gml:LineString");this.writeNode("posList",a.components,b);return b},Curve:function(a){var b=this.createElementNSPlus [...]
+this.writeNode("segments",a,b);return b},segments:function(a){var b=this.createElementNSPlus("gml:segments");this.writeNode("LineStringSegment",a,b);return b},LineStringSegment:function(a){var b=this.createElementNSPlus("gml:LineStringSegment");this.writeNode("posList",a.components,b);return b},posList:function(a){for(var b=a.length,c=Array(b),d,e=0;e<b;++e)d=a[e],c[e]=this.xy?d.x+" "+d.y:d.y+" "+d.x;return this.createElementNSPlus("gml:posList",{value:c.join(" ")})},Surface:function(a){ [...]
+this.writeNode("patches",a,b);return b},patches:function(a){var b=this.createElementNSPlus("gml:patches");this.writeNode("PolygonPatch",a,b);return b},PolygonPatch:function(a){var b=this.createElementNSPlus("gml:PolygonPatch",{attributes:{interpolation:"planar"}});this.writeNode("exterior",a.components[0],b);for(var c=1,d=a.components.length;c<d;++c)this.writeNode("interior",a.components[c],b);return b},Polygon:function(a){var b=this.createElementNSPlus("gml:Polygon");this.writeNode("ext [...]
+b);for(var c=1,d=a.components.length;c<d;++c)this.writeNode("interior",a.components[c],b);return b},exterior:function(a){var b=this.createElementNSPlus("gml:exterior");this.writeNode("LinearRing",a,b);return b},interior:function(a){var b=this.createElementNSPlus("gml:interior");this.writeNode("LinearRing",a,b);return b},LinearRing:function(a){var b=this.createElementNSPlus("gml:LinearRing");this.writeNode("posList",a.components,b);return b},MultiCurve:function(a){var b=this.createElement [...]
+a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("curveMember",a[c],b);return b},curveMember:function(a){var b=this.createElementNSPlus("gml:curveMember");this.curve?this.writeNode("Curve",a,b):this.writeNode("LineString",a,b);return b},MultiSurface:function(a){var b=this.createElementNSPlus("gml:MultiSurface");a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("surfaceMember",a[c],b);return b},surfaceMember:function(a){var b=this.createElementNSPlus("gml: [...]
+this.surface?this.writeNode("Surface",a,b):this.writeNode("Polygon",a,b);return b},Envelope:function(a){var b=this.createElementNSPlus("gml:Envelope");this.writeNode("lowerCorner",a,b);this.writeNode("upperCorner",a,b);this.srsName&&b.setAttribute("srsName",this.srsName);return b},lowerCorner:function(a){return this.createElementNSPlus("gml:lowerCorner",{value:this.xy?a.left+" "+a.bottom:a.bottom+" "+a.left})},upperCorner:function(a){return this.createElementNSPlus("gml:upperCorner",{val [...]
+a.right+" "+a.top:a.top+" "+a.right})}},OpenLayers.Format.GML.Base.prototype.writers.gml),feature:OpenLayers.Format.GML.Base.prototype.writers.feature,wfs:OpenLayers.Format.GML.Base.prototype.writers.wfs},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":!0===this.curve?"Curve":"LineString","OpenLayers.Geometry.MultiLineString":!1===this.multiCurve?"MultiLineString":"MultiCurv [...]
+this.surface?"Surface":"Polygon","OpenLayers.Geometry.MultiPolygon":!1===this.multiSurface?"MultiPolygon":"MultiSurface","OpenLayers.Geometry.Collection":"GeometryCollection"}},CLASS_NAME:"OpenLayers.Format.GML.v3"});OpenLayers.Format.Filter.v1_1_0=OpenLayers.Class(OpenLayers.Format.GML.v3,OpenLayers.Format.Filter.v1,{VERSION:"1.1.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.1.0/filter.xsd",initialize:function(a){OpenLayers.Format.GML.v3.prototype.initialize.apply(this,[a])},re [...]
+c);b.filters.push(c)},PropertyIsNotEqualTo:function(a,b){var c=a.getAttribute("matchCase"),c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO,matchCase:!("false"===c||"0"===c)});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLike:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(a,c);var d=a.getAttribute("wildCard"),e=a.getAttribute("singleChar"),f=a.getAttribute("escapeChar");c.value2 [...]
+f);b.filters.push(c)}},OpenLayers.Format.Filter.v1.prototype.readers.ogc),gml:OpenLayers.Format.GML.v3.prototype.readers.gml,feature:OpenLayers.Format.GML.v3.prototype.readers.feature},writers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsEqualTo",{attributes:{matchCase:a.matchCase}});this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsNotEqualTo:function(a){var b=this.createElementNSP [...]
+{attributes:{matchCase:a.matchCase}});this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsLike:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLike",{attributes:{matchCase:a.matchCase,wildCard:"*",singleChar:".",escapeChar:"!"}});this.writeNode("PropertyName",a,b);this.writeNode("Literal",a.regex2value(),b);return b},BBOX:function(a){var b=this.createElementNSPlus("ogc:BBOX");a.property&&this.writeNode("PropertyName",a,b);var c=this.writeNo [...]
+a.value);a.projection&&c.setAttribute("srsName",a.projection);b.appendChild(c);return b},SortBy:function(a){for(var b=this.createElementNSPlus("ogc:SortBy"),c=0,d=a.length;c<d;c++)this.writeNode("ogc:SortProperty",a[c],b);return b},SortProperty:function(a){var b=this.createElementNSPlus("ogc:SortProperty");this.writeNode("ogc:PropertyName",a,b);this.writeNode("ogc:SortOrder","DESC"==a.order?"DESC":"ASC",b);return b},SortOrder:function(a){return this.createElementNSPlus("ogc:SortOrder",{v [...]
+OpenLayers.Format.Filter.v1.prototype.writers.ogc),gml:OpenLayers.Format.GML.v3.prototype.writers.gml,feature:OpenLayers.Format.GML.v3.prototype.writers.feature},writeSpatial:function(a,b){var c=this.createElementNSPlus("ogc:"+b);this.writeNode("PropertyName",a,c);if(a.value instanceof OpenLayers.Filter.Function)this.writeNode("Function",a.value,c);else{var d;d=a.value instanceof OpenLayers.Geometry?this.writeNode("feature:_geometry",a.value).firstChild:this.writeNode("gml:Envelope",a.va [...]
+d.setAttribute("srsName",a.projection);c.appendChild(d)}return c},CLASS_NAME:"OpenLayers.Format.Filter.v1_1_0"});OpenLayers.Format.OWSCommon=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",getVersion:function(a,b){var c=this.version;if(!c){var d=a.getAttribute("xmlns:ows");d&&"1.1"===d.substring(d.lastIndexOf("/")+1)&&(c="1.1.0");c||(c=this.defaultVersion)}return c},CLASS_NAME:"OpenLayers.Format.OWSCommon"});OpenLayers.Format.OWSCommon.v1=OpenLayers.Class(Open [...]
+ServiceIdentification:function(a,b){b.serviceIdentification={};this.readChildNodes(a,b.serviceIdentification)},Title:function(a,b){b.title=this.getChildValue(a)},Abstract:function(a,b){b["abstract"]=this.getChildValue(a)},Keywords:function(a,b){b.keywords={};this.readChildNodes(a,b.keywords)},Keyword:function(a,b){b[this.getChildValue(a)]=!0},ServiceType:function(a,b){b.serviceType={codeSpace:a.getAttribute("codeSpace"),value:this.getChildValue(a)}},ServiceTypeVersion:function(a,b){b.ser [...]
+this.getChildValue(a)},Fees:function(a,b){b.fees=this.getChildValue(a)},AccessConstraints:function(a,b){b.accessConstraints=this.getChildValue(a)},ServiceProvider:function(a,b){b.serviceProvider={};this.readChildNodes(a,b.serviceProvider)},ProviderName:function(a,b){b.providerName=this.getChildValue(a)},ProviderSite:function(a,b){b.providerSite=this.getAttributeNS(a,this.namespaces.xlink,"href")},ServiceContact:function(a,b){b.serviceContact={};this.readChildNodes(a,b.serviceContact)},In [...]
+b){b.individualName=this.getChildValue(a)},PositionName:function(a,b){b.positionName=this.getChildValue(a)},ContactInfo:function(a,b){b.contactInfo={};this.readChildNodes(a,b.contactInfo)},Phone:function(a,b){b.phone={};this.readChildNodes(a,b.phone)},Voice:function(a,b){b.voice=this.getChildValue(a)},Address:function(a,b){b.address={};this.readChildNodes(a,b.address)},DeliveryPoint:function(a,b){b.deliveryPoint=this.getChildValue(a)},City:function(a,b){b.city=this.getChildValue(a)},Admi [...]
+b){b.administrativeArea=this.getChildValue(a)},PostalCode:function(a,b){b.postalCode=this.getChildValue(a)},Country:function(a,b){b.country=this.getChildValue(a)},ElectronicMailAddress:function(a,b){b.electronicMailAddress=this.getChildValue(a)},Role:function(a,b){b.role=this.getChildValue(a)},OperationsMetadata:function(a,b){b.operationsMetadata={};this.readChildNodes(a,b.operationsMetadata)},Operation:function(a,b){var c=a.getAttribute("name");b[c]={};this.readChildNodes(a,b[c])},DCP:f [...]
+b){b.dcp={};this.readChildNodes(a,b.dcp)},HTTP:function(a,b){b.http={};this.readChildNodes(a,b.http)},Get:function(a,b){b.get||(b.get=[]);var c={url:this.getAttributeNS(a,this.namespaces.xlink,"href")};this.readChildNodes(a,c);b.get.push(c)},Post:function(a,b){b.post||(b.post=[]);var c={url:this.getAttributeNS(a,this.namespaces.xlink,"href")};this.readChildNodes(a,c);b.post.push(c)},Parameter:function(a,b){b.parameters||(b.parameters={});var c=a.getAttribute("name");b.parameters[c]={};th [...]
+b.parameters[c])},Constraint:function(a,b){b.constraints||(b.constraints={});var c=a.getAttribute("name");b.constraints[c]={};this.readChildNodes(a,b.constraints[c])},Value:function(a,b){b[this.getChildValue(a)]=!0},OutputFormat:function(a,b){b.formats.push({value:this.getChildValue(a)});this.readChildNodes(a,b)},WGS84BoundingBox:function(a,b){var c={};c.crs=a.getAttribute("crs");b.BoundingBox?b.BoundingBox.push(c):(b.projection=c.crs,c=b);this.readChildNodes(a,c)},BoundingBox:function(a [...]
+[a,b])},LowerCorner:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),c=c.split(this.regExes.splitSpace);b.left=c[0];b.bottom=c[1]},UpperCorner:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),c=c.split(this.regExes.splitSpace);b.right=c[0];b.top=c[1];b.bounds=new OpenLayers.Bounds(b.left,b.bottom,b.right,b.top);delete b.left;delete b.bottom;delete b.righ [...]
+Language:function(a,b){b.language=this.getChildValue(a)}}},writers:{ows:{BoundingBox:function(a,b){var c=this.createElementNSPlus(b||"ows:BoundingBox",{attributes:{crs:a.projection}});this.writeNode("ows:LowerCorner",a,c);this.writeNode("ows:UpperCorner",a,c);return c},LowerCorner:function(a){return this.createElementNSPlus("ows:LowerCorner",{value:a.bounds.left+" "+a.bounds.bottom})},UpperCorner:function(a){return this.createElementNSPlus("ows:UpperCorner",{value:a.bounds.right+" "+a.bo [...]
+Identifier:function(a){return this.createElementNSPlus("ows:Identifier",{value:a})},Title:function(a){return this.createElementNSPlus("ows:Title",{value:a})},Abstract:function(a){return this.createElementNSPlus("ows:Abstract",{value:a})},OutputFormat:function(a){return this.createElementNSPlus("ows:OutputFormat",{value:a})}}},CLASS_NAME:"OpenLayers.Format.OWSCommon.v1"});OpenLayers.Format.OWSCommon.v1_0_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1,{namespaces:{ows:"http://www.opengi [...]
+CLASS_NAME:"OpenLayers.Format.OWSCommon.v1_0_0"});OpenLayers.Format.WFST.v1_1_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_1_0,OpenLayers.Format.WFST.v1,{version:"1.1.0",schemaLocations:{wfs:"http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"},initialize:function(a){OpenLayers.Format.Filter.v1_1_0.prototype.initialize.apply(this,[a]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[a])},readNode:function(a,b,c){return OpenLayers.Format.GML.v3.prototype.readNode.apply(this,argument [...]
+b){b.numberOfFeatures=parseInt(a.getAttribute("numberOfFeatures"));OpenLayers.Format.WFST.v1.prototype.readers.wfs.FeatureCollection.apply(this,arguments)},TransactionResponse:function(a,b){b.insertIds=[];b.success=!1;this.readChildNodes(a,b)},TransactionSummary:function(a,b){b.success=!0},InsertResults:function(a,b){this.readChildNodes(a,b)},Feature:function(a,b){var c={fids:[]};this.readChildNodes(a,c);b.insertIds.push(c.fids[0])}},OpenLayers.Format.WFST.v1.prototype.readers.wfs),gml:O [...]
+feature:OpenLayers.Format.GML.v3.prototype.readers.feature,ogc:OpenLayers.Format.Filter.v1_1_0.prototype.readers.ogc,ows:OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows},writers:{wfs:OpenLayers.Util.applyDefaults({GetFeature:function(a){var b=OpenLayers.Format.WFST.v1.prototype.writers.wfs.GetFeature.apply(this,arguments);a&&this.setAttributes(b,{resultType:a.resultType,startIndex:a.startIndex,count:a.count});return b},Query:function(a){a=OpenLayers.Util.extend({featureNS:this.f [...]
+featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName},a);var b=a.featurePrefix,c=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(b?b+":":"")+a.featureType,srsName:a.srsName}});a.featureNS&&c.setAttribute("xmlns:"+b,a.featureNS);if(a.propertyNames)for(var b=0,d=a.propertyNames.length;b<d;b++)this.writeNode("wfs:PropertyName",{property:a.propertyNames[b]},c);a.filter&&(OpenLayers.Format.WFST.v1_1_0.prototype.setFilterProperty.call(this,a.filter),th [...]
+a.filter,c));return c},PropertyName:function(a){return this.createElementNSPlus("wfs:PropertyName",{value:a.property})}},OpenLayers.Format.WFST.v1.prototype.writers.wfs),gml:OpenLayers.Format.GML.v3.prototype.writers.gml,feature:OpenLayers.Format.GML.v3.prototype.writers.feature,ogc:OpenLayers.Format.Filter.v1_1_0.prototype.writers.ogc},CLASS_NAME:"OpenLayers.Format.WFST.v1_1_0"});OpenLayers.Protocol=OpenLayers.Class({format:null,options:null,autoDestroy:!0,defaultFilter:null,initialize: [...]
+update:function(){},"delete":function(){},commit:function(){},abort:function(a){},createCallback:function(a,b,c){return OpenLayers.Function.bind(function(){a.apply(this,[b,c])},this)},CLASS_NAME:"OpenLayers.Protocol"});OpenLayers.Protocol.Response=OpenLayers.Class({code:null,requestType:null,last:!0,features:null,data:null,reqFeatures:null,priv:null,error:null,initialize:function(a){OpenLayers.Util.extend(this,a)},success:function(){return 0<this.code},CLASS_NAME:"OpenLayers.Protocol.Res [...]
+OpenLayers.Protocol.Response.SUCCESS=1;OpenLayers.Protocol.Response.FAILURE=0;OpenLayers.Format.JSON=OpenLayers.Class(OpenLayers.Format,{indent:"    ",space:" ",newline:"\n",level:0,pretty:!1,nativeJSON:function(){return!(!window.JSON||"function"!=typeof JSON.parse||"function"!=typeof JSON.stringify)}(),read:function(a,b){var c;if(this.nativeJSON)c=JSON.parse(a,b);else try{if(/^[\],:{}\s]*$/.test(a.replace(/\\["\\\/bfnrtu]/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[ [...]
+typeof b)){var d=function(a,c){if(c&&"object"===typeof c)for(var e in c)c.hasOwnProperty(e)&&(c[e]=d(e,c[e]));return b(a,c)};c=d("",c)}}catch(e){}this.keepData&&(this.data=c);return c},write:function(a,b){this.pretty=!!b;var c=null,d=typeof a;if(this.serialize[d])try{c=!this.pretty&&this.nativeJSON?JSON.stringify(a):this.serialize[d].apply(this,[a])}catch(e){OpenLayers.Console.error("Trouble serializing: "+e)}return c},writeIndent:function(){var a=[];if(this.pretty)for(var b=0;b<this.lev [...]
+return a.join("")},writeNewline:function(){return this.pretty?this.newline:""},writeSpace:function(){return this.pretty?this.space:""},serialize:{object:function(a){if(null==a)return"null";if(a.constructor==Date)return this.serialize.date.apply(this,[a]);if(a.constructor==Array)return this.serialize.array.apply(this,[a]);var b=["{"];this.level+=1;var c,d,e,f=!1;for(c in a)a.hasOwnProperty(c)&&(d=OpenLayers.Format.JSON.prototype.write.apply(this,[c,this.pretty]),e=OpenLayers.Format.JSON.p [...]
+[a[c],this.pretty]),null!=d&&null!=e&&(f&&b.push(","),b.push(this.writeNewline(),this.writeIndent(),d,":",this.writeSpace(),e),f=!0));this.level-=1;b.push(this.writeNewline(),this.writeIndent(),"}");return b.join("")},array:function(a){var b,c=["["];this.level+=1;for(var d=0,e=a.length;d<e;++d)b=OpenLayers.Format.JSON.prototype.write.apply(this,[a[d],this.pretty]),null!=b&&(0<d&&c.push(","),c.push(this.writeNewline(),this.writeIndent(),b));this.level-=1;c.push(this.writeNewline(),this.wr [...]
+"]");return c.join("")},string:function(a){var b={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};return/["\\\x00-\x1f]/.test(a)?'"'+a.replace(/([\x00-\x1f\\"])/g,function(a,d){var e=b[d];if(e)return e;e=d.charCodeAt();return"\\u00"+Math.floor(e/16).toString(16)+(e%16).toString(16)})+'"':'"'+a+'"'},number:function(a){return isFinite(a)?String(a):"null"},"boolean":function(a){return String(a)},date:function(a){function b(a){return 10>a?"0"+a:a}return'"'+a.get [...]
+"-"+b(a.getMonth()+1)+"-"+b(a.getDate())+"T"+b(a.getHours())+":"+b(a.getMinutes())+":"+b(a.getSeconds())+'"'}},CLASS_NAME:"OpenLayers.Format.JSON"});OpenLayers.Format.GeoJSON=OpenLayers.Class(OpenLayers.Format.JSON,{ignoreExtraDims:!1,read:function(a,b,c){b=b?b:"FeatureCollection";var d=null,e=null,e="string"==typeof a?OpenLayers.Format.JSON.prototype.read.apply(this,[a,c]):a;if(!e)OpenLayers.Console.error("Bad JSON: "+a);else if("string"!=typeof e.type)OpenLayers.Console.error("Bad GeoJ [...]
+this.parseFeature(e),d.type="Feature"}catch(g){OpenLayers.Console.error(g)}break;case "FeatureCollection":switch(d=[],e.type){case "Feature":try{d.push(this.parseFeature(e))}catch(h){d=null,OpenLayers.Console.error(h)}break;case "FeatureCollection":a=0;for(b=e.features.length;a<b;++a)try{d.push(this.parseFeature(e.features[a]))}catch(k){d=null,OpenLayers.Console.error(k)}break;default:try{var l=this.parseGeometry(e);d.push(new OpenLayers.Feature.Vector(l))}catch(m){d=null,OpenLayers.Cons [...]
+isValidType:function(a,b){var c=!1;switch(b){case "Geometry":-1==OpenLayers.Util.indexOf("Point MultiPoint LineString MultiLineString Polygon MultiPolygon Box GeometryCollection".split(" "),a.type)?OpenLayers.Console.error("Unsupported geometry type: "+a.type):c=!0;break;case "FeatureCollection":c=!0;break;default:a.type==b?c=!0:OpenLayers.Console.error("Cannot convert types from "+a.type+" to "+b)}return c},parseFeature:function(a){var b,c,d;c=a.properties?a.properties:{};d=a.geometry&& [...]
+a.bbox;try{b=this.parseGeometry(a.geometry)}catch(e){throw e;}b=new OpenLayers.Feature.Vector(b,c);d&&(b.bounds=OpenLayers.Bounds.fromArray(d));a.id&&(b.fid=a.id);return b},parseGeometry:function(a){if(null==a)return null;var b,c=!1;if("GeometryCollection"==a.type){if(!OpenLayers.Util.isArray(a.geometries))throw"GeometryCollection must have geometries array: "+a;b=a.geometries.length;for(var c=Array(b),d=0;d<b;++d)c[d]=this.parseGeometry.apply(this,[a.geometries[d]]);b=new OpenLayers.Geo [...]
+c=!0}else{if(!OpenLayers.Util.isArray(a.coordinates))throw"Geometry must have coordinates array: "+a;if(!this.parseCoords[a.type.toLowerCase()])throw"Unsupported geometry type: "+a.type;try{b=this.parseCoords[a.type.toLowerCase()].apply(this,[a.coordinates])}catch(e){throw e;}}this.internalProjection&&(this.externalProjection&&!c)&&b.transform(this.externalProjection,this.internalProjection);return b},parseCoords:{point:function(a){if(!1==this.ignoreExtraDims&&2!=a.length)throw"Only 2D p [...]
+a;return new OpenLayers.Geometry.Point(a[0],a[1])},multipoint:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.point.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiPoint(b)},linestring:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.point.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.LineString(b)},multilinestring:function(a){for(var b=[],c=null,d=0,e=a.length; [...]
+this.parseCoords.linestring.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiLineString(b)},polygon:function(a){for(var b=[],c,d,e=0,f=a.length;e<f;++e){try{d=this.parseCoords.linestring.apply(this,[a[e]])}catch(g){throw g;}c=new OpenLayers.Geometry.LinearRing(d.components);b.push(c)}return new OpenLayers.Geometry.Polygon(b)},multipolygon:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.polygon.apply(this,[a[d]])}catch(f){th [...]
+box:function(a){if(2!=a.length)throw"GeoJSON box coordinates must have 2 elements";return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(a[0][0],a[0][1]),new OpenLayers.Geometry.Point(a[1][0],a[0][1]),new OpenLayers.Geometry.Point(a[1][0],a[1][1]),new OpenLayers.Geometry.Point(a[0][0],a[1][1]),new OpenLayers.Geometry.Point(a[0][0],a[0][1])])])}},write:function(a,b){var c={type:null};if(OpenLayers.Util.isArray(a)){c.type="FeatureCollecti [...]
+a.length;c.features=Array(d);for(var e=0;e<d;++e){var f=a[e];if(!f instanceof OpenLayers.Feature.Vector)throw"FeatureCollection only supports collections of features: "+f;c.features[e]=this.extract.feature.apply(this,[f])}}else 0==a.CLASS_NAME.indexOf("OpenLayers.Geometry")?c=this.extract.geometry.apply(this,[a]):a instanceof OpenLayers.Feature.Vector&&(c=this.extract.feature.apply(this,[a]),a.layer&&a.layer.projection&&(c.crs=this.createCRSObject(a)));return OpenLayers.Format.JSON.proto [...]
+[c,b])},createCRSObject:function(a){a=a.layer.projection.toString();var b={};a.match(/epsg:/i)&&(a=parseInt(a.substring(a.indexOf(":")+1)),b=4326==a?{type:"name",properties:{name:"urn:ogc:def:crs:OGC:1.3:CRS84"}}:{type:"name",properties:{name:"EPSG:"+a}});return b},extract:{feature:function(a){var b=this.extract.geometry.apply(this,[a.geometry]),b={type:"Feature",properties:a.attributes,geometry:b};null!=a.fid&&(b.id=a.fid);return b},geometry:function(a){if(null==a)return null;this.inter [...]
+this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b=a.CLASS_NAME.split(".")[2];a=this.extract[b.toLowerCase()].apply(this,[a]);return"Collection"==b?{type:"GeometryCollection",geometries:a}:{type:b,coordinates:a}},point:function(a){return[a.x,a.y]},multipoint:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b},linestring:function(a){for(var b=[],c=0,d=a.componen [...]
+d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b},multilinestring:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.linestring.apply(this,[a.components[c]]));return b},polygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.linestring.apply(this,[a.components[c]]));return b},multipolygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.polygon.apply(this,[a.components[c]]));re [...]
+a.components.length,c=Array(b),d=0;d<b;++d)c[d]=this.extract.geometry.apply(this,[a.components[d]]);return c}},CLASS_NAME:"OpenLayers.Format.GeoJSON"});OpenLayers.Protocol.Script=OpenLayers.Class(OpenLayers.Protocol,{url:null,params:null,callback:null,callbackTemplate:"OpenLayers.Protocol.Script.registry.${id}",callbackKey:"callback",callbackPrefix:"",scope:null,format:null,pendingRequests:null,srsInBBOX:!1,initialize:function(a){a=a||{};this.params={};this.pendingRequests={};OpenLayers. [...]
+new OpenLayers.Format.QueryStringFilter({srsInBBOX:this.srsInBBOX});this.filterToParams=function(a,d){return b.write(a,d)}}},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=OpenLayers.Util.applyDefaults(a,this.options);a.params=OpenLayers.Util.applyDefaults(a.params,this.options.params);a.filter&&this.filterToParams&&(a.params=this.filterToParams(a.filter,a.params));var b=new OpenLayers.Protocol.Response({requestType:"read"}),c=this.createRequest(a.url,a.param [...]
+c;this.handleRead(b,a)},this));b.priv=c;return b},createRequest:function(a,b,c){c=OpenLayers.Protocol.Script.register(c);var d=OpenLayers.String.format(this.callbackTemplate,{id:c});b=OpenLayers.Util.extend({},b);b[this.callbackKey]=this.callbackPrefix+d;a=OpenLayers.Util.urlAppend(a,OpenLayers.Util.getParameterString(b));b=document.createElement("script");b.type="text/javascript";b.src=a;b.id="OpenLayers_Protocol_Script_"+c;this.pendingRequests[b.id]=b;document.getElementsByTagName("hea [...]
+return b},destroyRequest:function(a){OpenLayers.Protocol.Script.unregister(a.id.split("_").pop());delete this.pendingRequests[a.id];a.parentNode&&a.parentNode.removeChild(a)},handleRead:function(a,b){this.handleResponse(a,b)},handleResponse:function(a,b){b.callback&&(a.data?(a.features=this.parseFeatures(a.data),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE,this.destroyRequest(a.priv),b.callback.call(b.scope,a))},parseFeatures:function(a){return [...]
+abort:function(a){if(a)this.destroyRequest(a.priv);else for(var b in this.pendingRequests)this.destroyRequest(this.pendingRequests[b])},destroy:function(){this.abort();delete this.params;delete this.format;OpenLayers.Protocol.prototype.destroy.apply(this)},CLASS_NAME:"OpenLayers.Protocol.Script"});(function(){var a=OpenLayers.Protocol.Script,b=0;a.registry={};a.register=function(c){var d="c"+ ++b;a.registry[d]=function(){c.apply(this,arguments)};return d};a.unregister=function(b){delete  [...]
+2);for(var c=a.length,d=[],e=0;e+1<c;){var f=a[e++],g=a[e++];d.push(new OpenLayers.Geometry.Point(g,f))}return"point"==this.geometryType?new OpenLayers.Feature.Vector(d[0]):"polygon"==this.geometryType?new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(d)])):new OpenLayers.Feature.Vector(new b(d))},decode:function(a,b,c){a=this.decodeDeltas(a,b,c||1E5);c=a.length;for(var d=[],e=0;e+(b-1)<c;){for(var f=[],g=0;g<b;++g)f.push(a[e++]);d.push(f)} [...]
+write:function(a){a=(a.constructor==Array?a[0]:a).geometry;var b=a.CLASS_NAME.split(".")[2].toLowerCase();if("point"==b)a=Array(a);else if("linestring"==b||"linearring"==b||"multipoint"==b)a=a.components;else if("polygon"==b)a=a.components[0].components;else return null;for(var b=[],c=a.length,d=0;d<c;++d){var e=a[d];b.push(e.y);b.push(e.x)}return this.encodeDeltas(b,2)},encode:function(a,b,c){c=c||1E5;for(var d=[],e=a.length,f=0;f<e;++f)for(var g=a[f],h=0;h<b;++h)d.push(g[h]);return thi [...]
+b,c)},encodeDeltas:function(a,b,c){var d,e=Array(b);for(d=0;d<b;++d)e[d]=0;for(var f=a.length,g=0;g<f;)for(d=0;d<b;++d,++g){var h=a[g],k=h-e[d];e[d]=h;a[g]=k}return this.encodeFloats(a,c||1E5)},decodeDeltas:function(a,b,c){var d,e=Array(b);for(d=0;d<b;++d)e[d]=0;a=this.decodeFloats(a,c||1E5);c=a.length;for(var f=0;f<c;)for(d=0;d<b;++d,++f)e[d]+=a[f],a[f]=e[d];return a},encodeFloats:function(a,b){for(var c=b||1E5,d=a.length,e=0;e<d;++e)a[e]=Math.round(a[e]*c);return this.encodeSignedInteg [...]
+b){for(var c=b||1E5,d=this.decodeSignedIntegers(a),e=d.length,f=0;f<e;++f)d[f]/=c;return d},encodeSignedIntegers:function(a){for(var b=a.length,c=0;c<b;++c){var d=a[c],e=d<<1;0>d&&(e=~e);a[c]=e}return this.encodeUnsignedIntegers(a)},decodeSignedIntegers:function(a){a=this.decodeUnsignedIntegers(a);for(var b=a.length,c=0;c<b;++c){var d=a[c];a[c]=d&1?~(d>>1):d>>1}return a},encodeUnsignedIntegers:function(a){for(var b="",c=a.length,d=0;d<c;++d)b+=this.encodeUnsignedInteger(a[d]);return b},d [...]
+[],c=0,d=0,e=a.length,f=0;f<e;++f){var g=a.charCodeAt(f)-63,c=c|(g&31)<<d;32>g?(b.push(c),d=c=0):d+=5}return b},encodeFloat:function(a,b){a=Math.round(a*(b||1E5));return this.encodeSignedInteger(a)},decodeFloat:function(a,b){return this.decodeSignedInteger(a)/(b||1E5)},encodeSignedInteger:function(a){var b=a<<1;0>a&&(b=~b);return this.encodeUnsignedInteger(b)},decodeSignedInteger:function(a){a=this.decodeUnsignedInteger(a);return a&1?~(a>>1):a>>1},encodeUnsignedInteger:function(a){for(va [...]
+a;)b=(32|a&31)+63,c+=String.fromCharCode(b),a>>=5;return c+=String.fromCharCode(a+63)},decodeUnsignedInteger:function(a){for(var b=0,c=0,d=a.length,e=0;e<d;++e){var f=a.charCodeAt(e)-63,b=b|(f&31)<<c;if(32>f)break;c+=5}return b},CLASS_NAME:"OpenLayers.Format.EncodedPolyline"});OpenLayers.Control.Panel=OpenLayers.Class(OpenLayers.Control,{controls:null,autoActivate:!0,defaultControl:null,saveState:!1,allowDepress:!1,activeState:null,initialize:function(a){OpenLayers.Control.prototype.init [...]
+a.events.un({activate:this.iconOn,deactivate:this.iconOff}),a.panel_div=null;this.activeState=null},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){for(var a,b=0,c=this.controls.length;b<c;b++)a=this.controls[b],(a===this.defaultControl||this.saveState&&this.activeState[a.id])&&a.activate();!0===this.saveState&&(this.defaultControl=null);this.redraw();return!0}return!1},deactivate:function(){if(OpenLayers.Control.prototype.deactivate.apply(this,argumen [...]
+b=0,c=this.controls.length;b<c;b++)a=this.controls[b],this.activeState[a.id]=a.deactivate();this.redraw();return!0}return!1},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.outsideViewport?(this.events.attachToElement(this.div),this.events.register("buttonclick",this,this.onButtonClick)):this.map.events.register("buttonclick",this,this.onButtonClick);this.addControlsToMap(this.controls);return this.div},redraw:function(){for(var a=this.div.childNodes.length-1 [...]
+this.div.innerHTML="";if(this.active)for(var a=0,b=this.controls.length;a<b;a++)this.div.appendChild(this.controls[a].panel_div)},activateControl:function(a){if(!this.active)return!1;if(a.type==OpenLayers.Control.TYPE_BUTTON)a.trigger();else if(a.type==OpenLayers.Control.TYPE_TOGGLE)a.active?a.deactivate():a.activate();else if(this.allowDepress&&a.active)a.deactivate();else{for(var b,c=0,d=this.controls.length;c<d;c++)b=this.controls[c],b==a||b.type!==OpenLayers.Control.TYPE_TOOL&&null!= [...]
+a.activate()}},addControls:function(a){OpenLayers.Util.isArray(a)||(a=[a]);this.controls=this.controls.concat(a);for(var b=0,c=a.length;b<c;b++){var d=a[b],e=this.createControlMarkup(d);OpenLayers.Element.addClass(e,d.displayClass+"ItemInactive");OpenLayers.Element.addClass(e,"olButton");""==d.title||e.title||(e.title=d.title);d.panel_div=e}this.map&&(this.addControlsToMap(a),this.redraw())},createControlMarkup:function(a){return document.createElement("div")},addControlsToMap:function(a [...]
+c=0,d=a.length;c<d;c++)b=a[c],!0===b.autoActivate?(b.autoActivate=!1,this.map.addControl(b),b.autoActivate=!0):(this.map.addControl(b),b.deactivate()),b.events.on({activate:this.iconOn,deactivate:this.iconOff})},iconOn:function(){var a=this.panel_div;a.className=a.className.replace(RegExp("\\b("+this.displayClass+"Item)Inactive\\b"),"$1Active")},iconOff:function(){var a=this.panel_div;a.className=a.className.replace(RegExp("\\b("+this.displayClass+"Item)Active\\b"),"$1Inactive")},onButto [...]
+this.controls;a=a.buttonElement;for(var c=b.length-1;0<=c;--c)if(b[c].panel_div===a){this.activateControl(b[c]);break}},getControlsBy:function(a,b){var c="function"==typeof b.test;return OpenLayers.Array.filter(this.controls,function(d){return d[a]==b||c&&b.test(d[a])})},getControlsByName:function(a){return this.getControlsBy("name",a)},getControlsByClass:function(a){return this.getControlsBy("CLASS_NAME",a)},CLASS_NAME:"OpenLayers.Control.Panel"});OpenLayers.Control.Button=OpenLayers.Cl [...]
+return a=OpenLayers.Layer.prototype.clone.apply(this,[a])},setUrl:function(a){this.url=a},mergeNewParams:function(a){this.params=OpenLayers.Util.extend(this.params,a);a=this.redraw();null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"params"});return a},redraw:function(a){return a?this.mergeNewParams({_olSalt:Math.random()}):OpenLayers.Layer.prototype.redraw.apply(this,[])},selectUrl:function(a,b){for(var c=1,d=0,e=a.length;d<e;d++)c*=a.charCodeAt(d)*this.UR [...]
+c-=Math.floor(c);return b[Math.floor(c*b.length)]},getFullRequestString:function(a,b){var c=b||this.url,d=OpenLayers.Util.extend({},this.params),d=OpenLayers.Util.extend(d,a),e=OpenLayers.Util.getParameterString(d);OpenLayers.Util.isArray(c)&&(c=this.selectUrl(e,c));var e=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(c)),f;for(f in d)f.toUpperCase()in e&&delete d[f];e=OpenLayers.Util.getParameterString(d);return OpenLayers.Util.urlAppend(c,e)},CLASS_NAME:"OpenLayers.Layer [...]
+!1,this.events.triggerEvent("unload"))},destroy:function(){this.position=this.size=this.bounds=this.layer=null;this.eventListeners&&this.events.un(this.eventListeners);this.events.destroy();this.events=this.eventListeners=null},draw:function(a){a||this.clear();var b=this.shouldDraw();b&&(!a&&!1===this.events.triggerEvent("beforedraw"))&&(b=null);return b},shouldDraw:function(){var a=!1,b=this.layer.maxExtent;if(b){var c=this.layer.map,c=c.baseLayer.wrapDateLine&&c.getMaxExtent();this.bou [...]
+{inclusive:!1,worldBounds:c})&&(a=!0)}return a||this.layer.displayOutsideMaxExtent},setBounds:function(a){a=a.clone();if(this.layer.map.baseLayer.wrapDateLine){var b=this.layer.map.getMaxExtent(),c=this.layer.map.getResolution();a=a.wrapDateLine(b,{leftTolerance:c,rightTolerance:c})}this.bounds=a},moveTo:function(a,b,c){null==c&&(c=!0);this.setBounds(a);this.position=b.clone();c&&this.draw()},clear:function(a){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.Tile.Image=OpenLayers.Class(OpenLa [...]
+"absolute",this.frame.style.overflow="hidden";null!=this.maxGetUrlLength&&OpenLayers.Util.extend(this,OpenLayers.Tile.Image.IFrame)},destroy:function(){this.imgDiv&&(this.clear(),this.frame=this.imgDiv=null);this.asyncRequestId=null;OpenLayers.Tile.prototype.destroy.apply(this,arguments)},draw:function(){var a=OpenLayers.Tile.prototype.draw.apply(this,arguments);a?(this.layer!=this.layer.map.baseLayer&&this.layer.reproject&&(this.bounds=this.getBoundsFromBaseLayer(this.position)),this.is [...]
+"reload":(this.isLoading=!0,this._loadEvent="loadstart"),this.renderTile(),this.positionTile()):!1===a&&this.unload();return a},renderTile:function(){if(this.layer.async){var a=this.asyncRequestId=(this.asyncRequestId||0)+1;this.layer.getURLasync(this.bounds,function(b){a==this.asyncRequestId&&(this.url=b,this.initImage())},this)}else this.url=this.layer.getURL(this.bounds),this.initImage()},positionTile:function(){var a=this.getTile().style,b=this.frame?this.size:this.layer.getImageSize [...]
+c=1;this.layer instanceof OpenLayers.Layer.Grid&&(c=this.layer.getServerResolution()/this.layer.map.getResolution());a.left=this.position.x+"px";a.top=this.position.y+"px";a.width=Math.round(c*b.w)+"px";a.height=Math.round(c*b.h)+"px"},clear:function(){OpenLayers.Tile.prototype.clear.apply(this,arguments);var a=this.imgDiv;if(a){var b=this.getTile();b.parentNode===this.layer.div&&this.layer.div.removeChild(b);this.setImgSrc();!0===this.layerAlphaHack&&(a.style.filter="");OpenLayers.Eleme [...]
+"olImageLoadError")}this.canvasContext=null},getImage:function(){if(!this.imgDiv){this.imgDiv=OpenLayers.Tile.Image.IMAGE.cloneNode(!1);var a=this.imgDiv.style;if(this.frame){var b=0,c=0;this.layer.gutter&&(b=100*(this.layer.gutter/this.layer.tileSize.w),c=100*(this.layer.gutter/this.layer.tileSize.h));a.left=-b+"%";a.top=-c+"%";a.width=2*b+100+"%";a.height=2*c+100+"%"}a.visibility="hidden";a.opacity=0;1>this.layer.opacity&&(a.filter="alpha(opacity="+100*this.layer.opacity+")");a.positio [...]
+this.layerAlphaHack&&(a.paddingTop=a.height,a.height="0",a.width="100%");this.frame&&this.frame.appendChild(this.imgDiv)}return this.imgDiv},setImage:function(a){this.imgDiv=a},initImage:function(){if(this.url||this.imgDiv){this.events.triggerEvent("beforeload");this.layer.div.appendChild(this.getTile());this.events.triggerEvent(this._loadEvent);var a=this.getImage(),b=a.getAttribute("src")||"";this.url&&OpenLayers.Util.isEquivalentUrl(b,this.url)?this._loadTimeout=window.setTimeout(Open [...]
+this),0):(this.stopLoading(),this.crossOriginKeyword&&a.removeAttribute("crossorigin"),OpenLayers.Event.observe(a,"load",OpenLayers.Function.bind(this.onImageLoad,this)),OpenLayers.Event.observe(a,"error",OpenLayers.Function.bind(this.onImageError,this)),this.imageReloadAttempts=0,this.setImgSrc(this.url))}else this.isLoading=!1},setImgSrc:function(a){var b=this.imgDiv;a?(b.style.visibility="hidden",b.style.opacity=0,this.crossOriginKeyword&&("data:"!==a.substr(0,5)?b.setAttribute("cross [...]
+b.removeAttribute("crossorigin")),b.src=a):(this.stopLoading(),this.imgDiv=null,b.parentNode&&b.parentNode.removeChild(b))},getTile:function(){return this.frame?this.frame:this.getImage()},createBackBuffer:function(){if(this.imgDiv&&!this.isLoading){var a;this.frame?(a=this.frame.cloneNode(!1),a.appendChild(this.imgDiv)):a=this.imgDiv;this.imgDiv=null;return a}},onImageLoad:function(){var a=this.imgDiv;this.stopLoading();a.style.visibility="inherit";a.style.opacity=this.layer.opacity;thi [...]
+!1;this.canvasContext=null;this.events.triggerEvent("loadend");!0===this.layerAlphaHack&&(a.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+a.src+"', sizingMethod='scale')")},onImageError:function(){var a=this.imgDiv;null!=a.src&&(this.imageReloadAttempts++,this.imageReloadAttempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS?this.setImgSrc(this.layer.getURL(this.bounds)):(OpenLayers.Element.addClass(a,"olImageLoadError"),this.events.triggerEvent("loaderror"),this.onImageLoa [...]
+window.clearTimeout(this._loadTimeout);delete this._loadTimeout},getCanvasContext:function(){if(OpenLayers.CANVAS_SUPPORTED&&this.imgDiv&&!this.isLoading){if(!this.canvasContext){var a=document.createElement("canvas");a.width=this.size.w;a.height=this.size.h;this.canvasContext=a.getContext("2d");this.canvasContext.drawImage(this.imgDiv,0,0)}return this.canvasContext}},CLASS_NAME:"OpenLayers.Tile.Image"});
+OpenLayers.Tile.Image.IMAGE=function(){var a=new Image;a.className="olTileImage";a.galleryImg="no";return a}();OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,tileOriginCorner:"bl",tileOrigin:null,tileOptions:null,tileClass:OpenLayers.Tile.Image,grid:null,singleTile:!1,ratio:1.5,buffer:0,transitionEffect:"resize",numLoadingTiles:0,serverResolutions:null,loading:!1,backBuffer:null,gridResolution:null,backBufferResolution:null,backBufferLonLat:null,backBu [...]
+"webkitTransitionEnd","otransitionend","oTransitionEnd"],initialize:function(a,b,c,d){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.grid=[];this._removeBackBuffer=OpenLayers.Function.bind(this.removeBackBuffer,this);this.initProperties();this.rowSign="t"===this.tileOriginCorner.substr(0,1)?1:-1},initProperties:function(){void 0===this.options.removeBackBufferDelay&&(this.removeBackBufferDelay=this.singleTile?0:2500);void 0===this.options.className&&(this.cl [...]
+"olLayerGridSingleTile":"olLayerGrid")},setMap:function(a){OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this,a);OpenLayers.Element.addClass(this.div,this.className)},removeMap:function(a){this.removeBackBuffer()},destroy:function(){this.removeBackBuffer();this.clearGrid();this.tileSize=this.grid=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments)},clearGrid:function(){if(this.grid){for(var a=0,b=this.grid.length;a<b;a++)for(var c=this.grid[a],d=0,e=c.length;d< [...]
+this.grid=[];this.gridLayout=this.gridResolution=null}},addOptions:function(a,b){var c=void 0!==a.singleTile&&a.singleTile!==this.singleTile;OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this,arguments);this.map&&c&&(this.initProperties(),this.clearGrid(),this.tileSize=this.options.tileSize,this.setTileSize(),this.moveTo(null,!0))},clone:function(a){null==a&&(a=new OpenLayers.Layer.Grid(this.name,this.url,this.params,this.getOptions()));a=OpenLayers.Layer.HTTPRequest.prototype. [...]
+[a]);null!=this.tileSize&&(a.tileSize=this.tileSize.clone());a.grid=[];a.gridResolution=null;a.backBuffer=null;a.backBufferTimerId=null;a.loading=!1;a.numLoadingTiles=0;return a},moveTo:function(a,b,c){OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this,arguments);a=a||this.map.getExtent();if(null!=a){var d=!this.grid.length||b,e=this.getTilesBounds(),f=this.map.getResolution();this.getServerResolution(f);if(this.singleTile){if(d||!c&&!e.containsBounds(a))b&&"resize"!==this.transiti [...]
+this.removeBackBuffer(),b&&"resize"!==this.transitionEffect||this.applyBackBuffer(f),this.initSingleTile(a)}else(d=d||!e.intersectsBounds(a,{worldBounds:this.map.baseLayer.wrapDateLine&&this.map.getMaxExtent()}))?(!b||"resize"!==this.transitionEffect&&this.gridResolution!==f||this.applyBackBuffer(f),this.initGriddedTiles(a)):this.moveGriddedTiles()}},getTileData:function(a){var b=null,c=a.lon,d=a.lat,e=this.grid.length;if(this.map&&e){var f=this.map.getResolution();a=this.tileSize.w;var  [...]
+h=this.grid[0][0].bounds,k=h.left,h=h.top;if(c<k&&this.map.baseLayer.wrapDateLine)var l=this.map.getMaxExtent().getWidth(),m=Math.ceil((k-c)/l),c=c+l*m;c=(c-k)/(f*a);d=(h-d)/(f*g);f=Math.floor(c);k=Math.floor(d);0<=k&&k<e&&(e=this.grid[k][f])&&(b={tile:e,i:Math.floor((c-f)*a),j:Math.floor((d-k)*g)})}return b},destroyTile:function(a){this.removeTileMonitoringHooks(a);a.destroy()},getServerResolution:function(a){var b=Number.POSITIVE_INFINITY;a=a||this.map.getResolution();if(this.serverRes [...]
+-1===OpenLayers.Util.indexOf(this.serverResolutions,a)){var c,d,e,f;for(c=this.serverResolutions.length-1;0<=c;c--){e=this.serverResolutions[c];d=Math.abs(e-a);if(d>b)break;b=d;f=e}a=f}return a},getServerZoom:function(){var a=this.getServerResolution();return this.serverResolutions?OpenLayers.Util.indexOf(this.serverResolutions,a):this.map.getZoomForResolution(a)+(this.zoomOffset||0)},applyBackBuffer:function(a){null!==this.backBufferTimerId&&this.removeBackBuffer();var b=this.backBuffer [...]
+this.createBackBuffer();if(!b)return;a===this.gridResolution?this.div.insertBefore(b,this.div.firstChild):this.map.baseLayer.div.parentNode.insertBefore(b,this.map.baseLayer.div);this.backBuffer=b;var c=this.grid[0][0].bounds;this.backBufferLonLat={lon:c.left,lat:c.top};this.backBufferResolution=this.gridResolution}for(var c=this.backBufferResolution/a,d=b.childNodes,e,f=d.length-1;0<=f;--f)e=d[f],e.style.top=(c*e._i*e._h|0)+"px",e.style.left=(c*e._j*e._w|0)+"px",e.style.width=Math.round [...]
+"px",e.style.height=Math.round(c*e._h)+"px";a=this.getViewPortPxFromLonLat(this.backBufferLonLat,a);c=this.map.layerContainerOriginPx.y;b.style.left=Math.round(a.x-this.map.layerContainerOriginPx.x)+"px";b.style.top=Math.round(a.y-c)+"px"},createBackBuffer:function(){var a;if(0<this.grid.length){a=document.createElement("div");a.id=this.div.id+"_bb";a.className="olBackBuffer";a.style.position="absolute";var b=this.map;a.style.zIndex="resize"===this.transitionEffect?this.getZIndex()-1:b.Z [...]
+(b.getNumLayers()-b.getLayerIndex(this));for(var b=0,c=this.grid.length;b<c;b++)for(var d=0,e=this.grid[b].length;d<e;d++){var f=this.grid[b][d],g=this.grid[b][d].createBackBuffer();g&&(g._i=b,g._j=d,g._w=f.size.w,g._h=f.size.h,g.id=f.id+"_bb",a.appendChild(g))}}return a},removeBackBuffer:function(){if(this._transitionElement){for(var a=this.transitionendEvents.length-1;0<=a;--a)OpenLayers.Event.stopObserving(this._transitionElement,this.transitionendEvents[a],this._removeBackBuffer);del [...]
+(this.backBuffer.parentNode&&this.backBuffer.parentNode.removeChild(this.backBuffer),this.backBufferResolution=this.backBuffer=null,null!==this.backBufferTimerId&&(window.clearTimeout(this.backBufferTimerId),this.backBufferTimerId=null))},moveByPx:function(a,b){this.singleTile||this.moveGriddedTiles()},setTileSize:function(a){this.singleTile&&(a=this.map.getSize(),a.h=parseInt(a.h*this.ratio,10),a.w=parseInt(a.w*this.ratio,10));OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(thi [...]
+null,b=this.grid.length;if(b)var a=this.grid[b-1][0].bounds,b=this.grid[0].length*a.getWidth(),c=this.grid.length*a.getHeight(),a=new OpenLayers.Bounds(a.left,a.bottom,a.left+b,a.bottom+c);return a},initSingleTile:function(a){this.events.triggerEvent("retile");var b=a.getCenterLonLat(),c=a.getWidth()*this.ratio;a=a.getHeight()*this.ratio;b=new OpenLayers.Bounds(b.lon-c/2,b.lat-a/2,b.lon+c/2,b.lat+a/2);c=this.map.getLayerPxFromLonLat({lon:b.left,lat:b.top});this.grid.length||(this.grid[0] [...]
+a.moveTo(b,c):(a=this.addTile(b,c),this.addTileMonitoringHooks(a),a.draw(),this.grid[0][0]=a);this.removeExcessTiles(1,1);this.gridResolution=this.getServerResolution()},calculateGridLayout:function(a,b,c){var d=c*this.tileSize.w;c*=this.tileSize.h;var e=Math.floor((a.left-b.lon)/d)-this.buffer,f=this.rowSign;a=Math[~f?"floor":"ceil"](f*(b.lat-a.top+c)/c)-this.buffer*f;return{tilelon:d,tilelat:c,startcol:e,startrow:a}},getTileOrigin:function(){var a=this.tileOrigin;if(!a)var a=this.getMa [...]
+b={tl:["left","top"],tr:["right","top"],bl:["left","bottom"],br:["right","bottom"]}[this.tileOriginCorner],a=new OpenLayers.LonLat(a[b[0]],a[b[1]]);return a},getTileBoundsForGridIndex:function(a,b){var c=this.getTileOrigin(),d=this.gridLayout,e=d.tilelon,f=d.tilelat,g=d.startcol,d=d.startrow,h=this.rowSign;return new OpenLayers.Bounds(c.lon+(g+b)*e,c.lat-(d+a*h)*f*h,c.lon+(g+b+1)*e,c.lat-(d+(a-1)*h)*f*h)},initGriddedTiles:function(a){this.events.triggerEvent("retile");var b=this.map.getS [...]
+d=this.map.getResolution(),e=this.getServerResolution(),f=d/e,d=this.tileSize.w/f,f=this.tileSize.h/f,g=Math.ceil(b.h/f)+2*this.buffer+1,b=Math.ceil(b.w/d)+2*this.buffer+1;this.gridLayout=e=this.calculateGridLayout(a,c,e);var c=e.tilelon,h=e.tilelat,e=this.map.layerContainerOriginPx.x,k=this.map.layerContainerOriginPx.y,l=this.getTileBoundsForGridIndex(0,0),m=this.map.getViewPortPxFromLonLat(new OpenLayers.LonLat(l.left,l.top));m.x=Math.round(m.x)-e;m.y=Math.round(m.y)-k;var e=[],k=this. [...]
+n=0;do{var p=this.grid[n];p||(p=[],this.grid.push(p));var q=0;do{var l=this.getTileBoundsForGridIndex(n,q),r=m.clone();r.x+=q*Math.round(d);r.y+=n*Math.round(f);var s=p[q];s?s.moveTo(l,r,!1):(s=this.addTile(l,r),this.addTileMonitoringHooks(s),p.push(s));r=l.getCenterLonLat();e.push({tile:s,distance:Math.pow(r.lon-k.lon,2)+Math.pow(r.lat-k.lat,2)});q+=1}while(l.right<=a.right+c*this.buffer||q<b);n+=1}while(l.bottom>=a.bottom-h*this.buffer||n<g);this.removeExcessTiles(n,q);this.gridResolut [...]
+e.sort(function(a,b){return a.distance-b.distance});a=0;for(d=e.length;a<d;++a)e[a].tile.draw()},getMaxExtent:function(){return this.maxExtent},addTile:function(a,b){var c=new this.tileClass(this,b,a,null,this.tileSize,this.tileOptions);this.events.triggerEvent("addtile",{tile:c});return c},addTileMonitoringHooks:function(a){a.onLoadStart=function(){!1===this.loading&&(this.loading=!0,this.events.triggerEvent("loadstart"));this.events.triggerEvent("tileloadstart",{tile:a});this.numLoadin [...]
+!this.singleTile&&(this.backBuffer&&this.gridResolution===this.backBufferResolution)&&OpenLayers.Element.addClass(a.getTile(),"olTileReplacing")};a.onLoadEnd=function(b){this.numLoadingTiles--;b="unload"===b.type;this.events.triggerEvent("tileloaded",{tile:a,aborted:b});if(!this.singleTile&&!b&&this.backBuffer&&this.gridResolution===this.backBufferResolution){var c=a.getTile();if("none"===OpenLayers.Element.getStyle(c,"display")){var d=document.getElementById(a.id+"_bb");d&&d.parentNode. [...]
+"olTileReplacing")}if(0===this.numLoadingTiles){if(this.backBuffer)if(0===this.backBuffer.childNodes.length)this.removeBackBuffer();else{this._transitionElement=b?this.div.lastChild:a.imgDiv;b=this.transitionendEvents;for(c=b.length-1;0<=c;--c)OpenLayers.Event.observe(this._transitionElement,b[c],this._removeBackBuffer);this.backBufferTimerId=window.setTimeout(this._removeBackBuffer,this.removeBackBufferDelay)}this.loading=!1;this.events.triggerEvent("loadend")}};a.onLoadError=function() [...]
+{tile:a})};a.events.on({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,loaderror:a.onLoadError,scope:this})},removeTileMonitoringHooks:function(a){a.unload();a.events.un({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,loaderror:a.onLoadError,scope:this})},moveGriddedTiles:function(){for(var a=this.buffer+1;;){var b=this.grid[0][0],c=b.position.x+this.map.layerContainerOriginPx.x,b=b.position.y+this.map.layerContainerOriginPx.y,d=this.getServerResolution()/ [...]
+d={w:Math.round(this.tileSize.w*d),h:Math.round(this.tileSize.h*d)};if(c>-d.w*(a-1))this.shiftColumn(!0,d);else if(c<-d.w*a)this.shiftColumn(!1,d);else if(b>-d.h*(a-1))this.shiftRow(!0,d);else if(b<-d.h*a)this.shiftRow(!1,d);else break}},shiftRow:function(a,b){var c=this.grid,d=a?0:c.length-1,e=a?-1:1;this.gridLayout.startrow+=e*this.rowSign;for(var f=c[d],g=c[a?"pop":"shift"](),h=0,k=g.length;h<k;h++){var l=g[h],m=f[h].position.clone();m.y+=b.h*e;l.moveTo(this.getTileBoundsForGridIndex( [...]
+"unshift":"push"](g)},shiftColumn:function(a,b){var c=this.grid,d=a?0:c[0].length-1,e=a?-1:1;this.gridLayout.startcol+=e;for(var f=0,g=c.length;f<g;f++){var h=c[f],k=h[d].position.clone(),l=h[a?"pop":"shift"]();k.x+=b.w*e;l.moveTo(this.getTileBoundsForGridIndex(f,d),k);h[a?"unshift":"push"](l)}},removeExcessTiles:function(a,b){for(var c,d;this.grid.length>a;){var e=this.grid.pop();c=0;for(d=e.length;c<d;c++){var f=e[c];this.destroyTile(f)}}c=0;for(d=this.grid.length;c<d;c++)for(;this.gri [...]
+b;)e=this.grid[c],f=e.pop(),this.destroyTile(f)},onMapResize:function(){this.singleTile&&(this.clearGrid(),this.setTileSize())},getTileBounds:function(a){var b=this.maxExtent,c=this.getResolution(),d=c*this.tileSize.w,c=c*this.tileSize.h,e=this.getLonLatFromViewPortPx(a);a=b.left+d*Math.floor((e.lon-b.left)/d);b=b.bottom+c*Math.floor((e.lat-b.bottom)/c);return new OpenLayers.Bounds(a,b,a+d,b+c)},CLASS_NAME:"OpenLayers.Layer.Grid"});OpenLayers.Format.ArcXML=OpenLayers.Class(OpenLayers.For [...]
+this.addCoordSys(b.filtercoordsys,a.filterCoordSys);a.polygon?(b.isspatial=!0,b.spatialfilter.polygon=a.polygon):a.envelope&&(b.isspatial=!0,b.spatialfilter.envelope={minx:0,miny:0,maxx:0,maxy:0},this.parseEnvelope(b.spatialfilter.envelope,a.envelope))}else"image"==a.requesttype?(this.request.get_feature=null,b=this.request.get_image.properties,this.parseEnvelope(b.envelope,a.envelope),this.addLayers(b.layerlist,a.layers),this.addImageSize(b.imagesize,a.tileSize),this.addCoordSys(b.featu [...]
+a.featureCoordSys),this.addCoordSys(b.filtercoordsys,a.filterCoordSys)):this.request=null;OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},parseEnvelope:function(a,b){b&&4==b.length&&(a.minx=b[0],a.miny=b[1],a.maxx=b[2],a.maxy=b[3])},addLayers:function(a,b){for(var c=0,d=b.length;c<d;c++)a.push(b[c])},addImageSize:function(a,b){null!==b&&(a.width=b.w,a.height=b.h,a.printwidth=b.w,a.printheight=b.h)},addCoordSys:function(a,b){"string"==typeof b?(a.id=parseInt(b),a.string=b):"ob [...]
+null!==b.proj&&(a.id=b.proj.srsProjNumber,a.string=b.proj.srsCode)},iserror:function(a){var b=null;a?(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]),a=a.documentElement.getElementsByTagName("ERROR"),b=null!==a&&0<a.length):b=""!==this.response.error;return b},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=null;a&&a.documentElement&&(b="ARCXML"==a.documentElement.nodeName?a.documentElement:a.documentElement.getElementsByTagName( [...]
+if(!b||"parsererror"===b.firstChild.nodeName){var c,d;try{c=a.firstChild.nodeValue,d=a.firstChild.childNodes[1].firstChild.nodeValue}catch(e){}throw{message:"Error parsing the ArcXML request",error:c,source:d};}return this.parseResponse(b)},write:function(a){a||(a=this.request);var b=this.createElementNS("","ARCXML");b.setAttribute("version","1.1");var c=this.createElementNS("","REQUEST");if(null!=a.get_image){var d=this.createElementNS("","GET_IMAGE");c.appendChild(d);var e=this.createE [...]
+"PROPERTIES");d.appendChild(e);a=a.get_image.properties;null!=a.featurecoordsys&&(d=this.createElementNS("","FEATURECOORDSYS"),e.appendChild(d),0===a.featurecoordsys.id?d.setAttribute("string",a.featurecoordsys.string):d.setAttribute("id",a.featurecoordsys.id));null!=a.filtercoordsys&&(d=this.createElementNS("","FILTERCOORDSYS"),e.appendChild(d),0===a.filtercoordsys.id?d.setAttribute("string",a.filtercoordsys.string):d.setAttribute("id",a.filtercoordsys.id));null!=a.envelope&&(d=this.cre [...]
+"ENVELOPE"),e.appendChild(d),d.setAttribute("minx",a.envelope.minx),d.setAttribute("miny",a.envelope.miny),d.setAttribute("maxx",a.envelope.maxx),d.setAttribute("maxy",a.envelope.maxy));d=this.createElementNS("","IMAGESIZE");e.appendChild(d);d.setAttribute("height",a.imagesize.height);d.setAttribute("width",a.imagesize.width);if(a.imagesize.height!=a.imagesize.printheight||a.imagesize.width!=a.imagesize.printwidth)d.setAttribute("printheight",a.imagesize.printheight),d.setArrtibute("prin [...]
+null!=a.background&&(d=this.createElementNS("","BACKGROUND"),e.appendChild(d),d.setAttribute("color",a.background.color.r+","+a.background.color.g+","+a.background.color.b),null!==a.background.transcolor&&d.setAttribute("transcolor",a.background.transcolor.r+","+a.background.transcolor.g+","+a.background.transcolor.b));if(null!=a.layerlist&&0<a.layerlist.length)for(d=this.createElementNS("","LAYERLIST"),e.appendChild(d),e=0;e<a.layerlist.length;e++){var f=this.createElementNS("","LAYERDE [...]
+f.setAttribute("id",a.layerlist[e].id);f.setAttribute("visible",a.layerlist[e].visible);if("object"==typeof a.layerlist[e].query){var g=a.layerlist[e].query;if(0>g.where.length)continue;var h=null,h="boolean"==typeof g.spatialfilter&&g.spatialfilter?this.createElementNS("","SPATIALQUERY"):this.createElementNS("","QUERY");h.setAttribute("where",g.where);"number"==typeof g.accuracy&&0<g.accuracy&&h.setAttribute("accuracy",g.accuracy);"number"==typeof g.featurelimit&&2E3>g.featurelimit&&h.s [...]
+g.featurelimit);"string"==typeof g.subfields&&"#ALL#"!=g.subfields&&h.setAttribute("subfields",g.subfields);"string"==typeof g.joinexpression&&0<g.joinexpression.length&&h.setAttribute("joinexpression",g.joinexpression);"string"==typeof g.jointables&&0<g.jointables.length&&h.setAttribute("jointables",g.jointables);f.appendChild(h)}"object"==typeof a.layerlist[e].renderer&&this.addRenderer(f,a.layerlist[e].renderer)}}else null!=a.get_feature&&(d=this.createElementNS("","GET_FEATURES"),d.s [...]
+"newxml"),d.setAttribute("checkesc","true"),a.get_feature.geometry?d.setAttribute("geometry",a.get_feature.geometry):d.setAttribute("geometry","false"),a.get_feature.compact&&d.setAttribute("compact",a.get_feature.compact),"number"==a.get_feature.featurelimit&&d.setAttribute("featurelimit",a.get_feature.featurelimit),d.setAttribute("globalenvelope","true"),c.appendChild(d),null!=a.get_feature.layer&&0<a.get_feature.layer.length&&(e=this.createElementNS("","LAYER"),e.setAttribute("id",a.g [...]
+d.appendChild(e)),a=a.get_feature.query,null!=a&&(e=null,e=a.isspatial?this.createElementNS("","SPATIALQUERY"):this.createElementNS("","QUERY"),d.appendChild(e),"number"==typeof a.accuracy&&e.setAttribute("accuracy",a.accuracy),null!=a.featurecoordsys&&(d=this.createElementNS("","FEATURECOORDSYS"),0==a.featurecoordsys.id?d.setAttribute("string",a.featurecoordsys.string):d.setAttribute("id",a.featurecoordsys.id),e.appendChild(d)),null!=a.filtercoordsys&&(d=this.createElementNS("","FILTERC [...]
+0===a.filtercoordsys.id?d.setAttribute("string",a.filtercoordsys.string):d.setAttribute("id",a.filtercoordsys.id),e.appendChild(d)),0<a.buffer&&(d=this.createElementNS("","BUFFER"),d.setAttribute("distance",a.buffer),e.appendChild(d)),a.isspatial&&(d=this.createElementNS("","SPATIALFILTER"),d.setAttribute("relation",a.spatialfilter.relation),e.appendChild(d),a.spatialfilter.envelope?(f=this.createElementNS("","ENVELOPE"),f.setAttribute("minx",a.spatialfilter.envelope.minx),f.setAttribute [...]
+f.setAttribute("maxx",a.spatialfilter.envelope.maxx),f.setAttribute("maxy",a.spatialfilter.envelope.maxy),d.appendChild(f)):"object"==typeof a.spatialfilter.polygon&&d.appendChild(this.writePolygonGeometry(a.spatialfilter.polygon))),null!=a.where&&0<a.where.length&&e.setAttribute("where",a.where)));b.appendChild(c);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},addGroupRenderer:function(a,b){var c=this.createElementNS("","GROUPRENDERER");a.appendChild(c);for(var d=0;d<b.le [...]
+b[d])},addRenderer:function(a,b){if(OpenLayers.Util.isArray(b))this.addGroupRenderer(a,b);else{var c=this.createElementNS("",b.type.toUpperCase()+"RENDERER");a.appendChild(c);"VALUEMAPRENDERER"==c.tagName?this.addValueMapRenderer(c,b):"VALUEMAPLABELRENDERER"==c.tagName?this.addValueMapLabelRenderer(c,b):"SIMPLELABELRENDERER"==c.tagName?this.addSimpleLabelRenderer(c,b):"SCALEDEPENDENTRENDERER"==c.tagName&&this.addScaleDependentRenderer(c,b)}},addScaleDependentRenderer:function(a,b){"strin [...]
+"number"!=typeof b.lower||a.setAttribute("lower",b.lower);"string"!=typeof b.upper&&"number"!=typeof b.upper||a.setAttribute("upper",b.upper);this.addRenderer(a,b.renderer)},addValueMapLabelRenderer:function(a,b){a.setAttribute("lookupfield",b.lookupfield);a.setAttribute("labelfield",b.labelfield);if("object"==typeof b.exacts)for(var c=0,d=b.exacts.length;c<d;c++){var e=b.exacts[c],f=this.createElementNS("","EXACT");"string"==typeof e.value&&f.setAttribute("value",e.value);"string"==type [...]
+f.setAttribute("label",e.label);"string"==typeof e.method&&f.setAttribute("method",e.method);a.appendChild(f);if("object"==typeof e.symbol){var g=null;"text"==e.symbol.type&&(g=this.createElementNS("","TEXTSYMBOL"));if(null!=g){for(var h=this.fontStyleKeys,k=0,l=h.length;k<l;k++){var m=h[k];e.symbol[m]&&g.setAttribute(m,e.symbol[m])}f.appendChild(g)}}}},addValueMapRenderer:function(a,b){a.setAttribute("lookupfield",b.lookupfield);if("object"==typeof b.ranges)for(var c=0,d=b.ranges.length [...]
+b.ranges[c],f=this.createElementNS("","RANGE");f.setAttribute("lower",e.lower);f.setAttribute("upper",e.upper);a.appendChild(f);if("object"==typeof e.symbol){var g=null;"simplepolygon"==e.symbol.type&&(g=this.createElementNS("","SIMPLEPOLYGONSYMBOL"));null!=g&&("string"==typeof e.symbol.boundarycolor&&g.setAttribute("boundarycolor",e.symbol.boundarycolor),"string"==typeof e.symbol.fillcolor&&g.setAttribute("fillcolor",e.symbol.fillcolor),"number"==typeof e.symbol.filltransparency&&g.setA [...]
+e.symbol.filltransparency),f.appendChild(g))}}else if("object"==typeof b.exacts)for(c=0,d=b.exacts.length;c<d;c++)e=b.exacts[c],f=this.createElementNS("","EXACT"),"string"==typeof e.value&&f.setAttribute("value",e.value),"string"==typeof e.label&&f.setAttribute("label",e.label),"string"==typeof e.method&&f.setAttribute("method",e.method),a.appendChild(f),"object"==typeof e.symbol&&(g=null,"simplemarker"==e.symbol.type&&(g=this.createElementNS("","SIMPLEMARKERSYMBOL")),null!=g&&("string"= [...]
+g.setAttribute("antialiasing",e.symbol.antialiasing),"string"==typeof e.symbol.color&&g.setAttribute("color",e.symbol.color),"string"==typeof e.symbol.outline&&g.setAttribute("outline",e.symbol.outline),"string"==typeof e.symbol.overlap&&g.setAttribute("overlap",e.symbol.overlap),"string"==typeof e.symbol.shadow&&g.setAttribute("shadow",e.symbol.shadow),"number"==typeof e.symbol.transparency&&g.setAttribute("transparency",e.symbol.transparency),"string"==typeof e.symbol.usecentroid&&g.se [...]
+e.symbol.usecentroid),"number"==typeof e.symbol.width&&g.setAttribute("width",e.symbol.width),f.appendChild(g)))},addSimpleLabelRenderer:function(a,b){a.setAttribute("field",b.field);for(var c="featureweight howmanylabels labelbufferratio labelpriorities labelweight linelabelposition rotationalangles".split(" "),d=0,e=c.length;d<e;d++){var f=c[d];b[f]&&a.setAttribute(f,b[f])}if("text"==b.symbol.type){var g=b.symbol,h=this.createElementNS("","TEXTSYMBOL");a.appendChild(h);c=this.fontStyle [...]
+for(e=c.length;d<e;d++)f=c[d],g[f]&&h.setAttribute(f,b[f])}},writePolygonGeometry:function(a){if(!(a instanceof OpenLayers.Geometry.Polygon))throw{message:"Cannot write polygon geometry to ArcXML with an "+a.CLASS_NAME+" object.",geometry:a};for(var b=this.createElementNS("","POLYGON"),c=0,d=a.components.length;c<d;c++){for(var e=a.components[c],f=this.createElementNS("","RING"),g=0,h=e.components.length;g<h;g++){var k=e.components[g],l=this.createElementNS("","POINT");l.setAttribute("x" [...]
+k.y);f.appendChild(l)}b.appendChild(f)}return b},parseResponse:function(a){"string"==typeof a&&(a=(new OpenLayers.Format.XML).read(a));var b=new OpenLayers.Format.ArcXML.Response,c=a.getElementsByTagName("ERROR");if(null!=c&&0<c.length)b.error=this.getChildValue(c,"Unknown error.");else{c=a.getElementsByTagName("RESPONSE");if(null==c||0==c.length)return b.error="No RESPONSE tag found in ArcXML response.",b;var d=c[0].firstChild.nodeName;"#text"==d&&(d=c[0].firstChild.nextSibling.nodeName [...]
+d)c=a.getElementsByTagName("ENVELOPE"),a=a.getElementsByTagName("OUTPUT"),null==c||0==c.length?b.error="No ENVELOPE tag found in ArcXML response.":null==a||0==a.length?b.error="No OUTPUT tag found in ArcXML response.":(c=this.parseAttributes(c[0]),d=this.parseAttributes(a[0]),b.image="string"==typeof d.type?{envelope:c,output:{type:d.type,data:this.getChildValue(a[0])}}:{envelope:c,output:d});else if("FEATURES"==d){if(a=c[0].getElementsByTagName("FEATURES"),c=a[0].getElementsByTagName("F [...]
+b.features.featurecount=c[0].getAttribute("count"),0<b.features.featurecount)for(c=a[0].getElementsByTagName("ENVELOPE"),b.features.envelope=this.parseAttributes(c[0],"number"),a=a[0].getElementsByTagName("FEATURE"),c=0;c<a.length;c++){for(var d=new OpenLayers.Feature.Vector,e=a[c].getElementsByTagName("FIELD"),f=0;f<e.length;f++){var g=e[f].getAttribute("name"),h=e[f].getAttribute("value");d.attributes[g]=h}e=a[c].getElementsByTagName("POLYGON");if(0<e.length){e=e[0].getElementsByTagNam [...]
+f=[];for(g=0;g<e.length;g++){h=[];h.push(this.parsePointGeometry(e[g]));for(var k=e[g].getElementsByTagName("HOLE"),l=0;l<k.length;l++)h.push(this.parsePointGeometry(k[l]));f.push(new OpenLayers.Geometry.Polygon(h))}d.geometry=1==f.length?f[0]:new OpenLayers.Geometry.MultiPolygon(f)}b.features.feature.push(d)}}else b.error="Unidentified response type."}return b},parseAttributes:function(a,b){for(var c={},d=0;d<a.attributes.length;d++)c[a.attributes[d].nodeName]="number"==b?parseFloat(a.a [...]
+a.attributes[d].nodeValue;return c},parsePointGeometry:function(a){var b=[],c=a.getElementsByTagName("COORDS");if(0<c.length)for(a=this.getChildValue(c[0]),a=a.split(/;/),c=0;c<a.length;c++){var d=a[c].split(/ /);b.push(new OpenLayers.Geometry.Point(d[0],d[1]))}else if(a=a.getElementsByTagName("POINT"),0<a.length)for(c=0;c<a.length;c++)b.push(new OpenLayers.Geometry.Point(parseFloat(a[c].getAttribute("x")),parseFloat(a[c].getAttribute("y"))));return new OpenLayers.Geometry.LinearRing(b)} [...]
+OpenLayers.Format.ArcXML.Request=OpenLayers.Class({initialize:function(a){return OpenLayers.Util.extend(this,{get_image:{properties:{background:null,draw:!0,envelope:{minx:0,miny:0,maxx:0,maxy:0},featurecoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},filtercoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},imagesize:{height:0,width:0,dpi:96,printheight:0,printwidth:0,scalesymbols:!1},layerlist:[],output:{baseurl:"",legendbaseurl:"",legendname:"",leg [...]
+legendurl:"",name:"",path:"",type:"jpg",url:""}}},get_feature:{layer:"",query:{isspatial:!1,featurecoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},filtercoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},buffer:0,where:"",spatialfilter:{relation:"envelope_intersection",envelope:null}}},environment:{separators:{cs:" ",ts:";"}},layer:[],workspaces:[]})},CLASS_NAME:"OpenLayers.Format.ArcXML.Request"});
+OpenLayers.Format.ArcXML.Response=OpenLayers.Class({initialize:function(a){return OpenLayers.Util.extend(this,{image:{envelope:null,output:""},features:{featurecount:0,envelope:null,feature:[]},error:""})},CLASS_NAME:"OpenLayers.Format.ArcXML.Response"});(function(){function a(){this._object=f&&!k?new f:new window.ActiveXObject("Microsoft.XMLHTTP");this._listeners=[]}function b(){return new a}function c(a){b.onreadystatechange&&b.onreadystatechange.apply(a);a.dispatchEvent({type:"readyst [...]
+(e=new window.ActiveXObject("Microsoft.XMLDOM"),e.async=!1,e.validateOnParse=!1,e.loadXML(f));c=e&&(h&&0!=e.parseError||!e.documentElement||e.documentElement&&"parsererror"==e.documentElement.tagName)?null:e;a.responseXML=c}catch(g){}try{a.status=a._object.status}catch(k){}try{a.statusText=a._object.statusText}catch(u){}}function e(a){a._object.onreadystatechange=new window.Function}var f=window.XMLHttpRequest,g=!!window.controllers,h=window.document.all&&!window.opera,k=h&&window.naviga [...]
+b.prototype=a.prototype;g&&f.wrapped&&(b.wrapped=f.wrapped);b.UNSENT=0;b.OPENED=1;b.HEADERS_RECEIVED=2;b.LOADING=3;b.DONE=4;b.prototype.readyState=b.UNSENT;b.prototype.responseText="";b.prototype.responseXML=null;b.prototype.status=0;b.prototype.statusText="";b.prototype.priority="NORMAL";b.prototype.onreadystatechange=null;b.onreadystatechange=null;b.onopen=null;b.onsend=null;b.onabort=null;b.prototype.open=function(a,f,k,p,q){delete this._headers;3>arguments.length&&(k=!0);this._async= [...]
+s=this.readyState,t;h&&k&&(t=function(){s!=b.DONE&&(e(r),r.abort())},window.attachEvent("onunload",t));b.onopen&&b.onopen.apply(this,arguments);4<arguments.length?this._object.open(a,f,k,p,q):3<arguments.length?this._object.open(a,f,k,p):this._object.open(a,f,k);this.readyState=b.OPENED;c(this);this._object.onreadystatechange=function(){if(!g||k)r.readyState=r._object.readyState,d(r),r._aborted?r.readyState=b.UNSENT:(r.readyState==b.DONE&&(delete r._data,e(r),h&&k&&window.detachEvent("on [...]
+s!=r.readyState&&c(r),s=r.readyState)}};b.prototype.send=function(a){b.onsend&&b.onsend.apply(this,arguments);arguments.length||(a=null);a&&a.nodeType&&(a=window.XMLSerializer?(new window.XMLSerializer).serializeToString(a):a.xml,this._headers["Content-Type"]||this._object.setRequestHeader("Content-Type","application/xml"));this._data=a;a:if(this._object.send(this._data),g&&!this._async)for(this.readyState=b.OPENED,d(this);this.readyState<b.DONE;)if(this.readyState++,c(this),this._aborte [...]
+b.prototype.abort=function(){b.onabort&&b.onabort.apply(this,arguments);this.readyState>b.UNSENT&&(this._aborted=!0);this._object.abort();e(this);this.readyState=b.UNSENT;delete this._data};b.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders()};b.prototype.getResponseHeader=function(a){return this._object.getResponseHeader(a)};b.prototype.setRequestHeader=function(a,b){this._headers||(this._headers={});this._headers[a]=b;return this._object.setRequestHe [...]
+b.prototype.addEventListener=function(a,b,c){for(var d=0,e;e=this._listeners[d];d++)if(e[0]==a&&e[1]==b&&e[2]==c)return;this._listeners.push([a,b,c])};b.prototype.removeEventListener=function(a,b,c){for(var d=0,e;(e=this._listeners[d])&&(e[0]!=a||e[1]!=b||e[2]!=c);d++);e&&this._listeners.splice(d,1)};b.prototype.dispatchEvent=function(a){a={type:a.type,target:this,currentTarget:this,eventPhase:2,bubbles:a.bubbles,cancelable:a.cancelable,timeStamp:a.timeStamp,stopPropagation:function(){}, [...]
+initEvent:function(){}};"readystatechange"==a.type&&this.onreadystatechange&&(this.onreadystatechange.handleEvent||this.onreadystatechange).apply(this,[a]);for(var b=0,c;c=this._listeners[b];b++)c[0]!=a.type||c[2]||(c[1].handleEvent||c[1]).apply(this,[a])};b.prototype.toString=function(){return"[object XMLHttpRequest]"};b.toString=function(){return"[XMLHttpRequest]"};window.Function.prototype.apply||(window.Function.prototype.apply=function(a,b){b||(b=[]);a.__func=this;a.__func(b[0],b[1] [...]
+b[4]);delete a.__func});OpenLayers.Request||(OpenLayers.Request={});OpenLayers.Request.XMLHttpRequest=b})();OpenLayers.ProxyHost="";OpenLayers.Request||(OpenLayers.Request={});
+OpenLayers.Util.extend(OpenLayers.Request,{DEFAULT_CONFIG:{method:"GET",url:window.location.href,async:!0,user:void 0,password:void 0,params:null,proxy:OpenLayers.ProxyHost,headers:{},data:null,callback:function(){},success:null,failure:null,scope:null},URL_SPLIT_REGEX:/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,events:new OpenLayers.Events(this),makeSameOrigin:function(a,b){var c=0!==a.indexOf("http"),d=!c&&a.match(this.URL_SPLIT_REGEX);if(d){var e=window.location,c=d[1]==e.pr [...]
+e.hostname,d=d[4],e=e.port;if(80!=d&&""!=d||"80"!=e&&""!=e)c=c&&d==e}c||b&&(a="function"==typeof b?b(a):b+encodeURIComponent(a));return a},issue:function(a){var b=OpenLayers.Util.extend(this.DEFAULT_CONFIG,{proxy:OpenLayers.ProxyHost});a=a||{};a.headers=a.headers||{};a=OpenLayers.Util.applyDefaults(a,b);a.headers=OpenLayers.Util.applyDefaults(a.headers,b.headers);var b=!1,c;for(c in a.headers)a.headers.hasOwnProperty(c)&&"x-requested-with"===c.toLowerCase()&&(b=!0);!1===b&&(a.headers["X- [...]
+"XMLHttpRequest");var d=new OpenLayers.Request.XMLHttpRequest,e=OpenLayers.Util.urlAppend(a.url,OpenLayers.Util.getParameterString(a.params||{})),e=OpenLayers.Request.makeSameOrigin(e,a.proxy);d.open(a.method,e,a.async,a.user,a.password);for(var f in a.headers)d.setRequestHeader(f,a.headers[f]);var g=this.events,h=this;d.onreadystatechange=function(){d.readyState==OpenLayers.Request.XMLHttpRequest.DONE&&!1!==g.triggerEvent("complete",{request:d,config:a,requestUrl:e})&&h.runCallbacks({re [...]
+requestUrl:e})};!1===a.async?d.send(a.data):window.setTimeout(function(){0!==d.readyState&&d.send(a.data)},0);return d},runCallbacks:function(a){var b=a.request,c=a.config,d=c.scope?OpenLayers.Function.bind(c.callback,c.scope):c.callback,e;c.success&&(e=c.scope?OpenLayers.Function.bind(c.success,c.scope):c.success);var f;c.failure&&(f=c.scope?OpenLayers.Function.bind(c.failure,c.scope):c.failure);"file:"==OpenLayers.Util.createUrlObject(c.url).protocol&&b.responseText&&(b.status=200);d(b [...]
+200<=b.status&&300>b.status)this.events.triggerEvent("success",a),e&&e(b);b.status&&(200>b.status||300<=b.status)&&(this.events.triggerEvent("failure",a),f&&f(b))},GET:function(a){a=OpenLayers.Util.extend(a,{method:"GET"});return OpenLayers.Request.issue(a)},POST:function(a){a=OpenLayers.Util.extend(a,{method:"POST"});a.headers=a.headers?a.headers:{};"CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(a.headers)||(a.headers["Content-Type"]="application/xml");return OpenLayers.Request.issue( [...]
+OpenLayers.Util.extend(a,{method:"PUT"});a.headers=a.headers?a.headers:{};"CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(a.headers)||(a.headers["Content-Type"]="application/xml");return OpenLayers.Request.issue(a)},DELETE:function(a){a=OpenLayers.Util.extend(a,{method:"DELETE"});return OpenLayers.Request.issue(a)},HEAD:function(a){a=OpenLayers.Util.extend(a,{method:"HEAD"});return OpenLayers.Request.issue(a)},OPTIONS:function(a){a=OpenLayers.Util.extend(a,{method:"OPTIONS"});return Ope [...]
+this.DEFAULT_PARAMS);this.options=OpenLayers.Util.applyDefaults(c,this.DEFAULT_OPTIONS);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a,b,this.params,c]);this.transparent&&(this.isBaseLayer||(this.isBaseLayer=!1),"image/jpeg"==this.format&&(this.format=OpenLayers.Util.alphaHack()?"image/gif":"image/png"));null===this.options.layers&&(this.options.layers=[])},getURL:function(a){var b="";a=this.adjustBounds(a);a=new OpenLayers.Format.ArcXML(OpenLayers.Util.extend(this.options,{req [...]
+envelope:a.toArray(),tileSize:this.tileSize}));a=new OpenLayers.Request.POST({url:this.getFullRequestString(),data:a.write(),async:!1});null!=a&&(b=a.responseXML,b&&b.documentElement||(b=a.responseText),b=(new OpenLayers.Format.ArcXML).read(b),b=this.getUrlOrImage(b.image.output));return b},getURLasync:function(a,b,c){a=this.adjustBounds(a);a=new OpenLayers.Format.ArcXML(OpenLayers.Util.extend(this.options,{requesttype:"image",envelope:a.toArray(),tileSize:this.tileSize}));OpenLayers.Req [...]
+async:!0,data:a.write(),callback:function(a){var e=a.responseXML;e&&e.documentElement||(e=a.responseText);a=(new OpenLayers.Format.ArcXML).read(e);b.call(c,this.getUrlOrImage(a.image.output))},scope:this})},getUrlOrImage:function(a){var b="";a.url?b=a.url:a.data&&(b="data:image/"+a.type+";base64,"+a.data);return b},setLayerQuery:function(a,b){for(var c=0;c<this.options.layers.length;c++)if(a==this.options.layers[c].id){this.options.layers[c].query=b;return}this.options.layers.push({id:a, [...]
+query:b})},getFeatureInfo:function(a,b,c){var d=c.buffer||1,e=c.callback||function(){},f=c.scope||window,g={};OpenLayers.Util.extend(g,this.options);g.requesttype="feature";a instanceof OpenLayers.LonLat?(g.polygon=null,g.envelope=[a.lon-d,a.lat-d,a.lon+d,a.lat+d]):a instanceof OpenLayers.Geometry.Polygon&&(g.envelope=null,g.polygon=a);var h=new OpenLayers.Format.ArcXML(g);OpenLayers.Util.extend(h.request.get_feature,c);h.request.get_feature.layer=b.id;"number"==typeof b.query.accuracy?h [...]
+b.query.accuracy:(a=this.map.getCenter(),c=this.map.getViewPortPxFromLonLat(a),c.x++,c=this.map.getLonLatFromPixel(c),h.request.get_feature.query.accuracy=c.lon-a.lon);h.request.get_feature.query.where=b.query.where;h.request.get_feature.query.spatialfilter.relation="area_intersection";OpenLayers.Request.POST({url:this.getFullRequestString({CustomService:"Query"}),data:h.write(),callback:function(a){a=h.parseResponse(a.responseText);h.iserror()?e.call(f,null):e.call(f,a.features)}})},clo [...]
+a&&(a=new OpenLayers.Layer.ArcIMS(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},CLASS_NAME:"OpenLayers.Layer.ArcIMS"});OpenLayers.Control.PanZoom=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,slideRatio:null,buttons:null,position:null,initialize:function(a){this.position=new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,OpenLayers.Control.PanZoom.Y);OpenLayers.Control.prototype.initialize.apply(this,arguments)},destroy:functi [...]
+setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.register("buttonclick",this,this.onButtonClick)},draw:function(a){OpenLayers.Control.prototype.draw.apply(this,arguments);a=this.position;this.buttons=[];var b={w:18,h:18},c=new OpenLayers.Pixel(a.x+b.w/2,a.y);this._addButton("panup","north-mini.png",c,b);a.y=c.y+b.h;this._addButton("panleft","west-mini.png",a,b);this._addButton("panright","east-mini.png",a.add(b.w,0),b);this._addButton("pandown" [...]
+c.add(0,2*b.h),b);this._addButton("zoomin","zoom-plus-mini.png",c.add(0,3*b.h+5),b);this._addButton("zoomworld","zoom-world-mini.png",c.add(0,4*b.h+5),b);this._addButton("zoomout","zoom-minus-mini.png",c.add(0,5*b.h+5),b);return this.div},_addButton:function(a,b,c,d){b=OpenLayers.Util.getImageLocation(b);c=OpenLayers.Util.createAlphaImageDiv(this.id+"_"+a,c,d,b,"absolute");c.style.cursor="pointer";this.div.appendChild(c);c.action=a;c.className="olButton";this.buttons.push(c);return c},_r [...]
+OpenLayers.Util.removeItem(this.buttons,a)},removeButtons:function(){for(var a=this.buttons.length-1;0<=a;--a)this._removeButton(this.buttons[a])},onButtonClick:function(a){switch(a.buttonElement.action){case "panup":this.map.pan(0,-this.getSlideFactor("h"));break;case "pandown":this.map.pan(0,this.getSlideFactor("h"));break;case "panleft":this.map.pan(-this.getSlideFactor("w"),0);break;case "panright":this.map.pan(this.getSlideFactor("w"),0);break;case "zoomin":this.map.zoomIn();break;c [...]
+break;case "zoomworld":this.map.zoomToMaxExtent()}},getSlideFactor:function(a){return this.slideRatio?this.map.getSize()[a]*this.slideRatio:this.slideFactor},CLASS_NAME:"OpenLayers.Control.PanZoom"});OpenLayers.Control.PanZoom.X=4;OpenLayers.Control.PanZoom.Y=4;OpenLayers.Control.PanZoomBar=OpenLayers.Class(OpenLayers.Control.PanZoom,{zoomStopWidth:18,zoomStopHeight:11,slider:null,sliderEvents:null,zoombarDiv:null,zoomWorldIcon:!1,panIcons:!0,forceFixedZoomLevel:!1,mouseDragStart:null,de [...]
+arguments);this.map.events.on({changebaselayer:this.redraw,updatesize:this.redraw,scope:this})},redraw:function(){null!=this.div&&(this.removeButtons(),this._removeZoomBar());this.draw()},draw:function(a){OpenLayers.Control.prototype.draw.apply(this,arguments);a=this.position.clone();this.buttons=[];var b={w:18,h:18};if(this.panIcons){var c=new OpenLayers.Pixel(a.x+b.w/2,a.y),d=b.w;this.zoomWorldIcon&&(c=new OpenLayers.Pixel(a.x+b.w,a.y));this._addButton("panup","north-mini.png",c,b);a.y [...]
+this._addButton("panleft","west-mini.png",a,b);this.zoomWorldIcon&&(this._addButton("zoomworld","zoom-world-mini.png",a.add(b.w,0),b),d*=2);this._addButton("panright","east-mini.png",a.add(d,0),b);this._addButton("pandown","south-mini.png",c.add(0,2*b.h),b);this._addButton("zoomin","zoom-plus-mini.png",c.add(0,3*b.h+5),b);c=this._addZoomBar(c.add(0,4*b.h+5));this._addButton("zoomout","zoom-minus-mini.png",c,b)}else this._addButton("zoomin","zoom-plus-mini.png",a,b),c=this._addZoomBar(a.a [...]
+this._addButton("zoomout","zoom-minus-mini.png",c,b),this.zoomWorldIcon&&(c=c.add(0,b.h+3),this._addButton("zoomworld","zoom-world-mini.png",c,b));return this.div},_addZoomBar:function(a){var b=OpenLayers.Util.getImageLocation("slider.png"),c=this.id+"_"+this.map.id,d=this.map.getMinZoom(),e=this.map.getNumZoomLevels()-1-this.map.getZoom(),e=OpenLayers.Util.createAlphaImageDiv(c,a.add(-1,e*this.zoomStopHeight),{w:20,h:9},b,"absolute");e.style.cursor="move";this.slider=e;this.sliderEvents [...]
+e,null,!0,{includeXY:!0});this.sliderEvents.on({touchstart:this.zoomBarDown,touchmove:this.zoomBarDrag,touchend:this.zoomBarUp,mousedown:this.zoomBarDown,mousemove:this.zoomBarDrag,mouseup:this.zoomBarUp});var f={w:this.zoomStopWidth,h:this.zoomStopHeight*(this.map.getNumZoomLevels()-d)},b=OpenLayers.Util.getImageLocation("zoombar.png"),c=null;OpenLayers.Util.alphaHack()?(c=this.id+"_"+this.map.id,c=OpenLayers.Util.createAlphaImageDiv(c,a,{w:f.w,h:this.zoomStopHeight},b,"absolute",null," [...]
+f.h+"px"):c=OpenLayers.Util.createDiv("OpenLayers_Control_PanZoomBar_Zoombar"+this.map.id,a,f,b);c.style.cursor="pointer";c.className="olButton";this.zoombarDiv=c;this.div.appendChild(c);this.startTop=parseInt(c.style.top);this.div.appendChild(e);this.map.events.register("zoomend",this,this.moveZoomBar);return a=a.add(0,this.zoomStopHeight*(this.map.getNumZoomLevels()-d))},_removeZoomBar:function(){this.sliderEvents.un({touchstart:this.zoomBarDown,touchmove:this.zoomBarDrag,touchend:this [...]
+mousedown:this.zoomBarDown,mousemove:this.zoomBarDrag,mouseup:this.zoomBarUp});this.sliderEvents.destroy();this.div.removeChild(this.zoombarDiv);this.zoombarDiv=null;this.div.removeChild(this.slider);this.slider=null;this.map.events.unregister("zoomend",this,this.moveZoomBar)},onButtonClick:function(a){OpenLayers.Control.PanZoom.prototype.onButtonClick.apply(this,arguments);if(a.buttonElement===this.zoombarDiv){var b=a.buttonXY.y/this.zoomStopHeight;if(this.forceFixedZoomLevel||!this.map [...]
+Math.floor(b);b=this.map.getNumZoomLevels()-1-b;b=Math.min(Math.max(b,0),this.map.getNumZoomLevels()-1);this.map.zoomTo(b)}},passEventToSlider:function(a){this.sliderEvents.handleBrowserEvent(a)},zoomBarDown:function(a){if(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))this.map.events.on({touchmove:this.passEventToSlider,mousemove:this.passEventToSlider,mouseup:this.passEventToSlider,scope:this}),this.mouseDragStart=a.xy.clone(),this.zoomStart=a.xy.clone(),this.div.st [...]
+"move",this.zoombarDiv.offsets=null,OpenLayers.Event.stop(a)},zoomBarDrag:function(a){if(null!=this.mouseDragStart){var b=this.mouseDragStart.y-a.xy.y,c=OpenLayers.Util.pagePosition(this.zoombarDiv);0<a.clientY-c[1]&&a.clientY-c[1]<parseInt(this.zoombarDiv.style.height)-2&&(b=parseInt(this.slider.style.top)-b,this.slider.style.top=b+"px",this.mouseDragStart=a.xy.clone());this.deltaY=this.zoomStart.y-a.xy.y;OpenLayers.Event.stop(a)}},zoomBarUp:function(a){if((OpenLayers.Event.isLeftClick( [...]
+a.type)&&this.mouseDragStart){this.div.style.cursor="";this.map.events.un({touchmove:this.passEventToSlider,mouseup:this.passEventToSlider,mousemove:this.passEventToSlider,scope:this});var b=this.map.zoom;!this.forceFixedZoomLevel&&this.map.fractionalZoom?(b+=this.deltaY/this.zoomStopHeight,b=Math.min(Math.max(b,0),this.map.getNumZoomLevels()-1)):(b+=this.deltaY/this.zoomStopHeight,b=Math.max(Math.round(b),0));this.map.zoomTo(b);this.zoomStart=this.mouseDragStart=null;this.deltaY=0;OpenL [...]
+moveZoomBar:function(){var a=(this.map.getNumZoomLevels()-1-this.map.getZoom())*this.zoomStopHeight+this.startTop+1;this.slider.style.top=a+"px"},CLASS_NAME:"OpenLayers.Control.PanZoomBar"});OpenLayers.Format.WFSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.0",CLASS_NAME:"OpenLayers.Format.WFSCapabilities"});OpenLayers.Format.WFSCapabilities.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{wfs:"http://www.opengis.net/wfs",xlink:"http://www.w3 [...]
+b){this.readChildNodes(a,b)},FeatureTypeList:function(a,b){b.featureTypeList={featureTypes:[]};this.readChildNodes(a,b.featureTypeList)},FeatureType:function(a,b){var c={};this.readChildNodes(a,c);b.featureTypes.push(c)},Name:function(a,b){var c=this.getChildValue(a);c&&(c=c.split(":"),b.name=c.pop(),0<c.length&&(b.featureNS=this.lookupNamespaceURI(a,c[0])))},Title:function(a,b){var c=this.getChildValue(a);c&&(b.title=c)},Abstract:function(a,b){var c=this.getChildValue(a);c&&(b["abstract [...]
+CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1"});OpenLayers.Format.WFSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},readers:{wfs:OpenLayers.Util.applyDefaults({DefaultSRS:function(a,b){var c=this.getChildValue(a);c&&(b.srs=c)}},OpenLayers.Format.WFSCapabilities.v1.prototype.readers.wfs),ows:OpenLayers.Format.OWSCommon.v1.prototype.readers.ows},CLASS_NAME:"OpenLayers.F [...]
+arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Image(this.name,this.url,this.extent,this.size,this.getOptions()));return a=OpenLayers.Layer.prototype.clone.apply(this,[a])},setMap:function(a){null==this.options.maxResolution&&(this.options.maxResolution=this.aspectRatio*this.extent.getWidth()/this.size.w);OpenLayers.Layer.prototype.setMap.apply(this,arguments)},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var d=null==this.tile;if(b||d){th [...]
+var e=this.map.getLayerPxFromLonLat({lon:this.extent.left,lat:this.extent.top});d?(this.tile=new OpenLayers.Tile.Image(this,e,this.extent,null,this.tileSize),this.addTileMonitoringHooks(this.tile)):(this.tile.size=this.tileSize.clone(),this.tile.position=e.clone());this.tile.draw()}},setTileSize:function(){var a=this.extent.getWidth()/this.map.getResolution(),b=this.extent.getHeight()/this.map.getResolution();this.tileSize=new OpenLayers.Size(a,b)},addTileMonitoringHooks:function(a){a.on [...]
+function(){this.events.triggerEvent("loadstart")};a.events.register("loadstart",this,a.onLoadStart);a.onLoadEnd=function(){this.events.triggerEvent("loadend")};a.events.register("loadend",this,a.onLoadEnd);a.events.register("unload",this,a.onLoadEnd)},removeTileMonitoringHooks:function(a){a.unload();a.events.un({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,scope:this})},setUrl:function(a){this.url=a;this.tile.draw()},getURL:function(a){return this.url},CLASS_NAME:"OpenL [...]
+scope:this});return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&this.auto&&("number"===typeof this.auto?window.clearInterval(this.timer):this.layer.events.un({featureadded:this.triggerSave,afterfeaturemodified:this.triggerSave,scope:this}));return a},triggerSave:function(a){var b=a.feature;b.state!==OpenLayers.State.INSERT&&b.state!==OpenLayers.State.UPDATE&&b.state!==OpenLayers.State.DELETE||this.save([a.feature])},save:function(a){a||(a=this.la [...]
+this.events.triggerEvent("start",{features:a});var b=this.layer.projection,c=this.layer.map.getProjectionObject();if(!c.equals(b)){for(var d=a.length,e=Array(d),f,g,h=0;h<d;++h)f=a[h],g=f.clone(),g.fid=f.fid,g.state=f.state,f.url&&(g.url=f.url),g._original=f,g.geometry.transform(c,b),e[h]=g;a=e}this.layer.protocol.commit(a,{callback:this.onCommit,scope:this})},onCommit:function(a){var b={response:a};if(a.success()){for(var c=a.reqFeatures,d,e=[],f=a.insertIds||[],g=0,h=0,k=c.length;h<k;+ [...]
+d=d._original||d,a=d.state)a==OpenLayers.State.DELETE?e.push(d):a==OpenLayers.State.INSERT&&(d.fid=f[g],++g),d.state=null;0<e.length&&this.layer.destroyFeatures(e);this.events.triggerEvent("success",b)}else this.events.triggerEvent("fail",b)},CLASS_NAME:"OpenLayers.Strategy.Save"});OpenLayers.Events.featureclick=OpenLayers.Class({cache:null,map:null,provides:["featureclick","nofeatureclick","featureover","featureout"],initialize:function(a){this.target=a;if(a.object instanceof OpenLayers [...]
+for(var b=this.provides.length-1;0<=b;--b)a.extensions[this.provides[b]]=!0},setMap:function(a){this.map=a;this.cache={};a.events.register("mousedown",this,this.start,{extension:!0});a.events.register("mouseup",this,this.onClick,{extension:!0});a.events.register("touchstart",this,this.start,{extension:!0});a.events.register("touchmove",this,this.cancel,{extension:!0});a.events.register("touchend",this,this.onClick,{extension:!0});a.events.register("mousemove",this,this.onMousemove,{exten [...]
+start:function(a){this.startEvt=a},cancel:function(a){delete this.startEvt},onClick:function(a){if(this.startEvt&&("touchend"===a.type||OpenLayers.Event.isLeftClick(a))){a=this.getFeatures(this.startEvt);delete this.startEvt;for(var b,c,d={},e=0,f=a.length;e<f&&(b=a[e],c=b.layer,d[c.id]=!0,b=this.triggerEvent("featureclick",{feature:b}),!1!==b);++e);e=0;for(f=this.map.layers.length;e<f;++e)c=this.map.layers[e],c instanceof OpenLayers.Layer.Vector&&!d[c.id]&&this.triggerEvent("nofeaturecl [...]
+onMousemove:function(a){delete this.startEvt;var b=this.getFeatures(a),c={};a=[];for(var d,e=0,f=b.length;e<f;++e)d=b[e],c[d.id]=d,this.cache[d.id]||a.push(d);var b=[],g;for(g in this.cache)d=this.cache[g],d.layer&&d.layer.map?c[d.id]||b.push(d):delete this.cache[g];e=0;for(f=a.length;e<f&&(d=a[e],this.cache[d.id]=d,g=this.triggerEvent("featureover",{feature:d}),!1!==g);++e);e=0;for(f=b.length;e<f&&(d=b[e],delete this.cache[d.id],g=this.triggerEvent("featureout",{feature:d}),!1!==g);++e) [...]
+b){var c=b.feature?b.feature.layer:b.layer,d=this.target.object;if(d instanceof OpenLayers.Map||d===c)return this.target.triggerEvent(a,b)},getFeatures:function(a){var b=a.clientX,c=a.clientY,d=[],e=[],f=[],g,h,k,l;for(l=this.map.layers.length-1;0<=l;--l)if(g=this.map.layers[l],"none"!==g.div.style.display)if(g.renderer instanceof OpenLayers.Renderer.Elements){if(g instanceof OpenLayers.Layer.Vector)for(h=document.elementFromPoint(b,c);h&&h._featureId;)(k=g.getFeatureById(h._featureId))? [...]
+h.style.display="none",e.push(h),h=document.elementFromPoint(b,c)):h=!1;f.push(g);g.div.style.display="none"}else g.renderer instanceof OpenLayers.Renderer.Canvas&&(k=g.renderer.getFeatureIdFromEvent(a))&&(d.push(k),f.push(g));l=0;for(a=e.length;l<a;++l)e[l].style.display="";for(l=f.length-1;0<=l;--l)f[l].div.style.display="block";return d},destroy:function(){for(var a=this.provides.length-1;0<=a;--a)delete this.target.extensions[this.provides[a]];this.map.events.un({mousemove:this.onMou [...]
+mouseup:this.onClick,touchstart:this.start,touchmove:this.cancel,touchend:this.onClick,scope:this});delete this.cache;delete this.map;delete this.target}});OpenLayers.Events.nofeatureclick=OpenLayers.Events.featureclick;OpenLayers.Events.featureover=OpenLayers.Events.featureclick;OpenLayers.Events.featureout=OpenLayers.Events.featureclick;OpenLayers.Format.GPX=OpenLayers.Class(OpenLayers.Format.XML,{defaultDesc:"No description available",extractWaypoints:!0,extractTracks:!0,extractRoutes [...]
+[a])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=[];if(this.extractTracks)for(var c=a.getElementsByTagName("trk"),d=0,e=c.length;d<e;d++){var f={};this.extractAttributes&&(f=this.parseAttributes(c[d]));for(var g=this.getElementsByTagNameNS(c[d],c[d].namespaceURI,"trkseg"),h=0,k=g.length;h<k;h++){var l=this.extractSegment(g[h],"trkpt");b.push(new OpenLayers.Feature.Vector(l,f))}}if(this.extractRoutes)for(e=a.getElementsByTagName("rt [...]
+e.length;c<d;c++)f={},this.extractAttributes&&(f=this.parseAttributes(e[c])),g=this.extractSegment(e[c],"rtept"),b.push(new OpenLayers.Feature.Vector(g,f));if(this.extractWaypoints)for(a=a.getElementsByTagName("wpt"),c=0,e=a.length;c<e;c++)f={},this.extractAttributes&&(f=this.parseAttributes(a[c])),d=new OpenLayers.Geometry.Point(a[c].getAttribute("lon"),a[c].getAttribute("lat")),b.push(new OpenLayers.Feature.Vector(d,f));if(this.internalProjection&&this.externalProjection)for(f=0,a=b.le [...]
+this.internalProjection);return b},extractSegment:function(a,b){for(var c=this.getElementsByTagNameNS(a,a.namespaceURI,b),d=[],e=0,f=c.length;e<f;e++)d.push(new OpenLayers.Geometry.Point(c[e].getAttribute("lon"),c[e].getAttribute("lat")));return new OpenLayers.Geometry.LineString(d)},parseAttributes:function(a){var b={};a=a.firstChild;for(var c,d;a;)1==a.nodeType&&a.firstChild&&(c=a.firstChild,3==c.nodeType||4==c.nodeType)&&(d=a.prefix?a.nodeName.split(":")[1]:a.nodeName,"trkseg"!=d&&"rt [...]
+(b[d]=c.nodeValue)),a=a.nextSibling;return b},write:function(a,b){a=OpenLayers.Util.isArray(a)?a:[a];var c=this.createElementNS(this.namespaces.gpx,"gpx");c.setAttribute("version","1.1");c.setAttribute("creator",this.creator);this.setAttributes(c,{"xsi:schemaLocation":this.schemaLocation});b&&"object"==typeof b&&c.appendChild(this.buildMetadataNode(b));for(var d=0,e=a.length;d<e;d++)c.appendChild(this.buildFeatureNode(a[d]));return OpenLayers.Format.XML.prototype.write.apply(this,[c])},b [...]
+["name","desc","author"],c=this.createElementNS(this.namespaces.gpx,"metadata"),d=0;d<b.length;d++){var e=b[d];if(a[e]){var f=this.createElementNS(this.namespaces.gpx,e);f.appendChild(this.createTextNode(a[e]));c.appendChild(f)}}return c},buildFeatureNode:function(a){var b=a.geometry,b=b.clone();this.internalProjection&&this.externalProjection&&b.transform(this.internalProjection,this.externalProjection);if("OpenLayers.Geometry.Point"==b.CLASS_NAME){var c=this.buildWptNode(b);this.append [...]
+a);return c}c=this.createElementNS(this.namespaces.gpx,"trk");this.appendAttributesNode(c,a);a=this.buildTrkSegNode(b);a=OpenLayers.Util.isArray(a)?a:[a];for(var b=0,d=a.length;b<d;b++)c.appendChild(a[b]);return c},buildTrkSegNode:function(a){var b,c,d,e;if("OpenLayers.Geometry.LineString"==a.CLASS_NAME||"OpenLayers.Geometry.LinearRing"==a.CLASS_NAME){b=this.createElementNS(this.namespaces.gpx,"trkseg");c=0;for(d=a.components.length;c<d;c++)e=a.components[c],b.appendChild(this.buildTrkPt [...]
+return b}b=[];c=0;for(d=a.components.length;c<d;c++)b.push(this.buildTrkSegNode(a.components[c]));return b},buildTrkPtNode:function(a){var b=this.createElementNS(this.namespaces.gpx,"trkpt");b.setAttribute("lon",a.x);b.setAttribute("lat",a.y);return b},buildWptNode:function(a){var b=this.createElementNS(this.namespaces.gpx,"wpt");b.setAttribute("lon",a.x);b.setAttribute("lat",a.y);return b},appendAttributesNode:function(a,b){var c=this.createElementNS(this.namespaces.gpx,"name");c.append [...]
+b.id));a.appendChild(c);c=this.createElementNS(this.namespaces.gpx,"desc");c.appendChild(this.createTextNode(b.attributes.description||this.defaultDesc));a.appendChild(c)},CLASS_NAME:"OpenLayers.Format.GPX"});OpenLayers.Format.WMSDescribeLayer=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.1",CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer"});OpenLayers.Format.WMSDescribeLayer.v1_1_1=OpenLayers.Class(OpenLayers.Format.WMSDescribeLayer,{initialize:function(a){Open [...]
+h=d.getAttribute("owsURL")):""!=d.getAttribute("wfs")?(g="WFS",h=d.getAttribute("wfs")):""!=d.getAttribute("wcs")&&(g="WCS",h=d.getAttribute("wcs"));d=d.getElementsByTagName("Query");0<d.length&&((k=d[0].getAttribute("typeName"))||(k=d[0].getAttribute("typename")));d={layerName:e,owsType:g,owsURL:h,typeName:k};c.layerDescriptions.push(d);c.length=c.layerDescriptions.length;c[c.length-1]=d}else if("ServiceException"==e)return{error:(new OpenLayers.Format.OGCExceptionReport).read(a)};retur [...]
+OpenLayers.Format.WMSDescribeLayer.v1_1_0=OpenLayers.Format.WMSDescribeLayer.v1_1_1;OpenLayers.Layer.XYZ=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,sphericalMercator:!1,zoomOffset:0,serverResolutions:null,initialize:function(a,b,c){if(c&&c.sphericalMercator||this.sphericalMercator)c=OpenLayers.Util.extend({projection:"EPSG:900913",numZoomLevels:19},c);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a||this.name,b||this.url,{},c])},clone:function(a){null==a&&(a=new Open [...]
+[a])},getURL:function(a){a=this.getXYZ(a);var b=this.url;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(""+a.x+a.y+a.z,b));return OpenLayers.String.format(b,a)},getXYZ:function(a){var b=this.getServerResolution(),c=Math.round((a.left-this.maxExtent.left)/(b*this.tileSize.w));a=Math.round((this.maxExtent.top-a.top)/(b*this.tileSize.h));b=this.getServerZoom();if(this.wrapDateLine)var d=Math.pow(2,b),c=(c%d+d)%d;return{x:c,y:a,z:b}},setMap:function(a){OpenLayers.Layer.Grid.prototype.setMap.a [...]
+arguments);this.tileOrigin||(this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.bottom))},CLASS_NAME:"OpenLayers.Layer.XYZ"});OpenLayers.Layer.OSM=OpenLayers.Class(OpenLayers.Layer.XYZ,{name:"OpenStreetMap",url:["http://a.tile.openstreetmap.org/${z}/${x}/${y}.png","http://b.tile.openstreetmap.org/${z}/${x}/${y}.png","http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"],attribution:"© <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributor [...]
+OpenLayers.Util.extend({crossOriginKeyword:"anonymous"},this.options&&this.options.tileOptions)},clone:function(a){null==a&&(a=new OpenLayers.Layer.OSM(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},CLASS_NAME:"OpenLayers.Layer.OSM"});OpenLayers.Renderer=OpenLayers.Class({container:null,root:null,extent:null,locked:!1,size:null,resolution:null,map:null,featureDx:0,initialize:function(a,b){this.container=OpenLayers.Util.getElement(a); [...]
+a=a.scale(1/c);this.extent=a.wrapDateLine(this.map.getMaxExtent()).scale(c)}b&&(this.resolution=null);return!0},setSize:function(a){this.size=a.clone();this.resolution=null},getResolution:function(){return this.resolution=this.resolution||this.map.getResolution()},drawFeature:function(a,b){null==b&&(b=a.style);if(a.geometry){var c=a.geometry.getBounds();if(c){var d;this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&(d=this.map.getMaxExtent());c.intersectsBounds(this.extent,{worldBounds [...]
+d):b={display:"none"};c=this.drawGeometry(a.geometry,b,a.id);if("none"!=b.display&&b.label&&!1!==c){d=a.geometry.getCentroid();if(b.labelXOffset||b.labelYOffset){var e=isNaN(b.labelXOffset)?0:b.labelXOffset,f=isNaN(b.labelYOffset)?0:b.labelYOffset,g=this.getResolution();d.move(e*g,f*g)}this.drawText(a.id,b,d)}else this.removeText(a.id);return c}}},calculateFeatureDx:function(a,b){this.featureDx=0;if(b){var c=b.getWidth();this.featureDx=Math.round(((a.left+a.right)/2-(this.extent.left+thi [...]
+2)/c)*c}},drawGeometry:function(a,b,c){},drawText:function(a,b,c){},removeText:function(a){},clear:function(){},getFeatureIdFromEvent:function(a){},eraseFeatures:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0,c=a.length;b<c;++b){var d=a[b];this.eraseGeometry(d.geometry,d.id);this.removeText(d.id)}},eraseGeometry:function(a,b){},moveRoot:function(a){},getRenderLayerId:function(){return this.container.id},applyDefaultSymbolizer:function(a){var b=OpenLayers.Util.extend({},OpenL [...]
+!1===a.stroke&&(delete b.strokeWidth,delete b.strokeColor);!1===a.fill&&delete b.fillColor;OpenLayers.Util.extend(b,a);return b},CLASS_NAME:"OpenLayers.Renderer"});OpenLayers.Renderer.defaultSymbolizer={fillColor:"#000000",strokeColor:"#000000",strokeWidth:2,fillOpacity:1,strokeOpacity:1,pointRadius:0,labelAlign:"cm"};
+OpenLayers.Renderer.symbol={star:[350,75,379,161,469,161,397,215,423,301,350,250,277,301,303,215,231,161,321,161,350,75],cross:[4,0,6,0,6,4,10,4,10,6,6,6,6,10,4,10,4,6,0,6,0,4,4,4,4,0],x:[0,0,25,0,50,35,75,0,100,0,65,50,100,100,75,100,50,65,25,100,0,100,35,50,0,0],square:[0,0,0,1,1,1,1,0,0,0],triangle:[0,10,10,10,5,0,0,10]};OpenLayers.Renderer.Canvas=OpenLayers.Class(OpenLayers.Renderer,{hitDetection:!0,hitOverflow:0,canvas:null,features:null,pendingRedraw:!1,cachedSymbolBounds:{},initia [...]
+setExtent:function(){OpenLayers.Renderer.prototype.setExtent.apply(this,arguments);return!1},eraseGeometry:function(a,b){this.eraseFeatures(this.features[b][0])},supported:function(){return OpenLayers.CANVAS_SUPPORTED},setSize:function(a){this.size=a.clone();var b=this.root;b.style.width=a.w+"px";b.style.height=a.h+"px";b.width=a.w;b.height=a.h;this.resolution=null;this.hitDetection&&(b=this.hitCanvas,b.style.width=a.w+"px",b.style.height=a.h+"px",b.width=a.w,b.height=a.h)},drawFeature:f [...]
+b){var c;if(a.geometry){b=this.applyDefaultSymbolizer(b||a.style);c=a.geometry.getBounds();var d;this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&(d=this.map.getMaxExtent());d=c&&c.intersectsBounds(this.extent,{worldBounds:d});(c="none"!==b.display&&!!c&&d)?this.features[a.id]=[a,b]:delete this.features[a.id];this.pendingRedraw=!0}this.pendingRedraw&&!this.locked&&(this.redraw(),this.pendingRedraw=!1);return c},drawGeometry:function(a,b,c){var d=a.CLASS_NAME;if("OpenLayers.Geometry.C [...]
+d||"OpenLayers.Geometry.MultiPoint"==d||"OpenLayers.Geometry.MultiLineString"==d||"OpenLayers.Geometry.MultiPolygon"==d)for(d=0;d<a.components.length;d++)this.drawGeometry(a.components[d],b,c);else switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":this.drawPoint(a,b,c);break;case "OpenLayers.Geometry.LineString":this.drawLineString(a,b,c);break;case "OpenLayers.Geometry.LinearRing":this.drawLinearRing(a,b,c);break;case "OpenLayers.Geometry.Polygon":this.drawPolygon(a,b,c)}},drawExter [...]
+b,c){var d=new Image,e=b.title||b.graphicTitle;e&&(d.title=e);var f=b.graphicWidth||b.graphicHeight,g=b.graphicHeight||b.graphicWidth,f=f?f:2*b.pointRadius,g=g?g:2*b.pointRadius,h=void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*f),k=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*g),l=b.graphicOpacity||b.fillOpacity;d.onload=OpenLayers.Function.bind(function(){if(this.features[c]){var b=this.getLocalXY(a),e=b[0],b=b[1];if(!isNaN(e)&&!isNaN(b)){var e=e+h|0,b=b+k|0,p=this.canvas;p.globalAl [...]
+OpenLayers.Renderer.Canvas.drawImageScaleFactor||(OpenLayers.Renderer.Canvas.drawImageScaleFactor=/android 2.1/.test(navigator.userAgent.toLowerCase())?320/window.screen.width:1);p.drawImage(d,e*q,b*q,f*q,g*q);this.hitDetection&&(this.setHitContextStyle("fill",c),this.hitContext.fillRect(e,b,f,g))}}},this);d.src=b.externalGraphic},drawNamedSymbol:function(a,b,c){var d,e,f,g;f=Math.PI/180;var h=OpenLayers.Renderer.symbol[b.graphicName];if(!h)throw Error(b.graphicName+" is not a valid symb [...]
+if(!(!h.length||2>h.length||(a=this.getLocalXY(a),e=a[0],g=a[1],isNaN(e)||isNaN(g)))){this.canvas.lineCap="round";this.canvas.lineJoin="round";this.hitDetection&&(this.hitContext.lineCap="round",this.hitContext.lineJoin="round");if(b.graphicName in this.cachedSymbolBounds)d=this.cachedSymbolBounds[b.graphicName];else{d=new OpenLayers.Bounds;for(a=0;a<h.length;a+=2)d.extend(new OpenLayers.LonLat(h[a],h[a+1]));this.cachedSymbolBounds[b.graphicName]=d}this.canvas.save();this.hitDetection&&t [...]
+this.canvas.translate(e,g);this.hitDetection&&this.hitContext.translate(e,g);a=f*b.rotation;isNaN(a)||(this.canvas.rotate(a),this.hitDetection&&this.hitContext.rotate(a));f=2*b.pointRadius/Math.max(d.getWidth(),d.getHeight());this.canvas.scale(f,f);this.hitDetection&&this.hitContext.scale(f,f);a=d.getCenterLonLat().lon;d=d.getCenterLonLat().lat;this.canvas.translate(-a,-d);this.hitDetection&&this.hitContext.translate(-a,-d);g=b.strokeWidth;b.strokeWidth=g/f;if(!1!==b.fill){this.setCanvas [...]
+b);this.canvas.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.canvas.lineTo(d,e);this.canvas.closePath();this.canvas.fill();if(this.hitDetection){this.setHitContextStyle("fill",c,b);this.hitContext.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.hitContext.lineTo(d,e);this.hitContext.closePath();this.hitContext.fill()}}if(!1!==b.stroke){this.setCanvasStyle("stroke",b);this.canvas.beginPath();for(a=0;a<h.length [...]
+e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.canvas.lineTo(d,e);this.canvas.closePath();this.canvas.stroke();if(this.hitDetection){this.setHitContextStyle("stroke",c,b,f);this.hitContext.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.hitContext.moveTo(d,e),this.hitContext.lineTo(d,e);this.hitContext.closePath();this.hitContext.stroke()}}b.strokeWidth=g;this.canvas.restore();this.hitDetection&&this.hitContext.restore();this.setCanvasStyle("reset")}},setCanvasStyle:function( [...]
+a?(this.canvas.globalAlpha=b.fillOpacity,this.canvas.fillStyle=b.fillColor):"stroke"===a?(this.canvas.globalAlpha=b.strokeOpacity,this.canvas.strokeStyle=b.strokeColor,this.canvas.lineWidth=b.strokeWidth):(this.canvas.globalAlpha=0,this.canvas.lineWidth=1)},featureIdToHex:function(a){a=Number(a.split("_").pop())+1;16777216<=a&&(this.hitOverflow=a-16777215,a=a%16777216+1);a="000000"+a.toString(16);var b=a.length;return a="#"+a.substring(b-6,b)},setHitContextStyle:function(a,b,c,d){b=this. [...]
+"fill"==a?(this.hitContext.globalAlpha=1,this.hitContext.fillStyle=b):"stroke"==a?(this.hitContext.globalAlpha=1,this.hitContext.strokeStyle=b,"undefined"===typeof d?this.hitContext.lineWidth=c.strokeWidth+2:isNaN(d)||(this.hitContext.lineWidth=c.strokeWidth+2/d)):(this.hitContext.globalAlpha=0,this.hitContext.lineWidth=1)},drawPoint:function(a,b,c){if(!1!==b.graphic)if(b.externalGraphic)this.drawExternalGraphic(a,b,c);else if(b.graphicName&&"circle"!=b.graphicName)this.drawNamedSymbol(a [...]
+this.getLocalXY(a);a=d[0];d=d[1];if(!isNaN(a)&&!isNaN(d)){var e=2*Math.PI,f=b.pointRadius;!1!==b.fill&&(this.setCanvasStyle("fill",b),this.canvas.beginPath(),this.canvas.arc(a,d,f,0,e,!0),this.canvas.fill(),this.hitDetection&&(this.setHitContextStyle("fill",c,b),this.hitContext.beginPath(),this.hitContext.arc(a,d,f,0,e,!0),this.hitContext.fill()));!1!==b.stroke&&(this.setCanvasStyle("stroke",b),this.canvas.beginPath(),this.canvas.arc(a,d,f,0,e,!0),this.canvas.stroke(),this.hitDetection&& [...]
+c,b),this.hitContext.beginPath(),this.hitContext.arc(a,d,f,0,e,!0),this.hitContext.stroke()),this.setCanvasStyle("reset"))}}},drawLineString:function(a,b,c){b=OpenLayers.Util.applyDefaults({fill:!1},b);this.drawLinearRing(a,b,c)},drawLinearRing:function(a,b,c){!1!==b.fill&&(this.setCanvasStyle("fill",b),this.renderPath(this.canvas,a,b,c,"fill"),this.hitDetection&&(this.setHitContextStyle("fill",c,b),this.renderPath(this.hitContext,a,b,c,"fill")));!1!==b.stroke&&(this.setCanvasStyle("stro [...]
+a,b,c,"stroke"),this.hitDetection&&(this.setHitContextStyle("stroke",c,b),this.renderPath(this.hitContext,a,b,c,"stroke")));this.setCanvasStyle("reset")},renderPath:function(a,b,c,d,e){b=b.components;c=b.length;a.beginPath();d=this.getLocalXY(b[0]);var f=d[1];if(!isNaN(d[0])&&!isNaN(f)){a.moveTo(d[0],d[1]);for(d=1;d<c;++d)f=this.getLocalXY(b[d]),a.lineTo(f[0],f[1]);"fill"===e?a.fill():a.stroke()}},drawPolygon:function(a,b,c){a=a.components;var d=a.length;this.drawLinearRing(a[0],b,c);for [...]
+d;++e)this.canvas.globalCompositeOperation="destination-out",this.hitDetection&&(this.hitContext.globalCompositeOperation="destination-out"),this.drawLinearRing(a[e],OpenLayers.Util.applyDefaults({stroke:!1,fillOpacity:1},b),c),this.canvas.globalCompositeOperation="source-over",this.hitDetection&&(this.hitContext.globalCompositeOperation="source-over"),this.drawLinearRing(a[e],OpenLayers.Util.applyDefaults({fill:!1},b),c)},drawText:function(a,b){var c=this.getLocalXY(a);this.setCanvasSty [...]
+this.canvas.fillStyle=b.fontColor;this.canvas.globalAlpha=b.fontOpacity||1;var d=[b.fontStyle?b.fontStyle:"normal","normal",b.fontWeight?b.fontWeight:"normal",b.fontSize?b.fontSize:"1em",b.fontFamily?b.fontFamily:"sans-serif"].join(" "),e=b.label.split("\n"),f=e.length;if(this.canvas.fillText){this.canvas.font=d;this.canvas.textAlign=OpenLayers.Renderer.Canvas.LABEL_ALIGN[b.labelAlign[0]]||"center";this.canvas.textBaseline=OpenLayers.Renderer.Canvas.LABEL_ALIGN[b.labelAlign[1]]||"middle" [...]
+null==g&&(g=-0.5);d=this.canvas.measureText("Mg").height||this.canvas.measureText("xx").width;c[1]+=d*g*(f-1);for(g=0;g<f;g++)b.labelOutlineWidth&&(this.canvas.save(),this.canvas.globalAlpha=b.labelOutlineOpacity||b.fontOpacity||1,this.canvas.strokeStyle=b.labelOutlineColor,this.canvas.lineWidth=b.labelOutlineWidth,this.canvas.strokeText(e[g],c[0],c[1]+d*g+1),this.canvas.restore()),this.canvas.fillText(e[g],c[0],c[1]+d*g)}else if(this.canvas.mozDrawText){this.canvas.mozTextStyle=d;var h= [...]
+null==h&&(h=-0.5);g=OpenLayers.Renderer.Canvas.LABEL_FACTOR[b.labelAlign[1]];null==g&&(g=-0.5);d=this.canvas.mozMeasureText("xx");c[1]+=d*(1+g*f);for(g=0;g<f;g++){var k=c[0]+h*this.canvas.mozMeasureText(e[g]),l=c[1]+g*d;this.canvas.translate(k,l);this.canvas.mozDrawText(e[g]);this.canvas.translate(-k,-l)}}this.setCanvasStyle("reset")},getLocalXY:function(a){var b=this.getResolution(),c=this.extent;return[(a.x-this.featureDx)/b+-c.left/b,c.top/b-a.y/b]},clear:function(){var a=this.root.he [...]
+this.canvas.clearRect(0,0,b,a);this.features={};this.hitDetection&&this.hitContext.clearRect(0,0,b,a)},getFeatureIdFromEvent:function(a){var b;if(this.hitDetection&&"none"!==this.root.style.display&&!this.map.dragging&&(a=a.xy,a=this.hitContext.getImageData(a.x|0,a.y|0,1,1).data,255===a[3]&&(a=a[2]+256*(a[1]+256*a[0])))){a="OpenLayers_Feature_Vector_"+(a-1+this.hitOverflow);try{b=this.features[a][0]}catch(c){}}return b},eraseFeatures:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(va [...]
+this.redraw()},redraw:function(){if(!this.locked){var a=this.root.height,b=this.root.width;this.canvas.clearRect(0,0,b,a);this.hitDetection&&this.hitContext.clearRect(0,0,b,a);var a=[],c,d,e=this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&this.map.getMaxExtent(),f;for(f in this.features)this.features.hasOwnProperty(f)&&(b=this.features[f][0],c=b.geometry,this.calculateFeatureDx(c.getBounds(),e),d=this.features[f][1],this.drawGeometry(c,d,b.id),d.label&&a.push([b,d]));b=0;for(c=a.len [...]
+a[b],this.drawText(f[0].geometry.getCentroid(),f[1])}},CLASS_NAME:"OpenLayers.Renderer.Canvas"});OpenLayers.Renderer.Canvas.LABEL_ALIGN={l:"left",r:"right",t:"top",b:"bottom"};OpenLayers.Renderer.Canvas.LABEL_FACTOR={l:0,r:-1,t:0,b:-1};OpenLayers.Renderer.Canvas.drawImageScaleFactor=null;OpenLayers.Format.OSM=OpenLayers.Class(OpenLayers.Format.XML,{checkTags:!1,interestingTagsExclude:null,areaTags:null,initialize:function(a){var b={interestingTagsExclude:"source source_ref source:ref his [...]
+!0;b.areaTags=c;this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,[b])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=this.getNodes(a),c=this.getWays(a);a=Array(c.length);for(var d=0;d<c.length;d++){for(var e=Array(c[d].nodes.length),f=this.isWayArea(c[d])?1:0,g=0;g<c[d].nodes.length;g++){var h=b[c[d].nodes[g]],k=new OpenLayers.Geometry.Point(h.lon,h.lat);k.osm_id=pars [...]
+e[g]=k;h.used=!0}h=null;h=f?new OpenLayers.Geometry.Polygon(new OpenLayers.Geometry.LinearRing(e)):new OpenLayers.Geometry.LineString(e);this.internalProjection&&this.externalProjection&&h.transform(this.externalProjection,this.internalProjection);e=new OpenLayers.Feature.Vector(h,c[d].tags);e.osm_id=parseInt(c[d].id);e.fid="way."+e.osm_id;a[d]=e}for(var l in b){h=b[l];if(!h.used||this.checkTags){c=null;if(this.checkTags){c=this.getTags(h.node,!0);if(h.used&&!c[1])continue;c=c[0]}else c= [...]
+e=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(h.lon,h.lat),c);this.internalProjection&&this.externalProjection&&e.geometry.transform(this.externalProjection,this.internalProjection);e.osm_id=parseInt(l);e.fid="node."+e.osm_id;a.push(e)}h.node=null}return a},getNodes:function(a){a=a.getElementsByTagName("node");for(var b={},c=0;c<a.length;c++){var d=a[c],e=d.getAttribute("id");b[e]={lat:d.getAttribute("lat"),lon:d.getAttribute("lon"),node:d}}return b},getWays:function(a){a [...]
+for(var b=[],c=0;c<a.length;c++){var d=a[c],e={id:d.getAttribute("id")};e.tags=this.getTags(d);d=d.getElementsByTagName("nd");e.nodes=Array(d.length);for(var f=0;f<d.length;f++)e.nodes[f]=d[f].getAttribute("ref");b.push(e)}return b},getTags:function(a,b){for(var c=a.getElementsByTagName("tag"),d={},e=!1,f=0;f<c.length;f++){var g=c[f].getAttribute("k");d[g]=c[f].getAttribute("v");b&&(this.interestingTagsExclude[g]||(e=!0))}return b?[d,e]:d},isWayArea:function(a){var b=!1,c=!1;a.nodes[0]== [...]
+1]&&(b=!0);if(this.checkTags)for(var d in a.tags)if(this.areaTags[d]){c=!0;break}return b&&(this.checkTags?c:!0)},write:function(a){OpenLayers.Util.isArray(a)||(a=[a]);this.osm_id=1;this.created_nodes={};var b=this.createElementNS(null,"osm");b.setAttribute("version","0.5");b.setAttribute("generator","OpenLayers "+OpenLayers.VERSION_NUMBER);for(var c=a.length-1;0<=c;c--)for(var d=this.createFeatureNodes(a[c]),e=0;e<d.length;e++)b.appendChild(d[e]);return OpenLayers.Format.XML.prototype.w [...]
+[b])},createFeatureNodes:function(a){var b=[],c=a.geometry.CLASS_NAME,c=c.substring(c.lastIndexOf(".")+1),c=c.toLowerCase();(c=this.createXML[c])&&(b=c.apply(this,[a]));return b},createXML:{point:function(a){var b=null,c=a.geometry?a.geometry:a;this.internalProjection&&this.externalProjection&&(c=c.clone(),c.transform(this.internalProjection,this.externalProjection));var d=!1;a.osm_id?(b=a.osm_id,this.created_nodes[b]&&(d=!0)):(b=-this.osm_id,this.osm_id++);var e=d?this.created_nodes[b]: [...]
+"node");this.created_nodes[b]=e;e.setAttribute("id",b);e.setAttribute("lon",c.x);e.setAttribute("lat",c.y);a.attributes&&this.serializeTags(a,e);this.setState(a,e);return d?[]:[e]},linestring:function(a){var b,c=[],d=a.geometry;a.osm_id?b=a.osm_id:(b=-this.osm_id,this.osm_id++);var e=this.createElementNS(null,"way");e.setAttribute("id",b);for(b=0;b<d.components.length;b++){var f=this.createXML.point.apply(this,[d.components[b]]);if(f.length){var f=f[0],g=f.getAttribute("id");c.push(f)}el [...]
+f=this.created_nodes[g];this.setState(a,f);f=this.createElementNS(null,"nd");f.setAttribute("ref",g);e.appendChild(f)}this.serializeTags(a,e);c.push(e);return c},polygon:function(a){var b=OpenLayers.Util.extend({area:"yes"},a.attributes),b=new OpenLayers.Feature.Vector(a.geometry.components[0],b);b.osm_id=a.osm_id;return this.createXML.linestring.apply(this,[b])}},serializeTags:function(a,b){for(var c in a.attributes){var d=this.createElementNS(null,"tag");d.setAttribute("k",c);d.setAttr [...]
+a.attributes[c]);b.appendChild(d)}},setState:function(a,b){if(a.state){var c=null;switch(a.state){case OpenLayers.State.UPDATE:case OpenLayers.State.DELETE:c="delete"}c&&b.setAttribute("action",c)}},CLASS_NAME:"OpenLayers.Format.OSM"});OpenLayers.Handler.Keyboard=OpenLayers.Class(OpenLayers.Handler,{KEY_EVENTS:["keydown","keyup"],eventListener:null,observeElement:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.eventListener=OpenLayers.Fu [...]
+arguments)){this.observeElement=this.observeElement||document;for(var a=0,b=this.KEY_EVENTS.length;a<b;a++)OpenLayers.Event.observe(this.observeElement,this.KEY_EVENTS[a],this.eventListener);return!0}return!1},deactivate:function(){var a=!1;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){for(var a=0,b=this.KEY_EVENTS.length;a<b;a++)OpenLayers.Event.stopObserving(this.observeElement,this.KEY_EVENTS[a],this.eventListener);a=!0}return a},handleKeyEvent:function(a){this.che [...]
+this.callback(a.type,[a])},CLASS_NAME:"OpenLayers.Handler.Keyboard"});OpenLayers.Control.ModifyFeature=OpenLayers.Class(OpenLayers.Control,{documentDrag:!1,geometryTypes:null,clickout:!0,toggle:!0,standalone:!1,layer:null,feature:null,vertex:null,vertices:null,virtualVertices:null,handlers:null,deleteCodes:null,virtualStyle:null,vertexRenderIntent:null,mode:null,createVertices:!0,modified:!1,radiusHandle:null,dragHandle:null,onModificationStart:function(){},onModification:function(){},on [...]
+[];this.virtualVertices=[];this.virtualStyle=OpenLayers.Util.extend({},this.layer.style||this.layer.styleMap.createSymbolizer(null,b.vertexRenderIntent));this.virtualStyle.fillOpacity=0.3;this.virtualStyle.strokeOpacity=0.3;this.deleteCodes=[46,68];this.mode=OpenLayers.Control.ModifyFeature.RESHAPE;OpenLayers.Control.prototype.initialize.apply(this,[b]);OpenLayers.Util.isArray(this.deleteCodes)||(this.deleteCodes=[this.deleteCodes]);var c={documentDrag:this.documentDrag,stopDown:!1};this [...]
+{keyboard:new OpenLayers.Handler.Keyboard(this,{keydown:this.handleKeypress}),drag:new OpenLayers.Handler.Drag(this,{down:function(a){this.vertex=null;(a=this.layer.getFeatureFromEvent(this.handlers.drag.evt))?this.dragStart(a):this.clickout&&(this._unselect=this.feature)},move:function(a){delete this._unselect;this.vertex&&this.dragVertex(this.vertex,a)},up:function(){this.handlers.drag.stopDown=!1;this._unselect&&(this.unselectFeature(this._unselect),delete this._unselect)},done:functi [...]
+this.dragComplete(this.vertex)}},c)}},destroy:function(){this.map&&this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this});this.layer=null;OpenLayers.Control.prototype.destroy.apply(this,[])},activate:function(){this.moveLayerToTop();this.map.events.on({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this});return this.handlers.keyboard.activate()&&this.handlers.drag.activate()&&OpenLayers.Control.prototype.activate.apply [...]
+deactivate:function(){var a=!1;OpenLayers.Control.prototype.deactivate.apply(this,arguments)&&(this.moveLayerBack(),this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),this.layer.removeFeatures(this.vertices,{silent:!0}),this.layer.removeFeatures(this.virtualVertices,{silent:!0}),this.vertices=[],this.handlers.drag.deactivate(),this.handlers.keyboard.deactivate(),(a=this.feature)&&(a.geometry&&a.layer)&&this.unselectFeature(a),a=!0);return a [...]
+{feature:a})},selectFeature:function(a){if(!(this.feature===a||this.geometryTypes&&-1==OpenLayers.Util.indexOf(this.geometryTypes,a.geometry.CLASS_NAME))){!1!==this.beforeSelectFeature(a)&&(this.feature&&this.unselectFeature(this.feature),this.feature=a,this.layer.selectedFeatures.push(a),this.layer.drawFeature(a,"select"),this.modified=!1,this.resetVertices(),this.onModificationStart(this.feature));var b=a.modified;!a.geometry||b&&b.geometry||(this._originalGeometry=a.geometry.clone())} [...]
+{silent:!0});this.vertices=[];this.layer.destroyFeatures(this.virtualVertices,{silent:!0});this.virtualVertices=[];this.dragHandle&&(this.layer.destroyFeatures([this.dragHandle],{silent:!0}),delete this.dragHandle);this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),delete this.radiusHandle);this.layer.drawFeature(this.feature,"default");this.feature=null;OpenLayers.Util.removeItem(this.layer.selectedFeatures,a);this.onModificationEnd(a);this.layer.events.trig [...]
+{feature:a,modified:this.modified});this.modified=!1},dragStart:function(a){var b="OpenLayers.Geometry.Point"==a.geometry.CLASS_NAME;this.standalone||(a._sketch||!b)&&a._sketch||(this.toggle&&this.feature===a&&(this._unselect=a),this.selectFeature(a));if(a._sketch||b)this.vertex=a,this.handlers.drag.stopDown=!0},dragVertex:function(a,b){var c=this.map.getLonLatFromViewPortPx(b),d=a.geometry;d.move(c.lon-d.x,c.lat-d.y);this.modified=!0;"OpenLayers.Geometry.Point"==this.feature.geometry.CL [...]
+this.layer.events.triggerEvent("vertexmodified",{vertex:a.geometry,feature:this.feature,pixel:b}):(a._index?(a.geometry.parent.addComponent(a.geometry,a._index),delete a._index,OpenLayers.Util.removeItem(this.virtualVertices,a),this.vertices.push(a)):a==this.dragHandle?(this.layer.removeFeatures(this.vertices,{silent:!0}),this.vertices=[],this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),this.radiusHandle=null)):a!==this.radiusHandle&&this.layer.events.trigg [...]
+{vertex:a.geometry,feature:this.feature,pixel:b}),0<this.virtualVertices.length&&(this.layer.destroyFeatures(this.virtualVertices,{silent:!0}),this.virtualVertices=[]),this.layer.drawFeature(this.feature,this.standalone?void 0:"select"));this.layer.drawFeature(a)},dragComplete:function(a){this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature})},setFeatureState:function(){if(this.feature.state!= [...]
+this.feature.state!=OpenLayers.State.DELETE&&(this.feature.state=OpenLayers.State.UPDATE,this.modified&&this._originalGeometry)){var a=this.feature;a.modified=OpenLayers.Util.extend(a.modified,{geometry:this._originalGeometry});delete this._originalGeometry}},resetVertices:function(){0<this.vertices.length&&(this.layer.removeFeatures(this.vertices,{silent:!0}),this.vertices=[]);0<this.virtualVertices.length&&(this.layer.removeFeatures(this.virtualVertices,{silent:!0}),this.virtualVertice [...]
+(this.layer.destroyFeatures([this.dragHandle],{silent:!0}),this.dragHandle=null);this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),this.radiusHandle=null);this.feature&&"OpenLayers.Geometry.Point"!=this.feature.geometry.CLASS_NAME&&(this.mode&OpenLayers.Control.ModifyFeature.DRAG&&this.collectDragHandle(),this.mode&(OpenLayers.Control.ModifyFeature.ROTATE|OpenLayers.Control.ModifyFeature.RESIZE)&&this.collectRadiusHandle(),this.mode&OpenLayers.Control.Modify [...]
+(this.mode&OpenLayers.Control.ModifyFeature.RESIZE||this.collectVertices()))},handleKeypress:function(a){var b=a.keyCode;this.feature&&-1!=OpenLayers.Util.indexOf(this.deleteCodes,b)&&(b=this.layer.getFeatureFromEvent(this.handlers.drag.evt))&&(-1!=OpenLayers.Util.indexOf(this.vertices,b)&&!this.handlers.drag.dragging&&b.geometry.parent)&&(b.geometry.parent.removeComponent(b.geometry),this.layer.events.triggerEvent("vertexremoved",{vertex:b.geometry,feature:this.feature,pixel:a.xy}),this [...]
+this.standalone?void 0:"select"),this.modified=!0,this.resetVertices(),this.setFeatureState(),this.onModification(this.feature),this.layer.events.triggerEvent("featuremodified",{feature:this.feature}))},collectVertices:function(){function a(c){var d,e,f;if("OpenLayers.Geometry.Point"==c.CLASS_NAME)e=new OpenLayers.Feature.Vector(c),e._sketch=!0,e.renderIntent=b.vertexRenderIntent,b.vertices.push(e);else{f=c.components.length;"OpenLayers.Geometry.LinearRing"==c.CLASS_NAME&&(f-=1);for(d=0; [...]
+c.components[d],"OpenLayers.Geometry.Point"==e.CLASS_NAME?(e=new OpenLayers.Feature.Vector(e),e._sketch=!0,e.renderIntent=b.vertexRenderIntent,b.vertices.push(e)):a(e);if(b.createVertices&&"OpenLayers.Geometry.MultiPoint"!=c.CLASS_NAME)for(d=0,f=c.components.length;d<f-1;++d){e=c.components[d];var g=c.components[d+1];"OpenLayers.Geometry.Point"==e.CLASS_NAME&&"OpenLayers.Geometry.Point"==g.CLASS_NAME&&(e=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point((e.x+g.x)/2,(e.y+g.y)/2) [...]
+e.geometry.parent=c,e._index=d+1,e._sketch=!0,b.virtualVertices.push(e))}}}this.vertices=[];this.virtualVertices=[];var b=this;a.call(this,this.feature.geometry);this.layer.addFeatures(this.virtualVertices,{silent:!0});this.layer.addFeatures(this.vertices,{silent:!0})},collectDragHandle:function(){var a=this.feature.geometry,b=a.getBounds().getCenterLonLat(),b=new OpenLayers.Geometry.Point(b.lon,b.lat),c=new OpenLayers.Feature.Vector(b);b.move=function(b,c){OpenLayers.Geometry.Point.prot [...]
+b,c);a.move(b,c)};c._sketch=!0;this.dragHandle=c;this.dragHandle.renderIntent=this.vertexRenderIntent;this.layer.addFeatures([this.dragHandle],{silent:!0})},collectRadiusHandle:function(){var a=this.feature.geometry,b=a.getBounds(),c=b.getCenterLonLat(),d=new OpenLayers.Geometry.Point(c.lon,c.lat),b=new OpenLayers.Geometry.Point(b.right,b.bottom),c=new OpenLayers.Feature.Vector(b),e=this.mode&OpenLayers.Control.ModifyFeature.RESIZE,f=this.mode&OpenLayers.Control.ModifyFeature.RESHAPE,g=t [...]
+OpenLayers.Control.ModifyFeature.ROTATE;b.move=function(b,c){OpenLayers.Geometry.Point.prototype.move.call(this,b,c);var l=this.x-d.x,m=this.y-d.y,n=l-b,p=m-c;if(g){var q=Math.atan2(p,n),q=Math.atan2(m,l)-q,q=q*(180/Math.PI);a.rotate(q,d)}if(e){var r;f?(m/=p,r=l/n/m):(n=Math.sqrt(n*n+p*p),m=Math.sqrt(l*l+m*m)/n);a.resize(m,d,r)}};c._sketch=!0;this.radiusHandle=c;this.radiusHandle.renderIntent=this.vertexRenderIntent;this.layer.addFeatures([this.radiusHandle],{silent:!0})},setMap:function [...]
+OpenLayers.Control.prototype.setMap.apply(this,arguments)},handleMapEvents:function(a){"removelayer"!=a.type&&"order"!=a.property||this.moveLayerToTop()},moveLayerToTop:function(){var a=Math.max(this.map.Z_INDEX_BASE.Feature-1,this.layer.getZIndex())+1;this.layer.setZIndex(a)},moveLayerBack:function(){var a=this.layer.getZIndex()-1;a>=this.map.Z_INDEX_BASE.Feature?this.layer.setZIndex(a):this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer))},CLASS_NAME:"OpenLayers.Contro [...]
+OpenLayers.Control.ModifyFeature.RESHAPE=1;OpenLayers.Control.ModifyFeature.RESIZE=2;OpenLayers.Control.ModifyFeature.ROTATE=4;OpenLayers.Control.ModifyFeature.DRAG=8;OpenLayers.Layer.Bing=OpenLayers.Class(OpenLayers.Layer.XYZ,{key:null,serverResolutions:[156543.03390625,78271.516953125,39135.7584765625,19567.87923828125,9783.939619140625,4891.9698095703125,2445.9849047851562,1222.9924523925781,611.4962261962891,305.74811309814453,152.87405654907226,76.43702827453613,38.218514137268066,1 [...]
+metadata:null,protocolRegex:/^http:/i,type:"Road",culture:"en-US",metadataParams:null,tileOptions:null,protocol:~window.location.href.indexOf("http")?"":"http:",initialize:function(a){a=OpenLayers.Util.applyDefaults({sphericalMercator:!0},a);OpenLayers.Layer.XYZ.prototype.initialize.apply(this,[a.name||"Bing "+(a.type||this.type),null,a]);this.tileOptions=OpenLayers.Util.extend({crossOriginKeyword:"anonymous"},this.options.tileOptions);this.loadMetadata()},loadMetadata:function(){this._c [...]
+"_callback_"+this.id.replace(/\./g,"_");window[this._callbackId]=OpenLayers.Function.bind(OpenLayers.Layer.Bing.processMetadata,this);var a=OpenLayers.Util.applyDefaults({key:this.key,jsonp:this._callbackId,include:"ImageryProviders"},this.metadataParams),a=this.protocol+"//dev.virtualearth.net/REST/v1/Imagery/Metadata/"+this.type+"?"+OpenLayers.Util.getParameterString(a),b=document.createElement("script");b.type="text/javascript";b.src=a;b.id=this._callbackId;document.getElementsByTagNa [...]
+initLayer:function(){var a=this.metadata.resourceSets[0].resources[0],b=a.imageUrl.replace("{quadkey}","${quadkey}"),b=b.replace("{culture}",this.culture),b=b.replace(this.protocolRegex,this.protocol);this.url=[];for(var c=0;c<a.imageUrlSubdomains.length;++c)this.url.push(b.replace("{subdomain}",a.imageUrlSubdomains[c]));this.addOptions({maxResolution:Math.min(this.serverResolutions[a.zoomMin],this.maxResolution||Number.POSITIVE_INFINITY),numZoomLevels:Math.min(a.zoomMax+1-a.zoomMin,this [...]
+!0);this.isBaseLayer||this.redraw();this.updateAttribution()},getURL:function(a){if(this.url){var b=this.getXYZ(a);a=b.x;for(var c=b.y,b=b.z,d=[],e=b;0<e;--e){var f="0",g=1<<e-1;0!=(a&g)&&f++;0!=(c&g)&&(f++,f++);d.push(f)}d=d.join("");a=this.selectUrl(""+a+c+b,this.url);return OpenLayers.String.format(a,{quadkey:d})}},updateAttribution:function(){var a=this.metadata;if(a.resourceSets&&this.map&&this.map.center){var b=a.resourceSets[0].resources[0],c=this.map.getExtent().transform(this.ma [...]
+new OpenLayers.Projection("EPSG:4326")),d=b.imageryProviders||[],e=OpenLayers.Util.indexOf(this.serverResolutions,this.getServerResolution()),b="",f,g,h,k,l,m,n;g=0;for(h=d.length;g<h;++g)for(f=d[g],k=0,l=f.coverageAreas.length;k<l;++k)n=f.coverageAreas[k],m=OpenLayers.Bounds.fromArray(n.bbox,!0),c.intersectsBounds(m)&&(e<=n.zoomMax&&e>=n.zoomMin)&&(b+=f.attribution+" ");a=a.brandLogoUri.replace(this.protocolRegex,this.protocol);this.attribution=OpenLayers.String.format(this.attributionT [...]
+logo:a,copyrights:b});this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"attribution"})}},setMap:function(){OpenLayers.Layer.XYZ.prototype.setMap.apply(this,arguments);this.map.events.register("moveend",this,this.updateAttribution)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Bing(this.options));return a=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},destroy:function(){this.map&&this.map.events.unregister("moveend",this,this.updateAttribution);OpenLa [...]
+arguments)},CLASS_NAME:"OpenLayers.Layer.Bing"});OpenLayers.Layer.Bing.processMetadata=function(a){this.metadata=a;this.initLayer();a=document.getElementById(this._callbackId);a.parentNode.removeChild(a);window[this._callbackId]=void 0;delete this._callbackId};OpenLayers.StyleMap=OpenLayers.Class({styles:null,extendDefault:!0,initialize:function(a,b){this.styles={"default":new OpenLayers.Style(OpenLayers.Feature.Vector.style["default"]),select:new OpenLayers.Style(OpenLayers.Feature.Vect [...]
+a;else if("object"==typeof a)for(var c in a)if(a[c]instanceof OpenLayers.Style)this.styles[c]=a[c];else if("object"==typeof a[c])this.styles[c]=new OpenLayers.Style(a[c]);else{this.styles["default"]=new OpenLayers.Style(a);this.styles.select=new OpenLayers.Style(a);this.styles.temporary=new OpenLayers.Style(a);this.styles["delete"]=new OpenLayers.Style(a);break}OpenLayers.Util.extend(this,b)},destroy:function(){for(var a in this.styles)this.styles[a].destroy();this.styles=null},createSym [...]
+b){a||(a=new OpenLayers.Feature.Vector);this.styles[b]||(b="default");a.renderIntent=b;var c={};this.extendDefault&&"default"!=b&&(c=this.styles["default"].createSymbolizer(a));return OpenLayers.Util.extend(c,this.styles[b].createSymbolizer(a))},addUniqueValueRules:function(a,b,c,d){var e=[],f;for(f in c)e.push(new OpenLayers.Rule({symbolizer:c[f],context:d,filter:new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,property:b,value:f})}));this.styles[a].addRules( [...]
+(this.renderer=null,this.displayError());this.styleMap||(this.styleMap=new OpenLayers.StyleMap);this.features=[];this.selectedFeatures=[];this.unrenderedFeatures={};if(this.strategies)for(var c=0,d=this.strategies.length;c<d;c++)this.strategies[c].setLayer(this)},destroy:function(){if(this.strategies){var a,b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoDestroy&&a.destroy();this.strategies=null}this.protocol&&(this.protocol.autoDestroy&&this.protocol.destroy(),th [...]
+null);this.destroyFeatures();this.unrenderedFeatures=this.selectedFeatures=this.features=null;this.renderer&&this.renderer.destroy();this.drawn=this.geometryType=this.renderer=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Vector(this.name,this.getOptions()));a=OpenLayers.Layer.prototype.clone.apply(this,[a]);for(var b=this.features,c=b.length,d=Array(c),e=0;e<c;++e)d[e]=b[e].clone();a.features=d;return a},refresh:functio [...]
+this.visibility&&this.events.triggerEvent("refresh",a)},assignRenderer:function(){for(var a=0,b=this.renderers.length;a<b;a++){var c=this.renderers[a];if((c="function"==typeof c?c:OpenLayers.Renderer[c])&&c.prototype.supported()){this.renderer=new c(this.div,this.rendererOptions);break}}},displayError:function(){this.reportError&&OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",{renderers:this.renderers.join("\n")}))},setMap:function(a){OpenLayers.Layer.prototype.setMap [...]
+arguments);if(this.renderer){this.renderer.map=this.map;var b=this.map.getSize();b.w*=this.ratio;b.h*=this.ratio;this.renderer.setSize(b)}else this.map.removeLayer(this)},afterAdd:function(){if(this.strategies){var a,b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoActivate&&a.activate()}},removeMap:function(a){this.drawn=!1;if(this.strategies){var b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoActivate&&a.deactivate()}},onMapResize:functio [...]
+arguments);var a=this.map.getSize();a.w*=this.ratio;a.h*=this.ratio;this.renderer.setSize(a)},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var d=!0;if(!c){this.renderer.root.style.visibility="hidden";var d=this.map.getSize(),e=d.w,d=d.h,e=e/2*this.ratio-e/2,d=d/2*this.ratio-d/2,e=e+this.map.layerContainerOriginPx.x,e=-Math.round(e),d=d+this.map.layerContainerOriginPx.y,d=-Math.round(d);this.div.style.left=e+"px";this.div.style.top=d+"px";e=this.map.getEx [...]
+d=this.renderer.setExtent(e,b);this.renderer.root.style.visibility="visible";!0===OpenLayers.IS_GECKO&&(this.div.scrollLeft=this.div.scrollLeft);if(!b&&d)for(var f in this.unrenderedFeatures)e=this.unrenderedFeatures[f],this.drawFeature(e)}if(!this.drawn||b||!d)for(this.drawn=!0,f=0,d=this.features.length;f<d;f++)this.renderer.locked=f!==d-1,e=this.features[f],this.drawFeature(e)},display:function(a){OpenLayers.Layer.prototype.display.apply(this,arguments);var b=this.div.style.display;b! [...]
+(this.renderer.root.style.display=b)},addFeatures:function(a,b){OpenLayers.Util.isArray(a)||(a=[a]);var c=!b||!b.silent;if(c){var d={features:a};if(!1===this.events.triggerEvent("beforefeaturesadded",d))return;a=d.features}for(var d=[],e=0,f=a.length;e<f;e++){this.renderer.locked=e!=a.length-1?!0:!1;var g=a[e];if(this.geometryType&&!(g.geometry instanceof this.geometryType))throw new TypeError("addFeatures: component should be an "+this.geometryType.prototype.CLASS_NAME);g.layer=this;!g. [...]
+(g.style=OpenLayers.Util.extend({},this.style));if(c){if(!1===this.events.triggerEvent("beforefeatureadded",{feature:g}))continue;this.preFeatureInsert(g)}d.push(g);this.features.push(g);this.drawFeature(g);c&&(this.events.triggerEvent("featureadded",{feature:g}),this.onFeatureInsert(g))}c&&this.events.triggerEvent("featuresadded",{features:d})},removeFeatures:function(a,b){if(a&&0!==a.length){if(a===this.features)return this.removeAllFeatures(b);OpenLayers.Util.isArray(a)||(a=[a]);a===t [...]
+(a=a.slice());var c=!b||!b.silent;c&&this.events.triggerEvent("beforefeaturesremoved",{features:a});for(var d=a.length-1;0<=d;d--){this.renderer.locked=0!=d&&a[d-1].geometry?!0:!1;var e=a[d];delete this.unrenderedFeatures[e.id];c&&this.events.triggerEvent("beforefeatureremoved",{feature:e});this.features=OpenLayers.Util.removeItem(this.features,e);e.layer=null;e.geometry&&this.renderer.eraseFeatures(e);-1!=OpenLayers.Util.indexOf(this.selectedFeatures,e)&&OpenLayers.Util.removeItem(this. [...]
+e);c&&this.events.triggerEvent("featureremoved",{feature:e})}c&&this.events.triggerEvent("featuresremoved",{features:a})}},removeAllFeatures:function(a){a=!a||!a.silent;var b=this.features;a&&this.events.triggerEvent("beforefeaturesremoved",{features:b});for(var c,d=b.length-1;0<=d;d--)c=b[d],a&&this.events.triggerEvent("beforefeatureremoved",{feature:c}),c.layer=null,a&&this.events.triggerEvent("featureremoved",{feature:c});this.renderer.clear();this.features=[];this.unrenderedFeatures= [...]
+[];a&&this.events.triggerEvent("featuresremoved",{features:b})},destroyFeatures:function(a,b){void 0==a&&(a=this.features);if(a){this.removeFeatures(a,b);for(var c=a.length-1;0<=c;c--)a[c].destroy()}},drawFeature:function(a,b){if(this.drawn){if("object"!=typeof b){b||a.state!==OpenLayers.State.DELETE||(b="delete");var c=b||a.renderIntent;(b=a.style||this.style)||(b=this.styleMap.createSymbolizer(a,c))}c=this.renderer.drawFeature(a,b);!1===c||null===c?this.unrenderedFeatures[a.id]=a:delet [...]
+eraseFeatures:function(a){this.renderer.eraseFeatures(a)},getFeatureFromEvent:function(a){if(!this.renderer)throw Error("getFeatureFromEvent called on layer with no renderer. This usually means you destroyed a layer, but not some handler which is associated with it.");var b=null;(a=this.renderer.getFeatureIdFromEvent(a))&&(b="string"===typeof a?this.getFeatureById(a):a);return b},getFeatureBy:function(a,b){for(var c=null,d=0,e=this.features.length;d<e;++d)if(this.features[d][a]==b){c=thi [...]
+break}return c},getFeatureById:function(a){return this.getFeatureBy("id",a)},getFeatureByFid:function(a){return this.getFeatureBy("fid",a)},getFeaturesByAttribute:function(a,b){var c,d,e=this.features.length,f=[];for(c=0;c<e;c++)(d=this.features[c])&&d.attributes&&d.attributes[a]===b&&f.push(d);return f},onFeatureInsert:function(a){},preFeatureInsert:function(a){},getDataExtent:function(){var a=null,b=this.features;if(b&&0<b.length)for(var c=null,d=0,e=b.length;d<e;d++)if(c=b[d].geometry [...]
+(a=new OpenLayers.Bounds),a.extend(c.getBounds());return a},CLASS_NAME:"OpenLayers.Layer.Vector"});OpenLayers.Layer.PointGrid=OpenLayers.Class(OpenLayers.Layer.Vector,{dx:null,dy:null,ratio:1.5,maxFeatures:250,rotation:0,origin:null,gridBounds:null,initialize:function(a){a=a||{};OpenLayers.Layer.Vector.prototype.initialize.apply(this,[a.name,a])},setMap:function(a){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);a.events.register("moveend",this,this.onMoveEnd)},removeMap:f [...]
+arguments)},setRatio:function(a){this.ratio=a;this.updateGrid(!0)},setMaxFeatures:function(a){this.maxFeatures=a;this.updateGrid(!0)},setSpacing:function(a,b){this.dx=a;this.dy=b||a;this.updateGrid(!0)},setOrigin:function(a){this.origin=a;this.updateGrid(!0)},getOrigin:function(){this.origin||(this.origin=this.map.getExtent().getCenterLonLat());return this.origin},setRotation:function(a){this.rotation=a;this.updateGrid(!0)},onMoveEnd:function(){this.updateGrid()},getViewBounds:function() [...]
+if(this.rotation){var b=this.getOrigin(),b=new OpenLayers.Geometry.Point(b.lon,b.lat),a=a.toGeometry();a.rotate(-this.rotation,b);a=a.getBounds()}return a},updateGrid:function(a){if(a||this.invalidBounds()){var b=this.getViewBounds(),c=this.getOrigin();a=new OpenLayers.Geometry.Point(c.lon,c.lat);var d=b.getWidth(),e=b.getHeight(),f=d/e,g=Math.sqrt(this.dx*this.dy*this.maxFeatures/f),d=Math.min(d*this.ratio,g*f),e=Math.min(e*this.ratio,g),b=b.getCenterLonLat();this.gridBounds=new OpenLay [...]
+d/2,b.lat-e/2,b.lon+d/2,b.lat+e/2);for(var b=Math.floor(e/this.dy),d=Math.floor(d/this.dx),e=c.lon+this.dx*Math.ceil((this.gridBounds.left-c.lon)/this.dx),c=c.lat+this.dy*Math.ceil((this.gridBounds.bottom-c.lat)/this.dy),g=Array(b*d),h,k=0;k<d;++k)for(var f=e+k*this.dx,l=0;l<b;++l)h=c+l*this.dy,h=new OpenLayers.Geometry.Point(f,h),this.rotation&&h.rotate(this.rotation,a),g[k*b+l]=new OpenLayers.Feature.Vector(h);this.destroyFeatures(this.features,{silent:!0});this.addFeatures(g,{silent:! [...]
+!this.gridBounds.containsBounds(this.getViewBounds())},CLASS_NAME:"OpenLayers.Layer.PointGrid"});OpenLayers.Handler.MouseWheel=OpenLayers.Class(OpenLayers.Handler,{wheelListener:null,interval:0,maxDelta:Number.POSITIVE_INFINITY,delta:0,cumulative:!0,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.wheelListener=OpenLayers.Function.bindAsEventListener(this.onWheelEvent,this)},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,ar [...]
+!1,c=!1,d=!1,e=OpenLayers.Event.element(a);null!=e&&!d&&!b;){if(!b)try{var f,b=(f=e.currentStyle?e.currentStyle.overflow:document.defaultView.getComputedStyle(e,null).getPropertyValue("overflow"))&&"auto"==f||"scroll"==f}catch(g){}if(!c&&(c=OpenLayers.Element.hasClass(e,"olScrollable"),!c))for(var d=0,h=this.map.layers.length;d<h;d++){var k=this.map.layers[d];if(e==k.div||e==k.pane){c=!0;break}}d=e==this.map.div;e=e.parentNode}if(!b&&d){if(c)if(b=0,a.wheelDelta?(b=a.wheelDelta,0===b%160& [...]
+b/=120):a.detail&&(b=-(a.detail/Math.abs(a.detail))),this.delta+=b,window.clearTimeout(this._timeoutId),this.interval&&Math.abs(this.delta)<this.maxDelta){var l=OpenLayers.Util.extend({},a);this._timeoutId=window.setTimeout(OpenLayers.Function.bind(function(){this.wheelZoom(l)},this),this.interval)}else this.wheelZoom(a);OpenLayers.Event.stop(a)}}},wheelZoom:function(a){var b=this.delta;this.delta=0;b&&(a.xy=this.map.events.getMousePosition(a),0>b?this.callback("down",[a,this.cumulative? [...]
+b):-1]):this.callback("up",[a,this.cumulative?Math.min(this.maxDelta,b):1]))},activate:function(a){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var b=this.wheelListener;OpenLayers.Event.observe(window,"DOMMouseScroll",b);OpenLayers.Event.observe(window,"mousewheel",b);OpenLayers.Event.observe(document,"mousewheel",b);return!0}return!1},deactivate:function(a){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){var b=this.wheelListener;OpenLayers.Event.stop [...]
+"DOMMouseScroll",b);OpenLayers.Event.stopObserving(window,"mousewheel",b);OpenLayers.Event.stopObserving(document,"mousewheel",b);return!0}return!1},CLASS_NAME:"OpenLayers.Handler.MouseWheel"});OpenLayers.Symbolizer=OpenLayers.Class({zIndex:0,initialize:function(a){OpenLayers.Util.extend(this,a)},clone:function(){return new (eval(this.CLASS_NAME))(OpenLayers.Util.extend({},this))},CLASS_NAME:"OpenLayers.Symbolizer"});OpenLayers.Symbolizer.Raster=OpenLayers.Class(OpenLayers.Symbolizer,{in [...]
+this.getContext(a),c=!0;if(this.minScaleDenominator||this.maxScaleDenominator)var d=a.layer.map.getScale();this.minScaleDenominator&&(c=d>=OpenLayers.Style.createLiteral(this.minScaleDenominator,b));c&&this.maxScaleDenominator&&(c=d<OpenLayers.Style.createLiteral(this.maxScaleDenominator,b));c&&this.filter&&(c="OpenLayers.Filter.FeatureId"==this.filter.CLASS_NAME?this.filter.evaluate(a):this.filter.evaluate(b));return c},getContext:function(a){var b=this.context;b||(b=a.attributes||a.dat [...]
+typeof this.context&&(b=this.context(a));return b},clone:function(){var a=OpenLayers.Util.extend({},this);if(this.symbolizers){var b=this.symbolizers.length;a.symbolizers=Array(b);for(var c=0;c<b;++c)a.symbolizers[c]=this.symbolizers[c].clone()}else{a.symbolizer={};for(var d in this.symbolizer)b=this.symbolizer[d],c=typeof b,"object"===c?a.symbolizer[d]=OpenLayers.Util.extend({},b):"string"===c&&(a.symbolizer[d]=b)}a.filter=this.filter&&this.filter.clone();a.context=this.context&&OpenLay [...]
+this.context);return new OpenLayers.Rule(a)},CLASS_NAME:"OpenLayers.Rule"});OpenLayers.Format.SLD=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{profile:null,defaultVersion:"1.0.0",stringifyOutput:!0,namedLayersAsArray:!1,CLASS_NAME:"OpenLayers.Format.SLD"});OpenLayers.Symbolizer.Polygon=OpenLayers.Class(OpenLayers.Symbolizer,{initialize:function(a){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments)},CLASS_NAME:"OpenLayers.Symbolizer.Polygon"});OpenLayers.Format.GML.v [...]
+{};this.readChildNodes(a,c);b.components||(b.components=[]);var d=c.points[0],c=c.points[1];b.components.push(new OpenLayers.Bounds(d.x,d.y,c.x,c.y))}},OpenLayers.Format.GML.Base.prototype.readers.gml),feature:OpenLayers.Format.GML.Base.prototype.readers.feature,wfs:OpenLayers.Format.GML.Base.prototype.readers.wfs},write:function(a){var b;b=OpenLayers.Util.isArray(a)?"wfs:FeatureCollection":"gml:featureMember";a=this.writeNode(b,a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLoc [...]
+return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{gml:OpenLayers.Util.applyDefaults({Point:function(a){var b=this.createElementNSPlus("gml:Point");this.writeNode("coordinates",[a],b);return b},coordinates:function(a){for(var b=a.length,c=Array(b),d,e=0;e<b;++e)d=a[e],c[e]=this.xy?d.x+","+d.y:d.y+","+d.x,void 0!=d.z&&(c[e]+=","+d.z);return this.createElementNSPlus("gml:coordinates",{attributes:{decimal:".",cs:",",ts:" "},value:1==b?c[0]:c.join(" ")})},LineString:funct [...]
+this.createElementNSPlus("gml:LineString");this.writeNode("coordinates",a.components,b);return b},Polygon:function(a){var b=this.createElementNSPlus("gml:Polygon");this.writeNode("outerBoundaryIs",a.components[0],b);for(var c=1;c<a.components.length;++c)this.writeNode("innerBoundaryIs",a.components[c],b);return b},outerBoundaryIs:function(a){var b=this.createElementNSPlus("gml:outerBoundaryIs");this.writeNode("LinearRing",a,b);return b},innerBoundaryIs:function(a){var b=this.createElemen [...]
+this.writeNode("LinearRing",a,b);return b},LinearRing:function(a){var b=this.createElementNSPlus("gml:LinearRing");this.writeNode("coordinates",a.components,b);return b},Box:function(a){var b=this.createElementNSPlus("gml:Box");this.writeNode("coordinates",[{x:a.left,y:a.bottom},{x:a.right,y:a.top}],b);this.srsName&&b.setAttribute("srsName",this.srsName);return b}},OpenLayers.Format.GML.Base.prototype.writers.gml),feature:OpenLayers.Format.GML.Base.prototype.writers.feature,wfs:OpenLayer [...]
+CLASS_NAME:"OpenLayers.Format.GML.v2"});OpenLayers.Format.Filter.v1_0_0=OpenLayers.Class(OpenLayers.Format.GML.v2,OpenLayers.Format.Filter.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",initialize:function(a){OpenLayers.Format.GML.v2.prototype.initialize.apply(this,[a])},readers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO});this.readChildNode [...]
+b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLike:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(a,c);var d=a.getAttribute("wildCard"),e=a.getAttribute("singleChar"),f=a.getAttribute("escape");c.value2regex(d,e,f);b.filters.push(c)}},OpenLayers.Format.Filter.v1.prototype.readers.ogc),gml:OpenLayers.Format.GML.v2.prototype [...]
+feature:OpenLayers.Format.GML.v2.prototype.readers.feature},writers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsNotEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsNotEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsLike:function(a){var b=this.createElemen [...]
+{attributes:{wildCard:"*",singleChar:".",escape:"!"}});this.writeNode("PropertyName",a,b);this.writeNode("Literal",a.regex2value(),b);return b},BBOX:function(a){var b=this.createElementNSPlus("ogc:BBOX");a.property&&this.writeNode("PropertyName",a,b);var c=this.writeNode("gml:Box",a.value,b);a.projection&&c.setAttribute("srsName",a.projection);return b}},OpenLayers.Format.Filter.v1.prototype.writers.ogc),gml:OpenLayers.Format.GML.v2.prototype.writers.gml,feature:OpenLayers.Format.GML.v2. [...]
+writeSpatial:function(a,b){var c=this.createElementNSPlus("ogc:"+b);this.writeNode("PropertyName",a,c);if(a.value instanceof OpenLayers.Filter.Function)this.writeNode("Function",a.value,c);else{var d;d=a.value instanceof OpenLayers.Geometry?this.writeNode("feature:_geometry",a.value).firstChild:this.writeNode("gml:Box",a.value);a.projection&&d.setAttribute("srsName",a.projection);c.appendChild(d)}return c},CLASS_NAME:"OpenLayers.Format.Filter.v1_0_0"});OpenLayers.Format.WFST.v1_0_0=OpenL [...]
+b){b.insertIds=[];b.success=!1;this.readChildNodes(a,b)},InsertResult:function(a,b){var c={fids:[]};this.readChildNodes(a,c);b.insertIds=b.insertIds.concat(c.fids)},TransactionResult:function(a,b){this.readChildNodes(a,b)},Status:function(a,b){this.readChildNodes(a,b)},SUCCESS:function(a,b){b.success=!0}},OpenLayers.Format.WFST.v1.prototype.readers.wfs),gml:OpenLayers.Format.GML.v2.prototype.readers.gml,feature:OpenLayers.Format.GML.v2.prototype.readers.feature,ogc:OpenLayers.Format.Filt [...]
+writers:{wfs:OpenLayers.Util.applyDefaults({Query:function(a){a=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName,srsNameInQuery:this.srsNameInQuery},a);var b=a.featurePrefix,c=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(b?b+":":"")+a.featureType}});a.srsNameInQuery&&a.srsName&&c.setAttribute("srsName",a.srsName);a.featureNS&&c.setAttribute("xmlns:"+b,a.featureNS);if(a.propertyNames)for(var  [...]
+d;b++)this.writeNode("ogc:PropertyName",{property:a.propertyNames[b]},c);a.filter&&(this.setFilterProperty(a.filter),this.writeNode("ogc:Filter",a.filter,c));return c}},OpenLayers.Format.WFST.v1.prototype.writers.wfs),gml:OpenLayers.Format.GML.v2.prototype.writers.gml,feature:OpenLayers.Format.GML.v2.prototype.writers.feature,ogc:OpenLayers.Format.Filter.v1_0_0.prototype.writers.ogc},CLASS_NAME:"OpenLayers.Format.WFST.v1_0_0"});OpenLayers.ElementsIndexer=OpenLayers.Class({maxZIndex:null, [...]
+0,b);this.indices[b]=this.getZIndex(a);return this.getNextElement(d)},remove:function(a){a=a.id;var b=OpenLayers.Util.indexOf(this.order,a);0<=b&&(this.order.splice(b,1),delete this.indices[a],this.maxZIndex=0<this.order.length?this.indices[this.order[this.order.length-1]]:0)},clear:function(){this.order=[];this.indices={};this.maxZIndex=0},exists:function(a){return null!=this.indices[a.id]},getZIndex:function(a){return a._style.graphicZIndex},determineZIndex:function(a){var b=a._style.g [...]
+null==b?(b=this.maxZIndex,a._style.graphicZIndex=b):b>this.maxZIndex&&(this.maxZIndex=b)},getNextElement:function(a){a+=1;if(a<this.order.length){var b=OpenLayers.Util.getElement(this.order[a]);void 0==b&&(b=this.getNextElement(a));return b}return null},CLASS_NAME:"OpenLayers.ElementsIndexer"});
+OpenLayers.ElementsIndexer.IndexingMethods={Z_ORDER:function(a,b,c){b=a.getZIndex(b);var d=0;c&&(a=a.getZIndex(c),d=b-a);return d},Z_ORDER_DRAWING_ORDER:function(a,b,c){a=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(a,b,c);c&&0==a&&(a=1);return a},Z_ORDER_Y_ORDER:function(a,b,c){a=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(a,b,c);c&&0===a&&(b=c._boundsBottom-b._boundsBottom,a=0===b?1:b);return a}};
+OpenLayers.Renderer.Elements=OpenLayers.Class(OpenLayers.Renderer,{rendererRoot:null,root:null,vectorRoot:null,textRoot:null,xmlns:null,xOffset:0,indexer:null,BACKGROUND_ID_SUFFIX:"_background",LABEL_ID_SUFFIX:"_label",LABEL_OUTLINE_SUFFIX:"_outline",initialize:function(a,b){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.rendererRoot=this.createRenderRoot();this.root=this.createRoot("_root");this.vectorRoot=this.createRoot("_vroot");this.textRoot=this.createRoot("_tr [...]
+this.root.appendChild(this.textRoot);this.rendererRoot.appendChild(this.root);this.container.appendChild(this.rendererRoot);b&&(b.zIndexing||b.yOrdering)&&(this.indexer=new OpenLayers.ElementsIndexer(b.yOrdering))},destroy:function(){this.clear();this.xmlns=this.root=this.rendererRoot=null;OpenLayers.Renderer.prototype.destroy.apply(this,arguments)},clear:function(){var a,b=this.vectorRoot;if(b)for(;a=b.firstChild;)b.removeChild(a);if(b=this.textRoot)for(;a=b.firstChild;)b.removeChild(a) [...]
+this.indexer.clear()},setExtent:function(a,b){var c=OpenLayers.Renderer.prototype.setExtent.apply(this,arguments),d=this.getResolution();if(this.map.baseLayer&&this.map.baseLayer.wrapDateLine){var e,f=a.getWidth()/this.map.getExtent().getWidth();a=a.scale(1/f);f=this.map.getMaxExtent();f.right>a.left&&f.right<a.right?e=!0:f.left>a.left&&f.left<a.right&&(e=!1);if(e!==this.rightOfDateLine||b)c=!1,this.xOffset=!0===e?f.getWidth()/d:0;this.rightOfDateLine=e}return c},getNodeType:function(a,b [...]
+b,c){var d=a.CLASS_NAME,e=!0;if("OpenLayers.Geometry.Collection"==d||"OpenLayers.Geometry.MultiPoint"==d||"OpenLayers.Geometry.MultiLineString"==d||"OpenLayers.Geometry.MultiPolygon"==d){for(var d=0,f=a.components.length;d<f;d++)e=this.drawGeometry(a.components[d],b,c)&&e;return e}d=e=!1;"none"!=b.display&&(b.backgroundGraphic?this.redrawBackgroundNode(a.id,a,b,c):d=!0,e=this.redrawNode(a.id,a,b,c));!1==e&&(b=document.getElementById(a.id))&&(b._style.backgroundGraphic&&(d=!0),b.parentNod [...]
+d&&(b=document.getElementById(a.id+this.BACKGROUND_ID_SUFFIX))&&b.parentNode.removeChild(b);return e},redrawNode:function(a,b,c,d){c=this.applyDefaultSymbolizer(c);a=this.nodeFactory(a,this.getNodeType(b,c));a._featureId=d;a._boundsBottom=b.getBounds().bottom;a._geometryClass=b.CLASS_NAME;a._style=c;b=this.drawGeometryNode(a,b,c);if(!1===b)return!1;a=b.node;this.indexer?(c=this.indexer.insert(a))?this.vectorRoot.insertBefore(a,c):this.vectorRoot.appendChild(a):a.parentNode!==this.vectorR [...]
+this.postDraw(a);return b.complete},redrawBackgroundNode:function(a,b,c,d){c=OpenLayers.Util.extend({},c);c.externalGraphic=c.backgroundGraphic;c.graphicXOffset=c.backgroundXOffset;c.graphicYOffset=c.backgroundYOffset;c.graphicZIndex=c.backgroundGraphicZIndex;c.graphicWidth=c.backgroundWidth||c.graphicWidth;c.graphicHeight=c.backgroundHeight||c.graphicHeight;c.backgroundGraphic=null;c.backgroundXOffset=null;c.backgroundYOffset=null;c.backgroundGraphicZIndex=null;return this.redrawNode(a+ [...]
+b,c,null)},drawGeometryNode:function(a,b,c){c=c||a._style;var d={isFilled:void 0===c.fill?!0:c.fill,isStroked:void 0===c.stroke?!!c.strokeWidth:c.stroke},e;switch(b.CLASS_NAME){case "OpenLayers.Geometry.Point":!1===c.graphic&&(d.isFilled=!1,d.isStroked=!1);e=this.drawPoint(a,b);break;case "OpenLayers.Geometry.LineString":d.isFilled=!1;e=this.drawLineString(a,b);break;case "OpenLayers.Geometry.LinearRing":e=this.drawLinearRing(a,b);break;case "OpenLayers.Geometry.Polygon":e=this.drawPolyg [...]
+case "OpenLayers.Geometry.Rectangle":e=this.drawRectangle(a,b)}a._options=d;return!1!=e?{node:this.setStyle(a,c,d,b),complete:e}:!1},postDraw:function(a){},drawPoint:function(a,b){},drawLineString:function(a,b){},drawLinearRing:function(a,b){},drawPolygon:function(a,b){},drawRectangle:function(a,b){},drawCircle:function(a,b){},removeText:function(a){var b=document.getElementById(a+this.LABEL_ID_SUFFIX);b&&this.textRoot.removeChild(b);(a=document.getElementById(a+this.LABEL_OUTLINE_SUFFIX [...]
+getFeatureIdFromEvent:function(a){var b=a.target,c=b&&b.correspondingUseElement;return(c?c:b||a.srcElement)._featureId},eraseGeometry:function(a,b){if("OpenLayers.Geometry.MultiPoint"==a.CLASS_NAME||"OpenLayers.Geometry.MultiLineString"==a.CLASS_NAME||"OpenLayers.Geometry.MultiPolygon"==a.CLASS_NAME||"OpenLayers.Geometry.Collection"==a.CLASS_NAME)for(var c=0,d=a.components.length;c<d;c++)this.eraseGeometry(a.components[c],b);else(c=OpenLayers.Util.getElement(a.id))&&c.parentNode&&(c.geom [...]
+c.geometry=null),c.parentNode.removeChild(c),this.indexer&&this.indexer.remove(c),c._style.backgroundGraphic&&(c=OpenLayers.Util.getElement(a.id+this.BACKGROUND_ID_SUFFIX))&&c.parentNode&&c.parentNode.removeChild(c))},nodeFactory:function(a,b){var c=OpenLayers.Util.getElement(a);c?this.nodeTypeCompare(c,b)||(c.parentNode.removeChild(c),c=this.nodeFactory(a,b)):c=this.createNode(b,a);return c},nodeTypeCompare:function(a,b){},createNode:function(a,b){},moveRoot:function(a){var b=this.root; [...]
+this.rendererRoot&&(b=a.root);b.parentNode.removeChild(b);a.rendererRoot.appendChild(b)},getRenderLayerId:function(){return this.root.parentNode.parentNode.id},isComplexSymbol:function(a){return"circle"!=a&&!!a},CLASS_NAME:"OpenLayers.Renderer.Elements"});OpenLayers.Control.ArgParser=OpenLayers.Class(OpenLayers.Control,{center:null,zoom:null,layers:null,displayProjection:null,getParameters:function(a){a=a||window.location.href;var b=OpenLayers.Util.getParameters(a),c=a.indexOf("#");0<c&& [...]
+"OpenLayers.Control.ArgParser"==d.CLASS_NAME){d.displayProjection!=this.displayProjection&&(this.displayProjection=d.displayProjection);break}}b==this.map.controls.length&&(b=this.getParameters(),b.layers&&(this.layers=b.layers,this.map.events.register("addlayer",this,this.configureLayers),this.configureLayers()),b.lat&&b.lon&&(this.center=new OpenLayers.LonLat(parseFloat(b.lon),parseFloat(b.lat)),b.zoom&&(this.zoom=parseFloat(b.zoom)),this.map.events.register("changebaselayer",this,this [...]
+this.setCenter()))},setCenter:function(){this.map.baseLayer&&(this.map.events.unregister("changebaselayer",this,this.setCenter),this.displayProjection&&this.center.transform(this.displayProjection,this.map.getProjectionObject()),this.map.setCenter(this.center,this.zoom))},configureLayers:function(){if(this.layers.length==this.map.layers.length){this.map.events.unregister("addlayer",this,this.configureLayers);for(var a=0,b=this.layers.length;a<b;a++){var c=this.map.layers[a],d=this.layers [...]
+"B"==d?this.map.setBaseLayer(c):"T"!=d&&"F"!=d||c.setVisibility("T"==d)}}},CLASS_NAME:"OpenLayers.Control.ArgParser"});OpenLayers.Control.Permalink=OpenLayers.Class(OpenLayers.Control,{argParserClass:OpenLayers.Control.ArgParser,element:null,anchor:!1,base:"",displayProjection:null,initialize:function(a,b,c){null===a||"object"!=typeof a||OpenLayers.Util.isElement(a)?(OpenLayers.Control.prototype.initialize.apply(this,[c]),this.element=OpenLayers.Util.getElement(a),this.base=b||document.l [...]
+OpenLayers.Util.getElement(this.element)))},destroy:function(){this.element&&this.element.parentNode==this.div&&(this.div.removeChild(this.element),this.element=null);this.map&&this.map.events.unregister("moveend",this,this.updateLink);OpenLayers.Control.prototype.destroy.apply(this,arguments)},setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var b=0,c=this.map.controls.length;b<c;b++){var d=this.map.controls[b];if(d.CLASS_NAME==this.argParserClass.CLASS_N [...]
+this.displayProjection&&(this.displayProjection=d.displayProjection);break}}b==this.map.controls.length&&this.map.addControl(new this.argParserClass({displayProjection:this.displayProjection}))},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.element||this.anchor||(this.element=document.createElement("a"),this.element.innerHTML=OpenLayers.i18n("Permalink"),this.element.href="",this.div.appendChild(this.element));this.map.events.on({moveend:this.updateLink,cha [...]
+changebaselayer:this.updateLink,scope:this});this.updateLink();return this.div},updateLink:function(){var a=this.anchor?"#":"?",b=this.base,c=null;-1!=b.indexOf("#")&&!1==this.anchor&&(c=b.substring(b.indexOf("#"),b.length));-1!=b.indexOf(a)&&(b=b.substring(0,b.indexOf(a)));b=b.split("#")[0]+a+OpenLayers.Util.getParameterString(this.createParams());c&&(b+=c);this.anchor&&!this.element?window.location.href=b:this.element.href=b},createParams:function(a,b,c){a=a||this.map.getCenter();var d [...]
+if(a)for(d.zoom=b||this.map.getZoom(),b=a.lat,a=a.lon,this.displayProjection&&(b=OpenLayers.Projection.transform({x:a,y:b},this.map.getProjectionObject(),this.displayProjection),a=b.x,b=b.y),d.lat=Math.round(1E5*b)/1E5,d.lon=Math.round(1E5*a)/1E5,c=c||this.map.layers,d.layers="",a=0,b=c.length;a<b;a++){var e=c[a];d.layers=e.isBaseLayer?d.layers+(e==this.map.baseLayer?"B":"0"):d.layers+(e.getVisibility()?"T":"F")}return d},CLASS_NAME:"OpenLayers.Control.Permalink"});OpenLayers.Layer.TMS=O [...]
+c=Math.round((a.left-this.tileOrigin.lon)/(b*this.tileSize.w));a=Math.round((a.bottom-this.tileOrigin.lat)/(b*this.tileSize.h));b=this.getServerZoom();c=this.serviceVersion+"/"+this.layername+"/"+b+"/"+c+"/"+a+"."+this.type;a=this.url;OpenLayers.Util.isArray(a)&&(a=this.selectUrl(c,a));return a+c},setMap:function(a){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);this.tileOrigin||(this.tileOrigin=new OpenLayers.LonLat(this.map.maxExtent.left,this.map.maxExtent.bottom))},CLAS [...]
+this.getChildValue(a)},keywords:function(a,b){b.keywords=[];this.readChildNodes(a,b.keywords)},keyword:function(a,b){b.push(this.getChildValue(a))},responsibleParty:function(a,b){b.responsibleParty={};this.readChildNodes(a,b.responsibleParty)},individualName:function(a,b){b.individualName=this.getChildValue(a)},organisationName:function(a,b){b.organisationName=this.getChildValue(a)},positionName:function(a,b){b.positionName=this.getChildValue(a)},contactInfo:function(a,b){b.contactInfo={ [...]
+b.contactInfo)},phone:function(a,b){b.phone={};this.readChildNodes(a,b.phone)},voice:function(a,b){b.voice=this.getChildValue(a)},facsimile:function(a,b){b.facsimile=this.getChildValue(a)},address:function(a,b){b.address={};this.readChildNodes(a,b.address)},deliveryPoint:function(a,b){b.deliveryPoint=this.getChildValue(a)},city:function(a,b){b.city=this.getChildValue(a)},postalCode:function(a,b){b.postalCode=this.getChildValue(a)},country:function(a,b){b.country=this.getChildValue(a)},el [...]
+b){b.electronicMailAddress=this.getChildValue(a)},fees:function(a,b){b.fees=this.getChildValue(a)},accessConstraints:function(a,b){b.accessConstraints=this.getChildValue(a)},ContentMetadata:function(a,b){b.contentMetadata=[];this.readChildNodes(a,b.contentMetadata)},CoverageOfferingBrief:function(a,b){var c={};this.readChildNodes(a,c);b.push(c)},name:function(a,b){b.name=this.getChildValue(a)},label:function(a,b){b.label=this.getChildValue(a)},lonLatEnvelope:function(a,b){var c=this.getE [...]
+"http://www.opengis.net/gml","pos");if(2==c.length){var d={},e={};OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(this,[c[0],d]);OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(this,[c[1],e]);b.lonLatEnvelope={};b.lonLatEnvelope.srsName=a.getAttribute("srsName");b.lonLatEnvelope.min=d.points[0];b.lonLatEnvelope.max=e.points[0]}}}},CLASS_NAME:"OpenLayers.Format.WCSCapabilities.v1_0_0"});OpenLayers.Strategy.Fixed=OpenLayers.Class(OpenLayers.Strategy,{preload:!1,activate:f [...]
+scope:this});return a},load:function(a){var b=this.layer;b.events.triggerEvent("loadstart",{filter:b.filter});b.protocol.read(OpenLayers.Util.applyDefaults({callback:this.merge,filter:b.filter,scope:this},a));b.events.un({visibilitychanged:this.load,scope:this})},merge:function(a){var b=this.layer;b.destroyFeatures();var c=a.features;if(c&&0<c.length){var d=b.projection,e=b.map.getProjectionObject();if(!e.equals(d))for(var f,g=0,h=c.length;g<h;++g)(f=c[g].geometry)&&f.transform(d,e);b.ad [...]
+{response:a})},CLASS_NAME:"OpenLayers.Strategy.Fixed"});OpenLayers.Control.Zoom=OpenLayers.Class(OpenLayers.Control,{zoomInText:"+",zoomInId:"olZoomInLink",zoomOutText:"\u2212",zoomOutId:"olZoomOutLink",draw:function(){var a=OpenLayers.Control.prototype.draw.apply(this),b=this.getOrCreateLinks(a),c=b.zoomIn,b=b.zoomOut,d=this.map.events;b.parentNode!==a&&(d=this.events,d.attachToElement(b.parentNode));d.register("buttonclick",this,this.onZoomClick);this.zoomInLink=c;this.zoomOutLink=b;re [...]
+c=document.getElementById(this.zoomOutId);b||(b=document.createElement("a"),b.href="#zoomIn",b.appendChild(document.createTextNode(this.zoomInText)),b.className="olControlZoomIn",a.appendChild(b));OpenLayers.Element.addClass(b,"olButton");c||(c=document.createElement("a"),c.href="#zoomOut",c.appendChild(document.createTextNode(this.zoomOutText)),c.className="olControlZoomOut",a.appendChild(c));OpenLayers.Element.addClass(c,"olButton");return{zoomIn:b,zoomOut:c}},onZoomClick:function(a){a [...]
+a===this.zoomInLink?this.map.zoomIn():a===this.zoomOutLink&&this.map.zoomOut()},destroy:function(){this.map&&this.map.events.unregister("buttonclick",this,this.onZoomClick);delete this.zoomInLink;delete this.zoomOutLink;OpenLayers.Control.prototype.destroy.apply(this)},CLASS_NAME:"OpenLayers.Control.Zoom"});OpenLayers.Layer.PointTrack=OpenLayers.Class(OpenLayers.Layer.Vector,{dataFrom:null,styleFrom:null,addNodes:function(a,b){if(2>a.length)throw Error("At least two point features have t [...]
+a[g+this.dataFrom].data||a[g+this.dataFrom].attributes:null;var k=null!=this.styleFrom?a[g+this.styleFrom].style:null;e=new OpenLayers.Geometry.LineString([e,f]);c[g-1]=new OpenLayers.Feature.Vector(e,d,k)}e=f}this.addFeatures(c,b)},CLASS_NAME:"OpenLayers.Layer.PointTrack"});OpenLayers.Layer.PointTrack.SOURCE_NODE=-1;OpenLayers.Layer.PointTrack.TARGET_NODE=0;OpenLayers.Layer.PointTrack.dataFrom={SOURCE_NODE:-1,TARGET_NODE:0};OpenLayers.Protocol.WFS=function(a){a=OpenLayers.Util.applyDefa [...]
+OpenLayers.Protocol.WFS.fromWMSLayer=function(a,b){var c,d;c=a.params.LAYERS;c=(OpenLayers.Util.isArray(c)?c[0]:c).split(":");1<c.length&&(d=c[0]);c=c.pop();d={url:a.url,featureType:c,featurePrefix:d,srsName:a.projection&&a.projection.getCode()||a.map&&a.map.getProjectionObject().getCode(),version:"1.1.0"};return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(b,d))};OpenLayers.Protocol.WFS.DEFAULTS={version:"1.0.0"};OpenLayers.Layer.Markers=OpenLayers.Class(OpenLayers.Layer,{i [...]
+arguments);if(b||!this.drawn){for(var d=0,e=this.markers.length;d<e;d++)this.drawMarker(this.markers[d]);this.drawn=!0}},addMarker:function(a){this.markers.push(a);1>this.opacity&&a.setOpacity(this.opacity);this.map&&this.map.getExtent()&&(a.map=this.map,this.drawMarker(a))},removeMarker:function(a){this.markers&&this.markers.length&&(OpenLayers.Util.removeItem(this.markers,a),a.erase())},clearMarkers:function(){if(null!=this.markers)for(;0<this.markers.length;)this.removeMarker(this.mar [...]
+drawMarker:function(a){var b=this.map.getLayerPxFromLonLat(a.lonlat);null==b?a.display(!1):a.isDrawn()?a.icon&&a.icon.moveTo(b):(a=a.draw(b),this.div.appendChild(a))},getDataExtent:function(){var a=null;if(this.markers&&0<this.markers.length)for(var a=new OpenLayers.Bounds,b=0,c=this.markers.length;b<c;b++)a.extend(this.markers[b].lonlat);return a},CLASS_NAME:"OpenLayers.Layer.Markers"});OpenLayers.Control.Pan=OpenLayers.Class(OpenLayers.Control.Button,{slideFactor:50,slideRatio:null,dir [...]
+a("h"));break;case OpenLayers.Control.Pan.WEST:this.map.pan(-a("w"),0);break;case OpenLayers.Control.Pan.EAST:this.map.pan(a("w"),0)}}},CLASS_NAME:"OpenLayers.Control.Pan"});OpenLayers.Control.Pan.NORTH="North";OpenLayers.Control.Pan.SOUTH="South";OpenLayers.Control.Pan.EAST="East";OpenLayers.Control.Pan.WEST="West";OpenLayers.Format.CSWGetDomain=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Format.CSWGetDomain.DEFAULTS);var b=OpenLayers.Format.CSWGetDomain["v"+a.version.repla [...]
+a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},readers:{csw:{GetDomainResponse:function(a,b){this.readChildNodes(a,b)},DomainValues:function(a,b){OpenLayers.Util.isArray(b.DomainValues)||(b.DomainValues=[]);for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]=c[e].nodeValue;this.readChildNodes(a,d);b.DomainValues.push(d)},PropertyName:function(a,b){b.PropertyName=this.getChildValue(a)},ParameterName:function(a,b){b.ParameterName=this.getChildValue(a)}, [...]
+b){OpenLayers.Util.isArray(b.ListOfValues)||(b.ListOfValues=[]);this.readChildNodes(a,b.ListOfValues)},Value:function(a,b){for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]=c[e].nodeValue;d.value=this.getChildValue(a);b.push({Value:d})},ConceptualScheme:function(a,b){b.ConceptualScheme={};this.readChildNodes(a,b.ConceptualScheme)},Name:function(a,b){b.Name=this.getChildValue(a)},Document:function(a,b){b.Document=this.getChildValue(a)},Authority:function(a,b){b.Authority=thi [...]
+RangeOfValues:function(a,b){b.RangeOfValues={};this.readChildNodes(a,b.RangeOfValues)},MinValue:function(a,b){for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]=c[e].nodeValue;d.value=this.getChildValue(a);b.MinValue=d},MaxValue:function(a,b){for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]=c[e].nodeValue;d.value=this.getChildValue(a);b.MaxValue=d}}},write:function(a){a=this.writeNode("csw:GetDomain",a);return OpenLayers.Format.XML.prototype.write.apply(this,[ [...]
+this.createElementNSPlus("csw:GetDomain",{attributes:{service:"CSW",version:this.version}});a.PropertyName||this.PropertyName?this.writeNode("csw:PropertyName",a.PropertyName||this.PropertyName,b):(a.ParameterName||this.ParameterName)&&this.writeNode("csw:ParameterName",a.ParameterName||this.ParameterName,b);this.readChildNodes(b,a);return b},PropertyName:function(a){return this.createElementNSPlus("csw:PropertyName",{value:a})},ParameterName:function(a){return this.createElementNSPlus(" [...]
+{value:a})}}},CLASS_NAME:"OpenLayers.Format.CSWGetDomain.v2_0_2"});OpenLayers.Format.ArcXML.Features=OpenLayers.Class(OpenLayers.Format.XML,{read:function(a){return(new OpenLayers.Format.ArcXML).read(a).features.feature}});OpenLayers.Control.Snapping=OpenLayers.Class(OpenLayers.Control,{DEFAULTS:{tolerance:10,node:!0,edge:!0,vertex:!0},greedy:!0,precedence:["node","vertex","edge"],resolution:null,geoToleranceCache:null,layer:null,feature:null,point:null,initialize:function(a){OpenLayers. [...]
+0===this.targets.length&&this.layer&&this.addTargetLayer(this.layer);this.geoToleranceCache={}},setLayer:function(a){this.active?(this.deactivate(),this.layer=a,this.activate()):this.layer=a},setTargets:function(a){this.targets=[];if(a&&a.length)for(var b,c=0,d=a.length;c<d;++c)b=a[c],b instanceof OpenLayers.Layer.Vector?this.addTargetLayer(b):this.addTarget(b)},addTargetLayer:function(a){this.addTarget({layer:a})},addTarget:function(a){a=OpenLayers.Util.applyDefaults(a,this.defaults);a. [...]
+a.nodeTolerance||a.tolerance;a.vertexTolerance=a.vertexTolerance||a.tolerance;a.edgeTolerance=a.edgeTolerance||a.tolerance;this.targets.push(a)},removeTargetLayer:function(a){for(var b,c=this.targets.length-1;0<=c;--c)b=this.targets[c],b.layer===a&&this.removeTarget(b)},removeTarget:function(a){return OpenLayers.Util.removeItem(this.targets,a)},activate:function(){var a=OpenLayers.Control.prototype.activate.call(this);if(a&&this.layer&&this.layer.events)this.layer.events.on({sketchstarte [...]
+sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});return a},deactivate:function(){var a=OpenLayers.Control.prototype.deactivate.call(this);a&&this.layer&&this.layer.events&&this.layer.events.un({sketchstarted:this.onSketchModified,sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});this.point=this.feature=null;return a},onSketchModified:function(a){this.feature=a.feature;this.considerSnapping(a.vertex,a.vertex)},onVe [...]
+a.feature;var b=this.layer.map.getLonLatFromViewPortPx(a.pixel);this.considerSnapping(a.vertex,new OpenLayers.Geometry.Point(b.lon,b.lat))},considerSnapping:function(a,b){for(var c={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY,x:null,y:null},d=!1,e,f,g=0,h=this.targets.length;g<h;++g)if(f=this.targets[g],e=this.testTarget(f,b))if(this.greedy){c=e;c.target=f;d=!0;break}else if(e.rank<c.rank||e.rank===c.rank&&e.dist<c.dist)c=e,c.target=f,d=!0;d&&(!1!==this.events.triggerEven [...]
+{point:a,x:c.x,y:c.y,distance:c.dist,layer:c.target.layer,snapType:this.precedence[c.rank]})?(a.x=c.x,a.y=c.y,this.point=a,this.events.triggerEvent("snap",{point:a,snapType:this.precedence[c.rank],layer:c.target.layer,distance:c.dist})):d=!1);this.point&&!d&&(a.x=b.x,a.y=b.y,this.point=null,this.events.triggerEvent("unsnap",{point:a}))},testTarget:function(a,b){var c=this.layer.map.getResolution();if("minResolution"in a&&c<a.minResolution||"maxResolution"in a&&c>=a.maxResolution)return n [...]
+{node:this.getGeoTolerance(a.nodeTolerance,c),vertex:this.getGeoTolerance(a.vertexTolerance,c),edge:this.getGeoTolerance(a.edgeTolerance,c)},d=Math.max(c.node,c.vertex,c.edge),e={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY},f=!1,g=a.layer.features,h,k,l,m,n,p,q=this.precedence.length,r=new OpenLayers.LonLat(b.x,b.y),s=0,t=g.length;s<t;++s)if(h=g[s],h!==this.feature&&(!h._sketch&&h.state!==OpenLayers.State.DELETE&&(!a.filter||a.filter.evaluate(h)))&&h.atPoint(r,d,d))for(va [...]
+1,q);u<v;++u)if(k=this.precedence[u],a[k])if("edge"===k){if(l=h.geometry.distanceTo(b,{details:!0}),n=l.distance,n<=c[k]&&n<e.dist){e={rank:u,dist:n,x:l.x0,y:l.y0};f=!0;break}}else{l=h.geometry.getVertices("node"===k);p=!1;for(var w=0,x=l.length;w<x;++w)m=l[w],n=m.distanceTo(b),n<=c[k]&&(u<e.rank||u===e.rank&&n<e.dist)&&(e={rank:u,dist:n,x:m.x,y:m.y},p=f=!0);if(p)break}return f?e:null},getGeoTolerance:function(a,b){b!==this.resolution&&(this.resolution=b,this.geoToleranceCache={});var c= [...]
+void 0===c&&(c=a*b,this.geoToleranceCache[a]=c);return c},destroy:function(){this.active&&this.deactivate();delete this.layer;delete this.targets;OpenLayers.Control.prototype.destroy.call(this)},CLASS_NAME:"OpenLayers.Control.Snapping"});OpenLayers.Format.OWSCommon.v1_1_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1,{namespaces:{ows:"http://www.opengis.net/ows/1.1",xlink:"http://www.w3.org/1999/xlink"},readers:{ows:OpenLayers.Util.applyDefaults({ExceptionReport:function(a,b){b.excepti [...]
+!0},DataType:function(a,b){b.dataType=this.getChildValue(a)},Range:function(a,b){b.range={};this.readChildNodes(a,b.range)},MinimumValue:function(a,b){b.minValue=this.getChildValue(a)},MaximumValue:function(a,b){b.maxValue=this.getChildValue(a)},Identifier:function(a,b){b.identifier=this.getChildValue(a)},SupportedCRS:function(a,b){b.supportedCRS=this.getChildValue(a)}},OpenLayers.Format.OWSCommon.v1.prototype.readers.ows)},writers:{ows:OpenLayers.Util.applyDefaults({Range:function(a){va [...]
+{attributes:{"ows:rangeClosure":a.closure}});this.writeNode("ows:MinimumValue",a.minValue,b);this.writeNode("ows:MaximumValue",a.maxValue,b);return b},MinimumValue:function(a){return this.createElementNSPlus("ows:MinimumValue",{value:a})},MaximumValue:function(a){return this.createElementNSPlus("ows:MaximumValue",{value:a})},Value:function(a){return this.createElementNSPlus("ows:Value",{value:a})}},OpenLayers.Format.OWSCommon.v1.prototype.writers.ows)},CLASS_NAME:"OpenLayers.Format.OWSCo [...]
+a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{wcs:{GetCoverage:function(a){var b=this.createElementNSPlus("wcs:GetCoverage",{attributes:{version:a.version||this.VERSION,service:"WCS"}});this.writeNode("ows:Identifier",a.identifier,b);this.writeNode("wcs:DomainSubset",a.domainSubset,b);this.writeNode("wcs:Output",a.output,b);return b},DomainSubset:function(a){var b=this.createEl [...]
+{});this.writeNode("ows:BoundingBox",a.boundingBox,b);a.temporalSubset&&this.writeNode("wcs:TemporalSubset",a.temporalSubset,b);return b},TemporalSubset:function(a){for(var b=this.createElementNSPlus("wcs:TemporalSubset",{}),c=0,d=a.timePeriods.length;c<d;++c)this.writeNode("wcs:TimePeriod",a.timePeriods[c],b);return b},TimePeriod:function(a){var b=this.createElementNSPlus("wcs:TimePeriod",{});this.writeNode("wcs:BeginPosition",a.begin,b);this.writeNode("wcs:EndPosition",a.end,b);a.resol [...]
+a.resolution,b);return b},BeginPosition:function(a){return this.createElementNSPlus("wcs:BeginPosition",{value:a})},EndPosition:function(a){return this.createElementNSPlus("wcs:EndPosition",{value:a})},TimeResolution:function(a){return this.createElementNSPlus("wcs:TimeResolution",{value:a})},Output:function(a){var b=this.createElementNSPlus("wcs:Output",{attributes:{format:a.format,store:a.store}});a.gridCRS&&this.writeNode("wcs:GridCRS",a.gridCRS,b);return b},GridCRS:function(a){var b= [...]
+{});this.writeNode("wcs:GridBaseCRS",a.baseCRS,b);a.type&&this.writeNode("wcs:GridType",a.type,b);a.origin&&this.writeNode("wcs:GridOrigin",a.origin,b);this.writeNode("wcs:GridOffsets",a.offsets,b);a.CS&&this.writeNode("wcs:GridCS",a.CS,b);return b},GridBaseCRS:function(a){return this.createElementNSPlus("wcs:GridBaseCRS",{value:a})},GridOrigin:function(a){return this.createElementNSPlus("wcs:GridOrigin",{value:a})},GridType:function(a){return this.createElementNSPlus("wcs:GridType",{val [...]
+{value:a})},GridCS:function(a){return this.createElementNSPlus("wcs:GridCS",{value:a})}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.writers.ows},CLASS_NAME:"OpenLayers.Format.WCSGetCoverage"});OpenLayers.Format.KML=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{kml:"http://www.opengis.net/kml/2.2",gx:"http://www.google.com/kml/ext/2.2"},kmlns:"http://earth.google.com/kml/2.0",placemarksDesc:"No description available",foldersName:"OpenLayers export",foldersDesc:"Exported on "+n [...]
+{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g,kmlColor:/(\w{2})(\w{2})(\w{2})(\w{2})/,kmlIconPalette:/root:\/\/icons\/palette-(\d+)(\.\w+)/,straightBracket:/\$\[(.*?)\]/g};this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){this.features=[];this.styles={};this.fetched={};return this.parseData(a,{depth:0,styleBaseUrl:this.styleBaseUrl})},parseData:function(a,b){"strin [...]
+(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));for(var c=["Link","NetworkLink","Style","StyleMap","Placemark"],d=0,e=c.length;d<e;++d){var f=c[d],g=this.getElementsByTagNameNS(a,"*",f);if(0!=g.length)switch(f.toLowerCase()){case "link":case "networklink":this.parseLinks(g,b);break;case "style":this.extractStyles&&this.parseStyles(g,b);break;case "stylemap":this.extractStyles&&this.parseStyleMaps(g,b);break;case "placemark":this.parseFeatures(g,b)}}return this.features},parseLin [...]
+b){if(b.depth>=this.maxDepth)return!1;var c=OpenLayers.Util.extend({},b);c.depth++;for(var d=0,e=a.length;d<e;d++){var f=this.parseProperty(a[d],"*","href");f&&!this.fetched[f]&&(this.fetched[f]=!0,(f=this.fetchLink(f))&&this.parseData(f,c))}},fetchLink:function(a){if(a=OpenLayers.Request.GET({url:a,async:!1}))return a.responseText},parseStyles:function(a,b){for(var c=0,d=a.length;c<d;c++){var e=this.parseStyle(a[c]);e&&(this.styles[(b.styleBaseUrl||"")+"#"+e.id]=e)}},parseKmlColor:funct [...]
+null;a&&(a=a.match(this.regExes.kmlColor))&&(b={color:"#"+a[4]+a[3]+a[2],opacity:parseInt(a[1],16)/255});return b},parseStyle:function(a){for(var b={},c=["LineStyle","PolyStyle","IconStyle","BalloonStyle","LabelStyle"],d,e,f=0,g=c.length;f<g;++f)if(d=c[f],e=this.getElementsByTagNameNS(a,"*",d)[0])switch(d.toLowerCase()){case "linestyle":d=this.parseProperty(e,"*","color");if(d=this.parseKmlColor(d))b.strokeColor=d.color,b.strokeOpacity=d.opacity;(d=this.parseProperty(e,"*","width"))&&(b. [...]
+d);break;case "polystyle":d=this.parseProperty(e,"*","color");if(d=this.parseKmlColor(d))b.fillOpacity=d.opacity,b.fillColor=d.color;"0"==this.parseProperty(e,"*","fill")&&(b.fillColor="none");"0"==this.parseProperty(e,"*","outline")&&(b.strokeWidth="0");break;case "iconstyle":var h=parseFloat(this.parseProperty(e,"*","scale")||1);d=32*h;var k=32*h,l=this.getElementsByTagNameNS(e,"*","Icon")[0];if(l){var m=this.parseProperty(l,"*","href");if(m){var n=this.parseProperty(l,"*","w"),p=this. [...]
+"*","h");!OpenLayers.String.startsWith(m,"http://maps.google.com/mapfiles/kml")||(n||p)||(p=n=64,h/=2);n=n||p;p=p||n;n&&(d=parseInt(n)*h);p&&(k=parseInt(p)*h);if(p=m.match(this.regExes.kmlIconPalette))n=p[1],p=p[2],m=this.parseProperty(l,"*","x"),l=this.parseProperty(l,"*","y"),m="http://maps.google.com/mapfiles/kml/pal"+n+"/icon"+(8*(l?7-l/32:7)+(m?m/32:0))+p;b.graphicOpacity=1;b.externalGraphic=m}}if(e=this.getElementsByTagNameNS(e,"*","hotSpot")[0])m=parseFloat(e.getAttribute("x")),l= [...]
+n=e.getAttribute("xunits"),"pixels"==n?b.graphicXOffset=-m*h:"insetPixels"==n?b.graphicXOffset=-d+m*h:"fraction"==n&&(b.graphicXOffset=-d*m),e=e.getAttribute("yunits"),"pixels"==e?b.graphicYOffset=-k+l*h+1:"insetPixels"==e?b.graphicYOffset=-(l*h)+1:"fraction"==e&&(b.graphicYOffset=-k*(1-l)+1);b.graphicWidth=d;b.graphicHeight=k;break;case "balloonstyle":(e=OpenLayers.Util.getXmlNodeValue(e))&&(b.balloonStyle=e.replace(this.regExes.straightBracket,"${$1}"));break;case "labelstyle":if(d=thi [...]
+"*","color"),d=this.parseKmlColor(d))b.fontColor=d.color,b.fontOpacity=d.opacity}!b.strokeColor&&b.fillColor&&(b.strokeColor=b.fillColor);(a=a.getAttribute("id"))&&b&&(b.id=a);return b},parseStyleMaps:function(a,b){for(var c=0,d=a.length;c<d;c++)for(var e=a[c],f=this.getElementsByTagNameNS(e,"*","Pair"),e=e.getAttribute("id"),g=0,h=f.length;g<h;g++){var k=f[g],l=this.parseProperty(k,"*","key");(k=this.parseProperty(k,"*","styleUrl"))&&"normal"==l&&(this.styles[(b.styleBaseUrl||"")+"#"+e] [...]
+"")+k])}},parseFeatures:function(a,b){for(var c=[],d=0,e=a.length;d<e;d++){var f=a[d],g=this.parseFeature.apply(this,[f]);if(g){this.extractStyles&&(g.attributes&&g.attributes.styleUrl)&&(g.style=this.getStyle(g.attributes.styleUrl,b));if(this.extractStyles){var h=this.getElementsByTagNameNS(f,"*","Style")[0];h&&(h=this.parseStyle(h))&&(g.style=OpenLayers.Util.extend(g.style,h))}this.extractTracks?(f=this.getElementsByTagNameNS(f,this.namespaces.gx,"Track"))&&0<f.length&&(g={features:[], [...]
+this.readNode(f[0],g),0<g.features.length&&c.push.apply(c,g.features)):c.push(g)}else throw"Bad Placemark: "+d;}this.features=this.features.concat(c)},readers:{kml:{when:function(a,b){b.whens.push(OpenLayers.Date.parse(this.getChildValue(a)))},_trackPointAttribute:function(a,b){var c=a.nodeName.split(":").pop();b.attributes[c].push(this.getChildValue(a))}},gx:{Track:function(a,b){var c={whens:[],points:[],angles:[]};if(this.trackAttributes){var d;c.attributes={};for(var e=0,f=this.trackA [...]
+f;++e)d=this.trackAttributes[e],c.attributes[d]=[],d in this.readers.kml||(this.readers.kml[d]=this.readers.kml._trackPointAttribute)}this.readChildNodes(a,c);if(c.whens.length!==c.points.length)throw Error("gx:Track with unequal number of when ("+c.whens.length+") and gx:coord ("+c.points.length+") elements.");var g=0<c.angles.length;if(g&&c.whens.length!==c.angles.length)throw Error("gx:Track with unequal number of when ("+c.whens.length+") and gx:angles ("+c.angles.length+") elements. [...]
+e=0,f=c.whens.length;e<f;++e){h=b.feature.clone();h.fid=b.feature.fid||b.feature.id;d=c.points[e];h.geometry=d;"z"in d&&(h.attributes.altitude=d.z);this.internalProjection&&this.externalProjection&&h.geometry.transform(this.externalProjection,this.internalProjection);if(this.trackAttributes)for(var k=0,l=this.trackAttributes.length;k<l;++k)d=this.trackAttributes[k],h.attributes[d]=c.attributes[d][e];h.attributes.when=c.whens[e];h.attributes.trackId=b.feature.id;g&&(d=c.angles[e],h.attrib [...]
+parseFloat(d[0]),h.attributes.tilt=parseFloat(d[1]),h.attributes.roll=parseFloat(d[2]));b.features.push(h)}},coord:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(/\s+/),d=new OpenLayers.Geometry.Point(c[0],c[1]);2<c.length&&(d.z=parseFloat(c[2]));b.points.push(d)},angles:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(/\s+/);b.angles.push(c)}}},parseFeature:function(a){for(var b=["MultiGeometry","Polygon","LineString" [...]
+c,d,e,f=0,g=b.length;f<g;++f)if(c=b[f],this.internalns=a.namespaceURI?a.namespaceURI:this.kmlns,d=this.getElementsByTagNameNS(a,this.internalns,c),0<d.length){if(b=this.parseGeometry[c.toLowerCase()])e=b.apply(this,[d[0]]),this.internalProjection&&this.externalProjection&&e.transform(this.externalProjection,this.internalProjection);else throw new TypeError("Unsupported geometry type: "+c);break}var h;this.extractAttributes&&(h=this.parseAttributes(a));c=new OpenLayers.Feature.Vector(e,h) [...]
+a.getAttribute("name");null!=a&&(c.fid=a);return c},getStyle:function(a,b){var c=OpenLayers.Util.removeTail(a),d=OpenLayers.Util.extend({},b);d.depth++;d.styleBaseUrl=c;!this.styles[a]&&!OpenLayers.String.startsWith(a,"#")&&d.depth<=this.maxDepth&&!this.fetched[c]&&(c=this.fetchLink(c))&&this.parseData(c,d);return OpenLayers.Util.extend({},this.styles[a])},parseGeometry:{point:function(a){var b=this.getElementsByTagNameNS(a,this.internalns,"coordinates");a=[];if(0<b.length){var c=b[0].fi [...]
+c=c.replace(this.regExes.removeSpace,"");a=c.split(",")}b=null;if(1<a.length)2==a.length&&(a[2]=null),b=new OpenLayers.Geometry.Point(a[0],a[1],a[2]);else throw"Bad coordinate string: "+c;return b},linestring:function(a,b){var c=this.getElementsByTagNameNS(a,this.internalns,"coordinates"),d=null;if(0<c.length){for(var c=this.getChildValue(c[0]),c=c.replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),d=c.split(this.regExes.splitSpace),e=d.length,f=Array(e),g,h,k=0;k [...]
+d[k].split(","),h=g.length,1<h)2==g.length&&(g[2]=null),f[k]=new OpenLayers.Geometry.Point(g[0],g[1],g[2]);else throw"Bad LineString point coordinates: "+d[k];if(e)d=b?new OpenLayers.Geometry.LinearRing(f):new OpenLayers.Geometry.LineString(f);else throw"Bad LineString coordinates: "+c;}return d},polygon:function(a){a=this.getElementsByTagNameNS(a,this.internalns,"LinearRing");var b=a.length,c=Array(b);if(0<b)for(var d=0,e=a.length;d<e;++d)if(b=this.parseGeometry.linestring.apply(this,[a [...]
+b;else throw"Bad LinearRing geometry: "+d;return new OpenLayers.Geometry.Polygon(c)},multigeometry:function(a){for(var b,c=[],d=a.childNodes,e=0,f=d.length;e<f;++e)a=d[e],1==a.nodeType&&(b=a.prefix?a.nodeName.split(":")[1]:a.nodeName,(b=this.parseGeometry[b.toLowerCase()])&&c.push(b.apply(this,[a])));return new OpenLayers.Geometry.Collection(c)}},parseAttributes:function(a){var b={},c=a.getElementsByTagName("ExtendedData");c.length&&(b=this.parseExtendedData(c[0]));var d,e,f;a=a.childNod [...]
+0,g=a.length;c<g;++c)if(d=a[c],1==d.nodeType&&(e=d.childNodes,1<=e.length&&3>=e.length)){switch(e.length){case 1:f=e[0];break;case 2:f=e[0];e=e[1];f=3==f.nodeType||4==f.nodeType?f:e;break;default:f=e[1]}if(3==f.nodeType||4==f.nodeType)if(d=d.prefix?d.nodeName.split(":")[1]:d.nodeName,f=OpenLayers.Util.getXmlNodeValue(f))f=f.replace(this.regExes.trimSpace,""),b[d]=f}return b},parseExtendedData:function(a){var b={},c,d,e,f,g=a.getElementsByTagName("Data");c=0;for(d=g.length;c<d;c++){e=g[c] [...]
+var h={},k=e.getElementsByTagName("value");k.length&&(h.value=this.getChildValue(k[0]));this.kvpAttributes?b[f]=h.value:(e=e.getElementsByTagName("displayName"),e.length&&(h.displayName=this.getChildValue(e[0])),b[f]=h)}a=a.getElementsByTagName("SimpleData");c=0;for(d=a.length;c<d;c++)h={},e=a[c],f=e.getAttribute("name"),h.value=this.getChildValue(e),this.kvpAttributes?b[f]=h.value:(h.displayName=f,b[f]=h);return b},parseProperty:function(a,b,c){var d;a=this.getElementsByTagNameNS(a,b,c) [...]
+null}return d},write:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=this.createElementNS(this.kmlns,"kml"),c=this.createFolderXML(),d=0,e=a.length;d<e;++d)c.appendChild(this.createPlacemarkXML(a[d]));b.appendChild(c);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFolderXML:function(){var a=this.createElementNS(this.kmlns,"Folder");if(this.foldersName){var b=this.createElementNS(this.kmlns,"name"),c=this.createTextNode(this.foldersName);b.appendChild(c);a.a [...]
+(b=this.createElementNS(this.kmlns,"description"),c=this.createTextNode(this.foldersDesc),b.appendChild(c),a.appendChild(b));return a},createPlacemarkXML:function(a){var b=this.createElementNS(this.kmlns,"name"),c=a.style&&a.style.label?a.style.label:a.id;b.appendChild(this.createTextNode(a.attributes.name||c));var d=this.createElementNS(this.kmlns,"description");d.appendChild(this.createTextNode(a.attributes.description||this.placemarksDesc));c=this.createElementNS(this.kmlns,"Placemark [...]
+a.fid&&c.setAttribute("id",a.fid);c.appendChild(b);c.appendChild(d);b=this.buildGeometryNode(a.geometry);c.appendChild(b);a.attributes&&(a=this.buildExtendedData(a.attributes))&&c.appendChild(a);return c},buildGeometryNode:function(a){var b=a.CLASS_NAME,b=b.substring(b.lastIndexOf(".")+1),b=this.buildGeometry[b.toLowerCase()],c=null;b&&(c=b.apply(this,[a]));return c},buildGeometry:{point:function(a){var b=this.createElementNS(this.kmlns,"Point");b.appendChild(this.buildCoordinatesNode(a) [...]
+multipoint:function(a){return this.buildGeometry.collection.apply(this,[a])},linestring:function(a){var b=this.createElementNS(this.kmlns,"LineString");b.appendChild(this.buildCoordinatesNode(a));return b},multilinestring:function(a){return this.buildGeometry.collection.apply(this,[a])},linearring:function(a){var b=this.createElementNS(this.kmlns,"LinearRing");b.appendChild(this.buildCoordinatesNode(a));return b},polygon:function(a){var b=this.createElementNS(this.kmlns,"Polygon");a=a.co [...]
+for(var c,d,e=0,f=a.length;e<f;++e)c=0==e?"outerBoundaryIs":"innerBoundaryIs",c=this.createElementNS(this.kmlns,c),d=this.buildGeometry.linearring.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},multipolygon:function(a){return this.buildGeometry.collection.apply(this,[a])},collection:function(a){for(var b=this.createElementNS(this.kmlns,"MultiGeometry"),c,d=0,e=a.components.length;d<e;++d)(c=this.buildGeometryNode.apply(this,[a.components[d]]))&&b.appendChild(c);return b}} [...]
+this.createElementNS(this.kmlns,"coordinates"),c;if(c=a.components){for(var d=c.length,e=Array(d),f=0;f<d;++f)a=c[f],e[f]=this.buildCoordinates(a);c=e.join(" ")}else c=this.buildCoordinates(a);c=this.createTextNode(c);b.appendChild(c);return b},buildCoordinates:function(a){this.internalProjection&&this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));return a.x+","+a.y},buildExtendedData:function(a){var b=this.createElementNS(this.kmlns,"Exte [...]
+c;for(c in a)if(a[c]&&"name"!=c&&"description"!=c&&"styleUrl"!=c){var d=this.createElementNS(this.kmlns,"Data");d.setAttribute("name",c);var e=this.createElementNS(this.kmlns,"value");if("object"==typeof a[c]){if(a[c].value&&e.appendChild(this.createTextNode(a[c].value)),a[c].displayName){var f=this.createElementNS(this.kmlns,"displayName");f.appendChild(this.getXMLDoc().createCDATASection(a[c].displayName));d.appendChild(f)}}else e.appendChild(this.createTextNode(a[c]));d.appendChild(e) [...]
+null:b},CLASS_NAME:"OpenLayers.Format.KML"});OpenLayers.Format.WMSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.1",profile:null,CLASS_NAME:"OpenLayers.Format.WMSCapabilities"});OpenLayers.Format.WMSCapabilities.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{wms:"http://www.opengis.net/wms",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"wms",read:function(a){"string"==typeof a&&(a=OpenLay [...]
+b){b.service={};this.readChildNodes(a,b.service)},Name:function(a,b){b.name=this.getChildValue(a)},Title:function(a,b){b.title=this.getChildValue(a)},Abstract:function(a,b){b["abstract"]=this.getChildValue(a)},BoundingBox:function(a,b){var c={};c.bbox=[parseFloat(a.getAttribute("minx")),parseFloat(a.getAttribute("miny")),parseFloat(a.getAttribute("maxx")),parseFloat(a.getAttribute("maxy"))];var d={x:parseFloat(a.getAttribute("resx")),y:parseFloat(a.getAttribute("resy"))};isNaN(d.x)&&isNa [...]
+d);return c},OnlineResource:function(a,b){b.href=this.getAttributeNS(a,this.namespaces.xlink,"href")},ContactInformation:function(a,b){b.contactInformation={};this.readChildNodes(a,b.contactInformation)},ContactPersonPrimary:function(a,b){b.personPrimary={};this.readChildNodes(a,b.personPrimary)},ContactPerson:function(a,b){b.person=this.getChildValue(a)},ContactOrganization:function(a,b){b.organization=this.getChildValue(a)},ContactPosition:function(a,b){b.position=this.getChildValue(a) [...]
+b){b.contactAddress={};this.readChildNodes(a,b.contactAddress)},AddressType:function(a,b){b.type=this.getChildValue(a)},Address:function(a,b){b.address=this.getChildValue(a)},City:function(a,b){b.city=this.getChildValue(a)},StateOrProvince:function(a,b){b.stateOrProvince=this.getChildValue(a)},PostCode:function(a,b){b.postcode=this.getChildValue(a)},Country:function(a,b){b.country=this.getChildValue(a)},ContactVoiceTelephone:function(a,b){b.phone=this.getChildValue(a)},ContactFacsimileTe [...]
+b){b.fax=this.getChildValue(a)},ContactElectronicMailAddress:function(a,b){b.email=this.getChildValue(a)},Fees:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.fees=c)},AccessConstraints:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.accessConstraints=c)},Capability:function(a,b){b.capability={nestedLayers:[],layers:[]};this.readChildNodes(a,b.capability)},Request:function(a,b){b.request={};this.readChildNodes(a,b.request)},GetCapabilitie [...]
+b){b.getcapabilities={formats:[]};this.readChildNodes(a,b.getcapabilities)},Format:function(a,b){OpenLayers.Util.isArray(b.formats)?b.formats.push(this.getChildValue(a)):b.format=this.getChildValue(a)},DCPType:function(a,b){this.readChildNodes(a,b)},HTTP:function(a,b){this.readChildNodes(a,b)},Get:function(a,b){b.get={};this.readChildNodes(a,b.get);b.href||(b.href=b.get.href)},Post:function(a,b){b.post={};this.readChildNodes(a,b.post);b.href||(b.href=b.get.href)},GetMap:function(a,b){b.g [...]
+this.readChildNodes(a,b.getmap)},GetFeatureInfo:function(a,b){b.getfeatureinfo={formats:[]};this.readChildNodes(a,b.getfeatureinfo)},Exception:function(a,b){b.exception={formats:[]};this.readChildNodes(a,b.exception)},Layer:function(a,b){var c,d;b.capability?(d=b.capability,c=b):d=b;var e=a.getAttributeNode("queryable"),f=e&&e.specified?a.getAttribute("queryable"):null,g=(e=a.getAttributeNode("cascaded"))&&e.specified?a.getAttribute("cascaded"):null,e=(e=a.getAttributeNode("opaque"))&&e. [...]
+a.getAttribute("opaque"):null,h=a.getAttribute("noSubsets"),k=a.getAttribute("fixedWidth"),l=a.getAttribute("fixedHeight"),m=c||{},n=OpenLayers.Util.extend;c={nestedLayers:[],styles:c?[].concat(c.styles):[],srs:c?n({},m.srs):{},metadataURLs:[],bbox:c?n({},m.bbox):{},llbbox:m.llbbox,dimensions:c?n({},m.dimensions):{},authorityURLs:c?n({},m.authorityURLs):{},identifiers:{},keywords:[],queryable:f&&""!==f?"1"===f||"true"===f:m.queryable||!1,cascaded:null!==g?parseInt(g):m.cascaded||0,opaque [...]
+e||"true"===e:m.opaque||!1,noSubsets:null!==h?"1"===h||"true"===h:m.noSubsets||!1,fixedWidth:null!=k?parseInt(k):m.fixedWidth||0,fixedHeight:null!=l?parseInt(l):m.fixedHeight||0,minScale:m.minScale,maxScale:m.maxScale,attribution:m.attribution};b.nestedLayers.push(c);c.capability=d;this.readChildNodes(a,c);delete c.capability;c.name&&(f=c.name.split(":"),g=d.request,e=g.getfeatureinfo,0<f.length&&(c.prefix=f[0]),d.layers.push(c),void 0===c.formats&&(c.formats=g.getmap.formats),void 0===c [...]
+e&&(c.infoFormats=e.formats))},Attribution:function(a,b){b.attribution={};this.readChildNodes(a,b.attribution)},LogoURL:function(a,b){b.logo={width:a.getAttribute("width"),height:a.getAttribute("height")};this.readChildNodes(a,b.logo)},Style:function(a,b){var c={};b.styles.push(c);this.readChildNodes(a,c)},LegendURL:function(a,b){var c={width:a.getAttribute("width"),height:a.getAttribute("height")};b.legend=c;this.readChildNodes(a,c)},MetadataURL:function(a,b){var c={type:a.getAttribute( [...]
+b.metadataURLs.push(c);this.readChildNodes(a,c)},DataURL:function(a,b){b.dataURL={};this.readChildNodes(a,b.dataURL)},FeatureListURL:function(a,b){b.featureListURL={};this.readChildNodes(a,b.featureListURL)},AuthorityURL:function(a,b){var c=a.getAttribute("name"),d={};this.readChildNodes(a,d);b.authorityURLs[c]=d.href},Identifier:function(a,b){var c=a.getAttribute("authority");b.identifiers[c]=this.getChildValue(a)},KeywordList:function(a,b){this.readChildNodes(a,b)},SRS:function(a,b){b. [...]
+!0}}},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1"});OpenLayers.Format.WMSCapabilities.v1_1=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1,{readers:{wms:OpenLayers.Util.applyDefaults({WMT_MS_Capabilities:function(a,b){this.readChildNodes(a,b)},Keyword:function(a,b){b.keywords&&b.keywords.push(this.getChildValue(a))},DescribeLayer:function(a,b){b.describelayer={formats:[]};this.readChildNodes(a,b.describelayer)},GetLegendGraphic:function(a,b){b.getlegendgraphic={formats:[]};th [...]
+{formats:[]};this.readChildNodes(a,b.getstyles)},PutStyles:function(a,b){b.putstyles={formats:[]};this.readChildNodes(a,b.putstyles)},UserDefinedSymbolization:function(a,b){var c={supportSLD:1==parseInt(a.getAttribute("SupportSLD")),userLayer:1==parseInt(a.getAttribute("UserLayer")),userStyle:1==parseInt(a.getAttribute("UserStyle")),remoteWFS:1==parseInt(a.getAttribute("RemoteWFS"))};b.userSymbols=c},LatLonBoundingBox:function(a,b){b.llbbox=[parseFloat(a.getAttribute("minx")),parseFloat( [...]
+parseFloat(a.getAttribute("maxx")),parseFloat(a.getAttribute("maxy"))]},BoundingBox:function(a,b){var c=OpenLayers.Format.WMSCapabilities.v1.prototype.readers.wms.BoundingBox.apply(this,[a,b]);c.srs=a.getAttribute("SRS");b.bbox[c.srs]=c},ScaleHint:function(a,b){var c=a.getAttribute("min"),d=a.getAttribute("max"),e=Math.pow(2,0.5),f=OpenLayers.INCHES_PER_UNIT.m;0!=c&&(b.maxScale=parseFloat((c/e*f*OpenLayers.DOTS_PER_INCH).toPrecision(13)));d!=Number.POSITIVE_INFINITY&&(b.minScale=parseFlo [...]
+OpenLayers.DOTS_PER_INCH).toPrecision(13)))},Dimension:function(a,b){var c={name:a.getAttribute("name").toLowerCase(),units:a.getAttribute("units"),unitsymbol:a.getAttribute("unitSymbol")};b.dimensions[c.name]=c},Extent:function(a,b){var c=a.getAttribute("name").toLowerCase();if(c in b.dimensions){c=b.dimensions[c];c.nearestVal="1"===a.getAttribute("nearestValue");c.multipleVal="1"===a.getAttribute("multipleValues");c.current="1"===a.getAttribute("current");c["default"]=a.getAttribute("d [...]
+"";var d=this.getChildValue(a);c.values=d.split(",")}}},OpenLayers.Format.WMSCapabilities.v1.prototype.readers.wms)},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1"});OpenLayers.Format.WMSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_1,{version:"1.1.0",readers:{wms:OpenLayers.Util.applyDefaults({SRS:function(a,b){for(var c=this.getChildValue(a).split(/ +/),d=0,e=c.length;d<e;d++)b.srs[c[d]]=!0}},OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers.wms) [...]
+srsName:this.srsName,schema:this.schema},this.formatOptions)));!a.geometryName&&1<parseFloat(this.format.version)&&this.setGeometryName(null)},destroy:function(){this.options&&!this.options.format&&this.format.destroy();this.format=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=OpenLayers.Util.extend({},a);OpenLayers.Util.applyDefaults(a,this.options||{});var b=new OpenLayers.Protocol.Response({requestTy [...]
+c=OpenLayers.Format.XML.prototype.write.apply(this.format,[this.format.writeNode("wfs:GetFeature",a)]);b.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,b,a),params:a.params,headers:a.headers,data:c});return b},setFeatureType:function(a){this.featureType=a;this.format.featureType=a},setGeometryName:function(a){this.geometryName=a;this.format.geometryName=a},handleRead:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.op [...]
+a.priv;200<=c.status&&300>c.status?(c=this.parseResponse(c,b.readOptions))&&!1!==c.success?(b.readOptions&&"object"==b.readOptions.output?OpenLayers.Util.extend(a,c):a.features=c,a.code=OpenLayers.Protocol.Response.SUCCESS):(a.code=OpenLayers.Protocol.Response.FAILURE,a.error=c):a.code=OpenLayers.Protocol.Response.FAILURE;b.callback.call(b.scope,a)}},parseResponse:function(a,b){var c=a.responseXML;c&&c.documentElement||(c=a.responseText);if(!c||0>=c.length)return null;c=null!==this.readF [...]
+this.format.read(c,b);if(!this.featureNS){var d=this.readFormat||this.format;this.featureNS=d.featureNS;d.autoConfig=!1;this.geometryName||this.setGeometryName(d.geometryName)}return c},commit:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.options);var c=new OpenLayers.Protocol.Response({requestType:"commit",reqFeatures:a});c.priv=OpenLayers.Request.POST({url:b.url,headers:b.headers,data:this.format.write(a,b),callback:this.createCallback(this.handleCom [...]
+return c},handleCommit:function(a,b){if(b.callback){var c=a.priv,d=c.responseXML;d&&d.documentElement||(d=c.responseText);c=this.format.read(d)||{};a.insertIds=c.insertIds||[];c.success?a.code=OpenLayers.Protocol.Response.SUCCESS:(a.code=OpenLayers.Protocol.Response.FAILURE,a.error=c);b.callback.call(b.scope,a)}},filterDelete:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.options);new OpenLayers.Protocol.Response({requestType:"commit"});var c=this.forma [...]
+{attributes:{service:"WFS",version:this.version}}),d=this.format.createElementNSPlus("wfs:Delete",{attributes:{typeName:(b.featureNS?this.featurePrefix+":":"")+b.featureType}});b.featureNS&&d.setAttribute("xmlns:"+this.featurePrefix,b.featureNS);var e=this.format.writeNode("ogc:Filter",a);d.appendChild(e);c.appendChild(d);c=OpenLayers.Format.XML.prototype.write.apply(this.format,[c]);return OpenLayers.Request.POST({url:this.url,callback:b.callback||function(){},data:c})},abort:function(a [...]
+CLASS_NAME:"OpenLayers.Protocol.WFS.v1"});OpenLayers.Handler.Feature=OpenLayers.Class(OpenLayers.Handler,{EVENTMAP:{click:{"in":"click",out:"clickout"},mousemove:{"in":"over",out:"out"},dblclick:{"in":"dblclick",out:null},mousedown:{"in":null,out:null},mouseup:{"in":null,out:null},touchstart:{"in":"click",out:"clickout"}},feature:null,lastFeature:null,down:null,up:null,clickTolerance:4,geometryTypes:null,stopClick:!0,stopDown:!0,stopUp:!1,initialize:function(a,b,c,d){OpenLayers.Handler.p [...]
+b},touchstart:function(a){this.startTouch();return OpenLayers.Event.isMultiTouch(a)?!0:this.mousedown(a)},touchmove:function(a){OpenLayers.Event.preventDefault(a)},mousedown:function(a){if(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))this.down=a.xy;return this.handle(a)?!this.stopDown:!0},mouseup:function(a){this.up=a.xy;return this.handle(a)?!this.stopUp:!0},click:function(a){return this.handle(a)?!this.stopClick:!0},mousemove:function(a){if(!this.callbacks.over&&! [...]
+this.handle(a);return!0},dblclick:function(a){return!this.handle(a)},geometryTypeMatches:function(a){return null==this.geometryTypes||-1<OpenLayers.Util.indexOf(this.geometryTypes,a.geometry.CLASS_NAME)},handle:function(a){this.feature&&!this.feature.layer&&(this.feature=null);var b=a.type,c=!1,d=!!this.feature,e="click"==b||"dblclick"==b||"touchstart"==b;(this.feature=this.layer.getFeatureFromEvent(a))&&!this.feature.layer&&(this.feature=null);this.lastFeature&&!this.lastFeature.layer&& [...]
+null);this.feature?("touchstart"===b&&OpenLayers.Event.preventDefault(a),a=this.feature!=this.lastFeature,this.geometryTypeMatches(this.feature)?(d&&a?(this.lastFeature&&this.triggerCallback(b,"out",[this.lastFeature]),this.triggerCallback(b,"in",[this.feature])):d&&!e||this.triggerCallback(b,"in",[this.feature]),this.lastFeature=this.feature,c=!0):(this.lastFeature&&(d&&a||e)&&this.triggerCallback(b,"out",[this.lastFeature]),this.feature=null)):this.lastFeature&&(d||e)&&this.triggerCall [...]
+[this.lastFeature]);return c},triggerCallback:function(a,b,c){if(b=this.EVENTMAP[a][b])"click"==a&&this.up&&this.down?(Math.sqrt(Math.pow(this.up.x-this.down.x,2)+Math.pow(this.up.y-this.down.y,2))<=this.clickTolerance&&this.callback(b,c),this.up=this.down=null):this.callback(b,c)},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.moveLayerToTop(),this.map.events.on({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this [...]
+return a},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.moveLayerBack(),this.up=this.down=this.lastFeature=this.feature=null,this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),a=!0);return a},handleMapEvents:function(a){"removelayer"!=a.type&&"order"!=a.property||this.moveLayerToTop()},moveLayerToTop:function(){var a=Math.max(this.map.Z_INDEX_BASE.Feature-1,this.layer.getZIndex())+1;this [...]
+moveLayerBack:function(){var a=this.layer.getZIndex()-1;a>=this.map.Z_INDEX_BASE.Feature?this.layer.setZIndex(a):this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer))},CLASS_NAME:"OpenLayers.Handler.Feature"});OpenLayers.Layer.Vector.RootContainer=OpenLayers.Class(OpenLayers.Layer.Vector,{displayInLayerSwitcher:!1,layers:null,display:function(){},getFeatureFromEvent:function(a){for(var b=this.layers,c,d=0;d<b.length;d++)if(c=b[d].getFeatureFromEvent(a))return c},setMap:f [...]
+this.resetRoots();OpenLayers.Layer.Vector.prototype.removeMap.apply(this,arguments)},collectRoots:function(){for(var a,b=0;b<this.map.layers.length;++b)a=this.map.layers[b],-1!=OpenLayers.Util.indexOf(this.layers,a)&&a.renderer.moveRoot(this.renderer)},resetRoots:function(){for(var a,b=0;b<this.layers.length;++b)a=this.layers[b],this.renderer&&a.renderer.getRenderLayerId()==this.id&&this.renderer.moveRoot(a.renderer)},handleChangeLayer:function(a){var b=a.layer;"order"==a.property&&-1!=O [...]
+b)&&(this.resetRoots(),this.collectRoots())},CLASS_NAME:"OpenLayers.Layer.Vector.RootContainer"});OpenLayers.Control.SelectFeature=OpenLayers.Class(OpenLayers.Control,{multipleKey:null,toggleKey:null,multiple:!1,clickout:!0,toggle:!1,hover:!1,highlightOnly:!1,box:!1,onBeforeSelect:function(){},onSelect:function(){},onUnselect:function(){},scope:null,geometryTypes:null,layer:null,layers:null,callbacks:null,selectStyle:null,renderIntent:"select",handlers:null,initialize:function(a,b){OpenL [...]
+{click:this.clickFeature,clickout:this.clickoutFeature};this.hover&&(c.over=this.overFeature,c.out=this.outFeature);this.callbacks=OpenLayers.Util.extend(c,this.callbacks);this.handlers={feature:new OpenLayers.Handler.Feature(this,this.layer,this.callbacks,{geometryTypes:this.geometryTypes})};this.box&&(this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},{boxDivClassName:"olHandlerBoxSelectFeature"}))},initLayer:function(a){OpenLayers.Util.isArray(a)?(this.layers=a,th [...]
+new OpenLayers.Layer.Vector.RootContainer(this.id+"_container",{layers:a})):this.layer=a},destroy:function(){this.active&&this.layers&&this.map.removeLayer(this.layer);OpenLayers.Control.prototype.destroy.apply(this,arguments);this.layers&&this.layer.destroy()},activate:function(){this.active||(this.layers&&this.map.addLayer(this.layer),this.handlers.feature.activate(),this.box&&this.handlers.box&&this.handlers.box.activate());return OpenLayers.Control.prototype.activate.apply(this,argum [...]
+(this.handlers.feature.deactivate(),this.handlers.box&&this.handlers.box.deactivate(),this.layers&&this.map.removeLayer(this.layer));return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},unselectAll:function(a){var b=this.layers||[this.layer],c,d,e,f;for(e=0;e<b.length;++e)if(c=b[e],f=0,null!=c.selectedFeatures)for(;c.selectedFeatures.length>f;)d=c.selectedFeatures[f],a&&a.except==d?++f:this.unselect(d)},clickFeature:function(a){this.hover||(-1<OpenLayers.Util.indexOf(a.l [...]
+a)?this.toggleSelect()?this.unselect(a):this.multipleSelect()||this.unselectAll({except:a}):(this.multipleSelect()||this.unselectAll({except:a}),this.select(a)))},multipleSelect:function(){return this.multiple||this.handlers.feature.evt&&this.handlers.feature.evt[this.multipleKey]},toggleSelect:function(){return this.toggle||this.handlers.feature.evt&&this.handlers.feature.evt[this.toggleKey]},clickoutFeature:function(a){!this.hover&&this.clickout&&this.unselectAll()},overFeature:functio [...]
+a.layer;this.hover&&(this.highlightOnly?this.highlight(a):-1==OpenLayers.Util.indexOf(b.selectedFeatures,a)&&this.select(a))},outFeature:function(a){if(this.hover)if(this.highlightOnly){if(a._lastHighlighter==this.id)if(a._prevHighlighter&&a._prevHighlighter!=this.id){delete a._lastHighlighter;var b=this.map.getControl(a._prevHighlighter);b&&b.highlight(a)}else this.unhighlight(a)}else this.unselect(a)},highlight:function(a){var b=a.layer;!1!==this.events.triggerEvent("beforefeaturehighl [...]
+(a._prevHighlighter=a._lastHighlighter,a._lastHighlighter=this.id,b.drawFeature(a,this.selectStyle||this.renderIntent),this.events.triggerEvent("featurehighlighted",{feature:a}))},unhighlight:function(a){var b=a.layer;void 0==a._prevHighlighter?delete a._lastHighlighter:(a._prevHighlighter!=this.id&&(a._lastHighlighter=a._prevHighlighter),delete a._prevHighlighter);b.drawFeature(a,a.style||a.layer.style||"default");this.events.triggerEvent("featureunhighlighted",{feature:a})},select:func [...]
+this.onBeforeSelect.call(this.scope,a),c=a.layer;!1!==b&&(b=c.events.triggerEvent("beforefeatureselected",{feature:a}),!1!==b&&(c.selectedFeatures.push(a),this.highlight(a),this.handlers.feature.lastFeature||(this.handlers.feature.lastFeature=c.selectedFeatures[0]),c.events.triggerEvent("featureselected",{feature:a}),this.onSelect.call(this.scope,a)))},unselect:function(a){var b=a.layer;this.unhighlight(a);OpenLayers.Util.removeItem(b.selectedFeatures,a);b.events.triggerEvent("featureuns [...]
+{feature:a});this.onUnselect.call(this.scope,a)},selectBox:function(a){if(a instanceof OpenLayers.Bounds){var b=this.map.getLonLatFromPixel({x:a.left,y:a.bottom});a=this.map.getLonLatFromPixel({x:a.right,y:a.top});b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat);this.multipleSelect()||this.unselectAll();a=this.multiple;this.multiple=!0;var c=this.layers||[this.layer];this.events.triggerEvent("boxselectionstart",{layers:c});for(var d,e=0;e<c.length;++e){d=c[e];for(var f=0,g=d.features.len [...]
+d.features[f];h.getVisibility()&&(null==this.geometryTypes||-1<OpenLayers.Util.indexOf(this.geometryTypes,h.geometry.CLASS_NAME))&&b.toGeometry().intersects(h.geometry)&&-1==OpenLayers.Util.indexOf(d.selectedFeatures,h)&&this.select(h)}}this.multiple=a;this.events.triggerEvent("boxselectionend",{layers:c})}},setMap:function(a){this.handlers.feature.setMap(a);this.box&&this.handlers.box.setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},setLayer:function(a){var b=this.ac [...]
+this.deactivate();this.layers&&(this.layer.destroy(),this.layers=null);this.initLayer(a);this.handlers.feature.layer=this.layer;b&&this.activate()},CLASS_NAME:"OpenLayers.Control.SelectFeature"});OpenLayers.Handler.Point=OpenLayers.Class(OpenLayers.Handler,{point:null,layer:null,multi:!1,citeCompliant:!1,mouseDown:!1,stoppedDown:null,lastDown:null,lastUp:null,persist:!1,stopDown:!1,stopUp:!1,layerOptions:null,pixelTolerance:5,lastTouchPx:null,initialize:function(a,b,c){c&&c.layerOptions& [...]
+arguments))return!1;var a=OpenLayers.Util.extend({displayInLayerSwitcher:!1,calculateInRange:OpenLayers.Function.True,wrapDateLine:this.citeCompliant},this.layerOptions);this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,a);this.map.addLayer(this.layer);return!0},createFeature:function(a){a=this.layer.getLonLatFromViewPortPx(a);a=new OpenLayers.Geometry.Point(a.lon,a.lat);this.point=new OpenLayers.Feature.Vector(a);this.callback("create",[this.point.geometry,this.point]);this.point.g [...]
+this.layer.addFeatures([this.point],{silent:!0})},deactivate:function(){if(!OpenLayers.Handler.prototype.deactivate.apply(this,arguments))return!1;this.cancel();null!=this.layer.map&&(this.destroyFeature(!0),this.layer.destroy(!1));this.layer=null;return!0},destroyFeature:function(a){!this.layer||!a&&this.persist||this.layer.destroyFeatures();this.point=null},destroyPersistedFeature:function(){var a=this.layer;a&&1<a.features.length&&this.layer.features[0].destroy()},finalize:function(a) [...]
+!1;this.lastTouchPx=this.lastUp=this.lastDown=null;this.callback(a?"cancel":"done",[this.geometryClone()]);this.destroyFeature(a)},cancel:function(){this.finalize(!0)},click:function(a){OpenLayers.Event.stop(a);return!1},dblclick:function(a){OpenLayers.Event.stop(a);return!1},modifyFeature:function(a){this.point||this.createFeature(a);a=this.layer.getLonLatFromViewPortPx(a);this.point.geometry.x=a.lon;this.point.geometry.y=a.lat;this.callback("modify",[this.point.geometry,this.point,!1]) [...]
+this.drawFeature()},drawFeature:function(){this.layer.drawFeature(this.point,this.style)},getGeometry:function(){var a=this.point&&this.point.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiPoint([a]));return a},geometryClone:function(){var a=this.getGeometry();return a&&a.clone()},mousedown:function(a){return this.down(a)},touchstart:function(a){this.startTouch();this.lastTouchPx=a.xy;return this.down(a)},mousemove:function(a){return this.move(a)},touchmove:function(a){this.last [...]
+return this.move(a)},mouseup:function(a){return this.up(a)},touchend:function(a){a.xy=this.lastTouchPx;return this.up(a)},down:function(a){this.mouseDown=!0;this.lastDown=a.xy;this.touch||this.modifyFeature(a.xy);this.stoppedDown=this.stopDown;return!this.stopDown},move:function(a){this.touch||this.mouseDown&&!this.stoppedDown||this.modifyFeature(a.xy);return!0},up:function(a){this.mouseDown=!1;this.stoppedDown=this.stopDown;if(!this.checkModifiers(a)||this.lastUp&&this.lastUp.equals(a.x [...]
+!this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance))return!0;this.touch&&this.modifyFeature(a.xy);this.persist&&this.destroyPersistedFeature();this.lastUp=a.xy;this.finalize();return!this.stopUp},mouseout:function(a){OpenLayers.Util.mouseLeft(a,this.map.viewPortDiv)&&(this.stoppedDown=this.stopDown,this.mouseDown=!1)},passesTolerance:function(a,b,c){var d=!0;null!=c&&a&&b&&a.distanceTo(b)>c&&(d=!1);return d},CLASS_NAME:"OpenLayers.Handler.Point"});OpenLayers.Handler.Path=OpenLa [...]
+this.point.geometry.clearBounds();this.layer.addFeatures([this.line,this.point],{silent:!0})},destroyFeature:function(a){OpenLayers.Handler.Point.prototype.destroyFeature.call(this,a);this.line=null},destroyPersistedFeature:function(){var a=this.layer;a&&2<a.features.length&&this.layer.features[0].destroy()},removePoint:function(){this.point&&this.layer.removeFeatures([this.point])},addPoint:function(a){this.layer.removeFeatures([this.point]);a=this.layer.getLonLatFromViewPortPx(a);this. [...]
+a.lat));this.line.geometry.addComponent(this.point.geometry,this.line.geometry.components.length);this.layer.addFeatures([this.point]);this.callback("point",[this.point.geometry,this.getGeometry()]);this.callback("modify",[this.point.geometry,this.getSketch()]);this.drawFeature();delete this.redoStack},insertXY:function(a,b){this.line.geometry.addComponent(new OpenLayers.Geometry.Point(a,b),this.getCurrentPointIndex());this.drawFeature();delete this.redoStack},insertDeltaXY:function(a,b) [...]
+1,c=this.line.geometry.components[c];!c||(isNaN(c.x)||isNaN(c.y))||this.insertXY(c.x+a,c.y+b)},insertDirectionLength:function(a,b){a*=Math.PI/180;var c=b*Math.cos(a),d=b*Math.sin(a);this.insertDeltaXY(c,d)},insertDeflectionLength:function(a,b){var c=this.getCurrentPointIndex()-1;if(0<c){var d=this.line.geometry.components[c],c=this.line.geometry.components[c-1],d=Math.atan2(d.y-c.y,d.x-c.x);this.insertDirectionLength(180*d/Math.PI+a,b)}},getCurrentPointIndex:function(){return this.line.g [...]
+1},undo:function(){var a=this.line.geometry,b=a.components,c=this.getCurrentPointIndex()-1,d=b[c],e=a.removeComponent(d);e&&(this.touch&&0<c&&(b=a.components,a=b[c-1],c=this.getCurrentPointIndex(),b=b[c],b.x=a.x,b.y=a.y),this.redoStack||(this.redoStack=[]),this.redoStack.push(d),this.drawFeature());return e},redo:function(){var a=this.redoStack&&this.redoStack.pop();a&&(this.line.geometry.addComponent(a,this.getCurrentPointIndex()),this.drawFeature());return!!a},freehandMode:function(a){ [...]
+a[this.freehandToggle]?!this.freehand:this.freehand},modifyFeature:function(a,b){this.line||this.createFeature(a);var c=this.layer.getLonLatFromViewPortPx(a);this.point.geometry.x=c.lon;this.point.geometry.y=c.lat;this.callback("modify",[this.point.geometry,this.getSketch(),b]);this.point.geometry.clearBounds();this.drawFeature()},drawFeature:function(){this.layer.drawFeature(this.line,this.style);this.layer.drawFeature(this.point,this.style)},getSketch:function(){return this.line},getGe [...]
+this.line&&this.line.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiLineString([a]));return a},touchstart:function(a){if(this.timerId&&this.passesTolerance(this.lastTouchPx,a.xy,this.doubleTouchTolerance))return this.finishGeometry(),window.clearTimeout(this.timerId),this.timerId=null,!1;this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null);this.timerId=window.setTimeout(OpenLayers.Function.bind(function(){this.timerId=null},this),300);return OpenLayers.Handler.Poi [...]
+a)},down:function(a){var b=this.stopDown;this.freehandMode(a)&&(b=!0,this.touch&&(this.modifyFeature(a.xy,!!this.lastUp),OpenLayers.Event.stop(a)));this.touch||this.lastDown&&this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance)||this.modifyFeature(a.xy,!!this.lastUp);this.mouseDown=!0;this.lastDown=a.xy;this.stoppedDown=b;return!b},move:function(a){if(this.stoppedDown&&this.freehandMode(a))return this.persist&&this.destroyPersistedFeature(),this.maxVertices&&this.line&&this.line. [...]
+this.maxVertices?(this.removePoint(),this.finalize()):this.addPoint(a.xy),!1;this.touch||this.mouseDown&&!this.stoppedDown||this.modifyFeature(a.xy,!!this.lastUp);return!0},up:function(a){!this.mouseDown||this.lastUp&&this.lastUp.equals(a.xy)||(this.stoppedDown&&this.freehandMode(a)?(this.persist&&this.destroyPersistedFeature(),this.removePoint(),this.finalize()):this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance)&&(this.touch&&this.modifyFeature(a.xy),null==this.lastUp&&this.pe [...]
+this.addPoint(a.xy),this.lastUp=a.xy,this.line.geometry.components.length===this.maxVertices+1&&this.finishGeometry()));this.stoppedDown=this.stopDown;this.mouseDown=!1;return!this.stopUp},finishGeometry:function(){this.line.geometry.removeComponent(this.line.geometry.components[this.line.geometry.components.length-1]);this.removePoint();this.finalize()},dblclick:function(a){this.freehandMode(a)||this.finishGeometry();return!1},CLASS_NAME:"OpenLayers.Handler.Path"});OpenLayers.Spherical= [...]
+OpenLayers.Spherical.computeHeading=function(a,b){var c=Math.sin(Math.PI*(a.lon-b.lon)/180)*Math.cos(Math.PI*b.lat/180),d=Math.cos(Math.PI*a.lat/180)*Math.sin(Math.PI*b.lat/180)-Math.sin(Math.PI*a.lat/180)*Math.cos(Math.PI*b.lat/180)*Math.cos(Math.PI*(a.lon-b.lon)/180);return 180*Math.atan2(c,d)/Math.PI};OpenLayers.Control.CacheWrite=OpenLayers.Class(OpenLayers.Control,{layers:null,imageFormat:"image/png",quotaRegEx:/quota/i,setMap:function(a){OpenLayers.Control.prototype.setMap.apply(th [...]
+tileloaded:this.onTileLoaded,scope:this})},makeSameOrigin:function(a){if(this.active&&(a=a.tile,a instanceof OpenLayers.Tile.Image&&!a.crossOriginKeyword&&"data:"!==a.url.substr(0,5))){var b=OpenLayers.Request.makeSameOrigin(a.url,OpenLayers.ProxyHost);OpenLayers.Control.CacheWrite.urlMap[b]=a.url;a.url=b}},onTileLoaded:function(a){this.active&&(!a.aborted&&a.tile instanceof OpenLayers.Tile.Image&&"data:"!==a.tile.url.substr(0,5))&&(this.cache({tile:a.tile}),delete OpenLayers.Control.Cac [...]
+cache:function(a){if(window.localStorage){a=a.tile;try{var b=a.getCanvasContext();b&&window.localStorage.setItem("olCache_"+(OpenLayers.Control.CacheWrite.urlMap[a.url]||a.url),b.canvas.toDataURL(this.imageFormat))}catch(c){(b=c.name||c.message)&&this.quotaRegEx.test(b)?this.events.triggerEvent("cachefull",{tile:a}):OpenLayers.Console.error(c.toString())}}},destroy:function(){if(this.layers||this.map){var a,b=this.layers||this.map.layers;for(a=b.length-1;0<=a;--a)this.removeLayer({layer: [...]
+this.map.events.un({addlayer:this.addLayer,removeLayer:this.removeLayer,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments)},CLASS_NAME:"OpenLayers.Control.CacheWrite"});OpenLayers.Control.CacheWrite.clearCache=function(){if(window.localStorage){var a,b;for(a=window.localStorage.length-1;0<=a;--a)b=window.localStorage.key(a),"olCache_"===b.substr(0,8)&&window.localStorage.removeItem(b)}};OpenLayers.Control.CacheWrite.urlMap={};OpenLayers.Format.Context=OpenLayers.Clas [...]
+maxExtent:a.maxExtent,metadata:OpenLayers.Util.applyDefaults(a.metadata,{styles:a.styles,formats:a.formats,"abstract":a["abstract"],dataURL:a.dataURL}),numZoomLevels:a.numZoomLevels,units:a.units,isBaseLayer:a.isBaseLayer,opacity:a.opacity,displayInLayerSwitcher:a.displayInLayerSwitcher,singleTile:a.singleTile,tileSize:a.tileSize?new OpenLayers.Size(a.tileSize.width,a.tileSize.height):void 0,minScale:a.minScale||a.maxScaleDenominator,maxScale:a.maxScale||a.minScaleDenominator,srs:a.srs,d [...]
+metadataURL:a.metadataURL};this.layerOptions&&OpenLayers.Util.applyDefaults(d,this.layerOptions);var e={layers:a.name,transparent:a.transparent,version:a.version};if(a.formats&&0<a.formats.length)for(e.format=a.formats[0].value,b=0,c=a.formats.length;b<c;b++){var f=a.formats[b];if(!0==f.current){e.format=f.value;break}}if(a.styles&&0<a.styles.length)for(b=0,c=a.styles.length;b<c;b++)if(f=a.styles[b],!0==f.current){f.href?e.sld=f.href:f.body?e.sld_body=f.body:e.styles=f.name;break}this.la [...]
+OpenLayers.Util.applyDefaults(e,this.layerParams);b=null;c=a.service;c==OpenLayers.Format.Context.serviceTypes.WFS?(d.strategies=[new OpenLayers.Strategy.BBOX],d.protocol=new OpenLayers.Protocol.WFS({url:a.url,featurePrefix:a.name.split(":")[0],featureType:a.name.split(":").pop()}),b=new OpenLayers.Layer.Vector(a.title||a.name,d)):c==OpenLayers.Format.Context.serviceTypes.KML?(d.strategies=[new OpenLayers.Strategy.Fixed],d.protocol=new OpenLayers.Protocol.HTTP({url:a.url,format:new OpenL [...]
+b=new OpenLayers.Layer.Vector(a.title||a.name,d)):c==OpenLayers.Format.Context.serviceTypes.GML?(d.strategies=[new OpenLayers.Strategy.Fixed],d.protocol=new OpenLayers.Protocol.HTTP({url:a.url,format:new OpenLayers.Format.GML}),b=new OpenLayers.Layer.Vector(a.title||a.name,d)):a.features?(b=new OpenLayers.Layer.Vector(a.title||a.name,d),b.addFeatures(a.features)):!0!==a.categoryLayer&&(b=new OpenLayers.Layer.WMS(a.title||a.name,a.url,e,d));return b},getLayersFromContext:function(a){for(v [...]
+0,d=a.length;c<d;c++){var e=this.getLayerFromContext(a[c]);null!==e&&b.push(e)}return b},contextToMap:function(a,b){b=OpenLayers.Util.applyDefaults({maxExtent:a.maxExtent,projection:a.projection,units:a.units},b);b.maxExtent&&(b.maxResolution=b.maxExtent.getWidth()/OpenLayers.Map.TILE_WIDTH);b.metadata={contactInformation:a.contactInformation,"abstract":a["abstract"],keywords:a.keywords,logo:a.logo,descriptionURL:a.descriptionURL};var c=new OpenLayers.Map(b);c.addLayers(this.getLayersFro [...]
+c.setCenter(a.bounds.getCenterLonLat(),c.getZoomForExtent(a.bounds,!0));return c},mergeContextToMap:function(a,b){b.addLayers(this.getLayersFromContext(a.layersContext));return b},write:function(a,b){a=this.toContext(a);return OpenLayers.Format.XML.VersionedOGC.prototype.write.apply(this,arguments)},CLASS_NAME:"OpenLayers.Format.Context"});
+OpenLayers.Format.Context.serviceTypes={WMS:"urn:ogc:serviceType:WMS",WFS:"urn:ogc:serviceType:WFS",WCS:"urn:ogc:serviceType:WCS",GML:"urn:ogc:serviceType:GML",SLD:"urn:ogc:serviceType:SLD",FES:"urn:ogc:serviceType:FES",KML:"urn:ogc:serviceType:KML"};OpenLayers.Format.WMC=OpenLayers.Class(OpenLayers.Format.Context,{defaultVersion:"1.1.0",layerToContext:function(a){var b=this.getParser(),c={queryable:a.queryable,visibility:a.visibility,name:a.params.LAYERS,title:a.name,"abstract":a.metada [...]
+a.opacity,displayInLayerSwitcher:a.displayInLayerSwitcher,singleTile:a.singleTile,tileSize:a.singleTile||!a.tileSize?void 0:{width:a.tileSize.w,height:a.tileSize.h},minScale:a.options.resolutions||a.options.scales||a.options.maxResolution||a.options.minScale?a.minScale:void 0,maxScale:a.options.resolutions||a.options.scales||a.options.minResolution||a.options.maxScale?a.maxScale:void 0,formats:[],styles:[],srs:a.srs,dimensions:a.dimensions};a.metadata.servertitle&&(c.server.title=a.metad [...]
+if(a.metadata.formats&&0<a.metadata.formats.length)for(var d=0,e=a.metadata.formats.length;d<e;d++){var f=a.metadata.formats[d];c.formats.push({value:f.value,current:f.value==a.params.FORMAT})}else c.formats.push({value:a.params.FORMAT,current:!0});if(a.metadata.styles&&0<a.metadata.styles.length)for(d=0,e=a.metadata.styles.length;d<e;d++)b=a.metadata.styles[d],b.current=b.href==a.params.SLD||b.body==a.params.SLD_BODY||b.name==a.params.STYLES?!0:!1,c.styles.push(b);else c.styles.push({hr [...]
+body:a.params.SLD_BODY,name:a.params.STYLES||b.defaultStyleName,title:b.defaultStyleTitle,current:!0});return c},toContext:function(a){var b={},c=a.layers;if("OpenLayers.Map"==a.CLASS_NAME){var d=a.metadata||{};b.size=a.getSize();b.bounds=a.getExtent();b.projection=a.projection;b.title=a.title;b.keywords=d.keywords;b["abstract"]=d["abstract"];b.logo=d.logo;b.descriptionURL=d.descriptionURL;b.contactInformation=d.contactInformation;b.maxExtent=a.maxExtent}else OpenLayers.Util.applyDefault [...]
+b.layers&&delete b.layers;void 0==b.layersContext&&(b.layersContext=[]);if(void 0!=c&&OpenLayers.Util.isArray(c))for(a=0,d=c.length;a<d;a++){var e=c[a];e instanceof OpenLayers.Layer.WMS&&b.layersContext.push(this.layerToContext(e))}return b},CLASS_NAME:"OpenLayers.Format.WMC"});OpenLayers.Format.WMC.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ol:"http://openlayers.org/context",wmc:"http://www.opengis.net/context",sld:"http://www.opengis.net/sld",xlink:"http://www.w3.org/1999/x [...]
+defaultStyleTitle:"Default",initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a=a.documentElement;this.rootPrefix=a.prefix;var b={version:a.getAttribute("version")};this.runChildNodes(b,a);return b},runChildNodes:function(a,b){for(var c=b.childNodes,d,e,f,g=0,h=c.length;g<h;++g)d=c[g],1==d.nodeType&&(e=this.getNamespacePrefix(d.namespaceURI),f=d.nodeName.split(": [...]
+(e=this["read_"+e+"_"+f])&&e.apply(this,[a,d]))},read_wmc_General:function(a,b){this.runChildNodes(a,b)},read_wmc_BoundingBox:function(a,b){a.projection=b.getAttribute("SRS");a.bounds=new OpenLayers.Bounds(b.getAttribute("minx"),b.getAttribute("miny"),b.getAttribute("maxx"),b.getAttribute("maxy"))},read_wmc_LayerList:function(a,b){a.layersContext=[];this.runChildNodes(a,b)},read_wmc_Layer:function(a,b){var c={visibility:"1"!=b.getAttribute("hidden"),queryable:"1"==b.getAttribute("queryab [...]
+styles:[],metadata:{}};this.runChildNodes(c,b);a.layersContext.push(c)},read_wmc_Extension:function(a,b){this.runChildNodes(a,b)},read_ol_units:function(a,b){a.units=this.getChildValue(b)},read_ol_maxExtent:function(a,b){var c=new OpenLayers.Bounds(b.getAttribute("minx"),b.getAttribute("miny"),b.getAttribute("maxx"),b.getAttribute("maxy"));a.maxExtent=c},read_ol_transparent:function(a,b){a.transparent=this.getChildValue(b)},read_ol_numZoomLevels:function(a,b){a.numZoomLevels=parseInt(thi [...]
+read_ol_opacity:function(a,b){a.opacity=parseFloat(this.getChildValue(b))},read_ol_singleTile:function(a,b){a.singleTile="true"==this.getChildValue(b)},read_ol_tileSize:function(a,b){var c={width:b.getAttribute("width"),height:b.getAttribute("height")};a.tileSize=c},read_ol_isBaseLayer:function(a,b){a.isBaseLayer="true"==this.getChildValue(b)},read_ol_displayInLayerSwitcher:function(a,b){a.displayInLayerSwitcher="true"==this.getChildValue(b)},read_wmc_Server:function(a,b){a.version=b.get [...]
+a.url=this.getOnlineResource_href(b);a.metadata.servertitle=b.getAttribute("title")},read_wmc_FormatList:function(a,b){this.runChildNodes(a,b)},read_wmc_Format:function(a,b){var c={value:this.getChildValue(b)};"1"==b.getAttribute("current")&&(c.current=!0);a.formats.push(c)},read_wmc_StyleList:function(a,b){this.runChildNodes(a,b)},read_wmc_Style:function(a,b){var c={};this.runChildNodes(c,b);"1"==b.getAttribute("current")&&(c.current=!0);a.styles.push(c)},read_wmc_SLD:function(a,b){this [...]
+b)},read_sld_StyledLayerDescriptor:function(a,b){var c=OpenLayers.Format.XML.prototype.write.apply(this,[b]);a.body=c},read_sld_FeatureTypeStyle:function(a,b){var c=OpenLayers.Format.XML.prototype.write.apply(this,[b]);a.body=c},read_wmc_OnlineResource:function(a,b){a.href=this.getAttributeNS(b,this.namespaces.xlink,"href")},read_wmc_Name:function(a,b){var c=this.getChildValue(b);c&&(a.name=c)},read_wmc_Title:function(a,b){var c=this.getChildValue(b);c&&(a.title=c)},read_wmc_MetadataURL: [...]
+b){a.metadataURL=this.getOnlineResource_href(b)},read_wmc_KeywordList:function(a,b){a.keywords=[];this.runChildNodes(a.keywords,b)},read_wmc_Keyword:function(a,b){a.push(this.getChildValue(b))},read_wmc_Abstract:function(a,b){var c=this.getChildValue(b);c&&(a["abstract"]=c)},read_wmc_LogoURL:function(a,b){a.logo={width:b.getAttribute("width"),height:b.getAttribute("height"),format:b.getAttribute("format"),href:this.getOnlineResource_href(b)}},read_wmc_DescriptionURL:function(a,b){a.descr [...]
+this.getOnlineResource_href(b)},read_wmc_ContactInformation:function(a,b){var c={};this.runChildNodes(c,b);a.contactInformation=c},read_wmc_ContactPersonPrimary:function(a,b){var c={};this.runChildNodes(c,b);a.personPrimary=c},read_wmc_ContactPerson:function(a,b){var c=this.getChildValue(b);c&&(a.person=c)},read_wmc_ContactOrganization:function(a,b){var c=this.getChildValue(b);c&&(a.organization=c)},read_wmc_ContactPosition:function(a,b){var c=this.getChildValue(b);c&&(a.position=c)},rea [...]
+b){var c={};this.runChildNodes(c,b);a.contactAddress=c},read_wmc_AddressType:function(a,b){var c=this.getChildValue(b);c&&(a.type=c)},read_wmc_Address:function(a,b){var c=this.getChildValue(b);c&&(a.address=c)},read_wmc_City:function(a,b){var c=this.getChildValue(b);c&&(a.city=c)},read_wmc_StateOrProvince:function(a,b){var c=this.getChildValue(b);c&&(a.stateOrProvince=c)},read_wmc_PostCode:function(a,b){var c=this.getChildValue(b);c&&(a.postcode=c)},read_wmc_Country:function(a,b){var c=t [...]
+c&&(a.country=c)},read_wmc_ContactVoiceTelephone:function(a,b){var c=this.getChildValue(b);c&&(a.phone=c)},read_wmc_ContactFacsimileTelephone:function(a,b){var c=this.getChildValue(b);c&&(a.fax=c)},read_wmc_ContactElectronicMailAddress:function(a,b){var c=this.getChildValue(b);c&&(a.email=c)},read_wmc_DataURL:function(a,b){a.dataURL=this.getOnlineResource_href(b)},read_wmc_LegendURL:function(a,b){var c={width:b.getAttribute("width"),height:b.getAttribute("height"),format:b.getAttribute(" [...]
+href:this.getOnlineResource_href(b)};a.legend=c},read_wmc_DimensionList:function(a,b){a.dimensions={};this.runChildNodes(a.dimensions,b)},read_wmc_Dimension:function(a,b){var c={name:b.getAttribute("name").toLowerCase(),units:b.getAttribute("units")||"",unitSymbol:b.getAttribute("unitSymbol")||"",userValue:b.getAttribute("userValue")||"",nearestValue:"1"===b.getAttribute("nearestValue"),multipleValues:"1"===b.getAttribute("multipleValues"),current:"1"===b.getAttribute("current"),"default [...]
+""},d=this.getChildValue(b);c.values=d.split(",");a[c.name]=c},write:function(a,b){var c=this.createElementDefaultNS("ViewContext");this.setAttributes(c,{version:this.VERSION,id:b&&"string"==typeof b.id?b.id:OpenLayers.Util.createUniqueID("OpenLayers_Context_")});this.setAttributeNS(c,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);c.appendChild(this.write_wmc_General(a));c.appendChild(this.write_wmc_LayerList(a));return OpenLayers.Format.XML.prototype.write.apply(this,[c]) [...]
+b,c){a=this.createElementNS(this.namespaces[this.defaultPrefix],a);b&&a.appendChild(this.createTextNode(b));c&&this.setAttributes(a,c);return a},setAttributes:function(a,b){var c,d;for(d in b)c=b[d].toString(),c.match(/[A-Z]/)?this.setAttributeNS(a,null,d,c):a.setAttribute(d,c)},write_wmc_General:function(a){var b=this.createElementDefaultNS("General");a.size&&b.appendChild(this.createElementDefaultNS("Window",null,{width:a.size.w,height:a.size.h}));var c=a.bounds;b.appendChild(this.crea [...]
+null,{minx:c.left.toPrecision(18),miny:c.bottom.toPrecision(18),maxx:c.right.toPrecision(18),maxy:c.top.toPrecision(18),SRS:a.projection}));b.appendChild(this.createElementDefaultNS("Title",a.title));a.keywords&&b.appendChild(this.write_wmc_KeywordList(a.keywords));a["abstract"]&&b.appendChild(this.createElementDefaultNS("Abstract",a["abstract"]));a.logo&&b.appendChild(this.write_wmc_URLType("LogoURL",a.logo.href,a.logo));a.descriptionURL&&b.appendChild(this.write_wmc_URLType("Descriptio [...]
+a.contactInformation&&b.appendChild(this.write_wmc_ContactInformation(a.contactInformation));b.appendChild(this.write_ol_MapExtension(a));return b},write_wmc_KeywordList:function(a){for(var b=this.createElementDefaultNS("KeywordList"),c=0,d=a.length;c<d;c++)b.appendChild(this.createElementDefaultNS("Keyword",a[c]));return b},write_wmc_ContactInformation:function(a){var b=this.createElementDefaultNS("ContactInformation");a.personPrimary&&b.appendChild(this.write_wmc_ContactPersonPrimary(a [...]
+a.position&&b.appendChild(this.createElementDefaultNS("ContactPosition",a.position));a.contactAddress&&b.appendChild(this.write_wmc_ContactAddress(a.contactAddress));a.phone&&b.appendChild(this.createElementDefaultNS("ContactVoiceTelephone",a.phone));a.fax&&b.appendChild(this.createElementDefaultNS("ContactFacsimileTelephone",a.fax));a.email&&b.appendChild(this.createElementDefaultNS("ContactElectronicMailAddress",a.email));return b},write_wmc_ContactPersonPrimary:function(a){var b=this. [...]
+a.person&&b.appendChild(this.createElementDefaultNS("ContactPerson",a.person));a.organization&&b.appendChild(this.createElementDefaultNS("ContactOrganization",a.organization));return b},write_wmc_ContactAddress:function(a){var b=this.createElementDefaultNS("ContactAddress");a.type&&b.appendChild(this.createElementDefaultNS("AddressType",a.type));a.address&&b.appendChild(this.createElementDefaultNS("Address",a.address));a.city&&b.appendChild(this.createElementDefaultNS("City",a.city));a.s [...]
+b.appendChild(this.createElementDefaultNS("StateOrProvince",a.stateOrProvince));a.postcode&&b.appendChild(this.createElementDefaultNS("PostCode",a.postcode));a.country&&b.appendChild(this.createElementDefaultNS("Country",a.country));return b},write_ol_MapExtension:function(a){var b=this.createElementDefaultNS("Extension");if(a=a.maxExtent){var c=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(c,{minx:a.left.toPrecision(18),miny:a.bottom.toPrecision(18),maxx:a.r [...]
+maxy:a.top.toPrecision(18)});b.appendChild(c)}return b},write_wmc_LayerList:function(a){for(var b=this.createElementDefaultNS("LayerList"),c=0,d=a.layersContext.length;c<d;++c)b.appendChild(this.write_wmc_Layer(a.layersContext[c]));return b},write_wmc_Layer:function(a){var b=this.createElementDefaultNS("Layer",null,{queryable:a.queryable?"1":"0",hidden:a.visibility?"0":"1"});b.appendChild(this.write_wmc_Server(a));b.appendChild(this.createElementDefaultNS("Name",a.name));b.appendChild(th [...]
+a.title));a["abstract"]&&b.appendChild(this.createElementDefaultNS("Abstract",a["abstract"]));a.dataURL&&b.appendChild(this.write_wmc_URLType("DataURL",a.dataURL));a.metadataURL&&b.appendChild(this.write_wmc_URLType("MetadataURL",a.metadataURL));return b},write_wmc_LayerExtension:function(a){var b=this.createElementDefaultNS("Extension"),c=a.maxExtent,d=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(d,{minx:c.left.toPrecision(18),miny:c.bottom.toPrecision(18), [...]
+maxy:c.top.toPrecision(18)});b.appendChild(d);a.tileSize&&!a.singleTile&&(c=this.createElementNS(this.namespaces.ol,"ol:tileSize"),this.setAttributes(c,a.tileSize),b.appendChild(c));for(var c="transparent numZoomLevels units isBaseLayer opacity displayInLayerSwitcher singleTile".split(" "),e=0,f=c.length;e<f;++e)(d=this.createOLPropertyNode(a,c[e]))&&b.appendChild(d);return b},createOLPropertyNode:function(a,b){var c=null;null!=a[b]&&(c=this.createElementNS(this.namespaces.ol,"ol:"+b),c. [...]
+return c},write_wmc_Server:function(a){a=a.server;var b=this.createElementDefaultNS("Server"),c={service:"OGC:WMS",version:a.version};a.title&&(c.title=a.title);this.setAttributes(b,c);b.appendChild(this.write_wmc_OnlineResource(a.url));return b},write_wmc_URLType:function(a,b,c){a=this.createElementDefaultNS(a);a.appendChild(this.write_wmc_OnlineResource(b));if(c){b=["width","height","format"];for(var d=0;d<b.length;d++)b[d]in c&&a.setAttribute(b[d],c[b[d]])}return a},write_wmc_Dimensio [...]
+this.createElementDefaultNS("DimensionList"),c;for(c in a.dimensions){var d={},e=a.dimensions[c],f;for(f in e)d[f]="boolean"==typeof e[f]?Number(e[f]):e[f];e="";d.values&&(e=d.values.join(","),delete d.values);b.appendChild(this.createElementDefaultNS("Dimension",e,d))}return b},write_wmc_FormatList:function(a){for(var b=this.createElementDefaultNS("FormatList"),c=0,d=a.formats.length;c<d;c++){var e=a.formats[c];b.appendChild(this.createElementDefaultNS("Format",e.value,e.current&&!0==e. [...]
+null))}return b},write_wmc_StyleList:function(a){var b=this.createElementDefaultNS("StyleList");if((a=a.styles)&&OpenLayers.Util.isArray(a))for(var c,d=0,e=a.length;d<e;d++){var f=a[d],g=this.createElementDefaultNS("Style",null,f.current&&!0==f.current?{current:"1"}:null);f.href?(c=this.createElementDefaultNS("SLD"),f.name&&c.appendChild(this.createElementDefaultNS("Name",f.name)),f.title&&c.appendChild(this.createElementDefaultNS("Title",f.title)),f.legend&&c.appendChild(this.write_wmc_ [...]
+f.legend.href,f.legend)),f=this.write_wmc_OnlineResource(f.href),c.appendChild(f),g.appendChild(c)):f.body?(c=this.createElementDefaultNS("SLD"),f.name&&c.appendChild(this.createElementDefaultNS("Name",f.name)),f.title&&c.appendChild(this.createElementDefaultNS("Title",f.title)),f.legend&&c.appendChild(this.write_wmc_URLType("LegendURL",f.legend.href,f.legend)),f=OpenLayers.Format.XML.prototype.read.apply(this,[f.body]).documentElement,c.ownerDocument&&c.ownerDocument.importNode&&(f=c.ow [...]
+!0)),c.appendChild(f),g.appendChild(c)):(g.appendChild(this.createElementDefaultNS("Name",f.name)),g.appendChild(this.createElementDefaultNS("Title",f.title)),f["abstract"]&&g.appendChild(this.createElementDefaultNS("Abstract",f["abstract"])),f.legend&&g.appendChild(this.write_wmc_URLType("LegendURL",f.legend.href,f.legend)));b.appendChild(g)}return b},write_wmc_OnlineResource:function(a){var b=this.createElementDefaultNS("OnlineResource");this.setAttributeNS(b,this.namespaces.xlink,"xli [...]
+"simple");this.setAttributeNS(b,this.namespaces.xlink,"xlink:href",a);return b},getOnlineResource_href:function(a){var b={};a=a.getElementsByTagName("OnlineResource");0<a.length&&this.read_wmc_OnlineResource(b,a[0]);return b.href},CLASS_NAME:"OpenLayers.Format.WMC.v1"});OpenLayers.Control.PanPanel=OpenLayers.Class(OpenLayers.Control.Panel,{slideFactor:50,slideRatio:null,initialize:function(a){OpenLayers.Control.Panel.prototype.initialize.apply(this,[a]);a={slideFactor:this.slideFactor,sl [...]
+CLASS_NAME:"OpenLayers.Control.PanPanel"});OpenLayers.Control.Attribution=OpenLayers.Class(OpenLayers.Control,{separator:", ",template:"${layers}",destroy:function(){this.map.events.un({removelayer:this.updateAttribution,addlayer:this.updateAttribution,changelayer:this.updateAttribution,changebaselayer:this.updateAttribution,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.map.events.on({ [...]
+addlayer:this.updateAttribution,removelayer:this.updateAttribution,scope:this});this.updateAttribution();return this.div},updateAttribution:function(){var a=[];if(this.map&&this.map.layers){for(var b=0,c=this.map.layers.length;b<c;b++){var d=this.map.layers[b];d.attribution&&d.getVisibility()&&-1===OpenLayers.Util.indexOf(a,d.attribution)&&a.push(d.attribution)}this.div.innerHTML=OpenLayers.String.format(this.template,{layers:a.join(this.separator)})}},CLASS_NAME:"OpenLayers.Control.Attr [...]
+f.tick>this.delay)break;b=f}if(b&&(d=(new Date).getTime()-b.tick,c=Math.sqrt(Math.pow(a.x-b.xy.x,2)+Math.pow(a.y-b.xy.y,2)),d=c/d,!(0==d||d<this.threshold)))return c=Math.asin((a.y-b.xy.y)/c),b.xy.x<=a.x&&(c=Math.PI-c),{speed:d,theta:c}},move:function(a,b){var c=a.speed,d=Math.cos(a.theta),e=-Math.sin(a.theta),f=(new Date).getTime(),g=0,h=0;this.timerId=OpenLayers.Animation.start(OpenLayers.Function.bind(function(){if(null!=this.timerId){var a=(new Date).getTime()-f,l=-this.deceleration* [...]
+2)/2+c*a,m=l*d,l=l*e,n,p;n=!1;0>=-this.deceleration*a+c&&(OpenLayers.Animation.stop(this.timerId),this.timerId=null,n=!0);a=m-g;p=l-h;g=m;h=l;b(a,p,n)}},this))},CLASS_NAME:"OpenLayers.Kinetic"});OpenLayers.Format.WPSExecute=OpenLayers.Class(OpenLayers.Format.XML,OpenLayers.Format.Filter.v1_1_0,{namespaces:{ows:"http://www.opengis.net/ows/1.1",gml:"http://www.opengis.net/gml",wps:"http://www.opengis.net/wps/1.0.0",wfs:"http://www.opengis.net/wfs",ogc:"http://www.opengis.net/ogc",wcs:"http [...]
+schemaLocation:"http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd",schemaLocationAttr:function(a){},write:function(a){var b;window.ActiveXObject?this.xmldom=b=new ActiveXObject("Microsoft.XMLDOM"):b=document.implementation.createDocument("","",null);a=this.writeNode("wps:Execute",a,b);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},read:function(a){"string"==ty [...]
+OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},writers:{wps:{Execute:function(a){var b=this.createElementNSPlus("wps:Execute",{attributes:{version:this.VERSION,service:"WPS"}});this.writeNode("ows:Identifier",a.identifier,b);this.writeNode("wps:DataInputs",a.dataInputs,b);this.writeNode("wps:ResponseForm",a.responseForm,b);return b},ResponseForm:function(a){var b=this.createElementNSPlus("wps:ResponseFor [...]
+this.writeNode("wps:RawDataOutput",a.rawDataOutput,b);a.responseDocument&&this.writeNode("wps:ResponseDocument",a.responseDocument,b);return b},ResponseDocument:function(a){var b=this.createElementNSPlus("wps:ResponseDocument",{attributes:{storeExecuteResponse:a.storeExecuteResponse,lineage:a.lineage,status:a.status}});if(a.outputs)for(var c=0,d=a.outputs.length;c<d;c++)this.writeNode("wps:Output",a.outputs[c],b);return b},Output:function(a){var b=this.createElementNSPlus("wps:Output",{a [...]
+mimeType:a.mimeType,encoding:a.encoding,schema:a.schema}});this.writeNode("ows:Identifier",a.identifier,b);this.writeNode("ows:Title",a.title,b);this.writeNode("ows:Abstract",a["abstract"],b);return b},RawDataOutput:function(a){var b=this.createElementNSPlus("wps:RawDataOutput",{attributes:{mimeType:a.mimeType,encoding:a.encoding,schema:a.schema}});this.writeNode("ows:Identifier",a.identifier,b);return b},DataInputs:function(a){for(var b=this.createElementNSPlus("wps:DataInputs",{}),c=0, [...]
+d;++c)this.writeNode("wps:Input",a[c],b);return b},Input:function(a){var b=this.createElementNSPlus("wps:Input",{});this.writeNode("ows:Identifier",a.identifier,b);a.title&&this.writeNode("ows:Title",a.title,b);a.data&&this.writeNode("wps:Data",a.data,b);a.reference&&this.writeNode("wps:Reference",a.reference,b);a.boundingBoxData&&this.writeNode("wps:BoundingBoxData",a.boundingBoxData,b);return b},Data:function(a){var b=this.createElementNSPlus("wps:Data",{});a.literalData?this.writeNode [...]
+a.literalData,b):a.complexData?this.writeNode("wps:ComplexData",a.complexData,b):a.boundingBoxData&&this.writeNode("ows:BoundingBox",a.boundingBoxData,b);return b},LiteralData:function(a){return this.createElementNSPlus("wps:LiteralData",{attributes:{uom:a.uom},value:a.value})},ComplexData:function(a){var b=this.createElementNSPlus("wps:ComplexData",{attributes:{mimeType:a.mimeType,encoding:a.encoding,schema:a.schema}}),c=a.value;"string"===typeof c?b.appendChild(this.getXMLDoc().createC [...]
+b.appendChild(c);return b},Reference:function(a){var b=this.createElementNSPlus("wps:Reference",{attributes:{mimeType:a.mimeType,"xlink:href":a.href,method:a.method,encoding:a.encoding,schema:a.schema}});a.body&&this.writeNode("wps:Body",a.body,b);return b},BoundingBoxData:function(a,b){this.writers.ows.BoundingBox.apply(this,[a,b,"wps:BoundingBoxData"])},Body:function(a){var b=this.createElementNSPlus("wps:Body",{});a.wcs?this.writeNode("wcs:GetCoverage",a.wcs,b):a.wfs?(this.featureType [...]
+this.version=a.wfs.version,this.writeNode("wfs:GetFeature",a.wfs,b)):this.writeNode("wps:Execute",a,b);return b}},wcs:OpenLayers.Format.WCSGetCoverage.prototype.writers.wcs,wfs:OpenLayers.Format.WFST.v1_1_0.prototype.writers.wfs,ogc:OpenLayers.Format.Filter.v1_1_0.prototype.writers.ogc,ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.writers.ows},readers:{wps:{ExecuteResponse:function(a,b){b.executeResponse={lang:a.getAttribute("lang"),statusLocation:a.getAttribute("statusLocation"),serv [...]
+service:a.getAttribute("service")};this.readChildNodes(a,b.executeResponse)},Process:function(a,b){b.process={};this.readChildNodes(a,b.process)},Status:function(a,b){b.status={creationTime:a.getAttribute("creationTime")};this.readChildNodes(a,b.status)},ProcessSucceeded:function(a,b){b.processSucceeded=!0},ProcessOutputs:function(a,b){b.processOutputs=[];this.readChildNodes(a,b.processOutputs)},Output:function(a,b){var c={};this.readChildNodes(a,c);b.push(c)},Reference:function(a,b){b.r [...]
+{href:a.getAttribute("href"),mimeType:a.getAttribute("mimeType"),encoding:a.getAttribute("encoding"),schema:a.getAttribute("schema")}},Data:function(a,b){b.data={};this.readChildNodes(a,b)},LiteralData:function(a,b){b.literalData={dataType:a.getAttribute("dataType"),uom:a.getAttribute("uom"),value:this.getChildValue(a)}},ComplexData:function(a,b){b.complexData={mimeType:a.getAttribute("mimeType"),schema:a.getAttribute("schema"),encoding:a.getAttribute("encoding"),value:""};if(this.isSimp [...]
+for(c=a.firstChild;c;c=c.nextSibling)switch(c.nodeType){case 3:case 4:b.complexData.value+=c.nodeValue}}else for(c=a.firstChild;c;c=c.nextSibling)1==c.nodeType&&(b.complexData.value=c)},BoundingBox:function(a,b){b.boundingBoxData={dimensions:a.getAttribute("dimensions"),crs:a.getAttribute("crs")};this.readChildNodes(a,b.boundingBoxData)}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WPSExecute"});OpenLayers.Layer.GeoRSS=OpenLayers.Class(Open [...]
+success:this.parseData,scope:this}),this.loaded=!0)},moveTo:function(a,b,c){OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);this.visibility&&!this.loaded&&this.loadRSS()},parseData:function(a){var b=a.responseXML;b&&b.documentElement||(b=OpenLayers.Format.XML.prototype.read(a.responseText));if(this.useFeedTitle){a=null;try{a=b.getElementsByTagNameNS("*","title")[0].firstChild.nodeValue}catch(c){a=b.getElementsByTagName("title")[0].firstChild.nodeValue}a&&this.setName(a)}a [...]
+this.formatOptions);this.map&&!this.projection.equals(this.map.getProjectionObject())&&(a.externalProjection=this.projection,a.internalProjection=this.map.getProjectionObject());b=(new OpenLayers.Format.GeoRSS(a)).read(b);a=0;for(var d=b.length;a<d;a++){var e={},f=b[a];if(f.geometry){var g=f.attributes.title?f.attributes.title:"Untitled",h=f.attributes.description?f.attributes.description:"No description.",k=f.attributes.link?f.attributes.link:"",f=f.geometry.getBounds().getCenterLonLat( [...]
+null==this.icon?OpenLayers.Marker.defaultIcon():this.icon.clone();e.popupSize=this.popupSize?this.popupSize.clone():new OpenLayers.Size(250,120);if(g||h){e.title=g;e.description=h;var l='<div class="olLayerGeoRSSClose">[x]</div>',l=l+'<div class="olLayerGeoRSSTitle">';k&&(l+='<a class="link" href="'+k+'" target="_blank">');l+=g;k&&(l+="</a>");l+="</div>";l+='<div style="" class="olLayerGeoRSSDescription">';l+=h;l+="</div>";e.popupContentHTML=l}f=new OpenLayers.Feature(this,f,e);this.feat [...]
+e=f.createMarker();e.events.register("click",f,this.markerClick);this.addMarker(e)}}this.events.triggerEvent("loadend")},markerClick:function(a){var b=this==this.layer.selectedFeature;this.layer.selectedFeature=b?null:this;for(var c=0,d=this.layer.map.popups.length;c<d;c++)this.layer.map.removePopup(this.layer.map.popups[c]);b||(b=this.createPopup(),OpenLayers.Event.observe(b.div,"click",OpenLayers.Function.bind(function(){for(var a=0,b=this.layer.map.popups.length;a<b;a++)this.layer.map [...]
+this)),this.layer.map.addPopup(b));OpenLayers.Event.stop(a)},clearFeatures:function(){if(null!=this.features)for(;0<this.features.length;){var a=this.features[0];OpenLayers.Util.removeItem(this.features,a);a.destroy()}},CLASS_NAME:"OpenLayers.Layer.GeoRSS"});OpenLayers.Symbolizer.Point=OpenLayers.Class(OpenLayers.Symbolizer,{initialize:function(a){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments)},CLASS_NAME:"OpenLayers.Symbolizer.Point"});OpenLayers.Symbolizer.Line=OpenLa [...]
+graphicName:"square"},read:function(a,b){b=OpenLayers.Util.applyDefaults(b,this.options);var c={namedLayers:!0===b.namedLayersAsArray?[]:{}};this.readChildNodes(a,c);return c},readers:OpenLayers.Util.applyDefaults({sld:{StyledLayerDescriptor:function(a,b){b.version=a.getAttribute("version");this.readChildNodes(a,b)},Name:function(a,b){b.name=this.getChildValue(a)},Title:function(a,b){b.title=this.getChildValue(a)},Abstract:function(a,b){b.description=this.getChildValue(a)},NamedLayer:fun [...]
+{userStyles:[],namedStyles:[]};this.readChildNodes(a,c);for(var d=0,e=c.userStyles.length;d<e;++d)c.userStyles[d].layerName=c.name;OpenLayers.Util.isArray(b.namedLayers)?b.namedLayers.push(c):b.namedLayers[c.name]=c},NamedStyle:function(a,b){b.namedStyles.push(this.getChildName(a.firstChild))},UserStyle:function(a,b){var c={defaultsPerSymbolizer:!0,rules:[]};this.featureTypeCounter=-1;this.readChildNodes(a,c);this.multipleSymbolizers?(delete c.defaultsPerSymbolizer,c=new OpenLayers.Style [...]
+c);b.userStyles.push(c)},IsDefault:function(a,b){"1"==this.getChildValue(a)&&(b.isDefault=!0)},FeatureTypeStyle:function(a,b){++this.featureTypeCounter;var c={rules:this.multipleSymbolizers?b.rules:[]};this.readChildNodes(a,c);this.multipleSymbolizers||(b.rules=c.rules)},Rule:function(a,b){var c;this.multipleSymbolizers&&(c={symbolizers:[]});c=new OpenLayers.Rule(c);this.readChildNodes(a,c);b.rules.push(c)},ElseFilter:function(a,b){b.elseFilter=!0},MinScaleDenominator:function(a,b){b.min [...]
+parseFloat(this.getChildValue(a))},MaxScaleDenominator:function(a,b){b.maxScaleDenominator=parseFloat(this.getChildValue(a))},TextSymbolizer:function(a,b){var c={};this.readChildNodes(a,c);this.multipleSymbolizers?(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Text(c))):b.symbolizer.Text=OpenLayers.Util.applyDefaults(c,b.symbolizer.Text)},LabelPlacement:function(a,b){this.readChildNodes(a,b)},PointPlacement:function(a,b){var c={};this.readChildNodes(a,c);c [...]
+c.rotation;delete c.rotation;var d,e=b.labelAnchorPointX,f=b.labelAnchorPointY;e<=1/3?d="l":e>1/3&&e<2/3?d="c":e>=2/3&&(d="r");f<=1/3?d+="b":f>1/3&&f<2/3?d+="m":f>=2/3&&(d+="t");c.labelAlign=d;OpenLayers.Util.applyDefaults(b,c)},AnchorPoint:function(a,b){this.readChildNodes(a,b)},AnchorPointX:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelAnchorPointX=c)},AnchorPointY:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelAnchorPointY=c)},Displ [...]
+b){this.readChildNodes(a,b)},DisplacementX:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelXOffset=c)},DisplacementY:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelYOffset=c)},LinePlacement:function(a,b){this.readChildNodes(a,b)},PerpendicularOffset:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelPerpendicularOffset=c)},Label:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.label=c)},Font:funct [...]
+b)},Halo:function(a,b){var c={};this.readChildNodes(a,c);b.haloRadius=c.haloRadius;b.haloColor=c.fillColor;b.haloOpacity=c.fillOpacity},Radius:function(a,b){var c=this.readers.ogc._expression.call(this,a);null!=c&&(b.haloRadius=c)},RasterSymbolizer:function(a,b){var c={};this.readChildNodes(a,c);this.multipleSymbolizers?(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Raster(c))):b.symbolizer.Raster=OpenLayers.Util.applyDefaults(c,b.symbolizer.Raster)},Geome [...]
+b){b.geometry={};this.readChildNodes(a,b.geometry)},ColorMap:function(a,b){b.colorMap=[];this.readChildNodes(a,b.colorMap)},ColorMapEntry:function(a,b){var c=a.getAttribute("quantity"),d=a.getAttribute("opacity");b.push({color:a.getAttribute("color"),quantity:null!==c?parseFloat(c):void 0,label:a.getAttribute("label")||void 0,opacity:null!==d?parseFloat(d):void 0})},LineSymbolizer:function(a,b){var c={};this.readChildNodes(a,c);this.multipleSymbolizers?(c.zIndex=this.featureTypeCounter,b [...]
+b.symbolizer.Line=OpenLayers.Util.applyDefaults(c,b.symbolizer.Line)},PolygonSymbolizer:function(a,b){var c={fill:!1,stroke:!1};this.multipleSymbolizers||(c=b.symbolizer.Polygon||c);this.readChildNodes(a,c);this.multipleSymbolizers?(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Polygon(c))):b.symbolizer.Polygon=c},PointSymbolizer:function(a,b){var c={fill:!1,stroke:!1,graphic:!1};this.multipleSymbolizers||(c=b.symbolizer.Point||c);this.readChildNodes(a,c); [...]
+(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Point(c))):b.symbolizer.Point=c},Stroke:function(a,b){b.stroke=!0;this.readChildNodes(a,b)},Fill:function(a,b){b.fill=!0;this.readChildNodes(a,b)},CssParameter:function(a,b){var c=a.getAttribute("name"),d=this.cssMap[c];b.label&&("fill"===c?d="fontColor":"fill-opacity"===c&&(d="fontOpacity"));d&&(c=this.readers.ogc._expression.call(this,a))&&(b[d]=c)},Graphic:function(a,b){b.graphic=!0;var c={};this.readChildN [...]
+for(var d="stroke strokeColor strokeWidth strokeOpacity strokeLinecap fill fillColor fillOpacity graphicName rotation graphicFormat".split(" "),e,f,g=0,h=d.length;g<h;++g)e=d[g],f=c[e],void 0!=f&&(b[e]=f);void 0!=c.opacity&&(b.graphicOpacity=c.opacity);void 0!=c.size&&(isNaN(c.size/2)?b.graphicWidth=c.size:b.pointRadius=c.size/2);void 0!=c.href&&(b.externalGraphic=c.href);void 0!=c.rotation&&(b.rotation=c.rotation)},ExternalGraphic:function(a,b){this.readChildNodes(a,b)},Mark:function(a, [...]
+b)},WellKnownName:function(a,b){b.graphicName=this.getChildValue(a)},Opacity:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.opacity=c)},Size:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.size=c)},Rotation:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.rotation=c)},OnlineResource:function(a,b){b.href=this.getAttributeNS(a,this.namespaces.xlink,"href")},Format:function(a,b){b.graphicFormat=this.getChildValue(a)}}},OpenLayers.F [...]
+cssMap:{stroke:"strokeColor","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","stroke-linecap":"strokeLinecap","stroke-dasharray":"strokeDashstyle",fill:"fillColor","fill-opacity":"fillOpacity","font-family":"fontFamily","font-size":"fontSize","font-weight":"fontWeight","font-style":"fontStyle"},getCssProperty:function(a){var b=null,c;for(c in this.cssMap)if(this.cssMap[c]==a){b=c;break}return b},getGraphicFormat:function(a){var b,c;for(c in this.graphicFormats)if(this.graph [...]
+c;break}return b||this.defaultGraphicFormat},defaultGraphicFormat:"image/png",graphicFormats:{"image/jpeg":/\.jpe?g$/i,"image/gif":/\.gif$/i,"image/png":/\.png$/i},write:function(a){return this.writers.sld.StyledLayerDescriptor.apply(this,[a])},writers:OpenLayers.Util.applyDefaults({sld:{_OGCExpression:function(a,b){var c=this.createElementNSPlus(a),d="string"==typeof b?b.split("${"):[b];c.appendChild(this.createTextNode(d[0]));for(var e,f,g=1,h=d.length;g<h;g++)e=d[g],f=e.indexOf("}"),0 [...]
+{property:e.substring(0,f)},c),c.appendChild(this.createTextNode(e.substring(++f)))):c.appendChild(this.createTextNode("${"+e));return c},StyledLayerDescriptor:function(a){var b=this.createElementNSPlus("sld:StyledLayerDescriptor",{attributes:{version:this.VERSION,"xsi:schemaLocation":this.schemaLocation}});b.setAttribute("xmlns:ogc",this.namespaces.ogc);b.setAttribute("xmlns:gml",this.namespaces.gml);a.name&&this.writeNode("Name",a.name,b);a.title&&this.writeNode("Title",a.title,b);a.de [...]
+this.writeNode("Abstract",a.description,b);if(OpenLayers.Util.isArray(a.namedLayers))for(var c=0,d=a.namedLayers.length;c<d;++c)this.writeNode("NamedLayer",a.namedLayers[c],b);else for(c in a.namedLayers)this.writeNode("NamedLayer",a.namedLayers[c],b);return b},Name:function(a){return this.createElementNSPlus("sld:Name",{value:a})},Title:function(a){return this.createElementNSPlus("sld:Title",{value:a})},Abstract:function(a){return this.createElementNSPlus("sld:Abstract",{value:a})},Name [...]
+this.createElementNSPlus("sld:NamedLayer");this.writeNode("Name",a.name,b);if(a.namedStyles)for(var c=0,d=a.namedStyles.length;c<d;++c)this.writeNode("NamedStyle",a.namedStyles[c],b);if(a.userStyles)for(c=0,d=a.userStyles.length;c<d;++c)this.writeNode("UserStyle",a.userStyles[c],b);return b},NamedStyle:function(a){var b=this.createElementNSPlus("sld:NamedStyle");this.writeNode("Name",a,b);return b},UserStyle:function(a){var b=this.createElementNSPlus("sld:UserStyle");a.name&&this.writeNo [...]
+a.name,b);a.title&&this.writeNode("Title",a.title,b);a.description&&this.writeNode("Abstract",a.description,b);a.isDefault&&this.writeNode("IsDefault",a.isDefault,b);if(this.multipleSymbolizers&&a.rules){for(var c={0:[]},d=[0],e,f,g,h,k,l=0,m=a.rules.length;l<m;++l)if(e=a.rules[l],e.symbolizers){f={};for(var n=0,p=e.symbolizers.length;n<p;++n)g=e.symbolizers[n],h=g.zIndex,h in f||(k=e.clone(),k.symbolizers=[],f[h]=k),f[h].symbolizers.push(g.clone());for(h in f)h in c||(d.push(h),c[h]=[]) [...]
+d.sort();l=0;for(m=d.length;l<m;++l)e=c[d[l]],0<e.length&&(k=a.clone(),k.rules=c[d[l]],this.writeNode("FeatureTypeStyle",k,b))}else this.writeNode("FeatureTypeStyle",a,b);return b},IsDefault:function(a){return this.createElementNSPlus("sld:IsDefault",{value:a?"1":"0"})},FeatureTypeStyle:function(a){for(var b=this.createElementNSPlus("sld:FeatureTypeStyle"),c=0,d=a.rules.length;c<d;++c)this.writeNode("Rule",a.rules[c],b);return b},Rule:function(a){var b=this.createElementNSPlus("sld:Rule" [...]
+this.writeNode("Name",a.name,b);a.title&&this.writeNode("Title",a.title,b);a.description&&this.writeNode("Abstract",a.description,b);a.elseFilter?this.writeNode("ElseFilter",null,b):a.filter&&this.writeNode("ogc:Filter",a.filter,b);void 0!=a.minScaleDenominator&&this.writeNode("MinScaleDenominator",a.minScaleDenominator,b);void 0!=a.maxScaleDenominator&&this.writeNode("MaxScaleDenominator",a.maxScaleDenominator,b);var c,d;if(this.multipleSymbolizers&&a.symbolizers)for(var e=0,f=a.symboli [...]
+f;++e)d=a.symbolizers[e],c=d.CLASS_NAME.split(".").pop(),this.writeNode(c+"Symbolizer",d,b);else for(var f=OpenLayers.Style.SYMBOLIZER_PREFIXES,e=0,g=f.length;e<g;++e)c=f[e],(d=a.symbolizer[c])&&this.writeNode(c+"Symbolizer",d,b);return b},ElseFilter:function(){return this.createElementNSPlus("sld:ElseFilter")},MinScaleDenominator:function(a){return this.createElementNSPlus("sld:MinScaleDenominator",{value:a})},MaxScaleDenominator:function(a){return this.createElementNSPlus("sld:MaxScale [...]
+{value:a})},LineSymbolizer:function(a){var b=this.createElementNSPlus("sld:LineSymbolizer");this.writeNode("Stroke",a,b);return b},Stroke:function(a){var b=this.createElementNSPlus("sld:Stroke");void 0!=a.strokeColor&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeColor"},b);void 0!=a.strokeOpacity&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeOpacity"},b);void 0!=a.strokeWidth&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeWidth"},b);void 0!=a.strokeDashstyle& [...]
+a.strokeDashstyle&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeDashstyle"},b);void 0!=a.strokeLinecap&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeLinecap"},b);return b},CssParameter:function(a){return this.createElementNSPlus("sld:CssParameter",{attributes:{name:this.getCssProperty(a.key)},value:a.symbolizer[a.key]})},TextSymbolizer:function(a){var b=this.createElementNSPlus("sld:TextSymbolizer");null!=a.label&&this.writeNode("Label",a.label,b);null==a.fontFamily&& [...]
+null==a.fontWeight&&null==a.fontStyle||this.writeNode("Font",a,b);null==a.labelAnchorPointX&&null==a.labelAnchorPointY&&null==a.labelAlign&&null==a.labelXOffset&&null==a.labelYOffset&&null==a.labelRotation&&null==a.labelPerpendicularOffset||this.writeNode("LabelPlacement",a,b);null==a.haloRadius&&null==a.haloColor&&null==a.haloOpacity||this.writeNode("Halo",a,b);null==a.fontColor&&null==a.fontOpacity||this.writeNode("Fill",{fillColor:a.fontColor,fillOpacity:a.fontOpacity},b);return b},La [...]
+this.createElementNSPlus("sld:LabelPlacement");null==a.labelAnchorPointX&&null==a.labelAnchorPointY&&null==a.labelAlign&&null==a.labelXOffset&&null==a.labelYOffset&&null==a.labelRotation||null!=a.labelPerpendicularOffset||this.writeNode("PointPlacement",a,b);null!=a.labelPerpendicularOffset&&this.writeNode("LinePlacement",a,b);return b},LinePlacement:function(a){var b=this.createElementNSPlus("sld:LinePlacement");this.writeNode("PerpendicularOffset",a.labelPerpendicularOffset,b);return b [...]
+{value:a})},PointPlacement:function(a){var b=this.createElementNSPlus("sld:PointPlacement");null==a.labelAnchorPointX&&null==a.labelAnchorPointY&&null==a.labelAlign||this.writeNode("AnchorPoint",a,b);null==a.labelXOffset&&null==a.labelYOffset||this.writeNode("Displacement",a,b);null!=a.labelRotation&&this.writeNode("Rotation",a.labelRotation,b);return b},AnchorPoint:function(a){var b=this.createElementNSPlus("sld:AnchorPoint"),c=a.labelAnchorPointX,d=a.labelAnchorPointY;null!=c&&this.wri [...]
+c,b);null!=d&&this.writeNode("AnchorPointY",d,b);if(null==c&&null==d){var e=a.labelAlign.substr(0,1);a=a.labelAlign.substr(1,1);"l"===e?c=0:"c"===e?c=0.5:"r"===e&&(c=1);"b"===a?d=0:"m"===a?d=0.5:"t"===a&&(d=1);this.writeNode("AnchorPointX",c,b);this.writeNode("AnchorPointY",d,b)}return b},AnchorPointX:function(a){return this.createElementNSPlus("sld:AnchorPointX",{value:a})},AnchorPointY:function(a){return this.createElementNSPlus("sld:AnchorPointY",{value:a})},Displacement:function(a){v [...]
+null!=a.labelXOffset&&this.writeNode("DisplacementX",a.labelXOffset,b);null!=a.labelYOffset&&this.writeNode("DisplacementY",a.labelYOffset,b);return b},DisplacementX:function(a){return this.createElementNSPlus("sld:DisplacementX",{value:a})},DisplacementY:function(a){return this.createElementNSPlus("sld:DisplacementY",{value:a})},Font:function(a){var b=this.createElementNSPlus("sld:Font");a.fontFamily&&this.writeNode("CssParameter",{symbolizer:a,key:"fontFamily"},b);a.fontSize&&this.writ [...]
+{symbolizer:a,key:"fontSize"},b);a.fontWeight&&this.writeNode("CssParameter",{symbolizer:a,key:"fontWeight"},b);a.fontStyle&&this.writeNode("CssParameter",{symbolizer:a,key:"fontStyle"},b);return b},Label:function(a){return this.writers.sld._OGCExpression.call(this,"sld:Label",a)},Halo:function(a){var b=this.createElementNSPlus("sld:Halo");a.haloRadius&&this.writeNode("Radius",a.haloRadius,b);(a.haloColor||a.haloOpacity)&&this.writeNode("Fill",{fillColor:a.haloColor,fillOpacity:a.haloOpa [...]
+return b},Radius:function(a){return this.createElementNSPlus("sld:Radius",{value:a})},RasterSymbolizer:function(a){var b=this.createElementNSPlus("sld:RasterSymbolizer");a.geometry&&this.writeNode("Geometry",a.geometry,b);a.opacity&&this.writeNode("Opacity",a.opacity,b);a.colorMap&&this.writeNode("ColorMap",a.colorMap,b);return b},Geometry:function(a){var b=this.createElementNSPlus("sld:Geometry");a.property&&this.writeNode("ogc:PropertyName",a,b);return b},ColorMap:function(a){for(var b [...]
+c=0,d=a.length;c<d;++c)this.writeNode("ColorMapEntry",a[c],b);return b},ColorMapEntry:function(a){var b=this.createElementNSPlus("sld:ColorMapEntry");b.setAttribute("color",a.color);void 0!==a.opacity&&b.setAttribute("opacity",parseFloat(a.opacity));void 0!==a.quantity&&b.setAttribute("quantity",parseFloat(a.quantity));void 0!==a.label&&b.setAttribute("label",a.label);return b},PolygonSymbolizer:function(a){var b=this.createElementNSPlus("sld:PolygonSymbolizer");!1!==a.fill&&this.writeNo [...]
+a,b);!1!==a.stroke&&this.writeNode("Stroke",a,b);return b},Fill:function(a){var b=this.createElementNSPlus("sld:Fill");a.fillColor&&this.writeNode("CssParameter",{symbolizer:a,key:"fillColor"},b);null!=a.fillOpacity&&this.writeNode("CssParameter",{symbolizer:a,key:"fillOpacity"},b);return b},PointSymbolizer:function(a){var b=this.createElementNSPlus("sld:PointSymbolizer");this.writeNode("Graphic",a,b);return b},Graphic:function(a){var b=this.createElementNSPlus("sld:Graphic");void 0!=a.e [...]
+this.writeNode("ExternalGraphic",a,b):this.writeNode("Mark",a,b);void 0!=a.graphicOpacity&&this.writeNode("Opacity",a.graphicOpacity,b);void 0!=a.pointRadius?this.writeNode("Size",2*a.pointRadius,b):void 0!=a.graphicWidth&&this.writeNode("Size",a.graphicWidth,b);void 0!=a.rotation&&this.writeNode("Rotation",a.rotation,b);return b},ExternalGraphic:function(a){var b=this.createElementNSPlus("sld:ExternalGraphic");this.writeNode("OnlineResource",a.externalGraphic,b);a=a.graphicFormat||this. [...]
+this.writeNode("Format",a,b);return b},Mark:function(a){var b=this.createElementNSPlus("sld:Mark");a.graphicName&&this.writeNode("WellKnownName",a.graphicName,b);!1!==a.fill&&this.writeNode("Fill",a,b);!1!==a.stroke&&this.writeNode("Stroke",a,b);return b},WellKnownName:function(a){return this.createElementNSPlus("sld:WellKnownName",{value:a})},Opacity:function(a){return this.createElementNSPlus("sld:Opacity",{value:a})},Size:function(a){return this.writers.sld._OGCExpression.call(this,"s [...]
+a)},Rotation:function(a){return this.createElementNSPlus("sld:Rotation",{value:a})},OnlineResource:function(a){return this.createElementNSPlus("sld:OnlineResource",{attributes:{"xlink:type":"simple","xlink:href":a}})},Format:function(a){return this.createElementNSPlus("sld:Format",{value:a})}}},OpenLayers.Format.Filter.v1_0_0.prototype.writers),CLASS_NAME:"OpenLayers.Format.SLD.v1"});OpenLayers.Layer.WMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{service:"WMS",version:"1.1.1 [...]
+!this.noMagic&&(this.params.TRANSPARENT&&"true"==this.params.TRANSPARENT.toString().toLowerCase())&&(null!=d&&d.isBaseLayer||(this.isBaseLayer=!1),"image/jpeg"==this.params.FORMAT&&(this.params.FORMAT=OpenLayers.Util.alphaHack()?"image/gif":"image/png"))},clone:function(a){null==a&&(a=new OpenLayers.Layer.WMS(this.name,this.url,this.params,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},reverseAxisOrder:function(){var a=this.projection.getCode();return [...]
+!!(this.yx[a]||OpenLayers.Projection.defaults[a]&&OpenLayers.Projection.defaults[a].yx)},getURL:function(a){a=this.adjustBounds(a);var b=this.getImageSize(),c={},d=this.reverseAxisOrder();c.BBOX=this.encodeBBOX?a.toBBOX(null,d):a.toArray(d);c.WIDTH=b.w;c.HEIGHT=b.h;return this.getFullRequestString(c)},mergeNewParams:function(a){a=[OpenLayers.Util.upperCaseObject(a)];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,a)},getFullRequestString:function(a,b){var c=this.map.getP [...]
+c=this.projection&&this.projection.equals(c)?this.projection.getCode():c.getCode(),c="none"==c?null:c;1.3<=parseFloat(this.params.VERSION)?this.params.CRS=c:this.params.SRS=c;"boolean"==typeof this.params.TRANSPARENT&&(a.TRANSPARENT=this.params.TRANSPARENT?"TRUE":"FALSE");return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments)},CLASS_NAME:"OpenLayers.Layer.WMS"});OpenLayers.Layer.KaMap=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,DEFAULT_PARAMS:{i:"jpe [...]
+b,c){b=c*this.tileSize.w;c*=this.tileSize.h;return{tilelon:b,tilelat:c,startcol:Math.floor(a.left/b)-this.buffer,startrow:Math.floor(a.top/c)+this.buffer}},getTileBoundsForGridIndex:function(a,b){this.getTileOrigin();var c=this.gridLayout,d=c.tilelon,e=c.tilelat,f=(c.startcol+b)*d,c=(c.startrow-a)*e;return new OpenLayers.Bounds(f,c,f+d,c+e)},clone:function(a){null==a&&(a=new OpenLayers.Layer.KaMap(this.name,this.url,this.params,this.getOptions()));a=OpenLayers.Layer.Grid.prototype.clone. [...]
+[a]);null!=this.tileSize&&(a.tileSize=this.tileSize.clone());a.grid=[];return a},getTileBounds:function(a){var b=this.getResolution(),c=b*this.tileSize.w,b=b*this.tileSize.h,d=this.getLonLatFromViewPortPx(a);a=c*Math.floor(d.lon/c);d=b*Math.floor(d.lat/b);return new OpenLayers.Bounds(a,d,a+c,d+b)},CLASS_NAME:"OpenLayers.Layer.KaMap"});OpenLayers.Format.WMC.v1_1_0=OpenLayers.Class(OpenLayers.Format.WMC.v1,{VERSION:"1.1.0",schemaLocation:"http://www.opengis.net/context http://schemas.openg [...]
+a||(a.srs={});a.srs[this.getChildValue(b)]=!0},write_wmc_Layer:function(a){var b=OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(this,[a]);if(a.maxScale){var c=this.createElementNS(this.namespaces.sld,"sld:MinScaleDenominator");c.appendChild(this.createTextNode(a.maxScale.toPrecision(16)));b.appendChild(c)}a.minScale&&(c=this.createElementNS(this.namespaces.sld,"sld:MaxScaleDenominator"),c.appendChild(this.createTextNode(a.minScale.toPrecision(16))),b.appendChild(c));if(a.srs)fo [...]
+d));b.appendChild(this.write_wmc_FormatList(a));b.appendChild(this.write_wmc_StyleList(a));a.dimensions&&b.appendChild(this.write_wmc_DimensionList(a));b.appendChild(this.write_wmc_LayerExtension(a));return b},CLASS_NAME:"OpenLayers.Format.WMC.v1_1_0"});OpenLayers.Format.XLS=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.0",stringifyOutput:!0,CLASS_NAME:"OpenLayers.Format.XLS"});OpenLayers.Format.XLS.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xls:"ht [...]
+this.readChildNodes(a,b)},Response:function(a,b){this.readChildNodes(a,b)},GeocodeResponse:function(a,b){b.responseLists=[];this.readChildNodes(a,b)},GeocodeResponseList:function(a,b){var c={features:[],numberOfGeocodedAddresses:parseInt(a.getAttribute("numberOfGeocodedAddresses"))};b.responseLists.push(c);this.readChildNodes(a,c)},GeocodedAddress:function(a,b){var c=new OpenLayers.Feature.Vector;b.features.push(c);this.readChildNodes(a,c);c.geometry=c.components[0]},GeocodeMatchCode:fun [...]
+{accuracy:parseFloat(a.getAttribute("accuracy")),matchType:a.getAttribute("matchType")}},Address:function(a,b){var c={countryCode:a.getAttribute("countryCode"),addressee:a.getAttribute("addressee"),street:[],place:[]};b.attributes.address=c;this.readChildNodes(a,c)},freeFormAddress:function(a,b){b.freeFormAddress=this.getChildValue(a)},StreetAddress:function(a,b){this.readChildNodes(a,b)},Building:function(a,b){b.building={number:a.getAttribute("number"),subdivision:a.getAttribute("subdi [...]
+buildingName:a.getAttribute("buildingName")}},Street:function(a,b){b.street.push(this.getChildValue(a))},Place:function(a,b){b.place[a.getAttribute("type")]=this.getChildValue(a)},PostalCode:function(a,b){b.postalCode=this.getChildValue(a)}},gml:OpenLayers.Format.GML.v3.prototype.readers.gml},write:function(a){return this.writers.xls.XLS.apply(this,[a])},writers:{xls:{XLS:function(a){var b=this.createElementNSPlus("xls:XLS",{attributes:{version:this.VERSION,"xsi:schemaLocation":this.sche [...]
+this.writeNode("RequestHeader",a.header,b);this.writeNode("Request",a,b);return b},RequestHeader:function(a){return this.createElementNSPlus("xls:RequestHeader")},Request:function(a){var b=this.createElementNSPlus("xls:Request",{attributes:{methodName:"GeocodeRequest",requestID:a.requestID||"",version:this.VERSION}});this.writeNode("GeocodeRequest",a.addresses,b);return b},GeocodeRequest:function(a){for(var b=this.createElementNSPlus("xls:GeocodeRequest"),c=0,d=a.length;c<d;c++)this.writ [...]
+a[c],b);return b},Address:function(a){var b=this.createElementNSPlus("xls:Address",{attributes:{countryCode:a.countryCode}});a.freeFormAddress?this.writeNode("freeFormAddress",a.freeFormAddress,b):(a.street&&this.writeNode("StreetAddress",a,b),a.municipality&&this.writeNode("Municipality",a.municipality,b),a.countrySubdivision&&this.writeNode("CountrySubdivision",a.countrySubdivision,b),a.postalCode&&this.writeNode("PostalCode",a.postalCode,b));return b},freeFormAddress:function(a){retur [...]
+{value:a})},StreetAddress:function(a){var b=this.createElementNSPlus("xls:StreetAddress");a.building&&this.writeNode(b,"Building",a.building);a=a.street;OpenLayers.Util.isArray(a)||(a=[a]);for(var c=0,d=a.length;c<d;c++)this.writeNode("Street",a[c],b);return b},Building:function(a){return this.createElementNSPlus("xls:Building",{attributes:{number:a.number,subdivision:a.subdivision,buildingName:a.buildingName}})},Street:function(a){return this.createElementNSPlus("xls:Street",{value:a})} [...]
+{attributes:{type:"Municipality"},value:a})},CountrySubdivision:function(a){return this.createElementNSPlus("xls:Place",{attributes:{type:"CountrySubdivision"},value:a})},PostalCode:function(a){return this.createElementNSPlus("xls:PostalCode",{value:a})}}},CLASS_NAME:"OpenLayers.Format.XLS.v1"});OpenLayers.Format.XLS.v1_1_0=OpenLayers.Class(OpenLayers.Format.XLS.v1,{VERSION:"1.1",schemaLocation:"http://www.opengis.net/xls http://schemas.opengis.net/ols/1.1.0/LocationUtilityService.xsd",C [...]
+"1.1")||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"))},inValidRange:function(a,b,c){a+=c?0:this.translationParameters.x;b+=c?0:this.translationParameters.y;return a>=-this.MAX_PIXEL&&a<=this.MAX_PIXEL&&b>=-this.MAX_PIXEL&&b<=this.MAX_PIXEL},setExtent:function(a,b){var c=OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments),d=this.getResolution(),e=-a.left/d,d=a.top/d;if(b)return this.left=e,this.top=d,this.rendererRoot.set [...]
+"viewBox","0 0 "+this.size.w+" "+this.size.h),this.translate(this.xOffset,0),!0;(e=this.translate(e-this.left+this.xOffset,d-this.top))||this.setExtent(a,!0);return c&&e},translate:function(a,b){if(this.inValidRange(a,b,!0)){var c="";if(a||b)c="translate("+a+","+b+")";this.root.setAttributeNS(null,"transform",c);this.translationParameters={x:a,y:b};return!0}return!1},setSize:function(a){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);this.rendererRoot.setAttributeNS(null,"wid [...]
+this.rendererRoot.setAttributeNS(null,"height",this.size.h)},getNodeType:function(a,b){var c=null;switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":c=b.externalGraphic?"image":this.isComplexSymbol(b.graphicName)?"svg":"circle";break;case "OpenLayers.Geometry.Rectangle":c="rect";break;case "OpenLayers.Geometry.LineString":c="polyline";break;case "OpenLayers.Geometry.LinearRing":c="polygon";break;case "OpenLayers.Geometry.Polygon":case "OpenLayers.Geometry.Curve":c="path"}return c},set [...]
+b,c){b=b||a._style;c=c||a._options;var d=b.title||b.graphicTitle;if(d){a.setAttributeNS(null,"title",d);var e=a.getElementsByTagName("title");0<e.length?e[0].firstChild.textContent=d:(e=this.nodeFactory(null,"title"),e.textContent=d,a.appendChild(e))}var e=parseFloat(a.getAttributeNS(null,"r")),d=1,f;if("OpenLayers.Geometry.Point"==a._geometryClass&&e){a.style.visibility="";if(!1===b.graphic)a.style.visibility="hidden";else if(b.externalGraphic){f=this.getPosition(a);b.graphicWidth&&b.gr [...]
+a.setAttributeNS(null,"preserveAspectRatio","none");var e=b.graphicWidth||b.graphicHeight,g=b.graphicHeight||b.graphicWidth,e=e?e:2*b.pointRadius,g=g?g:2*b.pointRadius,h=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*g),k=b.graphicOpacity||b.fillOpacity;a.setAttributeNS(null,"x",(f.x+(void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*e))).toFixed());a.setAttributeNS(null,"y",(f.y+h).toFixed());a.setAttributeNS(null,"width",e);a.setAttributeNS(null,"height",g);a.setAttributeNS(this.xlinkns [...]
+b.externalGraphic);a.setAttributeNS(null,"style","opacity: "+k);a.onclick=OpenLayers.Event.preventDefault}else if(this.isComplexSymbol(b.graphicName)){var e=3*b.pointRadius,g=2*e,l=this.importSymbol(b.graphicName);f=this.getPosition(a);d=3*this.symbolMetrics[l.id][0]/g;h=a.parentNode;k=a.nextSibling;h&&h.removeChild(a);a.firstChild&&a.removeChild(a.firstChild);a.appendChild(l.firstChild.cloneNode(!0));a.setAttributeNS(null,"viewBox",l.getAttributeNS(null,"viewBox"));a.setAttributeNS(null [...]
+g);a.setAttributeNS(null,"height",g);a.setAttributeNS(null,"x",f.x-e);a.setAttributeNS(null,"y",f.y-e);k?h.insertBefore(a,k):h&&h.appendChild(a)}else a.setAttributeNS(null,"r",b.pointRadius);e=b.rotation;void 0===e&&void 0===a._rotation||!f||(a._rotation=e,e|=0,"svg"!==a.nodeName?a.setAttributeNS(null,"transform","rotate("+e+" "+f.x+" "+f.y+")"):(f=this.symbolMetrics[l.id],a.firstChild.setAttributeNS(null,"transform","rotate("+e+" "+f[1]+" "+f[2]+")")))}c.isFilled?(a.setAttributeNS(null, [...]
+a.setAttributeNS(null,"fill-opacity",b.fillOpacity)):a.setAttributeNS(null,"fill","none");c.isStroked?(a.setAttributeNS(null,"stroke",b.strokeColor),a.setAttributeNS(null,"stroke-opacity",b.strokeOpacity),a.setAttributeNS(null,"stroke-width",b.strokeWidth*d),a.setAttributeNS(null,"stroke-linecap",b.strokeLinecap||"round"),a.setAttributeNS(null,"stroke-linejoin","round"),b.strokeDashstyle&&a.setAttributeNS(null,"stroke-dasharray",this.dashStyle(b,d))):a.setAttributeNS(null,"stroke","none" [...]
+a.setAttributeNS(null,"pointer-events",b.pointerEvents);null!=b.cursor&&a.setAttributeNS(null,"cursor",b.cursor);return a},dashStyle:function(a,b){var c=a.strokeWidth*b,d=a.strokeDashstyle;switch(d){case "solid":return"none";case "dot":return[1,4*c].join();case "dash":return[4*c,4*c].join();case "dashdot":return[4*c,4*c,1,4*c].join();case "longdash":return[8*c,4*c].join();case "longdashdot":return[8*c,4*c,1,4*c].join();default:return OpenLayers.String.trim(d).replace(/\s+/g,",")}},create [...]
+b){var c=document.createElementNS(this.xmlns,a);b&&c.setAttributeNS(null,"id",b);return c},nodeTypeCompare:function(a,b){return b==a.nodeName},createRenderRoot:function(){var a=this.nodeFactory(this.container.id+"_svgRoot","svg");a.style.display="block";return a},createRoot:function(a){return this.nodeFactory(this.container.id+a,"g")},createDefs:function(){var a=this.nodeFactory(this.container.id+"_defs","defs");this.rendererRoot.appendChild(a);return a},drawPoint:function(a,b){return th [...]
+b,1)},drawCircle:function(a,b,c){var d=this.getResolution(),e=(b.x-this.featureDx)/d+this.left;b=this.top-b.y/d;return this.inValidRange(e,b)?(a.setAttributeNS(null,"cx",e),a.setAttributeNS(null,"cy",b),a.setAttributeNS(null,"r",c),a):!1},drawLineString:function(a,b){var c=this.getComponentsString(b.components);return c.path?(a.setAttributeNS(null,"points",c.path),c.complete?a:null):!1},drawLinearRing:function(a,b){var c=this.getComponentsString(b.components);return c.path?(a.setAttribut [...]
+"points",c.path),c.complete?a:null):!1},drawPolygon:function(a,b){for(var c="",d=!0,e=!0,f,g,h=0,k=b.components.length;h<k;h++)c+=" M",f=this.getComponentsString(b.components[h].components," "),(g=f.path)?(c+=" "+g,e=f.complete&&e):d=!1;return d?(a.setAttributeNS(null,"d",c+" z"),a.setAttributeNS(null,"fill-rule","evenodd"),e?a:null):!1},drawRectangle:function(a,b){var c=this.getResolution(),d=(b.x-this.featureDx)/c+this.left,e=this.top-b.y/c;return this.inValidRange(d,e)?(a.setAttribute [...]
+d),a.setAttributeNS(null,"y",e),a.setAttributeNS(null,"width",b.width/c),a.setAttributeNS(null,"height",b.height/c),a):!1},drawText:function(a,b,c){var d=!!b.labelOutlineWidth;if(d){var e=OpenLayers.Util.extend({},b);e.fontColor=e.labelOutlineColor;e.fontStrokeColor=e.labelOutlineColor;e.fontStrokeWidth=b.labelOutlineWidth;b.labelOutlineOpacity&&(e.fontOpacity=b.labelOutlineOpacity);delete e.labelOutlineWidth;this.drawText(a,e,c)}var f=this.getResolution(),e=(c.x-this.featureDx)/f+this.l [...]
+f-this.top,d=d?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX,f=this.nodeFactory(a+d,"text");f.setAttributeNS(null,"x",e);f.setAttributeNS(null,"y",-g);b.fontColor&&f.setAttributeNS(null,"fill",b.fontColor);b.fontStrokeColor&&f.setAttributeNS(null,"stroke",b.fontStrokeColor);b.fontStrokeWidth&&f.setAttributeNS(null,"stroke-width",b.fontStrokeWidth);b.fontOpacity&&f.setAttributeNS(null,"opacity",b.fontOpacity);b.fontFamily&&f.setAttributeNS(null,"font-family",b.fontFamily);b.fontSize&&f.s [...]
+"font-size",b.fontSize);b.fontWeight&&f.setAttributeNS(null,"font-weight",b.fontWeight);b.fontStyle&&f.setAttributeNS(null,"font-style",b.fontStyle);!0===b.labelSelect?(f.setAttributeNS(null,"pointer-events","visible"),f._featureId=a):f.setAttributeNS(null,"pointer-events","none");g=b.labelAlign||OpenLayers.Renderer.defaultSymbolizer.labelAlign;f.setAttributeNS(null,"text-anchor",OpenLayers.Renderer.SVG.LABEL_ALIGN[g[0]]||"middle");!0===OpenLayers.IS_GECKO&&f.setAttributeNS(null,"dominan [...]
+OpenLayers.Renderer.SVG.LABEL_ALIGN[g[1]]||"central");for(var h=b.label.split("\n"),k=h.length;f.childNodes.length>k;)f.removeChild(f.lastChild);for(var l=0;l<k;l++){var m=this.nodeFactory(a+d+"_tspan_"+l,"tspan");!0===b.labelSelect&&(m._featureId=a,m._geometry=c,m._geometryClass=c.CLASS_NAME);!1===OpenLayers.IS_GECKO&&m.setAttributeNS(null,"baseline-shift",OpenLayers.Renderer.SVG.LABEL_VSHIFT[g[1]]||"-35%");m.setAttribute("x",e);if(0==l){var n=OpenLayers.Renderer.SVG.LABEL_VFACTOR[g[1]] [...]
+(n=-0.5);m.setAttribute("dy",n*(k-1)+"em")}else m.setAttribute("dy","1em");m.textContent=""===h[l]?" ":h[l];m.parentNode||f.appendChild(m)}f.parentNode||this.textRoot.appendChild(f)},getComponentsString:function(a,b){for(var c=[],d=!0,e=a.length,f=[],g,h=0;h<e;h++)g=a[h],c.push(g),(g=this.getShortString(g))?f.push(g):(0<h&&this.getShortString(a[h-1])&&f.push(this.clipLine(a[h],a[h-1])),h<e-1&&this.getShortString(a[h+1])&&f.push(this.clipLine(a[h],a[h+1])),d=!1);return{path:f.join(b||",") [...]
+clipLine:function(a,b){if(b.equals(a))return"";var c=this.getResolution(),d=this.MAX_PIXEL-this.translationParameters.x,e=this.MAX_PIXEL-this.translationParameters.y,f=(b.x-this.featureDx)/c+this.left,g=this.top-b.y/c,h=(a.x-this.featureDx)/c+this.left,c=this.top-a.y/c,k;if(h<-d||h>d)k=(c-g)/(h-f),h=0>h?-d:d,c=g+(h-f)*k;if(c<-e||c>e)k=(h-f)/(c-g),c=0>c?-e:e,h=f+(c-g)*k;return h+","+c},getShortString:function(a){var b=this.getResolution(),c=(a.x-this.featureDx)/b+this.left;a=this.top-a.y/ [...]
+a)?c+","+a:!1},getPosition:function(a){return{x:parseFloat(a.getAttributeNS(null,"cx")),y:parseFloat(a.getAttributeNS(null,"cy"))}},importSymbol:function(a){this.defs||(this.defs=this.createDefs());var b=this.container.id+"-"+a,c=document.getElementById(b);if(null!=c)return c;var d=OpenLayers.Renderer.symbol[a];if(!d)throw Error(a+" is not a valid symbol name");a=this.nodeFactory(b,"symbol");var e=this.nodeFactory(null,"polygon");a.appendChild(e);for(var c=new OpenLayers.Bounds(Number.MA [...]
+0,0),f=[],g,h,k=0;k<d.length;k+=2)g=d[k],h=d[k+1],c.left=Math.min(c.left,g),c.bottom=Math.min(c.bottom,h),c.right=Math.max(c.right,g),c.top=Math.max(c.top,h),f.push(g,",",h);e.setAttributeNS(null,"points",f.join(" "));d=c.getWidth();e=c.getHeight();a.setAttributeNS(null,"viewBox",[c.left-d,c.bottom-e,3*d,3*e].join(" "));this.symbolMetrics[b]=[Math.max(d,e),c.getCenterLonLat().lon,c.getCenterLonLat().lat];this.defs.appendChild(a);return a},getFeatureIdFromEvent:function(a){var b=OpenLayer [...]
+arguments);b||(b=a.target,b=b.parentNode&&b!=this.rendererRoot?b.parentNode._featureId:void 0);return b},CLASS_NAME:"OpenLayers.Renderer.SVG"});OpenLayers.Renderer.SVG.LABEL_ALIGN={l:"start",r:"end",b:"bottom",t:"hanging"};OpenLayers.Renderer.SVG.LABEL_VSHIFT={t:"-70%",b:"0"};OpenLayers.Renderer.SVG.LABEL_VFACTOR={t:0,b:-1};OpenLayers.Renderer.SVG.preventDefault=function(a){OpenLayers.Event.preventDefault(a)};OpenLayers.Format.SLD.v1_0_0=OpenLayers.Class(OpenLayers.Format.SLD.v1,{VERSION [...]
+defaultPrefix:"owc",extractAttributes:!0,xy:!0,regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},featureNS:"http://mapserver.gis.umn.edu/mapserver",featureType:"vector",geometryName:"geometry",nestingLayerLookup:null,initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);OpenLayers.Format.GML.v2.prototype.setGeometryTypes.call(this)},setNestingPath:function(a){if(a.layersContext)for(var b=0,c=a.layersContext.length;b<c;b+ [...]
+a.layersContext[b],e=[],f=a.title||"";a.metadata&&a.metadata.nestingPath&&(e=a.metadata.nestingPath.slice());""!=f&&e.push(f);d.metadata.nestingPath=e;d.layersContext&&this.setNestingPath(d)}},decomposeNestingPath:function(a){var b=[];if(OpenLayers.Util.isArray(a)){for(a=a.slice();0<a.length;)b.push(a.slice()),a.pop();b.reverse()}return b},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.re [...]
+b);this.setNestingPath({layersContext:b.layersContext});a=[];this.processLayer(a,b);delete b.layersContext;b.layersContext=a;return b},processLayer:function(a,b){if(b.layersContext)for(var c=0,d=b.layersContext.length;c<d;c++){var e=b.layersContext[c];a.push(e);e.layersContext&&this.processLayer(a,e)}},write:function(a,b){this.nestingLayerLookup={};b=b||{};OpenLayers.Util.applyDefaults(b,a);var c=this.writeNode("OWSContext",b);this.nestingLayerLookup=null;this.setAttributeNS(c,this.names [...]
+"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[c])},readers:{kml:{Document:function(a,b){b.features=(new OpenLayers.Format.KML({kmlns:this.namespaces.kml,extractStyles:!0})).read(a)}},owc:{OWSContext:function(a,b){this.readChildNodes(a,b)},General:function(a,b){this.readChildNodes(a,b)},ResourceList:function(a,b){this.readChildNodes(a,b)},Layer:function(a,b){var c={metadata:{},visibility:"1"!=a.getAttribute("hidden"),queryable:"1"==a.ge [...]
+opacity:null!=a.getAttribute("opacity")?parseFloat(a.getAttribute("opacity")):null,name:a.getAttribute("name"),categoryLayer:null==a.getAttribute("name"),formats:[],styles:[]};b.layersContext||(b.layersContext=[]);b.layersContext.push(c);this.readChildNodes(a,c)},InlineGeometry:function(a,b){b.features=[];var c=this.getElementsByTagNameNS(a,this.namespaces.gml,"featureMember"),d;1<=c.length&&(d=c[0]);d&&d.firstChild&&(c=d.firstChild.nextSibling?d.firstChild.nextSibling:d.firstChild,this. [...]
+c.namespaceURI),this.featureType=c.localName||c.nodeName.split(":").pop(),this.readChildNodes(a,b))},Server:function(a,b){if(!b.service&&!b.version||b.service!=OpenLayers.Format.Context.serviceTypes.WMS)b.service=a.getAttribute("service"),b.version=a.getAttribute("version"),this.readChildNodes(a,b)},Name:function(a,b){b.name=this.getChildValue(a);this.readChildNodes(a,b)},Title:function(a,b){b.title=this.getChildValue(a);this.readChildNodes(a,b)},StyleList:function(a,b){this.readChildNod [...]
+Style:function(a,b){var c={};b.push(c);this.readChildNodes(a,c)},LegendURL:function(a,b){var c={};b.legend=c;this.readChildNodes(a,c)},OnlineResource:function(a,b){b.url=this.getAttributeNS(a,this.namespaces.xlink,"href");this.readChildNodes(a,b)}},ows:OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows,gml:OpenLayers.Format.GML.v2.prototype.readers.gml,sld:OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld,feature:OpenLayers.Format.GML.v2.prototype.readers.feature},writers:{owc:{OW [...]
+this.createElementNSPlus("OWSContext",{attributes:{version:this.VERSION,id:a.id||OpenLayers.Util.createUniqueID("OpenLayers_OWSContext_")}});this.writeNode("General",a,b);this.writeNode("ResourceList",a,b);return b},General:function(a){var b=this.createElementNSPlus("General");this.writeNode("ows:BoundingBox",a,b);this.writeNode("ows:Title",a.title||"OpenLayers OWSContext",b);return b},ResourceList:function(a){for(var b=this.createElementNSPlus("ResourceList"),c=0,d=a.layers.length;c<d;c [...]
+a.layers[c],f=this.decomposeNestingPath(e.metadata.nestingPath);this.writeNode("_Layer",{layer:e,subPaths:f},b)}return b},Server:function(a){var b=this.createElementNSPlus("Server",{attributes:{version:a.version,service:a.service}});this.writeNode("OnlineResource",a,b);return b},OnlineResource:function(a){return this.createElementNSPlus("OnlineResource",{attributes:{"xlink:href":a.url}})},InlineGeometry:function(a){var b=this.createElementNSPlus("InlineGeometry"),c=a.getDataExtent();null [...]
+c,b);for(var c=0,d=a.features.length;c<d;c++)this.writeNode("gml:featureMember",a.features[c],b);return b},StyleList:function(a){for(var b=this.createElementNSPlus("StyleList"),c=0,d=a.length;c<d;c++)this.writeNode("Style",a[c],b);return b},Style:function(a){var b=this.createElementNSPlus("Style");this.writeNode("Name",a,b);this.writeNode("Title",a,b);a.legend&&this.writeNode("LegendURL",a,b);return b},Name:function(a){return this.createElementNSPlus("Name",{value:a.name})},Title:functio [...]
+{value:a.title})},LegendURL:function(a){var b=this.createElementNSPlus("LegendURL");this.writeNode("OnlineResource",a.legend,b);return b},_WMS:function(a){var b=this.createElementNSPlus("Layer",{attributes:{name:a.params.LAYERS,queryable:a.queryable?"1":"0",hidden:a.visibility?"0":"1",opacity:a.hasOwnProperty("opacity")?a.opacity:null}});this.writeNode("ows:Title",a.name,b);this.writeNode("ows:OutputFormat",a.params.FORMAT,b);this.writeNode("Server",{service:OpenLayers.Format.Context.ser [...]
+version:a.params.VERSION,url:a.url},b);a.metadata.styles&&0<a.metadata.styles.length&&this.writeNode("StyleList",a.metadata.styles,b);return b},_Layer:function(a){var b,c,d;b=a.layer;c=a.subPaths;d=null;0<c.length?(b=c[0].join("/"),c=b.lastIndexOf("/"),d=this.nestingLayerLookup[b],c=0<c?b.substring(c+1,b.length):b,d||(d=this.createElementNSPlus("Layer"),this.writeNode("ows:Title",c,d),this.nestingLayerLookup[b]=d),a.subPaths.shift(),this.writeNode("_Layer",a,d)):(b instanceof OpenLayers. [...]
+d=this.writeNode("_WMS",b):b instanceof OpenLayers.Layer.Vector&&(b.protocol instanceof OpenLayers.Protocol.WFS.v1?d=this.writeNode("_WFS",b):b.protocol instanceof OpenLayers.Protocol.HTTP?b.protocol.format instanceof OpenLayers.Format.GML?(b.protocol.format.version="2.1.2",d=this.writeNode("_GML",b)):b.protocol.format instanceof OpenLayers.Format.KML&&(b.protocol.format.version="2.2",d=this.writeNode("_KML",b)):(this.setNamespace("feature",this.featureNS),d=this.writeNode("_InlineGeomet [...]
+b.options.maxScale&&this.writeNode("sld:MinScaleDenominator",b.options.maxScale,d),b.options.minScale&&this.writeNode("sld:MaxScaleDenominator",b.options.minScale,d),this.nestingLayerLookup[b.name]=d);return d},_WFS:function(a){var b=this.createElementNSPlus("Layer",{attributes:{name:a.protocol.featurePrefix+":"+a.protocol.featureType,hidden:a.visibility?"0":"1"}});this.writeNode("ows:Title",a.name,b);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.WFS,version:a.p [...]
+url:a.protocol.url},b);return b},_InlineGeometry:function(a){var b=this.createElementNSPlus("Layer",{attributes:{name:this.featureType,hidden:a.visibility?"0":"1"}});this.writeNode("ows:Title",a.name,b);this.writeNode("InlineGeometry",a,b);return b},_GML:function(a){var b=this.createElementNSPlus("Layer");this.writeNode("ows:Title",a.name,b);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.GML,url:a.protocol.url,version:a.protocol.format.version},b);return b},_KML: [...]
+this.createElementNSPlus("Layer");this.writeNode("ows:Title",a.name,b);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.KML,version:a.protocol.format.version,url:a.protocol.url},b);return b}},gml:OpenLayers.Util.applyDefaults({boundedBy:function(a){var b=this.createElementNSPlus("gml:boundedBy");this.writeNode("gml:Box",a,b);return b}},OpenLayers.Format.GML.v2.prototype.writers.gml),ows:OpenLayers.Format.OWSCommon.v1_0_0.prototype.writers.ows,sld:OpenLayers.Format. [...]
+feature:OpenLayers.Format.GML.v2.prototype.writers.feature},CLASS_NAME:"OpenLayers.Format.OWSContext.v0_3_1"});OpenLayers.Popup=OpenLayers.Class({events:null,id:"",lonlat:null,div:null,contentSize:null,size:null,contentHTML:null,backgroundColor:"",opacity:"",border:"",contentDiv:null,groupDiv:null,closeDiv:null,autoSize:!1,minSize:null,maxSize:null,displayClass:"olPopup",contentDisplayClass:"olPopupContent",padding:0,disableFirefoxOverflowHack:!1,fixPadding:function(){"number"==typeof th [...]
+keepInMap:!1,closeOnMove:!1,map:null,initialize:function(a,b,c,d,e,f){null==a&&(a=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_"));this.id=a;this.lonlat=b;this.contentSize=null!=c?c:new OpenLayers.Size(OpenLayers.Popup.WIDTH,OpenLayers.Popup.HEIGHT);null!=d&&(this.contentHTML=d);this.backgroundColor=OpenLayers.Popup.COLOR;this.opacity=OpenLayers.Popup.OPACITY;this.border=OpenLayers.Popup.BORDER;this.div=OpenLayers.Util.createDiv(this.id,null,null,null,null,null,"hidden");this.div.cla [...]
+this.groupDiv=OpenLayers.Util.createDiv(this.id+"_GroupDiv",null,null,null,"relative",null,"hidden");a=this.div.id+"_contentDiv";this.contentDiv=OpenLayers.Util.createDiv(a,null,this.contentSize.clone(),null,"relative");this.contentDiv.className=this.contentDisplayClass;this.groupDiv.appendChild(this.contentDiv);this.div.appendChild(this.groupDiv);e&&this.addCloseBox(f);this.registerEvents()},destroy:function(){this.border=this.opacity=this.backgroundColor=this.contentHTML=this.size=this [...]
+null;this.closeOnMove&&this.map&&this.map.events.unregister("movestart",this,this.hide);this.events.destroy();this.events=null;this.closeDiv&&(OpenLayers.Event.stopObservingElement(this.closeDiv),this.groupDiv.removeChild(this.closeDiv));this.closeDiv=null;this.div.removeChild(this.groupDiv);this.groupDiv=null;null!=this.map&&this.map.removePopup(this);this.panMapIfOutOfView=this.padding=this.maxSize=this.minSize=this.autoSize=this.div=this.map=null},draw:function(a){null==a&&null!=this. [...]
+this.map&&(a=this.map.getLayerPxFromLonLat(this.lonlat));this.closeOnMove&&this.map.events.register("movestart",this,this.hide);this.disableFirefoxOverflowHack||"firefox"!=OpenLayers.BROWSER_NAME||(this.map.events.register("movestart",this,function(){var a=document.defaultView.getComputedStyle(this.contentDiv,null).getPropertyValue("overflow");"hidden"!=a&&(this.contentDiv._oldOverflow=a,this.contentDiv.style.overflow="hidden")}),this.map.events.register("moveend",this,function(){var a=t [...]
+a&&(this.contentDiv.style.overflow=a,this.contentDiv._oldOverflow=null)}));this.moveTo(a);this.autoSize||this.size||this.setSize(this.contentSize);this.setBackgroundColor();this.setOpacity();this.setBorder();this.setContentHTML();this.panMapIfOutOfView&&this.panIntoView();return this.div},updatePosition:function(){if(this.lonlat&&this.map){var a=this.map.getLayerPxFromLonLat(this.lonlat);a&&this.moveTo(a)}},moveTo:function(a){null!=a&&null!=this.div&&(this.div.style.left=a.x+"px",this.di [...]
+a.y+"px")},visible:function(){return OpenLayers.Element.visible(this.div)},toggle:function(){this.visible()?this.hide():this.show()},show:function(){this.div.style.display="";this.panMapIfOutOfView&&this.panIntoView()},hide:function(){this.div.style.display="none"},setSize:function(a){this.size=a.clone();var b=this.getContentDivPadding(),c=b.left+b.right,d=b.top+b.bottom;this.fixPadding();c+=this.padding.left+this.padding.right;d+=this.padding.top+this.padding.bottom;if(this.closeDiv)var [...]
+c=c+(e+b.right);this.size.w+=c;this.size.h+=d;"msie"==OpenLayers.BROWSER_NAME&&(this.contentSize.w+=b.left+b.right,this.contentSize.h+=b.bottom+b.top);null!=this.div&&(this.div.style.width=this.size.w+"px",this.div.style.height=this.size.h+"px");null!=this.contentDiv&&(this.contentDiv.style.width=a.w+"px",this.contentDiv.style.height=a.h+"px")},updateSize:function(){var a="<div class='"+this.contentDisplayClass+"'>"+this.contentDiv.innerHTML+"</div>",b=this.map?this.map.div:document.body [...]
+null,{displayClass:this.displayClass,containerElement:b}),d=this.getSafeContentSize(c),e=null;d.equals(c)?e=c:(c={w:d.w<c.w?d.w:null,h:d.h<c.h?d.h:null},c.w&&c.h?e=d:(a=OpenLayers.Util.getRenderedDimensions(a,c,{displayClass:this.contentDisplayClass,containerElement:b}),"hidden"!=OpenLayers.Element.getStyle(this.contentDiv,"overflow")&&a.equals(d)&&(d=OpenLayers.Util.getScrollbarWidth(),c.w?a.h+=d:a.w+=d),e=this.getSafeContentSize(a)));this.setSize(e)},setBackgroundColor:function(a){void [...]
+a);null!=this.div&&(this.div.style.backgroundColor=this.backgroundColor)},setOpacity:function(a){void 0!=a&&(this.opacity=a);null!=this.div&&(this.div.style.opacity=this.opacity,this.div.style.filter="alpha(opacity="+100*this.opacity+")")},setBorder:function(a){void 0!=a&&(this.border=a);null!=this.div&&(this.div.style.border=this.border)},setContentHTML:function(a){null!=a&&(this.contentHTML=a);null!=this.contentDiv&&(null!=this.contentHTML&&this.contentHTML!=this.contentDiv.innerHTML)& [...]
+this.contentHTML,this.autoSize&&(this.registerImageListeners(),this.updateSize()))},registerImageListeners:function(){for(var a=function(){null!==this.popup.id&&(this.popup.updateSize(),this.popup.visible()&&this.popup.panMapIfOutOfView&&this.popup.panIntoView(),OpenLayers.Event.stopObserving(this.img,"load",this.img._onImgLoad))},b=this.contentDiv.getElementsByTagName("img"),c=0,d=b.length;c<d;c++){var e=b[c];if(0==e.width||0==e.height)e._onImgLoad=OpenLayers.Function.bind(a,{popup:this [...]
+OpenLayers.Event.observe(e,"load",e._onImgLoad)}},getSafeContentSize:function(a){a=a.clone();var b=this.getContentDivPadding(),c=b.left+b.right,d=b.top+b.bottom;this.fixPadding();c+=this.padding.left+this.padding.right;d+=this.padding.top+this.padding.bottom;if(this.closeDiv)var e=parseInt(this.closeDiv.style.width),c=c+(e+b.right);this.minSize&&(a.w=Math.max(a.w,this.minSize.w-c),a.h=Math.max(a.h,this.minSize.h-d));this.maxSize&&(a.w=Math.min(a.w,this.maxSize.w-c),a.h=Math.min(a.h,this. [...]
+d));if(this.map&&this.map.size){e=b=0;if(this.keepInMap&&!this.panMapIfOutOfView)switch(e=this.map.getPixelFromLonLat(this.lonlat),this.relativePosition){case "tr":b=e.x;e=this.map.size.h-e.y;break;case "tl":b=this.map.size.w-e.x;e=this.map.size.h-e.y;break;case "bl":b=this.map.size.w-e.x;e=e.y;break;case "br":b=e.x;e=e.y;break;default:b=e.x,e=this.map.size.h-e.y}d=this.map.size.h-this.map.paddingForPopups.top-this.map.paddingForPopups.bottom-d-e;a.w=Math.min(a.w,this.map.size.w-this.map [...]
+this.map.paddingForPopups.right-c-b);a.h=Math.min(a.h,d)}return a},getContentDivPadding:function(){var a=this._contentDivPadding;a||(null==this.div.parentNode&&(this.div.style.display="none",document.body.appendChild(this.div)),this._contentDivPadding=a=new OpenLayers.Bounds(OpenLayers.Element.getStyle(this.contentDiv,"padding-left"),OpenLayers.Element.getStyle(this.contentDiv,"padding-bottom"),OpenLayers.Element.getStyle(this.contentDiv,"padding-right"),OpenLayers.Element.getStyle(this. [...]
+"padding-top")),this.div.parentNode==document.body&&(document.body.removeChild(this.div),this.div.style.display=""));return a},addCloseBox:function(a){this.closeDiv=OpenLayers.Util.createDiv(this.id+"_close",null,{w:17,h:17});this.closeDiv.className="olPopupCloseBox";var b=this.getContentDivPadding();this.closeDiv.style.right=b.right+"px";this.closeDiv.style.top=b.top+"px";this.groupDiv.appendChild(this.closeDiv);a=a||function(a){this.hide();OpenLayers.Event.stop(a)};OpenLayers.Event.obs [...]
+"touchend",OpenLayers.Function.bindAsEventListener(a,this));OpenLayers.Event.observe(this.closeDiv,"click",OpenLayers.Function.bindAsEventListener(a,this))},panIntoView:function(){var a=this.map.getSize(),b=this.map.getViewPortPxFromLayerPx(new OpenLayers.Pixel(parseInt(this.div.style.left),parseInt(this.div.style.top))),c=b.clone();b.x<this.map.paddingForPopups.left?c.x=this.map.paddingForPopups.left:b.x+this.size.w>a.w-this.map.paddingForPopups.right&&(c.x=a.w-this.map.paddingForPopups [...]
+b.y<this.map.paddingForPopups.top?c.y=this.map.paddingForPopups.top:b.y+this.size.h>a.h-this.map.paddingForPopups.bottom&&(c.y=a.h-this.map.paddingForPopups.bottom-this.size.h);this.map.pan(b.x-c.x,b.y-c.y)},registerEvents:function(){this.events=new OpenLayers.Events(this,this.div,null,!0);this.events.on({mousedown:this.onmousedown,mousemove:this.onmousemove,mouseup:this.onmouseup,click:this.onclick,mouseout:this.onmouseout,dblclick:this.ondblclick,touchstart:function(a){OpenLayers.Event [...]
+scope:this})},onmousedown:function(a){this.mousedown=!0;OpenLayers.Event.stop(a,!0)},onmousemove:function(a){this.mousedown&&OpenLayers.Event.stop(a,!0)},onmouseup:function(a){this.mousedown&&(this.mousedown=!1,OpenLayers.Event.stop(a,!0))},onclick:function(a){OpenLayers.Event.stop(a,!0)},onmouseout:function(a){this.mousedown=!1},ondblclick:function(a){OpenLayers.Event.stop(a,!0)},CLASS_NAME:"OpenLayers.Popup"});OpenLayers.Popup.WIDTH=200;OpenLayers.Popup.HEIGHT=200;OpenLayers.Popup.COLO [...]
+OpenLayers.Popup.OPACITY=1;OpenLayers.Popup.BORDER="0px";OpenLayers.Control.ScaleLine=OpenLayers.Class(OpenLayers.Control,{maxWidth:100,topOutUnits:"km",topInUnits:"m",bottomOutUnits:"mi",bottomInUnits:"ft",eTop:null,eBottom:null,geodesic:!1,draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.eTop||(this.eTop=document.createElement("div"),this.eTop.className=this.displayClass+"Top",this.div.appendChild(this.eTop),this.eTop.style.visibility=""==this.topOutUnits||" [...]
+this.eBottom.className=this.displayClass+"Bottom",this.div.appendChild(this.eBottom),this.eBottom.style.visibility=""==this.bottomOutUnits||""==this.bottomInUnits?"hidden":"visible");this.map.events.register("moveend",this,this.update);this.update();return this.div},getBarLen:function(a){var b=parseInt(Math.log(a)/Math.log(10)),b=Math.pow(10,b);a=parseInt(a/b);return(5<a?5:2<a?2:1)*b},update:function(){var a=this.map.getResolution();if(a){var b=this.map.getUnits(),c=OpenLayers.INCHES_PER [...]
+a*c[b],e=1;!0===this.geodesic&&(e=(this.map.getGeodesicPixelSize().w||1E-6)*this.maxWidth/(d/c.km),d*=e);var f,g;1E5<d?(f=this.topOutUnits,g=this.bottomOutUnits):(f=this.topInUnits,g=this.bottomInUnits);var h=d/c[f],k=d/c[g],d=this.getBarLen(h),l=this.getBarLen(k),h=d/c[b]*c[f],k=l/c[b]*c[g],b=h/a/e,a=k/a/e;"visible"==this.eBottom.style.visibility&&(this.eBottom.style.width=Math.round(a)+"px",this.eBottom.innerHTML=l+" "+g);"visible"==this.eTop.style.visibility&&(this.eTop.style.width=Ma [...]
+"px",this.eTop.innerHTML=d+" "+f)}},CLASS_NAME:"OpenLayers.Control.ScaleLine"});OpenLayers.Icon=OpenLayers.Class({url:null,size:null,offset:null,calculateOffset:null,imageDiv:null,px:null,initialize:function(a,b,c,d){this.url=a;this.size=b||{w:20,h:20};this.offset=c||{x:-(this.size.w/2),y:-(this.size.h/2)};this.calculateOffset=d;a=OpenLayers.Util.createUniqueID("OL_Icon_");this.imageDiv=OpenLayers.Util.createAlphaImageDiv(a)},destroy:function(){this.erase();OpenLayers.Event.stopObserving [...]
+this.size,this.offset,this.calculateOffset)},setSize:function(a){null!=a&&(this.size=a);this.draw()},setUrl:function(a){null!=a&&(this.url=a);this.draw()},draw:function(a){OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,null,this.size,this.url,"absolute");this.moveTo(a);return this.imageDiv},erase:function(){null!=this.imageDiv&&null!=this.imageDiv.parentNode&&OpenLayers.Element.remove(this.imageDiv)},setOpacity:function(a){OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,nu [...]
+null,null,null,a)},moveTo:function(a){null!=a&&(this.px=a);null!=this.imageDiv&&(null==this.px?this.display(!1):(this.calculateOffset&&(this.offset=this.calculateOffset(this.size)),OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,{x:this.px.x+this.offset.x,y:this.px.y+this.offset.y})))},display:function(a){this.imageDiv.style.display=a?"":"none"},isDrawn:function(){return this.imageDiv&&this.imageDiv.parentNode&&11!=this.imageDiv.parentNode.nodeType},CLASS_NAME:"OpenLayers.Icon"}); [...]
+draw:function(a){return this.icon.draw(a)},erase:function(){null!=this.icon&&this.icon.erase()},moveTo:function(a){null!=a&&null!=this.icon&&this.icon.moveTo(a);this.lonlat=this.map.getLonLatFromLayerPx(a)},isDrawn:function(){return this.icon&&this.icon.isDrawn()},onScreen:function(){var a=!1;this.map&&(a=this.map.getExtent().containsLonLat(this.lonlat));return a},inflate:function(a){this.icon&&this.icon.setSize({w:this.icon.size.w*a,h:this.icon.size.h*a})},setOpacity:function(a){this.ic [...]
+setUrl:function(a){this.icon.setUrl(a)},display:function(a){this.icon.display(a)},CLASS_NAME:"OpenLayers.Marker"});OpenLayers.Marker.defaultIcon=function(){return new OpenLayers.Icon(OpenLayers.Util.getImageLocation("marker.png"),{w:21,h:25},{x:-10.5,y:-25})};OpenLayers.Layer.TileCache=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,format:"image/png",serverResolutions:null,initialize:function(a,b,c,d){this.layername=c;OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a,b,{}, [...]
+[a])},getURL:function(a){var b=this.getServerResolution(),c=this.maxExtent,d=this.tileSize,e=Math.round((a.left-c.left)/(b*d.w));a=Math.round((a.bottom-c.bottom)/(b*d.h));b=null!=this.serverResolutions?OpenLayers.Util.indexOf(this.serverResolutions,b):this.map.getZoom();e=[this.layername,OpenLayers.Number.zeroPad(b,2),OpenLayers.Number.zeroPad(parseInt(e/1E6),3),OpenLayers.Number.zeroPad(parseInt(e/1E3)%1E3,3),OpenLayers.Number.zeroPad(parseInt(e)%1E3,3),OpenLayers.Number.zeroPad(parseIn [...]
+3),OpenLayers.Number.zeroPad(parseInt(a/1E3)%1E3,3),OpenLayers.Number.zeroPad(parseInt(a)%1E3,3)+"."+this.extension].join("/");b=this.url;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(e,b));b="/"==b.charAt(b.length-1)?b:b+"/";return b+e},CLASS_NAME:"OpenLayers.Layer.TileCache"});OpenLayers.Strategy.Paging=OpenLayers.Class(OpenLayers.Strategy,{features:null,length:10,num:null,paging:!1,activate:function(){var a=OpenLayers.Strategy.prototype.activate.call(this);if(a)this.layer.events.on({b [...]
+this.features=a.features,this.pageNext(a))},clearCache:function(){if(this.features)for(var a=0;a<this.features.length;++a)this.features[a].destroy();this.num=this.features=null},pageCount:function(){return Math.ceil((this.features?this.features.length:0)/this.length)},pageNum:function(){return this.num},pageLength:function(a){a&&0<a&&(this.length=a);return this.length},pageNext:function(a){var b=!1;this.features&&(null===this.num&&(this.num=-1),b=this.page((this.num+1)*this.length,a));re [...]
+!1;this.features&&(null===this.num&&(this.num=this.pageCount()),a=this.page((this.num-1)*this.length));return a},page:function(a,b){var c=!1;if(this.features&&0<=a&&a<this.features.length){var d=Math.floor(a/this.length);d!=this.num&&(this.paging=!0,c=this.features.slice(a,a+this.length),this.layer.removeFeatures(this.layer.features),this.num=d,b&&b.features?b.features=c:this.layer.addFeatures(c),this.paging=!1,c=!0)}return c},CLASS_NAME:"OpenLayers.Strategy.Paging"});OpenLayers.Control. [...]
+up:this.upFeature,out:this.cancel,done:this.doneDragging},this.dragCallbacks),{documentDrag:this.documentDrag}),feature:new OpenLayers.Handler.Feature(this,this.layer,OpenLayers.Util.extend({click:this.clickFeature,clickout:this.clickoutFeature,over:this.overFeature,out:this.outFeature},this.featureCallbacks),{geometryTypes:this.geometryTypes})}},clickFeature:function(a){this.handlers.feature.touch&&(!this.over&&this.overFeature(a))&&(this.handlers.drag.dragstart(this.handlers.feature.ev [...]
+!1)},clickoutFeature:function(a){this.handlers.feature.touch&&this.over&&(this.outFeature(a),this.handlers.drag.stopDown=!0)},destroy:function(){this.layer=null;OpenLayers.Control.prototype.destroy.apply(this,[])},activate:function(){return this.handlers.feature.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){this.handlers.drag.deactivate();this.handlers.feature.deactivate();this.feature=null;this.dragging=!1;this.lastPixel=null;OpenLayers.E [...]
+this.displayClass+"Over");return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},overFeature:function(a){var b=!1;this.handlers.drag.dragging?this.over=this.feature.id==a.id?!0:!1:(this.feature=a,this.handlers.drag.activate(),this.over=b=!0,OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass+"Over"),this.onEnter(a));return b},downFeature:function(a){this.lastPixel=a;this.onStart(this.feature,a)},moveFeature:function(a){var b=this.map.getResolution();this.fea [...]
+(a.x-this.lastPixel.x),b*(this.lastPixel.y-a.y));this.layer.drawFeature(this.feature);this.lastPixel=a;this.onDrag(this.feature,a)},upFeature:function(a){this.over||this.handlers.drag.deactivate()},doneDragging:function(a){this.onComplete(this.feature,a)},outFeature:function(a){this.handlers.drag.dragging?this.feature.id==a.id&&(this.over=!1):(this.over=!1,this.handlers.drag.deactivate(),OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass+"Over"),this.onLeave(a),this.fe [...]
+cancel:function(){this.handlers.drag.deactivate();this.over=!1},setMap:function(a){this.handlers.drag.setMap(a);this.handlers.feature.setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},CLASS_NAME:"OpenLayers.Control.DragFeature"});OpenLayers.Control.TransformFeature=OpenLayers.Class(OpenLayers.Control,{geometryTypes:null,layer:null,preserveAspectRatio:!1,rotate:!0,feature:null,renderIntent:"temporary",rotationHandleSymbolizer:null,box:null,center:null,scale:1,ratio:1,ro [...]
+cursor:"pointer"});this.createBox();this.createControl()},activate:function(){var a=!1;OpenLayers.Control.prototype.activate.apply(this,arguments)&&(this.dragControl.activate(),this.layer.addFeatures([this.box]),this.rotate&&this.layer.addFeatures(this.rotationHandles),this.layer.addFeatures(this.handles),a=!0);return a},deactivate:function(){var a=!1;OpenLayers.Control.prototype.deactivate.apply(this,arguments)&&(this.layer.removeFeatures(this.handles),this.rotate&&this.layer.removeFeat [...]
+this.layer.removeFeatures([this.box]),this.dragControl.deactivate(),a=!0);return a},setMap:function(a){this.dragControl.setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},setFeature:function(a,b){b=OpenLayers.Util.applyDefaults(b,{rotation:0,scale:1,ratio:1});var c=this.rotation,d=this.center;OpenLayers.Util.extend(this,b);if(!1!==this.events.triggerEvent("beforesetfeature",{feature:a})){this.feature=a;this.activate();this._setfeature=!0;var e=this.feature.geometry.getB [...]
+this.box.geometry.rotate(-c,d);this._angle=0;this.rotation?(c=a.geometry.clone(),c.rotate(-this.rotation,this.center),c=new OpenLayers.Feature.Vector(c.getBounds().toGeometry()),c.geometry.rotate(this.rotation,this.center),this.box.geometry.rotate(this.rotation,this.center),this.box.move(c.geometry.getBounds().getCenterLonLat()),c=c.geometry.components[0].components[0].getBounds().getCenterLonLat()):c=new OpenLayers.LonLat(e.left,e.bottom);this.handles[0].move(c);delete this._setfeature; [...]
+{feature:a})}},unsetFeature:function(){this.active?this.deactivate():(this.feature=null,this.rotation=0,this.ratio=this.scale=1)},createBox:function(){var a=this;this.center=new OpenLayers.Geometry.Point(0,0);this.box=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([new OpenLayers.Geometry.Point(-1,-1),new OpenLayers.Geometry.Point(0,-1),new OpenLayers.Geometry.Point(1,-1),new OpenLayers.Geometry.Point(1,0),new OpenLayers.Geometry.Point(1,1),new OpenLayers.Geometry.Point [...]
+1),new OpenLayers.Geometry.Point(-1,0),new OpenLayers.Geometry.Point(-1,-1)]),null,"string"==typeof this.renderIntent?null:this.renderIntent);this.box.geometry.move=function(b,c){a._moving=!0;OpenLayers.Geometry.LineString.prototype.move.apply(this,arguments);a.center.move(b,c);delete a._moving};for(var b=function(a,b){OpenLayers.Geometry.Point.prototype.move.apply(this,arguments);this._rotationHandle&&this._rotationHandle.geometry.move(a,b);this._handle.geometry.move(a,b)},c=function(a, [...]
+arguments);this._rotationHandle&&this._rotationHandle.geometry.resize(a,b,c);this._handle.geometry.resize(a,b,c)},d=function(a,b){OpenLayers.Geometry.Point.prototype.rotate.apply(this,arguments);this._rotationHandle&&this._rotationHandle.geometry.rotate(a,b);this._handle.geometry.rotate(a,b)},e=function(b,c){var d=this.x,e=this.y;OpenLayers.Geometry.Point.prototype.move.call(this,b,c);if(!a._moving){var f=a.dragControl.handlers.drag.evt,g=!(!a._setfeature&&a.preserveAspectRatio)&&!(f&&f. [...]
+h=new OpenLayers.Geometry.Point(d,e),f=a.center;this.rotate(-a.rotation,f);h.rotate(-a.rotation,f);var k=this.x-f.x,l=this.y-f.y,m=k-(this.x-h.x),n=l-(this.y-h.y);a.irregular&&!a._setfeature&&(k-=(this.x-h.x)/2,l-=(this.y-h.y)/2);this.x=d;this.y=e;h=1;g?(l=1E-5>Math.abs(n)?1:l/n,h=(1E-5>Math.abs(m)?1:k/m)/l):(m=Math.sqrt(m*m+n*n),l=Math.sqrt(k*k+l*l)/m);a._moving=!0;a.box.geometry.rotate(-a.rotation,f);delete a._moving;a.box.geometry.resize(l,f,h);a.box.geometry.rotate(a.rotation,f);a.tr [...]
+ratio:h});a.irregular&&!a._setfeature&&(k=f.clone(),k.x+=1E-5>Math.abs(d-f.x)?0:this.x-d,k.y+=1E-5>Math.abs(e-f.y)?0:this.y-e,a.box.geometry.move(this.x-d,this.y-e),a.transformFeature({center:k}))}},f=function(b,c){var d=this.x,e=this.y;OpenLayers.Geometry.Point.prototype.move.call(this,b,c);if(!a._moving){var f=a.dragControl.handlers.drag.evt,f=f&&f.shiftKey?45:1,g=a.center,h=this.x-g.x,k=this.y-g.y;this.x=d;this.y=e;d=Math.atan2(k-c,h-b);d=Math.atan2(k,h)-d;d*=180/Math.PI;a._angle=(a._ [...]
+360;d=a.rotation%f;if(Math.abs(a._angle)>=f||0!==d)d=Math.round(a._angle/f)*f-d,a._angle=0,a.box.geometry.rotate(d,g),a.transformFeature({rotation:d})}},g=Array(8),h=Array(4),k,l,m,n="sw s se e ne n nw w".split(" "),p=0;8>p;++p)k=this.box.geometry.components[p],l=new OpenLayers.Feature.Vector(k.clone(),{role:n[p]+"-resize"},"string"==typeof this.renderIntent?null:this.renderIntent),0==p%2&&(m=new OpenLayers.Feature.Vector(k.clone(),{role:n[p]+"-rotate"},"string"==typeof this.rotationHand [...]
+null:this.rotationHandleSymbolizer),m.geometry.move=f,k._rotationHandle=m,h[p/2]=m),k.move=b,k.resize=c,k.rotate=d,l.geometry.move=e,k._handle=l,g[p]=l;this.rotationHandles=h;this.handles=g},createControl:function(){var a=this;this.dragControl=new OpenLayers.Control.DragFeature(this.layer,{documentDrag:!0,moveFeature:function(b){this.feature===a.feature&&(this.feature=a.box);OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this,arguments)},onDrag:function(b,c){b===a.box&&a.tran [...]
+onStart:function(b,c){var d=!a.geometryTypes||-1!==OpenLayers.Util.indexOf(a.geometryTypes,b.geometry.CLASS_NAME),e=OpenLayers.Util.indexOf(a.handles,b),e=e+OpenLayers.Util.indexOf(a.rotationHandles,b);b!==a.feature&&(b!==a.box&&-2==e&&d)&&a.setFeature(b)},onComplete:function(b,c){a.events.triggerEvent("transformcomplete",{feature:a.feature})}})},drawHandles:function(){for(var a=this.layer,b=0;8>b;++b)this.rotate&&0===b%2&&a.drawFeature(this.rotationHandles[b/2],this.rotationHandleSymbol [...]
+this.renderIntent)},transformFeature:function(a){if(!this._setfeature){this.scale*=a.scale||1;this.ratio*=a.ratio||1;var b=this.rotation;this.rotation=(this.rotation+(a.rotation||0))%360;if(!1!==this.events.triggerEvent("beforetransform",a)){var c=this.feature,d=c.geometry,e=this.center;d.rotate(-b,e);a.scale||a.ratio?d.resize(a.scale,e,a.ratio):a.center&&c.move(a.center.getBounds().getCenterLonLat());d.rotate(this.rotation,e);this.layer.drawFeature(c);c.toState(OpenLayers.State.UPDATE); [...]
+a)}}this.layer.drawFeature(this.box,this.renderIntent);this.drawHandles()},destroy:function(){for(var a,b=0;8>b;++b)a=this.box.geometry.components[b],a._handle.destroy(),a._handle=null,a._rotationHandle&&a._rotationHandle.destroy(),a._rotationHandle=null;this.rotationHandles=this.rotationHandleSymbolizer=this.handles=this.feature=this.center=null;this.box.destroy();this.layer=this.box=null;this.dragControl.destroy();this.dragControl=null;OpenLayers.Control.prototype.destroy.apply(this,ar [...]
+CLASS_NAME:"OpenLayers.Control.TransformFeature"});OpenLayers.Handler.Box=OpenLayers.Class(OpenLayers.Handler,{dragHandler:null,boxDivClassName:"olHandlerBoxZoomBox",boxOffsets:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.dragHandler=new OpenLayers.Handler.Drag(this,{down:this.startBox,move:this.moveBox,out:this.removeBox,up:this.endBox},{keyMask:this.keyMask})},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,argume [...]
+null)},setMap:function(a){OpenLayers.Handler.prototype.setMap.apply(this,arguments);this.dragHandler&&this.dragHandler.setMap(a)},startBox:function(a){this.callback("start",[]);this.zoomBox=OpenLayers.Util.createDiv("zoomBox",{x:-9999,y:-9999});this.zoomBox.className=this.boxDivClassName;this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE.Popup-1;this.map.viewPortDiv.appendChild(this.zoomBox);OpenLayers.Element.addClass(this.map.viewPortDiv,"olDrawBox")},moveBox:function(a){var b=this.dragHa [...]
+c=this.dragHandler.start.y,d=Math.abs(b-a.x),e=Math.abs(c-a.y),f=this.getBoxOffsets();this.zoomBox.style.width=d+f.width+1+"px";this.zoomBox.style.height=e+f.height+1+"px";this.zoomBox.style.left=(a.x<b?b-d-f.left:b-f.left)+"px";this.zoomBox.style.top=(a.y<c?c-e-f.top:c-f.top)+"px"},endBox:function(a){var b;if(5<Math.abs(this.dragHandler.start.x-a.x)||5<Math.abs(this.dragHandler.start.y-a.y)){var c=this.dragHandler.start;b=Math.min(c.y,a.y);var d=Math.max(c.y,a.y),e=Math.min(c.x,a.x);a=M [...]
+a.x);b=new OpenLayers.Bounds(e,d,a,b)}else b=this.dragHandler.start.clone();this.removeBox();this.callback("done",[b])},removeBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.boxOffsets=this.zoomBox=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDrawBox")},activate:function(){return OpenLayers.Handler.prototype.activate.apply(this,arguments)?(this.dragHandler.activate(),!0):!1},deactivate:function(){return OpenLayers.Handler.prototype.deactivate.apply(this, [...]
+(this.dragHandler.deactivate()&&this.zoomBox&&this.removeBox(),!0):!1},getBoxOffsets:function(){if(!this.boxOffsets){var a=document.createElement("div");a.style.position="absolute";a.style.border="1px solid black";a.style.width="3px";document.body.appendChild(a);var b=3==a.clientWidth;document.body.removeChild(a);var a=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-left-width")),c=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-right-width")),d=parseInt(OpenLayers.El [...]
+"border-top-width")),e=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-bottom-width"));this.boxOffsets={left:a,right:c,top:d,bottom:e,width:!1===b?a+c:0,height:!1===b?d+e:0}}return this.boxOffsets},CLASS_NAME:"OpenLayers.Handler.Box"});OpenLayers.Control.ZoomBox=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,out:!1,keyMask:null,alwaysZoom:!1,zoomOnClick:!0,draw:function(){this.handler=new OpenLayers.Handler.Box(this,{done:this.zoomBox},{keyMask:this.keyM [...]
+2*b;a=e.lon+d.getWidth()/2*b;var g=e.lat-d.getHeight()/2*b;b=e.lat+d.getHeight()/2*b;b=new OpenLayers.Bounds(f,g,a,b)}else f=this.map.getLonLatFromPixel({x:a.left,y:a.bottom}),a=this.map.getLonLatFromPixel({x:a.right,y:a.top}),b=new OpenLayers.Bounds(f.lon,f.lat,a.lon,a.lat);f=this.map.getZoom();g=this.map.getSize();a=g.w/2;g=g.h/2;b=this.map.getZoomForExtent(b);d=this.map.getResolution();e=this.map.getResolutionForZoom(b);d==e?this.map.setCenter(this.map.getLonLatFromPixel(c)):this.map. [...]
+{x:(d*c.x-e*a)/(d-e),y:(d*c.y-e*g)/(d-e)});f==this.map.getZoom()&&!0==this.alwaysZoom&&this.map.zoomTo(f+(this.out?-1:1))}else this.zoomOnClick&&(this.out?this.map.zoomTo(this.map.getZoom()-1,a):this.map.zoomTo(this.map.getZoom()+1,a))},CLASS_NAME:"OpenLayers.Control.ZoomBox"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:!1,interval:0,documentDrag:!1,kinetic:null,enableKinetic:!0,kineticInterval:10,draw:function(){if(this.enabl [...]
+{interval:this.interval,documentDrag:this.documentDrag})},panMapStart:function(){this.kinetic&&this.kinetic.begin()},panMap:function(a){this.kinetic&&this.kinetic.update(a);this.panned=!0;this.map.pan(this.handler.last.x-a.x,this.handler.last.y-a.y,{dragging:!0,animate:!1})},panMapDone:function(a){if(this.panned){var b=null;this.kinetic&&(b=this.kinetic.end(a));this.map.pan(this.handler.last.x-a.x,this.handler.last.y-a.y,{dragging:!!b,animate:!1});if(b){var c=this;this.kinetic.move(b,fun [...]
+f){c.map.pan(a,b,{dragging:!f,animate:!1})})}this.panned=!1}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.Control.Navigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,pinchZoom:null,pinchZoomOptions:null,documentDrag:!1,zoomBox:null,zoomBoxEnabled:!0,zoomWheelEnabled:!0,mouseWheelOptions:null,handleRightClicks:!1,zoomBoxKeyMask:OpenLayers.Handler.MOD_SHIFT,autoActivate:!0,initialize:function(a){this.handlers={};OpenLayers.Control.prototype.initialize. [...]
+this.zoomBox&&this.zoomBox.destroy();this.zoomBox=null;this.pinchZoom&&this.pinchZoom.destroy();this.pinchZoom=null;OpenLayers.Control.prototype.destroy.apply(this,arguments)},activate:function(){this.dragPan.activate();this.zoomWheelEnabled&&this.handlers.wheel.activate();this.handlers.click.activate();this.zoomBoxEnabled&&this.zoomBox.activate();this.pinchZoom&&this.pinchZoom.activate();return OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){this.pinch [...]
+this.zoomBox.deactivate();this.dragPan.deactivate();this.handlers.click.deactivate();this.handlers.wheel.deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},draw:function(){this.handleRightClicks&&(this.map.viewPortDiv.oncontextmenu=OpenLayers.Function.False);this.handlers.click=new OpenLayers.Handler.Click(this,{click:this.defaultClick,dblclick:this.defaultDblClick,dblrightclick:this.defaultDblRightClick},{"double":!0,stopDouble:!0});this.dragPan=new OpenL [...]
+documentDrag:this.documentDrag},this.dragPanOptions));this.zoomBox=new OpenLayers.Control.ZoomBox({map:this.map,keyMask:this.zoomBoxKeyMask});this.dragPan.draw();this.zoomBox.draw();this.handlers.wheel=new OpenLayers.Handler.MouseWheel(this,{up:this.wheelUp,down:this.wheelDown},OpenLayers.Util.extend(this.map.fractionalZoom?{}:{cumulative:!1,interval:50,maxDelta:6},this.mouseWheelOptions));OpenLayers.Control.PinchZoom&&(this.pinchZoom=new OpenLayers.Control.PinchZoom(OpenLayers.Util.exte [...]
+this.pinchZoomOptions)))},defaultClick:function(a){a.lastTouches&&2==a.lastTouches.length&&this.map.zoomOut()},defaultDblClick:function(a){this.map.zoomTo(this.map.zoom+1,a.xy)},defaultDblRightClick:function(a){this.map.zoomTo(this.map.zoom-1,a.xy)},wheelChange:function(a,b){this.map.fractionalZoom||(b=Math.round(b));var c=this.map.getZoom(),d;d=Math.max(c+b,0);d=Math.min(d,this.map.getNumZoomLevels());d!==c&&this.map.zoomTo(d,a.xy)},wheelUp:function(a,b){this.wheelChange(a,b||1)},wheelD [...]
+b){this.wheelChange(a,b||-1)},disableZoomBox:function(){this.zoomBoxEnabled=!1;this.zoomBox.deactivate()},enableZoomBox:function(){this.zoomBoxEnabled=!0;this.active&&this.zoomBox.activate()},disableZoomWheel:function(){this.zoomWheelEnabled=!1;this.handlers.wheel.deactivate()},enableZoomWheel:function(){this.zoomWheelEnabled=!0;this.active&&this.handlers.wheel.activate()},CLASS_NAME:"OpenLayers.Control.Navigation"});OpenLayers.Control.DrawFeature=OpenLayers.Class(OpenLayers.Control,{lay [...]
+this.handlerOptions||{};this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{renderers:a.renderers,rendererOptions:a.rendererOptions});"multi"in this.handlerOptions||(this.handlerOptions.multi=this.multi);if(a=this.layer.styleMap&&this.layer.styleMap.styles.temporary)this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":a})});this.handler=new b(this,this.call [...]
+drawFeature:function(a){a=new OpenLayers.Feature.Vector(a);!1!==this.layer.events.triggerEvent("sketchcomplete",{feature:a})&&(a.state=OpenLayers.State.INSERT,this.layer.addFeatures([a]),this.featureAdded(a),this.events.triggerEvent("featureadded",{feature:a}))},insertXY:function(a,b){this.handler&&this.handler.line&&this.handler.insertXY(a,b)},insertDeltaXY:function(a,b){this.handler&&this.handler.line&&this.handler.insertDeltaXY(a,b)},insertDirectionLength:function(a,b){this.handler&&t [...]
+this.handler.insertDirectionLength(a,b)},insertDeflectionLength:function(a,b){this.handler&&this.handler.line&&this.handler.insertDeflectionLength(a,b)},undo:function(){return this.handler.undo&&this.handler.undo()},redo:function(){return this.handler.redo&&this.handler.redo()},finishSketch:function(){this.handler.finishGeometry()},cancel:function(){this.handler.cancel()},CLASS_NAME:"OpenLayers.Control.DrawFeature"});OpenLayers.Handler.Polygon=OpenLayers.Class(OpenLayers.Handler.Path,{ho [...]
+this.getSketch()]);this.point.geometry.clearBounds();this.layer.addFeatures([this.polygon,this.point],{silent:!0})},addPoint:function(a){if(!this.drawingHole&&this.holeModifier&&this.evt&&this.evt[this.holeModifier])for(var b=this.point.geometry,c=this.control.layer.features,d,e=c.length-1;0<=e;--e)if(d=c[e].geometry,(d instanceof OpenLayers.Geometry.Polygon||d instanceof OpenLayers.Geometry.MultiPolygon)&&d.intersects(b)){b=c[e];this.control.layer.removeFeatures([b],{silent:!0});this.co [...]
+this,this.finalizeInteriorRing);this.control.layer.events.registerPriority("sketchmodified",this,this.enforceTopology);b.geometry.addComponent(this.line.geometry);this.polygon=b;this.drawingHole=!0;break}OpenLayers.Handler.Path.prototype.addPoint.apply(this,arguments)},getCurrentPointIndex:function(){return this.line.geometry.components.length-2},enforceTopology:function(a){a=a.vertex;var b=this.line.geometry.components;this.polygon.geometry.intersects(a)||(b=b[b.length-3],a.x=b.x,a.y=b. [...]
+2]);this.removePoint();this.finalize()},finalizeInteriorRing:function(){var a=this.line.geometry,b=0!==a.getArea();if(b){for(var c=this.polygon.geometry.components,d=c.length-2;0<=d;--d)if(a.intersects(c[d])){b=!1;break}if(b)a:for(d=c.length-2;0<d;--d)for(var e=c[d].components,f=0,g=e.length;f<g;++f)if(a.containsPoint(e[f])){b=!1;break a}}b?this.polygon.state!==OpenLayers.State.INSERT&&(this.polygon.state=OpenLayers.State.UPDATE):this.polygon.geometry.removeComponent(a);this.restoreFeatu [...]
+cancel:function(){this.drawingHole&&(this.polygon.geometry.removeComponent(this.line.geometry),this.restoreFeature(!0));return OpenLayers.Handler.Path.prototype.cancel.apply(this,arguments)},restoreFeature:function(a){this.control.layer.events.unregister("sketchcomplete",this,this.finalizeInteriorRing);this.control.layer.events.unregister("sketchmodified",this,this.enforceTopology);this.layer.removeFeatures([this.polygon],{silent:!0});this.control.layer.addFeatures([this.polygon],{silent [...]
+!1;a||this.control.layer.events.triggerEvent("sketchcomplete",{feature:this.polygon})},destroyFeature:function(a){OpenLayers.Handler.Path.prototype.destroyFeature.call(this,a);this.polygon=null},drawFeature:function(){this.layer.drawFeature(this.polygon,this.style);this.layer.drawFeature(this.point,this.style)},getSketch:function(){return this.polygon},getGeometry:function(){var a=this.polygon&&this.polygon.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiPolygon([a]));return a},C [...]
+new OpenLayers.Control.DrawFeature(a,OpenLayers.Handler.Polygon,{displayClass:"olControlDrawFeaturePolygon",handlerOptions:{citeCompliant:this.citeCompliant}})];this.addControls(c)},draw:function(){var a=OpenLayers.Control.Panel.prototype.draw.apply(this,arguments);null===this.defaultControl&&(this.defaultControl=this.controls[0]);return a},CLASS_NAME:"OpenLayers.Control.EditingToolbar"});OpenLayers.Strategy.BBOX=OpenLayers.Class(OpenLayers.Strategy,{bounds:null,resolution:null,ratio:2,r [...]
+scope:this});return a},update:function(a){var b=this.getMapBounds();null!==b&&(a&&a.force||this.layer.visibility&&this.layer.calculateInRange()&&this.invalidBounds(b))&&(this.calculateBounds(b),this.resolution=this.layer.map.getResolution(),this.triggerRead(a))},getMapBounds:function(){if(null===this.layer.map)return null;var a=this.layer.map.getExtent();a&&!this.layer.projection.equals(this.layer.map.getProjectionObject())&&(a=a.clone().transform(this.layer.map.getProjectionObject(),thi [...]
+return a},invalidBounds:function(a){a||(a=this.getMapBounds());a=!this.bounds||!this.bounds.containsBounds(a);!a&&this.resFactor&&(a=this.resolution/this.layer.map.getResolution(),a=a>=this.resFactor||a<=1/this.resFactor);return a},calculateBounds:function(a){a||(a=this.getMapBounds());var b=a.getCenterLonLat(),c=a.getWidth()*this.ratio;a=a.getHeight()*this.ratio;this.bounds=new OpenLayers.Bounds(b.lon-c/2,b.lat-a/2,b.lon+c/2,b.lat+a/2)},triggerRead:function(a){!this.response||a&&!0===a. [...]
+(this.layer.protocol.abort(this.response),this.layer.events.triggerEvent("loadend"));var b={filter:this.createFilter()};this.layer.events.triggerEvent("loadstart",b);this.response=this.layer.protocol.read(OpenLayers.Util.applyDefaults({filter:b.filter,callback:this.merge,scope:this},a))},createFilter:function(){var a=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,value:this.bounds,projection:this.layer.projection});this.layer.filter&&(a=new OpenLayers.Filter.Logical({ [...]
+filters:[this.layer.filter,a]}));return a},merge:function(a){this.layer.destroyFeatures();if(a.success()){var b=a.features;if(b&&0<b.length){var c=this.layer.projection,d=this.layer.map.getProjectionObject();if(!d.equals(c))for(var e,f=0,g=b.length;f<g;++f)(e=b[f].geometry)&&e.transform(c,d);this.layer.addFeatures(b)}}else this.bounds=null;this.response=null;this.layer.events.triggerEvent("loadend",{response:a})},CLASS_NAME:"OpenLayers.Strategy.BBOX"});OpenLayers.Layer.WorldWind=OpenLaye [...]
+var b=this.getZoom(),c=this.map.getMaxExtent(),d=this.lzd/Math.pow(2,this.getZoom()),e=Math.floor((a.left-c.left)/d);a=Math.floor((a.bottom-c.bottom)/d);return this.map.getResolution()<=this.lzd/512&&this.getZoom()<=this.zoomLevels?this.getFullRequestString({L:b,X:e,Y:a}):OpenLayers.Util.getImageLocation("blank.gif")},CLASS_NAME:"OpenLayers.Layer.WorldWind"});OpenLayers.Protocol.CSW=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Protocol.CSW.DEFAULTS);var b=OpenLayers.Protocol. [...]
+1<=d.tileMatrixSetLinks.length&&(h=c.tileMatrixSets[d.tileMatrixSetLinks[0].tileMatrixSet]);if(!h)throw Error("matrixSet not found");for(var k,e=0,f=d.styles.length;e<f&&(k=d.styles[e],!k.isDefault);++e);c=b.requestEncoding;if(!c&&(c="KVP",a.operationsMetadata.GetTile.dcp.http)){var l=a.operationsMetadata.GetTile.dcp.http;l.get[0].constraints&&(l=l.get[0].constraints.GetEncoding.allowedValues,l.KVP||!l.REST&&!l.RESTful||(c="REST"))}var l=[],m=b.params||{};delete b.params;for(var n=0,p=d. [...]
+p;n++){var q=d.dimensions[n];l.push(q.identifier);m.hasOwnProperty(q.identifier)||(m[q.identifier]=q["default"])}var n=b.projection||h.supportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"),p=b.units||("EPSG:4326"===n?"degrees":"m"),q=[],r;for(r in h.matrixIds)h.matrixIds.hasOwnProperty(r)&&q.push(2.8E-4*h.matrixIds[r].scaleDenominator/OpenLayers.METERS_PER_INCH/OpenLayers.INCHES_PER_UNIT[p]);if("REST"===c&&d.resourceUrls){r=[];for(var f=0,s=d.resourceUrls.length;f<s;++f)e=d. [...]
+e.format===g&&"tile"===e.resourceType&&r.push(e.template)}else{s=a.operationsMetadata.GetTile.dcp.http.get;r=[];for(var t,e=0,f=s.length;e<f;e++)t=s[e].constraints,(!t||t&&t.GetEncoding.allowedValues[c])&&r.push(s[e].url)}return new OpenLayers.Layer.WMTS(OpenLayers.Util.applyDefaults(b,{url:r,requestEncoding:c,name:d.title,style:k.identifier,format:g,matrixIds:h.matrixIds,matrixSet:h.identifier,projection:n,units:p,resolutions:!1===b.isBaseLayer?void 0:q,serverResolutions:q,tileFullExten [...]
+dimensions:l,params:m}))},CLASS_NAME:"OpenLayers.Format.WMTSCapabilities"});OpenLayers.Layer.Google.v3={DEFAULTS:{sphericalMercator:!0,projection:"EPSG:900913"},animationEnabled:!0,loadMapObject:function(){this.type||(this.type=google.maps.MapTypeId.ROADMAP);var a,b=OpenLayers.Layer.Google.cache[this.map.id];b?(a=b.mapObject,++b.count):(a=this.map.getCenter(),b=document.createElement("div"),b.className="olForeignContainer",b.style.width="100%",b.style.height="100%",a=new google.maps.Map( [...]
+0,mapTypeId:this.type,disableDefaultUI:!0,keyboardShortcuts:!1,draggable:!1,disableDoubleClickZoom:!0,scrollwheel:!1,streetViewControl:!1}),b=document.createElement("div"),b.style.width="100%",b.style.height="100%",a.controls[google.maps.ControlPosition.TOP_LEFT].push(b),b={googleControl:b,mapObject:a,count:1},OpenLayers.Layer.Google.cache[this.map.id]=b);this.mapObject=a;this.setGMapVisibility(this.visibility)},onMapResize:function(){this.visibility&&google.maps.event.trigger(this.mapOb [...]
+setGMapVisibility:function(a){var b=OpenLayers.Layer.Google.cache[this.map.id],c=this.map;if(b){for(var d=this.type,e=c.layers,f,g=e.length-1;0<=g;--g)if(f=e[g],f instanceof OpenLayers.Layer.Google&&!0===f.visibility&&!0===f.inRange){d=f.type;a=!0;break}e=this.mapObject.getDiv();if(!0===a){if(e.parentNode!==c.div)if(b.rendered)c.div.appendChild(e),b.googleControl.appendChild(c.viewPortDiv),google.maps.event.trigger(this.mapObject,"resize");else{var h=this;google.maps.event.addListenerOnc [...]
+"tilesloaded",function(){b.rendered=!0;h.setGMapVisibility(h.getVisibility());h.moveTo(h.map.getCenter())})}this.mapObject.setMapTypeId(d)}else b.googleControl.hasChildNodes()&&(c.div.appendChild(c.viewPortDiv),c.div.removeChild(e))}},getMapContainer:function(){return this.mapObject.getDiv()},getMapObjectBoundsFromOLBounds:function(a){var b=null;null!=a&&(b=this.sphericalMercator?this.inverseMercator(a.bottom,a.left):new OpenLayers.LonLat(a.bottom,a.left),a=this.sphericalMercator?this.in [...]
+a.right):new OpenLayers.LonLat(a.top,a.right),b=new google.maps.LatLngBounds(new google.maps.LatLng(b.lat,b.lon),new google.maps.LatLng(a.lat,a.lon)));return b},getMapObjectLonLatFromMapObjectPixel:function(a){var b=this.map.getSize(),c=this.getLongitudeFromMapObjectLonLat(this.mapObject.center),d=this.getLatitudeFromMapObjectLonLat(this.mapObject.center),e=this.map.getResolution();a=new OpenLayers.LonLat(c+(a.x-b.w/2)*e,d-(a.y-b.h/2)*e);this.wrapDateLine&&(a=a.wrapDateLine(this.maxExten [...]
+a.lat)},getMapObjectPixelFromMapObjectLonLat:function(a){var b=this.getLongitudeFromMapObjectLonLat(a);a=this.getLatitudeFromMapObjectLonLat(a);var c=this.map.getResolution(),d=this.map.getExtent();return this.getMapObjectPixelFromXY(1/c*(b-d.left),1/c*(d.top-a))},setMapObjectCenter:function(a,b){if(!1===this.animationEnabled&&b!=this.mapObject.zoom){var c=this.getMapContainer();google.maps.event.addListenerOnce(this.mapObject,"idle",function(){c.style.visibility=""});c.style.visibility= [...]
+zoom:b})},getMapObjectZoomFromMapObjectBounds:function(a){return this.mapObject.getBoundsZoomLevel(a)},getMapObjectLonLatFromLonLat:function(a,b){var c;this.sphericalMercator?(c=this.inverseMercator(a,b),c=new google.maps.LatLng(c.lat,c.lon)):c=new google.maps.LatLng(b,a);return c},getMapObjectPixelFromXY:function(a,b){return new google.maps.Point(a,b)}};OpenLayers.Format.WPSDescribeProcess=OpenLayers.Class(OpenLayers.Format.XML,{VERSION:"1.0.0",namespaces:{wps:"http://www.opengis.net/wp [...]
+[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},readers:{wps:{ProcessDescriptions:function(a,b){b.processDescriptions={};this.readChildNodes(a,b.processDescriptions)},ProcessDescription:function(a,b){var c={processVersion:this.getAttributeNS(a,this.namespaces.wps,"processVersion"),statusSupported:"true"===a.getAttribute("statusSupported"),storeSupported:"true"===a.getAttribute("storeSupported")};this.readChildNodes(a,c);b[c.identifier]=c},DataInputs:fu [...]
+b){b.dataInputs=[];this.readChildNodes(a,b.dataInputs)},ProcessOutputs:function(a,b){b.processOutputs=[];this.readChildNodes(a,b.processOutputs)},Output:function(a,b){var c={};this.readChildNodes(a,c);b.push(c)},ComplexOutput:function(a,b){b.complexOutput={};this.readChildNodes(a,b.complexOutput)},LiteralOutput:function(a,b){b.literalOutput={};this.readChildNodes(a,b.literalOutput)},Input:function(a,b){var c={maxOccurs:parseInt(a.getAttribute("maxOccurs")),minOccurs:parseInt(a.getAttribu [...]
+this.readChildNodes(a,c);b.push(c)},BoundingBoxData:function(a,b){b.boundingBoxData={};this.readChildNodes(a,b.boundingBoxData)},CRS:function(a,b){b.CRSs||(b.CRSs={});b.CRSs[this.getChildValue(a)]=!0},LiteralData:function(a,b){b.literalData={};this.readChildNodes(a,b.literalData)},ComplexData:function(a,b){b.complexData={};this.readChildNodes(a,b.complexData)},Default:function(a,b){b["default"]={};this.readChildNodes(a,b["default"])},Supported:function(a,b){b.supported={};this.readChildN [...]
+Format:function(a,b){var c={};this.readChildNodes(a,c);b.formats||(b.formats={});b.formats[c.mimeType]=!0},MimeType:function(a,b){b.mimeType=this.getChildValue(a)}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WPSDescribeProcess"});OpenLayers.Format.WKT=OpenLayers.Class(OpenLayers.Format,{initialize:function(a){this.regExes={typeStr:/^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,spaces:/\s+/,parenComma:/\)\s*,\s*\(/,doubleParenComma:/\)\s*\)\s*,\s*\(\s*\( [...]
+"OpenLayers.Feature.Vector"==b.CLASS_NAME)b.geometry.transform(this.externalProjection,this.internalProjection);else if(b&&"geometrycollection"!=a&&"object"==typeof b)for(a=0,c=b.length;a<c;a++)b[a].geometry.transform(this.externalProjection,this.internalProjection);return b},write:function(a){var b,c;a.constructor==Array?c=!0:(a=[a],c=!1);var d=[];c&&d.push("GEOMETRYCOLLECTION(");for(var e=0,f=a.length;e<f;++e)c&&0<e&&d.push(","),b=a[e].geometry,d.push(this.extractGeometry(b));c&&d.push [...]
+extractGeometry:function(a){var b=a.CLASS_NAME.split(".")[2].toLowerCase();if(!this.extract[b])return null;this.internalProjection&&this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));return("collection"==b?"GEOMETRYCOLLECTION":b.toUpperCase())+"("+this.extract[b].apply(this,[a])+")"},extract:{point:function(a){return a.x+" "+a.y},multipoint:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push("("+this.extract.point.apply(this, [...]
+")");return b.join(",")},linestring:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b.join(",")},multilinestring:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push("("+this.extract.linestring.apply(this,[a.components[c]])+")");return b.join(",")},polygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push("("+this.extract.linestring.apply(this,[a.components[c]])+")");return b.join(",") [...]
+[],c=0,d=a.components.length;c<d;++c)b.push("("+this.extract.polygon.apply(this,[a.components[c]])+")");return b.join(",")},collection:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extractGeometry.apply(this,[a.components[c]]));return b.join(",")}},parse:{point:function(a){a=OpenLayers.String.trim(a).split(this.regExes.spaces);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(a[0],a[1]))},multipoint:function(a){for(var b=OpenLayers.String.trim(a) [...]
+c=[],d=0,e=b.length;d<e;++d)a=b[d].replace(this.regExes.trimParens,"$1"),c.push(this.parse.point.apply(this,[a]).geometry);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPoint(c))},linestring:function(a){a=OpenLayers.String.trim(a).split(",");for(var b=[],c=0,d=a.length;c<d;++c)b.push(this.parse.point.apply(this,[a[c]]).geometry);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(b))},multilinestring:function(a){for(var b=OpenLayers.String.trim(a). [...]
+c=[],d=0,e=b.length;d<e;++d)a=b[d].replace(this.regExes.trimParens,"$1"),c.push(this.parse.linestring.apply(this,[a]).geometry);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiLineString(c))},polygon:function(a){var b;a=OpenLayers.String.trim(a).split(this.regExes.parenComma);for(var c=[],d=0,e=a.length;d<e;++d)b=a[d].replace(this.regExes.trimParens,"$1"),b=this.parse.linestring.apply(this,[b]).geometry,b=new OpenLayers.Geometry.LinearRing(b.components),c.push(b);return [...]
+multipolygon:function(a){for(var b=OpenLayers.String.trim(a).split(this.regExes.doubleParenComma),c=[],d=0,e=b.length;d<e;++d)a=b[d].replace(this.regExes.trimParens,"$1"),c.push(this.parse.polygon.apply(this,[a]).geometry);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPolygon(c))},geometrycollection:function(a){a=a.replace(/,\s*([A-Za-z])/g,"|$1");a=OpenLayers.String.trim(a).split("|");for(var b=[],c=0,d=a.length;c<d;++c)b.push(OpenLayers.Format.WKT.prototype.read.app [...]
+return b}},CLASS_NAME:"OpenLayers.Format.WKT"});OpenLayers.WPSProcess=OpenLayers.Class({client:null,server:null,identifier:null,description:null,localWPS:"http://geoserver/wps",formats:null,chained:0,executeCallbacks:null,initialize:function(a){OpenLayers.Util.extend(this,a);this.executeCallbacks=[];this.formats={"application/wkt":new OpenLayers.Format.WKT,"application/json":new OpenLayers.Format.GeoJSON}},describe:function(a){a=a||{};if(!this.description)this.client.describeProcess(this [...]
+a.callback&&a.callback.call(a.scope,this.description)},this);else if(a.callback){var b=this.description;window.setTimeout(function(){a.callback.call(a.scope,b)},0)}},configure:function(a){this.describe({callback:function(){var b=this.description,c=a.inputs,d,e,f;e=0;for(f=b.dataInputs.length;e<f;++e)d=b.dataInputs[e],this.setInputData(d,c[d.identifier]);a.callback&&a.callback.call(a.scope)},scope:this});return this},execute:function(a){this.configure({inputs:a.inputs,callback:function(){ [...]
+c=this.getOutputIndex(b.description.processOutputs,a.output);b.setResponseForm({outputIndex:c});(function e(){OpenLayers.Util.removeItem(b.executeCallbacks,e);0!==b.chained?b.executeCallbacks.push(e):OpenLayers.Request.POST({url:b.client.servers[b.server].url,data:(new OpenLayers.Format.WPSExecute).write(b.description),success:function(e){var g=b.findMimeType(b.description.processOutputs[c].complexOutput.supported.formats);e=b.formats[g].read(e.responseText);e instanceof OpenLayers.Featu [...]
+(e=[e]);a.success&&(g={},g[a.output||"result"]=e,a.success.call(a.scope,g))},scope:b})})()},scope:this})},output:function(a){return new OpenLayers.WPSProcess.ChainLink({process:this,output:a})},parseDescription:function(a){a=this.client.servers[this.server];this.description=(new OpenLayers.Format.WPSDescribeProcess).read(a.processDescription[this.identifier]).processDescriptions[this.identifier]},setInputData:function(a,b){delete a.data;delete a.reference;if(b instanceof OpenLayers.WPSPr [...]
+a.reference={method:"POST",href:b.process.server===this.server?this.localWPS:this.client.servers[b.process.server].url},b.process.describe({callback:function(){--this.chained;this.chainProcess(a,b)},scope:this});else{a.data={};var c=a.complexData;c?(c=this.findMimeType(c.supported.formats),a.data.complexData={mimeType:c,value:this.formats[c].write(this.toFeatures(b))}):a.data.literalData={value:b}}},setResponseForm:function(a){a=a||{};var b=this.description.processOutputs[a.outputIndex|| [...]
+{rawDataOutput:{identifier:b.identifier,mimeType:this.findMimeType(b.complexOutput.supported.formats,a.supportedFormats)}}},getOutputIndex:function(a,b){var c;if(b)for(var d=a.length-1;0<=d;--d){if(a[d].identifier===b){c=d;break}}else c=0;return c},chainProcess:function(a,b){var c=this.getOutputIndex(b.process.description.processOutputs,b.output);a.reference.mimeType=this.findMimeType(a.complexData.supported.formats,b.process.description.processOutputs[c].complexOutput.supported.formats) [...]
+d[a.reference.mimeType]=!0;b.process.setResponseForm({outputIndex:c,supportedFormats:d});for(a.reference.body=b.process.description;0<this.executeCallbacks.length;)this.executeCallbacks[0]()},toFeatures:function(a){var b=OpenLayers.Util.isArray(a);b||(a=[a]);for(var c=Array(a.length),d,e=0,f=a.length;e<f;++e)d=a[e],c[e]=d instanceof OpenLayers.Feature.Vector?d:new OpenLayers.Feature.Vector(d);return b?c:c[0]},findMimeType:function(a,b){b=b||this.formats;for(var c in a)if(c in b)return c} [...]
+OpenLayers.WPSProcess.ChainLink=OpenLayers.Class({process:null,output:null,initialize:function(a){OpenLayers.Util.extend(this,a)},CLASS_NAME:"OpenLayers.WPSProcess.ChainLink"});OpenLayers.WPSClient=OpenLayers.Class({servers:null,version:"1.0.0",lazy:!1,events:null,initialize:function(a){OpenLayers.Util.extend(this,a);this.events=new OpenLayers.Events(this);this.servers={};for(var b in a.servers)this.servers[b]="string"==typeof a.servers[b]?{url:a.servers[b],version:this.version,processDe [...]
+server:a,identifier:b});this.lazy||c.describe();return c},describeProcess:function(a,b,c,d){var e=this.servers[a];e.processDescription[b]?window.setTimeout(function(){c.call(d,e.processDescription[b])},0):b in e.processDescription?this.events.register("describeprocess",this,function g(a){a.identifier===b&&(this.events.unregister("describeprocess",this,g),c.call(d,a.raw))}):(e.processDescription[b]=null,OpenLayers.Request.GET({url:e.url,params:{SERVICE:"WPS",VERSION:e.version,REQUEST:"Des [...]
+IDENTIFIER:b},success:function(a){e.processDescription[b]=a.responseText;this.events.triggerEvent("describeprocess",{identifier:b,raw:a.responseText})},scope:this}))},destroy:function(){this.events.destroy();this.servers=this.events=null},CLASS_NAME:"OpenLayers.WPSClient"});OpenLayers.Format.CSWGetRecords.v2_0_2=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{csw:"http://www.opengis.net/cat/csw/2.0.2",dc:"http://purl.org/dc/elements/1.1/",dct:"http://purl.org/dc/terms/",gmd:"http://w [...]
+requestId:null,resultType:null,outputFormat:null,outputSchema:null,startPosition:null,maxRecords:null,DistributedSearch:null,ResponseHandler:null,Query:null,regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b); [...]
+readers:{csw:{GetRecordsResponse:function(a,b){b.records=[];this.readChildNodes(a,b);var c=this.getAttributeNS(a,"","version");""!=c&&(b.version=c)},RequestId:function(a,b){b.RequestId=this.getChildValue(a)},SearchStatus:function(a,b){b.SearchStatus={};var c=this.getAttributeNS(a,"","timestamp");""!=c&&(b.SearchStatus.timestamp=c)},SearchResults:function(a,b){this.readChildNodes(a,b);for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]="numberOfRecordsMatched"==c[e].name||"num [...]
+c[e].name||"nextRecord"==c[e].name?parseInt(c[e].nodeValue):c[e].nodeValue;b.SearchResults=d},SummaryRecord:function(a,b){var c={type:"SummaryRecord"};this.readChildNodes(a,c);b.records.push(c)},BriefRecord:function(a,b){var c={type:"BriefRecord"};this.readChildNodes(a,c);b.records.push(c)},DCMIRecord:function(a,b){var c={type:"DCMIRecord"};this.readChildNodes(a,c);b.records.push(c)},Record:function(a,b){var c={type:"Record"};this.readChildNodes(a,c);b.records.push(c)},"*":function(a,b){ [...]
+a.nodeName.split(":").pop();b[c]=this.getChildValue(a)}},geonet:{info:function(a,b){var c={};this.readChildNodes(a,c);b.gninfo=c}},dc:{"*":function(a,b){var c=a.localName||a.nodeName.split(":").pop();OpenLayers.Util.isArray(b[c])||(b[c]=[]);for(var d={},e=a.attributes,f=0,g=e.length;f<g;++f)d[e[f].name]=e[f].nodeValue;d.value=this.getChildValue(a);""!=d.value&&b[c].push(d)}},dct:{"*":function(a,b){var c=a.localName||a.nodeName.split(":").pop();OpenLayers.Util.isArray(b[c])||(b[c]=[]);b[c [...]
+ows:OpenLayers.Util.applyDefaults({BoundingBox:function(a,b){b.bounds&&(b.BoundingBox=[{crs:b.projection,value:[b.bounds.left,b.bounds.bottom,b.bounds.right,b.bounds.top]}],delete b.projection,delete b.bounds);OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows.BoundingBox.apply(this,arguments)}},OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows)},write:function(a){a=this.writeNode("csw:GetRecords",a);a.setAttribute("xmlns:gmd",this.namespaces.gmd);return OpenLayers.Format.XM [...]
+[a])},writers:{csw:{GetRecords:function(a){a||(a={});var b=this.createElementNSPlus("csw:GetRecords",{attributes:{service:"CSW",version:this.version,requestId:a.requestId||this.requestId,resultType:a.resultType||this.resultType,outputFormat:a.outputFormat||this.outputFormat,outputSchema:a.outputSchema||this.outputSchema,startPosition:a.startPosition||this.startPosition,maxRecords:a.maxRecords||this.maxRecords}});(a.DistributedSearch||this.DistributedSearch)&&this.writeNode("csw:Distribut [...]
+a.DistributedSearch||this.DistributedSearch,b);var c=a.ResponseHandler||this.ResponseHandler;if(OpenLayers.Util.isArray(c)&&0<c.length)for(var d=0,e=c.length;d<e;d++)this.writeNode("csw:ResponseHandler",c[d],b);this.writeNode("Query",a.Query||this.Query,b);return b},DistributedSearch:function(a){return this.createElementNSPlus("csw:DistributedSearch",{attributes:{hopCount:a.hopCount}})},ResponseHandler:function(a){return this.createElementNSPlus("csw:ResponseHandler",{value:a.value})},Qu [...]
+(a={});var b=this.createElementNSPlus("csw:Query",{attributes:{typeNames:a.typeNames||"csw:Record"}}),c=a.ElementName;if(OpenLayers.Util.isArray(c)&&0<c.length)for(var d=0,e=c.length;d<e;d++)this.writeNode("csw:ElementName",c[d],b);else this.writeNode("csw:ElementSetName",a.ElementSetName||{value:"summary"},b);a.Constraint&&this.writeNode("csw:Constraint",a.Constraint,b);a.SortBy&&this.writeNode("ogc:SortBy",a.SortBy,b);return b},ElementName:function(a){return this.createElementNSPlus("c [...]
+{value:a.value})},ElementSetName:function(a){return this.createElementNSPlus("csw:ElementSetName",{attributes:{typeNames:a.typeNames},value:a.value})},Constraint:function(a){var b=this.createElementNSPlus("csw:Constraint",{attributes:{version:a.version}});if(a.Filter){var c=new OpenLayers.Format.Filter({version:a.version});b.appendChild(c.write(a.Filter))}else a.CqlText&&(a=this.createElementNSPlus("CqlText",{value:a.CqlText.value}),b.appendChild(a));return b}},ogc:OpenLayers.Format.Filt [...]
+CLASS_NAME:"OpenLayers.Format.CSWGetRecords.v2_0_2"});/*
+ Apache 2 
+
+ Contains portions of Rico <http://openrico.org/>
+
+ Copyright 2005 Sabre Airline Solutions  
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you
+ may not use this file except in compliance with the License. You
+ may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0  
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied. See the License for the specific language governing
+ permissions and limitations under the License. 
+*/
+OpenLayers.Marker.Box=OpenLayers.Class(OpenLayers.Marker,{bounds:null,div:null,initialize:function(a,b,c){this.bounds=a;this.div=OpenLayers.Util.createDiv();this.div.style.overflow="hidden";this.events=new OpenLayers.Events(this,this.div);this.setBorder(b,c)},destroy:function(){this.div=this.bounds=null;OpenLayers.Marker.prototype.destroy.apply(this,arguments)},setBorder:function(a,b){a||(a="red");b||(b=2);this.div.style.border=b+"px solid "+a},draw:function(a,b){OpenLayers.Util.modifyDO [...]
+null,a,b);return this.div},onScreen:function(){var a=!1;this.map&&(a=this.map.getExtent().containsBounds(this.bounds,!0,!0));return a},display:function(a){this.div.style.display=a?"":"none"},CLASS_NAME:"OpenLayers.Marker.Box"});OpenLayers.Format.Text=OpenLayers.Class(OpenLayers.Format,{defaultStyle:null,extractStyles:!0,initialize:function(a){a=a||{};!1!==a.extractStyles&&(a.defaultStyle={externalGraphic:OpenLayers.Util.getImageLocation("marker.png"),graphicWidth:21,graphicHeight:25,grap [...]
+e.split("\t"),f=new OpenLayers.Geometry.Point(0,0),g={},h=this.defaultStyle?OpenLayers.Util.applyDefaults({},this.defaultStyle):null,k=!1,l=0;l<e.length;l++)if(e[l])if("point"==b[l])k=e[l].split(","),f.y=parseFloat(k[0]),f.x=parseFloat(k[1]),k=!0;else if("lat"==b[l])f.y=parseFloat(e[l]),k=!0;else if("lon"==b[l])f.x=parseFloat(e[l]),k=!0;else if("title"==b[l])g.title=e[l];else if("image"==b[l]||"icon"==b[l]&&h)h.externalGraphic=e[l];else if("iconSize"==b[l]&&h){var m=e[l].split(",");h.gra [...]
+parseFloat(m[0]);h.graphicHeight=parseFloat(m[1])}else"iconOffset"==b[l]&&h?(m=e[l].split(","),h.graphicXOffset=parseFloat(m[0]),h.graphicYOffset=parseFloat(m[1])):"description"==b[l]?g.description=e[l]:"overflow"==b[l]?g.overflow=e[l]:g[b[l]]=e[l];k&&(this.internalProjection&&this.externalProjection&&f.transform(this.externalProjection,this.internalProjection),e=new OpenLayers.Feature.Vector(f,g,h),c.push(e))}else b=e.split("\t")}return c},CLASS_NAME:"OpenLayers.Format.Text"});OpenLayer [...]
+success:this.parseData,failure:function(a){this.events.triggerEvent("loadend")},scope:this}),this.loaded=!0)},moveTo:function(a,b,c){OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);this.visibility&&!this.loaded&&this.loadText()},parseData:function(a){a=a.responseText;var b={};OpenLayers.Util.extend(b,this.formatOptions);this.map&&!this.projection.equals(this.map.getProjectionObject())&&(b.externalProjection=this.projection,b.internalProjection=this.map.getProjectionObject [...]
+for(var b=0,c=a.length;b<c;b++){var d={},e=a[b],f,g,h;f=new OpenLayers.LonLat(e.geometry.x,e.geometry.y);e.style.graphicWidth&&e.style.graphicHeight&&(g=new OpenLayers.Size(e.style.graphicWidth,e.style.graphicHeight));void 0!==e.style.graphicXOffset&&void 0!==e.style.graphicYOffset&&(h=new OpenLayers.Pixel(e.style.graphicXOffset,e.style.graphicYOffset));null!=e.style.externalGraphic?d.icon=new OpenLayers.Icon(e.style.externalGraphic,g,h):(d.icon=OpenLayers.Marker.defaultIcon(),null!=g&&d [...]
+null!=e.attributes.title&&null!=e.attributes.description&&(d.popupContentHTML="<h2>"+e.attributes.title+"</h2><p>"+e.attributes.description+"</p>");d.overflow=e.attributes.overflow||"auto";d=new OpenLayers.Feature(this,f,d);this.features.push(d);f=d.createMarker();null!=e.attributes.title&&null!=e.attributes.description&&f.events.register("click",d,this.markerClick);this.addMarker(f)}this.events.triggerEvent("loadend")},markerClick:function(a){var b=this==this.layer.selectedFeature;this. [...]
+b?null:this;for(var c=0,d=this.layer.map.popups.length;c<d;c++)this.layer.map.removePopup(this.layer.map.popups[c]);b||this.layer.map.addPopup(this.createPopup());OpenLayers.Event.stop(a)},clearFeatures:function(){if(null!=this.features)for(;0<this.features.length;){var a=this.features[0];OpenLayers.Util.removeItem(this.features,a);a.destroy()}},CLASS_NAME:"OpenLayers.Layer.Text"});OpenLayers.Handler.RegularPolygon=OpenLayers.Class(OpenLayers.Handler.Drag,{sides:4,radius:null,snapAngle:n [...]
+a);OpenLayers.Util.extend(this,a)},activate:function(){var a=!1;OpenLayers.Handler.Drag.prototype.activate.apply(this,arguments)&&(a=OpenLayers.Util.extend({displayInLayerSwitcher:!1,calculateInRange:OpenLayers.Function.True,wrapDateLine:this.citeCompliant},this.layerOptions),this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,a),this.map.addLayer(this.layer),a=!0);return a},deactivate:function(){var a=!1;OpenLayers.Handler.Drag.prototype.deactivate.apply(this,arguments)&&(this.draggi [...]
+null!=this.layer.map&&(this.layer.destroy(!1),this.feature&&this.feature.destroy()),this.feature=this.layer=null,a=!0);return a},down:function(a){this.fixedRadius=!!this.radius;a=this.layer.getLonLatFromViewPortPx(a.xy);this.origin=new OpenLayers.Geometry.Point(a.lon,a.lat);if(!this.fixedRadius||this.irregular)this.radius=this.map.getResolution();this.persist&&this.clear();this.feature=new OpenLayers.Feature.Vector;this.createGeometry();this.callback("create",[this.origin,this.feature]); [...]
+{silent:!0});this.layer.drawFeature(this.feature,this.style)},move:function(a){var b=this.layer.getLonLatFromViewPortPx(a.xy),b=new OpenLayers.Geometry.Point(b.lon,b.lat);this.irregular?(a=Math.sqrt(2)*Math.abs(b.y-this.origin.y)/2,this.radius=Math.max(this.map.getResolution()/2,a)):this.fixedRadius?this.origin=b:(this.calculateAngle(b,a),this.radius=Math.max(this.map.getResolution()/2,b.distanceTo(this.origin)));this.modifyGeometry();if(this.irregular){a=b.x-this.origin.x;var b=b.y-this [...]
+c;c=0==b?a/(this.radius*Math.sqrt(2)):a/b;this.feature.geometry.resize(1,this.origin,c);this.feature.geometry.move(a/2,b/2)}this.layer.drawFeature(this.feature,this.style)},up:function(a){this.finalize();this.start==this.last&&this.callback("done",[a.xy])},out:function(a){this.finalize()},createGeometry:function(){this.angle=Math.PI*(1/this.sides-0.5);this.snapAngle&&(this.angle+=this.snapAngle*(Math.PI/180));this.feature.geometry=OpenLayers.Geometry.Polygon.createRegularPolygon(this.ori [...]
+this.sides,this.snapAngle)},modifyGeometry:function(){var a,b,c=this.feature.geometry.components[0];c.components.length!=this.sides+1&&(this.createGeometry(),c=this.feature.geometry.components[0]);for(var d=0;d<this.sides;++d)b=c.components[d],a=this.angle+2*d*Math.PI/this.sides,b.x=this.origin.x+this.radius*Math.cos(a),b.y=this.origin.y+this.radius*Math.sin(a),b.clearBounds()},calculateAngle:function(a,b){var c=Math.atan2(a.y-this.origin.y,a.x-this.origin.x);if(this.snapAngle&&this.snap [...]
+Math.PI/180*this.snapAngle;this.angle=Math.round(c/d)*d}else this.angle=c},cancel:function(){this.callback("cancel",null);this.finalize()},finalize:function(){this.origin=null;this.radius=this.options.radius},clear:function(){this.layer&&(this.layer.renderer.clear(),this.layer.destroyFeatures())},callback:function(a,b){this.callbacks[a]&&this.callbacks[a].apply(this.control,[this.feature.geometry.clone()]);this.persist||"done"!=a&&"cancel"!=a||this.clear()},CLASS_NAME:"OpenLayers.Handler [...]
+this.handlerOptions=this.handlerOptions||{};this.layerOptions=OpenLayers.Util.applyDefaults(this.layerOptions,{displayInLayerSwitcher:!1,tileOptions:{maxGetUrlLength:2048}});this.sketchStyle&&(this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":this.sketchStyle})}));this.handler=new a(this,this.callbacks,this.handlerOptions)},destroy:function(){for(var a in this.layerCache)delete this.layerCache[a];f [...]
+OpenLayers.Control.prototype.destroy.apply(this,arguments)},coupleLayerVisiblity:function(a){this.setVisibility(a.object.getVisibility())},createSelectionLayer:function(a){var b;if(this.layerCache[a.id])b=this.layerCache[a.id];else{b=new OpenLayers.Layer.WMS(a.name,a.url,a.params,OpenLayers.Util.applyDefaults(this.layerOptions,a.getOptions()));this.layerCache[a.id]=b;if(!1===this.layerOptions.displayInLayerSwitcher)a.events.on({visibilitychanged:this.coupleLayerVisiblity,scope:b});this.m [...]
+createSLD:function(a,b,c){for(var d={version:"1.0.0",namedLayers:{}},e=(""+a.params.LAYERS).split(","),f=0,g=e.length;f<g;f++){var h=e[f];d.namedLayers[h]={name:h,userStyles:[]};var k=this.selectionSymbolizer,l=c[f];0<=l.type.indexOf("Polygon")?k={Polygon:this.selectionSymbolizer.Polygon}:0<=l.type.indexOf("LineString")?k={Line:this.selectionSymbolizer.Line}:0<=l.type.indexOf("Point")&&(k={Point:this.selectionSymbolizer.Point});d.namedLayers[h].userStyles.push({name:"default",rules:[new  [...]
+filter:b[f],maxScaleDenominator:a.options.minScale})]})}return(new OpenLayers.Format.SLD({srsName:this.map.getProjection()})).write(d)},parseDescribeLayer:function(a){var b=new OpenLayers.Format.WMSDescribeLayer,c=a.responseXML;c&&c.documentElement||(c=a.responseText);a=b.read(c);for(var b=[],c=null,d=0,e=a.length;d<e;d++)"WFS"==a[d].owsType&&(b.push(a[d].typeName),c=a[d].owsURL);OpenLayers.Request.GET({url:c,params:{SERVICE:"WFS",TYPENAME:b.toString(),REQUEST:"DescribeFeatureType",VERSI [...]
+callback:function(a){var b=new OpenLayers.Format.WFSDescribeFeatureType,c=a.responseXML;c&&c.documentElement||(c=a.responseText);a=b.read(c);this.control.wfsCache[this.layer.id]=a;this.control._queue&&this.control.applySelection()},scope:this})},getGeometryAttributes:function(a){var b=[];a=this.wfsCache[a.id];for(var c=0,d=a.featureTypes.length;c<d;c++)for(var e=a.featureTypes[c].properties,f=0,g=e.length;f<g;f++){var h=e[f],k=h.type;(0<=k.indexOf("LineString")||0<=k.indexOf("GeometryAss [...]
+0<=k.indexOf("GeometryPropertyType")||0<=k.indexOf("Point")||0<=k.indexOf("Polygon"))&&b.push(h)}return b},activate:function(){var a=OpenLayers.Control.prototype.activate.call(this);if(a)for(var b=0,c=this.layers.length;b<c;b++){var d=this.layers[b];d&&!this.wfsCache[d.id]&&OpenLayers.Request.GET({url:d.url,params:{SERVICE:"WMS",VERSION:d.params.VERSION,LAYERS:d.params.LAYERS,REQUEST:"DescribeLayer"},callback:this.parseDescribeLayer,scope:{layer:d,control:this}})}return a},deactivate:fun [...]
+OpenLayers.Control.prototype.deactivate.call(this);if(a)for(var b=0,c=this.layers.length;b<c;b++){var d=this.layers[b];if(d&&!0===this.clearOnDeactivate){var e=this.layerCache,f=e[d.id];f&&(d.events.un({visibilitychanged:this.coupleLayerVisiblity,scope:f}),f.destroy(),delete e[d.id])}}return a},setLayers:function(a){this.active?(this.deactivate(),this.layers=a,this.activate()):this.layers=a},createFilter:function(a,b){var c=null;this.handler instanceof OpenLayers.Handler.RegularPolygon?c [...]
+new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,property:a.name,value:b.getBounds()}):new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:a.name,value:b}):this.handler instanceof OpenLayers.Handler.Polygon?c=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:a.name,value:b}):this.handler instanceof OpenLayers.Handler.Path?c=0<=a.type.indexOf("Point")?new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spati [...]
+property:a.name,distance:0.01*this.map.getExtent().getWidth(),distanceUnits:this.map.getUnits(),value:b}):new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:a.name,value:b}):this.handler instanceof OpenLayers.Handler.Click&&(c=0<=a.type.indexOf("Polygon")?new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:a.name,value:b}):new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.DWITHIN,property:a.name,distance:0.01*this.m [...]
+distanceUnits:this.map.getUnits(),value:b}));return c},select:function(a){this._queue=function(){for(var b=0,c=this.layers.length;b<c;b++){for(var d=this.layers[b],e=this.getGeometryAttributes(d),f=[],g=0,h=e.length;g<h;g++){var k=e[g];if(null!==k){if(!(a instanceof OpenLayers.Geometry)){var l=this.map.getLonLatFromPixel(a.xy);a=new OpenLayers.Geometry.Point(l.lon,l.lat)}k=this.createFilter(k,a);null!==k&&f.push(k)}}g=this.createSelectionLayer(d);this.events.triggerEvent("selected",{laye [...]
+d=this.createSLD(d,f,e);g.mergeNewParams({SLD_BODY:d});delete this._queue}};this.applySelection()},applySelection:function(){for(var a=!0,b=0,c=this.layers.length;b<c;b++)if(!this.wfsCache[this.layers[b].id]){a=!1;break}a&&this._queue.call(this)},CLASS_NAME:"OpenLayers.Control.SLDSelect"});OpenLayers.Control.Scale=OpenLayers.Class(OpenLayers.Control,{element:null,geodesic:!1,initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);this.element=OpenLayers.Util.getE [...]
+if(!0===this.geodesic){if(!this.map.getUnits())return;a=OpenLayers.INCHES_PER_UNIT;a=(this.map.getGeodesicPixelSize().w||1E-6)*a.km*OpenLayers.DOTS_PER_INCH}else a=this.map.getScale();a&&(a=9500<=a&&95E4>=a?Math.round(a/1E3)+"K":95E4<=a?Math.round(a/1E6)+"M":Math.round(a),this.element.innerHTML=OpenLayers.i18n("Scale = 1 : ${scaleDenom}",{scaleDenom:a}))},CLASS_NAME:"OpenLayers.Control.Scale"});OpenLayers.Layer.MapGuide=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,useHttpTile:! [...]
+300),tileOriginCorner:"tl",initialize:function(a,b,c,d){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);if(null==d||null==d.isBaseLayer)this.isBaseLayer="true"!=this.transparent&&!0!=this.transparent;d&&null!=d.useOverlay&&(this.useOverlay=d.useOverlay);this.singleTile?this.useOverlay?(OpenLayers.Util.applyDefaults(this.params,this.OVERLAY_PARAMS),this.useAsyncOverlay||(this.params.version="1.0.0")):OpenLayers.Util.applyDefaults(this.params,this.SINGLE_TILE_PARAMS):(this [...]
+OpenLayers.Util.applyDefaults(this.params,this.FOLDER_PARAMS):OpenLayers.Util.applyDefaults(this.params,this.TILE_PARAMS),this.setTileSize(this.defaultSize))},clone:function(a){null==a&&(a=new OpenLayers.Layer.MapGuide(this.name,this.url,this.params,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){var b;b=a.getCenterLonLat();var c=this.map.getSize();this.singleTile?(a={setdisplaydpi:OpenLayers.DOTS_PER_INCH,setdisplayheight:c.h*this.r [...]
+this.ratio,setviewcenterx:b.lon,setviewcentery:b.lat,setviewscale:this.map.getScale()},this.useOverlay&&!this.useAsyncOverlay&&(b={},b=OpenLayers.Util.extend(b,a),b.operation="GETVISIBLEMAPEXTENT",b.version="1.0.0",b.session=this.params.session,b.mapName=this.params.mapName,b.format="text/xml",b=this.getFullRequestString(b),OpenLayers.Request.GET({url:b,async:!1})),b=this.getFullRequestString(a)):(c=this.map.getResolution(),b=Math.floor((a.left-this.maxExtent.left)/c),b=Math.round(b/this [...]
+a=Math.floor((this.maxExtent.top-a.top)/c),a=Math.round(a/this.tileSize.h),b=this.useHttpTile?this.getImageFilePath({tilecol:b,tilerow:a,scaleindex:this.resolutions.length-this.map.zoom-1}):this.getFullRequestString({tilecol:b,tilerow:a,scaleindex:this.resolutions.length-this.map.zoom-1}));return b},getFullRequestString:function(a,b){var c=null==b?this.url:b;"object"==typeof c&&(c=c[Math.floor(Math.random()*c.length)]);var d=c,e=OpenLayers.Util.extend({},this.params),e=OpenLayers.Util.ex [...]
+f=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(c)),g;for(g in e)g.toUpperCase()in f&&delete e[g];e=OpenLayers.Util.getParameterString(e);e=e.replace(/,/g,"+");""!=e&&(f=c.charAt(c.length-1),d="&"==f||"?"==f?d+e:-1==c.indexOf("?")?d+("?"+e):d+("&"+e));return d},getImageFilePath:function(a,b){var c=null==b?this.url:b;"object"==typeof c&&(c=c[Math.floor(Math.random()*c.length)]);var d="",e="";0>a.tilerow&&(d="-");d=0==a.tilerow?d+"0":d+Math.floor(Math.abs(a.tilerow/this.par [...]
+this.params.tileRowsPerFolder;0>a.tilecol&&(e="-");e=0==a.tilecol?e+"0":e+Math.floor(Math.abs(a.tilecol/this.params.tileColumnsPerFolder))*this.params.tileColumnsPerFolder;d="/S"+Math.floor(a.scaleindex)+"/"+this.params.basemaplayergroupname+"/R"+d+"/C"+e+"/"+a.tilerow%this.params.tileRowsPerFolder+"_"+a.tilecol%this.params.tileColumnsPerFolder+"."+this.params.format;this.params.querystring&&(d+="?"+this.params.querystring);return c+d},CLASS_NAME:"OpenLayers.Layer.MapGuide"});OpenLayers. [...]
+this.handlerOptions=OpenLayers.Util.extend({persist:this.persist},this.handlerOptions);this.handler=new a(this,this.callbacks,this.handlerOptions)},deactivate:function(){this.cancelDelay();return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},cancel:function(){this.cancelDelay();this.handler.cancel()},setImmediate:function(a){(this.immediate=a)?this.callbacks.modify=this.measureImmediate:delete this.callbacks.modify},updateHandler:function(a,b){var c=this.active;c&&this.d [...]
+this.handler=new a(this,this.callbacks,b);c&&this.activate()},measureComplete:function(a){this.cancelDelay();this.measure(a,"measure")},measurePartial:function(a,b){this.cancelDelay();b=b.clone();this.handler.freehandMode(this.handler.evt)?this.measure(b,"measurepartial"):this.delayedTrigger=window.setTimeout(OpenLayers.Function.bind(function(){this.delayedTrigger=null;this.measure(b,"measurepartial")},this),this.partialDelay)},measureImmediate:function(a,b,c){c&&!this.handler.freehandMo [...]
+(this.cancelDelay(),this.measure(b.geometry,"measurepartial"))},cancelDelay:function(){null!==this.delayedTrigger&&(window.clearTimeout(this.delayedTrigger),this.delayedTrigger=null)},measure:function(a,b){var c,d;-1<a.CLASS_NAME.indexOf("LineString")?(c=this.getBestLength(a),d=1):(c=this.getBestArea(a),d=2);this.events.triggerEvent(b,{measure:c[0],units:c[1],order:d,geometry:a})},getBestArea:function(a){for(var b=this.displaySystemUnits[this.displaySystem],c,d,e=0,f=b.length;e<f&&!(c=b[ [...]
+c),1<d);++e);return[d,c]},getArea:function(a,b){var c,d;this.geodesic?(c=a.getGeodesicArea(this.map.getProjectionObject()),d="m"):(c=a.getArea(),d=this.map.getUnits());var e=OpenLayers.INCHES_PER_UNIT[b];e&&(c*=Math.pow(OpenLayers.INCHES_PER_UNIT[d]/e,2));return c},getBestLength:function(a){for(var b=this.displaySystemUnits[this.displaySystem],c,d,e=0,f=b.length;e<f&&!(c=b[e],d=this.getLength(a,c),1<d);++e);return[d,c]},getLength:function(a,b){var c,d;this.geodesic?(c=a.getGeodesicLength [...]
+d="m"):(c=a.getLength(),d=this.map.getUnits());var e=OpenLayers.INCHES_PER_UNIT[b];e&&(c*=OpenLayers.INCHES_PER_UNIT[d]/e);return c},CLASS_NAME:"OpenLayers.Control.Measure"});OpenLayers.Format.WMC.v1_0_0=OpenLayers.Class(OpenLayers.Format.WMC.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd",initialize:function(a){OpenLayers.Format.WMC.v1.prototype.initialize.apply(this,[a])},read_wmc_SRS:function(a,b){var c=this.getC [...]
+[a]);if(a.srs){var c=[],d;for(d in a.srs)c.push(d);b.appendChild(this.createElementDefaultNS("SRS",c.join(" ")))}b.appendChild(this.write_wmc_FormatList(a));b.appendChild(this.write_wmc_StyleList(a));a.dimensions&&b.appendChild(this.write_wmc_DimensionList(a));b.appendChild(this.write_wmc_LayerExtension(a))},CLASS_NAME:"OpenLayers.Format.WMC.v1_0_0"});OpenLayers.Popup.Anchored=OpenLayers.Class(OpenLayers.Popup,{relativePosition:null,keepInMap:!0,anchor:null,initialize:function(a,b,c,d,e, [...]
+moveTo:function(a){var b=this.relativePosition;this.relativePosition=this.calculateRelativePosition(a);OpenLayers.Popup.prototype.moveTo.call(this,this.calculateNewPx(a));this.relativePosition!=b&&this.updateRelativePosition()},setSize:function(a){OpenLayers.Popup.prototype.setSize.apply(this,arguments);if(this.lonlat&&this.map){var b=this.map.getLayerPxFromLonLat(this.lonlat);this.moveTo(b)}},calculateRelativePosition:function(a){a=this.map.getLonLatFromLayerPx(a);a=this.map.getExtent() [...]
+return OpenLayers.Bounds.oppositeQuadrant(a)},updateRelativePosition:function(){},calculateNewPx:function(a){a=a.offset(this.anchor.offset);var b=this.size||this.contentSize,c="t"==this.relativePosition.charAt(0);a.y+=c?-b.h:this.anchor.size.h;c="l"==this.relativePosition.charAt(1);a.x+=c?-b.w:this.anchor.size.w;return a},CLASS_NAME:"OpenLayers.Popup.Anchored"});OpenLayers.Popup.Framed=OpenLayers.Class(OpenLayers.Popup.Anchored,{imageSrc:null,imageSize:null,isAlphaImage:!1,positionBlocks [...]
+1);this.groupDiv.style.position="absolute";this.groupDiv.style.top="0px";this.groupDiv.style.left="0px";this.groupDiv.style.height="100%";this.groupDiv.style.width="100%"},destroy:function(){this.isAlphaImage=this.imageSize=this.imageSrc=null;this.fixedRelativePosition=!1;this.positionBlocks=null;for(var a=0;a<this.blocks.length;a++){var b=this.blocks[a];b.image&&b.div.removeChild(b.image);b.image=null;b.div&&this.groupDiv.removeChild(b.div);b.div=null}this.blocks=null;OpenLayers.Popup.A [...]
+arguments)},setBackgroundColor:function(a){},setBorder:function(){},setOpacity:function(a){},setSize:function(a){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.updateBlocks()},updateRelativePosition:function(){this.padding=this.positionBlocks[this.relativePosition].padding;if(this.closeDiv){var a=this.getContentDivPadding();this.closeDiv.style.right=a.right+this.padding.right+"px";this.closeDiv.style.top=a.top+this.padding.top+"px"}this.updateBlocks()},calculateNe [...]
+OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(this,arguments);return b=b.offset(this.positionBlocks[this.relativePosition].offset)},createBlocks:function(){this.blocks=[];var a=null,b;for(b in this.positionBlocks){a=b;break}a=this.positionBlocks[a];for(b=0;b<a.blocks.length;b++){var c={};this.blocks.push(c);c.div=OpenLayers.Util.createDiv(this.id+"_FrameDecorationDiv_"+b,null,null,null,"absolute",null,"hidden",null);c.image=(this.isAlphaImage?OpenLayers.Util.createAlphaImageDi [...]
+"_FrameDecorationImg_"+b,null,this.imageSize,this.imageSrc,"absolute",null,null,null);c.div.appendChild(c.image);this.groupDiv.appendChild(c.div)}},updateBlocks:function(){this.blocks||this.createBlocks();if(this.size&&this.relativePosition){for(var a=this.positionBlocks[this.relativePosition],b=0;b<a.blocks.length;b++){var c=a.blocks[b],d=this.blocks[b],e=c.anchor.left,f=c.anchor.bottom,g=c.anchor.right,h=c.anchor.top,k=isNaN(c.size.w)?this.size.w-(g+e):c.size.w,l=isNaN(c.size.h)?this.s [...]
+h):c.size.h;d.div.style.width=(0>k?0:k)+"px";d.div.style.height=(0>l?0:l)+"px";d.div.style.left=null!=e?e+"px":"";d.div.style.bottom=null!=f?f+"px":"";d.div.style.right=null!=g?g+"px":"";d.div.style.top=null!=h?h+"px":"";d.image.style.left=c.position.x+"px";d.image.style.top=c.position.y+"px"}this.contentDiv.style.left=this.padding.left+"px";this.contentDiv.style.top=this.padding.top+"px"}},CLASS_NAME:"OpenLayers.Popup.Framed"});OpenLayers.Popup.FramedCloud=OpenLayers.Class(OpenLayers.Po [...]
+50,0,0),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,18),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-1238,-632)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(0,-688)}]},tr:{offset:new OpenLayers.Pixel(-45,0),padding:new OpenLayers.Bounds(8,40,8,9),blocks: [...]
+"auto"),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,50,0,0),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,19),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-1238,-631)},{size:new OpenLayers.Size(81,35),anchor:new OpenL [...]
+0,null,null),position:new OpenLayers.Pixel(-215,-687)}]},bl:{offset:new OpenLayers.Pixel(45,0),padding:new OpenLayers.Bounds(8,9,8,40),blocks:[{size:new OpenLayers.Size("auto","auto"),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0, [...]
+21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-1238,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(null,null,0,0),position:new OpenLayers.Pixel(-101,-674)}]},br:{offset:new OpenLayers.Pixel(-44,0),padding:new OpenLayers.Bounds(8,9,8,40),blocks:[{size:new OpenLayers.Size("auto","auto"),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,21,0 [...]
+0)},{size:new OpenLayers.Size("auto",21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-1238,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(0,null,null,0),position:new OpenLayers.Pixel(-311,-674)}]}},minSize:new OpenLayers.Size(105,10),maxSize:new OpenLayers.Size(1200,660),initialize:function(a,b,c,d,e,f,g){this.imageSrc=OpenL [...]
+OpenLayers.Popup.Framed.prototype.initialize.apply(this,arguments);this.contentDiv.className=this.contentDisplayClass},CLASS_NAME:"OpenLayers.Popup.FramedCloud"});OpenLayers.Tile.Image.IFrame={useIFrame:null,blankImageUrl:"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAQAIBRAA7",draw:function(){if(OpenLayers.Tile.Image.prototype.shouldDraw.call(this)){var a=this.layer.getURL(this.bounds),b=this.useIFrame;this.useIFrame=null!==this.maxGetUrlLength&&!this.layer.async [...]
+null,a&&this.frame.removeChild(this.frame.firstChild)}return OpenLayers.Tile.Image.prototype.draw.apply(this,arguments)},getImage:function(){if(!0===this.useIFrame){if(!this.frame.childNodes.length){var a=document.createElement("div"),b=a.style;b.position="absolute";b.width="100%";b.height="100%";b.zIndex=1;b.backgroundImage="url("+this.blankImageUrl+")";this.frame.appendChild(a)}a=this.id+"_iFrame";9>parseFloat(navigator.appVersion.split("MSIE")[1])?(b=document.createElement('<iframe na [...]
+b.style.backgroundColor="#FFFFFF",b.style.filter="chroma(color=#FFFFFF)"):(b=document.createElement("iframe"),b.style.backgroundColor="transparent",b.name=a);b.scrolling="no";b.marginWidth="0px";b.marginHeight="0px";b.frameBorder="0";b.style.position="absolute";b.style.width="100%";b.style.height="100%";1>this.layer.opacity&&OpenLayers.Util.modifyDOMElement(b,null,null,null,null,null,null,this.layer.opacity);this.frame.appendChild(b);return this.imgDiv=b}return OpenLayers.Tile.Image.prot [...]
+arguments)},createRequestForm:function(){var a=document.createElement("form");a.method="POST";var b=this.layer.params._OLSALT,b=(b?b+"_":"")+this.bounds.toBBOX();a.action=OpenLayers.Util.urlAppend(this.layer.url,b);a.target=this.id+"_iFrame";this.layer.getImageSize();var b=OpenLayers.Util.getParameters(this.url),c,d;for(d in b)c=document.createElement("input"),c.type="hidden",c.name=d,c.value=b[d],a.appendChild(c);return a},setImgSrc:function(a){if(!0===this.useIFrame)if(a){var b=this.cr [...]
+this.frame.appendChild(b);b.submit();this.frame.removeChild(b)}else this.imgDiv.parentNode===this.frame&&(this.frame.removeChild(this.imgDiv),this.imgDiv=null);else OpenLayers.Tile.Image.prototype.setImgSrc.apply(this,arguments)},onImageLoad:function(){OpenLayers.Tile.Image.prototype.onImageLoad.apply(this,arguments);!0===this.useIFrame&&(this.imgDiv.style.opacity=1,this.frame.style.opacity=this.layer.opacity)},createBackBuffer:function(){var a;!1===this.useIFrame&&(a=OpenLayers.Tile.Ima [...]
+return a}};OpenLayers.Format.SOSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",CLASS_NAME:"OpenLayers.Format.SOSCapabilities"});OpenLayers.Format.SOSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.SOSCapabilities,{namespaces:{ows:"http://www.opengis.net/ows/1.1",sos:"http://www.opengis.net/sos/1.0",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trim [...]
+[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},readers:{gml:OpenLayers.Util.applyDefaults({name:function(a,b){b.name=this.getChildValue(a)},TimePeriod:function(a,b){b.timePeriod={};this.readChildNodes(a,b.timePeriod)},beginPosition:function(a,b){b.beginPosition=this.getChildValue(a)},endPosition:function(a,b){b.endPosition=this.getChildValue(a)}},OpenLayers.Format.GML.v3.prototype.readers.gml),sos:{Capabilities:function(a,b){this.readChildNodes(a,b)}, [...]
+b){b.contents={};this.readChildNodes(a,b.contents)},ObservationOfferingList:function(a,b){b.offeringList={};this.readChildNodes(a,b.offeringList)},ObservationOffering:function(a,b){var c=this.getAttributeNS(a,this.namespaces.gml,"id");b[c]={procedures:[],observedProperties:[],featureOfInterestIds:[],responseFormats:[],resultModels:[],responseModes:[]};this.readChildNodes(a,b[c])},time:function(a,b){b.time={};this.readChildNodes(a,b.time)},procedure:function(a,b){b.procedures.push(this.ge [...]
+this.namespaces.xlink,"href"))},observedProperty:function(a,b){b.observedProperties.push(this.getAttributeNS(a,this.namespaces.xlink,"href"))},featureOfInterest:function(a,b){b.featureOfInterestIds.push(this.getAttributeNS(a,this.namespaces.xlink,"href"))},responseFormat:function(a,b){b.responseFormats.push(this.getChildValue(a))},resultModel:function(a,b){b.resultModels.push(this.getChildValue(a))},responseMode:function(a,b){b.responseModes.push(this.getChildValue(a))}},ows:OpenLayers.F [...]
+CLASS_NAME:"OpenLayers.Format.SOSCapabilities.v1_0_0"});OpenLayers.Handler.Pinch=OpenLayers.Class(OpenLayers.Handler,{started:!1,stopDown:!1,pinching:!1,last:null,start:null,touchstart:function(a){var b=!0;this.pinching=!1;if(OpenLayers.Event.isMultiTouch(a))this.started=!0,this.last=this.start={distance:this.getDistance(a.touches),delta:0,scale:1},this.callback("start",[a,this.start]),b=!this.stopDown;else{if(this.started)return!1;this.started=!1;this.last=this.start=null}OpenLayers.Eve [...]
+OpenLayers.Event.isMultiTouch(a)){this.pinching=!0;var b=this.getPinchData(a);this.callback("move",[a,b]);this.last=b;OpenLayers.Event.stop(a)}else if(this.started)return!1;return!0},touchend:function(a){return this.started&&!OpenLayers.Event.isMultiTouch(a)?(this.pinching=this.started=!1,this.callback("done",[a,this.start,this.last]),this.last=this.start=null,!1):!0},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.pinching=!1,a=!0);return  [...]
+!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.pinching=this.started=!1,this.last=this.start=null,a=!0);return a},getDistance:function(a){var b=a[0];a=a[1];return Math.sqrt(Math.pow(b.olClientX-a.olClientX,2)+Math.pow(b.olClientY-a.olClientY,2))},getPinchData:function(a){a=this.getDistance(a.touches);return{distance:a,delta:this.last.distance-a,scale:a/this.start.distance}},CLASS_NAME:"OpenLayers.Handler.Pinch"});OpenLayers.Control.NavToolbar=OpenLayers.Class(Ope [...]
+this.start():this.stop()},start:function(){this.interval&&("number"===typeof this.interval&&0<this.interval)&&(this.timer=window.setInterval(OpenLayers.Function.bind(this.refresh,this),this.interval))},refresh:function(){this.layer&&(this.layer.refresh&&"function"==typeof this.layer.refresh)&&this.layer.refresh({force:this.force})},stop:function(){null!==this.timer&&(window.clearInterval(this.timer),this.timer=null)},CLASS_NAME:"OpenLayers.Strategy.Refresh"});OpenLayers.Layer.ArcGIS93Res [...]
+(this.params.FORMAT=OpenLayers.Util.alphaHack()?"gif":"png"))},clone:function(a){null==a&&(a=new OpenLayers.Layer.ArcGIS93Rest(this.name,this.url,this.params,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){a=this.adjustBounds(a);var b=this.projection.getCode().split(":"),b=b[b.length-1],c=this.getImageSize();a={BBOX:a.toBBOX(),SIZE:c.w+","+c.h,F:"image",BBOXSR:b,IMAGESR:b};if(this.layerDefs){var b=[],d;for(d in this.layerDefs)this.la [...]
+this.layerDefs[d]&&(b.push(d),b.push(":"),b.push(this.layerDefs[d]),b.push(";"));0<b.length&&(a.LAYERDEFS=b.join(""))}return this.getFullRequestString(a)},setLayerFilter:function(a,b){this.layerDefs||(this.layerDefs={});b?this.layerDefs[a]=b:delete this.layerDefs[a]},clearLayerFilter:function(a){a?delete this.layerDefs[a]:delete this.layerDefs},mergeNewParams:function(a){a=[OpenLayers.Util.upperCaseObject(a)];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,a)},CLASS_NAME [...]
+passesTolerance:function(a){var b=!0;this.pixelTolerance&&this.px&&Math.sqrt(Math.pow(this.px.x-a.x,2)+Math.pow(this.px.y-a.y,2))<this.pixelTolerance&&(b=!1);return b},clearTimer:function(){null!=this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null)},delayedCall:function(a){this.callback("pause",[a])},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.clearTimer(),a=!0);return a},CLASS_NAME:"OpenLayers.Handler.Hover"});OpenLa [...]
+new OpenLayers.Handler.Click(this,{click:this.selectClick},this.handlerOptions.click||{}));this.box&&(this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},OpenLayers.Util.extend(this.handlerOptions.box,{boxDivClassName:"olHandlerBoxSelectFeature"})));this.hover&&(this.handlers.hover=new OpenLayers.Handler.Hover(this,{move:this.cancelHover,pause:this.selectHover},OpenLayers.Util.extend(this.handlerOptions.hover,{delay:250,pixelTolerance:2})))},activate:function(){if(!th [...]
+return OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){if(this.active)for(var a in this.handlers)this.handlers[a].deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},selectClick:function(a){var b=this.pixelToBounds(a.xy);this.setModifiers(a);this.request(b,{single:this.single})},selectBox:function(a){var b;if(a instanceof OpenLayers.Bounds)b=this.map.getLonLatFromPixel({x:a.left,y:a.bottom}),a=this.map.getLonLatFromPixel({ [...]
+y:a.top}),b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat);else{if(this.click)return;b=this.pixelToBounds(a)}this.setModifiers(this.handlers.box.dragHandler.evt);this.request(b)},selectHover:function(a){a=this.pixelToBounds(a.xy);this.request(a,{single:!0,hover:!0})},cancelHover:function(){this.hoverResponse&&(this.protocol.abort(this.hoverResponse),this.hoverResponse=null,OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait"))},request:function(a,b){b=b||{};var c=new OpenLa [...]
+value:a});OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");c=this.protocol.read({maxFeatures:!0==b.single?this.maxFeatures:void 0,filter:c,callback:function(c){c.success()&&(c.features.length?!0==b.single?this.selectBestFeature(c.features,a.getCenterLonLat(),b):this.select(c.features):b.hover?this.hoverSelect():(this.events.triggerEvent("clickout"),this.clickout&&this.unselectAll()));OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait")},scope:this});!0==b.h [...]
+c)},selectBestFeature:function(a,b,c){c=c||{};if(a.length){b=new OpenLayers.Geometry.Point(b.lon,b.lat);for(var d,e,f,g=Number.MAX_VALUE,h=0;h<a.length&&!(d=a[h],d.geometry&&(f=b.distanceTo(d.geometry,{edge:!1}),f<g&&(g=f,e=d,0==g)));++h);!0==c.hover?this.hoverSelect(e):this.select(e||a)}},setModifiers:function(a){this.modifiers={multiple:this.multiple||this.multipleKey&&a[this.multipleKey],toggle:this.toggle||this.toggleKey&&a[this.toggleKey]}},select:function(a){this.modifiers.multiple [...]
+this.unselectAll();OpenLayers.Util.isArray(a)||(a=[a]);var b=this.events.triggerEvent("beforefeaturesselected",{features:a});if(!1!==b){for(var c=[],d,e=0,f=a.length;e<f;++e)d=a[e],this.features[d.fid||d.id]?this.modifiers.toggle&&this.unselect(this.features[d.fid||d.id]):(b=this.events.triggerEvent("beforefeatureselected",{feature:d}),!1!==b&&(this.features[d.fid||d.id]=d,c.push(d),this.events.triggerEvent("featureselected",{feature:d})));this.events.triggerEvent("featuresselected",{fea [...]
+hoverSelect:function(a){var b=a?a.fid||a.id:null,c=this.hoverFeature?this.hoverFeature.fid||this.hoverFeature.id:null;c&&c!=b&&(this.events.triggerEvent("outfeature",{feature:this.hoverFeature}),this.hoverFeature=null);b&&b!=c&&(this.events.triggerEvent("hoverfeature",{feature:a}),this.hoverFeature=a)},unselect:function(a){delete this.features[a.fid||a.id];this.events.triggerEvent("featureunselected",{feature:a})},unselectAll:function(){for(var a in this.features)this.unselect(this.featu [...]
+setMap:function(a){for(var b in this.handlers)this.handlers[b].setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},pixelToBounds:function(a){var b=a.add(-this.clickTolerance/2,this.clickTolerance/2);a=a.add(this.clickTolerance/2,-this.clickTolerance/2);b=this.map.getLonLatFromPixel(b);a=this.map.getLonLatFromPixel(a);return new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat)},CLASS_NAME:"OpenLayers.Control.GetFeature"});OpenLayers.Format.QueryStringFilter=function(){function  [...]
+"lt";b[OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO]="lte";b[OpenLayers.Filter.Comparison.GREATER_THAN]="gt";b[OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO]="gte";b[OpenLayers.Filter.Comparison.LIKE]="ilike";return OpenLayers.Class(OpenLayers.Format,{wildcarded:!1,srsInBBOX:!1,write:function(c,d){d=d||{};var e=c.CLASS_NAME,e=e.substring(e.lastIndexOf(".")+1);switch(e){case "Spatial":switch(c.type){case OpenLayers.Filter.Spatial.BBOX:d.bbox=c.value.toArray();this.srsInBBOX& [...]
+d.bbox.push(c.projection.getCode());break;case OpenLayers.Filter.Spatial.DWITHIN:d.tolerance=c.distance;case OpenLayers.Filter.Spatial.WITHIN:d.lon=c.value.x;d.lat=c.value.y;break;default:OpenLayers.Console.warn("Unknown spatial filter type "+c.type)}break;case "Comparison":e=b[c.type];if(void 0!==e){var f=c.value;c.type==OpenLayers.Filter.Comparison.LIKE&&(f=a(f),this.wildcarded&&(f="%"+f+"%"));d[c.property+"__"+e]=f;d.queryable=d.queryable||[];d.queryable.push(c.property)}else OpenLaye [...]
+c.type);break;case "Logical":if(c.type===OpenLayers.Filter.Logical.AND)for(e=0,f=c.filters.length;e<f;e++)d=this.write(c.filters[e],d);else OpenLayers.Console.warn("Unsupported logical filter type "+c.type);break;default:OpenLayers.Console.warn("Unknown filter type "+e)}return d},CLASS_NAME:"OpenLayers.Format.QueryStringFilter"})}();OpenLayers.Control.MousePosition=OpenLayers.Class(OpenLayers.Control,{autoActivate:!0,element:null,prefix:"",separator:", ",suffix:"",numDigits:5,granularity [...]
+this.redraw(),!0):!1},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments)?(this.map.events.unregister("mousemove",this,this.redraw),this.map.events.unregister("mouseout",this,this.reset),this.element.innerHTML="",!0):!1},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.element||(this.div.left="",this.div.top="",this.element=this.div);return this.div},redraw:function(a){var b;if(null==a)this.reset();else if(null==this.last [...]
+this.lastXy.x)>this.granularity||Math.abs(a.xy.y-this.lastXy.y)>this.granularity)this.lastXy=a.xy;else if(b=this.map.getLonLatFromPixel(a.xy))this.displayProjection&&b.transform(this.map.getProjectionObject(),this.displayProjection),this.lastXy=a.xy,a=this.formatOutput(b),a!=this.element.innerHTML&&(this.element.innerHTML=a)},reset:function(a){null!=this.emptyString&&(this.element.innerHTML=this.emptyString)},formatOutput:function(a){var b=parseInt(this.numDigits);return this.prefix+a.lo [...]
+this.separator+a.lat.toFixed(b)+this.suffix},CLASS_NAME:"OpenLayers.Control.MousePosition"});OpenLayers.Control.Geolocate=OpenLayers.Class(OpenLayers.Control,{geolocation:null,available:"geolocation"in navigator,bind:!0,watch:!1,geolocationOptions:null,destroy:function(){this.deactivate();OpenLayers.Control.prototype.destroy.apply(this,arguments)},activate:function(){this.available&&!this.geolocation&&(this.geolocation=navigator.geolocation);return this.geolocation?OpenLayers.Control.pro [...]
+this),OpenLayers.Function.bind(this.failure,this),this.geolocationOptions):this.getCurrentLocation(),!0):!1:(this.events.triggerEvent("locationuncapable"),!1)},deactivate:function(){this.active&&null!==this.watchId&&this.geolocation.clearWatch(this.watchId);return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},geolocate:function(a){var b=(new OpenLayers.LonLat(a.coords.longitude,a.coords.latitude)).transform(new OpenLayers.Projection("EPSG:4326"),this.map.getProjectionObj [...]
+this.map.setCenter(b);this.events.triggerEvent("locationupdated",{position:a,point:new OpenLayers.Geometry.Point(b.lon,b.lat)})},getCurrentLocation:function(){if(!this.active||this.watch)return!1;this.geolocation.getCurrentPosition(OpenLayers.Function.bind(this.geolocate,this),OpenLayers.Function.bind(this.failure,this),this.geolocationOptions);return!0},failure:function(a){this.events.triggerEvent("locationfailed",{error:a})},CLASS_NAME:"OpenLayers.Control.Geolocate"});OpenLayers.Tile.U [...]
+callback:function(a){this.isLoading=!1;this.events.triggerEvent("loadend");this.json=a.data},scope:this});b.read();this.request=b}else this.request=OpenLayers.Request.GET({url:this.url,callback:function(a){this.isLoading=!1;this.events.triggerEvent("loadend");200===a.status&&this.parseData(a.responseText)},scope:this});else this.unload();return a},abortLoading:function(){this.request&&(this.request.abort(),delete this.request);this.isLoading=!1},getFeatureInfo:function(a,b){var c=null;if [...]
+this.getFeatureId(a,b);null!==d&&(c={id:d,data:this.json.data[d]})}return c},getFeatureId:function(a,b){var c=null;if(this.json){var d=this.utfgridResolution,d=this.json.grid[Math.floor(b/d)].charCodeAt(Math.floor(a/d)),d=this.indexFromCharCode(d),e=this.json.keys;!isNaN(d)&&d in e&&(c=e[d])}return c},indexFromCharCode:function(a){93<=a&&a--;35<=a&&a--;return a-32},parseData:function(a){this.format||(this.format=new OpenLayers.Format.JSON);this.json=this.format.read(a)},clear:function(){ [...]
+null},CLASS_NAME:"OpenLayers.Tile.UTFGrid"});OpenLayers.Protocol.HTTP=OpenLayers.Class(OpenLayers.Protocol,{url:null,headers:null,params:null,callback:null,scope:null,readWithPOST:!1,updateWithPOST:!1,deleteWithPOST:!1,wildcarded:!1,srsInBBOX:!1,initialize:function(a){a=a||{};this.params={};this.headers={};OpenLayers.Protocol.prototype.initialize.apply(this,arguments);if(!this.filterToParams&&OpenLayers.Format.QueryStringFilter){var b=new OpenLayers.Format.QueryStringFilter({wildcarded:t [...]
+function(a,d){return b.write(a,d)}}},destroy:function(){this.headers=this.params=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=a||{};a.params=OpenLayers.Util.applyDefaults(a.params,this.options.params);a=OpenLayers.Util.applyDefaults(a,this.options);a.filter&&this.filterToParams&&(a.params=this.filterToParams(a.filter,a.params));var b=void 0!==a.readWithPOST?a.readWithPOST:this.readWithPOST,c=new OpenLa [...]
+b?(b=a.headers||{},b["Content-Type"]="application/x-www-form-urlencoded",c.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,c,a),data:OpenLayers.Util.getParameterString(a.params),headers:b})):c.priv=OpenLayers.Request.GET({url:a.url,callback:this.createCallback(this.handleRead,c,a),params:a.params,headers:a.headers});return c},handleRead:function(a,b){this.handleResponse(a,b)},create:function(a,b){b=OpenLayers.Util.applyDefaults(b,this.options);var c=n [...]
+requestType:"create"});c.priv=OpenLayers.Request.POST({url:b.url,callback:this.createCallback(this.handleCreate,c,b),headers:b.headers,data:this.format.write(a)});return c},handleCreate:function(a,b){this.handleResponse(a,b)},update:function(a,b){b=b||{};var c=b.url||a.url||this.options.url+"/"+a.fid;b=OpenLayers.Util.applyDefaults(b,this.options);var d=new OpenLayers.Protocol.Response({reqFeatures:a,requestType:"update"});d.priv=OpenLayers.Request[this.updateWithPOST?"POST":"PUT"]({url: [...]
+d,b),headers:b.headers,data:this.format.write(a)});return d},handleUpdate:function(a,b){this.handleResponse(a,b)},"delete":function(a,b){b=b||{};var c=b.url||a.url||this.options.url+"/"+a.fid;b=OpenLayers.Util.applyDefaults(b,this.options);var d=new OpenLayers.Protocol.Response({reqFeatures:a,requestType:"delete"}),e=this.deleteWithPOST?"POST":"DELETE",c={url:c,callback:this.createCallback(this.handleDelete,d,b),headers:b.headers};this.deleteWithPOST&&(c.data=this.format.write(a));d.priv [...]
+return d},handleDelete:function(a,b){this.handleResponse(a,b)},handleResponse:function(a,b){var c=a.priv;b.callback&&(200<=c.status&&300>c.status?("delete"!=a.requestType&&(a.features=this.parseFeatures(c)),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE,b.callback.call(b.scope,a))},parseFeatures:function(a){var b=a.responseXML;b&&b.documentElement||(b=a.responseText);return!b||0>=b.length?null:this.format.read(b)},commit:function(a,b){function c( [...]
+a.features?a.features.length:0,c=Array(b),e=0;e<b;++e)c[e]=a.features[e].fid;r.insertIds=c;d.apply(this,[a])}function d(a){this.callUserCallback(a,b);q=q&&a.success();f++;f>=p&&b.callback&&(r.code=q?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE,b.callback.apply(b.scope,[r]))}b=OpenLayers.Util.applyDefaults(b,this.options);var e=[],f=0,g={};g[OpenLayers.State.INSERT]=[];g[OpenLayers.State.UPDATE]=[];g[OpenLayers.State.DELETE]=[];for(var h,k,l=[],m=0,n=a.length; [...]
+a[m],k=g[h.state])k.push(h),l.push(h);var p=(0<g[OpenLayers.State.INSERT].length?1:0)+g[OpenLayers.State.UPDATE].length+g[OpenLayers.State.DELETE].length,q=!0,r=new OpenLayers.Protocol.Response({reqFeatures:l});h=g[OpenLayers.State.INSERT];0<h.length&&e.push(this.create(h,OpenLayers.Util.applyDefaults({callback:c,scope:this},b.create)));h=g[OpenLayers.State.UPDATE];for(m=h.length-1;0<=m;--m)e.push(this.update(h[m],OpenLayers.Util.applyDefaults({callback:d,scope:this},b.update)));h=g[Open [...]
+for(m=h.length-1;0<=m;--m)e.push(this["delete"](h[m],OpenLayers.Util.applyDefaults({callback:d,scope:this},b["delete"])));return e},abort:function(a){a&&a.priv.abort()},callUserCallback:function(a,b){var c=b[a.requestType];c&&c.callback&&c.callback.call(c.scope,a)},CLASS_NAME:"OpenLayers.Protocol.HTTP"});OpenLayers.Strategy.Cluster=OpenLayers.Class(OpenLayers.Strategy,{distance:20,threshold:null,features:null,clusters:null,clustering:!1,resolution:null,activate:function(){var a=OpenLayer [...]
+featuresremoved:this.clearCache,moveend:this.cluster,scope:this}));return a},cacheFeatures:function(a){var b=!0;this.clustering||(this.clearCache(),this.features=a.features,this.cluster(),b=!1);return b},clearCache:function(){this.clustering||(this.features=null)},cluster:function(a){if((!a||a.zoomChanged)&&this.features&&(a=this.layer.map.getResolution(),a!=this.resolution||!this.clustersExist())){this.resolution=a;a=[];for(var b,c,d,e=0;e<this.features.length;++e)if(b=this.features[e], [...]
+!1;for(var f=a.length-1;0<=f;--f)if(d=a[f],this.shouldCluster(d,b)){this.addToCluster(d,b);c=!0;break}c||a.push(this.createCluster(this.features[e]))}this.clustering=!0;this.layer.removeAllFeatures();this.clustering=!1;if(0<a.length){if(1<this.threshold)for(b=a.slice(),a=[],e=0,d=b.length;e<d;++e)c=b[e],c.attributes.count<this.threshold?Array.prototype.push.apply(a,c.cluster):a.push(c);this.clustering=!0;this.layer.addFeatures(a);this.clustering=!1}this.clusters=a}},clustersExist:functio [...]
+!1;if(this.clusters&&0<this.clusters.length&&this.clusters.length==this.layer.features.length)for(var a=!0,b=0;b<this.clusters.length;++b)if(this.clusters[b]!=this.layer.features[b]){a=!1;break}return a},shouldCluster:function(a,b){var c=a.geometry.getBounds().getCenterLonLat(),d=b.geometry.getBounds().getCenterLonLat();return Math.sqrt(Math.pow(c.lon-d.lon,2)+Math.pow(c.lat-d.lat,2))/this.resolution<=this.distance},addToCluster:function(a,b){a.cluster.push(b);a.attributes.count+=1},crea [...]
+a.geometry.getBounds().getCenterLonLat(),b=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(b.lon,b.lat),{count:1});b.cluster=[a];return b},CLASS_NAME:"OpenLayers.Strategy.Cluster"});OpenLayers.Strategy.Filter=OpenLayers.Class(OpenLayers.Strategy,{filter:null,cache:null,caching:!1,activate:function(){var a=OpenLayers.Strategy.prototype.activate.apply(this,arguments);a&&(this.cache=[],this.layer.events.on({beforefeaturesadded:this.handleAdd,beforefeaturesremoved:this.handleRemo [...]
+return OpenLayers.Strategy.prototype.deactivate.apply(this,arguments)},handleAdd:function(a){if(!this.caching&&this.filter){var b=a.features;a.features=[];for(var c,d=0,e=b.length;d<e;++d)c=b[d],this.filter.evaluate(c)?a.features.push(c):this.cache.push(c)}},handleRemove:function(a){this.caching||(this.cache=[])},setFilter:function(a){this.filter=a;a=this.cache;this.cache=[];this.handleAdd({features:this.layer.features});0<this.cache.length&&(this.caching=!0,this.layer.removeFeatures(thi [...]
+this.caching=!1);0<a.length&&(a={features:a},this.handleAdd(a),0<a.features.length&&(this.caching=!0,this.layer.addFeatures(a.features),this.caching=!1))},CLASS_NAME:"OpenLayers.Strategy.Filter"});OpenLayers.Protocol.SOS=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Protocol.SOS.DEFAULTS);var b=OpenLayers.Protocol.SOS["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported SOS version: "+a.version;return new b(a)};OpenLayers.Protocol.SOS.DEFAULTS={version:"1.0.0"};OpenLayers [...]
+c.length;e<f;++e)g=c[e],h=d[g.typeName],d[g.typeName]&&(g.typeName=h.name)},complexType:function(a,b){var c={typeName:a.getAttribute("name")};this.readChildNodes(a,c);b.complexTypes.push(c)},complexContent:function(a,b){this.readChildNodes(a,b)},extension:function(a,b){this.readChildNodes(a,b)},sequence:function(a,b){var c={elements:[]};this.readChildNodes(a,c);b.properties=c.elements},element:function(a,b){var c;if(b.elements){var d={};c=a.attributes;for(var e,f=0,g=c.length;f<g;++f)e=c [...]
+e.value;c=d.type;c||(c={},this.readChildNodes(a,c),d.restriction=c,d.type=c.base);d.localType=(c.base||c).split(":").pop();b.elements.push(d);this.readChildNodes(a,d)}b.complexTypes&&(c=a.getAttribute("type"),d=c.split(":").pop(),b.customTypes[d]={name:a.getAttribute("name"),type:c})},annotation:function(a,b){b.annotation={};this.readChildNodes(a,b.annotation)},appinfo:function(a,b){b.appinfo||(b.appinfo=[]);b.appinfo.push(this.getChildValue(a))},documentation:function(a,b){b.documentati [...]
+[]);var c=this.getChildValue(a);b.documentation.push({lang:a.getAttribute("xml:lang"),textContent:c.replace(this.regExes.trimSpace,"")})},simpleType:function(a,b){this.readChildNodes(a,b)},restriction:function(a,b){b.base=a.getAttribute("base");this.readRestriction(a,b)}}},readRestriction:function(a,b){for(var c=a.childNodes,d,e,f=0,g=c.length;f<g;++f)d=c[f],1==d.nodeType&&(e=d.nodeName.split(":").pop(),d=d.getAttribute("value"),b[e]?("string"==typeof b[e]&&(b[e]=[b[e]]),b[e].push(d)):b[ [...]
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};if("ExceptionReport"===a.nodeName.split(":").pop()){var c=new OpenLayers.Format.OGCExceptionReport;b.error=c.read(a)}else this.readNode(a,b);return b},CLASS_NAME:"OpenLayers.Format.WFSDescribeFeatureType"});OpenLayers.Format.GeoRSS=OpenLayers.Class(OpenLayers.Format.XML,{rssns:"http://backend.userland.com/rss2",featureNS:"http://mapserver.gis.umn.edu/mapserver",georssns:"ht [...]
+this.geons,"long"),e=this.getElementsByTagNameNS(a,this.georssns,"line"),f=this.getElementsByTagNameNS(a,this.georssns,"polygon"),g=this.getElementsByTagNameNS(a,this.georssns,"where");a=this.getElementsByTagNameNS(a,this.georssns,"box");if(0<b.length||0<c.length&&0<d.length){0<b.length?(c=OpenLayers.String.trim(b[0].firstChild.nodeValue).split(/\s+/),2!=c.length&&(c=OpenLayers.String.trim(b[0].firstChild.nodeValue).split(/\s*,\s*/))):c=[parseFloat(c[0].firstChild.nodeValue),parseFloat(d [...]
+var h=new OpenLayers.Geometry.Point(c[1],c[0])}else if(0<e.length){c=OpenLayers.String.trim(this.getChildValue(e[0])).split(/\s+/);d=[];e=0;for(f=c.length;e<f;e+=2)b=new OpenLayers.Geometry.Point(c[e+1],c[e]),d.push(b);h=new OpenLayers.Geometry.LineString(d)}else if(0<f.length){c=OpenLayers.String.trim(this.getChildValue(f[0])).split(/\s+/);d=[];e=0;for(f=c.length;e<f;e+=2)b=new OpenLayers.Geometry.Point(c[e+1],c[e]),d.push(b);h=new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.Li [...]
+g.length?(this.gmlParser||(this.gmlParser=new OpenLayers.Format.GML({xy:this.xy})),h=this.gmlParser.parseFeature(g[0]).geometry):0<a.length&&(c=OpenLayers.String.trim(a[0].firstChild.nodeValue).split(/\s+/),d=[],3<c.length&&(b=new OpenLayers.Geometry.Point(c[1],c[0]),d.push(b),b=new OpenLayers.Geometry.Point(c[1],c[2]),d.push(b),b=new OpenLayers.Geometry.Point(c[3],c[2]),d.push(b),b=new OpenLayers.Geometry.Point(c[3],c[0]),d.push(b),b=new OpenLayers.Geometry.Point(c[1],c[0]),d.push(b)),h [...]
+h&&(this.internalProjection&&this.externalProjection)&&h.transform(this.externalProjection,this.internalProjection);return h},createFeatureFromItem:function(a){var b=this.createGeometryFromItem(a),c=this._getChildValue(a,"*","title",this.featureTitle),d=this._getChildValue(a,"*","description",this._getChildValue(a,"*","content",this._getChildValue(a,"*","summary",this.featureDescription))),e=this._getChildValue(a,"*","link");if(!e)try{e=this.getElementsByTagNameNS(a,"*","link")[0].getAtt [...]
+null}a=this._getChildValue(a,"*","id",null);b=new OpenLayers.Feature.Vector(b,{title:c,description:d,link:e});b.fid=a;return b},_getChildValue:function(a,b,c,d){return(a=this.getElementsByTagNameNS(a,b,c))&&a[0]&&a[0].firstChild&&a[0].firstChild.nodeValue?this.getChildValue(a[0]):void 0==d?"":d},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=null,b=this.getElementsByTagNameNS(a,"*","item");0==b.length&&(b=this.getElementsByTagNameNS(a, [...]
+a=b.length;for(var c=Array(a),d=0;d<a;d++)c[d]=this.createFeatureFromItem(b[d]);return c},write:function(a){var b;if(OpenLayers.Util.isArray(a)){b=this.createElementNS(this.rssns,"rss");for(var c=0,d=a.length;c<d;c++)b.appendChild(this.createFeatureXML(a[c]))}else b=this.createFeatureXML(a);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFeatureXML:function(a){var b=this.buildGeometryNode(a.geometry),c=this.createElementNS(this.rssns,"item"),d=this.createElementNS(thi [...]
+d.appendChild(this.createTextNode(a.attributes.title?a.attributes.title:""));var e=this.createElementNS(this.rssns,"description");e.appendChild(this.createTextNode(a.attributes.description?a.attributes.description:""));c.appendChild(d);c.appendChild(e);a.attributes.link&&(d=this.createElementNS(this.rssns,"link"),d.appendChild(this.createTextNode(a.attributes.link)),c.appendChild(d));for(var f in a.attributes)"link"!=f&&("title"!=f&&"description"!=f)&&(d=this.createTextNode(a.attributes[ [...]
+f.search(":")&&(e=f.split(":")[1]),e=this.createElementNS(this.featureNS,"feature:"+e),e.appendChild(d),c.appendChild(e));c.appendChild(b);return c},buildGeometryNode:function(a){this.internalProjection&&this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b;if("OpenLayers.Geometry.Polygon"==a.CLASS_NAME)b=this.createElementNS(this.georssns,"georss:polygon"),b.appendChild(this.buildCoordinatesNode(a.components[0]));else if("OpenLayers.Ge [...]
+a.CLASS_NAME)b=this.createElementNS(this.georssns,"georss:line"),b.appendChild(this.buildCoordinatesNode(a));else if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.createElementNS(this.georssns,"georss:point"),b.appendChild(this.buildCoordinatesNode(a));else throw"Couldn't parse "+a.CLASS_NAME;return b},buildCoordinatesNode:function(a){var b=null;a.components&&(b=a.components);if(b){a=b.length;for(var c=Array(a),d=0;d<a;d++)c[d]=b[d].y+" "+b[d].x;b=c.join(" ")}else b=a.y+" "+a.x;return [...]
+CLASS_NAME:"OpenLayers.Format.GeoRSS"});OpenLayers.Format.WPSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",CLASS_NAME:"OpenLayers.Format.WPSCapabilities"});OpenLayers.Format.WPSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ows:"http://www.opengis.net/ows/1.1",wps:"http://www.opengis.net/wps/1.0.0",xlink:"http://www.w3.org/1999/xlink"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s* [...]
+var b={};this.readNode(a,b);return b},readers:{wps:{Capabilities:function(a,b){this.readChildNodes(a,b)},ProcessOfferings:function(a,b){b.processOfferings={};this.readChildNodes(a,b.processOfferings)},Process:function(a,b){var c={processVersion:this.getAttributeNS(a,this.namespaces.wps,"processVersion")};this.readChildNodes(a,c);b[c.identifier]=c},Languages:function(a,b){b.languages=[];this.readChildNodes(a,b.languages)},Default:function(a,b){var c={isDefault:!0};this.readChildNodes(a,c) [...]
+Supported:function(a,b){var c={};this.readChildNodes(a,c);b.push(c)}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WPSCapabilities.v1_0_0"});OpenLayers.Control.PinchZoom=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,pinchOrigin:null,currentCenter:null,autoActivate:!0,preserveCenter:!1,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.handler=new OpenLayers.Handler.Pinch(this,{s [...]
+this.pinchOrigin=c},pinchMove:function(a,b){var c=b.scale,d=this.map.layerContainerOriginPx,e=this.pinchOrigin,f=this.preserveCenter?this.map.getPixelFromLonLat(this.map.getCenter()):a.xy,g=Math.round(d.x+f.x-e.x+(c-1)*(d.x-e.x)),d=Math.round(d.y+f.y-e.y+(c-1)*(d.y-e.y));this.map.applyTransform(g,d,c);this.currentCenter=f},pinchDone:function(a,b,c){this.map.applyTransform();a=this.map.getZoomForResolution(this.map.getResolution()/c.scale,!0);if(a!==this.map.getZoom()||!this.currentCenter [...]
+this.map.getResolutionForZoom(a);c=this.map.getLonLatFromPixel(this.pinchOrigin);var d=this.currentCenter,e=this.map.getSize();c.lon+=b*(e.w/2-d.x);c.lat-=b*(e.h/2-d.y);this.map.div.clientWidth=this.map.div.clientWidth;this.map.setCenter(c,a)}},CLASS_NAME:"OpenLayers.Control.PinchZoom"});OpenLayers.Control.TouchNavigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,pinchZoom:null,pinchZoomOptions:null,clickHandlerOptions:null,documentDrag:!1,autoActivate:!0,init [...]
+arguments)},activate:function(){return OpenLayers.Control.prototype.activate.apply(this,arguments)?(this.dragPan.activate(),this.handlers.click.activate(),this.pinchZoom.activate(),!0):!1},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments)?(this.dragPan.deactivate(),this.handlers.click.deactivate(),this.pinchZoom.deactivate(),!0):!1},draw:function(){var a={click:this.defaultClick,dblclick:this.defaultDblClick},b=OpenLayers.Util.extend({"double":!0, [...]
+pixelTolerance:2},this.clickHandlerOptions);this.handlers.click=new OpenLayers.Handler.Click(this,a,b);this.dragPan=new OpenLayers.Control.DragPan(OpenLayers.Util.extend({map:this.map,documentDrag:this.documentDrag},this.dragPanOptions));this.dragPan.draw();this.pinchZoom=new OpenLayers.Control.PinchZoom(OpenLayers.Util.extend({map:this.map},this.pinchZoomOptions))},defaultClick:function(a){a.lastTouches&&2==a.lastTouches.length&&this.map.zoomOut()},defaultDblClick:function(a){this.map.z [...]
+1,a.xy)},CLASS_NAME:"OpenLayers.Control.TouchNavigation"});OpenLayers.Console.warn("OpenLayers.Rico is deprecated");OpenLayers.Rico=OpenLayers.Rico||{};
+OpenLayers.Rico.Color=OpenLayers.Class({initialize:function(a,b,c){this.rgb={r:a,g:b,b:c}},setRed:function(a){this.rgb.r=a},setGreen:function(a){this.rgb.g=a},setBlue:function(a){this.rgb.b=a},setHue:function(a){var b=this.asHSB();b.h=a;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,b.b)},setSaturation:function(a){var b=this.asHSB();b.s=a;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,b.b)},setBrightness:function(a){var b=this.asHSB();b.b=a;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b [...]
+darken:function(a){var b=this.asHSB();this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,Math.max(b.b-a,0))},brighten:function(a){var b=this.asHSB();this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,Math.min(b.b+a,1))},blend:function(a){this.rgb.r=Math.floor((this.rgb.r+a.rgb.r)/2);this.rgb.g=Math.floor((this.rgb.g+a.rgb.g)/2);this.rgb.b=Math.floor((this.rgb.b+a.rgb.b)/2)},isBright:function(){this.asHSB();return 0.5<this.asHSB().b},isDark:function(){return!this.isBright()},asRGB:function(){re [...]
+this.rgb.r+","+this.rgb.g+","+this.rgb.b+")"},asHex:function(){return"#"+this.rgb.r.toColorPart()+this.rgb.g.toColorPart()+this.rgb.b.toColorPart()},asHSB:function(){return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r,this.rgb.g,this.rgb.b)},toString:function(){return this.asHex()}});
+OpenLayers.Rico.Color.createFromHex=function(a){if(4==a.length){var b=a;a="#";for(var c=1;4>c;c++)a+=b.charAt(c)+b.charAt(c)}0==a.indexOf("#")&&(a=a.substring(1));b=a.substring(0,2);c=a.substring(2,4);a=a.substring(4,6);return new OpenLayers.Rico.Color(parseInt(b,16),parseInt(c,16),parseInt(a,16))};
+OpenLayers.Rico.Color.createColorFromBackground=function(a){var b=OpenLayers.Element.getStyle(OpenLayers.Util.getElement(a),"backgroundColor");return"transparent"==b&&a.parentNode?OpenLayers.Rico.Color.createColorFromBackground(a.parentNode):null==b?new OpenLayers.Rico.Color(255,255,255):0==b.indexOf("rgb(")?(a=b.substring(4,b.length-1).split(","),new OpenLayers.Rico.Color(parseInt(a[0]),parseInt(a[1]),parseInt(a[2]))):0==b.indexOf("#")?OpenLayers.Rico.Color.createFromHex(b):new OpenLaye [...]
+255,255)};
+OpenLayers.Rico.Color.HSBtoRGB=function(a,b,c){var d=0,e=0,f=0;if(0==b)f=e=d=parseInt(255*c+0.5);else{a=6*(a-Math.floor(a));var g=a-Math.floor(a),h=c*(1-b),k=c*(1-b*g);b=c*(1-b*(1-g));switch(parseInt(a)){case 0:d=255*c+0.5;e=255*b+0.5;f=255*h+0.5;break;case 1:d=255*k+0.5;e=255*c+0.5;f=255*h+0.5;break;case 2:d=255*h+0.5;e=255*c+0.5;f=255*b+0.5;break;case 3:d=255*h+0.5;e=255*k+0.5;f=255*c+0.5;break;case 4:d=255*b+0.5;e=255*h+0.5;f=255*c+0.5;break;case 5:d=255*c+0.5,e=255*h+0.5,f=255*k+0.5} [...]
+b:parseInt(f)}};OpenLayers.Rico.Color.RGBtoHSB=function(a,b,c){var d,e=a>b?a:b;c>e&&(e=c);var f=a<b?a:b;c<f&&(f=c);d=0!=e?(e-f)/e:0;if(0==d)a=0;else{var g=(e-a)/(e-f),h=(e-b)/(e-f);c=(e-c)/(e-f);a=(a==e?c-h:b==e?2+g-c:4+h-g)/6;0>a&&(a+=1)}return{h:a,s:d,b:e/255}};OpenLayers.Style2=OpenLayers.Class({id:null,name:null,title:null,description:null,layerName:null,isDefault:!1,rules:null,initialize:function(a){OpenLayers.Util.extend(this,a);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAM [...]
+CLASS_NAME:"OpenLayers.Style2"});OpenLayers.Format.WFS=OpenLayers.Class(OpenLayers.Format.GML,{layer:null,wfsns:"http://www.opengis.net/wfs",ogcns:"http://www.opengis.net/ogc",initialize:function(a,b){OpenLayers.Format.GML.prototype.initialize.apply(this,[a]);this.layer=b;this.layer.featureNS&&(this.featureNS=this.layer.featureNS);this.layer.options.geometry_column&&(this.geometryName=this.layer.options.geometry_column);this.layer.options.typename&&(this.featureName=this.layer.options.ty [...]
+"wfs:Transaction");b.setAttribute("version","1.0.0");b.setAttribute("service","WFS");for(var c=0;c<a.length;c++)switch(a[c].state){case OpenLayers.State.INSERT:b.appendChild(this.insert(a[c]));break;case OpenLayers.State.UPDATE:b.appendChild(this.update(a[c]));break;case OpenLayers.State.DELETE:b.appendChild(this.remove(a[c]))}return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFeatureXML:function(a){var b=this.buildGeometryNode(a.geometry),c=this.createElementNS(this.fea [...]
+this.geometryName);c.appendChild(b);b=this.createElementNS(this.featureNS,"feature:"+this.featureName);b.appendChild(c);for(var d in a.attributes){var c=this.createTextNode(a.attributes[d]),e=d;-1!=d.search(":")&&(e=d.split(":")[1]);e=this.createElementNS(this.featureNS,"feature:"+e);e.appendChild(c);b.appendChild(e)}return b},insert:function(a){var b=this.createElementNS(this.wfsns,"wfs:Insert");b.appendChild(this.createFeatureXML(a));return b},update:function(a){a.fid||OpenLayers.Conso [...]
+var b=this.createElementNS(this.wfsns,"wfs:Update");b.setAttribute("typeName",this.featurePrefix+":"+this.featureName);b.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);var c=this.createElementNS(this.wfsns,"wfs:Property"),d=this.createElementNS(this.wfsns,"wfs:Name"),e=this.createTextNode(this.geometryName);d.appendChild(e);c.appendChild(d);d=this.createElementNS(this.wfsns,"wfs:Value");e=this.buildGeometryNode(a.geometry);a.layer&&e.setAttribute("srsName",a.layer.projection.ge [...]
+d.appendChild(e);c.appendChild(d);b.appendChild(c);for(var f in a.attributes)c=this.createElementNS(this.wfsns,"wfs:Property"),d=this.createElementNS(this.wfsns,"wfs:Name"),d.appendChild(this.createTextNode(f)),c.appendChild(d),d=this.createElementNS(this.wfsns,"wfs:Value"),d.appendChild(this.createTextNode(a.attributes[f])),c.appendChild(d),b.appendChild(c);c=this.createElementNS(this.ogcns,"ogc:Filter");f=this.createElementNS(this.ogcns,"ogc:FeatureId");f.setAttribute("fid",a.fid);c.ap [...]
+b.appendChild(c);return b},remove:function(a){if(!a.fid)return OpenLayers.Console.userError(OpenLayers.i18n("noFID")),!1;var b=this.createElementNS(this.wfsns,"wfs:Delete");b.setAttribute("typeName",this.featurePrefix+":"+this.featureName);b.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);var c=this.createElementNS(this.ogcns,"ogc:Filter"),d=this.createElementNS(this.ogcns,"ogc:FeatureId");d.setAttribute("fid",a.fid);c.appendChild(d);b.appendChild(c);return b},destroy:function() [...]
+null},CLASS_NAME:"OpenLayers.Format.WFS"});OpenLayers.Format.SLD.v1_0_0_GeoServer=OpenLayers.Class(OpenLayers.Format.SLD.v1_0_0,{version:"1.0.0",profile:"GeoServer",readers:OpenLayers.Util.applyDefaults({sld:OpenLayers.Util.applyDefaults({Priority:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.priority=c)},VendorOption:function(a,b){b.vendorOptions||(b.vendorOptions={});b.vendorOptions[a.getAttribute("name")]=this.getChildValue(a)},TextSymbolizer:function(a,b){OpenLa [...]
+arguments);var c=this.multipleSymbolizers?b.symbolizers[b.symbolizers.length-1]:b.symbolizer.Text;void 0===c.graphic&&(c.graphic=!1)}},OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld)},OpenLayers.Format.SLD.v1_0_0.prototype.readers),writers:OpenLayers.Util.applyDefaults({sld:OpenLayers.Util.applyDefaults({Priority:function(a){return this.writers.sld._OGCExpression.call(this,"sld:Priority",a)},VendorOption:function(a){return this.createElementNSPlus("sld:VendorOption",{attributes:{name [...]
+value:a.value})},TextSymbolizer:function(a){var b=OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld.TextSymbolizer.apply(this,arguments);!1!==a.graphic&&(a.externalGraphic||a.graphicName)&&this.writeNode("Graphic",a,b);"priority"in a&&this.writeNode("Priority",a.priority,b);return this.addVendorOptions(b,a)},PointSymbolizer:function(a){var b=OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld.PointSymbolizer.apply(this,arguments);return this.addVendorOptions(b,a)},LineSymbolizer:function [...]
+OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld.LineSymbolizer.apply(this,arguments);return this.addVendorOptions(b,a)},PolygonSymbolizer:function(a){var b=OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld.PolygonSymbolizer.apply(this,arguments);return this.addVendorOptions(b,a)}},OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld)},OpenLayers.Format.SLD.v1_0_0.prototype.writers),addVendorOptions:function(a,b){if(b.vendorOptions)for(var c in b.vendorOptions)this.writeNode("VendorOpti [...]
+value:b.vendorOptions[c]},a);return a},CLASS_NAME:"OpenLayers.Format.SLD.v1_0_0_GeoServer"});OpenLayers.Layer.Boxes=OpenLayers.Class(OpenLayers.Layer.Markers,{drawMarker:function(a){var b=this.map.getLayerPxFromLonLat({lon:a.bounds.left,lat:a.bounds.top}),c=this.map.getLayerPxFromLonLat({lon:a.bounds.right,lat:a.bounds.bottom});null==c||null==b?a.display(!1):(b=a.draw(b,{w:Math.max(1,c.x-b.x),h:Math.max(1,c.y-b.y)}),a.drawn||(this.div.appendChild(b),a.drawn=!0))},removeMarker:function(a) [...]
+CLASS_NAME:"OpenLayers.Layer.Boxes"});OpenLayers.Format.WFSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{readers:{wfs:OpenLayers.Util.applyDefaults({Service:function(a,b){b.service={};this.readChildNodes(a,b.service)},Fees:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.fees=c)},AccessConstraints:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.accessConstraints=c)},OnlineResource:function(a,b){var c=this.getCh [...]
+c)},Keywords:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.keywords=c.split(", "))},Capability:function(a,b){b.capability={};this.readChildNodes(a,b.capability)},Request:function(a,b){b.request={};this.readChildNodes(a,b.request)},GetFeature:function(a,b){b.getfeature={href:{},formats:[]};this.readChildNodes(a,b.getfeature)},ResultFormat:function(a,b){for(var c=a.childNodes,d,e=0;e<c.length;e++)d=c[e],1==d.nodeType&&b.formats.push(d.nodeName)},DCPType:function( [...]
+b)},HTTP:function(a,b){this.readChildNodes(a,b.href)},Get:function(a,b){b.get=a.getAttribute("onlineResource")},Post:function(a,b){b.post=a.getAttribute("onlineResource")},SRS:function(a,b){var c=this.getChildValue(a);c&&(b.srs=c)}},OpenLayers.Format.WFSCapabilities.v1.prototype.readers.wfs)},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_0_0"});OpenLayers.Format.WMSCapabilities.v1_3=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1,{readers:{wms:OpenLayers.Util.applyDefaults({WMS_ [...]
+[a,b]);c.srs=a.getAttribute("CRS");b.bbox[c.srs]=c},CRS:function(a,b){this.readers.wms.SRS.apply(this,[a,b])},EX_GeographicBoundingBox:function(a,b){b.llbbox=[];this.readChildNodes(a,b.llbbox)},westBoundLongitude:function(a,b){b[0]=this.getChildValue(a)},eastBoundLongitude:function(a,b){b[2]=this.getChildValue(a)},southBoundLatitude:function(a,b){b[1]=this.getChildValue(a)},northBoundLatitude:function(a,b){b[3]=this.getChildValue(a)},MinScaleDenominator:function(a,b){b.maxScale=parseFloa [...]
+MaxScaleDenominator:function(a,b){b.minScale=parseFloat(this.getChildValue(a)).toPrecision(16)},Dimension:function(a,b){var c={name:a.getAttribute("name").toLowerCase(),units:a.getAttribute("units"),unitsymbol:a.getAttribute("unitSymbol"),nearestVal:"1"===a.getAttribute("nearestValue"),multipleVal:"1"===a.getAttribute("multipleValues"),"default":a.getAttribute("default")||"",current:"1"===a.getAttribute("current"),values:this.getChildValue(a).split(",")};b.dimensions[c.name]=c},Keyword:f [...]
+b){var c={value:this.getChildValue(a),vocabulary:a.getAttribute("vocabulary")};b.keywords&&b.keywords.push(c)}},OpenLayers.Format.WMSCapabilities.v1.prototype.readers.wms),sld:{UserDefinedSymbolization:function(a,b){this.readers.wms.UserDefinedSymbolization.apply(this,[a,b]);b.userSymbols.inlineFeature=1==parseInt(a.getAttribute("InlineFeature"));b.userSymbols.remoteWCS=1==parseInt(a.getAttribute("RemoteWCS"))},DescribeLayer:function(a,b){this.readers.wms.DescribeLayer.apply(this,[a,b])} [...]
+b){this.readers.wms.GetLegendGraphic.apply(this,[a,b])}}},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_3"});OpenLayers.Layer.Zoomify=OpenLayers.Class(OpenLayers.Layer.Grid,{size:null,isBaseLayer:!0,standardTileSize:256,tileOriginCorner:"tl",numberOfTiers:0,tileCountUpToTier:null,tierSizeInTiles:null,tierImageSize:null,initialize:function(a,b,c,d){this.initializeZoomify(c);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a,b,c,{},d])},initializeZoomify:function(a){var b=a.clone( [...]
+[a];for(this.tierImageSize=[b];b.w>this.standardTileSize||b.h>this.standardTileSize;)b=new OpenLayers.Size(Math.floor(b.w/2),Math.floor(b.h/2)),a=new OpenLayers.Size(Math.ceil(b.w/this.standardTileSize),Math.ceil(b.h/this.standardTileSize)),this.tierSizeInTiles.push(a),this.tierImageSize.push(b);this.tierSizeInTiles.reverse();this.tierImageSize.reverse();this.numberOfTiers=this.tierSizeInTiles.length;b=[1];this.tileCountUpToTier=[0];for(a=1;a<this.numberOfTiers;a++)b.unshift(Math.pow(2,a [...]
+1].w*this.tierSizeInTiles[a-1].h+this.tileCountUpToTier[a-1]);this.serverResolutions||(this.serverResolutions=b)},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);this.tileCountUpToTier.length=0;this.tierSizeInTiles.length=0;this.tierImageSize.length=0},clone:function(a){null==a&&(a=new OpenLayers.Layer.Zoomify(this.name,this.url,this.size,this.options));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){a=this.adjustBounds( [...]
+this.getServerResolution(),c=Math.round((a.left-this.tileOrigin.lon)/(b*this.tileSize.w));a=Math.round((this.tileOrigin.lat-a.top)/(b*this.tileSize.h));b=this.getZoomForResolution(b);c="TileGroup"+Math.floor((c+a*this.tierSizeInTiles[b].w+this.tileCountUpToTier[b])/256)+"/"+b+"-"+c+"-"+a+".jpg";b=this.url;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(c,b));return b+c},getImageSize:function(){if(0<arguments.length){var a=this.adjustBounds(arguments[0]),b=this.getServerResolution(),c=Math. [...]
+this.tileOrigin.lon)/(b*this.tileSize.w)),a=Math.round((this.tileOrigin.lat-a.top)/(b*this.tileSize.h)),b=this.getZoomForResolution(b),d=this.standardTileSize,e=this.standardTileSize;c==this.tierSizeInTiles[b].w-1&&(d=this.tierImageSize[b].w%this.standardTileSize);a==this.tierSizeInTiles[b].h-1&&(e=this.tierImageSize[b].h%this.standardTileSize);return new OpenLayers.Size(d,e)}return this.tileSize},setMap:function(a){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);this.tileOr [...]
+this.map.maxExtent.top)},CLASS_NAME:"OpenLayers.Layer.Zoomify"});OpenLayers.Layer.MapServer=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{mode:"map",map_imagetype:"png"},initialize:function(a,b,c,d){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);if(null==d||null==d.isBaseLayer)this.isBaseLayer="true"!=this.params.transparent&&!0!=this.params.transparent},clone:function(a){null==a&&(a=ne [...]
+return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){a=this.adjustBounds(a);a=[a.left,a.bottom,a.right,a.top];var b=this.getImageSize();return this.getFullRequestString({mapext:a,imgext:a,map_size:[b.w,b.h],imgx:b.w/2,imgy:b.h/2,imgxy:[b.w,b.h]})},getFullRequestString:function(a,b){var c=null==b?this.url:b,d=OpenLayers.Util.extend({},this.params),d=OpenLayers.Util.extend(d,a),e=OpenLayers.Util.getParameterString(d);OpenLayers.Util.isArray(c)&&(c=this.selectU [...]
+var e=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(c)),f;for(f in d)f.toUpperCase()in e&&delete d[f];e=OpenLayers.Util.getParameterString(d);d=c;e=e.replace(/,/g,"+");""!=e&&(f=c.charAt(c.length-1),d="&"==f||"?"==f?d+e:-1==c.indexOf("?")?d+("?"+e):d+("&"+e));return d},CLASS_NAME:"OpenLayers.Layer.MapServer"});OpenLayers.Renderer.VML=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"urn:schemas-microsoft-com:vml",symbolCache:{},offset:null,initialize:function(a){if(th [...]
+arguments)}},supported:function(){return!!document.namespaces},setExtent:function(a,b){var c=OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments),d=this.getResolution(),e=a.left/d|0,d=a.top/d-this.size.h|0;b||!this.offset?(this.offset={x:e,y:d},d=e=0):(e-=this.offset.x,d-=this.offset.y);this.root.coordorigin=e-this.xOffset+" "+d;for(var e=[this.root,this.vectorRoot,this.textRoot],f=0,g=e.length;f<g;++f)d=e[f],d.coordsize=this.size.w+" "+this.size.h;this.root.style.flip= [...]
+setSize:function(a){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);for(var b=[this.rendererRoot,this.root,this.vectorRoot,this.textRoot],c=this.size.w+"px",d=this.size.h+"px",e,f=0,g=b.length;f<g;++f)e=b[f],e.style.width=c,e.style.height=d},getNodeType:function(a,b){var c=null;switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":c=b.externalGraphic?"olv:rect":this.isComplexSymbol(b.graphicName)?"olv:shape":"olv:oval";break;case "OpenLayers.Geometry.Rectangle":c="olv:rect";b [...]
+"olv:shape"}return c},setStyle:function(a,b,c,d){b=b||a._style;c=c||a._options;var e=b.fillColor,f=b.title||b.graphicTitle;f&&(a.title=f);if("OpenLayers.Geometry.Point"===a._geometryClass)if(b.externalGraphic){c.isFilled=!0;var e=b.graphicWidth||b.graphicHeight,f=b.graphicHeight||b.graphicWidth,e=e?e:2*b.pointRadius,f=f?f:2*b.pointRadius,g=this.getResolution(),h=void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*e),k=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*f);a.style.left=((d.x-this [...]
+g-this.offset.x+h|0)+"px";a.style.top=(d.y/g-this.offset.y-(k+f)|0)+"px";a.style.width=e+"px";a.style.height=f+"px";a.style.flip="y";e="none";c.isStroked=!1}else this.isComplexSymbol(b.graphicName)?(f=this.importSymbol(b.graphicName),a.path=f.path,a.coordorigin=f.left+","+f.bottom,f=f.size,a.coordsize=f+","+f,this.drawCircle(a,d,b.pointRadius),a.style.flip="y"):this.drawCircle(a,d,b.pointRadius);c.isFilled?a.fillcolor=e:a.filled="false";d=a.getElementsByTagName("fill");d=0==d.length?null [...]
+(d||(d=this.createNode("olv:fill",a.id+"_fill")),d.opacity=b.fillOpacity,"OpenLayers.Geometry.Point"===a._geometryClass&&b.externalGraphic&&(b.graphicOpacity&&(d.opacity=b.graphicOpacity),d.src=b.externalGraphic,d.type="frame",b.graphicWidth&&b.graphicHeight||(d.aspect="atmost")),d.parentNode!=a&&a.appendChild(d)):d&&a.removeChild(d);e=b.rotation;if(void 0!==e||void 0!==a._rotation)a._rotation=e,b.externalGraphic?(this.graphicRotate(a,h,k,b),d.opacity=0):"OpenLayers.Geometry.Point"===a._ [...]
+(a.style.rotation=e||0);h=a.getElementsByTagName("stroke");h=0==h.length?null:h[0];c.isStroked?(h||(h=this.createNode("olv:stroke",a.id+"_stroke"),a.appendChild(h)),h.on=!0,h.color=b.strokeColor,h.weight=b.strokeWidth+"px",h.opacity=b.strokeOpacity,h.endcap="butt"==b.strokeLinecap?"flat":b.strokeLinecap||"round",b.strokeDashstyle&&(h.dashstyle=this.dashStyle(b))):(a.stroked=!1,h&&(h.on=!1));"inherit"!=b.cursor&&null!=b.cursor&&(a.style.cursor=b.cursor);return a},graphicRotate:function(a, [...]
+d||a._style;var e=d.rotation||0,f,g;if(d.graphicWidth&&d.graphicHeight){g=Math.max(d.graphicWidth,d.graphicHeight);f=d.graphicWidth/d.graphicHeight;var h=Math.round(d.graphicWidth||g*f),k=Math.round(d.graphicHeight||g);a.style.width=h+"px";a.style.height=k+"px";var l=document.getElementById(a.id+"_image");l||(l=this.createNode("olv:imagedata",a.id+"_image"),a.appendChild(l));l.style.width=h+"px";l.style.height=k+"px";l.src=d.externalGraphic;l.style.filter="progid:DXImageTransform.Microso [...]
+l=e*Math.PI/180;e=Math.sin(l);l=Math.cos(l);e="progid:DXImageTransform.Microsoft.Matrix(M11="+l+",M12="+-e+",M21="+e+",M22="+l+",SizingMethod='auto expand')\n";(l=d.graphicOpacity||d.fillOpacity)&&1!=l&&(e+="progid:DXImageTransform.Microsoft.BasicImage(opacity="+l+")\n");a.style.filter=e;e=new OpenLayers.Geometry.Point(-b,-c);h=(new OpenLayers.Bounds(0,0,h,k)).toGeometry();h.rotate(d.rotation,e);h=h.getBounds();a.style.left=Math.round(parseInt(a.style.left)+h.left)+"px";a.style.top=Math. [...]
+h.bottom)+"px"}else{var m=new Image;m.onreadystatechange=OpenLayers.Function.bind(function(){if("complete"==m.readyState||"interactive"==m.readyState)f=m.width/m.height,g=Math.max(2*d.pointRadius,d.graphicWidth||0,d.graphicHeight||0),b*=f,d.graphicWidth=g*f,d.graphicHeight=g,this.graphicRotate(a,b,c,d)},this);m.src=d.externalGraphic}},postDraw:function(a){a.style.visibility="visible";var b=a._style.fillColor,c=a._style.strokeColor;"none"==b&&a.fillcolor!=b&&(a.fillcolor=b);"none"==c&&a.s [...]
+c&&(a.strokecolor=c)},setNodeDimension:function(a,b){var c=b.getBounds();if(c){var d=this.getResolution(),c=new OpenLayers.Bounds((c.left-this.featureDx)/d-this.offset.x|0,c.bottom/d-this.offset.y|0,(c.right-this.featureDx)/d-this.offset.x|0,c.top/d-this.offset.y|0);a.style.left=c.left+"px";a.style.top=c.top+"px";a.style.width=c.getWidth()+"px";a.style.height=c.getHeight()+"px";a.coordorigin=c.left+" "+c.top;a.coordsize=c.getWidth()+" "+c.getHeight()}},dashStyle:function(a){a=a.strokeDas [...]
+default:return a=a.split(/[ ,]/),2==a.length?1*a[0]>=2*a[1]?"longdash":1==a[0]||1==a[1]?"dot":"dash":4==a.length?1*a[0]>=2*a[1]?"longdashdot":"dashdot":"solid"}},createNode:function(a,b){var c=document.createElement(a);b&&(c.id=b);c.unselectable="on";c.onselectstart=OpenLayers.Function.False;return c},nodeTypeCompare:function(a,b){var c=b,d=c.indexOf(":");-1!=d&&(c=c.substr(d+1));var e=a.nodeName,d=e.indexOf(":");-1!=d&&(e=e.substr(d+1));return c==e},createRenderRoot:function(){return th [...]
+"_vmlRoot","div")},createRoot:function(a){return this.nodeFactory(this.container.id+a,"olv:group")},drawPoint:function(a,b){return this.drawCircle(a,b,1)},drawCircle:function(a,b,c){if(!isNaN(b.x)&&!isNaN(b.y)){var d=this.getResolution();a.style.left=((b.x-this.featureDx)/d-this.offset.x|0)-c+"px";a.style.top=(b.y/d-this.offset.y|0)-c+"px";b=2*c;a.style.width=b+"px";a.style.height=b+"px";return a}return!1},drawLineString:function(a,b){return this.drawLine(a,b,!1)},drawLinearRing:function [...]
+b,!0)},drawLine:function(a,b,c){this.setNodeDimension(a,b);for(var d=this.getResolution(),e=b.components.length,f=Array(e),g,h,k=0;k<e;k++)g=b.components[k],h=(g.x-this.featureDx)/d-this.offset.x|0,g=g.y/d-this.offset.y|0,f[k]=" "+h+","+g+" l ";b=c?" x e":" e";a.path="m"+f.join("")+b;return a},drawPolygon:function(a,b){this.setNodeDimension(a,b);var c=this.getResolution(),d=[],e,f,g,h,k,l,m,n,p,q;e=0;for(f=b.components.length;e<f;e++){d.push("m");g=b.components[e].components;h=0===e;l=k= [...]
+for(n=g.length;m<n;m++)p=g[m],q=(p.x-this.featureDx)/c-this.offset.x|0,p=p.y/c-this.offset.y|0,q=" "+q+","+p,d.push(q),0==m&&d.push(" l"),h||(k?k!=q&&(l?l!=q&&(h=!0):l=q):k=q);d.push(h?" x ":" ")}d.push("e");a.path=d.join("");return a},drawRectangle:function(a,b){var c=this.getResolution();a.style.left=((b.x-this.featureDx)/c-this.offset.x|0)+"px";a.style.top=(b.y/c-this.offset.y|0)+"px";a.style.width=(b.width/c|0)+"px";a.style.height=(b.height/c|0)+"px";return a},drawText:function(a,b,c [...]
+this.LABEL_ID_SUFFIX,"olv:rect"),e=this.nodeFactory(a+this.LABEL_ID_SUFFIX+"_textbox","olv:textbox"),f=this.getResolution();d.style.left=((c.x-this.featureDx)/f-this.offset.x|0)+"px";d.style.top=(c.y/f-this.offset.y|0)+"px";d.style.flip="y";e.innerText=b.label;"inherit"!=b.cursor&&null!=b.cursor&&(e.style.cursor=b.cursor);b.fontColor&&(e.style.color=b.fontColor);b.fontOpacity&&(e.style.filter="alpha(opacity="+100*b.fontOpacity+")");b.fontFamily&&(e.style.fontFamily=b.fontFamily);b.fontSi [...]
+b.fontSize);b.fontWeight&&(e.style.fontWeight=b.fontWeight);b.fontStyle&&(e.style.fontStyle=b.fontStyle);!0===b.labelSelect&&(d._featureId=a,e._featureId=a,e._geometry=c,e._geometryClass=c.CLASS_NAME);e.style.whiteSpace="nowrap";e.inset="1px,0px,0px,0px";d.parentNode||(d.appendChild(e),this.textRoot.appendChild(d));b=b.labelAlign||"cm";1==b.length&&(b+="m");a=e.clientWidth*OpenLayers.Renderer.VML.LABEL_SHIFT[b.substr(0,1)];e=e.clientHeight*OpenLayers.Renderer.VML.LABEL_SHIFT[b.substr(1,1 [...]
+parseInt(d.style.left)-a-1+"px";d.style.top=parseInt(d.style.top)+e+"px"},moveRoot:function(a){var b=this.map.getLayer(a.container.id);b instanceof OpenLayers.Layer.Vector.RootContainer&&(b=this.map.getLayer(this.container.id));b&&b.renderer.clear();OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this,arguments);b&&b.redraw()},importSymbol:function(a){var b=this.container.id+"-"+a,c=this.symbolCache[b];if(c)return c;c=OpenLayers.Renderer.symbol[a];if(!c)throw Error(a+" is not a val [...]
+a=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,0,0);for(var d=["m"],e=0;e<c.length;e+=2){var f=c[e],g=c[e+1];a.left=Math.min(a.left,f);a.bottom=Math.min(a.bottom,g);a.right=Math.max(a.right,f);a.top=Math.max(a.top,g);d.push(f);d.push(g);0==e&&d.push("l")}d.push("x e");c=d.join(" ");d=(a.getWidth()-a.getHeight())/2;0<d?(a.bottom-=d,a.top+=d):(a.left+=d,a.right-=d);c={path:c,size:a.getWidth(),left:a.left,bottom:a.bottom};return this.symbolCache[b]=c},CLASS_NAME:"OpenLayers.Rende [...]
+OpenLayers.Renderer.VML.LABEL_SHIFT={l:0,c:0.5,r:1,t:0,m:0.5,b:1};OpenLayers.Control.CacheRead=OpenLayers.Class(OpenLayers.Control,{fetchEvent:"tileloadstart",layers:null,autoActivate:!0,setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);var b,c=this.layers||a.layers;for(b=c.length-1;0<=b;--b)this.addLayer({layer:c[b]});if(!this.layers)a.events.on({addlayer:this.addLayer,removeLayer:this.removeLayer,scope:this})},addLayer:function(a){a.layer.events.register(this [...]
+this,this.fetch)},fetch:function(a){if(this.active&&window.localStorage&&a.tile instanceof OpenLayers.Tile.Image){var b=a.tile,c=b.url;!b.layer.crossOriginKeyword&&(OpenLayers.ProxyHost&&0===c.indexOf(OpenLayers.ProxyHost))&&(c=OpenLayers.Control.CacheWrite.urlMap[c]);if(c=window.localStorage.getItem("olCache_"+c))b.url=c,"tileerror"===a.type&&b.setImgSrc(c)}},destroy:function(){if(this.layers||this.map){var a,b=this.layers||this.map.layers;for(a=b.length-1;0<=a;--a)this.removeLayer({lay [...]
+this.map.events.un({addlayer:this.addLayer,removeLayer:this.removeLayer,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments)},CLASS_NAME:"OpenLayers.Control.CacheRead"});OpenLayers.Protocol.WFS.v1_0_0=OpenLayers.Class(OpenLayers.Protocol.WFS.v1,{version:"1.0.0",CLASS_NAME:"OpenLayers.Protocol.WFS.v1_0_0"});OpenLayers.Format.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Format.XML,{layerIdentifier:"_layer",featureIdentifier:"_feature",regExes:{trimSpace:/^\s*|\s*$/g,rem [...]
+[];if(a=this.getSiblingNodesByTagCriteria(a,this.layerIdentifier))for(var c=0,d=a.length;c<d;++c){var e=a[c],f=e.nodeName;e.prefix&&(f=f.split(":")[1]);f=f.replace(this.layerIdentifier,"");if(e=this.getSiblingNodesByTagCriteria(e,this.featureIdentifier))for(var g=0;g<e.length;g++){var h=e[g],k=this.parseGeometry(h),h=this.parseAttributes(h),h=new OpenLayers.Feature.Vector(k.geometry,h,null);h.bounds=k.bounds;h.type=f;b.push(h)}}return b},read_FeatureInfoResponse:function(a){var b=[];a=th [...]
+"*","FIELDS");for(var c=0,d=a.length;c<d;c++){var e=a[c],f={},g,h=e.attributes.length;if(0<h)for(g=0;g<h;g++){var k=e.attributes[g];f[k.nodeName]=k.nodeValue}else for(e=e.childNodes,g=0,h=e.length;g<h;++g)k=e[g],3!=k.nodeType&&(f[k.getAttribute("name")]=k.getAttribute("value"));b.push(new OpenLayers.Feature.Vector(null,f,null))}return b},getSiblingNodesByTagCriteria:function(a,b){var c=[],d,e,f,g;if(a&&a.hasChildNodes()){d=a.childNodes;f=d.length;for(var h=0;h<f;h++){for(g=d[h];g&&1!=g.n [...]
+g.nextSibling,h++;e=g?g.nodeName:"";0<e.length&&-1<e.indexOf(b)?c.push(g):(e=this.getSiblingNodesByTagCriteria(g,b),0<e.length&&(0==c.length?c=e:c.push(e)))}}return c},parseAttributes:function(a){var b={};if(1==a.nodeType){a=a.childNodes;for(var c=a.length,d=0;d<c;++d){var e=a[d];if(1==e.nodeType){var f=e.childNodes,e=e.prefix?e.nodeName.split(":")[1]:e.nodeName;0==f.length?b[e]=null:1==f.length&&(f=f[0],3==f.nodeType||4==f.nodeType)&&(f=f.nodeValue.replace(this.regExes.trimSpace,""),b[e [...]
+parseGeometry:function(a){this.gmlFormat||(this.gmlFormat=new OpenLayers.Format.GML);a=this.gmlFormat.parseFeature(a);var b,c=null;a&&(b=a.geometry&&a.geometry.clone(),c=a.bounds&&a.bounds.clone(),a.destroy());return{geometry:b,bounds:c}},CLASS_NAME:"OpenLayers.Format.WMSGetFeatureInfo"});OpenLayers.Control.WMTSGetFeatureInfo=OpenLayers.Class(OpenLayers.Control,{hover:!1,requestEncoding:"KVP",drillDown:!1,maxFeatures:10,clickCallback:"click",layers:null,queryVisible:!0,infoFormat:"text/h [...]
+!0===this.drillDown&&(this.hover=!1);this.hover?this.handler=new OpenLayers.Handler.Hover(this,{move:this.cancelHover,pause:this.getInfoForHover},OpenLayers.Util.extend(this.handlerOptions.hover||{},{delay:250})):(a={},a[this.clickCallback]=this.getInfoForClick,this.handler=new OpenLayers.Handler.Click(this,a,this.handlerOptions.click||{}))},getInfoForClick:function(a){this.request(a.xy,{})},getInfoForHover:function(a){this.request(a.xy,{hover:!0})},cancelHover:function(){this.hoverReque [...]
+0>=this.pending&&(OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait"),this.pending=0),this.hoverRequest.abort(),this.hoverRequest=null)},findLayers:function(){for(var a=this.layers||this.map.layers,b=[],c,d=a.length-1;0<=d&&(c=a[d],!(c instanceof OpenLayers.Layer.WMTS)||(c.requestEncoding!==this.requestEncoding||this.queryVisible&&!c.getVisibility())||(b.push(c),this.drillDown&&!this.hover));--d);return b},buildRequestOptions:function(a,b){var c=this.map.getLonLatFromPixe [...]
+c.lat,c.lon,c.lat)),d=OpenLayers.Util.getParameters(d),c=a.getTileInfo(c);OpenLayers.Util.extend(d,{service:"WMTS",version:a.version,request:"GetFeatureInfo",infoFormat:this.infoFormat,i:c.i,j:c.j});OpenLayers.Util.applyDefaults(d,this.vendorParams);return{url:OpenLayers.Util.isArray(a.url)?a.url[0]:a.url,params:OpenLayers.Util.upperCaseObject(d),callback:function(c){this.handleResponse(b,c,a)},scope:this}},request:function(a,b){b=b||{};var c=this.findLayers();if(0<c.length){for(var d,e, [...]
+g;f++)e=c[f],d=this.events.triggerEvent("beforegetfeatureinfo",{xy:a,layer:e}),!1!==d&&(++this.pending,d=this.buildRequestOptions(e,a),d=OpenLayers.Request.GET(d),!0===b.hover&&(this.hoverRequest=d));0<this.pending&&OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait")}},handleResponse:function(a,b,c){--this.pending;0>=this.pending&&(OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait"),this.pending=0);if(b.status&&(200>b.status||300<=b.status))this.events.trigge [...]
+{xy:a,request:b,layer:c});else{var d=b.responseXML;d&&d.documentElement||(d=b.responseText);var e,f;try{e=this.format.read(d)}catch(g){f=!0,this.events.triggerEvent("exception",{xy:a,request:b,error:g,layer:c})}f||this.events.triggerEvent("getfeatureinfo",{text:b.responseText,features:e,request:b,xy:a,layer:c})}},CLASS_NAME:"OpenLayers.Control.WMTSGetFeatureInfo"});OpenLayers.Protocol.CSW.v2_0_2=OpenLayers.Class(OpenLayers.Protocol,{formatOptions:null,initialize:function(a){OpenLayers.Pr [...]
+this.options||{});var b=new OpenLayers.Protocol.Response({requestType:"read"}),c=this.format.write(a.params||a);b.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,b,a),params:a.params,headers:a.headers,data:c});return b},handleRead:function(a,b){if(b.callback){var c=a.priv;200<=c.status&&300>c.status?(a.data=this.parseData(c),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE;b.callback.call(b.scope,a)}},parseData: [...]
+a.responseXML;b&&b.documentElement||(b=a.responseText);return!b||0>=b.length?null:this.format.read(b)},CLASS_NAME:"OpenLayers.Protocol.CSW.v2_0_2"});OpenLayers.Format.WCSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WCSCapabilities.v1,{namespaces:{wcs:"http://www.opengis.net/wcs/1.1",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",ows:"http://www.opengis.net/ows/1.1"},errorProperty:"operationsMetadata",readers:{wcs:OpenLayers.Util.applyDefaul [...]
+b){var c={};this.readChildNodes(a,c);b.push(c)},Identifier:function(a,b){b.identifier=this.getChildValue(a)},Title:function(a,b){b.title=this.getChildValue(a)},Abstract:function(a,b){b["abstract"]=this.getChildValue(a)},SupportedCRS:function(a,b){var c=this.getChildValue(a);c&&(b.supportedCRS||(b.supportedCRS=[]),b.supportedCRS.push(c))},SupportedFormat:function(a,b){var c=this.getChildValue(a);c&&(b.supportedFormat||(b.supportedFormat=[]),b.supportedFormat.push(c))}},OpenLayers.Format.W [...]
+ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WCSCapabilities.v1_1_0"});OpenLayers.Control.Graticule=OpenLayers.Class(OpenLayers.Control,{autoActivate:!0,intervals:[45,30,20,10,5,2,1,0.5,0.2,0.1,0.05,0.01,0.005,0.002,0.001],displayInLayerSwitcher:!0,visible:!0,numPoints:50,targetSize:200,layerName:null,labelled:!0,labelFormat:"dm",lineSymbolizer:{strokeColor:"#333",strokeWidth:1,strokeOpacity:0.5},labelSymbolizer:{},gratLayer:null,initialize: [...]
+this.labelSymbolizer.stroke=!1;this.labelSymbolizer.fill=!1;this.labelSymbolizer.label="${label}";this.labelSymbolizer.labelAlign="${labelAlign}";this.labelSymbolizer.labelXOffset="${xOffset}";this.labelSymbolizer.labelYOffset="${yOffset}"},destroy:function(){this.deactivate();OpenLayers.Control.prototype.destroy.apply(this,arguments);this.gratLayer&&(this.gratLayer.destroy(),this.gratLayer=null)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.gratLayer) [...]
+{rules:[new OpenLayers.Rule({symbolizer:{Point:this.labelSymbolizer,Line:this.lineSymbolizer}})]});this.gratLayer=new OpenLayers.Layer.Vector(this.layerName,{styleMap:new OpenLayers.StyleMap({"default":a}),visibility:this.visible,displayInLayerSwitcher:this.displayInLayerSwitcher})}return this.div},activate:function(){return OpenLayers.Control.prototype.activate.apply(this,arguments)?(this.map.addLayer(this.gratLayer),this.map.events.register("moveend",this,this.update),this.update(),!0) [...]
+arguments)?(this.map.events.unregister("moveend",this,this.update),this.map.removeLayer(this.gratLayer),!0):!1},update:function(){var a=this.map.getExtent();if(a){this.gratLayer.destroyFeatures();var b=new OpenLayers.Projection("EPSG:4326"),c=this.map.getProjectionObject(),d=this.map.getResolution();c.proj&&"longlat"==c.proj.projName&&(this.numPoints=1);var e=this.map.getCenter(),f=new OpenLayers.Pixel(e.lon,e.lat);OpenLayers.Projection.transform(f,c,b);for(var e=this.targetSize*d,e=e*e, [...]
+this.intervals[d];var h=g/2,k=f.offset({x:-h,y:-h}),h=f.offset({x:h,y:h});OpenLayers.Projection.transform(k,b,c);OpenLayers.Projection.transform(h,b,c);if((k.x-h.x)*(k.x-h.x)+(k.y-h.y)*(k.y-h.y)<=e)break}f.x=Math.floor(f.x/g)*g;f.y=Math.floor(f.y/g)*g;var d=0,e=[f.clone()],h=f.clone(),l;do h=h.offset({x:0,y:g}),l=OpenLayers.Projection.transform(h.clone(),b,c),e.unshift(h);while(a.containsPixel(l)&&1E3>++d);h=f.clone();do h=h.offset({x:0,y:-g}),l=OpenLayers.Projection.transform(h.clone(), [...]
+while(a.containsPixel(l)&&1E3>++d);d=0;k=[f.clone()];h=f.clone();do h=h.offset({x:-g,y:0}),l=OpenLayers.Projection.transform(h.clone(),b,c),k.unshift(h);while(a.containsPixel(l)&&1E3>++d);h=f.clone();do h=h.offset({x:g,y:0}),l=OpenLayers.Projection.transform(h.clone(),b,c),k.push(h);while(a.containsPixel(l)&&1E3>++d);g=[];for(d=0;d<k.length;++d){l=k[d].x;for(var f=[],m=null,n=Math.min(e[0].y,90),h=Math.max(e[e.length-1].y,-90),p=(n-h)/this.numPoints,n=h,h=0;h<=this.numPoints;++h){var q=n [...]
+n);q.transform(b,c);f.push(q);n+=p;q.y>=a.bottom&&!m&&(m=q)}this.labelled&&(m=new OpenLayers.Geometry.Point(m.x,a.bottom),l={value:l,label:this.labelled?OpenLayers.Util.getFormattedLonLat(l,"lon",this.labelFormat):"",labelAlign:"cb",xOffset:0,yOffset:2},this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(m,l)));f=new OpenLayers.Geometry.LineString(f);g.push(new OpenLayers.Feature.Vector(f))}for(h=0;h<e.length;++h)if(n=e[h].y,!(-90>n||90<n)){f=[];d=k[0].x;p=(k[k.length-1].x-d)/this.n [...]
+l=d;m=null;for(d=0;d<=this.numPoints;++d)q=new OpenLayers.Geometry.Point(l,n),q.transform(b,c),f.push(q),l+=p,q.x<a.right&&(m=q);this.labelled&&(m=new OpenLayers.Geometry.Point(a.right,m.y),l={value:n,label:this.labelled?OpenLayers.Util.getFormattedLonLat(n,"lat",this.labelFormat):"",labelAlign:"rb",xOffset:-2,yOffset:2},this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(m,l)));f=new OpenLayers.Geometry.LineString(f);g.push(new OpenLayers.Feature.Vector(f))}this.gratLayer.addFeatur [...]
+OpenLayers.Rico.Corner={round:function(a,b){a=OpenLayers.Util.getElement(a);this._setOptions(b);var c=this.options.color;"fromElement"==this.options.color&&(c=this._background(a));var d=this.options.bgColor;"fromParent"==this.options.bgColor&&(d=this._background(a.offsetParent));this._roundCornersImpl(a,c,d)},changeColor:function(a,b){a.style.backgroundColor=b;for(var c=a.parentNode.getElementsByTagName("span"),d=0;d<c.length;d++)c[d].style.backgroundColor=b},changeOpacity:function(a,b){ [...]
+100*b+")";a.style.opacity=b;a.style.filter=c;for(var d=a.parentNode.getElementsByTagName("span"),e=0;e<d.length;e++)d[e].style.opacity=b,d[e].style.filter=c},reRound:function(a,b){var c=a.parentNode.childNodes[2];a.parentNode.removeChild(a.parentNode.childNodes[0]);a.parentNode.removeChild(c);this.round(a.parentNode,b)},_roundCornersImpl:function(a,b,c){this.options.border&&this._renderBorder(a,c);this._isTopRounded()&&this._roundTopCorners(a,b,c);this._isBottomRounded()&&this._roundBott [...]
+b,c)},_renderBorder:function(a,b){var c="1px solid "+this._borderColor(b);a.innerHTML="<div "+("style='border-left: "+c+";"+("border-right: "+c)+"'")+">"+a.innerHTML+"</div>"},_roundTopCorners:function(a,b,c){for(var d=this._createCorner(c),e=0;e<this.options.numSlices;e++)d.appendChild(this._createCornerSlice(b,c,e,"top"));a.style.paddingTop=0;a.insertBefore(d,a.firstChild)},_roundBottomCorners:function(a,b,c){for(var d=this._createCorner(c),e=this.options.numSlices-1;0<=e;e--)d.appendC [...]
+c,e,"bottom"));a.style.paddingBottom=0;a.appendChild(d)},_createCorner:function(a){var b=document.createElement("div");b.style.backgroundColor=this._isTransparent()?"transparent":a;return b},_createCornerSlice:function(a,b,c,d){var e=document.createElement("span"),f=e.style;f.backgroundColor=a;f.display="block";f.height="1px";f.overflow="hidden";f.fontSize="1px";a=this._borderColor(a,b);this.options.border&&0==c?(f.borderTopStyle="solid",f.borderTopWidth="1px",f.borderLeftWidth="0px",f.b [...]
+"0px",f.borderBottomWidth="0px",f.height="0px",f.borderColor=a):a&&(f.borderColor=a,f.borderStyle="solid",f.borderWidth="0px 1px");this.options.compact||c!=this.options.numSlices-1||(f.height="2px");this._setMargin(e,c,d);this._setBorder(e,c,d);return e},_setOptions:function(a){this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:!0,border:!1,compact:!1};OpenLayers.Util.extend(this.options,a||{});this.options.numSlices=this.options.compact?2:4;this._isTransparent()& [...]
+!1)},_whichSideTop:function(){return this._hasString(this.options.corners,"all","top")||0<=this.options.corners.indexOf("tl")&&0<=this.options.corners.indexOf("tr")?"":0<=this.options.corners.indexOf("tl")?"left":0<=this.options.corners.indexOf("tr")?"right":""},_whichSideBottom:function(){return this._hasString(this.options.corners,"all","bottom")||0<=this.options.corners.indexOf("bl")&&0<=this.options.corners.indexOf("br")?"":0<=this.options.corners.indexOf("bl")?"left":0<=this.options [...]
+"right":""},_borderColor:function(a,b){return"transparent"==a?b:this.options.border?this.options.border:this.options.blend?this._blend(b,a):""},_setMargin:function(a,b,c){b=this._marginSize(b);c="top"==c?this._whichSideTop():this._whichSideBottom();"left"==c?(a.style.marginLeft=b+"px",a.style.marginRight="0px"):"right"==c?(a.style.marginRight=b+"px",a.style.marginLeft="0px"):(a.style.marginLeft=b+"px",a.style.marginRight=b+"px")},_setBorder:function(a,b,c){b=this._borderSize(b);c="top"== [...]
+this._whichSideBottom();"left"==c?(a.style.borderLeftWidth=b+"px",a.style.borderRightWidth="0px"):"right"==c?(a.style.borderRightWidth=b+"px",a.style.borderLeftWidth="0px"):(a.style.borderLeftWidth=b+"px",a.style.borderRightWidth=b+"px");!1!=this.options.border&&(a.style.borderLeftWidth=b+"px",a.style.borderRightWidth=b+"px")},_marginSize:function(a){if(this._isTransparent())return 0;var b=[5,3,2,1],c=[3,2,1,0],d=[2,1],e=[1,0];return this.options.compact&&this.options.blend?e[a]:this.opt [...]
+d[a]:this.options.blend?c[a]:b[a]},_borderSize:function(a){var b=[5,3,2,1],c=[2,1,1,1],d=[1,0],e=[0,2,0,0];return this.options.compact&&(this.options.blend||this._isTransparent())?1:this.options.compact?d[a]:this.options.blend?c[a]:this.options.border?e[a]:this._isTransparent()?b[a]:0},_hasString:function(a){for(var b=1;b<arguments.length;b++)if(0<=a.indexOf(arguments[b]))return!0;return!1},_blend:function(a,b){var c=OpenLayers.Rico.Color.createFromHex(a);c.blend(OpenLayers.Rico.Color.cr [...]
+return c},_background:function(a){try{return OpenLayers.Rico.Color.createColorFromBackground(a).asHex()}catch(b){return"#ffffff"}},_isTransparent:function(){return"transparent"==this.options.color},_isTopRounded:function(){return this._hasString(this.options.corners,"all","top","tl","tr")},_isBottomRounded:function(){return this._hasString(this.options.corners,"all","bottom","bl","br")},_hasSingleTextChild:function(a){return 1==a.childNodes.length&&3==a.childNodes[0].nodeType}};OpenLayer [...]
+this),displayClass:this.displayClass+" "+this.displayClass+"Previous"};OpenLayers.Util.extend(a,this.previousOptions);this.previous=new OpenLayers.Control.Button(a);a={trigger:OpenLayers.Function.bind(this.nextTrigger,this),displayClass:this.displayClass+" "+this.displayClass+"Next"};OpenLayers.Util.extend(a,this.nextOptions);this.next=new OpenLayers.Control.Button(a);this.clear()},onPreviousChange:function(a,b){a&&!this.previous.active?this.previous.activate():!a&&this.previous.active&& [...]
+onNextChange:function(a,b){a&&!this.next.active?this.next.activate():!a&&this.next.active&&this.next.deactivate()},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this);this.previous.destroy();this.next.destroy();this.deactivate();for(var a in this)this[a]=null},setMap:function(a){this.map=a;this.next.setMap(a);this.previous.setMap(a)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.next.draw();this.previous.draw()},previousTrigger:function(){va [...]
+b=this.previousStack.shift();void 0!=b?(this.nextStack.unshift(a),this.previousStack.unshift(b),this.restoring=!0,this.restore(b),this.restoring=!1,this.onNextChange(this.nextStack[0],this.nextStack.length),this.onPreviousChange(this.previousStack[1],this.previousStack.length-1)):this.previousStack.unshift(a);return b},nextTrigger:function(){var a=this.nextStack.shift();void 0!=a&&(this.previousStack.unshift(a),this.restoring=!0,this.restore(a),this.restoring=!1,this.onNextChange(this.ne [...]
+this.nextStack.length),this.onPreviousChange(this.previousStack[1],this.previousStack.length-1));return a},clear:function(){this.previousStack=[];this.previous.deactivate();this.nextStack=[];this.next.deactivate()},getState:function(){return{center:this.map.getCenter(),resolution:this.map.getResolution(),projection:this.map.getProjectionObject(),units:this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units}},restore:function(a){var b,c;if(this.map.getProjectio [...]
+a.projection)c=this.map.getZoomForResolution(a.resolution),b=a.center;else{b=a.center.clone();b.transform(a.projection,this.map.getProjectionObject());c=a.units;var d=this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units;c=this.map.getZoomForResolution((c&&d?OpenLayers.INCHES_PER_UNIT[c]/OpenLayers.INCHES_PER_UNIT[d]:1)*a.resolution)}this.map.setCenter(b,c)},setListeners:function(){this.listeners={};for(var a in this.registry)this.listeners[a]=OpenLayers.Fun [...]
+this.registry[a].apply(this,arguments);this.previousStack.unshift(b);if(1<this.previousStack.length)this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);this.previousStack.length>this.limit+1&&this.previousStack.pop();0<this.nextStack.length&&(this.nextStack=[],this.onNextChange(null,0))}return!0},this)},activate:function(){var a=!1;if(this.map&&OpenLayers.Control.prototype.activate.apply(this)){null==this.listeners&&this.setListeners();for(var b in this.listeners)thi [...]
+this,this.listeners[b]);a=!0;0==this.previousStack.length&&this.initStack()}return a},initStack:function(){this.map.getCenter()&&this.listeners.moveend()},deactivate:function(){var a=!1;if(this.map&&OpenLayers.Control.prototype.deactivate.apply(this)){for(var b in this.listeners)this.map.events.unregister(b,this,this.listeners[b]);this.clearOnDeactivate&&this.clear();a=!0}return a},CLASS_NAME:"OpenLayers.Control.NavigationHistory"});OpenLayers.Layer.UTFGrid=OpenLayers.Class(OpenLayers.La [...]
+[a])},getFeatureInfo:function(a){var b=null;(a=this.getTileData(a))&&a.tile&&(b=a.tile.getFeatureInfo(a.i,a.j));return b},getFeatureId:function(a){var b=null;a=this.getTileData(a);a.tile&&(b=a.tile.getFeatureId(a.i,a.j));return b},CLASS_NAME:"OpenLayers.Layer.UTFGrid"});OpenLayers.TileManager=OpenLayers.Class({cacheSize:256,tilesPerFrame:2,frameDelay:16,moveDelay:100,zoomDelay:200,maps:null,tileQueueId:null,tileQueue:null,tileCache:null,tileCacheIndex:null,initialize:function(a){OpenLaye [...]
+a.events.on({move:this.move,zoomend:this.zoomEnd,changelayer:this.changeLayer,addlayer:this.addLayer,preremovelayer:this.removeLayer,scope:this})}},removeMap:function(a){if(!this._destroyed&&OpenLayers.Layer.Grid){window.clearTimeout(this.tileQueueId[a.id]);if(a.layers)for(var b=0,c=a.layers.length;b<c;++b)this.removeLayer({layer:a.layers[b]});a.events&&a.events.un({move:this.move,zoomend:this.zoomEnd,changelayer:this.changeLayer,addlayer:this.addLayer,preremovelayer:this.removeLayer,sco [...]
+delete this.tileQueue[a.id];delete this.tileQueueId[a.id];OpenLayers.Util.removeItem(this.maps,a)}},move:function(a){this.updateTimeout(a.object,this.moveDelay,!0)},zoomEnd:function(a){this.updateTimeout(a.object,this.zoomDelay)},changeLayer:function(a){"visibility"!==a.property&&"params"!==a.property||this.updateTimeout(a.object,0)},addLayer:function(a){a=a.layer;if(a instanceof OpenLayers.Layer.Grid){a.events.on({addtile:this.addTile,retile:this.clearTileQueue,scope:this});var b,c,d;fo [...]
+1;0<=b;--b)for(c=a.grid[b].length-1;0<=c;--c)d=a.grid[b][c],this.addTile({tile:d}),d.url&&!d.imgDiv&&this.manageTileCache({object:d})}},removeLayer:function(a){a=a.layer;if(a instanceof OpenLayers.Layer.Grid&&(this.clearTileQueue({object:a}),a.events&&a.events.un({addtile:this.addTile,retile:this.clearTileQueue,scope:this}),a.grid)){var b,c,d;for(b=a.grid.length-1;0<=b;--b)for(c=a.grid[b].length-1;0<=c;--c)d=a.grid[b][c],this.unloadTile({object:d})}},updateTimeout:function(a,b,c){window. [...]
+var d=this.tileQueue[a.id];if(!c||d.length)this.tileQueueId[a.id]=window.setTimeout(OpenLayers.Function.bind(function(){this.drawTilesFromQueue(a);d.length&&this.updateTimeout(a,this.frameDelay)},this),b)},addTile:function(a){if(a.tile instanceof OpenLayers.Tile.Image)a.tile.events.on({beforedraw:this.queueTileDraw,beforeload:this.manageTileCache,loadend:this.addToCache,unload:this.unloadTile,scope:this});else this.removeLayer({layer:a.tile.layer})},unloadTile:function(a){a=a.object;a.ev [...]
+beforeload:this.manageTileCache,loadend:this.addToCache,unload:this.unloadTile,scope:this});OpenLayers.Util.removeItem(this.tileQueue[a.layer.map.id],a)},queueTileDraw:function(a){a=a.object;var b=!1,c=a.layer,d=c.getURL(a.bounds),e=this.tileCache[d];e&&"olTileImage"!==e.className&&(delete this.tileCache[d],OpenLayers.Util.removeItem(this.tileCacheIndex,d),e=null);!c.url||!c.async&&e||(b=this.tileQueue[c.map.id],~OpenLayers.Util.indexOf(b,a)||b.push(a),b=!0);return!b},drawTilesFromQueue: [...]
+this.tileQueue[a.id],c=this.tilesPerFrame;for(a=a.zoomTween&&a.zoomTween.playing;!a&&b.length&&c;)b.shift().draw(!0),--c},manageTileCache:function(a){a=a.object;var b=this.tileCache[a.url];b&&(b.parentNode&&OpenLayers.Element.hasClass(b.parentNode,"olBackBuffer")&&(b.parentNode.removeChild(b),b.id=null),b.parentNode||(b.style.visibility="hidden",b.style.opacity=0,a.setImage(b),OpenLayers.Util.removeItem(this.tileCacheIndex,a.url),this.tileCacheIndex.push(a.url)))},addToCache:function(a){ [...]
+this.tileCache[a.url]||OpenLayers.Element.hasClass(a.imgDiv,"olImageLoadError")||(this.tileCacheIndex.length>=this.cacheSize&&(delete this.tileCache[this.tileCacheIndex[0]],this.tileCacheIndex.shift()),this.tileCache[a.url]=a.imgDiv,this.tileCacheIndex.push(a.url))},clearTileQueue:function(a){a=a.object;for(var b=this.tileQueue[a.map.id],c=b.length-1;0<=c;--c)b[c].layer===a&&b.splice(c,1)},destroy:function(){for(var a=this.maps.length-1;0<=a;--a)this.removeMap(this.maps[a]);this.tileCach [...]
+this.tileQueueId=this.tileQueue=this.maps=null;this._destroyed=!0}});OpenLayers.Layer.ArcGISCache=OpenLayers.Class(OpenLayers.Layer.XYZ,{url:null,tileOrigin:null,tileSize:new OpenLayers.Size(256,256),useArcGISServer:!0,type:"png",useScales:!1,overrideDPI:!1,initialize:function(a,b,c){OpenLayers.Layer.XYZ.prototype.initialize.apply(this,arguments);this.resolutions&&(this.serverResolutions=this.resolutions,this.maxExtent=this.getMaxExtentForResolution(this.resolutions[0]));if(this.layerInf [...]
+d.fullExtent.xmax,d.fullExtent.ymax);this.projection="EPSG:"+d.spatialReference.wkid;this.sphericalMercator=102100==d.spatialReference.wkid;this.units="esriFeet"==d.units?"ft":"m";if(d.tileInfo){this.tileSize=new OpenLayers.Size(d.tileInfo.width||d.tileInfo.cols,d.tileInfo.height||d.tileInfo.rows);this.tileOrigin=new OpenLayers.LonLat(d.tileInfo.origin.x,d.tileInfo.origin.y);var f=new OpenLayers.Geometry.Point(e.left,e.top),e=new OpenLayers.Geometry.Point(e.right,e.bottom);this.useScales [...]
+[]:this.resolutions=[];this.lods=[];for(var g in d.tileInfo.lods)if(d.tileInfo.lods.hasOwnProperty(g)){var h=d.tileInfo.lods[g];this.useScales?this.scales.push(h.scale):this.resolutions.push(h.resolution);var k=this.getContainingTileCoords(f,h.resolution);h.startTileCol=k.x;h.startTileRow=k.y;k=this.getContainingTileCoords(e,h.resolution);h.endTileCol=k.x;h.endTileRow=k.y;this.lods.push(h)}this.maxExtent=this.calculateMaxExtentWithLOD(this.lods[0]);this.serverResolutions=this.resolutions [...]
+d.tileInfo.dpi&&(OpenLayers.DOTS_PER_INCH=d.tileInfo.dpi)}}},getContainingTileCoords:function(a,b){return new OpenLayers.Pixel(Math.max(Math.floor((a.x-this.tileOrigin.lon)/(this.tileSize.w*b)),0),Math.max(Math.floor((this.tileOrigin.lat-a.y)/(this.tileSize.h*b)),0))},calculateMaxExtentWithLOD:function(a){var b=this.tileOrigin.lon+a.startTileCol*this.tileSize.w*a.resolution,c=this.tileOrigin.lat-a.startTileRow*this.tileSize.h*a.resolution;return new OpenLayers.Bounds(b,c-(a.endTileRow-a. [...]
+1)*this.tileSize.h*a.resolution,b+(a.endTileCol-a.startTileCol+1)*this.tileSize.w*a.resolution,c)},calculateMaxExtentWithExtent:function(a,b){var c=new OpenLayers.Geometry.Point(a.left,a.top),d=new OpenLayers.Geometry.Point(a.right,a.bottom),c=this.getContainingTileCoords(c,b),d=this.getContainingTileCoords(d,b);return this.calculateMaxExtentWithLOD({resolution:b,startTileCol:c.x,startTileRow:c.y,endTileCol:d.x,endTileRow:d.y})},getUpperLeftTileCoord:function(a){var b=new OpenLayers.Geom [...]
+this.maxExtent.top);return this.getContainingTileCoords(b,a)},getLowerRightTileCoord:function(a){var b=new OpenLayers.Geometry.Point(this.maxExtent.right,this.maxExtent.bottom);return this.getContainingTileCoords(b,a)},getMaxExtentForResolution:function(a){var b=this.getUpperLeftTileCoord(a),c=this.getLowerRightTileCoord(a),d=this.tileOrigin.lon+b.x*this.tileSize.w*a,e=this.tileOrigin.lat-b.y*this.tileSize.h*a;return new OpenLayers.Bounds(d,e-(c.y-b.y+1)*this.tileSize.h*a,d+(c.x-b.x+1)*t [...]
+a,e)},clone:function(a){null==a&&(a=new OpenLayers.Layer.ArcGISCache(this.name,this.url,this.options));return OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},initGriddedTiles:function(a){delete this._tileOrigin;OpenLayers.Layer.XYZ.prototype.initGriddedTiles.apply(this,arguments)},getMaxExtent:function(){var a=this.map.getResolution();return this.maxExtent=this.getMaxExtentForResolution(a)},getTileOrigin:function(){if(!this._tileOrigin){var a=this.getMaxExtent();this._tileOrigin=ne [...]
+a.bottom)}return this._tileOrigin},getURL:function(a){var b=this.getResolution(),c=this.tileOrigin.lon+b*this.tileSize.w/2,d=this.tileOrigin.lat-b*this.tileSize.h/2;a=a.getCenterLonLat();c=Math.round(Math.abs((a.lon-c)/(b*this.tileSize.w)));d=Math.round(Math.abs((d-a.lat)/(b*this.tileSize.h)));a=this.map.getZoom();if(this.lods){if(b=this.lods[this.map.getZoom()],c<b.startTileCol||c>b.endTileCol||d<b.startTileRow||d>b.endTileRow)return null}else{var e=this.getUpperLeftTileCoord(b),b=this. [...]
+if(c<e.x||c>=b.x||d<e.y||d>=b.y)return null}b=this.url;e=""+c+d+a;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(e,b));this.useArcGISServer?b+="/tile/${z}/${y}/${x}":(c="C"+OpenLayers.Number.zeroPad(c,8,16),d="R"+OpenLayers.Number.zeroPad(d,8,16),a="L"+OpenLayers.Number.zeroPad(a,2,10),b=b+"/${z}/${y}/${x}."+this.type);b=OpenLayers.String.format(b,{x:c,y:d,z:a});return OpenLayers.Util.urlAppend(b,OpenLayers.Util.getParameterString(this.params))},CLASS_NAME:"OpenLayers.Layer.ArcGISCache"}) [...]
+!0===this.drillDown&&(this.hover=!1);this.hover?this.handler=new OpenLayers.Handler.Hover(this,{move:this.cancelHover,pause:this.getInfoForHover},OpenLayers.Util.extend(this.handlerOptions.hover||{},{delay:250})):(a={},a[this.clickCallback]=this.getInfoForClick,this.handler=new OpenLayers.Handler.Click(this,a,this.handlerOptions.click||{}))},getInfoForClick:function(a){this.events.triggerEvent("beforegetfeatureinfo",{xy:a.xy});OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWai [...]
+{})},getInfoForHover:function(a){this.events.triggerEvent("beforegetfeatureinfo",{xy:a.xy});this.request(a.xy,{hover:!0})},cancelHover:function(){this.hoverRequest&&(this.hoverRequest.abort(),this.hoverRequest=null)},findLayers:function(){for(var a=this.layers||this.map.layers,b=[],c,d,e=a.length-1;0<=e;--e)c=a[e],c instanceof OpenLayers.Layer.WMS&&(!this.queryVisible||c.getVisibility())&&(d=OpenLayers.Util.isArray(c.url)?c.url[0]:c.url,!1!==this.drillDown||this.url||(this.url=d),(!0===t [...]
+this.urlMatches(d))&&b.push(c));return b},urlMatches:function(a){var b=OpenLayers.Util.isEquivalentUrl(this.url,a);if(!b&&this.layerUrls)for(var c=0,d=this.layerUrls.length;c<d;++c)if(OpenLayers.Util.isEquivalentUrl(this.layerUrls[c],a)){b=!0;break}return b},buildWMSOptions:function(a,b,c,d){for(var e=[],f=[],g=0,h=b.length;g<h;g++)null!=b[g].params.LAYERS&&(e=e.concat(b[g].params.LAYERS),f=f.concat(this.getStyleNames(b[g])));b=b[0];g=this.map.getProjection();(h=b.projection)&&h.equals(t [...]
+(g=h.getCode());d=OpenLayers.Util.extend({service:"WMS",version:b.params.VERSION,request:"GetFeatureInfo",exceptions:b.params.EXCEPTIONS,bbox:this.map.getExtent().toBBOX(null,b.reverseAxisOrder()),feature_count:this.maxFeatures,height:this.map.getSize().h,width:this.map.getSize().w,format:d,info_format:b.params.INFO_FORMAT||this.infoFormat},1.3<=parseFloat(b.params.VERSION)?{crs:g,i:parseInt(c.x),j:parseInt(c.y)}:{srs:g,x:parseInt(c.x),y:parseInt(c.y)});0!=e.length&&(d=OpenLayers.Util.ex [...]
+query_layers:e,styles:f},d));OpenLayers.Util.applyDefaults(d,this.vendorParams);return{url:a,params:OpenLayers.Util.upperCaseObject(d),callback:function(b){this.handleResponse(c,b,a)},scope:this}},getStyleNames:function(a){return a.params.STYLES?a.params.STYLES:OpenLayers.Util.isArray(a.params.LAYERS)?Array(a.params.LAYERS.length):a.params.LAYERS.replace(/[^,]/g,"")},request:function(a,b){var c=this.findLayers();if(0==c.length)this.events.triggerEvent("nogetfeatureinfo"),OpenLayers.Eleme [...]
+"olCursorWait");else if(b=b||{},!1===this.drillDown){var c=this.buildWMSOptions(this.url,c,a,c[0].params.FORMAT),d=OpenLayers.Request.GET(c);!0===b.hover&&(this.hoverRequest=d)}else{this._numRequests=this._requestCount=0;this.features=[];for(var d={},e,f=0,g=c.length;f<g;f++){var h=c[f];e=OpenLayers.Util.isArray(h.url)?h.url[0]:h.url;e in d?d[e].push(h):(this._numRequests++,d[e]=[h])}for(e in d)c=d[e],c=this.buildWMSOptions(e,c,a,c[0].params.FORMAT),OpenLayers.Request.GET(c)}},triggerGet [...]
+b,c){this.events.triggerEvent("getfeatureinfo",{text:a.responseText,features:c,request:a,xy:b});OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait")},handleResponse:function(a,b,c){var d=b.responseXML;d&&d.documentElement||(d=b.responseText);d=this.format.read(d);!1===this.drillDown?this.triggerGetFeatureInfo(b,a,d):(this._requestCount++,this._features="object"===this.output?(this._features||[]).concat({url:c,features:d}):(this._features||[]).concat(d),this._requestCount== [...]
+(this.triggerGetFeatureInfo(b,a,this._features.concat()),delete this._features,delete this._requestCount,delete this._numRequests))},CLASS_NAME:"OpenLayers.Control.WMSGetFeatureInfo"});OpenLayers.Format.WMSCapabilities.v1_3_0=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_3,{version:"1.3.0",CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_3_0"});OpenLayers.Format.SOSGetFeatureOfInterest=OpenLayers.Class(OpenLayers.Format.XML,{VERSION:"1.0.0",namespaces:{sos:"http://www.opengis.net [...]
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={features:[]};this.readNode(a,b);a=[];for(var c=0,d=b.features.length;c<d;c++){var e=b.features[c];this.internalProjection&&(this.externalProjection&&e.components[0])&&e.components[0].transform(this.externalProjection,this.internalProjection);e=new OpenLayers.Feature.Vector(e.components[0],e.attributes);a.push(e)}return a},readers:{sa:{SamplingPoint:function(a,b){if(!b.attribu [...]
+{attributes:{}};b.features.push(c);b=c}b.attributes.id=this.getAttributeNS(a,this.namespaces.gml,"id");this.readChildNodes(a,b)},position:function(a,b){this.readChildNodes(a,b)}},gml:OpenLayers.Util.applyDefaults({FeatureCollection:function(a,b){this.readChildNodes(a,b)},featureMember:function(a,b){var c={attributes:{}};b.features.push(c);this.readChildNodes(a,c)},name:function(a,b){b.attributes.name=this.getChildValue(a)},pos:function(a,b){this.externalProjection||(this.externalProjecti [...]
+OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(this,[a,b])}},OpenLayers.Format.GML.v3.prototype.readers.gml)},writers:{sos:{GetFeatureOfInterest:function(a){for(var b=this.createElementNSPlus("GetFeatureOfInterest",{attributes:{version:this.VERSION,service:"SOS","xsi:schemaLocation":this.schemaLocation}}),c=0,d=a.fois.length;c<d;c++)this.writeNode("FeatureOfInterestId",{foi:a.fois[c]},b);return b},FeatureOfInterestId:function(a){return this.createElementNSPlus("FeatureOfInteres [...]
+CLASS_NAME:"OpenLayers.Format.SOSGetFeatureOfInterest"});OpenLayers.Format.SOSGetObservation=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ows:"http://www.opengis.net/ows",gml:"http://www.opengis.net/gml",sos:"http://www.opengis.net/sos/1.0",ogc:"http://www.opengis.net/ogc",om:"http://www.opengis.net/om/1.0",sa:"http://www.opengis.net/sampling/1.0",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/ [...]
+defaultPrefix:"sos",read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={measurements:[],observations:[]};this.readNode(a,b);return b},write:function(a){a=this.writeNode("sos:GetObservation",a);a.setAttribute("xmlns:om",this.namespaces.om);a.setAttribute("xmlns:ogc",this.namespaces.ogc);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.pro [...]
+[a])},readers:{om:{ObservationCollection:function(a,b){b.id=this.getAttributeNS(a,this.namespaces.gml,"id");this.readChildNodes(a,b)},member:function(a,b){this.readChildNodes(a,b)},Measurement:function(a,b){var c={};b.measurements.push(c);this.readChildNodes(a,c)},Observation:function(a,b){var c={};b.observations.push(c);this.readChildNodes(a,c)},samplingTime:function(a,b){var c={};b.samplingTime=c;this.readChildNodes(a,c)},observedProperty:function(a,b){b.observedProperty=this.getAttrib [...]
+"href");this.readChildNodes(a,b)},procedure:function(a,b){b.procedure=this.getAttributeNS(a,this.namespaces.xlink,"href");this.readChildNodes(a,b)},featureOfInterest:function(a,b){var c={features:[]};b.fois=[];b.fois.push(c);this.readChildNodes(a,c);for(var d=[],e=0,f=c.features.length;e<f;e++){var g=c.features[e];d.push(new OpenLayers.Feature.Vector(g.components[0],g.attributes))}c.features=d},result:function(a,b){var c={};b.result=c;""!==this.getChildValue(a)?(c.value=this.getChildValu [...]
+a.getAttribute("uom")):this.readChildNodes(a,c)}},sa:OpenLayers.Format.SOSGetFeatureOfInterest.prototype.readers.sa,gml:OpenLayers.Util.applyDefaults({TimeInstant:function(a,b){var c={};b.timeInstant=c;this.readChildNodes(a,c)},timePosition:function(a,b){b.timePosition=this.getChildValue(a)}},OpenLayers.Format.SOSGetFeatureOfInterest.prototype.readers.gml)},writers:{sos:{GetObservation:function(a){var b=this.createElementNSPlus("GetObservation",{attributes:{version:this.VERSION,service:" [...]
+a,b);a.eventTime&&this.writeNode("eventTime",a,b);for(var c in a.procedures)this.writeNode("procedure",a.procedures[c],b);for(var d in a.observedProperties)this.writeNode("observedProperty",a.observedProperties[d],b);a.foi&&this.writeNode("featureOfInterest",a.foi,b);this.writeNode("responseFormat",a,b);a.resultModel&&this.writeNode("resultModel",a,b);a.responseMode&&this.writeNode("responseMode",a,b);return b},featureOfInterest:function(a){var b=this.createElementNSPlus("featureOfIntere [...]
+a.objectId,b);return b},ObjectID:function(a){return this.createElementNSPlus("ObjectID",{value:a})},responseFormat:function(a){return this.createElementNSPlus("responseFormat",{value:a.responseFormat})},procedure:function(a){return this.createElementNSPlus("procedure",{value:a})},offering:function(a){return this.createElementNSPlus("offering",{value:a.offering})},observedProperty:function(a){return this.createElementNSPlus("observedProperty",{value:a})},eventTime:function(a){var b=this.c [...]
+"latest"===a.eventTime&&this.writeNode("ogc:TM_Equals",a,b);return b},resultModel:function(a){return this.createElementNSPlus("resultModel",{value:a.resultModel})},responseMode:function(a){return this.createElementNSPlus("responseMode",{value:a.responseMode})}},ogc:{TM_Equals:function(a){var b=this.createElementNSPlus("ogc:TM_Equals");this.writeNode("ogc:PropertyName",{property:"urn:ogc:data:time:iso8601"},b);"latest"===a.eventTime&&this.writeNode("gml:TimeInstant",{value:"latest"},b);re [...]
+{value:a.property})}},gml:{TimeInstant:function(a){var b=this.createElementNSPlus("gml:TimeInstant");this.writeNode("gml:timePosition",a,b);return b},timePosition:function(a){return this.createElementNSPlus("gml:timePosition",{value:a.value})}}},CLASS_NAME:"OpenLayers.Format.SOSGetObservation"});OpenLayers.Control.UTFGrid=OpenLayers.Class(OpenLayers.Control,{autoActivate:!0,layers:null,defaultHandlerOptions:{delay:300,pixelTolerance:4,stopMove:!1,single:!0,"double":!1,stopSingle:!1,stopD [...]
+this.handlerOptions):"click"==this.handlerMode?this.handler=new OpenLayers.Handler.Click(this,{click:this.handleEvent},this.handlerOptions):"move"==this.handlerMode&&(this.handler=new OpenLayers.Handler.Hover(this,{pause:this.handleEvent,move:this.handleEvent},this.handlerOptions));return this.handler?!0:!1},initialize:function(a){a=a||{};a.handlerOptions=a.handlerOptions||this.defaultHandlerOptions;OpenLayers.Control.prototype.initialize.apply(this,[a]);this.resetHandler()},handleEvent: [...]
+a)this.reset();else{var b=this.map.getLonLatFromPixel(a.xy);if(b){var c=this.findLayers();if(0<c.length){for(var d={},e,f,g=0,h=c.length;g<h;g++)e=c[g],f=OpenLayers.Util.indexOf(this.map.layers,e),d[f]=e.getFeatureInfo(b);this.callback(d,b,a.xy)}}}},callback:function(a){},reset:function(a){this.callback(null)},findLayers:function(){for(var a=this.layers||this.map.layers,b=[],c,d=a.length-1;0<=d;--d)c=a[d],c instanceof OpenLayers.Layer.UTFGrid&&b.push(c);return b},CLASS_NAME:"OpenLayers.C [...]
+b(),c=b(),new OpenLayers.Filter.Comparison({property:c,value:g,type:d[a.text.toUpperCase()]});case "IS_NULL":return c=b(),new OpenLayers.Filter.Comparison({property:c,type:d[a.text.toUpperCase()]});case "VALUE":return(c=a.text.match(/^'(.*)'$/))?c[1].replace(/''/g,"'"):Number(a.text);case "SPATIAL":switch(a.text.toUpperCase()){case "BBOX":var a=b(),c=b(),g=b(),h=b(),k=b();return new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,property:k,value:OpenLayers.Bounds.fromArra [...]
+a])});case "INTERSECTS":return g=b(),c=b(),new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:c,value:g});case "WITHIN":return g=b(),c=b(),new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.WITHIN,property:c,value:g});case "CONTAINS":return g=b(),c=b(),new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.CONTAINS,property:c,value:g});case "DWITHIN":return a=b(),g=b(),c=b(),new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.DWI [...]
+property:c,distance:Number(a)})}case "GEOMETRY":return OpenLayers.Geometry.fromWKT(a.text);default:return a.text}}for(var c=[],e=[];a.length;){var g=a.shift();switch(g.type){case "PROPERTY":case "GEOMETRY":case "VALUE":e.push(g);break;case "COMPARISON":case "BETWEEN":case "IS_NULL":case "LOGICAL":for(var k=h[g.type];0<c.length&&h[c[c.length-1].type]<=k;)e.push(c.pop());c.push(g);break;case "SPATIAL":case "NOT":case "LPAREN":c.push(g);break;case "RPAREN":for(;0<c.length&&"LPAREN"!=c[c.len [...]
+c.pop();0<c.length&&"SPATIAL"==c[c.length-1].type&&e.push(c.pop());case "COMMA":case "END":break;default:throw Error("Unknown token type "+g.type);}}for(;0<c.length;)e.push(c.pop());a=b();if(0<e.length){a="Remaining tokens after building AST: \n";for(c=e.length-1;0<=c;c--)a+=e[c].type+": "+e[c].text+"\n";throw Error(a);}return a}var b={PROPERTY:/^[_a-zA-Z]\w*/,COMPARISON:/^(=|<>|<=|<|>=|>|LIKE)/i,IS_NULL:/^IS NULL/i,COMMA:/^,/,LOGICAL:/^(AND|OR)/i,VALUE:/^('([^']|'')*'|\d+(\.\d*)?|\.\d+) [...]
+RPAREN:/^\)/,SPATIAL:/^(BBOX|INTERSECTS|DWITHIN|WITHIN|CONTAINS)/i,NOT:/^NOT/i,BETWEEN:/^BETWEEN/i,GEOMETRY:function(a){var b=/^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)/.exec(a);if(b){var c=a.length,b=a.indexOf("(",b[0].length);if(-1<b)for(var d=1;b<c&&0<d;)switch(b++,a.charAt(b)){case "(":d++;break;case ")":d--}return[a.substr(0,b+1)]}},END:/^$/},c={LPAREN:["GEOMETRY","SPATIAL","PROPERTY","VALUE","LPAREN"],RPAREN:["NOT","LOGICAL","END","RPARE [...]
+"BETWEEN","COMMA","IS_NULL"],BETWEEN:["VALUE"],IS_NULL:["END"],COMPARISON:["VALUE"],COMMA:["GEOMETRY","VALUE","PROPERTY"],VALUE:["LOGICAL","COMMA","RPAREN","END"],SPATIAL:["LPAREN"],LOGICAL:["NOT","VALUE","SPATIAL","PROPERTY","LPAREN"],NOT:["PROPERTY","LPAREN"],GEOMETRY:["COMMA","RPAREN"]},d={"=":OpenLayers.Filter.Comparison.EQUAL_TO,"<>":OpenLayers.Filter.Comparison.NOT_EQUAL_TO,"<":OpenLayers.Filter.Comparison.LESS_THAN,"<=":OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,">":OpenLa [...]
+">=":OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,LIKE:OpenLayers.Filter.Comparison.LIKE,BETWEEN:OpenLayers.Filter.Comparison.BETWEEN,"IS NULL":OpenLayers.Filter.Comparison.IS_NULL},e={},f={AND:OpenLayers.Filter.Logical.AND,OR:OpenLayers.Filter.Logical.OR},g={},h={RPAREN:3,LOGICAL:2,COMPARISON:1},k;for(k in d)d.hasOwnProperty(k)&&(e[d[k]]=k);for(k in f)f.hasOwnProperty(k)&&(g[f[k]]=k);return OpenLayers.Class(OpenLayers.Format,{read:function(d){var e=d;d=[];var f,g=["NOT","GEOMET [...]
+"PROPERTY","LPAREN"];do{a:{f=g;for(var h=void 0,g=void 0,k=f.length,h=0;h<k;h++){var g=f[h],s=b[g]instanceof RegExp?b[g].exec(e):(0,b[g])(e);if(s){f=s[0];e=e.substr(f.length).replace(/^\s*/,"");f={type:g,text:f,remainder:e};break a}}d="ERROR: In parsing: ["+e+"], expected one of: ";for(h=0;h<k;h++)g=f[h],d+="\n    "+g+": "+b[g];throw Error(d);}e=f.remainder;g=c[f.type];if("END"!=f.type&&!g)throw Error("No follows list for "+f.type);d.push(f)}while("END"!=f.type);d=a(d);this.keepData&&(th [...]
+return d},write:function(a){if(a instanceof OpenLayers.Geometry)return a.toString();switch(a.CLASS_NAME){case "OpenLayers.Filter.Spatial":switch(a.type){case OpenLayers.Filter.Spatial.BBOX:return"BBOX("+a.property+","+a.value.toBBOX()+")";case OpenLayers.Filter.Spatial.DWITHIN:return"DWITHIN("+a.property+", "+this.write(a.value)+", "+a.distance+")";case OpenLayers.Filter.Spatial.WITHIN:return"WITHIN("+a.property+", "+this.write(a.value)+")";case OpenLayers.Filter.Spatial.INTERSECTS:retur [...]
+a.property+", "+this.write(a.value)+")";case OpenLayers.Filter.Spatial.CONTAINS:return"CONTAINS("+a.property+", "+this.write(a.value)+")";default:throw Error("Unknown spatial filter type: "+a.type);}case "OpenLayers.Filter.Logical":if(a.type==OpenLayers.Filter.Logical.NOT)return"NOT ("+this.write(a.filters[0])+")";for(var b="(",c=!0,d=0;d<a.filters.length;d++)c?c=!1:b+=") "+g[a.type]+" (",b+=this.write(a.filters[d]);return b+")";case "OpenLayers.Filter.Comparison":return a.type==OpenLaye [...]
+a.property+" BETWEEN "+this.write(a.lowerBoundary)+" AND "+this.write(a.upperBoundary):null!==a.value?a.property+" "+e[a.type]+" "+this.write(a.value):a.property+" "+e[a.type];case void 0:if("string"===typeof a)return"'"+a.replace(/'/g,"''")+"'";if("number"===typeof a)return String(a);default:throw Error("Can't encode: "+a.CLASS_NAME+" "+a);}},CLASS_NAME:"OpenLayers.Format.CQL"})}();OpenLayers.Control.Split=OpenLayers.Class(OpenLayers.Control,{layer:null,source:null,sourceOptions:null,to [...]
+a},activate:function(){var a=OpenLayers.Control.prototype.activate.call(this);if(a)if(!this.source)this.handler||(this.handler=new OpenLayers.Handler.Path(this,{done:function(a){this.onSketchComplete({feature:new OpenLayers.Feature.Vector(a)})}},{layerOptions:this.sourceOptions})),this.handler.activate();else if(this.source.events)this.source.events.on({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});return a},deactivate:function(){var a=O [...]
+a&&this.source&&this.source.events&&this.source.events.un({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});return a},onSketchComplete:function(a){this.feature=null;return!this.considerSplit(a.feature)},afterFeatureModified:function(a){a.modified&&"function"===typeof a.feature.geometry.split&&(this.feature=a.feature,this.considerSplit(a.feature))},removeByGeometry:function(a,b){for(var c=0,d=a.length;c<d;++c)if(a[c].geometry===b){a.splice(c [...]
+isEligible:function(a){return a.geometry?a.state!==OpenLayers.State.DELETE&&"function"===typeof a.geometry.split&&this.feature!==a&&(!this.targetFilter||this.targetFilter.evaluate(a.attributes)):!1},considerSplit:function(a){var b=!1,c=!1;if(!this.sourceFilter||this.sourceFilter.evaluate(a.attributes)){for(var d=this.layer&&this.layer.features||[],e,f,g=[],h=[],k=this.layer===this.source&&this.mutual,l={edge:this.edge,tolerance:this.tolerance,mutual:k},m=[a.geometry],n,p,q,r=0,s=d.length [...]
+d[r],this.isEligible(n)){p=[n.geometry];for(var t=0;t<m.length;++t){q=m[t];for(var u=0;u<p.length;++u)if(e=p[u],q.getBounds().intersectsBounds(e.getBounds())&&(e=q.split(e,l)))f=this.events.triggerEvent("beforesplit",{source:a,target:n}),!1!==f&&(k&&(f=e[0],1<f.length&&(f.unshift(t,1),Array.prototype.splice.apply(m,f),t+=f.length-3),e=e[1]),1<e.length&&(e.unshift(u,1),Array.prototype.splice.apply(p,e),u+=e.length-3))}p&&1<p.length&&(this.geomsToFeatures(n,p),this.events.triggerEvent("spl [...]
+features:p}),Array.prototype.push.apply(g,p),h.push(n),c=!0)}m&&1<m.length&&(this.geomsToFeatures(a,m),this.events.triggerEvent("split",{original:a,features:m}),Array.prototype.push.apply(g,m),h.push(a),b=!0);if(b||c){if(this.deferDelete){d=[];r=0;for(s=h.length;r<s;++r)c=h[r],c.state===OpenLayers.State.INSERT?d.push(c):(c.state=OpenLayers.State.DELETE,this.layer.drawFeature(c));this.layer.destroyFeatures(d,{silent:!0});r=0;for(s=g.length;r<s;++r)g[r].state=OpenLayers.State.INSERT}else t [...]
+{silent:!0});this.layer.addFeatures(g,{silent:!0});this.events.triggerEvent("aftersplit",{source:a,features:g})}}return b},geomsToFeatures:function(a,b){var c=a.clone();delete c.geometry;for(var d,e=0,f=b.length;e<f;++e)d=c.clone(),d.geometry=b[e],d.state=OpenLayers.State.INSERT,b[e]=d},destroy:function(){this.active&&this.deactivate();OpenLayers.Control.prototype.destroy.call(this)},CLASS_NAME:"OpenLayers.Control.Split"});OpenLayers.Layer.WMTS=OpenLayers.Class(OpenLayers.Layer.Grid,{isB [...]
+{url:!0,layer:!0,style:!0,matrixSet:!0},c;for(c in b)if(!(c in a))throw Error("Missing property '"+c+"' in layer configuration.");a.params=OpenLayers.Util.upperCaseObject(a.params);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a.name,a.url,a.params,a]);this.formatSuffix||(this.formatSuffix=this.formatSuffixMap[this.format]||this.format.split("/").pop());if(this.matrixIds&&(a=this.matrixIds.length)&&"string"===typeof this.matrixIds[0])for(b=this.matrixIds,this.matrixIds=Array(a), [...]
+{identifier:b[c]}},setMap:function(){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments)},updateMatrixProperties:function(){if(this.matrix=this.getMatrix())this.matrix.topLeftCorner&&(this.tileOrigin=this.matrix.topLeftCorner),this.matrix.tileWidth&&this.matrix.tileHeight&&(this.tileSize=new OpenLayers.Size(this.matrix.tileWidth,this.matrix.tileHeight)),this.tileOrigin||(this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.top)),this.tileFullExtent||(this.til [...]
+this.maxExtent)},moveTo:function(a,b,c){!b&&this.matrix||this.updateMatrixProperties();return OpenLayers.Layer.Grid.prototype.moveTo.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.WMTS(this.options));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getIdentifier:function(){return this.getServerZoom()},getMatrix:function(){var a;if(this.matrixIds&&0!==this.matrixIds.length)if("scaleDenominator"in this.matrixIds[0])for(var b=OpenLayers.METERS_PER_I [...]
+this.getServerResolution()/2.8E-4,c=Number.POSITIVE_INFINITY,d,e=0,f=this.matrixIds.length;e<f;++e)d=Math.abs(1-this.matrixIds[e].scaleDenominator/b),d<c&&(c=d,a=this.matrixIds[e]);else a=this.matrixIds[this.getIdentifier()];else a={identifier:this.getIdentifier()};return a},getTileInfo:function(a){var b=this.getServerResolution(),c=(a.lon-this.tileOrigin.lon)/(b*this.tileSize.w);a=(this.tileOrigin.lat-a.lat)/(b*this.tileSize.h);var b=Math.floor(c),d=Math.floor(a);return{col:b,row:d,i:Ma [...]
+b)*this.tileSize.w),j:Math.floor((a-d)*this.tileSize.h)}},getURL:function(a){a=this.adjustBounds(a);var b="";if(!this.tileFullExtent||this.tileFullExtent.intersectsBounds(a)){a=a.getCenterLonLat();var c=this.getTileInfo(a);a=this.dimensions;var d,b=OpenLayers.Util.isArray(this.url)?this.selectUrl([this.version,this.style,this.matrixSet,this.matrix.identifier,c.row,c.col].join(),this.url):this.url;if("REST"===this.requestEncoding.toUpperCase())if(d=this.params,-1!==b.indexOf("{")){b=b.rep [...]
+"${");c={style:this.style,Style:this.style,TileMatrixSet:this.matrixSet,TileMatrix:this.matrix.identifier,TileRow:c.row,TileCol:c.col};if(a){var e,f;for(f=a.length-1;0<=f;--f)e=a[f],c[e]=d[e.toUpperCase()]}b=OpenLayers.String.format(b,c)}else{e=this.version+"/"+this.layer+"/"+this.style+"/";if(a)for(f=0;f<a.length;f++)d[a[f]]&&(e=e+d[a[f]]+"/");e=e+this.matrixSet+"/"+this.matrix.identifier+"/"+c.row+"/"+c.col+"."+this.formatSuffix;b.match(/\/$/)||(b+="/");b+=e}else"KVP"===this.requestEnc [...]
+(d={SERVICE:"WMTS",REQUEST:"GetTile",VERSION:this.version,LAYER:this.layer,STYLE:this.style,TILEMATRIXSET:this.matrixSet,TILEMATRIX:this.matrix.identifier,TILEROW:c.row,TILECOL:c.col,FORMAT:this.format},b=OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,[d]))}return b},mergeNewParams:function(a){if("KVP"===this.requestEncoding.toUpperCase())return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,[OpenLayers.Util.upperCaseObject(a)])},CLASS_NAME:"OpenLayers.Layer.W [...]
+{});var b=new OpenLayers.Protocol.Response({requestType:"read"}),c=this.format,c=OpenLayers.Format.XML.prototype.write.apply(c,[c.writeNode("sos:GetFeatureOfInterest",{fois:this.fois})]);b.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,b,a),data:c});return b},handleRead:function(a,b){if(b.callback){var c=a.priv;200<=c.status&&300>c.status?(a.features=this.parseFeatures(c),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Respons [...]
+b.callback.call(b.scope,a)}},parseFeatures:function(a){var b=a.responseXML;b&&b.documentElement||(b=a.responseText);return!b||0>=b.length?null:this.format.read(b)},CLASS_NAME:"OpenLayers.Protocol.SOS.v1_0_0"});OpenLayers.Layer.KaMapCache=OpenLayers.Class(OpenLayers.Layer.KaMap,{IMAGE_EXTENSIONS:{jpeg:"jpg",gif:"gif",png:"png",png8:"png",png24:"png",dithered:"png"},DEFAULT_FORMAT:"jpeg",initialize:function(a,b,c,d){OpenLayers.Layer.KaMap.prototype.initialize.apply(this,arguments);this.ext [...]
+-Math.round(a.top/b);var b=Math.floor(d/this.tileSize.w/this.params.metaTileSize.w)*this.tileSize.w*this.params.metaTileSize.w,e=Math.floor(a/this.tileSize.h/this.params.metaTileSize.h)*this.tileSize.h*this.params.metaTileSize.h,c=["/",this.params.map,"/",c,"/",this.params.g.replace(/\s/g,"_"),"/def/t",e,"/l",b,"/t",a,"l",d,".",this.extension],d=this.url;OpenLayers.Util.isArray(d)&&(d=this.selectUrl(c.join(""),d));return d+c.join("")},CLASS_NAME:"OpenLayers.Layer.KaMapCache"});OpenLayers [...]
+Width:function(a,b){b.width=parseInt(this.getChildValue(a))},Height:function(a,b){b.height=parseInt(this.getChildValue(a))},Layers:function(a,b){b.layers=this.getChildValue(a)},Styles:function(a,b){b.styles=this.getChildValue(a)}},OpenLayers.Format.WMSCapabilities.v1_1_1.prototype.readers.wms)},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC"});OpenLayers.Control.LayerSwitcher=OpenLayers.Class(OpenLayers.Control,{layerStates:null,layersDiv:null,baseLayersDiv:null,baseLayers:nul [...]
+removelayer:this.redraw,changebaselayer:this.redraw,scope:this});this.events.unregister("buttonclick",this,this.onButtonClick);OpenLayers.Control.prototype.destroy.apply(this,arguments)},setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.on({addlayer:this.redraw,changelayer:this.redraw,removelayer:this.redraw,changebaselayer:this.redraw,scope:this});this.outsideViewport?(this.events.attachToElement(this.div),this.events.register("buttonclick",thi [...]
+this.map.events.register("buttonclick",this,this.onButtonClick)},draw:function(){OpenLayers.Control.prototype.draw.apply(this);this.loadContents();this.outsideViewport||this.minimizeControl();this.redraw();return this.div},onButtonClick:function(a){a=a.buttonElement;a===this.minimizeDiv?this.minimizeControl():a===this.maximizeDiv?this.maximizeControl():a._layerSwitcher===this.id&&(a["for"]&&(a=document.getElementById(a["for"])),a.disabled||("radio"==a.type?(a.checked=!0,this.map.setBaseL [...]
+(a.checked=!a.checked,this.updateMap())))},clearLayersArray:function(a){this[a+"LayersDiv"].innerHTML="";this[a+"Layers"]=[]},checkRedraw:function(){if(!this.layerStates.length||this.map.layers.length!=this.layerStates.length)return!0;for(var a=0,b=this.layerStates.length;a<b;a++){var c=this.layerStates[a],d=this.map.layers[a];if(c.name!=d.name||c.inRange!=d.inRange||c.id!=d.id||c.visibility!=d.visibility)return!0}return!1},redraw:function(){if(!this.checkRedraw())return this.div;this.cl [...]
+this.clearLayersArray("data");var a=!1,b=!1,c=this.map.layers.length;this.layerStates=Array(c);for(var d=0;d<c;d++){var e=this.map.layers[d];this.layerStates[d]={name:e.name,visibility:e.visibility,inRange:e.inRange,id:e.id}}var f=this.map.layers.slice();this.ascending||f.reverse();d=0;for(c=f.length;d<c;d++){var e=f[d],g=e.isBaseLayer;if(e.displayInLayerSwitcher){g?b=!0:a=!0;var h=g?e==this.map.baseLayer:e.getVisibility(),k=document.createElement("input"),l=OpenLayers.Util.createUniqueI [...]
+"_input_");k.id=l;k.name=g?this.id+"_baseLayers":e.name;k.type=g?"radio":"checkbox";k.value=e.name;k.checked=h;k.defaultChecked=h;k.className="olButton";k._layer=e.id;k._layerSwitcher=this.id;g||e.inRange||(k.disabled=!0);h=document.createElement("label");h["for"]=k.id;OpenLayers.Element.addClass(h,"labelSpan olButton");h._layer=e.id;h._layerSwitcher=this.id;g||e.inRange||(h.style.color="gray");h.innerHTML=e.name;h.style.verticalAlign=g?"bottom":"baseline";l=document.createElement("br"); [...]
+this.dataLayers).push({layer:e,inputElem:k,labelSpan:h});e=g?this.baseLayersDiv:this.dataLayersDiv;e.appendChild(k);e.appendChild(h);e.appendChild(l)}}this.dataLbl.style.display=a?"":"none";this.baseLbl.style.display=b?"":"none";return this.div},updateMap:function(){for(var a=0,b=this.baseLayers.length;a<b;a++){var c=this.baseLayers[a];c.inputElem.checked&&this.map.setBaseLayer(c.layer,!1)}a=0;for(b=this.dataLayers.length;a<b;a++)c=this.dataLayers[a],c.layer.setVisibility(c.inputElem.che [...]
+"";this.div.style.height="";this.showControls(!1);null!=a&&OpenLayers.Event.stop(a)},minimizeControl:function(a){this.div.style.width="0px";this.div.style.height="0px";this.showControls(!0);null!=a&&OpenLayers.Event.stop(a)},showControls:function(a){this.maximizeDiv.style.display=a?"":"none";this.minimizeDiv.style.display=a?"none":"";this.layersDiv.style.display=a?"none":""},loadContents:function(){this.layersDiv=document.createElement("div");this.layersDiv.id=this.id+"_layersDiv";OpenLa [...]
+"layersDiv");this.baseLbl=document.createElement("div");this.baseLbl.innerHTML=OpenLayers.i18n("Base Layer");OpenLayers.Element.addClass(this.baseLbl,"baseLbl");this.baseLayersDiv=document.createElement("div");OpenLayers.Element.addClass(this.baseLayersDiv,"baseLayersDiv");this.dataLbl=document.createElement("div");this.dataLbl.innerHTML=OpenLayers.i18n("Overlays");OpenLayers.Element.addClass(this.dataLbl,"dataLbl");this.dataLayersDiv=document.createElement("div");OpenLayers.Element.addC [...]
+"dataLayersDiv");this.ascending?(this.layersDiv.appendChild(this.baseLbl),this.layersDiv.appendChild(this.baseLayersDiv),this.layersDiv.appendChild(this.dataLbl),this.layersDiv.appendChild(this.dataLayersDiv)):(this.layersDiv.appendChild(this.dataLbl),this.layersDiv.appendChild(this.dataLayersDiv),this.layersDiv.appendChild(this.baseLbl),this.layersDiv.appendChild(this.baseLayersDiv));this.div.appendChild(this.layersDiv);var a=OpenLayers.Util.getImageLocation("layer-switcher-maximize.png [...]
+OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MaximizeDiv",null,null,a,"absolute");OpenLayers.Element.addClass(this.maximizeDiv,"maximizeDiv olButton");this.maximizeDiv.style.display="none";this.div.appendChild(this.maximizeDiv);a=OpenLayers.Util.getImageLocation("layer-switcher-minimize.png");this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MinimizeDiv",null,null,a,"absolute");OpenLayers.Element.addClass(this.minimizeDiv,"minimizeDiv olButton");this.min [...]
+"none";this.div.appendChild(this.minimizeDiv)},CLASS_NAME:"OpenLayers.Control.LayerSwitcher"});OpenLayers.Format.Atom=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{atom:"http://www.w3.org/2005/Atom",georss:"http://www.georss.org/georss"},feedTitle:"untitled",defaultEntryTitle:"untitled",gmlParser:null,xy:!1,read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));return this.parseFeatures(a)},write:function(a){var b;if(OpenLayers.Util.isArray(a) [...]
+for(var c=0,d=a.length;c<d;c++)b.appendChild(this.buildEntryNode(a[c]))}else b=this.buildEntryNode(a);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},buildContentNode:function(a){var b=this.createElementNSPlus("atom:content",{attributes:{type:a.type||null}});if(a.src)b.setAttribute("src",a.src);else if("text"==a.type||null==a.type)b.appendChild(this.createTextNode(a.value));else if("html"==a.type){if("string"!=typeof a.value)throw"HTML content must be in form of an escaped  [...]
+a.type?b.appendChild(a.value):"xhtml"==a.type||a.type.match(/(\+|\/)xml$/)?b.appendChild(a.value):b.appendChild(this.createTextNode(a.value));return b},buildEntryNode:function(a){var b=a.attributes,c=b.atom||{},d=this.createElementNSPlus("atom:entry");if(c.authors)for(var e=OpenLayers.Util.isArray(c.authors)?c.authors:[c.authors],f=0,g=e.length;f<g;f++)d.appendChild(this.buildPersonConstructNode("author",e[f]));if(c.categories)for(var e=OpenLayers.Util.isArray(c.categories)?c.categories: [...]
+h,f=0,g=e.length;f<g;f++)h=e[f],d.appendChild(this.createElementNSPlus("atom:category",{attributes:{term:h.term,scheme:h.scheme||null,label:h.label||null}}));c.content&&d.appendChild(this.buildContentNode(c.content));if(c.contributors)for(e=OpenLayers.Util.isArray(c.contributors)?c.contributors:[c.contributors],f=0,g=e.length;f<g;f++)d.appendChild(this.buildPersonConstructNode("contributor",e[f]));a.fid&&d.appendChild(this.createElementNSPlus("atom:id",{value:a.fid}));if(c.links)for(e=Op [...]
+c.links:[c.links],f=0,g=e.length;f<g;f++)h=e[f],d.appendChild(this.createElementNSPlus("atom:link",{attributes:{href:h.href,rel:h.rel||null,type:h.type||null,hreflang:h.hreflang||null,title:h.title||null,length:h.length||null}}));c.published&&d.appendChild(this.createElementNSPlus("atom:published",{value:c.published}));c.rights&&d.appendChild(this.createElementNSPlus("atom:rights",{value:c.rights}));(c.summary||b.description)&&d.appendChild(this.createElementNSPlus("atom:summary",{value: [...]
+b.description}));d.appendChild(this.createElementNSPlus("atom:title",{value:c.title||b.title||this.defaultEntryTitle}));c.updated&&d.appendChild(this.createElementNSPlus("atom:updated",{value:c.updated}));a.geometry&&(b=this.createElementNSPlus("georss:where"),b.appendChild(this.buildGeometryNode(a.geometry)),d.appendChild(b));return d},initGmlParser:function(){this.gmlParser=new OpenLayers.Format.GML.v3({xy:this.xy,featureNS:"http://example.com#feature",internalProjection:this.internalP [...]
+externalProjection:this.externalProjection})},buildGeometryNode:function(a){this.gmlParser||this.initGmlParser();return this.gmlParser.writeNode("feature:_geometry",a).firstChild},buildPersonConstructNode:function(a,b){var c=["uri","email"],d=this.createElementNSPlus("atom:"+a);d.appendChild(this.createElementNSPlus("atom:name",{value:b.name}));for(var e=0,f=c.length;e<f;e++)b[c[e]]&&d.appendChild(this.createElementNSPlus("atom:"+c[e],{value:b[c[e]]}));return d},getFirstChildValue:functi [...]
+d){return(a=this.getElementsByTagNameNS(a,b,c))&&0<a.length?this.getChildValue(a[0],d):d},parseFeature:function(a){var b={},c=null,d=null,e=null,f=this.namespaces.atom;this.parsePersonConstructs(a,"author",b);d=this.getElementsByTagNameNS(a,f,"category");0<d.length&&(b.categories=[]);for(var g=0,h=d.length;g<h;g++){c={};c.term=d[g].getAttribute("term");if(e=d[g].getAttribute("scheme"))c.scheme=e;if(e=d[g].getAttribute("label"))c.label=e;b.categories.push(c)}d=this.getElementsByTagNameNS( [...]
+if(0<d.length){c={};if(e=d[0].getAttribute("type"))c.type=e;(e=d[0].getAttribute("src"))?c.src=e:("text"==c.type||"html"==c.type||null==c.type?c.value=this.getFirstChildValue(a,f,"content",null):"xhtml"==c.type||c.type.match(/(\+|\/)xml$/)?c.value=this.getChildEl(d[0]):c.value=this.getFirstChildValue(a,f,"content",null),b.content=c)}this.parsePersonConstructs(a,"contributor",b);b.id=this.getFirstChildValue(a,f,"id",null);d=this.getElementsByTagNameNS(a,f,"link");0<d.length&&(b.links=Arra [...]
+for(var k=["rel","type","hreflang","title","length"],g=0,h=d.length;g<h;g++){c={};c.href=d[g].getAttribute("href");for(var l=0,m=k.length;l<m;l++)(e=d[g].getAttribute(k[l]))&&(c[k[l]]=e);b.links[g]=c}if(c=this.getFirstChildValue(a,f,"published",null))b.published=c;if(c=this.getFirstChildValue(a,f,"rights",null))b.rights=c;if(c=this.getFirstChildValue(a,f,"summary",null))b.summary=c;b.title=this.getFirstChildValue(a,f,"title",null);b.updated=this.getFirstChildValue(a,f,"updated",null);c={ [...]
+description:b.summary,atom:b};a=this.parseLocations(a)[0];a=new OpenLayers.Feature.Vector(a,c);a.fid=b.id;return a},parseFeatures:function(a){var b=[],c=this.getElementsByTagNameNS(a,this.namespaces.atom,"entry");0==c.length&&(c=[a]);a=0;for(var d=c.length;a<d;a++)b.push(this.parseFeature(c[a]));return b},parseLocations:function(a){var b=this.namespaces.georss,c={components:[]},d=this.getElementsByTagNameNS(a,b,"where");if(d&&0<d.length){this.gmlParser||this.initGmlParser();for(var e=0,f [...]
+f;e++)this.gmlParser.readChildNodes(d[e],c)}c=c.components;if((d=this.getElementsByTagNameNS(a,b,"point"))&&0<d.length)for(e=0,f=d.length;e<f;e++){var g=OpenLayers.String.trim(d[e].firstChild.nodeValue).split(/\s+/);2!=g.length&&(g=OpenLayers.String.trim(d[e].firstChild.nodeValue).split(/\s*,\s*/));c.push(new OpenLayers.Geometry.Point(g[1],g[0]))}var h=this.getElementsByTagNameNS(a,b,"line");if(h&&0<h.length)for(var k,e=0,f=h.length;e<f;e++){d=OpenLayers.String.trim(h[e].firstChild.nodeV [...]
+k=[];for(var l=0,m=d.length;l<m;l+=2)g=new OpenLayers.Geometry.Point(d[l+1],d[l]),k.push(g);c.push(new OpenLayers.Geometry.LineString(k))}if((a=this.getElementsByTagNameNS(a,b,"polygon"))&&0<a.length)for(e=0,f=a.length;e<f;e++){d=OpenLayers.String.trim(a[e].firstChild.nodeValue).split(/\s+/);k=[];l=0;for(m=d.length;l<m;l+=2)g=new OpenLayers.Geometry.Point(d[l+1],d[l]),k.push(g);c.push(new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(k)]))}if(this.internalProjection&&th [...]
+0,f=c.length;e<f;e++)c[e]&&c[e].transform(this.externalProjection,this.internalProjection);return c},parsePersonConstructs:function(a,b,c){var d=[],e=this.namespaces.atom;a=this.getElementsByTagNameNS(a,e,b);for(var f=["uri","email"],g=0,h=a.length;g<h;g++){var k={};k.name=this.getFirstChildValue(a[g],e,"name",null);for(var l=0,m=f.length;l<m;l++){var n=this.getFirstChildValue(a[g],e,f[l],null);n&&(k[f[l]]=n)}d.push(k)}0<d.length&&(c[b+"s"]=d)},CLASS_NAME:"OpenLayers.Format.Atom"});OpenL [...]
+0);break;case OpenLayers.Event.KEY_UP:this.map.pan(0,-this.slideFactor);break;case OpenLayers.Event.KEY_DOWN:this.map.pan(0,this.slideFactor);break;case 33:b=this.map.getSize();this.map.pan(0,-0.75*b.h);break;case 34:b=this.map.getSize();this.map.pan(0,0.75*b.h);break;case 35:b=this.map.getSize();this.map.pan(0.75*b.w,0);break;case 36:b=this.map.getSize();this.map.pan(-0.75*b.w,0);break;case 43:case 61:case 187:case 107:this.map.zoomIn();break;case 45:case 109:case 189:case 95:this.map.z [...]
+break;default:c=!1}c&&OpenLayers.Event.stop(a)}},CLASS_NAME:"OpenLayers.Control.KeyboardDefaults"});OpenLayers.Format.WMTSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1_1_0,{version:"1.0.0",namespaces:{ows:"http://www.opengis.net/ows/1.1",wmts:"http://www.opengis.net/wmts/1.0",xlink:"http://www.w3.org/1999/xlink"},yx:null,defaultPrefix:"wmts",initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);this.options=a;a=OpenLayers.Util.extend({},O [...]
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);b.version=this.version;return b},readers:{wmts:{Capabilities:function(a,b){this.readChildNodes(a,b)},Contents:function(a,b){b.contents={};b.contents.layers=[];b.contents.tileMatrixSets={};this.readChildNodes(a,b.contents)},Layer:function(a,b){var c={styles:[],formats:[],dimensions:[],tileMatrixSetLinks:[],layers:[]};this.readChildNodes(a,c);b.layers.push( [...]
+b){var c={};c.isDefault="true"===a.getAttribute("isDefault");this.readChildNodes(a,c);b.styles.push(c)},Format:function(a,b){b.formats.push(this.getChildValue(a))},TileMatrixSetLink:function(a,b){var c={};this.readChildNodes(a,c);b.tileMatrixSetLinks.push(c)},TileMatrixSet:function(a,b){if(b.layers){var c={matrixIds:[]};this.readChildNodes(a,c);b.tileMatrixSets[c.identifier]=c}else b.tileMatrixSet=this.getChildValue(a)},TileMatrix:function(a,b){var c={supportedCRS:b.supportedCRS};this.re [...]
+c);b.matrixIds.push(c)},ScaleDenominator:function(a,b){b.scaleDenominator=parseFloat(this.getChildValue(a))},TopLeftCorner:function(a,b){var c=this.getChildValue(a).split(" "),d;b.supportedCRS&&(d=b.supportedCRS.replace(/urn:ogc:def:crs:(\w+):.+:(\w+)$/,"urn:ogc:def:crs:$1::$2"),d=!!this.yx[d]);b.topLeftCorner=d?new OpenLayers.LonLat(c[1],c[0]):new OpenLayers.LonLat(c[0],c[1])},TileWidth:function(a,b){b.tileWidth=parseInt(this.getChildValue(a))},TileHeight:function(a,b){b.tileHeight=pars [...]
+MatrixWidth:function(a,b){b.matrixWidth=parseInt(this.getChildValue(a))},MatrixHeight:function(a,b){b.matrixHeight=parseInt(this.getChildValue(a))},ResourceURL:function(a,b){b.resourceUrl=b.resourceUrl||{};var c=a.getAttribute("resourceType");b.resourceUrls||(b.resourceUrls=[]);c=b.resourceUrl[c]={format:a.getAttribute("format"),template:a.getAttribute("template"),resourceType:c};b.resourceUrls.push(c)},WSDL:function(a,b){b.wsdl={};b.wsdl.href=a.getAttribute("xlink:href")},ServiceMetadat [...]
+b){b.serviceMetadataUrl={};b.serviceMetadataUrl.href=a.getAttribute("xlink:href")},LegendURL:function(a,b){b.legend={};b.legend.href=a.getAttribute("xlink:href");b.legend.format=a.getAttribute("format")},Dimension:function(a,b){var c={values:[]};this.readChildNodes(a,c);b.dimensions.push(c)},Default:function(a,b){b["default"]=this.getChildValue(a)},Value:function(a,b){b.values.push(this.getChildValue(a))}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLaye [...]
diff --git a/media/js/OpenStreetMap.js b/media/js/OpenStreetMap.js
new file mode 100644
index 0000000..7868179
--- /dev/null
+++ b/media/js/OpenStreetMap.js
@@ -0,0 +1,162 @@
+/**
+ * OpenStreetMap javascript routines
+ * Copyright (C) 2007, 2008, 2009, 2010, 2011 Tom Hughes , Grant Slater
+ * and Christopher Schmidt from the OpenStreetMap team
+ * http://openstreetmap.org/
+ * 
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * Namespace: Util.OSM
+ */
+OpenLayers.Util.OSM = {};
+
+/**
+ * Constant: MISSING_TILE_URL
+ * {String} URL of image to display for missing tiles
+ */
+OpenLayers.Util.OSM.MISSING_TILE_URL = "http://openstreetmap.org/openlayers/img/404.png";
+
+/**
+ * Property: originalOnImageLoadError
+ * {Function} Original onImageLoadError function.
+ */
+OpenLayers.Util.OSM.originalOnImageLoadError = OpenLayers.Util.onImageLoadError;
+
+/**
+ * Function: onImageLoadError
+ */
+OpenLayers.Util.onImageLoadError = function() {
+    if (this.src.match(/^http:\/\/[abc]\.[a-z]+\.openstreetmap\.org\//)) {
+        this.src = OpenLayers.Util.OSM.MISSING_TILE_URL;
+    } else if (this.src.match(/^http:\/\/[def]\.tah\.openstreetmap\.org\//)) {
+        // do nothing - this layer is transparent
+    } else {
+        OpenLayers.Util.OSM.originalOnImageLoadError;
+    }
+};
+
+/**
+ * Class: OpenLayers.Layer.OSM.Mapnik
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.Mapnik = OpenLayers.Class(OpenLayers.Layer.OSM, {
+    /**
+     * Constructor: OpenLayers.Layer.OSM.Mapnik
+     *
+     * Parameters:
+     * name - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        var url = [
+            "http://a.tile.openstreetmap.org/${z}/${x}/${y}.png",
+            "http://b.tile.openstreetmap.org/${z}/${x}/${y}.png",
+            "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"
+        ];
+        options = OpenLayers.Util.extend({ numZoomLevels: 19 }, options);
+        var newArguments = [name, url, options];
+        OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.OSM.Mapnik"
+});
+
+/**
+ * Class: OpenLayers.Layer.OSM.Osmarender
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.Osmarender = OpenLayers.Class(OpenLayers.Layer.OSM, {
+    /**
+     * Constructor: OpenLayers.Layer.OSM.Osmarender
+     *
+     * Parameters:
+     * name - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        var url = [
+            "http://a.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png",
+            "http://b.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png",
+            "http://c.tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png"
+        ];
+        options = OpenLayers.Util.extend({ numZoomLevels: 18 }, options);
+        var newArguments = [name, url, options];
+        OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.OSM.Osmarender"
+});
+
+/**
+ * Class: OpenLayers.Layer.OSM.CycleMap
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.CycleMap = OpenLayers.Class(OpenLayers.Layer.OSM, {
+    /**
+     * Constructor: OpenLayers.Layer.OSM.CycleMap
+     *
+     * Parameters:
+     * name - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        var url = [
+            "http://a.andy.sandbox.cloudmade.com/tiles/cycle/${z}/${x}/${y}.png",
+            "http://b.andy.sandbox.cloudmade.com/tiles/cycle/${z}/${x}/${y}.png",
+            "http://c.andy.sandbox.cloudmade.com/tiles/cycle/${z}/${x}/${y}.png"
+        ];
+        options = OpenLayers.Util.extend({ numZoomLevels: 19 }, options);
+        var newArguments = [name, url, options];
+        OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.OSM.CycleMap"
+});
+
+/**
+ * Class: OpenLayers.Layer.OSM.Maplint
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.OSM>
+ */
+OpenLayers.Layer.OSM.Maplint = OpenLayers.Class(OpenLayers.Layer.OSM, {
+    /**
+     * Constructor: OpenLayers.Layer.OSM.Maplint
+     *
+     * Parameters:
+     * name - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        var url = [
+            "http://d.tah.openstreetmap.org/Tiles/maplint/${z}/${x}/${y}.png",
+            "http://e.tah.openstreetmap.org/Tiles/maplint/${z}/${x}/${y}.png",
+            "http://f.tah.openstreetmap.org/Tiles/maplint/${z}/${x}/${y}.png"
+        ];
+        options = OpenLayers.Util.extend({ numZoomLevels: 18, isBaseLayer: false, visibility: false }, options);
+        var newArguments = [name, url, options];
+        OpenLayers.Layer.OSM.prototype.initialize.apply(this, newArguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.OSM.Maplint"
+});
diff --git a/media/js/admin.js b/media/js/admin.js
new file mode 100644
index 0000000..526bb2b
--- /dev/null
+++ b/media/js/admin.js
@@ -0,0 +1,26 @@
+(function($) {
+	info_search = function() {
+		$("#info-search").submit();
+	}
+	show_addedit = function(toggle) {
+		var addEditForm = $("#addedit");
+		if (toggle)
+		{
+			addEditForm.toggle(400);
+		}
+		else
+		{
+			addEditForm.show(400);
+		}
+		// Clear fields, but not buttons or the CSRF token.
+		$(':input', '#addedit')
+			.not(':button, :submit, :reset, #action, :checkbox, [name="form_auth_token"]')
+			.val('')
+			.removeAttr('selected');
+
+		// Reset checkbox separately to avoid wiping its value
+		$(':checkbox', '#addedit').removeAttr('checked');
+
+		$("a.add").focus();
+	}
+})(jQuery);
diff --git a/media/js/bugs.js b/media/js/bugs.js
new file mode 100644
index 0000000..dbc2bce
--- /dev/null
+++ b/media/js/bugs.js
@@ -0,0 +1,51 @@
+$(function(){
+	$('#show_bugs').click(function() {
+		$('#bug_form').toggle();
+		$("input#subject").focus();
+	});
+	
+	$('.error').hide();
+});
+
+// Ajax Submission
+function validatePost()
+{
+	// validate and process form
+	// first hide any error messages
+	$('.error').hide();
+
+	var subject = $("input#subject").val();
+	if (subject == "") {
+		$("label#subject_error").show();
+		$("input#subject").focus();
+		return false;
+	}
+	var yourname = $("input#yourname").val();
+	if (yourname == "") {
+		$("label#yourname_error").show();
+		$("input#yourname").focus();
+		return false;
+	}
+	var email = $("input#email").val();
+	if (email == "") {
+		$("label#email_error").show();
+		$("input#email").focus();
+		return false;
+	}
+	var phone = $("input#phone").val();
+	if (phone == "") {
+		$("label#phone_error").show();
+		$("input#phone").focus();
+		return false;
+	}
+	var description = $("#description").val();
+	if (description == "") {
+		$("label#description_error").show();
+		$("input#description").focus();
+		return false;
+	}
+	
+	$("#submit").attr("disabled","disabled");
+	$("#submit").val("Sending...");
+	return true;
+}
\ No newline at end of file
diff --git a/media/js/colorpicker.js b/media/js/colorpicker.js
new file mode 100644
index 0000000..10a2b22
--- /dev/null
+++ b/media/js/colorpicker.js
@@ -0,0 +1,484 @@
+/**
+ *
+ * Color picker
+ * Author: Stefan Petre www.eyecon.ro
+ * 
+ * Dual licensed under the MIT and GPL licenses
+ * 
+ */
+(function ($) {
+	var ColorPicker = function () {
+		var
+			ids = {},
+			inAction,
+			charMin = 65,
+			visible,
+			tpl = '<div class="colorpicker"><div class="colorpicker_color"><div><div></div></div></div><div class="colorpicker_hue"><div></div></div><div class="colorpicker_new_color"></div><div class="colorpicker_current_color"></div><div class="colorpicker_hex"><input type="text" maxlength="6" size="6" /></div><div class="colorpicker_rgb_r colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_g colorpicker_field"><input type="text" maxleng [...]
+			defaults = {
+				eventName: 'click',
+				onShow: function () {},
+				onBeforeShow: function(){},
+				onHide: function () {},
+				onChange: function () {},
+				onSubmit: function () {},
+				color: 'ff0000',
+				livePreview: true,
+				flat: false
+			},
+			fillRGBFields = function  (hsb, cal) {
+				var rgb = HSBToRGB(hsb);
+				$(cal).data('colorpicker').fields
+					.eq(1).val(rgb.r).end()
+					.eq(2).val(rgb.g).end()
+					.eq(3).val(rgb.b).end();
+			},
+			fillHSBFields = function  (hsb, cal) {
+				$(cal).data('colorpicker').fields
+					.eq(4).val(hsb.h).end()
+					.eq(5).val(hsb.s).end()
+					.eq(6).val(hsb.b).end();
+			},
+			fillHexFields = function (hsb, cal) {
+				$(cal).data('colorpicker').fields
+					.eq(0).val(HSBToHex(hsb)).end();
+			},
+			setSelector = function (hsb, cal) {
+				$(cal).data('colorpicker').selector.css('backgroundColor', '#' + HSBToHex({h: hsb.h, s: 100, b: 100}));
+				$(cal).data('colorpicker').selectorIndic.css({
+					left: parseInt(150 * hsb.s/100, 10),
+					top: parseInt(150 * (100-hsb.b)/100, 10)
+				});
+			},
+			setHue = function (hsb, cal) {
+				$(cal).data('colorpicker').hue.css('top', parseInt(150 - 150 * hsb.h/360, 10));
+			},
+			setCurrentColor = function (hsb, cal) {
+				$(cal).data('colorpicker').currentColor.css('backgroundColor', '#' + HSBToHex(hsb));
+			},
+			setNewColor = function (hsb, cal) {
+				$(cal).data('colorpicker').newColor.css('backgroundColor', '#' + HSBToHex(hsb));
+			},
+			keyDown = function (ev) {
+				var pressedKey = ev.charCode || ev.keyCode || -1;
+				if ((pressedKey > charMin && pressedKey <= 90) || pressedKey == 32) {
+					return false;
+				}
+				var cal = $(this).parent().parent();
+				if (cal.data('colorpicker').livePreview === true) {
+					change.apply(this);
+				}
+			},
+			change = function (ev) {
+				var cal = $(this).parent().parent(), col;
+				if (this.parentNode.className.indexOf('_hex') > 0) {
+					cal.data('colorpicker').color = col = HexToHSB(fixHex(this.value));
+				} else if (this.parentNode.className.indexOf('_hsb') > 0) {
+					cal.data('colorpicker').color = col = fixHSB({
+						h: parseInt(cal.data('colorpicker').fields.eq(4).val(), 10),
+						s: parseInt(cal.data('colorpicker').fields.eq(5).val(), 10),
+						b: parseInt(cal.data('colorpicker').fields.eq(6).val(), 10)
+					});
+				} else {
+					cal.data('colorpicker').color = col = RGBToHSB(fixRGB({
+						r: parseInt(cal.data('colorpicker').fields.eq(1).val(), 10),
+						g: parseInt(cal.data('colorpicker').fields.eq(2).val(), 10),
+						b: parseInt(cal.data('colorpicker').fields.eq(3).val(), 10)
+					}));
+				}
+				if (ev) {
+					fillRGBFields(col, cal.get(0));
+					fillHexFields(col, cal.get(0));
+					fillHSBFields(col, cal.get(0));
+				}
+				setSelector(col, cal.get(0));
+				setHue(col, cal.get(0));
+				setNewColor(col, cal.get(0));
+				cal.data('colorpicker').onChange.apply(cal, [col, HSBToHex(col), HSBToRGB(col)]);
+			},
+			blur = function (ev) {
+				var cal = $(this).parent().parent();
+				cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus');
+			},
+			focus = function () {
+				charMin = this.parentNode.className.indexOf('_hex') > 0 ? 70 : 65;
+				$(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus');
+				$(this).parent().addClass('colorpicker_focus');
+			},
+			downIncrement = function (ev) {
+				var field = $(this).parent().find('input').focus();
+				var current = {
+					el: $(this).parent().addClass('colorpicker_slider'),
+					max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255),
+					y: ev.pageY,
+					field: field,
+					val: parseInt(field.val(), 10),
+					preview: $(this).parent().parent().data('colorpicker').livePreview					
+				};
+				$(document).bind('mouseup', current, upIncrement);
+				$(document).bind('mousemove', current, moveIncrement);
+			},
+			moveIncrement = function (ev) {
+				ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val + ev.pageY - ev.data.y, 10))));
+				if (ev.data.preview) {
+					change.apply(ev.data.field.get(0), [true]);
+				}
+				return false;
+			},
+			upIncrement = function (ev) {
+				change.apply(ev.data.field.get(0), [true]);
+				ev.data.el.removeClass('colorpicker_slider').find('input').focus();
+				$(document).unbind('mouseup', upIncrement);
+				$(document).unbind('mousemove', moveIncrement);
+				return false;
+			},
+			downHue = function (ev) {
+				var current = {
+					cal: $(this).parent(),
+					y: $(this).offset().top
+				};
+				current.preview = current.cal.data('colorpicker').livePreview;
+				$(document).bind('mouseup', current, upHue);
+				$(document).bind('mousemove', current, moveHue);
+			},
+			moveHue = function (ev) {
+				change.apply(
+					ev.data.cal.data('colorpicker')
+						.fields
+						.eq(4)
+						.val(parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.y))))/150, 10))
+						.get(0),
+					[ev.data.preview]
+				);
+				return false;
+			},
+			upHue = function (ev) {
+				fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
+				fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
+				$(document).unbind('mouseup', upHue);
+				$(document).unbind('mousemove', moveHue);
+				return false;
+			},
+			downSelector = function (ev) {
+				var current = {
+					cal: $(this).parent(),
+					pos: $(this).offset()
+				};
+				current.preview = current.cal.data('colorpicker').livePreview;
+				$(document).bind('mouseup', current, upSelector);
+				$(document).bind('mousemove', current, moveSelector);
+			},
+			moveSelector = function (ev) {
+				change.apply(
+					ev.data.cal.data('colorpicker')
+						.fields
+						.eq(6)
+						.val(parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.pos.top))))/150, 10))
+						.end()
+						.eq(5)
+						.val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX - ev.data.pos.left))))/150, 10))
+						.get(0),
+					[ev.data.preview]
+				);
+				return false;
+			},
+			upSelector = function (ev) {
+				fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
+				fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
+				$(document).unbind('mouseup', upSelector);
+				$(document).unbind('mousemove', moveSelector);
+				return false;
+			},
+			enterSubmit = function (ev) {
+				$(this).addClass('colorpicker_focus');
+			},
+			leaveSubmit = function (ev) {
+				$(this).removeClass('colorpicker_focus');
+			},
+			clickSubmit = function (ev) {
+				var cal = $(this).parent();
+				var col = cal.data('colorpicker').color;
+				cal.data('colorpicker').origColor = col;
+				setCurrentColor(col, cal.get(0));
+				cal.data('colorpicker').onSubmit(col, HSBToHex(col), HSBToRGB(col), cal.data('colorpicker').el);
+			},
+			show = function (ev) {
+				var cal = $('#' + $(this).data('colorpickerId'));
+				cal.data('colorpicker').onBeforeShow.apply(this, [cal.get(0)]);
+				var pos = $(this).offset();
+				var viewPort = getViewport();
+				var top = pos.top + this.offsetHeight;
+				var left = pos.left;
+				if (top + 176 > viewPort.t + viewPort.h) {
+					top -= this.offsetHeight + 176;
+				}
+				if (left + 356 > viewPort.l + viewPort.w) {
+					left -= 356;
+				}
+				cal.css({left: left + 'px', top: top + 'px'});
+				if (cal.data('colorpicker').onShow.apply(this, [cal.get(0)]) != false) {
+					cal.show();
+				}
+				$(document).bind('mousedown', {cal: cal}, hide);
+				return false;
+			},
+			hide = function (ev) {
+				if (!isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) {
+					if (ev.data.cal.data('colorpicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) {
+						ev.data.cal.hide();
+					}
+					$(document).unbind('mousedown', hide);
+				}
+			},
+			isChildOf = function(parentEl, el, container) {
+				if (parentEl == el) {
+					return true;
+				}
+				if (parentEl.contains) {
+					return parentEl.contains(el);
+				}
+				if ( parentEl.compareDocumentPosition ) {
+					return !!(parentEl.compareDocumentPosition(el) & 16);
+				}
+				var prEl = el.parentNode;
+				while(prEl && prEl != container) {
+					if (prEl == parentEl)
+						return true;
+					prEl = prEl.parentNode;
+				}
+				return false;
+			},
+			getViewport = function () {
+				var m = document.compatMode == 'CSS1Compat';
+				return {
+					l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft),
+					t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop),
+					w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth),
+					h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight)
+				};
+			},
+			fixHSB = function (hsb) {
+				return {
+					h: Math.min(360, Math.max(0, hsb.h)),
+					s: Math.min(100, Math.max(0, hsb.s)),
+					b: Math.min(100, Math.max(0, hsb.b))
+				};
+			}, 
+			fixRGB = function (rgb) {
+				return {
+					r: Math.min(255, Math.max(0, rgb.r)),
+					g: Math.min(255, Math.max(0, rgb.g)),
+					b: Math.min(255, Math.max(0, rgb.b))
+				};
+			},
+			fixHex = function (hex) {
+				var len = 6 - hex.length;
+				if (len > 0) {
+					var o = [];
+					for (var i=0; i<len; i++) {
+						o.push('0');
+					}
+					o.push(hex);
+					hex = o.join('');
+				}
+				return hex;
+			}, 
+			HexToRGB = function (hex) {
+				var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
+				return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
+			},
+			HexToHSB = function (hex) {
+				return RGBToHSB(HexToRGB(hex));
+			},
+			RGBToHSB = function (rgb) {
+				var hsb = {
+					h: 0,
+					s: 0,
+					b: 0
+				};
+				var min = Math.min(rgb.r, rgb.g, rgb.b);
+				var max = Math.max(rgb.r, rgb.g, rgb.b);
+				var delta = max - min;
+				hsb.b = max;
+				if (max != 0) {
+					
+				}
+				hsb.s = max != 0 ? 255 * delta / max : 0;
+				if (hsb.s != 0) {
+					if (rgb.r == max) {
+						hsb.h = (rgb.g - rgb.b) / delta;
+					} else if (rgb.g == max) {
+						hsb.h = 2 + (rgb.b - rgb.r) / delta;
+					} else {
+						hsb.h = 4 + (rgb.r - rgb.g) / delta;
+					}
+				} else {
+					hsb.h = -1;
+				}
+				hsb.h *= 60;
+				if (hsb.h < 0) {
+					hsb.h += 360;
+				}
+				hsb.s *= 100/255;
+				hsb.b *= 100/255;
+				return hsb;
+			},
+			HSBToRGB = function (hsb) {
+				var rgb = {};
+				var h = Math.round(hsb.h);
+				var s = Math.round(hsb.s*255/100);
+				var v = Math.round(hsb.b*255/100);
+				if(s == 0) {
+					rgb.r = rgb.g = rgb.b = v;
+				} else {
+					var t1 = v;
+					var t2 = (255-s)*v/255;
+					var t3 = (t1-t2)*(h%60)/60;
+					if(h==360) h = 0;
+					if(h<60) {rgb.r=t1;	rgb.b=t2; rgb.g=t2+t3}
+					else if(h<120) {rgb.g=t1; rgb.b=t2;	rgb.r=t1-t3}
+					else if(h<180) {rgb.g=t1; rgb.r=t2;	rgb.b=t2+t3}
+					else if(h<240) {rgb.b=t1; rgb.r=t2;	rgb.g=t1-t3}
+					else if(h<300) {rgb.b=t1; rgb.g=t2;	rgb.r=t2+t3}
+					else if(h<360) {rgb.r=t1; rgb.g=t2;	rgb.b=t1-t3}
+					else {rgb.r=0; rgb.g=0;	rgb.b=0}
+				}
+				return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)};
+			},
+			RGBToHex = function (rgb) {
+				var hex = [
+					rgb.r.toString(16),
+					rgb.g.toString(16),
+					rgb.b.toString(16)
+				];
+				$.each(hex, function (nr, val) {
+					if (val.length == 1) {
+						hex[nr] = '0' + val;
+					}
+				});
+				return hex.join('');
+			},
+			HSBToHex = function (hsb) {
+				return RGBToHex(HSBToRGB(hsb));
+			},
+			restoreOriginal = function () {
+				var cal = $(this).parent();
+				var col = cal.data('colorpicker').origColor;
+				cal.data('colorpicker').color = col;
+				fillRGBFields(col, cal.get(0));
+				fillHexFields(col, cal.get(0));
+				fillHSBFields(col, cal.get(0));
+				setSelector(col, cal.get(0));
+				setHue(col, cal.get(0));
+				setNewColor(col, cal.get(0));
+			};
+		return {
+			init: function (opt) {
+				opt = $.extend({}, defaults, opt||{});
+				if (typeof opt.color == 'string') {
+					opt.color = HexToHSB(opt.color);
+				} else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) {
+					opt.color = RGBToHSB(opt.color);
+				} else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) {
+					opt.color = fixHSB(opt.color);
+				} else {
+					return this;
+				}
+				return this.each(function () {
+					if (!$(this).data('colorpickerId')) {
+						var options = $.extend({}, opt);
+						options.origColor = opt.color;
+						var id = 'collorpicker_' + parseInt(Math.random() * 1000);
+						$(this).data('colorpickerId', id);
+						var cal = $(tpl).attr('id', id);
+						if (options.flat) {
+							cal.appendTo(this).show();
+						} else {
+							cal.appendTo(document.body);
+						}
+						options.fields = cal
+											.find('input')
+												.bind('keyup', keyDown)
+												.bind('change', change)
+												.bind('blur', blur)
+												.bind('focus', focus);
+						cal
+							.find('span').bind('mousedown', downIncrement).end()
+							.find('>div.colorpicker_current_color').bind('click', restoreOriginal);
+						options.selector = cal.find('div.colorpicker_color').bind('mousedown', downSelector);
+						options.selectorIndic = options.selector.find('div div');
+						options.el = this;
+						options.hue = cal.find('div.colorpicker_hue div');
+						cal.find('div.colorpicker_hue').bind('mousedown', downHue);
+						options.newColor = cal.find('div.colorpicker_new_color');
+						options.currentColor = cal.find('div.colorpicker_current_color');
+						cal.data('colorpicker', options);
+						cal.find('div.colorpicker_submit')
+							.bind('mouseenter', enterSubmit)
+							.bind('mouseleave', leaveSubmit)
+							.bind('click', clickSubmit);
+						fillRGBFields(options.color, cal.get(0));
+						fillHSBFields(options.color, cal.get(0));
+						fillHexFields(options.color, cal.get(0));
+						setHue(options.color, cal.get(0));
+						setSelector(options.color, cal.get(0));
+						setCurrentColor(options.color, cal.get(0));
+						setNewColor(options.color, cal.get(0));
+						if (options.flat) {
+							cal.css({
+								position: 'relative',
+								display: 'block'
+							});
+						} else {
+							$(this).bind(options.eventName, show);
+						}
+					}
+				});
+			},
+			showPicker: function() {
+				return this.each( function () {
+					if ($(this).data('colorpickerId')) {
+						show.apply(this);
+					}
+				});
+			},
+			hidePicker: function() {
+				return this.each( function () {
+					if ($(this).data('colorpickerId')) {
+						$('#' + $(this).data('colorpickerId')).hide();
+					}
+				});
+			},
+			setColor: function(col) {
+				if (typeof col == 'string') {
+					col = HexToHSB(col);
+				} else if (col.r != undefined && col.g != undefined && col.b != undefined) {
+					col = RGBToHSB(col);
+				} else if (col.h != undefined && col.s != undefined && col.b != undefined) {
+					col = fixHSB(col);
+				} else {
+					return this;
+				}
+				return this.each(function(){
+					if ($(this).data('colorpickerId')) {
+						var cal = $('#' + $(this).data('colorpickerId'));
+						cal.data('colorpicker').color = col;
+						cal.data('colorpicker').origColor = col;
+						fillRGBFields(col, cal.get(0));
+						fillHSBFields(col, cal.get(0));
+						fillHexFields(col, cal.get(0));
+						setHue(col, cal.get(0));
+						setSelector(col, cal.get(0));
+						setCurrentColor(col, cal.get(0));
+						setNewColor(col, cal.get(0));
+					}
+				});
+			}
+		};
+	}();
+	$.fn.extend({
+		ColorPicker: ColorPicker.init,
+		ColorPickerHide: ColorPicker.hidePicker,
+		ColorPickerShow: ColorPicker.showPicker,
+		ColorPickerSetColor: ColorPicker.setColor
+	});
+})(jQuery)
\ No newline at end of file
diff --git a/media/js/excanvas.min.js b/media/js/excanvas.min.js
new file mode 100644
index 0000000..12c74f7
--- /dev/null
+++ b/media/js/excanvas.min.js
@@ -0,0 +1 @@
+if(!document.createElement("canvas").getContext){(function(){var z=Math;var K=z.round;var J=z.sin;var U=z.cos;var b=z.abs;var k=z.sqrt;var D=10;var F=D/2;function T(){return this.context_||(this.context_=new W(this))}var O=Array.prototype.slice;function G(i,j,m){var Z=O.call(arguments,2);return function(){return i.apply(j,Z.concat(O.call(arguments)))}}function AD(Z){return String(Z).replace(/&/g,"&").replace(/"/g,""")}function r(i){if(!i.namespaces.g_vml_){i.namespaces.add("g_vm [...]
\ No newline at end of file
diff --git a/media/js/excanvas.pack.js b/media/js/excanvas.pack.js
new file mode 100644
index 0000000..71d6fbd
--- /dev/null
+++ b/media/js/excanvas.pack.js
@@ -0,0 +1 @@
+if(!window.CanvasRenderingContext2D){(function(){var m=Math;var mr=m.round;var ms=m.sin;var mc=m.cos;var Z=10;var Z2=Z/2;var G_vmlCanvasManager_={init:function(opt_doc){var doc=opt_doc||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var self=this;doc.attachEvent("onreadystatechange",function(){self.init_(doc)})}},init_:function(doc){if(doc.readyState=="complete"){if(!doc.namespaces["g_vml_"]){doc.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var ss=doc.createStyl [...]
diff --git a/media/js/global.js b/media/js/global.js
new file mode 100644
index 0000000..0acb94b
--- /dev/null
+++ b/media/js/global.js
@@ -0,0 +1,34 @@
+jQuery(document).ready(function( $ ) {
+
+	// TOGGLE DROPDOWN
+	$('.header_nav_dropdown .header_nav_cancel').on('click', function(e) {
+		$(this).closest('.header_nav_dropdown').fadeOut('fast');
+		$(this).closest('.header_nav_dropdown').siblings('p').removeClass('active');
+	});
+	$('.header_nav_has_dropdown > a, .header_nav_actions .header_nav_button_delete, .header_nav_actions .header_nav_button_change').on('click', function(e) {
+		$(this).toggleClass('active');
+		$(this).siblings('.header_nav_dropdown').fadeToggle('fast')
+		e.stopPropagation();
+		return false;
+	});
+	$('.header_nav_actions .header_nav_dropdown').on('click', function(e) {
+		e.stopPropagation();
+	});
+
+	$('#header_nav_forgot').click(function() {
+		$('#header_nav_userforgot_form').toggle('fast');
+	});
+	
+	// Silence JS errors if console not defined (ie. not firebug and not running chrome)
+	if(typeof(console) === 'undefined') {
+		var console = {};
+		console.log = console.error = console.info = console.debug = console.warn = console.trace = console.dir = console.dirxml = console.group = console.groupEnd = console.time = console.timeEnd = console.assert = console.profile = function() {};
+	}
+	
+	// Trigger pngFix
+	if($(document).pngFix)
+	{
+		$(document).pngFix();
+	}
+
+});
\ No newline at end of file
diff --git a/media/js/jqplot.barRenderer.min.js b/media/js/jqplot.barRenderer.min.js
new file mode 100755
index 0000000..c406b64
--- /dev/null
+++ b/media/js/jqplot.barRenderer.min.js
@@ -0,0 +1,57 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.0b2_r1012
+ *
+ * Copyright (c) 2009-2011 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ *
+ * included jsDate library by Chris Leonello:
+ *
+ * Copyright (c) 2010-2011 Chris Leonello
+ *
+ * jsDate is currently available for use in all personal or commercial projects 
+ * under both the MIT and GPL version 2.0 licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * jsDate borrows many concepts and ideas from the Date Instance 
+ * Methods by Ken Snyder along with some parts of Ken's actual code.
+ * 
+ * Ken's origianl Date Instance Methods and copyright notice:
+ * 
+ * Ken Snyder (ken d snyder at gmail dot com)
+ * 2008-09-10
+ * version 2.0.2 (http://kendsnyder.com/sandbox/date/)     
+ * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
+ *
+ * jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
+ * Larry has generously given permission to adapt his code for inclusion
+ * into jqPlot.
+ *
+ * Larry's original code can be found here:
+ *
+ * https://github.com/lsiden/export-jqplot-to-png
+ * 
+ * 
+ */
+(function(d){d.jqplot.BarRenderer=function(){d.jqplot.LineRenderer.call(this)};d.jqplot.BarRenderer.prototype=new d.jqplot.LineRenderer();d.jqplot.BarRenderer.prototype.constructor=d.jqplot.BarRenderer;d.jqplot.BarRenderer.prototype.init=function(n,p){this.barPadding=8;this.barMargin=10;this.barDirection="vertical";this.barWidth=null;this.shadowOffset=2;this.shadowDepth=5;this.shadowAlpha=0.08;this.waterfall=false;this.groups=1;this.varyBarColor=false;this.highlightMouseOver=true;this.hi [...]
\ No newline at end of file
diff --git a/media/js/jqplot.dateAxisRenderer.min.js b/media/js/jqplot.dateAxisRenderer.min.js
new file mode 100644
index 0000000..4173e91
--- /dev/null
+++ b/media/js/jqplot.dateAxisRenderer.min.js
@@ -0,0 +1,57 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.0b2_r1012
+ *
+ * Copyright (c) 2009-2011 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ *
+ * included jsDate library by Chris Leonello:
+ *
+ * Copyright (c) 2010-2011 Chris Leonello
+ *
+ * jsDate is currently available for use in all personal or commercial projects 
+ * under both the MIT and GPL version 2.0 licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * jsDate borrows many concepts and ideas from the Date Instance 
+ * Methods by Ken Snyder along with some parts of Ken's actual code.
+ * 
+ * Ken's origianl Date Instance Methods and copyright notice:
+ * 
+ * Ken Snyder (ken d snyder at gmail dot com)
+ * 2008-09-10
+ * version 2.0.2 (http://kendsnyder.com/sandbox/date/)     
+ * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
+ *
+ * jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
+ * Larry has generously given permission to adapt his code for inclusion
+ * into jqPlot.
+ *
+ * Larry's original code can be found here:
+ *
+ * https://github.com/lsiden/export-jqplot-to-png
+ * 
+ * 
+ */
+(function(h){h.jqplot.DateAxisRenderer=function(){h.jqplot.LinearAxisRenderer.call(this);this.date=new h.jsDate()};var c=1000;var e=60*c;var f=60*e;var l=24*f;var b=7*l;var j=30.4368499*l;var k=365.242199*l;var g=[31,28,31,30,31,30,31,30,31,30,31,30];var i=["%M:%S.%#N","%M:%S.%#N","%M:%S.%#N","%M:%S","%M:%S","%M:%S","%M:%S","%H:%M:%S","%H:%M:%S","%H:%M","%H:%M","%H:%M","%H:%M","%H:%M","%H:%M","%a %H:%M","%a %H:%M","%b %e %H:%M","%b %e %H:%M","%b %e %H:%M","%b %e %H:%M","%v","%v","%v","%v [...]
\ No newline at end of file
diff --git a/media/js/jqplot.pointLabels.min.js b/media/js/jqplot.pointLabels.min.js
new file mode 100755
index 0000000..7cf02ea
--- /dev/null
+++ b/media/js/jqplot.pointLabels.min.js
@@ -0,0 +1,57 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.0b2_r1012
+ *
+ * Copyright (c) 2009-2011 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ *
+ * included jsDate library by Chris Leonello:
+ *
+ * Copyright (c) 2010-2011 Chris Leonello
+ *
+ * jsDate is currently available for use in all personal or commercial projects 
+ * under both the MIT and GPL version 2.0 licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * jsDate borrows many concepts and ideas from the Date Instance 
+ * Methods by Ken Snyder along with some parts of Ken's actual code.
+ * 
+ * Ken's origianl Date Instance Methods and copyright notice:
+ * 
+ * Ken Snyder (ken d snyder at gmail dot com)
+ * 2008-09-10
+ * version 2.0.2 (http://kendsnyder.com/sandbox/date/)     
+ * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
+ *
+ * jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
+ * Larry has generously given permission to adapt his code for inclusion
+ * into jqPlot.
+ *
+ * Larry's original code can be found here:
+ *
+ * https://github.com/lsiden/export-jqplot-to-png
+ * 
+ * 
+ */
+(function(c){c.jqplot.PointLabels=function(e){this.show=c.jqplot.config.enablePlugins;this.location="n";this.labelsFromSeries=false;this.seriesLabelIndex=null;this.labels=[];this._labels=[];this.stackedValue=false;this.ypadding=6;this.xpadding=6;this.escapeHTML=true;this.edgeTolerance=-5;this.formatter=c.jqplot.DefaultTickFormatter;this.formatString="";this.hideZeros=false;this._elems=[];c.extend(true,this,e)};var a=["nw","n","ne","e","se","s","sw","w"];var d={nw:0,n:1,ne:2,e:3,se:4,s:5, [...]
\ No newline at end of file
diff --git a/media/js/jquery.autocomplete.pack.js b/media/js/jquery.autocomplete.pack.js
new file mode 100644
index 0000000..2d09b00
--- /dev/null
+++ b/media/js/jquery.autocomplete.pack.js
@@ -0,0 +1,12 @@
+/*
+ * jQuery Autocomplete plugin 1.1
+ *
+ * Copyright (c) 2009 Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}(';(3($){$.2e.1u({19:3(b,d){5 c=W b=="1B";d=$.1u({},$.M.1T,{Y:c?b:P,y:c?P:b,1J:c?$.M.1T.1J:10,X:d&&!d.1D?10:48},d);d.1y=d.1y||3(a){6 a};d.1v=d.1v||d.1R;6 A.I(3(){1M $.M [...]
\ No newline at end of file
diff --git a/media/js/jquery.base64.js b/media/js/jquery.base64.js
new file mode 100644
index 0000000..8cda79d
--- /dev/null
+++ b/media/js/jquery.base64.js
@@ -0,0 +1,190 @@
+/*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false,
+  debug: false, devel: true, eqeqeq: true, es5: false, evil: false,
+  forin: false, fragment: false, immed: true, laxbreak: false, newcap: true,
+  nomen: false, on: false, onevar: true, passfail: false, plusplus: true,
+  regexp: false, rhino: true, safe: false, strict: false, sub: false,
+  undef: true, white: false, widget: false, windows: false */
+/*global jQuery: false, window: false */
+"use strict";
+
+/*
+ * Original code (c) 2010 Nick Galbreath
+ * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
+ *
+ * jQuery port (c) 2010 Carlo Zottmann
+ * http://github.com/carlo/jquery-base64
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/* base64 encode/decode compatible with window.btoa/atob
+ *
+ * window.atob/btoa is a Firefox extension to convert binary data (the "b")
+ * to base64 (ascii, the "a").
+ *
+ * It is also found in Safari and Chrome.  It is not available in IE.
+ *
+ * if (!window.btoa) window.btoa = $.base64.encode
+ * if (!window.atob) window.atob = $.base64.decode
+ *
+ * The original spec's for atob/btoa are a bit lacking
+ * https://developer.mozilla.org/en/DOM/window.atob
+ * https://developer.mozilla.org/en/DOM/window.btoa
+ *
+ * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255]
+ * If any character is not [0,255], then an exception is thrown.
+ *
+ * window.atob and $.base64.decode take a base64-encoded string
+ * If the input length is not a multiple of 4, or contains invalid characters
+ *   then an exception is thrown.
+ */
+ 
+jQuery.base64 = ( function( $ ) {
+  
+  var _PADCHAR = "=",
+    _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
+    _VERSION = "1.0";
+
+
+  function _getbyte64( s, i ) {
+    // This is oddly fast, except on Chrome/V8.
+    // Minimal or no improvement in performance by using a
+    // object with properties mapping chars to value (eg. 'A': 0)
+
+    var idx = _ALPHA.indexOf( s.charAt( i ) );
+
+    if ( idx === -1 ) {
+      throw "Cannot decode base64";
+    }
+
+    return idx;
+  }
+  
+  
+  function _decode( s ) {
+    var pads = 0,
+      i,
+      b10,
+      imax = s.length,
+      x = [];
+
+    s = String( s );
+    
+    if ( imax === 0 ) {
+      return s;
+    }
+
+    if ( imax % 4 !== 0 ) {
+      throw "Cannot decode base64";
+    }
+
+    if ( s.charAt( imax - 1 ) === _PADCHAR ) {
+      pads = 1;
+
+      if ( s.charAt( imax - 2 ) === _PADCHAR ) {
+        pads = 2;
+      }
+
+      // either way, we want to ignore this last block
+      imax -= 4;
+    }
+
+    for ( i = 0; i < imax; i += 4 ) {
+      b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 );
+      x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) );
+    }
+
+    switch ( pads ) {
+      case 1:
+        b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 );
+        x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) );
+        break;
+
+      case 2:
+        b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 );
+        x.push( String.fromCharCode( b10 >> 16 ) );
+        break;
+    }
+
+    return x.join( "" );
+  }
+  
+  
+  function _getbyte( s, i ) {
+    var x = s.charCodeAt( i );
+
+    if ( x > 255 ) {
+      throw "INVALID_CHARACTER_ERR: DOM Exception 5";
+    }
+    
+    return x;
+  }
+
+
+  function _encode( s ) {
+    if ( arguments.length !== 1 ) {
+      throw "SyntaxError: exactly one argument required";
+    }
+
+    s = String( s );
+
+    var i,
+      b10,
+      x = [],
+      imax = s.length - s.length % 3;
+
+    if ( s.length === 0 ) {
+      return s;
+    }
+
+    for ( i = 0; i < imax; i += 3 ) {
+      b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 );
+      x.push( _ALPHA.charAt( b10 >> 18 ) );
+      x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) );
+      x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) );
+      x.push( _ALPHA.charAt( b10 & 0x3f ) );
+    }
+
+    switch ( s.length - imax ) {
+      case 1:
+        b10 = _getbyte( s, i ) << 16;
+        x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR );
+        break;
+
+      case 2:
+        b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 );
+        x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR );
+        break;
+    }
+
+    return x.join( "" );
+  }
+
+
+  return {
+    decode: _decode,
+    encode: _encode,
+    VERSION: _VERSION
+  };
+      
+}( jQuery ) );
+
diff --git a/media/js/jquery.bgiframe.min.js b/media/js/jquery.bgiframe.min.js
new file mode 100644
index 0000000..ce65109
--- /dev/null
+++ b/media/js/jquery.bgiframe.min.js
@@ -0,0 +1,39 @@
+/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
+ * Licensed under the MIT License
+ *
+ * Version 2.1.3-pre
+ */
+
+(function($){
+
+$.fn.bgiframe = ($.browser.msie && /msie 6\.0/i.test(navigator.userAgent) ? function(s) {
+    s = $.extend({
+        top     : 'auto', // auto == .currentStyle.borderTopWidth
+        left    : 'auto', // auto == .currentStyle.borderLeftWidth
+        width   : 'auto', // auto == offsetWidth
+        height  : 'auto', // auto == offsetHeight
+        opacity : true,
+        src     : 'javascript:false;'
+    }, s);
+    var html = '<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+
+                   'style="display:block;position:absolute;z-index:-1;'+
+                       (s.opacity !== false?'filter:Alpha(Opacity=\'0\');':'')+
+                       'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+
+                       'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+
+                       'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+
+                       'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+
+                '"/>';
+    return this.each(function() {
+        if ( $(this).children('iframe.bgiframe').length === 0 )
+            this.insertBefore( document.createElement(html), this.firstChild );
+    });
+} : function() { return this; });
+
+// old alias
+$.fn.bgIframe = $.fn.bgiframe;
+
+function prop(n) {
+    return n && n.constructor === Number ? n + 'px' : n;
+}
+
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jquery.datePicker.js b/media/js/jquery.datePicker.js
new file mode 100644
index 0000000..63290d2
--- /dev/null
+++ b/media/js/jquery.datePicker.js
@@ -0,0 +1,1735 @@
+/*
+ * Date prototype extensions. Doesn't depend on any
+ * other code. Doens't overwrite existing methods.
+ *
+ * Adds dayNames, abbrDayNames, monthNames and abbrMonthNames static properties and isLeapYear,
+ * isWeekend, isWeekDay, getDaysInMonth, getDayName, getMonthName, getDayOfYear, getWeekOfYear,
+ * setDayOfYear, addYears, addMonths, addDays, addHours, addMinutes, addSeconds methods
+ *
+ * Copyright (c) 2006 Jörn Zaefferer and Brandon Aaron (brandon.aaron at gmail.com || http://brandonaaron.net)
+ *
+ * Additional methods and properties added by Kelvin Luck: firstDayOfWeek, dateFormat, zeroTime, asString, fromString -
+ * I've added my name to these methods so you know who to blame if they are broken!
+ * 
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ */
+
+/**
+ * An Array of day names starting with Sunday.
+ * 
+ * @example dayNames[0]
+ * @result 'Sunday'
+ *
+ * @name dayNames
+ * @type Array
+ * @cat Plugins/Methods/Date
+ */
+Date.dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+
+/**
+ * An Array of abbreviated day names starting with Sun.
+ * 
+ * @example abbrDayNames[0]
+ * @result 'Sun'
+ *
+ * @name abbrDayNames
+ * @type Array
+ * @cat Plugins/Methods/Date
+ */
+Date.abbrDayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
+
+/**
+ * An Array of month names starting with Janurary.
+ * 
+ * @example monthNames[0]
+ * @result 'January'
+ *
+ * @name monthNames
+ * @type Array
+ * @cat Plugins/Methods/Date
+ */
+Date.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
+
+/**
+ * An Array of abbreviated month names starting with Jan.
+ * 
+ * @example abbrMonthNames[0]
+ * @result 'Jan'
+ *
+ * @name monthNames
+ * @type Array
+ * @cat Plugins/Methods/Date
+ */
+Date.abbrMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+
+/**
+ * The first day of the week for this locale.
+ *
+ * @name firstDayOfWeek
+ * @type Number
+ * @cat Plugins/Methods/Date
+ * @author Kelvin Luck
+ */
+Date.firstDayOfWeek = 1;
+
+/**
+ * The format that string dates should be represented as (e.g. 'dd/mm/yyyy' for UK, 'mm/dd/yyyy' for US, 'yyyy-mm-dd' for Unicode etc).
+ *
+ * @name format
+ * @type String
+ * @cat Plugins/Methods/Date
+ * @author Kelvin Luck
+ */
+//Date.format = 'dd/mm/yyyy';
+//Date.format = 'mm/dd/yyyy';
+//Date.format = 'yyyy-mm-dd';
+//Date.format = 'dd mmm yy';
+Date.format = 'yyyy/mm/dd';
+
+/**
+ * The first two numbers in the century to be used when decoding a two digit year. Since a two digit year is ambiguous (and date.setYear
+ * only works with numbers < 99 and so doesn't allow you to set years after 2000) we need to use this to disambiguate the two digit year codes.
+ *
+ * @name format
+ * @type String
+ * @cat Plugins/Methods/Date
+ * @author Kelvin Luck
+ */
+Date.fullYearStart = '20';
+
+(function() {
+
+	/**
+	 * Adds a given method under the given name 
+	 * to the Date prototype if it doesn't
+	 * currently exist.
+	 *
+	 * @private
+	 */
+	function add(name, method) {
+		if( !Date.prototype[name] ) {
+			Date.prototype[name] = method;
+		}
+	};
+	
+	/**
+	 * Checks if the year is a leap year.
+	 *
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.isLeapYear();
+	 * @result true
+	 *
+	 * @name isLeapYear
+	 * @type Boolean
+	 * @cat Plugins/Methods/Date
+	 */
+	add("isLeapYear", function() {
+		var y = this.getFullYear();
+		return (y%4==0 && y%100!=0) || y%400==0;
+	});
+	
+	/**
+	 * Checks if the day is a weekend day (Sat or Sun).
+	 *
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.isWeekend();
+	 * @result false
+	 *
+	 * @name isWeekend
+	 * @type Boolean
+	 * @cat Plugins/Methods/Date
+	 */
+	add("isWeekend", function() {
+		return this.getDay()==0 || this.getDay()==6;
+	});
+	
+	/**
+	 * Check if the day is a day of the week (Mon-Fri)
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.isWeekDay();
+	 * @result false
+	 * 
+	 * @name isWeekDay
+	 * @type Boolean
+	 * @cat Plugins/Methods/Date
+	 */
+	add("isWeekDay", function() {
+		return !this.isWeekend();
+	});
+	
+	/**
+	 * Gets the number of days in the month.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.getDaysInMonth();
+	 * @result 31
+	 * 
+	 * @name getDaysInMonth
+	 * @type Number
+	 * @cat Plugins/Methods/Date
+	 */
+	add("getDaysInMonth", function() {
+		return [31,(this.isLeapYear() ? 29:28),31,30,31,30,31,31,30,31,30,31][this.getMonth()];
+	});
+	
+	/**
+	 * Gets the name of the day.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.getDayName();
+	 * @result 'Saturday'
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.getDayName(true);
+	 * @result 'Sat'
+	 * 
+	 * @param abbreviated Boolean When set to true the name will be abbreviated.
+	 * @name getDayName
+	 * @type String
+	 * @cat Plugins/Methods/Date
+	 */
+	add("getDayName", function(abbreviated) {
+		return abbreviated ? Date.abbrDayNames[this.getDay()] : Date.dayNames[this.getDay()];
+	});
+
+	/**
+	 * Gets the name of the month.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.getMonthName();
+	 * @result 'Janurary'
+	 *
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.getMonthName(true);
+	 * @result 'Jan'
+	 * 
+	 * @param abbreviated Boolean When set to true the name will be abbreviated.
+	 * @name getDayName
+	 * @type String
+	 * @cat Plugins/Methods/Date
+	 */
+	add("getMonthName", function(abbreviated) {
+		return abbreviated ? Date.abbrMonthNames[this.getMonth()] : Date.monthNames[this.getMonth()];
+	});
+
+	/**
+	 * Get the number of the day of the year.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.getDayOfYear();
+	 * @result 11
+	 * 
+	 * @name getDayOfYear
+	 * @type Number
+	 * @cat Plugins/Methods/Date
+	 */
+	add("getDayOfYear", function() {
+		var tmpdtm = new Date("1/1/" + this.getFullYear());
+		return Math.floor((this.getTime() - tmpdtm.getTime()) / 86400000);
+	});
+	
+	/**
+	 * Get the number of the week of the year.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.getWeekOfYear();
+	 * @result 2
+	 * 
+	 * @name getWeekOfYear
+	 * @type Number
+	 * @cat Plugins/Methods/Date
+	 */
+	add("getWeekOfYear", function() {
+		return Math.ceil(this.getDayOfYear() / 7);
+	});
+
+	/**
+	 * Set the day of the year.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.setDayOfYear(1);
+	 * dtm.toString();
+	 * @result 'Tue Jan 01 2008 00:00:00'
+	 * 
+	 * @name setDayOfYear
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 */
+	add("setDayOfYear", function(day) {
+		this.setMonth(0);
+		this.setDate(day);
+		return this;
+	});
+	
+	/**
+	 * Add a number of years to the date object.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.addYears(1);
+	 * dtm.toString();
+	 * @result 'Mon Jan 12 2009 00:00:00'
+	 * 
+	 * @name addYears
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 */
+	add("addYears", function(num) {
+		this.setFullYear(this.getFullYear() + num);
+		return this;
+	});
+	
+	/**
+	 * Add a number of months to the date object.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.addMonths(1);
+	 * dtm.toString();
+	 * @result 'Tue Feb 12 2008 00:00:00'
+	 * 
+	 * @name addMonths
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 */
+	add("addMonths", function(num) {
+		var tmpdtm = this.getDate();
+		
+		this.setMonth(this.getMonth() + num);
+		
+		if (tmpdtm > this.getDate())
+			this.addDays(-this.getDate());
+		
+		return this;
+	});
+	
+	/**
+	 * Add a number of days to the date object.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.addDays(1);
+	 * dtm.toString();
+	 * @result 'Sun Jan 13 2008 00:00:00'
+	 * 
+	 * @name addDays
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 */
+	add("addDays", function(num) {
+		//this.setDate(this.getDate() + num);
+		this.setTime(this.getTime() + (num*86400000) );
+		return this;
+	});
+	
+	/**
+	 * Add a number of hours to the date object.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.addHours(24);
+	 * dtm.toString();
+	 * @result 'Sun Jan 13 2008 00:00:00'
+	 * 
+	 * @name addHours
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 */
+	add("addHours", function(num) {
+		this.setHours(this.getHours() + num);
+		return this;
+	});
+
+	/**
+	 * Add a number of minutes to the date object.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.addMinutes(60);
+	 * dtm.toString();
+	 * @result 'Sat Jan 12 2008 01:00:00'
+	 * 
+	 * @name addMinutes
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 */
+	add("addMinutes", function(num) {
+		this.setMinutes(this.getMinutes() + num);
+		return this;
+	});
+	
+	/**
+	 * Add a number of seconds to the date object.
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.addSeconds(60);
+	 * dtm.toString();
+	 * @result 'Sat Jan 12 2008 00:01:00'
+	 * 
+	 * @name addSeconds
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 */
+	add("addSeconds", function(num) {
+		this.setSeconds(this.getSeconds() + num);
+		return this;
+	});
+	
+	/**
+	 * Sets the time component of this Date to zero for cleaner, easier comparison of dates where time is not relevant.
+	 * 
+	 * @example var dtm = new Date();
+	 * dtm.zeroTime();
+	 * dtm.toString();
+	 * @result 'Sat Jan 12 2008 00:01:00'
+	 * 
+	 * @name zeroTime
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 * @author Kelvin Luck
+	 */
+	add("zeroTime", function() {
+		this.setMilliseconds(0);
+		this.setSeconds(0);
+		this.setMinutes(0);
+		this.setHours(0);
+		return this;
+	});
+	
+	/**
+	 * Returns a string representation of the date object according to Date.format.
+	 * (Date.toString may be used in other places so I purposefully didn't overwrite it)
+	 * 
+	 * @example var dtm = new Date("01/12/2008");
+	 * dtm.asString();
+	 * @result '12/01/2008' // (where Date.format == 'dd/mm/yyyy'
+	 * 
+	 * @name asString
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 * @author Kelvin Luck
+	 */
+	add("asString", function(format) {
+		var r = format || Date.format;
+		if (r.split('mm').length>1) { // ugly workaround to make sure we don't replace the m's in e.g. noveMber
+			r = r.split('mmmm').join(this.getMonthName(false))
+				.split('mmm').join(this.getMonthName(true))
+				.split('mm').join(_zeroPad(this.getMonth()+1))
+		} else {
+			r = r.split('m').join(this.getMonth()+1);
+		}
+		r = r.split('yyyy').join(this.getFullYear())
+			.split('yy').join((this.getFullYear() + '').substring(2))
+			.split('dd').join(_zeroPad(this.getDate()))
+			.split('d').join(this.getDate());
+		return r;
+	});
+	
+	/**
+	 * Returns a new date object created from the passed String according to Date.format or false if the attempt to do this results in an invalid date object
+	 * (We can't simple use Date.parse as it's not aware of locale and I chose not to overwrite it incase it's functionality is being relied on elsewhere)
+	 *
+	 * @example var dtm = Date.fromString("12/01/2008");
+	 * dtm.toString();
+	 * @result 'Sat Jan 12 2008 00:00:00' // (where Date.format == 'dd/mm/yyyy'
+	 * 
+	 * @name fromString
+	 * @type Date
+	 * @cat Plugins/Methods/Date
+	 * @author Kelvin Luck
+	 */
+	Date.fromString = function(s)
+	{
+		var f = Date.format;
+		
+		var d = new Date('01/01/1970');
+		
+		if (s == '') return d;
+
+		s = s.toLowerCase();
+		var matcher = '';
+		var order = [];
+		var r = /(dd?d?|mm?m?|yy?yy?)+([^(m|d|y)])?/g;
+		var results;
+		while ((results = r.exec(f)) != null)
+		{
+			switch (results[1]) {
+				case 'd':
+				case 'dd':
+				case 'm':
+				case 'mm':
+				case 'yy':
+				case 'yyyy':
+					matcher += '(\\d+\\d?\\d?\\d?)+';
+					order.push(results[1].substr(0, 1));
+					break;
+				case 'mmm':
+					matcher += '([a-z]{3})';
+					order.push('M');
+					break;
+			}
+			if (results[2]) {
+				matcher += results[2];
+			}
+			
+		}
+		var dm = new RegExp(matcher);
+		var result = s.match(dm);
+		for (var i=0; i<order.length; i++) {
+			var res = result[i+1];
+			switch(order[i]) {
+				case 'd':
+					d.setDate(res);
+					break;
+				case 'm':
+					d.setMonth(Number(res)-1);
+					break;
+				case 'M':
+					for (var j=0; j<Date.abbrMonthNames.length; j++) {
+						if (Date.abbrMonthNames[j].toLowerCase() == res) break;
+					}
+					d.setMonth(j);
+					break;
+				case 'y':
+					d.setYear(res);
+					break;
+			}
+		}
+
+		return d;
+	};
+	
+	// utility method
+	var _zeroPad = function(num) {
+		var s = '0'+num;
+		return s.substring(s.length-2)
+		//return ('0'+num).substring(-2); // doesn't work on IE :(
+	};
+	
+})();
+
+/**
+ * Copyright (c) 2008 Kelvin Luck (http://www.kelvinluck.com/)
+ * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ * .
+ * $Id$
+ **/
+
+(function($){
+    
+	$.fn.extend({
+/**
+ * Render a calendar table into any matched elements.
+ * 
+ * @param Object s (optional) Customize your calendars.
+ * @option Number month The month to render (NOTE that months are zero based). Default is today's month.
+ * @option Number year The year to render. Default is today's year.
+ * @option Function renderCallback A reference to a function that is called as each cell is rendered and which can add classes and event listeners to the created nodes. Default is no callback.
+ * @option Number showHeader Whether or not to show the header row, possible values are: $.dpConst.SHOW_HEADER_NONE (no header), $.dpConst.SHOW_HEADER_SHORT (first letter of each day) and $.dpConst.SHOW_HEADER_LONG (full name of each day). Default is $.dpConst.SHOW_HEADER_SHORT.
+ * @option String hoverClass The class to attach to each cell when you hover over it (to allow you to use hover effects in IE6 which doesn't support the :hover pseudo-class on elements other than links). Default is dp-hover. Pass false if you don't want a hover class.
+ * @type jQuery
+ * @name renderCalendar
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('#calendar-me').renderCalendar({month:0, year:2007});
+ * @desc Renders a calendar displaying January 2007 into the element with an id of calendar-me.
+ *
+ * @example
+ * var testCallback = function($td, thisDate, month, year)
+ * {
+ * if ($td.is('.current-month') && thisDate.getDay() == 4) {
+ *		var d = thisDate.getDate();
+ *		$td.bind(
+ *			'click',
+ *			function()
+ *			{
+ *				alert('You clicked on ' + d + '/' + (Number(month)+1) + '/' + year);
+ *			}
+ *		).addClass('thursday');
+ *	} else if (thisDate.getDay() == 5) {
+ *		$td.html('Friday the ' + $td.html() + 'th');
+ *	}
+ * }
+ * $('#calendar-me').renderCalendar({month:0, year:2007, renderCallback:testCallback});
+ * 
+ * @desc Renders a calendar displaying January 2007 into the element with an id of calendar-me. Every Thursday in the current month has a class of "thursday" applied to it, is clickable and shows an alert when clicked. Every Friday on the calendar has the number inside replaced with text.
+ **/
+		renderCalendar  :   function(s)
+		{
+			var dc = function(a)
+			{
+				return document.createElement(a);
+			};
+
+			s = $.extend({}, $.fn.datePicker.defaults, s);
+			
+			if (s.showHeader != $.dpConst.SHOW_HEADER_NONE) {
+				var headRow = $(dc('tr'));
+				for (var i=Date.firstDayOfWeek; i<Date.firstDayOfWeek+7; i++) {
+					var weekday = i%7;
+					var day = Date.dayNames[weekday];
+					headRow.append(
+						jQuery(dc('th')).attr({'scope':'col', 'abbr':day, 'title':day, 'class':(weekday == 0 || weekday == 6 ? 'weekend' : 'weekday')}).html(s.showHeader == $.dpConst.SHOW_HEADER_SHORT ? day.substr(0, 1) : day)
+					);
+				}
+			};
+			
+			var calendarTable = $(dc('table'))
+									.attr(
+										{
+											'cellspacing':2
+										}
+									)
+									.addClass('jCalendar')
+									.append(
+										(s.showHeader != $.dpConst.SHOW_HEADER_NONE ? 
+											$(dc('thead'))
+												.append(headRow)
+											:
+											dc('thead')
+										)
+									);
+			var tbody = $(dc('tbody'));
+			
+			var today = (new Date()).zeroTime();
+			today.setHours(12);
+			
+			var month = s.month == undefined ? today.getMonth() : s.month;
+			var year = s.year || today.getFullYear();
+			
+			var currentDate = (new Date(year, month, 1, 12, 0, 0));
+			
+			
+			var firstDayOffset = Date.firstDayOfWeek - currentDate.getDay() + 1;
+			if (firstDayOffset > 1) firstDayOffset -= 7;
+			var weeksToDraw = Math.ceil(( (-1*firstDayOffset+1) + currentDate.getDaysInMonth() ) /7);
+			currentDate.addDays(firstDayOffset-1);
+			
+			var doHover = function(firstDayInBounds)
+			{
+				return function()
+				{
+					if (s.hoverClass) {
+						var $this = $(this);
+						if (!s.selectWeek) {
+							$this.addClass(s.hoverClass);
+						} else if (firstDayInBounds && !$this.is('.disabled')) {
+							$this.parent().addClass('activeWeekHover');
+						}
+					}
+				}
+			};
+			var unHover = function()
+			{
+				if (s.hoverClass) {
+					var $this = $(this);
+					$this.removeClass(s.hoverClass);
+					$this.parent().removeClass('activeWeekHover');
+				}
+			};
+
+			var w = 0;
+			while (w++<weeksToDraw) {
+				var r = jQuery(dc('tr'));
+				var firstDayInBounds = s.dpController ? currentDate > s.dpController.startDate : false;
+				for (var i=0; i<7; i++) {
+					var thisMonth = currentDate.getMonth() == month;
+					var d = $(dc('td'))
+								.text(currentDate.getDate() + '')
+								.addClass((thisMonth ? 'current-month ' : 'other-month ') +
+													(currentDate.isWeekend() ? 'weekend ' : 'weekday ') +
+													(thisMonth && currentDate.getTime() == today.getTime() ? 'today ' : '')
+								)
+								.data('datePickerDate', currentDate.asString())
+								.hover(doHover(firstDayInBounds), unHover)
+							;
+					r.append(d);
+					if (s.renderCallback) {
+						s.renderCallback(d, currentDate, month, year);
+					}
+					// addDays(1) fails in some locales due to daylight savings. See issue 39.
+					//currentDate.addDays(1);
+					// set the time to midday to avoid any weird timezone issues??
+					currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate()+1, 12, 0, 0);
+				}
+				tbody.append(r);
+			}
+			calendarTable.append(tbody);
+			
+			return this.each(
+				function()
+				{
+					$(this).empty().append(calendarTable);
+				}
+			);
+		},
+/**
+ * Create a datePicker associated with each of the matched elements.
+ *
+ * The matched element will receive a few custom events with the following signatures:
+ *
+ * dateSelected(event, date, $td, status)
+ * Triggered when a date is selected. event is a reference to the event, date is the Date selected, $td is a jquery object wrapped around the TD that was clicked on and status is whether the date was selected (true) or deselected (false)
+ * 
+ * dpClosed(event, selected)
+ * Triggered when the date picker is closed. event is a reference to the event and selected is an Array containing Date objects.
+ *
+ * dpMonthChanged(event, displayedMonth, displayedYear)
+ * Triggered when the month of the popped up calendar is changed. event is a reference to the event, displayedMonth is the number of the month now displayed (zero based) and displayedYear is the year of the month.
+ *
+ * dpDisplayed(event, $datePickerDiv)
+ * Triggered when the date picker is created. $datePickerDiv is the div containing the date picker. Use this event to add custom content/ listeners to the popped up date picker.
+ *
+ * @param Object s (optional) Customize your date pickers.
+ * @option Number month The month to render when the date picker is opened (NOTE that months are zero based). Default is today's month.
+ * @option Number year The year to render when the date picker is opened. Default is today's year.
+ * @option String|Date startDate The first date date can be selected.
+ * @option String|Date endDate The last date that can be selected.
+ * @option Boolean inline Whether to create the datePicker as inline (e.g. always on the page) or as a model popup. Default is false (== modal popup)
+ * @option Boolean createButton Whether to create a .dp-choose-date anchor directly after the matched element which when clicked will trigger the showing of the date picker. Default is true.
+ * @option Boolean showYearNavigation Whether to display buttons which allow the user to navigate through the months a year at a time. Default is true.
+ * @option Boolean closeOnSelect Whether to close the date picker when a date is selected. Default is true.
+ * @option Boolean displayClose Whether to create a "Close" button within the date picker popup. Default is false.
+ * @option Boolean selectMultiple Whether a user should be able to select multiple dates with this date picker. Default is false.
+ * @option Number numSelectable The maximum number of dates that can be selected where selectMultiple is true. Default is a very high number.
+ * @option Boolean clickInput If the matched element is an input type="text" and this option is true then clicking on the input will cause the date picker to appear.
+ * @option Boolean rememberViewedMonth Whether the datePicker should remember the last viewed month and open on it. If false then the date picker will always open with the month for the first selected date visible.
+ * @option Boolean selectWeek Whether to select a complete week at a time...
+ * @option Number verticalPosition The vertical alignment of the popped up date picker to the matched element. One of $.dpConst.POS_TOP and $.dpConst.POS_BOTTOM. Default is $.dpConst.POS_TOP.
+ * @option Number horizontalPosition The horizontal alignment of the popped up date picker to the matched element. One of $.dpConst.POS_LEFT and $.dpConst.POS_RIGHT.
+ * @option Number verticalOffset The number of pixels offset from the defined verticalPosition of this date picker that it should pop up in. Default in 0.
+ * @option Number horizontalOffset The number of pixels offset from the defined horizontalPosition of this date picker that it should pop up in. Default in 0.
+ * @option (Function|Array) renderCallback A reference to a function (or an array of separate functions) that is called as each cell is rendered and which can add classes and event listeners to the created nodes. Each callback function will receive four arguments; a jquery object wrapping the created TD, a Date object containing the date this TD represents, a number giving the currently rendered month and a number giving the currently rendered year. Default is no callback.
+ * @option String hoverClass The class to attach to each cell when you hover over it (to allow you to use hover effects in IE6 which doesn't support the :hover pseudo-class on elements other than links). Default is dp-hover. Pass false if you don't want a hover class.
+ * @option String autoFocusNextInput Whether focus should be passed onto the next input in the form (true) or remain on this input (false) when a date is selected and the calendar closes
+ * @type jQuery
+ * @name datePicker
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('input.date-picker').datePicker();
+ * @desc Creates a date picker button next to all matched input elements. When the button is clicked on the value of the selected date will be placed in the corresponding input (formatted according to Date.format).
+ *
+ * @example demo/index.html
+ * @desc See the projects homepage for many more complex examples...
+ **/
+		datePicker : function(s)
+		{			
+			if (!$.event._dpCache) $.event._dpCache = [];
+			
+			// initialise the date picker controller with the relevant settings...
+			s = $.extend({}, $.fn.datePicker.defaults, s);
+			
+			return this.each(
+				function()
+				{
+					var $this = $(this);
+					var alreadyExists = true;
+					
+					if (!this._dpId) {
+						this._dpId = $.guid++;
+						$.event._dpCache[this._dpId] = new DatePicker(this);
+						alreadyExists = false;
+					}
+					
+					if (s.inline) {
+						s.createButton = false;
+						s.displayClose = false;
+						s.closeOnSelect = false;
+						$this.empty();
+					}
+					
+					var controller = $.event._dpCache[this._dpId];
+					
+					controller.init(s);
+					
+					if (!alreadyExists && s.createButton) {
+						// create it!
+						controller.button = $('<a href="#" class="dp-choose-date" title="' + $.dpText.TEXT_CHOOSE_DATE + '">' + $.dpText.TEXT_CHOOSE_DATE + '</a>')
+								.bind(
+									'click',
+									function()
+									{
+										$this.dpDisplay(this);
+										this.blur();
+										return false;
+									}
+								);
+						$this.after(controller.button);
+					}
+					
+					if (!alreadyExists && $this.is(':text')) {
+						$this
+							.bind(
+								'dateSelected',
+								function(e, selectedDate, $td)
+								{
+									this.value = selectedDate.asString();
+								}
+							).bind(
+								'change',
+								function()
+								{
+									if (this.value == '') {
+										controller.clearSelected();
+									} else {
+										var d = Date.fromString(this.value);
+										if (d) {
+											controller.setSelected(d, true, true);
+										}
+									}
+								}
+							);
+						if (s.clickInput) {
+							$this.bind(
+								'click',
+								function()
+								{
+									// The change event doesn't happen until the input loses focus so we need to manually trigger it...
+									$this.trigger('change');
+									$this.dpDisplay();
+								}
+							);
+						}
+						var d = Date.fromString(this.value);
+						if (this.value != '' && d) {
+							controller.setSelected(d, true, true);
+						}
+					}
+					
+					$this.addClass('dp-applied');
+					
+				}
+			)
+		},
+/**
+ * Disables or enables this date picker
+ *
+ * @param Boolean s Whether to disable (true) or enable (false) this datePicker
+ * @type jQuery
+ * @name dpSetDisabled
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('.date-picker').datePicker();
+ * $('.date-picker').dpSetDisabled(true);
+ * @desc Prevents this date picker from displaying and adds a class of dp-disabled to it (and it's associated button if it has one) for styling purposes. If the matched element is an input field then it will also set the disabled attribute to stop people directly editing the field.
+ **/
+		dpSetDisabled : function(s)
+		{
+			return _w.call(this, 'setDisabled', s);
+		},
+/**
+ * Updates the first selectable date for any date pickers on any matched elements.
+ *
+ * @param String|Date d A Date object or string representing the first selectable date (formatted according to Date.format).
+ * @type jQuery
+ * @name dpSetStartDate
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('.date-picker').datePicker();
+ * $('.date-picker').dpSetStartDate('01/01/2000');
+ * @desc Creates a date picker associated with all elements with a class of "date-picker" then sets the first selectable date for each of these to the first day of the millenium.
+ **/
+		dpSetStartDate : function(d)
+		{
+			return _w.call(this, 'setStartDate', d);
+		},
+/**
+ * Updates the last selectable date for any date pickers on any matched elements.
+ *
+ * @param String|Date d A Date object or string representing the last selectable date (formatted according to Date.format).
+ * @type jQuery
+ * @name dpSetEndDate
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('.date-picker').datePicker();
+ * $('.date-picker').dpSetEndDate('01/01/2010');
+ * @desc Creates a date picker associated with all elements with a class of "date-picker" then sets the last selectable date for each of these to the first Janurary 2010.
+ **/
+		dpSetEndDate : function(d)
+		{
+			return _w.call(this, 'setEndDate', d);
+		},
+/**
+ * Gets a list of Dates currently selected by this datePicker. This will be an empty array if no dates are currently selected or NULL if there is no datePicker associated with the matched element.
+ *
+ * @type Array
+ * @name dpGetSelected
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('.date-picker').datePicker();
+ * alert($('.date-picker').dpGetSelected());
+ * @desc Will alert an empty array (as nothing is selected yet)
+ **/
+		dpGetSelected : function()
+		{
+			var c = _getController(this[0]);
+			if (c) {
+				return c.getSelected();
+			}
+			return null;
+		},
+/**
+ * Selects or deselects a date on any matched element's date pickers. Deselcting is only useful on date pickers where selectMultiple==true. Selecting will only work if the passed date is within the startDate and endDate boundries for a given date picker.
+ *
+ * @param String|Date d A Date object or string representing the date you want to select (formatted according to Date.format).
+ * @param Boolean v Whether you want to select (true) or deselect (false) this date. Optional - default = true.
+ * @param Boolean m Whether you want the date picker to open up on the month of this date when it is next opened. Optional - default = true.
+ * @param Boolean e Whether you want the date picker to dispatch events related to this change of selection. Optional - default = true.
+ * @type jQuery
+ * @name dpSetSelected
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('.date-picker').datePicker();
+ * $('.date-picker').dpSetSelected('01/01/2010');
+ * @desc Creates a date picker associated with all elements with a class of "date-picker" then sets the selected date on these date pickers to the first Janurary 2010. When the date picker is next opened it will display Janurary 2010.
+ **/
+		dpSetSelected : function(d, v, m, e)
+		{
+			if (v == undefined) v=true;
+			if (m == undefined) m=true;
+			if (e == undefined) e=true;
+			return _w.call(this, 'setSelected', Date.fromString(d), v, m, e);
+		},
+/**
+ * Sets the month that will be displayed when the date picker is next opened. If the passed month is before startDate then the month containing startDate will be displayed instead. If the passed month is after endDate then the month containing the endDate will be displayed instead.
+ *
+ * @param Number m The month you want the date picker to display. Optional - defaults to the currently displayed month.
+ * @param Number y The year you want the date picker to display. Optional - defaults to the currently displayed year.
+ * @type jQuery
+ * @name dpSetDisplayedMonth
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('.date-picker').datePicker();
+ * $('.date-picker').dpSetDisplayedMonth(10, 2008);
+ * @desc Creates a date picker associated with all elements with a class of "date-picker" then sets the selected date on these date pickers to the first Janurary 2010. When the date picker is next opened it will display Janurary 2010.
+ **/
+		dpSetDisplayedMonth : function(m, y)
+		{
+			return _w.call(this, 'setDisplayedMonth', Number(m), Number(y), true);
+		},
+/**
+ * Displays the date picker associated with the matched elements. Since only one date picker can be displayed at once then the date picker associated with the last matched element will be the one that is displayed.
+ *
+ * @param HTMLElement e An element that you want the date picker to pop up relative in position to. Optional - default behaviour is to pop up next to the element associated with this date picker.
+ * @type jQuery
+ * @name dpDisplay
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('#date-picker').datePicker();
+ * $('#date-picker').dpDisplay();
+ * @desc Creates a date picker associated with the element with an id of date-picker and then causes it to pop up.
+ **/
+		dpDisplay : function(e)
+		{
+			return _w.call(this, 'display', e);
+		},
+/**
+ * Sets a function or array of functions that is called when each TD of the date picker popup is rendered to the page
+ *
+ * @param (Function|Array) a A function or an array of functions that are called when each td is rendered. Each function will receive four arguments; a jquery object wrapping the created TD, a Date object containing the date this TD represents, a number giving the currently rendered month and a number giving the currently rendered year.
+ * @type jQuery
+ * @name dpSetRenderCallback
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('#date-picker').datePicker();
+ * $('#date-picker').dpSetRenderCallback(function($td, thisDate, month, year)
+ * {
+ * 	// do stuff as each td is rendered dependant on the date in the td and the displayed month and year
+ * });
+ * @desc Creates a date picker associated with the element with an id of date-picker and then creates a function which is called as each td is rendered when this date picker is displayed.
+ **/
+		dpSetRenderCallback : function(a)
+		{
+			return _w.call(this, 'setRenderCallback', a);
+		},
+/**
+ * Sets the position that the datePicker will pop up (relative to it's associated element)
+ *
+ * @param Number v The vertical alignment of the created date picker to it's associated element. Possible values are $.dpConst.POS_TOP and $.dpConst.POS_BOTTOM
+ * @param Number h The horizontal alignment of the created date picker to it's associated element. Possible values are $.dpConst.POS_LEFT and $.dpConst.POS_RIGHT
+ * @type jQuery
+ * @name dpSetPosition
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('#date-picker').datePicker();
+ * $('#date-picker').dpSetPosition($.dpConst.POS_BOTTOM, $.dpConst.POS_RIGHT);
+ * @desc Creates a date picker associated with the element with an id of date-picker and makes it so that when this date picker pops up it will be bottom and right aligned to the #date-picker element.
+ **/
+		dpSetPosition : function(v, h)
+		{
+			return _w.call(this, 'setPosition', v, h);
+		},
+/**
+ * Sets the offset that the popped up date picker will have from it's default position relative to it's associated element (as set by dpSetPosition)
+ *
+ * @param Number v The vertical offset of the created date picker.
+ * @param Number h The horizontal offset of the created date picker.
+ * @type jQuery
+ * @name dpSetOffset
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('#date-picker').datePicker();
+ * $('#date-picker').dpSetOffset(-20, 200);
+ * @desc Creates a date picker associated with the element with an id of date-picker and makes it so that when this date picker pops up it will be 20 pixels above and 200 pixels to the right of it's default position.
+ **/
+		dpSetOffset : function(v, h)
+		{
+			return _w.call(this, 'setOffset', v, h);
+		},
+/**
+ * Closes the open date picker associated with this element.
+ *
+ * @type jQuery
+ * @name dpClose
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ * @example $('.date-pick')
+ *		.datePicker()
+ *		.bind(
+ *			'focus',
+ *			function()
+ *			{
+ *				$(this).dpDisplay();
+ *			}
+ *		).bind(
+ *			'blur',
+ *			function()
+ *			{
+ *				$(this).dpClose();
+ *			}
+ *		);
+ **/
+		dpClose : function()
+		{
+			return _w.call(this, '_closeCalendar', false, this[0]);
+		},
+/**
+ * Rerenders the date picker's current month (for use with inline calendars and renderCallbacks).
+ *
+ * @type jQuery
+ * @name dpRerenderCalendar
+ * @cat plugins/datePicker
+ * @author Kelvin Luck (http://www.kelvinluck.com/)
+ *
+ **/
+		dpRerenderCalendar : function()
+		{
+			return _w.call(this, '_rerenderCalendar');
+		},
+		// private function called on unload to clean up any expandos etc and prevent memory links...
+		_dpDestroy : function()
+		{
+			// TODO - implement this?
+		}
+	});
+	
+	// private internal function to cut down on the amount of code needed where we forward
+	// dp* methods on the jQuery object on to the relevant DatePicker controllers...
+	var _w = function(f, a1, a2, a3, a4)
+	{
+		return this.each(
+			function()
+			{
+				var c = _getController(this);
+				if (c) {
+					c[f](a1, a2, a3, a4);
+				}
+			}
+		);
+	};
+	
+	function DatePicker(ele)
+	{
+		this.ele = ele;
+		
+		// initial values...
+		this.displayedMonth		=	null;
+		this.displayedYear		=	null;
+		this.startDate			=	null;
+		this.endDate			=	null;
+		this.showYearNavigation	=	null;
+		this.closeOnSelect		=	null;
+		this.displayClose		=	null;
+		this.rememberViewedMonth=	null;
+		this.selectMultiple		=	null;
+		this.numSelectable		=	null;
+		this.numSelected		=	null;
+		this.verticalPosition	=	null;
+		this.horizontalPosition	=	null;
+		this.verticalOffset		=	null;
+		this.horizontalOffset	=	null;
+		this.button				=	null;
+		this.renderCallback		=	[];
+		this.selectedDates		=	{};
+		this.inline				=	null;
+		this.context			=	'#dp-popup';
+		this.settings			=	{};
+	};
+	$.extend(
+		DatePicker.prototype,
+		{	
+			init : function(s)
+			{
+				this.setStartDate(s.startDate);
+				this.setEndDate(s.endDate);
+				this.setDisplayedMonth(Number(s.month), Number(s.year));
+				this.setRenderCallback(s.renderCallback);
+				this.showYearNavigation = s.showYearNavigation;
+				this.closeOnSelect = s.closeOnSelect;
+				this.displayClose = s.displayClose;
+				this.rememberViewedMonth =	s.rememberViewedMonth;
+				this.selectMultiple = s.selectMultiple;
+				this.numSelectable = s.selectMultiple ? s.numSelectable : 1;
+				this.numSelected = 0;
+				this.verticalPosition = s.verticalPosition;
+				this.horizontalPosition = s.horizontalPosition;
+				this.hoverClass = s.hoverClass;
+				this.setOffset(s.verticalOffset, s.horizontalOffset);
+				this.inline = s.inline;
+				this.settings = s;
+				if (this.inline) {
+					this.context = this.ele;
+					this.display();
+				}
+			},
+			setStartDate : function(d)
+			{
+				if (d) {
+					if (d instanceof Date) {
+						this.startDate = d;
+					} else {
+						this.startDate = Date.fromString(d);
+					}
+				}
+				if (!this.startDate) {
+					this.startDate = (new Date()).zeroTime();
+				}
+				this.setDisplayedMonth(this.displayedMonth, this.displayedYear);
+			},
+			setEndDate : function(d)
+			{
+				if (d) {
+					if (d instanceof Date) {
+						this.endDate = d;
+					} else {
+						this.endDate = Date.fromString(d);
+					}
+				}
+				if (!this.endDate) {
+					this.endDate = (new Date('12/31/2999')); // using the JS Date.parse function which expects mm/dd/yyyy
+				}
+				if (this.endDate.getTime() < this.startDate.getTime()) {
+					this.endDate = this.startDate;
+				}
+				this.setDisplayedMonth(this.displayedMonth, this.displayedYear);
+			},
+			setPosition : function(v, h)
+			{
+				this.verticalPosition = v;
+				this.horizontalPosition = h;
+			},
+			setOffset : function(v, h)
+			{
+				this.verticalOffset = parseInt(v) || 0;
+				this.horizontalOffset = parseInt(h) || 0;
+			},
+			setDisabled : function(s)
+			{
+				$e = $(this.ele);
+				$e[s ? 'addClass' : 'removeClass']('dp-disabled');
+				if (this.button) {
+					$but = $(this.button);
+					$but[s ? 'addClass' : 'removeClass']('dp-disabled');
+					$but.attr('title', s ? '' : $.dpText.TEXT_CHOOSE_DATE);
+				}
+				if ($e.is(':text')) {
+					$e.attr('disabled', s ? 'disabled' : '');
+				}
+			},
+			setDisplayedMonth : function(m, y, rerender)
+			{
+				if (this.startDate == undefined || this.endDate == undefined) {
+					return;
+				}
+				var s = new Date(this.startDate.getTime());
+				s.setDate(1);
+				var e = new Date(this.endDate.getTime());
+				e.setDate(1);
+				
+				var t;
+				if ((!m && !y) || (isNaN(m) && isNaN(y))) {
+					// no month or year passed - default to current month
+					t = new Date().zeroTime();
+					t.setDate(1);
+				} else if (isNaN(m)) {
+					// just year passed in - presume we want the displayedMonth
+					t = new Date(y, this.displayedMonth, 1);
+				} else if (isNaN(y)) {
+					// just month passed in - presume we want the displayedYear
+					t = new Date(this.displayedYear, m, 1);
+				} else {
+					// year and month passed in - that's the date we want!
+					t = new Date(y, m, 1)
+				}
+				// check if the desired date is within the range of our defined startDate and endDate
+				if (t.getTime() < s.getTime()) {
+					t = s;
+				} else if (t.getTime() > e.getTime()) {
+					t = e;
+				}
+				var oldMonth = this.displayedMonth;
+				var oldYear = this.displayedYear;
+				this.displayedMonth = t.getMonth();
+				this.displayedYear = t.getFullYear();
+
+				if (rerender && (this.displayedMonth != oldMonth || this.displayedYear != oldYear))
+				{
+					this._rerenderCalendar();
+					$(this.ele).trigger('dpMonthChanged', [this.displayedMonth, this.displayedYear]);
+				}
+			},
+			setSelected : function(d, v, moveToMonth, dispatchEvents)
+			{
+				if (d < this.startDate || d.zeroTime() > this.endDate.zeroTime()) {
+					// Don't allow people to select dates outside range...
+					return;
+				}
+				var s = this.settings;
+				if (s.selectWeek)
+				{
+					d = d.addDays(- (d.getDay() - Date.firstDayOfWeek + 7) % 7);
+					if (d < this.startDate) // The first day of this week is before the start date so is unselectable...
+					{
+						return;
+					}
+				}
+				if (v == this.isSelected(d)) // this date is already un/selected
+				{
+					return;
+				}
+				if (this.selectMultiple == false) {
+					this.clearSelected();
+				} else if (v && this.numSelected == this.numSelectable) {
+					// can't select any more dates...
+					return;
+				}
+				if (moveToMonth && (this.displayedMonth != d.getMonth() || this.displayedYear != d.getFullYear())) {
+					this.setDisplayedMonth(d.getMonth(), d.getFullYear(), true);
+				}
+				this.selectedDates[d.asString()] = v;
+				this.numSelected += v ? 1 : -1;
+				var selectorString = 'td.' + (d.getMonth() == this.displayedMonth ? 'current-month' : 'other-month');
+				var $td;
+				$(selectorString, this.context).each(
+					function()
+					{
+						if ($(this).data('datePickerDate') == d.asString()) {
+							$td = $(this);
+							if (s.selectWeek)
+							{
+								$td.parent()[v ? 'addClass' : 'removeClass']('selectedWeek');
+							}
+							$td[v ? 'addClass' : 'removeClass']('selected'); 
+						}
+					}
+				);
+				$('td', this.context).not('.selected')[this.selectMultiple &&  this.numSelected == this.numSelectable ? 'addClass' : 'removeClass']('unselectable');
+				
+				if (dispatchEvents)
+				{
+					var s = this.isSelected(d);
+					$e = $(this.ele);
+					var dClone = Date.fromString(d.asString());
+					$e.trigger('dateSelected', [dClone, $td, s]);
+					$e.trigger('change');
+				}
+			},
+			isSelected : function(d)
+			{
+				return this.selectedDates[d.asString()];
+			},
+			getSelected : function()
+			{
+				var r = [];
+				for(var s in this.selectedDates) {
+					if (this.selectedDates[s] == true) {
+						r.push(Date.fromString(s));
+					}
+				}
+				return r;
+			},
+			clearSelected : function()
+			{
+				this.selectedDates = {};
+				this.numSelected = 0;
+				$('td.selected', this.context).removeClass('selected').parent().removeClass('selectedWeek');
+			},
+			display : function(eleAlignTo)
+			{
+				if ($(this.ele).is('.dp-disabled')) return;
+				
+				eleAlignTo = eleAlignTo || this.ele;
+				var c = this;
+				var $ele = $(eleAlignTo);
+				var eleOffset = $ele.offset();
+				
+				var $createIn;
+				var attrs;
+				var attrsCalendarHolder;
+				var cssRules;
+				
+				if (c.inline) {
+					$createIn = $(this.ele);
+					attrs = {
+						'id'		:	'calendar-' + this.ele._dpId,
+						'class'	:	'dp-popup dp-popup-inline'
+					};
+
+					$('.dp-popup', $createIn).remove();
+					cssRules = {
+					};
+				} else {
+					$createIn = $('body');
+					attrs = {
+						'id'		:	'dp-popup',
+						'class'	:	'dp-popup'
+					};
+					cssRules = {
+						'top'	:	eleOffset.top + c.verticalOffset,
+						'left'	:	eleOffset.left + c.horizontalOffset
+					};
+					
+					var _checkMouse = function(e)
+					{
+						var el = e.target;
+						var cal = $('#dp-popup')[0];
+						
+						while (true){
+							if (el == cal) {
+								return true;
+							} else if (el == document) {
+								c._closeCalendar();
+								return false;
+							} else {
+								el = $(el).parent()[0];
+							}
+						}
+					};
+					this._checkMouse = _checkMouse;
+					
+					c._closeCalendar(true);
+					$(document).bind(
+						'keydown.datepicker', 
+						function(event)
+						{
+							if (event.keyCode == 27) {
+								c._closeCalendar();
+							}
+						}
+					);
+				}
+				
+				if (!c.rememberViewedMonth)
+				{
+					var selectedDate = this.getSelected()[0];
+					if (selectedDate) {
+						selectedDate = new Date(selectedDate);
+						this.setDisplayedMonth(selectedDate.getMonth(), selectedDate.getFullYear(), false);
+					}
+				}
+				
+				$createIn
+					.append(
+						$('<div></div>')
+							.attr(attrs)
+							.css(cssRules)
+							.append(
+//								$('<a href="#" class="selecteee">aaa</a>'),
+								$('<h2></h2>'),
+								$('<div class="dp-nav-prev"></div>')
+									.append(
+										$('<a class="dp-nav-prev-year" href="#" title="' + $.dpText.TEXT_PREV_YEAR + '"><<</a>')
+											.bind(
+												'click',
+												function()
+												{
+													return c._displayNewMonth.call(c, this, 0, -1);
+												}
+											),
+										$('<a class="dp-nav-prev-month" href="#" title="' + $.dpText.TEXT_PREV_MONTH + '"><</a>')
+											.bind(
+												'click',
+												function()
+												{
+													return c._displayNewMonth.call(c, this, -1, 0);
+												}
+											)
+									),
+								$('<div class="dp-nav-next"></div>')
+									.append(
+										$('<a class="dp-nav-next-year" href="#" title="' + $.dpText.TEXT_NEXT_YEAR + '">>></a>')
+											.bind(
+												'click',
+												function()
+												{
+													return c._displayNewMonth.call(c, this, 0, 1);
+												}
+											),
+										$('<a class="dp-nav-next-month" href="#" title="' + $.dpText.TEXT_NEXT_MONTH + '">></a>')
+											.bind(
+												'click',
+												function()
+												{
+													return c._displayNewMonth.call(c, this, 1, 0);
+												}
+											)
+									),
+								$('<div class="dp-calendar"></div>')
+							)
+							.bgIframe()
+						);
+					
+				var $pop = this.inline ? $('.dp-popup', this.context) : $('#dp-popup');
+				
+				if (this.showYearNavigation == false) {
+					$('.dp-nav-prev-year, .dp-nav-next-year', c.context).css('display', 'none');
+				}
+				if (this.displayClose) {
+					$pop.append(
+						$('<a href="#" id="dp-close">' + $.dpText.TEXT_CLOSE + '</a>')
+							.bind(
+								'click',
+								function()
+								{
+									c._closeCalendar();
+									return false;
+								}
+							)
+					);
+				}
+				c._renderCalendar();
+
+				$(this.ele).trigger('dpDisplayed', $pop);
+				
+				if (!c.inline) {
+					if (this.verticalPosition == $.dpConst.POS_BOTTOM) {
+						$pop.css('top', eleOffset.top + $ele.height() - $pop.height() + c.verticalOffset);
+					}
+					if (this.horizontalPosition == $.dpConst.POS_RIGHT) {
+						$pop.css('left', eleOffset.left + $ele.width() - $pop.width() + c.horizontalOffset);
+					}
+//					$('.selectee', this.context).focus();
+					$(document).bind('mousedown.datepicker', this._checkMouse);
+				}
+				
+			},
+			setRenderCallback : function(a)
+			{
+				if (a == null) return;
+				if (a && typeof(a) == 'function') {
+					a = [a];
+				}
+				this.renderCallback = this.renderCallback.concat(a);
+			},
+			cellRender : function ($td, thisDate, month, year) {
+				var c = this.dpController;
+				var d = new Date(thisDate.getTime());
+				
+				// add our click handlers to deal with it when the days are clicked...
+				
+				$td.bind(
+					'click',
+					function()
+					{
+						var $this = $(this);
+						if (!$this.is('.disabled')) {
+							c.setSelected(d, !$this.is('.selected') || !c.selectMultiple, false, true);
+							if (c.closeOnSelect) {
+								// Focus the next input in the form…
+								if (c.settings.autoFocusNextInput) {
+									var ele = c.ele;
+									var found = false;
+									$(':input', ele.form).each(
+										function()
+										{
+											if (found) {
+												$(this).focus();
+												return false;
+											}
+											if (this == ele) {
+												found = true;
+											}
+										}
+									);
+								} else {
+									try {
+										c.ele.focus();
+									} catch (e) {}
+								}
+								c._closeCalendar();
+							}
+						}
+					}
+				);
+				if (c.isSelected(d)) {
+					$td.addClass('selected');
+					if (c.settings.selectWeek)
+					{
+						$td.parent().addClass('selectedWeek');
+					}
+				} else  if (c.selectMultiple && c.numSelected == c.numSelectable) {
+					$td.addClass('unselectable');
+				}
+				
+			},
+			_applyRenderCallbacks : function()
+			{
+				var c = this;
+				$('td', this.context).each(
+					function()
+					{
+						for (var i=0; i<c.renderCallback.length; i++) {
+							$td = $(this);
+							c.renderCallback[i].apply(this, [$td, Date.fromString($td.data('datePickerDate')), c.displayedMonth, c.displayedYear]);
+						}
+					}
+				);
+				return;
+			},
+			// ele is the clicked button - only proceed if it doesn't have the class disabled...
+			// m and y are -1, 0 or 1 depending which direction we want to go in...
+			_displayNewMonth : function(ele, m, y) 
+			{
+				if (!$(ele).is('.disabled')) {
+					this.setDisplayedMonth(this.displayedMonth + m, this.displayedYear + y, true);
+				}
+				ele.blur();
+				return false;
+			},
+			_rerenderCalendar : function()
+			{
+				this._clearCalendar();
+				this._renderCalendar();
+			},
+			_renderCalendar : function()
+			{
+				// set the title...
+				$('h2', this.context).html((new Date(this.displayedYear, this.displayedMonth, 1)).asString($.dpText.HEADER_FORMAT));
+				
+				// render the calendar...
+				$('.dp-calendar', this.context).renderCalendar(
+					$.extend(
+						{},
+						this.settings, 
+						{
+							month			: this.displayedMonth,
+							year			: this.displayedYear,
+							renderCallback	: this.cellRender,
+							dpController	: this,
+							hoverClass		: this.hoverClass
+						})
+				);
+				
+				// update the status of the control buttons and disable dates before startDate or after endDate...
+				// TODO: When should the year buttons be disabled? When you can't go forward a whole year from where you are or is that annoying?
+				if (this.displayedYear == this.startDate.getFullYear() && this.displayedMonth == this.startDate.getMonth()) {
+					$('.dp-nav-prev-year', this.context).addClass('disabled');
+					$('.dp-nav-prev-month', this.context).addClass('disabled');
+					$('.dp-calendar td.other-month', this.context).each(
+						function()
+						{
+							var $this = $(this);
+							if (Number($this.text()) > 20) {
+								$this.addClass('disabled');
+							}
+						}
+					);
+					var d = this.startDate.getDate();
+					$('.dp-calendar td.current-month', this.context).each(
+						function()
+						{
+							var $this = $(this);
+							if (Number($this.text()) < d) {
+								$this.addClass('disabled');
+							}
+						}
+					);
+				} else {
+					$('.dp-nav-prev-year', this.context).removeClass('disabled');
+					$('.dp-nav-prev-month', this.context).removeClass('disabled');
+					var d = this.startDate.getDate();
+					if (d > 20) {
+						// check if the startDate is last month as we might need to add some disabled classes...
+						var st = this.startDate.getTime();
+						var sd = new Date(st);
+						sd.addMonths(1);
+						if (this.displayedYear == sd.getFullYear() && this.displayedMonth == sd.getMonth()) {
+							$('.dp-calendar td.other-month', this.context).each(
+								function()
+								{
+									var $this = $(this);
+									if (Date.fromString($this.data('datePickerDate')).getTime() < st) {
+										$this.addClass('disabled');
+									}
+								}
+							);
+						}
+					}
+				}
+				if (this.displayedYear == this.endDate.getFullYear() && this.displayedMonth == this.endDate.getMonth()) {
+					$('.dp-nav-next-year', this.context).addClass('disabled');
+					$('.dp-nav-next-month', this.context).addClass('disabled');
+					$('.dp-calendar td.other-month', this.context).each(
+						function()
+						{
+							var $this = $(this);
+							if (Number($this.text()) < 14) {
+								$this.addClass('disabled');
+							}
+						}
+					);
+					var d = this.endDate.getDate();
+					$('.dp-calendar td.current-month', this.context).each(
+						function()
+						{
+							var $this = $(this);
+							if (Number($this.text()) > d) {
+								$this.addClass('disabled');
+							}
+						}
+					);
+				} else {
+					$('.dp-nav-next-year', this.context).removeClass('disabled');
+					$('.dp-nav-next-month', this.context).removeClass('disabled');
+					var d = this.endDate.getDate();
+					if (d < 13) {
+						// check if the endDate is next month as we might need to add some disabled classes...
+						var ed = new Date(this.endDate.getTime());
+						ed.addMonths(-1);
+						if (this.displayedYear == ed.getFullYear() && this.displayedMonth == ed.getMonth()) {
+							$('.dp-calendar td.other-month', this.context).each(
+								function()
+								{
+									var $this = $(this);
+									var cellDay = Number($this.text());
+									if (cellDay < 13 && cellDay > d) {
+										$this.addClass('disabled');
+									}
+								}
+							);
+						}
+					}
+				}
+				this._applyRenderCallbacks();
+			},
+			_closeCalendar : function(programatic, ele)
+			{
+				if (!ele || ele == this.ele)
+				{
+					$(document).unbind('mousedown.datepicker');
+					$(document).unbind('keydown.datepicker');
+					this._clearCalendar();
+					$('#dp-popup a').unbind();
+					$('#dp-popup').empty().remove();
+					if (!programatic) {
+						$(this.ele).trigger('dpClosed', [this.getSelected()]);
+					}
+				}
+			},
+			// empties the current dp-calendar div and makes sure that all events are unbound
+			// and expandos removed to avoid memory leaks...
+			_clearCalendar : function()
+			{
+				// TODO.
+				$('.dp-calendar td', this.context).unbind();
+				$('.dp-calendar', this.context).empty();
+			}
+		}
+	);
+	
+	// static constants
+	$.dpConst = {
+		SHOW_HEADER_NONE	:	0,
+		SHOW_HEADER_SHORT	:	1,
+		SHOW_HEADER_LONG	:	2,
+		POS_TOP				:	0,
+		POS_BOTTOM			:	1,
+		POS_LEFT			:	0,
+		POS_RIGHT			:	1,
+		DP_INTERNAL_FOCUS	:	'dpInternalFocusTrigger'
+	};
+	// localisable text
+	$.dpText = {
+		TEXT_PREV_YEAR		:	'Previous year',
+		TEXT_PREV_MONTH		:	'Previous month',
+		TEXT_NEXT_YEAR		:	'Next year',
+		TEXT_NEXT_MONTH		:	'Next month',
+		TEXT_CLOSE			:	'Close',
+		TEXT_CHOOSE_DATE	:	'Choose date',
+		HEADER_FORMAT		:	'mmmm yyyy'
+	};
+	// version
+	$.dpVersion = '$Id$';
+
+	$.fn.datePicker.defaults = {
+		month				: undefined,
+		year				: undefined,
+		showHeader			: $.dpConst.SHOW_HEADER_SHORT,
+		startDate			: undefined,
+		endDate				: undefined,
+		inline				: false,
+		renderCallback		: null,
+		createButton		: true,
+		showYearNavigation	: true,
+		closeOnSelect		: true,
+		displayClose		: false,
+		selectMultiple		: false,
+		numSelectable		: Number.MAX_VALUE,
+		clickInput			: false,
+		rememberViewedMonth	: true,
+		selectWeek			: false,
+		verticalPosition	: $.dpConst.POS_TOP,
+		horizontalPosition	: $.dpConst.POS_LEFT,
+		verticalOffset		: 0,
+		horizontalOffset	: 0,
+		hoverClass			: 'dp-hover',
+		autoFocusNextInput  : false
+	};
+
+	function _getController(ele)
+	{
+		if (ele._dpId) return $.event._dpCache[ele._dpId];
+		return false;
+	};
+	
+	// make it so that no error is thrown if bgIframe plugin isn't included (allows you to use conditional
+	// comments to only include bgIframe where it is needed in IE without breaking this plugin).
+	if ($.fn.bgIframe == undefined) {
+		$.fn.bgIframe = function() {return this; };
+	};
+
+
+	// clean-up
+	$(window)
+		.bind('unload', function() {
+			var els = $.event._dpCache || [];
+			for (var i in els) {
+				$(els[i].ele)._dpDestroy();
+			}
+		});
+		
+	
+})(jQuery);
diff --git a/media/js/jquery.form.js b/media/js/jquery.form.js
new file mode 100644
index 0000000..2407e6c
--- /dev/null
+++ b/media/js/jquery.form.js
@@ -0,0 +1,601 @@
+/*
+ * jQuery Form Plugin
+ * version: 2.12 (06/07/2008)
+ * @requires jQuery v1.2.2 or later
+ *
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id$
+ */
+(function($) {
+
+/*
+    Usage Note:  
+    -----------
+    Do not use both ajaxSubmit and ajaxForm on the same form.  These
+    functions are intended to be exclusive.  Use ajaxSubmit if you want
+    to bind your own submit handler to the form.  For example,
+
+    $(document).ready(function() {
+        $('#myForm').bind('submit', function() {
+            $(this).ajaxSubmit({
+                target: '#output'
+            });
+            return false; // <-- important!
+        });
+    });
+
+    Use ajaxForm when you want the plugin to manage all the event binding
+    for you.  For example,
+
+    $(document).ready(function() {
+        $('#myForm').ajaxForm({
+            target: '#output'
+        });
+    });
+        
+    When using ajaxForm, the ajaxSubmit function will be invoked for you
+    at the appropriate time.  
+*/
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting 
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+    // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+    if (!this.length) {
+        log('ajaxSubmit: skipping submit process - no element selected');
+        return this;
+    }
+
+    if (typeof options == 'function')
+        options = { success: options };
+
+    options = $.extend({
+        url:  this.attr('action') || window.location.toString(),
+        type: this.attr('method') || 'GET'
+    }, options || {});
+
+    // hook for manipulating the form data before it is extracted;
+    // convenient for use with rich editors like tinyMCE or FCKEditor
+    var veto = {};
+    this.trigger('form-pre-serialize', [this, options, veto]);
+    if (veto.veto) {
+        log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
+        return this;
+   }
+
+    var a = this.formToArray(options.semantic);
+    if (options.data) {
+        options.extraData = options.data;
+        for (var n in options.data)
+            a.push( { name: n, value: options.data[n] } );
+    }
+
+    // give pre-submit callback an opportunity to abort the submit
+    if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
+        log('ajaxSubmit: submit aborted via beforeSubmit callback');
+        return this;
+    }    
+
+    // fire vetoable 'validate' event
+    this.trigger('form-submit-validate', [a, this, options, veto]);
+    if (veto.veto) {
+        log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
+        return this;
+    }    
+
+    var q = $.param(a);
+
+    if (options.type.toUpperCase() == 'GET') {
+        options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
+        options.data = null;  // data is null for 'get'
+    }
+    else
+        options.data = q; // data is the query string for 'post'
+
+    var $form = this, callbacks = [];
+    if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
+    if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
+
+    // perform a load on the target only if dataType is not provided
+    if (!options.dataType && options.target) {
+        var oldSuccess = options.success || function(){};
+        callbacks.push(function(data) {
+            $(options.target).html(data).each(oldSuccess, arguments);
+        });
+    }
+    else if (options.success)
+        callbacks.push(options.success);
+
+    options.success = function(data, status) {
+        for (var i=0, max=callbacks.length; i < max; i++)
+            callbacks[i](data, status, $form);
+    };
+
+    // are there files to upload?
+    var files = $('input:file', this).fieldValue();
+    var found = false;
+    for (var j=0; j < files.length; j++)
+        if (files[j])
+            found = true;
+
+    // options.iframe allows user to force iframe mode
+   if (options.iframe || found) { 
+       // hack to fix Safari hang (thanks to Tim Molendijk for this)
+       // see:  http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+       if ($.browser.safari && options.closeKeepAlive)
+           $.get(options.closeKeepAlive, fileUpload);
+       else
+           fileUpload();
+       }
+   else
+       $.ajax(options);
+
+    // fire 'notify' event
+    this.trigger('form-submit-notify', [this, options]);
+    return this;
+
+
+    // private function for handling file uploads (hat tip to YAHOO!)
+    function fileUpload() {
+        var form = $form[0];
+        
+        if ($(':input[name=submit]', form).length) {
+            alert('Error: Form elements must not be named "submit".');
+            return;
+        }
+        
+        var opts = $.extend({}, $.ajaxSettings, options);
+
+        var id = 'jqFormIO' + (new Date().getTime());
+        var $io = $('<iframe id="' + id + '" name="' + id + '" />');
+        var io = $io[0];
+
+        if ($.browser.msie || $.browser.opera) 
+            io.src = 'javascript:false;document.write("");';
+        $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+
+        var xhr = { // mock object
+            responseText: null,
+            responseXML: null,
+            status: 0,
+            statusText: 'n/a',
+            getAllResponseHeaders: function() {},
+            getResponseHeader: function() {},
+            setRequestHeader: function() {}
+        };
+
+        var g = opts.global;
+        // trigger ajax global events so that activity/block indicators work like normal
+        if (g && ! $.active++) $.event.trigger("ajaxStart");
+        if (g) $.event.trigger("ajaxSend", [xhr, opts]);
+
+        var cbInvoked = 0;
+        var timedOut = 0;
+
+        // add submitting element to data if we know it
+        var sub = form.clk;
+        if (sub) {
+            var n = sub.name;
+            if (n && !sub.disabled) {
+                options.extraData = options.extraData || {};
+                options.extraData[n] = sub.value;
+                if (sub.type == "image") {
+                    options.extraData[name+'.x'] = form.clk_x;
+                    options.extraData[name+'.y'] = form.clk_y;
+                }
+            }
+        }
+        
+        // take a breath so that pending repaints get some cpu time before the upload starts
+        setTimeout(function() {
+            // make sure form attrs are set
+            var t = $form.attr('target'), a = $form.attr('action');
+            $form.attr({
+                target:   id,
+                encoding: 'multipart/form-data',
+                enctype:  'multipart/form-data',
+                method:   'POST',
+                action:   opts.url
+            });
+
+            // support timout
+            if (opts.timeout)
+                setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
+
+            // add "extra" data to form if provided in options
+            var extraInputs = [];
+            try {
+                if (options.extraData)
+                    for (var n in options.extraData)
+                        extraInputs.push(
+                            $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
+                                .appendTo(form)[0]);
+            
+                // add iframe to doc and submit the form
+                $io.appendTo('body');
+                io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
+                form.submit();
+            }
+            finally {
+                // reset attrs and remove "extra" input elements
+                $form.attr('action', a);
+                t ? $form.attr('target', t) : $form.removeAttr('target');
+                $(extraInputs).remove();
+            }
+        }, 10);
+
+        function cb() {
+            if (cbInvoked++) return;
+            
+            io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
+
+            var operaHack = 0;
+            var ok = true;
+            try {
+                if (timedOut) throw 'timeout';
+                // extract the server response from the iframe
+                var data, doc;
+
+                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
+                
+                if (doc.body == null && !operaHack && $.browser.opera) {
+                    // In Opera 9.2.x the iframe DOM is not always traversable when
+                    // the onload callback fires so we give Opera 100ms to right itself
+                    operaHack = 1;
+                    cbInvoked--;
+                    setTimeout(cb, 100);
+                    return;
+                }
+                
+                xhr.responseText = doc.body ? doc.body.innerHTML : null;
+                xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+                xhr.getResponseHeader = function(header){
+                    var headers = {'content-type': opts.dataType};
+                    return headers[header];
+                };
+
+                if (opts.dataType == 'json' || opts.dataType == 'script') {
+                    var ta = doc.getElementsByTagName('textarea')[0];
+                    xhr.responseText = ta ? ta.value : xhr.responseText;
+                }
+                else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
+                    xhr.responseXML = toXml(xhr.responseText);
+                }
+                data = $.httpData(xhr, opts.dataType);
+            }
+            catch(e){
+                ok = false;
+                $.handleError(opts, xhr, 'error', e);
+            }
+
+            // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+            if (ok) {
+                opts.success(data, 'success');
+                if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
+            }
+            if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
+            if (g && ! --$.active) $.event.trigger("ajaxStop");
+            if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
+
+            // clean up
+            setTimeout(function() {
+                $io.remove();
+                xhr.responseXML = null;
+            }, 100);
+        };
+
+        function toXml(s, doc) {
+            if (window.ActiveXObject) {
+                doc = new ActiveXObject('Microsoft.XMLDOM');
+                doc.async = 'false';
+                doc.loadXML(s);
+            }
+            else
+                doc = (new DOMParser()).parseFromString(s, 'text/xml');
+            return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
+        };
+    };
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
+ *    is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ *    used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit.  ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */ 
+$.fn.ajaxForm = function(options) {
+    return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
+        $(this).ajaxSubmit(options);
+        return false;
+    }).each(function() {
+        // store options in hash
+        $(":submit,input:image", this).bind('click.form-plugin',function(e) {
+            var $form = this.form;
+            $form.clk = this;
+            if (this.type == 'image') {
+                if (e.offsetX != undefined) {
+                    $form.clk_x = e.offsetX;
+                    $form.clk_y = e.offsetY;
+                } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
+                    var offset = $(this).offset();
+                    $form.clk_x = e.pageX - offset.left;
+                    $form.clk_y = e.pageY - offset.top;
+                } else {
+                    $form.clk_x = e.pageX - this.offsetLeft;
+                    $form.clk_y = e.pageY - this.offsetTop;
+                }
+            }
+            // clear form vars
+            setTimeout(function() { $form.clk = $form.clk_x = $form.clk_y = null; }, 10);
+        });
+    });
+};
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+    this.unbind('submit.form-plugin');
+    return this.each(function() {
+        $(":submit,input:image", this).unbind('click.form-plugin');
+    });
+
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property.  An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic) {
+    var a = [];
+    if (this.length == 0) return a;
+
+    var form = this[0];
+    var els = semantic ? form.getElementsByTagName('*') : form.elements;
+    if (!els) return a;
+    for(var i=0, max=els.length; i < max; i++) {
+        var el = els[i];
+        var n = el.name;
+        if (!n) continue;
+
+        if (semantic && form.clk && el.type == "image") {
+            // handle image inputs on the fly when semantic == true
+            if(!el.disabled && form.clk == el)
+                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+            continue;
+        }
+
+        var v = $.fieldValue(el, true);
+        if (v && v.constructor == Array) {
+            for(var j=0, jmax=v.length; j < jmax; j++)
+                a.push({name: n, value: v[j]});
+        }
+        else if (v !== null && typeof v != 'undefined')
+            a.push({name: n, value: v});
+    }
+
+    if (!semantic && form.clk) {
+        // input type=='image' are not found in elements array! handle them here
+        var inputs = form.getElementsByTagName("input");
+        for(var i=0, max=inputs.length; i < max; i++) {
+            var input = inputs[i];
+            var n = input.name;
+            if(n && !input.disabled && input.type == "image" && form.clk == input)
+                a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+        }
+    }
+    return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+    //hand off to jQuery.param for proper encoding
+    return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+    var a = [];
+    this.each(function() {
+        var n = this.name;
+        if (!n) return;
+        var v = $.fieldValue(this, successful);
+        if (v && v.constructor == Array) {
+            for (var i=0,max=v.length; i < max; i++)
+                a.push({name: n, value: v[i]});
+        }
+        else if (v !== null && typeof v != 'undefined')
+            a.push({name: this.name, value: v});
+    });
+    //hand off to jQuery.param for proper encoding
+    return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set.  For example, consider the following form:
+ *
+ *  <form><fieldset>
+ *      <input name="A" type="text" />
+ *      <input name="A" type="text" />
+ *      <input name="B" type="checkbox" value="B1" />
+ *      <input name="B" type="checkbox" value="B2"/>
+ *      <input name="C" type="radio" value="C1" />
+ *      <input name="C" type="radio" value="C2" />
+ *  </fieldset></form>
+ *
+ *  var v = $(':text').fieldValue();
+ *  // if no values are entered into the text inputs
+ *  v == ['','']
+ *  // if values entered into the text inputs are 'foo' and 'bar'
+ *  v == ['foo','bar']
+ *
+ *  var v = $(':checkbox').fieldValue();
+ *  // if neither checkbox is checked
+ *  v === undefined
+ *  // if both checkboxes are checked
+ *  v == ['B1', 'B2']
+ *
+ *  var v = $(':radio').fieldValue();
+ *  // if neither radio is checked
+ *  v === undefined
+ *  // if first radio is checked
+ *  v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true.  If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array.  If no valid value can be determined the
+ *       array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+    for (var val=[], i=0, max=this.length; i < max; i++) {
+        var el = this[i];
+        var v = $.fieldValue(el, successful);
+        if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
+            continue;
+        v.constructor == Array ? $.merge(val, v) : val.push(v);
+    }
+    return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+    var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+    if (typeof successful == 'undefined') successful = true;
+
+    if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+        (t == 'checkbox' || t == 'radio') && !el.checked ||
+        (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+        tag == 'select' && el.selectedIndex == -1))
+            return null;
+
+    if (tag == 'select') {
+        var index = el.selectedIndex;
+        if (index < 0) return null;
+        var a = [], ops = el.options;
+        var one = (t == 'select-one');
+        var max = (one ? index+1 : ops.length);
+        for(var i=(one ? index : 0); i < max; i++) {
+            var op = ops[i];
+            if (op.selected) {
+                // extra pain for IE...
+                var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
+                if (one) return v;
+                a.push(v);
+            }
+        }
+        return a;
+    }
+    return el.value;
+};
+
+/**
+ * Clears the form data.  Takes the following actions on the form's input fields:
+ *  - input text fields will have their 'value' property set to the empty string
+ *  - select elements will have their 'selectedIndex' property set to -1
+ *  - checkbox and radio inputs will have their 'checked' property set to false
+ *  - inputs of type submit, button, reset, and hidden will *not* be effected
+ *  - button elements will *not* be effected
+ */
+$.fn.clearForm = function() {
+    return this.each(function() {
+        $('input,select,textarea', this).clearFields();
+    });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function() {
+    return this.each(function() {
+        var t = this.type, tag = this.tagName.toLowerCase();
+        if (t == 'text' || t == 'password' || tag == 'textarea')
+            this.value = '';
+        else if (t == 'checkbox' || t == 'radio')
+            this.checked = false;
+        else if (tag == 'select')
+            this.selectedIndex = -1;
+    });
+};
+
+/**
+ * Resets the form data.  Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+    return this.each(function() {
+        // guard against an input with the name of 'reset'
+        // note that IE reports the reset function as an 'object'
+        if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
+            this.reset();
+    });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) { 
+    if (b == undefined) b = true;
+    return this.each(function() { 
+        this.disabled = !b 
+    });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.select = function(select) {
+    if (select == undefined) select = true;
+    return this.each(function() { 
+        var t = this.type;
+        if (t == 'checkbox' || t == 'radio')
+            this.checked = select;
+        else if (this.tagName.toLowerCase() == 'option') {
+            var $sel = $(this).parent('select');
+            if (select && $sel[0] && $sel[0].type == 'select-one') {
+                // deselect all other options
+                $sel.find('option').select(false);
+            }
+            this.selected = select;
+        }
+    });
+};
+
+// helper fn for console logging
+// set $.fn.ajaxSubmit.debug to true to enable debug logging
+function log() {
+    if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
+        window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
+};
+
+})(jQuery);
diff --git a/media/js/jquery.hovertip-1.0.js b/media/js/jquery.hovertip-1.0.js
new file mode 100644
index 0000000..0962e25
--- /dev/null
+++ b/media/js/jquery.hovertip-1.0.js
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2009 Cameron Zemek
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+;(function($) {
+    function Hovertip(elem, conf) {
+        // Create tooltip
+        var tooltip = $('<div></div>')
+            .addClass(conf.className)
+            .html(elem.attr('title'))
+            .insertAfter(elem);
+        tooltip.hide();
+
+        // Remove the browser tooltip
+        elem.removeAttr('title');
+
+        function setPosition(posX, posY) {
+            tooltip.css({ left: posX, top: posY });
+        }
+
+        function updatePosition(event) {
+            var tooltipWidth = tooltip.outerWidth();
+            var tooltipHeight = tooltip.outerHeight();
+            var $window = $(window);
+            var windowWidth = $window.width() + $window.scrollLeft();
+            var windowHeight = $window.height() + $window.scrollTop();
+            var posX = event.pageX + conf.offset[0];
+            var posY = event.pageY + conf.offset[1];
+            if (posX + tooltipWidth > windowWidth) {
+                // Move left
+                posX = windowWidth - tooltipWidth;
+            }
+            if (posY + tooltipHeight > windowHeight) {
+                // Move tooltip to above cursor
+                posY = event.pageY - conf.offset[1] - tooltipHeight;
+            }
+            setPosition(posX, posY);
+        }
+
+        elem.hover(
+            // Show
+            function(event) {
+                updatePosition(event);
+                conf.show(tooltip);
+            },
+            // Hide
+            function() {
+                conf.hide(tooltip);
+            }
+        );
+    }
+
+    $.fn.hovertip = function(conf) {
+        var defaultConf = {
+            offset: [10, 10],
+            className: 'hovertip',
+            show: function(tooltip) {
+                tooltip.fadeIn(150);
+            },
+            hide: function(tooltip) {
+                tooltip.fadeOut(150);
+            }
+        };
+        $.extend(defaultConf, conf);
+
+        this.each(function() {
+            var el = new Hovertip($(this), defaultConf);
+            $(this).data("hovertip", el);
+        });
+    }
+})(jQuery);
diff --git a/media/js/jquery.jqplot.min.js b/media/js/jquery.jqplot.min.js
new file mode 100644
index 0000000..52b5750
--- /dev/null
+++ b/media/js/jquery.jqplot.min.js
@@ -0,0 +1,57 @@
+/**
+ * jqPlot
+ * Pure JavaScript plotting plugin using jQuery
+ *
+ * Version: 1.0.0b2_r1012
+ *
+ * Copyright (c) 2009-2011 Chris Leonello
+ * jqPlot is currently available for use in all personal or commercial projects 
+ * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL 
+ * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly. 
+ *
+ * Although not required, the author would appreciate an email letting him 
+ * know of any substantial use of jqPlot.  You can reach the author at: 
+ * chris at jqplot dot com or see http://www.jqplot.com/info.php .
+ *
+ * If you are feeling kind and generous, consider supporting the project by
+ * making a donation at: http://www.jqplot.com/donate.php .
+ *
+ * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
+ *
+ *     version 2007.04.27
+ *     author Ash Searle
+ *     http://hexmen.com/blog/2007/03/printf-sprintf/
+ *     http://hexmen.com/js/sprintf.js
+ *     The author (Ash Searle) has placed this code in the public domain:
+ *     "This code is unrestricted: you are free to use it however you like."
+ *
+ * included jsDate library by Chris Leonello:
+ *
+ * Copyright (c) 2010-2011 Chris Leonello
+ *
+ * jsDate is currently available for use in all personal or commercial projects 
+ * under both the MIT and GPL version 2.0 licenses. This means that you can 
+ * choose the license that best suits your project and use it accordingly.
+ *
+ * jsDate borrows many concepts and ideas from the Date Instance 
+ * Methods by Ken Snyder along with some parts of Ken's actual code.
+ * 
+ * Ken's origianl Date Instance Methods and copyright notice:
+ * 
+ * Ken Snyder (ken d snyder at gmail dot com)
+ * 2008-09-10
+ * version 2.0.2 (http://kendsnyder.com/sandbox/date/)     
+ * Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
+ *
+ * jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
+ * Larry has generously given permission to adapt his code for inclusion
+ * into jqPlot.
+ *
+ * Larry's original code can be found here:
+ *
+ * https://github.com/lsiden/export-jqplot-to-png
+ * 
+ * 
+ */
+(function(H){var r;H.fn.emptyForce=function(){for(var ab=0,ac;(ac=H(this)[ab])!=null;ab++){if(ac.nodeType===1){jQuery.cleanData(ac.getElementsByTagName("*"))}if(H.jqplot_use_excanvas){ac.outerHTML=""}else{while(ac.firstChild){ac.removeChild(ac.firstChild)}}ac=null}return H(this)};H.fn.removeChildForce=function(ab){while(ab.firstChild){this.removeChildForce(ab.firstChild);ab.removeChild(ab.firstChild)}};H.jqplot=function(ah,ae,ac){var ad,ab;if(ac==null){if(jQuery.isArray(ae)){ad=ae;ab=nul [...]
\ No newline at end of file
diff --git a/media/js/jquery.js b/media/js/jquery.js
new file mode 100644
index 0000000..93adea1
--- /dev/null
+++ b/media/js/jquery.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.7.2 jquery.com | jquery.org/license */
+(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"<!doctype html>":"")+"<html><body>"),cl.close();d=cl.createElement(a),cl.bod [...]
+a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){ [...]
+.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagNa [...]
\ No newline at end of file
diff --git a/media/js/jquery.pngFix.pack.js b/media/js/jquery.pngFix.pack.js
new file mode 100644
index 0000000..ef4c0c0
--- /dev/null
+++ b/media/js/jquery.pngFix.pack.js
@@ -0,0 +1,11 @@
+/**
+ * --------------------------------------------------------------------
+ * jQuery-Plugin "pngFix"
+ * Version: 1.1, 11.09.2007
+ * by Andreas Eberhard, andreas.eberhard at gmail.com
+ *                      http://jquery.andreaseberhard.de/
+ *
+ * Copyright (c) 2007 Andreas Eberhard
+ * Licensed under GPL (http://www.opensource.org/licenses/gpl-license.php)
+ */
+eval(function(p,a,c,k,e,r){e=function(c){return(c<62?'':e(parseInt(c/62)))+((c=c%62)>35?String.fromCharCode(c+29):c.toString(36))};if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'([237-9n-zA-Z]|1\\w)'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('(s(m){3.fn.pngFix=s(c){c=3.extend({P:\'blank.gif\'},c);8 e=(o.Q=="t R S"&&T(o.u)==4&&o.u.A("U 5.5")!=-1);8 f=(o.Q=="t R S"&&T(o.u)==4&&o.u.A("U 6.0")!=-1) [...]
\ No newline at end of file
diff --git a/media/js/jquery.tablednd_0_5.js b/media/js/jquery.tablednd_0_5.js
new file mode 100644
index 0000000..d4c9fec
--- /dev/null
+++ b/media/js/jquery.tablednd_0_5.js
@@ -0,0 +1,382 @@
+/**
+ * TableDnD plug-in for JQuery, allows you to drag and drop table rows
+ * You can set up various options to control how the system will work
+ * Copyright (c) Denis Howlett <denish at isocra.com>
+ * Licensed like jQuery, see http://docs.jquery.com/License.
+ *
+ * Configuration options:
+ * 
+ * onDragStyle
+ *     This is the style that is assigned to the row during drag. There are limitations to the styles that can be
+ *     associated with a row (such as you can't assign a border--well you can, but it won't be
+ *     displayed). (So instead consider using onDragClass.) The CSS style to apply is specified as
+ *     a map (as used in the jQuery css(...) function).
+ * onDropStyle
+ *     This is the style that is assigned to the row when it is dropped. As for onDragStyle, there are limitations
+ *     to what you can do. Also this replaces the original style, so again consider using onDragClass which
+ *     is simply added and then removed on drop.
+ * onDragClass
+ *     This class is added for the duration of the drag and then removed when the row is dropped. It is more
+ *     flexible than using onDragStyle since it can be inherited by the row cells and other content. The default
+ *     is class is tDnD_whileDrag. So to use the default, simply customise this CSS class in your
+ *     stylesheet.
+ * onDrop
+ *     Pass a function that will be called when the row is dropped. The function takes 2 parameters: the table
+ *     and the row that was dropped. You can work out the new order of the rows by using
+ *     table.rows.
+ * onDragStart
+ *     Pass a function that will be called when the user starts dragging. The function takes 2 parameters: the
+ *     table and the row which the user has started to drag.
+ * onAllowDrop
+ *     Pass a function that will be called as a row is over another row. If the function returns true, allow 
+ *     dropping on that row, otherwise not. The function takes 2 parameters: the dragged row and the row under
+ *     the cursor. It returns a boolean: true allows the drop, false doesn't allow it.
+ * scrollAmount
+ *     This is the number of pixels to scroll if the user moves the mouse cursor to the top or bottom of the
+ *     window. The page should automatically scroll up or down as appropriate (tested in IE6, IE7, Safari, FF2,
+ *     FF3 beta
+ * dragHandle
+ *     This is the name of a class that you assign to one or more cells in each row that is draggable. If you
+ *     specify this class, then you are responsible for setting cursor: move in the CSS and only these cells
+ *     will have the drag behaviour. If you do not specify a dragHandle, then you get the old behaviour where
+ *     the whole row is draggable.
+ * 
+ * Other ways to control behaviour:
+ *
+ * Add class="nodrop" to any rows for which you don't want to allow dropping, and class="nodrag" to any rows
+ * that you don't want to be draggable.
+ *
+ * Inside the onDrop method you can also call $.tableDnD.serialize() this returns a string of the form
+ * <tableID>[]=<rowID1>&<tableID>[]=<rowID2> so that you can send this back to the server. The table must have
+ * an ID as must all the rows.
+ *
+ * Other methods:
+ *
+ * $("...").tableDnDUpdate() 
+ * Will update all the matching tables, that is it will reapply the mousedown method to the rows (or handle cells).
+ * This is useful if you have updated the table rows using Ajax and you want to make the table draggable again.
+ * The table maintains the original configuration (so you don't have to specify it again).
+ *
+ * $("...").tableDnDSerialize()
+ * Will serialize and return the serialized string as above, but for each of the matching tables--so it can be
+ * called from anywhere and isn't dependent on the currentTable being set up correctly before calling
+ *
+ * Known problems:
+ * - Auto-scoll has some problems with IE7  (it scrolls even when it shouldn't), work-around: set scrollAmount to 0
+ * 
+ * Version 0.2: 2008-02-20 First public version
+ * Version 0.3: 2008-02-07 Added onDragStart option
+ *                         Made the scroll amount configurable (default is 5 as before)
+ * Version 0.4: 2008-03-15 Changed the noDrag/noDrop attributes to nodrag/nodrop classes
+ *                         Added onAllowDrop to control dropping
+ *                         Fixed a bug which meant that you couldn't set the scroll amount in both directions
+ *                         Added serialize method
+ * Version 0.5: 2008-05-16 Changed so that if you specify a dragHandle class it doesn't make the whole row
+ *                         draggable
+ *                         Improved the serialize method to use a default (and settable) regular expression.
+ *                         Added tableDnDupate() and tableDnDSerialize() to be called when you are outside the table
+ */
+jQuery.tableDnD = {
+    /** Keep hold of the current table being dragged */
+    currentTable : null,
+    /** Keep hold of the current drag object if any */
+    dragObject: null,
+    /** The current mouse offset */
+    mouseOffset: null,
+    /** Remember the old value of Y so that we don't do too much processing */
+    oldY: 0,
+
+    /** Actually build the structure */
+    build: function(options) {
+        // Set up the defaults if any
+
+        this.each(function() {
+            // This is bound to each matching table, set up the defaults and override with user options
+            this.tableDnDConfig = jQuery.extend({
+                onDragStyle: null,
+                onDropStyle: null,
+				// Add in the default class for whileDragging
+				onDragClass: "tDnD_whileDrag",
+                onDrop: null,
+                onDragStart: null,
+                scrollAmount: 5,
+				serializeRegexp: /[^\-]*$/, // The regular expression to use to trim row IDs
+				serializeParamName: null, // If you want to specify another parameter name instead of the table ID
+                dragHandle: null // If you give the name of a class here, then only Cells with this class will be draggable
+            }, options || {});
+            // Now make the rows draggable
+            jQuery.tableDnD.makeDraggable(this);
+        });
+
+        // Now we need to capture the mouse up and mouse move event
+        // We can use bind so that we don't interfere with other event handlers
+        jQuery(document)
+            .bind('mousemove', jQuery.tableDnD.mousemove)
+            .bind('mouseup', jQuery.tableDnD.mouseup);
+
+        // Don't break the chain
+        return this;
+    },
+
+    /** This function makes all the rows on the table draggable apart from those marked as "NoDrag" */
+    makeDraggable: function(table) {
+        var config = table.tableDnDConfig;
+		if (table.tableDnDConfig.dragHandle) {
+			// We only need to add the event to the specified cells
+			var cells = jQuery("td."+table.tableDnDConfig.dragHandle, table);
+			cells.each(function() {
+				// The cell is bound to "this"
+                jQuery(this).mousedown(function(ev) {
+                    jQuery.tableDnD.dragObject = this.parentNode;
+                    jQuery.tableDnD.currentTable = table;
+                    jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
+                    if (config.onDragStart) {
+                        // Call the onDrop method if there is one
+                        config.onDragStart(table, this);
+                    }
+                    return false;
+                });
+			})
+		} else {
+			// For backwards compatibility, we add the event to the whole row
+	        var rows = jQuery("tr", table); // get all the rows as a wrapped set
+	        rows.each(function() {
+				// Iterate through each row, the row is bound to "this"
+				var row = jQuery(this);
+				if (! row.hasClass("nodrag")) {
+	                row.mousedown(function(ev) {
+	                    if (ev.target.tagName == "TD") {
+	                        jQuery.tableDnD.dragObject = this;
+	                        jQuery.tableDnD.currentTable = table;
+	                        jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(this, ev);
+	                        if (config.onDragStart) {
+	                            // Call the onDrop method if there is one
+	                            config.onDragStart(table, this);
+	                        }
+	                        return false;
+	                    }
+	                }).css("cursor", "move"); // Store the tableDnD object
+				}
+			});
+		}
+	},
+
+	updateTables: function() {
+		this.each(function() {
+			// this is now bound to each matching table
+			if (this.tableDnDConfig) {
+				jQuery.tableDnD.makeDraggable(this);
+			}
+		})
+	},
+
+    /** Get the mouse coordinates from the event (allowing for browser differences) */
+    mouseCoords: function(ev){
+        if(ev.pageX || ev.pageY){
+            return {x:ev.pageX, y:ev.pageY};
+        }
+        return {
+            x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
+            y:ev.clientY + document.body.scrollTop  - document.body.clientTop
+        };
+    },
+
+    /** Given a target element and a mouse event, get the mouse offset from that element.
+        To do this we need the element's position and the mouse position */
+    getMouseOffset: function(target, ev) {
+        ev = ev || window.event;
+
+        var docPos    = this.getPosition(target);
+        var mousePos  = this.mouseCoords(ev);
+        return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
+    },
+
+    /** Get the position of an element by going up the DOM tree and adding up all the offsets */
+    getPosition: function(e){
+        var left = 0;
+        var top  = 0;
+        /** Safari fix -- thanks to Luis Chato for this! */
+        if (e.offsetHeight == 0) {
+            /** Safari 2 doesn't correctly grab the offsetTop of a table row
+            this is detailed here:
+            http://jacob.peargrove.com/blog/2006/technical/table-row-offsettop-bug-in-safari/
+            the solution is likewise noted there, grab the offset of a table cell in the row - the firstChild.
+            note that firefox will return a text node as a first child, so designing a more thorough
+            solution may need to take that into account, for now this seems to work in firefox, safari, ie */
+            e = e.firstChild; // a table cell
+        }
+
+        while (e.offsetParent){
+            left += e.offsetLeft;
+            top  += e.offsetTop;
+            e     = e.offsetParent;
+        }
+
+        left += e.offsetLeft;
+        top  += e.offsetTop;
+
+        return {x:left, y:top};
+    },
+
+    mousemove: function(ev) {
+        if (jQuery.tableDnD.dragObject == null) {
+            return;
+        }
+
+        var dragObj = jQuery(jQuery.tableDnD.dragObject);
+        var config = jQuery.tableDnD.currentTable.tableDnDConfig;
+        var mousePos = jQuery.tableDnD.mouseCoords(ev);
+        var y = mousePos.y - jQuery.tableDnD.mouseOffset.y;
+        //auto scroll the window
+	    var yOffset = window.pageYOffset;
+	 	if (document.all) {
+	        // Windows version
+	        //yOffset=document.body.scrollTop;
+	        if (typeof document.compatMode != 'undefined' &&
+	             document.compatMode != 'BackCompat') {
+	           yOffset = document.documentElement.scrollTop;
+	        }
+	        else if (typeof document.body != 'undefined') {
+	           yOffset=document.body.scrollTop;
+	        }
+
+	    }
+		    
+		if (mousePos.y-yOffset < config.scrollAmount) {
+	    	window.scrollBy(0, -config.scrollAmount);
+	    } else {
+            var windowHeight = window.innerHeight ? window.innerHeight
+                    : document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
+            if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) {
+                window.scrollBy(0, config.scrollAmount);
+            }
+        }
+
+
+        if (y != jQuery.tableDnD.oldY) {
+            // work out if we're going up or down...
+            var movingDown = y > jQuery.tableDnD.oldY;
+            // update the old value
+            jQuery.tableDnD.oldY = y;
+            // update the style to show we're dragging
+			if (config.onDragClass) {
+				dragObj.addClass(config.onDragClass);
+			} else {
+	            dragObj.css(config.onDragStyle);
+			}
+            // If we're over a row then move the dragged row to there so that the user sees the
+            // effect dynamically
+            var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y);
+            if (currentRow) {
+                // TODO worry about what happens when there are multiple TBODIES
+                if (movingDown && jQuery.tableDnD.dragObject != currentRow) {
+                    jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
+                } else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) {
+                    jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
+                }
+            }
+        }
+
+        return false;
+    },
+
+    /** We're only worried about the y position really, because we can only move rows up and down */
+    findDropTargetRow: function(draggedRow, y) {
+        var rows = jQuery.tableDnD.currentTable.rows;
+        for (var i=0; i<rows.length; i++) {
+            var row = rows[i];
+            var rowY    = this.getPosition(row).y;
+            var rowHeight = parseInt(row.offsetHeight)/2;
+            if (row.offsetHeight == 0) {
+                rowY = this.getPosition(row.firstChild).y;
+                rowHeight = parseInt(row.firstChild.offsetHeight)/2;
+            }
+            // Because we always have to insert before, we need to offset the height a bit
+            if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
+                // that's the row we're over
+				// If it's the same as the current row, ignore it
+				if (row == draggedRow) {return null;}
+                var config = jQuery.tableDnD.currentTable.tableDnDConfig;
+                if (config.onAllowDrop) {
+                    if (config.onAllowDrop(draggedRow, row)) {
+                        return row;
+                    } else {
+                        return null;
+                    }
+                } else {
+					// If a row has nodrop class, then don't allow dropping (inspired by John Tarr and Famic)
+                    var nodrop = jQuery(row).hasClass("nodrop");
+                    if (! nodrop) {
+                        return row;
+                    } else {
+                        return null;
+                    }
+                }
+                return row;
+            }
+        }
+        return null;
+    },
+
+    mouseup: function(e) {
+        if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) {
+            var droppedRow = jQuery.tableDnD.dragObject;
+            var config = jQuery.tableDnD.currentTable.tableDnDConfig;
+            // If we have a dragObject, then we need to release it,
+            // The row will already have been moved to the right place so we just reset stuff
+			if (config.onDragClass) {
+	            jQuery(droppedRow).removeClass(config.onDragClass);
+			} else {
+	            jQuery(droppedRow).css(config.onDropStyle);
+			}
+            jQuery.tableDnD.dragObject   = null;
+            if (config.onDrop) {
+                // Call the onDrop method if there is one
+                config.onDrop(jQuery.tableDnD.currentTable, droppedRow);
+            }
+            jQuery.tableDnD.currentTable = null; // let go of the table too
+        }
+    },
+
+    serialize: function() {
+        if (jQuery.tableDnD.currentTable) {
+            return jQuery.tableDnD.serializeTable(jQuery.tableDnD.currentTable);
+        } else {
+            return "Error: No Table id set, you need to set an id on your table and every row";
+        }
+    },
+
+	serializeTable: function(table) {
+        var result = "";
+        var tableId = table.id;
+        var rows = table.rows;
+        for (var i=0; i<rows.length; i++) {
+            if (result.length > 0) result += "&";
+            var rowId = rows[i].id;
+            if (rowId && rowId && table.tableDnDConfig && table.tableDnDConfig.serializeRegexp) {
+                rowId = rowId.match(table.tableDnDConfig.serializeRegexp)[0];
+            }
+
+            result += tableId + '[]=' + rowId;
+        }
+        return result;
+	},
+
+	serializeTables: function() {
+        var result = "";
+        this.each(function() {
+			// this is now bound to each matching table
+			result += jQuery.tableDnD.serializeTable(this);
+		});
+        return result;
+    }
+
+}
+
+jQuery.fn.extend(
+	{
+		tableDnD : jQuery.tableDnD.build,
+		tableDnDUpdate : jQuery.tableDnD.updateTables,
+		tableDnDSerialize: jQuery.tableDnD.serializeTables
+	}
+);
\ No newline at end of file
diff --git a/media/js/jquery.timeago.js b/media/js/jquery.timeago.js
new file mode 100644
index 0000000..9fcf481
--- /dev/null
+++ b/media/js/jquery.timeago.js
@@ -0,0 +1,147 @@
+/*
+ * timeago: a jQuery plugin, version: 0.9.3 (2011-01-21)
+ * @requires jQuery v1.2.3 or later
+ *
+ * Timeago is a jQuery plugin that makes it easy to support automatically
+ * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
+ *
+ * For usage and examples, visit:
+ * http://timeago.yarp.com/
+ *
+ * Licensed under the MIT:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ * Copyright (c) 2008-2011, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
+ */
+(function($) {
+  $.timeago = function(timestamp) {
+    if (timestamp instanceof Date) {
+      return inWords(timestamp);
+    } else if (typeof timestamp === "string") {
+      return inWords($.timeago.parse(timestamp));
+    } else {
+      return inWords($.timeago.datetime(timestamp));
+    }
+  };
+  var $t = $.timeago;
+
+  $.extend($.timeago, {
+    settings: {
+      refreshMillis: 60000,
+      allowFuture: false,
+      strings: {
+        prefixAgo: null,
+        prefixFromNow: null,
+        suffixAgo: "ago",
+        suffixFromNow: "from now",
+        seconds: "less than a minute",
+        minute: "about a minute",
+        minutes: "%d minutes",
+        hour: "about an hour",
+        hours: "about %d hours",
+        day: "a day",
+        days: "%d days",
+        month: "about a month",
+        months: "%d months",
+        year: "about a year",
+        years: "%d years",
+        numbers: []
+      }
+    },
+    inWords: function(distanceMillis) {
+      var $l = this.settings.strings;
+      var prefix = $l.prefixAgo;
+      var suffix = $l.suffixAgo;
+      if (this.settings.allowFuture) {
+        if (distanceMillis < 0) {
+          prefix = $l.prefixFromNow;
+          suffix = $l.suffixFromNow;
+        }
+        distanceMillis = Math.abs(distanceMillis);
+      }
+
+      var seconds = distanceMillis / 1000;
+      var minutes = seconds / 60;
+      var hours = minutes / 60;
+      var days = hours / 24;
+      var years = days / 365;
+
+      function substitute(stringOrFunction, number) {
+        var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
+        var value = ($l.numbers && $l.numbers[number]) || number;
+        return string.replace(/%d/i, value);
+      }
+
+      var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
+        seconds < 90 && substitute($l.minute, 1) ||
+        minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
+        minutes < 90 && substitute($l.hour, 1) ||
+        hours < 24 && substitute($l.hours, Math.round(hours)) ||
+        hours < 48 && substitute($l.day, 1) ||
+        days < 30 && substitute($l.days, Math.floor(days)) ||
+        days < 60 && substitute($l.month, 1) ||
+        days < 365 && substitute($l.months, Math.floor(days / 30)) ||
+        years < 2 && substitute($l.year, 1) ||
+        substitute($l.years, Math.floor(years));
+
+      return $.trim([prefix, words, suffix].join(" "));
+    },
+    parse: function(iso8601) {
+      var s = $.trim(iso8601);
+      s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
+      s = s.replace(/-/,"/").replace(/-/,"/");
+      s = s.replace(/T/," ").replace(/Z/," UTC");
+      s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
+      return new Date(s);
+    },
+    datetime: function(elem) {
+      // jQuery's `is()` doesn't play well with HTML5 in IE
+      var isTime = $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
+      var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
+      return $t.parse(iso8601);
+    }
+  });
+
+  $.fn.timeago = function() {
+    var self = this;
+    self.each(refresh);
+
+    var $s = $t.settings;
+    if ($s.refreshMillis > 0) {
+      setInterval(function() { self.each(refresh); }, $s.refreshMillis);
+    }
+    return self;
+  };
+
+  function refresh() {
+    var data = prepareData(this);
+    if (!isNaN(data.datetime)) {
+      $(this).text(inWords(data.datetime));
+    }
+    return this;
+  }
+
+  function prepareData(element) {
+    element = $(element);
+    if (!element.data("timeago")) {
+      element.data("timeago", { datetime: $t.datetime(element) });
+      var text = $.trim(element.text());
+      if (text.length > 0) {
+        element.attr("title", text);
+      }
+    }
+    return element.data("timeago");
+  }
+
+  function inWords(date) {
+    return $t.inWords(distance(date));
+  }
+
+  function distance(date) {
+    return (new Date().getTime() - date.getTime());
+  }
+
+  // fix for IE6 suckage
+  document.createElement("abbr");
+  document.createElement("time");
+}(jQuery));
diff --git a/media/js/jquery.treeview.js b/media/js/jquery.treeview.js
new file mode 100644
index 0000000..bc5d9e4
--- /dev/null
+++ b/media/js/jquery.treeview.js
@@ -0,0 +1,251 @@
+/*
+ * Treeview 1.4 - jQuery plugin to hide and show branches of a tree
+ * 
+ * http://bassistance.de/jquery-plugins/jquery-plugin-treeview/
+ * http://docs.jquery.com/Plugins/Treeview
+ *
+ * Copyright (c) 2007 Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.treeview.js 4684 2008-02-07 19:08:06Z joern.zaefferer $
+ *
+ */
+
+;(function($) {
+
+	$.extend($.fn, {
+		swapClass: function(c1, c2) {
+			var c1Elements = this.filter('.' + c1);
+			this.filter('.' + c2).removeClass(c2).addClass(c1);
+			c1Elements.removeClass(c1).addClass(c2);
+			return this;
+		},
+		replaceClass: function(c1, c2) {
+			return this.filter('.' + c1).removeClass(c1).addClass(c2).end();
+		},
+		hoverClass: function(className) {
+			className = className || "hover";
+			return this.hover(function() {
+				$(this).addClass(className);
+			}, function() {
+				$(this).removeClass(className);
+			});
+		},
+		heightToggle: function(animated, callback) {
+			animated ?
+				this.animate({ height: "toggle" }, animated, callback) :
+				this.each(function(){
+					jQuery(this)[ jQuery(this).is(":hidden") ? "show" : "hide" ]();
+					if(callback)
+						callback.apply(this, arguments);
+				});
+		},
+		heightHide: function(animated, callback) {
+			if (animated) {
+				this.animate({ height: "hide" }, animated, callback);
+			} else {
+				this.hide();
+				if (callback)
+					this.each(callback);				
+			}
+		},
+		prepareBranches: function(settings) {
+			if (!settings.prerendered) {
+				// mark last tree items
+				this.filter(":last-child:not(ul)").addClass(CLASSES.last);
+				// collapse whole tree, or only those marked as closed, anyway except those marked as open
+				this.filter((settings.collapsed ? "" : "." + CLASSES.closed) + ":not(." + CLASSES.open + ")").find(">ul").hide();
+			}
+			// return all items with sublists
+			return this.filter(":has(>ul)");
+		},
+		applyClasses: function(settings, toggler) {
+			this.filter(":has(>ul):not(:has(>a))").find(">span").click(function(event) {
+				toggler.apply($(this).next());
+			}).add( $("a", this) ).hoverClass();
+			
+			if (!settings.prerendered) {
+				// handle closed ones first
+				this.filter(":has(>ul:hidden)")
+						.addClass(CLASSES.expandable)
+						.replaceClass(CLASSES.last, CLASSES.lastExpandable);
+						
+				// handle open ones
+				this.not(":has(>ul:hidden)")
+						.addClass(CLASSES.collapsable)
+						.replaceClass(CLASSES.last, CLASSES.lastCollapsable);
+						
+	            // create hitarea
+				this.prepend("<div class=\"" + CLASSES.hitarea + "\"/>").find("div." + CLASSES.hitarea).each(function() {
+					var classes = "";
+					$.each($(this).parent().attr("class").split(" "), function() {
+						classes += this + "-hitarea ";
+					});
+					$(this).addClass( classes );
+				});
+			}
+			
+			// apply event to hitarea
+			this.find("div." + CLASSES.hitarea).click( toggler );
+		},
+		treeview: function(settings) {
+			
+			settings = $.extend({
+				cookieId: "treeview"
+			}, settings);
+			
+			if (settings.add) {
+				return this.trigger("add", [settings.add]);
+			}
+			
+			if ( settings.toggle ) {
+				var callback = settings.toggle;
+				settings.toggle = function() {
+					return callback.apply($(this).parent()[0], arguments);
+				};
+			}
+		
+			// factory for treecontroller
+			function treeController(tree, control) {
+				// factory for click handlers
+				function handler(filter) {
+					return function() {
+						// reuse toggle event handler, applying the elements to toggle
+						// start searching for all hitareas
+						toggler.apply( $("div." + CLASSES.hitarea, tree).filter(function() {
+							// for plain toggle, no filter is provided, otherwise we need to check the parent element
+							return filter ? $(this).parent("." + filter).length : true;
+						}) );
+						return false;
+					};
+				}
+				// click on first element to collapse tree
+				$("a:eq(0)", control).click( handler(CLASSES.collapsable) );
+				// click on second to expand tree
+				$("a:eq(1)", control).click( handler(CLASSES.expandable) );
+				// click on third to toggle tree
+				$("a:eq(2)", control).click( handler() ); 
+			}
+		
+			// handle toggle event
+			function toggler() {
+				$(this)
+					.parent()
+					// swap classes for hitarea
+					.find(">.hitarea")
+						.swapClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
+						.swapClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
+					.end()
+					// swap classes for parent li
+					.swapClass( CLASSES.collapsable, CLASSES.expandable )
+					.swapClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
+					// find child lists
+					.find( ">ul" )
+					// toggle them
+					.heightToggle( settings.animated, settings.toggle );
+				if ( settings.unique ) {
+					$(this).parent()
+						.siblings()
+						// swap classes for hitarea
+						.find(">.hitarea")
+							.replaceClass( CLASSES.collapsableHitarea, CLASSES.expandableHitarea )
+							.replaceClass( CLASSES.lastCollapsableHitarea, CLASSES.lastExpandableHitarea )
+						.end()
+						.replaceClass( CLASSES.collapsable, CLASSES.expandable )
+						.replaceClass( CLASSES.lastCollapsable, CLASSES.lastExpandable )
+						.find( ">ul" )
+						.heightHide( settings.animated, settings.toggle );
+				}
+			}
+			
+			function serialize() {
+				function binary(arg) {
+					return arg ? 1 : 0;
+				}
+				var data = [];
+				branches.each(function(i, e) {
+					data[i] = $(e).is(":has(>ul:visible)") ? 1 : 0;
+				});
+				$.cookie(settings.cookieId, data.join("") );
+			}
+			
+			function deserialize() {
+				var stored = $.cookie(settings.cookieId);
+				if ( stored ) {
+					var data = stored.split("");
+					branches.each(function(i, e) {
+						$(e).find(">ul")[ parseInt(data[i]) ? "show" : "hide" ]();
+					});
+				}
+			}
+			
+			// add treeview class to activate styles
+			this.addClass("treeview");
+			
+			// prepare branches and find all tree items with child lists
+			var branches = this.find("li").prepareBranches(settings);
+			
+			switch(settings.persist) {
+			case "cookie":
+				var toggleCallback = settings.toggle;
+				settings.toggle = function() {
+					serialize();
+					if (toggleCallback) {
+						toggleCallback.apply(this, arguments);
+					}
+				};
+				deserialize();
+				break;
+			case "location":
+				var current = this.find("a").filter(function() { return this.href.toLowerCase() == location.href.toLowerCase(); });
+				if ( current.length ) {
+					current.addClass("selected").parents("ul, li").add( current.next() ).show();
+				}
+				break;
+			}
+			
+			branches.applyClasses(settings, toggler);
+				
+			// if control option is set, create the treecontroller and show it
+			if ( settings.control ) {
+				treeController(this, settings.control);
+				$(settings.control).show();
+			}
+			
+			return this.bind("add", function(event, branches) {
+				$(branches).prev()
+					.removeClass(CLASSES.last)
+					.removeClass(CLASSES.lastCollapsable)
+					.removeClass(CLASSES.lastExpandable)
+				.find(">.hitarea")
+					.removeClass(CLASSES.lastCollapsableHitarea)
+					.removeClass(CLASSES.lastExpandableHitarea);
+				$(branches).find("li").andSelf().prepareBranches(settings).applyClasses(settings, toggler);
+			});
+		}
+	});
+	
+	// classes used by the plugin
+	// need to be styled via external stylesheet, see first example
+	var CLASSES = $.fn.treeview.classes = {
+		open: "open",
+		closed: "closed",
+		expandable: "expandable",
+		expandableHitarea: "expandable-hitarea",
+		lastExpandableHitarea: "lastExpandable-hitarea",
+		collapsable: "collapsable",
+		collapsableHitarea: "collapsable-hitarea",
+		lastCollapsableHitarea: "lastCollapsable-hitarea",
+		lastCollapsable: "lastCollapsable",
+		lastExpandable: "lastExpandable",
+		last: "last",
+		hitarea: "hitarea"
+	};
+	
+	// provide backwards compability
+	$.fn.Treeview = $.fn.treeview;
+	
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jquery.ui.min.js b/media/js/jquery.ui.min.js
new file mode 100644
index 0000000..50c2ab7
--- /dev/null
+++ b/media/js/jquery.ui.min.js
@@ -0,0 +1,37 @@
+/*! jQuery UI - v1.8.21 - 2012-06-05
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.core.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){function c(b,c){var e=b.nodeName.toLowerCase();if("area"===e){var f=b.parentNode,g=f.name,h;return!b.href||!g||f.nodeName.toLowerCase()!=="map"?!1:(h=a("img[usemap=#"+g+"]")[0],!!h&&d(h))}return(/input|select|textarea|button|object/.test(e)?!b.disabled:"a"==e?b.href||c:c)&&d(b)}function d(b){return!a(b).parents().andSelf().filter(function(){return a.curCSS(this,"visibility")==="hidden"||a.expr.filters.hidden(this)}).length}a.ui=a.ui||{};if(a.ui.version)return;a.extend(a.ui [...]
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.widget.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){if(a.cleanData){var c=a.cleanData;a.cleanData=function(b){for(var d=0,e;(e=b[d])!=null;d++)try{a(e).triggerHandler("remove")}catch(f){}c(b)}}else{var d=a.fn.remove;a.fn.remove=function(b,c){return this.each(function(){return c||(!b||a.filter(b,[this]).length)&&a("*",this).add([this]).each(function(){try{a(this).triggerHandler("remove")}catch(b){}}),d.call(a(this),b,c)})}}a.widget=function(b,c,d){var e=b.split(".")[0],f;b=b.split(".")[1],f=e+"-"+b,d||(d=c,c=a.Widget),a.expr [...]
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.mouse.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c=!1;a(document).mouseup(function(a){c=!1}),a.widget("ui.mouse",{options:{cancel:":input,option",distance:1,delay:0},_mouseInit:function(){var b=this;this.element.bind("mousedown."+this.widgetName,function(a){return b._mouseDown(a)}).bind("click."+this.widgetName,function(c){if(!0===a.data(c.target,b.widgetName+".preventClickEvent"))return a.removeData(c.target,b.widgetName+".preventClickEvent"),c.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function( [...]
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.accordion.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.accordion",{options:{active:0,animated:"slide",autoHeight:!0,clearStyle:!1,collapsible:!1,event:"click",fillSpace:!1,header:"> li > :first-child,> :not(li):even",icons:{header:"ui-icon-triangle-1-e",headerSelected:"ui-icon-triangle-1-s"},navigation:!1,navigationFilter:function(){return this.href.toLowerCase()===location.href.toLowerCase()}},_create:function(){var b=this,c=b.options;b.running=0,b.element.addClass("ui-accordion ui-widget ui-helper-reset").childr [...]
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.slider.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){var c=5;a.widget("ui.slider",a.ui.mouse,{widgetEventPrefix:"slide",options:{animate:!1,distance:0,max:100,min:0,orientation:"horizontal",range:!1,step:1,value:0,values:null},_create:function(){var b=this,d=this.options,e=this.element.find(".ui-slider-handle").addClass("ui-state-default ui-corner-all"),f="<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",g=d.values&&d.values.length||1,h=[];this._keySliding=!1,this._mouseSliding=!1,this._animateOff=!0 [...]
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.datepicker.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function($,undefined){function Datepicker(){this.debug=!1,this._curInst=null,this._keyEvent=!1,this._disabledInputs=[],this._datepickerShowing=!1,this._inDialog=!1,this._mainDivId="ui-datepicker-div",this._inlineClass="ui-datepicker-inline",this._appendClass="ui-datepicker-append",this._triggerClass="ui-datepicker-trigger",this._dialogClass="ui-datepicker-dialog",this._disableClass="ui-datepicker-disabled",this._unselectableClass="ui-datepicker-unselectable",this._currentClass="ui-datep [...]
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.ui.progressbar.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.widget("ui.progressbar",{options:{value:0,max:100},min:0,_create:function(){this.element.addClass("ui-progressbar ui-widget ui-widget-content ui-corner-all").attr({role:"progressbar","aria-valuemin":this.min,"aria-valuemax":this.options.max,"aria-valuenow":this._value()}),this.valueDiv=a("<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>").appendTo(this.element),this.oldValue=this._value(),this._refreshValue()},destroy:function(){this.element.remov [...]
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.core.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+jQuery.effects||function(a,b){function c(b){var c;return b&&b.constructor==Array&&b.length==3?b:(c=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(b))?[parseInt(c[1],10),parseInt(c[2],10),parseInt(c[3],10)]:(c=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(b))?[parseFloat(c[1])*2.55,parseFloat(c[2])*2.55,parseFloat(c[3])*2.55]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(b))?[parseInt(c[1],16), [...]
+* https://github.com/jquery/jquery-ui
+* Includes: jquery.effects.highlight.js
+* Copyright (c) 2012 AUTHORS.txt; Licensed MIT, GPL */
+(function(a,b){a.effects.highlight=function(b){return this.queue(function(){var c=a(this),d=["backgroundImage","backgroundColor","opacity"],e=a.effects.setMode(c,b.options.mode||"show"),f={backgroundColor:c.css("backgroundColor")};e=="hide"&&(f.opacity=0),a.effects.save(c,d),c.show().css({backgroundImage:"none",backgroundColor:b.options.color||"#ffff99"}).animate(f,{queue:!1,duration:b.duration,easing:b.options.easing,complete:function(){e=="hide"&&c.hide(),a.effects.restore(c,d),e=="sho [...]
\ No newline at end of file
diff --git a/media/js/jquery.validate.min.js b/media/js/jquery.validate.min.js
new file mode 100644
index 0000000..a1a5fbb
--- /dev/null
+++ b/media/js/jquery.validate.min.js
@@ -0,0 +1,16 @@
+/*
+ * jQuery validation plug-in 1.5.5
+ *
+ * http://bassistance.de/jquery-plugins/jquery-plugin-validation/
+ * http://docs.jquery.com/Plugins/Validation
+ *
+ * Copyright (c) 2006 - 2008 Jörn Zaefferer
+ *
+ * $Id: jquery.validate.js 6403 2009-06-17 14:27:16Z joern.zaefferer $
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ */
+(function($){$.extend($.fn,{validate:function(options){if(!this.length){options&&options.debug&&window.console&&console.warn("nothing selected, can't validate, returning nothing");return;}var validator=$.data(this[0],'validator');if(validator){return validator;}validator=new $.validator(options,this[0]);$.data(this[0],'validator',validator);if(validator.settings.onsubmit){this.find("input, button").filter(".cancel").click(function(){validator.cancelSubmit=true;});if(validator.settings.su [...]
++", check the '"+rule.method+"' method");throw e;}}if(dependencyMismatch)return;if(this.objectLength(rules))this.successList.push(element);return true;},customMetaMessage:function(element,method){if(!$.metadata)return;var meta=this.settings.meta?$(element).metadata()[this.settings.meta]:$(element).metadata();return meta&&meta.messages&&meta.messages[method];},customMessage:function(name,method){var m=this.settings.messages[name];return m&&(m.constructor==String?m:m[method]);},findDefined [...]
\ No newline at end of file
diff --git a/media/js/json2.js b/media/js/json2.js
new file mode 100644
index 0000000..ae4c265
--- /dev/null
+++ b/media/js/json2.js
@@ -0,0 +1,25 @@
+var JSON;if(!JSON){JSON={};}
+(function(){"use strict";function f(n){return n<10?'0'+n:n;}
+if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+
+f(this.getUTCMonth()+1)+'-'+
+f(this.getUTCDate())+'T'+
+f(this.getUTCHours())+':'+
+f(this.getUTCMinutes())+':'+
+f(this.getUTCSeconds())+'Z':null;};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf();};}
+var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c= [...]
+function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key);}
+if(typeof rep==='function'){value=rep.call(holder,key,value);}
+switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null';}
+gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null';}
+v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v;}
+if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==='string'){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v);}}}}
+v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v;}}
+if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' ';}}else if(typeof space==='string'){indent=space;}
+rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify');}
+return str('',{'':value});};}
+if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v;}else{delete value[k];}}}}
+return reviver.call(holder,key,value);}
+text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+
+('0000'+a.charCodeAt(0).toString(16)).slice(-4);});}
+if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j;}
+throw new SyntaxError('JSON.parse');};}}());
\ No newline at end of file
diff --git a/media/js/jwysiwyg/CHANGES.markdown b/media/js/jwysiwyg/CHANGES.markdown
new file mode 100644
index 0000000..0852049
--- /dev/null
+++ b/media/js/jwysiwyg/CHANGES.markdown
@@ -0,0 +1,164 @@
+# Changes
+
+## master (not tagged yet)
+
+## Version 0.97.2 (From infinity)
+
+* Preparing to jQuery 1.6 - akzhan
+* Fixed infinite loops - frost-nzcr4
+* rmFormat a bit improved - frost-nzcr4
+* Fixed issue in Firefox when insertHtml is used - frost-nzcr4
+* Useless disabled attribute on LI tags has been removed, disabled class added instead of - akzhan
+
+## Version 0.97.1 (Follow me)
+
+Take a note that version number in jquery.wysiwyg.js is not changed to make Drupal users happy.
+Also I want to note that 0.97.1 supports ECMAScript strict mode.
+
+* jslint (fixes for common asset compressors) - filiptepper
+* Fix paste issues for Microsoft Word formatter - frost-nzcr4
+
+## Version 0.97 (Next step)
+
+* Bugfixes and core enhancements - frost-nzcr4
+* jslint - frost-nzcr4, akzhan
+* Documentation - vjt, ctide, afilina, elektronaut
+* Controls: increase/decrease font size, highlight, code - vjt, ctide, Tudmotu
+* Sorting controls by user - vjt
+* New options: initialMinHeight and maxLength - ctide
+* Enhance modal dialogs - ctide
+* New API method selectAll - ctide
+* Fix dialog in image control - simsalabim
+* CSS fixes - Jason Orrill, afilina
+* Switch to Uglify compressor - akzhan
+* Add Jasmine test suite - frost-nzcr4
+* Dev tools to generate unicode entities - EvanCarroll, akzhan
+* Enhance event handler - brentkirby
+* Fixed bugs in event system - EvanCarroll
+* Editor specific event system - alecgorge
+* XHTML5 and Unicode Entity Handling - EvanCarroll, alecgorge
+* Dutch locale for jwysiwyg - Erik van Dongen
+* Polish locale for jwysiwyg - aherok
+
+## Version 0.96 (Pretty girl)
+
+* Plugin rmFormat: fix for Word and IE markup - SugaSlide
+* Proper dialog focus with IE7/8 - frost-nczr4, academo
+* Closure for autoSaveFunction - mbj
+* New options - mbj, frost-nczr4
+* Encode entities - alecgorge
+* Link control - academo
+* Fixes for Internet Explorer - frost-nczr4
+* Updated structure of repository and GitHub pages - akzhan
+* Brazilian Portuguese locale for jwysiwyg - Marcelo Wergles
+* Czech locale for jwysiwyg - deepj
+* German locale for jwysiwyg - mbj
+* Italian locale for jwysiwyg - maurofranceschini
+* Japanese locale for jwysiwyg - rosiro
+* Slovenian locale for jwysiwyg - peterz
+* Spanish locale for jwysiwyg - academo
+* Ability to translate dialogs - frost-nczr4
+
+## Version 0.95 (Kino)
+
+* Directory structure of repository has been reorganized to be more friendly for Drupal users - frost-nzcr4, sun
+* Plugins API implemented - frost-nzcr4
+* Internationalization API implemented - frost-nzcr4
+* Color picker plugin - arincool, frost-nzcr4
+* rmFormat plugin - frost-nzcr4
+* Some core functionality has been splitted into plugins and has been extended (like inserting of images or tables) - frost-nzcr4
+* Image tag editing implemented in image control - frost-nzcr4
+* jQuery UI Dialog integration code fixed - mydevel
+* "this.get is not a function" error when trying to add a link fixed - everlee
+* In the saveContent function, the html needs to be saved if in html view - Justin Lewis
+* Multiple fixes to eliminate using of for..in loop - rych, frost-nzcr4, akzhan
+* Insertion of images and tables should trigger autogrow - J. Weir
+* loadCss option has been renamed to autoload - frost-nzcr4
+* Massive update of documentation - frost-nzcr4
+* jslint issues - akzhan, frost-nzcr4
+* Fixed exception in s.addRange() when savedRange is undefined - frost-nzcr4
+* new rmUnusedControls option added (see #52) - frost-nzcr4
+* French locale for jwysiwyg - MappaM
+* Swedish locale for jwysiwyg - ippa
+* Russian locale for jwysiwyg - frost-nzcr4
+
+## Version 0.94 (phase 2)
+
+* focus is properly returned after clicking on buttons - alecgorge
+* fix for getContent operation by class selector - alecgorge
+* new option for custom toolbar items (look at tests/issue 26.html for details) - alecgorge
+* fix IE8/XP compatibility issue - jsch
+* Fix incorrect handling of iFrameClass option - bbrewder
+* Refactoring - frost-nzcr4
+* Fixing of destroy, documentSelection in ltr, rtl  modes - frost-nzcr4
+* Adding of CSS autoload, initialContent option - frost-nzcr4
+* Adding of autoGrow option - Lukom
+* Use Cmd key on Macs - boutell
+* JSlint fixes - akzhan, filiptepper
+
+## Version 0.93 (koken)
+
+* Hide wysiwyg while html shown - akzhan
+* jwysiwyg destroy fixed - jalada
+* jwysiwyg iframe body now marked with wysiwyg class - TheQueenDrinksTea
+* Common save event for catching all the modifications added - Janne Hietamaki
+* Custom handler for chaining toolbar ordering etc. added - Janne Hietamaki
+* Iframe now can use class name to be styled - chris.haumesser
+* Version string in source file must ended with version number for Drupal integration folks - xeto
+
+## Version 0.92 (arigatou gozaimasu)
+
+* Fix work under quirks mode of Internet Explorer - kris.schwab
+* Workaround for Mozilla/WebKit misfunctionality of RemoveFormat over headings - aiveldesign
+* Experimental support for switching between LTR/RTL modes (no icons provided and markup issues) - abduljawad.mahmoud
+* More robust selection check in createLink - systeembeheer
+
+## Version 0.91 (maintenance release)
+
+* Editor now throw errors on unknown actions - akzhan
+* Getter methods were broken. Fixed - wordituk
+* headings formatting has been fixed in IE, Firefox and Chrome - kolpak
+
+## Version 0.9 (maintenance release)
+
+* Buttons are unselectable now and have no anchors (CSS reviewed) - mrapczynski
+* Way to return focus to editor has been corrected - mappam0
+* $.fn.documentSelection has been removed to minimize pollution of $.fn namespace - akzhan
+* Source mode fixed for all browsers (was inspired by 0.8)  - silvermuru
+
+## Version 0.8 (revival)
+
++ enabled, destroy, removeFormat, save actions added to $('#elt').wysiwyg(action) - fomojola
++ insertTable action/button added - academo
++ insertTable and insertImage buttons now support jQuery UI Dialog and SimpleModal plugin - academo
++ Event handlers supported through events - akzhan
++ Editor now supports jQuery UI resizable plugin through resizeOptions - akzhan
+* jWysiwyg now wraps Mozilla bug that disables editor creation in AJAX calls - akzhan
+* *MSG_EN* and *TOOLBAR* replaced with $.fn.wysiwyg.defaults/controls - akzhan
+* separators replaced with group indexes - akzhan
+* Directory structure reorganized - academo
+* $.fn.document has been removed to minimize pollution of $.fn namespace - akzhan
+
+## Version 0.7
+
+* Ctrl+B, Ctrl+I and Ctrl+U keystrokes in non-IE browsers now works like IE ones - akzhan
+* insertHtml in non-focused editor works now - akzhan
+* Appearance of toolbar buttons fixed under IE7/8 - ibnteo, mail2lx
+! Code reviewed.
+
+## Version 0.6
+
+* New $().wysiwyg('insertHtml', string) method - akzhan
+* New example (nearby full editor) added - deansofer
+* CSS styling replaced with tags in Firefox -  Svel.Sontz, tobinl, AndreyKostromin ans others
+* Minor problems with focus in non-IE fixed - tyler.schacht
+* Correct setup of form events - Mickael.Hoareau
+* Editor now correctly sets its tabindex on initialization - denis.vysotskiy
+* Insertion of headers in safari fixed - gpearman
+* More correct styling of editor - gamingforever
+* Correct behaviour on IE using https protocol - jf.stgermain
+* Incorrect initialization of editor when content contains "$" characters fixed - JackPDouglas
+* Improved CSS degradability - deansofer
+* Improved ARIA accessability - akzhan
+! Requires jQuery 1.3 or higher! Tested under jQuery 1.4 too.
+
diff --git a/media/js/jwysiwyg/GPL-LICENSE.txt b/media/js/jwysiwyg/GPL-LICENSE.txt
new file mode 100644
index 0000000..11dddd0
--- /dev/null
+++ b/media/js/jwysiwyg/GPL-LICENSE.txt
@@ -0,0 +1,278 @@
+        GNU GENERAL PUBLIC LICENSE
+           Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+          Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+        GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+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 Program or any portion
+of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+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 Program, 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 Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) 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; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, 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 executable.  However, as a
+special exception, the source code 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.
+
+If distribution of executable or 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 counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program 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.
+
+  5. 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 Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program 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 to
+this License.
+
+  7. 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 Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program 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 Program.
+
+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.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program 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.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies 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 Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, 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
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. 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 PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
diff --git a/media/js/jwysiwyg/MIT-LICENSE.txt b/media/js/jwysiwyg/MIT-LICENSE.txt
new file mode 100644
index 0000000..c2b8a12
--- /dev/null
+++ b/media/js/jwysiwyg/MIT-LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Juan M Martínez
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/media/js/jwysiwyg/README.rst b/media/js/jwysiwyg/README.rst
new file mode 100644
index 0000000..2b92856
--- /dev/null
+++ b/media/js/jwysiwyg/README.rst
@@ -0,0 +1,615 @@
+=========================
+jWYSIWYG 0.97 User Manual
+=========================
+
+Copyright (c) 2009-2010 Juan M Martínez, 2011 Akzhan Abdulin and all contributors
+
+Dual licensed under the `MIT
+<http://github.com/akzhan/jwysiwyg/raw/master/MIT-LICENSE.txt>`_ and `GPL
+<http://github.com/akzhan/jwysiwyg/raw/master/GPL-LICENSE.txt>`_ licenses.
+
+.. contents::
+
+========
+Requires
+========
+
+jQuery 1.3.2 or higher (tested with jQuery 1.5.2).
+
+Note that we do not support officially jQuery 1.6, but will support jQuery 1.6.1.
+
+========
+Supports
+========
+
+`jQuery UI Dialog
+<http://jqueryui.com/demos/dialog/>`_ and `SimpleModal
+<http://www.ericmmartin.com/projects/simplemodal/>`_ for insertTable and insertImage buttons.
+
+`jQuery UI Resizable
+<http://jqueryui.com/demos/resizable/>`_ for resize of editor.
+
+===========
+Tested with
+===========
+
+Tested in Safari 4, Firefox 3.5, Chrome 4.0, Internet Explorer 8.
+
+.. note::
+
+    In IE transparent GIF may have an `issue <https://github.com/akzhan/jwysiwyg/issues#issue/28>`_
+    to resolve it:
+
+    * open jquery.wysiwyg.css
+    * replace string *jquery.wysiwyg.gif* with *jquery.wysiwyg.no-alpha.gif*
+
+Some minor bugs still exist while 1.0 not reached.
+
+========
+Web site
+========
+
+`jWYSIWYG on GitHub <http://github.com/akzhan/jwysiwyg>`_
+
+===========
+Quick Start
+===========
+
+The following code creates a jWYSIWYG editor with the default options::
+
+    <script type="text/javascript" src="jquery.wysiwyg.js"></script>
+    <script type="text/javascript">
+    $(function() {
+        $('#wysiwyg').wysiwyg();
+    });
+    </script>
+
+    <textarea id="wysiwyg"></textarea>
+
+
+Activating Hidden Controls
+--------------------------
+
+Toolbar buttons are selected with the ``controls`` configuration option::
+
+    $('#wysiwyg').wysiwyg({
+        controls: {
+            strikeThrough: { visible: true },
+            underline: { visible: true },
+            subscript: { visible: true },
+            superscript: { visible: true }
+        }
+    });
+
+A full list of available ``controls`` options is available in ____.
+
+
+Adding Custom Controls
+----------------------
+
+Custom controls can also be specified with the ``controls`` option::
+
+    $('#wysiwyg').wysiwyg({
+        controls: {
+            alertSep: { separator: true },
+            alert: {
+                visible: true,
+                exec: function() { alert('Hello World'); },
+                className: 'alert'
+            }
+        }
+    })
+
+Another way::
+
+    $('#wysiwyg').wysiwyg("addControl",
+        "controlName",
+        {
+            icon: "/path/to/icon.png",
+            exec:  function() { alert('Hello World'); }
+        }
+    );
+
+
+Styling the Content Inside the Editor
+-------------------------------------
+
+To apply a CSS stylesheet to the content inside the editor, use the ``css``
+configuration option::
+
+    $('#wysiwyg').wysiwyg({
+        css: 'editor.css'
+    });
+
+The editor will not inherit the style of the containing page anyway, you must
+specify a CSS file to apply to it.
+
+
+Clear the Editor
+----------------
+
+To clear the editor at any time::
+
+    $('#wysiwyg').wysiwyg('clear');
+
+Focus the Editor
+----------------
+
+To focus on the editor at any time::
+
+    $('#wysiwyg').wysiwyg('focus');
+
+
+Insert an Image
+---------------
+
+When the #insertImage link is clicked, insert an image inline at the current
+cursor location in the editor::
+
+    $('a[href="#insertImage"]').click(function() {
+        $('#wysiwyg').wysiwyg('insertImage', 'img/hourglass.gif');
+    });
+
+.. note::
+
+    Include file wysiwyg.image.js to provide this function
+
+Insert an Image with Attributes
+-------------------------------
+
+Add some additional attributes to the image, as well::
+
+    $('a[href="#insertImage"]').click(function() {
+        $('#wysiwyg').wysiwyg('insertImage', 'img/hourglass.gif', { 'class': 'myClass', 'className': 'myClass' });
+    });
+
+Note that the class attribute is added twice, because the ``class`` DOM
+attribute is recognized on IE but not on Firefox, and the ``className``
+attribute is recognized on Firefox but not on IE.
+
+.. note::
+
+    Include file wysiwyg.image.js to provide this function
+
+======================
+Advanced Customization
+======================
+
+Available Configuration Options
+-------------------------------
+
+Additional configuration options are specified by passing a javascript object to
+the wysiwyg() function when it is first called on a textarea. Available keys are:
+
+``html``
+    A string containing the source HTML code used inside the editor's iframe.
+    This is a template where ``INITIAL_CONTENT`` later replaced by the
+    appropriate code for the editor instance, so this string must be present in
+    this option.
+
+``debug``
+    A boolean, enabling or disabling debugging.
+
+``css``
+    A string containing the path to a CSS file which will be included in the
+    editor's iframe.
+
+``autoGrow``
+    A boolean.
+
+``autoSave``
+    A boolean. If ``true``, the editor will copy its contents back to the
+    original textarea anytime it is updated. If ``false``, this must be done
+    manually.
+
+``brIE``
+    A boolean. If ``true``, a ``<br/>`` will be inserted for a newline in IE.
+    This is also true for other browsers and may seem a bit peculiar for users 
+    if they use "Header 1", or similar styles. The ENTER key will no longer reset
+    the style to "Paragraph", but continue writing "Header 1" until users explicitly
+    choose "Paragraph" in the toolbar.
+
+``formHeight``
+    An integer. Height of dialog form.
+
+``formWidth``
+    An integer. Width of dialog form.
+
+``iFrameClass``
+    A string, that specify ``class`` attribute of iframe element
+
+``initialContent``
+    A string. Default ``<p>Initial Content</p>``
+
+``maxHeight``
+    An integer. autoGrow max height
+
+``maxLength``
+    An integer. The maxlength attribute specifies the maximum length (in characters) that the editor will accept.  This number won't include any HTML markup.
+
+``messages``
+    A javascript object with key, value pairs setting custom messages for
+    certain conditions. Available keys are:
+    
+    * ``nonSelection``: Message to display when the Create Link button is
+      pressed with no text selected.
+
+``plugins``
+    ``autoload``
+        A bool or object. If ``false`` then no autoload, if ``true`` then defaults
+        is used, otherwise you can override provided defaults
+    
+    ``i18n``
+        A bool or object. If ``false`` then no internationalization, otherwise you
+        can set default language ``{ lang: "ru" }``
+    
+    ``rmFormat``
+        ``rmMsWordMarkup``
+            A bool. If true then remove MS Word markup is used
+        
+        .. note::
+            To run rmFormat by clicking on remove format control or using triggerControl
+            you also should set $.wysiwyg.rmFormat.enabled = true before they being used
+
+``toolbarHtml``
+    A string containing the source HTML code
+
+``resizeOptions``
+    A boolean. Depends on **jquery.ui.resizable**. If ``false`` the editor will
+    not be resizeable.
+
+``removeHeadings``
+    A boolean. If ``true``, the editor will remove also headings when remove format 
+    is used. Otherwise headings will not be removed. Default is ``false``.
+
+``rmUnusedControls``
+    A boolean. If ``true``, the editor will remove all controls which are not
+    mentioned in ``controls`` option.
+    In this example only bold control will be available in toolbar::
+    
+        $("textarea").wysiwyg({
+            rmUnusedControls: true,
+            controls: {
+                bold: { visible : true },
+            }
+        });
+    
+    See also `help/examples/10-custom-controls.html
+    <https://github.com/akzhan/jwysiwyg/blob/master/help/examples/10-custom-controls.html>`_
+
+``rmUnwantedBr``
+    A boolean. If ``true``, the editor will not add extraneous ``<br/>`` tags.
+
+``tableFiller``
+    A string. Default ``Lorem ipsum``
+
+``events``
+    A javascript object specifying events. Events are specified as ``key: value``
+    pairs in the javascript object,
+    where the key is the name of the event and the value is javascript function::
+
+        {
+            click: function(event) {
+                if ($("#click-inform:checked").length > 0) {
+                    event.preventDefault();
+                    alert("You have clicked jWysiwyg content!");
+                }
+            }
+        }
+
+``controls``
+    A javascript object specifying control buttons and separators to include in
+    the toolbar. This can consist of built-in controls and custom controls.
+    Controls are specified as key, value pairs in the javascript object, where
+    the key is the name of the control and the value is another javascript
+    object with a specific signature.
+    
+    The signature of a control object looks like this::
+    
+        {
+            // If true, this object will just be a vertical separator bar,
+            // and no other keys should be set.
+            separator: { true | false },
+            
+            // If false, this button will be hidden.
+            visible: { true | false },
+            
+            // In toolbar there are groups of controls. At the end of each group
+            // is placed an auto separator.
+            // Set which group to assign or create a new group with unique number.
+            groupIndex: { number },
+            
+            // Tags are used to hilight control when current selection
+            // is wrapped by one of these tags.
+            tags: ['b', 'strong'],
+            
+            // CSS classes are used to hilight control when current selection
+            // has chosen css classes.
+            css: {
+                textAlign: 'left',
+                fontStyle: 'italic',
+                ...
+            },
+            
+            // Function to execute when this command is triggered. If this
+            // key is provided, CSS classes/tags will not be applied, and
+            // any built-in functionality will not be triggered.
+            exec: function() { ... },
+            
+            // Hotkeys binds on keydown event
+            hotkey: {
+            	"alt":   1 | 0,
+            	"ctrl":  1 | 0,
+            	"shift": 1 | 0,
+            	"key":   { event.keyCode }
+            },
+            
+            // Tooltip
+            tooltip: { string },
+            
+            // Path to icon
+            icon: { string },
+            
+            // Automatically set when custom control is used
+            custom: { true | false }
+        }
+    
+    If you wish to override the default behavior of built-in controls, you can
+    do so by specifying only the keys which you wish to change the behavior of.
+    For example, since the ``strikeThrough`` control is not visibly by default,
+    to enable it we only have to specify::
+    
+        strikeThrough: { visible: true }
+    
+    Additionally, custom controls may be specified by adding new keys with the
+    same signature as a control object. For example, if we wish to create a
+    ``quote`` control which creates ``<blockquote>`` tags, we could do specify
+    this key::
+    
+        quote: { visible: true, tags: ['blockquote'], css: { class: 'quote', className: 'quote' } }
+    
+    Note that when defining custom controls, you will most likely want to add
+    additional CSS to style the resulting toolbar button. The CSS to style a
+    button looks like this::
+    
+        div.wysiwyg ul.toolbar li a.quote {
+            background: url('quote-button.gif') no-repeat 0px 0px;
+        }
+    
+    Available built-in controls are:
+    
+    * ``bold``: Make text bold.
+    * ``italic``: Make text italic.
+    * ``strikeThrough``: Make text strikethrough.
+    * ``underline``: Make text underlined.
+    * ``justifyLeft``: Left-align text.
+    * ``justifyCenter``: Center-align text.
+    * ``justifyRight``: Right-align text.
+    * ``justifyFull``: Justify text.
+    * ``indent``: Indent text.
+    * ``outdent``: Outdent text.
+    * ``subscript``: Make text subscript.
+    * ``superscript``: Make text superscript.
+    * ``undo``: Undo last action.
+    * ``redo``: Redo last action.
+    * ``insertOrderedList``: Insert ordered (numbered) list.
+    * ``insertUnorderedList``: Insert unordered (bullet) list.
+    * ``insertHorizontalRule``: Insert horizontal rule.
+    * ``createLink``: Create a link from the selected text, by prompting the
+      user for the URL.
+    * ``insertImage``: Insert an image, by prompting the user for the image path.
+    * ``h1``: Make text an h1 header
+    * ``h2``: Make text an h2 header
+    * ``h3``: Make text an h3 header
+    * ``paragraph``: Make paragraph from text or h1-h6 headers
+    * ``cut``: Cut selected text.
+    * ``copy``: Copy selected text.
+    * ``paste``: Paste from clipboard.
+    * ``increaseFontSize``: Increase font size.
+    * ``decreaseFontSize``: Decrease font size.
+    * ``html``: Show the original textarea with HTML source. When clicked again,
+      copy the textarea code back to the jWYSIWYG editor.
+    * ``removeFormat``: Remove all formatting.
+    * ``insertTable``: Insert a table, by prompting the user for the table
+      settings.
+
+
+Available Built-In Functions
+----------------------------
+
+Built-in editor functions can be triggered manually with the
+``.wysiwyg("functionName"[, arg1[, arg2[, ...]]])`` call.
+
+* addControl(name, settings)
+* clear
+* createLink(szURL)
+    .. note::
+
+        Include file wysiwyg.link.js to provide this function
+
+* destroy
+* document
+* getContent
+* insertHtml(szHTML)
+* insertImage(szURL, attributes)
+    .. note::
+
+        Include file wysiwyg.image.js to provide this function
+
+* insertTable(colCount, rowCount, filler)
+    .. note::
+
+        Include file wysiwyg.table.js to provide this function
+
+* removeFormat
+* save - save changes from editor to related textarea
+* selectAll
+* setContent
+
+For example, if you want to set new content to original textarea, and then
+remove the jWYSIWYG editor to bring original textarea back::
+
+    $("#original").wysiwyg("setContent", "<p>My new content</p>").wysiwyg("destroy")
+    
+=========================
+The jWYSIWYG File Manager
+=========================
+
+jWYSIWYG has a simple plugin for server-side ajax file management.
+The plugin uses a set of predefined server-side handlers for retrieving content of remote directories.
+The plugin supports four basic actions:
+
+1. Upload files
+2. Create directories
+3. Rename files
+4. Remove files
+
+Setup
+-----
+
+The File Manager plugin needs to be setup on server-side before it can be used.
+Along with the jWYSIWYG source code, come handlers for different languages, so you can use it on different platforms.
+The handler that shuold be set with .setAjaxHandler("...") is the one that contains the ``authentication`` response. This is usually the "file_manager.*" handler.
+Note that usually you will need to rewrite some of the handlers code, so it will fit your application.
+
+After you setup the server-side part, you need to add the javascript and css files for the file manager: ::
+
+	<link rel="stylesheet" href="../../plugins/fileManager/wysiwyg.fileManager.css" type="text/css"/> 
+	<script type="text/javascript" src="../../plugins/wysiwyg.fileManager.js"></script> 
+
+Then, all you have to do is start using it, as explained below.
+
+Usage
+-----
+
+The file manager has pretty simple syntax, and it uses two basic methods:
+
+* $.wysiwyg.fileManager.setAjaxHandler()
+* $.wysiwyg.fileManager.init()
+
+And another important boolean value:
+
+* $.wysiwyg.fileManager.ready
+
+First, you must set an ajax handler. The plugin does not force you to use its official available handlers, it enables you to set your own route for the handler.
+In order to initiate the file manager interface, you should call 'init()'. The init() method will not fire until there is an ajax handler.
+This may look something like: ::
+
+	// First we set the handler:
+	$.wysiwyg.fileManager.setAjaxHandler("http://example.com/jwysiwyg/handler.php");
+
+	// Then we fire-up the interface:
+	$.wysiwyg.fileManager.init(function (selected) {
+		alert(selected);
+	});
+	// The init() method takes a callback function, and returns the URL of the selected file.
+
+
+For convinience, the setAjaxHandler() method returns the $.wysiwyg.fileManager object, so it can be used in a short form: ::
+
+	$.wysiwyg.fileManager.setAjaxHandler("http://example.com/jwysiwyg/handler.php").init(function (selected) {
+		alert(selected);
+	});
+
+Use Within Other Plugins
+------------------------
+
+In addition to its stand-alone usage, the File Manager plugin can be incorporated quite easily into other plugins.
+Actually, the only thing the should be checked before using the plugin, is whether its ajax handler is set: ::
+
+	if ($.wysiwyg.fileManager.ready) {
+		$.wysiwyg.fileManager.init(function (selected) {
+			alert(selected);
+		});	
+	}
+
+This method exists in order to assure third-party plugins that the file manager is ready-to-go.
+
+.. note::
+		
+		In order to display the file manager icon, one can use a div with a "wysiwyg-fileManager" class.
+
+Sample custom File Manager Control
+----------------------------------
+
+This is a quick example of how to use the jWYSIWYG editor with a custom file manager control: ::
+
+	$('#wysiwyg').wysiwyg({
+		controls: {
+			'fileManager': { 
+				visible: true,
+				groupIndex: 12,
+				tooltip: "File Manager",
+				exec: function () {
+					$.wysiwyg.fileManager.init(function (file) {
+						file ? alert(file) : alert("No file selected.");
+					});
+				}
+			}
+		}
+	});
+	$.wysiwyg.fileManager.setAjaxHandler("http://example.com/jwysiwyg/handler.php");
+
+The file manager's css file contains the icon for this control, so it is recommended that if you use a custom control, you will name it "fileManager".
+
+Writing Custom Handlers
+-----------------------
+
+It is possible to use custom ajax handlers that you write, with the File Manager.
+As mentioned before, the file manager enables you to set the ajax handler you want. The only thing that is required, is for the handler to follow the protocol documented here:
+
+https://github.com/akzhan/jwysiwyg/wiki/File-Manager-API
+
+
+====================================
+Customizing the Editor Look and Feel
+====================================
+
+
+============
+How it Works
+============
+
+When jWYSIWYG is called on a textarea, it does the following things:
+
+1. Creates an additional container div to encapsulate the new editor.
+2. Hides the existing textarea.
+3. Creates an iframe inside the container div, populated with editor window and
+   toolbar.
+4. When ``saveContent()`` is called, copy its content to existing textarea.
+5. Listen for ``submit`` event of closest form to apply ``saveContent()`` before
+   form submition.
+
+=======
+Plugins
+=======
+
+Read document `help/docs/plugins.rst
+<https://github.com/akzhan/jwysiwyg/blob/master/help/docs/plugins.rst>`_
+
+============
+Contributing
+============
+
+Read document `help/docs/contributing.rst
+<https://github.com/akzhan/jwysiwyg/blob/master/help/docs/contributing.rst>`_
+
+====================
+Additional Resources
+====================
+
+Look at http://akzhan.github.com/jwysiwyg/examples/
+
+Dive into *help* folder that contains:
+
+* bin
+    compile.sh - to compile all files into one jquery.wysiwyg.full.js
+    (jquery.wysiwyg.js, controls/`*`, i18n/`*` and plugins/`*`)
+* docs
+    documents to help contributors
+* examples
+    latest examples
+* lib
+    to run examples and tests
+* tests
+    files that demonstrate some issues
diff --git a/media/js/jwysiwyg/ajax-loader.gif b/media/js/jwysiwyg/ajax-loader.gif
new file mode 100644
index 0000000..5b33f7e
Binary files /dev/null and b/media/js/jwysiwyg/ajax-loader.gif differ
diff --git a/media/js/jwysiwyg/controls/wysiwyg.colorpicker.js b/media/js/jwysiwyg/controls/wysiwyg.colorpicker.js
new file mode 100644
index 0000000..10c1027
--- /dev/null
+++ b/media/js/jwysiwyg/controls/wysiwyg.colorpicker.js
@@ -0,0 +1,278 @@
+/**
+ * Controls: Colorpicker plugin
+ * 
+ * Depends on jWYSIWYG, (farbtastic || other colorpicker plugins)
+ */
+(function ($) {
+	"use strict";
+
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.colorpicker.js depends on $.wysiwyg";
+	}
+
+	if (!$.wysiwyg.controls) {
+		$.wysiwyg.controls = {};
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	$.wysiwyg.controls.colorpicker = {
+		modalOpen: false,
+		color: {
+			back: {
+				prev: "#ffffff",
+				palette: []
+			},
+			fore: {
+				prev: "#123456",
+				palette: []
+			}
+		},
+
+		addColorToPalette: function (type, color) {
+			if (-1 === $.inArray(color, this.color[type].palette)) {
+				this.color[type].palette.push(color);
+			} else {
+				this.color[type].palette.sort(function (a, b) {
+					if (a === color) {
+						return 1;
+					}
+
+					return 0;
+				});
+			}
+		},
+
+		init: function (Wysiwyg) {
+			if ($.wysiwyg.controls.colorpicker.modalOpen === true) {
+				return false;
+			} else {
+				$.wysiwyg.controls.colorpicker.modalOpen = true;
+			}
+			var self = this, elements, dialog, colorpickerHtml, dialogReplacements, key, translation;
+
+			dialogReplacements = {
+				legend: "Colorpicker",
+				color: "Color",
+				applyForeColor: "Set Text",
+				applyBgColor: "Set Background",
+				reset: "Cancel"
+			};
+
+			colorpickerHtml = '<form class="wysiwyg"><fieldset><legend>{legend}</legend>' +
+				'<ul class="palette"></ul>' +
+				'<label>{color}: <input type="text" name="color" value="#123456"/></label>' +
+				'<div class="wheel"></div>' +
+				'<input type="button" class="button applyForeColor" value="{applyForeColor}"/> ' +
+				'<input type="button" class="button applyBgColor" value="{applyBgColor}"/> ' +
+				'<input type="reset" value="{reset}"/></fieldset></form>';
+
+			for (key in dialogReplacements) {
+				if ($.wysiwyg.i18n) {
+					translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs.colorpicker");
+
+					if (translation === dialogReplacements[key]) { // if not translated search in dialogs 
+						translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs");
+					}
+
+					dialogReplacements[key] = translation;
+				}
+
+				colorpickerHtml = colorpickerHtml.replace("{" + key + "}", dialogReplacements[key]);
+			}
+
+			if ($.modal) {
+				elements = $(colorpickerHtml);
+
+				if ($.farbtastic) {
+					this.renderPalette(elements, "fore");
+					elements.find(".wheel").farbtastic(elements.find("input:text"));
+				}
+
+				$.modal(elements.html(), {
+					maxWidth: Wysiwyg.defaults.formWidth,
+					maxHeight: Wysiwyg.defaults.formHeight,
+					overlayClose: true,
+
+					onShow: function (dialog) {
+						$("input:submit", dialog.data).click(function (e) {
+							var color = $('input[name="color"]', dialog.data).val();
+							self.color.fore.prev = color;
+							self.addColorToPalette("fore", color);
+
+							if ($.browser.msie) {
+								Wysiwyg.ui.returnRange();
+							}
+
+							Wysiwyg.editorDoc.execCommand('ForeColor', false, color);
+							$.modal.close();
+							return false;
+						});
+						$("input:reset", dialog.data).click(function (e) {
+							if ($.browser.msie) {
+								Wysiwyg.ui.returnRange();
+							}
+
+							$.modal.close();
+							return false;
+						});
+						$("fieldset", dialog.data).click(function (e) {
+							e.stopPropagation();
+						});
+					},
+
+					onClose: function (dialog) {
+						$.wysiwyg.controls.colorpicker.modalOpen = false;
+						$.modal.close();
+					}
+				});
+			} else if ($.fn.dialog) {
+				elements = $(colorpickerHtml);
+
+				if ($.farbtastic) {
+					this.renderPalette(elements, "fore");
+					elements.find(".wheel").farbtastic(elements.find("input:text"));
+				}
+
+				dialog = elements.appendTo("body");
+				var buttonSetup = {};
+				buttonSetup[ dialogReplacements['applyForeColor'] ] = function() {
+					dialog.find('input.applyForeColor').click();
+				};
+				buttonSetup[ dialogReplacements['applyBgColor'] ] = function() {
+					dialog.find('input.applyBgColor').click();
+				};
+				buttonSetup[ dialogReplacements['reset'] ] = function() {
+					dialog.find('input:reset').click();
+				};
+				
+				dialog.dialog({
+					modal: true,
+					open: function (event, ui) {
+						$("input:button,input:reset", elements).hide();
+						$("input.applyForeColor,input.applyBgColor", elements).click(function (e) {
+							var color = $('input[name="color"]', dialog).val();
+							self.color.fore.prev = color;
+							self.addColorToPalette("fore", color);
+
+							if ($.browser.msie) {
+								Wysiwyg.ui.returnRange();
+							}
+
+							if ($(this).hasClass('applyForeColor')) {
+								Wysiwyg.editorDoc.execCommand('ForeColor', false, color);
+							} else {
+								if ($.browser.msie)
+									Wysiwyg.editorDoc.execCommand('BackColor', false, color);
+								else
+									Wysiwyg.editorDoc.execCommand('hilitecolor',false,color);							
+							}							
+							
+							$(dialog).dialog("close");
+							return false;
+						});
+						$("input:reset", elements).click(function (e) {
+							if ($.browser.msie) {
+								Wysiwyg.ui.returnRange();
+							}
+
+							$(dialog).dialog("close");
+							return false;
+						});
+						$('fieldset', elements).click(function (e) {
+							e.stopPropagation();
+						});
+					},
+					buttons : buttonSetup,
+					close: function (event, ui) {
+						$.wysiwyg.controls.colorpicker.modalOpen = false;
+						dialog.dialog("destroy");
+						dialog.remove();
+					}
+				});
+			} else {
+				if ($.farbtastic) {
+					elements = $("<div/>")
+						.css({"position": "fixed",
+							"z-index": 2000,
+							"left": "50%", "top": "50%", "background": "rgb(0, 0, 0)",
+							"margin-top": -1 * Math.round(Wysiwyg.defaults.formHeight / 2),
+							"margin-left": -1 * Math.round(Wysiwyg.defaults.formWidth / 2)})
+						.html(colorpickerHtml);
+					this.renderPalette(elements, "fore");
+					elements.find("input[name=color]").val(self.color.fore.prev);
+					elements.find(".wheel").farbtastic(elements.find("input:text"));
+					$("input.applyForeColor,input.applyBgColor", elements).click(function (event) {
+						var color = $('input[name="color"]', elements).val();
+						self.color.fore.prev = color;
+						self.addColorToPalette("fore", color);
+
+						if ($.browser.msie) {
+							Wysiwyg.ui.returnRange();
+						}
+
+						if ($(this).hasClass('applyForeColor')) {
+							Wysiwyg.editorDoc.execCommand('ForeColor', false, color);
+						} else {
+							if ($.browser.msie)
+								Wysiwyg.editorDoc.execCommand('BackColor', false, color);
+							else
+								Wysiwyg.editorDoc.execCommand('hilitecolor',false,color);							
+						}
+
+						$(elements).remove();
+						$.wysiwyg.controls.colorpicker.modalOpen = false;
+						return false;
+					});
+					$("input:reset", elements).click(function (event) {
+
+						if ($.browser.msie) {
+							Wysiwyg.ui.returnRange();
+						}
+
+						$(elements).remove();
+						$.wysiwyg.controls.colorpicker.modalOpen = false;
+						return false;
+					});
+					$("body").append(elements);
+					elements.click(function (e) {
+						e.stopPropagation();
+					});
+				}
+			}
+		},
+
+		renderPalette: function (jqObj, type) {
+			var palette = jqObj.find(".palette"),
+				bind = function () {
+					var color = $(this).text();
+					jqObj.find("input[name=color]").val(color);
+					// farbtastic binds on keyup
+					if ($.farbtastic) {
+						jqObj.find("input[name=color]").trigger("keyup");
+					}
+				},
+				colorExample,
+				colorSelect,
+				i;
+
+			for (i = this.color[type].palette.length - 1; i > -1; i -= 1) {
+				colorExample = $("<div/>").css({
+					"float": "left",
+					"width": "16px",
+					"height": "16px",
+					"margin": "0px 5px 0px 0px",
+					"background-color": this.color[type].palette[i]
+				});
+
+				colorSelect = $("<li>" + this.color[type].palette[i] + "</li>")
+					.css({"cursor": "pointer", "list-style": "none"})
+					.append(colorExample)
+					.bind("click.wysiwyg", bind);
+
+				palette.append(colorSelect).css({"margin": "0px", "padding": "0px"});
+			}
+		}
+	};
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/controls/wysiwyg.cssWrap.js b/media/js/jwysiwyg/controls/wysiwyg.cssWrap.js
new file mode 100644
index 0000000..0215f02
--- /dev/null
+++ b/media/js/jwysiwyg/controls/wysiwyg.cssWrap.js
@@ -0,0 +1,134 @@
+/**
+ * Controls: Element CSS Wrapper plugin
+ *
+ * Depends on jWYSIWYG
+ * 
+ * By Yotam Bar-On (https://github.com/tudmotu)
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.cssWrap.js depends on $.wysiwyg";
+	}
+	/* For core enhancements #143
+	$.wysiwyg.ui.addControl("cssWrap", {
+		visible : false,
+		groupIndex: 6,
+		tooltip: "CSS Wrapper",
+		exec: function () { 
+				$.wysiwyg.controls.cssWrap.init(this);
+			}
+	}
+	*/	
+	if (!$.wysiwyg.controls) {
+		$.wysiwyg.controls = {};
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	$.wysiwyg.controls.cssWrap = {
+		init: function (Wysiwyg) {
+			var self = this, formWrapHtml, key, translation,
+				dialogReplacements = {
+					legend	: "Wrap Element",
+					wrapperType : "Wrapper Type",
+					ID : "ID",
+					"class" : "Class",
+					wrap  : "Wrap",
+					unwrap: "Unwrap",
+					cancel   : "Cancel"
+				};
+
+			formWrapHtml = '<form class="wysiwyg"><fieldset><legend>{legend}</legend>' +
+				'<div class="wysiwyg-dialogRow"><label>{wrapperType}:  <select name="type"><option value="span">Span</option><option value="div">Div</option></select></label></div>' +
+				'<div class="wysiwyg-dialogRow"><label>{ID}:  <input name="id" type="text" /></label></div>' + 
+				'<div class="wysiwyg-dialogRow"><label>{class}:  <input name="class" type="text" /></label></div>' +
+				'<div class="wysiwyg-dialogRow"><input type="button" class="button cssWrap-unwrap" style="display:none;" value="{unwrap}"/></label>' +
+				'<input type="submit"  class="button cssWrap-submit" value="{wrap}"/></label>' +
+				'<input type="reset" class="button cssWrap-cancel" value="{cancel}"/></div></fieldset></form>';
+
+			for (key in dialogReplacements) {
+				if ($.wysiwyg.i18n) {
+					translation = $.wysiwyg.i18n.t(dialogReplacements[key]);
+					if (translation === dialogReplacements[key]) { // if not translated search in dialogs 
+						translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs");
+					}
+					dialogReplacements[key] = translation;
+				}
+				formWrapHtml = formWrapHtml.replace("{" + key + "}", dialogReplacements[key]);
+			}
+			if (!$(".wysiwyg-dialog-wrapper").length) {
+				$(formWrapHtml).appendTo("body");
+				$("form.wysiwyg").dialog({
+					modal: true,
+					open: function (ev, ui) {
+						var $this = $(this), range	= Wysiwyg.getInternalRange(), common, $nodeName;
+						// We make sure that there is some selection:
+						if (range) {
+							if ($.browser.msie) {
+								Wysiwyg.ui.focus();
+							}
+							common	= $(range.commonAncestorContainer);
+						} else {
+							alert("You must select some elements before you can wrap them.");
+							$this.dialog("close");
+							return 0;
+						}
+						$nodeName = range.commonAncestorContainer.nodeName.toLowerCase();
+						// If the selection is already a .wysiwygCssWrapper, then we want to change it and not double-wrap it.
+						if (common.parent(".wysiwygCssWrapper").length) {
+							alert(common.parent(".wysiwygCssWrapper").get(0).nodeName.toLowerCase());
+							$this.find("select[name=type]").val(common.parent(".wysiwygCssWrapper").get(0).nodeName.toLowerCase());
+							$this.find("select[name=type]").attr("disabled", "disabled");
+							$this.find("input[name=id]").val(common.parent(".wysiwygCssWrapper").attr("id"));
+							$this.find("input[name=class]").val(common.parent(".wysiwygCssWrapper").attr("class").replace('wysiwygCssWrapper ', ''));
+							// Add the "unwrap" button:
+							$("form.wysiwyg").find(".cssWrap-unwrap").show();
+							$("form.wysiwyg").find(".cssWrap-unwrap").click(function (e) {
+								e.preventDefault();
+								if ($nodeName !== "body") {
+									common.unwrap();
+								}
+								$this.dialog("close");
+								return 1;
+							});
+						}
+						// Submit button.
+						$("form.wysiwyg").find(".cssWrap-submit").click(function (e) {
+							e.preventDefault();
+							var $wrapper = $("form.wysiwyg").find("select[name=type]").val(),
+								$id = $("form.wysiwyg").find("input[name=id]").val(),
+								$class = $("form.wysiwyg").find("input[name=class]").val();
+
+							if ($nodeName !== "body") {
+								// If the selection is already a .wysiwygCssWrapper, then we want to change it and not double-wrap it.
+								if (common.parent(".wysiwygCssWrapper").length) {
+									common.parent(".wysiwygCssWrapper").attr("id", $class);
+									common.parent(".wysiwygCssWrapper").attr("class", $class);
+								} else {
+									common.wrap("<" + $wrapper + " id=\"" + $id + "\" class=\"" + "wysiwygCssWrapper " + $class + "\"/>");
+								}
+							} else {
+								// Currently no implemntation for if $nodeName == 'body'.
+							}
+							$this.dialog("close");
+						});
+						// Cancel button.
+						$("form.wysiwyg").find(".cssWrap-cancel").click(function (e) {
+							e.preventDefault();
+							$this.dialog("close");
+							return 1;
+						});
+					},
+					close: function () {
+						$(this).dialog("destroy");
+						$(this).remove();
+					}
+				});
+				Wysiwyg.saveContent();
+			}
+			$(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");
+			return 1;
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/controls/wysiwyg.image.js b/media/js/jwysiwyg/controls/wysiwyg.image.js
new file mode 100644
index 0000000..d0b9162
--- /dev/null
+++ b/media/js/jwysiwyg/controls/wysiwyg.image.js
@@ -0,0 +1,285 @@
+/**
+ * Controls: Image plugin
+ *
+ * Depends on jWYSIWYG
+ */
+(function ($) {
+	"use strict";
+
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.image.js depends on $.wysiwyg";
+	}
+
+	if (!$.wysiwyg.controls) {
+		$.wysiwyg.controls = {};
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	$.wysiwyg.controls.image = {
+		groupIndex: 6,
+		visible: true,
+		exec: function () {
+			$.wysiwyg.controls.image.init(this);
+		},
+		tags: ["img"],
+		tooltip: "Insert image",
+		init: function (Wysiwyg) {
+			var self = this, elements, adialog, dialog, formImageHtml, regexp, dialogReplacements, key, translation,
+				img = {
+					alt: "",
+					self: Wysiwyg.dom ? Wysiwyg.dom.getElement("img") : null, // link to element node
+					src: "http://",
+					title: ""
+				};
+
+			dialogReplacements = {
+				legend	: "Insert Image",
+				preview : "Preview",
+				url     : "URL",
+				title   : "Title",
+				description : "Description",
+				width   : "Width",
+				height  : "Height",
+				original : "Original W x H",
+				"float"	: "Float",
+				floatNone : "None",
+				floatLeft : "Left",
+				floatRight : "Right",
+				submit  : "Insert Image",
+				reset   : "Cancel",
+				fileManagerIcon : "Select file from server"
+			};
+
+			formImageHtml = '<form class="wysiwyg" id="wysiwyg-addImage"><fieldset>' +
+				'<div class="form-row"><span class="form-row-key">{preview}:</span><div class="form-row-value"><img src="" alt="{preview}" style="margin: 2px; padding:5px; max-width: 100%; overflow:hidden; max-height: 100px; border: 1px solid rgb(192, 192, 192);"/></div></div>' +
+				'<div class="form-row"><label for="name">{url}:</label><div class="form-row-value"><input type="text" name="src" value=""/>';
+
+			if ($.wysiwyg.fileManager && $.wysiwyg.fileManager.ready) {
+				// Add the File Manager icon:
+				formImageHtml += '<div class="wysiwyg-fileManager" title="{fileManagerIcon}"/>';
+			}
+
+			formImageHtml += '</div></div>' +
+				'<div class="form-row"><label for="name">{title}:</label><div class="form-row-value"><input type="text" name="imgtitle" value=""/></div></div>' +
+				'<div class="form-row"><label for="name">{description}:</label><div class="form-row-value"><input type="text" name="description" value=""/></div></div>' +
+				'<div class="form-row"><label for="name">{width} x {height}:</label><div class="form-row-value"><input type="text" name="width" value="" class="width-small"/> x <input type="text" name="height" value="" class="width-small"/></div></div>' +
+				'<div class="form-row"><label for="name">{original}:</label><div class="form-row-value"><input type="text" name="naturalWidth" value="" class="width-small" disabled="disabled"/> x ' +
+				'<input type="text" name="naturalHeight" value="" class="width-small" disabled="disabled"/></div></div>' +
+				'<div class="form-row"><label for="name">{float}:</label><div class="form-row-value"><select name="float">' +
+				'<option value="">{floatNone}</option>' +
+				'<option value="left">{floatLeft}</option>' +
+				'<option value="right">{floatRight}</option></select></div></div>' +
+				'<div class="form-row form-row-last"><label for="name"></label><div class="form-row-value"><input type="submit" class="button" value="{submit}"/> ' +
+				'<input type="reset" value="{reset}"/></div></div></fieldset></form>';
+
+			for (key in dialogReplacements) {
+				if ($.wysiwyg.i18n) {
+					translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs.image");
+
+					if (translation === dialogReplacements[key]) { // if not translated search in dialogs 
+						translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs");
+					}
+
+					dialogReplacements[key] = translation;
+				}
+
+				regexp = new RegExp("{" + key + "}", "g");
+				formImageHtml = formImageHtml.replace(regexp, dialogReplacements[key]);
+			}
+
+			if (img.self) {
+				img.src    = img.self.src    ? img.self.src    : "";
+				img.alt    = img.self.alt    ? img.self.alt    : "";
+				img.title  = img.self.title  ? img.self.title  : "";
+				img.width  = img.self.width  ? img.self.width  : "";
+				img.height = img.self.height ? img.self.height : "";
+				img.styleFloat = $(img.self).css("float");
+			}
+
+			adialog = new $.wysiwyg.dialog(Wysiwyg, {
+				"title"   : dialogReplacements.legend,
+				"content" : formImageHtml
+			});
+
+			$(adialog).bind("afterOpen", function (e, dialog) {
+				dialog.find("form#wysiwyg-addImage").submit(function (e) {
+					e.preventDefault();
+					self.processInsert(dialog.container, Wysiwyg, img);
+
+					adialog.close();
+					return false;
+				});
+
+				// File Manager (select file):
+				if ($.wysiwyg.fileManager) {
+					$("div.wysiwyg-fileManager").bind("click", function () {
+						$.wysiwyg.fileManager.init(Wysiwyg, function (selected) {
+							dialog.find("input[name=src]").val(selected);
+							dialog.find("input[name=src]").trigger("change");
+						});
+					});
+				}
+
+				$("input:reset", dialog).click(function (e) {
+					adialog.close();
+
+					return false;
+				});
+
+				$("fieldset", dialog).click(function (e) {
+					e.stopPropagation();
+				});
+
+				self.makeForm(dialog, img);
+			});
+
+			adialog.open();
+
+			$(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");
+		},
+
+		processInsert: function (context, Wysiwyg, img) {
+			var image,
+				url = $('input[name="src"]', context).val(),
+				title = $('input[name="imgtitle"]', context).val(),
+				description = $('input[name="description"]', context).val(),
+				width = $('input[name="width"]', context).val(),
+				height = $('input[name="height"]', context).val(),
+				styleFloat = $('select[name="float"]', context).val(),
+				styles = [],
+				style = "",
+				found,
+				baseUrl;
+
+			if (Wysiwyg.options.controlImage && Wysiwyg.options.controlImage.forceRelativeUrls) {
+				baseUrl = window.location.protocol + "//" + window.location.hostname
+					+ (window.location.port ? ":" + window.location.port : "");
+				if (0 === url.indexOf(baseUrl)) {
+					url = url.substr(baseUrl.length);
+				}
+			}
+
+			if (img.self) {
+				// to preserve all img attributes
+				$(img.self).attr("src", url)
+					.attr("title", title)
+					.attr("alt", description)
+					.css("float", styleFloat);
+
+				if (width.toString().match(/^[0-9]+(px|%)?$/)) {
+					$(img.self).css("width", width);
+				} else {
+					$(img.self).css("width", "");
+				}
+
+				if (height.toString().match(/^[0-9]+(px|%)?$/)) {
+					$(img.self).css("height", height);
+				} else {
+					$(img.self).css("height", "");
+				}
+
+				Wysiwyg.saveContent();
+			} else {
+				found = width.toString().match(/^[0-9]+(px|%)?$/);
+				if (found) {
+					if (found[1]) {
+						styles.push("width: " + width + ";");
+					} else {
+						styles.push("width: " + width + "px;");
+					}
+				}
+
+				found = height.toString().match(/^[0-9]+(px|%)?$/);
+				if (found) {
+					if (found[1]) {
+						styles.push("height: " + height + ";");
+					} else {
+						styles.push("height: " + height + "px;");
+					}
+				}
+
+				if (styleFloat.length > 0) {
+					styles.push("float: " + styleFloat + ";");
+				}
+
+				if (styles.length > 0) {
+					style = ' style="' + styles.join(" ") + '"';
+				}
+
+				image = "<img src='" + url + "' title='" + title + "' alt='" + description + "'" + style + "/>";
+				Wysiwyg.insertHtml(image);
+			}
+		},
+
+		makeForm: function (form, img) {
+			form.find("input[name=src]").val(img.src);
+			form.find("input[name=imgtitle]").val(img.title);
+			form.find("input[name=description]").val(img.alt);
+			form.find('input[name="width"]').val(img.width);
+			form.find('input[name="height"]').val(img.height);
+			form.find('select[name="float"]').val(img.styleFloat);
+			form.find('img').attr("src", img.src);
+
+			form.find('img').bind("load", function () {
+				if (form.find('img').get(0).naturalWidth) {
+					form.find('input[name="naturalWidth"]').val(form.find('img').get(0).naturalWidth);
+					form.find('input[name="naturalHeight"]').val(form.find('img').get(0).naturalHeight);
+				} else if (form.find('img').attr("naturalWidth")) {
+					form.find('input[name="naturalWidth"]').val(form.find('img').attr("naturalWidth"));
+					form.find('input[name="naturalHeight"]').val(form.find('img').attr("naturalHeight"));
+				}
+			});
+
+			form.find("input[name=src]").bind("change", function () {
+				form.find('img').attr("src", this.value);
+			});
+
+			return form;
+		}
+	};
+
+	$.wysiwyg.insertImage = function (object, url, attributes) {
+		return object.each(function () {
+			var Wysiwyg = $(this).data("wysiwyg"),
+				image,
+				attribute;
+
+			if (!Wysiwyg) {
+				return this;
+			}
+
+			if (!url || url.length === 0) {
+				return this;
+			}
+
+			if ($.browser.msie) {
+				Wysiwyg.ui.focus();
+			}
+
+			if (attributes) {
+				Wysiwyg.editorDoc.execCommand("insertImage", false, "#jwysiwyg#");
+				image = Wysiwyg.getElementByAttributeValue("img", "src", "#jwysiwyg#");
+
+				if (image) {
+					image.src = url;
+
+					for (attribute in attributes) {
+						if (attributes.hasOwnProperty(attribute)) {
+							image.setAttribute(attribute, attributes[attribute]);
+						}
+					}
+				}
+			} else {
+				Wysiwyg.editorDoc.execCommand("insertImage", false, url);
+			}
+
+			Wysiwyg.saveContent();
+
+			$(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");
+
+			return this;
+		});
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/controls/wysiwyg.link.js b/media/js/jwysiwyg/controls/wysiwyg.link.js
new file mode 100644
index 0000000..95e8948
--- /dev/null
+++ b/media/js/jwysiwyg/controls/wysiwyg.link.js
@@ -0,0 +1,267 @@
+/**
+ * Controls: Link plugin
+ *
+ * Depends on jWYSIWYG
+ *
+ * By: Esteban Beltran (academo) <sergies at gmail.com>
+ */
+(function ($) {
+	"use strict";
+
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.link.js depends on $.wysiwyg";
+	}
+
+	if (!$.wysiwyg.controls) {
+		$.wysiwyg.controls = {};
+	}
+
+	/*
+	* Wysiwyg namespace: public properties and methods
+	*/
+	$.wysiwyg.controls.link = {
+		init: function (Wysiwyg) {
+			var self = this, elements, dialog, url, a, selection,
+				formLinkHtml, dialogReplacements, key, translation, regexp,
+				baseUrl, img;
+
+			dialogReplacements = {
+				legend: "Insert Link",
+				url   : "Link URL",
+				title : "Link Title",
+				target: "Link Target",
+				submit: "Insert Link",
+				reset: "Cancel"
+			};
+
+			formLinkHtml = '<form class="wysiwyg"><fieldset><legend>{legend}</legend>' +
+				'<label>{url}: <input type="text" name="linkhref" value=""/></label>' +
+				'<label>{title}: <input type="text" name="linktitle" value=""/></label>' +
+				'<label>{target}: <input type="text" name="linktarget" value=""/></label>' +
+				'<input type="submit" class="button" value="{submit}"/> ' +
+				'<input type="reset" value="{reset}"/></fieldset></form>';
+
+			for (key in dialogReplacements) {
+				if ($.wysiwyg.i18n) {
+					translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs.link");
+
+					if (translation === dialogReplacements[key]) { // if not translated search in dialogs 
+						translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs");
+					}
+
+					dialogReplacements[key] = translation;
+				}
+
+				regexp = new RegExp("{" + key + "}", "g");
+				formLinkHtml = formLinkHtml.replace(regexp, dialogReplacements[key]);
+			}
+
+			a = {
+				self: Wysiwyg.dom.getElement("a"), // link to element node
+				href: "http://",
+				title: "",
+				target: ""
+			};
+
+			if (a.self) {
+				a.href = a.self.href ? a.self.href : a.href;
+				a.title = a.self.title ? a.self.title : "";
+				a.target = a.self.target ? a.self.target : "";
+			}
+
+			if ($.fn.dialog) {
+				elements = $(formLinkHtml);
+				elements.find("input[name=linkhref]").val(a.href);
+				elements.find("input[name=linktitle]").val(a.title);
+				elements.find("input[name=linktarget]").val(a.target);
+
+				if ($.browser.msie) {
+					try {
+						dialog = elements.appendTo(Wysiwyg.editorDoc.body);
+					} catch (err) {
+						dialog = elements.appendTo("body");
+					}
+				} else {
+					dialog = elements.appendTo("body");
+				}
+
+				dialog.dialog({
+					modal: true,
+					open: function (ev, ui) {
+						$("input:submit", dialog).click(function (e) {
+							e.preventDefault();
+
+							var url = $('input[name="linkhref"]', dialog).val(),
+								title = $('input[name="linktitle"]', dialog).val(),
+								target = $('input[name="linktarget"]', dialog).val(),
+								baseUrl,
+								img;
+
+							if (Wysiwyg.options.controlLink.forceRelativeUrls) {
+								baseUrl = window.location.protocol + "//" + window.location.hostname;
+								if (0 === url.indexOf(baseUrl)) {
+									url = url.substr(baseUrl.length);
+								}
+							}
+
+							if (a.self) {
+								if ("string" === typeof (url)) {
+									if (url.length > 0) {
+										// to preserve all link attributes
+										$(a.self).attr("href", url).attr("title", title).attr("target", target);
+									} else {
+										$(a.self).replaceWith(a.self.innerHTML);
+									}
+								}
+							} else {
+								if ($.browser.msie) {
+									Wysiwyg.ui.returnRange();
+								}
+
+								//Do new link element
+								selection = Wysiwyg.getRangeText();
+								img = Wysiwyg.dom.getElement("img");
+
+								if ((selection && selection.length > 0) || img) {
+									if ($.browser.msie) {
+										Wysiwyg.ui.focus();
+									}
+
+									if ("string" === typeof (url)) {
+										if (url.length > 0) {
+											Wysiwyg.editorDoc.execCommand("createLink", false, url);
+										} else {
+											Wysiwyg.editorDoc.execCommand("unlink", false, null);
+										}
+									}
+
+									a.self = Wysiwyg.dom.getElement("a");
+
+									$(a.self).attr("href", url).attr("title", title);
+
+									/**
+									 * @url https://github.com/akzhan/jwysiwyg/issues/16
+									 */
+									$(a.self).attr("target", target);
+								} else if (Wysiwyg.options.messages.nonSelection) {
+									window.alert(Wysiwyg.options.messages.nonSelection);
+								}
+							}
+
+							Wysiwyg.saveContent();
+
+							$(dialog).dialog("close");
+
+							 Wysiwyg.ui.focus();
+							 return false;
+						});
+						$("input:reset", dialog).click(function (e) {
+							e.preventDefault();
+							$(dialog).dialog("close");
+							Wysiwyg.ui.focus();
+						});
+					},
+					close: function (ev, ui) {
+						dialog.dialog("destroy");
+						dialog.remove();
+						Wysiwyg.ui.focus();
+					}
+				});
+			} else {
+				if (a.self) {
+					url = window.prompt("URL", a.href);
+
+					if (Wysiwyg.options.controlLink.forceRelativeUrls) {
+						baseUrl = window.location.protocol + "//" + window.location.hostname;
+						if (0 === url.indexOf(baseUrl)) {
+							url = url.substr(baseUrl.length);
+						}
+					}
+
+					if ("string" === typeof (url)) {
+						if (url.length > 0) {
+							$(a.self).attr("href", url);
+						} else {
+							$(a.self).replaceWith(a.self.innerHTML);
+						}
+					}
+				} else {
+					//Do new link element
+					selection = Wysiwyg.getRangeText();
+					img = Wysiwyg.dom.getElement("img");
+
+					if ((selection && selection.length > 0) || img) {
+						if ($.browser.msie) {
+							Wysiwyg.ui.focus();
+							Wysiwyg.editorDoc.execCommand("createLink", true, null);
+						} else {
+							url = window.prompt(dialogReplacements.url, a.href);
+
+							if (Wysiwyg.options.controlLink.forceRelativeUrls) {
+								baseUrl = window.location.protocol + "//" + window.location.hostname;
+								if (0 === url.indexOf(baseUrl)) {
+									url = url.substr(baseUrl.length);
+								}
+							}
+
+							if ("string" === typeof (url)) {
+								if (url.length > 0) {
+									Wysiwyg.editorDoc.execCommand("createLink", false, url);
+								} else {
+									Wysiwyg.editorDoc.execCommand("unlink", false, null);
+								}
+							}
+						}
+					} else if (Wysiwyg.options.messages.nonSelection) {
+						window.alert(Wysiwyg.options.messages.nonSelection);
+					}
+				}
+
+				Wysiwyg.saveContent();
+			}
+
+			$(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");
+		}
+	};
+
+	$.wysiwyg.createLink = function (object, url, title) {
+		return object.each(function () {
+			var oWysiwyg = $(this).data("wysiwyg"),
+				selection;
+
+			if (!oWysiwyg) {
+				return this;
+			}
+
+			if (!url || url.length === 0) {
+				return this;
+			}
+
+			selection = oWysiwyg.getRangeText();
+			// ability to link selected img - just hack
+			var internalRange = oWysiwyg.getInternalRange();
+			var isNodeSelected = false;
+			if (internalRange && internalRange.extractContents) {
+				var rangeContents = internalRange.cloneContents();
+				if (rangeContents!=null && rangeContents.childNodes && rangeContents.childNodes.length>0)
+					isNodeSelected = true;
+			}
+			
+			if ( (selection && selection.length > 0) || isNodeSelected ) {
+				if ($.browser.msie) {
+					oWysiwyg.ui.focus();
+				}
+				oWysiwyg.editorDoc.execCommand("unlink", false, null);
+				oWysiwyg.editorDoc.execCommand("createLink", false, url);
+			} else {
+				if (title) {
+					oWysiwyg.insertHtml('<a href="'+url+'">'+title+'</a>');
+				} else {
+					if (oWysiwyg.options.messages.nonSelection) 
+						window.alert(oWysiwyg.options.messages.nonSelection);
+				}
+			}
+			return this;
+		});
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/controls/wysiwyg.table.js b/media/js/jwysiwyg/controls/wysiwyg.table.js
new file mode 100644
index 0000000..5a754dc
--- /dev/null
+++ b/media/js/jwysiwyg/controls/wysiwyg.table.js
@@ -0,0 +1,129 @@
+/**
+ * Controls: Table plugin
+ * 
+ * Depends on jWYSIWYG
+ */
+(function ($) {
+	"use strict";
+
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.table.js depends on $.wysiwyg";
+	}
+
+	if (!$.wysiwyg.controls) {
+		$.wysiwyg.controls = {};
+	}
+
+	var insertTable = function (colCount, rowCount, filler) {
+		if (isNaN(rowCount) || isNaN(colCount) || rowCount === null || colCount === null) {
+			return;
+		}
+
+		var i, j, html = ['<table border="1" style="width: 100%;"><tbody>'];
+
+		colCount = parseInt(colCount, 10);
+		rowCount = parseInt(rowCount, 10);
+
+		if (filler === null) {
+			filler = " ";
+		}
+		filler = "<td>" + filler + "</td>";
+
+		for (i = rowCount; i > 0; i -= 1) {
+			html.push("<tr>");
+			for (j = colCount; j > 0; j -= 1) {
+				html.push(filler);
+			}
+			html.push("</tr>");
+		}
+		html.push("</tbody></table>");
+
+		return this.insertHtml(html.join(""));
+	};
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	$.wysiwyg.controls.table = function (Wysiwyg) {
+		var adialog, dialog, colCount, rowCount, formTableHtml, dialogReplacements, key, translation, regexp;
+
+		dialogReplacements = {
+			legend: "Insert table",
+			cols  : "Count of columns",
+			rows  : "Count of rows",
+			submit: "Insert table",
+			reset: "Cancel"
+		};
+
+		formTableHtml = '<form class="wysiwyg" id="wysiwyg-tableInsert"><fieldset><legend>{legend}</legend>' +
+			'<label>{cols}: <input type="text" name="colCount" value="3" /></label><br/>' +
+			'<label>{rows}: <input type="text" name="rowCount" value="3" /></label><br/>' +
+			'<input type="submit" class="button" value="{submit}"/> ' +
+			'<input type="reset" value="{reset}"/></fieldset></form>';
+		
+		for (key in dialogReplacements) {
+			if ($.wysiwyg.i18n) {
+				translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs.table");
+
+				if (translation === dialogReplacements[key]) { // if not translated search in dialogs 
+					translation = $.wysiwyg.i18n.t(dialogReplacements[key], "dialogs");
+				}
+
+				dialogReplacements[key] = translation;
+			}
+
+			regexp = new RegExp("{" + key + "}", "g");
+			formTableHtml = formTableHtml.replace(regexp, dialogReplacements[key]);
+		}
+
+		if (!Wysiwyg.insertTable) {
+			Wysiwyg.insertTable = insertTable;
+		}
+
+		adialog = new $.wysiwyg.dialog(Wysiwyg, {
+			"title"   : dialogReplacements.legend,
+			"content" : formTableHtml,
+			"open"    : function (e, dialog) {
+				dialog.find("form#wysiwyg-tableInsert").submit(function (e) {
+					e.preventDefault();
+					rowCount = dialog.find("input[name=rowCount]").val();
+					colCount = dialog.find("input[name=colCount]").val();
+
+					Wysiwyg.insertTable(colCount, rowCount, Wysiwyg.defaults.tableFiller);
+
+					adialog.close();
+					return false;
+				});
+
+				dialog.find("input:reset").click(function (e) {
+					e.preventDefault();
+					adialog.close();
+					return false;
+				});
+			}
+		});
+		
+		adialog.open();
+
+		$(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");
+	};
+
+	$.wysiwyg.insertTable = function (object, colCount, rowCount, filler) {
+		return object.each(function () {
+			var Wysiwyg = $(this).data("wysiwyg");
+
+			if (!Wysiwyg.insertTable) {
+				Wysiwyg.insertTable = insertTable;
+			}
+
+			if (!Wysiwyg) {
+				return this;
+			}
+
+			Wysiwyg.insertTable(colCount, rowCount, filler);
+			$(Wysiwyg.editorDoc).trigger("editorRefresh.wysiwyg");
+
+			return this;
+		});
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.ca.js b/media/js/jwysiwyg/i18n/lang.ca.js
new file mode 100644
index 0000000..101ead0
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.ca.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: Catalan language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Josep Anguera Peralta <josep.anguera at gmail.com>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.ca.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.ca.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.ca = {
+		controls: {
+			"Bold": "Negreta",
+			"Colorpicker": "Triar color",
+			"Copy": "Copiar",
+			"Create link": "Crear link",
+			"Cut": "Tallar",
+			"Decrease font size": "Disminuir tamany font",
+			"Fullscreen": "Pantalla completa",
+			"Header 1": "Títol 1",
+			"Header 2": "Títol 2",
+			"Header 3": "Títol 3",
+			"View source code": "Veure codi",
+			"Increase font size": "Aumentar tamany font",
+			"Indent": "Afegir Sangrat",
+			"Insert Horizontal Rule": "Insertar línia horitzontal",
+			"Insert image": "Insertar imatge",
+			"Insert Ordered List": "Insertar llista numèrica",
+			"Insert table": "Insertar taula",
+			"Insert Unordered List": "Insertar llista sense ordre",
+			"Italic": "Cursiva",
+			"Justify Center": "Centrar",
+			"Justify Full": "Justificar",
+			"Justify Left": "Alinear a la esquerra",
+			"Justify Right": "Alinear a la dreta",
+			"Left to Right": "Esquerra a dreta",
+			"Outdent": "Treure sangrat",
+			"Paste": "Enganxar",
+			"Redo": "Restaurar",
+			"Remove formatting": "Treure format",
+			"Right to Left": "Dreta a esquerra",
+			"Strike-through": "Invertir",
+			"Subscript": "Subíndex",
+			"Superscript": "Superíndex",
+			"Underline": "Subratllar",
+			"Undo": "Desfer"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Aplicar",
+			"Cancel": "Cancelar",
+
+			colorpicker: {
+				"Colorpicker": "Triar color",
+				"Color": "Color"
+			},
+
+			image: {
+				"Insert Image": "Insertar imatge",
+				"Preview": "Previsualització",
+				"URL": "URL",
+				"Title": "Títol",
+				"Description": "Descripció",
+				"Width": "Amplada",
+				"Height": "Alçada",
+				"Original W x H": "Amplada x Alçada original",
+				"Float": "Flotant",
+				"None": "No",
+				"Left": "Esquerra",
+				"Right": "Dreta"
+			},
+
+			link: {
+				"Insert Link": "Insertar link",
+				"Link URL": "URL del link",
+				"Link Title": "Títol del link",
+				"Link Target": "Target de link"
+			},
+
+			table: {
+				"Insert table": "Insertar taula",
+				"Count of columns": "Número de columnes",
+				"Count of rows": "Número de files"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.cs.js b/media/js/jwysiwyg/i18n/lang.cs.js
new file mode 100644
index 0000000..f35394c
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.cs.js
@@ -0,0 +1,116 @@
+/**
+ * Internationalization: czech language
+ *
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: deepj on github.com
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.cs.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.cs.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.cs = {
+		controls: {
+			"Bold": "Tučné",
+			"Colorpicker": "Výběr barvy",
+			"Copy": "Kopírovat",
+			"Create link": "Vytvořit odkaz",
+			"Cut": "Vyjmout",
+			"Decrease font size": "Zmenšit velikost písma",
+			"Fullscreen": "Celá obrazovka",
+			"Header 1": "Nadpis 1",
+			"Header 2": "Nadpis 2",
+			"Header 3": "Nadpis 3",
+			"View source code": "Zobrazit zdrojový kód",
+			"Increase font size": "Zvětšit velikost písma",
+			"Indent": "Zvětšit odsazení",
+			"Insert Horizontal Rule": "Vložit horizontální čáru",
+			"Insert image": "Vložit obrázek",
+			"Insert Ordered List": "Vložit číslovaný seznam",
+			"Insert table": "Vložit tabulku",
+			"Insert Unordered List": "Vložit odrážkový seznam",
+			"Italic": "Kurzíva",
+			"Justify Center": "Zarovnat na střed",
+			"Justify Full": "Zarovnat do bloku",
+			"Justify Left": "Zarovnat doleva",
+			"Justify Right": "Zarovnat doprava",
+			"Left to Right": "Zleva doprava",
+			"Outdent": "Zmenšit odsazení",
+			"Paste": "Vložit",
+			"Redo": "Znovu",
+			"Remove formatting": "Odstranit formátování",
+			"Right to Left": "Zprava doleva",
+			"Strike-through": "Přeškrnuté",
+			"Subscript": "Dolní index",
+			"Superscript": "Horní index",
+			"Underline": "Potržené",
+			"Undo": "Zpět"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Použij",
+			"Cancel": "Zrušit",
+
+			colorpicker: {
+				"Colorpicker": "Výběr barvy",
+				"Color": "Barva"
+			},
+
+			fileManager: {
+				"file_manager": "Správce souborů",
+				"upload_title": "Nahrát soubor",
+				"rename_title": "Přejmenovat soubor",
+				"remove_title": "Odstranit soubor",
+				"mkdir_title": "Vytvořit adresář",
+				"upload_action": "Nahrát nový soubor do aktualního adresáře",
+				"mkdir_action": "Vytvořit nový adresář",
+				"remove_action": "Odstranit tento soubor",
+				"rename_action": "Přejmenovat tento soubor" ,
+				"delete_message": "Jste si jist, že chcete smazat tento soubor?",
+				"new_directory": "Nový adresář",
+				"previous_directory": "Vrať se do přechozího adresáře",
+				"rename": "Přejmenovat",
+				"select": "Vybrat",
+				"create": "Vytvořit",
+				"submit": "Vložit",
+				"cancel": "Zrušit",
+				"yes": "Ano",
+				"no": "Ne"
+			},
+
+			image: {
+				"Insert Image": "Vložit obrázek",
+				"Preview": "Náhled",
+				"URL": "Odkaz",
+				"Title": "Název",
+				"Description": "Popis",
+				"Width": "Šířka",
+				"Height": "Výška",
+				"Original W x H": "Původní šířka a výška",
+				"Float": "Obtékání",
+				"None": "Žádné",
+				"Left": "Doleva",
+				"Right": "Doprava",
+				"Select file from server": "Vybrat soubor ze serveru"
+			},
+
+			link: {
+				"Insert Link": "Vložit odkaz",
+				"Link URL": "Odkaz",
+				"Link Title": "Název odkazu",
+				"Link Target": "Cíl odkazu"
+			},
+
+			table: {
+				"Insert table": "Vložit tabulku",
+				"Count of columns": "Počet sloupců",
+				"Count of rows": "Počet řádků"
+			}
+		}
+	};
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/i18n/lang.de.js b/media/js/jwysiwyg/i18n/lang.de.js
new file mode 100644
index 0000000..ecbb450
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.de.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: German language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Markus Schirp (mbj) <mbj at seonic.net>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.de.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.de.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.de = {
+		controls: {
+			"Bold": "Fett",
+			"Colorpicker": "Farbe wählen",
+			"Copy": "Kopieren",
+			"Create link": "Link erstellen",
+			"Cut": "Ausschneiden",
+			"Decrease font size": "Schriftgröße verkleinern",
+			"Fullscreen": "Vollbild",
+			"Header 1": "Überschrift 1",
+			"Header 2": "Überschrift 2",
+			"Header 3": "Überschrift 3",
+			"View source code": "Quellcode anzeigen",
+			"Increase font size": "Schriftgröße vergrößern",
+			"Indent": "Einrücken",
+			"Insert Horizontal Rule": "Horizontalen Trennbalken einfügen",
+			"Insert image": "Bild einfügen",
+			"Insert Ordered List": "Nummerierte Liste einfügen",
+			"Insert table": "Tabelle einfügen",
+			"Insert Unordered List": "Unnummerierte Liste einfügen",
+			"Italic": "Kursiv",
+			"Justify Center": "Zentrieren",
+			"Justify Full": "Blocksatz",
+			"Justify Left": "Links ausrichten",
+			"Justify Right": "Rechts ausrichten",
+			"Left to Right": "Links nach Rechts",
+			"Outdent": "Einrückung zurücknehmen",
+			"Paste": "Einfügen",
+			"Redo": "Wiederherstellen",
+			"Remove formatting": "Formatierung entfernen",
+			"Right to Left": "Rechts nach Links",
+			"Strike-through": "Durchstreichen",
+			"Subscript": "Tiefstellen",
+			"Superscript": "Hochstellen",
+			"Underline": "Unterstreichen",
+			"Undo": "Rückgängig"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Anwenden",
+			"Cancel": "Abbrechen",
+
+			colorpicker: {
+				"Colorpicker": "Farbwähler",
+				"Color": "Farbe"
+			},
+
+			image: {
+				"Insert Image": "Bild einfügen",
+				"Preview": "Vorschau",
+				"URL": "URL",
+				"Title": "Titel",
+				"Description": "Beschreibung",
+				"Width": "Breite",
+				"Height": "Höhe",
+				"Original W x H": "Original W x H",
+				"Float": "",
+				"None": "",
+				"Left": "",
+				"Right": ""
+			},
+
+			link: {
+				"Insert Link": "Link einfügen",
+				"Link URL": "Link URL",
+				"Link Title": "Link Titel",
+				"Link Target": "Link Ziel"
+			},
+
+			table: {
+				"Insert table": "Tabelle einfügen",
+				"Count of columns": "Spaltenanzahl",
+				"Count of rows": "Zeilenanzahl"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.en.js b/media/js/jwysiwyg/i18n/lang.en.js
new file mode 100644
index 0000000..5116ef0
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.en.js
@@ -0,0 +1,117 @@
+/**
+ * Internationalization: English language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ * 
+ * By: frost-nzcr4 on github.com
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.en.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.en.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.en = {
+		controls: {
+			"Bold": "",
+			"Colorpicker": "",
+			"Copy": "",
+			"Create link": "",
+			"Cut": "",
+			"Decrease font size": "",
+			"File Manager": "",
+			"Fullscreen": "",
+			"Header 1": "",
+			"Header 2": "",
+			"Header 3": "",
+			"View source code": "",
+			"Increase font size": "",
+			"Indent": "",
+			"Insert Horizontal Rule": "",
+			"Insert image": "",
+			"Insert Ordered List": "",
+			"Insert table": "",
+			"Insert Unordered List": "",
+			"Italic": "",
+			"Justify Center": "",
+			"Justify Full": "",
+			"Justify Left": "",
+			"Justify Right": "",
+			"Left to Right": "",
+			"Outdent": "",
+			"Paste": "",
+			"Redo": "",
+			"Remove formatting": "",
+			"Right to Left": "",
+			"Strike-through": "",
+			"Subscript": "",
+			"Superscript": "",
+			"Underline": "",
+			"Undo": ""
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "",
+			"Cancel": "",
+
+			colorpicker: {
+				"Colorpicker": "",
+				"Color": ""
+			},
+
+			fileManager: {
+				"file_manager": 		"File Manager",
+				"upload_title":			"Upload File",
+				"rename_title":			"Rename File",
+				"remove_title":			"Remove File",
+				"mkdir_title":			"Create Directory",
+				"upload_action": 		"Upload new file to current directory",
+				"mkdir_action": 		"Create new directory",
+				"remove_action": 		"Remove this file",
+				"rename_action": 		"Rename this file" ,	
+				"delete_message": 		"Are you sure you want to delete this file?",
+				"new_directory": 		"New Directory",
+				"previous_directory": 	"Go to previous directory",
+				"rename":				"Rename",
+				"select": 				"Select",
+				"create": 				"Create",
+				"submit": 				"Submit",
+				"cancel": 				"Cancel",
+				"yes":					"Yes",
+				"no":					"No"
+			},
+
+			image: {
+				"Insert Image": "",
+				"Preview": "",
+				"URL": "",
+				"Title": "",
+				"Description": "",
+				"Width": "",
+				"Height": "",
+				"Original W x H": "",
+				"Float": "",
+				"None": "",
+				"Left": "",
+				"Right": "",
+				"Select file from server": ""
+			},
+
+			link: {
+				"Insert Link": "",
+				"Link URL": "",
+				"Link Title": "",
+				"Link Target": ""
+			},
+
+			table: {
+				"Insert table": "",
+				"Count of columns": "",
+				"Count of rows": ""
+			}
+		}
+	};
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/i18n/lang.es.js b/media/js/jwysiwyg/i18n/lang.es.js
new file mode 100644
index 0000000..c11178f
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.es.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: Spanish language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Esteban Beltran (academo) <sergies at gmail.com>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.es.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.es.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.es = {
+		controls: {
+			"Bold": "Negrilla",
+			"Colorpicker": "",
+			"Copy": "Copiar",
+			"Create link": "Crear Link",
+			"Cut": "Cortar",
+			"Decrease font size": "Disminuir tamaño fuente",
+			"Fullscreen": "",
+			"Header 1": "Titulo 1",
+			"Header 2": "Titulo 2",
+			"Header 3": "Titulo 3",
+			"View source code": "Ver fuente",
+			"Increase font size": "Aumentar tamaño fuente",
+			"Indent": "Agregar Sangría",
+			"Insert Horizontal Rule": "Insertar linea horizontal",
+			"Insert image": "Insertar Imagen",
+			"Insert Ordered List": "Insertar lista numérica",
+			"Insert table": "Insertar Tabla",
+			"Insert Unordered List": "Insertar Lista viñetas",
+			"Italic": "Cursiva",
+			"Justify Center": "Centrar",
+			"Justify Full": "Justificar",
+			"Justify Left": "Alinear a la Izquierda",
+			"Justify Right": "Alinear a la derecha",
+			"Left to Right": "Izquierda a derecha",
+			"Outdent": "Quitar Sangría",
+			"Paste": "Pegar",
+			"Redo": "Restaurar",
+			"Remove formatting": "Quitar Formato",
+			"Right to Left": "Derecha a izquierda",
+			"Strike-through": "Invertir",
+			"Subscript": "Subíndice",
+			"Superscript": "Superíndice",
+			"Underline": "Subrayar",
+			"Undo": "Deshacer"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Aplicar",
+			"Cancel": "Cancelar",
+
+			colorpicker: {
+				"Colorpicker": "Selector de color",
+				"Color": "Color"
+			},
+
+			image: {
+				"Insert Image": "Insertar imagen",
+				"Preview": "Vista previa",
+				"URL": "URL",
+				"Title": "Título",
+				"Description": "Descripción",
+				"Width": "Ancho",
+				"Height": "Alto",
+				"Original W x H": "Original Al X An",
+				"Float": "Flotación",
+				"None": "Ninguna",
+				"Left": "Izquierda",
+				"Right": "Derecha"
+			},
+
+			link: {
+				"Insert Link": "Insertar Link",
+				"Link URL": "URL del link",
+				"Link Title": "Título del link",
+				"Link Target": "Target del Link"
+			},
+
+			table: {
+				"Insert table": "Insertar tabla",
+				"Count of columns": "Cuenta de columnas",
+				"Count of rows": "Cuenta de filas"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.fr.js b/media/js/jwysiwyg/i18n/lang.fr.js
new file mode 100644
index 0000000..d869589
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.fr.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: french language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Tom Barbette <mappam0 at gmail.com>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.fr.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.fr.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.fr = {
+		controls: {
+			"Bold": "Gras",
+			"Colorpicker": "Choisir une couleur",
+			"Copy": "Copier",
+			"Create link": "Créer un lien",
+			"Cut": "Couper",
+			"Decrease font size": "Diminuer la taille du texte",
+			"Fullscreen": "Plein écran",
+			"Header 1": "Titre 1",
+			"Header 2": "Titre 2",
+			"Header 3": "Titre 3",
+			"View source code": "Voir le code source",
+			"Increase font size": "Augmenter la taille du texte",
+			"Indent": "Augmenter le retrait",
+			"Insert Horizontal Rule": "Insérer une règle horyzontale",
+			"Insert image": "Insérer une image",
+			"Insert Ordered List": "Insérer une liste ordonnée",
+			"Insert table": "Insérer un tableau",
+			"Insert Unordered List": "Insérer une liste",
+			"Italic": "Italique",
+			"Justify Center": "Centré",
+			"Justify Full": "Justifié",
+			"Justify Left": "Aligné à gauche",
+			"Justify Right": "Aligné à droite",
+			"Left to Right": "Gauche à droite",
+			"Outdent": "Réduire le retrait",
+			"Paste": "Coller",
+			"Redo": "Restaurer",
+			"Remove formatting": "Supprimer le formatage",
+			"Right to Left": "Droite à gauche",
+			"Strike-through": "Barré",
+			"Subscript": "Indice",
+			"Superscript": "Exposant",
+			"Underline": "Souligné",
+			"Undo": "Annuler"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Appliquer",
+			"Cancel": "Annuler",
+
+			colorpicker: {
+				"Colorpicker": "Choisir une couleur",
+				"Color": "Couleur"
+			},
+
+			image: {
+				"Insert Image": "Insérer une image",
+				"Preview": "Prévisualiser",
+				"URL": "URL",
+				"Title": "Titre",
+				"Description": "Description",
+				"Width": "Largeur",
+				"Height": "Hauteur",
+				"Original W x H": "L x H originale",
+				"Float": "",
+				"None": "",
+				"Left": "",
+				"Right": ""
+			},
+
+			link: {
+				"Insert Link": "Insérer un lien",
+				"Link URL": "URL du lien",
+				"Link Title": "Titre du lien",
+				"Link Target": "Cible du lien"
+			},
+
+			table: {
+				"Insert table": "Insérer un tableau",
+				"Count of columns": "Nombre de colonnes",
+				"Count of rows": "Nombre de lignes"
+			}
+		}
+	};
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/i18n/lang.he.js b/media/js/jwysiwyg/i18n/lang.he.js
new file mode 100644
index 0000000..e7ff862
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.he.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: English language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ * 
+ * By: 	Tudmotu, frost-nzcr4 on github.com
+ * 		Yotam Bar-On
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.he.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.he.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.he = {
+		controls: {
+			"Bold": "מודגש",
+			"Colorpicker": "פלטת צבעים",
+			"Copy": "העתק",
+			"Create link": "צור קישור",
+			"Cut": "חתוך",
+			"Decrease font size": "הקטן גופן",
+			"Fullscreen": "מסך מלא",
+			"Header 1": "כותרת 1",
+			"Header 2": "כותרת 2",
+			"Header 3": "כותרת 3",
+			"View source code": "הצג קוד מקור",
+			"Increase font size": "הגדל גופן",
+			"Indent": "הגדל הזחה",
+			"Insert Horizontal Rule": "הכנס קו אופקי",
+			"Insert image": "הוסף תמונה",
+			"Insert Ordered List": "הוספך רשימה ממוספרת",
+			"Insert table": "הוסף טבלה",
+			"Insert Unordered List": "הוספת רשימה בלתי ממוספרת",
+			"Italic": "נטוי",
+			"Justify Center": "מרכז",
+			"Justify Full": "יישור לשוליים",
+			"Justify Left": "הצמד לשמאל",
+			"Justify Right": "הצמד לימין",
+			"Left to Right": "שמאל לימין",
+			"Outdent": "הורד הזחה",
+			"Paste": "הדבק",
+			"Redo": "עשה שוב",
+			"Remove formatting": "הסר עיצוב",
+			"Right to Left": "ימין לשמאל",
+			"Strike-through": "כיתוב מחוק",
+			"Subscript": "כתיב עילי",
+			"Superscript": "כתיב תחתי",
+			"Underline": "קו תחתון",
+			"Undo": "בטל פעולה"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "החל",
+			"Cancel": "בטל",
+
+			colorpicker: {
+				"Colorpicker": "פלטת צבעים",
+				"Color": "צבע"
+			},
+
+			image: {
+				"Insert Image": "הכנס תמונה",
+				"Preview": "תצוגה מקדימה",
+				"URL": "כתובת רשת",
+				"Title": "כותרת",
+				"Description": "תיאור",
+				"Width": "רוחב",
+				"Height": "גובה",
+				"Original W x H": "מימדים מקוריים",
+				"Float": "צף",
+				"None": "שום כיוון",
+				"Left": "שמאל",
+				"Right": "ימין"
+			},
+
+			link: {
+				"Insert Link": "צור קישור",
+				"Link URL": "כתובת רשת",
+				"Link Title": "כותרת",
+				"Link Target": "מטרה"
+			},
+
+			table: {
+				"Insert table": "הוסף טבלה",
+				"Count of columns": "מספר עמודות",
+				"Count of rows": "מספר שורות"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.hr.js b/media/js/jwysiwyg/i18n/lang.hr.js
new file mode 100644
index 0000000..ed3f254
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.hr.js
@@ -0,0 +1,98 @@
+/**
+ * Internationalization: Croatian language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Boris Strahija (bstrahija) <boris at creolab.hr>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.hr.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.hr.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.hr = {
+		controls: {
+			"Bold": "Podebljano",
+			"Colorpicker": "Izbor boje",
+			"Copy": "Kopiraj",
+			"Create link": "Umetni link",
+			"Cut": "Izreži",
+			"Decrease font size": "Smanji font",
+			"Fullscreen": "Cijeli ekran",
+			"Header 1": "Naslov 1",
+			"Header 2": "Naslov 2",
+			"Header 3": "Naslov 3",
+			"Header 4": "Naslov 4",
+			"Header 5": "Naslov 5",
+			"Header 6": "Naslov 6",
+			"View source code": "Kod",
+			"Increase font size": "Povećaj font",
+			"Indent": "Uvuci",
+			"Insert Horizontal Rule": "Horizontalna linija",
+			"Insert image": "Umetni sliku",
+			"Insert Ordered List": "Numerirana lista",
+			"Insert table": "Umetni tabelu",
+			"Insert Unordered List": "Nenumerirana lista",
+			"Italic": "Ukošeno",
+			"Justify Center": "Centriraj",
+			"Justify Full": "Poravnaj obostrano",
+			"Justify Left": "Poravnaj lijevo",
+			"Justify Right": "Poravnaj desno",
+			"Left to Right": "Lijevo na desno",
+			"Outdent": "Izvuci",
+			"Paste": "Zalijepi",
+			"Redo": "Ponovi",
+			"Remove formatting": "Poništi oblikovanje",
+			"Right to Left": "Desno na lijevo",
+			"Strike-through": "Precrtano",
+			"Subscript": "Indeks",
+			"Superscript": "Eksponent",
+			"Underline": "Podcrtano",
+			"Undo": "Poništi",
+			"Code snippet": "Isječak koda"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Primjeni",
+			"Cancel": "Odustani",
+
+			colorpicker: {
+				"Colorpicker": "Izbor boje",
+				"Color": "Boja"
+			},
+
+			image: {
+				"Insert Image": "Umetni sliku",
+				"Preview": "Predprikaz",
+				"URL": "URL",
+				"Title": "Naslov",
+				"Description": "Opis",
+				"Width": "Širina",
+				"Height": "Visina",
+				"Original W x H": "Originalna Š x V",
+				"Float": "",
+				"None": "Nema",
+				"Left": "Lijevo",
+				"Right": "Desno"
+			},
+
+			link: {
+				"Insert Link": "Umetni link",
+				"Link URL": "URL linka",
+				"Link Title": "Naslov linka",
+				"Link Target": "Meta linka"
+			},
+
+			table: {
+				"Insert table": "Umetni tabelu",
+				"Count of columns": "Broj kolona",
+				"Count of rows": "Broj redova"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.it.js b/media/js/jwysiwyg/i18n/lang.it.js
new file mode 100644
index 0000000..4a92e65
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.it.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: italian language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Mauro Franceschini <mauro.franceschini at gmail.com>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.it.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.it.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.it = {
+		controls: {
+			"Bold": "Grassetto",
+			"Colorpicker": "Scegli un colore",
+			"Copy": "Copia",
+			"Create link": "Crea collegamento",
+			"Cut": "Taglia",
+			"Decrease font size": "Diminuisci dimensione testo",
+			"Fullscreen": "Schermo intero",
+			"Header 1": "Titolo 1",
+			"Header 2": "Titolo 2",
+			"Header 3": "Titolo 3",
+			"View source code": "Visualizza codice sorgente",
+			"Increase font size": "Aumenta dimensione testo",
+			"Indent": "Aumenta il rientro",
+			"Insert Horizontal Rule": "Inserisci separatore orizzontale",
+			"Insert image": "Inserisci immagine",
+			"Insert Ordered List": "Inserisci lista ordinata",
+			"Insert table": "Inserisci tabella",
+			"Insert Unordered List": "Inserisci lista non ordinata",
+			"Italic": "Corsivo",
+			"Justify Center": "Centrato",
+			"Justify Full": "Giustificato",
+			"Justify Left": "Allineato a sinistra",
+			"Justify Right": "Allineato a destra",
+			"Left to Right": "Da sinistra a destra",
+			"Outdent": "Riduci il rientro",
+			"Paste": "Incolla",
+			"Redo": "Ripristina",
+			"Remove formatting": "Cancella formattazione",
+			"Right to Left": "Da destra a sinistra",
+			"Strike-through": "Barrato",
+			"Subscript": "Pedice",
+			"Superscript": "Apice",
+			"Underline": "Sottolineato",
+			"Undo": "Annulla"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Applica",
+			"Cancel": "Annulla",
+
+			colorpicker: {
+				"Colorpicker": "Scegli un colore",
+				"Color": "Colore"
+			},
+
+			image: {
+				"Insert Image": "Inserisci immagine",
+				"Preview": "Anteprima",
+				"URL": "Indirizzo internet (URL)",
+				"Title": "Titolo",
+				"Description": "Descrizione",
+				"Width": "Larghezza",
+				"Height": "Altezza",
+				"Original W x H": "Dimensioni originali (L x A)",
+				"Float": "",
+				"None": "",
+				"Left": "",
+				"Right": ""
+			},
+
+			link: {
+				"Insert Link": "Inserisci collegamento",
+				"Link URL": "Indirizzo internet (URL)",
+				"Link Title": "Titolo",
+				"Link Target": "Destinazione"
+			},
+
+			table: {
+				"Insert table": "Inserisci tabella",
+				"Count of columns": "Numero di colonne",
+				"Count of rows": "Numero di righe"
+			}
+		}
+	};
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/i18n/lang.ja.js b/media/js/jwysiwyg/i18n/lang.ja.js
new file mode 100644
index 0000000..6d3f701
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.ja.js
@@ -0,0 +1,95 @@
+/**
+ * Internationalization: japanese language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: https://github.com/rosiro
+ *
+ */
+
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.ja.js depends on $.wysiwyg";
+		return false;
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.ja.js depends on $.wysiwyg.i18n";
+		return false;
+	}
+
+	$.wysiwyg.i18n.lang.ja = {
+		controls: {
+			"Bold": "ボールド",
+			"Copy": "コピー",
+			"Create link": "リンク作成",
+			"Cut": "切り取り",
+			"Decrease font size": "フォントサイズを小さく",
+			"Header 1": "見出し1",
+			"Header 2": "見出し2",
+			"Header 3": "見出し3",
+			"View source code": "ソースコードを見る",
+			"Increase font size": "フォントサイズを大きく",
+			"Indent": "インデント",
+			"Insert Horizontal Rule": "水平線<HR>を挿入",
+			"Insert image": "画像を挿入",
+			"Insert Ordered List": "順序付きリストの追加",
+			"Insert table": "テーブルを挿入",
+			"Insert Unordered List": "順序なしリストを追加",
+			"Italic": "イタリック",
+			"Justify Center": "中央寄せ",
+			"Justify Full": "左右一杯に揃える",
+			"Justify Left": "左寄せ",
+			"Justify Right": "右寄せ",
+			"Left to Right": "左から右へ",
+			"Outdent": "インデント解除",
+			"Paste": "貼り付け",
+			"Redo": "やり直し",
+			"Remove formatting": "書式設定を削除",
+			"Right to Left": "右から左へ",
+			"Strike-through": "取り消し線",
+			"Subscript": "下付き文字",
+			"Superscript": "上付き文字",
+			"Underline": "下線",
+			"Undo": "元に戻す"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "適用",
+			"Cancel": "キャンセル",
+
+			colorpicker: {
+				"Colorpicker": "カラーピッカー",
+				"Color": "カラー"
+			},
+
+			image: {
+				"Insert Image": "画像を挿入",
+				"Preview": "プレビュー",
+				"URL": "URL",
+				"Title": "タイトル",
+				"Description": "概要",
+				"Width": "横幅",
+				"Height": "高さ",
+				"Original W x H": "オリジナル 横 x 高",
+				"Float": "フロート",
+				"None": "画像無し",
+				"Left": "左寄せ",
+				"Right": "右寄せ"
+			},
+
+			link: {
+				"Insert Link": "リンクの挿入",
+				"Link URL": "リンク URL",
+				"Link Title": "リンク タイトル",
+				"Link Target": "リンク ターゲット"
+			},
+
+			table: {
+				"Insert table": "テーブルを挿入",
+				"Count of columns": "列数",
+				"Count of rows": "行数"
+			}
+		}
+	};
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/i18n/lang.nb.js b/media/js/jwysiwyg/i18n/lang.nb.js
new file mode 100644
index 0000000..1d4d565
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.nb.js
@@ -0,0 +1,116 @@
+/**
+ * Internationalization: norwegian (bokmål) language
+ *
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: strauman on github.com / strauman.net
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.nb.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.nb.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.nb = {
+		controls: {
+			"Bold": "Fet",
+			"Colorpicker": "Fargevelger",
+			"Copy": "Kopier",
+			"Create link": "Lag lenke",
+			"Cut": "Klipp ut",
+			"Decrease font size": "Reduser skriftstørrelse",
+			"Fullscreen": "Fullskjerm",
+			"Header 1": "Overskrift 1",
+			"Header 2": "Overskrift 2",
+			"Header 3": "Overskrift 3",
+			"View source code": "Vis kildekode",
+			"Increase font size": "Øk skriftstørrelse",
+			"Indent": "Innrykk",
+			"Insert Horizontal Rule": "Sett inn horisontal linje",
+			"Insert image": "Sett inn bilde",
+			"Insert Ordered List": "Sett inn sortert liste",
+			"Insert table": "Sett inn tabell",
+			"Insert Unordered List": "Sett inn usortert liste",
+			"Italic": "Kursiv",
+			"Justify Center": "Midtstillt",
+			"Justify Full": "Blokkjustert",
+			"Justify Left": "Ventrejustert",
+			"Justify Right": "Høyrejustert",
+			"Left to Right": "Venstre til høyre",
+			"Outdent": "Rykk ut",
+			"Paste": "Lim inn",
+			"Redo": "Gjør om",
+			"Remove formatting": "Fjern formatering",
+			"Right to Left": "Høyre til venstre",
+			"Strike-through": "Gjennomstreking",
+			"Subscript": "Hevet skrift",
+			"Superscript": "Senket skrift",
+			"Underline": "Understrek",
+			"Undo": "Angre"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Bruk",
+			"Cancel": "Avbryt",
+
+			colorpicker: {
+				"Colorpicker": "Fargevelger",
+				"Color": "Farge"
+			},
+
+			fileManager: {
+				"file_manager": 		"Filbehandler",
+				"upload_title":			"Last opp fil",
+				"rename_title":			"Gi nytt navn",
+				"remove_title":			"Slett fil",
+				"mkdir_title":			"Ny mappe",
+				"upload_action": 		"Last opp fil til denne mappen",
+				"mkdir_action": 		"Lag ny mappe",
+				"remove_action": 		"Slett filen",
+				"rename_action": 		"Nytt navn" ,	
+				"delete_message": 		"Er du sikker på at du vil slette denne filen?",
+				"new_directory": 		"Mappe uten navn",
+				"previous_directory": 	"Opp",
+				"rename":				"Gi nytt navn",
+				"select": 				"Velg",
+				"create": 				"Lag",
+				"submit": 				"Send",
+				"cancel": 				"Avbryt",
+				"yes":					"Ja",
+				"no":					"Nei"
+			},
+
+			image: {
+				"Insert Image": "Sett inn bilde",
+				"Preview": "Forhåndsvisning",
+				"URL": "URL",
+				"Title": "Tittel",
+				"Description": "Beskrivelse",
+				"Width": "Bredde",
+				"Height": "Høyde",
+				"Original W x H": "Original B x H",
+				"Float": "Flyt",
+				"None": "Ingen",
+				"Left": "Venstre",
+				"Right": "Høyre",
+				"Select file from server": "Velg fil fra tjener"
+			},
+
+			link: {
+				"Insert Link": "Sett inn lenke",
+				"Link URL": "Lenke-URL",
+				"Link Title": "Lenketittel",
+				"Link Target": "Lenkemål"
+			},
+
+			table: {
+				"Insert table": "Sett inn tabell",
+				"Count of columns": "Antall kolonner",
+				"Count of rows": "Antall rader"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.nl.js b/media/js/jwysiwyg/i18n/lang.nl.js
new file mode 100644
index 0000000..082c290
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.nl.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: Dutch
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Erik van Dongen <dongen at connexys.com>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.nl.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.nl.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.nl = {
+		controls: {
+			"Bold": "Vet",
+			"Colorpicker": "Kleur kiezen",
+			"Copy": "Kopiëren",
+			"Create link": "Link maken",
+			"Cut": "Knippen",
+			"Decrease font size": "Lettergrootte verkleinen",
+			"Fullscreen": "Volledig scherm",
+			"Header 1": "Kop 1",
+			"Header 2": "Kop 2",
+			"Header 3": "Kop 3",
+			"View source code": "Broncode bekijken",
+			"Increase font size": "Lettergrootte vergroten",
+			"Indent": "Inspringen",
+			"Insert Horizontal Rule": "Horizontale lijn invoegen",
+			"Insert image": "Afbeelding invoegen",
+			"Insert Ordered List": "Genummerde lijst",
+			"Insert table": "Tabel invoegen",
+			"Insert Unordered List": "Lijst met opsommingstekens",
+			"Italic": "Cursief",
+			"Justify Center": "Centreren",
+			"Justify Full": "Uitvullen",
+			"Justify Left": "Links uitlijnen",
+			"Justify Right": "Rechts uitlijnen",
+			"Left to Right": "Links naar Rechts",
+			"Outdent": "Uitspringen",
+			"Paste": "Plakken",
+			"Redo": "Opnieuw uitvoeren",
+			"Remove formatting": "Opmaak verwijderen",
+			"Right to Left": "Rechts naar Links",
+			"Strike-through": "Doorstrepen",
+			"Subscript": "Subscript",
+			"Superscript": "Superscript",
+			"Underline": "Onderstrepen",
+			"Undo": "Ongedaan maken"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Toepassen",
+			"Cancel": "Annuleren",
+
+			colorpicker: {
+				"Colorpicker": "Kleur kiezen",
+				"Color": "Kleur"
+			},
+
+			image: {
+				"Insert Image": "Afbeeldingen invoegen",
+				"Preview": "Voorbeeld",
+				"URL": "URL",
+				"Title": "Titel",
+				"Description": "Beschrijving",
+				"Width": "Breedte",
+				"Height": "Hoogte",
+				"Original W x H": "Originele B x H",
+				"Float": "Float",
+				"None": "None",
+				"Left": "Left",
+				"Right": "Right"
+			},
+
+			link: {
+				"Insert Link": "Link invoegen",
+				"Link URL": "Link URL",
+				"Link Title": "Linktitel",
+				"Link Target": "Link target"
+			},
+
+			table: {
+				"Insert table": "Tabel invoegen",
+				"Count of columns": "Aantal kolommen",
+				"Count of rows": "Aantal rijen"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.pl.js b/media/js/jwysiwyg/i18n/lang.pl.js
new file mode 100644
index 0000000..fc8d44d
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.pl.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: Polish language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Andrzej Herok
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.pl.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.pl.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.pl = {
+		controls: {
+			"Bold": "Pogrubienie",
+			"Colorpicker": "Wybór koloru",
+			"Copy": "Kopiuj",
+			"Create link": "Utwórz łącze",
+			"Cut": "Wytnij",
+			"Decrease font size": "Zmniejsz rozmiar czcionki",
+			"Fullscreen": "Pełny ekran",
+			"Header 1": "Nagłówek 1",
+			"Header 2": "Nagłówek 2",
+			"Header 3": "Nagłówek 3",
+			"View source code": "Pokaż kod źródłowy",
+			"Increase font size": "Zwiększ rozmiar czcionki",
+			"Indent": "Zwiększ wcięcie",
+			"Insert Horizontal Rule": "Wstaw poziomą linię",
+			"Insert image": "Wstaw obrazek",
+			"Insert Ordered List": "Lista numerowana",
+			"Insert table": "Wstaw tabelę",
+			"Insert Unordered List": "Lista nienumerowana",
+			"Italic": "Kursywa",
+			"Justify Center": "Wyśrodkuj",
+			"Justify Full": "Justowanie",
+			"Justify Left": "Do lewej",
+			"Justify Right": "Do prawej",
+			"Left to Right": "Od lewej do prawej",
+			"Outdent": "Zmniejsz wcięcie",
+			"Paste": "Wklej",
+			"Redo": "Powtórz",
+			"Remove formatting": "Usuń formatowanie",
+			"Right to Left": "Od prawej do lewej",
+			"Strike-through": "Przekreślenie",
+			"Subscript": "Indeks dolny",
+			"Superscript": "Indeks górny",
+			"Underline": "Podkreślenie",
+			"Undo": "Cofnij"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Zastosuj",
+			"Cancel": "Anuluj",
+
+			colorpicker: {
+				"Colorpicker": "Próbnik koloru",
+				"Color": "Kolor"
+			},
+
+			image: {
+				"Insert Image": "Wstaw obrazek",
+				"Preview": "Podgląd",
+				"URL": "URL",
+				"Title": "Tytuł",
+				"Description": "Opis",
+				"Width": "Szerokość",
+				"Height": "Wysokość",
+				"Original W x H": "Oryginalne wymiary",
+				"Float": "Przyleganie",
+				"None": "Brak",
+				"Left": "Do lewej",
+				"Right": "Do prawej"
+			},
+
+			link: {
+				"Insert Link": "Wstaw łącze",
+				"Link URL": "URL łącza",
+				"Link Title": "Tytuł łącza",
+				"Link Target": "Target"
+			},
+
+			table: {
+				"Insert table": "Wstaw tabelę",
+				"Count of columns": "Liczba kolumn",
+				"Count of rows": "Liczba wierszy"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.pt_br.js b/media/js/jwysiwyg/i18n/lang.pt_br.js
new file mode 100644
index 0000000..3fee619
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.pt_br.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: Brazilian Portugese language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Marcelo Wergles <mwergles at gmail.com>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.pt_br.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.pt_br.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.pt_br = {
+		controls: {
+			"Bold": "Negrito",
+			"Colorpicker": "Escolher uma cor",
+			"Copy": "Copiar",
+			"Create link": "Criar link",
+			"Cut": "Recortar",
+			"Decrease font size": "Diminuir o tamanho da fonte",
+			"Fullscreen": "Tela cheia",
+			"Header 1": "Título 1",
+			"Header 2": "Título 2",
+			"Header 3": "Título 3",
+			"View source code": "Ver código fonte",
+			"Increase font size": "Aumentar o tamanho da fonte",
+			"Indent": "Aumentar recuo",
+			"Insert Horizontal Rule": "Inserir linha horizontal",
+			"Insert image": "Inserir imagem",
+			"Insert Ordered List": "Inserir numeração",
+			"Insert table": "Inserir tabela",
+			"Insert Unordered List": "Inserir marcadores",
+			"Italic": "Itálico",
+			"Justify Center": "Centralizar",
+			"Justify Full": "Justificar",
+			"Justify Left": "Alinhar à esquerda",
+			"Justify Right": "Alinhar à direita",
+			"Left to Right": "Esquerda à direita",
+			"Outdent": "Diminuir recuo",
+			"Paste": "Colar",
+			"Redo": "Refazer",
+			"Remove formatting": "Remover formatação",
+			"Right to Left": "Direita à esquerda",
+			"Strike-through": "Riscar",
+			"Subscript": "Subscrito",
+			"Superscript": "Sobrescrito",
+			"Underline": "Sublinhar",
+			"Undo": "Desfazer"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Aplicar",
+			"Cancel": "Cancelar",
+
+			colorpicker: {
+				"Colorpicker": "Escolher uma cor",
+				"Color": "Cor"
+			},
+
+			image: {
+				"Insert Image": "Inserir Imagem",
+				"Preview": "Pré-visualizar",
+				"URL": "URL",
+				"Title": "Título",
+				"Description": "Descrição",
+				"Width": "Largura",
+				"Height": "Altura",
+				"Original W x H": "L x A original",
+				"Float": "",
+				"None": "",
+				"Left": "",
+				"Right": ""
+			},
+
+			link: {
+				"Insert Link": "Inserir Link",
+				"Link URL": "URL do link",
+				"Link Title": "Título do link",
+				"Link Target": "Alvo do link"
+			},
+
+			table: {
+				"Insert table": "Inserir tabela",
+				"Count of columns": "Número de colunas",
+				"Count of rows": "Número de linhas"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.ru.js b/media/js/jwysiwyg/i18n/lang.ru.js
new file mode 100644
index 0000000..43d206a
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.ru.js
@@ -0,0 +1,117 @@
+/**
+ * Internationalization: Russian language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ * 
+ * By: frost-nzcr4 on github.com
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.ru.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.ru.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.ru = {
+		controls: {
+			"Bold": "Жирный",
+			"Colorpicker": "Выбор цвета",
+			"Copy": "Копировать",
+			"Create link": "Создать ссылку",
+			"Cut": "Вырезать",
+			"Decrease font size": "Уменьшить шрифт",
+			"File Manager": "Управление файлами",
+			"Fullscreen": "На весь экран",
+			"Header 1": "Заголовок 1",
+			"Header 2": "Заголовок 2",
+			"Header 3": "Заголовок 3",
+			"View source code": "Посмотреть исходный код",
+			"Increase font size": "Увеличить шрифт",
+			"Indent": "Отступ",
+			"Insert Horizontal Rule": "Вставить горизонтальную прямую",
+			"Insert image": "Вставить изображение",
+			"Insert Ordered List": "Вставить нумерованный список",
+			"Insert table": "Вставить таблицу",
+			"Insert Unordered List": "Вставить ненумерованный список",
+			"Italic": "Курсив",
+			"Justify Center": "Выровнять по центру",
+			"Justify Full": "Выровнять по ширине",
+			"Justify Left": "Выровнять по левой стороне",
+			"Justify Right": "Выровнять по правой стороне",
+			"Left to Right": "Слева направо",
+			"Outdent": "Убрать отступ",
+			"Paste": "Вставить",
+			"Redo": "Вернуть действие",
+			"Remove formatting": "Убрать форматирование",
+			"Right to Left": "Справа налево",
+			"Strike-through": "Зачёркнутый",
+			"Subscript": "Нижний регистр",
+			"Superscript": "Верхний регистр",
+			"Underline": "Подчёркнутый",
+			"Undo": "Отменить действие"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Применить",
+			"Cancel": "Отмена",
+
+			colorpicker: {
+				"Colorpicker": "Выбор цвета",
+				"Color": "Цвет"
+			},
+
+			fileManager: {
+				"file_manager": 		"Управление файлами",
+				"upload_title":			"Загрузить файл",
+				"rename_title":			"Переименовать файл",
+				"remove_title":			"Удалить файл",
+				"mkdir_title":			"Создать папку",
+				"upload_action": 		"Загружает новый файл в текущую папку",
+				"mkdir_action": 		"Создаёт новую папку",
+				"remove_action": 		"Удалить этот файл",
+				"rename_action": 		"Переименовать этот файл" ,	
+				"delete_message": 		"Хотите удалить этот файл?",
+				"new_directory": 		"Новая папка",
+				"previous_directory": 	"Вернуться к предыдущей папке",
+				"rename":				"Переименовать",
+				"select": 				"Выбрать",
+				"create": 				"Создать",
+				"submit": 				"Послать",
+				"cancel": 				"Отмена",
+				"yes":					"Да",
+				"no":					"Нет"
+			},
+
+			image: {
+				"Insert Image": "Вставить изображение",
+				"Preview": "Просмотр",
+				"URL": "URL адрес",
+				"Title": "Название",
+				"Description": "Альт. текст",
+				"Width": "Ширина",
+				"Height": "Высота",
+				"Original W x H": "Оригинальные Ш x В",
+				"Float": "Положение",
+				"None": "Не выбрано",
+				"Left": "Слева",
+				"Right": "Справа",
+				"Select file from server": "Выбрать файл с сервера"
+			},
+
+			link: {
+				"Insert Link": "Вставить ссылку",
+				"Link URL": "URL адрес",
+				"Link Title": "Название",
+				"Link Target": "Цель"
+			},
+
+			table: {
+				"Insert table": "Вставить таблицу",
+				"Count of columns": "Кол-во колонок",
+				"Count of rows": "Кол-во строк"
+			}
+		}
+	};
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/i18n/lang.se.js b/media/js/jwysiwyg/i18n/lang.se.js
new file mode 100644
index 0000000..9c6cb4b
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.se.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: swedish language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: ippa at rubylicio.us
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.se.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.se.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.se = {
+		controls: {
+			"Bold": "Tjock",
+			"Colorpicker": "",
+			"Copy": "Kopiera",
+			"Create link": "Skapa länk",
+			"Cut": "Klipp",
+			"Decrease font size": "Minska storlek",
+			"Fullscreen": "",
+			"Header 1": "Rubrik 1",
+			"Header 2": "Rubrik 2",
+			"Header 3": "Rubrik 3",
+			"View source code": "Se källkod",
+			"Increase font size": "Öka fontstorlek",
+			"Indent": "Öka indrag",
+			"Insert Horizontal Rule": "Lägg in vertical avskiljare ",
+			"Insert image": "Infoga bild",
+			"Insert Ordered List": "Infoga numrerad lista",
+			"Insert table": "Infoga tabell",
+			"Insert Unordered List": "Infoga lista",
+			"Italic": "Kursiv",
+			"Justify Center": "Centrera",
+			"Justify Full": "Marginaljustera",
+			"Justify Left": "Vänsterjustera",
+			"Justify Right": "Högerjustera",
+			"Left to Right": "Vänster till höger",
+			"Outdent": "Minska indrag",
+			"Paste": "Klistra",
+			"Redo": "Gör om",
+			"Remove formatting": "Ta bort formatering",
+			"Right to Left": "Höger till vänster",
+			"Strike-through": "Genomstrykning",
+			"Subscript": "Subscript",
+			"Superscript": "Superscript",
+			"Underline": "Understruken",
+			"Undo": "Ångra"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Applicera",
+			"Cancel": "Avbryt",
+
+			colorpicker: {
+				"Colorpicker": "Färgval",
+				"Color": "Färg"
+			},
+
+			image: {
+				"Insert Image": "Lägg in bild",
+				"Preview": "Förhandsgranska",
+				"URL": "URL",
+				"Title": "Rubrik",
+				"Description": "Beskrivning",
+				"Width": "Bredd",
+				"Height": "Höjd",
+				"Original W x H": "Original Bredd x Höjd",
+				"Float": "Flytande",
+				"None": "Ingen",
+				"Left": "Vänster",
+				"Right": "Höger"
+			},
+
+			link: {
+				"Insert Link": "Skapa länk",
+				"Link URL": "LänkURL",
+				"Link Title": "Länkrubrik",
+				"Link Target": "Länkmål"
+			},
+
+			table: {
+				"Insert table": "Skapa tabell",
+				"Count of columns": "Antal kolumner",
+				"Count of rows": "Antal rader"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.sl.js b/media/js/jwysiwyg/i18n/lang.sl.js
new file mode 100644
index 0000000..5a740e8
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.sl.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: Slovenian language
+ *
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Peter Zlatnar <peter.zlatnar at gmail.com>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.sl.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.sl.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang.sl = {
+		controls: {
+			"Bold": "Krepko",
+			"Colorpicker": "Izbirnik barv",
+			"Copy": "Kopiraj",
+			"Create link": "Dodaj povezavo",
+			"Cut": "Izreži",
+			"Decrease font size": "Zmanjšaj pisavo",
+			"Fullscreen": "Celozaslonski način",
+			"Header 1": "Naslov 1",
+			"Header 2": "Naslov 2",
+			"Header 3": "Naslov 3",
+			"View source code": "Prikaži izvorno kodo",
+			"Increase font size": "Povečaj pisavo",
+			"Indent": "Zamik v desno",
+			"Insert Horizontal Rule": "Vstavi vodoravno črto ",
+			"Insert image": "Vstavi sliko",
+			"Insert Ordered List": "Vstavi oštevilčen seznam",
+			"Insert table": "Vstavi tabelo",
+			"Insert Unordered List": "Vstavi označen seznam",
+			"Italic": "Ležeče",
+			"Justify Center": "Sredinska poravnava",
+			"Justify Full": "Obojestranska poravnava",
+			"Justify Left": "Leva poravnava",
+			"Justify Right": "Desna poravnava",
+			"Left to Right": "Od leve proti desni",
+			"Outdent": "Zamik v levo",
+			"Paste": "Prilepi",
+			"Redo": "Ponovi",
+			"Remove formatting": "Odstrani oblikovanje",
+			"Right to Left": "Od desne proti levi",
+			"Strike-through": "Prečrtano",
+			"Subscript": "Podpisano",
+			"Superscript": "Nadpisano",
+			"Underline": "Podčrtano",
+			"Undo": "Razveljavi"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Uporabi",
+			"Cancel": "Prekliči",
+			
+			colorpicker: {
+				"Colorpicker": "Izbirnik barv",
+				"Color": "Barva"
+			},
+
+			image: {
+				"Insert Image": "Vstavi sliko",
+				"Preview": "Predogled",
+				"URL": "URL",
+				"Title": "Naslov",
+				"Description": "Opis",
+				"Width": "Širina",
+				"Height": "Višina",
+				"Original W x H": "Prvotna Š x V",
+				"Float": "",
+				"None": "",
+				"Left": "",
+				"Right": ""
+			},
+
+			link: {
+				"Insert Link": "Vstavi povezavo",
+				"Link URL": "URL povezave",
+				"Link Title": "Naslov povezave",
+				"Link Target": "Cilj povezave"
+			},
+
+			table: {
+				"Insert table": "Vstavi tabelo",
+				"Count of columns": "Število stolpcev",
+				"Count of rows": "Število vrstic"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.tr.js b/media/js/jwysiwyg/i18n/lang.tr.js
new file mode 100644
index 0000000..7a9b26c
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.tr.js
@@ -0,0 +1,94 @@
+/**
+ * Internationalization: Turkish language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ *
+ * By: Kadir Atesoglu <kadir.atesoglu at gmail.com>
+ *
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.tr.js, $.wysiwyg olmadan çalışamaz";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+	    throw "lang.tr.js, $.wysiwyg.i18n olmadan çalışamaz";
+	}
+
+	$.wysiwyg.i18n.lang.de = {
+		controls: {
+			"Bold": "Kalın",
+			"Colorpicker": "Renk Seçimi",
+			"Copy": "Kopyala",
+			"Create link": "Link Oluştur",
+			"Cut": "Kes",
+			"Decrease font size": "Font Küçült",
+			"Fullscreen": "Tam Ekran",
+			"Header 1": "Başlık 1",
+			"Header 2": "Başlık 2",
+			"Header 3": "Başlık 3",
+			"View source code": "Kaynak Kod",
+			"Increase font size": "Font Büyült",
+			"Indent": "Girinti",
+			"Insert Horizontal Rule": "Yatay Çizgi Ekle",
+			"Insert image": "Resim Ekle",
+			"Insert Ordered List": "Sıralı Liste Ekle",
+			"Insert table": "Tablo Ekle",
+			"Insert Unordered List": "Sırasız Liste Ekle",
+			"Italic": "İtalik",
+			"Justify Center": "Ortala",
+			"Justify Full": "İki Yana Dayalı",
+			"Justify Left": "Sola Dayalı",
+			"Justify Right": "Sağa Dayalı",
+			"Left to Right": "Soldan Sağa",
+			"Outdent": "Çıkıntı",
+			"Paste": "Yapıştır",
+			"Redo": "Yinele",
+			"Remove formatting": "Format Temizle",
+			"Right to Left": "Sağdan Sola",
+			"Strike-through": "Üstçizgi",
+			"Subscript": "Kök",
+			"Superscript": "Kare",
+			"Underline": "Altçizgi",
+			"Undo": "Geri Al"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "Uygula",
+			"Cancel": "Vazgeç",
+
+			colorpicker: {
+				"Colorpicker": "Renk Seç",
+				"Color": "Renk"
+			},
+
+			image: {
+				"Insert Image": "Resim Ekle",
+				"Preview": "Önizleme",
+				"URL": "Link",
+				"Title": "Başlık",
+				"Description": "Açıklama",
+				"Width": "Genişlik",
+				"Height": "Yükseklik",
+				"Original W x H": "Orjinal Genişlik * Yükseklik",
+				"Float": "Hizalama",
+				"None": "Yok",
+				"Left": "Sol",
+				"Right": "Sağ"
+			},
+
+			link: {
+				"Insert Link": "Link Ekle",
+				"Link URL": "Link Adresi",
+				"Link Title": "Link Başlığı",
+				"Link Target": "Link Davranışı"
+			},
+
+			table: {
+				"Insert table": "Tablo Ekle",
+				"Count of columns": "Sütun Sayısı",
+				"Count of rows": "Satır Sayısı"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/i18n/lang.zh-cn.js b/media/js/jwysiwyg/i18n/lang.zh-cn.js
new file mode 100644
index 0000000..5b69f9f
--- /dev/null
+++ b/media/js/jwysiwyg/i18n/lang.zh-cn.js
@@ -0,0 +1,93 @@
+/**
+ * Internationalization: Chinese (Simplified) language
+ * 
+ * Depends on jWYSIWYG, $.wysiwyg.i18n
+ * 
+ * By: https://github.com/mengxy
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "lang.zh-cn.js depends on $.wysiwyg";
+	}
+	if (undefined === $.wysiwyg.i18n) {
+		throw "lang.zh-cn.js depends on $.wysiwyg.i18n";
+	}
+
+	$.wysiwyg.i18n.lang['zh-cn'] = {
+		controls: {
+			"Bold": "加粗",
+			"Colorpicker": "取色器",
+			"Copy": "复制",
+			"Create link": "创建链接",
+			"Cut": "剪切",
+			"Decrease font size": "减小字号",
+			"Fullscreen": "全屏",
+			"Header 1": "标题1",
+			"Header 2": "标题2",
+			"Header 3": "标题3",
+			"View source code": "查看源码",
+			"Increase font size": "增大字号",
+			"Indent": "缩进",
+			"Insert Horizontal Rule": "插入水平线",
+			"Insert image": "插入图片",
+			"Insert Ordered List": "插入有序列表",
+			"Insert table": "插入表格",
+			"Insert Unordered List": "插入无序列表",
+			"Italic": "斜体",
+			"Justify Center": "居中对齐",
+			"Justify Full": "填充整行",
+			"Justify Left": "左对齐",
+			"Justify Right": "右对齐",
+			"Left to Right": "从左到右",
+			"Outdent": "取消缩进",
+			"Paste": "粘贴",
+			"Redo": "前进",
+			"Remove formatting": "清除格式",
+			"Right to Left": "从右到左",
+			"Strike-through": "删除线",
+			"Subscript": "上角标",
+			"Superscript": "下角标",
+			"Underline": "下划线",
+			"Undo": "撤销"
+		},
+
+		dialogs: {
+			// for all
+			"Apply": "应用",
+			"Cancel": "取消",
+
+			colorpicker: {
+				"Colorpicker": "取色器",
+				"Color": "颜色"
+			},
+
+			image: {
+				"Insert Image": "插入图片",
+				"Preview": "预览",
+				"URL": "URL",
+				"Title": "标题",
+				"Description": "描述",
+				"Width": "宽度",
+				"Height": "高度",
+				"Original W x H": "原始宽高",
+				"Float": "浮动",
+				"None": "无",
+				"Left": "左",
+				"Right": "右"
+			},
+
+			link: {
+				"Insert Link": "插入链接",
+				"Link URL": "链接URL",
+				"Link Title": "链接Title",
+				"Link Target": "链接Target"
+			},
+
+			table: {
+				"Insert table": "插入表格",
+				"Count of columns": "列数",
+				"Count of rows": "行数"
+			}
+		}
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/jquery.wysiwyg.bg.png b/media/js/jwysiwyg/jquery.wysiwyg.bg.png
new file mode 100644
index 0000000..477e85c
Binary files /dev/null and b/media/js/jwysiwyg/jquery.wysiwyg.bg.png differ
diff --git a/media/js/jwysiwyg/jquery.wysiwyg.css b/media/js/jwysiwyg/jquery.wysiwyg.css
new file mode 100644
index 0000000..a2ea312
--- /dev/null
+++ b/media/js/jwysiwyg/jquery.wysiwyg.css
@@ -0,0 +1,89 @@
+div.wysiwyg { border: 1px solid #999; padding: 0; background: #fff url('jquery.wysiwyg.bg.png') repeat-x top; -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); }
+div.wysiwyg * { margin: 0; padding: 0; }
+
+div.wysiwyg ul.toolbar li.jwysiwyg-custom-command { overflow: hidden; }
+
+div.wysiwyg div.toolbar-wrap { width: 100%; border-bottom: 1px solid #ccc; }
+div.wysiwyg ul.toolbar { list-style: none;  float: left; padding: 0 3px 0 3px; margin: 0; }
+div.wysiwyg ul.toolbar li { display: block; float: left; width: 18px; height: 18px; padding: 0; border: 1px solid transparent; margin: 3px 0 3px 0; text-indent: -5000px; background: url('jquery.wysiwyg.gif') no-repeat -640px -800px; cursor: pointer; -moz-user-select: none; -webkit-user-select: none; user-select: none; }
+div.wysiwyg ul.toolbar li.separator { background: none; width: 1px; height: 20px; margin: 3px 4px 3px 5px; border: none; border-left: 1px solid #ccc; }
+div.wysiwyg ul.toolbar li.wysiwyg-button-hover,div.wysiwyg ul.toolbar li.active { background-color: transparent; width: 18px; height: 18px; border: 1px solid rgb(208, 208, 208); border-left-color: #aaa; border-top-color: #aaa; }
+div.wysiwyg ul.toolbar li.disabled { background-color: transparent; opacity: 0.5; filter:alpha(opacity=50); cursor: auto; }
+
+div.wysiwyg ul.toolbar li.bold { background-position: -1px -15px; }
+div.wysiwyg ul.toolbar li.italic { background-position: -18px -15px; }
+div.wysiwyg ul.toolbar li.strikeThrough { background-position: -36px -15px; }
+div.wysiwyg ul.toolbar li.underline { background-position: -55px -15px; }
+div.wysiwyg ul.toolbar li.highlight { background-position: -48px -96px; }
+
+div.wysiwyg ul.toolbar li.justifyLeft { background-position: 0 2px; }
+div.wysiwyg ul.toolbar li.justifyCenter { background-position: -18px 2px; }
+div.wysiwyg ul.toolbar li.justifyRight { background-position: -36px 2px; }
+div.wysiwyg ul.toolbar li.justifyFull { background-position: -55px 2px; }
+
+div.wysiwyg ul.toolbar li.indent { background-position: -74px 1px; }
+div.wysiwyg ul.toolbar li.outdent { background-position: -92px 1px; }
+
+div.wysiwyg ul.toolbar li.subscript { background-position: -74px -15px; }
+div.wysiwyg ul.toolbar li.superscript { background-position: -92px -15px; }
+
+div.wysiwyg ul.toolbar li.undo { background-position: 0px -68px; }
+div.wysiwyg ul.toolbar li.redo { background-position: -18px -69px; }
+
+div.wysiwyg ul.toolbar li.insertOrderedList { background-position: -36px -49px; }
+div.wysiwyg ul.toolbar li.insertUnorderedList { background-position: -19px -49px; }
+div.wysiwyg ul.toolbar li.insertHorizontalRule { background-position: 0 -49px; }
+
+div.wysiwyg ul.toolbar li.h1 { background-position: 0px -31px; }
+div.wysiwyg ul.toolbar li.h2 { background-position: -18px -31px; }
+div.wysiwyg ul.toolbar li.h3 { background-position: -36px -31px; }
+div.wysiwyg ul.toolbar li.h4 { background-position: -55px -31px; }
+div.wysiwyg ul.toolbar li.h5 { background-position: -74px -31px; }
+div.wysiwyg ul.toolbar li.h6 { background-position: -92px -31px; }
+
+div.wysiwyg ul.toolbar li.paragraph { background-position: 0px -106px; }
+div.wysiwyg ul.toolbar li.colorpicker { background-position: -18px -106px; }
+div.wysiwyg ul.toolbar li.fullscreen { background-position: -36px -106px; }
+
+div.wysiwyg ul.toolbar li.cut { background-position: -36px -68px; }
+div.wysiwyg ul.toolbar li.copy { background-position: -55px -68px; }
+div.wysiwyg ul.toolbar li.paste { background-position: -74px -68px; }
+div.wysiwyg ul.toolbar li.insertTable { background-position: -74px -49px; }
+
+div.wysiwyg ul.toolbar li.increaseFontSize { background-position: -18px -87px; }
+div.wysiwyg ul.toolbar li.decreaseFontSize { background-position: -36px -87px; }
+
+div.wysiwyg ul.toolbar li.createLink { background-position: -92px -48px; }
+div.wysiwyg ul.toolbar li.unLink { background-position: -74px -87px; }
+div.wysiwyg ul.toolbar li.insertImage { background-position: -92px -87px; }
+
+div.wysiwyg ul.toolbar li.html { background-position: -55px -49px; }
+div.wysiwyg ul.toolbar li.removeFormat { background-position: -92px -68px; }
+
+div.wysiwyg ul.toolbar li.empty { background-position: -73px -86px; }
+
+div.wysiwyg ul.toolbar li.code { background-position: -74px -106px; }
+div.wysiwyg ul.toolbar li.cssWrap { background-position: -92px -106px; }
+
+div.wysiwyg-dialogRow { float:left; width:100%; font-size: 16px; }
+
+div.wysiwyg iframe { clear: left; background-color:white; padding:0px; margin:0; display:block; width: 100%; }
+div.wysiwyg > textarea { border: 0; outline: none; box-shadow: none; }
+
+/* dialog */
+.wysiwyg-dialog { position:fixed; top:50px; left:50px; width:450px; height:300px; background:transparent; font:14px "Helvetic Neue", Helvetica,Arial,sans-serif; }
+.wysiwyg-dialog .wysiwyg-dialog-topbar { background:#333; border:1px #111 solid; color:white; padding:10px; position:relative; }
+.wysiwyg-dialog .wysiwyg-dialog-topbar .wysiwyg-dialog-close-wrapper .wysiwyg-dialog-close-button { color:white; text-decoration:none; display:block; padding:6px 10px; position:absolute; right:12px; top:50%; height:14px; margin-top:-12px; }
+.wysiwyg-dialog .wysiwyg-dialog-topbar .wysiwyg-dialog-close-wrapper a.wysiwyg-dialog-close-button:hover { background:#666; }
+.wysiwyg-dialog .wysiwyg-dialog-topbar .wysiwyg-dialog-title { font-size:20px; font-weight:bold; padding:5px; }
+.wysiwyg-dialog .wysiwyg-dialog-content { border:1px #ccc solid; border-top:0; padding:15px; background:white; }
+.wysiwyg-dialog-modal-div { position:absolute; top:0px; left:0px; width:100%; height:100%; background-color:rgb(255,255,255); background-color:rgba(0,0,0,0.5); filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000); -ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";}
+.wysiwyg-dialog-content form.wysiwyg fieldset { border:1px #ccc solid; }
+.wysiwyg-dialog-content form.wysiwyg legend { padding:7px; }
+.wysiwyg-dialog-content form.wysiwyg .form-row { clear:both; padding:4px 0; }
+.wysiwyg-dialog-content form.wysiwyg .form-row label, .wysiwyg-dialog form.wysiwyg .form-row .form-row-key { display:block; float:left; width:35%; text-align:right; padding:4px 5px; }
+.wysiwyg-dialog-content form.wysiwyg .form-row .form-row-value { display:block; float:left; width:55%; }
+.wysiwyg-dialog-content form.wysiwyg .form-row input.width-auto { width:auto; }
+.wysiwyg-dialog-content form.wysiwyg input.width-small { width:50px; min-width:50px; max-width:50px; }
+.wysiwyg-dialog-content form.wysiwyg input, .wysiwyg-dialog form.wysiwyg select { padding:2px; width:100%; margin:2px; }
+.wysiwyg-dialog-content form.wysiwyg input[type=submit], .wysiwyg-dialog form.wysiwyg input[type=reset] { padding:2px 7px; width:auto; }
diff --git a/media/js/jwysiwyg/jquery.wysiwyg.gif b/media/js/jwysiwyg/jquery.wysiwyg.gif
new file mode 100644
index 0000000..e24631e
Binary files /dev/null and b/media/js/jwysiwyg/jquery.wysiwyg.gif differ
diff --git a/media/js/jwysiwyg/jquery.wysiwyg.jpg b/media/js/jwysiwyg/jquery.wysiwyg.jpg
new file mode 100644
index 0000000..6fbc197
Binary files /dev/null and b/media/js/jwysiwyg/jquery.wysiwyg.jpg differ
diff --git a/media/js/jwysiwyg/jquery.wysiwyg.js b/media/js/jwysiwyg/jquery.wysiwyg.js
new file mode 100644
index 0000000..e2c96e1
--- /dev/null
+++ b/media/js/jwysiwyg/jquery.wysiwyg.js
@@ -0,0 +1,2538 @@
+/**
+ * WYSIWYG - jQuery plugin 0.97
+ * (0.97.2 - From infinity)
+ *
+ * Copyright (c) 2008-2009 Juan M Martinez, 2010-2011 Akzhan Abdulin and all contributors
+ * https://github.com/akzhan/jwysiwyg
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ */
+
+/*jslint browser: true, forin: true, white: true */
+
+(function ($) {
+	"use strict";
+	/* Wysiwyg namespace: private properties and methods */
+
+	var console = window.console || {
+		log: $.noop,
+		error: function (msg) {
+			$.error(msg);
+		}
+	},
+	supportsProp = ($.fn.prop !== undefined) && ($.fn.removeProp !== undefined);
+
+	function Wysiwyg() {
+		// - the item is added by this.ui.appendControls and then appendItem
+		// - click triggers this.triggerControl
+		// cmd or[key] - designMode exec function name
+		// tags - activates control for these tags (@see checkTargets)
+		// css - activates control if one of css is applied
+		this.controls = {
+			bold: {
+				groupIndex: 0,
+				visible: true,
+				tags: ["b", "strong"],
+				css: {
+					fontWeight: "bold"
+				},
+				tooltip: "Bold",
+				hotkey: {"ctrl": 1, "key": 66}
+			},
+
+			copy: {
+				groupIndex: 8,
+				visible: false,
+				tooltip: "Copy"
+			},
+
+			createLink: {
+				groupIndex: 6,
+				visible: true,
+				exec: function () {
+					var self = this;
+					if ($.wysiwyg.controls && $.wysiwyg.controls.link) {
+						$.wysiwyg.controls.link.init(this);
+					} else if ($.wysiwyg.autoload) {
+						$.wysiwyg.autoload.control("wysiwyg.link.js", function () {
+							self.controls.createLink.exec.apply(self);
+						});
+					} else {
+						console.error("$.wysiwyg.controls.link not defined. You need to include wysiwyg.link.js file");
+					}
+				},
+				tags: ["a"],
+				tooltip: "Create link"
+			},
+			
+			unLink : {
+				groupIndex: 6,
+				visible: true,
+				exec : function() {
+					this.editorDoc.execCommand("unlink", false, null);
+				},
+				tooltip: "Remove link"
+			},
+
+			cut: {
+				groupIndex: 8,
+				visible: false,
+				tooltip: "Cut"
+			},
+
+			decreaseFontSize: {
+				groupIndex: 9,
+				visible: false,
+				tags: ["small"],
+				tooltip: "Decrease font size",
+				exec: function () {
+					this.decreaseFontSize();
+				}
+			},
+
+			h1: {
+				groupIndex: 7,
+				visible: true,
+				className: "h1",
+				command: ($.browser.msie || $.browser.opera) ? "FormatBlock" : "heading",
+				"arguments": ($.browser.msie || $.browser.opera) ? "<h1>" : "h1",
+				tags: ["h1"],
+				tooltip: "Header 1"
+			},
+
+			h2: {
+				groupIndex: 7,
+				visible: true,
+				className: "h2",
+				command: ($.browser.msie || $.browser.opera)	? "FormatBlock" : "heading",
+				"arguments": ($.browser.msie || $.browser.opera) ? "<h2>" : "h2",
+				tags: ["h2"],
+				tooltip: "Header 2"
+			},
+
+			h3: {
+				groupIndex: 7,
+				visible: true,
+				className: "h3",
+				command: ($.browser.msie || $.browser.opera) ? "FormatBlock" : "heading",
+				"arguments": ($.browser.msie || $.browser.opera) ? "<h3>" : "h3",
+				tags: ["h3"],
+				tooltip: "Header 3"
+			},
+
+			highlight: {
+				tooltip:     "Highlight",
+				className:   "highlight",
+				groupIndex:  1,
+				visible:     false,
+				css: {
+					backgroundColor: "rgb(255, 255, 102)"
+				},
+				exec: function () {
+					var command, node, selection, args;
+
+					if ($.browser.msie || $.browser.opera) {
+						command = "backcolor";
+					} else {
+						command = "hilitecolor";
+					}
+
+					if ($.browser.msie) {
+						node = this.getInternalRange().parentElement();
+					} else {
+						selection = this.getInternalSelection();
+						node = selection.extentNode || selection.focusNode;
+
+						while (node.style === undefined) {
+							node = node.parentNode;
+							if (node.tagName && node.tagName.toLowerCase() === "body") {
+								return;
+							}
+						}
+					}
+
+					if (node.style.backgroundColor === "rgb(255, 255, 102)" ||
+							node.style.backgroundColor === "#ffff66") {
+						args = "#ffffff";
+					} else {
+						args = "#ffff66";
+					}
+
+					this.editorDoc.execCommand(command, false, args);
+				}
+			},
+
+			html: {
+				groupIndex: 10,
+				visible: false,
+				exec: function (preProcessor, postProcessor) {
+					var elementHeight;
+
+					if (this.options.resizeOptions && $.fn.resizable) {
+						elementHeight = this.element.height();
+					}
+
+					if (this.viewHTML) { //textarea is shown
+						this.setContent((typeof postProcessor === 'function') ? postProcessor(this.original.value) : this.original.value);
+
+						$(this.original).hide();
+						this.editor.show();
+
+						if (this.options.resizeOptions && $.fn.resizable) {
+							// if element.height still the same after frame was shown
+							if (elementHeight === this.element.height()) {
+								this.element.height(elementHeight + this.editor.height());
+							}
+
+							this.element.resizable($.extend(true, {
+								alsoResize: this.editor
+							}, this.options.resizeOptions));
+						}
+						
+						this.ui.toolbar.find("li").each(function () {
+							var li = $(this);
+
+							if (li.hasClass("html")) {
+								li.removeClass("active");
+							} else {
+								li.removeClass('disabled');
+							}
+						});
+					} else { //wysiwyg is shown
+						this.saveContent(preProcessor);
+
+						$(this.original).css({
+							width:	this.editor.width(),
+							height: this.editor.height(),
+							resize: "none"
+						}).show();
+						this.editor.hide();
+						
+						if (this.options.resizeOptions && $.fn.resizable) {
+							// if element.height still the same after frame was hidden
+							if (elementHeight === this.element.height()) {
+								this.element.height(this.ui.toolbar.height());
+							}
+
+							this.element.resizable("destroy");
+						}
+
+						this.ui.toolbar.find("li").each(function() {
+							var li = $(this);
+
+							if (li.hasClass("html")) {
+								li.addClass("active");
+							} else {
+								if (false === li.hasClass("fullscreen")) {
+									li.removeClass("active").addClass('disabled');
+								}
+							}
+						});
+					}
+
+					this.viewHTML = !(this.viewHTML);
+				},
+				tooltip: "View source code"
+			},
+
+			increaseFontSize: {
+				groupIndex: 9,
+				visible: false,
+				tags: ["big"],
+				tooltip: "Increase font size",
+				exec: function () {
+					this.increaseFontSize();
+				}
+			},
+
+			indent: {
+				groupIndex: 2,
+				visible: true,
+				tooltip: "Indent"
+			},
+
+			insertHorizontalRule: {
+				groupIndex: 6,
+				visible: true,
+				tags: ["hr"],
+				tooltip: "Insert Horizontal Rule"
+			},
+
+			insertImage: {
+				groupIndex: 6,
+				visible: true,
+				exec: function () {
+					var self = this;
+
+					if ($.wysiwyg.controls && $.wysiwyg.controls.image) {
+						$.wysiwyg.controls.image.init(this);
+					} else if ($.wysiwyg.autoload) {
+						$.wysiwyg.autoload.control("wysiwyg.image.js", function () {
+							self.controls.insertImage.exec.apply(self);
+						});
+					} else {
+						console.error("$.wysiwyg.controls.image not defined. You need to include wysiwyg.image.js file");
+					}
+				},
+				tags: ["img"],
+				tooltip: "Insert image"
+			},
+
+			insertOrderedList: {
+				groupIndex: 5,
+				visible: true,
+				tags: ["ol"],
+				tooltip: "Insert Ordered List"
+			},
+
+			insertTable: {
+				groupIndex: 6,
+				visible: true,
+				exec: function () {
+					var self = this;
+
+					if ($.wysiwyg.controls && $.wysiwyg.controls.table) {
+						$.wysiwyg.controls.table(this);
+					} else if ($.wysiwyg.autoload) {
+						$.wysiwyg.autoload.control("wysiwyg.table.js", function () {
+							self.controls.insertTable.exec.apply(self);
+						});
+					} else {
+						console.error("$.wysiwyg.controls.table not defined. You need to include wysiwyg.table.js file");
+					}
+				},
+				tags: ["table"],
+				tooltip: "Insert table"
+			},
+
+			insertUnorderedList: {
+				groupIndex: 5,
+				visible: true,
+				tags: ["ul"],
+				tooltip: "Insert Unordered List"
+			},
+
+			italic: {
+				groupIndex: 0,
+				visible: true,
+				tags: ["i", "em"],
+				css: {
+					fontStyle: "italic"
+				},
+				tooltip: "Italic",
+				hotkey: {"ctrl": 1, "key": 73}
+			},
+
+			justifyCenter: {
+				groupIndex: 1,
+				visible: true,
+				tags: ["center"],
+				css: {
+					textAlign: "center"
+				},
+				tooltip: "Justify Center"
+			},
+
+			justifyFull: {
+				groupIndex: 1,
+				visible: true,
+				css: {
+					textAlign: "justify"
+				},
+				tooltip: "Justify Full"
+			},
+
+			justifyLeft: {
+				visible: true,
+				groupIndex: 1,
+				css: {
+					textAlign: "left"
+				},
+				tooltip: "Justify Left"
+			},
+
+			justifyRight: {
+				groupIndex: 1,
+				visible: true,
+				css: {
+					textAlign: "right"
+				},
+				tooltip: "Justify Right"
+			},
+
+			ltr: {
+				groupIndex: 10,
+				visible: false,
+				exec: function () {
+					var p = this.dom.getElement("p");
+
+					if (!p) {
+						return false;
+					}
+
+					$(p).attr("dir", "ltr");
+					return true;
+				},
+				tooltip : "Left to Right"
+			},
+
+			outdent: {
+				groupIndex: 2,
+				visible: true,
+				tooltip: "Outdent"
+			},
+
+			paragraph: {
+				groupIndex: 7,
+				visible: false,
+				className: "paragraph",
+				command: "FormatBlock",
+				"arguments": ($.browser.msie || $.browser.opera) ? "<p>" : "p",
+				tags: ["p"],
+				tooltip: "Paragraph"
+			},
+
+			paste: {
+				groupIndex: 8,
+				visible: false,
+				tooltip: "Paste"
+			},
+
+			redo: {
+				groupIndex: 4,
+				visible: true,
+				tooltip: "Redo"
+			},
+
+			removeFormat: {
+				groupIndex: 10,
+				visible: true,
+				exec: function () {
+					this.removeFormat();
+				},
+				tooltip: "Remove formatting"
+			},
+
+			rtl: {
+				groupIndex: 10,
+				visible: false,
+				exec: function () {
+					var p = this.dom.getElement("p");
+
+					if (!p) {
+						return false;
+					}
+
+					$(p).attr("dir", "rtl");
+					return true;
+				},
+				tooltip : "Right to Left"
+			},
+
+			strikeThrough: {
+				groupIndex: 0,
+				visible: true,
+				tags: ["s", "strike"],
+				css: {
+					textDecoration: "line-through"
+				},
+				tooltip: "Strike-through"
+			},
+
+			subscript: {
+				groupIndex: 3,
+				visible: true,
+				tags: ["sub"],
+				tooltip: "Subscript"
+			},
+
+			superscript: {
+				groupIndex: 3,
+				visible: true,
+				tags: ["sup"],
+				tooltip: "Superscript"
+			},
+
+			underline: {
+				groupIndex: 0,
+				visible: true,
+				tags: ["u"],
+				css: {
+					textDecoration: "underline"
+				},
+				tooltip: "Underline",
+				hotkey: {"ctrl": 1, "key": 85}
+			},
+
+			undo: {
+				groupIndex: 4,
+				visible: true,
+				tooltip: "Undo"
+			},
+
+			code: {
+				visible : true,
+				groupIndex: 6,
+				tooltip: "Code snippet",
+				exec: function () {
+					var range	= this.getInternalRange(),
+						common	= $(range.commonAncestorContainer),
+						$nodeName = range.commonAncestorContainer.nodeName.toLowerCase();
+					if (common.parent("code").length) {
+						common.unwrap();
+					} else {
+						if ($nodeName !== "body") {
+							common.wrap("<code/>");
+						}
+					}
+				}
+			},
+			
+			cssWrap: {
+				visible : false,
+				groupIndex: 6,
+				tooltip: "CSS Wrapper",
+				exec: function () { 
+					$.wysiwyg.controls.cssWrap.init(this);
+				}
+			}
+			
+		};
+
+		this.defaults = {
+html: '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" style="margin:0"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head><body style="margin:0;">INITIAL_CONTENT</body></html>',
+			debug: false,
+			controls: {},
+			css: {},
+			events: {},
+			autoGrow: false,
+			autoSave: true,
+			brIE: false,					// http://code.google.com/p/jwysiwyg/issues/detail?id=15
+			formHeight: 270,
+			formWidth: 440,
+			iFrameClass: null,
+			initialContent: "<p>Initial content</p>",
+			maxHeight: 10000,			// see autoGrow
+			maxLength: 0,
+			messages: {
+				nonSelection: "Select the text you wish to link"
+			},
+			toolbarHtml: '<ul role="menu" class="toolbar"></ul>',
+			removeHeadings: false,
+			replaceDivWithP: false,
+			resizeOptions: false,
+			rmUnusedControls: false,	// https://github.com/akzhan/jwysiwyg/issues/52
+			rmUnwantedBr: true,			// http://code.google.com/p/jwysiwyg/issues/detail?id=11
+			tableFiller: "Lorem ipsum",
+			initialMinHeight: null,
+
+			controlImage: {
+				forceRelativeUrls: false
+			},
+
+			controlLink: {
+				forceRelativeUrls: false
+			},
+
+			plugins: { // placeholder for plugins settings
+				autoload: false,
+				i18n: false,
+				rmFormat: {
+					rmMsWordMarkup: false
+				}
+			},
+			
+			dialog : "default"
+		};
+
+		//these properties are set from control hashes
+		this.availableControlProperties = [
+			"arguments",
+			"callback",
+			"callbackArguments",
+			"className",
+			"command",
+			"css",
+			"custom",
+			"exec",
+			"groupIndex",
+			"hotkey",
+			"icon",
+			"separator",
+			"tags",
+			"tooltip",
+			"visible"
+		];
+
+		this.editor			= null;  //jquery iframe holder
+		this.editorDoc		= null;
+		this.element		= null;
+		this.options		= {};
+		this.original		= null;
+		this.savedRange		= null;
+		this.timers			= [];
+		this.validKeyCodes	= [8, 9, 13, 16, 17, 18, 19, 20, 27, 33, 34, 35, 36, 37, 38, 39, 40, 45, 46];
+
+		this.isDestroyed	= false;
+
+		this.dom		= { // DOM related properties and methods
+			ie:		{
+				parent: null // link to dom
+			},
+			w3c:	{
+				parent: null // link to dom
+			}
+		};
+		this.dom.parent		= this;
+		this.dom.ie.parent	= this.dom;
+		this.dom.w3c.parent	= this.dom;
+
+		this.ui			= {};	// UI related properties and methods
+		this.ui.self	= this;
+		this.ui.toolbar	= null;
+		this.ui.initialHeight = null; // ui.grow
+
+		this.dom.getAncestor = function (element, filterTagName) {
+			filterTagName = filterTagName.toLowerCase();
+			
+			while (element && element.tagName !== undefined && "body" !== element.tagName.toLowerCase()) {
+				if (filterTagName === element.tagName.toLowerCase()) {
+					return element;
+				}
+
+				element = element.parentNode;
+			}
+			if(!element.tagName && (element.previousSibling || element.nextSibling)) {
+				if(element.previousSibling) {
+					if(element.previousSibling.tagName.toLowerCase() === filterTagName) {
+						return element.previousSibling;
+					}
+				}	
+				if(element.nextSibling) {
+					if(element.nextSibling.tagName.toLowerCase() === filterTagName) {
+						return element.nextSibling;
+					}
+				}	
+			}
+
+			return null;
+		};
+
+		this.dom.getElement = function (filterTagName) {
+			var dom = this;
+			filterTagName = filterTagName.toLowerCase();			
+			return window.getSelection ? dom.w3c.getElement(filterTagName) : dom.ie.getElement(filterTagName);
+		};
+
+		this.dom.ie.getElement = function (filterTagName) {
+			var dom			= this.parent,
+				selection	= dom.parent.getInternalSelection(),
+				range		= selection.createRange(),
+				element;
+
+			if ("Control" === selection.type) {
+				// control selection
+				if (1 === range.length) {
+					element = range.item(0);
+				} else {
+					// multiple control selection
+					return null;
+				}
+			} else {
+				element = range.parentElement();
+			}
+
+			return dom.getAncestor(element, filterTagName);
+		};
+
+		this.dom.w3c.getElement = function (filterTagName) {
+			var dom		= this.parent,
+				range	= dom.parent.getInternalRange(),
+				element;
+				
+			if (!range) {
+				return null;
+			}
+
+			element	= range.commonAncestorContainer;
+
+			if (3 === element.nodeType) {
+				element = element.parentNode;
+			}
+
+			// if startContainer not Text, Comment, or CDATASection element then
+			// startOffset is the number of child nodes between the start of the
+			// startContainer and the boundary point of the Range
+			if (element === range.startContainer) {
+				element = element.childNodes[range.startOffset];
+			}
+			
+			if(!element.tagName && (element.previousSibling || element.nextSibling)) {
+				if(element.previousSibling) {
+					if(element.previousSibling.tagName.toLowerCase() === filterTagName) {
+						return element.previousSibling;
+					}
+				}	
+				if(element.nextSibling) {
+					if(element.nextSibling.tagName.toLowerCase() === filterTagName) {
+						return element.nextSibling;
+					}
+				}	
+			}
+
+			return dom.getAncestor(element, filterTagName);
+		};
+
+		this.ui.addHoverClass = function () {
+			$(this).addClass("wysiwyg-button-hover");
+		};
+
+		this.ui.appendControls = function () {
+			var ui = this,
+				self = this.self,
+				controls = self.parseControls(),
+				hasVisibleControls	= true, // to prevent separator before first item
+				groups = [],
+				controlsByGroup = {},
+				i,
+				currentGroupIndex, // jslint wants all vars at top of function
+				iterateGroup = function (controlName, control) { //called for every group when adding
+					if (control.groupIndex && currentGroupIndex !== control.groupIndex) {
+						currentGroupIndex = control.groupIndex;
+						hasVisibleControls = false;
+					}
+
+					if (!control.visible) {
+						return;
+					}
+
+					if (!hasVisibleControls) {
+						ui.appendItemSeparator();
+						hasVisibleControls = true;
+					}
+
+					if (control.custom) {
+						ui.appendItemCustom(controlName, control);
+					} else {
+						ui.appendItem(controlName, control);
+					}
+				};
+
+			$.each(controls, function (name, c) { //sort by groupIndex
+				var index = "empty";
+
+				if (undefined !== c.groupIndex) {
+					if ("" === c.groupIndex) {
+						index = "empty";
+					} else {
+						index = c.groupIndex;
+					}
+				}
+
+				if (undefined === controlsByGroup[index]) {
+					groups.push(index);
+					controlsByGroup[index] = {};
+				}
+				controlsByGroup[index][name] = c;
+			});
+
+			groups.sort(function (a, b) { //just sort group indexes by
+				if ( (typeof a === "number" ) && (typeof b === "number") ) {
+					return (a - b);
+				}
+				
+				a = a.toString();
+				b = b.toString();
+
+				if (a > b) {
+					return 1;
+				}
+
+				if (a === b) {
+					return 0;
+				}
+
+				return -1;
+			});
+
+			if (0 < groups.length) {
+				// set to first index in groups to proper placement of separator
+				currentGroupIndex = groups[0];
+			}
+
+			for (i = 0; i < groups.length; i += 1) {
+				$.each(controlsByGroup[groups[i]], iterateGroup);
+			}
+		};
+
+		this.ui.appendItem = function (name, control) {
+			var self = this.self,
+				className = control.className || control.command || name || "empty",
+				tooltip = control.tooltip || control.command || name || "";
+
+			return $('<li role="menuitem" unselectable="on">' + className + "</li>")
+				.addClass(className)
+				.attr("title", tooltip)
+				.hover(this.addHoverClass, this.removeHoverClass)
+				.click(function (event) {
+					if ($(this).hasClass("disabled")) {
+						return false;
+					}
+
+					self.triggerControl(name, control);
+
+					/**
+					* @link https://github.com/jwysiwyg/jwysiwyg/issues/219
+					*/
+					var $target = $(event.target);
+					for (var controlName in self.controls) {
+						if ($target.hasClass(controlName)) {
+							self.ui.toolbar.find("." + controlName).toggleClass("active");
+							self.editorDoc.rememberCommand = true;
+							break;
+						}
+					}
+                    
+					this.blur();
+					self.ui.returnRange();
+					self.ui.focus();
+					return true;
+				})
+				.appendTo(self.ui.toolbar);
+		};
+
+		this.ui.appendItemCustom = function (name, control) {
+			var self = this.self,
+				tooltip = control.tooltip || control.command || name || "";
+
+			if (control.callback) {
+				$(window).bind("trigger-" + name + ".wysiwyg", control.callback);
+			}
+
+			return $('<li role="menuitem" unselectable="on" style="background: url(\'' + control.icon + '\') no-repeat;"></li>')
+				.addClass("custom-command-" + name)
+				.addClass("wysiwyg-custom-command")
+				.addClass(name)
+				.attr("title", tooltip)
+				.hover(this.addHoverClass, this.removeHoverClass)
+				.click(function () {
+					if ($(this).hasClass("disabled")) {
+						return false;
+					}
+
+					self.triggerControl.apply(self, [name, control]);
+
+					this.blur();
+					self.ui.returnRange();
+					self.ui.focus();
+
+					self.triggerControlCallback(name);
+					return true;
+				})
+				.appendTo(self.ui.toolbar);
+		};
+
+		this.ui.appendItemSeparator = function () {
+			var self = this.self;
+			return $('<li role="separator" class="separator"></li>').appendTo(self.ui.toolbar);
+		};
+
+		this.autoSaveFunction = function () {
+			this.saveContent();
+		};
+
+		//called after click in wysiwyg "textarea"
+		this.ui.checkTargets = function (element) {
+			var self = this.self;
+
+			//activate controls
+			$.each(self.options.controls, function (name, control) {
+				var className = control.className || control.command || name || "empty",
+					tags,
+					elm,
+					css,
+					el,
+					// need to check multiple properties for the toolbar
+					// if we check one-by-one, a single property match
+					// on toolbar elements that have multiple css properties
+					// will trigger the button as "active"
+					checkActiveStatus = function (cssObject) {
+						// set a count flag for how many matches we've encountered
+						var matches = 0;
+
+						// set an iterator to count the number of properties
+						var total = 0;
+
+						$.each(cssObject, function(cssProperty, cssValue) {
+							if ( "function" === typeof cssValue ) {
+								if ( cssValue.apply(self, [el.css(cssProperty).toString().toLowerCase(), self]) ) {
+									matches += 1;
+								}
+							} else {
+								if ( el.css(cssProperty).toString().toLowerCase() === cssValue ) {
+									matches += 1;
+								}
+							}
+							total += 1;
+						});
+						if ( total === matches ) {
+							self.ui.toolbar.find("." + className).addClass("active");
+						}
+					};
+
+				if ("fullscreen" !== className) {
+					self.ui.toolbar.find("." + className).removeClass("active");
+				}
+
+				//activate by allowed tags
+				if (control.tags || (control.options && control.options.tags)) {
+					tags = control.tags || (control.options && control.options.tags);
+
+					elm = element;
+					while (elm) {
+						if (elm.nodeType !== 1) {
+							break;
+						}
+
+						if ($.inArray(elm.tagName.toLowerCase(), tags) !== -1) {
+							self.ui.toolbar.find("." + className).addClass("active");
+						}
+
+						elm = elm.parentNode;
+					}
+				}
+
+				//activate by supposed css
+				if (control.css || (control.options && control.options.css)) {
+					css = control.css || (control.options && control.options.css);
+					el = $(element);
+
+					while (el) {
+						if (el[0].nodeType !== 1) {
+							break;
+						}
+						checkActiveStatus(css);
+
+						el = el.parent();
+					}
+				}
+			});
+		};
+
+		this.ui.designMode = function () {
+			var attempts = 3,
+				self = this.self,
+				runner;
+				runner = function (attempts) {
+					if ("on" === self.editorDoc.designMode) {
+						if (self.timers.designMode) {
+							window.clearTimeout(self.timers.designMode);
+						}
+
+						// IE needs to reget the document element (this.editorDoc) after designMode was set
+						if (self.innerDocument() !== self.editorDoc) {
+							self.ui.initFrame();
+						}
+
+						return;
+					}
+
+					try {
+						self.editorDoc.designMode = "on";
+					} catch (e) {
+					}
+
+					attempts -= 1;
+					if (attempts > 0) {
+						self.timers.designMode = window.setTimeout(function () { runner(attempts); }, 100);
+					}
+				};
+
+			runner(attempts);
+		};
+
+		this.destroy = function () {
+			this.isDestroyed = true;
+
+			var i, $form = this.element.closest("form");
+
+			for (i = 0; i < this.timers.length; i += 1) {
+				window.clearTimeout(this.timers[i]);
+			}
+			
+			// Move textarea back to its original position
+			$(this.original).appendTo($(this.element.parent()));
+
+			// Remove bindings
+			$form.unbind(".wysiwyg");
+			this.element.remove();
+			$.removeData(this.original, "wysiwyg");
+			$(this.original).show();
+			return this;
+		};
+
+		this.getRangeText = function () {
+			var r = this.getInternalRange();
+
+			if (r.toString) {
+				r = r.toString();
+			} else if (r.text) {	// IE
+				r = r.text;
+			}
+
+			return r;
+		};
+		//not used?
+		this.execute = function (command, arg) {
+			if (typeof (arg) === "undefined") {
+				arg = null;
+			}
+			this.editorDoc.execCommand(command, false, arg);
+		};
+
+		this.extendOptions = function (options) {
+			var controls = {};
+
+			/**
+			 * If the user set custom controls, we catch it, and merge with the
+			 * defaults controls later.
+			 */
+			if ("object" === typeof options.controls) {
+				controls = options.controls;
+				delete options.controls;
+			}
+
+			options = $.extend(true, {}, this.defaults, options);
+			options.controls = $.extend(true, {}, controls, this.controls, controls);
+
+			if (options.rmUnusedControls) {
+				$.each(options.controls, function (controlName) {
+					if (!controls[controlName]) {
+						delete options.controls[controlName];
+					}
+				});
+			}
+
+			return options;
+		};
+
+		this.ui.focus = function () {
+			var self = this.self;
+
+			self.editor.get(0).contentWindow.focus();
+			return self;
+		};
+
+		this.ui.returnRange = function () {
+			var self = this.self, sel;
+
+			if (self.savedRange !== null) {
+				if (window.getSelection) { //non IE and there is already a selection
+					sel = window.getSelection();
+					if (sel.rangeCount > 0) {
+						sel.removeAllRanges();
+					}
+					try {
+						sel.addRange(self.savedRange);
+					} catch (e) {
+						console.error(e);
+					}
+				} else if (window.document.createRange) { // non IE and no selection
+					window.getSelection().addRange(self.savedRange);
+				} else if (window.document.selection) { //IE
+					self.savedRange.select();
+				}
+
+				self.savedRange = null;
+			}
+		};
+
+		this.increaseFontSize = function () {
+			if ($.browser.mozilla || $.browser.opera) {
+				this.editorDoc.execCommand("increaseFontSize", false, null);
+			} else if ($.browser.webkit) {
+				var Range = this.getInternalRange(),
+					Selection = this.getInternalSelection(),
+					newNode = this.editorDoc.createElement("big");
+
+				// If cursor placed on text node
+				if (true === Range.collapsed && 3 === Range.commonAncestorContainer.nodeType) {
+					var text = Range.commonAncestorContainer.nodeValue.toString(),
+						start = text.lastIndexOf(" ", Range.startOffset) + 1,
+						end = (-1 === text.indexOf(" ", Range.startOffset)) ? text : text.indexOf(" ", Range.startOffset);
+
+					Range.setStart(Range.commonAncestorContainer, start);
+					Range.setEnd(Range.commonAncestorContainer, end);
+
+					Range.surroundContents(newNode);
+					Selection.addRange(Range);
+				} else {
+					Range.surroundContents(newNode);
+					Selection.removeAllRanges();
+					Selection.addRange(Range);
+				}
+			} else {
+				console.error("Internet Explorer?");
+			}
+		};
+
+		this.decreaseFontSize = function () {
+			if ($.browser.mozilla || $.browser.opera) {
+				this.editorDoc.execCommand("decreaseFontSize", false, null);
+			} else if ($.browser.webkit) {
+				var Range = this.getInternalRange(),
+					Selection = this.getInternalSelection(),
+					newNode = this.editorDoc.createElement("small");
+
+				// If cursor placed on text node
+				if (true === Range.collapsed && 3 === Range.commonAncestorContainer.nodeType) {
+					var text = Range.commonAncestorContainer.nodeValue.toString(),
+						start = text.lastIndexOf(" ", Range.startOffset) + 1,
+						end = (-1 === text.indexOf(" ", Range.startOffset)) ? text : text.indexOf(" ", Range.startOffset);
+	
+					Range.setStart(Range.commonAncestorContainer, start);
+					Range.setEnd(Range.commonAncestorContainer, end);
+	
+					Range.surroundContents(newNode);
+					Selection.addRange(Range);
+				} else {
+					Range.surroundContents(newNode);
+					Selection.removeAllRanges();
+					Selection.addRange(Range);
+				}
+			} else {
+				console.error("Internet Explorer?");
+			}
+		};
+
+		this.getContent = function () {
+			if (this.viewHTML) {
+				this.setContent(this.original.value);
+			}
+			return this.events.filter('getContent', this.editorDoc.body.innerHTML);
+		};
+		
+		/**
+		 * A jWysiwyg specific event system.
+		 *
+		 * Example:
+		 * 
+		 * $("#editor").getWysiwyg().events.bind("getContent", function (orig) {
+		 *     return "<div id='content'>"+orgi+"</div>";
+		 * });
+		 * 
+		 * This makes it so that when ever getContent is called, it is wrapped in a div#content.
+		 */
+		this.events = {
+			_events : {},
+			
+			/**
+			 * Similar to jQuery's bind, but for jWysiwyg only.
+			 */
+			bind : function (eventName, callback) {
+				if (typeof (this._events.eventName) !== "object") {
+					this._events[eventName] = [];
+				}
+				this._events[eventName].push(callback);
+			},
+			
+			/**
+			 * Similar to jQuery's trigger, but for jWysiwyg only.
+			 */
+			trigger : function (eventName, args) {
+				if (typeof (this._events.eventName) === "object") {
+					var editor = this.editor;
+					$.each(this._events[eventName], function (k, v) {
+						if (typeof (v) === "function") {
+							v.apply(editor, args);
+						}
+					});
+				}
+			},
+			
+			/**
+			 * This "filters" `originalText` by passing it as the first argument to every callback
+			 * with the name `eventName` and taking the return value and passing it to the next function.
+			 *
+			 * This function returns the result after all the callbacks have been applied to `originalText`.
+			 */
+			filter : function (eventName, originalText) {
+				if (typeof (this._events[eventName]) === "object") {
+					var editor = this.editor,
+						args = Array.prototype.slice.call(arguments, 1);
+
+					$.each(this._events[eventName], function (k, v) {
+						if (typeof (v) === "function") {
+							originalText = v.apply(editor, args);
+						}
+					});
+				}
+				return originalText;
+			}
+		};
+		
+		
+		this.getElementByAttributeValue = function (tagName, attributeName, attributeValue) {
+			var i, value, elements = this.editorDoc.getElementsByTagName(tagName);
+
+			for (i = 0; i < elements.length; i += 1) {
+				value = elements[i].getAttribute(attributeName);
+
+				if ($.browser.msie) {
+					/** IE add full path, so I check by the last chars. */
+					value = value.substr(value.length - attributeValue.length);
+				}
+
+				if (value === attributeValue) {
+					return elements[i];
+				}
+			}
+
+			return false;
+		};
+
+		this.getInternalRange = function () {
+			var selection = this.getInternalSelection();
+
+			if (!selection) {
+				return null;
+			}
+
+			if (selection.rangeCount && selection.rangeCount > 0) { // w3c
+				return selection.getRangeAt(0);
+			} else if (selection.createRange) { // ie
+				return selection.createRange();
+			}
+
+			return null;
+		};
+
+		this.getInternalSelection = function () {
+			// firefox: document.getSelection is deprecated
+			var editorWindow = this.editor.get(0).contentWindow;
+			if (editorWindow && editorWindow.getSelection) {
+				return editorWindow.getSelection();
+			}
+			else if (this.editorDoc.getSelection) {
+				return this.editorDoc.getSelection();
+			}
+			else if (this.editorDoc.selection) {
+				return this.editorDoc.selection;
+			}
+
+			return null;
+		};
+
+		this.getRange = function () {
+			var selection = this.getSelection();
+
+			if (!selection) {
+				return null;
+			}
+
+			if (selection.rangeCount && selection.rangeCount > 0) { // w3c
+				selection.getRangeAt(0);
+			} else if (selection.createRange) { // ie
+				return selection.createRange();
+			}
+
+			return null;
+		};
+
+		this.getSelection = function () {
+			return (window.getSelection) ? window.getSelection() : window.document.selection;
+		};
+
+		// :TODO: you can type long string and letters will be hidden because of overflow
+		this.ui.grow = function () {
+			var self = this.self,
+				innerBody = $(self.editorDoc.body),
+				innerHeight = $.browser.msie ? innerBody[0].scrollHeight : innerBody.height() + 2 + 20, // 2 - borders, 20 - to prevent content jumping on grow
+				minHeight = self.ui.initialHeight,
+				height = Math.max(innerHeight, minHeight);
+
+			height = Math.min(height, self.options.maxHeight);
+
+			self.editor.attr("scrolling", height < self.options.maxHeight ? "no" : "auto"); // hide scrollbar firefox
+			innerBody.css("overflow", height < self.options.maxHeight ? "hidden" : ""); // hide scrollbar chrome
+
+			self.editor.get(0).height = height;
+
+			return self;
+		};
+
+		this.init = function (element, options) {
+			var self = this,
+				$form = $(element).closest("form"),
+				newX = (element.width || element.clientWidth || 0),
+				newY = (element.height || element.clientHeight || 0)
+				;
+
+			this.options	= this.extendOptions(options);
+			this.original	= element;
+			this.ui.toolbar	= $(this.options.toolbarHtml);
+
+			if ($.browser.msie && parseInt($.browser.version, 10) < 8) {
+				this.options.autoGrow = false;
+			}
+
+			if (newX === 0 && element.cols) {
+				newX = (element.cols * 8) + 21;
+			}
+			if (newY === 0 && element.rows) {
+				newY = (element.rows * 16) + 16;
+			}
+
+			this.editor = $(window.location.protocol === "https:" ? '<iframe src="javascript:false;"></iframe>' : "<iframe></iframe>").attr("frameborder", "0");
+
+			if (this.options.iFrameClass) {
+				this.editor.addClass(this.options.iFrameClass);
+			} else {
+				this.editor.css({
+					minHeight: (newY - 6).toString() + "px",
+					// fix for issue 12 ( http://github.com/akzhan/jwysiwyg/issues/issue/12 )
+					width: (newX > 50) ? newX.toString() + "px" : ""
+				});
+				if ($.browser.msie && parseInt($.browser.version, 10) < 7) {
+					this.editor.css("height", newY.toString() + "px");
+				}
+			}
+			/** 
+			 * Automagically add id to iframe if textarea has its own when possible 
+			 * ( http://github.com/akzhan/jwysiwyg/issues/245 )
+			 */
+			if (element.id) {
+				var proposedId = element.id + '-wysiwyg-iframe';
+				if (! document.getElementById(proposedId)) {
+					this.editor.attr('id', proposedId);
+				}
+			}
+
+			/**
+			 * http://code.google.com/p/jwysiwyg/issues/detail?id=96
+			 */
+			this.editor.attr("tabindex", $(element).attr("tabindex"));
+
+			this.element = $("<div/>").addClass("wysiwyg");
+
+			if (!this.options.iFrameClass) {
+				this.element.css({
+					width: (newX > 0) ? newX.toString() + "px" : "100%"
+				});
+			}
+
+			$(element).hide().before(this.element);
+
+			this.viewHTML = false;
+
+			/**
+			 * @link http://code.google.com/p/jwysiwyg/issues/detail?id=52
+			 */
+			this.initialContent = $(element).val();
+			this.ui.initFrame();
+
+			if (this.options.resizeOptions && $.fn.resizable) {
+				this.element.resizable($.extend(true, {
+					alsoResize: this.editor
+				}, this.options.resizeOptions));
+			}
+
+			if (this.options.autoSave) {
+				$form.bind("submit.wysiwyg", function () { self.autoSaveFunction(); });
+			}
+
+			$form.bind("reset.wysiwyg", function () { self.resetFunction(); });
+		};
+
+		this.ui.initFrame = function () {
+			var self = this.self,
+				stylesheet,
+				growHandler,
+				saveHandler,
+				toolbarWrapEl;
+
+			toolbarWrapEl = $('<div class="toolbar-wrap"><div style="clear: both"><!-- --></div>').prepend(self.ui.toolbar);
+			self.ui.appendControls();
+			self.element.append(toolbarWrapEl)
+				.append(self.editor)
+				.append(self.original);
+
+			self.editorDoc = self.innerDocument();
+
+			if (self.isDestroyed) {
+				return null;
+			}
+
+			self.ui.designMode();
+			self.editorDoc.open();
+			self.editorDoc.write(
+				self.options.html
+					/**
+					 * @link http://code.google.com/p/jwysiwyg/issues/detail?id=144
+					 */
+					.replace(/INITIAL_CONTENT/, function () { return self.wrapInitialContent(); })
+			);
+			self.editorDoc.close();
+
+			$.wysiwyg.plugin.bind(self);
+
+			$(self.editorDoc).trigger("initFrame.wysiwyg");
+
+			$(self.editorDoc).bind("click.wysiwyg", function (event) {
+				self.ui.checkTargets(event.target ? event.target : event.srcElement);
+			});
+            
+			/**
+			 * @link http://code.google.com/p/jwysiwyg/issues/detail?id=20
+			 * @link https://github.com/akzhan/jwysiwyg/issues/330
+			 */
+			$(self.original).focus(function () {
+				if ($(this).filter(":visible").length === 0 || $.browser.opera) {
+					return;
+				}
+				self.ui.focus();
+			});
+			
+			$($.wysiwyg.quirk.quirks).each(function(i, quirk) { quirk.init(self); });
+			
+			$(self.editorDoc).keydown(function (event) {
+				var emptyContentRegex;
+				if (event.keyCode === 8) { // backspace
+					emptyContentRegex = /^<([\w]+)[^>]*>(<br\/?>)?<\/\1>$/;
+					if (emptyContentRegex.test(self.getContent())) { // if content is empty
+						event.stopPropagation(); // prevent remove single empty tag
+						return false;
+					}
+				}
+                
+                self.editorDoc.rememberCommand = false;
+				return true;
+			});
+			
+			if (!$.browser.msie) {
+				$(self.editorDoc).keydown(function (event) {
+					var controlName;
+					var control;
+
+					/* Meta for Macs. tom at punkave.com */
+					if (event.ctrlKey || event.metaKey) {
+						for (controlName in self.options.controls) {
+							control = self.options.controls[controlName];
+							if (control.hotkey && control.hotkey.ctrl) {
+								if (event.keyCode === control.hotkey.key) {
+									self.triggerControl.apply(self, [controlName, control]);
+
+									return false;
+								}
+							}
+						}
+					}
+					return true;
+				});
+			} 
+			
+			if (self.options.brIE) {
+				$(self.editorDoc).keydown(function (event) {
+					if (event.keyCode === 13) {
+						
+						if ($.browser.msie || $.browser.opera) {
+							var rng = self.getRange();
+							if (rng) {
+								rng.pasteHTML("<br/>");
+								rng.collapse(false);
+								rng.select();
+							} else {
+								self.insertHtml('<br/>');
+							}						
+						} else {
+							var selection = self.editorDoc.getSelection();
+							if (selection && selection.getRangeAt && selection.rangeCount) {
+								var range = selection.getRangeAt(0);
+								if (!range) return true;
+								
+								// Replace selected content by a newline
+								var newlineEl = document.createElement('br');
+								range.deleteContents();
+								range.insertNode(newlineEl);
+
+								// Remove selection and place cursor after newline
+								range.setStartAfter(newlineEl);
+								range.collapse(true);
+								selection.removeAllRanges();
+								selection.addRange(range);
+							} else {
+								return true;
+							}
+
+						}
+						return false;
+					}
+
+					return true;
+				});
+			}
+
+			if (self.options.plugins.rmFormat.rmMsWordMarkup) {
+				$(self.editorDoc).bind("keyup.wysiwyg", function (event) {
+					if (event.ctrlKey || event.metaKey) {
+						// CTRL + V (paste)
+						if (86 === event.keyCode) {
+							if ($.wysiwyg.rmFormat) {
+								if ("object" === typeof (self.options.plugins.rmFormat.rmMsWordMarkup)) {
+									$.wysiwyg.rmFormat.run(self, {rules: { msWordMarkup: self.options.plugins.rmFormat.rmMsWordMarkup }});
+								} else {
+									$.wysiwyg.rmFormat.run(self, {rules: { msWordMarkup: { enabled: true }}});
+								}
+							}
+						}
+					}
+				});
+			}
+
+			if (self.options.autoSave) {
+				$(self.editorDoc).keydown(function () { self.autoSaveFunction(); })
+					.keyup(function () { self.autoSaveFunction(); })
+					.mousedown(function () { self.autoSaveFunction(); })
+					.bind($.support.noCloneEvent ? "input.wysiwyg" : "paste.wysiwyg", function () { self.autoSaveFunction(); });
+			}
+
+			if (self.options.autoGrow) {
+				if (self.options.initialMinHeight !== null) {
+					self.ui.initialHeight = self.options.initialMinHeight;
+				} else {
+					self.ui.initialHeight = $(self.editorDoc).height();
+				}
+				$(self.editorDoc.body).css("border", "1px solid white"); // cancel margin collapsing
+
+				growHandler = function () {
+					self.ui.grow();
+				};
+
+				$(self.editorDoc).keyup(growHandler);
+				$(self.editorDoc).bind("editorRefresh.wysiwyg", growHandler);
+
+				// fix when content height > textarea height
+				self.ui.grow();
+			}
+
+			if (self.options.css) {
+				if (String === self.options.css.constructor) {
+					if ($.browser.msie) {
+						stylesheet = self.editorDoc.createStyleSheet(self.options.css);
+						$(stylesheet).attr({
+							"media":	"all"
+						});
+					} else {
+						stylesheet = $("<link/>").attr({
+							"href":		self.options.css,
+							"media":	"all",
+							"rel":		"stylesheet",
+							"type":		"text/css"
+						});
+
+						$(self.editorDoc).find("head").append(stylesheet);
+					}
+				} else {
+					self.timers.initFrame_Css = window.setTimeout(function () {
+						$(self.editorDoc.body).css(self.options.css);
+					}, 0);
+				}
+			}
+
+			if (self.initialContent.length === 0) {
+				if ("function" === typeof (self.options.initialContent)) {
+					self.setContent(self.options.initialContent());
+				} else {
+					self.setContent(self.options.initialContent);
+				}
+			}
+
+			if (self.options.maxLength > 0) {
+				$(self.editorDoc).keydown(function (event) {
+					if ($(self.editorDoc).text().length >= self.options.maxLength && $.inArray(event.which, self.validKeyCodes) === -1) {
+						event.preventDefault();
+					}
+				});
+			}
+			
+			// Support event callbacks
+			$.each(self.options.events, function (key, handler) {
+				$(self.editorDoc).bind(key + ".wysiwyg", function (event) {
+					// Trigger event handler, providing the event and api to 
+					// support additional functionality.
+					handler.apply(self.editorDoc, [event, self]);
+				});
+			});
+
+			// restores selection properly on focus
+			if ($.browser.msie) {
+				// Event chain: beforedeactivate => focusout => blur.
+				// Focusout & blur fired too late to handle internalRange() in dialogs.
+				// When clicked on input boxes both got range = null
+				$(self.editorDoc).bind("beforedeactivate.wysiwyg", function () {
+					self.savedRange = self.getInternalRange();
+				});
+			} else {
+				$(self.editorDoc).bind("blur.wysiwyg", function () {
+					self.savedRange = self.getInternalRange();
+				});
+			}
+
+			$(self.editorDoc.body).addClass("wysiwyg");
+			if (self.options.events && self.options.events.save) {
+				saveHandler = self.options.events.save;
+
+				$(self.editorDoc).bind("keyup.wysiwyg", saveHandler);
+				$(self.editorDoc).bind("change.wysiwyg", saveHandler);
+
+				if ($.support.noCloneEvent) {
+					$(self.editorDoc).bind("input.wysiwyg", saveHandler);
+				} else {
+					$(self.editorDoc).bind("paste.wysiwyg", saveHandler);
+					$(self.editorDoc).bind("cut.wysiwyg", saveHandler);
+				}
+			}
+			
+			/**
+			 * XHTML5 {@link https://github.com/akzhan/jwysiwyg/issues/152}
+			 */
+			if (self.options.xhtml5 && self.options.unicode) {
+				var replacements = {ne:8800,le:8804,para:182,xi:958,darr:8595,nu:957,oacute:243,Uacute:218,omega:969,prime:8242,pound:163,igrave:236,thorn:254,forall:8704,emsp:8195,lowast:8727,brvbar:166,alefsym:8501,nbsp:160,delta:948,clubs:9827,lArr:8656,Omega:937,Auml:196,cedil:184,and:8743,plusmn:177,ge:8805,raquo:187,uml:168,equiv:8801,laquo:171,rdquo:8221,Epsilon:917,divide:247,fnof:402,chi:967,Dagger:8225,iacute:237,rceil:8969,sigma:963,Oslash:216,acute:180,frac34:190,lrm:8206,upsih:978,Scaro [...]
+				self.events.bind("getContent", function (text) {
+					return text.replace(/&(?:amp;)?(?!amp|lt|gt|quot)([a-z][a-z0-9]*);/gi, function (str, p1) {
+						if (!replacements[p1]) {
+							p1 = p1.toLowerCase();
+							if (!replacements[p1]) {
+								p1 = "__replacement";
+							}
+						}
+						
+						var num = replacements[p1];
+						/* Numeric return if ever wanted: return replacements[p1] ? "&#"+num+";" : ""; */
+						return String.fromCharCode(num);
+					});
+				});
+			}
+			$(self.original).trigger('ready.jwysiwyg', [self.editorDoc, self]);
+		};
+
+		this.innerDocument = function () {
+			var element = this.editor.get(0);
+
+			if (element.nodeName.toLowerCase() === "iframe") {
+				if (element.contentDocument) {				// Gecko
+					return element.contentDocument;
+				} else if (element.contentWindow) {			// IE
+					return element.contentWindow.document;
+				}
+
+				if (this.isDestroyed) {
+					return null;
+				}
+
+				console.error("Unexpected error in innerDocument");
+			}
+
+			return element;
+		};
+
+		this.insertHtml = function (szHTML) {
+			var img, range;
+
+			if (!szHTML || szHTML.length === 0) {
+				return this;
+			}
+
+			if ($.browser.msie) {
+				this.ui.focus();
+				this.editorDoc.execCommand("insertImage", false, "#jwysiwyg#");
+				img = this.getElementByAttributeValue("img", "src", "#jwysiwyg#");
+				if (img) {
+					$(img).replaceWith(szHTML);
+				}
+			} else {
+				if ($.browser.mozilla) { // @link https://github.com/akzhan/jwysiwyg/issues/50
+					if (1 === $(szHTML).length) {
+						range = this.getInternalRange();
+						range.deleteContents();
+						range.insertNode($(szHTML).get(0));
+					} else {
+						this.editorDoc.execCommand("insertHTML", false, szHTML);
+					}
+				} else {
+					if (!this.editorDoc.execCommand("insertHTML", false, szHTML)) {
+						this.editor.focus();
+						/* :TODO: place caret at the end
+						if (window.getSelection) {
+						} else {
+						}
+						this.editor.focus();
+						*/
+						this.editorDoc.execCommand("insertHTML", false, szHTML);
+					}
+				}
+			}
+
+			this.saveContent();
+			
+			return this;
+		};
+
+		//check allowed properties
+		this.parseControls = function () {
+			var self = this;
+
+			$.each(this.options.controls, function (controlName, control) {
+				$.each(control, function (propertyName) {
+					if (-1 === $.inArray(propertyName, self.availableControlProperties)) {
+						throw controlName + '["' + propertyName + '"]: property "' + propertyName + '" not exists in Wysiwyg.availableControlProperties';
+					}
+				});
+			});
+
+			if (this.options.parseControls) { //user callback
+				return this.options.parseControls.call(this);
+			}
+
+			return this.options.controls;
+		};
+
+		this.removeFormat = function () {
+			if ($.browser.msie) {
+				this.ui.focus();
+			}
+
+			if (this.options.removeHeadings) {
+				this.editorDoc.execCommand("formatBlock", false, "<p>"); // remove headings
+			}
+
+			this.editorDoc.execCommand("removeFormat", false, null);
+			this.editorDoc.execCommand("unlink", false, null);
+
+			if ($.wysiwyg.rmFormat && $.wysiwyg.rmFormat.enabled) {
+				if ("object" === typeof (this.options.plugins.rmFormat.rmMsWordMarkup)) {
+					$.wysiwyg.rmFormat.run(this, {rules: { msWordMarkup: this.options.plugins.rmFormat.rmMsWordMarkup }});
+				} else {
+					$.wysiwyg.rmFormat.run(this, {rules: { msWordMarkup: { enabled: true }}});
+				}
+			}
+
+			return this;
+		};
+
+		this.ui.removeHoverClass = function () {
+			$(this).removeClass("wysiwyg-button-hover");
+		};
+
+		this.resetFunction = function () {
+			this.setContent(this.initialContent);
+		};
+
+		this.saveContent = function (filter) {
+			if (this.viewHTML) {
+				return; // no need
+			}
+			if (this.original) {
+				var content, newContent;
+
+				content = (typeof filter === 'function') ? filter(this.getContent()) : this.getContent();
+
+				if (this.options.rmUnwantedBr) {
+					content = content.replace(/<br\/?>$/, "");
+				}
+
+				if (this.options.replaceDivWithP) {
+					newContent = $("<div/>").addClass("temp").append(content);
+
+					newContent.children("div").each(function () {
+						var element = $(this), p = element.find("p"), i;
+
+						if (0 === p.length) {
+							p = $("<p></p>");
+
+							if (this.attributes.length > 0) {
+								for (i = 0; i < this.attributes.length; i += 1) {
+									p.attr(this.attributes[i].name, element.attr(this.attributes[i].name));
+								}
+							}
+
+							p.append(element.html());
+
+							element.replaceWith(p);
+						}
+					});
+					
+					content = newContent.html();
+				}
+
+				var event = $.Event('change');
+				event.source = this;
+				$(this.original).val(content).trigger(event);
+
+				if (this.options.events && this.options.events.save) {
+					this.options.events.save.call(this);
+				}
+			}
+
+			return this;
+		};
+
+		this.setContent = function (newContent) {
+			this.editorDoc.body.innerHTML = newContent;
+			this.saveContent();
+
+			return this;
+		};
+
+		this.triggerControl = function (name, control) {
+			var cmd = control.command || name,							//command directly for designMode=on iframe (this.editorDoc)
+				args = control["arguments"] || [];
+
+			if (control.exec) {
+				control.exec.apply(this, control.callbackArguments);  //custom exec function in control, allows DOM changing
+			} else {
+				this.ui.focus();
+				this.ui.withoutCss(); //disable style="" attr inserting in mozzila's designMode
+				// when click <Cut>, <Copy> or <Paste> got "Access to XPConnect service denied" code: "1011"
+				// in Firefox untrusted JavaScript is not allowed to access the clipboard
+				try {
+					this.editorDoc.execCommand(cmd, false, args);
+				} catch (e) {
+					console.error(e);
+				}
+			}
+
+			if (this.options.autoSave) {
+				this.autoSaveFunction();
+			}
+		};
+
+		this.triggerControlCallback = function (name) {
+			$(window).trigger("trigger-" + name + ".wysiwyg", [this]);
+		};
+
+		this.ui.withoutCss = function () {
+			var self = this.self;
+
+			if ($.browser.mozilla) {
+				try {
+					self.editorDoc.execCommand("styleWithCSS", false, false);
+				} catch (e) {
+					try {
+						self.editorDoc.execCommand("useCSS", false, true);
+					} catch (e2) {
+					}
+				}
+			}
+
+			return self;
+		};
+
+		this.wrapInitialContent = function () {
+			var content = this.initialContent;
+			return content;
+		};
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	$.wysiwyg = {
+		messages: {
+			noObject: "Something goes wrong, check object"
+		},
+
+		/**
+		 * Custom control support by Alec Gorge ( http://github.com/alecgorge )
+		 */
+		addControl: function (object, name, settings) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg"),
+					customControl = {},
+					toolbar;
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				customControl[name] = $.extend(true, {visible: true, custom: true}, settings);
+				$.extend(true, oWysiwyg.options.controls, customControl);
+
+				// render new toolbar
+				toolbar = $(oWysiwyg.options.toolbarHtml);
+				oWysiwyg.ui.toolbar.replaceWith(toolbar);
+				oWysiwyg.ui.toolbar = toolbar;
+				oWysiwyg.ui.appendControls();
+			});
+		},
+
+		clear: function (object) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg");
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				oWysiwyg.setContent("");
+			});
+		},
+
+		console: console, // let our console be available for extensions
+
+		destroy: function (object) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg");
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				oWysiwyg.destroy();
+			});
+		},
+
+		"document": function (object) {
+			// no chains because of return
+			var oWysiwyg = object.data("wysiwyg");
+
+			if (!oWysiwyg) {
+				return undefined;
+			}
+
+			return $(oWysiwyg.editorDoc);
+		},
+
+        focus: function(object) {
+            var oWysiwyg = object.data("wysiwyg");
+
+            if (!oWysiwyg) {
+                return undefined;
+            }
+
+            oWysiwyg.ui.focus();
+
+            return object;
+        },
+
+		getContent: function (object) {
+			// no chains because of return
+			var oWysiwyg = object.data("wysiwyg");
+
+			if (!oWysiwyg) {
+				return undefined;
+			}
+
+			return oWysiwyg.getContent();
+		},
+    
+		getSelection: function (object) {
+			// no chains because of return
+			var oWysiwyg = object.data("wysiwyg");
+
+			if (!oWysiwyg) {
+				return undefined;
+			}
+
+			return oWysiwyg.getRangeText();
+		},
+
+		init: function (object, options) {
+			return object.each(function () {
+				var opts = $.extend(true, {}, options),
+					obj;
+
+				// :4fun:
+				// remove this textarea validation and change line in this.saveContent function
+				// $(this.original).val(content); to $(this.original).html(content);
+				// now you can make WYSIWYG editor on h1, p, and many more tags
+				if (("textarea" !== this.nodeName.toLowerCase()) || $(this).data("wysiwyg")) {
+					return;
+				}
+
+				obj = new Wysiwyg();
+				obj.init(this, opts);
+				$.data(this, "wysiwyg", obj);
+
+				$(obj.editorDoc).trigger("afterInit.wysiwyg");
+			});
+		},
+
+		insertHtml: function (object, szHTML) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg");
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				oWysiwyg.insertHtml(szHTML);
+			});
+		},
+
+		plugin: {
+			listeners: {},
+
+			bind: function (Wysiwyg) {
+				var self = this;
+				
+				var makeHandler = function() {
+					return function(event) {
+						var pluginName = event.data.plugin.name;
+						var methodName = event.data.plugin.method;
+						$.wysiwyg[pluginName][methodName].apply($.wysiwyg[pluginName], [Wysiwyg]);
+					};
+				};
+
+				$.each(this.listeners, function (action, handlers) {
+					var i, plugin;
+
+					for (i = 0; i < handlers.length; i += 1) {
+						plugin = self.parseName(handlers[i]);
+
+						$(Wysiwyg.editorDoc).bind(action + ".wysiwyg", {plugin: plugin}, 
+							makeHandler()
+						);
+					}
+				});
+			},
+
+			exists: function (name) {
+				var plugin;
+
+				if ("string" !== typeof (name)) {
+					return false;
+				}
+
+				plugin = this.parseName(name);
+
+				if (!$.wysiwyg[plugin.name] || !$.wysiwyg[plugin.name][plugin.method]) {
+					return false;
+				}
+
+				return true;
+			},
+
+			listen: function (action, handler) {
+				var plugin;
+
+				plugin = this.parseName(handler);
+
+				if (!$.wysiwyg[plugin.name] || !$.wysiwyg[plugin.name][plugin.method]) {
+					return false;
+				}
+
+				if (!this.listeners[action]) {
+					this.listeners[action] = [];
+				}
+
+				this.listeners[action].push(handler);
+
+				return true;
+			},
+
+			parseName: function (name) {
+				var elements;
+
+				if ("string" !== typeof (name)) {
+					return false;
+				}
+
+				elements = name.split(".");
+
+				if (2 > elements.length) {
+					return false;
+				}
+
+				return {name: elements[0], method: elements[1]};
+			},
+
+			register: function (data) {
+				if (!data.name) {
+					console.error("Plugin name missing");
+				}
+
+				$.each($.wysiwyg, function (pluginName) {
+					if (pluginName === data.name) {
+						console.error("Plugin with name '" + data.name + "' was already registered");
+					}
+				});
+
+				$.wysiwyg[data.name] = data;
+
+				return true;
+			}
+		},
+		
+		quirk: {
+			quirks: [],
+			
+			assert: function(expression, message) {
+				if (!expression) throw new Error(message);
+			},
+			
+			register: function(quirk) {
+				this.assert(typeof quirk.init === 'function', 'quirk.init must be a function');
+				this.quirks.push(quirk);
+			}
+		},
+
+		removeFormat: function (object) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg");
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				oWysiwyg.removeFormat();
+			});
+		},
+
+		save: function (object) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg");
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				oWysiwyg.saveContent();
+			});
+		},
+
+		selectAll: function (object) {
+			var oWysiwyg = object.data("wysiwyg"), oBody, oRange, selection;
+
+			if (!oWysiwyg) {
+				return this;
+			}
+
+			oBody = oWysiwyg.editorDoc.body;
+			if (window.getSelection) {
+				selection = oWysiwyg.getInternalSelection();
+				selection.selectAllChildren(oBody);
+			} else {
+				oRange = oBody.createTextRange();
+				oRange.moveToElementText(oBody);
+				oRange.select();
+			}
+		},
+
+		setContent: function (object, newContent) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg");
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				oWysiwyg.setContent(newContent);
+			});
+		},
+
+		triggerControl: function (object, controlName) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg");
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				if (!oWysiwyg.controls[controlName]) {
+					console.error("Control '" + controlName + "' not exists");
+				}
+
+				oWysiwyg.triggerControl.apply(oWysiwyg, [controlName, oWysiwyg.controls[controlName]]);
+			});
+		},
+
+		support: {
+			prop: supportsProp
+		},
+
+		utils: {
+			extraSafeEntities: [["<", ">", "'", '"', " "], [32]],
+
+			encodeEntities: function (str) {
+				var self = this, aStr, aRet = [];
+
+				if (this.extraSafeEntities[1].length === 0) {
+					$.each(this.extraSafeEntities[0], function (i, ch) {
+						self.extraSafeEntities[1].push(ch.charCodeAt(0));
+					});
+				}
+				aStr = str.split("");
+				$.each(aStr, function (i) {
+					var iC = aStr[i].charCodeAt(0);
+					if ($.inArray(iC, self.extraSafeEntities[1]) && (iC < 65 || iC > 127 || (iC > 90 && iC < 97))) {
+						aRet.push('&#' + iC + ';');
+					} else {
+						aRet.push(aStr[i]);
+					}
+				});
+
+				return aRet.join('');
+			}
+		}
+	};
+
+	/**
+	 * Unifies dialog methods to allow custom implementations
+	 * 
+	 * Events:
+	 *     * afterOpen
+	 *     * beforeShow
+	 *     * afterShow
+	 *     * beforeHide
+	 *     * afterHide
+	 *     * beforeClose
+	 *     * afterClose
+	 * 
+	 * Example:
+	 * var dialog = new ($.wysiwyg.dialog)($('#idToTextArea').data('wysiwyg'), {"title": "Test", "content": "form data, etc."});
+	 * 
+	 * dialog.bind("afterOpen", function () { alert('you should see a dialog behind this one!'); });
+	 * 
+	 * dialog.open();
+	 * 
+	 * 
+	 */
+	$.wysiwyg.dialog = function (jWysiwyg, opts) {
+		
+		var theme	= (jWysiwyg && jWysiwyg.options && jWysiwyg.options.dialog) ? jWysiwyg.options.dialog : (opts.theme ? opts.theme : "default"),
+			obj		= new $.wysiwyg.dialog.createDialog(theme),
+			that	= this,
+			$that	= $(that);
+				
+		this.options = {
+			"modal": true,
+			"draggable": true,
+			"title": "Title",
+			"content": "Content",
+			"width":  "auto",
+			"height": "auto",
+			"zIndex": 2000,
+			"open": false,
+			"close": false
+		};
+
+		this.isOpen = false;
+
+		$.extend(this.options, opts);
+
+		this.object = obj;
+
+		// Opens a dialog with the specified content
+		this.open = function () {
+			this.isOpen = true;
+
+			obj.init.apply(that, []);
+			var $dialog = obj.show.apply(that, []);
+
+			$that.trigger("afterOpen", [$dialog]);
+			
+		};
+
+		this.show = function () {
+			this.isOpen = true;
+			$that.trigger("beforeShow");
+			$that.trigger("afterShow");
+		};
+
+		this.hide = function () {
+			this.isOpen = false;
+			
+			$that.trigger("beforeHide");
+			
+			var $dialog = obj.hide.apply(that, []);
+			
+			$that.trigger("afterHide", [$dialog]);
+		};
+
+		// Closes the dialog window.
+		this.close = function () {
+			this.isOpen = false;
+						
+			var $dialog = obj.hide.apply(that, []);
+			
+			$that.trigger("beforeClose", [$dialog]);
+			
+			obj.destroy.apply(that, []);
+			
+			$that.trigger("afterClose", [$dialog]);
+
+			jWysiwyg.ui.focus();
+		};
+
+		if (this.options.open) {
+			$that.bind("afterOpen", this.options.open);
+		}
+		if (this.options.close) {
+			$that.bind("afterClose", this.options.close);
+		}
+
+		return this;
+	};
+
+	// "Static" Dialog methods.
+	$.extend(true, $.wysiwyg.dialog, {
+		_themes : {}, // sample {"Theme Name": object}
+		_theme : "", // the current theme
+
+		register : function(name, obj) {
+			$.wysiwyg.dialog._themes[name] = obj;
+		},
+
+		deregister : function (name) {
+			delete $.wysiwyg.dialog._themes[name];
+		},
+
+		createDialog : function (name) {
+			return new $.wysiwyg.dialog._themes[name]();
+		},
+		
+		getDimensions : function () {
+			var width  = document.body.scrollWidth,
+				height = document.body.scrollHeight;
+
+			if ($.browser.opera) {
+				height = Math.max(
+					$(document).height(),
+					$(window).height(),
+					document.documentElement.clientHeight);
+			}
+
+			return [width, height];
+		}
+	});
+
+	$(function () { // need access to jQuery UI stuff.
+		if ($.ui) {
+			$.wysiwyg.dialog.register("jqueryui", function () {
+				var that = this;
+
+				this._$dialog = null;
+
+				this.init = function() {
+					var content = this.options.content;
+
+					if (typeof content === 'object') {
+						if (typeof content.html === 'function') {
+							content = content.html();
+						} else if(typeof content.toString === 'function') {
+							content = content.toString();
+						}
+					}
+
+					that._$dialog = $('<div></div>').attr('title', this.options.title).html(content);
+
+					var dialogHeight = this.options.height === 'auto' ? 300 : this.options.height,
+						dialogWidth = this.options.width === 'auto' ? 450 : this.options.width;
+
+					// console.log(that._$dialog);
+					
+					that._$dialog.dialog({
+						modal: this.options.modal,
+						draggable: this.options.draggable,
+						height: dialogHeight,
+						width: dialogWidth
+					});
+
+					return that._$dialog;
+				};
+
+				this.show = function () {
+					that._$dialog.dialog("open");
+					return that._$dialog;
+				};
+
+				this.hide = function () {
+					that._$dialog.dialog("close");
+					return that._$dialog;
+				};
+
+				this.destroy = function() {
+					that._$dialog.dialog("destroy");
+					return that._$dialog;
+				};
+			});
+		}
+
+		$.wysiwyg.dialog.register("default", function () {
+			var that = this;
+
+			this._$dialog = null;
+
+			this.init = function() {
+				var abstractDialog	= this,
+					content			= this.options.content;
+
+				if (typeof content === 'object') {
+					if(typeof content.html === 'function') {
+						content = content.html();
+					}
+					else if(typeof content.toString === 'function') {
+						content = content.toString();
+					}
+				}
+
+				that._$dialog = $('<div class="wysiwyg-dialog"></div>').css({"z-index": this.options.zIndex});
+
+				var $topbar = $('<div class="wysiwyg-dialog-topbar"><div class="wysiwyg-dialog-close-wrapper"></div><div class="wysiwyg-dialog-title">'+this.options.title+'</div></div>');
+				var $link = $('<a href="#" class="wysiwyg-dialog-close-button">X</a>');
+
+				$link.click(function () {
+					abstractDialog.close(); // this is important it makes sure that is close from the abstract $.wysiwyg.dialog instace, not just locally 
+				});
+				
+				$topbar.find('.wysiwyg-dialog-close-wrapper').prepend($link);
+
+				var $dcontent = $('<div class="wysiwyg-dialog-content">'+content+'</div>');
+
+				that._$dialog.append($topbar).append($dcontent);
+				
+				// Set dialog's height & width, and position it correctly:
+				var dialogHeight = this.options.height === 'auto' ? 300 : this.options.height,
+					dialogWidth = this.options.width === 'auto' ? 450 : this.options.width;
+				that._$dialog.hide().css({
+					"width": dialogWidth,
+					"height": dialogHeight,
+					"left": (($(window).width() - dialogWidth) / 2),
+					"top": (($(window).height() - dialogHeight) / 3)
+				});
+
+				$("body").append(that._$dialog);
+
+				return that._$dialog;
+			};
+
+			this.show = function () {
+
+				// Modal feature:
+				if (this.options.modal) {
+					var dimensions = $.wysiwyg.dialog.getDimensions(),
+						wrapper    = $('<div class="wysiwyg-dialog-modal-div"></div>')
+						.css({"width": dimensions[0], "height": dimensions[1]});
+					that._$dialog.wrap(wrapper);
+				}
+				
+				// Draggable feature:
+				if (this.options.draggable) { 
+					
+					var mouseDown = false;
+					
+					that._$dialog.find("div.wysiwyg-dialog-topbar").bind("mousedown", function (e) {
+						e.preventDefault();
+						$(this).css({ "cursor": "move" });
+						var $topbar = $(this),
+							_dialog = $(this).parents(".wysiwyg-dialog"),
+							offsetX = (e.pageX - parseInt(_dialog.css("left"), 10)),
+							offsetY = (e.pageY - parseInt(_dialog.css("top"), 10));
+						mouseDown = true;
+						$(this).css({ "cursor": "move" });
+						
+						$(document).bind("mousemove", function (e) {
+							e.preventDefault();
+							if (mouseDown) {
+								_dialog.css({
+									"top": (e.pageY - offsetY),
+									"left": (e.pageX - offsetX)
+								});
+							}
+						}).bind("mouseup", function (e) {
+							e.preventDefault();
+							mouseDown = false;
+							$topbar.css({ "cursor": "auto" });
+							$(document).unbind("mousemove").unbind("mouseup");
+						});
+					
+					});
+				}
+				
+				that._$dialog.show();
+				return that._$dialog;
+
+			};
+
+			this.hide = function () {
+				that._$dialog.hide();
+				return that._$dialog;
+			};
+
+			this.destroy = function() {
+			
+				// Modal feature:
+				if (this.options.modal) { 
+					that._$dialog.unwrap();
+				}
+				
+				// Draggable feature:
+				if (this.options.draggable) { 
+					that._$dialog.find("div.wysiwyg-dialog-topbar").unbind("mousedown");
+				}
+				
+				that._$dialog.remove();
+				return that._$dialog;
+			};
+		});
+	});
+	// end Dialog
+
+	// $.browser fallback for jQuery 1.9+.
+	if ($.browser === undefined) {
+		jQuery.browser = function () {
+			var ua_match = function (ua) {
+				ua = ua.toLowerCase();
+				var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
+				/(webkit)[ \/]([\w.]+)/.exec(ua) ||
+				/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
+				/(msie) ([\w.]+)/.exec(ua) ||
+				ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
+				[];
+				
+				return { browser:match[ 1 ] || "", version:match[ 2 ] || "0" };
+			},
+			matched = ua_match(navigator.userAgent),
+			browser = {};
+			
+			if (matched.browser) {
+				browser[ matched.browser ] = true;
+				browser.version = matched.version;
+			}
+			
+			if (browser.chrome) {
+				browser.webkit = true;
+			} else if (browser.webkit) {
+				browser.safari = true;
+			}
+			return browser;
+		};
+	}
+
+	$.fn.wysiwyg = function (method) {
+		var args = arguments, plugin;
+
+		if ("undefined" !== typeof $.wysiwyg[method]) {
+			// set argument object to undefined
+			args = Array.prototype.concat.call([args[0]], [this], Array.prototype.slice.call(args, 1));
+			return $.wysiwyg[method].apply($.wysiwyg, Array.prototype.slice.call(args, 1));
+		} else if ("object" === typeof method || !method) {
+			Array.prototype.unshift.call(args, this);
+			return $.wysiwyg.init.apply($.wysiwyg, args);
+		} else if ($.wysiwyg.plugin.exists(method)) {
+			plugin = $.wysiwyg.plugin.parseName(method);
+			args = Array.prototype.concat.call([args[0]], [this], Array.prototype.slice.call(args, 1));
+			return $.wysiwyg[plugin.name][plugin.method].apply($.wysiwyg[plugin.name], Array.prototype.slice.call(args, 1));
+		} else {
+			console.error("Method '" +  method + "' does not exist on jQuery.wysiwyg.\nTry to include some extra controls or plugins");
+		}
+	};
+	
+	$.fn.getWysiwyg = function () {
+		return this.data("wysiwyg");
+	};
+})(jQuery);
diff --git a/media/js/jwysiwyg/jquery.wysiwyg.modal.css b/media/js/jwysiwyg/jquery.wysiwyg.modal.css
new file mode 100644
index 0000000..739a70d
--- /dev/null
+++ b/media/js/jwysiwyg/jquery.wysiwyg.modal.css
@@ -0,0 +1,62 @@
+form.wysiwyg {
+	background:#fff;
+	padding:1em;
+	border:1px solid #eee;
+	margin:2px;
+	width:25em;
+}
+
+form.wysiwyg fieldset div {
+	margin:0.3em 0;
+	clear:both;
+	margin-bottom:5px;
+}
+
+form.wysiwyg label {
+	display: block;
+	text-align:right;
+	margin-right:1em;
+}
+
+form.wysiwyg legend {
+	color:#0b77b7;
+	font-size:1.2em;
+}
+
+form.wysiwyg legend span {
+	width:10em;
+	text-align:right;
+}
+
+form.wysiwyg input {
+	padding:0.15em;
+	width:10em;
+	border:1px solid #ddd;
+	background:#fafafa;
+	font:bold 0.95em arial, sans-serif;
+	margin-bottom:5px;
+	-moz-border-radius:0.4em;
+	-khtml-border-radius:0.4em;
+}
+
+form.wysiwyg input.width, form.wysiwyg input.height {
+	width: 4em;
+}
+
+form.wysiwyg input:hover, form.wysiwyg input:focus {
+	border-color:#c5c5c5;
+	background:#f6f6f6;
+}
+
+form.wysiwyg .button{
+	margin-top:8px;
+}
+
+form.wysiwyg fieldset {
+	border:1px solid #ddd;
+	padding:0 0.5em 0.5em;
+}
+
+form.wysiwyg input.default {
+	color:#bbb;
+}
diff --git a/media/js/jwysiwyg/jquery.wysiwyg.no-alpha.gif b/media/js/jwysiwyg/jquery.wysiwyg.no-alpha.gif
new file mode 100644
index 0000000..5283e71
Binary files /dev/null and b/media/js/jwysiwyg/jquery.wysiwyg.no-alpha.gif differ
diff --git a/media/js/jwysiwyg/jquery.wysiwyg.old-school.css b/media/js/jwysiwyg/jquery.wysiwyg.old-school.css
new file mode 100644
index 0000000..6a54612
--- /dev/null
+++ b/media/js/jwysiwyg/jquery.wysiwyg.old-school.css
@@ -0,0 +1,63 @@
+
+div.wysiwyg { border: 1px solid #ccc; padding: 5px; background-color: #fff; }
+div.wysiwyg * { margin: 0; padding: 0; }
+
+div.wysiwyg ul.toolbar li.jwysiwyg-custom-command { overflow: hidden; }
+
+div.wysiwyg ul.toolbar { border-bottom: 1px solid #ccc; float: left; width: 100%; padding: 0; }
+div.wysiwyg ul.toolbar li { list-style: none; float: left; margin: 1px 2px 3px 0;  background: rgb(240, 240, 240); -moz-user-select: none; -webkit-user-select: none; user-select: none; clear: none; padding: 0 }
+div.wysiwyg ul.toolbar li.separator { width: 1px; height: 16px; margin: 0 4px; border-left: 1px solid #ccc; }
+div.wysiwyg ul.toolbar li { text-indent: -5000px; opacity: 0.85; filter: alpha(opacity=85); display: block; width: 16px; height: 16px; background: url('jquery.wysiwyg.gif') no-repeat -64px -80px; border: 1px dotted rgb(240, 240, 240); cursor: pointer; margin: 0px; }
+div.wysiwyg ul.toolbar li.wysiwyg-button-hover, div.wysiwyg ul.toolbar li.active { opacity: 1.00; filter:alpha(opacity=100); border: 1px outset rgb(224, 224, 224); }
+div.wysiwyg ul.toolbar li.active { background-color: rgb(255, 255, 64); border: 1px solid rgb(208, 208, 208); border-left-color: #aaa; border-top-color: #aaa; margin: 0; }
+
+div.wysiwyg ul.toolbar li.disabled, div.wysiwyg ul.toolbar li.wysiwyg-button-hover.disabled, div.wysiwyg ul.toolbar li.active.disabled { opacity: 0.5; filter:alpha(opacity=50); border: 0px none transparent; padding: 1px; pointer: auto; }
+
+div.wysiwyg ul.toolbar li.bold { background-position: 0 -16px; }
+div.wysiwyg ul.toolbar li.italic { background-position: -16px -16px; }
+div.wysiwyg ul.toolbar li.strikeThrough { background-position: -32px -16px; }
+div.wysiwyg ul.toolbar li.underline { background-position: -48px -16px; }
+
+div.wysiwyg ul.toolbar li.justifyLeft { background-position: 0 0; }
+div.wysiwyg ul.toolbar li.justifyCenter { background-position: -16px 0; }
+div.wysiwyg ul.toolbar li.justifyRight { background-position: -32px 0; }
+div.wysiwyg ul.toolbar li.justifyFull { background-position: -48px 0; }
+
+div.wysiwyg ul.toolbar li.indent { background-position: -64px 0; }
+div.wysiwyg ul.toolbar li.outdent { background-position: -80px 0; }
+
+div.wysiwyg ul.toolbar li.subscript { background-position: -64px -16px; }
+div.wysiwyg ul.toolbar li.superscript { background-position: -80px -16px; }
+
+div.wysiwyg ul.toolbar li.undo { background-position: 0 -64px; }
+div.wysiwyg ul.toolbar li.redo { background-position: -16px -64px; }
+
+div.wysiwyg ul.toolbar li.insertOrderedList { background-position: -32px -48px; }
+div.wysiwyg ul.toolbar li.insertUnorderedList { background-position: -16px -48px; }
+div.wysiwyg ul.toolbar li.insertHorizontalRule { background-position: 0 -48px; }
+
+div.wysiwyg ul.toolbar li.h1 { background-position: 0 -32px; }
+div.wysiwyg ul.toolbar li.h2 { background-position: -16px -32px; }
+div.wysiwyg ul.toolbar li.h3 { background-position: -32px -32px; }
+div.wysiwyg ul.toolbar li.h4 { background-position: -48px -32px; }
+div.wysiwyg ul.toolbar li.h5 { background-position: -64px -32px; }
+div.wysiwyg ul.toolbar li.h6 { background-position: -80px -32px; }
+
+div.wysiwyg ul.toolbar li.cut { background-position: -32px -64px; }
+div.wysiwyg ul.toolbar li.copy { background-position: -48px -64px; }
+div.wysiwyg ul.toolbar li.paste { background-position: -64px -64px; }
+div.wysiwyg ul.toolbar li.insertTable { background-position: -64px -48px; }
+
+div.wysiwyg ul.toolbar li.increaseFontSize { background-position: -16px -80px; }
+div.wysiwyg ul.toolbar li.decreaseFontSize { background-position: -32px -80px; }
+
+div.wysiwyg ul.toolbar li.createLink { background-position: -80px -48px; }
+div.wysiwyg ul.toolbar li.insertImage { background-position: -80px -80px; }
+
+div.wysiwyg ul.toolbar li.html { background-position: -48px -48px; }
+div.wysiwyg ul.toolbar li.removeFormat { background-position: -80px -64px; }
+
+div.wysiwyg ul.toolbar li.empty { background-position: -64px -80px; }
+
+div.wysiwyg iframe { border: 0;  clear: left; margin: 4px 0 0 1px; }
+
diff --git a/media/js/jwysiwyg/jquery/jquery-1.3.2.js b/media/js/jwysiwyg/jquery/jquery-1.3.2.js
new file mode 100644
index 0000000..9263574
--- /dev/null
+++ b/media/js/jwysiwyg/jquery/jquery-1.3.2.js
@@ -0,0 +1,4376 @@
+/*!
+ * jQuery JavaScript Library v1.3.2
+ * http://jquery.com/
+ *
+ * Copyright (c) 2009 John Resig
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ *
+ * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
+ * Revision: 6246
+ */
+(function(){
+
+var 
+	// Will speed up references to window, and allows munging its name.
+	window = this,
+	// Will speed up references to undefined, and allows munging its name.
+	undefined,
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+	// Map over the $ in case of overwrite
+	_$ = window.$,
+
+	jQuery = window.jQuery = window.$ = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		return new jQuery.fn.init( selector, context );
+	},
+
+	// A simple way to check for HTML strings or ID strings
+	// (both of which we optimize for)
+	quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
+	// Is it a simple selector
+	isSimple = /^.[^:#\[\.,]*$/;
+
+jQuery.fn = jQuery.prototype = {
+	init: function( selector, context ) {
+		// Make sure that a selection was provided
+		selector = selector || document;
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this[0] = selector;
+			this.length = 1;
+			this.context = selector;
+			return this;
+		}
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			// Are we dealing with HTML string or an ID?
+			var match = quickExpr.exec( selector );
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] )
+					selector = jQuery.clean( [ match[1] ], context );
+
+				// HANDLE: $("#id")
+				else {
+					var elem = document.getElementById( match[3] );
+
+					// Handle the case where IE and Opera return items
+					// by name instead of ID
+					if ( elem && elem.id != match[3] )
+						return jQuery().find( selector );
+
+					// Otherwise, we inject the element directly into the jQuery object
+					var ret = jQuery( elem || [] );
+					ret.context = document;
+					ret.selector = selector;
+					return ret;
+				}
+
+			// HANDLE: $(expr, [context])
+			// (which is just equivalent to: $(content).find(expr)
+			} else
+				return jQuery( context ).find( selector );
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) )
+			return jQuery( document ).ready( selector );
+
+		// Make sure that old selector state is passed along
+		if ( selector.selector && selector.context ) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return this.setArray(jQuery.isArray( selector ) ?
+			selector :
+			jQuery.makeArray(selector));
+	},
+
+	// Start with an empty selector
+	selector: "",
+
+	// The current version of jQuery being used
+	jquery: "1.3.2",
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num === undefined ?
+
+			// Return a 'clean' array
+			Array.prototype.slice.call( this ) :
+
+			// Return just the object
+			this[ num ];
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems, name, selector ) {
+		// Build a new jQuery matched element set
+		var ret = jQuery( elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		ret.context = this.context;
+
+		if ( name === "find" )
+			ret.selector = this.selector + (this.selector ? " " : "") + selector;
+		else if ( name )
+			ret.selector = this.selector + "." + name + "(" + selector + ")";
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Force the current matched set of elements to become
+	// the specified array of elements (destroying the stack in the process)
+	// You should use pushStack() in order to do this, but maintain the stack
+	setArray: function( elems ) {
+		// Resetting the length to 0, then using the native Array push
+		// is a super-fast way to populate an object with array-like properties
+		this.length = 0;
+		Array.prototype.push.apply( this, elems );
+
+		return this;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	// Determine the position of an element within
+	// the matched set of elements
+	index: function( elem ) {
+		// Locate the position of the desired element
+		return jQuery.inArray(
+			// If it receives a jQuery object, the first element is used
+			elem && elem.jquery ? elem[0] : elem
+		, this );
+	},
+
+	attr: function( name, value, type ) {
+		var options = name;
+
+		// Look for the case where we're accessing a style value
+		if ( typeof name === "string" )
+			if ( value === undefined )
+				return this[0] && jQuery[ type || "attr" ]( this[0], name );
+
+			else {
+				options = {};
+				options[ name ] = value;
+			}
+
+		// Check to see if we're setting style values
+		return this.each(function(i){
+			// Set all the styles
+			for ( name in options )
+				jQuery.attr(
+					type ?
+						this.style :
+						this,
+					name, jQuery.prop( this, options[ name ], type, i, name )
+				);
+		});
+	},
+
+	css: function( key, value ) {
+		// ignore negative width and height values
+		if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
+			value = undefined;
+		return this.attr( key, value, "curCSS" );
+	},
+
+	text: function( text ) {
+		if ( typeof text !== "object" && text != null )
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+
+		var ret = "";
+
+		jQuery.each( text || this, function(){
+			jQuery.each( this.childNodes, function(){
+				if ( this.nodeType != 8 )
+					ret += this.nodeType != 1 ?
+						this.nodeValue :
+						jQuery.fn.text( [ this ] );
+			});
+		});
+
+		return ret;
+	},
+
+	wrapAll: function( html ) {
+		if ( this[0] ) {
+			// The elements to wrap the target around
+			var wrap = jQuery( html, this[0].ownerDocument ).clone();
+
+			if ( this[0].parentNode )
+				wrap.insertBefore( this[0] );
+
+			wrap.map(function(){
+				var elem = this;
+
+				while ( elem.firstChild )
+					elem = elem.firstChild;
+
+				return elem;
+			}).append(this);
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		return this.each(function(){
+			jQuery( this ).contents().wrapAll( html );
+		});
+	},
+
+	wrap: function( html ) {
+		return this.each(function(){
+			jQuery( this ).wrapAll( html );
+		});
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, function(elem){
+			if (this.nodeType == 1)
+				this.appendChild( elem );
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, function(elem){
+			if (this.nodeType == 1)
+				this.insertBefore( elem, this.firstChild );
+		});
+	},
+
+	before: function() {
+		return this.domManip(arguments, false, function(elem){
+			this.parentNode.insertBefore( elem, this );
+		});
+	},
+
+	after: function() {
+		return this.domManip(arguments, false, function(elem){
+			this.parentNode.insertBefore( elem, this.nextSibling );
+		});
+	},
+
+	end: function() {
+		return this.prevObject || jQuery( [] );
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: [].push,
+	sort: [].sort,
+	splice: [].splice,
+
+	find: function( selector ) {
+		if ( this.length === 1 ) {
+			var ret = this.pushStack( [], "find", selector );
+			ret.length = 0;
+			jQuery.find( selector, this[0], ret );
+			return ret;
+		} else {
+			return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
+				return jQuery.find( selector, elem );
+			})), "find", selector );
+		}
+	},
+
+	clone: function( events ) {
+		// Do the clone
+		var ret = this.map(function(){
+			if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
+				// IE copies events bound via attachEvent when
+				// using cloneNode. Calling detachEvent on the
+				// clone will also remove the events from the orignal
+				// In order to get around this, we use innerHTML.
+				// Unfortunately, this means some modifications to
+				// attributes in IE that are actually only stored
+				// as properties will not be copied (such as the
+				// the name attribute on an input).
+				var html = this.outerHTML;
+				if ( !html ) {
+					var div = this.ownerDocument.createElement("div");
+					div.appendChild( this.cloneNode(true) );
+					html = div.innerHTML;
+				}
+
+				return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
+			} else
+				return this.cloneNode(true);
+		});
+
+		// Copy the events from the original to the clone
+		if ( events === true ) {
+			var orig = this.find("*").andSelf(), i = 0;
+
+			ret.find("*").andSelf().each(function(){
+				if ( this.nodeName !== orig[i].nodeName )
+					return;
+
+				var events = jQuery.data( orig[i], "events" );
+
+				for ( var type in events ) {
+					for ( var handler in events[ type ] ) {
+						jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
+					}
+				}
+
+				i++;
+			});
+		}
+
+		// Return the cloned set
+		return ret;
+	},
+
+	filter: function( selector ) {
+		return this.pushStack(
+			jQuery.isFunction( selector ) &&
+			jQuery.grep(this, function(elem, i){
+				return selector.call( elem, i );
+			}) ||
+
+			jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
+				return elem.nodeType === 1;
+			}) ), "filter", selector );
+	},
+
+	closest: function( selector ) {
+		var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
+			closer = 0;
+
+		return this.map(function(){
+			var cur = this;
+			while ( cur && cur.ownerDocument ) {
+				if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
+					jQuery.data(cur, "closest", closer);
+					return cur;
+				}
+				cur = cur.parentNode;
+				closer++;
+			}
+		});
+	},
+
+	not: function( selector ) {
+		if ( typeof selector === "string" )
+			// test special case where just one selector is passed in
+			if ( isSimple.test( selector ) )
+				return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
+			else
+				selector = jQuery.multiFilter( selector, this );
+
+		var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
+		return this.filter(function() {
+			return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
+		});
+	},
+
+	add: function( selector ) {
+		return this.pushStack( jQuery.unique( jQuery.merge(
+			this.get(),
+			typeof selector === "string" ?
+				jQuery( selector ) :
+				jQuery.makeArray( selector )
+		)));
+	},
+
+	is: function( selector ) {
+		return !!selector && jQuery.multiFilter( selector, this ).length > 0;
+	},
+
+	hasClass: function( selector ) {
+		return !!selector && this.is( "." + selector );
+	},
+
+	val: function( value ) {
+		if ( value === undefined ) {			
+			var elem = this[0];
+
+			if ( elem ) {
+				if( jQuery.nodeName( elem, 'option' ) )
+					return (elem.attributes.value || {}).specified ? elem.value : elem.text;
+				
+				// We need to handle select boxes special
+				if ( jQuery.nodeName( elem, "select" ) ) {
+					var index = elem.selectedIndex,
+						values = [],
+						options = elem.options,
+						one = elem.type == "select-one";
+
+					// Nothing was selected
+					if ( index < 0 )
+						return null;
+
+					// Loop through all the selected options
+					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+						var option = options[ i ];
+
+						if ( option.selected ) {
+							// Get the specifc value for the option
+							value = jQuery(option).val();
+
+							// We don't need an array for one selects
+							if ( one )
+								return value;
+
+							// Multi-Selects return an array
+							values.push( value );
+						}
+					}
+
+					return values;				
+				}
+
+				// Everything else, we just grab the value
+				return (elem.value || "").replace(/\r/g, "");
+
+			}
+
+			return undefined;
+		}
+
+		if ( typeof value === "number" )
+			value += '';
+
+		return this.each(function(){
+			if ( this.nodeType != 1 )
+				return;
+
+			if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
+				this.checked = (jQuery.inArray(this.value, value) >= 0 ||
+					jQuery.inArray(this.name, value) >= 0);
+
+			else if ( jQuery.nodeName( this, "select" ) ) {
+				var values = jQuery.makeArray(value);
+
+				jQuery( "option", this ).each(function(){
+					this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
+						jQuery.inArray( this.text, values ) >= 0);
+				});
+
+				if ( !values.length )
+					this.selectedIndex = -1;
+
+			} else
+				this.value = value;
+		});
+	},
+
+	html: function( value ) {
+		return value === undefined ?
+			(this[0] ?
+				this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
+				null) :
+			this.empty().append( value );
+	},
+
+	replaceWith: function( value ) {
+		return this.after( value ).remove();
+	},
+
+	eq: function( i ) {
+		return this.slice( i, +i + 1 );
+	},
+
+	slice: function() {
+		return this.pushStack( Array.prototype.slice.apply( this, arguments ),
+			"slice", Array.prototype.slice.call(arguments).join(",") );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function(elem, i){
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	},
+
+	domManip: function( args, table, callback ) {
+		if ( this[0] ) {
+			var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
+				scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
+				first = fragment.firstChild;
+
+			if ( first )
+				for ( var i = 0, l = this.length; i < l; i++ )
+					callback.call( root(this[i], first), this.length > 1 || i > 0 ?
+							fragment.cloneNode(true) : fragment );
+		
+			if ( scripts )
+				jQuery.each( scripts, evalScript );
+		}
+
+		return this;
+		
+		function root( elem, cur ) {
+			return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
+				(elem.getElementsByTagName("tbody")[0] ||
+				elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+				elem;
+		}
+	}
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+function evalScript( i, elem ) {
+	if ( elem.src )
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+
+	else
+		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+
+	if ( elem.parentNode )
+		elem.parentNode.removeChild( elem );
+}
+
+function now(){
+	return +new Date;
+}
+
+jQuery.extend = jQuery.fn.extend = function() {
+	// copy reference to target object
+	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction(target) )
+		target = {};
+
+	// extend jQuery itself if only one argument is passed
+	if ( length == i ) {
+		target = this;
+		--i;
+	}
+
+	for ( ; i < length; i++ )
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null )
+			// Extend the base object
+			for ( var name in options ) {
+				var src = target[ name ], copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy )
+					continue;
+
+				// Recurse if we're merging object values
+				if ( deep && copy && typeof copy === "object" && !copy.nodeType )
+					target[ name ] = jQuery.extend( deep, 
+						// Never move original objects, clone them
+						src || ( copy.length != null ? [ ] : { } )
+					, copy );
+
+				// Don't bring in undefined values
+				else if ( copy !== undefined )
+					target[ name ] = copy;
+
+			}
+
+	// Return the modified object
+	return target;
+};
+
+// exclude the following css properties to add px
+var	exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
+	// cache defaultView
+	defaultView = document.defaultView || {},
+	toString = Object.prototype.toString;
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		window.$ = _$;
+
+		if ( deep )
+			window.jQuery = _jQuery;
+
+		return jQuery;
+	},
+
+	// See test/unit/core.js for details concerning isFunction.
+	// Since version 1.3, DOM methods and functions like alert
+	// aren't supported. They return false on IE (#2968).
+	isFunction: function( obj ) {
+		return toString.call(obj) === "[object Function]";
+	},
+
+	isArray: function( obj ) {
+		return toString.call(obj) === "[object Array]";
+	},
+
+	// check if an element is in a (or is an) XML document
+	isXMLDoc: function( elem ) {
+		return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+			!!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
+	},
+
+	// Evalulates a script in a global context
+	globalEval: function( data ) {
+		if ( data && /\S/.test(data) ) {
+			// Inspired by code by Andrea Giammarchi
+			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+			var head = document.getElementsByTagName("head")[0] || document.documentElement,
+				script = document.createElement("script");
+
+			script.type = "text/javascript";
+			if ( jQuery.support.scriptEval )
+				script.appendChild( document.createTextNode( data ) );
+			else
+				script.text = data;
+
+			// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+			// This arises when a base node is used (#2709).
+			head.insertBefore( script, head.firstChild );
+			head.removeChild( script );
+		}
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		var name, i = 0, length = object.length;
+
+		if ( args ) {
+			if ( length === undefined ) {
+				for ( name in object )
+					if ( callback.apply( object[ name ], args ) === false )
+						break;
+			} else
+				for ( ; i < length; )
+					if ( callback.apply( object[ i++ ], args ) === false )
+						break;
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( length === undefined ) {
+				for ( name in object )
+					if ( callback.call( object[ name ], name, object[ name ] ) === false )
+						break;
+			} else
+				for ( var value = object[0];
+					i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
+		}
+
+		return object;
+	},
+
+	prop: function( elem, value, type, i, name ) {
+		// Handle executable functions
+		if ( jQuery.isFunction( value ) )
+			value = value.call( elem, i );
+
+		// Handle passing in a number to a CSS property
+		return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ?
+			value + "px" :
+			value;
+	},
+
+	className: {
+		// internal only, use addClass("class")
+		add: function( elem, classNames ) {
+			jQuery.each((classNames || "").split(/\s+/), function(i, className){
+				if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
+					elem.className += (elem.className ? " " : "") + className;
+			});
+		},
+
+		// internal only, use removeClass("class")
+		remove: function( elem, classNames ) {
+			if (elem.nodeType == 1)
+				elem.className = classNames !== undefined ?
+					jQuery.grep(elem.className.split(/\s+/), function(className){
+						return !jQuery.className.has( classNames, className );
+					}).join(" ") :
+					"";
+		},
+
+		// internal only, use hasClass("class")
+		has: function( elem, className ) {
+			return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( var name in options )
+			elem.style[ name ] = old[ name ];
+	},
+
+	css: function( elem, name, force, extra ) {
+		if ( name == "width" || name == "height" ) {
+			var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];
+
+			function getWH() {
+				val = name == "width" ? elem.offsetWidth : elem.offsetHeight;
+
+				if ( extra === "border" )
+					return;
+
+				jQuery.each( which, function() {
+					if ( !extra )
+						val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
+					if ( extra === "margin" )
+						val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
+					else
+						val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
+				});
+			}
+
+			if ( elem.offsetWidth !== 0 )
+				getWH();
+			else
+				jQuery.swap( elem, props, getWH );
+
+			return Math.max(0, Math.round(val));
+		}
+
+		return jQuery.curCSS( elem, name, force );
+	},
+
+	curCSS: function( elem, name, force ) {
+		var ret, style = elem.style;
+
+		// We need to handle opacity special in IE
+		if ( name == "opacity" && !jQuery.support.opacity ) {
+			ret = jQuery.attr( style, "opacity" );
+
+			return ret == "" ?
+				"1" :
+				ret;
+		}
+
+		// Make sure we're using the right name for getting the float value
+		if ( name.match( /float/i ) )
+			name = styleFloat;
+
+		if ( !force && style && style[ name ] )
+			ret = style[ name ];
+
+		else if ( defaultView.getComputedStyle ) {
+
+			// Only "float" is needed here
+			if ( name.match( /float/i ) )
+				name = "float";
+
+			name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();
+
+			var computedStyle = defaultView.getComputedStyle( elem, null );
+
+			if ( computedStyle )
+				ret = computedStyle.getPropertyValue( name );
+
+			// We should always get a number back from opacity
+			if ( name == "opacity" && ret == "" )
+				ret = "1";
+
+		} else if ( elem.currentStyle ) {
+			var camelCase = name.replace(/\-(\w)/g, function(all, letter){
+				return letter.toUpperCase();
+			});
+
+			ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];
+
+			// From the awesome hack by Dean Edwards
+			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+			// If we're not dealing with a regular pixel number
+			// but a number that has a weird ending, we need to convert it to pixels
+			if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
+				// Remember the original values
+				var left = style.left, rsLeft = elem.runtimeStyle.left;
+
+				// Put in the new values to get a computed value out
+				elem.runtimeStyle.left = elem.currentStyle.left;
+				style.left = ret || 0;
+				ret = style.pixelLeft + "px";
+
+				// Revert the changed values
+				style.left = left;
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret;
+	},
+
+	clean: function( elems, context, fragment ) {
+		context = context || document;
+
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if ( typeof context.createElement === "undefined" )
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+
+		// If a single string is passed in and it's a single tag
+		// just do a createElement and skip the rest
+		if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
+			var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
+			if ( match )
+				return [ context.createElement( match[1] ) ];
+		}
+
+		var ret = [], scripts = [], div = context.createElement("div");
+
+		jQuery.each(elems, function(i, elem){
+			if ( typeof elem === "number" )
+				elem += '';
+
+			if ( !elem )
+				return;
+
+			// Convert html string into DOM nodes
+			if ( typeof elem === "string" ) {
+				// Fix "XHTML"-style tags in all browsers
+				elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
+					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
+						all :
+						front + "></" + tag + ">";
+				});
+
+				// Trim whitespace, otherwise indexOf won't work as expected
+				var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();
+
+				var wrap =
+					// option or optgroup
+					!tags.indexOf("<opt") &&
+					[ 1, "<select multiple='multiple'>", "</select>" ] ||
+
+					!tags.indexOf("<leg") &&
+					[ 1, "<fieldset>", "</fieldset>" ] ||
+
+					tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
+					[ 1, "<table>", "</table>" ] ||
+
+					!tags.indexOf("<tr") &&
+					[ 2, "<table><tbody>", "</tbody></table>" ] ||
+
+				 	// <thead> matched above
+					(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
+					[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||
+
+					!tags.indexOf("<col") &&
+					[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||
+
+					// IE can't serialize <link> and <script> tags normally
+					!jQuery.support.htmlSerialize &&
+					[ 1, "div<div>", "</div>" ] ||
+
+					[ 0, "", "" ];
+
+				// Go to html and back, then peel off extra wrappers
+				div.innerHTML = wrap[1] + elem + wrap[2];
+
+				// Move to the right depth
+				while ( wrap[0]-- )
+					div = div.lastChild;
+
+				// Remove IE's autoinserted <tbody> from table fragments
+				if ( !jQuery.support.tbody ) {
+
+					// String was a <table>, *may* have spurious <tbody>
+					var hasBody = /<tbody/i.test(elem),
+						tbody = !tags.indexOf("<table") && !hasBody ?
+							div.firstChild && div.firstChild.childNodes :
+
+						// String was a bare <thead> or <tfoot>
+						wrap[1] == "<table>" && !hasBody ?
+							div.childNodes :
+							[];
+
+					for ( var j = tbody.length - 1; j >= 0 ; --j )
+						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
+							tbody[ j ].parentNode.removeChild( tbody[ j ] );
+
+					}
+
+				// IE completely kills leading whitespace when innerHTML is used
+				if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
+					div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
+				
+				elem = jQuery.makeArray( div.childNodes );
+			}
+
+			if ( elem.nodeType )
+				ret.push( elem );
+			else
+				ret = jQuery.merge( ret, elem );
+
+		});
+
+		if ( fragment ) {
+			for ( var i = 0; ret[i]; i++ ) {
+				if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+				} else {
+					if ( ret[i].nodeType === 1 )
+						ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
+					fragment.appendChild( ret[i] );
+				}
+			}
+			
+			return scripts;
+		}
+
+		return ret;
+	},
+
+	attr: function( elem, name, value ) {
+		// don't set attributes on text and comment nodes
+		if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
+			return undefined;
+
+		var notxml = !jQuery.isXMLDoc( elem ),
+			// Whether we are setting (or getting)
+			set = value !== undefined;
+
+		// Try to normalize/fix the name
+		name = notxml && jQuery.props[ name ] || name;
+
+		// Only do all the following if this is a node (faster for style)
+		// IE elem.getAttribute passes even for style
+		if ( elem.tagName ) {
+
+			// These attributes require special treatment
+			var special = /href|src|style/.test( name );
+
+			// Safari mis-reports the default selected property of a hidden option
+			// Accessing the parent's selectedIndex property fixes it
+			if ( name == "selected" && elem.parentNode )
+				elem.parentNode.selectedIndex;
+
+			// If applicable, access the attribute via the DOM 0 way
+			if ( name in elem && notxml && !special ) {
+				if ( set ){
+					// We can't allow the type property to be changed (since it causes problems in IE)
+					if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
+						throw "type property can't be changed";
+
+					elem[ name ] = value;
+				}
+
+				// browsers index elements by id/name on forms, give priority to attributes.
+				if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
+					return elem.getAttributeNode( name ).nodeValue;
+
+				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				if ( name == "tabIndex" ) {
+					var attributeNode = elem.getAttributeNode( "tabIndex" );
+					return attributeNode && attributeNode.specified
+						? attributeNode.value
+						: elem.nodeName.match(/(button|input|object|select|textarea)/i)
+							? 0
+							: elem.nodeName.match(/^(a|area)$/i) && elem.href
+								? 0
+								: undefined;
+				}
+
+				return elem[ name ];
+			}
+
+			if ( !jQuery.support.style && notxml &&  name == "style" )
+				return jQuery.attr( elem.style, "cssText", value );
+
+			if ( set )
+				// convert the value to a string (all browsers do this but IE) see #1070
+				elem.setAttribute( name, "" + value );
+
+			var attr = !jQuery.support.hrefNormalized && notxml && special
+					// Some attributes require a special call on IE
+					? elem.getAttribute( name, 2 )
+					: elem.getAttribute( name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return attr === null ? undefined : attr;
+		}
+
+		// elem is actually elem.style ... set the style
+
+		// IE uses filters for opacity
+		if ( !jQuery.support.opacity && name == "opacity" ) {
+			if ( set ) {
+				// IE has trouble with opacity if it does not have layout
+				// Force it by setting the zoom level
+				elem.zoom = 1;
+
+				// Set the alpha filter to set the opacity
+				elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
+					(parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
+			}
+
+			return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
+				(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
+				"";
+		}
+
+		name = name.replace(/-([a-z])/ig, function(all, letter){
+			return letter.toUpperCase();
+		});
+
+		if ( set )
+			elem[ name ] = value;
+
+		return elem[ name ];
+	},
+
+	trim: function( text ) {
+		return (text || "").replace( /^\s+|\s+$/g, "" );
+	},
+
+	makeArray: function( array ) {
+		var ret = [];
+
+		if( array != null ){
+			var i = array.length;
+			// The window, strings (and functions) also have 'length'
+			if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval )
+				ret[0] = array;
+			else
+				while( i )
+					ret[--i] = array[i];
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, array ) {
+		for ( var i = 0, length = array.length; i < length; i++ )
+		// Use === because on IE, window == document
+			if ( array[ i ] === elem )
+				return i;
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		// We have to loop this way because IE & Opera overwrite the length
+		// expando of getElementsByTagName
+		var i = 0, elem, pos = first.length;
+		// Also, we need to make sure that the correct elements are being returned
+		// (IE returns comment nodes in a '*' query)
+		if ( !jQuery.support.getAll ) {
+			while ( (elem = second[ i++ ]) != null )
+				if ( elem.nodeType != 8 )
+					first[ pos++ ] = elem;
+
+		} else
+			while ( (elem = second[ i++ ]) != null )
+				first[ pos++ ] = elem;
+
+		return first;
+	},
+
+	unique: function( array ) {
+		var ret = [], done = {};
+
+		try {
+
+			for ( var i = 0, length = array.length; i < length; i++ ) {
+				var id = jQuery.data( array[ i ] );
+
+				if ( !done[ id ] ) {
+					done[ id ] = true;
+					ret.push( array[ i ] );
+				}
+			}
+
+		} catch( e ) {
+			ret = array;
+		}
+
+		return ret;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var ret = [];
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ )
+			if ( !inv != !callback( elems[ i ], i ) )
+				ret.push( elems[ i ] );
+
+		return ret;
+	},
+
+	map: function( elems, callback ) {
+		var ret = [];
+
+		// Go through the array, translating each of the items to their
+		// new value (or values).
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			var value = callback( elems[ i ], i );
+
+			if ( value != null )
+				ret[ ret.length ] = value;
+		}
+
+		return ret.concat.apply( [], ret );
+	}
+});
+
+// Use of jQuery.browser is deprecated.
+// It's included for backwards compatibility and plugins,
+// although they should work to migrate away.
+
+var userAgent = navigator.userAgent.toLowerCase();
+
+// Figure out what browser is being used
+jQuery.browser = {
+	version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
+	safari: /webkit/.test( userAgent ),
+	opera: /opera/.test( userAgent ),
+	msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
+	mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
+};
+
+jQuery.each({
+	parent: function(elem){return elem.parentNode;},
+	parents: function(elem){return jQuery.dir(elem,"parentNode");},
+	next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
+	prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
+	nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
+	prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
+	siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
+	children: function(elem){return jQuery.sibling(elem.firstChild);},
+	contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
+}, function(name, fn){
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = jQuery.map( this, fn );
+
+		if ( selector && typeof selector == "string" )
+			ret = jQuery.multiFilter( selector, ret );
+
+		return this.pushStack( jQuery.unique( ret ), name, selector );
+	};
+});
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function(name, original){
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = [], insert = jQuery( selector );
+
+		for ( var i = 0, l = insert.length; i < l; i++ ) {
+			var elems = (i > 0 ? this.clone(true) : this).get();
+			jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
+			ret = ret.concat( elems );
+		}
+
+		return this.pushStack( ret, name, selector );
+	};
+});
+
+jQuery.each({
+	removeAttr: function( name ) {
+		jQuery.attr( this, name, "" );
+		if (this.nodeType == 1)
+			this.removeAttribute( name );
+	},
+
+	addClass: function( classNames ) {
+		jQuery.className.add( this, classNames );
+	},
+
+	removeClass: function( classNames ) {
+		jQuery.className.remove( this, classNames );
+	},
+
+	toggleClass: function( classNames, state ) {
+		if( typeof state !== "boolean" )
+			state = !jQuery.className.has( this, classNames );
+		jQuery.className[ state ? "add" : "remove" ]( this, classNames );
+	},
+
+	remove: function( selector ) {
+		if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
+			// Prevent memory leaks
+			jQuery( "*", this ).add([this]).each(function(){
+				jQuery.event.remove(this);
+				jQuery.removeData(this);
+			});
+			if (this.parentNode)
+				this.parentNode.removeChild( this );
+		}
+	},
+
+	empty: function() {
+		// Remove element nodes and prevent memory leaks
+		jQuery(this).children().remove();
+
+		// Remove any remaining nodes
+		while ( this.firstChild )
+			this.removeChild( this.firstChild );
+	}
+}, function(name, fn){
+	jQuery.fn[ name ] = function(){
+		return this.each( fn, arguments );
+	};
+});
+
+// Helper function used by the dimensions and offset modules
+function num(elem, prop) {
+	return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
+}
+var expando = "jQuery" + now(), uuid = 0, windowData = {};
+
+jQuery.extend({
+	cache: {},
+
+	data: function( elem, name, data ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// Compute a unique ID for the element
+		if ( !id )
+			id = elem[ expando ] = ++uuid;
+
+		// Only generate the data cache if we're
+		// trying to access or manipulate it
+		if ( name && !jQuery.cache[ id ] )
+			jQuery.cache[ id ] = {};
+
+		// Prevent overriding the named cache with undefined values
+		if ( data !== undefined )
+			jQuery.cache[ id ][ name ] = data;
+
+		// Return the named cache data, or the ID for the element
+		return name ?
+			jQuery.cache[ id ][ name ] :
+			id;
+	},
+
+	removeData: function( elem, name ) {
+		elem = elem == window ?
+			windowData :
+			elem;
+
+		var id = elem[ expando ];
+
+		// If we want to remove a specific section of the element's data
+		if ( name ) {
+			if ( jQuery.cache[ id ] ) {
+				// Remove the section of cache data
+				delete jQuery.cache[ id ][ name ];
+
+				// If we've removed all the data, remove the element's cache
+				name = "";
+
+				for ( name in jQuery.cache[ id ] )
+					break;
+
+				if ( !name )
+					jQuery.removeData( elem );
+			}
+
+		// Otherwise, we want to remove all of the element's data
+		} else {
+			// Clean up the element expando
+			try {
+				delete elem[ expando ];
+			} catch(e){
+				// IE has trouble directly removing the expando
+				// but it's ok with using removeAttribute
+				if ( elem.removeAttribute )
+					elem.removeAttribute( expando );
+			}
+
+			// Completely remove the data cache
+			delete jQuery.cache[ id ];
+		}
+	},
+	queue: function( elem, type, data ) {
+		if ( elem ){
+	
+			type = (type || "fx") + "queue";
+	
+			var q = jQuery.data( elem, type );
+	
+			if ( !q || jQuery.isArray(data) )
+				q = jQuery.data( elem, type, jQuery.makeArray(data) );
+			else if( data )
+				q.push( data );
+	
+		}
+		return q;
+	},
+
+	dequeue: function( elem, type ){
+		var queue = jQuery.queue( elem, type ),
+			fn = queue.shift();
+		
+		if( !type || type === "fx" )
+			fn = queue[0];
+			
+		if( fn !== undefined )
+			fn.call(elem);
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ){
+		var parts = key.split(".");
+		parts[1] = parts[1] ? "." + parts[1] : "";
+
+		if ( value === undefined ) {
+			var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+			if ( data === undefined && this.length )
+				data = jQuery.data( this[0], key );
+
+			return data === undefined && parts[1] ?
+				this.data( parts[0] ) :
+				data;
+		} else
+			return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
+				jQuery.data( this, key, value );
+			});
+	},
+
+	removeData: function( key ){
+		return this.each(function(){
+			jQuery.removeData( this, key );
+		});
+	},
+	queue: function(type, data){
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+		}
+
+		if ( data === undefined )
+			return jQuery.queue( this[0], type );
+
+		return this.each(function(){
+			var queue = jQuery.queue( this, type, data );
+			
+			 if( type == "fx" && queue.length == 1 )
+				queue[0].call(this);
+		});
+	},
+	dequeue: function(type){
+		return this.each(function(){
+			jQuery.dequeue( this, type );
+		});
+	}
+});/*!
+ * Sizzle CSS Selector Engine - v0.9.3
+ *  Copyright 2009, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
+	done = 0,
+	toString = Object.prototype.toString;
+
+var Sizzle = function(selector, context, results, seed) {
+	results = results || [];
+	context = context || document;
+
+	if ( context.nodeType !== 1 && context.nodeType !== 9 )
+		return [];
+	
+	if ( !selector || typeof selector !== "string" ) {
+		return results;
+	}
+
+	var parts = [], m, set, checkSet, check, mode, extra, prune = true;
+	
+	// Reset the position of the chunker regexp (start from head)
+	chunker.lastIndex = 0;
+	
+	while ( (m = chunker.exec(selector)) !== null ) {
+		parts.push( m[1] );
+		
+		if ( m[2] ) {
+			extra = RegExp.rightContext;
+			break;
+		}
+	}
+
+	if ( parts.length > 1 && origPOS.exec( selector ) ) {
+		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+			set = posProcess( parts[0] + parts[1], context );
+		} else {
+			set = Expr.relative[ parts[0] ] ?
+				[ context ] :
+				Sizzle( parts.shift(), context );
+
+			while ( parts.length ) {
+				selector = parts.shift();
+
+				if ( Expr.relative[ selector ] )
+					selector += parts.shift();
+
+				set = posProcess( selector, set );
+			}
+		}
+	} else {
+		var ret = seed ?
+			{ expr: parts.pop(), set: makeArray(seed) } :
+			Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
+		set = Sizzle.filter( ret.expr, ret.set );
+
+		if ( parts.length > 0 ) {
+			checkSet = makeArray(set);
+		} else {
+			prune = false;
+		}
+
+		while ( parts.length ) {
+			var cur = parts.pop(), pop = cur;
+
+			if ( !Expr.relative[ cur ] ) {
+				cur = "";
+			} else {
+				pop = parts.pop();
+			}
+
+			if ( pop == null ) {
+				pop = context;
+			}
+
+			Expr.relative[ cur ]( checkSet, pop, isXML(context) );
+		}
+	}
+
+	if ( !checkSet ) {
+		checkSet = set;
+	}
+
+	if ( !checkSet ) {
+		throw "Syntax error, unrecognized expression: " + (cur || selector);
+	}
+
+	if ( toString.call(checkSet) === "[object Array]" ) {
+		if ( !prune ) {
+			results.push.apply( results, checkSet );
+		} else if ( context.nodeType === 1 ) {
+			for ( var i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+					results.push( set[i] );
+				}
+			}
+		} else {
+			for ( var i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+					results.push( set[i] );
+				}
+			}
+		}
+	} else {
+		makeArray( checkSet, results );
+	}
+
+	if ( extra ) {
+		Sizzle( extra, context, results, seed );
+
+		if ( sortOrder ) {
+			hasDuplicate = false;
+			results.sort(sortOrder);
+
+			if ( hasDuplicate ) {
+				for ( var i = 1; i < results.length; i++ ) {
+					if ( results[i] === results[i-1] ) {
+						results.splice(i--, 1);
+					}
+				}
+			}
+		}
+	}
+
+	return results;
+};
+
+Sizzle.matches = function(expr, set){
+	return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+	var set, match;
+
+	if ( !expr ) {
+		return [];
+	}
+
+	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+		var type = Expr.order[i], match;
+		
+		if ( (match = Expr.match[ type ].exec( expr )) ) {
+			var left = RegExp.leftContext;
+
+			if ( left.substr( left.length - 1 ) !== "\\" ) {
+				match[1] = (match[1] || "").replace(/\\/g, "");
+				set = Expr.find[ type ]( match, context, isXML );
+				if ( set != null ) {
+					expr = expr.replace( Expr.match[ type ], "" );
+					break;
+				}
+			}
+		}
+	}
+
+	if ( !set ) {
+		set = context.getElementsByTagName("*");
+	}
+
+	return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+	var old = expr, result = [], curLoop = set, match, anyFound,
+		isXMLFilter = set && set[0] && isXML(set[0]);
+
+	while ( expr && set.length ) {
+		for ( var type in Expr.filter ) {
+			if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+				var filter = Expr.filter[ type ], found, item;
+				anyFound = false;
+
+				if ( curLoop == result ) {
+					result = [];
+				}
+
+				if ( Expr.preFilter[ type ] ) {
+					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+					if ( !match ) {
+						anyFound = found = true;
+					} else if ( match === true ) {
+						continue;
+					}
+				}
+
+				if ( match ) {
+					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+						if ( item ) {
+							found = filter( item, match, i, curLoop );
+							var pass = not ^ !!found;
+
+							if ( inplace && found != null ) {
+								if ( pass ) {
+									anyFound = true;
+								} else {
+									curLoop[i] = false;
+								}
+							} else if ( pass ) {
+								result.push( item );
+								anyFound = true;
+							}
+						}
+					}
+				}
+
+				if ( found !== undefined ) {
+					if ( !inplace ) {
+						curLoop = result;
+					}
+
+					expr = expr.replace( Expr.match[ type ], "" );
+
+					if ( !anyFound ) {
+						return [];
+					}
+
+					break;
+				}
+			}
+		}
+
+		// Improper expression
+		if ( expr == old ) {
+			if ( anyFound == null ) {
+				throw "Syntax error, unrecognized expression: " + expr;
+			} else {
+				break;
+			}
+		}
+
+		old = expr;
+	}
+
+	return curLoop;
+};
+
+var Expr = Sizzle.selectors = {
+	order: [ "ID", "NAME", "TAG" ],
+	match: {
+		ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
+		CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
+		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
+		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+		TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
+		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+		PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+	},
+	attrMap: {
+		"class": "className",
+		"for": "htmlFor"
+	},
+	attrHandle: {
+		href: function(elem){
+			return elem.getAttribute("href");
+		}
+	},
+	relative: {
+		"+": function(checkSet, part, isXML){
+			var isPartStr = typeof part === "string",
+				isTag = isPartStr && !/\W/.test(part),
+				isPartStrNotTag = isPartStr && !isTag;
+
+			if ( isTag && !isXML ) {
+				part = part.toUpperCase();
+			}
+
+			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+				if ( (elem = checkSet[i]) ) {
+					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+					checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+						elem || false :
+						elem === part;
+				}
+			}
+
+			if ( isPartStrNotTag ) {
+				Sizzle.filter( part, checkSet, true );
+			}
+		},
+		">": function(checkSet, part, isXML){
+			var isPartStr = typeof part === "string";
+
+			if ( isPartStr && !/\W/.test(part) ) {
+				part = isXML ? part : part.toUpperCase();
+
+				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+					var elem = checkSet[i];
+					if ( elem ) {
+						var parent = elem.parentNode;
+						checkSet[i] = parent.nodeName === part ? parent : false;
+					}
+				}
+			} else {
+				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+					var elem = checkSet[i];
+					if ( elem ) {
+						checkSet[i] = isPartStr ?
+							elem.parentNode :
+							elem.parentNode === part;
+					}
+				}
+
+				if ( isPartStr ) {
+					Sizzle.filter( part, checkSet, true );
+				}
+			}
+		},
+		"": function(checkSet, part, isXML){
+			var doneName = done++, checkFn = dirCheck;
+
+			if ( !part.match(/\W/) ) {
+				var nodeCheck = part = isXML ? part : part.toUpperCase();
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+		},
+		"~": function(checkSet, part, isXML){
+			var doneName = done++, checkFn = dirCheck;
+
+			if ( typeof part === "string" && !part.match(/\W/) ) {
+				var nodeCheck = part = isXML ? part : part.toUpperCase();
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+		}
+	},
+	find: {
+		ID: function(match, context, isXML){
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+				return m ? [m] : [];
+			}
+		},
+		NAME: function(match, context, isXML){
+			if ( typeof context.getElementsByName !== "undefined" ) {
+				var ret = [], results = context.getElementsByName(match[1]);
+
+				for ( var i = 0, l = results.length; i < l; i++ ) {
+					if ( results[i].getAttribute("name") === match[1] ) {
+						ret.push( results[i] );
+					}
+				}
+
+				return ret.length === 0 ? null : ret;
+			}
+		},
+		TAG: function(match, context){
+			return context.getElementsByTagName(match[1]);
+		}
+	},
+	preFilter: {
+		CLASS: function(match, curLoop, inplace, result, not, isXML){
+			match = " " + match[1].replace(/\\/g, "") + " ";
+
+			if ( isXML ) {
+				return match;
+			}
+
+			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+				if ( elem ) {
+					if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+						if ( !inplace )
+							result.push( elem );
+					} else if ( inplace ) {
+						curLoop[i] = false;
+					}
+				}
+			}
+
+			return false;
+		},
+		ID: function(match){
+			return match[1].replace(/\\/g, "");
+		},
+		TAG: function(match, curLoop){
+			for ( var i = 0; curLoop[i] === false; i++ ){}
+			return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+		},
+		CHILD: function(match){
+			if ( match[1] == "nth" ) {
+				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+					match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+				// calculate the numbers (first)n+(last) including if they are negative
+				match[2] = (test[1] + (test[2] || 1)) - 0;
+				match[3] = test[3] - 0;
+			}
+
+			// TODO: Move to normal caching system
+			match[0] = done++;
+
+			return match;
+		},
+		ATTR: function(match, curLoop, inplace, result, not, isXML){
+			var name = match[1].replace(/\\/g, "");
+			
+			if ( !isXML && Expr.attrMap[name] ) {
+				match[1] = Expr.attrMap[name];
+			}
+
+			if ( match[2] === "~=" ) {
+				match[4] = " " + match[4] + " ";
+			}
+
+			return match;
+		},
+		PSEUDO: function(match, curLoop, inplace, result, not){
+			if ( match[1] === "not" ) {
+				// If we're dealing with a complex expression, or a simple one
+				if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
+					match[3] = Sizzle(match[3], null, null, curLoop);
+				} else {
+					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+					if ( !inplace ) {
+						result.push.apply( result, ret );
+					}
+					return false;
+				}
+			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+				return true;
+			}
+			
+			return match;
+		},
+		POS: function(match){
+			match.unshift( true );
+			return match;
+		}
+	},
+	filters: {
+		enabled: function(elem){
+			return elem.disabled === false && elem.type !== "hidden";
+		},
+		disabled: function(elem){
+			return elem.disabled === true;
+		},
+		checked: function(elem){
+			return elem.checked === true;
+		},
+		selected: function(elem){
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			elem.parentNode.selectedIndex;
+			return elem.selected === true;
+		},
+		parent: function(elem){
+			return !!elem.firstChild;
+		},
+		empty: function(elem){
+			return !elem.firstChild;
+		},
+		has: function(elem, i, match){
+			return !!Sizzle( match[3], elem ).length;
+		},
+		header: function(elem){
+			return /h\d/i.test( elem.nodeName );
+		},
+		text: function(elem){
+			return "text" === elem.type;
+		},
+		radio: function(elem){
+			return "radio" === elem.type;
+		},
+		checkbox: function(elem){
+			return "checkbox" === elem.type;
+		},
+		file: function(elem){
+			return "file" === elem.type;
+		},
+		password: function(elem){
+			return "password" === elem.type;
+		},
+		submit: function(elem){
+			return "submit" === elem.type;
+		},
+		image: function(elem){
+			return "image" === elem.type;
+		},
+		reset: function(elem){
+			return "reset" === elem.type;
+		},
+		button: function(elem){
+			return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+		},
+		input: function(elem){
+			return /input|select|textarea|button/i.test(elem.nodeName);
+		}
+	},
+	setFilters: {
+		first: function(elem, i){
+			return i === 0;
+		},
+		last: function(elem, i, match, array){
+			return i === array.length - 1;
+		},
+		even: function(elem, i){
+			return i % 2 === 0;
+		},
+		odd: function(elem, i){
+			return i % 2 === 1;
+		},
+		lt: function(elem, i, match){
+			return i < match[3] - 0;
+		},
+		gt: function(elem, i, match){
+			return i > match[3] - 0;
+		},
+		nth: function(elem, i, match){
+			return match[3] - 0 == i;
+		},
+		eq: function(elem, i, match){
+			return match[3] - 0 == i;
+		}
+	},
+	filter: {
+		PSEUDO: function(elem, match, i, array){
+			var name = match[1], filter = Expr.filters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+			} else if ( name === "contains" ) {
+				return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+			} else if ( name === "not" ) {
+				var not = match[3];
+
+				for ( var i = 0, l = not.length; i < l; i++ ) {
+					if ( not[i] === elem ) {
+						return false;
+					}
+				}
+
+				return true;
+			}
+		},
+		CHILD: function(elem, match){
+			var type = match[1], node = elem;
+			switch (type) {
+				case 'only':
+				case 'first':
+					while (node = node.previousSibling)  {
+						if ( node.nodeType === 1 ) return false;
+					}
+					if ( type == 'first') return true;
+					node = elem;
+				case 'last':
+					while (node = node.nextSibling)  {
+						if ( node.nodeType === 1 ) return false;
+					}
+					return true;
+				case 'nth':
+					var first = match[2], last = match[3];
+
+					if ( first == 1 && last == 0 ) {
+						return true;
+					}
+					
+					var doneName = match[0],
+						parent = elem.parentNode;
+	
+					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+						var count = 0;
+						for ( node = parent.firstChild; node; node = node.nextSibling ) {
+							if ( node.nodeType === 1 ) {
+								node.nodeIndex = ++count;
+							}
+						} 
+						parent.sizcache = doneName;
+					}
+					
+					var diff = elem.nodeIndex - last;
+					if ( first == 0 ) {
+						return diff == 0;
+					} else {
+						return ( diff % first == 0 && diff / first >= 0 );
+					}
+			}
+		},
+		ID: function(elem, match){
+			return elem.nodeType === 1 && elem.getAttribute("id") === match;
+		},
+		TAG: function(elem, match){
+			return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+		},
+		CLASS: function(elem, match){
+			return (" " + (elem.className || elem.getAttribute("class")) + " ")
+				.indexOf( match ) > -1;
+		},
+		ATTR: function(elem, match){
+			var name = match[1],
+				result = Expr.attrHandle[ name ] ?
+					Expr.attrHandle[ name ]( elem ) :
+					elem[ name ] != null ?
+						elem[ name ] :
+						elem.getAttribute( name ),
+				value = result + "",
+				type = match[2],
+				check = match[4];
+
+			return result == null ?
+				type === "!=" :
+				type === "=" ?
+				value === check :
+				type === "*=" ?
+				value.indexOf(check) >= 0 :
+				type === "~=" ?
+				(" " + value + " ").indexOf(check) >= 0 :
+				!check ?
+				value && result !== false :
+				type === "!=" ?
+				value != check :
+				type === "^=" ?
+				value.indexOf(check) === 0 :
+				type === "$=" ?
+				value.substr(value.length - check.length) === check :
+				type === "|=" ?
+				value === check || value.substr(0, check.length + 1) === check + "-" :
+				false;
+		},
+		POS: function(elem, match, i, array){
+			var name = match[2], filter = Expr.setFilters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+			}
+		}
+	}
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+	Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+}
+
+var makeArray = function(array, results) {
+	array = Array.prototype.slice.call( array );
+
+	if ( results ) {
+		results.push.apply( results, array );
+		return results;
+	}
+	
+	return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+try {
+	Array.prototype.slice.call( document.documentElement.childNodes );
+
+// Provide a fallback method if it does not work
+} catch(e){
+	makeArray = function(array, results) {
+		var ret = results || [];
+
+		if ( toString.call(array) === "[object Array]" ) {
+			Array.prototype.push.apply( ret, array );
+		} else {
+			if ( typeof array.length === "number" ) {
+				for ( var i = 0, l = array.length; i < l; i++ ) {
+					ret.push( array[i] );
+				}
+			} else {
+				for ( var i = 0; array[i]; i++ ) {
+					ret.push( array[i] );
+				}
+			}
+		}
+
+		return ret;
+	};
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+	sortOrder = function( a, b ) {
+		var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+		if ( ret === 0 ) {
+			hasDuplicate = true;
+		}
+		return ret;
+	};
+} else if ( "sourceIndex" in document.documentElement ) {
+	sortOrder = function( a, b ) {
+		var ret = a.sourceIndex - b.sourceIndex;
+		if ( ret === 0 ) {
+			hasDuplicate = true;
+		}
+		return ret;
+	};
+} else if ( document.createRange ) {
+	sortOrder = function( a, b ) {
+		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+		aRange.selectNode(a);
+		aRange.collapse(true);
+		bRange.selectNode(b);
+		bRange.collapse(true);
+		var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+		if ( ret === 0 ) {
+			hasDuplicate = true;
+		}
+		return ret;
+	};
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+	// We're going to inject a fake input element with a specified name
+	var form = document.createElement("form"),
+		id = "script" + (new Date).getTime();
+	form.innerHTML = "<input name='" + id + "'/>";
+
+	// Inject it into the root element, check its status, and remove it quickly
+	var root = document.documentElement;
+	root.insertBefore( form, root.firstChild );
+
+	// The workaround has to do additional checks after a getElementById
+	// Which slows things down for other browsers (hence the branching)
+	if ( !!document.getElementById( id ) ) {
+		Expr.find.ID = function(match, context, isXML){
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+			}
+		};
+
+		Expr.filter.ID = function(elem, match){
+			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+			return elem.nodeType === 1 && node && node.nodeValue === match;
+		};
+	}
+
+	root.removeChild( form );
+})();
+
+(function(){
+	// Check to see if the browser returns only elements
+	// when doing getElementsByTagName("*")
+
+	// Create a fake element
+	var div = document.createElement("div");
+	div.appendChild( document.createComment("") );
+
+	// Make sure no comments are found
+	if ( div.getElementsByTagName("*").length > 0 ) {
+		Expr.find.TAG = function(match, context){
+			var results = context.getElementsByTagName(match[1]);
+
+			// Filter out possible comments
+			if ( match[1] === "*" ) {
+				var tmp = [];
+
+				for ( var i = 0; results[i]; i++ ) {
+					if ( results[i].nodeType === 1 ) {
+						tmp.push( results[i] );
+					}
+				}
+
+				results = tmp;
+			}
+
+			return results;
+		};
+	}
+
+	// Check to see if an attribute returns normalized href attributes
+	div.innerHTML = "<a href='#'></a>";
+	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+			div.firstChild.getAttribute("href") !== "#" ) {
+		Expr.attrHandle.href = function(elem){
+			return elem.getAttribute("href", 2);
+		};
+	}
+})();
+
+if ( document.querySelectorAll ) (function(){
+	var oldSizzle = Sizzle, div = document.createElement("div");
+	div.innerHTML = "<p class='TEST'></p>";
+
+	// Safari can't handle uppercase or unicode characters when
+	// in quirks mode.
+	if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+		return;
+	}
+	
+	Sizzle = function(query, context, extra, seed){
+		context = context || document;
+
+		// Only use querySelectorAll on non-XML documents
+		// (ID selectors don't work in non-HTML documents)
+		if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+			try {
+				return makeArray( context.querySelectorAll(query), extra );
+			} catch(e){}
+		}
+		
+		return oldSizzle(query, context, extra, seed);
+	};
+
+	Sizzle.find = oldSizzle.find;
+	Sizzle.filter = oldSizzle.filter;
+	Sizzle.selectors = oldSizzle.selectors;
+	Sizzle.matches = oldSizzle.matches;
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
+	var div = document.createElement("div");
+	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+	// Opera can't find a second classname (in 9.6)
+	if ( div.getElementsByClassName("e").length === 0 )
+		return;
+
+	// Safari caches class attributes, doesn't catch changes (in 3.2)
+	div.lastChild.className = "e";
+
+	if ( div.getElementsByClassName("e").length === 1 )
+		return;
+
+	Expr.order.splice(1, 0, "CLASS");
+	Expr.find.CLASS = function(match, context, isXML) {
+		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+			return context.getElementsByClassName(match[1]);
+		}
+	};
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	var sibDir = dir == "previousSibling" && !isXML;
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+		if ( elem ) {
+			if ( sibDir && elem.nodeType === 1 ){
+				elem.sizcache = doneName;
+				elem.sizset = i;
+			}
+			elem = elem[dir];
+			var match = false;
+
+			while ( elem ) {
+				if ( elem.sizcache === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 && !isXML ){
+					elem.sizcache = doneName;
+					elem.sizset = i;
+				}
+
+				if ( elem.nodeName === cur ) {
+					match = elem;
+					break;
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	var sibDir = dir == "previousSibling" && !isXML;
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+		if ( elem ) {
+			if ( sibDir && elem.nodeType === 1 ) {
+				elem.sizcache = doneName;
+				elem.sizset = i;
+			}
+			elem = elem[dir];
+			var match = false;
+
+			while ( elem ) {
+				if ( elem.sizcache === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 ) {
+					if ( !isXML ) {
+						elem.sizcache = doneName;
+						elem.sizset = i;
+					}
+					if ( typeof cur !== "string" ) {
+						if ( elem === cur ) {
+							match = true;
+							break;
+						}
+
+					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+						match = elem;
+						break;
+					}
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+var contains = document.compareDocumentPosition ?  function(a, b){
+	return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+	return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+	return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+		!!elem.ownerDocument && isXML( elem.ownerDocument );
+};
+
+var posProcess = function(selector, context){
+	var tmpSet = [], later = "", match,
+		root = context.nodeType ? [context] : context;
+
+	// Position selectors must be done after the filter
+	// And so must :not(positional) so we move all PSEUDOs to the end
+	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+		later += match[0];
+		selector = selector.replace( Expr.match.PSEUDO, "" );
+	}
+
+	selector = Expr.relative[selector] ? selector + "*" : selector;
+
+	for ( var i = 0, l = root.length; i < l; i++ ) {
+		Sizzle( selector, root[i], tmpSet );
+	}
+
+	return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.filter = Sizzle.filter;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+
+Sizzle.selectors.filters.hidden = function(elem){
+	return elem.offsetWidth === 0 || elem.offsetHeight === 0;
+};
+
+Sizzle.selectors.filters.visible = function(elem){
+	return elem.offsetWidth > 0 || elem.offsetHeight > 0;
+};
+
+Sizzle.selectors.filters.animated = function(elem){
+	return jQuery.grep(jQuery.timers, function(fn){
+		return elem === fn.elem;
+	}).length;
+};
+
+jQuery.multiFilter = function( expr, elems, not ) {
+	if ( not ) {
+		expr = ":not(" + expr + ")";
+	}
+
+	return Sizzle.matches(expr, elems);
+};
+
+jQuery.dir = function( elem, dir ){
+	var matched = [], cur = elem[dir];
+	while ( cur && cur != document ) {
+		if ( cur.nodeType == 1 )
+			matched.push( cur );
+		cur = cur[dir];
+	}
+	return matched;
+};
+
+jQuery.nth = function(cur, result, dir, elem){
+	result = result || 1;
+	var num = 0;
+
+	for ( ; cur; cur = cur[dir] )
+		if ( cur.nodeType == 1 && ++num == result )
+			break;
+
+	return cur;
+};
+
+jQuery.sibling = function(n, elem){
+	var r = [];
+
+	for ( ; n; n = n.nextSibling ) {
+		if ( n.nodeType == 1 && n != elem )
+			r.push( n );
+	}
+
+	return r;
+};
+
+return;
+
+window.Sizzle = Sizzle;
+
+})();
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+	// Bind an event to an element
+	// Original by Dean Edwards
+	add: function(elem, types, handler, data) {
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+			return;
+
+		// For whatever reason, IE has trouble passing the window object
+		// around, causing it to be cloned in the process
+		if ( elem.setInterval && elem != window )
+			elem = window;
+
+		// Make sure that the function being executed has a unique ID
+		if ( !handler.guid )
+			handler.guid = this.guid++;
+
+		// if data is passed, bind to handler
+		if ( data !== undefined ) {
+			// Create temporary function pointer to original handler
+			var fn = handler;
+
+			// Create unique handler function, wrapped around original handler
+			handler = this.proxy( fn );
+
+			// Store data in unique handler
+			handler.data = data;
+		}
+
+		// Init the element's event structure
+		var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
+			handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
+				// Handle the second event of a trigger and when
+				// an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+					jQuery.event.handle.apply(arguments.callee.elem, arguments) :
+					undefined;
+			});
+		// Add elem as a property of the handle function
+		// This is to prevent a memory leak with non-native
+		// event in IE.
+		handle.elem = elem;
+
+		// Handle multiple events separated by a space
+		// jQuery(...).bind("mouseover mouseout", fn);
+		jQuery.each(types.split(/\s+/), function(index, type) {
+			// Namespaced event handlers
+			var namespaces = type.split(".");
+			type = namespaces.shift();
+			handler.type = namespaces.slice().sort().join(".");
+
+			// Get the current list of functions bound to this event
+			var handlers = events[type];
+			
+			if ( jQuery.event.specialAll[type] )
+				jQuery.event.specialAll[type].setup.call(elem, data, namespaces);
+
+			// Init the event handler queue
+			if (!handlers) {
+				handlers = events[type] = {};
+
+				// Check for a special event handler
+				// Only use addEventListener/attachEvent if the special
+				// events handler returns false
+				if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
+					// Bind the global event handler to the element
+					if (elem.addEventListener)
+						elem.addEventListener(type, handle, false);
+					else if (elem.attachEvent)
+						elem.attachEvent("on" + type, handle);
+				}
+			}
+
+			// Add the function to the element's handler list
+			handlers[handler.guid] = handler;
+
+			// Keep track of which events have been used, for global triggering
+			jQuery.event.global[type] = true;
+		});
+
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	guid: 1,
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function(elem, types, handler) {
+		// don't do events on text and comment nodes
+		if ( elem.nodeType == 3 || elem.nodeType == 8 )
+			return;
+
+		var events = jQuery.data(elem, "events"), ret, index;
+
+		if ( events ) {
+			// Unbind all events for the element
+			if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
+				for ( var type in events )
+					this.remove( elem, type + (types || "") );
+			else {
+				// types is actually an event object here
+				if ( types.type ) {
+					handler = types.handler;
+					types = types.type;
+				}
+
+				// Handle multiple events seperated by a space
+				// jQuery(...).unbind("mouseover mouseout", fn);
+				jQuery.each(types.split(/\s+/), function(index, type){
+					// Namespaced event handlers
+					var namespaces = type.split(".");
+					type = namespaces.shift();
+					var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+
+					if ( events[type] ) {
+						// remove the given handler for the given type
+						if ( handler )
+							delete events[type][handler.guid];
+
+						// remove all handlers for the given type
+						else
+							for ( var handle in events[type] )
+								// Handle the removal of namespaced events
+								if ( namespace.test(events[type][handle].type) )
+									delete events[type][handle];
+									
+						if ( jQuery.event.specialAll[type] )
+							jQuery.event.specialAll[type].teardown.call(elem, namespaces);
+
+						// remove generic event handler if no more handlers exist
+						for ( ret in events[type] ) break;
+						if ( !ret ) {
+							if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
+								if (elem.removeEventListener)
+									elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
+								else if (elem.detachEvent)
+									elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
+							}
+							ret = null;
+							delete events[type];
+						}
+					}
+				});
+			}
+
+			// Remove the expando if it's no longer used
+			for ( ret in events ) break;
+			if ( !ret ) {
+				var handle = jQuery.data( elem, "handle" );
+				if ( handle ) handle.elem = null;
+				jQuery.removeData( elem, "events" );
+				jQuery.removeData( elem, "handle" );
+			}
+		}
+	},
+
+	// bubbling is internal
+	trigger: function( event, data, elem, bubbling ) {
+		// Event object or event type
+		var type = event.type || event;
+
+		if( !bubbling ){
+			event = typeof event === "object" ?
+				// jQuery.Event object
+				event[expando] ? event :
+				// Object literal
+				jQuery.extend( jQuery.Event(type), event ) :
+				// Just the event type (string)
+				jQuery.Event(type);
+
+			if ( type.indexOf("!") >= 0 ) {
+				event.type = type = type.slice(0, -1);
+				event.exclusive = true;
+			}
+
+			// Handle a global trigger
+			if ( !elem ) {
+				// Don't bubble custom events when global (to avoid too much overhead)
+				event.stopPropagation();
+				// Only trigger if we've ever bound an event for it
+				if ( this.global[type] )
+					jQuery.each( jQuery.cache, function(){
+						if ( this.events && this.events[type] )
+							jQuery.event.trigger( event, data, this.handle.elem );
+					});
+			}
+
+			// Handle triggering a single element
+
+			// don't do events on text and comment nodes
+			if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
+				return undefined;
+			
+			// Clean up in case it is reused
+			event.result = undefined;
+			event.target = elem;
+			
+			// Clone the incoming data, if any
+			data = jQuery.makeArray(data);
+			data.unshift( event );
+		}
+
+		event.currentTarget = elem;
+
+		// Trigger the event, it is assumed that "handle" is a function
+		var handle = jQuery.data(elem, "handle");
+		if ( handle )
+			handle.apply( elem, data );
+
+		// Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
+		if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
+			event.result = false;
+
+		// Trigger the native events (except for clicks on links)
+		if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
+			this.triggered = true;
+			try {
+				elem[ type ]();
+			// prevent IE from throwing an error for some hidden elements
+			} catch (e) {}
+		}
+
+		this.triggered = false;
+
+		if ( !event.isPropagationStopped() ) {
+			var parent = elem.parentNode || elem.ownerDocument;
+			if ( parent )
+				jQuery.event.trigger(event, data, parent, true);
+		}
+	},
+
+	handle: function(event) {
+		// returned undefined or false
+		var all, handlers;
+
+		event = arguments[0] = jQuery.event.fix( event || window.event );
+		event.currentTarget = this;
+		
+		// Namespaced event handlers
+		var namespaces = event.type.split(".");
+		event.type = namespaces.shift();
+
+		// Cache this now, all = true means, any handler
+		all = !namespaces.length && !event.exclusive;
+		
+		var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");
+
+		handlers = ( jQuery.data(this, "events") || {} )[event.type];
+
+		for ( var j in handlers ) {
+			var handler = handlers[j];
+
+			// Filter the functions by class
+			if ( all || namespace.test(handler.type) ) {
+				// Pass in a reference to the handler function itself
+				// So that we can later remove it
+				event.handler = handler;
+				event.data = handler.data;
+
+				var ret = handler.apply(this, arguments);
+
+				if( ret !== undefined ){
+					event.result = ret;
+					if ( ret === false ) {
+						event.preventDefault();
+						event.stopPropagation();
+					}
+				}
+
+				if( event.isImmediatePropagationStopped() )
+					break;
+
+			}
+		}
+	},
+
+	props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+	fix: function(event) {
+		if ( event[expando] )
+			return event;
+
+		// store a copy of the original event object
+		// and "clone" to set read-only properties
+		var originalEvent = event;
+		event = jQuery.Event( originalEvent );
+
+		for ( var i = this.props.length, prop; i; ){
+			prop = this.props[ --i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Fix target property, if necessary
+		if ( !event.target )
+			event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either
+
+		// check if target is a textnode (safari)
+		if ( event.target.nodeType == 3 )
+			event.target = event.target.parentNode;
+
+		// Add relatedTarget, if necessary
+		if ( !event.relatedTarget && event.fromElement )
+			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;
+
+		// Calculate pageX/Y if missing and clientX/Y available
+		if ( event.pageX == null && event.clientX != null ) {
+			var doc = document.documentElement, body = document.body;
+			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
+			event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
+		}
+
+		// Add which for key events
+		if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
+			event.which = event.charCode || event.keyCode;
+
+		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+		if ( !event.metaKey && event.ctrlKey )
+			event.metaKey = event.ctrlKey;
+
+		// Add which for click: 1 == left; 2 == middle; 3 == right
+		// Note: button is not normalized, so don't use it
+		if ( !event.which && event.button )
+			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+
+		return event;
+	},
+
+	proxy: function( fn, proxy ){
+		proxy = proxy || function(){ return fn.apply(this, arguments); };
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
+		// So proxy can be declared as an argument
+		return proxy;
+	},
+
+	special: {
+		ready: {
+			// Make sure the ready event is setup
+			setup: bindReady,
+			teardown: function() {}
+		}
+	},
+	
+	specialAll: {
+		live: {
+			setup: function( selector, namespaces ){
+				jQuery.event.add( this, namespaces[0], liveHandler );
+			},
+			teardown:  function( namespaces ){
+				if ( namespaces.length ) {
+					var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
+					
+					jQuery.each( (jQuery.data(this, "events").live || {}), function(){
+						if ( name.test(this.type) )
+							remove++;
+					});
+					
+					if ( remove < 1 )
+						jQuery.event.remove( this, namespaces[0], liveHandler );
+				}
+			}
+		}
+	}
+};
+
+jQuery.Event = function( src ){
+	// Allow instantiation without the 'new' keyword
+	if( !this.preventDefault )
+		return new jQuery.Event(src);
+	
+	// Event object
+	if( src && src.type ){
+		this.originalEvent = src;
+		this.type = src.type;
+	// Event type
+	}else
+		this.type = src;
+
+	// timeStamp is buggy for some events on Firefox(#3843)
+	// So we won't rely on the native value
+	this.timeStamp = now();
+	
+	// Mark it as fixed
+	this[expando] = true;
+};
+
+function returnFalse(){
+	return false;
+}
+function returnTrue(){
+	return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	preventDefault: function() {
+		this.isDefaultPrevented = returnTrue;
+
+		var e = this.originalEvent;
+		if( !e )
+			return;
+		// if preventDefault exists run it on the original event
+		if (e.preventDefault)
+			e.preventDefault();
+		// otherwise set the returnValue property of the original event to false (IE)
+		e.returnValue = false;
+	},
+	stopPropagation: function() {
+		this.isPropagationStopped = returnTrue;
+
+		var e = this.originalEvent;
+		if( !e )
+			return;
+		// if stopPropagation exists run it on the original event
+		if (e.stopPropagation)
+			e.stopPropagation();
+		// otherwise set the cancelBubble property of the original event to true (IE)
+		e.cancelBubble = true;
+	},
+	stopImmediatePropagation:function(){
+		this.isImmediatePropagationStopped = returnTrue;
+		this.stopPropagation();
+	},
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse
+};
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function(event) {
+	// Check if mouse(over|out) are still within the same parent element
+	var parent = event.relatedTarget;
+	// Traverse up the tree
+	while ( parent && parent != this )
+		try { parent = parent.parentNode; }
+		catch(e) { parent = this; }
+	
+	if( parent != this ){
+		// set the correct event type
+		event.type = event.data;
+		// handle event if we actually just moused on to a non sub-element
+		jQuery.event.handle.apply( this, arguments );
+	}
+};
+	
+jQuery.each({ 
+	mouseover: 'mouseenter', 
+	mouseout: 'mouseleave'
+}, function( orig, fix ){
+	jQuery.event.special[ fix ] = {
+		setup: function(){
+			jQuery.event.add( this, orig, withinElement, fix );
+		},
+		teardown: function(){
+			jQuery.event.remove( this, orig, withinElement );
+		}
+	};			   
+});
+
+jQuery.fn.extend({
+	bind: function( type, data, fn ) {
+		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
+			jQuery.event.add( this, type, fn || data, fn && data );
+		});
+	},
+
+	one: function( type, data, fn ) {
+		var one = jQuery.event.proxy( fn || data, function(event) {
+			jQuery(this).unbind(event, one);
+			return (fn || data).apply( this, arguments );
+		});
+		return this.each(function(){
+			jQuery.event.add( this, type, one, fn && data);
+		});
+	},
+
+	unbind: function( type, fn ) {
+		return this.each(function(){
+			jQuery.event.remove( this, type, fn );
+		});
+	},
+
+	trigger: function( type, data ) {
+		return this.each(function(){
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+
+	triggerHandler: function( type, data ) {
+		if( this[0] ){
+			var event = jQuery.Event(type);
+			event.preventDefault();
+			event.stopPropagation();
+			jQuery.event.trigger( event, data, this[0] );
+			return event.result;
+		}		
+	},
+
+	toggle: function( fn ) {
+		// Save reference to arguments for access in closure
+		var args = arguments, i = 1;
+
+		// link all the functions, so any of them can unbind this click handler
+		while( i < args.length )
+			jQuery.event.proxy( fn, args[i++] );
+
+		return this.click( jQuery.event.proxy( fn, function(event) {
+			// Figure out which function to execute
+			this.lastToggle = ( this.lastToggle || 0 ) % i;
+
+			// Make sure that clicks stop
+			event.preventDefault();
+
+			// and execute the function
+			return args[ this.lastToggle++ ].apply( this, arguments ) || false;
+		}));
+	},
+
+	hover: function(fnOver, fnOut) {
+		return this.mouseenter(fnOver).mouseleave(fnOut);
+	},
+
+	ready: function(fn) {
+		// Attach the listeners
+		bindReady();
+
+		// If the DOM is already ready
+		if ( jQuery.isReady )
+			// Execute the function immediately
+			fn.call( document, jQuery );
+
+		// Otherwise, remember the function for later
+		else
+			// Add the function to the wait list
+			jQuery.readyList.push( fn );
+
+		return this;
+	},
+	
+	live: function( type, fn ){
+		var proxy = jQuery.event.proxy( fn );
+		proxy.guid += this.selector + type;
+
+		jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );
+
+		return this;
+	},
+	
+	die: function( type, fn ){
+		jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
+		return this;
+	}
+});
+
+function liveHandler( event ){
+	var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
+		stop = true,
+		elems = [];
+
+	jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
+		if ( check.test(fn.type) ) {
+			var elem = jQuery(event.target).closest(fn.data)[0];
+			if ( elem )
+				elems.push({ elem: elem, fn: fn });
+		}
+	});
+
+	elems.sort(function(a,b) {
+		return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
+	});
+	
+	jQuery.each(elems, function(){
+		if ( this.fn.call(this.elem, event, this.fn.data) === false )
+			return (stop = false);
+	});
+
+	return stop;
+}
+
+function liveConvert(type, selector){
+	return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
+}
+
+jQuery.extend({
+	isReady: false,
+	readyList: [],
+	// Handle when the DOM is ready
+	ready: function() {
+		// Make sure that the DOM is not already loaded
+		if ( !jQuery.isReady ) {
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+
+			// If there are functions bound, to execute
+			if ( jQuery.readyList ) {
+				// Execute all of them
+				jQuery.each( jQuery.readyList, function(){
+					this.call( document, jQuery );
+				});
+
+				// Reset the list of functions
+				jQuery.readyList = null;
+			}
+
+			// Trigger any bound ready events
+			jQuery(document).triggerHandler("ready");
+		}
+	}
+});
+
+var readyBound = false;
+
+function bindReady(){
+	if ( readyBound ) return;
+	readyBound = true;
+
+	// Mozilla, Opera and webkit nightlies currently support this event
+	if ( document.addEventListener ) {
+		// Use the handy event callback
+		document.addEventListener( "DOMContentLoaded", function(){
+			document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
+			jQuery.ready();
+		}, false );
+
+	// If IE event model is used
+	} else if ( document.attachEvent ) {
+		// ensure firing before onload,
+		// maybe late but safe also for iframes
+		document.attachEvent("onreadystatechange", function(){
+			if ( document.readyState === "complete" ) {
+				document.detachEvent( "onreadystatechange", arguments.callee );
+				jQuery.ready();
+			}
+		});
+
+		// If IE and not an iframe
+		// continually check to see if the document is ready
+		if ( document.documentElement.doScroll && window == window.top ) (function(){
+			if ( jQuery.isReady ) return;
+
+			try {
+				// If IE is used, use the trick by Diego Perini
+				// http://javascript.nwbox.com/IEContentLoaded/
+				document.documentElement.doScroll("left");
+			} catch( error ) {
+				setTimeout( arguments.callee, 0 );
+				return;
+			}
+
+			// and execute any waiting functions
+			jQuery.ready();
+		})();
+	}
+
+	// A fallback to window.onload, that will always work
+	jQuery.event.add( window, "load", jQuery.ready );
+}
+
+jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
+	"mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
+	"change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){
+
+	// Handle event binding
+	jQuery.fn[name] = function(fn){
+		return fn ? this.bind(name, fn) : this.trigger(name);
+	};
+});
+
+// Prevent memory leaks in IE
+// And prevent errors on refresh with events like mouseover in other browsers
+// Window isn't included so as not to unbind existing unload events
+jQuery( window ).bind( 'unload', function(){ 
+	for ( var id in jQuery.cache )
+		// Skip the window
+		if ( id != 1 && jQuery.cache[ id ].handle )
+			jQuery.event.remove( jQuery.cache[ id ].handle.elem );
+}); 
+(function(){
+
+	jQuery.support = {};
+
+	var root = document.documentElement,
+		script = document.createElement("script"),
+		div = document.createElement("div"),
+		id = "script" + (new Date).getTime();
+
+	div.style.display = "none";
+	div.innerHTML = '   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';
+
+	var all = div.getElementsByTagName("*"),
+		a = div.getElementsByTagName("a")[0];
+
+	// Can't get basic test support
+	if ( !all || !all.length || !a ) {
+		return;
+	}
+
+	jQuery.support = {
+		// IE strips leading whitespace when .innerHTML is used
+		leadingWhitespace: div.firstChild.nodeType == 3,
+		
+		// Make sure that tbody elements aren't automatically inserted
+		// IE will insert them into empty tables
+		tbody: !div.getElementsByTagName("tbody").length,
+		
+		// Make sure that you can get all elements in an <object> element
+		// IE 7 always returns no results
+		objectAll: !!div.getElementsByTagName("object")[0]
+			.getElementsByTagName("*").length,
+		
+		// Make sure that link elements get serialized correctly by innerHTML
+		// This requires a wrapper element in IE
+		htmlSerialize: !!div.getElementsByTagName("link").length,
+		
+		// Get the style information from getAttribute
+		// (IE uses .cssText insted)
+		style: /red/.test( a.getAttribute("style") ),
+		
+		// Make sure that URLs aren't manipulated
+		// (IE normalizes it by default)
+		hrefNormalized: a.getAttribute("href") === "/a",
+		
+		// Make sure that element opacity exists
+		// (IE uses filter instead)
+		opacity: a.style.opacity === "0.5",
+		
+		// Verify style float existence
+		// (IE uses styleFloat instead of cssFloat)
+		cssFloat: !!a.style.cssFloat,
+
+		// Will be defined later
+		scriptEval: false,
+		noCloneEvent: true,
+		boxModel: null
+	};
+	
+	script.type = "text/javascript";
+	try {
+		script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
+	} catch(e){}
+
+	root.insertBefore( script, root.firstChild );
+	
+	// Make sure that the execution of code works by injecting a script
+	// tag with appendChild/createTextNode
+	// (IE doesn't support this, fails, and uses .text instead)
+	if ( window[ id ] ) {
+		jQuery.support.scriptEval = true;
+		delete window[ id ];
+	}
+
+	root.removeChild( script );
+
+	if ( div.attachEvent && div.fireEvent ) {
+		div.attachEvent("onclick", function(){
+			// Cloning a node shouldn't copy over any
+			// bound event handlers (IE does this)
+			jQuery.support.noCloneEvent = false;
+			div.detachEvent("onclick", arguments.callee);
+		});
+		div.cloneNode(true).fireEvent("onclick");
+	}
+
+	// Figure out if the W3C box model works as expected
+	// document.body must exist before we can do this
+	jQuery(function(){
+		var div = document.createElement("div");
+		div.style.width = div.style.paddingLeft = "1px";
+
+		document.body.appendChild( div );
+		jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
+		document.body.removeChild( div ).style.display = 'none';
+	});
+})();
+
+var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat";
+
+jQuery.props = {
+	"for": "htmlFor",
+	"class": "className",
+	"float": styleFloat,
+	cssFloat: styleFloat,
+	styleFloat: styleFloat,
+	readonly: "readOnly",
+	maxlength: "maxLength",
+	cellspacing: "cellSpacing",
+	rowspan: "rowSpan",
+	tabindex: "tabIndex"
+};
+jQuery.fn.extend({
+	// Keep a copy of the old load
+	_load: jQuery.fn.load,
+
+	load: function( url, params, callback ) {
+		if ( typeof url !== "string" )
+			return this._load( url );
+
+		var off = url.indexOf(" ");
+		if ( off >= 0 ) {
+			var selector = url.slice(off, url.length);
+			url = url.slice(0, off);
+		}
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params )
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = null;
+
+			// Otherwise, build a param string
+			} else if( typeof params === "object" ) {
+				params = jQuery.param( params );
+				type = "POST";
+			}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			complete: function(res, status){
+				// If successful, inject the HTML into all the matched elements
+				if ( status == "success" || status == "notmodified" )
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div/>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						res.responseText );
+
+				if( callback )
+					self.each( callback, [res.responseText, status, res] );
+			}
+		});
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param(this.serializeArray());
+	},
+	serializeArray: function() {
+		return this.map(function(){
+			return this.elements ? jQuery.makeArray(this.elements) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled &&
+				(this.checked || /select|textarea/i.test(this.nodeName) ||
+					/text|hidden|password|search/i.test(this.type));
+		})
+		.map(function(i, elem){
+			var val = jQuery(this).val();
+			return val == null ? null :
+				jQuery.isArray(val) ?
+					jQuery.map( val, function(val, i){
+						return {name: elem.name, value: val};
+					}) :
+					{name: elem.name, value: val};
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
+	jQuery.fn[o] = function(f){
+		return this.bind(o, f);
+	};
+});
+
+var jsc = now();
+
+jQuery.extend({
+  
+	get: function( url, data, callback, type ) {
+		// shift arguments if data argument was ommited
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = null;
+		}
+
+		return jQuery.ajax({
+			type: "GET",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get(url, null, callback, "script");
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get(url, data, callback, "json");
+	},
+
+	post: function( url, data, callback, type ) {
+		if ( jQuery.isFunction( data ) ) {
+			callback = data;
+			data = {};
+		}
+
+		return jQuery.ajax({
+			type: "POST",
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	},
+
+	ajaxSetup: function( settings ) {
+		jQuery.extend( jQuery.ajaxSettings, settings );
+	},
+
+	ajaxSettings: {
+		url: location.href,
+		global: true,
+		type: "GET",
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		/*
+		timeout: 0,
+		data: null,
+		username: null,
+		password: null,
+		*/
+		// Create the request object; Microsoft failed to properly
+		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
+		// This function can be overriden by calling jQuery.ajaxSetup
+		xhr:function(){
+			return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
+		},
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			script: "text/javascript, application/javascript",
+			json: "application/json, text/javascript",
+			text: "text/plain",
+			_default: "*/*"
+		}
+	},
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+
+	ajax: function( s ) {
+		// Extend the settings, but re-extend 's' so that it can be
+		// checked again later (in the test suite, specifically)
+		s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));
+
+		var jsonp, jsre = /=\?(&|$)/g, status, data,
+			type = s.type.toUpperCase();
+
+		// convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" )
+			s.data = jQuery.param(s.data);
+
+		// Handle JSONP Parameter Callbacks
+		if ( s.dataType == "jsonp" ) {
+			if ( type == "GET" ) {
+				if ( !s.url.match(jsre) )
+					s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+			} else if ( !s.data || !s.data.match(jsre) )
+				s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+			s.dataType = "json";
+		}
+
+		// Build temporary JSONP function
+		if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
+			jsonp = "jsonp" + jsc++;
+
+			// Replace the =? sequence both in the query string and the data
+			if ( s.data )
+				s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+			s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+			// We need to make sure
+			// that a JSONP style response is executed properly
+			s.dataType = "script";
+
+			// Handle JSONP-style loading
+			window[ jsonp ] = function(tmp){
+				data = tmp;
+				success();
+				complete();
+				// Garbage collect
+				window[ jsonp ] = undefined;
+				try{ delete window[ jsonp ]; } catch(e){}
+				if ( head )
+					head.removeChild( script );
+			};
+		}
+
+		if ( s.dataType == "script" && s.cache == null )
+			s.cache = false;
+
+		if ( s.cache === false && type == "GET" ) {
+			var ts = now();
+			// try replacing _= if it is there
+			var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
+			// if nothing was replaced, add timestamp to the end
+			s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
+		}
+
+		// If data is available, append data to url for get requests
+		if ( s.data && type == "GET" ) {
+			s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;
+
+			// IE likes to send both get and post data, prevent this
+			s.data = null;
+		}
+
+		// Watch for a new set of requests
+		if ( s.global && ! jQuery.active++ )
+			jQuery.event.trigger( "ajaxStart" );
+
+		// Matches an absolute URL, and saves the domain
+		var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url );
+
+		// If we're requesting a remote document
+		// and trying to load JSON or Script with a GET
+		if ( s.dataType == "script" && type == "GET" && parts
+			&& ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){
+
+			var head = document.getElementsByTagName("head")[0];
+			var script = document.createElement("script");
+			script.src = s.url;
+			if (s.scriptCharset)
+				script.charset = s.scriptCharset;
+
+			// Handle Script loading
+			if ( !jsonp ) {
+				var done = false;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function(){
+					if ( !done && (!this.readyState ||
+							this.readyState == "loaded" || this.readyState == "complete") ) {
+						done = true;
+						success();
+						complete();
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+						head.removeChild( script );
+					}
+				};
+			}
+
+			head.appendChild(script);
+
+			// We handle everything using the script element injection
+			return undefined;
+		}
+
+		var requestDone = false;
+
+		// Create the request object
+		var xhr = s.xhr();
+
+		// Open the socket
+		// Passing null username, generates a login popup on Opera (#2865)
+		if( s.username )
+			xhr.open(type, s.url, s.async, s.username, s.password);
+		else
+			xhr.open(type, s.url, s.async);
+
+		// Need an extra try/catch for cross domain requests in Firefox 3
+		try {
+			// Set the correct header, if data is being sent
+			if ( s.data )
+				xhr.setRequestHeader("Content-Type", s.contentType);
+
+			// Set the If-Modified-Since header, if ifModified mode.
+			if ( s.ifModified )
+				xhr.setRequestHeader("If-Modified-Since",
+					jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );
+
+			// Set header so the called script knows that it's an XMLHttpRequest
+			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+			// Set the Accepts header for the server, depending on the dataType
+			xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
+				s.accepts[ s.dataType ] + ", */*" :
+				s.accepts._default );
+		} catch(e){}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
+			// Handle the global AJAX counter
+			if ( s.global && ! --jQuery.active )
+				jQuery.event.trigger( "ajaxStop" );
+			// close opended socket
+			xhr.abort();
+			return false;
+		}
+
+		if ( s.global )
+			jQuery.event.trigger("ajaxSend", [xhr, s]);
+
+		// Wait for a response to come back
+		var onreadystatechange = function(isTimeout){
+			// The request was aborted, clear the interval and decrement jQuery.active
+			if (xhr.readyState == 0) {
+				if (ival) {
+					// clear poll interval
+					clearInterval(ival);
+					ival = null;
+					// Handle the global AJAX counter
+					if ( s.global && ! --jQuery.active )
+						jQuery.event.trigger( "ajaxStop" );
+				}
+			// The transfer is complete and the data is available, or the request timed out
+			} else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
+				requestDone = true;
+
+				// clear poll interval
+				if (ival) {
+					clearInterval(ival);
+					ival = null;
+				}
+
+				status = isTimeout == "timeout" ? "timeout" :
+					!jQuery.httpSuccess( xhr ) ? "error" :
+					s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" :
+					"success";
+
+				if ( status == "success" ) {
+					// Watch for, and catch, XML document parse errors
+					try {
+						// process the data (runs the xml through httpData regardless of callback)
+						data = jQuery.httpData( xhr, s.dataType, s );
+					} catch(e) {
+						status = "parsererror";
+					}
+				}
+
+				// Make sure that the request was successful or notmodified
+				if ( status == "success" ) {
+					// Cache Last-Modified header, if ifModified mode.
+					var modRes;
+					try {
+						modRes = xhr.getResponseHeader("Last-Modified");
+					} catch(e) {} // swallow exception thrown by FF if header is not available
+
+					if ( s.ifModified && modRes )
+						jQuery.lastModified[s.url] = modRes;
+
+					// JSONP handles its own success callback
+					if ( !jsonp )
+						success();
+				} else
+					jQuery.handleError(s, xhr, status);
+
+				// Fire the complete handlers
+				complete();
+
+				if ( isTimeout )
+					xhr.abort();
+
+				// Stop memory leaks
+				if ( s.async )
+					xhr = null;
+			}
+		};
+
+		if ( s.async ) {
+			// don't attach the handler to the request, just poll it instead
+			var ival = setInterval(onreadystatechange, 13);
+
+			// Timeout checker
+			if ( s.timeout > 0 )
+				setTimeout(function(){
+					// Check to see if the request is still happening
+					if ( xhr && !requestDone )
+						onreadystatechange( "timeout" );
+				}, s.timeout);
+		}
+
+		// Send the data
+		try {
+			xhr.send(s.data);
+		} catch(e) {
+			jQuery.handleError(s, xhr, null, e);
+		}
+
+		// firefox 1.5 doesn't fire statechange for sync requests
+		if ( !s.async )
+			onreadystatechange();
+
+		function success(){
+			// If a local callback was specified, fire it and pass it the data
+			if ( s.success )
+				s.success( data, status );
+
+			// Fire the global callback
+			if ( s.global )
+				jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
+		}
+
+		function complete(){
+			// Process result
+			if ( s.complete )
+				s.complete(xhr, status);
+
+			// The request was completed
+			if ( s.global )
+				jQuery.event.trigger( "ajaxComplete", [xhr, s] );
+
+			// Handle the global AJAX counter
+			if ( s.global && ! --jQuery.active )
+				jQuery.event.trigger( "ajaxStop" );
+		}
+
+		// return XMLHttpRequest to allow aborting the request etc.
+		return xhr;
+	},
+
+	handleError: function( s, xhr, status, e ) {
+		// If a local callback was specified, fire it
+		if ( s.error ) s.error( xhr, status, e );
+
+		// Fire the global callback
+		if ( s.global )
+			jQuery.event.trigger( "ajaxError", [xhr, s, e] );
+	},
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Determines if an XMLHttpRequest was successful or not
+	httpSuccess: function( xhr ) {
+		try {
+			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
+			return !xhr.status && location.protocol == "file:" ||
+				( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
+		} catch(e){}
+		return false;
+	},
+
+	// Determines if an XMLHttpRequest returns NotModified
+	httpNotModified: function( xhr, url ) {
+		try {
+			var xhrRes = xhr.getResponseHeader("Last-Modified");
+
+			// Firefox always returns 200. check Last-Modified date
+			return xhr.status == 304 || xhrRes == jQuery.lastModified[url];
+		} catch(e){}
+		return false;
+	},
+
+	httpData: function( xhr, type, s ) {
+		var ct = xhr.getResponseHeader("content-type"),
+			xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
+			data = xml ? xhr.responseXML : xhr.responseText;
+
+		if ( xml && data.documentElement.tagName == "parsererror" )
+			throw "parsererror";
+			
+		// Allow a pre-filtering function to sanitize the response
+		// s != null is checked to keep backwards compatibility
+		if( s && s.dataFilter )
+			data = s.dataFilter( data, type );
+
+		// The filter can actually parse the response
+		if( typeof data === "string" ){
+
+			// If the type is "script", eval it in global context
+			if ( type == "script" )
+				jQuery.globalEval( data );
+
+			// Get the JavaScript object, if JSON is used.
+			if ( type == "json" )
+				data = window["eval"]("(" + data + ")");
+		}
+		
+		return data;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a ) {
+		var s = [ ];
+
+		function add( key, value ){
+			s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
+		};
+
+		// If an array was passed in, assume that it is an array
+		// of form elements
+		if ( jQuery.isArray(a) || a.jquery )
+			// Serialize the form elements
+			jQuery.each( a, function(){
+				add( this.name, this.value );
+			});
+
+		// Otherwise, assume that it's an object of key/value pairs
+		else
+			// Serialize the key/values
+			for ( var j in a )
+				// If the value is an array then the key names need to be repeated
+				if ( jQuery.isArray(a[j]) )
+					jQuery.each( a[j], function(){
+						add( j, this );
+					});
+				else
+					add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );
+
+		// Return the resulting serialization
+		return s.join("&").replace(/%20/g, "+");
+	}
+
+});
+var elemdisplay = {},
+	timerId,
+	fxAttrs = [
+		// height animations
+		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+		// width animations
+		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+		// opacity animations
+		[ "opacity" ]
+	];
+
+function genFx( type, num ){
+	var obj = {};
+	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
+		obj[ this ] = type;
+	});
+	return obj;
+}
+
+jQuery.fn.extend({
+	show: function(speed,callback){
+		if ( speed ) {
+			return this.animate( genFx("show", 3), speed, callback);
+		} else {
+			for ( var i = 0, l = this.length; i < l; i++ ){
+				var old = jQuery.data(this[i], "olddisplay");
+				
+				this[i].style.display = old || "";
+				
+				if ( jQuery.css(this[i], "display") === "none" ) {
+					var tagName = this[i].tagName, display;
+					
+					if ( elemdisplay[ tagName ] ) {
+						display = elemdisplay[ tagName ];
+					} else {
+						var elem = jQuery("<" + tagName + " />").appendTo("body");
+						
+						display = elem.css("display");
+						if ( display === "none" )
+							display = "block";
+						
+						elem.remove();
+						
+						elemdisplay[ tagName ] = display;
+					}
+					
+					jQuery.data(this[i], "olddisplay", display);
+				}
+			}
+
+			// Set the display of the elements in a second loop
+			// to avoid the constant reflow
+			for ( var i = 0, l = this.length; i < l; i++ ){
+				this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
+			}
+			
+			return this;
+		}
+	},
+
+	hide: function(speed,callback){
+		if ( speed ) {
+			return this.animate( genFx("hide", 3), speed, callback);
+		} else {
+			for ( var i = 0, l = this.length; i < l; i++ ){
+				var old = jQuery.data(this[i], "olddisplay");
+				if ( !old && old !== "none" )
+					jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
+			}
+
+			// Set the display of the elements in a second loop
+			// to avoid the constant reflow
+			for ( var i = 0, l = this.length; i < l; i++ ){
+				this[i].style.display = "none";
+			}
+
+			return this;
+		}
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+
+	toggle: function( fn, fn2 ){
+		var bool = typeof fn === "boolean";
+
+		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
+			this._toggle.apply( this, arguments ) :
+			fn == null || bool ?
+				this.each(function(){
+					var state = bool ? fn : jQuery(this).is(":hidden");
+					jQuery(this)[ state ? "show" : "hide" ]();
+				}) :
+				this.animate(genFx("toggle", 3), fn, fn2);
+	},
+
+	fadeTo: function(speed,to,callback){
+		return this.animate({opacity: to}, speed, callback);
+	},
+
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed(speed, easing, callback);
+
+		return this[ optall.queue === false ? "each" : "queue" ](function(){
+		
+			var opt = jQuery.extend({}, optall), p,
+				hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
+				self = this;
+	
+			for ( p in prop ) {
+				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
+					return opt.complete.call(this);
+
+				if ( ( p == "height" || p == "width" ) && this.style ) {
+					// Store display property
+					opt.display = jQuery.css(this, "display");
+
+					// Make sure that nothing sneaks out
+					opt.overflow = this.style.overflow;
+				}
+			}
+
+			if ( opt.overflow != null )
+				this.style.overflow = "hidden";
+
+			opt.curAnim = jQuery.extend({}, prop);
+
+			jQuery.each( prop, function(name, val){
+				var e = new jQuery.fx( self, opt, name );
+
+				if ( /toggle|show|hide/.test(val) )
+					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+				else {
+					var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
+						start = e.cur(true) || 0;
+
+					if ( parts ) {
+						var end = parseFloat(parts[2]),
+							unit = parts[3] || "px";
+
+						// We need to compute starting value
+						if ( unit != "px" ) {
+							self.style[ name ] = (end || 1) + unit;
+							start = ((end || 1) / e.cur(true)) * start;
+							self.style[ name ] = start + unit;
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] )
+							end = ((parts[1] == "-=" ? -1 : 1) * end) + start;
+
+						e.custom( start, end, unit );
+					} else
+						e.custom( start, val, "" );
+				}
+			});
+
+			// For JS strict compliance
+			return true;
+		});
+	},
+
+	stop: function(clearQueue, gotoEnd){
+		var timers = jQuery.timers;
+
+		if (clearQueue)
+			this.queue([]);
+
+		this.each(function(){
+			// go in reverse order so anything added to the queue during the loop is ignored
+			for ( var i = timers.length - 1; i >= 0; i-- )
+				if ( timers[i].elem == this ) {
+					if (gotoEnd)
+						// force the next step to be the last
+						timers[i](true);
+					timers.splice(i, 1);
+				}
+		});
+
+		// start the next in the queue if the last step wasn't forced
+		if (!gotoEnd)
+			this.dequeue();
+
+		return this;
+	}
+
+});
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx("show", 1),
+	slideUp: genFx("hide", 1),
+	slideToggle: genFx("toggle", 1),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" }
+}, function( name, props ){
+	jQuery.fn[ name ] = function( speed, callback ){
+		return this.animate( props, speed, callback );
+	};
+});
+
+jQuery.extend({
+
+	speed: function(speed, easing, fn) {
+		var opt = typeof speed === "object" ? speed : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+		};
+
+		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+			jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;
+
+		// Queueing
+		opt.old = opt.complete;
+		opt.complete = function(){
+			if ( opt.queue !== false )
+				jQuery(this).dequeue();
+			if ( jQuery.isFunction( opt.old ) )
+				opt.old.call( this );
+		};
+
+		return opt;
+	},
+
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+		}
+	},
+
+	timers: [],
+
+	fx: function( elem, options, prop ){
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		if ( !options.orig )
+			options.orig = {};
+	}
+
+});
+
+jQuery.fx.prototype = {
+
+	// Simple function for setting a style value
+	update: function(){
+		if ( this.options.step )
+			this.options.step.call( this.elem, this.now, this );
+
+		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+
+		// Set display property to block for height/width animations
+		if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style )
+			this.elem.style.display = "block";
+	},
+
+	// Get the current size
+	cur: function(force){
+		if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) )
+			return this.elem[ this.prop ];
+
+		var r = parseFloat(jQuery.css(this.elem, this.prop, force));
+		return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
+	},
+
+	// Start an animation from one number to another
+	custom: function(from, to, unit){
+		this.startTime = now();
+		this.start = from;
+		this.end = to;
+		this.unit = unit || this.unit || "px";
+		this.now = this.start;
+		this.pos = this.state = 0;
+
+		var self = this;
+		function t(gotoEnd){
+			return self.step(gotoEnd);
+		}
+
+		t.elem = this.elem;
+
+		if ( t() && jQuery.timers.push(t) && !timerId ) {
+			timerId = setInterval(function(){
+				var timers = jQuery.timers;
+
+				for ( var i = 0; i < timers.length; i++ )
+					if ( !timers[i]() )
+						timers.splice(i--, 1);
+
+				if ( !timers.length ) {
+					clearInterval( timerId );
+					timerId = undefined;
+				}
+			}, 13);
+		}
+	},
+
+	// Simple 'show' function
+	show: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		// Make sure that we start at a small width/height to avoid any
+		// flash of content
+		this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());
+
+		// Start by showing the element
+		jQuery(this.elem).show();
+	},
+
+	// Simple 'hide' function
+	hide: function(){
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom(this.cur(), 0);
+	},
+
+	// Each step of an animation
+	step: function(gotoEnd){
+		var t = now();
+
+		if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			this.options.curAnim[ this.prop ] = true;
+
+			var done = true;
+			for ( var i in this.options.curAnim )
+				if ( this.options.curAnim[i] !== true )
+					done = false;
+
+			if ( done ) {
+				if ( this.options.display != null ) {
+					// Reset the overflow
+					this.elem.style.overflow = this.options.overflow;
+
+					// Reset the display
+					this.elem.style.display = this.options.display;
+					if ( jQuery.css(this.elem, "display") == "none" )
+						this.elem.style.display = "block";
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( this.options.hide )
+					jQuery(this.elem).hide();
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( this.options.hide || this.options.show )
+					for ( var p in this.options.curAnim )
+						jQuery.attr(this.elem.style, p, this.options.orig[p]);
+					
+				// Execute the complete function
+				this.options.complete.call( this.elem );
+			}
+
+			return false;
+		} else {
+			var n = t - this.startTime;
+			this.state = n / this.options.duration;
+
+			// Perform the easing function, defaults to swing
+			this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
+			this.now = this.start + ((this.end - this.start) * this.pos);
+
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+
+};
+
+jQuery.extend( jQuery.fx, {
+	speeds:{
+		slow: 600,
+ 		fast: 200,
+ 		// Default speed
+ 		_default: 400
+	},
+	step: {
+
+		opacity: function(fx){
+			jQuery.attr(fx.elem.style, "opacity", fx.now);
+		},
+
+		_default: function(fx){
+			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
+				fx.elem.style[ fx.prop ] = fx.now + fx.unit;
+			else
+				fx.elem[ fx.prop ] = fx.now;
+		}
+	}
+});
+if ( document.documentElement["getBoundingClientRect"] )
+	jQuery.fn.offset = function() {
+		if ( !this[0] ) return { top: 0, left: 0 };
+		if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
+		var box  = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement,
+			clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
+			top  = box.top  + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop  || body.scrollTop ) - clientTop,
+			left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
+		return { top: top, left: left };
+	};
+else 
+	jQuery.fn.offset = function() {
+		if ( !this[0] ) return { top: 0, left: 0 };
+		if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
+		jQuery.offset.initialized || jQuery.offset.initialize();
+
+		var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem,
+			doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
+			body = doc.body, defaultView = doc.defaultView,
+			prevComputedStyle = defaultView.getComputedStyle(elem, null),
+			top = elem.offsetTop, left = elem.offsetLeft;
+
+		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+			computedStyle = defaultView.getComputedStyle(elem, null);
+			top -= elem.scrollTop, left -= elem.scrollLeft;
+			if ( elem === offsetParent ) {
+				top += elem.offsetTop, left += elem.offsetLeft;
+				if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) )
+					top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
+					left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
+				prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
+			}
+			if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" )
+				top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
+				left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
+			prevComputedStyle = computedStyle;
+		}
+
+		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" )
+			top  += body.offsetTop,
+			left += body.offsetLeft;
+
+		if ( prevComputedStyle.position === "fixed" )
+			top  += Math.max(docElem.scrollTop, body.scrollTop),
+			left += Math.max(docElem.scrollLeft, body.scrollLeft);
+
+		return { top: top, left: left };
+	};
+
+jQuery.offset = {
+	initialize: function() {
+		if ( this.initialized ) return;
+		var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
+			html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';
+
+		rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
+		for ( prop in rules ) container.style[prop] = rules[prop];
+
+		container.innerHTML = html;
+		body.insertBefore(container, body.firstChild);
+		innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild;
+
+		this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+		this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+		innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
+		this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+		body.style.marginTop = '1px';
+		this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
+		body.style.marginTop = bodyMarginTop;
+
+		body.removeChild(container);
+		this.initialized = true;
+	},
+
+	bodyOffset: function(body) {
+		jQuery.offset.initialized || jQuery.offset.initialize();
+		var top = body.offsetTop, left = body.offsetLeft;
+		if ( jQuery.offset.doesNotIncludeMarginInBodyOffset )
+			top  += parseInt( jQuery.curCSS(body, 'marginTop',  true), 10 ) || 0,
+			left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0;
+		return { top: top, left: left };
+	}
+};
+
+
+jQuery.fn.extend({
+	position: function() {
+		var left = 0, top = 0, results;
+
+		if ( this[0] ) {
+			// Get *real* offsetParent
+			var offsetParent = this.offsetParent(),
+
+			// Get correct offsets
+			offset       = this.offset(),
+			parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+			// Subtract element margins
+			// note: when an element has margin: auto the offsetLeft and marginLeft 
+			// are the same in Safari causing offset.left to incorrectly be 0
+			offset.top  -= num( this, 'marginTop'  );
+			offset.left -= num( this, 'marginLeft' );
+
+			// Add offsetParent borders
+			parentOffset.top  += num( offsetParent, 'borderTopWidth'  );
+			parentOffset.left += num( offsetParent, 'borderLeftWidth' );
+
+			// Subtract the two offsets
+			results = {
+				top:  offset.top  - parentOffset.top,
+				left: offset.left - parentOffset.left
+			};
+		}
+
+		return results;
+	},
+
+	offsetParent: function() {
+		var offsetParent = this[0].offsetParent || document.body;
+		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
+			offsetParent = offsetParent.offsetParent;
+		return jQuery(offsetParent);
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ['Left', 'Top'], function(i, name) {
+	var method = 'scroll' + name;
+	
+	jQuery.fn[ method ] = function(val) {
+		if (!this[0]) return null;
+
+		return val !== undefined ?
+
+			// Set the scroll offset
+			this.each(function() {
+				this == window || this == document ?
+					window.scrollTo(
+						!i ? val : jQuery(window).scrollLeft(),
+						 i ? val : jQuery(window).scrollTop()
+					) :
+					this[ method ] = val;
+			}) :
+
+			// Return the scroll offset
+			this[0] == window || this[0] == document ?
+				self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
+					jQuery.boxModel && document.documentElement[ method ] ||
+					document.body[ method ] :
+				this[0][ method ];
+	};
+});
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function(i, name){
+
+	var tl = i ? "Left"  : "Top",  // top or left
+		br = i ? "Right" : "Bottom", // bottom or right
+		lower = name.toLowerCase();
+
+	// innerHeight and innerWidth
+	jQuery.fn["inner" + name] = function(){
+		return this[0] ?
+			jQuery.css( this[0], lower, false, "padding" ) :
+			null;
+	};
+
+	// outerHeight and outerWidth
+	jQuery.fn["outer" + name] = function(margin) {
+		return this[0] ?
+			jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
+			null;
+	};
+	
+	var type = name.toLowerCase();
+
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		return this[0] == window ?
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] ||
+			document.body[ "client" + name ] :
+
+			// Get document width or height
+			this[0] == document ?
+				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+				Math.max(
+					document.documentElement["client" + name],
+					document.body["scroll" + name], document.documentElement["scroll" + name],
+					document.body["offset" + name], document.documentElement["offset" + name]
+				) :
+
+				// Get or set width or height on the element
+				size === undefined ?
+					// Get width or height on the element
+					(this.length ? jQuery.css( this[0], type ) : null) :
+
+					// Set the width or height on the element (default to pixels if value is unitless)
+					this.css( type, typeof size === "string" ? size : size + "px" );
+	};
+
+});
+})();
diff --git a/media/js/jwysiwyg/lib/jquery1.5.js b/media/js/jwysiwyg/lib/jquery1.5.js
new file mode 100644
index 0000000..5ba8c43
--- /dev/null
+++ b/media/js/jwysiwyg/lib/jquery1.5.js
@@ -0,0 +1,8176 @@
+/*!
+ * jQuery JavaScript Library v1.5
+ * http://jquery.com/
+ *
+ * Copyright 2011, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2011, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Mon Jan 31 08:31:29 2011 -0500
+ */
+(function( window, undefined ) {
+
+// Use the correct document accordingly with window argument (sandbox)
+var document = window.document;
+var jQuery = (function() {
+
+// Define a local copy of jQuery
+var jQuery = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		return new jQuery.fn.init( selector, context, rootjQuery );
+	},
+
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$,
+
+	// A central reference to the root jQuery(document)
+	rootjQuery,
+
+	// A simple way to check for HTML strings or ID strings
+	// (both of which we optimize for)
+	quickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,
+
+	// Check if a string has a non-whitespace character in it
+	rnotwhite = /\S/,
+
+	// Used for trimming whitespace
+	trimLeft = /^\s+/,
+	trimRight = /\s+$/,
+
+	// Check for digits
+	rdigit = /\d/,
+
+	// Match a standalone tag
+	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
+
+	// JSON RegExp
+	rvalidchars = /^[\],:{}\s]*$/,
+	rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+	rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+
+	// Useragent RegExp
+	rwebkit = /(webkit)[ \/]([\w.]+)/,
+	ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
+	rmsie = /(msie) ([\w.]+)/,
+	rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
+
+	// Keep a UserAgent string for use with jQuery.browser
+	userAgent = navigator.userAgent,
+
+	// For matching the engine and version of the browser
+	browserMatch,
+
+	// Has the ready events already been bound?
+	readyBound = false,
+
+	// The deferred used on DOM ready
+	readyList,
+
+	// Promise methods
+	promiseMethods = "then done fail isResolved isRejected promise".split( " " ),
+
+	// The ready event handler
+	DOMContentLoaded,
+
+	// Save a reference to some core methods
+	toString = Object.prototype.toString,
+	hasOwn = Object.prototype.hasOwnProperty,
+	push = Array.prototype.push,
+	slice = Array.prototype.slice,
+	trim = String.prototype.trim,
+	indexOf = Array.prototype.indexOf,
+
+	// [[Class]] -> type pairs
+	class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+	constructor: jQuery,
+	init: function( selector, context, rootjQuery ) {
+		var match, elem, ret, doc;
+
+		// Handle $(""), $(null), or $(undefined)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Handle $(DOMElement)
+		if ( selector.nodeType ) {
+			this.context = this[0] = selector;
+			this.length = 1;
+			return this;
+		}
+
+		// The body element only exists once, optimize finding it
+		if ( selector === "body" && !context && document.body ) {
+			this.context = document;
+			this[0] = document.body;
+			this.selector = "body";
+			this.length = 1;
+			return this;
+		}
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			// Are we dealing with HTML string or an ID?
+			match = quickExpr.exec( selector );
+
+			// Verify a match, and that no context was specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] ) {
+					context = context instanceof jQuery ? context[0] : context;
+					doc = (context ? context.ownerDocument || context : document);
+
+					// If a single string is passed in and it's a single tag
+					// just do a createElement and skip the rest
+					ret = rsingleTag.exec( selector );
+
+					if ( ret ) {
+						if ( jQuery.isPlainObject( context ) ) {
+							selector = [ document.createElement( ret[1] ) ];
+							jQuery.fn.attr.call( selector, context, true );
+
+						} else {
+							selector = [ doc.createElement( ret[1] ) ];
+						}
+
+					} else {
+						ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
+						selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes;
+					}
+
+					return jQuery.merge( this, selector );
+
+				// HANDLE: $("#id")
+				} else {
+					elem = document.getElementById( match[2] );
+
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document #6963
+					if ( elem && elem.parentNode ) {
+						// Handle the case where IE and Opera return items
+						// by name instead of ID
+						if ( elem.id !== match[2] ) {
+							return rootjQuery.find( selector );
+						}
+
+						// Otherwise, we inject the element directly into the jQuery object
+						this.length = 1;
+						this[0] = elem;
+					}
+
+					this.context = document;
+					this.selector = selector;
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return (context || rootjQuery).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) ) {
+			return rootjQuery.ready( selector );
+		}
+
+		if (selector.selector !== undefined) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	},
+
+	// Start with an empty selector
+	selector: "",
+
+	// The current version of jQuery being used
+	jquery: "1.5",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	// The number of elements contained in the matched element set
+	size: function() {
+		return this.length;
+	},
+
+	toArray: function() {
+		return slice.call( this, 0 );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num == null ?
+
+			// Return a 'clean' array
+			this.toArray() :
+
+			// Return just the object
+			( num < 0 ? this[ this.length + num ] : this[ num ] );
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems, name, selector ) {
+		// Build a new jQuery matched element set
+		var ret = this.constructor();
+
+		if ( jQuery.isArray( elems ) ) {
+			push.apply( ret, elems );
+
+		} else {
+			jQuery.merge( ret, elems );
+		}
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+
+		ret.context = this.context;
+
+		if ( name === "find" ) {
+			ret.selector = this.selector + (this.selector ? " " : "") + selector;
+		} else if ( name ) {
+			ret.selector = this.selector + "." + name + "(" + selector + ")";
+		}
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	ready: function( fn ) {
+		// Attach the listeners
+		jQuery.bindReady();
+
+		// Add the callback
+		readyList.done( fn );
+
+		return this;
+	},
+
+	eq: function( i ) {
+		return i === -1 ?
+			this.slice( i ) :
+			this.slice( i, +i + 1 );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	slice: function() {
+		return this.pushStack( slice.apply( this, arguments ),
+			"slice", slice.call(arguments).join(",") );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor(null);
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: push,
+	sort: [].sort,
+	splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+	 var options, name, src, copy, copyIsArray, clone,
+		target = arguments[0] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+		target = arguments[1] || {};
+		// skip the boolean and the target
+		i = 2;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+		target = {};
+	}
+
+	// extend jQuery itself if only one argument is passed
+	if ( length === i ) {
+		target = this;
+		--i;
+	}
+
+	for ( ; i < length; i++ ) {
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null ) {
+			// Extend the base object
+			for ( name in options ) {
+				src = target[ name ];
+				copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+					if ( copyIsArray ) {
+						copyIsArray = false;
+						clone = src && jQuery.isArray(src) ? src : [];
+
+					} else {
+						clone = src && jQuery.isPlainObject(src) ? src : {};
+					}
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend({
+	noConflict: function( deep ) {
+		window.$ = _$;
+
+		if ( deep ) {
+			window.jQuery = _jQuery;
+		}
+
+		return jQuery;
+	},
+
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+		// A third-party is pushing the ready event forwards
+		if ( wait === true ) {
+			jQuery.readyWait--;
+		}
+
+		// Make sure that the DOM is not already loaded
+		if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) {
+			// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+			if ( !document.body ) {
+				return setTimeout( jQuery.ready, 1 );
+			}
+
+			// Remember that the DOM is ready
+			jQuery.isReady = true;
+
+			// If a normal DOM Ready event fired, decrement, and wait if need be
+			if ( wait !== true && --jQuery.readyWait > 0 ) {
+				return;
+			}
+
+			// If there are functions bound, to execute
+			readyList.resolveWith( document, [ jQuery ] );
+
+			// Trigger any bound ready events
+			if ( jQuery.fn.trigger ) {
+				jQuery( document ).trigger( "ready" ).unbind( "ready" );
+			}
+		}
+	},
+
+	bindReady: function() {
+		if ( readyBound ) {
+			return;
+		}
+
+		readyBound = true;
+
+		// Catch cases where $(document).ready() is called after the
+		// browser event has already occurred.
+		if ( document.readyState === "complete" ) {
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			return setTimeout( jQuery.ready, 1 );
+		}
+
+		// Mozilla, Opera and webkit nightlies currently support this event
+		if ( document.addEventListener ) {
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", jQuery.ready, false );
+
+		// If IE event model is used
+		} else if ( document.attachEvent ) {
+			// ensure firing before onload,
+			// maybe late but safe also for iframes
+			document.attachEvent("onreadystatechange", DOMContentLoaded);
+
+			// A fallback to window.onload, that will always work
+			window.attachEvent( "onload", jQuery.ready );
+
+			// If IE and not a frame
+			// continually check to see if the document is ready
+			var toplevel = false;
+
+			try {
+				toplevel = window.frameElement == null;
+			} catch(e) {}
+
+			if ( document.documentElement.doScroll && toplevel ) {
+				doScrollCheck();
+			}
+		}
+	},
+
+	// See test/unit/core.js for details concerning isFunction.
+	// Since version 1.3, DOM methods and functions like alert
+	// aren't supported. They return false on IE (#2968).
+	isFunction: function( obj ) {
+		return jQuery.type(obj) === "function";
+	},
+
+	isArray: Array.isArray || function( obj ) {
+		return jQuery.type(obj) === "array";
+	},
+
+	// A crude way of determining if an object is a window
+	isWindow: function( obj ) {
+		return obj && typeof obj === "object" && "setInterval" in obj;
+	},
+
+	isNaN: function( obj ) {
+		return obj == null || !rdigit.test( obj ) || isNaN( obj );
+	},
+
+	type: function( obj ) {
+		return obj == null ?
+			String( obj ) :
+			class2type[ toString.call(obj) ] || "object";
+	},
+
+	isPlainObject: function( obj ) {
+		// Must be an Object.
+		// Because of IE, we also have to check the presence of the constructor property.
+		// Make sure that DOM nodes and window objects don't pass through, as well
+		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		// Not own constructor property must be Object
+		if ( obj.constructor &&
+			!hasOwn.call(obj, "constructor") &&
+			!hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+			return false;
+		}
+
+		// Own properties are enumerated firstly, so to speed up,
+		// if last one is own, then all properties are own.
+
+		var key;
+		for ( key in obj ) {}
+
+		return key === undefined || hasOwn.call( obj, key );
+	},
+
+	isEmptyObject: function( obj ) {
+		for ( var name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	error: function( msg ) {
+		throw msg;
+	},
+
+	parseJSON: function( data ) {
+		if ( typeof data !== "string" || !data ) {
+			return null;
+		}
+
+		// Make sure leading/trailing whitespace is removed (IE can't handle it)
+		data = jQuery.trim( data );
+
+		// Make sure the incoming data is actual JSON
+		// Logic borrowed from http://json.org/json2.js
+		if ( rvalidchars.test(data.replace(rvalidescape, "@")
+			.replace(rvalidtokens, "]")
+			.replace(rvalidbraces, "")) ) {
+
+			// Try to use the native JSON parser first
+			return window.JSON && window.JSON.parse ?
+				window.JSON.parse( data ) :
+				(new Function("return " + data))();
+
+		} else {
+			jQuery.error( "Invalid JSON: " + data );
+		}
+	},
+
+	// Cross-browser xml parsing
+	// (xml & tmp used internally)
+	parseXML: function( data , xml , tmp ) {
+
+		if ( window.DOMParser ) { // Standard
+			tmp = new DOMParser();
+			xml = tmp.parseFromString( data , "text/xml" );
+		} else { // IE
+			xml = new ActiveXObject( "Microsoft.XMLDOM" );
+			xml.async = "false";
+			xml.loadXML( data );
+		}
+
+		tmp = xml.documentElement;
+
+		if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) {
+			jQuery.error( "Invalid XML: " + data );
+		}
+
+		return xml;
+	},
+
+	noop: function() {},
+
+	// Evalulates a script in a global context
+	globalEval: function( data ) {
+		if ( data && rnotwhite.test(data) ) {
+			// Inspired by code by Andrea Giammarchi
+			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
+			var head = document.getElementsByTagName("head")[0] || document.documentElement,
+				script = document.createElement("script");
+
+			script.type = "text/javascript";
+
+			if ( jQuery.support.scriptEval() ) {
+				script.appendChild( document.createTextNode( data ) );
+			} else {
+				script.text = data;
+			}
+
+			// Use insertBefore instead of appendChild to circumvent an IE6 bug.
+			// This arises when a base node is used (#2709).
+			head.insertBefore( script, head.firstChild );
+			head.removeChild( script );
+		}
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
+	},
+
+	// args is for internal usage only
+	each: function( object, callback, args ) {
+		var name, i = 0,
+			length = object.length,
+			isObj = length === undefined || jQuery.isFunction(object);
+
+		if ( args ) {
+			if ( isObj ) {
+				for ( name in object ) {
+					if ( callback.apply( object[ name ], args ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( ; i < length; ) {
+					if ( callback.apply( object[ i++ ], args ) === false ) {
+						break;
+					}
+				}
+			}
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( isObj ) {
+				for ( name in object ) {
+					if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( var value = object[0];
+					i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {}
+			}
+		}
+
+		return object;
+	},
+
+	// Use native String.trim function wherever possible
+	trim: trim ?
+		function( text ) {
+			return text == null ?
+				"" :
+				trim.call( text );
+		} :
+
+		// Otherwise use our own trimming functionality
+		function( text ) {
+			return text == null ?
+				"" :
+				text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
+		},
+
+	// results is for internal usage only
+	makeArray: function( array, results ) {
+		var ret = results || [];
+
+		if ( array != null ) {
+			// The window, strings (and functions) also have 'length'
+			// The extra typeof function check is to prevent crashes
+			// in Safari 2 (See: #3039)
+			// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+			var type = jQuery.type(array);
+
+			if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
+				push.call( ret, array );
+			} else {
+				jQuery.merge( ret, array );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, array ) {
+		if ( array.indexOf ) {
+			return array.indexOf( elem );
+		}
+
+		for ( var i = 0, length = array.length; i < length; i++ ) {
+			if ( array[ i ] === elem ) {
+				return i;
+			}
+		}
+
+		return -1;
+	},
+
+	merge: function( first, second ) {
+		var i = first.length,
+			j = 0;
+
+		if ( typeof second.length === "number" ) {
+			for ( var l = second.length; j < l; j++ ) {
+				first[ i++ ] = second[ j ];
+			}
+
+		} else {
+			while ( second[j] !== undefined ) {
+				first[ i++ ] = second[ j++ ];
+			}
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, inv ) {
+		var ret = [], retVal;
+		inv = !!inv;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			retVal = !!callback( elems[ i ], i );
+			if ( inv !== retVal ) {
+				ret.push( elems[ i ] );
+			}
+		}
+
+		return ret;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var ret = [], value;
+
+		// Go through the array, translating each of the items to their
+		// new value (or values).
+		for ( var i = 0, length = elems.length; i < length; i++ ) {
+			value = callback( elems[ i ], i, arg );
+
+			if ( value != null ) {
+				ret[ ret.length ] = value;
+			}
+		}
+
+		// Flatten any nested arrays
+		return ret.concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	proxy: function( fn, proxy, thisObject ) {
+		if ( arguments.length === 2 ) {
+			if ( typeof proxy === "string" ) {
+				thisObject = fn;
+				fn = thisObject[ proxy ];
+				proxy = undefined;
+
+			} else if ( proxy && !jQuery.isFunction( proxy ) ) {
+				thisObject = proxy;
+				proxy = undefined;
+			}
+		}
+
+		if ( !proxy && fn ) {
+			proxy = function() {
+				return fn.apply( thisObject || this, arguments );
+			};
+		}
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		if ( fn ) {
+			proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
+		}
+
+		// So proxy can be declared as an argument
+		return proxy;
+	},
+
+	// Mutifunctional method to get and set values to a collection
+	// The value/s can be optionally by executed if its a function
+	access: function( elems, key, value, exec, fn, pass ) {
+		var length = elems.length;
+
+		// Setting many attributes
+		if ( typeof key === "object" ) {
+			for ( var k in key ) {
+				jQuery.access( elems, k, key[k], exec, fn, value );
+			}
+			return elems;
+		}
+
+		// Setting one attribute
+		if ( value !== undefined ) {
+			// Optionally, function values get executed if exec is true
+			exec = !pass && exec && jQuery.isFunction(value);
+
+			for ( var i = 0; i < length; i++ ) {
+				fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+			}
+
+			return elems;
+		}
+
+		// Getting an attribute
+		return length ? fn( elems[0], key ) : undefined;
+	},
+
+	now: function() {
+		return (new Date()).getTime();
+	},
+
+	// Create a simple deferred (one callbacks list)
+	_Deferred: function() {
+		var // callbacks list
+			callbacks = [],
+			// stored [ context , args ]
+			fired,
+			// to avoid firing when already doing so
+			firing,
+			// flag to know if the deferred has been cancelled
+			cancelled,
+			// the deferred itself
+			deferred  = {
+
+				// done( f1, f2, ...)
+				done: function() {
+					if ( !cancelled ) {
+						var args = arguments,
+							i,
+							length,
+							elem,
+							type,
+							_fired;
+						if ( fired ) {
+							_fired = fired;
+							fired = 0;
+						}
+						for ( i = 0, length = args.length; i < length; i++ ) {
+							elem = args[ i ];
+							type = jQuery.type( elem );
+							if ( type === "array" ) {
+								deferred.done.apply( deferred, elem );
+							} else if ( type === "function" ) {
+								callbacks.push( elem );
+							}
+						}
+						if ( _fired ) {
+							deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] );
+						}
+					}
+					return this;
+				},
+
+				// resolve with given context and args
+				resolveWith: function( context, args ) {
+					if ( !cancelled && !fired && !firing ) {
+						firing = 1;
+						try {
+							while( callbacks[ 0 ] ) {
+								callbacks.shift().apply( context, args );
+							}
+						}
+						finally {
+							fired = [ context, args ];
+							firing = 0;
+						}
+					}
+					return this;
+				},
+
+				// resolve with this as context and given arguments
+				resolve: function() {
+					deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments );
+					return this;
+				},
+
+				// Has this deferred been resolved?
+				isResolved: function() {
+					return !!( firing || fired );
+				},
+
+				// Cancel
+				cancel: function() {
+					cancelled = 1;
+					callbacks = [];
+					return this;
+				}
+			};
+
+		return deferred;
+	},
+
+	// Full fledged deferred (two callbacks list)
+	Deferred: function( func ) {
+		var deferred = jQuery._Deferred(),
+			failDeferred = jQuery._Deferred(),
+			promise;
+		// Add errorDeferred methods, then and promise
+		jQuery.extend( deferred, {
+			then: function( doneCallbacks, failCallbacks ) {
+				deferred.done( doneCallbacks ).fail( failCallbacks );
+				return this;
+			},
+			fail: failDeferred.done,
+			rejectWith: failDeferred.resolveWith,
+			reject: failDeferred.resolve,
+			isRejected: failDeferred.isResolved,
+			// Get a promise for this deferred
+			// If obj is provided, the promise aspect is added to the object
+			promise: function( obj , i /* internal */ ) {
+				if ( obj == null ) {
+					if ( promise ) {
+						return promise;
+					}
+					promise = obj = {};
+				}
+				i = promiseMethods.length;
+				while( i-- ) {
+					obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ];
+				}
+				return obj;
+			}
+		} );
+		// Make sure only one callback list will be used
+		deferred.then( failDeferred.cancel, deferred.cancel );
+		// Unexpose cancel
+		delete deferred.cancel;
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( object ) {
+		var args = arguments,
+			length = args.length,
+			deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ?
+				object :
+				jQuery.Deferred(),
+			promise = deferred.promise(),
+			resolveArray;
+
+		if ( length > 1 ) {
+			resolveArray = new Array( length );
+			jQuery.each( args, function( index, element ) {
+				jQuery.when( element ).then( function( value ) {
+					resolveArray[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
+					if( ! --length ) {
+						deferred.resolveWith( promise, resolveArray );
+					}
+				}, deferred.reject );
+			} );
+		} else if ( deferred !== object ) {
+			deferred.resolve( object );
+		}
+		return promise;
+	},
+
+	// Use of jQuery.browser is frowned upon.
+	// More details: http://docs.jquery.com/Utilities/jQuery.browser
+	uaMatch: function( ua ) {
+		ua = ua.toLowerCase();
+
+		var match = rwebkit.exec( ua ) ||
+			ropera.exec( ua ) ||
+			rmsie.exec( ua ) ||
+			ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
+			[];
+
+		return { browser: match[1] || "", version: match[2] || "0" };
+	},
+
+	sub: function() {
+		function jQuerySubclass( selector, context ) {
+			return new jQuerySubclass.fn.init( selector, context );
+		}
+		jQuery.extend( true, jQuerySubclass, this );
+		jQuerySubclass.superclass = this;
+		jQuerySubclass.fn = jQuerySubclass.prototype = this();
+		jQuerySubclass.fn.constructor = jQuerySubclass;
+		jQuerySubclass.subclass = this.subclass;
+		jQuerySubclass.fn.init = function init( selector, context ) {
+			if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) {
+				context = jQuerySubclass(context);
+			}
+
+			return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass );
+		};
+		jQuerySubclass.fn.init.prototype = jQuerySubclass.fn;
+		var rootjQuerySubclass = jQuerySubclass(document);
+		return jQuerySubclass;
+	},
+
+	browser: {}
+});
+
+// Create readyList deferred
+readyList = jQuery._Deferred();
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+browserMatch = jQuery.uaMatch( userAgent );
+if ( browserMatch.browser ) {
+	jQuery.browser[ browserMatch.browser ] = true;
+	jQuery.browser.version = browserMatch.version;
+}
+
+// Deprecated, use jQuery.browser.webkit instead
+if ( jQuery.browser.webkit ) {
+	jQuery.browser.safari = true;
+}
+
+if ( indexOf ) {
+	jQuery.inArray = function( elem, array ) {
+		return indexOf.call( array, elem );
+	};
+}
+
+// IE doesn't match non-breaking spaces with \s
+if ( rnotwhite.test( "\xA0" ) ) {
+	trimLeft = /^[\s\xA0]+/;
+	trimRight = /[\s\xA0]+$/;
+}
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+
+// Cleanup functions for the document ready method
+if ( document.addEventListener ) {
+	DOMContentLoaded = function() {
+		document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+		jQuery.ready();
+	};
+
+} else if ( document.attachEvent ) {
+	DOMContentLoaded = function() {
+		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+		if ( document.readyState === "complete" ) {
+			document.detachEvent( "onreadystatechange", DOMContentLoaded );
+			jQuery.ready();
+		}
+	};
+}
+
+// The DOM ready check for Internet Explorer
+function doScrollCheck() {
+	if ( jQuery.isReady ) {
+		return;
+	}
+
+	try {
+		// If IE is used, use the trick by Diego Perini
+		// http://javascript.nwbox.com/IEContentLoaded/
+		document.documentElement.doScroll("left");
+	} catch(e) {
+		setTimeout( doScrollCheck, 1 );
+		return;
+	}
+
+	// and execute any waiting functions
+	jQuery.ready();
+}
+
+// Expose jQuery to the global object
+return (window.jQuery = window.$ = jQuery);
+
+})();
+
+
+(function() {
+
+	jQuery.support = {};
+
+	var div = document.createElement("div");
+
+	div.style.display = "none";
+	div.innerHTML = "   <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+
+	var all = div.getElementsByTagName("*"),
+		a = div.getElementsByTagName("a")[0],
+		select = document.createElement("select"),
+		opt = select.appendChild( document.createElement("option") );
+
+	// Can't get basic test support
+	if ( !all || !all.length || !a ) {
+		return;
+	}
+
+	jQuery.support = {
+		// IE strips leading whitespace when .innerHTML is used
+		leadingWhitespace: div.firstChild.nodeType === 3,
+
+		// Make sure that tbody elements aren't automatically inserted
+		// IE will insert them into empty tables
+		tbody: !div.getElementsByTagName("tbody").length,
+
+		// Make sure that link elements get serialized correctly by innerHTML
+		// This requires a wrapper element in IE
+		htmlSerialize: !!div.getElementsByTagName("link").length,
+
+		// Get the style information from getAttribute
+		// (IE uses .cssText insted)
+		style: /red/.test( a.getAttribute("style") ),
+
+		// Make sure that URLs aren't manipulated
+		// (IE normalizes it by default)
+		hrefNormalized: a.getAttribute("href") === "/a",
+
+		// Make sure that element opacity exists
+		// (IE uses filter instead)
+		// Use a regex to work around a WebKit issue. See #5145
+		opacity: /^0.55$/.test( a.style.opacity ),
+
+		// Verify style float existence
+		// (IE uses styleFloat instead of cssFloat)
+		cssFloat: !!a.style.cssFloat,
+
+		// Make sure that if no value is specified for a checkbox
+		// that it defaults to "on".
+		// (WebKit defaults to "" instead)
+		checkOn: div.getElementsByTagName("input")[0].value === "on",
+
+		// Make sure that a selected-by-default option has a working selected property.
+		// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+		optSelected: opt.selected,
+
+		// Will be defined later
+		deleteExpando: true,
+		optDisabled: false,
+		checkClone: false,
+		_scriptEval: null,
+		noCloneEvent: true,
+		boxModel: null,
+		inlineBlockNeedsLayout: false,
+		shrinkWrapBlocks: false,
+		reliableHiddenOffsets: true
+	};
+
+	// Make sure that the options inside disabled selects aren't marked as disabled
+	// (WebKit marks them as diabled)
+	select.disabled = true;
+	jQuery.support.optDisabled = !opt.disabled;
+
+	jQuery.support.scriptEval = function() {
+		if ( jQuery.support._scriptEval === null ) {
+			var root = document.documentElement,
+				script = document.createElement("script"),
+				id = "script" + jQuery.now();
+
+			script.type = "text/javascript";
+			try {
+				script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
+			} catch(e) {}
+
+			root.insertBefore( script, root.firstChild );
+
+			// Make sure that the execution of code works by injecting a script
+			// tag with appendChild/createTextNode
+			// (IE doesn't support this, fails, and uses .text instead)
+			if ( window[ id ] ) {
+				jQuery.support._scriptEval = true;
+				delete window[ id ];
+			} else {
+				jQuery.support._scriptEval = false;
+			}
+
+			root.removeChild( script );
+			// release memory in IE
+			root = script = id  = null;
+		}
+
+		return jQuery.support._scriptEval;
+	};
+
+	// Test to see if it's possible to delete an expando from an element
+	// Fails in Internet Explorer
+	try {
+		delete div.test;
+
+	} catch(e) {
+		jQuery.support.deleteExpando = false;
+	}
+
+	if ( div.attachEvent && div.fireEvent ) {
+		div.attachEvent("onclick", function click() {
+			// Cloning a node shouldn't copy over any
+			// bound event handlers (IE does this)
+			jQuery.support.noCloneEvent = false;
+			div.detachEvent("onclick", click);
+		});
+		div.cloneNode(true).fireEvent("onclick");
+	}
+
+	div = document.createElement("div");
+	div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>";
+
+	var fragment = document.createDocumentFragment();
+	fragment.appendChild( div.firstChild );
+
+	// WebKit doesn't clone checked state correctly in fragments
+	jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked;
+
+	// Figure out if the W3C box model works as expected
+	// document.body must exist before we can do this
+	jQuery(function() {
+		var div = document.createElement("div"),
+			body = document.getElementsByTagName("body")[0];
+
+		// Frameset documents with no body should not run this code
+		if ( !body ) {
+			return;
+		}
+
+		div.style.width = div.style.paddingLeft = "1px";
+		body.appendChild( div );
+		jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
+
+		if ( "zoom" in div.style ) {
+			// Check if natively block-level elements act like inline-block
+			// elements when setting their display to 'inline' and giving
+			// them layout
+			// (IE < 8 does this)
+			div.style.display = "inline";
+			div.style.zoom = 1;
+			jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2;
+
+			// Check if elements with layout shrink-wrap their children
+			// (IE 6 does this)
+			div.style.display = "";
+			div.innerHTML = "<div style='width:4px;'></div>";
+			jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2;
+		}
+
+		div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
+		var tds = div.getElementsByTagName("td");
+
+		// Check if table cells still have offsetWidth/Height when they are set
+		// to display:none and there are still other visible table cells in a
+		// table row; if so, offsetWidth/Height are not reliable for use when
+		// determining if an element has been hidden directly using
+		// display:none (it is still safe to use offsets if a parent element is
+		// hidden; don safety goggles and see bug #4512 for more information).
+		// (only IE 8 fails this test)
+		jQuery.support.reliableHiddenOffsets = tds[0].offsetHeight === 0;
+
+		tds[0].style.display = "";
+		tds[1].style.display = "none";
+
+		// Check if empty table cells still have offsetWidth/Height
+		// (IE < 8 fail this test)
+		jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0;
+		div.innerHTML = "";
+
+		body.removeChild( div ).style.display = "none";
+		div = tds = null;
+	});
+
+	// Technique from Juriy Zaytsev
+	// http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
+	var eventSupported = function( eventName ) {
+		var el = document.createElement("div");
+		eventName = "on" + eventName;
+
+		// We only care about the case where non-standard event systems
+		// are used, namely in IE. Short-circuiting here helps us to
+		// avoid an eval call (in setAttribute) which can cause CSP
+		// to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+		if ( !el.attachEvent ) {
+			return true;
+		}
+
+		var isSupported = (eventName in el);
+		if ( !isSupported ) {
+			el.setAttribute(eventName, "return;");
+			isSupported = typeof el[eventName] === "function";
+		}
+		el = null;
+
+		return isSupported;
+	};
+
+	jQuery.support.submitBubbles = eventSupported("submit");
+	jQuery.support.changeBubbles = eventSupported("change");
+
+	// release memory in IE
+	div = all = a = null;
+})();
+
+
+
+var rbrace = /^(?:\{.*\}|\[.*\])$/;
+
+jQuery.extend({
+	cache: {},
+
+	// Please use with caution
+	uuid: 0,
+
+	// Unique for each copy of jQuery on the page
+	// Non-digits removed to match rinlinejQuery
+	expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+	// The following elements throw uncatchable exceptions if you
+	// attempt to add expando properties to them.
+	noData: {
+		"embed": true,
+		// Ban all objects except for Flash (which handle expandos)
+		"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+		"applet": true
+	},
+
+	hasData: function( elem ) {
+		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+
+		return !!elem && !jQuery.isEmptyObject(elem);
+	},
+
+	data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache,
+
+			// We have to handle DOM nodes and JS objects differently because IE6-7
+			// can't GC object references properly across the DOM-JS boundary
+			isNode = elem.nodeType,
+
+			// Only DOM nodes need the global jQuery cache; JS object data is
+			// attached directly to the object so GC can occur automatically
+			cache = isNode ? jQuery.cache : elem,
+
+			// Only defining an ID for JS objects if its cache already exists allows
+			// the code to shortcut on the same path as a DOM node with no cache
+			id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando;
+
+		// Avoid doing any more work than we need to when trying to get data on an
+		// object that has no data at all
+		if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) {
+			return;
+		}
+
+		if ( !id ) {
+			// Only DOM nodes need a new unique ID for each element since their data
+			// ends up in the global cache
+			if ( isNode ) {
+				elem[ jQuery.expando ] = id = ++jQuery.uuid;
+			} else {
+				id = jQuery.expando;
+			}
+		}
+
+		if ( !cache[ id ] ) {
+			cache[ id ] = {};
+		}
+
+		// An object can be passed to jQuery.data instead of a key/value pair; this gets
+		// shallow copied over onto the existing cache
+		if ( typeof name === "object" ) {
+			if ( pvt ) {
+				cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name);
+			} else {
+				cache[ id ] = jQuery.extend(cache[ id ], name);
+			}
+		}
+
+		thisCache = cache[ id ];
+
+		// Internal jQuery data is stored in a separate object inside the object's data
+		// cache in order to avoid key collisions between internal data and user-defined
+		// data
+		if ( pvt ) {
+			if ( !thisCache[ internalKey ] ) {
+				thisCache[ internalKey ] = {};
+			}
+
+			thisCache = thisCache[ internalKey ];
+		}
+
+		if ( data !== undefined ) {
+			thisCache[ name ] = data;
+		}
+
+		// TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should
+		// not attempt to inspect the internal events object using jQuery.data, as this
+		// internal data object is undocumented and subject to change.
+		if ( name === "events" && !thisCache[name] ) {
+			return thisCache[ internalKey ] && thisCache[ internalKey ].events;
+		}
+
+		return getByName ? thisCache[ name ] : thisCache;
+	},
+
+	removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+		if ( !jQuery.acceptData( elem ) ) {
+			return;
+		}
+
+		var internalKey = jQuery.expando, isNode = elem.nodeType,
+
+			// See jQuery.data for more information
+			cache = isNode ? jQuery.cache : elem,
+
+			// See jQuery.data for more information
+			id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+		// If there is already no cache entry for this object, there is no
+		// purpose in continuing
+		if ( !cache[ id ] ) {
+			return;
+		}
+
+		if ( name ) {
+			var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ];
+
+			if ( thisCache ) {
+				delete thisCache[ name ];
+
+				// If there is no data left in the cache, we want to continue
+				// and let the cache object itself get destroyed
+				if ( !jQuery.isEmptyObject(thisCache) ) {
+					return;
+				}
+			}
+		}
+
+		// See jQuery.data for more information
+		if ( pvt ) {
+			delete cache[ id ][ internalKey ];
+
+			// Don't destroy the parent cache unless the internal data object
+			// had been the only thing left in it
+			if ( !jQuery.isEmptyObject(cache[ id ]) ) {
+				return;
+			}
+		}
+
+		var internalCache = cache[ id ][ internalKey ];
+
+		// Browsers that fail expando deletion also refuse to delete expandos on
+		// the window, but it will allow it on all other JS objects; other browsers
+		// don't care
+		if ( jQuery.support.deleteExpando || cache != window ) {
+			delete cache[ id ];
+		} else {
+			cache[ id ] = null;
+		}
+
+		// We destroyed the entire user cache at once because it's faster than
+		// iterating through each key, but we need to continue to persist internal
+		// data if it existed
+		if ( internalCache ) {
+			cache[ id ] = {};
+			cache[ id ][ internalKey ] = internalCache;
+
+		// Otherwise, we need to eliminate the expando on the node to avoid
+		// false lookups in the cache for entries that no longer exist
+		} else if ( isNode ) {
+			// IE does not allow us to delete expando properties from nodes,
+			// nor does it have a removeAttribute function on Document nodes;
+			// we must handle all of these cases
+			if ( jQuery.support.deleteExpando ) {
+				delete elem[ jQuery.expando ];
+			} else if ( elem.removeAttribute ) {
+				elem.removeAttribute( jQuery.expando );
+			} else {
+				elem[ jQuery.expando ] = null;
+			}
+		}
+	},
+
+	// For internal use only.
+	_data: function( elem, name, data ) {
+		return jQuery.data( elem, name, data, true );
+	},
+
+	// A method for determining if a DOM node can handle the data expando
+	acceptData: function( elem ) {
+		if ( elem.nodeName ) {
+			var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+			if ( match ) {
+				return !(match === true || elem.getAttribute("classid") !== match);
+			}
+		}
+
+		return true;
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ) {
+		var data = null;
+
+		if ( typeof key === "undefined" ) {
+			if ( this.length ) {
+				data = jQuery.data( this[0] );
+
+				if ( this[0].nodeType === 1 ) {
+					var attr = this[0].attributes, name;
+					for ( var i = 0, l = attr.length; i < l; i++ ) {
+						name = attr[i].name;
+
+						if ( name.indexOf( "data-" ) === 0 ) {
+							name = name.substr( 5 );
+							dataAttr( this[0], name, data[ name ] );
+						}
+					}
+				}
+			}
+
+			return data;
+
+		} else if ( typeof key === "object" ) {
+			return this.each(function() {
+				jQuery.data( this, key );
+			});
+		}
+
+		var parts = key.split(".");
+		parts[1] = parts[1] ? "." + parts[1] : "";
+
+		if ( value === undefined ) {
+			data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
+
+			// Try to fetch any internally stored data first
+			if ( data === undefined && this.length ) {
+				data = jQuery.data( this[0], key );
+				data = dataAttr( this[0], key, data );
+			}
+
+			return data === undefined && parts[1] ?
+				this.data( parts[0] ) :
+				data;
+
+		} else {
+			return this.each(function() {
+				var $this = jQuery( this ),
+					args = [ parts[0], value ];
+
+				$this.triggerHandler( "setData" + parts[1] + "!", args );
+				jQuery.data( this, key, value );
+				$this.triggerHandler( "changeData" + parts[1] + "!", args );
+			});
+		}
+	},
+
+	removeData: function( key ) {
+		return this.each(function() {
+			jQuery.removeData( this, key );
+		});
+	}
+});
+
+function dataAttr( elem, key, data ) {
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+		data = elem.getAttribute( "data-" + key );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = data === "true" ? true :
+				data === "false" ? false :
+				data === "null" ? null :
+				!jQuery.isNaN( data ) ? parseFloat( data ) :
+					rbrace.test( data ) ? jQuery.parseJSON( data ) :
+					data;
+			} catch( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			jQuery.data( elem, key, data );
+
+		} else {
+			data = undefined;
+		}
+	}
+
+	return data;
+}
+
+
+
+
+jQuery.extend({
+	queue: function( elem, type, data ) {
+		if ( !elem ) {
+			return;
+		}
+
+		type = (type || "fx") + "queue";
+		var q = jQuery._data( elem, type );
+
+		// Speed up dequeue by getting out quickly if this is just a lookup
+		if ( !data ) {
+			return q || [];
+		}
+
+		if ( !q || jQuery.isArray(data) ) {
+			q = jQuery._data( elem, type, jQuery.makeArray(data) );
+
+		} else {
+			q.push( data );
+		}
+
+		return q;
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			fn = queue.shift();
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+		}
+
+		if ( fn ) {
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift("inprogress");
+			}
+
+			fn.call(elem, function() {
+				jQuery.dequeue(elem, type);
+			});
+		}
+
+		if ( !queue.length ) {
+			jQuery.removeData( elem, type + "queue", true );
+		}
+	}
+});
+
+jQuery.fn.extend({
+	queue: function( type, data ) {
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+		}
+
+		if ( data === undefined ) {
+			return jQuery.queue( this[0], type );
+		}
+		return this.each(function( i ) {
+			var queue = jQuery.queue( this, type, data );
+
+			if ( type === "fx" && queue[0] !== "inprogress" ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	},
+	dequeue: function( type ) {
+		return this.each(function() {
+			jQuery.dequeue( this, type );
+		});
+	},
+
+	// Based off of the plugin by Clint Helfers, with permission.
+	// http://blindsignals.com/index.php/2009/07/jquery-delay/
+	delay: function( time, type ) {
+		time = jQuery.fx ? jQuery.fx.speeds[time] || time : time;
+		type = type || "fx";
+
+		return this.queue( type, function() {
+			var elem = this;
+			setTimeout(function() {
+				jQuery.dequeue( elem, type );
+			}, time );
+		});
+	},
+
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	}
+});
+
+
+
+
+var rclass = /[\n\t\r]/g,
+	rspaces = /\s+/,
+	rreturn = /\r/g,
+	rspecialurl = /^(?:href|src|style)$/,
+	rtype = /^(?:button|input)$/i,
+	rfocusable = /^(?:button|input|object|select|textarea)$/i,
+	rclickable = /^a(?:rea)?$/i,
+	rradiocheck = /^(?:radio|checkbox)$/i;
+
+jQuery.props = {
+	"for": "htmlFor",
+	"class": "className",
+	readonly: "readOnly",
+	maxlength: "maxLength",
+	cellspacing: "cellSpacing",
+	rowspan: "rowSpan",
+	colspan: "colSpan",
+	tabindex: "tabIndex",
+	usemap: "useMap",
+	frameborder: "frameBorder"
+};
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return jQuery.access( this, name, value, true, jQuery.attr );
+	},
+
+	removeAttr: function( name, fn ) {
+		return this.each(function(){
+			jQuery.attr( this, name, "" );
+			if ( this.nodeType === 1 ) {
+				this.removeAttribute( name );
+			}
+		});
+	},
+
+	addClass: function( value ) {
+		if ( jQuery.isFunction(value) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				self.addClass( value.call(this, i, self.attr("class")) );
+			});
+		}
+
+		if ( value && typeof value === "string" ) {
+			var classNames = (value || "").split( rspaces );
+
+			for ( var i = 0, l = this.length; i < l; i++ ) {
+				var elem = this[i];
+
+				if ( elem.nodeType === 1 ) {
+					if ( !elem.className ) {
+						elem.className = value;
+
+					} else {
+						var className = " " + elem.className + " ",
+							setClass = elem.className;
+
+						for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+							if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) {
+								setClass += " " + classNames[c];
+							}
+						}
+						elem.className = jQuery.trim( setClass );
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		if ( jQuery.isFunction(value) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				self.removeClass( value.call(this, i, self.attr("class")) );
+			});
+		}
+
+		if ( (value && typeof value === "string") || value === undefined ) {
+			var classNames = (value || "").split( rspaces );
+
+			for ( var i = 0, l = this.length; i < l; i++ ) {
+				var elem = this[i];
+
+				if ( elem.nodeType === 1 && elem.className ) {
+					if ( value ) {
+						var className = (" " + elem.className + " ").replace(rclass, " ");
+						for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
+							className = className.replace(" " + classNames[c] + " ", " ");
+						}
+						elem.className = jQuery.trim( className );
+
+					} else {
+						elem.className = "";
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value,
+			isBool = typeof stateVal === "boolean";
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal );
+			});
+		}
+
+		return this.each(function() {
+			if ( type === "string" ) {
+				// toggle individual class names
+				var className,
+					i = 0,
+					self = jQuery( this ),
+					state = stateVal,
+					classNames = value.split( rspaces );
+
+				while ( (className = classNames[ i++ ]) ) {
+					// check each className given, space seperated list
+					state = isBool ? state : !self.hasClass( className );
+					self[ state ? "addClass" : "removeClass" ]( className );
+				}
+
+			} else if ( type === "undefined" || type === "boolean" ) {
+				if ( this.className ) {
+					// store className if set
+					jQuery._data( this, "__className__", this.className );
+				}
+
+				// toggle whole className
+				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+			}
+		});
+	},
+
+	hasClass: function( selector ) {
+		var className = " " + selector + " ";
+		for ( var i = 0, l = this.length; i < l; i++ ) {
+			if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
+				return true;
+			}
+		}
+
+		return false;
+	},
+
+	val: function( value ) {
+		if ( !arguments.length ) {
+			var elem = this[0];
+
+			if ( elem ) {
+				if ( jQuery.nodeName( elem, "option" ) ) {
+					// attributes.value is undefined in Blackberry 4.7 but
+					// uses .value. See #6932
+					var val = elem.attributes.value;
+					return !val || val.specified ? elem.value : elem.text;
+				}
+
+				// We need to handle select boxes special
+				if ( jQuery.nodeName( elem, "select" ) ) {
+					var index = elem.selectedIndex,
+						values = [],
+						options = elem.options,
+						one = elem.type === "select-one";
+
+					// Nothing was selected
+					if ( index < 0 ) {
+						return null;
+					}
+
+					// Loop through all the selected options
+					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
+						var option = options[ i ];
+
+						// Don't return options that are disabled or in a disabled optgroup
+						if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+								(!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+							// Get the specific value for the option
+							value = jQuery(option).val();
+
+							// We don't need an array for one selects
+							if ( one ) {
+								return value;
+							}
+
+							// Multi-Selects return an array
+							values.push( value );
+						}
+					}
+
+					return values;
+				}
+
+				// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+				if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) {
+					return elem.getAttribute("value") === null ? "on" : elem.value;
+				}
+
+				// Everything else, we just grab the value
+				return (elem.value || "").replace(rreturn, "");
+
+			}
+
+			return undefined;
+		}
+
+		var isFunction = jQuery.isFunction(value);
+
+		return this.each(function(i) {
+			var self = jQuery(this), val = value;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call(this, i, self.val());
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+			} else if ( typeof val === "number" ) {
+				val += "";
+			} else if ( jQuery.isArray(val) ) {
+				val = jQuery.map(val, function (value) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) {
+				this.checked = jQuery.inArray( self.val(), val ) >= 0;
+
+			} else if ( jQuery.nodeName( this, "select" ) ) {
+				var values = jQuery.makeArray(val);
+
+				jQuery( "option", this ).each(function() {
+					this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+				});
+
+				if ( !values.length ) {
+					this.selectedIndex = -1;
+				}
+
+			} else {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	attrFn: {
+		val: true,
+		css: true,
+		html: true,
+		text: true,
+		data: true,
+		width: true,
+		height: true,
+		offset: true
+	},
+
+	attr: function( elem, name, value, pass ) {
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) {
+			return undefined;
+		}
+
+		if ( pass && name in jQuery.attrFn ) {
+			return jQuery(elem)[name](value);
+		}
+
+		var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ),
+			// Whether we are setting (or getting)
+			set = value !== undefined;
+
+		// Try to normalize/fix the name
+		name = notxml && jQuery.props[ name ] || name;
+
+		// Only do all the following if this is a node (faster for style)
+		if ( elem.nodeType === 1 ) {
+			// These attributes require special treatment
+			var special = rspecialurl.test( name );
+
+			// Safari mis-reports the default selected property of an option
+			// Accessing the parent's selectedIndex property fixes it
+			if ( name === "selected" && !jQuery.support.optSelected ) {
+				var parent = elem.parentNode;
+				if ( parent ) {
+					parent.selectedIndex;
+
+					// Make sure that it also works with optgroups, see #5701
+					if ( parent.parentNode ) {
+						parent.parentNode.selectedIndex;
+					}
+				}
+			}
+
+			// If applicable, access the attribute via the DOM 0 way
+			// 'in' checks fail in Blackberry 4.7 #6931
+			if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) {
+				if ( set ) {
+					// We can't allow the type property to be changed (since it causes problems in IE)
+					if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) {
+						jQuery.error( "type property can't be changed" );
+					}
+
+					if ( value === null ) {
+						if ( elem.nodeType === 1 ) {
+							elem.removeAttribute( name );
+						}
+
+					} else {
+						elem[ name ] = value;
+					}
+				}
+
+				// browsers index elements by id/name on forms, give priority to attributes.
+				if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) {
+					return elem.getAttributeNode( name ).nodeValue;
+				}
+
+				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+				if ( name === "tabIndex" ) {
+					var attributeNode = elem.getAttributeNode( "tabIndex" );
+
+					return attributeNode && attributeNode.specified ?
+						attributeNode.value :
+						rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+							0 :
+							undefined;
+				}
+
+				return elem[ name ];
+			}
+
+			if ( !jQuery.support.style && notxml && name === "style" ) {
+				if ( set ) {
+					elem.style.cssText = "" + value;
+				}
+
+				return elem.style.cssText;
+			}
+
+			if ( set ) {
+				// convert the value to a string (all browsers do this but IE) see #1070
+				elem.setAttribute( name, "" + value );
+			}
+
+			// Ensure that missing attributes return undefined
+			// Blackberry 4.7 returns "" from getAttribute #6938
+			if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) {
+				return undefined;
+			}
+
+			var attr = !jQuery.support.hrefNormalized && notxml && special ?
+					// Some attributes require a special call on IE
+					elem.getAttribute( name, 2 ) :
+					elem.getAttribute( name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return attr === null ? undefined : attr;
+		}
+		// Handle everything which isn't a DOM element node
+		if ( set ) {
+			elem[ name ] = value;
+		}
+		return elem[ name ];
+	}
+});
+
+
+
+
+var rnamespaces = /\.(.*)$/,
+	rformElems = /^(?:textarea|input|select)$/i,
+	rperiod = /\./g,
+	rspace = / /g,
+	rescape = /[^\w\s.|`]/g,
+	fcleanup = function( nm ) {
+		return nm.replace(rescape, "\\$&");
+	},
+	eventKey = "events";
+
+/*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+jQuery.event = {
+
+	// Bind an event to an element
+	// Original by Dean Edwards
+	add: function( elem, types, handler, data ) {
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		// For whatever reason, IE has trouble passing the window object
+		// around, causing it to be cloned in the process
+		if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) {
+			elem = window;
+		}
+
+		if ( handler === false ) {
+			handler = returnFalse;
+		} else if ( !handler ) {
+			// Fixes bug #7229. Fix recommended by jdalton
+		  return;
+		}
+
+		var handleObjIn, handleObj;
+
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+		}
+
+		// Make sure that the function being executed has a unique ID
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure
+		var elemData = jQuery._data( elem );
+
+		// If no elemData is found then we must be trying to bind to one of the
+		// banned noData elements
+		if ( !elemData ) {
+			return;
+		}
+
+		var events = elemData[ eventKey ],
+			eventHandle = elemData.handle;
+
+		if ( typeof events === "function" ) {
+			// On plain objects events is a fn that holds the the data
+			// which prevents this data from being JSON serialized
+			// the function does not need to be called, it just contains the data
+			eventHandle = events.handle;
+			events = events.events;
+
+		} else if ( !events ) {
+			if ( !elem.nodeType ) {
+				// On plain objects, create a fn that acts as the holder
+				// of the values to avoid JSON serialization of event data
+				elemData[ eventKey ] = elemData = function(){};
+			}
+
+			elemData.events = events = {};
+		}
+
+		if ( !eventHandle ) {
+			elemData.handle = eventHandle = function() {
+				// Handle the second event of a trigger and when
+				// an event is called after a page has unloaded
+				return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
+					jQuery.event.handle.apply( eventHandle.elem, arguments ) :
+					undefined;
+			};
+		}
+
+		// Add elem as a property of the handle function
+		// This is to prevent a memory leak with non-native events in IE.
+		eventHandle.elem = elem;
+
+		// Handle multiple events separated by a space
+		// jQuery(...).bind("mouseover mouseout", fn);
+		types = types.split(" ");
+
+		var type, i = 0, namespaces;
+
+		while ( (type = types[ i++ ]) ) {
+			handleObj = handleObjIn ?
+				jQuery.extend({}, handleObjIn) :
+				{ handler: handler, data: data };
+
+			// Namespaced event handlers
+			if ( type.indexOf(".") > -1 ) {
+				namespaces = type.split(".");
+				type = namespaces.shift();
+				handleObj.namespace = namespaces.slice(0).sort().join(".");
+
+			} else {
+				namespaces = [];
+				handleObj.namespace = "";
+			}
+
+			handleObj.type = type;
+			if ( !handleObj.guid ) {
+				handleObj.guid = handler.guid;
+			}
+
+			// Get the current list of functions bound to this event
+			var handlers = events[ type ],
+				special = jQuery.event.special[ type ] || {};
+
+			// Init the event handler queue
+			if ( !handlers ) {
+				handlers = events[ type ] = [];
+
+				// Check for a special event handler
+				// Only use addEventListener/attachEvent if the special
+				// events handler returns false
+				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+					// Bind the global event handler to the element
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle, false );
+
+					} else if ( elem.attachEvent ) {
+						elem.attachEvent( "on" + type, eventHandle );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add the function to the element's handler list
+			handlers.push( handleObj );
+
+			// Keep track of which events have been used, for global triggering
+			jQuery.event.global[ type ] = true;
+		}
+
+		// Nullify elem to prevent memory leaks in IE
+		elem = null;
+	},
+
+	global: {},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, pos ) {
+		// don't do events on text and comment nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		if ( handler === false ) {
+			handler = returnFalse;
+		}
+
+		var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType,
+			elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
+			events = elemData && elemData[ eventKey ];
+
+		if ( !elemData || !events ) {
+			return;
+		}
+
+		if ( typeof events === "function" ) {
+			elemData = events;
+			events = events.events;
+		}
+
+		// types is actually an event object here
+		if ( types && types.type ) {
+			handler = types.handler;
+			types = types.type;
+		}
+
+		// Unbind all events for the element
+		if ( !types || typeof types === "string" && types.charAt(0) === "." ) {
+			types = types || "";
+
+			for ( type in events ) {
+				jQuery.event.remove( elem, type + types );
+			}
+
+			return;
+		}
+
+		// Handle multiple events separated by a space
+		// jQuery(...).unbind("mouseover mouseout", fn);
+		types = types.split(" ");
+
+		while ( (type = types[ i++ ]) ) {
+			origType = type;
+			handleObj = null;
+			all = type.indexOf(".") < 0;
+			namespaces = [];
+
+			if ( !all ) {
+				// Namespaced event handlers
+				namespaces = type.split(".");
+				type = namespaces.shift();
+
+				namespace = new RegExp("(^|\\.)" +
+					jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)");
+			}
+
+			eventType = events[ type ];
+
+			if ( !eventType ) {
+				continue;
+			}
+
+			if ( !handler ) {
+				for ( j = 0; j < eventType.length; j++ ) {
+					handleObj = eventType[ j ];
+
+					if ( all || namespace.test( handleObj.namespace ) ) {
+						jQuery.event.remove( elem, origType, handleObj.handler, j );
+						eventType.splice( j--, 1 );
+					}
+				}
+
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+
+			for ( j = pos || 0; j < eventType.length; j++ ) {
+				handleObj = eventType[ j ];
+
+				if ( handler.guid === handleObj.guid ) {
+					// remove the given handler for the given type
+					if ( all || namespace.test( handleObj.namespace ) ) {
+						if ( pos == null ) {
+							eventType.splice( j--, 1 );
+						}
+
+						if ( special.remove ) {
+							special.remove.call( elem, handleObj );
+						}
+					}
+
+					if ( pos != null ) {
+						break;
+					}
+				}
+			}
+
+			// remove generic event handler if no more handlers exist
+			if ( eventType.length === 0 || pos != null && eventType.length === 1 ) {
+				if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				ret = null;
+				delete events[ type ];
+			}
+		}
+
+		// Remove the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			var handle = elemData.handle;
+			if ( handle ) {
+				handle.elem = null;
+			}
+
+			delete elemData.events;
+			delete elemData.handle;
+
+			if ( typeof elemData === "function" ) {
+				jQuery.removeData( elem, eventKey, true );
+
+			} else if ( jQuery.isEmptyObject( elemData ) ) {
+				jQuery.removeData( elem, undefined, true );
+			}
+		}
+	},
+
+	// bubbling is internal
+	trigger: function( event, data, elem /*, bubbling */ ) {
+		// Event object or event type
+		var type = event.type || event,
+			bubbling = arguments[3];
+
+		if ( !bubbling ) {
+			event = typeof event === "object" ?
+				// jQuery.Event object
+				event[ jQuery.expando ] ? event :
+				// Object literal
+				jQuery.extend( jQuery.Event(type), event ) :
+				// Just the event type (string)
+				jQuery.Event(type);
+
+			if ( type.indexOf("!") >= 0 ) {
+				event.type = type = type.slice(0, -1);
+				event.exclusive = true;
+			}
+
+			// Handle a global trigger
+			if ( !elem ) {
+				// Don't bubble custom events when global (to avoid too much overhead)
+				event.stopPropagation();
+
+				// Only trigger if we've ever bound an event for it
+				if ( jQuery.event.global[ type ] ) {
+					// XXX This code smells terrible. event.js should not be directly
+					// inspecting the data cache
+					jQuery.each( jQuery.cache, function() {
+						// internalKey variable is just used to make it easier to find
+						// and potentially change this stuff later; currently it just
+						// points to jQuery.expando
+						var internalKey = jQuery.expando,
+							internalCache = this[ internalKey ];
+						if ( internalCache && internalCache.events && internalCache.events[type] ) {
+							jQuery.event.trigger( event, data, internalCache.handle.elem );
+						}
+					});
+				}
+			}
+
+			// Handle triggering a single element
+
+			// don't do events on text and comment nodes
+			if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) {
+				return undefined;
+			}
+
+			// Clean up in case it is reused
+			event.result = undefined;
+			event.target = elem;
+
+			// Clone the incoming data, if any
+			data = jQuery.makeArray( data );
+			data.unshift( event );
+		}
+
+		event.currentTarget = elem;
+
+		// Trigger the event, it is assumed that "handle" is a function
+		var handle = elem.nodeType ?
+			jQuery._data( elem, "handle" ) :
+			(jQuery._data( elem, eventKey ) || {}).handle;
+
+		if ( handle ) {
+			handle.apply( elem, data );
+		}
+
+		var parent = elem.parentNode || elem.ownerDocument;
+
+		// Trigger an inline bound script
+		try {
+			if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) {
+				if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) {
+					event.result = false;
+					event.preventDefault();
+				}
+			}
+
+		// prevent IE from throwing an error for some elements with some event types, see #3533
+		} catch (inlineError) {}
+
+		if ( !event.isPropagationStopped() && parent ) {
+			jQuery.event.trigger( event, data, parent, true );
+
+		} else if ( !event.isDefaultPrevented() ) {
+			var old,
+				target = event.target,
+				targetType = type.replace( rnamespaces, "" ),
+				isClick = jQuery.nodeName( target, "a" ) && targetType === "click",
+				special = jQuery.event.special[ targetType ] || {};
+
+			if ( (!special._default || special._default.call( elem, event ) === false) &&
+				!isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) {
+
+				try {
+					if ( target[ targetType ] ) {
+						// Make sure that we don't accidentally re-trigger the onFOO events
+						old = target[ "on" + targetType ];
+
+						if ( old ) {
+							target[ "on" + targetType ] = null;
+						}
+
+						jQuery.event.triggered = true;
+						target[ targetType ]();
+					}
+
+				// prevent IE from throwing an error for some elements with some event types, see #3533
+				} catch (triggerError) {}
+
+				if ( old ) {
+					target[ "on" + targetType ] = old;
+				}
+
+				jQuery.event.triggered = false;
+			}
+		}
+	},
+
+	handle: function( event ) {
+		var all, handlers, namespaces, namespace_re, events,
+			namespace_sort = [],
+			args = jQuery.makeArray( arguments );
+
+		event = args[0] = jQuery.event.fix( event || window.event );
+		event.currentTarget = this;
+
+		// Namespaced event handlers
+		all = event.type.indexOf(".") < 0 && !event.exclusive;
+
+		if ( !all ) {
+			namespaces = event.type.split(".");
+			event.type = namespaces.shift();
+			namespace_sort = namespaces.slice(0).sort();
+			namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)");
+		}
+
+		event.namespace = event.namespace || namespace_sort.join(".");
+
+		events = jQuery._data(this, eventKey);
+
+		if ( typeof events === "function" ) {
+			events = events.events;
+		}
+
+		handlers = (events || {})[ event.type ];
+
+		if ( events && handlers ) {
+			// Clone the handlers to prevent manipulation
+			handlers = handlers.slice(0);
+
+			for ( var j = 0, l = handlers.length; j < l; j++ ) {
+				var handleObj = handlers[ j ];
+
+				// Filter the functions by class
+				if ( all || namespace_re.test( handleObj.namespace ) ) {
+					// Pass in a reference to the handler function itself
+					// So that we can later remove it
+					event.handler = handleObj.handler;
+					event.data = handleObj.data;
+					event.handleObj = handleObj;
+
+					var ret = handleObj.handler.apply( this, args );
+
+					if ( ret !== undefined ) {
+						event.result = ret;
+						if ( ret === false ) {
+							event.preventDefault();
+							event.stopPropagation();
+						}
+					}
+
+					if ( event.isImmediatePropagationStopped() ) {
+						break;
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+
+	fix: function( event ) {
+		if ( event[ jQuery.expando ] ) {
+			return event;
+		}
+
+		// store a copy of the original event object
+		// and "clone" to set read-only properties
+		var originalEvent = event;
+		event = jQuery.Event( originalEvent );
+
+		for ( var i = this.props.length, prop; i; ) {
+			prop = this.props[ --i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Fix target property, if necessary
+		if ( !event.target ) {
+			// Fixes #1925 where srcElement might not be defined either
+			event.target = event.srcElement || document;
+		}
+
+		// check if target is a textnode (safari)
+		if ( event.target.nodeType === 3 ) {
+			event.target = event.target.parentNode;
+		}
+
+		// Add relatedTarget, if necessary
+		if ( !event.relatedTarget && event.fromElement ) {
+			event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
+		}
+
+		// Calculate pageX/Y if missing and clientX/Y available
+		if ( event.pageX == null && event.clientX != null ) {
+			var doc = document.documentElement,
+				body = document.body;
+
+			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+			event.pageY = event.clientY + (doc && doc.scrollTop  || body && body.scrollTop  || 0) - (doc && doc.clientTop  || body && body.clientTop  || 0);
+		}
+
+		// Add which for key events
+		if ( event.which == null && (event.charCode != null || event.keyCode != null) ) {
+			event.which = event.charCode != null ? event.charCode : event.keyCode;
+		}
+
+		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
+		if ( !event.metaKey && event.ctrlKey ) {
+			event.metaKey = event.ctrlKey;
+		}
+
+		// Add which for click: 1 === left; 2 === middle; 3 === right
+		// Note: button is not normalized, so don't use it
+		if ( !event.which && event.button !== undefined ) {
+			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
+		}
+
+		return event;
+	},
+
+	// Deprecated, use jQuery.guid instead
+	guid: 1E8,
+
+	// Deprecated, use jQuery.proxy instead
+	proxy: jQuery.proxy,
+
+	special: {
+		ready: {
+			// Make sure the ready event is setup
+			setup: jQuery.bindReady,
+			teardown: jQuery.noop
+		},
+
+		live: {
+			add: function( handleObj ) {
+				jQuery.event.add( this,
+					liveConvert( handleObj.origType, handleObj.selector ),
+					jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) );
+			},
+
+			remove: function( handleObj ) {
+				jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj );
+			}
+		},
+
+		beforeunload: {
+			setup: function( data, namespaces, eventHandle ) {
+				// We only want to do this special case on windows
+				if ( jQuery.isWindow( this ) ) {
+					this.onbeforeunload = eventHandle;
+				}
+			},
+
+			teardown: function( namespaces, eventHandle ) {
+				if ( this.onbeforeunload === eventHandle ) {
+					this.onbeforeunload = null;
+				}
+			}
+		}
+	}
+};
+
+jQuery.removeEvent = document.removeEventListener ?
+	function( elem, type, handle ) {
+		if ( elem.removeEventListener ) {
+			elem.removeEventListener( type, handle, false );
+		}
+	} :
+	function( elem, type, handle ) {
+		if ( elem.detachEvent ) {
+			elem.detachEvent( "on" + type, handle );
+		}
+	};
+
+jQuery.Event = function( src ) {
+	// Allow instantiation without the 'new' keyword
+	if ( !this.preventDefault ) {
+		return new jQuery.Event( src );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || 
+			src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// timeStamp is buggy for some events on Firefox(#3843)
+	// So we won't rely on the native value
+	this.timeStamp = jQuery.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+	return false;
+}
+function returnTrue() {
+	return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	preventDefault: function() {
+		this.isDefaultPrevented = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+
+		// if preventDefault exists run it on the original event
+		if ( e.preventDefault ) {
+			e.preventDefault();
+
+		// otherwise set the returnValue property of the original event to false (IE)
+		} else {
+			e.returnValue = false;
+		}
+	},
+	stopPropagation: function() {
+		this.isPropagationStopped = returnTrue;
+
+		var e = this.originalEvent;
+		if ( !e ) {
+			return;
+		}
+		// if stopPropagation exists run it on the original event
+		if ( e.stopPropagation ) {
+			e.stopPropagation();
+		}
+		// otherwise set the cancelBubble property of the original event to true (IE)
+		e.cancelBubble = true;
+	},
+	stopImmediatePropagation: function() {
+		this.isImmediatePropagationStopped = returnTrue;
+		this.stopPropagation();
+	},
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse
+};
+
+// Checks if an event happened on an element within another element
+// Used in jQuery.event.special.mouseenter and mouseleave handlers
+var withinElement = function( event ) {
+	// Check if mouse(over|out) are still within the same parent element
+	var parent = event.relatedTarget;
+
+	// Firefox sometimes assigns relatedTarget a XUL element
+	// which we cannot access the parentNode property of
+	try {
+		// Traverse up the tree
+		while ( parent && parent !== this ) {
+			parent = parent.parentNode;
+		}
+
+		if ( parent !== this ) {
+			// set the correct event type
+			event.type = event.data;
+
+			// handle event if we actually just moused on to a non sub-element
+			jQuery.event.handle.apply( this, arguments );
+		}
+
+	// assuming we've left the element since we most likely mousedover a xul element
+	} catch(e) { }
+},
+
+// In case of event delegation, we only need to rename the event.type,
+// liveHandler will take care of the rest.
+delegate = function( event ) {
+	event.type = event.data;
+	jQuery.event.handle.apply( this, arguments );
+};
+
+// Create mouseenter and mouseleave events
+jQuery.each({
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		setup: function( data ) {
+			jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig );
+		},
+		teardown: function( data ) {
+			jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement );
+		}
+	};
+});
+
+// submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+	jQuery.event.special.submit = {
+		setup: function( data, namespaces ) {
+			if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) {
+				jQuery.event.add(this, "click.specialSubmit", function( e ) {
+					var elem = e.target,
+						type = elem.type;
+
+					if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
+						e.liveFired = undefined;
+						return trigger( "submit", this, arguments );
+					}
+				});
+
+				jQuery.event.add(this, "keypress.specialSubmit", function( e ) {
+					var elem = e.target,
+						type = elem.type;
+
+					if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
+						e.liveFired = undefined;
+						return trigger( "submit", this, arguments );
+					}
+				});
+
+			} else {
+				return false;
+			}
+		},
+
+		teardown: function( namespaces ) {
+			jQuery.event.remove( this, ".specialSubmit" );
+		}
+	};
+
+}
+
+// change delegation, happens here so we have bind.
+if ( !jQuery.support.changeBubbles ) {
+
+	var changeFilters,
+
+	getVal = function( elem ) {
+		var type = elem.type, val = elem.value;
+
+		if ( type === "radio" || type === "checkbox" ) {
+			val = elem.checked;
+
+		} else if ( type === "select-multiple" ) {
+			val = elem.selectedIndex > -1 ?
+				jQuery.map( elem.options, function( elem ) {
+					return elem.selected;
+				}).join("-") :
+				"";
+
+		} else if ( elem.nodeName.toLowerCase() === "select" ) {
+			val = elem.selectedIndex;
+		}
+
+		return val;
+	},
+
+	testChange = function testChange( e ) {
+		var elem = e.target, data, val;
+
+		if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) {
+			return;
+		}
+
+		data = jQuery._data( elem, "_change_data" );
+		val = getVal(elem);
+
+		// the current data will be also retrieved by beforeactivate
+		if ( e.type !== "focusout" || elem.type !== "radio" ) {
+			jQuery._data( elem, "_change_data", val );
+		}
+
+		if ( data === undefined || val === data ) {
+			return;
+		}
+
+		if ( data != null || val ) {
+			e.type = "change";
+			e.liveFired = undefined;
+			return jQuery.event.trigger( e, arguments[1], elem );
+		}
+	};
+
+	jQuery.event.special.change = {
+		filters: {
+			focusout: testChange,
+
+			beforedeactivate: testChange,
+
+			click: function( e ) {
+				var elem = e.target, type = elem.type;
+
+				if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) {
+					return testChange.call( this, e );
+				}
+			},
+
+			// Change has to be called before submit
+			// Keydown will be called before keypress, which is used in submit-event delegation
+			keydown: function( e ) {
+				var elem = e.target, type = elem.type;
+
+				if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") ||
+					(e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
+					type === "select-multiple" ) {
+					return testChange.call( this, e );
+				}
+			},
+
+			// Beforeactivate happens also before the previous element is blurred
+			// with this event you can't trigger a change event, but you can store
+			// information
+			beforeactivate: function( e ) {
+				var elem = e.target;
+				jQuery._data( elem, "_change_data", getVal(elem) );
+			}
+		},
+
+		setup: function( data, namespaces ) {
+			if ( this.type === "file" ) {
+				return false;
+			}
+
+			for ( var type in changeFilters ) {
+				jQuery.event.add( this, type + ".specialChange", changeFilters[type] );
+			}
+
+			return rformElems.test( this.nodeName );
+		},
+
+		teardown: function( namespaces ) {
+			jQuery.event.remove( this, ".specialChange" );
+
+			return rformElems.test( this.nodeName );
+		}
+	};
+
+	changeFilters = jQuery.event.special.change.filters;
+
+	// Handle when the input is .focus()'d
+	changeFilters.focus = changeFilters.beforeactivate;
+}
+
+function trigger( type, elem, args ) {
+	args[0].type = type;
+	return jQuery.event.handle.apply( elem, args );
+}
+
+// Create "bubbling" focus and blur events
+if ( document.addEventListener ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				this.addEventListener( orig, handler, true );
+			}, 
+			teardown: function() { 
+				this.removeEventListener( orig, handler, true );
+			}
+		};
+
+		function handler( e ) {
+			e = jQuery.event.fix( e );
+			e.type = fix;
+			return jQuery.event.handle.call( this, e );
+		}
+	});
+}
+
+jQuery.each(["bind", "one"], function( i, name ) {
+	jQuery.fn[ name ] = function( type, data, fn ) {
+		// Handle object literals
+		if ( typeof type === "object" ) {
+			for ( var key in type ) {
+				this[ name ](key, data, type[key], fn);
+			}
+			return this;
+		}
+
+		if ( jQuery.isFunction( data ) || data === false ) {
+			fn = data;
+			data = undefined;
+		}
+
+		var handler = name === "one" ? jQuery.proxy( fn, function( event ) {
+			jQuery( this ).unbind( event, handler );
+			return fn.apply( this, arguments );
+		}) : fn;
+
+		if ( type === "unload" && name !== "one" ) {
+			this.one( type, data, fn );
+
+		} else {
+			for ( var i = 0, l = this.length; i < l; i++ ) {
+				jQuery.event.add( this[i], type, handler, data );
+			}
+		}
+
+		return this;
+	};
+});
+
+jQuery.fn.extend({
+	unbind: function( type, fn ) {
+		// Handle object literals
+		if ( typeof type === "object" && !type.preventDefault ) {
+			for ( var key in type ) {
+				this.unbind(key, type[key]);
+			}
+
+		} else {
+			for ( var i = 0, l = this.length; i < l; i++ ) {
+				jQuery.event.remove( this[i], type, fn );
+			}
+		}
+
+		return this;
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.live( types, data, fn, selector );
+	},
+
+	undelegate: function( selector, types, fn ) {
+		if ( arguments.length === 0 ) {
+				return this.unbind( "live" );
+
+		} else {
+			return this.die( types, null, fn, selector );
+		}
+	},
+
+	trigger: function( type, data ) {
+		return this.each(function() {
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+
+	triggerHandler: function( type, data ) {
+		if ( this[0] ) {
+			var event = jQuery.Event( type );
+			event.preventDefault();
+			event.stopPropagation();
+			jQuery.event.trigger( event, data, this[0] );
+			return event.result;
+		}
+	},
+
+	toggle: function( fn ) {
+		// Save reference to arguments for access in closure
+		var args = arguments,
+			i = 1;
+
+		// link all the functions, so any of them can unbind this click handler
+		while ( i < args.length ) {
+			jQuery.proxy( fn, args[ i++ ] );
+		}
+
+		return this.click( jQuery.proxy( fn, function( event ) {
+			// Figure out which function to execute
+			var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+			jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+			// Make sure that clicks stop
+			event.preventDefault();
+
+			// and execute the function
+			return args[ lastToggle ].apply( this, arguments ) || false;
+		}));
+	},
+
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	}
+});
+
+var liveMap = {
+	focus: "focusin",
+	blur: "focusout",
+	mouseenter: "mouseover",
+	mouseleave: "mouseout"
+};
+
+jQuery.each(["live", "die"], function( i, name ) {
+	jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) {
+		var type, i = 0, match, namespaces, preType,
+			selector = origSelector || this.selector,
+			context = origSelector ? this : jQuery( this.context );
+
+		if ( typeof types === "object" && !types.preventDefault ) {
+			for ( var key in types ) {
+				context[ name ]( key, data, types[key], selector );
+			}
+
+			return this;
+		}
+
+		if ( jQuery.isFunction( data ) ) {
+			fn = data;
+			data = undefined;
+		}
+
+		types = (types || "").split(" ");
+
+		while ( (type = types[ i++ ]) != null ) {
+			match = rnamespaces.exec( type );
+			namespaces = "";
+
+			if ( match )  {
+				namespaces = match[0];
+				type = type.replace( rnamespaces, "" );
+			}
+
+			if ( type === "hover" ) {
+				types.push( "mouseenter" + namespaces, "mouseleave" + namespaces );
+				continue;
+			}
+
+			preType = type;
+
+			if ( type === "focus" || type === "blur" ) {
+				types.push( liveMap[ type ] + namespaces );
+				type = type + namespaces;
+
+			} else {
+				type = (liveMap[ type ] || type) + namespaces;
+			}
+
+			if ( name === "live" ) {
+				// bind live handler
+				for ( var j = 0, l = context.length; j < l; j++ ) {
+					jQuery.event.add( context[j], "live." + liveConvert( type, selector ),
+						{ data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } );
+				}
+
+			} else {
+				// unbind live handler
+				context.unbind( "live." + liveConvert( type, selector ), fn );
+			}
+		}
+
+		return this;
+	};
+});
+
+function liveHandler( event ) {
+	var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret,
+		elems = [],
+		selectors = [],
+		events = jQuery._data( this, eventKey );
+
+	if ( typeof events === "function" ) {
+		events = events.events;
+	}
+
+	// Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911)
+	if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) {
+		return;
+	}
+
+	if ( event.namespace ) {
+		namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)");
+	}
+
+	event.liveFired = this;
+
+	var live = events.live.slice(0);
+
+	for ( j = 0; j < live.length; j++ ) {
+		handleObj = live[j];
+
+		if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) {
+			selectors.push( handleObj.selector );
+
+		} else {
+			live.splice( j--, 1 );
+		}
+	}
+
+	match = jQuery( event.target ).closest( selectors, event.currentTarget );
+
+	for ( i = 0, l = match.length; i < l; i++ ) {
+		close = match[i];
+
+		for ( j = 0; j < live.length; j++ ) {
+			handleObj = live[j];
+
+			if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) {
+				elem = close.elem;
+				related = null;
+
+				// Those two events require additional checking
+				if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) {
+					event.type = handleObj.preType;
+					related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0];
+				}
+
+				if ( !related || related !== elem ) {
+					elems.push({ elem: elem, handleObj: handleObj, level: close.level });
+				}
+			}
+		}
+	}
+
+	for ( i = 0, l = elems.length; i < l; i++ ) {
+		match = elems[i];
+
+		if ( maxLevel && match.level > maxLevel ) {
+			break;
+		}
+
+		event.currentTarget = match.elem;
+		event.data = match.handleObj.data;
+		event.handleObj = match.handleObj;
+
+		ret = match.handleObj.origHandler.apply( match.elem, arguments );
+
+		if ( ret === false || event.isPropagationStopped() ) {
+			maxLevel = match.level;
+
+			if ( ret === false ) {
+				stop = false;
+			}
+			if ( event.isImmediatePropagationStopped() ) {
+				break;
+			}
+		}
+	}
+
+	return stop;
+}
+
+function liveConvert( type, selector ) {
+	return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&");
+}
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup error").split(" "), function( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		if ( fn == null ) {
+			fn = data;
+			data = null;
+		}
+
+		return arguments.length > 0 ?
+			this.bind( name, data, fn ) :
+			this.trigger( name );
+	};
+
+	if ( jQuery.attrFn ) {
+		jQuery.attrFn[ name ] = true;
+	}
+});
+
+
+/*!
+ * Sizzle CSS Selector Engine
+ *  Copyright 2011, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+(function(){
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+	done = 0,
+	toString = Object.prototype.toString,
+	hasDuplicate = false,
+	baseHasDuplicate = true;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+//   Thus far that includes Google Chrome.
+[0, 0].sort(function() {
+	baseHasDuplicate = false;
+	return 0;
+});
+
+var Sizzle = function( selector, context, results, seed ) {
+	results = results || [];
+	context = context || document;
+
+	var origContext = context;
+
+	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+		return [];
+	}
+	
+	if ( !selector || typeof selector !== "string" ) {
+		return results;
+	}
+
+	var m, set, checkSet, extra, ret, cur, pop, i,
+		prune = true,
+		contextXML = Sizzle.isXML( context ),
+		parts = [],
+		soFar = selector;
+	
+	// Reset the position of the chunker regexp (start from head)
+	do {
+		chunker.exec( "" );
+		m = chunker.exec( soFar );
+
+		if ( m ) {
+			soFar = m[3];
+		
+			parts.push( m[1] );
+		
+			if ( m[2] ) {
+				extra = m[3];
+				break;
+			}
+		}
+	} while ( m );
+
+	if ( parts.length > 1 && origPOS.exec( selector ) ) {
+
+		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+			set = posProcess( parts[0] + parts[1], context );
+
+		} else {
+			set = Expr.relative[ parts[0] ] ?
+				[ context ] :
+				Sizzle( parts.shift(), context );
+
+			while ( parts.length ) {
+				selector = parts.shift();
+
+				if ( Expr.relative[ selector ] ) {
+					selector += parts.shift();
+				}
+				
+				set = posProcess( selector, set );
+			}
+		}
+
+	} else {
+		// Take a shortcut and set the context if the root selector is an ID
+		// (but not if it'll be faster if the inner selector is an ID)
+		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+
+			ret = Sizzle.find( parts.shift(), context, contextXML );
+			context = ret.expr ?
+				Sizzle.filter( ret.expr, ret.set )[0] :
+				ret.set[0];
+		}
+
+		if ( context ) {
+			ret = seed ?
+				{ expr: parts.pop(), set: makeArray(seed) } :
+				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+
+			set = ret.expr ?
+				Sizzle.filter( ret.expr, ret.set ) :
+				ret.set;
+
+			if ( parts.length > 0 ) {
+				checkSet = makeArray( set );
+
+			} else {
+				prune = false;
+			}
+
+			while ( parts.length ) {
+				cur = parts.pop();
+				pop = cur;
+
+				if ( !Expr.relative[ cur ] ) {
+					cur = "";
+				} else {
+					pop = parts.pop();
+				}
+
+				if ( pop == null ) {
+					pop = context;
+				}
+
+				Expr.relative[ cur ]( checkSet, pop, contextXML );
+			}
+
+		} else {
+			checkSet = parts = [];
+		}
+	}
+
+	if ( !checkSet ) {
+		checkSet = set;
+	}
+
+	if ( !checkSet ) {
+		Sizzle.error( cur || selector );
+	}
+
+	if ( toString.call(checkSet) === "[object Array]" ) {
+		if ( !prune ) {
+			results.push.apply( results, checkSet );
+
+		} else if ( context && context.nodeType === 1 ) {
+			for ( i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
+					results.push( set[i] );
+				}
+			}
+
+		} else {
+			for ( i = 0; checkSet[i] != null; i++ ) {
+				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+					results.push( set[i] );
+				}
+			}
+		}
+
+	} else {
+		makeArray( checkSet, results );
+	}
+
+	if ( extra ) {
+		Sizzle( extra, origContext, results, seed );
+		Sizzle.uniqueSort( results );
+	}
+
+	return results;
+};
+
+Sizzle.uniqueSort = function( results ) {
+	if ( sortOrder ) {
+		hasDuplicate = baseHasDuplicate;
+		results.sort( sortOrder );
+
+		if ( hasDuplicate ) {
+			for ( var i = 1; i < results.length; i++ ) {
+				if ( results[i] === results[ i - 1 ] ) {
+					results.splice( i--, 1 );
+				}
+			}
+		}
+	}
+
+	return results;
+};
+
+Sizzle.matches = function( expr, set ) {
+	return Sizzle( expr, null, null, set );
+};
+
+Sizzle.matchesSelector = function( node, expr ) {
+	return Sizzle( expr, null, null, [node] ).length > 0;
+};
+
+Sizzle.find = function( expr, context, isXML ) {
+	var set;
+
+	if ( !expr ) {
+		return [];
+	}
+
+	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+		var match,
+			type = Expr.order[i];
+		
+		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+			var left = match[1];
+			match.splice( 1, 1 );
+
+			if ( left.substr( left.length - 1 ) !== "\\" ) {
+				match[1] = (match[1] || "").replace(/\\/g, "");
+				set = Expr.find[ type ]( match, context, isXML );
+
+				if ( set != null ) {
+					expr = expr.replace( Expr.match[ type ], "" );
+					break;
+				}
+			}
+		}
+	}
+
+	if ( !set ) {
+		set = typeof context.getElementsByTagName !== "undefined" ?
+			context.getElementsByTagName( "*" ) :
+			[];
+	}
+
+	return { set: set, expr: expr };
+};
+
+Sizzle.filter = function( expr, set, inplace, not ) {
+	var match, anyFound,
+		old = expr,
+		result = [],
+		curLoop = set,
+		isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
+
+	while ( expr && set.length ) {
+		for ( var type in Expr.filter ) {
+			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
+				var found, item,
+					filter = Expr.filter[ type ],
+					left = match[1];
+
+				anyFound = false;
+
+				match.splice(1,1);
+
+				if ( left.substr( left.length - 1 ) === "\\" ) {
+					continue;
+				}
+
+				if ( curLoop === result ) {
+					result = [];
+				}
+
+				if ( Expr.preFilter[ type ] ) {
+					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+					if ( !match ) {
+						anyFound = found = true;
+
+					} else if ( match === true ) {
+						continue;
+					}
+				}
+
+				if ( match ) {
+					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+						if ( item ) {
+							found = filter( item, match, i, curLoop );
+							var pass = not ^ !!found;
+
+							if ( inplace && found != null ) {
+								if ( pass ) {
+									anyFound = true;
+
+								} else {
+									curLoop[i] = false;
+								}
+
+							} else if ( pass ) {
+								result.push( item );
+								anyFound = true;
+							}
+						}
+					}
+				}
+
+				if ( found !== undefined ) {
+					if ( !inplace ) {
+						curLoop = result;
+					}
+
+					expr = expr.replace( Expr.match[ type ], "" );
+
+					if ( !anyFound ) {
+						return [];
+					}
+
+					break;
+				}
+			}
+		}
+
+		// Improper expression
+		if ( expr === old ) {
+			if ( anyFound == null ) {
+				Sizzle.error( expr );
+
+			} else {
+				break;
+			}
+		}
+
+		old = expr;
+	}
+
+	return curLoop;
+};
+
+Sizzle.error = function( msg ) {
+	throw "Syntax error, unrecognized expression: " + msg;
+};
+
+var Expr = Sizzle.selectors = {
+	order: [ "ID", "NAME", "TAG" ],
+
+	match: {
+		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
+		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
+		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
+		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
+		CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
+		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
+		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
+	},
+
+	leftMatch: {},
+
+	attrMap: {
+		"class": "className",
+		"for": "htmlFor"
+	},
+
+	attrHandle: {
+		href: function( elem ) {
+			return elem.getAttribute( "href" );
+		}
+	},
+
+	relative: {
+		"+": function(checkSet, part){
+			var isPartStr = typeof part === "string",
+				isTag = isPartStr && !/\W/.test( part ),
+				isPartStrNotTag = isPartStr && !isTag;
+
+			if ( isTag ) {
+				part = part.toLowerCase();
+			}
+
+			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+				if ( (elem = checkSet[i]) ) {
+					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
+						elem || false :
+						elem === part;
+				}
+			}
+
+			if ( isPartStrNotTag ) {
+				Sizzle.filter( part, checkSet, true );
+			}
+		},
+
+		">": function( checkSet, part ) {
+			var elem,
+				isPartStr = typeof part === "string",
+				i = 0,
+				l = checkSet.length;
+
+			if ( isPartStr && !/\W/.test( part ) ) {
+				part = part.toLowerCase();
+
+				for ( ; i < l; i++ ) {
+					elem = checkSet[i];
+
+					if ( elem ) {
+						var parent = elem.parentNode;
+						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
+					}
+				}
+
+			} else {
+				for ( ; i < l; i++ ) {
+					elem = checkSet[i];
+
+					if ( elem ) {
+						checkSet[i] = isPartStr ?
+							elem.parentNode :
+							elem.parentNode === part;
+					}
+				}
+
+				if ( isPartStr ) {
+					Sizzle.filter( part, checkSet, true );
+				}
+			}
+		},
+
+		"": function(checkSet, part, isXML){
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !/\W/.test(part) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
+		},
+
+		"~": function( checkSet, part, isXML ) {
+			var nodeCheck,
+				doneName = done++,
+				checkFn = dirCheck;
+
+			if ( typeof part === "string" && !/\W/.test( part ) ) {
+				part = part.toLowerCase();
+				nodeCheck = part;
+				checkFn = dirNodeCheck;
+			}
+
+			checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
+		}
+	},
+
+	find: {
+		ID: function( match, context, isXML ) {
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+				// Check parentNode to catch when Blackberry 4.6 returns
+				// nodes that are no longer in the document #6963
+				return m && m.parentNode ? [m] : [];
+			}
+		},
+
+		NAME: function( match, context ) {
+			if ( typeof context.getElementsByName !== "undefined" ) {
+				var ret = [],
+					results = context.getElementsByName( match[1] );
+
+				for ( var i = 0, l = results.length; i < l; i++ ) {
+					if ( results[i].getAttribute("name") === match[1] ) {
+						ret.push( results[i] );
+					}
+				}
+
+				return ret.length === 0 ? null : ret;
+			}
+		},
+
+		TAG: function( match, context ) {
+			if ( typeof context.getElementsByTagName !== "undefined" ) {
+				return context.getElementsByTagName( match[1] );
+			}
+		}
+	},
+	preFilter: {
+		CLASS: function( match, curLoop, inplace, result, not, isXML ) {
+			match = " " + match[1].replace(/\\/g, "") + " ";
+
+			if ( isXML ) {
+				return match;
+			}
+
+			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+				if ( elem ) {
+					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
+						if ( !inplace ) {
+							result.push( elem );
+						}
+
+					} else if ( inplace ) {
+						curLoop[i] = false;
+					}
+				}
+			}
+
+			return false;
+		},
+
+		ID: function( match ) {
+			return match[1].replace(/\\/g, "");
+		},
+
+		TAG: function( match, curLoop ) {
+			return match[1].toLowerCase();
+		},
+
+		CHILD: function( match ) {
+			if ( match[1] === "nth" ) {
+				if ( !match[2] ) {
+					Sizzle.error( match[0] );
+				}
+
+				match[2] = match[2].replace(/^\+|\s*/g, '');
+
+				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+				var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
+					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
+					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+				// calculate the numbers (first)n+(last) including if they are negative
+				match[2] = (test[1] + (test[2] || 1)) - 0;
+				match[3] = test[3] - 0;
+			}
+			else if ( match[2] ) {
+				Sizzle.error( match[0] );
+			}
+
+			// TODO: Move to normal caching system
+			match[0] = done++;
+
+			return match;
+		},
+
+		ATTR: function( match, curLoop, inplace, result, not, isXML ) {
+			var name = match[1] = match[1].replace(/\\/g, "");
+			
+			if ( !isXML && Expr.attrMap[name] ) {
+				match[1] = Expr.attrMap[name];
+			}
+
+			// Handle if an un-quoted value was used
+			match[4] = ( match[4] || match[5] || "" ).replace(/\\/g, "");
+
+			if ( match[2] === "~=" ) {
+				match[4] = " " + match[4] + " ";
+			}
+
+			return match;
+		},
+
+		PSEUDO: function( match, curLoop, inplace, result, not ) {
+			if ( match[1] === "not" ) {
+				// If we're dealing with a complex expression, or a simple one
+				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+					match[3] = Sizzle(match[3], null, null, curLoop);
+
+				} else {
+					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+
+					if ( !inplace ) {
+						result.push.apply( result, ret );
+					}
+
+					return false;
+				}
+
+			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+				return true;
+			}
+			
+			return match;
+		},
+
+		POS: function( match ) {
+			match.unshift( true );
+
+			return match;
+		}
+	},
+	
+	filters: {
+		enabled: function( elem ) {
+			return elem.disabled === false && elem.type !== "hidden";
+		},
+
+		disabled: function( elem ) {
+			return elem.disabled === true;
+		},
+
+		checked: function( elem ) {
+			return elem.checked === true;
+		},
+		
+		selected: function( elem ) {
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			elem.parentNode.selectedIndex;
+			
+			return elem.selected === true;
+		},
+
+		parent: function( elem ) {
+			return !!elem.firstChild;
+		},
+
+		empty: function( elem ) {
+			return !elem.firstChild;
+		},
+
+		has: function( elem, i, match ) {
+			return !!Sizzle( match[3], elem ).length;
+		},
+
+		header: function( elem ) {
+			return (/h\d/i).test( elem.nodeName );
+		},
+
+		text: function( elem ) {
+			return "text" === elem.type;
+		},
+		radio: function( elem ) {
+			return "radio" === elem.type;
+		},
+
+		checkbox: function( elem ) {
+			return "checkbox" === elem.type;
+		},
+
+		file: function( elem ) {
+			return "file" === elem.type;
+		},
+		password: function( elem ) {
+			return "password" === elem.type;
+		},
+
+		submit: function( elem ) {
+			return "submit" === elem.type;
+		},
+
+		image: function( elem ) {
+			return "image" === elem.type;
+		},
+
+		reset: function( elem ) {
+			return "reset" === elem.type;
+		},
+
+		button: function( elem ) {
+			return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
+		},
+
+		input: function( elem ) {
+			return (/input|select|textarea|button/i).test( elem.nodeName );
+		}
+	},
+	setFilters: {
+		first: function( elem, i ) {
+			return i === 0;
+		},
+
+		last: function( elem, i, match, array ) {
+			return i === array.length - 1;
+		},
+
+		even: function( elem, i ) {
+			return i % 2 === 0;
+		},
+
+		odd: function( elem, i ) {
+			return i % 2 === 1;
+		},
+
+		lt: function( elem, i, match ) {
+			return i < match[3] - 0;
+		},
+
+		gt: function( elem, i, match ) {
+			return i > match[3] - 0;
+		},
+
+		nth: function( elem, i, match ) {
+			return match[3] - 0 === i;
+		},
+
+		eq: function( elem, i, match ) {
+			return match[3] - 0 === i;
+		}
+	},
+	filter: {
+		PSEUDO: function( elem, match, i, array ) {
+			var name = match[1],
+				filter = Expr.filters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+
+			} else if ( name === "contains" ) {
+				return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
+
+			} else if ( name === "not" ) {
+				var not = match[3];
+
+				for ( var j = 0, l = not.length; j < l; j++ ) {
+					if ( not[j] === elem ) {
+						return false;
+					}
+				}
+
+				return true;
+
+			} else {
+				Sizzle.error( name );
+			}
+		},
+
+		CHILD: function( elem, match ) {
+			var type = match[1],
+				node = elem;
+
+			switch ( type ) {
+				case "only":
+				case "first":
+					while ( (node = node.previousSibling) )	 {
+						if ( node.nodeType === 1 ) { 
+							return false; 
+						}
+					}
+
+					if ( type === "first" ) { 
+						return true; 
+					}
+
+					node = elem;
+
+				case "last":
+					while ( (node = node.nextSibling) )	 {
+						if ( node.nodeType === 1 ) { 
+							return false; 
+						}
+					}
+
+					return true;
+
+				case "nth":
+					var first = match[2],
+						last = match[3];
+
+					if ( first === 1 && last === 0 ) {
+						return true;
+					}
+					
+					var doneName = match[0],
+						parent = elem.parentNode;
+	
+					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+						var count = 0;
+						
+						for ( node = parent.firstChild; node; node = node.nextSibling ) {
+							if ( node.nodeType === 1 ) {
+								node.nodeIndex = ++count;
+							}
+						} 
+
+						parent.sizcache = doneName;
+					}
+					
+					var diff = elem.nodeIndex - last;
+
+					if ( first === 0 ) {
+						return diff === 0;
+
+					} else {
+						return ( diff % first === 0 && diff / first >= 0 );
+					}
+			}
+		},
+
+		ID: function( elem, match ) {
+			return elem.nodeType === 1 && elem.getAttribute("id") === match;
+		},
+
+		TAG: function( elem, match ) {
+			return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
+		},
+		
+		CLASS: function( elem, match ) {
+			return (" " + (elem.className || elem.getAttribute("class")) + " ")
+				.indexOf( match ) > -1;
+		},
+
+		ATTR: function( elem, match ) {
+			var name = match[1],
+				result = Expr.attrHandle[ name ] ?
+					Expr.attrHandle[ name ]( elem ) :
+					elem[ name ] != null ?
+						elem[ name ] :
+						elem.getAttribute( name ),
+				value = result + "",
+				type = match[2],
+				check = match[4];
+
+			return result == null ?
+				type === "!=" :
+				type === "=" ?
+				value === check :
+				type === "*=" ?
+				value.indexOf(check) >= 0 :
+				type === "~=" ?
+				(" " + value + " ").indexOf(check) >= 0 :
+				!check ?
+				value && result !== false :
+				type === "!=" ?
+				value !== check :
+				type === "^=" ?
+				value.indexOf(check) === 0 :
+				type === "$=" ?
+				value.substr(value.length - check.length) === check :
+				type === "|=" ?
+				value === check || value.substr(0, check.length + 1) === check + "-" :
+				false;
+		},
+
+		POS: function( elem, match, i, array ) {
+			var name = match[2],
+				filter = Expr.setFilters[ name ];
+
+			if ( filter ) {
+				return filter( elem, i, match, array );
+			}
+		}
+	}
+};
+
+var origPOS = Expr.match.POS,
+	fescape = function(all, num){
+		return "\\" + (num - 0 + 1);
+	};
+
+for ( var type in Expr.match ) {
+	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
+	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
+}
+
+var makeArray = function( array, results ) {
+	array = Array.prototype.slice.call( array, 0 );
+
+	if ( results ) {
+		results.push.apply( results, array );
+		return results;
+	}
+	
+	return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+// Also verifies that the returned array holds DOM nodes
+// (which is not the case in the Blackberry browser)
+try {
+	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
+
+// Provide a fallback method if it does not work
+} catch( e ) {
+	makeArray = function( array, results ) {
+		var i = 0,
+			ret = results || [];
+
+		if ( toString.call(array) === "[object Array]" ) {
+			Array.prototype.push.apply( ret, array );
+
+		} else {
+			if ( typeof array.length === "number" ) {
+				for ( var l = array.length; i < l; i++ ) {
+					ret.push( array[i] );
+				}
+
+			} else {
+				for ( ; array[i]; i++ ) {
+					ret.push( array[i] );
+				}
+			}
+		}
+
+		return ret;
+	};
+}
+
+var sortOrder, siblingCheck;
+
+if ( document.documentElement.compareDocumentPosition ) {
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+			return a.compareDocumentPosition ? -1 : 1;
+		}
+
+		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
+	};
+
+} else {
+	sortOrder = function( a, b ) {
+		var al, bl,
+			ap = [],
+			bp = [],
+			aup = a.parentNode,
+			bup = b.parentNode,
+			cur = aup;
+
+		// The nodes are identical, we can exit early
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+
+		// If the nodes are siblings (or identical) we can do a quick check
+		} else if ( aup === bup ) {
+			return siblingCheck( a, b );
+
+		// If no parents were found then the nodes are disconnected
+		} else if ( !aup ) {
+			return -1;
+
+		} else if ( !bup ) {
+			return 1;
+		}
+
+		// Otherwise they're somewhere else in the tree so we need
+		// to build up a full list of the parentNodes for comparison
+		while ( cur ) {
+			ap.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		cur = bup;
+
+		while ( cur ) {
+			bp.unshift( cur );
+			cur = cur.parentNode;
+		}
+
+		al = ap.length;
+		bl = bp.length;
+
+		// Start walking down the tree looking for a discrepancy
+		for ( var i = 0; i < al && i < bl; i++ ) {
+			if ( ap[i] !== bp[i] ) {
+				return siblingCheck( ap[i], bp[i] );
+			}
+		}
+
+		// We ended someplace up the tree so do a sibling check
+		return i === al ?
+			siblingCheck( a, bp[i], -1 ) :
+			siblingCheck( ap[i], b, 1 );
+	};
+
+	siblingCheck = function( a, b, ret ) {
+		if ( a === b ) {
+			return ret;
+		}
+
+		var cur = a.nextSibling;
+
+		while ( cur ) {
+			if ( cur === b ) {
+				return -1;
+			}
+
+			cur = cur.nextSibling;
+		}
+
+		return 1;
+	};
+}
+
+// Utility function for retreiving the text value of an array of DOM nodes
+Sizzle.getText = function( elems ) {
+	var ret = "", elem;
+
+	for ( var i = 0; elems[i]; i++ ) {
+		elem = elems[i];
+
+		// Get the text from text nodes and CDATA nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+			ret += elem.nodeValue;
+
+		// Traverse everything else, except comment nodes
+		} else if ( elem.nodeType !== 8 ) {
+			ret += Sizzle.getText( elem.childNodes );
+		}
+	}
+
+	return ret;
+};
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+	// We're going to inject a fake input element with a specified name
+	var form = document.createElement("div"),
+		id = "script" + (new Date()).getTime(),
+		root = document.documentElement;
+
+	form.innerHTML = "<a name='" + id + "'/>";
+
+	// Inject it into the root element, check its status, and remove it quickly
+	root.insertBefore( form, root.firstChild );
+
+	// The workaround has to do additional checks after a getElementById
+	// Which slows things down for other browsers (hence the branching)
+	if ( document.getElementById( id ) ) {
+		Expr.find.ID = function( match, context, isXML ) {
+			if ( typeof context.getElementById !== "undefined" && !isXML ) {
+				var m = context.getElementById(match[1]);
+
+				return m ?
+					m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
+						[m] :
+						undefined :
+					[];
+			}
+		};
+
+		Expr.filter.ID = function( elem, match ) {
+			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+
+			return elem.nodeType === 1 && node && node.nodeValue === match;
+		};
+	}
+
+	root.removeChild( form );
+
+	// release memory in IE
+	root = form = null;
+})();
+
+(function(){
+	// Check to see if the browser returns only elements
+	// when doing getElementsByTagName("*")
+
+	// Create a fake element
+	var div = document.createElement("div");
+	div.appendChild( document.createComment("") );
+
+	// Make sure no comments are found
+	if ( div.getElementsByTagName("*").length > 0 ) {
+		Expr.find.TAG = function( match, context ) {
+			var results = context.getElementsByTagName( match[1] );
+
+			// Filter out possible comments
+			if ( match[1] === "*" ) {
+				var tmp = [];
+
+				for ( var i = 0; results[i]; i++ ) {
+					if ( results[i].nodeType === 1 ) {
+						tmp.push( results[i] );
+					}
+				}
+
+				results = tmp;
+			}
+
+			return results;
+		};
+	}
+
+	// Check to see if an attribute returns normalized href attributes
+	div.innerHTML = "<a href='#'></a>";
+
+	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+			div.firstChild.getAttribute("href") !== "#" ) {
+
+		Expr.attrHandle.href = function( elem ) {
+			return elem.getAttribute( "href", 2 );
+		};
+	}
+
+	// release memory in IE
+	div = null;
+})();
+
+if ( document.querySelectorAll ) {
+	(function(){
+		var oldSizzle = Sizzle,
+			div = document.createElement("div"),
+			id = "__sizzle__";
+
+		div.innerHTML = "<p class='TEST'></p>";
+
+		// Safari can't handle uppercase or unicode characters when
+		// in quirks mode.
+		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+			return;
+		}
+	
+		Sizzle = function( query, context, extra, seed ) {
+			context = context || document;
+
+			// Only use querySelectorAll on non-XML documents
+			// (ID selectors don't work in non-HTML documents)
+			if ( !seed && !Sizzle.isXML(context) ) {
+				// See if we find a selector to speed up
+				var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
+				
+				if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
+					// Speed-up: Sizzle("TAG")
+					if ( match[1] ) {
+						return makeArray( context.getElementsByTagName( query ), extra );
+					
+					// Speed-up: Sizzle(".CLASS")
+					} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
+						return makeArray( context.getElementsByClassName( match[2] ), extra );
+					}
+				}
+				
+				if ( context.nodeType === 9 ) {
+					// Speed-up: Sizzle("body")
+					// The body element only exists once, optimize finding it
+					if ( query === "body" && context.body ) {
+						return makeArray( [ context.body ], extra );
+						
+					// Speed-up: Sizzle("#ID")
+					} else if ( match && match[3] ) {
+						var elem = context.getElementById( match[3] );
+
+						// Check parentNode to catch when Blackberry 4.6 returns
+						// nodes that are no longer in the document #6963
+						if ( elem && elem.parentNode ) {
+							// Handle the case where IE and Opera return items
+							// by name instead of ID
+							if ( elem.id === match[3] ) {
+								return makeArray( [ elem ], extra );
+							}
+							
+						} else {
+							return makeArray( [], extra );
+						}
+					}
+					
+					try {
+						return makeArray( context.querySelectorAll(query), extra );
+					} catch(qsaError) {}
+
+				// qSA works strangely on Element-rooted queries
+				// We can work around this by specifying an extra ID on the root
+				// and working up from there (Thanks to Andrew Dupont for the technique)
+				// IE 8 doesn't work on object elements
+				} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+					var old = context.getAttribute( "id" ),
+						nid = old || id,
+						hasParent = context.parentNode,
+						relativeHierarchySelector = /^\s*[+~]/.test( query );
+
+					if ( !old ) {
+						context.setAttribute( "id", nid );
+					} else {
+						nid = nid.replace( /'/g, "\\$&" );
+					}
+					if ( relativeHierarchySelector && hasParent ) {
+						context = context.parentNode;
+					}
+
+					try {
+						if ( !relativeHierarchySelector || hasParent ) {
+							return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
+						}
+
+					} catch(pseudoError) {
+					} finally {
+						if ( !old ) {
+							context.removeAttribute( "id" );
+						}
+					}
+				}
+			}
+		
+			return oldSizzle(query, context, extra, seed);
+		};
+
+		for ( var prop in oldSizzle ) {
+			Sizzle[ prop ] = oldSizzle[ prop ];
+		}
+
+		// release memory in IE
+		div = null;
+	})();
+}
+
+(function(){
+	var html = document.documentElement,
+		matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector,
+		pseudoWorks = false;
+
+	try {
+		// This should fail with an exception
+		// Gecko does not error, returns false instead
+		matches.call( document.documentElement, "[test!='']:sizzle" );
+	
+	} catch( pseudoError ) {
+		pseudoWorks = true;
+	}
+
+	if ( matches ) {
+		Sizzle.matchesSelector = function( node, expr ) {
+			// Make sure that attribute selectors are quoted
+			expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
+
+			if ( !Sizzle.isXML( node ) ) {
+				try { 
+					if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
+						return matches.call( node, expr );
+					}
+				} catch(e) {}
+			}
+
+			return Sizzle(expr, null, null, [node]).length > 0;
+		};
+	}
+})();
+
+(function(){
+	var div = document.createElement("div");
+
+	div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+	// Opera can't find a second classname (in 9.6)
+	// Also, make sure that getElementsByClassName actually exists
+	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
+		return;
+	}
+
+	// Safari caches class attributes, doesn't catch changes (in 3.2)
+	div.lastChild.className = "e";
+
+	if ( div.getElementsByClassName("e").length === 1 ) {
+		return;
+	}
+	
+	Expr.order.splice(1, 0, "CLASS");
+	Expr.find.CLASS = function( match, context, isXML ) {
+		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+			return context.getElementsByClassName(match[1]);
+		}
+	};
+
+	// release memory in IE
+	div = null;
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+
+			elem = elem[dir];
+
+			while ( elem ) {
+				if ( elem.sizcache === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 && !isXML ){
+					elem.sizcache = doneName;
+					elem.sizset = i;
+				}
+
+				if ( elem.nodeName.toLowerCase() === cur ) {
+					match = elem;
+					break;
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+		var elem = checkSet[i];
+
+		if ( elem ) {
+			var match = false;
+			
+			elem = elem[dir];
+
+			while ( elem ) {
+				if ( elem.sizcache === doneName ) {
+					match = checkSet[elem.sizset];
+					break;
+				}
+
+				if ( elem.nodeType === 1 ) {
+					if ( !isXML ) {
+						elem.sizcache = doneName;
+						elem.sizset = i;
+					}
+
+					if ( typeof cur !== "string" ) {
+						if ( elem === cur ) {
+							match = true;
+							break;
+						}
+
+					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+						match = elem;
+						break;
+					}
+				}
+
+				elem = elem[dir];
+			}
+
+			checkSet[i] = match;
+		}
+	}
+}
+
+if ( document.documentElement.contains ) {
+	Sizzle.contains = function( a, b ) {
+		return a !== b && (a.contains ? a.contains(b) : true);
+	};
+
+} else if ( document.documentElement.compareDocumentPosition ) {
+	Sizzle.contains = function( a, b ) {
+		return !!(a.compareDocumentPosition(b) & 16);
+	};
+
+} else {
+	Sizzle.contains = function() {
+		return false;
+	};
+}
+
+Sizzle.isXML = function( elem ) {
+	// documentElement is verified for cases where it doesn't yet exist
+	// (such as loading iframes in IE - #4833) 
+	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
+
+	return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+var posProcess = function( selector, context ) {
+	var match,
+		tmpSet = [],
+		later = "",
+		root = context.nodeType ? [context] : context;
+
+	// Position selectors must be done after the filter
+	// And so must :not(positional) so we move all PSEUDOs to the end
+	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+		later += match[0];
+		selector = selector.replace( Expr.match.PSEUDO, "" );
+	}
+
+	selector = Expr.relative[selector] ? selector + "*" : selector;
+
+	for ( var i = 0, l = root.length; i < l; i++ ) {
+		Sizzle( selector, root[i], tmpSet );
+	}
+
+	return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.filters;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})();
+
+
+var runtil = /Until$/,
+	rparentsprev = /^(?:parents|prevUntil|prevAll)/,
+	// Note: This RegExp should be improved, or likely pulled from Sizzle
+	rmultiselector = /,/,
+	isSimple = /^.[^:#\[\.,]*$/,
+	slice = Array.prototype.slice,
+	POS = jQuery.expr.match.POS,
+	// methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.fn.extend({
+	find: function( selector ) {
+		var ret = this.pushStack( "", "find", selector ),
+			length = 0;
+
+		for ( var i = 0, l = this.length; i < l; i++ ) {
+			length = ret.length;
+			jQuery.find( selector, this[i], ret );
+
+			if ( i > 0 ) {
+				// Make sure that the results are unique
+				for ( var n = length; n < ret.length; n++ ) {
+					for ( var r = 0; r < length; r++ ) {
+						if ( ret[r] === ret[n] ) {
+							ret.splice(n--, 1);
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	has: function( target ) {
+		var targets = jQuery( target );
+		return this.filter(function() {
+			for ( var i = 0, l = targets.length; i < l; i++ ) {
+				if ( jQuery.contains( this, targets[i] ) ) {
+					return true;
+				}
+			}
+		});
+	},
+
+	not: function( selector ) {
+		return this.pushStack( winnow(this, selector, false), "not", selector);
+	},
+
+	filter: function( selector ) {
+		return this.pushStack( winnow(this, selector, true), "filter", selector );
+	},
+
+	is: function( selector ) {
+		return !!selector && jQuery.filter( selector, this ).length > 0;
+	},
+
+	closest: function( selectors, context ) {
+		var ret = [], i, l, cur = this[0];
+
+		if ( jQuery.isArray( selectors ) ) {
+			var match, selector,
+				matches = {},
+				level = 1;
+
+			if ( cur && selectors.length ) {
+				for ( i = 0, l = selectors.length; i < l; i++ ) {
+					selector = selectors[i];
+
+					if ( !matches[selector] ) {
+						matches[selector] = jQuery.expr.match.POS.test( selector ) ?
+							jQuery( selector, context || this.context ) :
+							selector;
+					}
+				}
+
+				while ( cur && cur.ownerDocument && cur !== context ) {
+					for ( selector in matches ) {
+						match = matches[selector];
+
+						if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) {
+							ret.push({ selector: selector, elem: cur, level: level });
+						}
+					}
+
+					cur = cur.parentNode;
+					level++;
+				}
+			}
+
+			return ret;
+		}
+
+		var pos = POS.test( selectors ) ?
+			jQuery( selectors, context || this.context ) : null;
+
+		for ( i = 0, l = this.length; i < l; i++ ) {
+			cur = this[i];
+
+			while ( cur ) {
+				if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+					ret.push( cur );
+					break;
+
+				} else {
+					cur = cur.parentNode;
+					if ( !cur || !cur.ownerDocument || cur === context ) {
+						break;
+					}
+				}
+			}
+		}
+
+		ret = ret.length > 1 ? jQuery.unique(ret) : ret;
+
+		return this.pushStack( ret, "closest", selectors );
+	},
+
+	// Determine the position of an element within
+	// the matched set of elements
+	index: function( elem ) {
+		if ( !elem || typeof elem === "string" ) {
+			return jQuery.inArray( this[0],
+				// If it receives a string, the selector is used
+				// If it receives nothing, the siblings are used
+				elem ? jQuery( elem ) : this.parent().children() );
+		}
+		// Locate the position of the desired element
+		return jQuery.inArray(
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[0] : elem, this );
+	},
+
+	add: function( selector, context ) {
+		var set = typeof selector === "string" ?
+				jQuery( selector, context ) :
+				jQuery.makeArray( selector ),
+			all = jQuery.merge( this.get(), set );
+
+		return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+			all :
+			jQuery.unique( all ) );
+	},
+
+	andSelf: function() {
+		return this.add( this.prevObject );
+	}
+});
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+	return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+jQuery.each({
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return jQuery.dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return jQuery.nth( elem, 2, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return jQuery.nth( elem, 2, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return jQuery.dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return jQuery.dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return jQuery.sibling( elem.parentNode.firstChild, elem );
+	},
+	children: function( elem ) {
+		return jQuery.sibling( elem.firstChild );
+	},
+	contents: function( elem ) {
+		return jQuery.nodeName( elem, "iframe" ) ?
+			elem.contentDocument || elem.contentWindow.document :
+			jQuery.makeArray( elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var ret = jQuery.map( this, fn, until ),
+                // The variable 'args' was introduced in
+                // https://github.com/jquery/jquery/commit/52a0238
+                // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed.
+                // http://code.google.com/p/v8/issues/detail?id=1050
+                    args = slice.call(arguments);
+
+		if ( !runtil.test( name ) ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			ret = jQuery.filter( selector, ret );
+		}
+
+		ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+		if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
+			ret = ret.reverse();
+		}
+
+		return this.pushStack( ret, name, args.join(",") );
+	};
+});
+
+jQuery.extend({
+	filter: function( expr, elems, not ) {
+		if ( not ) {
+			expr = ":not(" + expr + ")";
+		}
+
+		return elems.length === 1 ?
+			jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+			jQuery.find.matches(expr, elems);
+	},
+
+	dir: function( elem, dir, until ) {
+		var matched = [],
+			cur = elem[ dir ];
+
+		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+			if ( cur.nodeType === 1 ) {
+				matched.push( cur );
+			}
+			cur = cur[dir];
+		}
+		return matched;
+	},
+
+	nth: function( cur, result, dir, elem ) {
+		result = result || 1;
+		var num = 0;
+
+		for ( ; cur; cur = cur[dir] ) {
+			if ( cur.nodeType === 1 && ++num === result ) {
+				break;
+			}
+		}
+
+		return cur;
+	},
+
+	sibling: function( n, elem ) {
+		var r = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				r.push( n );
+			}
+		}
+
+		return r;
+	}
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+	if ( jQuery.isFunction( qualifier ) ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			var retVal = !!qualifier.call( elem, i, elem );
+			return retVal === keep;
+		});
+
+	} else if ( qualifier.nodeType ) {
+		return jQuery.grep(elements, function( elem, i ) {
+			return (elem === qualifier) === keep;
+		});
+
+	} else if ( typeof qualifier === "string" ) {
+		var filtered = jQuery.grep(elements, function( elem ) {
+			return elem.nodeType === 1;
+		});
+
+		if ( isSimple.test( qualifier ) ) {
+			return jQuery.filter(qualifier, filtered, !keep);
+		} else {
+			qualifier = jQuery.filter( qualifier, filtered );
+		}
+	}
+
+	return jQuery.grep(elements, function( elem, i ) {
+		return (jQuery.inArray( elem, qualifier ) >= 0) === keep;
+	});
+}
+
+
+
+
+var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
+	rleadingWhitespace = /^\s+/,
+	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
+	rtagName = /<([\w:]+)/,
+	rtbody = /<tbody/i,
+	rhtml = /<|&#?\w+;/,
+	rnocache = /<(?:script|object|embed|option|style)/i,
+	// checked="checked" or checked (html5)
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	wrapMap = {
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+		legend: [ 1, "<fieldset>", "</fieldset>" ],
+		thead: [ 1, "<table>", "</table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+		area: [ 1, "<map>", "</map>" ],
+		_default: [ 0, "", "" ]
+	};
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE can't serialize <link> and <script> tags normally
+if ( !jQuery.support.htmlSerialize ) {
+	wrapMap._default = [ 1, "div<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+	text: function( text ) {
+		if ( jQuery.isFunction(text) ) {
+			return this.each(function(i) {
+				var self = jQuery( this );
+
+				self.text( text.call(this, i, self.text()) );
+			});
+		}
+
+		if ( typeof text !== "object" && text !== undefined ) {
+			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
+		}
+
+		return jQuery.text( this );
+	},
+
+	wrapAll: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[0] ) {
+			// The elements to wrap the target around
+			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+			if ( this[0].parentNode ) {
+				wrap.insertBefore( this[0] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+					elem = elem.firstChild;
+				}
+
+				return elem;
+			}).append(this);
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function(i) {
+				jQuery(this).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		return this.each(function() {
+			jQuery( this ).wrapAll( html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	},
+
+	append: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip(arguments, true, function( elem ) {
+			if ( this.nodeType === 1 ) {
+				this.insertBefore( elem, this.firstChild );
+			}
+		});
+	},
+
+	before: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this );
+			});
+		} else if ( arguments.length ) {
+			var set = jQuery(arguments[0]);
+			set.push.apply( set, this.toArray() );
+			return this.pushStack( set, "before", arguments );
+		}
+	},
+
+	after: function() {
+		if ( this[0] && this[0].parentNode ) {
+			return this.domManip(arguments, false, function( elem ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			});
+		} else if ( arguments.length ) {
+			var set = this.pushStack( this, "after", arguments );
+			set.push.apply( set, jQuery(arguments[0]).toArray() );
+			return set;
+		}
+	},
+
+	// keepData is for internal use only--do not document
+	remove: function( selector, keepData ) {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+				if ( !keepData && elem.nodeType === 1 ) {
+					jQuery.cleanData( elem.getElementsByTagName("*") );
+					jQuery.cleanData( [ elem ] );
+				}
+
+				if ( elem.parentNode ) {
+					 elem.parentNode.removeChild( elem );
+				}
+			}
+		}
+
+		return this;
+	},
+
+	empty: function() {
+		for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
+			// Remove element nodes and prevent memory leaks
+			if ( elem.nodeType === 1 ) {
+				jQuery.cleanData( elem.getElementsByTagName("*") );
+			}
+
+			// Remove any remaining nodes
+			while ( elem.firstChild ) {
+				elem.removeChild( elem.firstChild );
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? true : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map( function () {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		if ( value === undefined ) {
+			return this[0] && this[0].nodeType === 1 ?
+				this[0].innerHTML.replace(rinlinejQuery, "") :
+				null;
+
+		// See if we can take a shortcut and just use innerHTML
+		} else if ( typeof value === "string" && !rnocache.test( value ) &&
+			(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
+			!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
+
+			value = value.replace(rxhtmlTag, "<$1></$2>");
+
+			try {
+				for ( var i = 0, l = this.length; i < l; i++ ) {
+					// Remove element nodes and prevent memory leaks
+					if ( this[i].nodeType === 1 ) {
+						jQuery.cleanData( this[i].getElementsByTagName("*") );
+						this[i].innerHTML = value;
+					}
+				}
+
+			// If using innerHTML throws an exception, use the fallback method
+			} catch(e) {
+				this.empty().append( value );
+			}
+
+		} else if ( jQuery.isFunction( value ) ) {
+			this.each(function(i){
+				var self = jQuery( this );
+
+				self.html( value.call(this, i, self.html()) );
+			});
+
+		} else {
+			this.empty().append( value );
+		}
+
+		return this;
+	},
+
+	replaceWith: function( value ) {
+		if ( this[0] && this[0].parentNode ) {
+			// Make sure that the elements are removed from the DOM before they are inserted
+			// this can help fix replacing a parent with child elements
+			if ( jQuery.isFunction( value ) ) {
+				return this.each(function(i) {
+					var self = jQuery(this), old = self.html();
+					self.replaceWith( value.call( this, i, old ) );
+				});
+			}
+
+			if ( typeof value !== "string" ) {
+				value = jQuery( value ).detach();
+			}
+
+			return this.each(function() {
+				var next = this.nextSibling,
+					parent = this.parentNode;
+
+				jQuery( this ).remove();
+
+				if ( next ) {
+					jQuery(next).before( value );
+				} else {
+					jQuery(parent).append( value );
+				}
+			});
+		} else {
+			return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value );
+		}
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, table, callback ) {
+		var results, first, fragment, parent,
+			value = args[0],
+			scripts = [];
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
+			return this.each(function() {
+				jQuery(this).domManip( args, table, callback, true );
+			});
+		}
+
+		if ( jQuery.isFunction(value) ) {
+			return this.each(function(i) {
+				var self = jQuery(this);
+				args[0] = value.call(this, i, table ? self.html() : undefined);
+				self.domManip( args, table, callback );
+			});
+		}
+
+		if ( this[0] ) {
+			parent = value && value.parentNode;
+
+			// If we're in a fragment, just use that instead of building a new one
+			if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
+				results = { fragment: parent };
+
+			} else {
+				results = jQuery.buildFragment( args, this, scripts );
+			}
+
+			fragment = results.fragment;
+
+			if ( fragment.childNodes.length === 1 ) {
+				first = fragment = fragment.firstChild;
+			} else {
+				first = fragment.firstChild;
+			}
+
+			if ( first ) {
+				table = table && jQuery.nodeName( first, "tr" );
+
+				for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
+					callback.call(
+						table ?
+							root(this[i], first) :
+							this[i],
+						// Make sure that we do not leak memory by inadvertently discarding
+						// the original fragment (which might have attached data) instead of
+						// using it; in addition, use the original fragment object for the last
+						// item instead of first because it can end up being emptied incorrectly
+						// in certain situations (Bug #8070).
+						// Fragments from the fragment cache must always be cloned and never used
+						// in place.
+						results.cacheable || (l > 1 && i < lastIndex) ?
+							jQuery.clone( fragment, true, true ) :
+							fragment
+					);
+				}
+			}
+
+			if ( scripts.length ) {
+				jQuery.each( scripts, evalScript );
+			}
+		}
+
+		return this;
+	}
+});
+
+function root( elem, cur ) {
+	return jQuery.nodeName(elem, "table") ?
+		(elem.getElementsByTagName("tbody")[0] ||
+		elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
+		elem;
+}
+
+function cloneCopyEvent( src, dest ) {
+
+	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+		return;
+	}
+
+	var internalKey = jQuery.expando,
+			oldData = jQuery.data( src ),
+			curData = jQuery.data( dest, oldData );
+
+	// Switch to use the internal data object, if it exists, for the next
+	// stage of data copying
+	if ( (oldData = oldData[ internalKey ]) ) {
+		var events = oldData.events;
+				curData = curData[ internalKey ] = jQuery.extend({}, oldData);
+
+		if ( events ) {
+			delete curData.handle;
+			curData.events = {};
+
+			for ( var type in events ) {
+				for ( var i = 0, l = events[ type ].length; i < l; i++ ) {
+					jQuery.event.add( dest, type, events[ type ][ i ], events[ type ][ i ].data );
+				}
+			}
+		}
+	}
+}
+
+function cloneFixAttributes(src, dest) {
+	// We do not need to do anything for non-Elements
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	var nodeName = dest.nodeName.toLowerCase();
+
+	// clearAttributes removes the attributes, which we don't want,
+	// but also removes the attachEvent events, which we *do* want
+	dest.clearAttributes();
+
+	// mergeAttributes, in contrast, only merges back on the
+	// original attributes, not the events
+	dest.mergeAttributes(src);
+
+	// IE6-8 fail to clone children inside object elements that use
+	// the proprietary classid attribute value (rather than the type
+	// attribute) to identify the type of content to display
+	if ( nodeName === "object" ) {
+		dest.outerHTML = src.outerHTML;
+
+	} else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
+		// IE6-8 fails to persist the checked state of a cloned checkbox
+		// or radio button. Worse, IE6-7 fail to give the cloned element
+		// a checked appearance if the defaultChecked value isn't also set
+		if ( src.checked ) {
+			dest.defaultChecked = dest.checked = src.checked;
+		}
+
+		// IE6-7 get confused and end up setting the value of a cloned
+		// checkbox/radio button to an empty string instead of "on"
+		if ( dest.value !== src.value ) {
+			dest.value = src.value;
+		}
+
+	// IE6-8 fails to return the selected option to the default selected
+	// state when cloning options
+	} else if ( nodeName === "option" ) {
+		dest.selected = src.defaultSelected;
+
+	// IE6-8 fails to set the defaultValue to the correct value when
+	// cloning other types of input fields
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+
+	// Event data gets referenced instead of copied if the expando
+	// gets copied too
+	dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, nodes, scripts ) {
+	var fragment, cacheable, cacheresults,
+		doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document);
+
+	// Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+	// Cloning options loses the selected state, so don't cache them
+	// IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+	// Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+	if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document &&
+		args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) {
+
+		cacheable = true;
+		cacheresults = jQuery.fragments[ args[0] ];
+		if ( cacheresults ) {
+			if ( cacheresults !== 1 ) {
+				fragment = cacheresults;
+			}
+		}
+	}
+
+	if ( !fragment ) {
+		fragment = doc.createDocumentFragment();
+		jQuery.clean( args, doc, fragment, scripts );
+	}
+
+	if ( cacheable ) {
+		jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1;
+	}
+
+	return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var ret = [],
+			insert = jQuery( selector ),
+			parent = this.length === 1 && this[0].parentNode;
+
+		if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+			insert[ original ]( this[0] );
+			return this;
+
+		} else {
+			for ( var i = 0, l = insert.length; i < l; i++ ) {
+				var elems = (i > 0 ? this.clone(true) : this).get();
+				jQuery( insert[i] )[ original ]( elems );
+				ret = ret.concat( elems );
+			}
+
+			return this.pushStack( ret, name, insert.selector );
+		}
+	};
+});
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var clone = elem.cloneNode(true),
+				srcElements,
+				destElements,
+				i;
+
+		if ( !jQuery.support.noCloneEvent && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+			// IE copies events bound via attachEvent when using cloneNode.
+			// Calling detachEvent on the clone will also remove the events
+			// from the original. In order to get around this, we use some
+			// proprietary methods to clear the events. Thanks to MooTools
+			// guys for this hotness.
+
+			// Using Sizzle here is crazy slow, so we use getElementsByTagName
+			// instead
+			srcElements = elem.getElementsByTagName("*");
+			destElements = clone.getElementsByTagName("*");
+
+			// Weird iteration because IE will replace the length property
+			// with an element if you are cloning the body and one of the
+			// elements on the page has a name or id of "length"
+			for ( i = 0; srcElements[i]; ++i ) {
+				cloneFixAttributes( srcElements[i], destElements[i] );
+			}
+
+			cloneFixAttributes( elem, clone );
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+
+			cloneCopyEvent( elem, clone );
+
+			if ( deepDataAndEvents && "getElementsByTagName" in elem ) {
+
+				srcElements = elem.getElementsByTagName("*");
+				destElements = clone.getElementsByTagName("*");
+
+				if ( srcElements.length ) {
+					for ( i = 0; srcElements[i]; ++i ) {
+						cloneCopyEvent( srcElements[i], destElements[i] );
+					}
+				}
+			}
+		}
+		// Return the cloned set
+		return clone;
+  },
+	clean: function( elems, context, fragment, scripts ) {
+		context = context || document;
+
+		// !context.createElement fails in IE with an error but returns typeof 'object'
+		if ( typeof context.createElement === "undefined" ) {
+			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
+		}
+
+		var ret = [];
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( typeof elem === "number" ) {
+				elem += "";
+			}
+
+			if ( !elem ) {
+				continue;
+			}
+
+			// Convert html string into DOM nodes
+			if ( typeof elem === "string" && !rhtml.test( elem ) ) {
+				elem = context.createTextNode( elem );
+
+			} else if ( typeof elem === "string" ) {
+				// Fix "XHTML"-style tags in all browsers
+				elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+				// Trim whitespace, otherwise indexOf won't work as expected
+				var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(),
+					wrap = wrapMap[ tag ] || wrapMap._default,
+					depth = wrap[0],
+					div = context.createElement("div");
+
+				// Go to html and back, then peel off extra wrappers
+				div.innerHTML = wrap[1] + elem + wrap[2];
+
+				// Move to the right depth
+				while ( depth-- ) {
+					div = div.lastChild;
+				}
+
+				// Remove IE's autoinserted <tbody> from table fragments
+				if ( !jQuery.support.tbody ) {
+
+					// String was a <table>, *may* have spurious <tbody>
+					var hasBody = rtbody.test(elem),
+						tbody = tag === "table" && !hasBody ?
+							div.firstChild && div.firstChild.childNodes :
+
+							// String was a bare <thead> or <tfoot>
+							wrap[1] === "<table>" && !hasBody ?
+								div.childNodes :
+								[];
+
+					for ( var j = tbody.length - 1; j >= 0 ; --j ) {
+						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+							tbody[ j ].parentNode.removeChild( tbody[ j ] );
+						}
+					}
+
+				}
+
+				// IE completely kills leading whitespace when innerHTML is used
+				if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+					div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+				}
+
+				elem = div.childNodes;
+			}
+
+			if ( elem.nodeType ) {
+				ret.push( elem );
+			} else {
+				ret = jQuery.merge( ret, elem );
+			}
+		}
+
+		if ( fragment ) {
+			for ( i = 0; ret[i]; i++ ) {
+				if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
+					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
+
+				} else {
+					if ( ret[i].nodeType === 1 ) {
+						ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
+					}
+					fragment.appendChild( ret[i] );
+				}
+			}
+		}
+
+		return ret;
+	},
+
+	cleanData: function( elems ) {
+		var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special,
+			deleteExpando = jQuery.support.deleteExpando;
+
+		for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
+			if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
+				continue;
+			}
+
+			id = elem[ jQuery.expando ];
+
+			if ( id ) {
+				data = cache[ id ] && cache[ id ][ internalKey ];
+
+				if ( data && data.events ) {
+					for ( var type in data.events ) {
+						if ( special[ type ] ) {
+							jQuery.event.remove( elem, type );
+
+						// This is a shortcut to avoid jQuery.event.remove's overhead
+						} else {
+							jQuery.removeEvent( elem, type, data.handle );
+						}
+					}
+
+					// Null the DOM reference to avoid IE6/7/8 leak (#7054)
+					if ( data.handle ) {
+						data.handle.elem = null;
+					}
+				}
+
+				if ( deleteExpando ) {
+					delete elem[ jQuery.expando ];
+
+				} else if ( elem.removeAttribute ) {
+					elem.removeAttribute( jQuery.expando );
+				}
+
+				delete cache[ id ];
+			}
+		}
+	}
+});
+
+function evalScript( i, elem ) {
+	if ( elem.src ) {
+		jQuery.ajax({
+			url: elem.src,
+			async: false,
+			dataType: "script"
+		});
+	} else {
+		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );
+	}
+
+	if ( elem.parentNode ) {
+		elem.parentNode.removeChild( elem );
+	}
+}
+
+
+
+
+var ralpha = /alpha\([^)]*\)/i,
+	ropacity = /opacity=([^)]*)/,
+	rdashAlpha = /-([a-z])/ig,
+	rupper = /([A-Z])/g,
+	rnumpx = /^-?\d+(?:px)?$/i,
+	rnum = /^-?\d/,
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssWidth = [ "Left", "Right" ],
+	cssHeight = [ "Top", "Bottom" ],
+	curCSS,
+
+	getComputedStyle,
+	currentStyle,
+
+	fcamelCase = function( all, letter ) {
+		return letter.toUpperCase();
+	};
+
+jQuery.fn.css = function( name, value ) {
+	// Setting 'undefined' is a no-op
+	if ( arguments.length === 2 && value === undefined ) {
+		return this;
+	}
+
+	return jQuery.access( this, name, value, true, function( elem, name, value ) {
+		return value !== undefined ?
+			jQuery.style( elem, name, value ) :
+			jQuery.css( elem, name );
+	});
+};
+
+jQuery.extend({
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity", "opacity" );
+					return ret === "" ? "1" : ret;
+
+				} else {
+					return elem.style.opacity;
+				}
+			}
+		}
+	},
+
+	// Exclude the following css properties to add px
+	cssNumber: {
+		"zIndex": true,
+		"fontWeight": true,
+		"opacity": true,
+		"zoom": true,
+		"lineHeight": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		// normalize float css property
+		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, origName = jQuery.camelCase( name ),
+			style = elem.style, hooks = jQuery.cssHooks[ origName ];
+
+		name = jQuery.cssProps[ origName ] || origName;
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			// Make sure that NaN and null values aren't set. See: #7116
+			if ( typeof value === "number" && isNaN( value ) || value == null ) {
+				return;
+			}
+
+			// If a number was passed in, add 'px' to the (except for certain CSS properties)
+			if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
+				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+				// Fixes bug #5509
+				try {
+					style[ name ] = value;
+				} catch(e) {}
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra ) {
+		// Make sure that we're working with the right name
+		var ret, origName = jQuery.camelCase( name ),
+			hooks = jQuery.cssHooks[ origName ];
+
+		name = jQuery.cssProps[ origName ] || origName;
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
+			return ret;
+
+		// Otherwise, if a way to get the computed value exists, use that
+		} else if ( curCSS ) {
+			return curCSS( elem, name, origName );
+		}
+	},
+
+	// A method for quickly swapping in/out CSS properties to get correct calculations
+	swap: function( elem, options, callback ) {
+		var old = {};
+
+		// Remember the old values, and insert the new ones
+		for ( var name in options ) {
+			old[ name ] = elem.style[ name ];
+			elem.style[ name ] = options[ name ];
+		}
+
+		callback.call( elem );
+
+		// Revert the old values
+		for ( name in options ) {
+			elem.style[ name ] = old[ name ];
+		}
+	},
+
+	camelCase: function( string ) {
+		return string.replace( rdashAlpha, fcamelCase );
+	}
+});
+
+// DEPRECATED, Use jQuery.css() instead
+jQuery.curCSS = jQuery.css;
+
+jQuery.each(["height", "width"], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			var val;
+
+			if ( computed ) {
+				if ( elem.offsetWidth !== 0 ) {
+					val = getWH( elem, name, extra );
+
+				} else {
+					jQuery.swap( elem, cssShow, function() {
+						val = getWH( elem, name, extra );
+					});
+				}
+
+				if ( val <= 0 ) {
+					val = curCSS( elem, name, name );
+
+					if ( val === "0px" && currentStyle ) {
+						val = currentStyle( elem, name, name );
+					}
+
+					if ( val != null ) {
+						// Should return "auto" instead of 0, use 0 for
+						// temporary backwards-compat
+						return val === "" || val === "auto" ? "0px" : val;
+					}
+				}
+
+				if ( val < 0 || val == null ) {
+					val = elem.style[ name ];
+
+					// Should return "auto" instead of 0, use 0 for
+					// temporary backwards-compat
+					return val === "" || val === "auto" ? "0px" : val;
+				}
+
+				return typeof val === "string" ? val : val + "px";
+			}
+		},
+
+		set: function( elem, value ) {
+			if ( rnumpx.test( value ) ) {
+				// ignore negative width and height values #1599
+				value = parseFloat(value);
+
+				if ( value >= 0 ) {
+					return value + "px";
+				}
+
+			} else {
+				return value;
+			}
+		}
+	};
+});
+
+if ( !jQuery.support.opacity ) {
+	jQuery.cssHooks.opacity = {
+		get: function( elem, computed ) {
+			// IE uses filters for opacity
+			return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ?
+				(parseFloat(RegExp.$1) / 100) + "" :
+				computed ? "1" : "";
+		},
+
+		set: function( elem, value ) {
+			var style = elem.style;
+
+			// IE has trouble with opacity if it does not have layout
+			// Force it by setting the zoom level
+			style.zoom = 1;
+
+			// Set the alpha filter to set the opacity
+			var opacity = jQuery.isNaN(value) ?
+				"" :
+				"alpha(opacity=" + value * 100 + ")",
+				filter = style.filter || "";
+
+			style.filter = ralpha.test(filter) ?
+				filter.replace(ralpha, opacity) :
+				style.filter + ' ' + opacity;
+		}
+	};
+}
+
+if ( document.defaultView && document.defaultView.getComputedStyle ) {
+	getComputedStyle = function( elem, newName, name ) {
+		var ret, defaultView, computedStyle;
+
+		name = name.replace( rupper, "-$1" ).toLowerCase();
+
+		if ( !(defaultView = elem.ownerDocument.defaultView) ) {
+			return undefined;
+		}
+
+		if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
+			ret = computedStyle.getPropertyValue( name );
+			if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
+				ret = jQuery.style( elem, name );
+			}
+		}
+
+		return ret;
+	};
+}
+
+if ( document.documentElement.currentStyle ) {
+	currentStyle = function( elem, name ) {
+		var left, 
+			ret = elem.currentStyle && elem.currentStyle[ name ],
+			rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ],
+			style = elem.style;
+
+		// From the awesome hack by Dean Edwards
+		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+		// If we're not dealing with a regular pixel number
+		// but a number that has a weird ending, we need to convert it to pixels
+		if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
+			// Remember the original values
+			left = style.left;
+
+			// Put in the new values to get a computed value out
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = elem.currentStyle.left;
+			}
+			style.left = name === "fontSize" ? "1em" : (ret || 0);
+			ret = style.pixelLeft + "px";
+
+			// Revert the changed values
+			style.left = left;
+			if ( rsLeft ) {
+				elem.runtimeStyle.left = rsLeft;
+			}
+		}
+
+		return ret === "" ? "auto" : ret;
+	};
+}
+
+curCSS = getComputedStyle || currentStyle;
+
+function getWH( elem, name, extra ) {
+	var which = name === "width" ? cssWidth : cssHeight,
+		val = name === "width" ? elem.offsetWidth : elem.offsetHeight;
+
+	if ( extra === "border" ) {
+		return val;
+	}
+
+	jQuery.each( which, function() {
+		if ( !extra ) {
+			val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0;
+		}
+
+		if ( extra === "margin" ) {
+			val += parseFloat(jQuery.css( elem, "margin" + this )) || 0;
+
+		} else {
+			val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0;
+		}
+	});
+
+	return val;
+}
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.hidden = function( elem ) {
+		var width = elem.offsetWidth,
+			height = elem.offsetHeight;
+
+		return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none");
+	};
+
+	jQuery.expr.filters.visible = function( elem ) {
+		return !jQuery.expr.filters.hidden( elem );
+	};
+}
+
+
+
+
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rhash = /#.*$/,
+	rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL
+	rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rquery = /\?/,
+	rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+	rselectTextarea = /^(?:select|textarea)/i,
+	rspacesAjax = /\s+/,
+	rts = /([?&])_=[^&]*/,
+	rurl = /^(\w+:)\/\/([^\/?#:]+)(?::(\d+))?/,
+
+	// Keep a copy of the old load method
+	_load = jQuery.fn.load,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {};
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		if ( jQuery.isFunction( func ) ) {
+			var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
+				i = 0,
+				length = dataTypes.length,
+				dataType,
+				list,
+				placeBefore;
+
+			// For each dataType in the dataTypeExpression
+			for(; i < length; i++ ) {
+				dataType = dataTypes[ i ];
+				// We control if we're asked to add before
+				// any existing element
+				placeBefore = /^\+/.test( dataType );
+				if ( placeBefore ) {
+					dataType = dataType.substr( 1 ) || "*";
+				}
+				list = structure[ dataType ] = structure[ dataType ] || [];
+				// then we add to the structure accordingly
+				list[ placeBefore ? "unshift" : "push" ]( func );
+			}
+		}
+	};
+}
+
+//Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR,
+		dataType /* internal */, inspected /* internal */ ) {
+
+	dataType = dataType || options.dataTypes[ 0 ];
+	inspected = inspected || {};
+
+	inspected[ dataType ] = true;
+
+	var list = structure[ dataType ],
+		i = 0,
+		length = list ? list.length : 0,
+		executeOnly = ( structure === prefilters ),
+		selection;
+
+	for(; i < length && ( executeOnly || !selection ); i++ ) {
+		selection = list[ i ]( options, originalOptions, jXHR );
+		// If we got redirected to another dataType
+		// we try there if not done already
+		if ( typeof selection === "string" ) {
+			if ( inspected[ selection ] ) {
+				selection = undefined;
+			} else {
+				options.dataTypes.unshift( selection );
+				selection = inspectPrefiltersOrTransports(
+						structure, options, originalOptions, jXHR, selection, inspected );
+			}
+		}
+	}
+	// If we're only executing or nothing was selected
+	// we try the catchall dataType if not done already
+	if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+		selection = inspectPrefiltersOrTransports(
+				structure, options, originalOptions, jXHR, "*", inspected );
+	}
+	// unnecessary when only executing (prefilters)
+	// but it'll be ignored by the caller in that case
+	return selection;
+}
+
+jQuery.fn.extend({
+	load: function( url, params, callback ) {
+		if ( typeof url !== "string" && _load ) {
+			return _load.apply( this, arguments );
+
+		// Don't do a request if no elements are being requested
+		} else if ( !this.length ) {
+			return this;
+		}
+
+		var off = url.indexOf( " " );
+		if ( off >= 0 ) {
+			var selector = url.slice( off, url.length );
+			url = url.slice( 0, off );
+		}
+
+		// Default to a GET request
+		var type = "GET";
+
+		// If the second parameter was provided
+		if ( params ) {
+			// If it's a function
+			if ( jQuery.isFunction( params ) ) {
+				// We assume that it's the callback
+				callback = params;
+				params = null;
+
+			// Otherwise, build a param string
+			} else if ( typeof params === "object" ) {
+				params = jQuery.param( params, jQuery.ajaxSettings.traditional );
+				type = "POST";
+			}
+		}
+
+		var self = this;
+
+		// Request the remote document
+		jQuery.ajax({
+			url: url,
+			type: type,
+			dataType: "html",
+			data: params,
+			// Complete callback (responseText is used internally)
+			complete: function( jXHR, status, responseText ) {
+				// Store the response as specified by the jXHR object
+				responseText = jXHR.responseText;
+				// If successful, inject the HTML into all the matched elements
+				if ( jXHR.isResolved() ) {
+					// #4825: Get the actual response in case
+					// a dataFilter is present in ajaxSettings
+					jXHR.done(function( r ) {
+						responseText = r;
+					});
+					// See if a selector was specified
+					self.html( selector ?
+						// Create a dummy div to hold the results
+						jQuery("<div>")
+							// inject the contents of the document in, removing the scripts
+							// to avoid any 'Permission Denied' errors in IE
+							.append(responseText.replace(rscript, ""))
+
+							// Locate the specified elements
+							.find(selector) :
+
+						// If not, just inject the full result
+						responseText );
+				}
+
+				if ( callback ) {
+					self.each( callback, [ responseText, status, jXHR ] );
+				}
+			}
+		});
+
+		return this;
+	},
+
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+
+	serializeArray: function() {
+		return this.map(function(){
+			return this.elements ? jQuery.makeArray( this.elements ) : this;
+		})
+		.filter(function(){
+			return this.name && !this.disabled &&
+				( this.checked || rselectTextarea.test( this.nodeName ) ||
+					rinput.test( this.type ) );
+		})
+		.map(function( i, elem ){
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val, i ){
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+	jQuery.fn[ o ] = function( f ){
+		return this.bind( o, f );
+	};
+} );
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = null;
+		}
+
+		return jQuery.ajax({
+			type: method,
+			url: url,
+			data: data,
+			success: callback,
+			dataType: type
+		});
+	};
+} );
+
+jQuery.extend({
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, null, callback, "script" );
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	ajaxSetup: function( settings ) {
+		jQuery.extend( true, jQuery.ajaxSettings, settings );
+		if ( settings.context ) {
+			jQuery.ajaxSettings.context = settings.context;
+		}
+	},
+
+	ajaxSettings: {
+		url: location.href,
+		global: true,
+		type: "GET",
+		contentType: "application/x-www-form-urlencoded",
+		processData: true,
+		async: true,
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		traditional: false,
+		headers: {},
+		crossDomain: null,
+		*/
+
+		accepts: {
+			xml: "application/xml, text/xml",
+			html: "text/html",
+			text: "text/plain",
+			json: "application/json, text/javascript",
+			"*": "*/*"
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText"
+		},
+
+		// List of data converters
+		// 1) key format is "source_type destination_type" (a single space in-between)
+		// 2) the catchall symbol "*" can be used for source_type
+		converters: {
+
+			// Convert anything to text
+			"* text": window.String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		}
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If options is not an object,
+		// we simulate pre-1.5 signature
+		if ( typeof options !== "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var // Create the final options object
+			s = jQuery.extend( true, {}, jQuery.ajaxSettings, options ),
+			// Callbacks contexts
+			// We force the original context if it exists
+			// or take it from jQuery.ajaxSettings otherwise
+			// (plain objects used as context get extended)
+			callbackContext =
+				( s.context = ( "context" in options ? options : jQuery.ajaxSettings ).context ) || s,
+			globalEventContext = callbackContext === s ? jQuery.event : jQuery( callbackContext ),
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery._Deferred(),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// transport
+			transport,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			loc = document.location,
+			protocol = loc.protocol || "http:",
+			parts,
+			// The jXHR state
+			state = 0,
+			// Loop variable
+			i,
+			// Fake xhr
+			jXHR = {
+
+				readyState: 0,
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					if ( state === 0 ) {
+						requestHeaders[ name.toLowerCase() ] = value;
+					}
+					return this;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match || null;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					statusText = statusText || "abort";
+					if ( transport ) {
+						transport.abort( statusText );
+					}
+					done( 0, statusText );
+					return this;
+				}
+			};
+
+		// Callback for when everything is done
+		// It is defined here because jslint complains if it is declared
+		// at the end of the function (which would be more logical and readable)
+		function done( status, statusText, responses, headers) {
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jXHR.readyState = status ? 4 : 0;
+
+			var isSuccess,
+				success,
+				error,
+				response = responses ? ajaxHandleResponses( s, jXHR, responses ) : undefined,
+				lastModified,
+				etag;
+
+			// If successful, handle type chaining
+			if ( status >= 200 && status < 300 || status === 304 ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+
+					if ( ( lastModified = jXHR.getResponseHeader( "Last-Modified" ) ) ) {
+						jQuery.lastModified[ s.url ] = lastModified;
+					}
+					if ( ( etag = jXHR.getResponseHeader( "Etag" ) ) ) {
+						jQuery.etag[ s.url ] = etag;
+					}
+				}
+
+				// If not modified
+				if ( status === 304 ) {
+
+					statusText = "notmodified";
+					isSuccess = true;
+
+				// If we have data
+				} else {
+
+					try {
+						success = ajaxConvert( s, response );
+						statusText = "success";
+						isSuccess = true;
+					} catch(e) {
+						// We have a parsererror
+						statusText = "parsererror";
+						error = e;
+					}
+				}
+			} else {
+				// We extract error from statusText
+				// then normalize statusText and status for non-aborts
+				error = statusText;
+				if( status ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jXHR.status = status;
+			jXHR.statusText = statusText;
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( s.global ) {
+				globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+						[ jXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.resolveWith( callbackContext, [ jXHR, statusText ] );
+
+			if ( s.global ) {
+				globalEventContext.trigger( "ajaxComplete", [ jXHR, s] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger( "ajaxStop" );
+				}
+			}
+		}
+
+		// Attach deferreds
+		deferred.promise( jXHR );
+		jXHR.success = jXHR.done;
+		jXHR.error = jXHR.fail;
+		jXHR.complete = completeDeferred.done;
+
+		// Status-dependent callbacks
+		jXHR.statusCode = function( map ) {
+			if ( map ) {
+				var tmp;
+				if ( state < 2 ) {
+					for( tmp in map ) {
+						statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+					}
+				} else {
+					tmp = map[ jXHR.status ];
+					jXHR.then( tmp, tmp );
+				}
+			}
+			return this;
+		};
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+		// We also use the url parameter if available
+		s.url = ( "" + ( url || s.url ) ).replace( rhash, "" ).replace( rprotocol, protocol + "//" );
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
+
+		// Determine if a cross-domain request is in order
+		if ( !s.crossDomain ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] != protocol || parts[ 2 ] != loc.hostname ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+						( loc.port || ( protocol === "http:" ? 80 : 443 ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jXHR );
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Watch for a new set of requests
+		if ( s.global && jQuery.active++ === 0 ) {
+			jQuery.event.trigger( "ajaxStart" );
+		}
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+			}
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+
+				var ts = jQuery.now(),
+					// try replacing _= if it is there
+					ret = s.url.replace( rts, "$1_=" + ts );
+
+				// if nothing was replaced, add timestamp to the end
+				s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			requestHeaders[ "content-type" ] = s.contentType;
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			if ( jQuery.lastModified[ s.url ] ) {
+				requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ s.url ];
+			}
+			if ( jQuery.etag[ s.url ] ) {
+				requestHeaders[ "if-none-match" ] = jQuery.etag[ s.url ];
+			}
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+			s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) :
+			s.accepts[ "*" ];
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			requestHeaders[ i.toLowerCase() ] = s.headers[ i ];
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jXHR, s ) === false || state === 2 ) ) {
+				// Abort if not done already
+				done( 0, "abort" );
+				// Return false
+				jXHR = false;
+
+		} else {
+
+			// Install callbacks on deferreds
+			for ( i in { success: 1, error: 1, complete: 1 } ) {
+				jXHR[ i ]( s[ i ] );
+			}
+
+			// Get transport
+			transport = inspectPrefiltersOrTransports( transports, s, options, jXHR );
+
+			// If no transport, we auto-abort
+			if ( !transport ) {
+				done( -1, "No Transport" );
+			} else {
+				// Set state as sending
+				state = jXHR.readyState = 1;
+				// Send global event
+				if ( s.global ) {
+					globalEventContext.trigger( "ajaxSend", [ jXHR, s ] );
+				}
+				// Timeout
+				if ( s.async && s.timeout > 0 ) {
+					timeoutTimer = setTimeout( function(){
+						jXHR.abort( "timeout" );
+					}, s.timeout );
+				}
+
+				try {
+					transport.send( requestHeaders, done );
+				} catch (e) {
+					// Propagate exception as error if not done
+					if ( status < 2 ) {
+						done( -1, e );
+					// Simply rethrow otherwise
+					} else {
+						jQuery.error( e );
+					}
+				}
+			}
+		}
+		return jXHR;
+	},
+
+	// Serialize an array of form elements or a set of
+	// key/values into a query string
+	param: function( a, traditional ) {
+		var s = [],
+			add = function( key, value ) {
+				// If value is a function, invoke it and return its value
+				value = jQuery.isFunction( value ) ? value() : value;
+				s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+			};
+
+		// Set traditional to true for jQuery <= 1.3.2 behavior.
+		if ( traditional === undefined ) {
+			traditional = jQuery.ajaxSettings.traditional;
+		}
+
+		// If an array was passed in, assume that it is an array of form elements.
+		if ( jQuery.isArray( a ) || a.jquery ) {
+			// Serialize the form elements
+			jQuery.each( a, function() {
+				add( this.name, this.value );
+			} );
+
+		} else {
+			// If traditional, encode the "old" way (the way 1.3.2 or older
+			// did it), otherwise encode params recursively.
+			for ( var prefix in a ) {
+				buildParams( prefix, a[ prefix ], traditional, add );
+			}
+		}
+
+		// Return the resulting serialization
+		return s.join( "&" ).replace( r20, "+" );
+	}
+});
+
+function buildParams( prefix, obj, traditional, add ) {
+	if ( jQuery.isArray( obj ) && obj.length ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// If array item is non-scalar (array or object), encode its
+				// numeric index to resolve deserialization ambiguity issues.
+				// Note that rack (as of 1.0.0) can't currently deserialize
+				// nested arrays properly, and attempting to do so may cause
+				// a server error. Possible fixes are to modify rack's
+				// deserialization algorithm or to provide an option or flag
+				// to force array serialization to be shallow.
+				buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && obj != null && typeof obj === "object" ) {
+		// If we see an array here, it is empty and should be treated as an empty
+		// object
+		if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) {
+			add( prefix, "" );
+
+		// Serialize object item.
+		} else {
+			jQuery.each( obj, function( k, v ) {
+				buildParams( prefix + "[" + k + "]", v, traditional, add );
+			});
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+
+// This is still on the jQuery object... for now
+// Want to move this to jQuery.ajax some day
+jQuery.extend({
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jXHR, responses ) {
+
+	var contents = s.contents,
+		dataTypes = s.dataTypes,
+		responseFields = s.responseFields,
+		ct,
+		type,
+		finalDataType,
+		firstDataType;
+
+	// Fill responseXXX fields
+	for( type in responseFields ) {
+		if ( type in responses ) {
+			jXHR[ responseFields[type] ] = responses[ type ];
+		}
+	}
+
+	// Remove auto dataType and get content-type in the process
+	while( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = jXHR.getResponseHeader( "content-type" );
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+	// Apply the dataFilter if provided
+	if ( s.dataFilter ) {
+		response = s.dataFilter( response, s.dataType );
+	}
+
+	var dataTypes = s.dataTypes,
+		converters = s.converters,
+		i,
+		length = dataTypes.length,
+		tmp,
+		// Current and previous dataTypes
+		current = dataTypes[ 0 ],
+		prev,
+		// Conversion expression
+		conversion,
+		// Conversion function
+		conv,
+		// Conversion functions (transitive conversion)
+		conv1,
+		conv2;
+
+	// For each dataType in the chain
+	for( i = 1; i < length; i++ ) {
+
+		// Get the dataTypes
+		prev = current;
+		current = dataTypes[ i ];
+
+		// If current is auto dataType, update it to prev
+		if( current === "*" ) {
+			current = prev;
+		// If no auto and dataTypes are actually different
+		} else if ( prev !== "*" && prev !== current ) {
+
+			// Get the converter
+			conversion = prev + " " + current;
+			conv = converters[ conversion ] || converters[ "* " + current ];
+
+			// If there is no direct converter, search transitively
+			if ( !conv ) {
+				conv2 = undefined;
+				for( conv1 in converters ) {
+					tmp = conv1.split( " " );
+					if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
+						conv2 = converters[ tmp[1] + " " + current ];
+						if ( conv2 ) {
+							conv1 = converters[ conv1 ];
+							if ( conv1 === true ) {
+								conv = conv2;
+							} else if ( conv2 === true ) {
+								conv = conv1;
+							}
+							break;
+						}
+					}
+				}
+			}
+			// If we found no converter, dispatch an error
+			if ( !( conv || conv2 ) ) {
+				jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
+			}
+			// If found converter is not an equivalence
+			if ( conv !== true ) {
+				// Convert with 1 or 2 converters accordingly
+				response = conv ? conv( response ) : conv2( conv1(response) );
+			}
+		}
+	}
+	return response;
+}
+
+
+
+
+var jsc = jQuery.now(),
+	jsre = /(\=)\?(&|$)|()\?\?()/i;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		return jQuery.expando + "_" + ( jsc++ );
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, dataIsString /* internal */ ) {
+
+	dataIsString = ( typeof s.data === "string" );
+
+	if ( s.dataTypes[ 0 ] === "jsonp" ||
+		originalSettings.jsonpCallback ||
+		originalSettings.jsonp != null ||
+		s.jsonp !== false && ( jsre.test( s.url ) ||
+				dataIsString && jsre.test( s.data ) ) ) {
+
+		var responseContainer,
+			jsonpCallback = s.jsonpCallback =
+				jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
+			previous = window[ jsonpCallback ],
+			url = s.url,
+			data = s.data,
+			replace = "$1" + jsonpCallback + "$2";
+
+		if ( s.jsonp !== false ) {
+			url = url.replace( jsre, replace );
+			if ( s.url === url ) {
+				if ( dataIsString ) {
+					data = data.replace( jsre, replace );
+				}
+				if ( s.data === data ) {
+					// Add callback manually
+					url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
+				}
+			}
+		}
+
+		s.url = url;
+		s.data = data;
+
+		window[ jsonpCallback ] = function( response ) {
+			responseContainer = [ response ];
+		};
+
+		s.complete = [ function() {
+
+			// Set callback back to previous value
+			window[ jsonpCallback ] = previous;
+
+			// Call if it was a function and we have a response
+			if ( previous) {
+				if ( responseContainer && jQuery.isFunction( previous ) ) {
+					window[ jsonpCallback ] ( responseContainer[ 0 ] );
+				}
+			} else {
+				// else, more memory leak avoidance
+				try{
+					delete window[ jsonpCallback ];
+				} catch( e ) {}
+			}
+
+		}, s.complete ];
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( ! responseContainer ) {
+				jQuery.error( jsonpCallback + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Delegate to script
+		return "script";
+	}
+} );
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript"
+	},
+	contents: {
+		script: /javascript/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+		s.global = false;
+	}
+} );
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+
+		var script,
+			head = document.getElementsByTagName( "head" )[ 0 ] || document.documentElement;
+
+		return {
+
+			send: function( _, callback ) {
+
+				script = document.createElement( "script" );
+
+				script.async = "async";
+
+				if ( s.scriptCharset ) {
+					script.charset = s.scriptCharset;
+				}
+
+				script.src = s.url;
+
+				// Attach handlers for all browsers
+				script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+					if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+						// Handle memory leak in IE
+						script.onload = script.onreadystatechange = null;
+
+						// Remove the script
+						if ( head && script.parentNode ) {
+							head.removeChild( script );
+						}
+
+						// Dereference the script
+						script = undefined;
+
+						// Callback if not abort
+						if ( !isAbort ) {
+							callback( 200, "success" );
+						}
+					}
+				};
+				// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
+				// This arises when a base node is used (#2709 and #4378).
+				head.insertBefore( script, head.firstChild );
+			},
+
+			abort: function() {
+				if ( script ) {
+					script.onload( 0, 1 );
+				}
+			}
+		};
+	}
+} );
+
+
+
+
+var // Next active xhr id
+	xhrId = jQuery.now(),
+
+	// active xhrs
+	xhrs = {},
+
+	// #5280: see below
+	xhrUnloadAbortInstalled,
+
+	// XHR used to determine supports properties
+	testXHR;
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+	/* Microsoft failed to properly
+	 * implement the XMLHttpRequest in IE7 (can't request local files),
+	 * so we use the ActiveXObject when it is available
+	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+	 * we need a fallback.
+	 */
+	function() {
+		if ( window.location.protocol !== "file:" ) {
+			try {
+				return new window.XMLHttpRequest();
+			} catch( xhrError ) {}
+		}
+
+		try {
+			return new window.ActiveXObject("Microsoft.XMLHTTP");
+		} catch( activeError ) {}
+	} :
+	// For all other browsers, use the standard XMLHttpRequest object
+	function() {
+		return new window.XMLHttpRequest();
+	};
+
+// Test if we can create an xhr object
+try {
+	testXHR = jQuery.ajaxSettings.xhr();
+} catch( xhrCreationException ) {}
+
+//Does this browser support XHR requests?
+jQuery.support.ajax = !!testXHR;
+
+// Does this browser support crossDomain XHR requests
+jQuery.support.cors = testXHR && ( "withCredentials" in testXHR );
+
+// No need for the temporary xhr anymore
+testXHR = undefined;
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+	jQuery.ajaxTransport(function( s ) {
+		// Cross domain only allowed if supported through XMLHttpRequest
+		if ( !s.crossDomain || jQuery.support.cors ) {
+
+			var callback;
+
+			return {
+				send: function( headers, complete ) {
+
+					// #5280: we need to abort on unload or IE will keep connections alive
+					if ( !xhrUnloadAbortInstalled ) {
+
+						xhrUnloadAbortInstalled = 1;
+
+						jQuery(window).bind( "unload", function() {
+
+							// Abort all pending requests
+							jQuery.each( xhrs, function( _, xhr ) {
+								if ( xhr.onreadystatechange ) {
+									xhr.onreadystatechange( 1 );
+								}
+							} );
+
+						} );
+					}
+
+					// Get a new xhr
+					var xhr = s.xhr(),
+						handle;
+
+					// Open the socket
+					// Passing null username, generates a login popup on Opera (#2865)
+					if ( s.username ) {
+						xhr.open( s.type, s.url, s.async, s.username, s.password );
+					} else {
+						xhr.open( s.type, s.url, s.async );
+					}
+
+					// Requested-With header
+					// Not set for crossDomain requests with no content
+					// (see why at http://trac.dojotoolkit.org/ticket/9486)
+					// Won't change header if already provided
+					if ( !( s.crossDomain && !s.hasContent ) && !headers["x-requested-with"] ) {
+						headers[ "x-requested-with" ] = "XMLHttpRequest";
+					}
+
+					// Need an extra try/catch for cross domain requests in Firefox 3
+					try {
+						jQuery.each( headers, function( key, value ) {
+							xhr.setRequestHeader( key, value );
+						} );
+					} catch( _ ) {}
+
+					// Do send the request
+					// This may raise an exception which is actually
+					// handled in jQuery.ajax (so no try/catch here)
+					xhr.send( ( s.hasContent && s.data ) || null );
+
+					// Listener
+					callback = function( _, isAbort ) {
+
+						// Was never called and is aborted or complete
+						if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+							// Only called once
+							callback = 0;
+
+							// Do not keep as active anymore
+							if ( handle ) {
+								xhr.onreadystatechange = jQuery.noop;
+								delete xhrs[ handle ];
+							}
+
+							// If it's an abort
+							if ( isAbort ) {
+								// Abort it manually if needed
+								if ( xhr.readyState !== 4 ) {
+									xhr.abort();
+								}
+							} else {
+								// Get info
+								var status = xhr.status,
+									statusText,
+									responseHeaders = xhr.getAllResponseHeaders(),
+									responses = {},
+									xml = xhr.responseXML;
+
+								// Construct response list
+								if ( xml && xml.documentElement /* #4958 */ ) {
+									responses.xml = xml;
+								}
+								responses.text = xhr.responseText;
+
+								// Firefox throws an exception when accessing
+								// statusText for faulty cross-domain requests
+								try {
+									statusText = xhr.statusText;
+								} catch( e ) {
+									// We normalize with Webkit giving an empty statusText
+									statusText = "";
+								}
+
+								// Filter status for non standard behaviours
+								status =
+									// Opera returns 0 when it should be 304
+									// Webkit returns 0 for failing cross-domain no matter the real status
+									status === 0 ?
+										(
+											// Webkit, Firefox: filter out faulty cross-domain requests
+											!s.crossDomain || statusText ?
+											(
+												// Opera: filter out real aborts #6060
+												responseHeaders ?
+												304 :
+												0
+											) :
+											// We assume 302 but could be anything cross-domain related
+											302
+										) :
+										(
+											// IE sometimes returns 1223 when it should be 204 (see #1450)
+											status == 1223 ?
+												204 :
+												status
+										);
+
+								// Call complete
+								complete( status, statusText, responses, responseHeaders );
+							}
+						}
+					};
+
+					// if we're in sync mode or it's in cache
+					// and has been retrieved directly (IE6 & IE7)
+					// we need to manually fire the callback
+					if ( !s.async || xhr.readyState === 4 ) {
+						callback();
+					} else {
+						// Add to list of active xhrs
+						handle = xhrId++;
+						xhrs[ handle ] = xhr;
+						xhr.onreadystatechange = callback;
+					}
+				},
+
+				abort: function() {
+					if ( callback ) {
+						callback(0,1);
+					}
+				}
+			};
+		}
+	});
+}
+
+
+
+
+var elemdisplay = {},
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
+	timerId,
+	fxAttrs = [
+		// height animations
+		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
+		// width animations
+		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
+		// opacity animations
+		[ "opacity" ]
+	];
+
+jQuery.fn.extend({
+	show: function( speed, easing, callback ) {
+		var elem, display;
+
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("show", 3), speed, easing, callback);
+
+		} else {
+			for ( var i = 0, j = this.length; i < j; i++ ) {
+				elem = this[i];
+				display = elem.style.display;
+
+				// Reset the inline display of this element to learn if it is
+				// being hidden by cascaded rules or not
+				if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
+					display = elem.style.display = "";
+				}
+
+				// Set elements which have been overridden with display: none
+				// in a stylesheet to whatever the default browser style is
+				// for such an element
+				if ( display === "" && jQuery.css( elem, "display" ) === "none" ) {
+					jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName));
+				}
+			}
+
+			// Set the display of most of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				elem = this[i];
+				display = elem.style.display;
+
+				if ( display === "" || display === "none" ) {
+					elem.style.display = jQuery._data(elem, "olddisplay") || "";
+				}
+			}
+
+			return this;
+		}
+	},
+
+	hide: function( speed, easing, callback ) {
+		if ( speed || speed === 0 ) {
+			return this.animate( genFx("hide", 3), speed, easing, callback);
+
+		} else {
+			for ( var i = 0, j = this.length; i < j; i++ ) {
+				var display = jQuery.css( this[i], "display" );
+
+				if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) {
+					jQuery._data( this[i], "olddisplay", display );
+				}
+			}
+
+			// Set the display of the elements in a second loop
+			// to avoid the constant reflow
+			for ( i = 0; i < j; i++ ) {
+				this[i].style.display = "none";
+			}
+
+			return this;
+		}
+	},
+
+	// Save the old toggle function
+	_toggle: jQuery.fn.toggle,
+
+	toggle: function( fn, fn2, callback ) {
+		var bool = typeof fn === "boolean";
+
+		if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
+			this._toggle.apply( this, arguments );
+
+		} else if ( fn == null || bool ) {
+			this.each(function() {
+				var state = bool ? fn : jQuery(this).is(":hidden");
+				jQuery(this)[ state ? "show" : "hide" ]();
+			});
+
+		} else {
+			this.animate(genFx("toggle", 3), fn, fn2, callback);
+		}
+
+		return this;
+	},
+
+	fadeTo: function( speed, to, easing, callback ) {
+		return this.filter(":hidden").css("opacity", 0).show().end()
+					.animate({opacity: to}, speed, easing, callback);
+	},
+
+	animate: function( prop, speed, easing, callback ) {
+		var optall = jQuery.speed(speed, easing, callback);
+
+		if ( jQuery.isEmptyObject( prop ) ) {
+			return this.each( optall.complete );
+		}
+
+		return this[ optall.queue === false ? "each" : "queue" ](function() {
+			// XXX 'this' does not always have a nodeName when running the
+			// test suite
+
+			var opt = jQuery.extend({}, optall), p,
+				isElement = this.nodeType === 1,
+				hidden = isElement && jQuery(this).is(":hidden"),
+				self = this;
+
+			for ( p in prop ) {
+				var name = jQuery.camelCase( p );
+
+				if ( p !== name ) {
+					prop[ name ] = prop[ p ];
+					delete prop[ p ];
+					p = name;
+				}
+
+				if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) {
+					return opt.complete.call(this);
+				}
+
+				if ( isElement && ( p === "height" || p === "width" ) ) {
+					// Make sure that nothing sneaks out
+					// Record all 3 overflow attributes because IE does not
+					// change the overflow attribute when overflowX and
+					// overflowY are set to the same value
+					opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
+
+					// Set display property to inline-block for height/width
+					// animations on inline elements that are having width/height
+					// animated
+					if ( jQuery.css( this, "display" ) === "inline" &&
+							jQuery.css( this, "float" ) === "none" ) {
+						if ( !jQuery.support.inlineBlockNeedsLayout ) {
+							this.style.display = "inline-block";
+
+						} else {
+							var display = defaultDisplay(this.nodeName);
+
+							// inline-level elements accept inline-block;
+							// block-level elements need to be inline with layout
+							if ( display === "inline" ) {
+								this.style.display = "inline-block";
+
+							} else {
+								this.style.display = "inline";
+								this.style.zoom = 1;
+							}
+						}
+					}
+				}
+
+				if ( jQuery.isArray( prop[p] ) ) {
+					// Create (if needed) and add to specialEasing
+					(opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1];
+					prop[p] = prop[p][0];
+				}
+			}
+
+			if ( opt.overflow != null ) {
+				this.style.overflow = "hidden";
+			}
+
+			opt.curAnim = jQuery.extend({}, prop);
+
+			jQuery.each( prop, function( name, val ) {
+				var e = new jQuery.fx( self, opt, name );
+
+				if ( rfxtypes.test(val) ) {
+					e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop );
+
+				} else {
+					var parts = rfxnum.exec(val),
+						start = e.cur() || 0;
+
+					if ( parts ) {
+						var end = parseFloat( parts[2] ),
+							unit = parts[3] || "px";
+
+						// We need to compute starting value
+						if ( unit !== "px" ) {
+							jQuery.style( self, name, (end || 1) + unit);
+							start = ((end || 1) / e.cur()) * start;
+							jQuery.style( self, name, start + unit);
+						}
+
+						// If a +=/-= token was provided, we're doing a relative animation
+						if ( parts[1] ) {
+							end = ((parts[1] === "-=" ? -1 : 1) * end) + start;
+						}
+
+						e.custom( start, end, unit );
+
+					} else {
+						e.custom( start, val, "" );
+					}
+				}
+			});
+
+			// For JS strict compliance
+			return true;
+		});
+	},
+
+	stop: function( clearQueue, gotoEnd ) {
+		var timers = jQuery.timers;
+
+		if ( clearQueue ) {
+			this.queue([]);
+		}
+
+		this.each(function() {
+			// go in reverse order so anything added to the queue during the loop is ignored
+			for ( var i = timers.length - 1; i >= 0; i-- ) {
+				if ( timers[i].elem === this ) {
+					if (gotoEnd) {
+						// force the next step to be the last
+						timers[i](true);
+					}
+
+					timers.splice(i, 1);
+				}
+			}
+		});
+
+		// start the next in the queue if the last step wasn't forced
+		if ( !gotoEnd ) {
+			this.dequeue();
+		}
+
+		return this;
+	}
+
+});
+
+function genFx( type, num ) {
+	var obj = {};
+
+	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() {
+		obj[ this ] = type;
+	});
+
+	return obj;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx("show", 1),
+	slideUp: genFx("hide", 1),
+	slideToggle: genFx("toggle", 1),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.extend({
+	speed: function( speed, easing, fn ) {
+		var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : {
+			complete: fn || !fn && easing ||
+				jQuery.isFunction( speed ) && speed,
+			duration: speed,
+			easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
+		};
+
+		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+			opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default;
+
+		// Queueing
+		opt.old = opt.complete;
+		opt.complete = function() {
+			if ( opt.queue !== false ) {
+				jQuery(this).dequeue();
+			}
+			if ( jQuery.isFunction( opt.old ) ) {
+				opt.old.call( this );
+			}
+		};
+
+		return opt;
+	},
+
+	easing: {
+		linear: function( p, n, firstNum, diff ) {
+			return firstNum + diff * p;
+		},
+		swing: function( p, n, firstNum, diff ) {
+			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
+		}
+	},
+
+	timers: [],
+
+	fx: function( elem, options, prop ) {
+		this.options = options;
+		this.elem = elem;
+		this.prop = prop;
+
+		if ( !options.orig ) {
+			options.orig = {};
+		}
+	}
+
+});
+
+jQuery.fx.prototype = {
+	// Simple function for setting a style value
+	update: function() {
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );
+	},
+
+	// Get the current size
+	cur: function() {
+		if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) {
+			return this.elem[ this.prop ];
+		}
+
+		var r = parseFloat( jQuery.css( this.elem, this.prop ) );
+		return r || 0;
+	},
+
+	// Start an animation from one number to another
+	custom: function( from, to, unit ) {
+		var self = this,
+			fx = jQuery.fx;
+
+		this.startTime = jQuery.now();
+		this.start = from;
+		this.end = to;
+		this.unit = unit || this.unit || "px";
+		this.now = this.start;
+		this.pos = this.state = 0;
+
+		function t( gotoEnd ) {
+			return self.step(gotoEnd);
+		}
+
+		t.elem = this.elem;
+
+		if ( t() && jQuery.timers.push(t) && !timerId ) {
+			timerId = setInterval(fx.tick, fx.interval);
+		}
+	},
+
+	// Simple 'show' function
+	show: function() {
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+		this.options.show = true;
+
+		// Begin the animation
+		// Make sure that we start at a small width/height to avoid any
+		// flash of content
+		this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur());
+
+		// Start by showing the element
+		jQuery( this.elem ).show();
+	},
+
+	// Simple 'hide' function
+	hide: function() {
+		// Remember where we started, so that we can go back to it later
+		this.options.orig[this.prop] = jQuery.style( this.elem, this.prop );
+		this.options.hide = true;
+
+		// Begin the animation
+		this.custom(this.cur(), 0);
+	},
+
+	// Each step of an animation
+	step: function( gotoEnd ) {
+		var t = jQuery.now(), done = true;
+
+		if ( gotoEnd || t >= this.options.duration + this.startTime ) {
+			this.now = this.end;
+			this.pos = this.state = 1;
+			this.update();
+
+			this.options.curAnim[ this.prop ] = true;
+
+			for ( var i in this.options.curAnim ) {
+				if ( this.options.curAnim[i] !== true ) {
+					done = false;
+				}
+			}
+
+			if ( done ) {
+				// Reset the overflow
+				if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
+					var elem = this.elem,
+						options = this.options;
+
+					jQuery.each( [ "", "X", "Y" ], function (index, value) {
+						elem.style[ "overflow" + value ] = options.overflow[index];
+					} );
+				}
+
+				// Hide the element if the "hide" operation was done
+				if ( this.options.hide ) {
+					jQuery(this.elem).hide();
+				}
+
+				// Reset the properties, if the item has been hidden or shown
+				if ( this.options.hide || this.options.show ) {
+					for ( var p in this.options.curAnim ) {
+						jQuery.style( this.elem, p, this.options.orig[p] );
+					}
+				}
+
+				// Execute the complete function
+				this.options.complete.call( this.elem );
+			}
+
+			return false;
+
+		} else {
+			var n = t - this.startTime;
+			this.state = n / this.options.duration;
+
+			// Perform the easing function, defaults to swing
+			var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop];
+			var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear");
+			this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration);
+			this.now = this.start + ((this.end - this.start) * this.pos);
+
+			// Perform the next step of the animation
+			this.update();
+		}
+
+		return true;
+	}
+};
+
+jQuery.extend( jQuery.fx, {
+	tick: function() {
+		var timers = jQuery.timers;
+
+		for ( var i = 0; i < timers.length; i++ ) {
+			if ( !timers[i]() ) {
+				timers.splice(i--, 1);
+			}
+		}
+
+		if ( !timers.length ) {
+			jQuery.fx.stop();
+		}
+	},
+
+	interval: 13,
+
+	stop: function() {
+		clearInterval( timerId );
+		timerId = null;
+	},
+
+	speeds: {
+		slow: 600,
+		fast: 200,
+		// Default speed
+		_default: 400
+	},
+
+	step: {
+		opacity: function( fx ) {
+			jQuery.style( fx.elem, "opacity", fx.now );
+		},
+
+		_default: function( fx ) {
+			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
+				fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit;
+			} else {
+				fx.elem[ fx.prop ] = fx.now;
+			}
+		}
+	}
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+	jQuery.expr.filters.animated = function( elem ) {
+		return jQuery.grep(jQuery.timers, function( fn ) {
+			return elem === fn.elem;
+		}).length;
+	};
+}
+
+function defaultDisplay( nodeName ) {
+	if ( !elemdisplay[ nodeName ] ) {
+		var elem = jQuery("<" + nodeName + ">").appendTo("body"),
+			display = elem.css("display");
+
+		elem.remove();
+
+		if ( display === "none" || display === "" ) {
+			display = "block";
+		}
+
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return elemdisplay[ nodeName ];
+}
+
+
+
+
+var rtable = /^t(?:able|d|h)$/i,
+	rroot = /^(?:body|html)$/i;
+
+if ( "getBoundingClientRect" in document.documentElement ) {
+	jQuery.fn.offset = function( options ) {
+		var elem = this[0], box;
+
+		if ( options ) {
+			return this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+		}
+
+		if ( !elem || !elem.ownerDocument ) {
+			return null;
+		}
+
+		if ( elem === elem.ownerDocument.body ) {
+			return jQuery.offset.bodyOffset( elem );
+		}
+
+		try {
+			box = elem.getBoundingClientRect();
+		} catch(e) {}
+
+		var doc = elem.ownerDocument,
+			docElem = doc.documentElement;
+
+		// Make sure we're not dealing with a disconnected DOM node
+		if ( !box || !jQuery.contains( docElem, elem ) ) {
+			return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
+		}
+
+		var body = doc.body,
+			win = getWindow(doc),
+			clientTop  = docElem.clientTop  || body.clientTop  || 0,
+			clientLeft = docElem.clientLeft || body.clientLeft || 0,
+			scrollTop  = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop  || body.scrollTop ),
+			scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft),
+			top  = box.top  + scrollTop  - clientTop,
+			left = box.left + scrollLeft - clientLeft;
+
+		return { top: top, left: left };
+	};
+
+} else {
+	jQuery.fn.offset = function( options ) {
+		var elem = this[0];
+
+		if ( options ) {
+			return this.each(function( i ) {
+				jQuery.offset.setOffset( this, options, i );
+			});
+		}
+
+		if ( !elem || !elem.ownerDocument ) {
+			return null;
+		}
+
+		if ( elem === elem.ownerDocument.body ) {
+			return jQuery.offset.bodyOffset( elem );
+		}
+
+		jQuery.offset.initialize();
+
+		var computedStyle,
+			offsetParent = elem.offsetParent,
+			prevOffsetParent = elem,
+			doc = elem.ownerDocument,
+			docElem = doc.documentElement,
+			body = doc.body,
+			defaultView = doc.defaultView,
+			prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
+			top = elem.offsetTop,
+			left = elem.offsetLeft;
+
+		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
+			if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+				break;
+			}
+
+			computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
+			top  -= elem.scrollTop;
+			left -= elem.scrollLeft;
+
+			if ( elem === offsetParent ) {
+				top  += elem.offsetTop;
+				left += elem.offsetLeft;
+
+				if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
+					top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+					left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+				}
+
+				prevOffsetParent = offsetParent;
+				offsetParent = elem.offsetParent;
+			}
+
+			if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
+				top  += parseFloat( computedStyle.borderTopWidth  ) || 0;
+				left += parseFloat( computedStyle.borderLeftWidth ) || 0;
+			}
+
+			prevComputedStyle = computedStyle;
+		}
+
+		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
+			top  += body.offsetTop;
+			left += body.offsetLeft;
+		}
+
+		if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) {
+			top  += Math.max( docElem.scrollTop, body.scrollTop );
+			left += Math.max( docElem.scrollLeft, body.scrollLeft );
+		}
+
+		return { top: top, left: left };
+	};
+}
+
+jQuery.offset = {
+	initialize: function() {
+		var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0,
+			html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+
+		jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } );
+
+		container.innerHTML = html;
+		body.insertBefore( container, body.firstChild );
+		innerDiv = container.firstChild;
+		checkDiv = innerDiv.firstChild;
+		td = innerDiv.nextSibling.firstChild.firstChild;
+
+		this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
+		this.doesAddBorderForTableAndCells = (td.offsetTop === 5);
+
+		checkDiv.style.position = "fixed";
+		checkDiv.style.top = "20px";
+
+		// safari subtracts parent border width here which is 5px
+		this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15);
+		checkDiv.style.position = checkDiv.style.top = "";
+
+		innerDiv.style.overflow = "hidden";
+		innerDiv.style.position = "relative";
+
+		this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);
+
+		this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop);
+
+		body.removeChild( container );
+		body = container = innerDiv = checkDiv = table = td = null;
+		jQuery.offset.initialize = jQuery.noop;
+	},
+
+	bodyOffset: function( body ) {
+		var top = body.offsetTop,
+			left = body.offsetLeft;
+
+		jQuery.offset.initialize();
+
+		if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) {
+			top  += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+			left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+		}
+
+		return { top: top, left: left };
+	},
+
+	setOffset: function( elem, options, i ) {
+		var position = jQuery.css( elem, "position" );
+
+		// set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		var curElem = jQuery( elem ),
+			curOffset = curElem.offset(),
+			curCSSTop = jQuery.css( elem, "top" ),
+			curCSSLeft = jQuery.css( elem, "left" ),
+			calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1),
+			props = {}, curPosition = {}, curTop, curLeft;
+
+		// need to be able to calculate position if either top or left is auto and position is absolute
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+		}
+
+		curTop  = calculatePosition ? curPosition.top  : parseInt( curCSSTop,  10 ) || 0;
+		curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0;
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if (options.top != null) {
+			props.top = (options.top - curOffset.top) + curTop;
+		}
+		if (options.left != null) {
+			props.left = (options.left - curOffset.left) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+
+jQuery.fn.extend({
+	position: function() {
+		if ( !this[0] ) {
+			return null;
+		}
+
+		var elem = this[0],
+
+		// Get *real* offsetParent
+		offsetParent = this.offsetParent(),
+
+		// Get correct offsets
+		offset       = this.offset(),
+		parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+		// Subtract element margins
+		// note: when an element has margin: auto the offsetLeft and marginLeft
+		// are the same in Safari causing offset.left to incorrectly be 0
+		offset.top  -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+		offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+		// Add offsetParent borders
+		parentOffset.top  += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+		parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+		// Subtract the two offsets
+		return {
+			top:  offset.top  - parentOffset.top,
+			left: offset.left - parentOffset.left
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || document.body;
+			while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+			return offsetParent;
+		});
+	}
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( ["Left", "Top"], function( i, name ) {
+	var method = "scroll" + name;
+
+	jQuery.fn[ method ] = function(val) {
+		var elem = this[0], win;
+
+		if ( !elem ) {
+			return null;
+		}
+
+		if ( val !== undefined ) {
+			// Set the scroll offset
+			return this.each(function() {
+				win = getWindow( this );
+
+				if ( win ) {
+					win.scrollTo(
+						!i ? val : jQuery(win).scrollLeft(),
+						 i ? val : jQuery(win).scrollTop()
+					);
+
+				} else {
+					this[ method ] = val;
+				}
+			});
+		} else {
+			win = getWindow( elem );
+
+			// Return the scroll offset
+			return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
+				jQuery.support.boxModel && win.document.documentElement[ method ] ||
+					win.document.body[ method ] :
+				elem[ method ];
+		}
+	};
+});
+
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ?
+		elem :
+		elem.nodeType === 9 ?
+			elem.defaultView || elem.parentWindow :
+			false;
+}
+
+
+
+
+// Create innerHeight, innerWidth, outerHeight and outerWidth methods
+jQuery.each([ "Height", "Width" ], function( i, name ) {
+
+	var type = name.toLowerCase();
+
+	// innerHeight and innerWidth
+	jQuery.fn["inner" + name] = function() {
+		return this[0] ?
+			parseFloat( jQuery.css( this[0], type, "padding" ) ) :
+			null;
+	};
+
+	// outerHeight and outerWidth
+	jQuery.fn["outer" + name] = function( margin ) {
+		return this[0] ?
+			parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) :
+			null;
+	};
+
+	jQuery.fn[ type ] = function( size ) {
+		// Get window width or height
+		var elem = this[0];
+		if ( !elem ) {
+			return size == null ? null : this;
+		}
+
+		if ( jQuery.isFunction( size ) ) {
+			return this.each(function( i ) {
+				var self = jQuery( this );
+				self[ type ]( size.call( this, i, self[ type ]() ) );
+			});
+		}
+
+		if ( jQuery.isWindow( elem ) ) {
+			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
+			// 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
+			var docElemProp = elem.document.documentElement[ "client" + name ];
+			return elem.document.compatMode === "CSS1Compat" && docElemProp ||
+				elem.document.body[ "client" + name ] || docElemProp;
+
+		// Get document width or height
+		} else if ( elem.nodeType === 9 ) {
+			// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
+			return Math.max(
+				elem.documentElement["client" + name],
+				elem.body["scroll" + name], elem.documentElement["scroll" + name],
+				elem.body["offset" + name], elem.documentElement["offset" + name]
+			);
+
+		// Get or set width or height on the element
+		} else if ( size === undefined ) {
+			var orig = jQuery.css( elem, type ),
+				ret = parseFloat( orig );
+
+			return jQuery.isNaN( ret ) ? orig : ret;
+
+		// Set the width or height on the element (default to pixels if value is unitless)
+		} else {
+			return this.css( type, typeof size === "string" ? size : size + "px" );
+		}
+	};
+
+});
+
+
+})(window);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/plugins/fileManager/icon.png b/media/js/jwysiwyg/plugins/fileManager/icon.png
new file mode 100644
index 0000000..364ccb3
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/icon.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/application.png b/media/js/jwysiwyg/plugins/fileManager/images/application.png
new file mode 100644
index 0000000..1dee9e3
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/application.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/code.png b/media/js/jwysiwyg/plugins/fileManager/images/code.png
new file mode 100644
index 0000000..0c76bd1
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/code.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/css.png b/media/js/jwysiwyg/plugins/fileManager/images/css.png
new file mode 100644
index 0000000..f907e44
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/css.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/db.png b/media/js/jwysiwyg/plugins/fileManager/images/db.png
new file mode 100644
index 0000000..bddba1f
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/db.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/directory.png b/media/js/jwysiwyg/plugins/fileManager/images/directory.png
new file mode 100644
index 0000000..784e8fa
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/directory.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/doc.png b/media/js/jwysiwyg/plugins/fileManager/images/doc.png
new file mode 100644
index 0000000..ae8ecbf
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/doc.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/file.png b/media/js/jwysiwyg/plugins/fileManager/images/file.png
new file mode 100644
index 0000000..8b8b1ca
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/file.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/film.png b/media/js/jwysiwyg/plugins/fileManager/images/film.png
new file mode 100644
index 0000000..b0ce7bb
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/film.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/flash.png b/media/js/jwysiwyg/plugins/fileManager/images/flash.png
new file mode 100644
index 0000000..5769120
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/flash.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/folder_open.png b/media/js/jwysiwyg/plugins/fileManager/images/folder_open.png
new file mode 100644
index 0000000..4e35483
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/folder_open.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/html.png b/media/js/jwysiwyg/plugins/fileManager/images/html.png
new file mode 100644
index 0000000..6ed2490
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/html.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/java.png b/media/js/jwysiwyg/plugins/fileManager/images/java.png
new file mode 100644
index 0000000..b7bfcd1
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/java.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/linux.png b/media/js/jwysiwyg/plugins/fileManager/images/linux.png
new file mode 100644
index 0000000..52699bf
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/linux.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/mkdir.png b/media/js/jwysiwyg/plugins/fileManager/images/mkdir.png
new file mode 100644
index 0000000..529fe8f
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/mkdir.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/music.png b/media/js/jwysiwyg/plugins/fileManager/images/music.png
new file mode 100644
index 0000000..a8b3ede
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/music.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/pdf.png b/media/js/jwysiwyg/plugins/fileManager/images/pdf.png
new file mode 100644
index 0000000..8f8095e
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/pdf.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/php.png b/media/js/jwysiwyg/plugins/fileManager/images/php.png
new file mode 100644
index 0000000..7868a25
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/php.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/picture.png b/media/js/jwysiwyg/plugins/fileManager/images/picture.png
new file mode 100644
index 0000000..4a158fe
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/picture.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/ppt.png b/media/js/jwysiwyg/plugins/fileManager/images/ppt.png
new file mode 100644
index 0000000..c4eff03
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/ppt.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/prev-directory.png b/media/js/jwysiwyg/plugins/fileManager/images/prev-directory.png
new file mode 100644
index 0000000..22f693c
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/prev-directory.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/psd.png b/media/js/jwysiwyg/plugins/fileManager/images/psd.png
new file mode 100644
index 0000000..73c5b3f
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/psd.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/remove.png b/media/js/jwysiwyg/plugins/fileManager/images/remove.png
new file mode 100644
index 0000000..08f2493
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/remove.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/rename.png b/media/js/jwysiwyg/plugins/fileManager/images/rename.png
new file mode 100644
index 0000000..4e3688e
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/rename.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/ruby.png b/media/js/jwysiwyg/plugins/fileManager/images/ruby.png
new file mode 100644
index 0000000..f59b7c4
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/ruby.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/script.png b/media/js/jwysiwyg/plugins/fileManager/images/script.png
new file mode 100644
index 0000000..63fe6ce
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/script.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/txt.png b/media/js/jwysiwyg/plugins/fileManager/images/txt.png
new file mode 100644
index 0000000..813f712
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/txt.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/upload.png b/media/js/jwysiwyg/plugins/fileManager/images/upload.png
new file mode 100644
index 0000000..802bd6c
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/upload.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/xls.png b/media/js/jwysiwyg/plugins/fileManager/images/xls.png
new file mode 100644
index 0000000..b977d7e
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/xls.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/images/zip.png b/media/js/jwysiwyg/plugins/fileManager/images/zip.png
new file mode 100644
index 0000000..fd4bbcc
Binary files /dev/null and b/media/js/jwysiwyg/plugins/fileManager/images/zip.png differ
diff --git a/media/js/jwysiwyg/plugins/fileManager/wysiwyg.fileManager.css b/media/js/jwysiwyg/plugins/fileManager/wysiwyg.fileManager.css
new file mode 100644
index 0000000..63ee529
--- /dev/null
+++ b/media/js/jwysiwyg/plugins/fileManager/wysiwyg.fileManager.css
@@ -0,0 +1,113 @@
+/*
+ * Style Sheet for the File Manager plugin for jWYSIWYG
+ * 
+ * Yotam Bar-On, 2011
+ * 
+ * Uses the Silk Icon Set: http://www.famfamfam.com/lab/icons/silk/
+ * The Silk Icon Set is released under CC-3.0/2.5  and was created by FAMFAMFAM.
+ */
+ 
+div.wysiwyg ul.toolbar li.fileManager, div.wysiwyg-fileManager { background: url('icon.png'); }
+div.wysiwyg-fileManager { width:16px; height:16px; cursor: pointer; cursor: hand; }
+
+
+.wysiwyg-files-wrapper { padding:10px; }
+
+#wysiwyg-files-list-wrapper { min-height:50px; max-height:400px; overflow:auto; }
+* html #wysiwyg-files-list-wrapper { height: 600px; }
+
+.wysiwyg-files-ajax { background: url(../../ajax-loader.gif) left top no-repeat; }
+
+.wysiwyg-files-file-preview { position:absolute; background-position:center center; background-color:#fff; border:solid 1px; padding:2px; min-width:16px; min-height:16px; max-width:300px; z-index:200000; }
+* html .wysiwyg-files-file-preview { width:90px; }
+
+/* CSS3 for fun */
+.wysiwyg-files-wrapper li {
+	-moz-transition: all 0.5s ease-out;  /* FF4+ */
+	-o-transition: all 0.5s ease-out;  /* Opera 10.5+ */
+	-webkit-transition: all 0.5s ease-out;  /* Saf3.2+, Chrome */
+	-ms-transition: all 0.5s ease-out;  /* IE10? */
+	 transition: all 0.5s ease-out;
+}
+
+.wysiwyg-files-wrapper ul { padding:0px; margin:0px; }
+.wysiwyg-files-wrapper li { list-style:none; padding:0px; padding-left:20px; margin: 2px; }
+.wysiwyg-files-wrapper li a { display:inline-block; text-decoration:none; cursor: pointer; cursor: hand; }
+.wysiwyg-files-textfield { width:250px; border:solid 1px !important; }
+
+.wysiwyg-files-wrapper li.wysiwyg-files-hover { background-color: #BDF !important; }
+.wysiwyg-files-wrapper li.wysiwyg-files-dir { background: url(images/directory.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-dir-expanded { background: url(images/folder_open.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-file { background: url(images/file.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-dir-prev { background: url(images/prev-directory.png) left top no-repeat; }
+
+.wysiwyg-files-wrapper li .wysiwyg-files-action { float:right; width:16px; height:16px; margin:2px; cursor: pointer; cursor: hand; }
+.wysiwyg-files-wrapper li .wysiwyg-files-action-rename { background: url(images/rename.png) left top no-repeat; }
+.wysiwyg-files-wrapper li .wysiwyg-files-action-remove { background: url(images/remove.png) left top no-repeat; }
+.wysiwyg-files-wrapper .wysiwyg-files-action-upload, .wysiwyg-files-wrapper .wysiwyg-files-action-mkdir { float:right; width:16px; height:16px; margin:2px; cursor: pointer; cursor: hand; }
+.wysiwyg-files-wrapper .wysiwyg-files-action-upload { background: url(images/upload.png); }
+.wysiwyg-files-wrapper .wysiwyg-files-action-mkdir { background: url(images/mkdir.png);}
+
+/*
+ * Below is the list of file-types classes.
+ */
+ 
+ /* This REALLY needs to become a sprite */
+.wysiwyg-files-wrapper li.wysiwyg-files-3gp { background: url(images/film.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-afp { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-afpa { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-asp { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-aspx { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-avi { background: url(images/film.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-bat { background: url(images/application.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-bmp { background: url(images/picture.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-c { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-cfm { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-cgi { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-com { background: url(images/application.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-cpp { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-css { background: url(images/css.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-doc { background: url(images/doc.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-exe { background: url(images/application.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-gif { background: url(images/picture.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-fla { background: url(images/flash.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-h { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-htm { background: url(images/html.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-html { background: url(images/html.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-jar { background: url(images/java.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-jpg { background: url(images/picture.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-jpeg { background: url(images/picture.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-js { background: url(images/script.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-lasso { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-log { background: url(images/txt.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-m4p { background: url(images/music.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-mov { background: url(images/film.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-mp3 { background: url(images/music.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-mp4 { background: url(images/film.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-mpg { background: url(images/film.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-mpeg { background: url(images/film.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-ogg { background: url(images/music.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-pcx { background: url(images/picture.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-pdf { background: url(images/pdf.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-php { background: url(images/php.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-png { background: url(images/picture.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-ppt { background: url(images/ppt.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-psd { background: url(images/psd.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-pl { background: url(images/script.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-py { background: url(images/script.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-rb { background: url(images/ruby.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-rbx { background: url(images/ruby.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-rhtml { background: url(images/ruby.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-rpm { background: url(images/linux.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-ruby { background: url(images/ruby.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-sql { background: url(images/db.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-swf { background: url(images/flash.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-tif { background: url(images/picture.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-tiff { background: url(images/picture.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-txt { background: url(images/txt.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-vb { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-wav { background: url(images/music.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-wmv { background: url(images/film.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-xls { background: url(images/xls.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-xml { background: url(images/code.png) left top no-repeat; }
+.wysiwyg-files-wrapper li.wysiwyg-files-zip { background: url(images/zip.png) left top no-repeat; }
diff --git a/media/js/jwysiwyg/plugins/wysiwyg.autoload.js b/media/js/jwysiwyg/plugins/wysiwyg.autoload.js
new file mode 100644
index 0000000..58b64e7
--- /dev/null
+++ b/media/js/jwysiwyg/plugins/wysiwyg.autoload.js
@@ -0,0 +1,61 @@
+/**
+ * Autoload plugin
+ * 
+ * Depends on jWYSIWYG, autoload
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.autoload.js depends on $.wysiwyg";
+	}
+
+	if (undefined === $.autoload) {
+		throw "wysiwyg.autoload.js depends on $.autoload";
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	var autoload = {
+		name: "autoload",
+		version: "",
+		defaults: {
+			baseFile:		"jquery.wysiwyg.js",
+			css:			["jquery.wysiwyg.css", "jquery.wysiwyg.modal.css"],
+			cssPath:		"",
+			controlPath:	"controls/",
+			i18nPath:		"i18n/"
+		},
+		options: {},
+
+		css: function (names) {
+			$.autoload.css(names, this.options);
+		},
+
+		control: function (names, successCallback) {
+			$.autoload.js(names, {"baseFile": this.options.baseFile, "jsPath": this.options.controlPath, "successCallback": successCallback});
+		},
+
+		init: function (Wysiwyg) {
+			if (!Wysiwyg.options.plugins[this.name]) {
+				return true;
+			}
+
+			var i;
+
+			this.options = $.extend(true, this.defaults, Wysiwyg.options.plugins[this.name]);
+
+			if (this.options.css) {
+				for (i = 0; i < this.options.css.length; i += 1) {
+					this.css(this.options.css[i]);
+				}
+			}
+		},
+
+		lang: function (names, successCallback) {
+			$.autoload.js(names, {"baseFile": this.options.baseFile, "jsPath": this.options.i18nPath, "successCallback": successCallback});
+		}
+	};
+
+	$.wysiwyg.plugin.register(autoload);
+	$.wysiwyg.plugin.listen("initFrame", "autoload.init");
+})(jQuery);
diff --git a/media/js/jwysiwyg/plugins/wysiwyg.fileManager.js b/media/js/jwysiwyg/plugins/wysiwyg.fileManager.js
new file mode 100644
index 0000000..fbd30c6
--- /dev/null
+++ b/media/js/jwysiwyg/plugins/wysiwyg.fileManager.js
@@ -0,0 +1,574 @@
+/**
+ * File Manager plugin for jWYSIWYG
+ * 
+ * Yotam Bar-On, 2011
+ * 
+ * The file manager ui uses the Silk icon set from FAMFAMFAM
+ * 
+ */
+
+(function ($) {
+	"use strict";
+
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.fileManager.js depends on $.wysiwyg";
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	// Only for show
+	var fileManager = {
+		name: "fileManager",
+		version: "0.98", // Same as jwysiwyg
+		ajaxHandler: "",
+		selected: null,
+		setAjaxHandler: function (_handler) {
+			this.ajaxHandler = _handler;
+			this.ready = true;
+
+			return this;
+		},
+		ready: false,
+		init: function (Wysiwyg, callback) {
+			if (this.ready) {
+				var manager = new fileManagerObj(this.ajaxHandler);
+				manager.load(Wysiwyg, callback);
+			} else {
+				console.log("$.wysiwyg.fileManager: Must set ajax handler first, using $.wysiwyg.fileManager.setAjaxHandler()");
+				return false;
+			}
+		}
+	};
+
+	// Register:
+	$.wysiwyg.plugin.register(fileManager);
+
+	// Private object:
+	function fileManagerObj(_handler) {
+		this.handler = _handler;
+		this.loaded = false;
+		this.move = false;
+		this.rename = false;
+		this.remove = false;
+		this.upload = false;
+		this.mkdir = false;
+		this.selectedFile = "";
+		this.curDir = "/";
+		this.curListHtml = "";
+		this.dialog = null;
+		this.baseUrl = "";
+
+		/**
+		 * Methods
+		 */
+		var console = $.wysiwyg.console;
+		//console.log("handler: " + this.handler);
+
+		this.load = function (Wysiwyg, callback) {
+			var self = this;
+			self.loaded = true;
+			self.authenticate(function (response) {
+				if (response !== "success") {
+					var dialog = new $.wysiwyg.dialog(null, {
+						"title"   : "Error",
+						"content" : response
+					});
+					dialog.open();
+
+					return false;
+				}
+
+				// Wrap the file list:
+				var uiHtml = '<div class="wysiwyg-files-wrapper">' +
+								'<input type="text" name="url" />' +
+								'<div id="wysiwyg-files-list-wrapper"></div>';
+
+				// If handler does not support upload, icon will not appear:
+				if (self.upload.enabled) {
+					uiHtml += '<div class="wysiwyg-files-action-upload" title="{{upload_action}}"></div>';
+				}
+
+				// If handler does not support mkdir, icon will not appear:
+				if (self.mkdir.enabled) {
+					uiHtml += '<div class="wysiwyg-files-action-mkdir" title="{{mkdir_action}}"></div>';
+				}
+
+				uiHtml += '<input style="display:none;" type="button" name="submit" value="{{select}}" />' +
+							'</div>';
+
+				uiHtml = self.i18n(uiHtml);
+				if ($.wysiwyg.dialog) {
+					// Support for native $.wysiwyg.dialog()
+					var _title = self.i18n("{{file_manager}}");
+					var fileManagerUI = new $.wysiwyg.dialog(Wysiwyg, {
+						"title": _title,
+						"content": uiHtml,
+						"close": function (e, dialog) {
+							self.dialog = null;
+						},
+						"open": function (e, dialog) {
+
+							self.dialog = dialog;
+
+							self.loadDir();
+							self.bindHover();
+							self.bindBrowse();
+
+							// Select file bindings
+							dialog.find("input[name=submit]").bind("click", function () {
+								var file = dialog.find("input[name=url]").val();
+								fileManagerUI.close();
+								self.loaded = false;
+								callback(self.baseUrl+file);
+							});
+
+							// Create Directory
+							$(".wysiwyg-files-action-mkdir").bind("click", function (e) {
+								e.preventDefault();
+								var uiHtml = '<div>' +
+												'<input type="text" class="wysiwyg-files-textfield" name="newName" value="{{new_directory}}" />' +
+												'<input type="button" name="cancel" value="{{cancel}}" />' +
+												'<input type="button" name="create" value="{{create}}" />' +
+												'</div>';
+								uiHtml = self.i18n(uiHtml);
+								var _mkdirTitle = self.i18n("{{mkdir_title}}");
+								var mkdirDialog = new $.wysiwyg.dialog(null, {
+									"title": _mkdirTitle,
+									"content": uiHtml,
+									"close": function () {
+
+									},
+									"open": function (e, _dialog) {
+										_dialog.find("input[name=create]").bind("click", function () {
+											self.mkDir(_dialog.find("input[name=newName]").val(), function (response) {
+												self.loadDir();
+												mkdirDialog.close();
+											});
+										});
+
+										_dialog.find("input[name=cancel]").bind("click", function () {
+											mkdirDialog.close();
+										});
+									}
+								});
+								mkdirDialog.open();
+							});
+
+							// Upload File
+							$(".wysiwyg-files-action-upload").bind("click", function (e) {
+								self.loadUploadUI();
+							});
+
+						},
+						"modal": false
+						// "theme": "jqueryui"
+					});
+
+					fileManagerUI.open();
+
+				} else {
+					// If $.wysiwyg.dialog() does not work..
+					console.error("$.wysiwyg.fileManager: This plugin uses the native dialog system of jWYSIWYG. Make sure you are using version > 0.98");
+				}
+			});
+		};
+
+		this.authenticate = function (callback) {
+			if (!this.loaded) {
+				return false;
+			}
+			var self = this;
+			$.getJSON(self.handler, { "action": "auth", "auth": "jwysiwyg" }, function (json, textStatus) {
+				if (json.success) {
+					self.move = json.data.move;
+					self.rename = json.data.rename;
+					self.remove = json.data.remove;
+					self.mkdir = json.data.mkdir;
+					self.upload = json.data.upload;
+					self.baseUrl = json.data.baseUrl;
+					callback("success");
+				} else {
+					callback(json.error + "\n<br>$.wysiwyg.fileManager: Unable to authenticate handler.");
+				}
+			});
+		};
+
+		this.loadDir = function () {
+			if (!this.loaded) {
+				return false;
+			}
+			var self = this;
+			self.curDir = self.curDir.replace(/\/$/, '') + '/';
+
+			// Retreives list of files inside a certain directory:
+			$.getJSON(self.handler, { "dir": self.curDir, "action": "list" }, function (json) {
+				if (json.success) {
+					self.dialog.find("#wysiwyg-files-list-wrapper").removeClass("wysiwyg-files-ajax").html(self.listDir(json));
+					self.bindHover();
+					self.bindBrowse();
+				} else {
+					alert(json.error);
+				}
+			});
+		};
+
+		/**
+		 * Ajax Methods.
+		 */
+
+		// List Directory
+		this.listDir = function (json) {
+			if (!this.loaded) {
+				return false;
+			}
+			var self = this;
+			var treeHtml = '<ul class="wysiwyg-files-list">';
+			if (self.curDir !== "/") {
+				var prevDir = self.curDir.replace(/[^\/]+\/?$/, '');
+				treeHtml += '<li class="wysiwyg-files-dir wysiwyg-files-dir-prev">' +
+							'<a href="#" rel="' + prevDir + '" title="{{previous_directory}}">' +
+							self.curDir +
+							'</a></li>';
+			}
+			$.each(json.data.directories, function (name, dirPath) {
+				treeHtml += '<li class="wysiwyg-files-dir">' +
+							'<a href="#" rel="' + dirPath + '">' +
+							name +
+							'</a></li>';
+			});
+			$.each(json.data.files, function (name, url) {
+				var ext = name.replace(/^.*?\./, '').toLowerCase();
+				treeHtml += '<li class="wysiwyg-files-file wysiwyg-files-' + ext + '">' +
+							'<a href="#" rel="' + url + '">' +
+							name +
+							'</a></li>';
+			});
+			treeHtml += '</ul>';
+
+			return self.i18n(treeHtml);
+		};
+
+/**
+ * Should be remembered for future implementation:
+ * If handler does not support certain actions - do not show their icons/button.
+ * Only action a handler MUST support is "list" (list directory).
+ * 
+ * Implemented: 28-May-2011, Yotam Bar-On
+ */
+
+		// Remove File Method:
+		this.removeFile = function (type, callback) {
+			if (!this.loaded) { return false; }
+			if (!this.remove.enabled) { console.log("$.wysiwyg.fileManager: handler: remove is disabled."); return false; }
+
+			var self = this;
+			$.getJSON(self.remove.handler, { "action": "remove", "type": type, "dir": self.curDir, "file": self.selectedFile  }, function (json) {
+				if (json.success) {
+					alert(json.data);
+				} else {
+					alert(json.error);
+				}
+				callback(json);
+			});
+		};
+
+		// Rename File Method
+		this.renameFile = function (type, newName, callback) {
+			if (!this.loaded) { return false; }
+			if (!this.rename.enabled) { console.log("$.wysiwyg.fileManager: handler: rename is disabled."); return false; }
+
+			var self = this;
+			$.getJSON(self.rename.handler, { "action": "rename", "type": type, "dir": self.curDir, "file": self.selectedFile, "newName": newName  }, function (json) {
+				if (json.success) {
+					alert(json.data);
+				} else {
+					alert(json.error);
+				}
+				callback(json);
+			});
+		};
+
+		// Make Directory Method
+		this.mkDir = function (newName, callback) {
+			if (!this.loaded) { return false; }
+			if (!this.mkdir.enabled) { console.log("$.wysiwyg.fileManager: handler: mkdir is disabled."); return false; }
+
+			var self = this;
+			$.getJSON(self.mkdir.handler, { "action": "mkdir", "dir": self.curDir, "newName": newName  }, function (json) {
+				if (json.success) {
+					alert(json.data);
+				} else {
+					alert(json.error);
+				}
+				callback(json);
+			});
+		};
+
+		/**
+		 * Currently we will not support moving of files. This will be supported only when a more interactive interface will be introduced.
+		 */
+		this.moveFile = function () {
+			if (!this.loaded) {
+				return false;
+			}
+			if (!this.move.enabled) {
+				console.log("$.wysiwyg.fileManager: handler: move is disabled."); return false;
+			}
+			var self = this;
+			return false;
+		};
+
+		// Upload:
+		this.loadUploadUI = function () {
+			if (!this.loaded) { return false; }
+			if (!this.upload.enabled) { console.log("$.wysiwyg.fileManager: handler: move is disabled."); return false; }
+			var self = this;
+			var uiHtml = '<form enctype="multipart/form-data" method="post" action="' + self.upload.handler + '">' +
+							'<p><input type="file" name="handle" /><br>' +
+							'<input type="text" name="newName" style="width:250px; border:solid 1px !important;" /><br>' +
+							'<input type="text" name="action" style="display:none;" value="upload" /><br></p>' +
+							'<input type="text" name="dir" style="display:none;" value="' + self.curDir + '" /></p>' +
+							'<input type="submit" name="submit" value="{{submit}}" />' +
+							'</form>';
+			uiHtml = self.i18n(uiHtml);
+
+			var _uploadTitle = self.i18n("{{upload_title}}");
+
+			var dialog = new $.wysiwyg.dialog(null, {
+				"title": _uploadTitle,
+				"content": "",
+				"open": function (e, _dialog) {
+
+					$("<iframe/>", { "class": "wysiwyg-files-upload" }).load(function () {
+						var $doc = $(this).contents();
+						$doc.find("body").append(uiHtml);
+						$doc.find("input[type=file]").change(function () {
+							var $val = $(this).val();
+							$val = $val.replace(/.*[\\\/]/, '');
+							// Should implement validation of extensions before submitting form
+							$doc.find("input[name=newName]").val($val);
+						});
+
+					}).appendTo(_dialog.find(".wysiwyg-dialog-content"));
+
+				},
+				"close": function () {
+					self.loadDir();
+				}
+			});
+
+			dialog.open();
+		};
+
+		/**
+		 * i18n Support.
+		 * The below methods will enable basic support for i18n
+		 */
+
+		// Default translations (EN):
+		this.defaultTranslations = {
+			"file_manager": 		"File Manager",
+			"upload_title":			"Upload File",
+			"rename_title":			"Rename File",
+			"remove_title":			"Remove File",
+			"mkdir_title":			"Create Directory",
+			"upload_action": 		"Upload new file to current directory",
+			"mkdir_action": 		"Create new directory",
+			"remove_action": 		"Remove this file",
+			"rename_action": 		"Rename this file",
+			"delete_message": 		"Are you sure you want to delete this file?",
+			"new_directory": 		"New Directory",
+			"previous_directory": 	"Go to previous directory",
+			"rename":				"Rename",
+			"select": 				"Select",
+			"create": 				"Create",
+			"submit": 				"Submit",
+			"cancel": 				"Cancel",
+			"yes":					"Yes",
+			"no":					"No"
+		};
+		/** 
+		 * Take an html string with placeholders: {{placeholder}} and translate it. 
+		 * It takes all labels and trys to translate them. 
+		 * If there is no translation (or i18n plugin is not loaded) it will use the defaults.
+		 */
+		this.i18n = function (tHtml) {
+			var map = this.defaultTranslations;
+			// If i18n plugin exists:
+			if ($.wysiwyg.i18n) {
+				$.each(map, function (key, val) {
+					map[key] = $.wysiwyg.i18n.t(key, "dialogs.fileManager");
+				});
+			}
+
+			$.each(map, function (key, val) {
+				tHtml = tHtml.replace("{{" + key + "}}", val);
+			});
+
+			return tHtml;
+		};
+
+		/**
+		 * BINDINGS FOR ELEMENTS
+		 * The below methods are bind methods for elements inside the File Manager's dialogs.
+		 * Their purpose is to enable simple coding of the dialog interfaces,
+		 * and to make the use of "live" deprecated.
+		 */
+
+		this.bindHover = function () {
+			var self = this,
+				dialog = self.dialog,
+				object = dialog.find("li");
+
+			/** 
+			 * HOVER + ACTIONS BINDINGS:
+			 */
+			object.bind("mouseenter", function () {
+				$(this).addClass("wysiwyg-files-hover");
+
+				if ($(this).hasClass("wysiwyg-files-dir")) {
+					$(this).addClass("wysiwyg-files-dir-expanded");
+				}
+
+				// Add action buttons:
+				if (!$(this).hasClass("wysiwyg-files-dir-prev")) {
+
+					$(".wysiwyg-files-action").remove();
+
+					// If handler does not support remove, icon will not appear:
+					if (self.remove.enabled) {
+						var rmText = self.i18n("{{remove_action}}");
+						$("<div/>", { "class": "wysiwyg-files-action wysiwyg-files-action-remove", "title": rmText }).appendTo(this);
+
+						// "Remove" binding:
+						$(".wysiwyg-files-action-remove").bind("click", function (e) {
+							e.preventDefault();
+							var entry = $(this).parent("li");
+							// What are we deleting?
+							var type = entry.hasClass("wysiwyg-files-file") ? "file" : "dir";
+							var uiHtml = 	"<p>{{delete_message}}</p>" +
+											'<div class="">' +
+											'<input type="button" name="cancel" value="{{no}}" />' +
+											'<input type="button" name="remove" value="{{yes}}" />' +
+											"</div>";
+							uiHtml = self.i18n(uiHtml);
+
+							var _removeTitle = self.i18n("{{remove_title}}");
+
+							var removeDialog = 	new $.wysiwyg.dialog(null, {
+								"title": _removeTitle,
+								"content": uiHtml,
+								"close": function () {
+
+								},
+								"open": function (e, _dialog) {
+									_dialog.find("input[name=remove]").bind("click", function () {
+										self.selectedFile = entry.find("a").text();
+										self.removeFile(type, function (response) {
+											self.loadDir();
+											removeDialog.close();
+										});
+									});
+
+									_dialog.find("input[name=cancel]").bind("click", function () {
+										removeDialog.close();
+									});
+								}
+							});
+
+							removeDialog.open();
+						});
+					}
+
+					// If handler does not support rename, icon will not appear:
+					if (self.rename.enabled) {
+						var rnText = self.i18n("{{rename_action}}");
+						$("<div/>", { "class": "wysiwyg-files-action wysiwyg-files-action-rename", "title": rnText }).appendTo(this);
+
+						// "Rename" binding:
+						$(".wysiwyg-files-action-rename").bind("click", function (e) {
+							e.preventDefault();
+							var entry = $(this).parent("li");
+							// What are we deleting?
+							var type = entry.hasClass("wysiwyg-files-file") ? "file" : "dir";
+							var uiHtml = 	'<div>' +
+											'<input type="text" class="wysiwyg-files-textfield" name="newName" value="' + entry.find("a").text() + '" />' +
+											'<input type="button" name="cancel" value="{{cancel}}" />' +
+											'<input type="button" name="rename" value="{{rename}}" />' +
+											'</div>';
+							uiHtml = self.i18n(uiHtml);
+							var _renameTitle = self.i18n("{{rename_title}}");
+
+							var renameDialog = new $.wysiwyg.dialog(null, {
+								"title": _renameTitle,
+								"content": uiHtml,
+								"close": function () {
+
+								},
+								"open": function (e, _dialog) {
+									_dialog.find("input[name=rename]").bind("click", function () {
+										self.selectedFile = entry.find("a").text();
+										self.renameFile(type, _dialog.find("input[name=newName]").val(), function (response) {
+											self.loadDir();
+											renameDialog.close();
+										});
+									});
+
+									_dialog.find("input[name=cancel]").bind("click", function () {
+										renameDialog.close();
+									});
+								}
+							});
+
+							renameDialog.open();
+						});
+					}
+				}
+			}).bind("mouseleave", function () {
+				$(this).removeClass("wysiwyg-files-dir-expanded");
+				$(this).removeClass("wysiwyg-files-hover");
+
+				// Remove action buttons:
+				$(".wysiwyg-files-action").remove();
+			});
+		};
+
+		/**
+		 * BROWSING BINDINGS
+		 */
+		this.bindBrowse = function () {
+			var self = this,
+				dialog = self.dialog,
+				object = self.dialog.find("li").find("a");
+
+			// Browse:
+			object.bind("click", function (e) {
+				$(".wysiwyg-files-wrapper").find("li").css("backgroundColor", "#FFF");
+
+				// Browse Directory:
+				if ($(this).parent("li").hasClass("wysiwyg-files-dir")) {
+					self.selectedFile = $(this).attr("rel");
+					self.curDir = $(this).attr("rel");
+					dialog.find("input[name=submit]").hide();
+					$(".wysiwyg-files-wrapper").find("input[name=url]").val("");
+					$('#wysiwyg-files-list-wrapper').addClass("wysiwyg-files-ajax");
+					$('#wysiwyg-files-list-wrapper').html("");
+					self.loadDir();
+					dialog.find("input[name=submit]").hide();
+
+				// Select Entry:
+				} else {
+					self.selectedFile = $(this).text();
+					$(this).parent("li").css("backgroundColor", "#BDF");
+					$(".wysiwyg-files-wrapper").find("input[name=url]").val($(this).attr("rel"));
+					dialog.find("input[name=submit]").show();
+				}
+			});
+		};
+
+		this.bindPreview = function (object) {
+			var self = this;
+		};
+	}
+})(jQuery);
diff --git a/media/js/jwysiwyg/plugins/wysiwyg.fullscreen.js b/media/js/jwysiwyg/plugins/wysiwyg.fullscreen.js
new file mode 100644
index 0000000..6d73b65
--- /dev/null
+++ b/media/js/jwysiwyg/plugins/wysiwyg.fullscreen.js
@@ -0,0 +1,141 @@
+/**
+ * Fullscreen plugin
+ * 
+ * Depends on jWYSIWYG
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.fullscreen.js depends on $.wysiwyg";
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	var fullscreen = {
+		name: "fullscreen",
+		version: "",
+		defaults: {
+			css: {
+				editor: {
+					width:	"100%",
+					height:	"100%"
+				},
+				element: {
+					width:	"100%",
+					height:	"100%",
+					position: "fixed",
+					left:	"0px",
+					top:	"0px",
+					background: "rgb(255, 255, 255)",
+					padding: "0px",
+					"border-bottom-color": "",
+					"border-bottom-style": "",
+					"border-bottom-width": "0px",
+					"border-left-color": "",
+					"border-left-style": "",
+					"border-left-width": "0px",
+					"border-right-color": "",
+					"border-right-style": "",
+					"border-right-width": "0px",
+					"border-top-color": "",
+					"border-top-style": "",
+					"border-top-width": "0px",
+					"z-index": 1000
+				},
+				original: {
+					width:	"100%",
+					height:	"100%",
+					position: "fixed",
+					left:	"0px",
+					top:	"0px",
+					background: "rgb(255, 255, 255)",
+					padding: "0px",
+					"border-bottom-color": "",
+					"border-bottom-style": "",
+					"border-bottom-width": "0px",
+					"border-left-color": "",
+					"border-left-style": "",
+					"border-left-width": "0px",
+					"border-right-color": "",
+					"border-right-style": "",
+					"border-right-width": "0px",
+					"border-top-color": "",
+					"border-top-style": "",
+					"border-top-width": "0px",
+					"z-index": 1000
+				}
+			}
+		},
+		options: {},
+		originalBoundary: {
+			editor: {},
+			element: {},
+			original: {}
+		},
+
+		init: function (Wysiwyg, options) {
+			options = options || {};
+			this.options = $.extend(true, this.defaults, options);
+
+			if (Wysiwyg.ui.toolbar.find(".fullscreen").hasClass("active")) {
+				this.restore(Wysiwyg);
+				Wysiwyg.ui.toolbar.find(".fullscreen").removeClass("active");
+			} else {
+				this.stretch(Wysiwyg);
+				Wysiwyg.ui.toolbar.find(".fullscreen").addClass("active");
+			}
+		},
+
+		restore: function (Wysiwyg) {
+			var propertyName;
+
+			for (propertyName in this.defaults.css.editor) {
+				Wysiwyg.editor.css(propertyName, this.originalBoundary.editor[propertyName]);
+				this.originalBoundary.editor[propertyName] = null;
+			}
+
+			for (propertyName in this.defaults.css.element) {
+				Wysiwyg.element.css(propertyName, this.originalBoundary.element[propertyName]);
+				this.originalBoundary.element[propertyName] = null;
+			}
+
+			for (propertyName in this.defaults.css.original) {
+				$(Wysiwyg.original).css(propertyName, this.originalBoundary.original[propertyName]);
+				this.originalBoundary.original[propertyName] = null;
+			}
+		},
+
+		stretch: function (Wysiwyg) {
+			var propertyName;
+
+			// save previous values
+			for (propertyName in this.defaults.css.editor) {
+				this.originalBoundary.editor[propertyName] = Wysiwyg.editor.css(propertyName);
+			}
+
+			for (propertyName in this.defaults.css.element) {
+				this.originalBoundary.element[propertyName] = Wysiwyg.element.css(propertyName);
+			}
+
+			for (propertyName in this.defaults.css.original) {
+				this.originalBoundary.original[propertyName] = $(Wysiwyg.original).css(propertyName);
+			}
+
+			// set new values
+			for (propertyName in this.defaults.css.editor) {
+				Wysiwyg.editor.css(propertyName, this.options.css.editor[propertyName]);
+			}
+
+			for (propertyName in this.defaults.css.element) {
+				Wysiwyg.element.css(propertyName, this.options.css.element[propertyName]);
+			}
+
+			this.options.css.original.top = Wysiwyg.ui.toolbar.css("height");
+			for (propertyName in this.defaults.css.original) {
+				$(Wysiwyg.original).css(propertyName, this.options.css.original[propertyName]);
+			}
+		}
+	};
+
+	$.wysiwyg.plugin.register(fullscreen);
+})(jQuery);
diff --git a/media/js/jwysiwyg/plugins/wysiwyg.i18n.js b/media/js/jwysiwyg/plugins/wysiwyg.i18n.js
new file mode 100644
index 0000000..161c138
--- /dev/null
+++ b/media/js/jwysiwyg/plugins/wysiwyg.i18n.js
@@ -0,0 +1,99 @@
+/**
+ * Internationalization plugin
+ * 
+ * Depends on jWYSIWYG
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.i18n.js depends on $.wysiwyg";
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	var i18n = {
+		name: "i18n",
+		version: "",
+		defaults: {
+			lang: "en",			// change to your language by passing lang option
+			wysiwygLang: "en"	// default WYSIWYG language
+		},
+		lang: {},
+		options: {},
+
+		init: function (Wysiwyg, lang) {
+			if (!Wysiwyg.options.plugins[this.name]) {
+				return true;
+			}
+
+			this.options = $.extend(true, this.defaults, Wysiwyg.options.plugins[this.name]);
+
+			if (lang) {
+				this.options.lang = lang;
+			} else {
+				lang = this.options.lang;
+			}
+
+			if ((lang !== this.options.wysiwygLang) && (undefined === $.wysiwyg.i18n.lang[lang])) {
+				if ($.wysiwyg.autoload) {
+					$.wysiwyg.autoload.lang("lang." + lang + ".js", function () {
+						$.wysiwyg.i18n.init(Wysiwyg, lang);
+					});
+				} else {
+					throw 'Language "' + lang + '" not found in $.wysiwyg.i18n. You need to include this language file';
+				}
+			}
+
+			this.translateControls(Wysiwyg, lang);
+		},
+
+		translateControls: function (Wysiwyg, lang) {
+			Wysiwyg.ui.toolbar.find("li").each(function () {
+				if (Wysiwyg.options.controls[$(this).attr("class")] && Wysiwyg.options.controls[$(this).attr("class")].visible) {
+					$(this).attr("title", $.wysiwyg.i18n.t(Wysiwyg.options.controls[$(this).attr("class")].tooltip, "controls", lang));
+				}
+			});
+		},
+
+		run: function (object, lang) {
+			return object.each(function () {
+				var oWysiwyg = $(this).data("wysiwyg");
+
+				if (!oWysiwyg) {
+					return this;
+				}
+
+				$.wysiwyg.i18n.init(oWysiwyg, lang);
+			});
+		},
+
+		t: function (phrase, section, lang) {
+			var i, section_array, transObject;
+
+			if (!lang) {
+				lang = this.options.lang;
+			}
+
+			if ((lang === this.options.wysiwygLang) || (!this.lang[lang])) {
+				return phrase;
+			}
+
+			transObject = this.lang[lang];
+			section_array = section.split(".");
+			for (i = 0; i < section_array.length; i += 1) {
+				if (transObject[section_array[i]]) {
+					transObject = transObject[section_array[i]];
+				}
+			}
+
+			if (transObject[phrase]) {
+				return transObject[phrase];
+			}
+
+			return phrase;
+		}
+	};
+
+	$.wysiwyg.plugin.register(i18n);
+	$.wysiwyg.plugin.listen("initFrame", "i18n.init");
+})(jQuery);
diff --git a/media/js/jwysiwyg/plugins/wysiwyg.rmFormat.js b/media/js/jwysiwyg/plugins/wysiwyg.rmFormat.js
new file mode 100644
index 0000000..810f153
--- /dev/null
+++ b/media/js/jwysiwyg/plugins/wysiwyg.rmFormat.js
@@ -0,0 +1,357 @@
+/**
+ * rmFormat plugin
+ *
+ * Depends on jWYSIWYG
+ */
+(function ($) {
+	if (undefined === $.wysiwyg) {
+		throw "wysiwyg.rmFormat.js depends on $.wysiwyg";
+	}
+
+	/*
+	 * Wysiwyg namespace: public properties and methods
+	 */
+	var rmFormat = {
+		name: "rmFormat",
+		version: "",
+		defaults: {
+			rules: {
+				heading: false,
+				table: false,
+				inlineCSS: false,
+				/*
+				 * rmAttr       - { "all" | object with names } remove all
+				 *                attributes or attributes with following names
+				 *
+				 * rmWhenEmpty  - if element contains no text or { \s, \n, <br>, <br/> }
+				 *                then it will be removed
+				 *
+				 * rmWhenNoAttr - if element contains no attributes (i.e. <span>Some Text</span>)
+				 *                then it will be removed
+				 */
+				msWordMarkup: {
+					enabled: false,
+					tags: {
+						"a": {
+							rmWhenEmpty: true
+						},
+
+						"b": {
+							rmWhenEmpty: true
+						},
+
+						"div": {
+							rmWhenEmpty: true,
+							rmWhenNoAttr: true
+						},
+
+						"em": {
+							rmWhenEmpty: true
+						},
+
+						"font": {
+							rmAttr: {
+								"face": "",
+								"size": ""
+							},
+							rmWhenEmpty: true,
+							rmWhenNoAttr: true
+						},
+
+						"h1": {
+							rmAttr: "all",
+							rmWhenEmpty: true
+						},
+						"h2": {
+							rmAttr: "all",
+							rmWhenEmpty: true
+						},
+						"h3": {
+							rmAttr: "all",
+							rmWhenEmpty: true
+						},
+						"h4": {
+							rmAttr: "all",
+							rmWhenEmpty: true
+						},
+						"h5": {
+							rmAttr: "all",
+							rmWhenEmpty: true
+						},
+						"h6": {
+							rmAttr: "all",
+							rmWhenEmpty: true
+						},
+
+						"i": {
+							rmWhenEmpty: true
+						},
+
+						"p": {
+							rmAttr: "all",
+							rmWhenEmpty: true
+						},
+
+						"span": {
+							rmAttr: {
+								lang: ""
+							},
+							rmWhenEmpty: true,
+							rmWhenNoAttr: true
+						},
+
+						"strong": {
+							rmWhenEmpty: true
+						},
+
+						"u": {
+							rmWhenEmpty: true
+						}
+					}
+				}
+			}
+		},
+		options: {},
+		enabled: false,
+		debug:	false,
+
+		domRemove: function (node) {
+			// replace h1-h6 with p
+			if (this.options.rules.heading) {
+				if (node.nodeName.toLowerCase().match(/^h[1-6]$/)) {
+					// in chromium change this to
+					// $(node).replaceWith($('<p/>').html(node.innerHTML));
+					// to except DOM error: also try in other browsers
+					$(node).replaceWith($('<p/>').html($(node).contents()));
+
+					return true;
+				}
+			}
+
+			// remove tables not smart enough )
+			if (this.options.rules.table) {
+				if (node.nodeName.toLowerCase().match(/^(table|t[dhr]|t(body|foot|head))$/)) {
+					$(node).replaceWith($(node).contents());
+
+					return true;
+				}
+			}
+
+			return false;
+		},
+		
+		removeStyle: function(node) {
+		  if (this.options.rules.inlineCSS) {
+		    $(node).removeAttr('style');
+		  }
+		},
+
+		domTraversing: function (el, start, end, canRemove, cnt) {
+			if (null === canRemove) {
+				canRemove = false;
+			}
+
+			var isDomRemoved, p;
+
+			while (el) {
+				if (this.debug) { console.log(cnt, "canRemove=", canRemove); }
+				
+				this.removeStyle(el);
+
+				if (el.firstElementChild) {
+
+					if (this.debug) { console.log(cnt, "domTraversing", el.firstElementChild); }
+
+					return this.domTraversing(el.firstElementChild, start, end, canRemove, cnt + 1);
+				} else {
+
+					if (this.debug) { console.log(cnt, "routine", el); }
+
+					isDomRemoved = false;
+
+					if (start === el) {
+						canRemove = true;
+					}
+
+					if (canRemove) {
+						if (el.previousElementSibling) {
+							p = el.previousElementSibling;
+						} else {
+							p = el.parentNode;
+						}
+
+						if (this.debug) { console.log(cnt, el.nodeName, el.previousElementSibling, el.parentNode, p); }
+
+						isDomRemoved = this.domRemove(el);
+						if (this.domRemove(el)) {
+
+							if (this.debug) { console.log("p", p); }
+
+							// step back to parent or previousElement to traverse again then element is removed
+							el = p;
+						}
+
+						if (start !== end && end === el) {
+							return true;
+						}
+					}
+
+					if (false === isDomRemoved) {
+						el = el.nextElementSibling;
+					}
+				}
+			}
+
+			return false;
+		},
+
+		msWordMarkup: function (text) {
+			var tagName, attrName, rules, reg, regAttr, found, attrs;
+
+			// @link https://github.com/akzhan/jwysiwyg/issues/165
+			text = text.replace(/</g, "<").replace(/>/g, ">");
+
+			text = text.replace(/<meta\s[^>]+>/g, "");
+			text = text.replace(/<link\s[^>]+>/g, "");
+			text = text.replace(/<title>[\s\S]*?<\/title>/g, "");
+			text = text.replace(/<style(?:\s[^>]*)?>[\s\S]*?<\/style>/gm, "");
+			text = text.replace(/<w:([^\s>]+)(?:\s[^\/]+)?\/>/g, "");
+			text = text.replace(/<w:([^\s>]+)(?:\s[^>]+)?>[\s\S]*?<\/w:\1>/gm, "");
+			text = text.replace(/<m:([^\s>]+)(?:\s[^\/]+)?\/>/g, "");
+			text = text.replace(/<m:([^\s>]+)(?:\s[^>]+)?>[\s\S]*?<\/m:\1>/gm, "");
+
+			// after running the above.. it still needed these
+			text = text.replace(/<xml>[\s\S]*?<\/xml>/g, "");
+			text = text.replace(/<object(?:\s[^>]*)?>[\s\S]*?<\/object>/g, "");
+			text = text.replace(/<o:([^\s>]+)(?:\s[^\/]+)?\/>/g, "");
+			text = text.replace(/<o:([^\s>]+)(?:\s[^>]+)?>[\s\S]*?<\/o:\1>/gm, "");
+			text = text.replace(/<st1:([^\s>]+)(?:\s[^\/]+)?\/>/g, "");
+			text = text.replace(/<st1:([^\s>]+)(?:\s[^>]+)?>[\s\S]*?<\/st1:\1>/gm, "");
+			// ----
+			text = text.replace(/<!--[^>]+>\s*<[^>]+>/gm, ""); // firefox needed this 1
+
+						
+			text = text.replace(/^[\s\n]+/gm, "");
+
+			if (this.options.rules.msWordMarkup.tags) {
+				for (tagName in this.options.rules.msWordMarkup.tags) {
+					rules = this.options.rules.msWordMarkup.tags[tagName];
+					
+					if ("string" === typeof (rules)) {
+						if ("" === rules) {
+							reg = new RegExp("<" + tagName + "(?:\\s[^>]+)?/?>", "gmi");
+							text = text.replace(reg, "<" + tagName + ">");
+						}
+					} else if ("object" === typeof (rules)) {
+						reg = new RegExp("<" + tagName + "(\\s[^>]+)?/?>", "gmi");
+						found = reg.exec(text);
+						attrs = "";
+
+						if (found && found[1]) {
+							attrs = found[1];
+						}
+
+						if (rules.rmAttr) {
+							if ("all" === rules.rmAttr) {
+								attrs = "";
+							} else if ("object" === typeof (rules.rmAttr) && attrs) {
+								for (attrName in rules.rmAttr) {
+									regAttr = new RegExp(attrName + '="[^"]*"', "mi");
+									attrs = attrs.replace(regAttr, "");
+								}
+							}
+						}
+
+						if (attrs) {
+							attrs = attrs.replace(/[\s\n]+/gm, " ");
+							
+							if (" " === attrs) {
+								attrs = "";
+							}
+						}
+
+						text = text.replace(reg, "<" + tagName + attrs + ">");
+					}
+				}
+
+				for (tagName in this.options.rules.msWordMarkup.tags) {
+					rules = this.options.rules.msWordMarkup.tags[tagName];
+
+					if ("string" === typeof (rules)) {
+						//
+					} else if ("object" === typeof (rules)) {
+						if (rules.rmWhenEmpty) {
+							reg = new RegExp("<" + tagName + "(\\s[^>]+)?>(?:[\\s\\n]|<br/?>)*?</" + tagName + ">", "gmi");
+							text = text.replace(reg, "");
+						}
+
+						if (rules.rmWhenNoAttr) {
+							reg = new RegExp("<" + tagName + ">(?!<" + tagName + ">)([\\s\\S]*?)</" + tagName + ">", "mi");
+							found = reg.exec(text);
+							while (found) {
+								text = text.replace(reg, found[1]);
+
+								found = reg.exec(text);
+							}
+						}
+					}
+				}
+			}
+
+			return text;
+		},
+
+		run: function (Wysiwyg, options) {
+			options = options || {};
+			this.options = $.extend(true, this.defaults, options);
+
+			if (this.options.rules.heading || this.options.rules.table) {
+				var r = Wysiwyg.getInternalRange(),
+					start,
+					end,
+					node,
+					traversing;
+
+				start = r.startContainer;
+				if (start.nodeType === 3) {
+					start = start.parentNode;
+				}
+
+				end = r.endContainer;
+				if (end.nodeType === 3) {
+					end = end.parentNode;
+				}
+
+				if (this.debug) {
+					console.log("start", start, start.nodeType, start.nodeName, start.parentNode);
+					console.log("end", end, end.nodeType, end.nodeName, end.parentNode);
+				}
+
+				node = r.commonAncestorContainer;
+				if (node.nodeType === 3) {
+					node = node.parentNode;
+				}
+
+				if (this.debug) {
+					console.log("node", node, node.nodeType, node.nodeName.toLowerCase(), node.parentNode, node.firstElementChild);
+					console.log(start === end);
+				}
+
+				traversing = null;
+				if (start === end) {
+					traversing = this.domTraversing(node, start, end, true, 1);
+				} else {
+					traversing = this.domTraversing(node.firstElementChild, start, end, null, 1);
+				}
+
+				if (this.debug) { console.log("DomTraversing=", traversing); }
+			}
+
+			if (this.options.rules.msWordMarkup.enabled) {
+				Wysiwyg.setContent(this.msWordMarkup(Wysiwyg.getContent()));
+			}
+		}
+	};
+
+	$.wysiwyg.plugin.register(rmFormat);
+})(jQuery);
diff --git a/media/js/jwysiwyg/quirks/placeholder.mozilla.js b/media/js/jwysiwyg/quirks/placeholder.mozilla.js
new file mode 100644
index 0000000..6123db3
--- /dev/null
+++ b/media/js/jwysiwyg/quirks/placeholder.mozilla.js
@@ -0,0 +1,102 @@
+(function($) {
+	"use strict";
+	$.wysiwyg.quirk.register({
+		defaultBlock: 'p',
+		placeholder: 'br',
+		
+		/**
+		 * @author stianlik at github
+		 * @link https://github.com/jwysiwyg/jwysiwyg/issues/345
+		 * @param {Wysiwyg} editor Editor instance
+		 */
+		init: function(editor) {
+			if ($.browser.mozilla) {
+				var that = this;
+				$(editor.editorDoc).on('input cut paste keyup', function() {
+					var context = {
+						editor: editor,
+						container: editor.editorDoc.body,
+						selection: editor.getInternalSelection()
+					};
+					// A bit hacky, but this should push apply() to end of execution
+					// queue so that it is executed after the event has been processed.
+					// http://ejohn.org/blog/how-javascript-timers-work/
+					setTimeout( function() {that.apply(context);}, 0);
+				});
+			}
+		},
+		
+		apply: function(context) {
+			var range = context.editor.getInternalRange();
+			if (!range) return;
+			
+			if (this.isNotEnclosed(context, range)) {
+				// Avoid empty root node by enclosing range with block element
+				this.enclose(context, range);
+			}
+			else if(this.isRootNode(context, range.startContainer) && range.endOffset === 0) {
+				// Avoid writing directly to root node by jumping to existing block element
+				// Handles cases where users focus the editor by clicking TAB
+				range.selectNodeContents(context.container.firstChild);
+				range.collapse(true);
+			}
+		},
+		
+		
+		enclose: function(context, range) {
+			var el = context.editor.editorDoc.createElement(this.defaultBlock);
+			
+			// Append non-enclosed content to container
+			for (var i = 0; i < context.container.childNodes.length; ++i) {
+				el.appendChild(context.container.childNodes[i]);
+			}
+			
+			// Append placeholder if there are no content
+			if (el.childNodes.length === 0) {
+				el.appendChild(this.createPlaceholder(context));
+			}
+			
+			// Replace mozilla placeholder if found
+			else if (this.isPlaceholder(el.lastChild)) {
+				el.replaceChild(this.createPlaceholder(context), el.lastChild);
+			}
+			
+			context.container.appendChild(el);
+			
+			// Move cursor into block element
+			context.selection.removeAllRanges();
+			range.selectNode(el.lastChild);
+			range.collapse(el.lastChild.tagName === 'BR');
+			context.selection.addRange(range);
+		},
+		
+		createPlaceholder: function(context) {
+			return context.editor.editorDoc.createElement(this.placeholder);
+		},
+		
+		isNotEnclosed: function(context, range) {
+			return (this.isRootNode(context, range.startContainer) || this.isTextNode(range.startContainer)) &&
+			(this.hasNoElements(context.container) || this.hasOnlyPlaceholderElement(context.container));
+		},
+		
+		isTextNode: function(node) {
+			return node.nodeType === 3;
+		},
+		
+		isRootNode: function(context, node) {
+			return node === context.container;
+		},
+		
+		isPlaceholder: function(node) {
+			return node.tagName && node.tagName === 'BR';
+		},
+		
+		hasNoElements: function(element) {
+			return element.children.length === 0;
+		},
+		
+		hasOnlyPlaceholderElement: function(element) {
+			return element.children.length === 1 && this.isPlaceholder(element.lastChild);
+		}
+	});
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/quirks/toolbarHighlight.interval.js b/media/js/jwysiwyg/quirks/toolbarHighlight.interval.js
new file mode 100644
index 0000000..7b8c69f
--- /dev/null
+++ b/media/js/jwysiwyg/quirks/toolbarHighlight.interval.js
@@ -0,0 +1,28 @@
+
+(function($) {
+	$.wysiwyg.quirk.register({
+		/**
+		 * @link https://github.com/jwysiwyg/jwysiwyg/issues/251
+		 * @param {Wysiwyg} editor
+		 */
+		init: function(editor) {
+			setInterval(function () {
+				var offset = null;
+				try {
+					var range = editor.getInternalRange();
+					if (!range) return;
+					offset = {
+						range: range,
+						parent: range.endContainer ? range.endContainer.parentNode : range.parentElement(),
+						width: (range.startOffset ? (range.startOffset - range.endOffset) : range.boundingWidth) || 0
+					};
+				}
+				catch (e) { console.error(e); }
+				
+				if (offset && offset.width == 0 && !editor.editorDoc.rememberCommand) {
+					editor.ui.checkTargets(offset.parent);
+				}
+			}, 400);
+		},
+	});
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/jwysiwyg/quirks/toolbarHighlight.keyup.js b/media/js/jwysiwyg/quirks/toolbarHighlight.keyup.js
new file mode 100644
index 0000000..62c7309
--- /dev/null
+++ b/media/js/jwysiwyg/quirks/toolbarHighlight.keyup.js
@@ -0,0 +1,27 @@
+(function($) {
+	$.wysiwyg.quirk.register({
+		/**
+		 * @author rmlmedia at github
+		 * @link https://github.com/jwysiwyg/jwysiwyg/issues/251
+		 * @param {Wysiwyg} editor
+		 */
+		init: function(editor) {
+			$(editor.editorDoc).bind("keyup.wysiwyg", function(event) {
+				var node = null;
+				var selection = editor.getInternalSelection();
+				node = selection.extentNode || selection.focusNode;
+				// Allow for older versions of IE (8 or lower)
+				if ($.browser.msie && node == null) node = editor.getInternalRange().parentElement();
+	
+				while (node.style === undefined) {
+					node = node.parentNode;
+					if (node.tagName && node.tagName.toLowerCase() === "body") {
+						return;
+					}
+				}
+				
+				if (node != null) editor.ui.checkTargets(node);
+			});
+		}
+	});
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/login.js b/media/js/login.js
new file mode 100644
index 0000000..1e49020
--- /dev/null
+++ b/media/js/login.js
@@ -0,0 +1,42 @@
+/**
+ * various functions and code used for the installation process
+ * 
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+$(function(){
+	
+	// Hove behavior for the forms
+	$(".fields input").focus(function(){
+		//clear previous hover states
+		$(".fields tr").removeClass("hover");
+		//grab the row and add "hover" to it
+        $(this).parent().parent().addClass("hover");
+	});
+	
+	// Map API Interactions
+	$("#select_map_provider option").click(function(){
+			
+			//set the labels
+			$("#map-provider-label span").text($(this).text());
+			$("#map-provider-title").text($(this).text());
+			//set the API URL
+			$("#api-link").attr("href",$(this).attr("url"));
+	});
+	
+	//Close button for system messages
+	$("a.btn-close").click(function(){$(this).parent().slideUp(); return false;});
+	
+	// Submit form if user clicks anywhere in the box
+	$("#install-box-basic").click(function() { $('form').append("<input type='hidden' name='install_mode_basic' value='1' />").submit(); } )
+	$("#install-box-advanced").click(function() { $('form').append("<input type='hidden' name='install_mode_advanced' value='1' />").submit(); } );
+
+});
diff --git a/media/js/openid/jquery-1.2.6.min.js b/media/js/openid/jquery-1.2.6.min.js
new file mode 100644
index 0000000..82b98e1
--- /dev/null
+++ b/media/js/openid/jquery-1.2.6.min.js
@@ -0,0 +1,32 @@
+/*
+ * jQuery 1.2.6 - New Wave Javascript
+ *
+ * Copyright (c) 2008 John Resig (jquery.com)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $
+ * $Rev: 5685 $
+ */
+(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){ [...]
+return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);retur [...]
+return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.g [...]
+selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:fun [...]
+return(this[0].value||"").replace(/\r/g,"");}return undefined;}if(value.constructor==Number)value+='';return this.each(function(){if(this.nodeType!=1)return;if(value.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,value)>=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this. [...]
+this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);} [...]
+return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByT [...]
+jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments[i])!=null)for( [...]
+script.appendChild(document.createTextNode(data));head.insertBefore(script,head.firstChild);head.removeChild(script);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData: [...]
+for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else{if(length==undefined){for(name in object)if(callback.call(object[name],name,object[name])===false)break;}else
+for(var value=object[0];i<length&&callback.call(value,i,value)!==false;value=object[++i]){}}return object;},prop:function(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(name)?value+"px":value;},className:{add:function(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function(i,className){if(elem.nodeType==1&&!jQuery.className.has(elem.className,className))elem.className+=(elem.c [...]
+jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid [...]
+ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&&notxml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode) [...]
+while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)return i;return-1;},merge:function(first,second){var i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i++])if(elem.nodeType!=8)first[pos++]=elem;}else
+while(elem=second[i++])first[pos++]=elem;return first;},unique:function(array){var ret=[],done={};try{for(var i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}return ret;},grep:function(elems,callback,inv){var ret=[];for(var i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=[];for(var i=0,length=elems.length;i<lengt [...]
+for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuer [...]
+jQuery.readyList.push(function(){return fn.call(this,jQuery);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.call(document);});jQuery.readyList=null;}jQuery(document).triggerHandler("ready");}}});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(document.addEventListener&&!jQuery.browser.opera)document.addEventListener("DO [...]
+xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false; [...]
+jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.compl [...]
+for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else
+s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style. [...]
+e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timer [...]
\ No newline at end of file
diff --git a/media/js/openid/openid-jquery-en.js b/media/js/openid/openid-jquery-en.js
new file mode 100644
index 0000000..07db4f7
--- /dev/null
+++ b/media/js/openid/openid-jquery-en.js
@@ -0,0 +1,101 @@
+/*
+	Simple OpenID Plugin
+	http://code.google.com/p/openid-selector/
+	
+	This code is licensed under the New BSD License.
+*/
+
+var providers_large = {
+	google : {
+		name : 'Google',
+		url : 'https://www.google.com/accounts/o8/id'
+	},
+	facebook : {
+      name: 'Facebook',
+      url: "javascript:facebook_click();"
+    },
+	yahoo : {
+		name : 'Yahoo',
+		url : 'http://me.yahoo.com/'
+	},
+	/*aol : {
+		name : 'AOL',
+		label : 'Enter your AOL screenname.',
+		url : 'http://openid.aol.com/{username}'
+	},
+	*/
+	myopenid : {
+		name : 'MyOpenID',
+		label : 'Enter your MyOpenID username.',
+		url : 'http://{username}.myopenid.com/'
+	},
+	openid : {
+		name : 'OpenID',
+		label : 'Enter your OpenID.',
+		url : null
+	}
+};
+
+var providers_small = {
+	livejournal : {
+		name : 'LiveJournal',
+		label : 'Enter your Livejournal username.',
+		url : 'http://{username}.livejournal.com/'
+	},
+	/* flickr: {
+		name: 'Flickr',        
+		label: 'Enter your Flickr username.',
+		url: 'http://flickr.com/{username}/'
+	}, */
+	/* technorati: {
+		name: 'Technorati',
+		label: 'Enter your Technorati username.',
+		url: 'http://technorati.com/people/technorati/{username}/'
+	}, */
+	wordpress : {
+		name : 'Wordpress',
+		label : 'Enter your Wordpress.com username.',
+		url : 'http://{username}.wordpress.com/'
+	},
+	blogger : {
+		name : 'Blogger',
+		label : 'Your Blogger account',
+		url : 'http://{username}.blogspot.com/'
+	},
+	verisign : {
+		name : 'Verisign',
+		label : 'Your Verisign username',
+		url : 'http://{username}.pip.verisignlabs.com/'
+	},
+	/* vidoop: {
+		name: 'Vidoop',
+		label: 'Your Vidoop username',
+		url: 'http://{username}.myvidoop.com/'
+	}, */
+	/* launchpad: {
+		name: 'Launchpad',
+		label: 'Your Launchpad username',
+		url: 'https://launchpad.net/~{username}'
+	}, */
+	claimid : {
+		name : 'ClaimID',
+		label : 'Your ClaimID username',
+		url : 'http://claimid.com/{username}'
+	},
+	clickpass : {
+		name : 'ClickPass',
+		label : 'Enter your ClickPass username',
+		url : 'http://clickpass.com/public/{username}'
+	},
+	google_profile : {
+		name : 'Google Profile',
+		label : 'Enter your Google Profile username',
+		url : 'http://www.google.com/profiles/{username}'
+	}
+};
+
+openid.locale = 'en';
+openid.sprite = 'en'; // reused in german& japan localization
+openid.demo_text = 'In client demo mode. Normally would have submitted OpenID:';
+openid.signin_text = 'Sign-In';
+openid.image_title = 'log in with {provider}';
\ No newline at end of file
diff --git a/media/js/openid/openid-jquery-ru.js b/media/js/openid/openid-jquery-ru.js
new file mode 100644
index 0000000..b493279
--- /dev/null
+++ b/media/js/openid/openid-jquery-ru.js
@@ -0,0 +1,74 @@
+/*
+	Simple OpenID Plugin
+	http://code.google.com/p/openid-selector/
+	
+	This code is licensed under the New BSD License.
+*/
+
+var providers_large = {
+	yandex : {
+		name : 'Яндекс',
+		url : 'http://openid.yandex.ru'
+	},
+	rambler : {
+		name : 'Рамблер',
+		url : 'http://www.rambler.ru'
+	},
+	google : {
+		name : 'Google',
+		url : 'https://www.google.com/accounts/o8/id'
+	},
+	yahoo : {
+		name : 'Yahoo',
+		url : 'http://me.yahoo.com/'
+	},
+	myopenid : {
+		name : 'MyOpenID',
+		label : 'Введите ваше имя пользователя на MyOpenID.',
+		url : 'http://{username}.myopenid.com/'
+	}
+};
+
+var providers_small = {
+	openid : {
+		name : 'OpenID',
+		label : 'Введите ваш OpenID.',
+		url : null
+	},
+	livejournal : {
+		name : 'Живой Журнал',
+		label : 'Введите ваше имя в Живом Журнале.',
+		url : 'http://{username}.livejournal.com/'
+	},
+	flickr : {
+		name : 'Flickr',
+		label : 'Введите ваше имя на Flickr.',
+		url : 'http://flickr.com/{username}/'
+	},
+	wordpress : {
+		name : 'Wordpress',
+		label : 'Введите ваше имя на Wordpress.com.',
+		url : 'http://{username}.wordpress.com/'
+	},
+	blogger : {
+		name : 'Blogger',
+		label : 'Ваш Blogger аккаунт',
+		url : 'http://{username}.blogspot.com/'
+	},
+	verisign : {
+		name : 'Verisign',
+		label : 'Ваше имя пользователя на Verisign',
+		url : 'http://{username}.pip.verisignlabs.com/'
+	},
+	google_profile : {
+		name : 'Профиль Google',
+		label : 'Введите ваше имя на Google Profile',
+		url : 'http://www.google.com/profiles/{username}'
+	}
+};
+
+openid.locale = 'ru';
+openid.sprite = 'ru'; // reused in ukrainian localization
+openid.demo_text = 'В демонстрационном режиме на клиенте. В действительности произошел бы сабмит следующего OpenID:';
+openid.signin_text = 'Войти';
+openid.image_title = 'войти c {provider}';
\ No newline at end of file
diff --git a/media/js/openid/openid-jquery-uk.js b/media/js/openid/openid-jquery-uk.js
new file mode 100644
index 0000000..932b39d
--- /dev/null
+++ b/media/js/openid/openid-jquery-uk.js
@@ -0,0 +1,74 @@
+/*
+	Simple OpenID Plugin
+	http://code.google.com/p/openid-selector/
+	
+	This code is licensed under the New BSD License.
+*/
+
+var providers_large = {
+	yandex : {
+		name : 'Яндекс',
+		url : 'http://openid.yandex.ru'
+	},
+	rambler : {
+		name : 'Рамблер',
+		url : 'http://www.rambler.ru'
+	},
+	google : {
+		name : 'Google',
+		url : 'https://www.google.com/accounts/o8/id'
+	},
+	yahoo : {
+		name : 'Yahoo',
+		url : 'http://me.yahoo.com/'
+	},
+	myopenid : {
+		name : 'MyOpenID',
+		label : 'Введіть ваше ім\'я користувача на MyOpenID.',
+		url : 'http://{username}.myopenid.com/'
+	}
+};
+
+var providers_small = {
+	openid : {
+		name : 'OpenID',
+		label : 'Введіть ваш OpenID.',
+		url : null
+	},
+	livejournal : {
+		name : 'Живий Журнал',
+		label : 'Введіть ваше ім\'я в Живому Журналі.',
+		url : 'http://{username}.livejournal.com/'
+	},
+	flickr : {
+		name : 'Flickr',
+		label : 'Введіть ваше ім\'я на Flickr.',
+		url : 'http://flickr.com/{username}/'
+	},
+	wordpress : {
+		name : 'Wordpress',
+		label : 'Введіть ваше ім\'я на Wordpress.com.',
+		url : 'http://{username}.wordpress.com/'
+	},
+	blogger : {
+		name : 'Blogger',
+		label : 'Ваш Blogger аккаунт',
+		url : 'http://{username}.blogspot.com/'
+	},
+	verisign : {
+		name : 'Verisign',
+		label : 'Ваше ім\'я користувача на Verisign',
+		url : 'http://{username}.pip.verisignlabs.com/'
+	},
+	google_profile : {
+		name : 'Профіль Google',
+		label : 'Введіть ваше ім\'я на Google Profile',
+		url : 'http://www.google.com/profiles/{username}'
+	}
+};
+
+openid.locale = 'uk';
+openid.sprite = 'ru'; // use same sprite as russian localization
+openid.demo_text = 'В демонстраційному режимі на клієнті. Насправді пройшов би сабміт наступного OpenID:';
+openid.signin_text = 'Увійти';
+openid.image_title = 'увійти з {provider}';
\ No newline at end of file
diff --git a/media/js/openid/openid-jquery.js b/media/js/openid/openid-jquery.js
new file mode 100644
index 0000000..a7813e5
--- /dev/null
+++ b/media/js/openid/openid-jquery.js
@@ -0,0 +1,202 @@
+/*
+	Simple OpenID Plugin
+	http://code.google.com/p/openid-selector/
+	
+	This code is licensed under the New BSD License.
+*/
+
+var providers;
+var openid;
+(function ($) {
+openid = {
+	version : '1.3', // version constant
+	demo : false,
+	demo_text : null,
+	cookie_expires : 6 * 30, // 6 months.
+	cookie_name : 'openid_provider',
+	cookie_path : '/',
+
+	img_path : '../media/img/openid/',
+	locale : null, // is set in openid-<locale>.js
+	sprite : null, // usually equals to locale, is set in
+	// openid-<locale>.js
+	signin_text : null, // text on submit button on the form
+	all_small : false, // output large providers w/ small icons
+	no_sprite : false, // don't use sprite image
+	image_title : '{provider}', // for image title
+
+	input_id : null,
+	provider_url : null,
+	provider_id : null,
+
+	/**
+	 * Class constructor
+	 * 
+	 * @return {Void}
+	 */
+	init : function(input_id) {
+		providers = $.extend({}, providers_large, providers_small);
+		var openid_btns = $('#openid_btns');
+		this.input_id = input_id;
+		$('#openid_choice').show();
+		$('#openid_input_area').empty();
+		var i = 0;
+		// add box for each provider
+		for (id in providers_large) {
+			box = this.getBoxHTML(id, providers_large[id], (this.all_small ? 'small' : 'large'), i++);
+			openid_btns.append(box);
+		}
+		if (providers_small) {
+			openid_btns.append('<br/>');
+			for (id in providers_small) {
+				box = this.getBoxHTML(id, providers_small[id], 'small', i++);
+				openid_btns.append(box);
+			}
+		}
+		$('#openid_form').submit(this.submit);
+		var box_id = this.readCookie();
+		if (box_id) {
+			this.signin(box_id, true);
+		}
+	},
+
+	/**
+	 * @return {String}
+	 */
+	getBoxHTML : function(box_id, provider, box_size, index) {
+		if (this.no_sprite) {
+			var image_ext = box_size == 'small' ? '.ico.gif' : '.gif';
+			return '<a title="' + this.image_title.replace('{provider}', provider["name"]) + '" href="javascript:openid.signin(\'' + box_id + '\');"'
+					+ ' style="background: #FFF url(' + this.img_path + '../images.' + box_size + '/' + box_id + image_ext + ') no-repeat center center" '
+					+ 'class="' + box_id + ' openid_' + box_size + '_btn"></a>';
+		}
+		var x = box_size == 'small' ? -index * 24 : -index * 100;
+		var y = box_size == 'small' ? -60 : 0;
+		return '<a title="' + this.image_title.replace('{provider}', provider["name"]) + '" href="javascript:openid.signin(\'' + box_id + '\');"'
+				+ ' style="background: #FFF url(' + this.img_path + 'openid-providers-' + this.sprite + '.png); background-position: ' + x + 'px ' + y + 'px" '
+				+ 'class="' + box_id + ' openid_' + box_size + '_btn"></a>';
+	},
+
+	/**
+	 * Provider image click
+	 * 
+	 * @return {Void}
+	 */
+	signin : function(box_id, onload) {
+		var provider = providers[box_id];
+		if (!provider) {
+			return;
+		}
+		this.highlight(box_id);
+		this.setCookie(box_id);
+		this.provider_id = box_id;
+		this.provider_url = provider['url'];
+		// prompt user for input?
+		if (provider['label']) {
+			this.useInputBox(provider);
+		} else {
+			$('#openid_input_area').empty();
+			if (!onload) {
+				$('#openid_form').submit();
+			}
+		}
+	},
+
+	/**
+	 * Sign-in button click
+	 * 
+	 * @return {Boolean}
+	 */
+	submit : function() {
+		var url = openid.provider_url;
+		if (url) {
+			url = url.replace('{username}', $('#openid_username').val());
+			openid.setOpenIdUrl(url);
+		}
+		if (openid.demo) {
+			alert(openid.demo_text + "\r\n" + document.getElementById(openid.input_id).value);
+			return false;
+		}
+		if (url.indexOf("javascript:") == 0) {
+			url = url.substr("javascript:".length);
+			eval(url);
+			return false;
+		}
+		return true;
+	},
+
+	/**
+	 * @return {Void}
+	 */
+	setOpenIdUrl : function(url) {
+		var hidden = document.getElementById(this.input_id);
+		if (hidden != null) {
+			hidden.value = url;
+		} else {
+			$('#openid_form').append('<input type="hidden" id="' + this.input_id + '" name="' + this.input_id + '" value="' + url + '"/>');
+		}
+	},
+
+	/**
+	 * @return {Void}
+	 */
+	highlight : function(box_id) {
+		// remove previous highlight.
+		var highlight = $('#openid_highlight');
+		if (highlight) {
+			highlight.replaceWith($('#openid_highlight a')[0]);
+		}
+		// add new highlight.
+		$('.' + box_id).wrap('<div id="openid_highlight"></div>');
+	},
+
+	setCookie : function(value) {
+		var date = new Date();
+		date.setTime(date.getTime() + (this.cookie_expires * 24 * 60 * 60 * 1000));
+		var expires = "; expires=" + date.toGMTString();
+		document.cookie = this.cookie_name + "=" + value + expires + "; path=" + this.cookie_path;
+	},
+
+	readCookie : function() {
+		var nameEQ = this.cookie_name + "=";
+		var ca = document.cookie.split(';');
+		for ( var i = 0; i < ca.length; i++) {
+			var c = ca[i];
+			while (c.charAt(0) == ' ')
+				c = c.substring(1, c.length);
+			if (c.indexOf(nameEQ) == 0)
+				return c.substring(nameEQ.length, c.length);
+		}
+		return null;
+	},
+
+	/**
+	 * @return {Void}
+	 */
+	useInputBox : function(provider) {
+		var input_area = $('#openid_input_area');
+		var html = '';
+		var id = 'openid_username';
+		var value = '';
+		var label = provider['label'];
+		var style = '';
+		if (label) {
+			html = '<p>' + label + '</p>';
+		}
+		if (provider['name'] == 'OpenID') {
+			id = this.input_id;
+			value = 'http://';
+			style = 'background: #FFF url(' + this.img_path + 'openid-inputicon.gif) no-repeat scroll 0 50%; padding-left:18px;';
+		}
+		html += '<input id="' + id + '" type="text" style="' + style + '" name="' + id + '" value="' + value + '" />'
+				+ '<input id="openid_submit" type="submit" value="' + this.signin_text + '"/>';
+		input_area.empty();
+		input_area.append(html);
+		$('#' + id).focus();
+	},
+
+	setDemoMode : function(demoMode) {
+		this.demo = demoMode;
+	}
+};
+})(jQuery);
\ No newline at end of file
diff --git a/media/js/picbox.js b/media/js/picbox.js
new file mode 100644
index 0000000..7c29716
--- /dev/null
+++ b/media/js/picbox.js
@@ -0,0 +1,22 @@
+/*!
+	Picbox v2.1
+	(c) 2010 Ben Kay <http://bunnyfire.co.uk>
+
+	Based on code from Slimbox v1.7 - The ultimate lightweight Lightbox clone
+	(c) 2007-2009 Christophe Beyls <http://www.digitalia.be>
+	
+	Uses jQuery-mousewheel Version: 3.0.2
+	(c) 2009 Brandon Aaron <http://brandonaaron.net>
+	
+	MIT-style license.
+*/
+
+(function(b){function $(){var a={x:j.scrollLeft(),y:j.scrollTop()};p=j.width()/2;q=j.height()/2;if(L){p+=a.x;q+=a.y;b(k).css({left:a.x,top:a.y,width:j.width(),height:j.height()})}b(h).css({top:q,left:p,width:"1px",height:"1px"})}function M(a){e.hideFlash&&b.each(["object","embed"],function(d,f){b(f).each(function(){if(a)this._picbox=this.style.visibility;this.style.visibility=a?"hidden":this._picbox})});k.style.display="";var c=a?"bind":"unbind";b(document)[c]("keydown",aa);b(document)[c [...]
+
+if (!/android|iphone|ipod|series60|symbian|windows ce|blackberry/i.test(navigator.userAgent)) {
+	jQuery(function($) {
+		$("a[rel^='lightbox']").picbox({/* Put custom options here */}, null, function(el) {
+			return (this == el) || ((this.rel.length > 8) && (this.rel == el.rel));
+		});
+	});
+}
\ No newline at end of file
diff --git a/media/js/plugins.js b/media/js/plugins.js
new file mode 100644
index 0000000..db63bed
--- /dev/null
+++ b/media/js/plugins.js
@@ -0,0 +1,63 @@
+/*
+ * jQuery Color Animations
+ * Copyright 2007 John Resig
+ * Released under the MIT and GPL licenses.
+ */
+(function(jQuery){jQuery.each(['backgroundColor','borderBottomColor','borderLeftColor','borderRightColor','borderTopColor','color','outlineColor'],function(i,attr){jQuery.fx.step[attr]=function(fx){if(fx.state==0){fx.start=getColor(fx.elem,attr);fx.end=getRGB(fx.end);}
+fx.elem.style[attr]="rgb("+[Math.max(Math.min(parseInt((fx.pos*(fx.end[0]-fx.start[0]))+fx.start[0]),255),0),Math.max(Math.min(parseInt((fx.pos*(fx.end[1]-fx.start[1]))+fx.start[1]),255),0),Math.max(Math.min(parseInt((fx.pos*(fx.end[2]-fx.start[2]))+fx.start[2]),255),0)].join(",")+")";}});function getRGB(color){var result;if(color&&color.constructor==Array&&color.length==3)
+return color;if(result=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(color))
+return[parseInt(result[1]),parseInt(result[2]),parseInt(result[3])];if(result=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(color))
+return[parseFloat(result[1])*2.55,parseFloat(result[2])*2.55,parseFloat(result[3])*2.55];if(result=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(color))
+return[parseInt(result[1],16),parseInt(result[2],16),parseInt(result[3],16)];if(result=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(color))
+return[parseInt(result[1]+result[1],16),parseInt(result[2]+result[2],16),parseInt(result[3]+result[3],16)];return colors[jQuery.trim(color).toLowerCase()];}
+function getColor(elem,attr){var color;do{color=jQuery.curCSS(elem,attr);if(color!=''&&color!='transparent'||jQuery.nodeName(elem,"body"))
+break;attr="backgroundColor";}while(elem=elem.parentNode);return getRGB(color);};var colors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia [...]
+
+/*
+ * jQuery ifixpng plugin
+ * (previously known as pngfix)
+ * Version 2.0  (04/11/2007)
+ * @requires jQuery v1.1.3 or above
+ *
+ * Examples at: http://jquery.khurshid.com
+ * Copyright (c) 2007 Kush M.
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ */
+(function($){$.ifixpng=function(customPixel){$.ifixpng.pixel=customPixel;};$.ifixpng.getPixel=function(){return $.ifixpng.pixel||'images/pixel.gif';};var hack={ltie7:$.browser.msie&&$.browser.version<7,filter:function(src){return"progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true,sizingMethod=crop,src='"+src+"')";}};$.fn.ifixpng=hack.ltie7?function(){return this.each(function(){var $$=$(this);var base=$('base').attr('href');if($$.is('img')||$$.is('input')){if($$.attr('src')) [...]
+
+/**
+ * Flash (http://jquery.lukelutman.com/plugins/flash)
+ * A jQuery plugin for embedding Flash movies.
+ *
+ * Version 1.0
+ * November 9th, 2006
+ *
+ * Copyright (c) 2006 Luke Lutman (http://www.lukelutman.com)
+ * Dual licensed under the MIT and GPL licenses.
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.opensource.org/licenses/gpl-license.php
+ *
+ * Inspired by:
+ * SWFObject (http://blog.deconcept.com/swfobject/)
+ * UFO (http://www.bobbyvandersluis.com/ufo/)
+ * sIFR (http://www.mikeindustries.com/sifr/)
+ *
+ * IMPORTANT:
+ * The packed version of jQuery breaks ActiveX control
+ * activation in Internet Explorer. Use JSMin to minifiy
+ * jQuery (see: http://jquery.lukelutman.com/plugins/flash#activex).
+ *
+ **/
+(function(){var $$;$$=jQuery.fn.flash=function(htmlOptions,pluginOptions,replace,update){var block=replace||$$.replace;pluginOptions=$$.copy($$.pluginOptions,pluginOptions);if(!$$.hasFlash(pluginOptions.version)){if(pluginOptions.expressInstall&&$$.hasFlash(6,0,65)){var expressInstallOptions={flashvars:{MMredirectURL:location,MMplayerType:'PlugIn',MMdoctitle:jQuery('title').text()}};}else if(pluginOptions.update){block=update||$$.update;}else{return this;}}
+htmlOptions=$$.copy($$.htmlOptions,expressInstallOptions,htmlOptions);return this.each(function(){block.call(this,$$.copy(htmlOptions));});};$$.copy=function(){var options={},flashvars={};for(var i=0;i<arguments.length;i++){var arg=arguments[i];if(arg==undefined)continue;jQuery.extend(options,arg);if(arg.flashvars==undefined)continue;jQuery.extend(flashvars,arg.flashvars);}
+options.flashvars=flashvars;return options;};$$.hasFlash=function(){if(/hasFlash\=true/.test(location))return true;if(/hasFlash\=false/.test(location))return false;var pv=$$.hasFlash.playerVersion().match(/\d+/g);var rv=String([arguments[0],arguments[1],arguments[2]]).match(/\d+/g)||String($$.pluginOptions.version).match(/\d+/g);for(var i=0;i<3;i++){pv[i]=parseInt(pv[i]||0);rv[i]=parseInt(rv[i]||0);if(pv[i]<rv[i])return false;if(pv[i]>rv[i])return true;}
+return true;};$$.hasFlash.playerVersion=function(){try{try{var axo=new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6');try{axo.AllowScriptAccess='always';}
+catch(e){return'6,0,0';}}catch(e){}
+return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g,',').match(/^,?(.+),?$/)[1];}catch(e){try{if(navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin){return(navigator.plugins["Shockwave Flash 2.0"]||navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g,",").match(/^,?(.+),?$/)[1];}}catch(e){}}
+return'0,0,0';};$$.htmlOptions={height:240,flashvars:{},pluginspage:'http://www.adobe.com/go/getflashplayer',src:'#',type:'application/x-shockwave-flash',width:320};$$.pluginOptions={expressInstall:false,update:true,version:'6.0.65'};$$.replace=function(htmlOptions){this.innerHTML='<div class="alt">'+this.innerHTML+'</div>';jQuery(this).addClass('flash-replaced').prepend($$.transform(htmlOptions));};$$.update=function(htmlOptions){var url=String(location).split('?');url.splice(1,0,'?hasF [...]
+if(typeof this[key]!='function')
+s+=key+'="'+this[key]+'" ';return s;};function toFlashvarsString(){var s='';for(var key in this)
+if(typeof this[key]!='function')
+s+=key+'='+encodeURIComponent(this[key])+'&';return s.replace(/&$/,'');};$$.transform=function(htmlOptions){htmlOptions.toString=toAttributeString;if(htmlOptions.flashvars)htmlOptions.flashvars.toString=toFlashvarsString;return'<embed '+String(htmlOptions)+'/>';};if(window.attachEvent){window.attachEvent("onbeforeunload",function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};});}})();
\ No newline at end of file
diff --git a/media/js/protochart/ProtoChart.js b/media/js/protochart/ProtoChart.js
new file mode 100644
index 0000000..4e60f18
--- /dev/null
+++ b/media/js/protochart/ProtoChart.js
@@ -0,0 +1,2653 @@
+/**
+ * Class: ProtoChart 
+ * Version: v0.5 beta
+ * 
+ * ProtoChart is a charting lib on top of Prototype.
+ * This library is heavily motivated by excellent work done by:
+ * * Flot <http://code.google.com/p/flot/>
+ * * Flotr <http://solutoire.com/flotr/>
+ * 
+ * Complete examples can be found at: <http://www.deensoft.com/lab/protochart>
+ */
+
+/**
+ * Events:
+ * ProtoChart:mousemove - Fired when mouse is moved over the chart
+ * ProtoChart:plotclick - Fired when graph is clicked
+ * ProtoChart:dataclick - Fired when graph is clicked AND the click is on a data point
+ * ProtoChart:selected	- Fired when certain region on the graph is selected
+ * ProtoChart:hit		- Fired when mouse is moved near or over certain data point on the graph
+ */
+
+
+if(!Proto) var Proto = {};
+
+Proto.Chart = Class.create({
+	/**
+	 * Function: 
+	 * {Object} elem
+	 * {Object} data
+	 * {Object} options
+	 */
+	initialize: function(elem, data, options)
+	{
+		options = options || {};
+		this.graphData = [];
+		/**
+		 * Property: options
+		 * 
+		 * Description: Various options can be set. More details in description.
+		 * 
+		 * colors:
+		 * {Array}		- pass in a array which contains strings of colors you want to use. Default has 6 color set.
+		 * 
+		 * legend:
+		 * {BOOL} 		- show				- if you want to show the legend. Default is false
+		 * {integer} 	- noColumns			- Number of columns for the legend. Default is 1
+		 * {function} 	- labelFormatter	- A function that returns a string. The function is called with a string and is expected to return a string. Default = null
+		 * {string}		- labelBoxBorderColor - border color for the little label boxes. Default #CCC
+		 * {HTMLElem}	- container			- an HTML id or HTML element where the legend should be rendered. If left null means to put the legend on top of the Chart
+		 * {string}		- position			- position for the legend on the Chart. Default value 'ne'
+		 * {integer}	- margin			- default valud of 5
+		 * {string} 	- backgroundColor	- default to null (which means auto-detect)
+		 * {float} 		- backgroundOpacity - leave it 0 to avoid background
+		 * 
+		 * xaxis (yaxis) options:
+		 * {string} 	- mode 		- default is null but you can pass a string "time" to indicate time series
+		 * {integer}	- min
+		 * {integer}	- max
+		 * {float}		- autoscaleMargin - in % to add if auto-setting min/max
+		 * {mixed}		- ticks - either [1, 3] or [[1, "a"], 3] or a function which gets axis info and returns ticks
+		 * {function} 	- tickFormatter - A function that returns a string as a tick label. Default is null
+		 * {float}		- tickDecimals
+		 * {integer}	- tickSize
+		 * {integer} 	- minTickSize
+		 * {array}		- monthNames
+		 * {string}		- timeformat
+		 * 
+		 * Points / Lines / Bars options:
+		 * {bool}		- show, default is false
+		 * {integer}	- radius: default is 3
+		 * {integer}	- lineWidth : default is 2
+		 * {bool}		- fill : default is true
+		 * {string}		- fillColor: default is #ffffff
+		 * 
+		 * Grid options:
+		 * {string}		- color
+		 * {string}		- backgroundColor 	- defualt is *null*
+		 * {string}		- tickColor			- default is *#dddddd*
+		 * {integer}	- labelMargin		- should be in pixels default is 3
+		 * {integer}	- borderWidth		- default *1*
+		 * {bool}		- clickable 		- default *null* - pass in TRUE if you wish to monitor click events
+		 * {mixed}		- coloredAreas 		- default *null* - pass in mixed object eg. {x1, x2}
+		 * {string}		- coloredAreasColor	- default *#f4f4f4*
+		 * {bool}		- drawXAxis			- default *true*
+		 * {bool}		- drawYAxis			- default *true*
+		 * 
+		 * selection options:
+		 * {string}		- mode : either "x", "y" or "xy"
+		 * {string}		- color : string
+		 */
+		this.options = this.merge(options,{
+			colors: ["#edc240", "#00A8F0", "#C0D800", "#cb4b4b", "#4da74d", "#9440ed"],
+            legend: {
+                show: false,
+                noColumns: 1,
+                labelFormatter: null,
+                labelBoxBorderColor: "#ccc",
+                container: null, 
+                position: "ne",
+                margin: 5,
+                backgroundColor: null,
+                backgroundOpacity: 0.85
+            },
+            xaxis: {
+				mode: null, 
+                min: null,
+                max: null,
+                autoscaleMargin: null,
+                ticks: null,
+                tickFormatter: null,
+                tickDecimals: null,
+                tickSize: null,
+                minTickSize: null,
+                monthNames: null,
+                timeformat: null
+            },
+            yaxis: {
+				mode: null,
+				min: null,
+				max: null,
+				ticks: null,
+				tickFormatter: null,
+				tickDecimals: null,
+				tickSize: null,
+				minTickSize: null,
+				monthNames: null,
+				timeformat: null,				
+                autoscaleMargin: 0.02
+            },
+
+            points: {
+                show: false,
+                radius: 3,
+                lineWidth: 2,
+                fill: true,
+                fillColor: "#ffffff"
+            },
+            lines: {
+                show: false,
+                lineWidth: 2,
+                fill: false,
+                fillColor: null
+            },
+            bars: {
+                show: false,
+                lineWidth: 2,
+                barWidth: 1,
+                fill: true,
+                fillColor: null,
+				showShadow: false,
+				fillOpacity: 0.4,
+				autoScale: true
+            },
+			pies: {
+				show: false,
+				radius: 50,
+				borderWidth: 1,
+				fill: true,
+				fillColor: null,
+				fillOpacity: 0.90,
+				labelWidth: 30,
+				fontSize: 11,
+				autoScale: true
+			},
+            grid: {
+                color: "#545454",
+                backgroundColor: null,
+                tickColor: "#dddddd",
+                labelMargin: 3,
+                borderWidth: 1,
+                clickable: null,
+                coloredAreas: null,
+                coloredAreasColor: "#f4f4f4",
+				drawXAxis: true,
+				drawYAxis: true
+            },
+			mouse: {
+				track: false,
+				position: 'se',
+				fixedPosition: true,
+				clsName: 'mouseValHolder',
+				trackFormatter: this.defaultTrackFormatter,
+				margin: 3,
+				color: '#ff3f19',
+				trackDecimals: 1,
+				sensibility: 2,
+				radius: 5,
+				lineColor: '#cb4b4b'
+			},
+            selection: {
+                mode: null,
+                color: "#97CBFF"
+            },
+			allowDataClick: true,
+			makeRandomColor: false,
+            shadowSize: 4			
+		});
+		
+		/*
+		 * Local variables.
+		 */
+		this.canvas = null; 
+		this.overlay = null;
+		this.eventHolder = null;
+		this.context = null;
+		this.overlayContext = null;
+		
+		this.domObj = $(elem);
+
+		this.xaxis = {};
+		this.yaxis = {};
+		this.chartOffset = {left: 0, right: 0, top: 0, bottom: 0};
+		this.yLabelMaxWidth = 0;
+		this.yLabelMaxHeight = 0;
+		this.xLabelBoxWidth = 0;
+		this.canvasWidth = 0;
+		this.canvasHeight = 0;
+		this.chartWidth = 0;
+		this.chartHeight = 0;
+		this.hozScale = 0;
+		this.vertScale = 0;
+		this.workarounds = {};
+		
+		this.domObj = $(elem);
+		
+		this.barDataRange = [];
+		
+        this.lastMousePos = { pageX: null, pageY: null };
+        this.selection = { first: { x: -1, y: -1}, second: { x: -1, y: -1} };
+        this.prevSelection = null;
+        this.selectionInterval = null;
+        this.ignoreClick = false;	
+		this.prevHit = null;
+			
+		if(this.options.makeRandomColor)
+			this.options.color = this.makeRandomColor(this.options.colors);
+		
+		this.setData(data);
+		this.constructCanvas();
+		this.setupGrid();
+		this.draw();
+	},
+	/**
+	 * Private function internally used.
+	 */
+	merge: function(src, dest)
+	{
+		var result = dest || {};
+		for(var i in src){		  
+			result[i] = (typeof(src[i]) == 'object' && !(src[i].constructor == Array || src[i].constructor == RegExp)) ? this.merge(src[i], dest[i]) : result[i] = src[i];		
+		}
+		return result;	
+	},
+	/**
+	 * Function: setData
+	 * {Object} data
+	 * 
+	 * Description:
+	 * Sets datasoruces properly then sets the Bar Width accordingly, then copies the default data options and then processes the graph data
+	 * 
+	 * Returns: none
+	 * 
+	 */	
+	setData: function(data) 
+	{
+        this.graphData = this.parseData(data);		
+		this.setBarWidth();
+        this.copyGraphDataOptions();
+        this.processGraphData();
+    },
+	/**
+	 * Function: parseData
+	 * {Object} data
+	 * 
+	 * Return: 
+	 * {Object} result
+	 * 
+	 * Description:
+	 * Takes the provided data object and converts it into generic data that we can understand. User can pass in data in 3 different ways:
+	 * - [d1, d2]
+	 * - [{data: d1, label: "data1"}, {data: d2, label: "data2"}]
+	 * - [d1, {data: d1, label: "data1"}]
+	 * 
+	 * This function parses these senarios and makes it readable
+	 */
+	parseData: function(data)
+	{
+		var res = [];
+		data.each(function(d){
+			var s;
+			if(d.data) {
+				s = {};
+				for(var v in d) {
+					s[v] = d[v];
+				}
+			}
+			else {
+				s = {data: d};
+			}
+			res.push(s);
+		}.bind(this));
+		return res;
+	},
+	/**
+	 * function: makeRandomColor
+	 * {Object} colorSet
+	 * 
+	 * Return: 
+	 * {Array} result - array containing random colors
+	 */
+	makeRandomColor: function(colorSet)
+	{
+		var randNum = Math.floor(Math.random() * colorSet.length);
+		var randArr = [];
+		var newArr = [];
+		randArr.push(randNum);
+		
+		while(randArr.length < colorSet.length)
+		{
+			var tempNum = Math.floor(Math.random() * colorSet.length);
+	
+			while(checkExisted(tempNum, randArr))
+				tempNum = Math.floor(Math.random() * colorSet.length);
+				
+			randArr.push(tempNum);
+		}
+		
+		randArr.each(function(ra){
+			newArr.push(colorSet[ra]);
+			
+		}.bind(this));
+		return newArr;		
+	},
+	/**
+	 * function: checkExisted
+	 * {Object} needle
+	 * {Object} haystack
+	 * 
+	 * return: 
+	 * {bool} existed - true if it finds needle in the haystack
+	 */
+	checkExisted: function(needle, haystack)
+	{
+		var existed = false;
+		haystack.each(function(aNeedle){
+			if(aNeedle == needle) {
+				existed = true;
+				throw $break;
+			}
+		}.bind(this));
+		return existed;
+	},
+	/**
+	 * function: setBarWidth
+	 * 
+	 * Description: sets the bar width for Bar Graph, you should enable *autoScale* property for bar graph
+	 */
+	setBarWidth: function()
+	{
+		if(this.options.bars.show && this.options.bars.autoScale)
+		{
+			this.options.bars.barWidth = 1 / this.graphData.length / 1.2;	
+		}
+	},
+	/**
+	 * Function: copyGraphDataOptions
+	 * 
+	 * Description: Private function that goes through each graph data (series) and assigned the graph
+	 * properties to it.
+	 */
+	copyGraphDataOptions: function()
+	{
+		var i, neededColors = this.graphData.length, usedColors = [], assignedColors = [];
+		
+		this.graphData.each(function(gd){
+			var sc = gd.color;
+			if(sc) {
+				--neededColors;
+				if(Object.isNumber(sc)) {
+					assignedColors.push(sc);
+				}
+				else {
+					usedColors.push(this.parseColor(sc));
+				}
+			}
+		}.bind(this));
+		
+		
+		assignedColors.each(function(ac){
+			neededColors = Math.max(neededColors, ac + 1);
+		});
+
+        var colors = [];
+        var variation = 0;
+        i = 0;
+        while (colors.length < neededColors) {
+            var c;
+            if (this.options.colors.length == i) {
+				c = new Proto.Color(100, 100, 100);
+			}
+			else {
+				c = this.parseColor(this.options.colors[i]);
+			}
+
+            var sign = variation % 2 == 1 ? -1 : 1;
+            var factor = 1 + sign * Math.ceil(variation / 2) * 0.2;
+            c.scale(factor, factor, factor);
+
+            colors.push(c);
+            
+            ++i;
+            if (i >= this.options.colors.length) {
+                i = 0;
+                ++variation;
+            }
+        }
+
+        var colorIndex = 0, s;
+		
+		this.graphData.each(function(gd){
+			if(gd.color == null)
+			{
+				gd.color = colors[colorIndex].toString();
+				++colorIndex;
+			}
+			else if(Object.isNumber(gd.color)) {
+				gd.color = colors[gd.color].toString();
+			}
+			
+            gd.lines = Object.extend(Object.clone(this.options.lines), gd.lines); 
+            gd.points = Object.extend(Object.clone(this.options.points), gd.points); 
+            gd.bars = Object.extend(Object.clone(this.options.bars), gd.bars); 
+            gd.mouse = Object.extend(Object.clone(this.options.mouse), gd.mouse);
+            if (gd.shadowSize == null) {
+                gd.shadowSize = this.options.shadowSize;
+			}
+		}.bind(this));
+			
+	},
+	/**
+	 * Function: processGraphData
+	 * 
+	 * Description: processes graph data, setup xaxis and yaxis min and max points. 
+	 */
+	processGraphData: function() {
+		
+		this.xaxis.datamin = this.yaxis.datamin = Number.MAX_VALUE;
+		this.xaxis.datamax = this.yaxis.datamax = Number.MIN_VALUE;
+			
+		this.graphData.each(function(gd) {
+			var data = gd.data;
+			data.each(function(d){
+				if(d == null) {
+					return;
+				}
+				
+				var x = d[0], y = d[1];
+				if(!x || !y || isNaN(x = +x) || isNaN(y = +y)) {
+					d = null;
+					return;
+				}
+				
+				if (x < this.xaxis.datamin)
+					this.xaxis.datamin = x;
+				if (x > this.xaxis.datamax)
+					this.xaxis.datamax = x;
+				if (y < this.yaxis.datamin)
+					this.yaxis.datamin = y;
+				if (y > this.yaxis.datamax)
+					this.yaxis.datamax = y;
+			}.bind(this));
+		}.bind(this));
+
+		
+		if (this.xaxis.datamin == Number.MAX_VALUE)
+			this.xaxis.datamin = 0;
+		if (this.yaxis.datamin == Number.MAX_VALUE)
+			this.yaxis.datamin = 0;
+		if (this.xaxis.datamax == Number.MIN_VALUE)
+			this.xaxis.datamax = 1;
+		if (this.yaxis.datamax == Number.MIN_VALUE)
+			this.yaxis.datamax = 1;
+	},
+	/**
+	 * Function: constructCanvas
+	 * 
+	 * Description: constructs the main canvas for drawing. It replicates the HTML elem (usually DIV) passed
+	 * in via constructor. If there is no height/width assigned to the HTML elem then we take a default size
+	 * of 400px (width) and 300px (height)
+	 */
+	constructCanvas: function() {
+
+		this.canvasWidth = this.domObj.getWidth();
+		this.canvasHeight = this.domObj.getHeight();
+		this.domObj.update(""); // clear target
+		this.domObj.setStyle({
+			"position": "relative"
+		}); 
+
+		if (this.canvasWidth <= 0) {
+			this.canvasWdith = 400;
+		}
+		if(this.canvasHeight <= 0) {
+			this.canvasHeight = 300;
+		}
+		
+		this.canvas = (Prototype.Browser.IE) ? document.createElement("canvas") : new Element("CANVAS", {'width': this.canvasWidth, 'height': this.canvasHeight});
+		Element.extend(this.canvas);
+		this.canvas.style.width = this.canvasWidth + "px";
+		this.canvas.style.height = this.canvasHeight + "px";
+		
+		this.domObj.appendChild(this.canvas);
+		
+		if (Prototype.Browser.IE) // excanvas hack
+		{
+			this.canvas = $(window.G_vmlCanvasManager.initElement(this.canvas));
+		}
+		this.canvas = $(this.canvas);
+		
+		this.context = this.canvas.getContext("2d");
+
+		this.overlay = (Prototype.Browser.IE) ? document.createElement("canvas") :  new Element("CANVAS", {'width': this.canvasWidth, 'height': this.canvasHeight});
+		Element.extend(this.overlay);
+		this.overlay.style.width = this.canvasWidth + "px";
+		this.overlay.style.height = this.canvasHeight + "px";
+		this.overlay.style.position = "absolute";
+		this.overlay.style.left = "0px";
+		this.overlay.style.right = "0px";
+		
+		this.overlay.setStyle({
+			'position': 'absolute',
+			'left': '0px',
+			'right': '0px'
+		});
+		this.domObj.appendChild(this.overlay);
+		
+		if (Prototype.Browser.IE) {
+			this.overlay = $(window.G_vmlCanvasManager.initElement(this.overlay));
+		}
+		
+		this.overlay = $(this.overlay);
+		this.overlayContext = this.overlay.getContext("2d");
+
+		if(this.options.selection.mode)
+		{
+			this.overlay.observe('mousedown', this.onMouseDown.bind(this));
+			this.overlay.observe('mousemove', this.onMouseMove.bind(this));
+		}
+		if(this.options.grid.clickable) {
+			this.overlay.observe('click', this.onClick.bind(this));
+		}
+		if(this.options.mouse.track)
+		{
+			this.overlay.observe('mousemove', this.onMouseMove.bind(this));
+		}
+	},
+	/**
+	 * function: setupGrid
+	 * 
+	 * Description: a container function that does a few interesting things.
+	 * 
+	 * 1. calls <extendXRangeIfNeededByBar> function which makes sure that our axis are expanded if needed
+	 * 
+	 * 2. calls <setRange> function providing xaxis options which fixes the ranges according to data points
+	 * 
+	 * 3. calls <prepareTickGeneration> function for xaxis which generates ticks according to options provided by user
+	 * 
+	 * 4. calls <setTicks> function for xaxis that sets the ticks
+	 * 
+	 * similar sequence is called for y-axis. 
+	 * 
+	 * At the end if this is a pie chart than we insert Labels (around the pie chart) via <insertLabels> and we also call <insertLegend>
+	 */
+	setupGrid: function()
+	{
+		if(this.options.bars.show)
+		{
+			this.xaxis.max += 0.5;
+			this.xaxis.min -= 0.5;
+		}
+		//x-axis
+		this.extendXRangeIfNeededByBar();
+		this.setRange(this.xaxis, this.options.xaxis);
+		this.prepareTickGeneration(this.xaxis, this.options.xaxis);
+		this.setTicks(this.xaxis, this.options.xaxis);
+		
+		
+		//y-axis
+		this.setRange(this.yaxis, this.options.yaxis);
+		this.prepareTickGeneration(this.yaxis, this.options.yaxis);
+		this.setTicks(this.yaxis, this.options.yaxis);
+		this.setSpacing();
+		
+		if(!this.options.pies.show)
+		{
+			this.insertLabels();
+		}
+		this.insertLegend();
+	},
+	/**
+	 * function: setRange
+	 * 
+	 * parameters:
+	 * {Object} axis
+	 * {Object} axisOptions
+	 */
+	setRange: function(axis, axisOptions) {
+		var min = axisOptions.min != null ? axisOptions.min : axis.datamin;
+		var max = axisOptions.max != null ? axisOptions.max : axis.datamax;
+
+		if (max - min == 0.0) {
+			// degenerate case
+			var widen;
+			if (max == 0.0)
+				widen = 1.0;
+			else
+				widen = 0.01;
+
+			min -= widen;
+			max += widen;
+		}
+		else {
+			// consider autoscaling
+			var margin = axisOptions.autoscaleMargin;
+			if (margin != null) {
+				if (axisOptions.min == null) {
+					min -= (max - min) * margin;
+					// make sure we don't go below zero if all values
+					// are positive
+					if (min < 0 && axis.datamin >= 0)
+						min = 0;
+				}
+				if (axisOptions.max == null) {
+					max += (max - min) * margin;
+					if (max > 0 && axis.datamax <= 0)
+						max = 0;
+				}
+			}
+		}
+		axis.min = min;
+		axis.max = max;
+	},
+	/**
+	 * function: prepareTickGeneration
+	 * 
+	 * Parameters:
+	 * {Object} axis
+	 * {Object} axisOptions
+	 */
+	prepareTickGeneration: function(axis, axisOptions) {
+		// estimate number of ticks
+		var noTicks;
+		if (Object.isNumber(axisOptions.ticks) && axisOptions.ticks > 0)
+			noTicks = axisOptions.ticks;
+		else if (axis == this.xaxis)
+			noTicks = this.canvasWidth / 100;
+		else
+			noTicks = this.canvasHeight / 60;
+		
+		var delta = (axis.max - axis.min) / noTicks;
+		var size, generator, unit, formatter, i, magn, norm;
+
+		if (axisOptions.mode == "time") {
+			function formatDate(d, fmt, monthNames) {
+				var leftPad = function(n) {
+					n = "" + n;
+					return n.length == 1 ? "0" + n : n;
+				};
+				
+				var r = [];
+				var escape = false;
+				if (monthNames == null)
+					monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+				for (var i = 0; i < fmt.length; ++i) {
+					var c = fmt.charAt(i);
+					
+					if (escape) {
+						switch (c) {
+						case 'h': c = "" + d.getHours(); break;
+						case 'H': c = leftPad(d.getHours()); break;
+						case 'M': c = leftPad(d.getMinutes()); break;
+						case 'S': c = leftPad(d.getSeconds()); break;
+						case 'd': c = "" + d.getDate(); break;
+						case 'm': c = "" + (d.getMonth() + 1); break;
+						case 'y': c = "" + d.getFullYear(); break;
+						case 'b': c = "" + monthNames[d.getMonth()]; break;
+						}
+						r.push(c);
+						escape = false;
+					}
+					else {
+						if (c == "%")
+							escape = true;
+						else
+							r.push(c);
+					}
+				}
+				return r.join("");
+			}
+			
+				
+			// map of app. size of time units in milliseconds
+			var timeUnitSize = {
+				"second": 1000,
+				"minute": 60 * 1000,
+				"hour": 60 * 60 * 1000,
+				"day": 24 * 60 * 60 * 1000,
+				"month": 30 * 24 * 60 * 60 * 1000,
+				"year": 365.2425 * 24 * 60 * 60 * 1000
+			};
+
+
+			// the allowed tick sizes, after 1 year we use
+			// an integer algorithm
+			var spec = [
+				[1, "second"], [2, "second"], [5, "second"], [10, "second"],
+				[30, "second"], 
+				[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
+				[30, "minute"], 
+				[1, "hour"], [2, "hour"], [4, "hour"],
+				[8, "hour"], [12, "hour"],
+				[1, "day"], [2, "day"], [3, "day"],
+				[0.25, "month"], [0.5, "month"], [1, "month"],
+				[2, "month"], [3, "month"], [6, "month"],
+				[1, "year"]
+			];
+
+			var minSize = 0;
+			if (axisOptions.minTickSize != null) {
+				if (typeof axisOptions.tickSize == "number")
+					minSize = axisOptions.tickSize;
+				else
+					minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]];
+			}
+			
+			for (i = 0; i < spec.length - 1; ++i) {
+				if (delta < (spec[i][0] * timeUnitSize[spec[i][1]] + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
+					break;
+				}
+			}
+			
+			size = spec[i][0];
+			unit = spec[i][1];
+			
+			// special-case the possibility of several years
+			if (unit == "year") {
+				magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
+				norm = (delta / timeUnitSize.year) / magn;
+				if (norm < 1.5)
+					size = 1;
+				else if (norm < 3)
+					size = 2;
+				else if (norm < 7.5)
+					size = 5;
+				else
+					size = 10;
+
+				size *= magn;
+			}
+
+			if (axisOptions.tickSize) {
+				size = axisOptions.tickSize[0];
+				unit = axisOptions.tickSize[1];
+			}
+			
+			var floorInBase = this.floorInBase; //gives us a reference to a global function.. 
+			
+			generator = function(axis) {
+				var ticks = [],
+					tickSize = axis.tickSize[0], unit = axis.tickSize[1],
+					d = new Date(axis.min);
+				
+				var step = tickSize * timeUnitSize[unit];
+				
+				
+				
+				if (unit == "second")
+					d.setSeconds(floorInBase(d.getSeconds(), tickSize));
+				if (unit == "minute")
+					d.setMinutes(floorInBase(d.getMinutes(), tickSize));
+				if (unit == "hour")
+					d.setHours(floorInBase(d.getHours(), tickSize));
+				if (unit == "month")
+					d.setMonth(floorInBase(d.getMonth(), tickSize));
+				if (unit == "year")
+					d.setFullYear(floorInBase(d.getFullYear(), tickSize));
+				
+				// reset smaller components
+				d.setMilliseconds(0);
+				if (step >= timeUnitSize.minute)
+					d.setSeconds(0);
+				if (step >= timeUnitSize.hour)
+					d.setMinutes(0);
+				if (step >= timeUnitSize.day)
+					d.setHours(0);
+				if (step >= timeUnitSize.day * 4)
+					d.setDate(1);
+				if (step >= timeUnitSize.year)
+					d.setMonth(0);
+
+
+				var carry = 0, v;
+				do {
+					v = d.getTime();
+					ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
+					if (unit == "month") {
+						if (tickSize < 1) {
+							d.setDate(1);
+							var start = d.getTime();
+							d.setMonth(d.getMonth() + 1);
+							var end = d.getTime();
+							d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
+							carry = d.getHours();
+							d.setHours(0);
+						}
+						else
+							d.setMonth(d.getMonth() + tickSize);
+					}
+					else if (unit == "year") {
+						d.setFullYear(d.getFullYear() + tickSize);
+					}
+					else
+						d.setTime(v + step);
+				} while (v < axis.max);
+
+				return ticks;
+			};
+
+			formatter = function (v, axis) {
+				var d = new Date(v);
+
+				// first check global format
+				if (axisOptions.timeformat != null)
+					return formatDate(d, axisOptions.timeformat, axisOptions.monthNames);
+				
+				var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
+				var span = axis.max - axis.min;
+				
+				if (t < timeUnitSize.minute)
+					fmt = "%h:%M:%S";
+				else if (t < timeUnitSize.day) {
+					if (span < 2 * timeUnitSize.day)
+						fmt = "%h:%M";
+					else
+						fmt = "%b %d %h:%M";
+				}
+				else if (t < timeUnitSize.month)
+					fmt = "%b %d";
+				else if (t < timeUnitSize.year) {
+					if (span < timeUnitSize.year)
+						fmt = "%b";
+					else
+						fmt = "%b %y";
+				}
+				else
+					fmt = "%y";
+				
+				return formatDate(d, fmt, axisOptions.monthNames);
+			};
+		}
+		else {
+			// pretty rounding of base-10 numbers
+			var maxDec = axisOptions.tickDecimals;
+			var dec = -Math.floor(Math.log(delta) / Math.LN10);
+			if (maxDec != null && dec > maxDec)
+				dec = maxDec;
+			
+			magn = Math.pow(10, -dec);
+			norm = delta / magn; // norm is between 1.0 and 10.0
+			
+			if (norm < 1.5)
+				size = 1;
+			else if (norm < 3) {
+				size = 2;
+				// special case for 2.5, requires an extra decimal
+				if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
+					size = 2.5;
+					++dec;
+				}
+			}
+			else if (norm < 7.5)
+				size = 5;
+			else
+				size = 10;
+
+			size *= magn;
+			
+			if (axisOptions.minTickSize != null && size < axisOptions.minTickSize)
+				size = axisOptions.minTickSize;
+
+			if (axisOptions.tickSize != null)
+				size = axisOptions.tickSize;
+			
+			axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec);
+			
+			var floorInBase = this.floorInBase;
+			
+			generator = function (axis) {
+				var ticks = [];
+				var start = floorInBase(axis.min, axis.tickSize);
+				// then spew out all possible ticks
+				var i = 0, v;
+				do {
+					v = start + i * axis.tickSize;
+					ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
+					++i;
+				} while (v < axis.max);
+				return ticks;
+			};
+
+			formatter = function (v, axis) {
+				if(v) {
+				return v.toFixed(axis.tickDecimals);
+				}
+				return 0;
+			};
+		}
+
+		axis.tickSize = unit ? [size, unit] : size;
+		axis.tickGenerator = generator;
+		if (Object.isFunction(axisOptions.tickFormatter))
+			axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); };
+		else
+			axis.tickFormatter = formatter;
+	},
+	/**
+	 * function: extendXRangeIfNeededByBar
+	 */
+	extendXRangeIfNeededByBar: function() {
+
+		if (this.options.xaxis.max == null) {
+			// great, we're autoscaling, check if we might need a bump
+			var newmax = this.xaxis.max;
+			this.graphData.each(function(gd){
+				if(gd.bars.show && gd.bars.barWidth + this.xaxis.datamax > newmax)
+				{
+					newmax = this.xaxis.datamax + gd.bars.barWidth;
+				}
+			}.bind(this));
+			this.xaxis.nax = newmax;
+			
+		}
+	},
+	/**
+	 * function: setTicks
+	 * 
+	 * parameters:
+	 * {Object} axis
+	 * {Object} axisOptions
+	 */
+	setTicks: function(axis, axisOptions) {
+		axis.ticks = [];
+		
+		if (axisOptions.ticks == null)
+			axis.ticks = axis.tickGenerator(axis);
+		else if (typeof axisOptions.ticks == "number") {
+			if (axisOptions.ticks > 0)
+				axis.ticks = axis.tickGenerator(axis);
+		}
+		else if (axisOptions.ticks) {
+			var ticks = axisOptions.ticks;
+
+			if (Object.isFunction(ticks))
+				// generate the ticks
+				ticks = ticks({ min: axis.min, max: axis.max });
+			
+			// clean up the user-supplied ticks, copy them over
+			//var i, v;
+			ticks.each(function(t, i){
+				var v = null;
+				var label = null;
+				if(typeof t == 'object') {
+					v = t[0];
+					if(t.length > 1) { label = t[1]; }
+				}
+				else {
+					v = t;
+				}
+				if(!label) {
+					label = axis.tickFormatter(v, axis);
+				}
+				axis.ticks[i] = {v: v, label: label}
+			}.bind(this));
+
+		}
+
+		if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) {
+			if (axisOptions.min == null)
+				axis.min = Math.min(axis.min, axis.ticks[0].v);
+			if (axisOptions.max == null && axis.ticks.length > 1)
+				axis.max = Math.min(axis.max, axis.ticks[axis.ticks.length - 1].v);
+		}
+	},
+	/**
+	 * Function: setSpacing
+	 * 
+	 * Parameters: none
+	 */
+	setSpacing: function() {
+		// calculate y label dimensions
+		var i, labels = [], l;
+		for (i = 0; i < this.yaxis.ticks.length; ++i) {
+			l = this.yaxis.ticks[i].label;
+
+			if (l)
+				labels.push('<div class="tickLabel">' + l + '</div>');
+		}
+
+		if (labels.length > 0) {
+			var dummyDiv = new Element('div', {'style': 'position:absolute;top:-10000px;font-size:smaller'});
+			dummyDiv.update(labels.join(""));
+			this.domObj.insert(dummyDiv);
+			this.yLabelMaxWidth = dummyDiv.getWidth();
+			this.yLabelMaxHeight = dummyDiv.select('div')[0].getHeight();
+			dummyDiv.remove();
+		}
+
+		var maxOutset = this.options.grid.borderWidth;
+		if (this.options.points.show)
+			maxOutset = Math.max(maxOutset, this.options.points.radius + this.options.points.lineWidth/2);
+		for (i = 0; i < this.graphData.length; ++i) {
+			if (this.graphData[i].points.show)
+				maxOutset = Math.max(maxOutset, this.graphData[i].points.radius + this.graphData[i].points.lineWidth/2);
+		}
+
+		this.chartOffset.left = this.chartOffset.right = this.chartOffset.top = this.chartOffset.bottom = maxOutset;
+		
+		this.chartOffset.left += this.yLabelMaxWidth + this.options.grid.labelMargin;
+		this.chartWidth = this.canvasWidth - this.chartOffset.left - this.chartOffset.right;
+
+		this.xLabelBoxWidth = this.chartWidth / 6;
+		labels = [];
+
+		for (i = 0; i < this.xaxis.ticks.length; ++i) {
+			l = this.xaxis.ticks[i].label;
+			if (l) {
+				labels.push('<span class="tickLabel" width="' + this.xLabelBoxWidth + '">' + l + '</span>');
+			}
+		}
+
+		var xLabelMaxHeight = 0;
+		if (labels.length > 0) {
+			var dummyDiv = new Element('div', {'style': 'position:absolute;top:-10000px;font-size:smaller'});
+			dummyDiv.update(labels.join(""));
+			this.domObj.appendChild(dummyDiv);	
+			xLabelMaxHeight = dummyDiv.getHeight();
+			dummyDiv.remove();
+		}
+
+		this.chartOffset.bottom += xLabelMaxHeight + this.options.grid.labelMargin;
+		this.chartHeight = this.canvasHeight - this.chartOffset.bottom - this.chartOffset.top;
+		this.hozScale = this.chartWidth / (this.xaxis.max - this.xaxis.min);
+		this.vertScale = this.chartHeight / (this.yaxis.max - this.yaxis.min);
+	},
+	/**
+	 * function: draw
+	 */
+	draw: function() {
+		if(this.options.bars.show)
+		{
+			this.extendXRangeIfNeededByBar();
+			this.setSpacing();
+			this.drawGrid();
+			this.drawBarGraph(this.graphData, this.barDataRange);
+		}
+		else if(this.options.pies.show)
+		{
+			this.preparePieData(this.graphData);
+			this.drawPieGraph(this.graphData);
+		}
+		else
+		{
+			this.drawGrid();
+			for (var i = 0; i < this.graphData.length; i++) {
+				this.drawGraph(this.graphData[i]);
+			}
+		}
+	},
+    /**
+     * function: translateHoz
+     * 
+     * Paramters:
+     * {Object} x
+     * 
+     * Description: Given a value this function translate it to relative x coord on canvas
+     */
+	translateHoz: function(x) {
+        return (x - this.xaxis.min) * this.hozScale;
+    },
+	/**
+	 * function: translateVert
+	 * 
+	 * parameters:
+	 * {Object} y
+	 * 
+	 * Description: Given a value this function translate it to relative y coord on canvas
+	 */
+    translateVert: function(y) {
+        return this.chartHeight - (y - this.yaxis.min) * this.vertScale;
+    },	
+	/**
+	 * function: drawGrid
+	 * 
+	 * parameters: none
+	 * 
+	 * description: draws the actual grid on the canvas
+	 */
+	drawGrid: function() {
+		var i;
+		
+		this.context.save();
+		this.context.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
+		this.context.translate(this.chartOffset.left, this.chartOffset.top);
+
+		// draw background, if any
+		if (this.options.grid.backgroundColor != null) {
+			this.context.fillStyle = this.options.grid.backgroundColor;
+			this.context.fillRect(0, 0, this.chartWidth, this.chartHeight);
+		}
+
+		// draw colored areas
+		if (this.options.grid.coloredAreas) {
+			var areas = this.options.grid.coloredAreas;
+			if (Object.isFunction(areas)) {
+				areas = areas({ xmin: this.xaxis.min, xmax: this.xaxis.max, ymin: this.yaxis.min, ymax: this.yaxis.max });
+			}
+			
+			areas.each(function(a){
+				// clip
+				if (a.x1 == null || a.x1 < this.xaxis.min)
+					a.x1 = this.xaxis.min;
+				if (a.x2 == null || a.x2 > this.xaxis.max)
+					a.x2 = this.xaxis.max;
+				if (a.y1 == null || a.y1 < this.yaxis.min)
+					a.y1 = this.yaxis.min;
+				if (a.y2 == null || a.y2 > this.yaxis.max)
+					a.y2 = this.yaxis.max;
+
+				var tmp;
+				if (a.x1 > a.x2) {
+					tmp = a.x1;
+					a.x1 = a.x2;
+					a.x2 = tmp;
+				}
+				if (a.y1 > a.y2) {
+					tmp = a.y1;
+					a.y1 = a.y2;
+					a.y2 = tmp;
+				}
+
+				if (a.x1 >= this.xaxis.max || a.x2 <= this.xaxis.min || a.x1 == a.x2
+					|| a.y1 >= this.yaxis.max || a.y2 <= this.yaxis.min || a.y1 == a.y2)
+					return;
+
+				this.context.fillStyle = a.color || this.options.grid.coloredAreasColor;
+				this.context.fillRect(Math.floor(this.translateHoz(a.x1)), Math.floor(this.translateVert(a.y2)),
+							 Math.floor(this.translateHoz(a.x2) - this.translateHoz(a.x1)), Math.floor(this.translateVert(a.y1) - this.translateVert(a.y2)));				
+			}.bind(this));
+
+			
+		}
+		
+		// draw the inner grid
+		this.context.lineWidth = 1;
+		this.context.strokeStyle = this.options.grid.tickColor;
+		this.context.beginPath();
+		var v;
+		if (this.options.grid.drawXAxis) {
+			this.xaxis.ticks.each(function(aTick){
+				v = aTick.v;
+				if(v <= this.xaxis.min || v >= this.xaxis.max) {
+					return;
+				}
+				this.context.moveTo(Math.floor(this.translateHoz(v)) + this.context.lineWidth / 2, 0);
+				this.context.lineTo(Math.floor(this.translateHoz(v)) + this.context.lineWidth / 2, this.chartHeight);
+			}.bind(this));
+
+		}
+		
+		if (this.options.grid.drawYAxis) {
+			this.yaxis.ticks.each(function(aTick){
+				v = aTick.v;
+				if(v <= this.yaxis.min || v >= this.yaxis.max) {
+					return;
+				}
+				this.context.moveTo(0, Math.floor(this.translateVert(v)) + this.context.lineWidth / 2);
+				this.context.lineTo(this.chartWidth, Math.floor(this.translateVert(v)) + this.context.lineWidth / 2);
+			}.bind(this));
+			
+		}
+		this.context.stroke();
+		
+		if (this.options.grid.borderWidth) {
+			// draw border
+			this.context.lineWidth = this.options.grid.borderWidth;
+			this.context.strokeStyle = this.options.grid.color;
+			this.context.lineJoin = "round";
+			this.context.strokeRect(0, 0, this.chartWidth, this.chartHeight);
+			this.context.restore();
+		}
+	},
+	/**
+	 * function: insertLabels
+	 * 
+	 * parameters: none
+	 * 
+	 * description: inserts the label with proper spacing. Both on X and Y axis
+	 */
+	insertLabels: function() {
+		this.domObj.select(".tickLabels").invoke('remove');
+		
+		var i, tick;
+		var html = '<div class="tickLabels" style="font-size:smaller;color:' + this.options.grid.color + '">';
+		
+		// do the x-axis
+		this.xaxis.ticks.each(function(tick){
+			if (!tick.label || tick.v < this.xaxis.min || tick.v > this.xaxis.max)
+				return;
+			html += '<div style="position:absolute;top:' + (this.chartOffset.top + this.chartHeight + this.options.grid.labelMargin) + 'px;left:' + (this.chartOffset.left + this.translateHoz(tick.v) - this.xLabelBoxWidth/2) + 'px;width:' + this.xLabelBoxWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
+			
+		}.bind(this));
+		
+		// do the y-axis
+		this.yaxis.ticks.each(function(tick){
+			if (!tick.label || tick.v < this.yaxis.min || tick.v > this.yaxis.max)
+				return;
+			html += '<div id="ylabels" style="position:absolute;top:' + (this.chartOffset.top + this.translateVert(tick.v) - this.yLabelMaxHeight/2) + 'px;left:0;width:' + this.yLabelMaxWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>";
+		}.bind(this));
+
+		html += '</div>';
+		
+		this.domObj.insert(html);
+	},
+	/**
+	 * function: drawGraph
+	 * 
+	 * Paramters:
+	 * {Object} graphData
+	 * 
+	 * Description: given a graphData (series) this function calls a proper lower level method to draw it.
+	 */
+	drawGraph: function(graphData) {
+		if (graphData.lines.show || (!graphData.bars.show && !graphData.points.show))
+			this.drawGraphLines(graphData);
+		if (graphData.bars.show)
+			this.drawGraphBar(graphData);
+		if (graphData.points.show)
+			this.drawGraphPoints(graphData);
+	},
+	/**
+	 * function: plotLine
+	 * 
+	 * parameters:
+	 * {Object} data
+	 * {Object} offset
+	 * 
+	 * description: 
+	 * Helper function that plots a line based on the data provided
+	 */
+	plotLine: function(data, offset) {
+        var prev, cur = null, drawx = null, drawy = null;
+        
+        this.context.beginPath();
+        for (var i = 0; i < data.length; ++i) {
+            prev = cur;
+            cur = data[i];
+
+            if (prev == null || cur == null)
+                continue;
+            
+            var x1 = prev[0], y1 = prev[1],
+                x2 = cur[0], y2 = cur[1];
+
+            // clip with ymin
+            if (y1 <= y2 && y1 < this.yaxis.min) {
+                if (y2 < this.yaxis.min)
+                    continue;   // line segment is outside
+                // compute new intersection point
+                x1 = (this.yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+                y1 = this.yaxis.min;
+            }
+            else if (y2 <= y1 && y2 < this.yaxis.min) {
+                if (y1 < this.yaxis.min)
+                    continue;
+                x2 = (this.yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+                y2 = this.yaxis.min;
+            }
+
+            // clip with ymax
+            if (y1 >= y2 && y1 > this.yaxis.max) {
+                if (y2 > this.yaxis.max)
+                    continue;
+                x1 = (this.yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+                y1 = this.yaxis.max;
+            }
+            else if (y2 >= y1 && y2 > this.yaxis.max) {
+                if (y1 > this.yaxis.max)
+                    continue;
+                x2 = (this.yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+                y2 = this.yaxis.max;
+            }
+
+            // clip with xmin
+            if (x1 <= x2 && x1 < this.xaxis.min) {
+                if (x2 < this.xaxis.min)
+                    continue;
+                y1 = (this.xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+                x1 = this.xaxis.min;
+            }
+            else if (x2 <= x1 && x2 < this.xaxis.min) {
+                if (x1 < this.xaxis.min)
+                    continue;
+                y2 = (this.xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+                x2 = this.xaxis.min;
+            }
+
+            // clip with xmax
+            if (x1 >= x2 && x1 > this.xaxis.max) {
+                if (x2 > this.xaxis.max)
+                    continue;
+                y1 = (this.xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+                x1 = this.xaxis.max;
+            }
+            else if (x2 >= x1 && x2 > this.xaxis.max) {
+                if (x1 > this.xaxis.max)
+                    continue;
+                y2 = (this.xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+                x2 = this.xaxis.max;
+            }
+
+            if (drawx != this.translateHoz(x1) || drawy != this.translateVert(y1) + offset)
+                this.context.moveTo(this.translateHoz(x1), this.translateVert(y1) + offset);
+            
+            drawx = this.translateHoz(x2);
+            drawy = this.translateVert(y2) + offset;
+            this.context.lineTo(drawx, drawy);
+        }
+        this.context.stroke();
+    },
+	/**
+	 * function: plotLineArea
+	 * 
+	 * parameters:
+	 * {Object} data
+	 * 
+	 * description:
+	 * Helper functoin that plots a colored line graph. This function
+	 * takes the data nad then fill in the area on the graph properly
+	 */
+	plotLineArea: function(data) {
+        var prev, cur = null;
+        
+        var bottom = Math.min(Math.max(0, this.yaxis.min), this.yaxis.max);
+        var top, lastX = 0;
+
+        var areaOpen = false;
+        
+        for (var i = 0; i < data.length; ++i) {
+            prev = cur;
+            cur = data[i];
+
+            if (areaOpen && prev != null && cur == null) {
+                // close area
+                this.context.lineTo(this.translateHoz(lastX), this.translateVert(bottom));
+                this.context.fill();
+                areaOpen = false;
+                continue;
+            }
+
+            if (prev == null || cur == null)
+                continue;
+                
+            var x1 = prev[0], y1 = prev[1],
+                x2 = cur[0], y2 = cur[1];
+
+            // clip x values
+            
+            // clip with xmin
+            if (x1 <= x2 && x1 < this.xaxis.min) {
+                if (x2 < this.xaxis.min)
+                    continue;
+                y1 = (this.xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+                x1 = this.xaxis.min;
+            }
+            else if (x2 <= x1 && x2 < this.xaxis.min) {
+                if (x1 < this.xaxis.min)
+                    continue;
+                y2 = (this.xaxis.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+                x2 = this.xaxis.min;
+            }
+
+            // clip with xmax
+            if (x1 >= x2 && x1 > this.xaxis.max) {
+                if (x2 > this.xaxis.max)
+                    continue;
+                y1 = (this.xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+                x1 = this.xaxis.max;
+            }
+            else if (x2 >= x1 && x2 > this.xaxis.max) {
+                if (x1 > this.xaxis.max)
+                    continue;
+                y2 = (this.xaxis.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+                x2 = this.xaxis.max;
+            }
+
+            if (!areaOpen) {
+                // open area
+                this.context.beginPath();
+                this.context.moveTo(this.translateHoz(x1), this.translateVert(bottom));
+                areaOpen = true;
+            }
+            
+            // now first check the case where both is outside
+            if (y1 >= this.yaxis.max && y2 >= this.yaxis.max) {
+                this.context.lineTo(this.translateHoz(x1), this.translateVert(this.yaxis.max));
+                this.context.lineTo(this.translateHoz(x2), this.translateVert(this.yaxis.max));
+                continue;
+            }
+            else if (y1 <= this.yaxis.min && y2 <= this.yaxis.min) {
+                this.context.lineTo(this.translateHoz(x1), this.translateVert(this.yaxis.min));
+                this.context.lineTo(this.translateHoz(x2), this.translateVert(this.yaxis.min));
+                continue;
+            }
+            
+            var x1old = x1, x2old = x2;
+
+            // clip with ymin
+            if (y1 <= y2 && y1 < this.yaxis.min && y2 >= this.yaxis.min) {
+                x1 = (this.yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+                y1 = this.yaxis.min;
+            }
+            else if (y2 <= y1 && y2 < this.yaxis.min && y1 >= this.yaxis.min) {
+                x2 = (this.yaxis.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+                y2 = this.yaxis.min;
+            }
+
+            // clip with ymax
+            if (y1 >= y2 && y1 > this.yaxis.max && y2 <= this.yaxis.max) {
+                x1 = (this.yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+                y1 = this.yaxis.max;
+            }
+            else if (y2 >= y1 && y2 > this.yaxis.max && y1 <= this.yaxis.max) {
+                x2 = (this.yaxis.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+                y2 = this.yaxis.max;
+            }
+
+
+            // if the x value was changed we got a rectangle
+            // to fill
+            if (x1 != x1old) {
+                if (y1 <= this.yaxis.min)
+                    top = this.yaxis.min;
+                else
+                    top = this.yaxis.max;
+                
+                this.context.lineTo(this.translateHoz(x1old), this.translateVert(top));
+                this.context.lineTo(this.translateHoz(x1), this.translateVert(top));
+            }
+            
+            // fill the triangles
+            this.context.lineTo(this.translateHoz(x1), this.translateVert(y1));
+            this.context.lineTo(this.translateHoz(x2), this.translateVert(y2));
+
+            // fill the other rectangle if it's there
+            if (x2 != x2old) {
+                if (y2 <= this.yaxis.min)
+                    top = this.yaxis.min;
+                else
+                    top = this.yaxis.max;
+                
+                this.context.lineTo(this.translateHoz(x2old), this.translateVert(top));
+                this.context.lineTo(this.translateHoz(x2), this.translateVert(top));
+            }
+
+            lastX = Math.max(x2, x2old);
+        }
+
+        if (areaOpen) {
+            this.context.lineTo(this.translateHoz(lastX), this.translateVert(bottom));
+            this.context.fill();
+        }
+    },		
+	/**
+	 * function: drawGraphLines
+	 * 
+	 * parameters:
+	 * {Object} graphData
+	 * 
+	 * description:
+	 * Main function that daws the line graph. This function is called 
+	 * if <options> lines property is set to show or no other type of 
+	 * graph is specified. This function depends on <plotLineArea> and 
+	 * <plotLine> functions.
+	 */
+	drawGraphLines: function(graphData) {
+        this.context.save();
+        this.context.translate(this.chartOffset.left, this.chartOffset.top);
+        this.context.lineJoin = "round";
+
+        var lw = graphData.lines.lineWidth;
+        var sw = graphData.shadowSize;
+        // FIXME: consider another form of shadow when filling is turned on
+        if (sw > 0) {
+            // draw shadow in two steps
+            this.context.lineWidth = sw / 2;
+            this.context.strokeStyle = "rgba(0,0,0,0.1)";
+            this.plotLine(graphData.data, lw/2 + sw/2 + this.context.lineWidth/2);
+
+            this.context.lineWidth = sw / 2;
+            this.context.strokeStyle = "rgba(0,0,0,0.2)";
+            this.plotLine(graphData.data, lw/2 + this.context.lineWidth/2);
+        }
+
+        this.context.lineWidth = lw;
+        this.context.strokeStyle = graphData.color;
+        if (graphData.lines.fill) {
+            this.context.fillStyle = graphData.lines.fillColor != null ? graphData.lines.fillColor : this.parseColor(graphData.color).scale(null, null, null, 0.4).toString();
+            this.plotLineArea(graphData.data, 0);
+        }
+
+        this.plotLine(graphData.data, 0);
+        this.context.restore();
+    },
+	/**
+	 * function: plotPoints
+	 * 
+	 * parameters:
+	 * {Object} data
+	 * {Object} radius
+	 * {Object} fill
+	 * 
+	 * description:
+	 * Helper function that draws the point graph according to the data provided. Size of each 
+	 * point is provided by radius variable and fill specifies if points 
+	 * are filled
+	 */
+	plotPoints: function(data, radius, fill) {
+        for (var i = 0; i < data.length; ++i) {
+            if (data[i] == null)
+                continue;
+            
+            var x = data[i][0], y = data[i][1];
+            if (x < this.xaxis.min || x > this.xaxis.max || y < this.yaxis.min || y > this.yaxis.max)
+                continue;
+            
+            this.context.beginPath();
+            this.context.arc(this.translateHoz(x), this.translateVert(y), radius, 0, 2 * Math.PI, true);
+            if (fill)
+                this.context.fill();
+            this.context.stroke();
+        }
+    },
+	/**
+	 * function: plotPointShadows
+	 * 
+	 * parameters:
+	 * {Object} data
+	 * {Object} offset
+	 * {Object} radius
+	 * 
+	 * description: 
+	 * Helper function that draws the shadows for the points.
+	 */
+    plotPointShadows: function(data, offset, radius) {
+        for (var i = 0; i < data.length; ++i) {
+            if (data[i] == null)
+                continue;
+            
+            var x = data[i][0], y = data[i][1];
+            if (x < this.xaxis.min || x > this.xaxis.max || y < this.yaxis.min || y > this.yaxis.max)
+                continue;
+            this.context.beginPath();
+            this.context.arc(this.translateHoz(x), this.translateVert(y) + offset, radius, 0, Math.PI, false);
+            this.context.stroke();
+        }
+    },
+	/**
+	 * function: drawGraphPoints
+	 * 
+	 * paramters:
+	 * {Object} graphData
+	 * 
+	 * description:
+	 * Draws the point graph onto the canvas. This function depends on helper 
+	 * functions <plotPointShadows> and <plotPoints>
+	 */
+    drawGraphPoints: function(graphData) {
+       	this.context.save();
+        this.context.translate(this.chartOffset.left, this.chartOffset.top);
+
+        var lw = graphData.lines.lineWidth;
+        var sw = graphData.shadowSize;
+        if (sw > 0) {
+            // draw shadow in two steps
+            this.context.lineWidth = sw / 2;
+            this.context.strokeStyle = "rgba(0,0,0,0.1)";
+            this.plotPointShadows(graphData.data, sw/2 + this.context.lineWidth/2, graphData.points.radius);
+
+            this.context.lineWidth = sw / 2;
+            this.context.strokeStyle = "rgba(0,0,0,0.2)";
+            this.plotPointShadows(graphData.data, this.context.lineWidth/2, graphData.points.radius);
+        }
+
+        this.context.lineWidth = graphData.points.lineWidth;
+        this.context.strokeStyle = graphData.color;
+        this.context.fillStyle = graphData.points.fillColor != null ? graphData.points.fillColor : graphData.color;
+        this.plotPoints(graphData.data, graphData.points.radius, graphData.points.fill);
+        this.context.restore();
+    },
+	/**
+	 * function: preparePieData
+	 * 
+	 * parameters:
+	 * {Object} graphData
+	 * 
+	 * Description: 
+	 * Helper function that manipulates the given data stream so that it can 
+	 * be plotted as a Pie Chart
+	 */
+	preparePieData: function(graphData)
+	{
+		for(i = 0; i < graphData.length; i++)
+		{
+			var data = 0;
+			for(j = 0; j < graphData[i].data.length; j++){
+				data += parseInt(graphData[i].data[j][1]);
+			}
+			graphData[i].data = data;
+		}
+	},
+	/**
+	 * function: drawPieShadow
+	 * 
+	 * {Object} anchorX
+	 * {Object} anchorY
+	 * {Object} radius
+	 * 
+	 * description:
+	 * Helper function that draws a shadow for the Pie Chart. This just draws 
+	 * a circle with offset that simulates shadow. We do not give each piece 
+	 * of the pie an individual shadow.
+	 */
+	drawPieShadow: function(anchorX, anchorY, radius)
+	{
+		this.context.beginPath();
+		this.context.moveTo(anchorX, anchorY);
+		this.context.fillStyle = 'rgba(0,0,0,' + 0.1 + ')';
+		startAngle = 0;
+		endAngle = (Math.PI/180)*360;	
+		this.context.arc(anchorX + 2, anchorY +2, radius + (this.options.shadowSize/2), startAngle, endAngle, false);
+		this.context.fill();
+		this.context.closePath();
+	},
+	/**
+	 * function: drawPieGraph
+	 * 
+	 * parameters:
+	 * {Object} graphData
+	 * 
+	 * description: 
+	 * Draws the actual pie chart. This function depends on helper function 
+	 * <drawPieShadow> to draw the actual shadow
+	 */
+	drawPieGraph: function(graphData)
+	{
+		var sumData = 0;
+		var radius = 0;
+		var centerX = this.chartWidth/2;
+		var centerY = this.chartHeight/2;
+		var startAngle = 0;
+		var endAngle = 0;
+		var fontSize = this.options.pies.fontSize;
+		var labelWidth = this.options.pies.labelWidth;
+
+		//determine Pie Radius
+		if(!this.options.pies.autoScale)
+			radius = this.options.pies.radius;
+		else
+			radius = (this.chartHeight * 0.85)/2;
+
+		var labelRadius = radius * 1.05;
+
+		for(i = 0; i < graphData.length; i++)
+			sumData += graphData[i].data;
+
+		// used to adjust labels so that everything adds up to 100%
+		totalPct = 0;
+		
+		//lets draw the shadow first.. we don't need an individual shadow to every pie rather we just
+		//draw a circle underneath to simulate the shadow...
+		this.drawPieShadow(centerX, centerY, radius, 0, 0); 
+		
+		//lets draw the actual pie chart now.
+		graphData.each(function(gd, j){
+			var pct = gd.data / sumData;
+			startAngle = endAngle;
+			endAngle += pct * (2 * Math.PI);
+			var sliceMiddle = (endAngle - startAngle) / 2 + startAngle;
+			var labelX = centerX + Math.cos(sliceMiddle) * labelRadius;
+			var labelY = centerY + Math.sin(sliceMiddle) * labelRadius;
+			var anchorX = centerX;
+			var anchorY = centerY;
+			var textAlign = null;
+			var verticalAlign = null;
+			var left = 0;
+			var top = 0;
+			
+			//draw pie:
+			//drawing pie	
+			this.context.beginPath();
+			this.context.moveTo(anchorX, anchorY);				
+			this.context.arc(anchorX, anchorY, radius, startAngle, endAngle, false);
+			this.context.closePath();
+			this.context.fillStyle = this.parseColor(gd.color).scale(null, null, null, this.options.pies.fillOpacity).toString();
+
+			if(this.options.pies.fill)	{ this.context.fill(); }
+
+			// drawing labels
+			if (sliceMiddle <= 0.25 * (2 * Math.PI)) 
+			{
+				// text on top and align left
+				textAlign = "left";
+				verticalAlign = "top";
+				left = labelX;
+				top = labelY + fontSize;
+			}
+			else if (sliceMiddle > 0.25 * (2 * Math.PI) && sliceMiddle <= 0.5 * (2 * Math.PI)) 
+			{
+				// text on bottom and align left
+				textAlign = "left";
+				verticalAlign = "bottom";
+				left = labelX - labelWidth;
+				top = labelY;
+			}
+			else if (sliceMiddle > 0.5 * (2 * Math.PI) && sliceMiddle <= 0.75 * (2 * Math.PI)) 
+			{
+				// text on bottom and align right
+				textAlign = "right";
+				verticalAlign = "bottom";
+				left = labelX - labelWidth;
+				top = labelY - fontSize;
+			}
+			else 
+			{
+				// text on top and align right
+				textAlign = "right";
+				verticalAlign = "bottom";
+				left = labelX;
+				top = labelY - fontSize;
+			}
+
+			left = left + "px";
+			top = top + "px";
+			var textVal = Math.round(pct * 100);
+
+			if (j == graphData.length - 1) {
+				if (textVal + totalPct < 100) {
+					textVal = textVal + 1;
+				} else if (textVal + totalPct > 100) {
+					textVal = textVal - 1;
+				};
+			}
+
+			var html = "<div style=\"position: absolute;zindex:11; width:" + labelWidth + "px;fontSize:" + fontSize + "px;overflow:hidden;top:"+ top + ";left:"+ left + ";textAlign:" + textAlign + ";verticalAlign:" + verticalAlign +"\">" +  textVal + "%</div>";
+			//$(html).appendTo(target);
+			this.domObj.insert(html);
+
+			totalPct = totalPct + textVal;			
+		}.bind(this));
+		
+	},
+	/**
+	 * function: drawBarGraph
+	 * 
+	 * parameters:
+	 * {Object} graphData
+	 * {Object} barDataRange
+	 * 
+	 * description: 
+	 * Goes through each series in graphdata and passes it onto <drawBarGraphs> function
+	 */
+	drawBarGraph: function(graphData, barDataRange)
+	{
+		graphData.each(function(gd, i){
+			this.drawGraphBars(gd, i, graphData.size(), barDataRange);
+		}.bind(this));
+	},
+	/**
+	 * function: drawGraphBar
+	 * 
+	 * parameters:
+	 * {Object} graphData
+	 * 
+	 * description:
+	 * This function is called when an individual series in GraphData is bar graph and plots it
+	 */
+	drawGraphBar: function(graphData)
+	{
+		this.drawGraphBars(graphData, 0, this.graphData.length, this.barDataRange);			
+	},	
+	/**
+	 * function: plotBars
+	 * 
+	 * parameters:
+	 * {Object} graphData
+	 * {Object} data
+	 * {Object} barWidth
+	 * {Object} offset
+	 * {Object} fill
+	 * {Object} counter
+	 * {Object} total
+	 * {Object} barDataRange
+	 * 
+	 * description: 
+	 * Helper function that draws the bar graph based on data.
+	 */
+	plotBars: function(graphData, data, barWidth, offset, fill,counter, total, barDataRange) {
+		var shift = 0;
+		
+		if(total % 2 == 0)
+		{
+			shift = (1 + ((counter  - total /2 ) - 1)) * barWidth;
+		}
+		else
+		{
+			var interval = 0.5;			
+			if(counter == (total/2 - interval )) {
+				shift = - barWidth * interval;
+			}
+			else {
+				shift = (interval + (counter  - Math.round(total/2))) * barWidth;
+			}
+		}
+
+		var rangeData = [];
+		data.each(function(d){
+			if(!d) return;
+			
+			var x = d[0], y = d[1];
+			var drawLeft = true, drawTop = true, drawRight = true;
+			var left = x + shift, right = x + barWidth + shift, bottom = 0, top = y;
+			var rangeDataPoint = {};
+			rangeDataPoint.left = left;
+			rangeDataPoint.right = right;
+			rangeDataPoint.value = top;
+			rangeData.push(rangeDataPoint);
+
+			if (right < this.xaxis.min || left > this.xaxis.max || top < this.yaxis.min || bottom > this.yaxis.max)
+				return;
+
+			// clip
+			if (left < this.xaxis.min) {
+				left = this.xaxis.min;
+				drawLeft = false;
+			}
+
+			if (right > this.xaxis.max) {
+				right = this.xaxis.max;
+				drawRight = false;
+			}
+
+			if (bottom < this.yaxis.min)
+				bottom = this.yaxis.min;
+
+			if (top > this.yaxis.max) {
+				top = this.yaxis.max;
+				drawTop = false;
+			}
+			
+			if(graphData.bars.showShadow && graphData.shadowSize > 0)
+				this.plotShadowOutline(graphData, this.context.strokeStyle, left, bottom, top, right, drawLeft, drawRight, drawTop);
+				
+			// fill the bar
+			if (fill) {
+				this.context.beginPath();
+				this.context.moveTo(this.translateHoz(left), this.translateVert(bottom) + offset);
+				this.context.lineTo(this.translateHoz(left), this.translateVert(top) + offset);
+				this.context.lineTo(this.translateHoz(right), this.translateVert(top) + offset);
+				this.context.lineTo(this.translateHoz(right), this.translateVert(bottom) + offset);
+				this.context.fill();
+			}
+
+			// draw outline
+			if (drawLeft || drawRight || drawTop) {
+				this.context.beginPath();
+				this.context.moveTo(this.translateHoz(left), this.translateVert(bottom) + offset);
+				if (drawLeft)
+					this.context.lineTo(this.translateHoz(left), this.translateVert(top) + offset);
+				else
+					this.context.moveTo(this.translateHoz(left), this.translateVert(top) + offset);
+
+				if (drawTop)
+					this.context.lineTo(this.translateHoz(right), this.translateVert(top) + offset);
+				else
+					this.context.moveTo(this.translateHoz(right), this.translateVert(top) + offset);
+				if (drawRight)
+					this.context.lineTo(this.translateHoz(right), this.translateVert(bottom) + offset);
+				else
+					this.context.moveTo(this.translateHoz(right), this.translateVert(bottom) + offset);
+				this.context.stroke();
+			}
+		}.bind(this));
+		
+		barDataRange.push(rangeData);
+	},
+	/**
+	 * function: plotShadowOutline
+	 * 
+	 * parameters:
+	 * {Object} graphData
+	 * {Object} orgStrokeStyle
+	 * {Object} left
+	 * {Object} bottom
+	 * {Object} top
+	 * {Object} right
+	 * {Object} drawLeft
+	 * {Object} drawRight
+	 * {Object} drawTop
+	 * 
+	 * description:
+	 * Helper function that draws a outline simulating shadow for bar chart
+	 */
+	plotShadowOutline: function(graphData, orgStrokeStyle, left, bottom, top, right, drawLeft, drawRight, drawTop)
+	{
+		var orgOpac = 0.3;
+		
+		for(var n = 1; n <= this.options.shadowSize/2; n++)
+		{
+			var opac = orgOpac * n;
+			this.context.beginPath();
+			this.context.strokeStyle = "rgba(0,0,0," + opac + ")";
+
+			this.context.moveTo(this.translateHoz(left) + n, this.translateVert(bottom));
+
+			if(drawLeft)
+				this.context.lineTo(this.translateHoz(left) + n, this.translateVert(top) - n);
+			else
+				this.context.moveTo(this.translateHoz(left) + n, this.translateVert(top) - n);
+
+			if(drawTop)	
+				this.context.lineTo(this.translateHoz(right) + n, this.translateVert(top) - n);
+			else
+				this.context.moveTo(this.translateHoz(right) + n, this.translateVert(top) - n);
+
+			if(drawRight)
+				this.context.lineTo(this.translateHoz(right) + n, this.translateVert(bottom));
+			else
+				this.context.lineTo(this.translateHoz(right) + n, this.translateVert(bottom));
+
+			this.context.stroke();
+			this.context.closePath();
+		}
+
+		this.context.strokeStyle = orgStrokeStyle;
+	},
+	/**
+	 * function: drawGraphBars
+	 * 
+	 * parameters:
+	 * {Object} graphData 
+	 * {Object} counter
+	 * {Object} total
+	 * {Object} barDataRange
+	 * 
+	 * description:
+	 * Draws the actual bar graphs. Calls <plotBars> to draw the individual bar
+	 */
+	drawGraphBars: function(graphData, counter, total, barDataRange){
+		this.context.save();
+		this.context.translate(this.chartOffset.left, this.chartOffset.top);
+		this.context.lineJoin = "round";
+
+		var bw = graphData.bars.barWidth;
+		var lw = Math.min(graphData.bars.lineWidth, bw);
+
+
+		this.context.lineWidth = lw;
+		this.context.strokeStyle = graphData.color;
+		if (graphData.bars.fill) {
+			this.context.fillStyle = graphData.bars.fillColor != null ? graphData.bars.fillColor : this.parseColor(graphData.color).scale(null, null, null, this.options.bars.fillOpacity).toString();
+		}
+		this.plotBars(graphData, graphData.data, bw, 0, graphData.bars.fill, counter, total, barDataRange);
+		this.context.restore();
+	},
+	/**
+	 * function: insertLegend
+	 * 
+	 * description:
+	 * inserts legend onto the graph. *legend: {show: true}* must be set in <options> 
+	 * for for this to work.
+	 */
+	insertLegend: function() {
+		this.domObj.select(".legend").invoke('remove');
+
+		if (!this.options.legend.show)
+			return;
+		
+		var fragments = [];
+		var rowStarted = false;
+		this.graphData.each(function(gd, index){
+			if(!gd.label) {
+				return;
+			}
+			if(index % this.options.legend.noColumns == 0) {
+				if(rowStarted) {
+					fragments.push('</tr>');
+				}
+				fragments.push('<tr>');
+				rowStarted = true;
+			}
+			var label = gd.label;
+			if(this.options.legend.labelFormatter != null) {
+				label = this.options.legend.labelFormatter(label);
+			}
+			
+			fragments.push(
+				'<td class="legendColorBox"><div style="border:1px solid ' + this.options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:14px;height:10px;background-color:' + gd.color + ';overflow:hidden"></div></div></td>' +
+				'<td class="legendLabel">' + label + '</td>');
+			
+		}.bind(this));
+
+		if (rowStarted)
+			fragments.push('</tr>');
+		
+		if(fragments.length > 0){
+			var table = '<table style="font-size:smaller;color:' + this.options.grid.color + '">' + fragments.join("") + '</table>';
+			if($(this.options.legend.container) != null){
+				$(this.options.legend.container).insert(table);
+			}else{
+				var pos = '';
+				var p = this.options.legend.position, m = this.options.legend.margin;
+				
+				if(p.charAt(0) == 'n') pos += 'top:' + (m + this.chartOffset.top) + 'px;';
+				else if(p.charAt(0) == 's') pos += 'bottom:' + (m + this.chartOffset.bottom) + 'px;';					
+				if(p.charAt(1) == 'e') pos += 'right:' + (m + this.chartOffset.right) + 'px;';
+				else if(p.charAt(1) == 'w') pos += 'left:' + (m + this.chartOffset.bottom) + 'px;';
+				var div = this.domObj.insert('<div class="ProtoChart-legend" style="border: 1px solid '+this.options.legend.borderColor+'; position:absolute;z-index:2;' + pos +'">' + table + '</div>').getElementsBySelector('div.ProtoChart-legend').first();
+				
+				if(this.options.legend.backgroundOpacity != 0.0){
+					var c = this.options.legend.backgroundColor;
+					if(c == null){
+						var tmp = (this.options.grid.backgroundColor != null) ? this.options.grid.backgroundColor : this.extractColor(div);
+						c = this.parseColor(tmp).adjust(null, null, null, 1).toString();
+					}
+					this.domObj.insert('<div class="ProtoChart-legend-bg" style="position:absolute;width:' + div.getWidth() + 'px;height:' + div.getHeight() + 'px;' + pos +'background-color:' + c + ';"> </div>').select('div.ProtoChart-legend-bg').first().setStyle({
+						'opacity': this.options.legend.backgroundOpacity
+					});						
+				}
+			}
+		}
+	},
+	/**
+	 * Function: onMouseMove
+	 * 
+	 * parameters:
+	 * event: {Object} ev
+	 * 
+	 * Description:
+	 * Called whenever the mouse is moved on the graph. This takes care of the mousetracking.
+	 * This event also fires <ProtoChart:mousemove> event, which gets current position of the 
+	 * mouse as a parameters. 
+	 */
+	onMouseMove: function(ev) {
+		var e = ev || window.event;
+		if (e.pageX == null && e.clientX != null) {
+			var de = document.documentElement, b = $(document.body);
+			this.lastMousePos.pageX = e.clientX + (de && de.scrollLeft || b.scrollLeft || 0);
+			this.lastMousePos.pageY = e.clientY + (de && de.scrollTop || b.scrollTop || 0);
+		}
+		else {
+			this.lastMousePos.pageX = e.pageX;
+			this.lastMousePos.pageY = e.pageY;
+		}
+		
+		var offset = this.overlay.cumulativeOffset();
+		var pos = {
+			x: this.xaxis.min + (e.pageX - offset.left - this.chartOffset.left) / this.hozScale,
+			y: this.yaxis.max - (e.pageY - offset.top - this.chartOffset.top) / this.vertScale
+		};
+		
+		if(this.options.mouse.track && this.selectionInterval == null) {
+			this.hit(ev, pos);
+		}
+		this.domObj.fire("ProtoChart:mousemove", [ pos ]);
+	},
+	/**
+	 * Function: onMouseDown
+	 * 
+	 * Parameters:
+	 * Event - {Object} e
+	 * 
+	 * Description:
+	 * Called whenever the mouse is clicked.
+	 */
+	onMouseDown: function(e) {
+		if (e.which != 1)  // only accept left-click
+			return;
+		
+		document.body.focus();
+
+		if (document.onselectstart !== undefined && this.workarounds.onselectstart == null) {
+			this.workarounds.onselectstart = document.onselectstart;
+			document.onselectstart = function () { return false; };
+		}
+		if (document.ondrag !== undefined && this.workarounds.ondrag == null) {
+			this.workarounds.ondrag = document.ondrag;
+			document.ondrag = function () { return false; };
+		}
+		
+		this.setSelectionPos(this.selection.first, e);
+			
+		if (this.selectionInterval != null)
+			clearInterval(this.selectionInterval);
+		this.lastMousePos.pageX = null;
+		this.selectionInterval = setInterval(this.updateSelectionOnMouseMove.bind(this), 200);
+
+		this.overlay.observe("mouseup", this.onSelectionMouseUp.bind(this));
+	},
+	/**
+	 * Function: onClick
+	 * parameters:
+	 * Event - {Object} e
+	 * Description: 
+	 * Handles the "click" event on the chart. This function fires <ProtoChart:plotclick> event. If
+	 * <options.allowDataClick> is enabled then it also fires <ProtoChart:dataclick> event which gives
+	 * you access to exact data point where user clicked.
+	 */
+	onClick: function(e) {
+		if (this.ignoreClick) {
+			this.ignoreClick = false;
+			return;
+		}
+		var offset = this.overlay.cumulativeOffset(); 
+		var pos ={
+			x: this.xaxis.min + (e.pageX - offset.left - this.chartOffset.left) / this.hozScale,
+			y: this.yaxis.max - (e.pageY - offset.top - this.chartOffset.top) / this.vertScale
+		};
+		this.domObj.fire("ProtoChart:plotclick", [ pos ]);
+
+		if(this.options.allowDataClick)
+		{
+			var dataPoint = {};
+			if(this.options.points.show)
+			{
+				dataPoint = this.getDataClickPoint(pos, this.options);
+				this.domObj.fire("ProtoChart:dataclick", [dataPoint]);
+			}
+			else if(this.options.lines.show && this.options.points.show)
+			{
+				dataPoint = this.getDataClickPoint(pos, this.options);
+				this.domObj.fire("ProtoChart:dataclick", [dataPoint]);
+			}
+			else if(this.options.bars.show)
+			{
+				if(this.barDataRange.length > 0)
+				{
+					dataPoint = this.getDataClickPoint(pos, this.options, this.barDataRange);
+					this.domObj.fire("ProtoChart:dataclick", [dataPoint]);
+				}
+			}
+		}
+	},
+	/**
+	 * Internal function used by onClick method.
+	 */
+	getDataClickPoint: function(pos, options, barDataRange)
+	{
+		pos.x = parseInt(pos.x);
+		pos.y = parseInt(pos.y);
+		var yClick = pos.y.toFixed(0);
+		var dataVal = {};
+
+		dataVal.position = pos;
+		dataVal.value = '';
+
+		if(options.points.show)
+		{
+			this.graphData.each(function(gd){
+				var temp = gd.data;
+				var xClick = parseInt(pos.x.toFixed(0));
+				if(xClick < 0) { xClick = 0; }
+				if(temp[xClick] && yClick >= temp[xClick][1] - (this.options.points.radius * 10) && yClick <= temp[xClick][1] + (this.options.points.radius * 10)) {
+					dataVal.value = temp[xClick][1];
+					throw $break;
+				}
+				
+			}.bind(this));
+		}
+		else if(options.bars.show)
+		{
+			xClick = pos.x;
+			this.barDataRange.each(function(barData){
+				barData.each(function(data){
+					var temp = data;
+					if(xClick > temp.left && xClick < temp.right) {
+						dataVal.value = temp.value;
+						throw $break;
+					}
+				}.bind(this));
+			}.bind(this));
+
+		}
+
+		return dataVal;
+	},
+	/**
+	 * Function: triggerSelectedEvent
+	 * 
+	 * Description:
+	 * Internal function called when a selection on the graph is made. This function
+	 * fires <ProtoChart:selected> event which has a parameter representing the selection
+	 * {
+	 * 	x1: {int}, y1: {int},
+	 * 	x2: {int}, y2: {int}
+	 * }
+	 */
+	triggerSelectedEvent: function() {
+		var x1, x2, y1, y2;
+		if (this.selection.first.x <= this.selection.second.x) {
+			x1 = this.selection.first.x;
+			x2 = this.selection.second.x;
+		}
+		else {
+			x1 = this.selection.second.x;
+			x2 = this.selection.first.x;
+		}
+
+		if (this.selection.first.y >= this.selection.second.y) {
+			y1 = this.selection.first.y;
+			y2 = this.selection.second.y;
+		}
+		else {
+			y1 = this.selection.second.y;
+			y2 = this.selection.first.y;
+		}
+		
+		x1 = this.xaxis.min + x1 / this.hozScale;
+		x2 = this.xaxis.min + x2 / this.hozScale;
+
+		y1 = this.yaxis.max - y1 / this.vertScale;
+		y2 = this.yaxis.max - y2 / this.vertScale;
+
+		this.domObj.fire("ProtoChart:selected", [ { x1: x1, y1: y1, x2: x2, y2: y2 } ]);
+	},
+	/**
+	 * Internal function
+	 */
+	onSelectionMouseUp: function(e) {
+		if (document.onselectstart !== undefined)
+			document.onselectstart = this.workarounds.onselectstart;
+		if (document.ondrag !== undefined)
+			document.ondrag = this.workarounds.ondrag;
+		
+		if (this.selectionInterval != null) {
+			clearInterval(this.selectionInterval);
+			this.selectionInterval = null;
+		}
+
+		this.setSelectionPos(this.selection.second, e);
+		this.clearSelection();
+		if (!this.selectionIsSane() || e.which != 1)
+			return false;
+		
+		this.drawSelection();
+		this.triggerSelectedEvent();
+		this.ignoreClick = true;
+
+		return false;
+	},
+	setSelectionPos: function(pos, e) {
+		var offset = $(this.overlay).cumulativeOffset();
+		if (this.options.selection.mode == "y") {
+			if (pos == this.selection.first)
+				pos.x = 0;
+			else
+				pos.x = this.chartWidth;
+		}
+		else {
+			pos.x = e.pageX - offset.left - this.chartOffset.left;
+			pos.x = Math.min(Math.max(0, pos.x), this.chartWidth);
+		}
+
+		if (this.options.selection.mode == "x") {
+			if (pos == this.selection.first)
+				pos.y = 0;
+			else
+				pos.y = this.chartHeight;
+		}
+		else {
+			pos.y = e.pageY - offset.top - this.chartOffset.top;
+			pos.y = Math.min(Math.max(0, pos.y), this.chartHeight);
+		}
+	},
+	updateSelectionOnMouseMove: function() {
+		if (this.lastMousePos.pageX == null)
+			return;
+		
+		this.setSelectionPos(this.selection.second, this.lastMousePos);
+		this.clearSelection();
+		if (this.selectionIsSane())
+			this.drawSelection();
+	},
+	clearSelection: function() {
+		if (this.prevSelection == null)
+			return;
+
+		var x = Math.min(this.prevSelection.first.x, this.prevSelection.second.x),
+			y = Math.min(this.prevSelection.first.y, this.prevSelection.second.y),
+			w = Math.abs(this.prevSelection.second.x - this.prevSelection.first.x),
+			h = Math.abs(this.prevSelection.second.y - this.prevSelection.first.y);
+		
+		this.overlayContext.clearRect(x + this.chartOffset.left - this.overlayContext.lineWidth,
+					   		y + this.chartOffset.top - this.overlayContext.lineWidth,
+					   		w + this.overlayContext.lineWidth*2,
+					   		h + this.overlayContext.lineWidth*2);
+		
+		this.prevSelection = null;
+	},
+	/**
+	 * Function: setSelection
+	 * 
+	 * Parameters:
+	 * Area - {Object} area represented as a range like: {x1: 3, y1: 3, x2: 4, y2: 8}
+	 * 
+	 * Description: 
+	 * Sets the current graph selection to the provided range. Calls <drawSelection> and 
+	 * <triggerSelectedEvent> functions internally.
+	 */
+	setSelection: function(area) {
+		this.clearSelection();
+		
+		if (this.options.selection.mode == "x") {
+			this.selection.first.y = 0;
+			this.selection.second.y = this.chartHeight;
+		}
+		else {
+			this.selection.first.y = (this.yaxis.max - area.y1) * this.vertScale;
+			this.selection.second.y = (this.yaxis.max - area.y2) * this.vertScale;
+		}
+		if (this.options.selection.mode == "y") {
+			this.selection.first.x = 0;
+			this.selection.second.x = this.chartWidth;
+		}
+		else {
+			this.selection.first.x = (area.x1 - this.xaxis.min) * this.hozScale;
+			this.selection.second.x = (area.x2 - this.xaxis.min) * this.hozScale;
+		}
+
+		this.drawSelection();
+		this.triggerSelectedEvent();
+	},
+	/**
+	 * Function: drawSelection
+	 * Description: Internal function called to draw the selection made on the graph. 
+	 */
+	drawSelection: function() {
+		if (this.prevSelection != null &&
+			this.selection.first.x == this.prevSelection.first.x &&
+			this.selection.first.y == this.prevSelection.first.y && 
+			this.selection.second.x == this.prevSelection.second.x &&
+			this.selection.second.y == this.prevSelection.second.y)
+		{
+			return;	
+		}
+		
+		this.overlayContext.strokeStyle = this.parseColor(this.options.selection.color).scale(null, null, null, 0.8).toString();
+		this.overlayContext.lineWidth = 1;
+		this.context.lineJoin = "round";
+		this.overlayContext.fillStyle = this.parseColor(this.options.selection.color).scale(null, null, null, 0.4).toString();
+
+		this.prevSelection = { first:  { x: this.selection.first.x,
+									y: this.selection.first.y },
+						  second: { x: this.selection.second.x,
+									y: this.selection.second.y } };
+
+		var x = Math.min(this.selection.first.x, this.selection.second.x),
+			y = Math.min(this.selection.first.y, this.selection.second.y),
+			w = Math.abs(this.selection.second.x - this.selection.first.x),
+			h = Math.abs(this.selection.second.y - this.selection.first.y);
+		
+		this.overlayContext.fillRect(x + this.chartOffset.left, y + this.chartOffset.top, w, h);
+		this.overlayContext.strokeRect(x + this.chartOffset.left, y + this.chartOffset.top, w, h);
+	},
+	/**
+	 * Internal function
+	 */
+	selectionIsSane: function() {
+		var minSize = 5;
+		return Math.abs(this.selection.second.x - this.selection.first.x) >= minSize &&
+			Math.abs(this.selection.second.y - this.selection.first.y) >= minSize;
+	},
+	/**
+	 * Internal function that formats the track. This is the format the text is shown when mouse
+	 * tracking is enabled.
+	 */
+	defaultTrackFormatter: function(val)
+	{
+		return '['+val.x+', '+val.y+']';
+	},
+	/**
+	 * Function: clearHit
+	 */
+	clearHit: function(){
+		if(this.prevHit){
+			this.overlayContext.clearRect(
+				this.translateHoz(this.prevHit.x) + this.chartOffset.left - this.options.mouse.radius*2,
+				this.translateVert(this.prevHit.y) + this.chartOffset.top - this.options.mouse.radius*2,
+				this.options.mouse.radius*3 + this.options.points.lineWidth*3, 
+				this.options.mouse.radius*3 + this.options.points.lineWidth*3
+			);
+			this.prevHit = null;
+		}		
+	},	
+	/**
+	 * Function: hit
+	 * 
+	 * Parameters: 
+	 * 	event - {Object} event object
+	 * 	mouse - {Object} mouse object that is used to keep track of mouse movement
+	 * 
+	 * Description:
+	 * 	If hit occurs this function will fire a ProtoChart:hit event.
+	 */
+	hit: function(event, mouse){	
+		/**
+		 * Nearest data element.
+		 */
+		var n = {
+			dist:Number.MAX_VALUE,
+			x:null,
+			y:null,
+			mouse:null
+		};
+		
+		
+		for(var i = 0, data, xsens, ysens; i < this.graphData.length; i++){
+			if(!this.graphData[i].mouse.track) continue;
+			data = this.graphData[i].data;				
+			xsens = (this.hozScale*this.graphData[i].mouse.sensibility);
+			ysens = (this.vertScale*this.graphData[i].mouse.sensibility);
+			for(var j = 0, xabs, yabs; j < data.length; j++){
+				xabs = this.hozScale*Math.abs(data[j][0] - mouse.x);
+				yabs = this.vertScale*Math.abs(data[j][1] - mouse.y);
+				
+				if(xabs < xsens && yabs < ysens && (xabs+yabs) < n.dist){
+					n.dist = (xabs+yabs);
+					n.x = data[j][0];
+					n.y = data[j][1];
+					n.mouse = this.graphData[i].mouse;
+				}
+			}
+		}
+		
+		if(n.mouse && n.mouse.track && !this.prevHit || (this.prevHit && n.x != this.prevHit.x && n.y != this.prevHit.y)){
+			var el = this.domObj.select('.'+this.options.mouse.clsName).first();
+			if(!el){
+				var pos = '', p = this.options.mouse.position, m = this.options.mouse.margin;					
+				if(p.charAt(0) == 'n') pos += 'top:' + (m + this.chartOffset.top) + 'px;';
+				else if(p.charAt(0) == 's') pos += 'bottom:' + (m + this.chartOffset.bottom) + 'px;';					
+				if(p.charAt(1) == 'e') pos += 'right:' + (m + this.chartOffset.right) + 'px;';
+				else if(p.charAt(1) == 'w') pos += 'left:' + (m + this.chartOffset.bottom) + 'px;';
+				
+				this.domObj.insert('<div class="'+this.options.mouse.clsName+'" style="display:none;position:absolute;'+pos+'"></div>');
+				return;
+			}
+			if(n.x !== null && n.y !== null){
+				el.setStyle({display:'block'});					
+				
+				this.clearHit();
+				if(n.mouse.lineColor != null){
+					this.overlayContext.save();
+					this.overlayContext.translate(this.chartOffset.left, this.chartOffset.top);
+					this.overlayContext.lineWidth = this.options.points.lineWidth;
+					this.overlayContext.strokeStyle = n.mouse.lineColor;
+					this.overlayContext.fillStyle = '#ffffff';
+					this.overlayContext.beginPath();
+					
+					
+					this.overlayContext.arc(this.translateHoz(n.x), this.translateVert(n.y), this.options.mouse.radius, 0, 2 * Math.PI, true);
+					this.overlayContext.fill();
+					this.overlayContext.stroke();
+					this.overlayContext.restore();
+				} 
+				this.prevHit = n;
+								
+				var decimals = n.mouse.trackDecimals;
+				if(decimals == null || decimals < 0) decimals = 0;
+				if(!this.options.mouse.fixedPosition)
+				{
+					el.setStyle({
+						left: (this.translateHoz(n.x) + this.options.mouse.radius + 10) + "px",
+						top: (this.translateVert(n.y) + this.options.mouse.radius + 10) + "px"
+					});
+				}
+				el.innerHTML = n.mouse.trackFormatter({x: n.x.toFixed(decimals), y: n.y.toFixed(decimals)});
+				this.domObj.fire( 'ProtoChart:hit', [n] )					
+			}else if(this.options.prevHit){
+				el.setStyle({display:'none'});
+				this.clearHit();
+			}
+		}
+	},	
+	/**
+	 * Internal function
+	 */
+	floorInBase: function(n, base) {
+        return base * Math.floor(n / base);
+    },	
+	/**
+	 * Function: extractColor
+	 * 
+	 * Parameters:
+	 * 		element - HTML element or ID of an HTML element
+	 * 
+	 * Returns: 
+	 * 		color in string format
+	 */
+	extractColor: function(element)
+	{
+		var color;
+		do
+		{
+			color = $(element).getStyle('background-color').toLowerCase();
+			if(color  != '' && color != 'transparent')
+			{
+				break;
+			}
+			element = element.up(0); //or else just get the parent ....
+		} while(element.nodeName.toLowerCase() != 'body');
+		
+		//safari fix
+		if(color == 'rgba(0, 0, 0, 0)') 
+			return 'transparent';
+		return color;
+	},
+	/**
+	 * Function: parseColor
+	 * 
+	 * Parameters: 
+	 * 		str - color string in different formats
+	 * 
+	 * Returns:
+	 * 		a Proto.Color Object - use toString() function to retreive the color in rgba/rgb format
+	 */
+	parseColor: function(str)
+	{
+		var result;
+	
+		/**
+		 * rgb(num,num,num)
+		 */
+		if((result = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str)))
+			return new Proto.Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]));
+	
+		/**
+		 * rgba(num,num,num,num)
+		 */
+		if((result = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))
+			return new Proto.Color(parseInt(result[1]), parseInt(result[2]), parseInt(result[3]), parseFloat(result[4]));
+			
+		/**
+		 * rgb(num%,num%,num%)
+		 */
+		if((result = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str)))
+			return new Proto.Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55);
+	
+		/**
+		 * rgba(num%,num%,num%,num)
+		 */
+		if((result = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str)))
+			return new Proto.Color(parseFloat(result[1])*2.55, parseFloat(result[2])*2.55, parseFloat(result[3])*2.55, parseFloat(result[4]));
+			
+		/**
+		 * #a0b1c2
+		 */
+		if((result = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str)))
+			return new Proto.Color(parseInt(result[1],16), parseInt(result[2],16), parseInt(result[3],16));
+	
+		/**
+		 * #fff
+		 */
+		if((result = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str)))
+			return new Proto.Color(parseInt(result[1]+result[1],16), parseInt(result[2]+result[2],16), parseInt(result[3]+result[3],16));
+
+		/**
+		 * Otherwise, check if user wants transparent .. or we just return a standard color;
+		 */
+		var name = str.strip().toLowerCase();
+		if(name == 'transparent'){
+			return new Proto.Color(255, 255, 255, 0);
+		}
+
+		return new Proto.Color(100,100,100, 1);
+					
+	}		
+});
+
+if(!Proto) var Proto = {};
+
+/**
+ * Class: Proto.Color
+ * 
+ * Helper class that manipulates colors using RGBA values. 
+ * 
+ */
+
+Proto.Color = Class.create({
+	initialize: function(r, g, b, a) {
+		this.rgba = ['r', 'g', 'b', 'a'];
+		var x = 4;
+		while(-1<--x) {
+			this[this.rgba[x]] = arguments[x] || ((x==3) ? 1.0 : 0);
+		}
+	},
+	toString: function()  {
+		if(this.a >= 1.0) {
+			return "rgb(" + [this.r, this.g, this.b].join(",") +")";
+		}
+		else {
+			return "rgba("+[this.r, this.g, this.b, this.a].join(",")+")";
+		}
+	},
+	scale: function(rf, gf, bf, af) {
+		x = 4;
+		while(-1<--x) {
+			if(arguments[x] != null) {
+				this[this.rgba[x]] *= arguments[x];
+			}
+		}
+		return this.normalize();
+	},
+	adjust: function(rd, gd, bd, ad) {
+        x = 4; //rgba.length
+        while (-1<--x) {
+            if (arguments[x] != null)
+                this[this.rgba[x]] += arguments[x];
+        }
+        return this.normalize();		
+	},
+	clone: function() {
+        return new Proto.Color(this.r, this.b, this.g, this.a);
+    },
+	limit: function(val,minVal,maxVal) {
+        return Math.max(Math.min(val, maxVal), minVal);
+    },
+    normalize: function() {
+        this.r = this.limit(parseInt(this.r), 0, 255);
+        this.g = this.limit(parseInt(this.g), 0, 255);
+        this.b = this.limit(parseInt(this.b), 0, 255);
+        this.a = this.limit(this.a, 0, 1);
+        return this;
+    }
+});
\ No newline at end of file
diff --git a/media/js/protochart/excanvas-compressed.js b/media/js/protochart/excanvas-compressed.js
new file mode 100644
index 0000000..9d71658
--- /dev/null
+++ b/media/js/protochart/excanvas-compressed.js
@@ -0,0 +1,19 @@
+if(!window.CanvasRenderingContext2D){(function(){var I=Math,i=I.round,L=I.sin,M=I.cos,m=10,A=m/2,Q={init:function(a){var b=a||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=this;b.attachEvent("onreadystatechange",function(){c.r(b)})}},r:function(a){if(a.readyState=="complete"){if(!a.namespaces["s"]){a.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var b=a.createStyleSheet();b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;he [...]
+var c=a.getElementsByTagName("canvas");for(var d=0;d<c.length;d++){if(!c[d].getContext){this.initElement(c[d])}}}},q:function(a){var b=a.outerHTML,c=a.ownerDocument.createElement(b);if(b.slice(-2)!="/>"){var d="/"+a.tagName,e;while((e=a.nextSibling)&&e.tagName!=d){e.removeNode()}if(e){e.removeNode()}}a.parentNode.replaceChild(c,a);return c},initElement:function(a){a=this.q(a);a.getContext=function(){if(this.l){return this.l}return this.l=new K(this)};a.attachEvent("onpropertychange",V);a [...]
+W);var b=a.attributes;if(b.width&&b.width.specified){a.style.width=b.width.nodeValue+"px"}else{a.width=a.clientWidth}if(b.height&&b.height.specified){a.style.height=b.height.nodeValue+"px"}else{a.height=a.clientHeight}return a}};function V(a){var b=a.srcElement;switch(a.propertyName){case "width":b.style.width=b.attributes.width.nodeValue+"px";b.getContext().clearRect();break;case "height":b.style.height=b.attributes.height.nodeValue+"px";b.getContext().clearRect();break}}function W(a){v [...]
+if(b.firstChild){b.firstChild.style.width=b.clientWidth+"px";b.firstChild.style.height=b.clientHeight+"px"}}Q.init();var R=[];for(var E=0;E<16;E++){for(var F=0;F<16;F++){R[E*16+F]=E.toString(16)+F.toString(16)}}function J(){return[[1,0,0],[0,1,0],[0,0,1]]}function G(a,b){var c=J();for(var d=0;d<3;d++){for(var e=0;e<3;e++){var g=0;for(var h=0;h<3;h++){g+=a[d][h]*b[h][e]}c[d][e]=g}}return c}function N(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.line [...]
+a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX;b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.d=a.d;b.e=a.e}function O(a){var b,c=1;a=String(a);if(a.substring(0,3)=="rgb"){var d=a.indexOf("(",3),e=a.indexOf(")",d+1),g=a.substring(d+1,e).split(",");b="#";for(var h=0;h<3;h++){b+=R[Number(g[h])]}if(g.length==4&&a.substr(3,1)=="a"){c=g[3]}}else{b=a}return[b,c]}function S(a){switch(a){case "butt":return"flat";case "round":retu [...]
+case "square":default:return"square"}}function K(a){this.a=J();this.m=[];this.k=[];this.c=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=m*1;this.globalAlpha=1;this.canvas=a;var b=a.ownerDocument.createElement("div");b.style.width=a.clientWidth+"px";b.style.height=a.clientHeight+"px";b.style.overflow="hidden";b.style.position="absolute";a.appendChild(b);this.j=b;this.d=1;this.e=1}var j=K.prototype;j.clearRect=fu [...]
+"";this.c=[]};j.beginPath=function(){this.c=[]};j.moveTo=function(a,b){this.c.push({type:"moveTo",x:a,y:b});this.f=a;this.g=b};j.lineTo=function(a,b){this.c.push({type:"lineTo",x:a,y:b});this.f=a;this.g=b};j.bezierCurveTo=function(a,b,c,d,e,g){this.c.push({type:"bezierCurveTo",cp1x:a,cp1y:b,cp2x:c,cp2y:d,x:e,y:g});this.f=e;this.g=g};j.quadraticCurveTo=function(a,b,c,d){var e=this.f+0.6666666666666666*(a-this.f),g=this.g+0.6666666666666666*(b-this.g),h=e+(c-this.f)/3,l=g+(d-this.g)/3;this [...]
+g,h,l,c,d)};j.arc=function(a,b,c,d,e,g){c*=m;var h=g?"at":"wa",l=a+M(d)*c-A,n=b+L(d)*c-A,o=a+M(e)*c-A,f=b+L(e)*c-A;if(l==o&&!g){l+=0.125}this.c.push({type:h,x:a,y:b,radius:c,xStart:l,yStart:n,xEnd:o,yEnd:f})};j.rect=function(a,b,c,d){this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath()};j.strokeRect=function(a,b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.stroke()};j.fillRe [...]
+b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.fill()};j.createLinearGradient=function(a,b,c,d){var e=new H("gradient");return e};j.createRadialGradient=function(a,b,c,d,e,g){var h=new H("gradientradial");h.n=c;h.o=g;h.i.x=a;h.i.y=b;return h};j.drawImage=function(a,b){var c,d,e,g,h,l,n,o,f=a.runtimeStyle.width,k=a.runtimeStyle.height;a.runtimeStyle.width="auto";a.runtimeStyle.height="auto";var q=a.width,r=a.height [...]
+f;a.runtimeStyle.height=k;if(arguments.length==3){c=arguments[1];d=arguments[2];h=(l=0);n=(e=q);o=(g=r)}else if(arguments.length==5){c=arguments[1];d=arguments[2];e=arguments[3];g=arguments[4];h=(l=0);n=q;o=r}else if(arguments.length==9){h=arguments[1];l=arguments[2];n=arguments[3];o=arguments[4];c=arguments[5];d=arguments[6];e=arguments[7];g=arguments[8]}else{throw"Invalid number of arguments";}var s=this.b(c,d),t=[],v=10,w=10;t.push(" <g_vml_:group",' coordsize="',m*v,",",m*w,'"',' coo [...]
+' style="width:',v,";height:",w,";position:absolute;");if(this.a[0][0]!=1||this.a[0][1]){var x=[];x.push("M11='",this.a[0][0],"',","M12='",this.a[1][0],"',","M21='",this.a[0][1],"',","M22='",this.a[1][1],"',","Dx='",i(s.x/m),"',","Dy='",i(s.y/m),"'");var p=s,y=this.b(c+e,d),z=this.b(c,d+g),B=this.b(c+e,d+g);p.x=Math.max(p.x,y.x,z.x,B.x);p.y=Math.max(p.y,y.y,z.y,B.y);t.push("padding:0 ",i(p.x/m),"px ",i(p.y/m),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",x.join(""),", sizingmet [...]
+i(s.y/m),"px;left:",i(s.x/m),"px;")}t.push(' ">','<g_vml_:image src="',a.src,'"',' style="width:',m*e,";"," height:",m*g,';"',' cropleft="',h/q,'"',' croptop="',l/r,'"',' cropright="',(q-h-n)/q,'"',' cropbottom="',(r-l-o)/r,'"'," />","</g_vml_:group>");this.j.insertAdjacentHTML("BeforeEnd",t.join(""))};j.stroke=function(a){var b=[],c=O(a?this.fillStyle:this.strokeStyle),d=c[0],e=c[1]*this.globalAlpha,g=10,h=10;b.push("<g_vml_:shape",' fillcolor="',d,'"',' filled="',Boolean(a),'"',' style [...]
+g,";height:",h,';"',' coordorigin="0 0" coordsize="',m*g," ",m*h,'"',' stroked="',!a,'"',' strokeweight="',this.lineWidth,'"',' strokecolor="',d,'"',' path="');var l={x:null,y:null},n={x:null,y:null};for(var o=0;o<this.c.length;o++){var f=this.c[o];if(f.type=="moveTo"){b.push(" m ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="lineTo"){b.push(" l ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="close"){b.push(" x ")}else if(f.type=="bezierCurveTo"){ [...]
+var k=this.b(f.x,f.y),q=this.b(f.cp1x,f.cp1y),r=this.b(f.cp2x,f.cp2y);b.push(i(q.x),",",i(q.y),",",i(r.x),",",i(r.y),",",i(k.x),",",i(k.y))}else if(f.type=="at"||f.type=="wa"){b.push(" ",f.type," ");var k=this.b(f.x,f.y),s=this.b(f.xStart,f.yStart),t=this.b(f.xEnd,f.yEnd);b.push(i(k.x-this.d*f.radius),",",i(k.y-this.e*f.radius)," ",i(k.x+this.d*f.radius),",",i(k.y+this.e*f.radius)," ",i(s.x),",",i(s.y)," ",i(t.x),",",i(t.y))}if(k){if(l.x==null||k.x<l.x){l.x=k.x}if(n.x==null||k.x>n.x){n.x [...]
+null||k.y<l.y){l.y=k.y}if(n.y==null||k.y>n.y){n.y=k.y}}}b.push(' ">');if(typeof this.fillStyle=="object"){var v={x:"50%",y:"50%"},w=n.x-l.x,x=n.y-l.y,p=w>x?w:x;v.x=i(this.fillStyle.i.x/w*100+50)+"%";v.y=i(this.fillStyle.i.y/x*100+50)+"%";var y=[];if(this.fillStyle.p=="gradientradial"){var z=this.fillStyle.n/p*100,B=this.fillStyle.o/p*100-z}else{var z=0,B=100}var C={offset:null,color:null},D={offset:null,color:null};this.fillStyle.h.sort(function(T,U){return T.offset-U.offset});for(var o= [...]
+this.fillStyle.h[o];y.push(u.offset*B+z,"% ",u.color,",");if(u.offset>C.offset||C.offset==null){C.offset=u.offset;C.color=u.color}if(u.offset<D.offset||D.offset==null){D.offset=u.offset;D.color=u.color}}y.pop();b.push("<g_vml_:fill",' color="',D.color,'"',' color2="',C.color,'"',' type="',this.fillStyle.p,'"',' focusposition="',v.x,", ",v.y,'"',' colors="',y.join(""),'"',' opacity="',e,'" />')}else if(a){b.push('<g_vml_:fill color="',d,'" opacity="',e,'" />')}else{b.push("<g_vml_:stroke" [...]
+e,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',S(this.lineCap),'"',' weight="',this.lineWidth,'px"',' color="',d,'" />')}b.push("</g_vml_:shape>");this.j.insertAdjacentHTML("beforeEnd",b.join(""));this.c=[]};j.fill=function(){this.stroke(true)};j.closePath=function(){this.c.push({type:"close"})};j.b=function(a,b){return{x:m*(a*this.a[0][0]+b*this.a[1][0]+this.a[2][0])-A,y:m*(a*this.a[0][1]+b*this.a[1][1]+this.a[2][1])-A}};j.save=function(){var a={} [...]
+this.k.push(a);this.m.push(this.a);this.a=G(J(),this.a)};j.restore=function(){N(this.k.pop(),this);this.a=this.m.pop()};j.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];this.a=G(c,this.a)};j.rotate=function(a){var b=M(a),c=L(a),d=[[b,c,0],[-c,b,0],[0,0,1]];this.a=G(d,this.a)};j.scale=function(a,b){this.d*=a;this.e*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];this.a=G(c,this.a)};j.clip=function(){};j.arcTo=function(){};j.createPattern=function(){return new P};function H(a){this.p=a;this.n= [...]
+0;this.h=[];this.i={x:0,y:0}}H.prototype.addColorStop=function(a,b){b=O(b);this.h.push({offset:1-a,color:b})};function P(){}G_vmlCanvasManager=Q;CanvasRenderingContext2D=K;CanvasGradient=H;CanvasPattern=P})()};
diff --git a/media/js/protochart/excanvas.js b/media/js/protochart/excanvas.js
new file mode 100644
index 0000000..7914cb9
--- /dev/null
+++ b/media/js/protochart/excanvas.js
@@ -0,0 +1,785 @@
+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns are not implemented.
+// * Radial gradient are not implemented. The VML version of these look very
+//   different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+//   width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+//   Quirks mode will draw the canvas using border-box. Either change your
+//   doctype to HTML5
+//   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+//   or use Box Sizing Behavior from WebFX
+//   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Optimize. There is always room for speed improvements.
+
+// only add this code if we do not already have a canvas implementation
+if (!window.CanvasRenderingContext2D) {
+
+(function () {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+
+  // this is used for sub pixel precision
+  var Z = 10;
+  var Z2 = Z / 2;
+
+  var G_vmlCanvasManager_ = {
+    init: function (opt_doc) {
+      var doc = opt_doc || document;
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var self = this;
+        doc.attachEvent("onreadystatechange", function () {
+          self.init_(doc);
+        });
+      }
+    },
+
+    init_: function (doc) {
+      if (doc.readyState == "complete") {
+        // create xmlns
+        if (!doc.namespaces["g_vml_"]) {
+          doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
+        }
+
+        // setup default css
+        var ss = doc.createStyleSheet();
+        ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
+            // default size is 300x150 in Gecko and Opera
+            "text-align:left;width:300px;height:150px}" +
+            "g_vml_\\:*{behavior:url(#default#VML)}";
+
+        // find all canvas elements
+        var els = doc.getElementsByTagName("canvas");
+        for (var i = 0; i < els.length; i++) {
+          if (!els[i].getContext) {
+            this.initElement(els[i]);
+          }
+        }
+      }
+    },
+
+    fixElement_: function (el) {
+      // in IE before version 5.5 we would need to add HTML: to the tag name
+      // but we do not care about IE before version 6
+      var outerHTML = el.outerHTML;
+
+      var newEl = el.ownerDocument.createElement(outerHTML);
+      // if the tag is still open IE has created the children as siblings and
+      // it has also created a tag with the name "/FOO"
+      if (outerHTML.slice(-2) != "/>") {
+        var tagName = "/" + el.tagName;
+        var ns;
+        // remove content
+        while ((ns = el.nextSibling) && ns.tagName != tagName) {
+          ns.removeNode();
+        }
+        // remove the incorrect closing tag
+        if (ns) {
+          ns.removeNode();
+        }
+      }
+      el.parentNode.replaceChild(newEl, el);
+      return newEl;
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function (el) {
+      el = this.fixElement_(el);
+      el.getContext = function () {
+        if (this.context_) {
+          return this.context_;
+        }
+        return this.context_ = new CanvasRenderingContext2D_(this);
+      };
+
+      // do not use inline function because that will leak memory
+      el.attachEvent('onpropertychange', onPropertyChange);
+      el.attachEvent('onresize', onResize);
+
+      var attrs = el.attributes;
+      if (attrs.width && attrs.width.specified) {
+        // TODO: use runtimeStyle and coordsize
+        // el.getContext().setWidth_(attrs.width.nodeValue);
+        el.style.width = attrs.width.nodeValue + "px";
+      } else {
+        el.width = el.clientWidth;
+      }
+      if (attrs.height && attrs.height.specified) {
+        // TODO: use runtimeStyle and coordsize
+        // el.getContext().setHeight_(attrs.height.nodeValue);
+        el.style.height = attrs.height.nodeValue + "px";
+      } else {
+        el.height = el.clientHeight;
+      }
+      //el.getContext().setCoordsize_()
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    var el = e.srcElement;
+
+    switch (e.propertyName) {
+      case 'width':
+        el.style.width = el.attributes.width.nodeValue + "px";
+        el.getContext().clearRect();
+        break;
+      case 'height':
+        el.style.height = el.attributes.height.nodeValue + "px";
+        el.getContext().clearRect();
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var dec2hex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+    o2.arcScaleX_    = o1.arcScaleX_;
+    o2.arcScaleY_    = o1.arcScaleY_;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.substring(0, 3) == "rgb") {
+      var start = styleString.indexOf("(", 3);
+      var end = styleString.indexOf(")", start + 1);
+      var guts = styleString.substring(start + 1, end).split(",");
+
+      str = "#";
+      for (var i = 0; i < 3; i++) {
+        str += dec2hex[Number(guts[i])];
+      }
+
+      if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
+        alpha = guts[3];
+      }
+    } else {
+      str = styleString;
+    }
+
+    return [str, alpha];
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case "butt":
+        return "flat";
+      case "round":
+        return "round";
+      case "square":
+      default:
+        return "square";
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+   function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = "#000";
+    this.fillStyle = "#000";
+
+    this.lineWidth = 1;
+    this.lineJoin = "miter";
+    this.lineCap = "butt";
+    this.miterLimit = Z * 1;
+    this.globalAlpha = 1;
+    this.canvas = surfaceElement;
+
+    var el = surfaceElement.ownerDocument.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+  }
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    this.element_.innerHTML = "";
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    this.currentPath_.push({type: "moveTo", x: aX, y: aY});
+    this.currentX_ = aX;
+    this.currentY_ = aY;
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    this.currentPath_.push({type: "lineTo", x: aX, y: aY});
+    this.currentX_ = aX;
+    this.currentY_ = aY;
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    this.currentPath_.push({type: "bezierCurveTo",
+                           cp1x: aCP1x,
+                           cp1y: aCP1y,
+                           cp2x: aCP2x,
+                           cp2y: aCP2y,
+                           x: aX,
+                           y: aY});
+    this.currentX_ = aX;
+    this.currentY_ = aY;
+  };
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // the following is lifted almost directly from
+    // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+    var cp1x = this.currentX_ + 2.0 / 3.0 * (aCPx - this.currentX_);
+    var cp1y = this.currentY_ + 2.0 / 3.0 * (aCPy - this.currentY_);
+    var cp2x = cp1x + (aX - this.currentX_) / 3.0;
+    var cp2y = cp1y + (aY - this.currentY_) / 3.0;
+    this.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, aX, aY);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= Z;
+    var arcType = aClockwise ? "at" : "wa";
+
+    var xStart = aX + (mc(aStartAngle) * aRadius) - Z2;
+    var yStart = aY + (ms(aStartAngle) * aRadius) - Z2;
+
+    var xEnd = aX + (mc(aEndAngle) * aRadius) - Z2;
+    var yEnd = aY + (ms(aEndAngle) * aRadius) - Z2;
+
+    // IE won't render arches drawn counter clockwise if xStart == xEnd.
+    if (xStart == xEnd && !aClockwise) {
+      xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+                       // that can be represented in binary
+    }
+
+    this.currentPath_.push({type: arcType,
+                           x: aX,
+                           y: aY,
+                           radius: aRadius,
+                           xStart: xStart,
+                           yStart: yStart,
+                           xEnd: xEnd,
+                           yEnd: yEnd});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    // Will destroy any existing path (same as FF behaviour)
+    this.beginPath();
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    // Will destroy any existing path (same as FF behaviour)
+    this.beginPath();
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_("gradient");
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0,
+                                                   aR0, aX1,
+                                                   aY1, aR1) {
+    var gradient = new CanvasGradient_("gradientradial");
+    gradient.radius1_ = aR0;
+    gradient.radius2_ = aR1;
+    gradient.focus_.x = aX0;
+    gradient.focus_.y = aY0;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function (image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+
+    // to find the original width we overide the width and height
+    var oldRuntimeWidth = image.runtimeStyle.width;
+    var oldRuntimeHeight = image.runtimeStyle.height;
+    image.runtimeStyle.width = 'auto';
+    image.runtimeStyle.height = 'auto';
+
+    // get the original size
+    var w = image.width;
+    var h = image.height;
+
+    // and remove overides
+    image.runtimeStyle.width = oldRuntimeWidth;
+    image.runtimeStyle.height = oldRuntimeHeight;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw "Invalid number of arguments";
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = sw / 2;
+    var h2 = sh / 2;
+
+    var vmlStr = [];
+
+    var W = 10;
+    var H = 10;
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="', Z * W, ',', Z * H, '"',
+                ' coordorigin="0,0"' ,
+                ' style="width:', W, ';height:', H, ';position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push("M11='", this.m_[0][0], "',",
+                  "M12='", this.m_[1][0], "',",
+                  "M21='", this.m_[0][1], "',",
+                  "M22='", this.m_[1][1], "',",
+                  "Dx='", mr(d.x / Z), "',",
+                  "Dy='", mr(d.y / Z), "'");
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx + dw, dy);
+      var c3 = this.getCoords_(dx, dy + dh);
+      var c4 = this.getCoords_(dx + dw, dy + dh);
+
+      max.x = Math.max(max.x, c2.x, c3.x, c4.x);
+      max.y = Math.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push("padding:0 ", mr(max.x / Z), "px ", mr(max.y / Z),
+                  "px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",
+                  filter.join(""), ", sizingmethod='clip');");
+    } else {
+      vmlStr.push("top:", mr(d.y / Z), "px;left:", mr(d.x / Z), "px;");
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', Z * dw, ';',
+                ' height:', Z * dh, ';"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML("BeforeEnd",
+                                    vmlStr.join(""));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var lineStr = [];
+    var lineOpen = false;
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+    var color = a[0];
+    var opacity = a[1] * this.globalAlpha;
+
+    var W = 10;
+    var H = 10;
+
+    lineStr.push('<g_vml_:shape',
+                 ' fillcolor="', color, '"',
+                 ' filled="', Boolean(aFill), '"',
+                 ' style="position:absolute;width:', W, ';height:', H, ';"',
+                 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
+                 ' stroked="', !aFill, '"',
+                 ' strokeweight="', this.lineWidth, '"',
+                 ' strokecolor="', color, '"',
+                 ' path="');
+
+    var newSeq = false;
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var i = 0; i < this.currentPath_.length; i++) {
+      var p = this.currentPath_[i];
+
+      if (p.type == "moveTo") {
+        lineStr.push(" m ");
+        var c = this.getCoords_(p.x, p.y);
+        lineStr.push(mr(c.x), ",", mr(c.y));
+      } else if (p.type == "lineTo") {
+        lineStr.push(" l ");
+        var c = this.getCoords_(p.x, p.y);
+        lineStr.push(mr(c.x), ",", mr(c.y));
+      } else if (p.type == "close") {
+        lineStr.push(" x ");
+      } else if (p.type == "bezierCurveTo") {
+        lineStr.push(" c ");
+        var c = this.getCoords_(p.x, p.y);
+        var c1 = this.getCoords_(p.cp1x, p.cp1y);
+        var c2 = this.getCoords_(p.cp2x, p.cp2y);
+        lineStr.push(mr(c1.x), ",", mr(c1.y), ",",
+                     mr(c2.x), ",", mr(c2.y), ",",
+                     mr(c.x), ",", mr(c.y));
+      } else if (p.type == "at" || p.type == "wa") {
+        lineStr.push(" ", p.type, " ");
+        var c  = this.getCoords_(p.x, p.y);
+        var cStart = this.getCoords_(p.xStart, p.yStart);
+        var cEnd = this.getCoords_(p.xEnd, p.yEnd);
+
+        lineStr.push(mr(c.x - this.arcScaleX_ * p.radius), ",",
+                     mr(c.y - this.arcScaleY_ * p.radius), " ",
+                     mr(c.x + this.arcScaleX_ * p.radius), ",",
+                     mr(c.y + this.arcScaleY_ * p.radius), " ",
+                     mr(cStart.x), ",", mr(cStart.y), " ",
+                     mr(cEnd.x), ",", mr(cEnd.y));
+      }
+
+
+      // TODO: Following is broken for curves due to
+      //       move to proper paths.
+
+      // Figure out dimensions so we can do gradient fills
+      // properly
+      if(c) {
+        if (min.x == null || c.x < min.x) {
+          min.x = c.x;
+        }
+        if (max.x == null || c.x > max.x) {
+          max.x = c.x;
+        }
+        if (min.y == null || c.y < min.y) {
+          min.y = c.y;
+        }
+        if (max.y == null || c.y > max.y) {
+          max.y = c.y;
+        }
+      }
+    }
+    lineStr.push(' ">');
+
+    if (typeof this.fillStyle == "object") {
+      var focus = {x: "50%", y: "50%"};
+      var width = (max.x - min.x);
+      var height = (max.y - min.y);
+      var dimension = (width > height) ? width : height;
+
+      focus.x = mr((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
+      focus.y = mr((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
+
+      var colors = [];
+
+      // inside radius (%)
+      if (this.fillStyle.type_ == "gradientradial") {
+        var inside = (this.fillStyle.radius1_ / dimension * 100);
+
+        // percentage that outside radius exceeds inside radius
+        var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
+      } else {
+        var inside = 0;
+        var expansion = 100;
+      }
+
+      var insidecolor = {offset: null, color: null};
+      var outsidecolor = {offset: null, color: null};
+
+      // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
+      // won't interpret it correctly
+      this.fillStyle.colors_.sort(function (cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      for (var i = 0; i < this.fillStyle.colors_.length; i++) {
+        var fs = this.fillStyle.colors_[i];
+
+        colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
+
+        if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
+          insidecolor.offset = fs.offset;
+          insidecolor.color = fs.color;
+        }
+
+        if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
+          outsidecolor.offset = fs.offset;
+          outsidecolor.color = fs.color;
+        }
+      }
+      colors.pop();
+
+      lineStr.push('<g_vml_:fill',
+                   ' color="', outsidecolor.color, '"',
+                   ' color2="', insidecolor.color, '"',
+                   ' type="', this.fillStyle.type_, '"',
+                   ' focusposition="', focus.x, ', ', focus.y, '"',
+                   ' colors="', colors.join(""), '"',
+                   ' opacity="', opacity, '" />');
+    } else if (aFill) {
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity, '" />');
+    } else {
+      lineStr.push(
+        '<g_vml_:stroke',
+        ' opacity="', opacity,'"',
+        ' joinstyle="', this.lineJoin, '"',
+        ' miterlimit="', this.miterLimit, '"',
+        ' endcap="', processLineCap(this.lineCap) ,'"',
+        ' weight="', this.lineWidth, 'px"',
+        ' color="', color,'" />'
+      );
+    }
+
+    lineStr.push("</g_vml_:shape>");
+
+    this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
+
+    //this.currentPath_ = [];
+  };
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  };
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: "close"});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    return {
+      x: Z * (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]) - Z2,
+      y: Z * (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1]) - Z2
+    }
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    copyState(this.aStack_.pop(), this);
+    this.m_ = this.mStack_.pop();
+  };
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function() {
+    return new CanvasPattern_;
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.radius1_ = 0;
+    this.radius2_ = 0;
+    this.colors_ = [];
+    this.focus_ = {x: 0, y: 0};
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: 1-aOffset, color: aColor});
+  };
+
+  function CanvasPattern_() {}
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if
diff --git a/media/js/protochart/prototype.js b/media/js/protochart/prototype.js
new file mode 100644
index 0000000..bd67dc9
--- /dev/null
+++ b/media/js/protochart/prototype.js
@@ -0,0 +1,4221 @@
+/*  Prototype JavaScript framework, version 1.6.0.2
+ *  (c) 2005-2008 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.2',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
+  }
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+  for (var property in source)
+    destination[property] = source[property];
+  return destination;
+};
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return object && object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object != null && typeof object == "object" &&
+      'splice' in object && 'join' in object;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
+  }
+});
+
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+    return names.length == 1 && !names[0] ? [] : names;
+  },
+
+  bind: function() {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
+
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
+  }
+});
+
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = new Element('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+  each: function(iterator, context) {
+    var index = 0;
+    iterator = iterator.bind(context);
+    try {
+      this._each(function(value) {
+        iterator(value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  },
+
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
+    return this.map(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+};
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
+});
+function $A(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  $A = function(iterable) {
+    if (!iterable) return [];
+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length || 0, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
+  };
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(Object.isArray(value) ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&');
+        }
+        return toQueryPair(key, values);
+      }).join('&');
+    },
+
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
+  },
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).select("*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      element.select(expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (element.sourceIndex && !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+      }
+      if (nextAncestor && nextAncestor.sourceIndex)
+       return (e > a && e < nextAncestor.sourceIndex);
+    }
+
+    while (element = element.parentNode)
+      if (element == originalAncestor) return true;
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value) {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  // IE doesn't report offsets correctly for static elements, so we change them
+  // to "relative" to get the values, then change them back.
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+    function(proceed, element) {
+      element = $(element);
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    }
+  );
+
+  $w('positionedOffset viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+        // Trigger hasLayout on the offset parent so that IE6 reports
+        // accurate offsetTop and offsetLeft values for position: fixed.
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
+      }
+    }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if ('outerHTML' in document.createElement('div')) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+    });
+    return dimensions;
+  },
+
+  getWidth: function() {
+    return this.getDimensions().width;
+  },
+
+  getHeight: function() {
+    return this.getDimensions().height;
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
+  },
+
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    if (this.shouldUseXPath())
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = _true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._countedByPrototype = Prototype.emptyFunction;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._countedByPrototype) {
+          n._countedByPrototype = Prototype.emptyFunction;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._countedByPrototype) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._countedByPrototype) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = $$(expression), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._countedByPrototype) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    expressions = Selector.split(expressions.join(','));
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    // IE returns comment nodes on getElementsByTagName("*").
+    // Filter them out.
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    },
+
+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node.removeAttribute('_countedByPrototype');
+      return nodes;
+    }
+  });
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
+
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
+      event.preventDefault();
+      event.stopPropagation();
+      event.stopped = true;
+    }
+  };
+})();
+
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._prototypeEventID) return element._prototypeEventID[0];
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._prototypeEventID = [++arguments.callee.id];
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event);
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
+    }
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
+    }
+
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  // Deprecation layer -- use newer Element methods now (1.5.2).
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods();
\ No newline at end of file
diff --git a/media/js/raphael-ushahidi-impact.js b/media/js/raphael-ushahidi-impact.js
new file mode 100644
index 0000000..c3dee06
--- /dev/null
+++ b/media/js/raphael-ushahidi-impact.js
@@ -0,0 +1,137 @@
+var process = function (impact_json) {
+
+    var x = 0; // left margin
+    var width = (impact_json.buckets.length * 100);
+    var height = "99%";
+    var r = Raphael("impact_chart", width, height);
+    var labels = {};
+    var textattr = {"font": '9px "Arial"', stroke: "none", fill: "#fff"};
+    var pathes = {};
+    var nmhldr = $("#impact_info")[0];
+    var nmhldr2 = $("#impact_info2")[0];
+    var lgnd = $("#impact_legend")[0];
+    var usrnm = $("#impact_message")[0];
+    var lgnd2 = $("#impact_legend2")[0];
+    var usrnm2 = $("#impact_message2")[0];
+    var plchldr = $("#impact_placeholder")[0];
+    
+    function finishes() {
+        for (var i in impact_json.categories) {
+            var start, end;
+            for (var j = impact_json.buckets.length - 1; j >= 0; j--) {
+                var isin = false;
+                for (var k = 0, kk = impact_json.buckets[j].i.length; k < kk; k++) {
+                    isin = isin || (impact_json.buckets[j].i[k][0] == i);
+                }
+                if (isin) {
+                    end = j;
+                    break;
+                }
+            }
+            for (var j = 0, jj = impact_json.buckets.length; j < jj; j++) {
+                var isin = false;
+                for (var k = 0, kk = impact_json.buckets[j].i.length; k < kk; k++) {
+                    isin = isin || (impact_json.buckets[j].i[k][0] == i);
+                };
+                if (isin) {
+                    start = j;
+                    break;
+                }
+            }
+            for (var j = start, jj = end; j < jj; j++) {
+                var isin = false;
+                for (var k = 0, kk = impact_json.buckets[j].i.length; k < kk; k++) {
+                    isin = isin || (impact_json.buckets[j].i[k][0] == i);
+                }
+                if (!isin) {
+                    impact_json.buckets[j].i.push([i, 0]);
+                }
+            }
+        }
+    }
+    function block() {
+        var p, h;
+        finishes();
+        for (var j = 0, jj = impact_json.buckets.length; j < jj; j++) {
+            var category_count = impact_json.buckets[j].i;
+            var category = 0;
+            var count = 0;
+            h = 0;
+            for (var i = 0, ii = category_count.length; i < ii; i++) {
+            	category = category_count[i][0];
+            	count = category_count[i][1];
+            	// This makes things nice and fat
+            	modified_count = count * 2;
+                p = pathes[category];
+                if (!p) {
+                    p = pathes[category] = {f:[], b:[]};
+                }
+                p.f.push([x, h, count]);
+                
+                if(impact_json.use_log == 1) {
+                	p.b.unshift([x, h += Math.max(Math.round(Math.log(modified_count) * 5), 1)]);
+                }else{
+                	p.b.unshift([x, h += Math.max(Math.round(modified_count * 3), 1)]);
+                }
+                
+                h += 2;
+            }
+            var dt = new Date(impact_json.buckets[j].d * 1000);
+            var dtext = dt.getDate() + " " + ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"][dt.getMonth()] + " " + dt.getFullYear();
+            r.text(x + 25, h + 10, dtext).attr({"font": '9px "Arial"', stroke: "none", fill: "#aaa"});
+            x += 100;
+        }
+        var c = 0;
+        for (var i in pathes) {
+            labels[i] = r.set();
+            var clr = impact_json.categories[i].fill;
+            pathes[i].p = r.path().attr({fill: clr, stroke: clr});
+            var path = "M".concat(pathes[i].f[0][0], ",", pathes[i].f[0][1], "L", pathes[i].f[0][0] + 50, ",", pathes[i].f[0][1]);
+            var th = Math.round(pathes[i].f[0][1] + (pathes[i].b[pathes[i].b.length - 1][1] - pathes[i].f[0][1]) / 2 + 3);
+            labels[i].push(r.text(pathes[i].f[0][0] + 25, th, pathes[i].f[0][2]).attr(textattr));
+            var X = pathes[i].f[0][0] + 50,
+                Y = pathes[i].f[0][1];
+            for (var j = 1, jj = pathes[i].f.length; j < jj; j++) {
+                path = path.concat("C", X + 20, ",", Y, ",");
+                X = pathes[i].f[j][0];
+                Y = pathes[i].f[j][1];
+                path = path.concat(X - 20, ",", Y, ",", X, ",", Y, "L", X += 50, ",", Y);
+                th = Math.round(Y + (pathes[i].b[pathes[i].b.length - 1 - j][1] - Y) / 2 + 3);
+                if (th - 9 > Y) {
+                    labels[i].push(r.text(X - 25, th, pathes[i].f[j][2]).attr(textattr));
+                }
+            }
+            path = path.concat("L", pathes[i].b[0][0] + 50, ",", pathes[i].b[0][1], ",", pathes[i].b[0][0], ",", pathes[i].b[0][1]);
+            for (var j = 1, jj = pathes[i].b.length; j < jj; j++) {
+                path = path.concat("C", pathes[i].b[j][0] + 70, ",", pathes[i].b[j - 1][1], ",", pathes[i].b[j][0] + 70, ",", pathes[i].b[j][1], ",", pathes[i].b[j][0] + 50, ",", pathes[i].b[j][1], "L", pathes[i].b[j][0], ",", pathes[i].b[j][1]);
+            }
+            pathes[i].p.attr({path: path + "z"});
+            labels[i].hide();
+            var current = null;
+            (function (i) {
+                pathes[i].p.mouseover(function () {
+                    if (current != null) {
+                        labels[current].hide();
+                    }
+                    current = i;
+                    labels[i].show();
+                    pathes[i].p.toFront();
+                    labels[i].toFront();
+                    usrnm2.innerHTML = impact_json.categories[i].name + " <em>(" + impact_json.categories[i].reports + " Reports)</em>";
+                    lgnd2.style.backgroundColor = pathes[i].p.attr("fill");
+                    nmhldr2.className = "";
+                    plchldr.className = "impact_hidden";
+                });
+            })(i);
+        }
+    }
+    if (impact_json.error) {
+        alert("JSON Error. Try again.");
+    } else {
+        block();
+    }
+};
+
+$(function () {
+    process(impact_json);
+});
\ No newline at end of file
diff --git a/media/js/raphael.js b/media/js/raphael.js
new file mode 100644
index 0000000..b81c9f7
--- /dev/null
+++ b/media/js/raphael.js
@@ -0,0 +1,7 @@
+/*
+ * Raphael 1.2.1 - JavaScript Vector Library
+ *
+ * Copyright (c) 2008 - 2009 Dmitry Baranovskiy (http://raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+window.Raphael=(function(){var a=/[, ]+/,z=document,ab=window,h={was:"Raphael" in ab,is:ab.Raphael},Y=function(){if(Y.is(arguments[0],"array")){var e=arguments[0],E=p[ax](Y,e.splice(0,3+Y.is(e[0],W))),aA=E.set();for(var S=0,aB=e[j];S<aB;S++){var R=e[S]||{};({circle:1,rect:1,path:1,ellipse:1,text:1,image:1}[F](R.type))&&aA[c](E[R.type]().attr(R));}return aA;}return p[ax](Y,arguments);},ar={},u=["click","dblclick","mousedown","mousemove","mouseout","mouseover","mouseup"],aa="",X=" ",F="has [...]
\ No newline at end of file
diff --git a/media/js/selectToUISlider.jQuery.js b/media/js/selectToUISlider.jQuery.js
new file mode 100644
index 0000000..bcc5dd2
--- /dev/null
+++ b/media/js/selectToUISlider.jQuery.js
@@ -0,0 +1,241 @@
+/*
+ * --------------------------------------------------------------------
+ * jQuery-Plugin - selectToUISlider - creates a UI slider component from a select element(s)
+ * by Scott Jehl, scott at filamentgroup.com
+ * http://www.filamentgroup.com
+ * reference article: http://www.filamentgroup.com/lab/update_jquery_ui_16_slider_from_a_select_element/
+ * demo page: http://www.filamentgroup.com/examples/slider_v2/index.html
+ * 
+ * Copyright (c) 2008 Filament Group, Inc
+ * Dual licensed under the MIT (filamentgroup.com/examples/mit-license.txt) and GPL (filamentgroup.com/examples/gpl-license.txt) licenses.
+ *
+ * Usage Notes: please refer to our article above for documentation
+ *  
+ * --------------------------------------------------------------------
+ */
+
+
+jQuery.fn.selectToUISlider = function(settings){
+	var selects = jQuery(this);
+	
+	//accessible slider options
+	var options = jQuery.extend({
+		labels: 3, //number of visible labels
+		tooltip: true, //show tooltips, boolean
+		tooltipSrc: 'text',//accepts 'value' as well
+		labelSrc: 'value',//accepts 'value' as well	,
+		sliderOptions: null
+	}, settings);
+
+
+	//handle ID attrs - selects each need IDs for handles to find them
+	var handleIds = (function(){
+		var tempArr = [];
+		selects.each(function(){
+			tempArr.push('handle_'+jQuery(this).attr('id'));
+		});
+		return tempArr;
+	})();
+	
+	//array of all option elements in select element (ignores optgroups)
+	var selectOptions = (function(){
+		var opts = [];
+		selects.eq(0).find('option').each(function(){
+			opts.push({
+				value: jQuery(this).attr('value'),
+				text: jQuery(this).text()
+			});
+		});
+		return opts;
+	})();
+	
+	//array of opt groups if present
+	var groups = (function(){
+		if(selects.eq(0).find('optgroup').size()>0){
+			var groupedData = [];
+			selects.eq(0).find('optgroup').each(function(i){
+				groupedData[i] = {};
+				groupedData[i].label = jQuery(this).attr('label');
+				groupedData[i].options = [];
+				jQuery(this).find('option').each(function(){
+					groupedData[i].options.push({text: jQuery(this).text(), value: jQuery(this).attr('value')});
+				});
+			});
+			return groupedData;
+		}
+		else return null;
+	})();	
+	
+	//check if obj is array
+	function isArray(obj) {
+		return obj.constructor == Array;
+	}
+	//return tooltip text from option index
+	function ttText(optIndex){
+		return (options.tooltipSrc == 'text') ? selectOptions[optIndex].text : selectOptions[optIndex].value;
+	}
+	
+	//plugin-generated slider options (can be overridden)
+	var sliderOptions = {
+		step: 1,
+		min: 0,
+		orientation: 'horizontal',
+		max: selectOptions.length-1,
+		range: selects.length > 1,//multiple select elements = true
+		slide: function(e, ui) {//slide function
+				var thisHandle = jQuery(ui.handle);
+				//handle feedback 
+				var textval = ttText(ui.value);
+				thisHandle
+					.attr('aria-valuetext', textval)
+					.attr('aria-valuenow', ui.value)
+					.find('.ui-slider-tooltip .ttContent')
+						.text( textval );
+
+				//control original select menu
+				var currSelect = jQuery('#' + thisHandle.attr('id').split('handle_')[1]);
+				currSelect.find('option').eq(ui.value).attr('selected', 'selected');
+		},
+		values: (function(){
+			var values = [];
+			selects.each(function(){
+				values.push( jQuery(this).get(0).selectedIndex );
+			});
+			return values;
+		})()
+	};
+	
+	//slider options from settings
+	options.sliderOptions = (settings) ? jQuery.extend(sliderOptions, settings.sliderOptions) : sliderOptions;
+		
+	//select element change event	
+	selects.bind('change keyup click', function(){
+		var thisIndex = jQuery(this).get(0).selectedIndex;
+		var thisHandle = jQuery('#handle_'+ jQuery(this).attr('id'));
+		var handleIndex = thisHandle.data('handleNum');
+		thisHandle.parents('.ui-slider:eq(0)').slider("values", handleIndex, thisIndex);
+	});
+	
+
+	//create slider component div
+	var sliderComponent = jQuery('<div></div>');
+
+	//CREATE HANDLES
+	selects.each(function(i){
+		var hidett = '';
+		
+		//associate label for ARIA
+		var thisLabel = jQuery('label[for=' + jQuery(this).attr('id') +']');
+		//labelled by aria doesn't seem to work on slider handle. Using title attr as backup
+		var labelText = (thisLabel.size()>0) ? 'Slider control for '+ thisLabel.text()+'' : '';
+		var thisLabelId = thisLabel.attr('id') || thisLabel.attr('id', 'label_'+handleIds[i]).attr('id');
+		
+		
+		if( options.tooltip == false ){hidett = ' style="display: none;"';}
+		jQuery('<a '+
+				'href="#" tabindex="0" '+
+				'id="'+handleIds[i]+'" '+
+				'class="ui-slider-handle" '+
+				'role="slider" '+
+				'aria-labelledby="'+thisLabelId+'" '+
+				'aria-valuemin="'+options.sliderOptions.min+'" '+
+				'aria-valuemax="'+options.sliderOptions.max+'" '+
+				'aria-valuenow="'+options.sliderOptions.values[i]+'" '+
+				'aria-valuetext="'+ttText(options.sliderOptions.values[i])+'" '+
+			'><span class="screenReaderContext">'+labelText+'</span>'+
+			'<span class="ui-slider-tooltip ui-widget-content ui-corner-all"'+ hidett +'><span class="ttContent"></span>'+
+				'<span class="ui-tooltip-pointer-down ui-widget-content"><span class="ui-tooltip-pointer-down-inner"></span></span>'+
+			'</span></a>')
+			.data('handleNum',i)
+			.appendTo(sliderComponent);
+	});
+	
+	//CREATE SCALE AND TICS
+	
+	//write dl if there are optgroups
+	if(groups) {
+		var inc = 0;
+		var scale = sliderComponent.append('<dl class="ui-slider-scale ui-helper-reset" role="presentation"></dl>').find('.ui-slider-scale:eq(0)');
+		jQuery(groups).each(function(h){
+			scale.append('<dt style="width: '+ (100/groups.length).toFixed(2) +'%' +'; left:'+ (h/(groups.length-1) * 100).toFixed(2)  +'%' +'"><span>'+this.label+'</span></dt>');//class name becomes camelCased label
+			var groupOpts = this.options;
+			jQuery(this.options).each(function(i){
+				var style = (inc == selectOptions.length-1 || inc == 0) ? 'style="display: none;"' : '' ;
+				var labelText = (options.labelSrc == 'text') ? groupOpts[i].text : groupOpts[i].value;
+				scale.append('<dd style="left:'+ leftVal(inc) +'"><span class="ui-slider-label">'+ labelText +'</span><span class="ui-slider-tic ui-widget-content"'+ style +'></span></dd>');
+				inc++;
+			});
+		});
+	}
+	//write ol
+	else {
+		var scale = sliderComponent.append('<ol class="ui-slider-scale ui-helper-reset" role="presentation"></ol>').find('.ui-slider-scale:eq(0)');
+		jQuery(selectOptions).each(function(i){
+			var style = (i == selectOptions.length-1 || i == 0) ? 'style="display: none;"' : '' ;
+			var labelText = (options.labelSrc == 'text') ? this.text : this.value;
+			scale.append('<li style="left:'+ leftVal(i) +'"><span class="ui-slider-label">'+ labelText +'</span><span class="ui-slider-tic ui-widget-content"'+ style +'></span></li>');
+		});
+	}
+	
+	function leftVal(i){
+		return (i/(selectOptions.length-1) * 100).toFixed(2)  +'%';
+		
+	}
+	
+
+	
+	
+	//show and hide labels depending on labels pref
+	//show the last one if there are more than 1 specified
+	if(options.labels > 1) sliderComponent.find('.ui-slider-scale li:last span.ui-slider-label, .ui-slider-scale dd:last span.ui-slider-label').addClass('ui-slider-label-show');
+
+	//set increment
+	var increm = Math.max(1, Math.round(selectOptions.length / options.labels));
+	//show em based on inc
+	for(var j=0; j<selectOptions.length; j+=increm){
+		if((selectOptions.length - j) > increm){//don't show if it's too close to the end label
+			sliderComponent.find('.ui-slider-scale li:eq('+ j +') span.ui-slider-label, .ui-slider-scale dd:eq('+ j +') span.ui-slider-label').addClass('ui-slider-label-show');
+		}
+	}
+
+	//style the dt's
+	sliderComponent.find('.ui-slider-scale dt').each(function(i){
+		jQuery(this).css({
+			'left': ((100 /( groups.length))*i).toFixed(2) + '%'
+		});
+	});
+	
+
+	//inject and return 
+	sliderComponent
+	.insertAfter(jQuery(this).eq(this.length-1))
+	.slider(options.sliderOptions)
+	.attr('role','application');
+
+	var labels = jQuery('.ui-slider-label', sliderComponent);
+	var width = labels.width() / 2;
+	labels.css("marginLeft", width);
+
+	
+	//update tooltip arrow inner color
+	sliderComponent.find('.ui-tooltip-pointer-down-inner').each(function(){
+				var bWidth = jQuery('.ui-tooltip-pointer-down-inner').css('borderTopWidth');
+				var bColor = jQuery(this).parents('.ui-slider-tooltip').css('backgroundColor')
+				jQuery(this).css('border-top', bWidth+' solid '+bColor);
+	});
+	
+	var values = sliderComponent.slider('values');
+	
+	if(isArray(values)){
+		jQuery(values).each(function(i){
+			sliderComponent.find('.ui-slider-tooltip .ttContent').eq(i).text( ttText(this) );
+		});
+	}
+	else {
+		sliderComponent.find('.ui-slider-tooltip .ttContent').eq(0).text( ttText(values) );
+	}
+	
+	return this;
+}
+
+
diff --git a/media/js/tinymce/jquery.tinymce.js b/media/js/tinymce/jquery.tinymce.js
new file mode 100644
index 0000000..bb01508
--- /dev/null
+++ b/media/js/tinymce/jquery.tinymce.js
@@ -0,0 +1 @@
+(function(b){var c,a=[];function e(g,f,i){var h;h=b.fn[f];b.fn[f]=function(){var j;if(g!=="after"){j=i.apply(this,arguments);if(j!==undefined){return j}}j=h.apply(this,arguments);if(g!=="before"){i.apply(this,arguments)}return j}}b.fn.tinymce=function(i){var h=this,g,j="",f;if(!h.length){return}if(!i){return tinyMCE.get(this[0].id)}function k(){if(d){d();d=null}h.each(function(m,p){var l,o=p.id||tinymce.DOM.uniqueId();p.id=o;l=new tinymce.Editor(o,i);l.render()})}if(!window.tinymce&&!c&& [...]
\ No newline at end of file
diff --git a/media/js/tinymce/langs/en.js b/media/js/tinymce/langs/en.js
new file mode 100644
index 0000000..8519b4d
--- /dev/null
+++ b/media/js/tinymce/langs/en.js
@@ -0,0 +1,154 @@
+tinyMCE.addI18n({en:{
+common:{
+edit_confirm:"Do you want to use the WYSIWYG mode for this textarea?",
+apply:"Apply",
+insert:"Insert",
+update:"Update",
+cancel:"Cancel",
+close:"Close",
+browse:"Browse",
+class_name:"Class",
+not_set:"-- Not set --",
+clipboard_msg:"Copy/Cut/Paste is not available in Mozilla and Firefox.\nDo you want more information about this issue?",
+clipboard_no_support:"Currently not supported by your browser, use keyboard shortcuts instead.",
+popup_blocked:"Sorry, but we have noticed that your popup-blocker has disabled a window that provides application functionality. You will need to disable popup blocking on this site in order to fully utilize this tool.",
+invalid_data:"Error: Invalid values entered, these are marked in red.",
+more_colors:"More colors"
+},
+contextmenu:{
+align:"Alignment",
+left:"Left",
+center:"Center",
+right:"Right",
+full:"Full"
+},
+insertdatetime:{
+date_fmt:"%Y-%m-%d",
+time_fmt:"%H:%M:%S",
+insertdate_desc:"Insert date",
+inserttime_desc:"Insert time",
+months_long:"January,February,March,April,May,June,July,August,September,October,November,December",
+months_short:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
+day_long:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday",
+day_short:"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun"
+},
+print:{
+print_desc:"Print"
+},
+preview:{
+preview_desc:"Preview"
+},
+directionality:{
+ltr_desc:"Direction left to right",
+rtl_desc:"Direction right to left"
+},
+layer:{
+insertlayer_desc:"Insert new layer",
+forward_desc:"Move forward",
+backward_desc:"Move backward",
+absolute_desc:"Toggle absolute positioning",
+content:"New layer..."
+},
+save:{
+save_desc:"Save",
+cancel_desc:"Cancel all changes"
+},
+nonbreaking:{
+nonbreaking_desc:"Insert non-breaking space character"
+},
+iespell:{
+iespell_desc:"Run spell checking",
+download:"ieSpell not detected. Do you want to install it now?"
+},
+advhr:{
+advhr_desc:"Horizontal rule"
+},
+emotions:{
+emotions_desc:"Emotions"
+},
+searchreplace:{
+search_desc:"Find",
+replace_desc:"Find/Replace"
+},
+advimage:{
+image_desc:"Insert/edit image"
+},
+advlink:{
+link_desc:"Insert/edit link"
+},
+xhtmlxtras:{
+cite_desc:"Citation",
+abbr_desc:"Abbreviation",
+acronym_desc:"Acronym",
+del_desc:"Deletion",
+ins_desc:"Insertion",
+attribs_desc:"Insert/Edit Attributes"
+},
+style:{
+desc:"Edit CSS Style"
+},
+paste:{
+paste_text_desc:"Paste as Plain Text",
+paste_word_desc:"Paste from Word",
+selectall_desc:"Select All"
+},
+paste_dlg:{
+text_title:"Use CTRL+V on your keyboard to paste the text into the window.",
+text_linebreaks:"Keep linebreaks",
+word_title:"Use CTRL+V on your keyboard to paste the text into the window."
+},
+table:{
+desc:"Inserts a new table",
+row_before_desc:"Insert row before",
+row_after_desc:"Insert row after",
+delete_row_desc:"Delete row",
+col_before_desc:"Insert column before",
+col_after_desc:"Insert column after",
+delete_col_desc:"Remove column",
+split_cells_desc:"Split merged table cells",
+merge_cells_desc:"Merge table cells",
+row_desc:"Table row properties",
+cell_desc:"Table cell properties",
+props_desc:"Table properties",
+paste_row_before_desc:"Paste table row before",
+paste_row_after_desc:"Paste table row after",
+cut_row_desc:"Cut table row",
+copy_row_desc:"Copy table row",
+del:"Delete table",
+row:"Row",
+col:"Column",
+cell:"Cell"
+},
+autosave:{
+unload_msg:"The changes you made will be lost if you navigate away from this page."
+},
+fullscreen:{
+desc:"Toggle fullscreen mode"
+},
+media:{
+desc:"Insert / edit embedded media",
+edit:"Edit embedded media"
+},
+fullpage:{
+desc:"Document properties"
+},
+template:{
+desc:"Insert predefined template content"
+},
+visualchars:{
+desc:"Visual control characters on/off."
+},
+spellchecker:{
+desc:"Toggle spellchecker",
+menu:"Spellchecker settings",
+ignore_word:"Ignore word",
+ignore_words:"Ignore all",
+langs:"Languages",
+wait:"Please wait...",
+sug:"Suggestions",
+no_sug:"No suggestions",
+no_mpell:"No misspellings found."
+},
+pagebreak:{
+desc:"Insert page break."
+}}});
\ No newline at end of file
diff --git a/media/js/tinymce/license.txt b/media/js/tinymce/license.txt
new file mode 100644
index 0000000..60d6d4c
--- /dev/null
+++ b/media/js/tinymce/license.txt
@@ -0,0 +1,504 @@
+		  GNU LESSER GENERAL PUBLIC LICENSE
+		       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  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.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/media/js/tinymce/plugins/advhr/css/advhr.css b/media/js/tinymce/plugins/advhr/css/advhr.css
new file mode 100644
index 0000000..0e22834
--- /dev/null
+++ b/media/js/tinymce/plugins/advhr/css/advhr.css
@@ -0,0 +1,5 @@
+input.radio {border:1px none #000; background:transparent; vertical-align:middle;}
+.panel_wrapper div.current {height:80px;}
+#width {width:50px; vertical-align:middle;}
+#width2 {width:50px; vertical-align:middle;}
+#size {width:100px;}
diff --git a/media/js/tinymce/plugins/advhr/editor_plugin.js b/media/js/tinymce/plugins/advhr/editor_plugin.js
new file mode 100644
index 0000000..4d3b062
--- /dev/null
+++ b/media/js/tinymce/plugins/advhr/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.AdvancedHRPlugin",{init:function(a,b){a.addCommand("mceAdvancedHr",function(){a.windowManager.open({file:b+"/rule.htm",width:250+parseInt(a.getLang("advhr.delta_width",0)),height:160+parseInt(a.getLang("advhr.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("advhr",{title:"advhr.advhr_desc",cmd:"mceAdvancedHr"});a.onNodeChange.add(function(d,c,e){c.setActive("advhr",e.nodeName=="HR")});a.onClick.add(function(c,d){d=d.target;if(d.nodeNa [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advhr/editor_plugin_src.js b/media/js/tinymce/plugins/advhr/editor_plugin_src.js
new file mode 100644
index 0000000..8a84753
--- /dev/null
+++ b/media/js/tinymce/plugins/advhr/editor_plugin_src.js
@@ -0,0 +1,54 @@
+/**
+ * $Id: editor_plugin_src.js 520 2008-01-07 16:30:32Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.AdvancedHRPlugin', {
+		init : function(ed, url) {
+			// Register commands
+			ed.addCommand('mceAdvancedHr', function() {
+				ed.windowManager.open({
+					file : url + '/rule.htm',
+					width : 250 + parseInt(ed.getLang('advhr.delta_width', 0)),
+					height : 160 + parseInt(ed.getLang('advhr.delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			// Register buttons
+			ed.addButton('advhr', {
+				title : 'advhr.advhr_desc',
+				cmd : 'mceAdvancedHr'
+			});
+
+			ed.onNodeChange.add(function(ed, cm, n) {
+				cm.setActive('advhr', n.nodeName == 'HR');
+			});
+
+			ed.onClick.add(function(ed, e) {
+				e = e.target;
+
+				if (e.nodeName === 'HR')
+					ed.selection.select(e);
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Advanced HR',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advhr',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('advhr', tinymce.plugins.AdvancedHRPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advhr/js/rule.js b/media/js/tinymce/plugins/advhr/js/rule.js
new file mode 100644
index 0000000..b6cbd66
--- /dev/null
+++ b/media/js/tinymce/plugins/advhr/js/rule.js
@@ -0,0 +1,43 @@
+var AdvHRDialog = {
+	init : function(ed) {
+		var dom = ed.dom, f = document.forms[0], n = ed.selection.getNode(), w;
+
+		w = dom.getAttrib(n, 'width');
+		f.width.value = w ? parseInt(w) : (dom.getStyle('width') || '');
+		f.size.value = dom.getAttrib(n, 'size') || parseInt(dom.getStyle('height')) || '';
+		f.noshade.checked = !!dom.getAttrib(n, 'noshade') || !!dom.getStyle('border-width');
+		selectByValue(f, 'width2', w.indexOf('%') != -1 ? '%' : 'px');
+	},
+
+	update : function() {
+		var ed = tinyMCEPopup.editor, h, f = document.forms[0], st = '';
+
+		h = '<hr';
+
+		if (f.size.value) {
+			h += ' size="' + f.size.value + '"';
+			st += ' height:' + f.size.value + 'px;';
+		}
+
+		if (f.width.value) {
+			h += ' width="' + f.width.value + (f.width2.value == '%' ? '%' : '') + '"';
+			st += ' width:' + f.width.value + (f.width2.value == '%' ? '%' : 'px') + ';';
+		}
+
+		if (f.noshade.checked) {
+			h += ' noshade="noshade"';
+			st += ' border-width: 1px; border-style: solid; border-color: #CCCCCC; color: #ffffff;';
+		}
+
+		if (ed.settings.inline_styles)
+			h += ' style="' + tinymce.trim(st) + '"';
+
+		h += ' />';
+
+		ed.execCommand("mceInsertContent", false, h);
+		tinyMCEPopup.close();
+	}
+};
+
+tinyMCEPopup.requireLangPack();
+tinyMCEPopup.onInit.add(AdvHRDialog.init, AdvHRDialog);
diff --git a/media/js/tinymce/plugins/advhr/langs/en_dlg.js b/media/js/tinymce/plugins/advhr/langs/en_dlg.js
new file mode 100644
index 0000000..873bfd8
--- /dev/null
+++ b/media/js/tinymce/plugins/advhr/langs/en_dlg.js
@@ -0,0 +1,5 @@
+tinyMCE.addI18n('en.advhr_dlg',{
+width:"Width",
+size:"Height",
+noshade:"No shadow"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advhr/rule.htm b/media/js/tinymce/plugins/advhr/rule.htm
new file mode 100644
index 0000000..75ca339
--- /dev/null
+++ b/media/js/tinymce/plugins/advhr/rule.htm
@@ -0,0 +1,62 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advhr.advhr_desc}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/rule.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<link href="css/advhr.css" rel="stylesheet" type="text/css" />
+</head>
+<body>
+<form onsubmit="AdvHRDialog.update();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advhr.advhr_desc}</a></span></li>
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+			<table border="0" cellpadding="4" cellspacing="0">
+                    <tr>
+                        <td><label for="width">{#advhr_dlg.width}</label></td>
+                        <td class="nowrap">
+                            <input id="width" name="width" type="text" value="" class="mceFocus" />
+                            <select name="width2" id="width2">
+                                <option value="">px</option>
+                                <option value="%">%</option>
+                            </select>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td><label for="size">{#advhr_dlg.size}</label></td>
+                        <td><select id="size" name="size">
+                            <option value="">Normal</option>
+                            <option value="1">1</option>
+                            <option value="2">2</option>
+                            <option value="3">3</option>
+                            <option value="4">4</option>
+                            <option value="5">5</option>
+                        </select></td>
+                    </tr>
+                    <tr>
+                        <td><label for="noshade">{#advhr_dlg.noshade}</label></td>
+                        <td><input type="checkbox" name="noshade" id="noshade" class="radio" /></td>
+                    </tr>
+            </table>
+		</div>
+	</div>
+
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#insert}" />
+		</div>
+
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/advimage/css/advimage.css b/media/js/tinymce/plugins/advimage/css/advimage.css
new file mode 100644
index 0000000..0a6251a
--- /dev/null
+++ b/media/js/tinymce/plugins/advimage/css/advimage.css
@@ -0,0 +1,13 @@
+#src_list, #over_list, #out_list {width:280px;}
+.mceActionPanel {margin-top:7px;}
+.alignPreview {border:1px solid #000; width:140px; height:140px; overflow:hidden; padding:5px;}
+.checkbox {border:0;}
+.panel_wrapper div.current {height:305px;}
+#prev {margin:0; border:1px solid #000; width:428px; height:150px; overflow:auto;}
+#align, #classlist {width:150px;}
+#width, #height {vertical-align:middle; width:50px; text-align:center;}
+#vspace, #hspace, #border {vertical-align:middle; width:30px; text-align:center;}
+#class_list {width:180px;}
+input {width: 280px;}
+#constrain, #onmousemovecheck {width:auto;}
+#id, #dir, #lang, #usemap, #longdesc {width:200px;}
diff --git a/media/js/tinymce/plugins/advimage/editor_plugin.js b/media/js/tinymce/plugins/advimage/editor_plugin.js
new file mode 100644
index 0000000..4c7a9c3
--- /dev/null
+++ b/media/js/tinymce/plugins/advimage/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.AdvancedImagePlugin",{init:function(a,b){a.addCommand("mceAdvImage",function(){if(a.dom.getAttrib(a.selection.getNode(),"class").indexOf("mceItem")!=-1){return}a.windowManager.open({file:b+"/image.htm",width:480+parseInt(a.getLang("advimage.delta_width",0)),height:385+parseInt(a.getLang("advimage.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("image",{title:"advimage.image_desc",cmd:"mceAdvImage"})},getInfo:function(){return{longname [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advimage/editor_plugin_src.js b/media/js/tinymce/plugins/advimage/editor_plugin_src.js
new file mode 100644
index 0000000..f526842
--- /dev/null
+++ b/media/js/tinymce/plugins/advimage/editor_plugin_src.js
@@ -0,0 +1,47 @@
+/**
+ * $Id: editor_plugin_src.js 677 2008-03-07 13:52:41Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.AdvancedImagePlugin', {
+		init : function(ed, url) {
+			// Register commands
+			ed.addCommand('mceAdvImage', function() {
+				// Internal image object like a flash placeholder
+				if (ed.dom.getAttrib(ed.selection.getNode(), 'class').indexOf('mceItem') != -1)
+					return;
+
+				ed.windowManager.open({
+					file : url + '/image.htm',
+					width : 480 + parseInt(ed.getLang('advimage.delta_width', 0)),
+					height : 385 + parseInt(ed.getLang('advimage.delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			// Register buttons
+			ed.addButton('image', {
+				title : 'advimage.image_desc',
+				cmd : 'mceAdvImage'
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Advanced image',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('advimage', tinymce.plugins.AdvancedImagePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advimage/image.htm b/media/js/tinymce/plugins/advimage/image.htm
new file mode 100644
index 0000000..5d26150
--- /dev/null
+++ b/media/js/tinymce/plugins/advimage/image.htm
@@ -0,0 +1,237 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advimage_dlg.dialog_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/validate.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/image.js"></script>
+	<link href="css/advimage.css" rel="stylesheet" type="text/css" />
+</head>
+<body id="advimage" style="display: none">
+    <form onsubmit="ImageDialog.insert();return false;" action="#"> 
+		<div class="tabs">
+			<ul>
+				<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advimage_dlg.tab_general}</a></span></li>
+				<li id="appearance_tab"><span><a href="javascript:mcTabs.displayTab('appearance_tab','appearance_panel');" onmousedown="return false;">{#advimage_dlg.tab_appearance}</a></span></li>
+				<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#advimage_dlg.tab_advanced}</a></span></li>
+			</ul>
+		</div>
+
+		<div class="panel_wrapper">
+			<div id="general_panel" class="panel current">
+				<fieldset>
+						<legend>{#advimage_dlg.general}</legend>
+
+						<table class="properties">
+							<tr>
+								<td class="column1"><label id="srclabel" for="src">{#advimage_dlg.src}</label></td>
+								<td colspan="2"><table border="0" cellspacing="0" cellpadding="0">
+									<tr> 
+									  <td><input name="src" type="text" id="src" value="" class="mceFocus" onchange="ImageDialog.showPreviewImage(this.value);" /></td> 
+									  <td id="srcbrowsercontainer"> </td>
+									</tr>
+								  </table></td>
+							</tr>
+							<tr>
+								<td><label for="src_list">{#advimage_dlg.image_list}</label></td>
+								<td><select id="src_list" name="src_list" onchange="document.getElementById('src').value=this.options[this.selectedIndex].value;document.getElementById('alt').value=this.options[this.selectedIndex].text;document.getElementById('title').value=this.options[this.selectedIndex].text;ImageDialog.showPreviewImage(this.options[this.selectedIndex].value);"><option value=""></option></select></td>
+							</tr>
+							<tr> 
+								<td class="column1"><label id="altlabel" for="alt">{#advimage_dlg.alt}</label></td> 
+								<td colspan="2"><input id="alt" name="alt" type="text" value="" /></td> 
+							</tr> 
+							<tr> 
+								<td class="column1"><label id="titlelabel" for="title">{#advimage_dlg.title}</label></td> 
+								<td colspan="2"><input id="title" name="title" type="text" value="" /></td> 
+							</tr>
+						</table>
+				</fieldset>
+
+				<fieldset>
+					<legend>{#advimage_dlg.preview}</legend>
+					<div id="prev"></div>
+				</fieldset>
+			</div>
+
+			<div id="appearance_panel" class="panel">
+				<fieldset>
+					<legend>{#advimage_dlg.tab_appearance}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr> 
+							<td class="column1"><label id="alignlabel" for="align">{#advimage_dlg.align}</label></td> 
+							<td><select id="align" name="align" onchange="ImageDialog.updateStyle('align');ImageDialog.changeAppearance();"> 
+									<option value="">{#not_set}</option> 
+									<option value="baseline">{#advimage_dlg.align_baseline}</option>
+									<option value="top">{#advimage_dlg.align_top}</option>
+									<option value="middle">{#advimage_dlg.align_middle}</option>
+									<option value="bottom">{#advimage_dlg.align_bottom}</option>
+									<option value="text-top">{#advimage_dlg.align_texttop}</option>
+									<option value="text-bottom">{#advimage_dlg.align_textbottom}</option>
+									<option value="left">{#advimage_dlg.align_left}</option>
+									<option value="right">{#advimage_dlg.align_right}</option>
+								</select> 
+							</td>
+							<td rowspan="6" valign="top">
+								<div class="alignPreview">
+									<img id="alignSampleImg" src="img/sample.gif" alt="{#advimage_dlg.example_img}" />
+									Lorem ipsum, Dolor sit amet, consectetuer adipiscing loreum ipsum edipiscing elit, sed diam
+									nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.Loreum ipsum
+									edipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam
+									erat volutpat.
+								</div>
+							</td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="widthlabel" for="width">{#advimage_dlg.dimensions}</label></td>
+							<td class="nowrap">
+								<input name="width" type="text" id="width" value="" size="5" maxlength="5" class="size" onchange="ImageDialog.changeHeight();" /> x 
+								<input name="height" type="text" id="height" value="" size="5" maxlength="5" class="size" onchange="ImageDialog.changeWidth();" /> px
+							</td>
+						</tr>
+
+						<tr>
+							<td> </td>
+							<td><table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="constrain" type="checkbox" name="constrain" class="checkbox" /></td>
+										<td><label id="constrainlabel" for="constrain">{#advimage_dlg.constrain_proportions}</label></td>
+									</tr>
+								</table></td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="vspacelabel" for="vspace">{#advimage_dlg.vspace}</label></td> 
+							<td><input name="vspace" type="text" id="vspace" value="" size="3" maxlength="3" class="number" onchange="ImageDialog.updateStyle('vspace');ImageDialog.changeAppearance();" onblur="ImageDialog.updateStyle('vspace');ImageDialog.changeAppearance();" />
+							</td>
+						</tr>
+
+						<tr> 
+							<td class="column1"><label id="hspacelabel" for="hspace">{#advimage_dlg.hspace}</label></td> 
+							<td><input name="hspace" type="text" id="hspace" value="" size="3" maxlength="3" class="number" onchange="ImageDialog.updateStyle('hspace');ImageDialog.changeAppearance();" onblur="ImageDialog.updateStyle('hspace');ImageDialog.changeAppearance();" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="borderlabel" for="border">{#advimage_dlg.border}</label></td> 
+							<td><input id="border" name="border" type="text" value="" size="3" maxlength="3" class="number" onchange="ImageDialog.updateStyle('border');ImageDialog.changeAppearance();" onblur="ImageDialog.updateStyle('border');ImageDialog.changeAppearance();" /></td> 
+						</tr>
+
+						<tr>
+							<td><label for="class_list">{#class_name}</label></td>
+							<td colspan="2"><select id="class_list" name="class_list" class="mceEditableSelect"><option value=""></option></select></td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="stylelabel" for="style">{#advimage_dlg.style}</label></td> 
+							<td colspan="2"><input id="style" name="style" type="text" value="" onchange="ImageDialog.changeAppearance();" /></td> 
+						</tr>
+
+						<!-- <tr>
+							<td class="column1"><label id="classeslabel" for="classes">{#advimage_dlg.classes}</label></td> 
+							<td colspan="2"><input id="classes" name="classes" type="text" value="" onchange="selectByValue(this.form,'classlist',this.value,true);" /></td> 
+						</tr> -->
+					</table>
+				</fieldset>
+			</div>
+
+			<div id="advanced_panel" class="panel">
+				<fieldset>
+					<legend>{#advimage_dlg.swap_image}</legend>
+
+					<input type="checkbox" id="onmousemovecheck" name="onmousemovecheck" class="checkbox" onclick="ImageDialog.setSwapImage(this.checked);" />
+					<label id="onmousemovechecklabel" for="onmousemovecheck">{#advimage_dlg.alt_image}</label>
+
+					<table border="0" cellpadding="4" cellspacing="0" width="100%">
+							<tr>
+								<td class="column1"><label id="onmouseoversrclabel" for="onmouseoversrc">{#advimage_dlg.mouseover}</label></td> 
+								<td><table border="0" cellspacing="0" cellpadding="0"> 
+									<tr> 
+									  <td><input id="onmouseoversrc" name="onmouseoversrc" type="text" value="" /></td> 
+									  <td id="onmouseoversrccontainer"> </td>
+									</tr>
+								  </table></td>
+							</tr>
+							<tr>
+								<td><label for="over_list">{#advimage_dlg.image_list}</label></td>
+								<td><select id="over_list" name="over_list" onchange="document.getElementById('onmouseoversrc').value=this.options[this.selectedIndex].value;"><option value=""></option></select></td>
+							</tr>
+							<tr> 
+								<td class="column1"><label id="onmouseoutsrclabel" for="onmouseoutsrc">{#advimage_dlg.mouseout}</label></td> 
+								<td class="column2"><table border="0" cellspacing="0" cellpadding="0"> 
+									<tr> 
+									  <td><input id="onmouseoutsrc" name="onmouseoutsrc" type="text" value="" /></td> 
+									  <td id="onmouseoutsrccontainer"> </td>
+									</tr> 
+								  </table></td> 
+							</tr>
+							<tr>
+								<td><label for="out_list">{#advimage_dlg.image_list}</label></td>
+								<td><select id="out_list" name="out_list" onchange="document.getElementById('onmouseoutsrc').value=this.options[this.selectedIndex].value;"><option value=""></option></select></td>
+							</tr>
+					</table>
+				</fieldset>
+
+				<fieldset>
+					<legend>{#advimage_dlg.misc}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label id="idlabel" for="id">{#advimage_dlg.id}</label></td> 
+							<td><input id="id" name="id" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="dirlabel" for="dir">{#advimage_dlg.langdir}</label></td> 
+							<td>
+								<select id="dir" name="dir" onchange="ImageDialog.changeAppearance();"> 
+										<option value="">{#not_set}</option> 
+										<option value="ltr">{#advimage_dlg.ltr}</option> 
+										<option value="rtl">{#advimage_dlg.rtl}</option> 
+								</select>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="langlabel" for="lang">{#advimage_dlg.langcode}</label></td> 
+							<td>
+								<input id="lang" name="lang" type="text" value="" />
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="usemaplabel" for="usemap">{#advimage_dlg.map}</label></td> 
+							<td>
+								<input id="usemap" name="usemap" type="text" value="" />
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="longdesclabel" for="longdesc">{#advimage_dlg.long_desc}</label></td>
+							<td><table border="0" cellspacing="0" cellpadding="0">
+									<tr>
+									  <td><input id="longdesc" name="longdesc" type="text" value="" /></td>
+									  <td id="longdesccontainer"> </td>
+									</tr>
+								</table></td> 
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+		</div>
+
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" id="insert" name="insert" value="{#insert}" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+    </form>
+</body> 
+</html> 
diff --git a/media/js/tinymce/plugins/advimage/img/sample.gif b/media/js/tinymce/plugins/advimage/img/sample.gif
new file mode 100644
index 0000000..53bf689
Binary files /dev/null and b/media/js/tinymce/plugins/advimage/img/sample.gif differ
diff --git a/media/js/tinymce/plugins/advimage/js/image.js b/media/js/tinymce/plugins/advimage/js/image.js
new file mode 100644
index 0000000..3477226
--- /dev/null
+++ b/media/js/tinymce/plugins/advimage/js/image.js
@@ -0,0 +1,443 @@
+var ImageDialog = {
+	preInit : function() {
+		var url;
+
+		tinyMCEPopup.requireLangPack();
+
+		if (url = tinyMCEPopup.getParam("external_image_list_url"))
+			document.write('<script language="javascript" type="text/javascript" src="' + tinyMCEPopup.editor.documentBaseURI.toAbsolute(url) + '"></script>');
+	},
+
+	init : function(ed) {
+		var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, dom = ed.dom, n = ed.selection.getNode();
+
+		tinyMCEPopup.resizeToInnerSize();
+		this.fillClassList('class_list');
+		this.fillFileList('src_list', 'tinyMCEImageList');
+		this.fillFileList('over_list', 'tinyMCEImageList');
+		this.fillFileList('out_list', 'tinyMCEImageList');
+		TinyMCE_EditableSelects.init();
+
+		if (n.nodeName == 'IMG') {
+			nl.src.value = dom.getAttrib(n, 'src');
+			nl.width.value = dom.getAttrib(n, 'width');
+			nl.height.value = dom.getAttrib(n, 'height');
+			nl.alt.value = dom.getAttrib(n, 'alt');
+			nl.title.value = dom.getAttrib(n, 'title');
+			nl.vspace.value = this.getAttrib(n, 'vspace');
+			nl.hspace.value = this.getAttrib(n, 'hspace');
+			nl.border.value = this.getAttrib(n, 'border');
+			selectByValue(f, 'align', this.getAttrib(n, 'align'));
+			selectByValue(f, 'class_list', dom.getAttrib(n, 'class'), true, true);
+			nl.style.value = dom.getAttrib(n, 'style');
+			nl.id.value = dom.getAttrib(n, 'id');
+			nl.dir.value = dom.getAttrib(n, 'dir');
+			nl.lang.value = dom.getAttrib(n, 'lang');
+			nl.usemap.value = dom.getAttrib(n, 'usemap');
+			nl.longdesc.value = dom.getAttrib(n, 'longdesc');
+			nl.insert.value = ed.getLang('update');
+
+			if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseover')))
+				nl.onmouseoversrc.value = dom.getAttrib(n, 'onmouseover').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1');
+
+			if (/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/.test(dom.getAttrib(n, 'onmouseout')))
+				nl.onmouseoutsrc.value = dom.getAttrib(n, 'onmouseout').replace(/^\s*this.src\s*=\s*\'([^\']+)\';?\s*$/, '$1');
+
+			if (ed.settings.inline_styles) {
+				// Move attribs to styles
+				if (dom.getAttrib(n, 'align'))
+					this.updateStyle('align');
+
+				if (dom.getAttrib(n, 'hspace'))
+					this.updateStyle('hspace');
+
+				if (dom.getAttrib(n, 'border'))
+					this.updateStyle('border');
+
+				if (dom.getAttrib(n, 'vspace'))
+					this.updateStyle('vspace');
+			}
+		}
+
+		// Setup browse button
+		document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image');
+		if (isVisible('srcbrowser'))
+			document.getElementById('src').style.width = '260px';
+
+		// Setup browse button
+		document.getElementById('onmouseoversrccontainer').innerHTML = getBrowserHTML('overbrowser','onmouseoversrc','image','theme_advanced_image');
+		if (isVisible('overbrowser'))
+			document.getElementById('onmouseoversrc').style.width = '260px';
+
+		// Setup browse button
+		document.getElementById('onmouseoutsrccontainer').innerHTML = getBrowserHTML('outbrowser','onmouseoutsrc','image','theme_advanced_image');
+		if (isVisible('outbrowser'))
+			document.getElementById('onmouseoutsrc').style.width = '260px';
+
+		// If option enabled default contrain proportions to checked
+		if (ed.getParam("advimage_constrain_proportions", true))
+			f.constrain.checked = true;
+
+		// Check swap image if valid data
+		if (nl.onmouseoversrc.value || nl.onmouseoutsrc.value)
+			this.setSwapImage(true);
+		else
+			this.setSwapImage(false);
+
+		this.changeAppearance();
+		this.showPreviewImage(nl.src.value, 1);
+	},
+
+	insert : function(file, title) {
+		var ed = tinyMCEPopup.editor, t = this, f = document.forms[0];
+
+		if (f.src.value === '') {
+			if (ed.selection.getNode().nodeName == 'IMG') {
+				ed.dom.remove(ed.selection.getNode());
+				ed.execCommand('mceRepaint');
+			}
+
+			tinyMCEPopup.close();
+			return;
+		}
+
+		if (tinyMCEPopup.getParam("accessibility_warnings", 1)) {
+			if (!f.alt.value) {
+				tinyMCEPopup.confirm(tinyMCEPopup.getLang('advimage_dlg.missing_alt'), function(s) {
+					if (s)
+						t.insertAndClose();
+				});
+
+				return;
+			}
+		}
+
+		t.insertAndClose();
+	},
+
+	insertAndClose : function() {
+		var ed = tinyMCEPopup.editor, f = document.forms[0], nl = f.elements, v, args = {}, el;
+
+		tinyMCEPopup.restoreSelection();
+
+		// Fixes crash in Safari
+		if (tinymce.isWebKit)
+			ed.getWin().focus();
+
+		if (!ed.settings.inline_styles) {
+			args = {
+				vspace : nl.vspace.value,
+				hspace : nl.hspace.value,
+				border : nl.border.value,
+				align : getSelectValue(f, 'align')
+			};
+		} else {
+			// Remove deprecated values
+			args = {
+				vspace : '',
+				hspace : '',
+				border : '',
+				align : ''
+			};
+		}
+
+		tinymce.extend(args, {
+			src : nl.src.value,
+			width : nl.width.value,
+			height : nl.height.value,
+			alt : nl.alt.value,
+			title : nl.title.value,
+			'class' : getSelectValue(f, 'class_list'),
+			style : nl.style.value,
+			id : nl.id.value,
+			dir : nl.dir.value,
+			lang : nl.lang.value,
+			usemap : nl.usemap.value,
+			longdesc : nl.longdesc.value
+		});
+
+		args.onmouseover = args.onmouseout = '';
+
+		if (f.onmousemovecheck.checked) {
+			if (nl.onmouseoversrc.value)
+				args.onmouseover = "this.src='" + nl.onmouseoversrc.value + "';";
+
+			if (nl.onmouseoutsrc.value)
+				args.onmouseout = "this.src='" + nl.onmouseoutsrc.value + "';";
+		}
+
+		el = ed.selection.getNode();
+
+		if (el && el.nodeName == 'IMG') {
+			ed.dom.setAttribs(el, args);
+		} else {
+			ed.execCommand('mceInsertContent', false, '<img id="__mce_tmp" />', {skip_undo : 1});
+			ed.dom.setAttribs('__mce_tmp', args);
+			ed.dom.setAttrib('__mce_tmp', 'id', '');
+			ed.undoManager.add();
+		}
+
+		tinyMCEPopup.close();
+	},
+
+	getAttrib : function(e, at) {
+		var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2;
+
+		if (ed.settings.inline_styles) {
+			switch (at) {
+				case 'align':
+					if (v = dom.getStyle(e, 'float'))
+						return v;
+
+					if (v = dom.getStyle(e, 'vertical-align'))
+						return v;
+
+					break;
+
+				case 'hspace':
+					v = dom.getStyle(e, 'margin-left')
+					v2 = dom.getStyle(e, 'margin-right');
+
+					if (v && v == v2)
+						return parseInt(v.replace(/[^0-9]/g, ''));
+
+					break;
+
+				case 'vspace':
+					v = dom.getStyle(e, 'margin-top')
+					v2 = dom.getStyle(e, 'margin-bottom');
+					if (v && v == v2)
+						return parseInt(v.replace(/[^0-9]/g, ''));
+
+					break;
+
+				case 'border':
+					v = 0;
+
+					tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) {
+						sv = dom.getStyle(e, 'border-' + sv + '-width');
+
+						// False or not the same as prev
+						if (!sv || (sv != v && v !== 0)) {
+							v = 0;
+							return false;
+						}
+
+						if (sv)
+							v = sv;
+					});
+
+					if (v)
+						return parseInt(v.replace(/[^0-9]/g, ''));
+
+					break;
+			}
+		}
+
+		if (v = dom.getAttrib(e, at))
+			return v;
+
+		return '';
+	},
+
+	setSwapImage : function(st) {
+		var f = document.forms[0];
+
+		f.onmousemovecheck.checked = st;
+		setBrowserDisabled('overbrowser', !st);
+		setBrowserDisabled('outbrowser', !st);
+
+		if (f.over_list)
+			f.over_list.disabled = !st;
+
+		if (f.out_list)
+			f.out_list.disabled = !st;
+
+		f.onmouseoversrc.disabled = !st;
+		f.onmouseoutsrc.disabled  = !st;
+	},
+
+	fillClassList : function(id) {
+		var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl;
+
+		if (v = tinyMCEPopup.getParam('theme_advanced_styles')) {
+			cl = [];
+
+			tinymce.each(v.split(';'), function(v) {
+				var p = v.split('=');
+
+				cl.push({'title' : p[0], 'class' : p[1]});
+			});
+		} else
+			cl = tinyMCEPopup.editor.dom.getClasses();
+
+		if (cl.length > 0) {
+			lst.options.length = 0;
+			lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), '');
+
+			tinymce.each(cl, function(o) {
+				lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']);
+			});
+		} else
+			dom.remove(dom.getParent(id, 'tr'));
+	},
+
+	fillFileList : function(id, l) {
+		var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl;
+
+		l = window[l];
+		lst.options.length = 0;
+
+		if (l && l.length > 0) {
+			lst.options[lst.options.length] = new Option('', '');
+
+			tinymce.each(l, function(o) {
+				lst.options[lst.options.length] = new Option(o[0], o[1]);
+			});
+		} else
+			dom.remove(dom.getParent(id, 'tr'));
+	},
+
+	resetImageData : function() {
+		var f = document.forms[0];
+
+		f.elements.width.value = f.elements.height.value = '';
+	},
+
+	updateImageData : function(img, st) {
+		var f = document.forms[0];
+
+		if (!st) {
+			f.elements.width.value = img.width;
+			f.elements.height.value = img.height;
+		}
+
+		this.preloadImg = img;
+	},
+
+	changeAppearance : function() {
+		var ed = tinyMCEPopup.editor, f = document.forms[0], img = document.getElementById('alignSampleImg');
+
+		if (img) {
+			if (ed.getParam('inline_styles')) {
+				ed.dom.setAttrib(img, 'style', f.style.value);
+			} else {
+				img.align = f.align.value;
+				img.border = f.border.value;
+				img.hspace = f.hspace.value;
+				img.vspace = f.vspace.value;
+			}
+		}
+	},
+
+	changeHeight : function() {
+		var f = document.forms[0], tp, t = this;
+
+		if (!f.constrain.checked || !t.preloadImg) {
+			return;
+		}
+
+		if (f.width.value == "" || f.height.value == "")
+			return;
+
+		tp = (parseInt(f.width.value) / parseInt(t.preloadImg.width)) * t.preloadImg.height;
+		f.height.value = tp.toFixed(0);
+	},
+
+	changeWidth : function() {
+		var f = document.forms[0], tp, t = this;
+
+		if (!f.constrain.checked || !t.preloadImg) {
+			return;
+		}
+
+		if (f.width.value == "" || f.height.value == "")
+			return;
+
+		tp = (parseInt(f.height.value) / parseInt(t.preloadImg.height)) * t.preloadImg.width;
+		f.width.value = tp.toFixed(0);
+	},
+
+	updateStyle : function(ty) {
+		var dom = tinyMCEPopup.dom, st, v, f = document.forms[0], img = dom.create('img', {style : dom.get('style').value});
+
+		if (tinyMCEPopup.editor.settings.inline_styles) {
+			// Handle align
+			if (ty == 'align') {
+				dom.setStyle(img, 'float', '');
+				dom.setStyle(img, 'vertical-align', '');
+
+				v = getSelectValue(f, 'align');
+				if (v) {
+					if (v == 'left' || v == 'right')
+						dom.setStyle(img, 'float', v);
+					else
+						img.style.verticalAlign = v;
+				}
+			}
+
+			// Handle border
+			if (ty == 'border') {
+				dom.setStyle(img, 'border', '');
+
+				v = f.border.value;
+				if (v || v == '0') {
+					if (v == '0')
+						img.style.border = '0';
+					else
+						img.style.border = v + 'px solid black';
+				}
+			}
+
+			// Handle hspace
+			if (ty == 'hspace') {
+				dom.setStyle(img, 'marginLeft', '');
+				dom.setStyle(img, 'marginRight', '');
+
+				v = f.hspace.value;
+				if (v) {
+					img.style.marginLeft = v + 'px';
+					img.style.marginRight = v + 'px';
+				}
+			}
+
+			// Handle vspace
+			if (ty == 'vspace') {
+				dom.setStyle(img, 'marginTop', '');
+				dom.setStyle(img, 'marginBottom', '');
+
+				v = f.vspace.value;
+				if (v) {
+					img.style.marginTop = v + 'px';
+					img.style.marginBottom = v + 'px';
+				}
+			}
+
+			// Merge
+			dom.get('style').value = dom.serializeStyle(dom.parseStyle(img.style.cssText));
+		}
+	},
+
+	changeMouseMove : function() {
+	},
+
+	showPreviewImage : function(u, st) {
+		if (!u) {
+			tinyMCEPopup.dom.setHTML('prev', '');
+			return;
+		}
+
+		if (!st && tinyMCEPopup.getParam("advimage_update_dimensions_onchange", true))
+			this.resetImageData();
+
+		u = tinyMCEPopup.editor.documentBaseURI.toAbsolute(u);
+
+		if (!st)
+			tinyMCEPopup.dom.setHTML('prev', '<img id="previewImg" src="' + u + '" border="0" onload="ImageDialog.updateImageData(this);" onerror="ImageDialog.resetImageData();" />');
+		else
+			tinyMCEPopup.dom.setHTML('prev', '<img id="previewImg" src="' + u + '" border="0" onload="ImageDialog.updateImageData(this, 1);" />');
+	}
+};
+
+ImageDialog.preInit();
+tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog);
diff --git a/media/js/tinymce/plugins/advimage/langs/en_dlg.js b/media/js/tinymce/plugins/advimage/langs/en_dlg.js
new file mode 100644
index 0000000..f493d19
--- /dev/null
+++ b/media/js/tinymce/plugins/advimage/langs/en_dlg.js
@@ -0,0 +1,43 @@
+tinyMCE.addI18n('en.advimage_dlg',{
+tab_general:"General",
+tab_appearance:"Appearance",
+tab_advanced:"Advanced",
+general:"General",
+title:"Title",
+preview:"Preview",
+constrain_proportions:"Constrain proportions",
+langdir:"Language direction",
+langcode:"Language code",
+long_desc:"Long description link",
+style:"Style",
+classes:"Classes",
+ltr:"Left to right",
+rtl:"Right to left",
+id:"Id",
+map:"Image map",
+swap_image:"Swap image",
+alt_image:"Alternative image",
+mouseover:"for mouse over",
+mouseout:"for mouse out",
+misc:"Miscellaneous",
+example_img:"Appearance preview image",
+missing_alt:"Are you sure you want to continue without including an Image Description? Without it the image may not be accessible to some users with disabilities, or to those using a text browser, or browsing the Web with images turned off.",
+dialog_title:"Insert/edit image",
+src:"Image URL",
+alt:"Image description",
+list:"Image list",
+border:"Border",
+dimensions:"Dimensions",
+vspace:"Vertical space",
+hspace:"Horizontal space",
+align:"Alignment",
+align_baseline:"Baseline",
+align_top:"Top",
+align_middle:"Middle",
+align_bottom:"Bottom",
+align_texttop:"Text top",
+align_textbottom:"Text bottom",
+align_left:"Left",
+align_right:"Right",
+image_list:"Image list"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advlink/css/advlink.css b/media/js/tinymce/plugins/advlink/css/advlink.css
new file mode 100644
index 0000000..1436431
--- /dev/null
+++ b/media/js/tinymce/plugins/advlink/css/advlink.css
@@ -0,0 +1,8 @@
+.mceLinkList, .mceAnchorList, #targetlist {width:280px;}
+.mceActionPanel {margin-top:7px;}
+.panel_wrapper div.current {height:320px;}
+#classlist, #title, #href {width:280px;}
+#popupurl, #popupname {width:200px;}
+#popupwidth, #popupheight, #popupleft, #popuptop {width:30px;vertical-align:middle;text-align:center;}
+#id, #style, #classes, #target, #dir, #hreflang, #lang, #charset, #type, #rel, #rev, #tabindex, #accesskey {width:200px;}
+#events_panel input {width:200px;}
diff --git a/media/js/tinymce/plugins/advlink/editor_plugin.js b/media/js/tinymce/plugins/advlink/editor_plugin.js
new file mode 100644
index 0000000..983fe5a
--- /dev/null
+++ b/media/js/tinymce/plugins/advlink/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.AdvancedLinkPlugin",{init:function(a,b){this.editor=a;a.addCommand("mceAdvLink",function(){var c=a.selection;if(c.isCollapsed()&&!a.dom.getParent(c.getNode(),"A")){return}a.windowManager.open({file:b+"/link.htm",width:480+parseInt(a.getLang("advlink.delta_width",0)),height:400+parseInt(a.getLang("advlink.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("link",{title:"advlink.link_desc",cmd:"mceAdvLink"});a.addShortcut("ctrl+k","advlink [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advlink/editor_plugin_src.js b/media/js/tinymce/plugins/advlink/editor_plugin_src.js
new file mode 100644
index 0000000..fc5325a
--- /dev/null
+++ b/media/js/tinymce/plugins/advlink/editor_plugin_src.js
@@ -0,0 +1,58 @@
+/**
+ * $Id: editor_plugin_src.js 539 2008-01-14 19:08:58Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.AdvancedLinkPlugin', {
+		init : function(ed, url) {
+			this.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceAdvLink', function() {
+				var se = ed.selection;
+
+				// No selection and not in link
+				if (se.isCollapsed() && !ed.dom.getParent(se.getNode(), 'A'))
+					return;
+
+				ed.windowManager.open({
+					file : url + '/link.htm',
+					width : 480 + parseInt(ed.getLang('advlink.delta_width', 0)),
+					height : 400 + parseInt(ed.getLang('advlink.delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			// Register buttons
+			ed.addButton('link', {
+				title : 'advlink.link_desc',
+				cmd : 'mceAdvLink'
+			});
+
+			ed.addShortcut('ctrl+k', 'advlink.advlink_desc', 'mceAdvLink');
+
+			ed.onNodeChange.add(function(ed, cm, n, co) {
+				cm.setDisabled('link', co && n.nodeName != 'A');
+				cm.setActive('link', n.nodeName == 'A' && !n.name);
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Advanced link',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlink',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('advlink', tinymce.plugins.AdvancedLinkPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advlink/js/advlink.js b/media/js/tinymce/plugins/advlink/js/advlink.js
new file mode 100644
index 0000000..bb7922a
--- /dev/null
+++ b/media/js/tinymce/plugins/advlink/js/advlink.js
@@ -0,0 +1,528 @@
+/* Functions for the advlink plugin popup */
+
+tinyMCEPopup.requireLangPack();
+
+var templates = {
+	"window.open" : "window.open('${url}','${target}','${options}')"
+};
+
+function preinit() {
+	var url;
+
+	if (url = tinyMCEPopup.getParam("external_link_list_url"))
+		document.write('<script language="javascript" type="text/javascript" src="' + tinyMCEPopup.editor.documentBaseURI.toAbsolute(url) + '"></script>');
+}
+
+function changeClass() {
+	var f = document.forms[0];
+
+	f.classes.value = getSelectValue(f, 'classlist');
+}
+
+function init() {
+	tinyMCEPopup.resizeToInnerSize();
+
+	var formObj = document.forms[0];
+	var inst = tinyMCEPopup.editor;
+	var elm = inst.selection.getNode();
+	var action = "insert";
+	var html;
+
+	document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser','href','file','advlink');
+	document.getElementById('popupurlbrowsercontainer').innerHTML = getBrowserHTML('popupurlbrowser','popupurl','file','advlink');
+	document.getElementById('linklisthrefcontainer').innerHTML = getLinkListHTML('linklisthref','href');
+	document.getElementById('anchorlistcontainer').innerHTML = getAnchorListHTML('anchorlist','href');
+	document.getElementById('targetlistcontainer').innerHTML = getTargetListHTML('targetlist','target');
+
+	// Link list
+	html = getLinkListHTML('linklisthref','href');
+	if (html == "")
+		document.getElementById("linklisthrefrow").style.display = 'none';
+	else
+		document.getElementById("linklisthrefcontainer").innerHTML = html;
+
+	// Resize some elements
+	if (isVisible('hrefbrowser'))
+		document.getElementById('href').style.width = '260px';
+
+	if (isVisible('popupurlbrowser'))
+		document.getElementById('popupurl').style.width = '180px';
+
+	elm = inst.dom.getParent(elm, "A");
+	if (elm != null && elm.nodeName == "A")
+		action = "update";
+
+	formObj.insert.value = tinyMCEPopup.getLang(action, 'Insert', true); 
+
+	setPopupControlsDisabled(true);
+
+	if (action == "update") {
+		var href = inst.dom.getAttrib(elm, 'href');
+		var onclick = inst.dom.getAttrib(elm, 'onclick');
+
+		// Setup form data
+		setFormValue('href', href);
+		setFormValue('title', inst.dom.getAttrib(elm, 'title'));
+		setFormValue('id', inst.dom.getAttrib(elm, 'id'));
+		setFormValue('style', inst.dom.getAttrib(elm, "style"));
+		setFormValue('rel', inst.dom.getAttrib(elm, 'rel'));
+		setFormValue('rev', inst.dom.getAttrib(elm, 'rev'));
+		setFormValue('charset', inst.dom.getAttrib(elm, 'charset'));
+		setFormValue('hreflang', inst.dom.getAttrib(elm, 'hreflang'));
+		setFormValue('dir', inst.dom.getAttrib(elm, 'dir'));
+		setFormValue('lang', inst.dom.getAttrib(elm, 'lang'));
+		setFormValue('tabindex', inst.dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : ""));
+		setFormValue('accesskey', inst.dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : ""));
+		setFormValue('type', inst.dom.getAttrib(elm, 'type'));
+		setFormValue('onfocus', inst.dom.getAttrib(elm, 'onfocus'));
+		setFormValue('onblur', inst.dom.getAttrib(elm, 'onblur'));
+		setFormValue('onclick', onclick);
+		setFormValue('ondblclick', inst.dom.getAttrib(elm, 'ondblclick'));
+		setFormValue('onmousedown', inst.dom.getAttrib(elm, 'onmousedown'));
+		setFormValue('onmouseup', inst.dom.getAttrib(elm, 'onmouseup'));
+		setFormValue('onmouseover', inst.dom.getAttrib(elm, 'onmouseover'));
+		setFormValue('onmousemove', inst.dom.getAttrib(elm, 'onmousemove'));
+		setFormValue('onmouseout', inst.dom.getAttrib(elm, 'onmouseout'));
+		setFormValue('onkeypress', inst.dom.getAttrib(elm, 'onkeypress'));
+		setFormValue('onkeydown', inst.dom.getAttrib(elm, 'onkeydown'));
+		setFormValue('onkeyup', inst.dom.getAttrib(elm, 'onkeyup'));
+		setFormValue('target', inst.dom.getAttrib(elm, 'target'));
+		setFormValue('classes', inst.dom.getAttrib(elm, 'class'));
+
+		// Parse onclick data
+		if (onclick != null && onclick.indexOf('window.open') != -1)
+			parseWindowOpen(onclick);
+		else
+			parseFunction(onclick);
+
+		// Select by the values
+		selectByValue(formObj, 'dir', inst.dom.getAttrib(elm, 'dir'));
+		selectByValue(formObj, 'rel', inst.dom.getAttrib(elm, 'rel'));
+		selectByValue(formObj, 'rev', inst.dom.getAttrib(elm, 'rev'));
+		selectByValue(formObj, 'linklisthref', href);
+
+		if (href.charAt(0) == '#')
+			selectByValue(formObj, 'anchorlist', href);
+
+		addClassesToList('classlist', 'advlink_styles');
+
+		selectByValue(formObj, 'classlist', inst.dom.getAttrib(elm, 'class'), true);
+		selectByValue(formObj, 'targetlist', inst.dom.getAttrib(elm, 'target'), true);
+	} else
+		addClassesToList('classlist', 'advlink_styles');
+}
+
+function checkPrefix(n) {
+	if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_email')))
+		n.value = 'mailto:' + n.value;
+
+	if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advlink_dlg.is_external')))
+		n.value = 'http://' + n.value;
+}
+
+function setFormValue(name, value) {
+	document.forms[0].elements[name].value = value;
+}
+
+function parseWindowOpen(onclick) {
+	var formObj = document.forms[0];
+
+	// Preprocess center code
+	if (onclick.indexOf('return false;') != -1) {
+		formObj.popupreturn.checked = true;
+		onclick = onclick.replace('return false;', '');
+	} else
+		formObj.popupreturn.checked = false;
+
+	var onClickData = parseLink(onclick);
+
+	if (onClickData != null) {
+		formObj.ispopup.checked = true;
+		setPopupControlsDisabled(false);
+
+		var onClickWindowOptions = parseOptions(onClickData['options']);
+		var url = onClickData['url'];
+
+		formObj.popupname.value = onClickData['target'];
+		formObj.popupurl.value = url;
+		formObj.popupwidth.value = getOption(onClickWindowOptions, 'width');
+		formObj.popupheight.value = getOption(onClickWindowOptions, 'height');
+
+		formObj.popupleft.value = getOption(onClickWindowOptions, 'left');
+		formObj.popuptop.value = getOption(onClickWindowOptions, 'top');
+
+		if (formObj.popupleft.value.indexOf('screen') != -1)
+			formObj.popupleft.value = "c";
+
+		if (formObj.popuptop.value.indexOf('screen') != -1)
+			formObj.popuptop.value = "c";
+
+		formObj.popuplocation.checked = getOption(onClickWindowOptions, 'location') == "yes";
+		formObj.popupscrollbars.checked = getOption(onClickWindowOptions, 'scrollbars') == "yes";
+		formObj.popupmenubar.checked = getOption(onClickWindowOptions, 'menubar') == "yes";
+		formObj.popupresizable.checked = getOption(onClickWindowOptions, 'resizable') == "yes";
+		formObj.popuptoolbar.checked = getOption(onClickWindowOptions, 'toolbar') == "yes";
+		formObj.popupstatus.checked = getOption(onClickWindowOptions, 'status') == "yes";
+		formObj.popupdependent.checked = getOption(onClickWindowOptions, 'dependent') == "yes";
+
+		buildOnClick();
+	}
+}
+
+function parseFunction(onclick) {
+	var formObj = document.forms[0];
+	var onClickData = parseLink(onclick);
+
+	// TODO: Add stuff here
+}
+
+function getOption(opts, name) {
+	return typeof(opts[name]) == "undefined" ? "" : opts[name];
+}
+
+function setPopupControlsDisabled(state) {
+	var formObj = document.forms[0];
+
+	formObj.popupname.disabled = state;
+	formObj.popupurl.disabled = state;
+	formObj.popupwidth.disabled = state;
+	formObj.popupheight.disabled = state;
+	formObj.popupleft.disabled = state;
+	formObj.popuptop.disabled = state;
+	formObj.popuplocation.disabled = state;
+	formObj.popupscrollbars.disabled = state;
+	formObj.popupmenubar.disabled = state;
+	formObj.popupresizable.disabled = state;
+	formObj.popuptoolbar.disabled = state;
+	formObj.popupstatus.disabled = state;
+	formObj.popupreturn.disabled = state;
+	formObj.popupdependent.disabled = state;
+
+	setBrowserDisabled('popupurlbrowser', state);
+}
+
+function parseLink(link) {
+	link = link.replace(new RegExp(''', 'g'), "'");
+
+	var fnName = link.replace(new RegExp("\\s*([A-Za-z0-9\.]*)\\s*\\(.*", "gi"), "$1");
+
+	// Is function name a template function
+	var template = templates[fnName];
+	if (template) {
+		// Build regexp
+		var variableNames = template.match(new RegExp("'?\\$\\{[A-Za-z0-9\.]*\\}'?", "gi"));
+		var regExp = "\\s*[A-Za-z0-9\.]*\\s*\\(";
+		var replaceStr = "";
+		for (var i=0; i<variableNames.length; i++) {
+			// Is string value
+			if (variableNames[i].indexOf("'${") != -1)
+				regExp += "'(.*)'";
+			else // Number value
+				regExp += "([0-9]*)";
+
+			replaceStr += "$" + (i+1);
+
+			// Cleanup variable name
+			variableNames[i] = variableNames[i].replace(new RegExp("[^A-Za-z0-9]", "gi"), "");
+
+			if (i != variableNames.length-1) {
+				regExp += "\\s*,\\s*";
+				replaceStr += "<delim>";
+			} else
+				regExp += ".*";
+		}
+
+		regExp += "\\);?";
+
+		// Build variable array
+		var variables = [];
+		variables["_function"] = fnName;
+		var variableValues = link.replace(new RegExp(regExp, "gi"), replaceStr).split('<delim>');
+		for (var i=0; i<variableNames.length; i++)
+			variables[variableNames[i]] = variableValues[i];
+
+		return variables;
+	}
+
+	return null;
+}
+
+function parseOptions(opts) {
+	if (opts == null || opts == "")
+		return [];
+
+	// Cleanup the options
+	opts = opts.toLowerCase();
+	opts = opts.replace(/;/g, ",");
+	opts = opts.replace(/[^0-9a-z=,]/g, "");
+
+	var optionChunks = opts.split(',');
+	var options = [];
+
+	for (var i=0; i<optionChunks.length; i++) {
+		var parts = optionChunks[i].split('=');
+
+		if (parts.length == 2)
+			options[parts[0]] = parts[1];
+	}
+
+	return options;
+}
+
+function buildOnClick() {
+	var formObj = document.forms[0];
+
+	if (!formObj.ispopup.checked) {
+		formObj.onclick.value = "";
+		return;
+	}
+
+	var onclick = "window.open('";
+	var url = formObj.popupurl.value;
+
+	onclick += url + "','";
+	onclick += formObj.popupname.value + "','";
+
+	if (formObj.popuplocation.checked)
+		onclick += "location=yes,";
+
+	if (formObj.popupscrollbars.checked)
+		onclick += "scrollbars=yes,";
+
+	if (formObj.popupmenubar.checked)
+		onclick += "menubar=yes,";
+
+	if (formObj.popupresizable.checked)
+		onclick += "resizable=yes,";
+
+	if (formObj.popuptoolbar.checked)
+		onclick += "toolbar=yes,";
+
+	if (formObj.popupstatus.checked)
+		onclick += "status=yes,";
+
+	if (formObj.popupdependent.checked)
+		onclick += "dependent=yes,";
+
+	if (formObj.popupwidth.value != "")
+		onclick += "width=" + formObj.popupwidth.value + ",";
+
+	if (formObj.popupheight.value != "")
+		onclick += "height=" + formObj.popupheight.value + ",";
+
+	if (formObj.popupleft.value != "") {
+		if (formObj.popupleft.value != "c")
+			onclick += "left=" + formObj.popupleft.value + ",";
+		else
+			onclick += "left='+(screen.availWidth/2-" + (formObj.popupwidth.value/2) + ")+',";
+	}
+
+	if (formObj.popuptop.value != "") {
+		if (formObj.popuptop.value != "c")
+			onclick += "top=" + formObj.popuptop.value + ",";
+		else
+			onclick += "top='+(screen.availHeight/2-" + (formObj.popupheight.value/2) + ")+',";
+	}
+
+	if (onclick.charAt(onclick.length-1) == ',')
+		onclick = onclick.substring(0, onclick.length-1);
+
+	onclick += "');";
+
+	if (formObj.popupreturn.checked)
+		onclick += "return false;";
+
+	// tinyMCE.debug(onclick);
+
+	formObj.onclick.value = onclick;
+
+	if (formObj.href.value == "")
+		formObj.href.value = url;
+}
+
+function setAttrib(elm, attrib, value) {
+	var formObj = document.forms[0];
+	var valueElm = formObj.elements[attrib.toLowerCase()];
+	var dom = tinyMCEPopup.editor.dom;
+
+	if (typeof(value) == "undefined" || value == null) {
+		value = "";
+
+		if (valueElm)
+			value = valueElm.value;
+	}
+
+	// Clean up the style
+	if (attrib == 'style')
+		value = dom.serializeStyle(dom.parseStyle(value));
+
+	dom.setAttrib(elm, attrib, value);
+}
+
+function getAnchorListHTML(id, target) {
+	var inst = tinyMCEPopup.editor;
+	var nodes = inst.dom.select('a.mceItemAnchor,img.mceItemAnchor'), name, i;
+	var html = "";
+
+	html += '<select id="' + id + '" name="' + id + '" class="mceAnchorList" o2nfocus="tinyMCE.addSelectAccessibility(event, this, window);" onchange="this.form.' + target + '.value=';
+	html += 'this.options[this.selectedIndex].value;">';
+	html += '<option value="">---</option>';
+
+	for (i=0; i<nodes.length; i++) {
+		if ((name = inst.dom.getAttrib(nodes[i], "name")) != "")
+			html += '<option value="#' + name + '">' + name + '</option>';
+	}
+
+	html += '</select>';
+
+	return html;
+}
+
+function insertAction() {
+	var inst = tinyMCEPopup.editor;
+	var elm, elementArray, i;
+
+	elm = inst.selection.getNode();
+	checkPrefix(document.forms[0].href);
+
+	elm = inst.dom.getParent(elm, "A");
+
+	// Remove element if there is no href
+	if (!document.forms[0].href.value) {
+		tinyMCEPopup.execCommand("mceBeginUndoLevel");
+		i = inst.selection.getBookmark();
+		inst.dom.remove(elm, 1);
+		inst.selection.moveToBookmark(i);
+		tinyMCEPopup.execCommand("mceEndUndoLevel");
+		tinyMCEPopup.close();
+		return;
+	}
+
+	tinyMCEPopup.execCommand("mceBeginUndoLevel");
+
+	// Create new anchor elements
+	if (elm == null) {
+		inst.getDoc().execCommand("unlink", false, null);
+		tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1});
+
+		elementArray = tinymce.grep(inst.dom.select("a"), function(n) {return inst.dom.getAttrib(n, 'href') == '#mce_temp_url#';});
+		for (i=0; i<elementArray.length; i++)
+			setAllAttribs(elm = elementArray[i]);
+	} else
+		setAllAttribs(elm);
+
+	// Don't move caret if selection was image
+	if (elm.childNodes.length != 1 || elm.firstChild.nodeName != 'IMG') {
+		inst.focus();
+		inst.selection.select(elm);
+		inst.selection.collapse(0);
+		tinyMCEPopup.storeSelection();
+	}
+
+	tinyMCEPopup.execCommand("mceEndUndoLevel");
+	tinyMCEPopup.close();
+}
+
+function setAllAttribs(elm) {
+	var formObj = document.forms[0];
+	var href = formObj.href.value;
+	var target = getSelectValue(formObj, 'targetlist');
+
+	setAttrib(elm, 'href', href);
+	setAttrib(elm, 'title');
+	setAttrib(elm, 'target', target == '_self' ? '' : target);
+	setAttrib(elm, 'id');
+	setAttrib(elm, 'style');
+	setAttrib(elm, 'class', getSelectValue(formObj, 'classlist'));
+	setAttrib(elm, 'rel');
+	setAttrib(elm, 'rev');
+	setAttrib(elm, 'charset');
+	setAttrib(elm, 'hreflang');
+	setAttrib(elm, 'dir');
+	setAttrib(elm, 'lang');
+	setAttrib(elm, 'tabindex');
+	setAttrib(elm, 'accesskey');
+	setAttrib(elm, 'type');
+	setAttrib(elm, 'onfocus');
+	setAttrib(elm, 'onblur');
+	setAttrib(elm, 'onclick');
+	setAttrib(elm, 'ondblclick');
+	setAttrib(elm, 'onmousedown');
+	setAttrib(elm, 'onmouseup');
+	setAttrib(elm, 'onmouseover');
+	setAttrib(elm, 'onmousemove');
+	setAttrib(elm, 'onmouseout');
+	setAttrib(elm, 'onkeypress');
+	setAttrib(elm, 'onkeydown');
+	setAttrib(elm, 'onkeyup');
+
+	// Refresh in old MSIE
+	if (tinyMCE.isMSIE5)
+		elm.outerHTML = elm.outerHTML;
+}
+
+function getSelectValue(form_obj, field_name) {
+	var elm = form_obj.elements[field_name];
+
+	if (!elm || elm.options == null || elm.selectedIndex == -1)
+		return "";
+
+	return elm.options[elm.selectedIndex].value;
+}
+
+function getLinkListHTML(elm_id, target_form_element, onchange_func) {
+	if (typeof(tinyMCELinkList) == "undefined" || tinyMCELinkList.length == 0)
+		return "";
+
+	var html = "";
+
+	html += '<select id="' + elm_id + '" name="' + elm_id + '"';
+	html += ' class="mceLinkList" onfoc2us="tinyMCE.addSelectAccessibility(event, this, window);" onchange="this.form.' + target_form_element + '.value=';
+	html += 'this.options[this.selectedIndex].value;';
+
+	if (typeof(onchange_func) != "undefined")
+		html += onchange_func + '(\'' + target_form_element + '\',this.options[this.selectedIndex].text,this.options[this.selectedIndex].value);';
+
+	html += '"><option value="">---</option>';
+
+	for (var i=0; i<tinyMCELinkList.length; i++)
+		html += '<option value="' + tinyMCELinkList[i][1] + '">' + tinyMCELinkList[i][0] + '</option>';
+
+	html += '</select>';
+
+	return html;
+
+	// tinyMCE.debug('-- image list start --', html, '-- image list end --');
+}
+
+function getTargetListHTML(elm_id, target_form_element) {
+	var targets = tinyMCEPopup.getParam('theme_advanced_link_targets', '').split(';');
+	var html = '';
+
+	html += '<select id="' + elm_id + '" name="' + elm_id + '" onf2ocus="tinyMCE.addSelectAccessibility(event, this, window);" onchange="this.form.' + target_form_element + '.value=';
+	html += 'this.options[this.selectedIndex].value;">';
+	html += '<option value="_self">' + tinyMCEPopup.getLang('advlink_dlg.target_same') + '</option>';
+	html += '<option value="_blank">' + tinyMCEPopup.getLang('advlink_dlg.target_blank') + ' (_blank)</option>';
+	html += '<option value="_parent">' + tinyMCEPopup.getLang('advlink_dlg.target_parent') + ' (_parent)</option>';
+	html += '<option value="_top">' + tinyMCEPopup.getLang('advlink_dlg.target_top') + ' (_top)</option>';
+
+	for (var i=0; i<targets.length; i++) {
+		var key, value;
+
+		if (targets[i] == "")
+			continue;
+
+		key = targets[i].split('=')[0];
+		value = targets[i].split('=')[1];
+
+		html += '<option value="' + key + '">' + value + ' (' + key + ')</option>';
+	}
+
+	html += '</select>';
+
+	return html;
+}
+
+// While loading
+preinit();
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/advlink/langs/en_dlg.js b/media/js/tinymce/plugins/advlink/langs/en_dlg.js
new file mode 100644
index 0000000..c71ffbd
--- /dev/null
+++ b/media/js/tinymce/plugins/advlink/langs/en_dlg.js
@@ -0,0 +1,52 @@
+tinyMCE.addI18n('en.advlink_dlg',{
+title:"Insert/edit link",
+url:"Link URL",
+target:"Target",
+titlefield:"Title",
+is_email:"The URL you entered seems to be an email address, do you want to add the required mailto: prefix?",
+is_external:"The URL you entered seems to external link, do you want to add the required http:// prefix?",
+list:"Link list",
+general_tab:"General",
+popup_tab:"Popup",
+events_tab:"Events",
+advanced_tab:"Advanced",
+general_props:"General properties",
+popup_props:"Popup properties",
+event_props:"Events",
+advanced_props:"Advanced properties",
+popup_opts:"Options",
+anchor_names:"Anchors",
+target_same:"Open in this window / frame",
+target_parent:"Open in parent window / frame",
+target_top:"Open in top frame (replaces all frames)",
+target_blank:"Open in new window",
+popup:"Javascript popup",
+popup_url:"Popup URL",
+popup_name:"Window name",
+popup_return:"Insert 'return false'",
+popup_scrollbars:"Show scrollbars",
+popup_statusbar:"Show status bar",
+popup_toolbar:"Show toolbars",
+popup_menubar:"Show menu bar",
+popup_location:"Show location bar",
+popup_resizable:"Make window resizable",
+popup_dependent:"Dependent (Mozilla/Firefox only)",
+popup_size:"Size",
+popup_position:"Position (X/Y)",
+id:"Id",
+style:"Style",
+classes:"Classes",
+target_name:"Target name",
+langdir:"Language direction",
+target_langcode:"Target language",
+langcode:"Language code",
+encoding:"Target character encoding",
+mime:"Target MIME type",
+rel:"Relationship page to target",
+rev:"Relationship target to page",
+tabindex:"Tabindex",
+accesskey:"Accesskey",
+ltr:"Left to right",
+rtl:"Right to left",
+link_list:"Link list"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/advlink/link.htm b/media/js/tinymce/plugins/advlink/link.htm
new file mode 100644
index 0000000..cc8b0b8
--- /dev/null
+++ b/media/js/tinymce/plugins/advlink/link.htm
@@ -0,0 +1,338 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advlink_dlg.title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/validate.js"></script>
+	<script type="text/javascript" src="js/advlink.js"></script>
+	<link href="css/advlink.css" rel="stylesheet" type="text/css" />
+</head>
+<body id="advlink" style="display: none">
+    <form onsubmit="insertAction();return false;" action="#">
+		<div class="tabs">
+			<ul>
+				<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advlink_dlg.general_tab}</a></span></li>
+				<li id="popup_tab"><span><a href="javascript:mcTabs.displayTab('popup_tab','popup_panel');" onmousedown="return false;">{#advlink_dlg.popup_tab}</a></span></li>
+				<li id="events_tab"><span><a href="javascript:mcTabs.displayTab('events_tab','events_panel');" onmousedown="return false;">{#advlink_dlg.events_tab}</a></span></li>
+				<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#advlink_dlg.advanced_tab}</a></span></li>
+			</ul>
+		</div>
+
+		<div class="panel_wrapper">
+			<div id="general_panel" class="panel current">
+				<fieldset>
+					<legend>{#advlink_dlg.general_props}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+						  <td class="nowrap"><label id="hreflabel" for="href">{#advlink_dlg.url}</label></td>
+						  <td><table border="0" cellspacing="0" cellpadding="0">
+								<tr>
+								  <td><input id="href" name="href" type="text" class="mceFocus" value="" onchange="selectByValue(this.form,'linklisthref',this.value);" /></td>
+								  <td id="hrefbrowsercontainer"> </td>
+								</tr>
+							  </table></td>
+						</tr>
+						<tr id="linklisthrefrow">
+							<td class="column1"><label for="linklisthref">{#advlink_dlg.list}</label></td>
+							<td colspan="2" id="linklisthrefcontainer"><select id="linklisthref"><option value=""></option></select></td>
+						</tr>
+						<tr>
+							<td class="column1"><label for="anchorlist">{#advlink_dlg.anchor_names}</label></td>
+							<td colspan="2" id="anchorlistcontainer"><select id="anchorlist"><option value=""></option></select></td>
+						</tr>
+						<tr>
+							<td><label id="targetlistlabel" for="targetlist">{#advlink_dlg.target}</label></td>
+							<td id="targetlistcontainer"><select id="targetlist"><option value=""></option></select></td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label id="titlelabel" for="title">{#advlink_dlg.titlefield}</label></td>
+							<td><input id="title" name="title" type="text" value="" /></td>
+						</tr>
+						<tr>
+							<td><label id="classlabel" for="classlist">{#class_name}</label></td>
+							<td>
+								 <select id="classlist" name="classlist" onchange="changeClass();">
+									<option value="" selected="selected">{#not_set}</option>
+								 </select>
+							</td>
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+
+			<div id="popup_panel" class="panel">
+				<fieldset>
+					<legend>{#advlink_dlg.popup_props}</legend>
+
+					<input type="checkbox" id="ispopup" name="ispopup" class="radio" onclick="setPopupControlsDisabled(!this.checked);buildOnClick();" />
+					<label id="ispopuplabel" for="ispopup">{#advlink_dlg.popup}</label>
+
+					<table border="0" cellpadding="0" cellspacing="4">
+						<tr>
+							<td class="nowrap"><label for="popupurl">{#advlink_dlg.popup_url}</label> </td>
+							<td>
+								<table border="0" cellspacing="0" cellpadding="0">
+									<tr>
+										<td><input type="text" name="popupurl" id="popupurl" value="" onchange="buildOnClick();" /></td>
+										<td id="popupurlbrowsercontainer"> </td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="popupname">{#advlink_dlg.popup_name}</label> </td>
+							<td><input type="text" name="popupname" id="popupname" value="" onchange="buildOnClick();" /></td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label>{#advlink_dlg.popup_size}</label> </td>
+							<td class="nowrap">
+								<input type="text" id="popupwidth" name="popupwidth" value="" onchange="buildOnClick();" /> x
+								<input type="text" id="popupheight" name="popupheight" value="" onchange="buildOnClick();" /> px
+							</td>
+						</tr>
+						<tr>
+							<td class="nowrap" id="labelleft"><label>{#advlink_dlg.popup_position}</label> </td>
+							<td class="nowrap">
+								<input type="text" id="popupleft" name="popupleft" value="" onchange="buildOnClick();" /> /                                
+								<input type="text" id="popuptop" name="popuptop" value="" onchange="buildOnClick();" /> (c /c = center)
+							</td>
+						</tr>
+					</table>
+
+					<fieldset>
+						<legend>{#advlink_dlg.popup_opts}</legend>
+
+						<table border="0" cellpadding="0" cellspacing="4">
+							<tr>
+								<td><input type="checkbox" id="popuplocation" name="popuplocation" class="checkbox" onchange="buildOnClick();" /></td>
+								<td class="nowrap"><label id="popuplocationlabel" for="popuplocation">{#advlink_dlg.popup_location}</label></td>
+								<td><input type="checkbox" id="popupscrollbars" name="popupscrollbars" class="checkbox" onchange="buildOnClick();" /></td>
+								<td class="nowrap"><label id="popupscrollbarslabel" for="popupscrollbars">{#advlink_dlg.popup_scrollbars}</label></td>
+							</tr>
+							<tr>
+								<td><input type="checkbox" id="popupmenubar" name="popupmenubar" class="checkbox" onchange="buildOnClick();" /></td>
+								<td class="nowrap"><label id="popupmenubarlabel" for="popupmenubar">{#advlink_dlg.popup_menubar}</label></td>
+								<td><input type="checkbox" id="popupresizable" name="popupresizable" class="checkbox" onchange="buildOnClick();" /></td>
+								<td class="nowrap"><label id="popupresizablelabel" for="popupresizable">{#advlink_dlg.popup_resizable}</label></td>
+							</tr>
+							<tr>
+								<td><input type="checkbox" id="popuptoolbar" name="popuptoolbar" class="checkbox" onchange="buildOnClick();" /></td>
+								<td class="nowrap"><label id="popuptoolbarlabel" for="popuptoolbar">{#advlink_dlg.popup_toolbar}</label></td>
+								<td><input type="checkbox" id="popupdependent" name="popupdependent" class="checkbox" onchange="buildOnClick();" /></td>
+								<td class="nowrap"><label id="popupdependentlabel" for="popupdependent">{#advlink_dlg.popup_dependent}</label></td>
+							</tr>
+							<tr>
+								<td><input type="checkbox" id="popupstatus" name="popupstatus" class="checkbox" onchange="buildOnClick();" /></td>
+								<td class="nowrap"><label id="popupstatuslabel" for="popupstatus">{#advlink_dlg.popup_statusbar}</label></td>
+								<td><input type="checkbox" id="popupreturn" name="popupreturn" class="checkbox" onchange="buildOnClick();" checked="checked" /></td>
+								<td class="nowrap"><label id="popupreturnlabel" for="popupreturn">{#advlink_dlg.popup_return}</label></td>
+							</tr>
+						</table>
+					</fieldset>
+				</fieldset>
+			</div>
+
+			<div id="advanced_panel" class="panel">
+			<fieldset>
+					<legend>{#advlink_dlg.advanced_props}</legend>
+
+					<table border="0" cellpadding="0" cellspacing="4">
+						<tr>
+							<td class="column1"><label id="idlabel" for="id">{#advlink_dlg.id}</label></td> 
+							<td><input id="id" name="id" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td><label id="stylelabel" for="style">{#advlink_dlg.style}</label></td>
+							<td><input type="text" id="style" name="style" value="" /></td>
+						</tr>
+
+						<tr>
+							<td><label id="classeslabel" for="classes">{#advlink_dlg.classes}</label></td>
+							<td><input type="text" id="classes" name="classes" value="" onchange="selectByValue(this.form,'classlist',this.value,true);" /></td>
+						</tr>
+
+						<tr>
+							<td><label id="targetlabel" for="target">{#advlink_dlg.target_name}</label></td>
+							<td><input type="text" id="target" name="target" value="" onchange="selectByValue(this.form,'targetlist',this.value,true);" /></td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="dirlabel" for="dir">{#advlink_dlg.langdir}</label></td> 
+							<td>
+								<select id="dir" name="dir"> 
+										<option value="">{#not_set}</option> 
+										<option value="ltr">{#advlink_dlg.ltr}</option> 
+										<option value="rtl">{#advlink_dlg.rtl}</option> 
+								</select>
+							</td> 
+						</tr>
+
+						<tr>
+							<td><label id="hreflanglabel" for="hreflang">{#advlink_dlg.target_langcode}</label></td>
+							<td><input type="text" id="hreflang" name="hreflang" value="" /></td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="langlabel" for="lang">{#advlink_dlg.langcode}</label></td> 
+							<td>
+								<input id="lang" name="lang" type="text" value="" />
+							</td> 
+						</tr>
+
+						<tr>
+							<td><label id="charsetlabel" for="charset">{#advlink_dlg.encoding}</label></td>
+							<td><input type="text" id="charset" name="charset" value="" /></td>
+						</tr>
+
+						<tr>
+							<td><label id="typelabel" for="type">{#advlink_dlg.mime}</label></td>
+							<td><input type="text" id="type" name="type" value="" /></td>
+						</tr>
+
+						<tr>
+							<td><label id="rellabel" for="rel">{#advlink_dlg.rel}</label></td>
+							<td><select id="rel" name="rel"> 
+									<option value="">{#not_set}</option> 
+									<option value="lightbox">Lightbox</option> 
+									<option value="alternate">Alternate</option> 
+									<option value="designates">Designates</option> 
+									<option value="stylesheet">Stylesheet</option> 
+									<option value="start">Start</option> 
+									<option value="next">Next</option> 
+									<option value="prev">Prev</option> 
+									<option value="contents">Contents</option> 
+									<option value="index">Index</option> 
+									<option value="glossary">Glossary</option> 
+									<option value="copyright">Copyright</option> 
+									<option value="chapter">Chapter</option> 
+									<option value="subsection">Subsection</option> 
+									<option value="appendix">Appendix</option> 
+									<option value="help">Help</option> 
+									<option value="bookmark">Bookmark</option>
+									<option value="nofollow">No Follow</option>
+									<option value="tag">Tag</option>
+								</select> 
+							</td>
+						</tr>
+
+						<tr>
+							<td><label id="revlabel" for="rev">{#advlink_dlg.rev}</label></td>
+							<td><select id="rev" name="rev"> 
+									<option value="">{#not_set}</option> 
+									<option value="alternate">Alternate</option> 
+									<option value="designates">Designates</option> 
+									<option value="stylesheet">Stylesheet</option> 
+									<option value="start">Start</option> 
+									<option value="next">Next</option> 
+									<option value="prev">Prev</option> 
+									<option value="contents">Contents</option> 
+									<option value="index">Index</option> 
+									<option value="glossary">Glossary</option> 
+									<option value="copyright">Copyright</option> 
+									<option value="chapter">Chapter</option> 
+									<option value="subsection">Subsection</option> 
+									<option value="appendix">Appendix</option> 
+									<option value="help">Help</option> 
+									<option value="bookmark">Bookmark</option> 
+								</select> 
+							</td>
+						</tr>
+
+						<tr>
+							<td><label id="tabindexlabel" for="tabindex">{#advlink_dlg.tabindex}</label></td>
+							<td><input type="text" id="tabindex" name="tabindex" value="" /></td>
+						</tr>
+
+						<tr>
+							<td><label id="accesskeylabel" for="accesskey">{#advlink_dlg.accesskey}</label></td>
+							<td><input type="text" id="accesskey" name="accesskey" value="" /></td>
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+
+			<div id="events_panel" class="panel">
+			<fieldset>
+					<legend>{#advlink_dlg.event_props}</legend>
+
+					<table border="0" cellpadding="0" cellspacing="4">
+						<tr>
+							<td class="column1"><label for="onfocus">onfocus</label></td> 
+							<td><input id="onfocus" name="onfocus" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onblur">onblur</label></td> 
+							<td><input id="onblur" name="onblur" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onclick">onclick</label></td> 
+							<td><input id="onclick" name="onclick" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="ondblclick">ondblclick</label></td> 
+							<td><input id="ondblclick" name="ondblclick" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onmousedown">onmousedown</label></td> 
+							<td><input id="onmousedown" name="onmousedown" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onmouseup">onmouseup</label></td> 
+							<td><input id="onmouseup" name="onmouseup" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onmouseover">onmouseover</label></td> 
+							<td><input id="onmouseover" name="onmouseover" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onmousemove">onmousemove</label></td> 
+							<td><input id="onmousemove" name="onmousemove" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onmouseout">onmouseout</label></td> 
+							<td><input id="onmouseout" name="onmouseout" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onkeypress">onkeypress</label></td> 
+							<td><input id="onkeypress" name="onkeypress" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onkeydown">onkeydown</label></td> 
+							<td><input id="onkeydown" name="onkeydown" type="text" value="" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="onkeyup">onkeyup</label></td> 
+							<td><input id="onkeyup" name="onkeyup" type="text" value="" /></td> 
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+		</div>
+
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" id="insert" name="insert" value="{#insert}" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+    </form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/autoresize/editor_plugin.js b/media/js/tinymce/plugins/autoresize/editor_plugin.js
new file mode 100644
index 0000000..220b84a
--- /dev/null
+++ b/media/js/tinymce/plugins/autoresize/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.AutoResizePlugin",{init:function(a,c){var d=this;if(a.getParam("fullscreen_is_enabled")){return}function b(){var h=a.getDoc(),e=h.body,j=h.documentElement,g=tinymce.DOM,i=d.autoresize_min_height,f;f=tinymce.isIE?e.scrollHeight:j.offsetHeight;if(f>d.autoresize_min_height){i=f}g.setStyle(g.get(a.id+"_ifr"),"height",i+"px");if(d.throbbing){a.setProgressState(false);a.setProgressState(true)}}d.editor=a;d.autoresize_min_height=a.getElement().offsetH [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/autoresize/editor_plugin_src.js b/media/js/tinymce/plugins/autoresize/editor_plugin_src.js
new file mode 100644
index 0000000..8b2f374
--- /dev/null
+++ b/media/js/tinymce/plugins/autoresize/editor_plugin_src.js
@@ -0,0 +1,114 @@
+/**
+ * $Id: editor_plugin_src.js 539 2008-01-14 19:08:58Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	/**
+	 * Auto Resize
+	 * 
+	 * This plugin automatically resizes the content area to fit its content height.
+	 * It will retain a minimum height, which is the height of the content area when
+	 * it's initialized.
+	 */
+	tinymce.create('tinymce.plugins.AutoResizePlugin', {
+		/**
+		 * Initializes the plugin, this will be executed after the plugin has been created.
+		 * This call is done before the editor instance has finished it's initialization so use the onInit event
+		 * of the editor instance to intercept that event.
+		 *
+		 * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
+		 * @param {string} url Absolute URL to where the plugin is located.
+		 */
+		init : function(ed, url) {
+			var t = this;
+
+			if (ed.getParam('fullscreen_is_enabled'))
+				return;
+
+			/**
+			 * This method gets executed each time the editor needs to resize.
+			 */
+			function resize() {
+				var d = ed.getDoc(), b = d.body, de = d.documentElement, DOM = tinymce.DOM, resizeHeight = t.autoresize_min_height, myHeight;
+
+				// Get height differently depending on the browser used
+				myHeight = tinymce.isIE ? b.scrollHeight : de.offsetHeight;
+
+				// Don't make it smaller than the minimum height
+				if (myHeight > t.autoresize_min_height)
+					resizeHeight = myHeight;
+
+				// Resize content element
+				DOM.setStyle(DOM.get(ed.id + '_ifr'), 'height', resizeHeight + 'px');
+
+				// if we're throbbing, we'll re-throb to match the new size
+				if (t.throbbing) {
+					ed.setProgressState(false);
+					ed.setProgressState(true);
+				}
+			};
+
+			t.editor = ed;
+
+			// Define minimum height
+			t.autoresize_min_height = ed.getElement().offsetHeight;
+
+			// Things to do when the editor is ready
+			ed.onInit.add(function(ed, l) {
+				// Show throbber until content area is resized properly
+				ed.setProgressState(true);
+				t.throbbing = true;
+
+				// Hide scrollbars
+				ed.getBody().style.overflowY = "hidden";
+			});
+
+			// Add appropriate listeners for resizing content area
+			ed.onChange.add(resize);
+			ed.onSetContent.add(resize);
+			ed.onPaste.add(resize);
+			ed.onKeyUp.add(resize);
+			ed.onPostRender.add(resize);
+
+			ed.onLoadContent.add(function(ed, l) {
+				resize();
+
+				// Because the content area resizes when its content CSS loads,
+				// and we can't easily add a listener to its onload event,
+				// we'll just trigger a resize after a short loading period
+				setTimeout(function() {
+					resize();
+
+					// Disable throbber
+					ed.setProgressState(false);
+					t.throbbing = false;
+				}, 1250);
+			});
+
+			// Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceExample');
+			ed.addCommand('mceAutoResize', resize);
+		},
+
+		/**
+		 * Returns information about the plugin as a name/value array.
+		 * The current keys are longname, author, authorurl, infourl and version.
+		 *
+		 * @return {Object} Name/value array containing information about the plugin.
+		 */
+		getInfo : function() {
+			return {
+				longname : 'Auto Resize',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('autoresize', tinymce.plugins.AutoResizePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/autosave/editor_plugin.js b/media/js/tinymce/plugins/autosave/editor_plugin.js
new file mode 100644
index 0000000..091a063
--- /dev/null
+++ b/media/js/tinymce/plugins/autosave/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.AutoSavePlugin",{init:function(a,b){var c=this;c.editor=a;window.onbeforeunload=tinymce.plugins.AutoSavePlugin._beforeUnloadHandler},getInfo:function(){return{longname:"Auto save",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave",version:tinymce.majorVersion+"."+tinymce.minorVersion}},"static":{_beforeUnloadHandler:function(){var a;tinymce.each(tinyMCE.e [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/autosave/editor_plugin_src.js b/media/js/tinymce/plugins/autosave/editor_plugin_src.js
new file mode 100644
index 0000000..3c4325a
--- /dev/null
+++ b/media/js/tinymce/plugins/autosave/editor_plugin_src.js
@@ -0,0 +1,51 @@
+/**
+ * $Id: editor_plugin_src.js 520 2008-01-07 16:30:32Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.AutoSavePlugin', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			window.onbeforeunload = tinymce.plugins.AutoSavePlugin._beforeUnloadHandler;
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Auto save',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private plugin internal methods
+
+		'static' : {
+			_beforeUnloadHandler : function() {
+				var msg;
+
+				tinymce.each(tinyMCE.editors, function(ed) {
+					if (ed.getParam("fullscreen_is_enabled"))
+						return;
+
+					if (ed.isDirty()) {
+						msg = ed.getLang("autosave.unload_msg");
+						return false;
+					}
+				});
+
+				return msg;
+			}
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('autosave', tinymce.plugins.AutoSavePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/bbcode/editor_plugin.js b/media/js/tinymce/plugins/bbcode/editor_plugin.js
new file mode 100644
index 0000000..930fdff
--- /dev/null
+++ b/media/js/tinymce/plugins/bbcode/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.BBCodePlugin",{init:function(a,b){var d=this,c=a.getParam("bbcode_dialect","punbb").toLowerCase();a.onBeforeSetContent.add(function(e,f){f.content=d["_"+c+"_bbcode2html"](f.content)});a.onPostProcess.add(function(e,f){if(f.set){f.content=d["_"+c+"_bbcode2html"](f.content)}if(f.get){f.content=d["_"+c+"_html2bbcode"](f.content)}})},getInfo:function(){return{longname:"BBCode Plugin",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/bbcode/editor_plugin_src.js b/media/js/tinymce/plugins/bbcode/editor_plugin_src.js
new file mode 100644
index 0000000..1d7493e
--- /dev/null
+++ b/media/js/tinymce/plugins/bbcode/editor_plugin_src.js
@@ -0,0 +1,117 @@
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.BBCodePlugin', {
+		init : function(ed, url) {
+			var t = this, dialect = ed.getParam('bbcode_dialect', 'punbb').toLowerCase();
+
+			ed.onBeforeSetContent.add(function(ed, o) {
+				o.content = t['_' + dialect + '_bbcode2html'](o.content);
+			});
+
+			ed.onPostProcess.add(function(ed, o) {
+				if (o.set)
+					o.content = t['_' + dialect + '_bbcode2html'](o.content);
+
+				if (o.get)
+					o.content = t['_' + dialect + '_html2bbcode'](o.content);
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'BBCode Plugin',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private methods
+
+		// HTML -> BBCode in PunBB dialect
+		_punbb_html2bbcode : function(s) {
+			s = tinymce.trim(s);
+
+			function rep(re, str) {
+				s = s.replace(re, str);
+			};
+
+			// example: <strong> to [b]
+			rep(/<a.*?href=\"(.*?)\".*?>(.*?)<\/a>/gi,"[url=$1]$2[/url]");
+			rep(/<font.*?color=\"(.*?)\".*?class=\"codeStyle\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");
+			rep(/<font.*?color=\"(.*?)\".*?class=\"quoteStyle\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");
+			rep(/<font.*?class=\"codeStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[code][color=$1]$2[/color][/code]");
+			rep(/<font.*?class=\"quoteStyle\".*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[quote][color=$1]$2[/color][/quote]");
+			rep(/<span style=\"color: ?(.*?);\">(.*?)<\/span>/gi,"[color=$1]$2[/color]");
+			rep(/<font.*?color=\"(.*?)\".*?>(.*?)<\/font>/gi,"[color=$1]$2[/color]");
+			rep(/<span style=\"font-size:(.*?);\">(.*?)<\/span>/gi,"[size=$1]$2[/size]");
+			rep(/<font>(.*?)<\/font>/gi,"$1");
+			rep(/<img.*?src=\"(.*?)\".*?\/>/gi,"[img]$1[/img]");
+			rep(/<span class=\"codeStyle\">(.*?)<\/span>/gi,"[code]$1[/code]");
+			rep(/<span class=\"quoteStyle\">(.*?)<\/span>/gi,"[quote]$1[/quote]");
+			rep(/<strong class=\"codeStyle\">(.*?)<\/strong>/gi,"[code][b]$1[/b][/code]");
+			rep(/<strong class=\"quoteStyle\">(.*?)<\/strong>/gi,"[quote][b]$1[/b][/quote]");
+			rep(/<em class=\"codeStyle\">(.*?)<\/em>/gi,"[code][i]$1[/i][/code]");
+			rep(/<em class=\"quoteStyle\">(.*?)<\/em>/gi,"[quote][i]$1[/i][/quote]");
+			rep(/<u class=\"codeStyle\">(.*?)<\/u>/gi,"[code][u]$1[/u][/code]");
+			rep(/<u class=\"quoteStyle\">(.*?)<\/u>/gi,"[quote][u]$1[/u][/quote]");
+			rep(/<\/(strong|b)>/gi,"[/b]");
+			rep(/<(strong|b)>/gi,"[b]");
+			rep(/<\/(em|i)>/gi,"[/i]");
+			rep(/<(em|i)>/gi,"[i]");
+			rep(/<\/u>/gi,"[/u]");
+			rep(/<span style=\"text-decoration: ?underline;\">(.*?)<\/span>/gi,"[u]$1[/u]");
+			rep(/<u>/gi,"[u]");
+			rep(/<blockquote[^>]*>/gi,"[quote]");
+			rep(/<\/blockquote>/gi,"[/quote]");
+			rep(/<br \/>/gi,"\n");
+			rep(/<br\/>/gi,"\n");
+			rep(/<br>/gi,"\n");
+			rep(/<p>/gi,"");
+			rep(/<\/p>/gi,"\n");
+			rep(/ /gi," ");
+			rep(/"/gi,"\"");
+			rep(/</gi,"<");
+			rep(/>/gi,">");
+			rep(/&/gi,"&");
+
+			return s; 
+		},
+
+		// BBCode -> HTML from PunBB dialect
+		_punbb_bbcode2html : function(s) {
+			s = tinymce.trim(s);
+
+			function rep(re, str) {
+				s = s.replace(re, str);
+			};
+
+			// example: [b] to <strong>
+			rep(/\n/gi,"<br />");
+			rep(/\[b\]/gi,"<strong>");
+			rep(/\[\/b\]/gi,"</strong>");
+			rep(/\[i\]/gi,"<em>");
+			rep(/\[\/i\]/gi,"</em>");
+			rep(/\[u\]/gi,"<u>");
+			rep(/\[\/u\]/gi,"</u>");
+			rep(/\[url=([^\]]+)\](.*?)\[\/url\]/gi,"<a href=\"$1\">$2</a>");
+			rep(/\[url\](.*?)\[\/url\]/gi,"<a href=\"$1\">$1</a>");
+			rep(/\[img\](.*?)\[\/img\]/gi,"<img src=\"$1\" />");
+			rep(/\[color=(.*?)\](.*?)\[\/color\]/gi,"<font color=\"$1\">$2</font>");
+			rep(/\[code\](.*?)\[\/code\]/gi,"<span class=\"codeStyle\">$1</span> ");
+			rep(/\[quote.*?\](.*?)\[\/quote\]/gi,"<span class=\"quoteStyle\">$1</span> ");
+
+			return s; 
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('bbcode', tinymce.plugins.BBCodePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/contextmenu/editor_plugin.js b/media/js/tinymce/plugins/contextmenu/editor_plugin.js
new file mode 100644
index 0000000..24ee2eb
--- /dev/null
+++ b/media/js/tinymce/plugins/contextmenu/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.dom.Event,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.ContextMenu",{init:function(d){var f=this;f.editor=d;f.onContextMenu=new tinymce.util.Dispatcher(this);d.onContextMenu.add(function(g,h){if(!h.ctrlKey){f._getMenu(g).showMenu(h.clientX,h.clientY);a.add(g.getDoc(),"click",e);a.cancel(h)}});function e(){if(f._menu){f._menu.removeAll();f._menu.destroy();a.remove(d.getDoc(),"click",e)}}d.onMouseDown.add(e);d.onKeyDown.add(e)},getInfo:function(){r [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/contextmenu/editor_plugin_src.js b/media/js/tinymce/plugins/contextmenu/editor_plugin_src.js
new file mode 100644
index 0000000..a2c1866
--- /dev/null
+++ b/media/js/tinymce/plugins/contextmenu/editor_plugin_src.js
@@ -0,0 +1,95 @@
+/**
+ * $Id: editor_plugin_src.js 848 2008-05-15 11:54:40Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var Event = tinymce.dom.Event, each = tinymce.each, DOM = tinymce.DOM;
+
+	tinymce.create('tinymce.plugins.ContextMenu', {
+		init : function(ed) {
+			var t = this;
+
+			t.editor = ed;
+			t.onContextMenu = new tinymce.util.Dispatcher(this);
+
+			ed.onContextMenu.add(function(ed, e) {
+				if (!e.ctrlKey) {
+					t._getMenu(ed).showMenu(e.clientX, e.clientY);
+					Event.add(ed.getDoc(), 'click', hide);
+					Event.cancel(e);
+				}
+			});
+
+			function hide() {
+				if (t._menu) {
+					t._menu.removeAll();
+					t._menu.destroy();
+					Event.remove(ed.getDoc(), 'click', hide);
+				}
+			};
+
+			ed.onMouseDown.add(hide);
+			ed.onKeyDown.add(hide);
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Contextmenu',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		_getMenu : function(ed) {
+			var t = this, m = t._menu, se = ed.selection, col = se.isCollapsed(), el = se.getNode() || ed.getBody(), am, p1, p2;
+
+			if (m) {
+				m.removeAll();
+				m.destroy();
+			}
+
+			p1 = DOM.getPos(ed.getContentAreaContainer());
+			p2 = DOM.getPos(ed.getContainer());
+
+			m = ed.controlManager.createDropMenu('contextmenu', {
+				offset_x : p1.x + ed.getParam('contextmenu_offset_x', 0),
+				offset_y : p1.y + ed.getParam('contextmenu_offset_y', 0),
+				constrain : 1
+			});
+
+			t._menu = m;
+
+			m.add({title : 'advanced.cut_desc', icon : 'cut', cmd : 'Cut'}).setDisabled(col);
+			m.add({title : 'advanced.copy_desc', icon : 'copy', cmd : 'Copy'}).setDisabled(col);
+			m.add({title : 'advanced.paste_desc', icon : 'paste', cmd : 'Paste'});
+
+			if ((el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) || !col) {
+				m.addSeparator();
+				m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
+				m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
+			}
+
+			m.addSeparator();
+			m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
+
+			m.addSeparator();
+			am = m.addMenu({title : 'contextmenu.align'});
+			am.add({title : 'contextmenu.left', icon : 'justifyleft', cmd : 'JustifyLeft'});
+			am.add({title : 'contextmenu.center', icon : 'justifycenter', cmd : 'JustifyCenter'});
+			am.add({title : 'contextmenu.right', icon : 'justifyright', cmd : 'JustifyRight'});
+			am.add({title : 'contextmenu.full', icon : 'justifyfull', cmd : 'JustifyFull'});
+
+			t.onContextMenu.dispatch(t, m, el, col);
+
+			return m;
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('contextmenu', tinymce.plugins.ContextMenu);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/directionality/editor_plugin.js b/media/js/tinymce/plugins/directionality/editor_plugin.js
new file mode 100644
index 0000000..bce8e73
--- /dev/null
+++ b/media/js/tinymce/plugins/directionality/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.Directionality",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceDirectionLTR",function(){var d=a.dom.getParent(a.selection.getNode(),a.dom.isBlock);if(d){if(a.dom.getAttrib(d,"dir")!="ltr"){a.dom.setAttrib(d,"dir","ltr")}else{a.dom.setAttrib(d,"dir","")}}a.nodeChanged()});a.addCommand("mceDirectionRTL",function(){var d=a.dom.getParent(a.selection.getNode(),a.dom.isBlock);if(d){if(a.dom.getAttrib(d,"dir")!="rtl"){a.dom.setAttrib(d,"di [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/directionality/editor_plugin_src.js b/media/js/tinymce/plugins/directionality/editor_plugin_src.js
new file mode 100644
index 0000000..81818e3
--- /dev/null
+++ b/media/js/tinymce/plugins/directionality/editor_plugin_src.js
@@ -0,0 +1,79 @@
+/**
+ * $Id: editor_plugin_src.js 520 2008-01-07 16:30:32Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.Directionality', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			ed.addCommand('mceDirectionLTR', function() {
+				var e = ed.dom.getParent(ed.selection.getNode(), ed.dom.isBlock);
+
+				if (e) {
+					if (ed.dom.getAttrib(e, "dir") != "ltr")
+						ed.dom.setAttrib(e, "dir", "ltr");
+					else
+						ed.dom.setAttrib(e, "dir", "");
+				}
+
+				ed.nodeChanged();
+			});
+
+			ed.addCommand('mceDirectionRTL', function() {
+				var e = ed.dom.getParent(ed.selection.getNode(), ed.dom.isBlock);
+
+				if (e) {
+					if (ed.dom.getAttrib(e, "dir") != "rtl")
+						ed.dom.setAttrib(e, "dir", "rtl");
+					else
+						ed.dom.setAttrib(e, "dir", "");
+				}
+
+				ed.nodeChanged();
+			});
+
+			ed.addButton('ltr', {title : 'directionality.ltr_desc', cmd : 'mceDirectionLTR'});
+			ed.addButton('rtl', {title : 'directionality.rtl_desc', cmd : 'mceDirectionRTL'});
+
+			ed.onNodeChange.add(t._nodeChange, t);
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Directionality',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/directionality',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private methods
+
+		_nodeChange : function(ed, cm, n) {
+			var dom = ed.dom, dir;
+
+			n = dom.getParent(n, dom.isBlock);
+			if (!n) {
+				cm.setDisabled('ltr', 1);
+				cm.setDisabled('rtl', 1);
+				return;
+			}
+
+			dir = dom.getAttrib(n, 'dir');
+			cm.setActive('ltr', dir == "ltr");
+			cm.setDisabled('ltr', 0);
+			cm.setActive('rtl', dir == "rtl");
+			cm.setDisabled('rtl', 0);
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('directionality', tinymce.plugins.Directionality);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/emotions/editor_plugin.js b/media/js/tinymce/plugins/emotions/editor_plugin.js
new file mode 100644
index 0000000..4783bc3
--- /dev/null
+++ b/media/js/tinymce/plugins/emotions/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.EmotionsPlugin",{init:function(a,b){a.addCommand("mceEmotion",function(){a.windowManager.open({file:b+"/emotions.htm",width:250+parseInt(a.getLang("emotions.delta_width",0)),height:160+parseInt(a.getLang("emotions.delta_height",0)),inline:1},{plugin_url:b})});a.addButton("emotions",{title:"emotions.emotions_desc",cmd:"mceEmotion"})},getInfo:function(){return{longname:"Emotions",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.c [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/emotions/editor_plugin_src.js b/media/js/tinymce/plugins/emotions/editor_plugin_src.js
new file mode 100644
index 0000000..df0d370
--- /dev/null
+++ b/media/js/tinymce/plugins/emotions/editor_plugin_src.js
@@ -0,0 +1,40 @@
+/**
+ * $Id: editor_plugin_src.js 520 2008-01-07 16:30:32Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.EmotionsPlugin', {
+		init : function(ed, url) {
+			// Register commands
+			ed.addCommand('mceEmotion', function() {
+				ed.windowManager.open({
+					file : url + '/emotions.htm',
+					width : 250 + parseInt(ed.getLang('emotions.delta_width', 0)),
+					height : 160 + parseInt(ed.getLang('emotions.delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			// Register buttons
+			ed.addButton('emotions', {title : 'emotions.emotions_desc', cmd : 'mceEmotion'});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Emotions',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/emotions',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('emotions', tinymce.plugins.EmotionsPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/emotions/emotions.htm b/media/js/tinymce/plugins/emotions/emotions.htm
new file mode 100644
index 0000000..55a1d72
--- /dev/null
+++ b/media/js/tinymce/plugins/emotions/emotions.htm
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#emotions_dlg.title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/emotions.js"></script>
+</head>
+<body style="display: none">
+	<div align="center">
+		<div class="title">{#emotions_dlg.title}:<br /><br /></div>
+
+		<table border="0" cellspacing="0" cellpadding="4">
+		  <tr>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-cool.gif','emotions_dlg.cool');"><img src="img/smiley-cool.gif" width="18" height="18" border="0" alt="{#emotions_dlg.cool}" title="{#emotions_dlg.cool}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-cry.gif','emotions_dlg.cry');"><img src="img/smiley-cry.gif" width="18" height="18" border="0" alt="{#emotions_dlg.cry}" title="{#emotions_dlg.cry}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-embarassed.gif','emotions_dlg.embarassed');"><img src="img/smiley-embarassed.gif" width="18" height="18" border="0" alt="{#emotions_dlg.embarassed}" title="{#emotions_dlg.embarassed}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-foot-in-mouth.gif','emotions_dlg.foot_in_mouth');"><img src="img/smiley-foot-in-mouth.gif" width="18" height="18" border="0" alt="{#emotions_dlg.foot_in_mouth}" title="{#emotions_dlg.foot_in_mouth}" /></a></td>
+		  </tr>
+		  <tr>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-frown.gif','emotions_dlg.frown');"><img src="img/smiley-frown.gif" width="18" height="18" border="0" alt="{#emotions_dlg.frown}" title="{#emotions_dlg.frown}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-innocent.gif','emotions_dlg.innocent');"><img src="img/smiley-innocent.gif" width="18" height="18" border="0" alt="{#emotions_dlg.innocent}" title="{#emotions_dlg.innocent}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-kiss.gif','emotions_dlg.kiss');"><img src="img/smiley-kiss.gif" width="18" height="18" border="0" alt="{#emotions_dlg.kiss}" title="{#emotions_dlg.kiss}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-laughing.gif','emotions_dlg.laughing');"><img src="img/smiley-laughing.gif" width="18" height="18" border="0" alt="{#emotions_dlg.laughing}" title="{#emotions_dlg.laughing}" /></a></td>
+		  </tr>
+		  <tr>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-money-mouth.gif','emotions_dlg.money_mouth');"><img src="img/smiley-money-mouth.gif" width="18" height="18" border="0" alt="{#emotions_dlg.money_mouth}" title="{#emotions_dlg.money_mouth}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-sealed.gif','emotions_dlg.sealed');"><img src="img/smiley-sealed.gif" width="18" height="18" border="0" alt="{#emotions_dlg.sealed}" title="{#emotions_dlg.sealed}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-smile.gif','emotions_dlg.smile');"><img src="img/smiley-smile.gif" width="18" height="18" border="0" alt="{#emotions_dlg.smile}" title="{#emotions_dlg.smile}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-surprised.gif','emotions_dlg.surprised');"><img src="img/smiley-surprised.gif" width="18" height="18" border="0" alt="{#emotions_dlg.surprised}" title="{#emotions_dlg.surprised}" /></a></td>
+		  </tr>
+		  <tr>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-tongue-out.gif','emotions_dlg.tongue_out');"><img src="img/smiley-tongue-out.gif" width="18" height="18" border="0" alt="{#emotions_dlg.tongue-out}" title="{#emotions_dlg.tongue_out}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-undecided.gif','emotions_dlg.undecided');"><img src="img/smiley-undecided.gif" width="18" height="18" border="0" alt="{#emotions_dlg.undecided}" title="{#emotions_dlg.undecided}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-wink.gif','emotions_dlg.wink');"><img src="img/smiley-wink.gif" width="18" height="18" border="0" alt="{#emotions_dlg.wink}" title="{#emotions_dlg.wink}" /></a></td>
+			<td><a href="javascript:EmotionsDialog.insert('smiley-yell.gif','emotions_dlg.yell');"><img src="img/smiley-yell.gif" width="18" height="18" border="0" alt="{#emotions_dlg.yell}" title="{#emotions_dlg.yell}" /></a></td>
+		  </tr>
+		</table>
+	</div>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-cool.gif b/media/js/tinymce/plugins/emotions/img/smiley-cool.gif
new file mode 100644
index 0000000..ba90cc3
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-cool.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-cry.gif b/media/js/tinymce/plugins/emotions/img/smiley-cry.gif
new file mode 100644
index 0000000..74d897a
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-cry.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-embarassed.gif b/media/js/tinymce/plugins/emotions/img/smiley-embarassed.gif
new file mode 100644
index 0000000..963a96b
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-embarassed.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-foot-in-mouth.gif b/media/js/tinymce/plugins/emotions/img/smiley-foot-in-mouth.gif
new file mode 100644
index 0000000..16f68cc
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-foot-in-mouth.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-frown.gif b/media/js/tinymce/plugins/emotions/img/smiley-frown.gif
new file mode 100644
index 0000000..716f55e
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-frown.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-innocent.gif b/media/js/tinymce/plugins/emotions/img/smiley-innocent.gif
new file mode 100644
index 0000000..334d49e
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-innocent.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-kiss.gif b/media/js/tinymce/plugins/emotions/img/smiley-kiss.gif
new file mode 100644
index 0000000..4efd549
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-kiss.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-laughing.gif b/media/js/tinymce/plugins/emotions/img/smiley-laughing.gif
new file mode 100644
index 0000000..1606c11
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-laughing.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-money-mouth.gif b/media/js/tinymce/plugins/emotions/img/smiley-money-mouth.gif
new file mode 100644
index 0000000..ca2451e
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-money-mouth.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-sealed.gif b/media/js/tinymce/plugins/emotions/img/smiley-sealed.gif
new file mode 100644
index 0000000..b33d3cc
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-sealed.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-smile.gif b/media/js/tinymce/plugins/emotions/img/smiley-smile.gif
new file mode 100644
index 0000000..e6a9e60
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-smile.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-surprised.gif b/media/js/tinymce/plugins/emotions/img/smiley-surprised.gif
new file mode 100644
index 0000000..cb99cdd
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-surprised.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-tongue-out.gif b/media/js/tinymce/plugins/emotions/img/smiley-tongue-out.gif
new file mode 100644
index 0000000..2075dc1
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-tongue-out.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-undecided.gif b/media/js/tinymce/plugins/emotions/img/smiley-undecided.gif
new file mode 100644
index 0000000..bef7e25
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-undecided.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-wink.gif b/media/js/tinymce/plugins/emotions/img/smiley-wink.gif
new file mode 100644
index 0000000..9faf1af
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-wink.gif differ
diff --git a/media/js/tinymce/plugins/emotions/img/smiley-yell.gif b/media/js/tinymce/plugins/emotions/img/smiley-yell.gif
new file mode 100644
index 0000000..648e6e8
Binary files /dev/null and b/media/js/tinymce/plugins/emotions/img/smiley-yell.gif differ
diff --git a/media/js/tinymce/plugins/emotions/js/emotions.js b/media/js/tinymce/plugins/emotions/js/emotions.js
new file mode 100644
index 0000000..c549367
--- /dev/null
+++ b/media/js/tinymce/plugins/emotions/js/emotions.js
@@ -0,0 +1,22 @@
+tinyMCEPopup.requireLangPack();
+
+var EmotionsDialog = {
+	init : function(ed) {
+		tinyMCEPopup.resizeToInnerSize();
+	},
+
+	insert : function(file, title) {
+		var ed = tinyMCEPopup.editor, dom = ed.dom;
+
+		tinyMCEPopup.execCommand('mceInsertContent', false, dom.createHTML('img', {
+			src : tinyMCEPopup.getWindowArg('plugin_url') + '/img/' + file,
+			alt : ed.getLang(title),
+			title : ed.getLang(title),
+			border : 0
+		}));
+
+		tinyMCEPopup.close();
+	}
+};
+
+tinyMCEPopup.onInit.add(EmotionsDialog.init, EmotionsDialog);
diff --git a/media/js/tinymce/plugins/emotions/langs/en_dlg.js b/media/js/tinymce/plugins/emotions/langs/en_dlg.js
new file mode 100644
index 0000000..3b57ad9
--- /dev/null
+++ b/media/js/tinymce/plugins/emotions/langs/en_dlg.js
@@ -0,0 +1,20 @@
+tinyMCE.addI18n('en.emotions_dlg',{
+title:"Insert emotion",
+desc:"Emotions",
+cool:"Cool",
+cry:"Cry",
+embarassed:"Embarassed",
+foot_in_mouth:"Foot in mouth",
+frown:"Frown",
+innocent:"Innocent",
+kiss:"Kiss",
+laughing:"Laughing",
+money_mouth:"Money mouth",
+sealed:"Sealed",
+smile:"Smile",
+surprised:"Surprised",
+tongue_out:"Tongue out",
+undecided:"Undecided",
+wink:"Wink",
+yell:"Yell"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/example/dialog.htm b/media/js/tinymce/plugins/example/dialog.htm
new file mode 100644
index 0000000..b4c6284
--- /dev/null
+++ b/media/js/tinymce/plugins/example/dialog.htm
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#example_dlg.title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/dialog.js"></script>
+</head>
+<body>
+
+<form onsubmit="ExampleDialog.insert();return false;" action="#">
+	<p>Here is a example dialog.</p>
+	<p>Selected text: <input id="someval" name="someval" type="text" class="text" /></p>
+	<p>Custom arg: <input id="somearg" name="somearg" type="text" class="text" /></p>
+
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="button" id="insert" name="insert" value="{#insert}" onclick="ExampleDialog.insert();" />
+		</div>
+
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+</form>
+
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/example/editor_plugin.js b/media/js/tinymce/plugins/example/editor_plugin.js
new file mode 100644
index 0000000..ec1f81e
--- /dev/null
+++ b/media/js/tinymce/plugins/example/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.PluginManager.requireLangPack("example");tinymce.create("tinymce.plugins.ExamplePlugin",{init:function(a,b){a.addCommand("mceExample",function(){a.windowManager.open({file:b+"/dialog.htm",width:320+parseInt(a.getLang("example.delta_width",0)),height:120+parseInt(a.getLang("example.delta_height",0)),inline:1},{plugin_url:b,some_custom_arg:"custom arg"})});a.addButton("example",{title:"example.desc",cmd:"mceExample",image:b+"/img/example.gif"});a.onNodeChange.add(functi [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/example/editor_plugin_src.js b/media/js/tinymce/plugins/example/editor_plugin_src.js
new file mode 100644
index 0000000..5050550
--- /dev/null
+++ b/media/js/tinymce/plugins/example/editor_plugin_src.js
@@ -0,0 +1,81 @@
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	// Load plugin specific language pack
+	tinymce.PluginManager.requireLangPack('example');
+
+	tinymce.create('tinymce.plugins.ExamplePlugin', {
+		/**
+		 * Initializes the plugin, this will be executed after the plugin has been created.
+		 * This call is done before the editor instance has finished it's initialization so use the onInit event
+		 * of the editor instance to intercept that event.
+		 *
+		 * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
+		 * @param {string} url Absolute URL to where the plugin is located.
+		 */
+		init : function(ed, url) {
+			// Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceExample');
+			ed.addCommand('mceExample', function() {
+				ed.windowManager.open({
+					file : url + '/dialog.htm',
+					width : 320 + parseInt(ed.getLang('example.delta_width', 0)),
+					height : 120 + parseInt(ed.getLang('example.delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url, // Plugin absolute URL
+					some_custom_arg : 'custom arg' // Custom argument
+				});
+			});
+
+			// Register example button
+			ed.addButton('example', {
+				title : 'example.desc',
+				cmd : 'mceExample',
+				image : url + '/img/example.gif'
+			});
+
+			// Add a node change handler, selects the button in the UI when a image is selected
+			ed.onNodeChange.add(function(ed, cm, n) {
+				cm.setActive('example', n.nodeName == 'IMG');
+			});
+		},
+
+		/**
+		 * Creates control instances based in the incomming name. This method is normally not
+		 * needed since the addButton method of the tinymce.Editor class is a more easy way of adding buttons
+		 * but you sometimes need to create more complex controls like listboxes, split buttons etc then this
+		 * method can be used to create those.
+		 *
+		 * @param {String} n Name of the control to create.
+		 * @param {tinymce.ControlManager} cm Control manager to use inorder to create new control.
+		 * @return {tinymce.ui.Control} New control instance or null if no control was created.
+		 */
+		createControl : function(n, cm) {
+			return null;
+		},
+
+		/**
+		 * Returns information about the plugin as a name/value array.
+		 * The current keys are longname, author, authorurl, infourl and version.
+		 *
+		 * @return {Object} Name/value array containing information about the plugin.
+		 */
+		getInfo : function() {
+			return {
+				longname : 'Example plugin',
+				author : 'Some author',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/example',
+				version : "1.0"
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('example', tinymce.plugins.ExamplePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/example/img/example.gif b/media/js/tinymce/plugins/example/img/example.gif
new file mode 100644
index 0000000..1ab5da4
Binary files /dev/null and b/media/js/tinymce/plugins/example/img/example.gif differ
diff --git a/media/js/tinymce/plugins/example/js/dialog.js b/media/js/tinymce/plugins/example/js/dialog.js
new file mode 100644
index 0000000..fa83411
--- /dev/null
+++ b/media/js/tinymce/plugins/example/js/dialog.js
@@ -0,0 +1,19 @@
+tinyMCEPopup.requireLangPack();
+
+var ExampleDialog = {
+	init : function() {
+		var f = document.forms[0];
+
+		// Get the selected contents as text and place it in the input
+		f.someval.value = tinyMCEPopup.editor.selection.getContent({format : 'text'});
+		f.somearg.value = tinyMCEPopup.getWindowArg('some_custom_arg');
+	},
+
+	insert : function() {
+		// Insert the contents from the input into the document
+		tinyMCEPopup.editor.execCommand('mceInsertContent', false, document.forms[0].someval.value);
+		tinyMCEPopup.close();
+	}
+};
+
+tinyMCEPopup.onInit.add(ExampleDialog.init, ExampleDialog);
diff --git a/media/js/tinymce/plugins/example/langs/en.js b/media/js/tinymce/plugins/example/langs/en.js
new file mode 100644
index 0000000..e0784f8
--- /dev/null
+++ b/media/js/tinymce/plugins/example/langs/en.js
@@ -0,0 +1,3 @@
+tinyMCE.addI18n('en.example',{
+	desc : 'This is just a template button'
+});
diff --git a/media/js/tinymce/plugins/example/langs/en_dlg.js b/media/js/tinymce/plugins/example/langs/en_dlg.js
new file mode 100644
index 0000000..ebcf948
--- /dev/null
+++ b/media/js/tinymce/plugins/example/langs/en_dlg.js
@@ -0,0 +1,3 @@
+tinyMCE.addI18n('en.example_dlg',{
+	title : 'This is just a example title'
+});
diff --git a/media/js/tinymce/plugins/fullpage/css/fullpage.css b/media/js/tinymce/plugins/fullpage/css/fullpage.css
new file mode 100644
index 0000000..7a3334f
--- /dev/null
+++ b/media/js/tinymce/plugins/fullpage/css/fullpage.css
@@ -0,0 +1,182 @@
+/* Hide the advanced tab */
+#advanced_tab {
+	display: none;
+}
+
+#metatitle, #metakeywords, #metadescription, #metaauthor, #metacopyright {
+	width: 280px;
+}
+
+#doctype, #docencoding {
+	width: 200px;
+}
+
+#langcode {
+	width: 30px;
+}
+
+#bgimage {
+	width: 220px;	
+}
+
+#fontface {
+	width: 240px;
+}
+
+#leftmargin, #rightmargin, #topmargin, #bottommargin {
+	width: 50px;
+}
+
+.panel_wrapper div.current {
+	height: 400px;
+}
+
+#stylesheet, #style {
+	width: 240px;
+}
+
+/* Head list classes */
+
+.headlistwrapper {
+	width: 100%;
+}
+
+.addbutton, .removebutton, .moveupbutton, .movedownbutton {
+	border-top: 1px solid;
+	border-left: 1px solid;
+	border-bottom: 1px solid;
+	border-right: 1px solid;
+	border-color: #F0F0EE;
+	cursor: default;
+	display: block;
+	width: 20px;
+	height: 20px;
+}
+
+#doctypes {
+	width: 200px;
+}
+
+.addbutton:hover, .removebutton:hover, .moveupbutton:hover, .movedownbutton:hover {
+	border: 1px solid #0A246A;
+	background-color: #B6BDD2;
+}
+
+.addbutton {
+	background-image: url('../images/add.gif');
+	float: left;
+	margin-right: 3px;
+}
+
+.removebutton {
+	background-image: url('../images/remove.gif');
+	float: left;
+}
+
+.moveupbutton {
+	background-image: url('../images/move_up.gif');
+	float: left;
+	margin-right: 3px;
+}
+
+.movedownbutton {
+	background-image: url('../images/move_down.gif');
+	float: left;
+}
+
+.selected {
+	border: 1px solid #0A246A;
+	background-color: #B6BDD2;
+}
+
+.toolbar {
+	width: 100%;
+}
+
+#headlist {
+	width: 100%;
+	margin-top: 3px;
+	font-size: 11px;
+}
+
+#info, #title_element, #meta_element, #script_element, #style_element, #base_element, #link_element, #comment_element, #unknown_element {
+	display: none;
+}
+
+#addmenu {
+	position: absolute;
+	border: 1px solid gray;
+	display: none;
+	z-index: 100;
+	background-color: white;
+}
+
+#addmenu a {
+	display: block;
+	width: 100%;
+	line-height: 20px;
+	text-decoration: none;
+	background-color: white;
+}
+
+#addmenu a:hover {
+	background-color: #B6BDD2;
+	color: black;
+}
+
+#addmenu span {
+	padding-left: 10px;
+	padding-right: 10px;
+}
+
+#updateElementPanel {
+	display: none;
+}
+
+#script_element .panel_wrapper div.current {
+	height: 108px;
+}
+
+#style_element .panel_wrapper div.current {
+	height: 108px;
+}
+
+#link_element  .panel_wrapper div.current {
+	height: 140px;
+}
+
+#element_script_value {
+	width: 100%;
+	height: 100px;
+}
+
+#element_comment_value {
+	width: 100%;
+	height: 120px;
+}
+
+#element_style_value {
+	width: 100%;
+	height: 100px;
+}
+
+#element_title, #element_script_src, #element_meta_name, #element_meta_content, #element_base_href, #element_link_href, #element_link_title {
+	width: 250px;
+}
+
+.updateElementButton {
+	margin-top: 3px;
+}
+
+/* MSIE specific styles */
+
+* html .addbutton, * html .removebutton, * html .moveupbutton, * html .movedownbutton {
+	width: 22px;
+	height: 22px;
+}
+
+textarea {
+	height: 55px;
+}
+
+.panel_wrapper div.current {height:420px;}
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/fullpage/editor_plugin.js b/media/js/tinymce/plugins/fullpage/editor_plugin.js
new file mode 100644
index 0000000..8e11bfc
--- /dev/null
+++ b/media/js/tinymce/plugins/fullpage/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.FullPagePlugin",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceFullPageProperties",function(){a.windowManager.open({file:b+"/fullpage.htm",width:430+parseInt(a.getLang("fullpage.delta_width",0)),height:495+parseInt(a.getLang("fullpage.delta_height",0)),inline:1},{plugin_url:b,head_html:c.head})});a.addButton("fullpage",{title:"fullpage.desc",cmd:"mceFullPageProperties"});a.onBeforeSetContent.add(c._setContent,c);a.onSetContent.add(c [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/fullpage/editor_plugin_src.js b/media/js/tinymce/plugins/fullpage/editor_plugin_src.js
new file mode 100644
index 0000000..c7d5aca
--- /dev/null
+++ b/media/js/tinymce/plugins/fullpage/editor_plugin_src.js
@@ -0,0 +1,146 @@
+/**
+ * $Id: editor_plugin_src.js 1029 2009-02-24 22:32:21Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.FullPagePlugin', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceFullPageProperties', function() {
+				ed.windowManager.open({
+					file : url + '/fullpage.htm',
+					width : 430 + parseInt(ed.getLang('fullpage.delta_width', 0)),
+					height : 495 + parseInt(ed.getLang('fullpage.delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url,
+					head_html : t.head
+				});
+			});
+
+			// Register buttons
+			ed.addButton('fullpage', {title : 'fullpage.desc', cmd : 'mceFullPageProperties'});
+
+			ed.onBeforeSetContent.add(t._setContent, t);
+			ed.onSetContent.add(t._setBodyAttribs, t);
+			ed.onGetContent.add(t._getContent, t);
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Fullpage',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullpage',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private plugin internal methods
+
+		_setBodyAttribs : function(ed, o) {
+			var bdattr, i, len, kv, k, v, t, attr = this.head.match(/body(.*?)>/i);
+
+			if (attr && attr[1]) {
+				bdattr = attr[1].match(/\s*(\w+\s*=\s*".*?"|\w+\s*=\s*'.*?'|\w+\s*=\s*\w+|\w+)\s*/g);
+
+				if (bdattr) {
+					for(i = 0, len = bdattr.length; i < len; i++) {
+						kv = bdattr[i].split('=');
+						k = kv[0].replace(/\s/,'');
+						v = kv[1];
+
+						if (v) {
+							v = v.replace(/^\s+/,'').replace(/\s+$/,'');
+							t = v.match(/^["'](.*)["']$/);
+
+							if (t)
+								v = t[1];
+						} else
+							v = k;
+
+						ed.dom.setAttrib(ed.getBody(), 'style', v);
+					}
+				}
+			}
+		},
+
+		_createSerializer : function() {
+			return new tinymce.dom.Serializer({
+				dom : this.editor.dom,
+				apply_source_formatting : true
+			});
+		},
+
+		_setContent : function(ed, o) {
+			var t = this, sp, ep, c = o.content, v, st = '';
+
+			if (o.source_view && ed.getParam('fullpage_hide_in_source_view'))
+				return;
+
+			// Parse out head, body and footer
+			c = c.replace(/<(\/?)BODY/gi, '<$1body');
+			sp = c.indexOf('<body');
+
+			if (sp != -1) {
+				sp = c.indexOf('>', sp);
+				t.head = c.substring(0, sp + 1);
+
+				ep = c.indexOf('</body', sp);
+				if (ep == -1)
+					ep = c.indexOf('</body', ep);
+
+				o.content = c.substring(sp + 1, ep);
+				t.foot = c.substring(ep);
+
+				function low(s) {
+					return s.replace(/<\/?[A-Z]+/g, function(a) {
+						return a.toLowerCase();
+					})
+				};
+
+				t.head = low(t.head);
+				t.foot = low(t.foot);
+			} else {
+				t.head = '';
+				if (ed.getParam('fullpage_default_xml_pi'))
+					t.head += '<?xml version="1.0" encoding="' + ed.getParam('fullpage_default_encoding', 'ISO-8859-1') + '" ?>\n';
+
+				t.head += ed.getParam('fullpage_default_doctype', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">');
+				t.head += '\n<html>\n<head>\n<title>' + ed.getParam('fullpage_default_title', 'Untitled document') + '</title>\n';
+
+				if (v = ed.getParam('fullpage_default_encoding'))
+					t.head += '<meta http-equiv="Content-Type" content="' + v + '" />\n';
+
+				if (v = ed.getParam('fullpage_default_font_family'))
+					st += 'font-family: ' + v + ';';
+
+				if (v = ed.getParam('fullpage_default_font_size'))
+					st += 'font-size: ' + v + ';';
+
+				if (v = ed.getParam('fullpage_default_text_color'))
+					st += 'color: ' + v + ';';
+
+				t.head += '</head>\n<body' + (st ? ' style="' + st + '"' : '') + '>\n';
+				t.foot = '\n</body>\n</html>';
+			}
+		},
+
+		_getContent : function(ed, o) {
+			var t = this;
+
+			if (!o.source_view || !ed.getParam('fullpage_hide_in_source_view'))
+				o.content = tinymce.trim(t.head) + '\n' + tinymce.trim(o.content) + '\n' + tinymce.trim(t.foot);
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('fullpage', tinymce.plugins.FullPagePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/fullpage/fullpage.htm b/media/js/tinymce/plugins/fullpage/fullpage.htm
new file mode 100644
index 0000000..3ea4081
--- /dev/null
+++ b/media/js/tinymce/plugins/fullpage/fullpage.htm
@@ -0,0 +1,576 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#fullpage_dlg.title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="js/fullpage.js"></script>
+	<link href="css/fullpage.css" rel="stylesheet" type="text/css" />
+</head>
+<body id="advlink" style="display: none">
+    <form onsubmit="updateAction();return false;" name="fullpage" action="#">
+		<div class="tabs">
+			<ul>
+				<li id="meta_tab" class="current"><span><a href="javascript:mcTabs.displayTab('meta_tab','meta_panel');" onmousedown="return false;">{#fullpage_dlg.meta_tab}</a></span></li>
+				<li id="appearance_tab"><span><a href="javascript:mcTabs.displayTab('appearance_tab','appearance_panel');" onmousedown="return false;">{#fullpage_dlg.appearance_tab}</a></span></li>
+				<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#fullpage_dlg.advanced_tab}</a></span></li>
+			</ul>
+		</div>
+
+		<div class="panel_wrapper">
+			<div id="meta_panel" class="panel current">
+				<fieldset>
+					<legend>{#fullpage_dlg.meta_props}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="nowrap"><label for="metatitle">{#fullpage_dlg.meta_title}</label> </td>
+							<td><input type="text" id="metatitle" name="metatitle" value="" class="mceFocus" /></td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="metakeywords">{#fullpage_dlg.meta_keywords}</label> </td>
+							<td><textarea id="metakeywords" name="metakeywords" rows="4"></textarea></td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="metadescription">{#fullpage_dlg.meta_description}</label> </td>
+							<td><textarea id="metadescription" name="metadescription" rows="4"></textarea></td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="metaauthor">{#fullpage_dlg.author}</label> </td>
+							<td><input type="text" id="metaauthor" name="metaauthor" value="" /></td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="metacopyright">{#fullpage_dlg.copyright}</label> </td>
+							<td><input type="text" id="metacopyright" name="metacopyright" value="" /></td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="metarobots">{#fullpage_dlg.meta_robots}</label> </td>
+							<td>
+								<select id="metarobots" name="metarobots">
+											<option value="">{#not_set}</option> 
+											<option value="index,follow">{#fullpage_dlg.meta_index_follow}</option>
+											<option value="index,nofollow">{#fullpage_dlg.meta_index_nofollow}</option>
+											<option value="noindex,follow">{#fullpage_dlg.meta_noindex_follow}</option>
+											<option value="noindex,nofollow">{#fullpage_dlg.meta_noindex_nofollow}</option>
+								</select>
+							</td>
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset>
+					<legend>{#fullpage_dlg.langprops}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="docencoding">{#fullpage_dlg.encoding}</label></td> 
+							<td>
+								<select id="docencoding" name="docencoding"> 
+										<option value="">{#not_set}</option>
+								</select>
+							</td> 
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="doctypes">{#fullpage_dlg.doctypes}</label> </td>
+							<td>
+								<select id="doctypes" name="doctypes">
+										<option value="">{#not_set}</option>
+								</select>
+							</td>
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="langcode">{#fullpage_dlg.langcode}</label> </td>
+							<td><input type="text" id="langcode" name="langcode" value="" /></td>
+						</tr>
+						<tr>
+							<td class="column1"><label for="langdir">{#fullpage_dlg.langdir}</label></td> 
+							<td>
+								<select id="langdir" name="langdir"> 
+										<option value="">{#not_set}</option> 
+										<option value="ltr">{#fullpage_dlg.ltr}</option> 
+										<option value="rtl">{#fullpage_dlg.rtl}</option> 
+								</select>
+							</td> 
+						</tr>
+						<tr>
+							<td class="nowrap"><label for="xml_pi">{#fullpage_dlg.xml_pi}</label> </td>
+							<td><input type="checkbox" id="xml_pi" name="xml_pi" class="checkbox" /></td>
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+
+			<div id="appearance_panel" class="panel">
+				<fieldset>
+					<legend>{#fullpage_dlg.appearance_textprops}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="fontface">{#fullpage_dlg.fontface}</label></td> 
+							<td>
+								<select id="fontface" name="fontface" onchange="changedStyleField(this);">
+										<option value="">{#not_set}</option>
+								</select>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="fontsize">{#fullpage_dlg.fontsize}</label></td> 
+							<td>
+								<select id="fontsize" name="fontsize" onchange="changedStyleField(this);">
+										<option value="">{#not_set}</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="textcolor">{#fullpage_dlg.textcolor}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="textcolor" name="textcolor" type="text" value="" size="9" onchange="updateColor('textcolor_pick','textcolor');changedStyleField(this);" /></td>
+										<td id="textcolor_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset>
+					<legend>{#fullpage_dlg.appearance_bgprops}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="bgimage">{#fullpage_dlg.bgimage}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="bgimage" name="bgimage" type="text" value="" onchange="changedStyleField(this);" /></td>
+										<td id="bgimage_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+						<tr>
+							<td class="column1"><label for="bgcolor">{#fullpage_dlg.bgcolor}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="bgcolor" name="bgcolor" type="text" value="" size="9" onchange="updateColor('bgcolor_pick','bgcolor');changedStyleField(this);" /></td>
+										<td id="bgcolor_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset>
+					<legend>{#fullpage_dlg.appearance_marginprops}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="leftmargin">{#fullpage_dlg.left_margin}</label></td> 
+							<td><input id="leftmargin" name="leftmargin" type="text" value="" onchange="changedStyleField(this);" /></td>
+							<td class="column1"><label for="rightmargin">{#fullpage_dlg.right_margin}</label></td> 
+							<td><input id="rightmargin" name="rightmargin" type="text" value="" onchange="changedStyleField(this);" /></td>
+						</tr>
+						<tr>
+							<td class="column1"><label for="topmargin">{#fullpage_dlg.top_margin}</label></td> 
+							<td><input id="topmargin" name="topmargin" type="text" value="" onchange="changedStyleField(this);" /></td>
+							<td class="column1"><label for="bottommargin">{#fullpage_dlg.bottom_margin}</label></td> 
+							<td><input id="bottommargin" name="bottommargin" type="text" value="" onchange="changedStyleField(this);" /></td>
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset>
+					<legend>{#fullpage_dlg.appearance_linkprops}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="link_color">{#fullpage_dlg.link_color}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="link_color" name="link_color" type="text" value="" size="9" onchange="updateColor('link_color_pick','link_color');changedStyleField(this);" /></td>
+										<td id="link_color_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td>
+
+							<td class="column1"><label for="visited_color">{#fullpage_dlg.visited_color}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="visited_color" name="visited_color" type="text" value="" size="9" onchange="updateColor('visited_color_pick','visited_color');changedStyleField(this);" /></td>
+										<td id="visited_color_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="active_color">{#fullpage_dlg.active_color}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="active_color" name="active_color" type="text" value="" size="9" onchange="updateColor('active_color_pick','active_color');changedStyleField(this);" /></td>
+										<td id="active_color_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td>
+
+							<td> </td>
+							<td> </td>
+
+<!--							<td class="column1"><label for="hover_color">{#fullpage_dlg.hover_color}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="hover_color" name="hover_color" type="text" value="" size="9" onchange="changedStyleField(this);" /></td>
+										<td id="hover_color_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td> -->
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset>
+					<legend>{#fullpage_dlg.appearance_style}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="stylesheet">{#fullpage_dlg.stylesheet}</label></td> 
+							<td><table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="stylesheet" name="stylesheet" type="text" value="" /></td>
+										<td id="stylesheet_browsercontainer"> </td>
+									</tr>
+								</table></td>
+						</tr>
+						<tr>
+							<td class="column1"><label for="style">{#fullpage_dlg.style}</label></td> 
+							<td><input id="style" name="style" type="text" value="" onchange="changedStyleField(this);" /></td>
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+
+			<div id="advanced_panel" class="panel">
+				<div id="addmenu">
+					<table border="0" cellpadding="0" cellspacing="0">
+						<tr><td><a href="javascript:addHeadElm('title');" onmousedown="return false;"><span>{#fullpage_dlg.add_title}</span></a></td></tr>
+						<tr><td><a href="javascript:addHeadElm('meta');" onmousedown="return false;"><span>{#fullpage_dlg.add_meta}</span></a></td></tr>
+						<tr><td><a href="javascript:addHeadElm('script');" onmousedown="return false;"><span>{#fullpage_dlg.add_script}</span></a></td></tr>
+						<tr><td><a href="javascript:addHeadElm('style');" onmousedown="return false;"><span>{#fullpage_dlg.add_style}</span></a></td></tr>
+						<tr><td><a href="javascript:addHeadElm('link');" onmousedown="return false;"><span>{#fullpage_dlg.add_link}</span></a></td></tr>
+						<tr><td><a href="javascript:addHeadElm('base');" onmousedown="return false;"><span>{#fullpage_dlg.add_base}</span></a></td></tr>
+						<tr><td><a href="javascript:addHeadElm('comment');" onmousedown="return false;"><span>{#fullpage_dlg.add_comment}</span></a></td></tr>
+					</table>
+				</div>
+
+				<fieldset>
+					<legend>{#fullpage_dlg.head_elements}</legend>
+
+					<div class="headlistwrapper">
+						<div class="toolbar">
+							<div style="float: left">
+								<a id="addbutton" href="javascript:showAddMenu();" onmousedown="return false;" class="addbutton" title="{#fullpage_dlg.add}"></a>
+								<a href="#" onmousedown="return false;" class="removebutton" title="{#fullpage_dlg.remove}"></a>
+							</div>
+							<div style="float: right">
+								<a href="#" onmousedown="return false;" class="moveupbutton" title="{#fullpage_dlg.moveup}"></a>
+								<a href="#" onmousedown="return false;" class="movedownbutton" title="{#fullpage_dlg.movedown}"></a>
+							</div>
+							<br style="clear: both" />
+						</div>
+						<select id="headlist" size="26" onchange="updateHeadElm(this.options[this.selectedIndex].value);">
+							<option value="title_0"><title>Some title bla bla bla</title></option>
+							<option value="meta_1"><meta name="keywords">Some bla bla bla</meta></option>
+							<option value="meta_2"><meta name="description">Some bla bla bla bla bla bla bla bla bla</meta></option>
+							<option value="script_3"><script language="javascript">...</script></option>
+							<option value="style_4"><style>...</style></option>
+							<option value="base_5"><base href="." /></option>
+							<option value="comment_6"><!-- ... --></option>
+							<option value="link_7"><link href="." /></option>
+						</select>
+					</div>
+				</fieldset>
+
+				<fieldset id="meta_element">
+					<legend>{#fullpage_dlg.meta_element}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="element_meta_type">{#fullpage_dlg.type}</label></td> 
+							<td><select id="element_meta_type">
+										<option value="name">name</option>
+										<option value="http-equiv">http-equiv</option>
+								</select></td>
+						</tr>
+						<tr>
+							<td class="column1"><label for="element_meta_name">{#fullpage_dlg.name}</label></td> 
+							<td><input id="element_meta_name" name="element_meta_name" type="text" value="" /></td>
+						</tr>
+						<tr>
+							<td class="column1"><label for="element_meta_content">{#fullpage_dlg.content}</label></td> 
+							<td><input id="element_meta_content" name="element_meta_content" type="text" value="" /></td>
+						</tr>
+					</table>
+
+					<input type="button" id="meta_updateelement" class="updateElementButton" name="update" value="{#update}" onclick="updateElement();" />
+				</fieldset>
+
+				<fieldset id="title_element">
+					<legend>{#fullpage_dlg.title_element}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="element_title">{#fullpage_dlg.meta_title}</label></td> 
+							<td><input id="element_title" name="element_title" type="text" value="" /></td>
+						</tr>
+					</table>
+
+					<input type="button" id="title_updateelement" class="updateElementButton" name="update" value="{#update}" onclick="updateElement();" />
+				</fieldset>
+
+				<fieldset id="script_element">
+					<legend>{#fullpage_dlg.script_element}</legend>
+
+					<div class="tabs">
+						<ul>
+							<li id="script_props_tab" class="current"><span><a href="javascript:mcTabs.displayTab('script_props_tab','script_props_panel');" onmousedown="return false;">{#fullpage_dlg.properties}</a></span></li>
+							<li id="script_value_tab"><span><a href="javascript:mcTabs.displayTab('script_value_tab','script_value_panel');" onmousedown="return false;">{#fullpage_dlg.value}</a></span></li>
+						</ul>
+					</div>
+
+					<br style="clear: both" />
+
+					<div class="panel_wrapper">
+						<div id="script_props_panel" class="panel current">
+							<table border="0" cellpadding="4" cellspacing="0">
+								<tr>
+									<td class="column1"><label for="element_script_type">{#fullpage_dlg.type}</label></td> 
+									<td><select id="element_script_type">
+										<option value="text/javascript">text/javascript</option>
+										<option value="text/jscript">text/jscript</option>
+										<option value="text/vbscript">text/vbscript</option>
+										<option value="text/vbs">text/vbs</option>
+										<option value="text/ecmascript">text/ecmascript</option>
+										<option value="text/xml">text/xml</option>
+									</select></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_script_src">{#fullpage_dlg.src}</label></td> 
+									<td><table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="element_script_src" name="element_script_src" type="text" value="" /></td>
+										<td id="script_src_pickcontainer"> </td>
+									</tr>
+								</table></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_script_charset">{#fullpage_dlg.charset}</label></td> 
+									<td><select id="element_script_charset"><option value="">{#not_set}</option></select></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_script_defer">{#fullpage_dlg.defer}</label></td> 
+									<td><input type="checkbox" id="element_script_defer" name="element_script_defer" class="checkbox" /></td>
+								</tr>
+							</table>
+						</div>
+
+						<div id="script_value_panel" class="panel">
+							<textarea id="element_script_value"></textarea>
+						</div>
+					</div>
+
+					<input type="button" id="script_updateelement" class="updateElementButton" name="update" value="{#update}" onclick="updateElement();" />
+				</fieldset>
+
+				<fieldset id="style_element">
+					<legend>{#fullpage_dlg.style_element}</legend>
+
+					<div class="tabs">
+						<ul>
+							<li id="style_props_tab" class="current"><span><a href="javascript:mcTabs.displayTab('style_props_tab','style_props_panel');" onmousedown="return false;">{#fullpage_dlg.properties}</a></span></li>
+							<li id="style_value_tab"><span><a href="javascript:mcTabs.displayTab('style_value_tab','style_value_panel');" onmousedown="return false;">{#fullpage_dlg.value}</a></span></li>
+						</ul>
+					</div>
+
+					<br style="clear: both" />
+
+					<div class="panel_wrapper">
+						<div id="style_props_panel" class="panel current">
+							<table border="0" cellpadding="4" cellspacing="0">
+								<tr>
+									<td class="column1"><label for="element_style_type">{#fullpage_dlg.type}</label></td> 
+									<td><select id="element_style_type">
+										<option value="text/css">text/css</option>
+									</select></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_style_media">{#fullpage_dlg.media}</label></td> 
+									<td><select id="element_style_media"></select></td>
+								</tr>
+							</table>
+						</div>
+
+						<div id="style_value_panel" class="panel">
+							<textarea id="element_style_value"></textarea>
+						</div>
+					</div>
+
+					<input type="button" id="style_updateelement" class="updateElementButton" name="update" value="{#update}" onclick="updateElement();" />
+				</fieldset>
+
+				<fieldset id="base_element">
+					<legend>{#fullpage_dlg.base_element}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td class="column1"><label for="element_base_href">{#fullpage_dlg.href}</label></td> 
+							<td><input id="element_base_href" name="element_base_href" type="text" value="" /></td>
+						</tr>
+						<tr>
+							<td class="column1"><label for="element_base_target">{#fullpage_dlg.target}</label></td> 
+							<td><input id="element_base_target" name="element_base_target" type="text" value="" /></td>
+						</tr>
+					</table>
+
+					<input type="button" id="base_updateelement" class="updateElementButton" name="update" value="{#update}" onclick="updateElement();" />
+				</fieldset>
+
+				<fieldset id="link_element">
+					<legend>{#fullpage_dlg.link_element}</legend>
+
+					<div class="tabs">
+						<ul>
+							<li id="link_general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('link_general_tab','link_general_panel');" onmousedown="return false;">{#fullpage_dlg.general_props}</a></span></li>
+							<li id="link_advanced_tab"><span><a href="javascript:mcTabs.displayTab('link_advanced_tab','link_advanced_panel');" onmousedown="return false;">{#fullpage_dlg.advanced_props}</a></span></li>
+						</ul>
+					</div>
+
+					<br style="clear: both" />
+
+					<div class="panel_wrapper">
+						<div id="link_general_panel" class="panel current">
+							<table border="0" cellpadding="4" cellspacing="0">
+								<tr>
+									<td class="column1"><label for="element_link_href">{#fullpage_dlg.href}</label></td> 
+									<td><table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="element_link_href" name="element_link_href" type="text" value="" /></td>
+										<td id="link_href_pickcontainer"> </td>
+									</tr>
+								</table></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_link_title">{#fullpage_dlg.meta_title}</label></td> 
+									<td><input id="element_link_title" name="element_link_title" type="text" value="" /></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_link_type">{#fullpage_dlg.type}</label></td> 
+									<td><select id="element_link_type" name="element_link_type">
+										<option value="text/css">text/css</option>
+										<option value="text/javascript">text/javascript</option>
+									</select></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_link_media">{#fullpage_dlg.media}</label></td> 
+									<td><select id="element_link_media" name="element_link_media"></select></td>
+								</tr>
+								<tr>
+									<td><label for="element_style_rel">{#fullpage_dlg.rel}</label></td>
+									<td><select id="element_style_rel" name="element_style_rel"> 
+											<option value="">{#not_set}</option> 
+											<option value="stylesheet">Stylesheet</option>
+											<option value="alternate">Alternate</option>
+											<option value="designates">Designates</option>
+											<option value="start">Start</option>
+											<option value="next">Next</option>
+											<option value="prev">Prev</option>
+											<option value="contents">Contents</option>
+											<option value="index">Index</option>
+											<option value="glossary">Glossary</option>
+											<option value="copyright">Copyright</option>
+											<option value="chapter">Chapter</option>
+											<option value="subsection">Subsection</option>
+											<option value="appendix">Appendix</option>
+											<option value="help">Help</option>
+											<option value="bookmark">Bookmark</option>
+										</select> 
+									</td>
+								</tr>
+							</table>
+						</div>
+
+						<div id="link_advanced_panel" class="panel">
+							<table border="0" cellpadding="4" cellspacing="0">
+								<tr>
+									<td class="column1"><label for="element_link_charset">{#fullpage_dlg.charset}</label></td> 
+									<td><select id="element_link_charset"><option value="">{#not_set}</option></select></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_link_hreflang">{#fullpage_dlg.hreflang}</label></td> 
+									<td><input id="element_link_hreflang" name="element_link_hreflang" type="text" value="" /></td>
+								</tr>
+								<tr>
+									<td class="column1"><label for="element_link_target">{#fullpage_dlg.target}</label></td> 
+									<td><input id="element_link_target" name="element_link_target" type="text" value="" /></td>
+								</tr>
+								<tr>
+									<td><label for="element_style_rev">{#fullpage_dlg.rev}</label></td>
+									<td><select id="element_style_rev" name="element_style_rev"> 
+											<option value="">{#not_set}</option> 
+											<option value="alternate">Alternate</option> 
+											<option value="designates">Designates</option> 
+											<option value="stylesheet">Stylesheet</option> 
+											<option value="start">Start</option> 
+											<option value="next">Next</option> 
+											<option value="prev">Prev</option> 
+											<option value="contents">Contents</option> 
+											<option value="index">Index</option> 
+											<option value="glossary">Glossary</option> 
+											<option value="copyright">Copyright</option> 
+											<option value="chapter">Chapter</option> 
+											<option value="subsection">Subsection</option> 
+											<option value="appendix">Appendix</option> 
+											<option value="help">Help</option> 
+											<option value="bookmark">Bookmark</option> 
+										</select> 
+									</td>
+								</tr>
+							</table>
+						</div>
+					</div>
+
+					<input type="button" id="link_updateelement" class="updateElementButton" name="update" value="{#update}" onclick="updateElement();" />
+				</fieldset>
+
+				<fieldset id="comment_element">
+					<legend>{#fullpage_dlg.comment_element}</legend>
+
+					<textarea id="element_comment_value"></textarea>
+
+					<input type="button" id="comment_updateelement" class="updateElementButton" name="update" value="{#update}" onclick="updateElement();" />
+				</fieldset>
+			</div>
+		</div>
+
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" id="insert" name="update" value="{#update}" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+    </form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/fullpage/js/fullpage.js b/media/js/tinymce/plugins/fullpage/js/fullpage.js
new file mode 100644
index 0000000..dd3a29c
--- /dev/null
+++ b/media/js/tinymce/plugins/fullpage/js/fullpage.js
@@ -0,0 +1,461 @@
+tinyMCEPopup.requireLangPack();
+
+var doc;
+
+var defaultDocTypes = 
+	'XHTML 1.0 Transitional=<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">,' +
+	'XHTML 1.0 Frameset=<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">,' +
+	'XHTML 1.0 Strict=<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">,' +
+	'XHTML 1.1=<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">,' +
+	'HTML 4.01 Transitional=<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">,' +
+	'HTML 4.01 Strict=<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">,' +
+	'HTML 4.01 Frameset=<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">';
+
+var defaultEncodings = 
+	'Western european (iso-8859-1)=iso-8859-1,' +
+	'Central European (iso-8859-2)=iso-8859-2,' +
+	'Unicode (UTF-8)=utf-8,' +
+	'Chinese traditional (Big5)=big5,' +
+	'Cyrillic (iso-8859-5)=iso-8859-5,' +
+	'Japanese (iso-2022-jp)=iso-2022-jp,' +
+	'Greek (iso-8859-7)=iso-8859-7,' +
+	'Korean (iso-2022-kr)=iso-2022-kr,' +
+	'ASCII (us-ascii)=us-ascii';
+
+var defaultMediaTypes = 
+	'all=all,' +
+	'screen=screen,' +
+	'print=print,' +
+	'tty=tty,' +
+	'tv=tv,' +
+	'projection=projection,' +
+	'handheld=handheld,' +
+	'braille=braille,' +
+	'aural=aural';
+
+var defaultFontNames = 'Arial=arial,helvetica,sans-serif;Courier New=courier new,courier,monospace;Georgia=georgia,times new roman,times,serif;Tahoma=tahoma,arial,helvetica,sans-serif;Times New Roman=times new roman,times,serif;Verdana=verdana,arial,helvetica,sans-serif;Impact=impact;WingDings=wingdings';
+var defaultFontSizes = '10px,11px,12px,13px,14px,15px,16px';
+
+function init() {
+	var f = document.forms['fullpage'], el = f.elements, e, i, p, doctypes, encodings, mediaTypes, fonts, ed = tinyMCEPopup.editor, dom = tinyMCEPopup.dom, style;
+
+	// Setup doctype select box
+	doctypes = ed.getParam("fullpage_doctypes", defaultDocTypes).split(',');
+	for (i=0; i<doctypes.length; i++) {
+		p = doctypes[i].split('=');
+
+		if (p.length > 1)
+			addSelectValue(f, 'doctypes', p[0], p[1]);
+	}
+
+	// Setup fonts select box
+	fonts = ed.getParam("fullpage_fonts", defaultFontNames).split(';');
+	for (i=0; i<fonts.length; i++) {
+		p = fonts[i].split('=');
+
+		if (p.length > 1)
+			addSelectValue(f, 'fontface', p[0], p[1]);
+	}
+
+	// Setup fontsize select box
+	fonts = ed.getParam("fullpage_fontsizes", defaultFontSizes).split(',');
+	for (i=0; i<fonts.length; i++)
+		addSelectValue(f, 'fontsize', fonts[i], fonts[i]);
+
+	// Setup mediatype select boxs
+	mediaTypes = ed.getParam("fullpage_media_types", defaultMediaTypes).split(',');
+	for (i=0; i<mediaTypes.length; i++) {
+		p = mediaTypes[i].split('=');
+
+		if (p.length > 1) {
+			addSelectValue(f, 'element_style_media', p[0], p[1]);
+			addSelectValue(f, 'element_link_media', p[0], p[1]);
+		}
+	}
+
+	// Setup encodings select box
+	encodings = ed.getParam("fullpage_encodings", defaultEncodings).split(',');
+	for (i=0; i<encodings.length; i++) {
+		p = encodings[i].split('=');
+
+		if (p.length > 1) {
+			addSelectValue(f, 'docencoding', p[0], p[1]);
+			addSelectValue(f, 'element_script_charset', p[0], p[1]);
+			addSelectValue(f, 'element_link_charset', p[0], p[1]);
+		}
+	}
+
+	document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor');
+	document.getElementById('link_color_pickcontainer').innerHTML = getColorPickerHTML('link_color_pick','link_color');
+	//document.getElementById('hover_color_pickcontainer').innerHTML = getColorPickerHTML('hover_color_pick','hover_color');
+	document.getElementById('visited_color_pickcontainer').innerHTML = getColorPickerHTML('visited_color_pick','visited_color');
+	document.getElementById('active_color_pickcontainer').innerHTML = getColorPickerHTML('active_color_pick','active_color');
+	document.getElementById('textcolor_pickcontainer').innerHTML = getColorPickerHTML('textcolor_pick','textcolor');
+	document.getElementById('stylesheet_browsercontainer').innerHTML = getBrowserHTML('stylesheetbrowser','stylesheet','file','fullpage');
+	document.getElementById('link_href_pickcontainer').innerHTML = getBrowserHTML('link_href_browser','element_link_href','file','fullpage');
+	document.getElementById('script_src_pickcontainer').innerHTML = getBrowserHTML('script_src_browser','element_script_src','file','fullpage');
+	document.getElementById('bgimage_pickcontainer').innerHTML = getBrowserHTML('bgimage_browser','bgimage','image','fullpage');
+
+	// Resize some elements
+	if (isVisible('stylesheetbrowser'))
+		document.getElementById('stylesheet').style.width = '220px';
+
+	if (isVisible('link_href_browser'))
+		document.getElementById('element_link_href').style.width = '230px';
+
+	if (isVisible('bgimage_browser'))
+		document.getElementById('bgimage').style.width = '210px';
+
+	// Add iframe
+	dom.add(document.body, 'iframe', {id : 'documentIframe', src : 'javascript:""', style : {display : 'none'}});
+	doc = dom.get('documentIframe').contentWindow.document;
+	h = tinyMCEPopup.getWindowArg('head_html');
+
+	// Preprocess the HTML disable scripts and urls
+	h = h.replace(/<script>/gi, '<script type="text/javascript">');
+	h = h.replace(/type=([\"\'])?/gi, 'type=$1-mce-');
+	h = h.replace(/(src=|href=)/g, 'mce_$1');
+
+	// Write in the content in the iframe
+	doc.write(h + '</body></html>');
+	doc.close();
+
+	// Parse xml and doctype
+	xmlVer = getReItem(/<\?\s*?xml.*?version\s*?=\s*?"(.*?)".*?\?>/gi, h, 1);
+	xmlEnc = getReItem(/<\?\s*?xml.*?encoding\s*?=\s*?"(.*?)".*?\?>/gi, h, 1);
+	docType = getReItem(/<\!DOCTYPE.*?>/gi, h.replace(/\n/g, ''), 0).replace(/ +/g, ' ');
+	f.langcode.value = getReItem(/lang="(.*?)"/gi, h, 1);
+
+	// Parse title
+	if (e = doc.getElementsByTagName('title')[0])
+		el.metatitle.value = e.textContent || e.text;
+
+	// Parse meta
+	tinymce.each(doc.getElementsByTagName('meta'), function(n) {
+		var na = (n.getAttribute('name', 2) || '').toLowerCase(), va = n.getAttribute('content', 2), eq = n.getAttribute('httpEquiv', 2) || '';
+
+		e = el['meta' + na];
+
+		if (na == 'robots') {
+			selectByValue(f, 'metarobots', tinymce.trim(va), true, true);
+			return;
+		}
+
+		switch (eq.toLowerCase()) {
+			case "content-type":
+				tmp = getReItem(/charset\s*=\s*(.*)\s*/gi, va, 1);
+
+				// Override XML encoding
+				if (tmp != "")
+					xmlEnc = tmp;
+
+				return;
+		}
+
+		if (e)
+			e.value = va;
+	});
+
+	selectByValue(f, 'doctypes', docType, true, true);
+	selectByValue(f, 'docencoding', xmlEnc, true, true);
+	selectByValue(f, 'langdir', doc.body.getAttribute('dir', 2) || '', true, true);
+
+	if (xmlVer != '')
+		el.xml_pi.checked = true;
+
+	// Parse appearance
+
+	// Parse primary stylesheet
+	tinymce.each(doc.getElementsByTagName("link"), function(l) {
+		var m = l.getAttribute('media', 2) || '', t = l.getAttribute('type', 2) || '';
+
+		if (t == "-mce-text/css" && (m == "" || m == "screen" || m == "all") && (l.getAttribute('rel', 2) || '') == "stylesheet") {
+			f.stylesheet.value = l.getAttribute('mce_href', 2) || '';
+			return false;
+		}
+	});
+
+	// Get from style elements
+	tinymce.each(doc.getElementsByTagName("style"), function(st) {
+		var tmp = parseStyleElement(st);
+
+		for (x=0; x<tmp.length; x++) {
+			if (tmp[x].rule.indexOf('a:visited') != -1 && tmp[x].data['color'])
+				f.visited_color.value = tmp[x].data['color'];
+
+			if (tmp[x].rule.indexOf('a:link') != -1 && tmp[x].data['color'])
+				f.link_color.value = tmp[x].data['color'];
+
+			if (tmp[x].rule.indexOf('a:active') != -1 && tmp[x].data['color'])
+				f.active_color.value = tmp[x].data['color'];
+		}
+	});
+
+	f.textcolor.value = tinyMCEPopup.dom.getAttrib(doc.body, "text");
+	f.active_color.value = tinyMCEPopup.dom.getAttrib(doc.body, "alink");
+	f.link_color.value = tinyMCEPopup.dom.getAttrib(doc.body, "link");
+	f.visited_color.value = tinyMCEPopup.dom.getAttrib(doc.body, "vlink");
+	f.bgcolor.value = tinyMCEPopup.dom.getAttrib(doc.body, "bgcolor");
+	f.bgimage.value = tinyMCEPopup.dom.getAttrib(doc.body, "background");
+
+	// Get from style info
+	style = tinyMCEPopup.dom.parseStyle(tinyMCEPopup.dom.getAttrib(doc.body, 'style'));
+
+	if (style['font-family'])
+		selectByValue(f, 'fontface', style['font-family'], true, true);
+	else
+		selectByValue(f, 'fontface', ed.getParam("fullpage_default_fontface", ""), true, true);
+
+	if (style['font-size'])
+		selectByValue(f, 'fontsize', style['font-size'], true, true);
+	else
+		selectByValue(f, 'fontsize', ed.getParam("fullpage_default_fontsize", ""), true, true);
+
+	if (style['color'])
+		f.textcolor.value = convertRGBToHex(style['color']);
+
+	if (style['background-image'])
+		f.bgimage.value = style['background-image'].replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");
+
+	if (style['background-color'])
+		f.bgcolor.value = style['background-color'];
+
+	if (style['margin']) {
+		tmp = style['margin'].replace(/[^0-9 ]/g, '');
+		tmp = tmp.split(/ +/);
+		f.topmargin.value = tmp.length > 0 ? tmp[0] : '';
+		f.rightmargin.value = tmp.length > 1 ? tmp[1] : tmp[0];
+		f.bottommargin.value = tmp.length > 2 ? tmp[2] : tmp[0];
+		f.leftmargin.value = tmp.length > 3 ? tmp[3] : tmp[0];
+	}
+
+	if (style['margin-left'])
+		f.leftmargin.value = style['margin-left'].replace(/[^0-9]/g, '');
+
+	if (style['margin-right'])
+		f.rightmargin.value = style['margin-right'].replace(/[^0-9]/g, '');
+
+	if (style['margin-top'])
+		f.topmargin.value = style['margin-top'].replace(/[^0-9]/g, '');
+
+	if (style['margin-bottom'])
+		f.bottommargin.value = style['margin-bottom'].replace(/[^0-9]/g, '');
+
+	f.style.value = tinyMCEPopup.dom.serializeStyle(style);
+
+	// Update colors
+	updateColor('textcolor_pick', 'textcolor');
+	updateColor('bgcolor_pick', 'bgcolor');
+	updateColor('visited_color_pick', 'visited_color');
+	updateColor('active_color_pick', 'active_color');
+	updateColor('link_color_pick', 'link_color');
+}
+
+function getReItem(r, s, i) {
+	var c = r.exec(s);
+
+	if (c && c.length > i)
+		return c[i];
+
+	return '';
+}
+
+function updateAction() {
+	var f = document.forms[0], nl, i, h, v, s, head, html, l, tmp, addlink = true, ser;
+
+	head = doc.getElementsByTagName('head')[0];
+
+	// Fix scripts without a type
+	nl = doc.getElementsByTagName('script');
+	for (i=0; i<nl.length; i++) {
+		if (tinyMCEPopup.dom.getAttrib(nl[i], 'mce_type') == '')
+			nl[i].setAttribute('mce_type', 'text/javascript');
+	}
+
+	// Get primary stylesheet
+	nl = doc.getElementsByTagName("link");
+	for (i=0; i<nl.length; i++) {
+		l = nl[i];
+
+		tmp = tinyMCEPopup.dom.getAttrib(l, 'media');
+
+		if (tinyMCEPopup.dom.getAttrib(l, 'mce_type') == "text/css" && (tmp == "" || tmp == "screen" || tmp == "all") && tinyMCEPopup.dom.getAttrib(l, 'rel') == "stylesheet") {
+			addlink = false;
+
+			if (f.stylesheet.value == '')
+				l.parentNode.removeChild(l);
+			else
+				l.setAttribute('mce_href', f.stylesheet.value);
+
+			break;
+		}
+	}
+
+	// Add new link
+	if (f.stylesheet.value != '') {
+		l = doc.createElement('link');
+
+		l.setAttribute('type', 'text/css');
+		l.setAttribute('mce_href', f.stylesheet.value);
+		l.setAttribute('rel', 'stylesheet');
+
+		head.appendChild(l);
+	}
+
+	setMeta(head, 'keywords', f.metakeywords.value);
+	setMeta(head, 'description', f.metadescription.value);
+	setMeta(head, 'author', f.metaauthor.value);
+	setMeta(head, 'copyright', f.metacopyright.value);
+	setMeta(head, 'robots', getSelectValue(f, 'metarobots'));
+	setMeta(head, 'Content-Type', getSelectValue(f, 'docencoding'));
+
+	doc.body.dir = getSelectValue(f, 'langdir');
+	doc.body.style.cssText = f.style.value;
+
+	doc.body.setAttribute('vLink', f.visited_color.value);
+	doc.body.setAttribute('link', f.link_color.value);
+	doc.body.setAttribute('text', f.textcolor.value);
+	doc.body.setAttribute('aLink', f.active_color.value);
+
+	doc.body.style.fontFamily = getSelectValue(f, 'fontface');
+	doc.body.style.fontSize = getSelectValue(f, 'fontsize');
+	doc.body.style.backgroundColor = f.bgcolor.value;
+
+	if (f.leftmargin.value != '')
+		doc.body.style.marginLeft = f.leftmargin.value + 'px';
+
+	if (f.rightmargin.value != '')
+		doc.body.style.marginRight = f.rightmargin.value + 'px';
+
+	if (f.bottommargin.value != '')
+		doc.body.style.marginBottom = f.bottommargin.value + 'px';
+
+	if (f.topmargin.value != '')
+		doc.body.style.marginTop = f.topmargin.value + 'px';
+
+	html = doc.getElementsByTagName('html')[0];
+	html.setAttribute('lang', f.langcode.value);
+	html.setAttribute('xml:lang', f.langcode.value);
+
+	if (f.bgimage.value != '')
+		doc.body.style.backgroundImage = "url('" + f.bgimage.value + "')";
+	else
+		doc.body.style.backgroundImage = '';
+
+	ser = tinyMCEPopup.editor.plugins.fullpage._createSerializer();
+	ser.setRules('-title,meta[http-equiv|name|content],base[href|target],link[href|rel|type|title|media],style[type],script[type|language|src],html[lang|xml::lang|xmlns],body[style|dir|vlink|link|text|alink],head');
+
+	h = ser.serialize(doc.documentElement);
+	h = h.substring(0, h.lastIndexOf('</body>'));
+
+	if (h.indexOf('<title>') == -1)
+		h = h.replace(/<head.*?>/, '$&\n' + '<title>' + tinyMCEPopup.dom.encode(f.metatitle.value) + '</title>');
+	else
+		h = h.replace(/<title>(.*?)<\/title>/, '<title>' + tinyMCEPopup.dom.encode(f.metatitle.value) + '</title>');
+
+	if ((v = getSelectValue(f, 'doctypes')) != '')
+		h = v + '\n' + h;
+
+	if (f.xml_pi.checked) {
+		s = '<?xml version="1.0"';
+
+		if ((v = getSelectValue(f, 'docencoding')) != '')
+			s += ' encoding="' + v + '"';
+
+		s += '?>\n';
+		h = s + h;
+	}
+
+	h = h.replace(/type=\"\-mce\-/gi, 'type="');
+
+	tinyMCEPopup.editor.plugins.fullpage.head = h;
+	tinyMCEPopup.editor.plugins.fullpage._setBodyAttribs(tinyMCEPopup.editor, {});
+	tinyMCEPopup.close();
+}
+
+function changedStyleField(field) {
+}
+
+function setMeta(he, k, v) {
+	var nl, i, m;
+
+	nl = he.getElementsByTagName('meta');
+	for (i=0; i<nl.length; i++) {
+		if (k == 'Content-Type' && tinyMCEPopup.dom.getAttrib(nl[i], 'http-equiv') == k) {
+			if (v == '')
+				nl[i].parentNode.removeChild(nl[i]);
+			else
+				nl[i].setAttribute('content', "text/html; charset=" + v);
+
+			return;
+		}
+
+		if (tinyMCEPopup.dom.getAttrib(nl[i], 'name') == k) {
+			if (v == '')
+				nl[i].parentNode.removeChild(nl[i]);
+			else
+				nl[i].setAttribute('content', v);
+			return;
+		}
+	}
+
+	if (v == '')
+		return;
+
+	m = doc.createElement('meta');
+
+	if (k == 'Content-Type')
+		m.httpEquiv = k;
+	else
+		m.setAttribute('name', k);
+
+	m.setAttribute('content', v);
+	he.appendChild(m);
+}
+
+function parseStyleElement(e) {
+	var v = e.innerHTML;
+	var p, i, r;
+
+	v = v.replace(/<!--/gi, '');
+	v = v.replace(/-->/gi, '');
+	v = v.replace(/[\n\r]/gi, '');
+	v = v.replace(/\s+/gi, ' ');
+
+	r = [];
+	p = v.split(/{|}/);
+
+	for (i=0; i<p.length; i+=2) {
+		if (p[i] != "")
+			r[r.length] = {rule : tinymce.trim(p[i]), data : tinyMCEPopup.dom.parseStyle(p[i+1])};
+	}
+
+	return r;
+}
+
+function serializeStyleElement(d) {
+	var i, s, st;
+
+	s = '<!--\n';
+
+	for (i=0; i<d.length; i++) {
+		s += d[i].rule + ' {\n';
+
+		st = tinyMCE.serializeStyle(d[i].data);
+
+		if (st != '')
+			st += ';';
+
+		s += st.replace(/;/g, ';\n');
+		s += '}\n';
+
+		if (i != d.length - 1)
+			s += '\n';
+	}
+
+	s += '\n-->';
+
+	return s;
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/fullpage/langs/en_dlg.js b/media/js/tinymce/plugins/fullpage/langs/en_dlg.js
new file mode 100644
index 0000000..f5801b8
--- /dev/null
+++ b/media/js/tinymce/plugins/fullpage/langs/en_dlg.js
@@ -0,0 +1,85 @@
+tinyMCE.addI18n('en.fullpage_dlg',{
+title:"Document properties",
+meta_tab:"General",
+appearance_tab:"Appearance",
+advanced_tab:"Advanced",
+meta_props:"Meta information",
+langprops:"Language and encoding",
+meta_title:"Title",
+meta_keywords:"Keywords",
+meta_description:"Description",
+meta_robots:"Robots",
+doctypes:"Doctype",
+langcode:"Language code",
+langdir:"Language direction",
+ltr:"Left to right",
+rtl:"Right to left",
+xml_pi:"XML declaration",
+encoding:"Character encoding",
+appearance_bgprops:"Background properties",
+appearance_marginprops:"Body margins",
+appearance_linkprops:"Link colors",
+appearance_textprops:"Text properties",
+bgcolor:"Background color",
+bgimage:"Background image",
+left_margin:"Left margin",
+right_margin:"Right margin",
+top_margin:"Top margin",
+bottom_margin:"Bottom margin",
+text_color:"Text color",
+font_size:"Font size",
+font_face:"Font face",
+link_color:"Link color",
+hover_color:"Hover color",
+visited_color:"Visited color",
+active_color:"Active color",
+textcolor:"Color",
+fontsize:"Font size",
+fontface:"Font family",
+meta_index_follow:"Index and follow the links",
+meta_index_nofollow:"Index and don't follow the links",
+meta_noindex_follow:"Do not index but follow the links",
+meta_noindex_nofollow:"Do not index and don\'t follow the links",
+appearance_style:"Stylesheet and style properties",
+stylesheet:"Stylesheet",
+style:"Style",
+author:"Author",
+copyright:"Copyright",
+add:"Add new element",
+remove:"Remove selected element",
+moveup:"Move selected element up",
+movedown:"Move selected element down",
+head_elements:"Head elements",
+info:"Information",
+add_title:"Title element",
+add_meta:"Meta element",
+add_script:"Script element",
+add_style:"Style element",
+add_link:"Link element",
+add_base:"Base element",
+add_comment:"Comment node",
+title_element:"Title element",
+script_element:"Script element",
+style_element:"Style element",
+base_element:"Base element",
+link_element:"Link element",
+meta_element:"Meta element",
+comment_element:"Comment",
+src:"Src",
+language:"Language",
+href:"Href",
+target:"Target",
+type:"Type",
+charset:"Charset",
+defer:"Defer",
+media:"Media",
+properties:"Properties",
+name:"Name",
+value:"Value",
+content:"Content",
+rel:"Rel",
+rev:"Rev",
+hreflang:"Href lang",
+general_props:"General",
+advanced_props:"Advanced"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/fullscreen/editor_plugin.js b/media/js/tinymce/plugins/fullscreen/editor_plugin.js
new file mode 100644
index 0000000..dfb3f16
--- /dev/null
+++ b/media/js/tinymce/plugins/fullscreen/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.DOM;tinymce.create("tinymce.plugins.FullScreenPlugin",{init:function(c,d){var e=this,f={},b;e.editor=c;c.addCommand("mceFullScreen",function(){var h,i=a.doc.documentElement;if(c.getParam("fullscreen_is_enabled")){if(c.getParam("fullscreen_new_window")){closeFullscreen()}else{a.win.setTimeout(function(){tinymce.dom.Event.remove(a.win,"resize",e.resizeFunc);tinyMCE.get(c.getParam("fullscreen_editor_id")).setContent(c.getContent({format:"raw"}),{format:"raw"});tiny [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/fullscreen/editor_plugin_src.js b/media/js/tinymce/plugins/fullscreen/editor_plugin_src.js
new file mode 100644
index 0000000..77a8c3b
--- /dev/null
+++ b/media/js/tinymce/plugins/fullscreen/editor_plugin_src.js
@@ -0,0 +1,145 @@
+/**
+ * $Id: editor_plugin_src.js 923 2008-09-09 16:45:29Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var DOM = tinymce.DOM;
+
+	tinymce.create('tinymce.plugins.FullScreenPlugin', {
+		init : function(ed, url) {
+			var t = this, s = {}, vp;
+
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceFullScreen', function() {
+				var win, de = DOM.doc.documentElement;
+
+				if (ed.getParam('fullscreen_is_enabled')) {
+					if (ed.getParam('fullscreen_new_window'))
+						closeFullscreen(); // Call to close in new window
+					else {
+						DOM.win.setTimeout(function() {
+							tinymce.dom.Event.remove(DOM.win, 'resize', t.resizeFunc);
+							tinyMCE.get(ed.getParam('fullscreen_editor_id')).setContent(ed.getContent({format : 'raw'}), {format : 'raw'});
+							tinyMCE.remove(ed);
+							DOM.remove('mce_fullscreen_container');
+							de.style.overflow = ed.getParam('fullscreen_html_overflow');
+							DOM.setStyle(DOM.doc.body, 'overflow', ed.getParam('fullscreen_overflow'));
+							DOM.win.scrollTo(ed.getParam('fullscreen_scrollx'), ed.getParam('fullscreen_scrolly'));
+							tinyMCE.settings = tinyMCE.oldSettings; // Restore old settings
+						}, 10);
+					}
+
+					return;
+				}
+
+				if (ed.getParam('fullscreen_new_window')) {
+					win = DOM.win.open(url + "/fullscreen.htm", "mceFullScreenPopup", "fullscreen=yes,menubar=no,toolbar=no,scrollbars=no,resizable=yes,left=0,top=0,width=" + screen.availWidth + ",height=" + screen.availHeight);
+					try {
+						win.resizeTo(screen.availWidth, screen.availHeight);
+					} catch (e) {
+						// Ignore
+					}
+				} else {
+					tinyMCE.oldSettings = tinyMCE.settings; // Store old settings
+					s.fullscreen_overflow = DOM.getStyle(DOM.doc.body, 'overflow', 1) || 'auto';
+					s.fullscreen_html_overflow = DOM.getStyle(de, 'overflow', 1);
+					vp = DOM.getViewPort();
+					s.fullscreen_scrollx = vp.x;
+					s.fullscreen_scrolly = vp.y;
+
+					// Fixes an Opera bug where the scrollbars doesn't reappear
+					if (tinymce.isOpera && s.fullscreen_overflow == 'visible')
+						s.fullscreen_overflow = 'auto';
+
+					// Fixes an IE bug where horizontal scrollbars would appear
+					if (tinymce.isIE && s.fullscreen_overflow == 'scroll')
+						s.fullscreen_overflow = 'auto';
+
+					// Fixes an IE bug where the scrollbars doesn't reappear
+					if (tinymce.isIE && (s.fullscreen_html_overflow == 'visible' || s.fullscreen_html_overflow == 'scroll'))
+						s.fullscreen_html_overflow = 'auto'; 
+
+					if (s.fullscreen_overflow == '0px')
+						s.fullscreen_overflow = '';
+
+					DOM.setStyle(DOM.doc.body, 'overflow', 'hidden');
+					de.style.overflow = 'hidden'; //Fix for IE6/7
+					vp = DOM.getViewPort();
+					DOM.win.scrollTo(0, 0);
+
+					if (tinymce.isIE)
+						vp.h -= 1;
+
+					n = DOM.add(DOM.doc.body, 'div', {id : 'mce_fullscreen_container', style : 'position:' + (tinymce.isIE6 || (tinymce.isIE && !DOM.boxModel) ? 'absolute' : 'fixed') + ';top:0;left:0;width:' + vp.w + 'px;height:' + vp.h + 'px;z-index:200000;'});
+					DOM.add(n, 'div', {id : 'mce_fullscreen'});
+
+					tinymce.each(ed.settings, function(v, n) {
+						s[n] = v;
+					});
+
+					s.id = 'mce_fullscreen';
+					s.width = n.clientWidth;
+					s.height = n.clientHeight - 15;
+					s.fullscreen_is_enabled = true;
+					s.fullscreen_editor_id = ed.id;
+					s.theme_advanced_resizing = false;
+					s.save_onsavecallback = function() {
+						ed.setContent(tinyMCE.get(s.id).getContent({format : 'raw'}), {format : 'raw'});
+						ed.execCommand('mceSave');
+					};
+
+					tinymce.each(ed.getParam('fullscreen_settings'), function(v, k) {
+						s[k] = v;
+					});
+
+					if (s.theme_advanced_toolbar_location === 'external')
+						s.theme_advanced_toolbar_location = 'top';
+
+					t.fullscreenEditor = new tinymce.Editor('mce_fullscreen', s);
+					t.fullscreenEditor.onInit.add(function() {
+						t.fullscreenEditor.setContent(ed.getContent());
+						t.fullscreenEditor.focus();
+					});
+
+					t.fullscreenEditor.render();
+					tinyMCE.add(t.fullscreenEditor);
+
+					t.fullscreenElement = new tinymce.dom.Element('mce_fullscreen_container');
+					t.fullscreenElement.update();
+					//document.body.overflow = 'hidden';
+
+					t.resizeFunc = tinymce.dom.Event.add(DOM.win, 'resize', function() {
+						var vp = tinymce.DOM.getViewPort();
+
+						t.fullscreenEditor.theme.resizeTo(vp.w, vp.h);
+					});
+				}
+			});
+
+			// Register buttons
+			ed.addButton('fullscreen', {title : 'fullscreen.desc', cmd : 'mceFullScreen'});
+
+			ed.onNodeChange.add(function(ed, cm) {
+				cm.setActive('fullscreen', ed.getParam('fullscreen_is_enabled'));
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Fullscreen',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('fullscreen', tinymce.plugins.FullScreenPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/fullscreen/fullscreen.htm b/media/js/tinymce/plugins/fullscreen/fullscreen.htm
new file mode 100644
index 0000000..6ec4f26
--- /dev/null
+++ b/media/js/tinymce/plugins/fullscreen/fullscreen.htm
@@ -0,0 +1,110 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title></title>
+	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+	<script type="text/javascript" src="../../tiny_mce.js"></script>
+	<script type="text/javascript">
+		function patchCallback(settings, key) {
+			if (settings[key])
+				settings[key] = "window.opener." + settings[key];
+		}
+
+		var settings = {}, paSe = window.opener.tinyMCE.activeEditor.settings, oeID = window.opener.tinyMCE.activeEditor.id;
+
+		// Clone array
+		for (var n in paSe)
+			settings[n] = paSe[n];
+
+		// Override options for fullscreen
+		for (var n in paSe.fullscreen_settings)
+			settings[n] = paSe.fullscreen_settings[n];
+
+		// Patch callbacks, make them point to window.opener
+		patchCallback(settings, 'urlconverter_callback');
+		patchCallback(settings, 'insertlink_callback');
+		patchCallback(settings, 'insertimage_callback');
+		patchCallback(settings, 'setupcontent_callback');
+		patchCallback(settings, 'save_callback');
+		patchCallback(settings, 'onchange_callback');
+		patchCallback(settings, 'init_instance_callback');
+		patchCallback(settings, 'file_browser_callback');
+		patchCallback(settings, 'cleanup_callback');
+		patchCallback(settings, 'execcommand_callback');
+		patchCallback(settings, 'oninit');
+
+		// Set options
+		delete settings.id;
+		settings['mode'] = 'exact';
+		settings['elements'] = 'fullscreenarea';
+		settings['add_unload_trigger'] = false;
+		settings['ask'] = false;
+		settings['document_base_url'] = window.opener.tinyMCE.activeEditor.documentBaseURI.getURI();
+		settings['fullscreen_is_enabled'] = true;
+		settings['fullscreen_editor_id'] = oeID;
+		settings['theme_advanced_resizing'] = false;
+		settings['strict_loading_mode'] = true;
+
+		settings.save_onsavecallback = function() {
+			window.opener.tinyMCE.get(oeID).setContent(tinyMCE.get('fullscreenarea').getContent({format : 'raw'}), {format : 'raw'});
+			window.opener.tinyMCE.get(oeID).execCommand('mceSave');
+			window.close();
+		};
+
+		function unloadHandler(e) {
+			moveContent();
+		}
+
+		function moveContent() {
+			window.opener.tinyMCE.get(oeID).setContent(tinyMCE.activeEditor.getContent());
+		}
+
+		function closeFullscreen() {
+			moveContent();
+			window.close();
+		}
+
+		function doParentSubmit() {
+			moveContent();
+
+			if (window.opener.tinyMCE.selectedInstance.formElement.form)
+				window.opener.tinyMCE.selectedInstance.formElement.form.submit();
+
+			window.close();
+
+			return false;
+		}
+
+		function render() {
+			var e = document.getElementById('fullscreenarea'), vp, ed, ow, oh, dom = tinymce.DOM;
+
+			e.value = window.opener.tinyMCE.get(oeID).getContent();
+
+			vp = dom.getViewPort();
+			settings.width = vp.w;
+			settings.height = vp.h - 15;
+
+			tinymce.dom.Event.add(window, 'resize', function() {
+				var vp = dom.getViewPort();
+
+				tinyMCE.activeEditor.theme.resizeTo(vp.w, vp.h);
+			});
+
+			tinyMCE.init(settings);
+		}
+
+		// Add onunload
+		tinymce.dom.Event.add(window, "beforeunload", unloadHandler);
+	</script>
+</head>
+<body style="margin:0;overflow:hidden;width:100%;height:100%" scrolling="no" scroll="no">
+<form onsubmit="doParentSubmit();">
+<textarea id="fullscreenarea" style="width:100%; height:100%"></textarea>
+</form>
+
+<script type="text/javascript">
+	render();
+</script>
+
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/iespell/editor_plugin.js b/media/js/tinymce/plugins/iespell/editor_plugin.js
new file mode 100644
index 0000000..e9cba10
--- /dev/null
+++ b/media/js/tinymce/plugins/iespell/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.IESpell",{init:function(a,b){var c=this,d;if(!tinymce.isIE){return}c.editor=a;a.addCommand("mceIESpell",function(){try{d=new ActiveXObject("ieSpell.ieSpellExtension");d.CheckDocumentNode(a.getDoc().documentElement)}catch(f){if(f.number==-2146827859){a.windowManager.confirm(a.getLang("iespell.download"),function(e){if(e){window.open("http://www.iespell.com/download.php","ieSpellDownload","")}})}else{a.windowManager.alert("Error Loading ieSpell:  [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/iespell/editor_plugin_src.js b/media/js/tinymce/plugins/iespell/editor_plugin_src.js
new file mode 100644
index 0000000..a68f69a
--- /dev/null
+++ b/media/js/tinymce/plugins/iespell/editor_plugin_src.js
@@ -0,0 +1,51 @@
+/**
+ * $Id: editor_plugin_src.js 520 2008-01-07 16:30:32Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.IESpell', {
+		init : function(ed, url) {
+			var t = this, sp;
+
+			if (!tinymce.isIE)
+				return;
+
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceIESpell', function() {
+				try {
+					sp = new ActiveXObject("ieSpell.ieSpellExtension");
+					sp.CheckDocumentNode(ed.getDoc().documentElement);
+				} catch (e) {
+					if (e.number == -2146827859) {
+						ed.windowManager.confirm(ed.getLang("iespell.download"), function(s) {
+							if (s)
+								window.open('http://www.iespell.com/download.php', 'ieSpellDownload', '');
+						});
+					} else
+						ed.windowManager.alert("Error Loading ieSpell: Exception " + e.number);
+				}
+			});
+
+			// Register buttons
+			ed.addButton('iespell', {title : 'iespell.iespell_desc', cmd : 'mceIESpell'});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'IESpell (IE Only)',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/iespell',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('iespell', tinymce.plugins.IESpell);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/inlinepopups/editor_plugin.js b/media/js/tinymce/plugins/inlinepopups/editor_plugin.js
new file mode 100644
index 0000000..07ea477
--- /dev/null
+++ b/media/js/tinymce/plugins/inlinepopups/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var d=tinymce.DOM,b=tinymce.dom.Element,a=tinymce.dom.Event,e=tinymce.each,c=tinymce.is;tinymce.create("tinymce.plugins.InlinePopups",{init:function(f,g){f.onBeforeRenderUI.add(function(){f.windowManager=new tinymce.InlineWindowManager(f);d.loadCSS(g+"/skins/"+(f.settings.inlinepopups_skin||"clearlooks2")+"/window.css")})},getInfo:function(){return{longname:"InlinePopups",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/inlinepopups/editor_plugin_src.js b/media/js/tinymce/plugins/inlinepopups/editor_plugin_src.js
new file mode 100644
index 0000000..fffca5a
--- /dev/null
+++ b/media/js/tinymce/plugins/inlinepopups/editor_plugin_src.js
@@ -0,0 +1,632 @@
+/**
+ * $Id: editor_plugin_src.js 1150 2009-06-01 11:50:46Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var DOM = tinymce.DOM, Element = tinymce.dom.Element, Event = tinymce.dom.Event, each = tinymce.each, is = tinymce.is;
+
+	tinymce.create('tinymce.plugins.InlinePopups', {
+		init : function(ed, url) {
+			// Replace window manager
+			ed.onBeforeRenderUI.add(function() {
+				ed.windowManager = new tinymce.InlineWindowManager(ed);
+				DOM.loadCSS(url + '/skins/' + (ed.settings.inlinepopups_skin || 'clearlooks2') + "/window.css");
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'InlinePopups',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	tinymce.create('tinymce.InlineWindowManager:tinymce.WindowManager', {
+		InlineWindowManager : function(ed) {
+			var t = this;
+
+			t.parent(ed);
+			t.zIndex = 300000;
+			t.count = 0;
+			t.windows = {};
+		},
+
+		open : function(f, p) {
+			var t = this, id, opt = '', ed = t.editor, dw = 0, dh = 0, vp, po, mdf, clf, we, w, u;
+
+			f = f || {};
+			p = p || {};
+
+			// Run native windows
+			if (!f.inline)
+				return t.parent(f, p);
+
+			// Only store selection if the type is a normal window
+			if (!f.type)
+				t.bookmark = ed.selection.getBookmark(1);
+
+			id = DOM.uniqueId();
+			vp = DOM.getViewPort();
+			f.width = parseInt(f.width || 320);
+			f.height = parseInt(f.height || 240) + (tinymce.isIE ? 8 : 0);
+			f.min_width = parseInt(f.min_width || 150);
+			f.min_height = parseInt(f.min_height || 100);
+			f.max_width = parseInt(f.max_width || 2000);
+			f.max_height = parseInt(f.max_height || 2000);
+			f.left = f.left || Math.round(Math.max(vp.x, vp.x + (vp.w / 2.0) - (f.width / 2.0)));
+			f.top = f.top || Math.round(Math.max(vp.y, vp.y + (vp.h / 2.0) - (f.height / 2.0)));
+			f.movable = f.resizable = true;
+			p.mce_width = f.width;
+			p.mce_height = f.height;
+			p.mce_inline = true;
+			p.mce_window_id = id;
+			p.mce_auto_focus = f.auto_focus;
+
+			// Transpose
+//			po = DOM.getPos(ed.getContainer());
+//			f.left -= po.x;
+//			f.top -= po.y;
+
+			t.features = f;
+			t.params = p;
+			t.onOpen.dispatch(t, f, p);
+
+			if (f.type) {
+				opt += ' mceModal';
+
+				if (f.type)
+					opt += ' mce' + f.type.substring(0, 1).toUpperCase() + f.type.substring(1);
+
+				f.resizable = false;
+			}
+
+			if (f.statusbar)
+				opt += ' mceStatusbar';
+
+			if (f.resizable)
+				opt += ' mceResizable';
+
+			if (f.minimizable)
+				opt += ' mceMinimizable';
+
+			if (f.maximizable)
+				opt += ' mceMaximizable';
+
+			if (f.movable)
+				opt += ' mceMovable';
+
+			// Create DOM objects
+			t._addAll(DOM.doc.body, 
+				['div', {id : id, 'class' : ed.settings.inlinepopups_skin || 'clearlooks2', style : 'width:100px;height:100px'}, 
+					['div', {id : id + '_wrapper', 'class' : 'mceWrapper' + opt},
+						['div', {id : id + '_top', 'class' : 'mceTop'}, 
+							['div', {'class' : 'mceLeft'}],
+							['div', {'class' : 'mceCenter'}],
+							['div', {'class' : 'mceRight'}],
+							['span', {id : id + '_title'}, f.title || '']
+						],
+
+						['div', {id : id + '_middle', 'class' : 'mceMiddle'}, 
+							['div', {id : id + '_left', 'class' : 'mceLeft'}],
+							['span', {id : id + '_content'}],
+							['div', {id : id + '_right', 'class' : 'mceRight'}]
+						],
+
+						['div', {id : id + '_bottom', 'class' : 'mceBottom'},
+							['div', {'class' : 'mceLeft'}],
+							['div', {'class' : 'mceCenter'}],
+							['div', {'class' : 'mceRight'}],
+							['span', {id : id + '_status'}, 'Content']
+						],
+
+						['a', {'class' : 'mceMove', tabindex : '-1', href : 'javascript:;'}],
+						['a', {'class' : 'mceMin', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}],
+						['a', {'class' : 'mceMax', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}],
+						['a', {'class' : 'mceMed', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}],
+						['a', {'class' : 'mceClose', tabindex : '-1', href : 'javascript:;', onmousedown : 'return false;'}],
+						['a', {id : id + '_resize_n', 'class' : 'mceResize mceResizeN', tabindex : '-1', href : 'javascript:;'}],
+						['a', {id : id + '_resize_s', 'class' : 'mceResize mceResizeS', tabindex : '-1', href : 'javascript:;'}],
+						['a', {id : id + '_resize_w', 'class' : 'mceResize mceResizeW', tabindex : '-1', href : 'javascript:;'}],
+						['a', {id : id + '_resize_e', 'class' : 'mceResize mceResizeE', tabindex : '-1', href : 'javascript:;'}],
+						['a', {id : id + '_resize_nw', 'class' : 'mceResize mceResizeNW', tabindex : '-1', href : 'javascript:;'}],
+						['a', {id : id + '_resize_ne', 'class' : 'mceResize mceResizeNE', tabindex : '-1', href : 'javascript:;'}],
+						['a', {id : id + '_resize_sw', 'class' : 'mceResize mceResizeSW', tabindex : '-1', href : 'javascript:;'}],
+						['a', {id : id + '_resize_se', 'class' : 'mceResize mceResizeSE', tabindex : '-1', href : 'javascript:;'}]
+					]
+				]
+			);
+
+			DOM.setStyles(id, {top : -10000, left : -10000});
+
+			// Fix gecko rendering bug, where the editors iframe messed with window contents
+			if (tinymce.isGecko)
+				DOM.setStyle(id, 'overflow', 'auto');
+
+			// Measure borders
+			if (!f.type) {
+				dw += DOM.get(id + '_left').clientWidth;
+				dw += DOM.get(id + '_right').clientWidth;
+				dh += DOM.get(id + '_top').clientHeight;
+				dh += DOM.get(id + '_bottom').clientHeight;
+			}
+
+			// Resize window
+			DOM.setStyles(id, {top : f.top, left : f.left, width : f.width + dw, height : f.height + dh});
+
+			u = f.url || f.file;
+			if (u) {
+				if (tinymce.relaxedDomain)
+					u += (u.indexOf('?') == -1 ? '?' : '&') + 'mce_rdomain=' + tinymce.relaxedDomain;
+
+				u = tinymce._addVer(u);
+			}
+
+			if (!f.type) {
+				DOM.add(id + '_content', 'iframe', {id : id + '_ifr', src : 'javascript:""', frameBorder : 0, style : 'border:0;width:10px;height:10px'});
+				DOM.setStyles(id + '_ifr', {width : f.width, height : f.height});
+				DOM.setAttrib(id + '_ifr', 'src', u);
+			} else {
+				DOM.add(id + '_wrapper', 'a', {id : id + '_ok', 'class' : 'mceButton mceOk', href : 'javascript:;', onmousedown : 'return false;'}, 'Ok');
+
+				if (f.type == 'confirm')
+					DOM.add(id + '_wrapper', 'a', {'class' : 'mceButton mceCancel', href : 'javascript:;', onmousedown : 'return false;'}, 'Cancel');
+
+				DOM.add(id + '_middle', 'div', {'class' : 'mceIcon'});
+				DOM.setHTML(id + '_content', f.content.replace('\n', '<br />'));
+			}
+
+			// Register events
+			mdf = Event.add(id, 'mousedown', function(e) {
+				var n = e.target, w, vp;
+
+				w = t.windows[id];
+				t.focus(id);
+
+				if (n.nodeName == 'A' || n.nodeName == 'a') {
+					if (n.className == 'mceMax') {
+						w.oldPos = w.element.getXY();
+						w.oldSize = w.element.getSize();
+
+						vp = DOM.getViewPort();
+
+						// Reduce viewport size to avoid scrollbars
+						vp.w -= 2;
+						vp.h -= 2;
+
+						w.element.moveTo(vp.x, vp.y);
+						w.element.resizeTo(vp.w, vp.h);
+						DOM.setStyles(id + '_ifr', {width : vp.w - w.deltaWidth, height : vp.h - w.deltaHeight});
+						DOM.addClass(id + '_wrapper', 'mceMaximized');
+					} else if (n.className == 'mceMed') {
+						// Reset to old size
+						w.element.moveTo(w.oldPos.x, w.oldPos.y);
+						w.element.resizeTo(w.oldSize.w, w.oldSize.h);
+						w.iframeElement.resizeTo(w.oldSize.w - w.deltaWidth, w.oldSize.h - w.deltaHeight);
+
+						DOM.removeClass(id + '_wrapper', 'mceMaximized');
+					} else if (n.className == 'mceMove')
+						return t._startDrag(id, e, n.className);
+					else if (DOM.hasClass(n, 'mceResize'))
+						return t._startDrag(id, e, n.className.substring(13));
+				}
+			});
+
+			clf = Event.add(id, 'click', function(e) {
+				var n = e.target;
+
+				t.focus(id);
+
+				if (n.nodeName == 'A' || n.nodeName == 'a') {
+					switch (n.className) {
+						case 'mceClose':
+							t.close(null, id);
+							return Event.cancel(e);
+
+						case 'mceButton mceOk':
+						case 'mceButton mceCancel':
+							f.button_func(n.className == 'mceButton mceOk');
+							return Event.cancel(e);
+					}
+				}
+			});
+
+			// Add window
+			w = t.windows[id] = {
+				id : id,
+				mousedown_func : mdf,
+				click_func : clf,
+				element : new Element(id, {blocker : 1, container : ed.getContainer()}),
+				iframeElement : new Element(id + '_ifr'),
+				features : f,
+				deltaWidth : dw,
+				deltaHeight : dh
+			};
+
+			w.iframeElement.on('focus', function() {
+				t.focus(id);
+			});
+
+			// Setup blocker
+			if (t.count == 0 && t.editor.getParam('dialog_type', 'modal') == 'modal') {
+				DOM.add(DOM.doc.body, 'div', {
+					id : 'mceModalBlocker',
+					'class' : (t.editor.settings.inlinepopups_skin || 'clearlooks2') + '_modalBlocker',
+					style : {zIndex : t.zIndex - 1}
+				});
+
+				DOM.show('mceModalBlocker'); // Reduces flicker in IE
+			} else
+				DOM.setStyle('mceModalBlocker', 'z-index', t.zIndex - 1);
+
+			if (tinymce.isIE6 || /Firefox\/2\./.test(navigator.userAgent) || (tinymce.isIE && !DOM.boxModel))
+				DOM.setStyles('mceModalBlocker', {position : 'absolute', left : vp.x, top : vp.y, width : vp.w - 2, height : vp.h - 2});
+
+			t.focus(id);
+			t._fixIELayout(id, 1);
+
+			// Focus ok button
+			if (DOM.get(id + '_ok'))
+				DOM.get(id + '_ok').focus();
+
+			t.count++;
+
+			return w;
+		},
+
+		focus : function(id) {
+			var t = this, w;
+
+			if (w = t.windows[id]) {
+				w.zIndex = this.zIndex++;
+				w.element.setStyle('zIndex', w.zIndex);
+				w.element.update();
+
+				id = id + '_wrapper';
+				DOM.removeClass(t.lastId, 'mceFocus');
+				DOM.addClass(id, 'mceFocus');
+				t.lastId = id;
+			}
+		},
+
+		_addAll : function(te, ne) {
+			var i, n, t = this, dom = tinymce.DOM;
+
+			if (is(ne, 'string'))
+				te.appendChild(dom.doc.createTextNode(ne));
+			else if (ne.length) {
+				te = te.appendChild(dom.create(ne[0], ne[1]));
+
+				for (i=2; i<ne.length; i++)
+					t._addAll(te, ne[i]);
+			}
+		},
+
+		_startDrag : function(id, se, ac) {
+			var t = this, mu, mm, d = DOM.doc, eb, w = t.windows[id], we = w.element, sp = we.getXY(), p, sz, ph, cp, vp, sx, sy, sex, sey, dx, dy, dw, dh;
+
+			// Get positons and sizes
+//			cp = DOM.getPos(t.editor.getContainer());
+			cp = {x : 0, y : 0};
+			vp = DOM.getViewPort();
+
+			// Reduce viewport size to avoid scrollbars while dragging
+			vp.w -= 2;
+			vp.h -= 2;
+
+			sex = se.screenX;
+			sey = se.screenY;
+			dx = dy = dw = dh = 0;
+
+			// Handle mouse up
+			mu = Event.add(d, 'mouseup', function(e) {
+				Event.remove(d, 'mouseup', mu);
+				Event.remove(d, 'mousemove', mm);
+
+				if (eb)
+					eb.remove();
+
+				we.moveBy(dx, dy);
+				we.resizeBy(dw, dh);
+				sz = we.getSize();
+				DOM.setStyles(id + '_ifr', {width : sz.w - w.deltaWidth, height : sz.h - w.deltaHeight});
+				t._fixIELayout(id, 1);
+
+				return Event.cancel(e);
+			});
+
+			if (ac != 'Move')
+				startMove();
+
+			function startMove() {
+				if (eb)
+					return;
+
+				t._fixIELayout(id, 0);
+
+				// Setup event blocker
+				DOM.add(d.body, 'div', {
+					id : 'mceEventBlocker',
+					'class' : 'mceEventBlocker ' + (t.editor.settings.inlinepopups_skin || 'clearlooks2'),
+					style : {zIndex : t.zIndex + 1}
+				});
+
+				if (tinymce.isIE6 || (tinymce.isIE && !DOM.boxModel))
+					DOM.setStyles('mceEventBlocker', {position : 'absolute', left : vp.x, top : vp.y, width : vp.w - 2, height : vp.h - 2});
+
+				eb = new Element('mceEventBlocker');
+				eb.update();
+
+				// Setup placeholder
+				p = we.getXY();
+				sz = we.getSize();
+				sx = cp.x + p.x - vp.x;
+				sy = cp.y + p.y - vp.y;
+				DOM.add(eb.get(), 'div', {id : 'mcePlaceHolder', 'class' : 'mcePlaceHolder', style : {left : sx, top : sy, width : sz.w, height : sz.h}});
+				ph = new Element('mcePlaceHolder');
+			};
+
+			// Handle mouse move/drag
+			mm = Event.add(d, 'mousemove', function(e) {
+				var x, y, v;
+
+				startMove();
+
+				x = e.screenX - sex;
+				y = e.screenY - sey;
+
+				switch (ac) {
+					case 'ResizeW':
+						dx = x;
+						dw = 0 - x;
+						break;
+
+					case 'ResizeE':
+						dw = x;
+						break;
+
+					case 'ResizeN':
+					case 'ResizeNW':
+					case 'ResizeNE':
+						if (ac == "ResizeNW") {
+							dx = x;
+							dw = 0 - x;
+						} else if (ac == "ResizeNE")
+							dw = x;
+
+						dy = y;
+						dh = 0 - y;
+						break;
+
+					case 'ResizeS':
+					case 'ResizeSW':
+					case 'ResizeSE':
+						if (ac == "ResizeSW") {
+							dx = x;
+							dw = 0 - x;
+						} else if (ac == "ResizeSE")
+							dw = x;
+
+						dh = y;
+						break;
+
+					case 'mceMove':
+						dx = x;
+						dy = y;
+						break;
+				}
+
+				// Boundary check
+				if (dw < (v = w.features.min_width - sz.w)) {
+					if (dx !== 0)
+						dx += dw - v;
+
+					dw = v;
+				}
+	
+				if (dh < (v = w.features.min_height - sz.h)) {
+					if (dy !== 0)
+						dy += dh - v;
+
+					dh = v;
+				}
+
+				dw = Math.min(dw, w.features.max_width - sz.w);
+				dh = Math.min(dh, w.features.max_height - sz.h);
+				dx = Math.max(dx, vp.x - (sx + vp.x));
+				dy = Math.max(dy, vp.y - (sy + vp.y));
+				dx = Math.min(dx, (vp.w + vp.x) - (sx + sz.w + vp.x));
+				dy = Math.min(dy, (vp.h + vp.y) - (sy + sz.h + vp.y));
+
+				// Move if needed
+				if (dx + dy !== 0) {
+					if (sx + dx < 0)
+						dx = 0;
+	
+					if (sy + dy < 0)
+						dy = 0;
+
+					ph.moveTo(sx + dx, sy + dy);
+				}
+
+				// Resize if needed
+				if (dw + dh !== 0)
+					ph.resizeTo(sz.w + dw, sz.h + dh);
+
+				return Event.cancel(e);
+			});
+
+			return Event.cancel(se);
+		},
+
+		resizeBy : function(dw, dh, id) {
+			var w = this.windows[id];
+
+			if (w) {
+				w.element.resizeBy(dw, dh);
+				w.iframeElement.resizeBy(dw, dh);
+			}
+		},
+
+		close : function(win, id) {
+			var t = this, w, d = DOM.doc, ix = 0, fw, id;
+
+			id = t._findId(id || win);
+
+			// Probably not inline
+			if (!t.windows[id]) {
+				t.parent(win);
+				return;
+			}
+
+			t.count--;
+
+			if (t.count == 0)
+				DOM.remove('mceModalBlocker');
+
+			if (w = t.windows[id]) {
+				t.onClose.dispatch(t);
+				Event.remove(d, 'mousedown', w.mousedownFunc);
+				Event.remove(d, 'click', w.clickFunc);
+				Event.clear(id);
+				Event.clear(id + '_ifr');
+
+				DOM.setAttrib(id + '_ifr', 'src', 'javascript:""'); // Prevent leak
+				w.element.remove();
+				delete t.windows[id];
+
+				// Find front most window and focus that
+				each (t.windows, function(w) {
+					if (w.zIndex > ix) {
+						fw = w;
+						ix = w.zIndex;
+					}
+				});
+
+				if (fw)
+					t.focus(fw.id);
+			}
+		},
+
+		setTitle : function(w, ti) {
+			var e;
+
+			w = this._findId(w);
+
+			if (e = DOM.get(w + '_title'))
+				e.innerHTML = DOM.encode(ti);
+		},
+
+		alert : function(txt, cb, s) {
+			var t = this, w;
+
+			w = t.open({
+				title : t,
+				type : 'alert',
+				button_func : function(s) {
+					if (cb)
+						cb.call(s || t, s);
+
+					t.close(null, w.id);
+				},
+				content : DOM.encode(t.editor.getLang(txt, txt)),
+				inline : 1,
+				width : 400,
+				height : 130
+			});
+		},
+
+		confirm : function(txt, cb, s) {
+			var t = this, w;
+
+			w = t.open({
+				title : t,
+				type : 'confirm',
+				button_func : function(s) {
+					if (cb)
+						cb.call(s || t, s);
+
+					t.close(null, w.id);
+				},
+				content : DOM.encode(t.editor.getLang(txt, txt)),
+				inline : 1,
+				width : 400,
+				height : 130
+			});
+		},
+
+		// Internal functions
+
+		_findId : function(w) {
+			var t = this;
+
+			if (typeof(w) == 'string')
+				return w;
+
+			each(t.windows, function(wo) {
+				var ifr = DOM.get(wo.id + '_ifr');
+
+				if (ifr && w == ifr.contentWindow) {
+					w = wo.id;
+					return false;
+				}
+			});
+
+			return w;
+		},
+
+		_fixIELayout : function(id, s) {
+			var w, img;
+
+			if (!tinymce.isIE6)
+				return;
+
+			// Fixes the bug where hover flickers and does odd things in IE6
+			each(['n','s','w','e','nw','ne','sw','se'], function(v) {
+				var e = DOM.get(id + '_resize_' + v);
+
+				DOM.setStyles(e, {
+					width : s ? e.clientWidth : '',
+					height : s ? e.clientHeight : '',
+					cursor : DOM.getStyle(e, 'cursor', 1)
+				});
+
+				DOM.setStyle(id + "_bottom", 'bottom', '-1px');
+
+				e = 0;
+			});
+
+			// Fixes graphics glitch
+			if (w = this.windows[id]) {
+				// Fixes rendering bug after resize
+				w.element.hide();
+				w.element.show();
+
+				// Forced a repaint of the window
+				//DOM.get(id).style.filter = '';
+
+				// IE has a bug where images used in CSS won't get loaded
+				// sometimes when the cache in the browser is disabled
+				// This fix tries to solve it by loading the images using the image object
+				each(DOM.select('div,a', id), function(e, i) {
+					if (e.currentStyle.backgroundImage != 'none') {
+						img = new Image();
+						img.src = e.currentStyle.backgroundImage.replace(/url\(\"(.+)\"\)/, '$1');
+					}
+				});
+
+				DOM.get(id).style.filter = '';
+			}
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('inlinepopups', tinymce.plugins.InlinePopups);
+})();
+
diff --git a/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif
new file mode 100644
index 0000000..94abd08
Binary files /dev/null and b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/alert.gif differ
diff --git a/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/button.gif b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/button.gif
new file mode 100644
index 0000000..e671094
Binary files /dev/null and b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/button.gif differ
diff --git a/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/buttons.gif b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/buttons.gif
new file mode 100644
index 0000000..6baf64a
Binary files /dev/null and b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/buttons.gif differ
diff --git a/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif
new file mode 100644
index 0000000..497307a
Binary files /dev/null and b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/confirm.gif differ
diff --git a/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif
new file mode 100644
index 0000000..c894b2e
Binary files /dev/null and b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/corners.gif differ
diff --git a/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/horizontal.gif b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/horizontal.gif
new file mode 100644
index 0000000..c2a2ad4
Binary files /dev/null and b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/horizontal.gif differ
diff --git a/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif
new file mode 100644
index 0000000..43a735f
Binary files /dev/null and b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/img/vertical.gif differ
diff --git a/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/window.css b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/window.css
new file mode 100644
index 0000000..5e6fd7d
--- /dev/null
+++ b/media/js/tinymce/plugins/inlinepopups/skins/clearlooks2/window.css
@@ -0,0 +1,90 @@
+/* Clearlooks 2 */
+
+/* Reset */
+.clearlooks2, .clearlooks2 div, .clearlooks2 span, .clearlooks2 a {vertical-align:baseline; text-align:left; position:absolute; border:0; padding:0; margin:0; background:transparent; font-family:Arial,Verdana; font-size:11px; color:#000; text-decoration:none; font-weight:normal; width:auto; height:auto; overflow:hidden; display:block}
+
+/* General */
+.clearlooks2 {position:absolute; direction:ltr}
+.clearlooks2 .mceWrapper {position:static}
+.mceEventBlocker {position:fixed; left:0; top:0; background:url(img/horizontal.gif) no-repeat 0 -75px; width:100%; height:100%}
+.clearlooks2 .mcePlaceHolder {border:1px solid #000; background:#888; top:0; left:0; opacity:0.5; -ms-filter:'alpha(opacity=50)'; filter:alpha(opacity=50)}
+.clearlooks2_modalBlocker {position:fixed; left:0; top:0; width:100%; height:100%; background:#FFF; opacity:0.6; -ms-filter:'alpha(opacity=60)'; filter:alpha(opacity=60); display:none}
+
+/* Top */
+.clearlooks2 .mceTop, .clearlooks2 .mceTop div {top:0; width:100%; height:23px}
+.clearlooks2 .mceTop .mceLeft {width:6px; background:url(img/corners.gif)}
+.clearlooks2 .mceTop .mceCenter {right:6px; width:100%; height:23px; background:url(img/horizontal.gif) 12px 0; clip:rect(auto auto auto 12px)}
+.clearlooks2 .mceTop .mceRight {right:0; width:6px; height:23px; background:url(img/corners.gif) -12px 0}
+.clearlooks2 .mceTop span {width:100%; text-align:center; vertical-align:middle; line-height:23px; font-weight:bold}
+.clearlooks2 .mceFocus .mceTop .mceLeft {background:url(img/corners.gif) -6px 0}
+.clearlooks2 .mceFocus .mceTop .mceCenter {background:url(img/horizontal.gif) 0 -23px}
+.clearlooks2 .mceFocus .mceTop .mceRight {background:url(img/corners.gif) -18px 0}
+.clearlooks2 .mceFocus .mceTop span {color:#FFF}
+
+/* Middle */
+.clearlooks2 .mceMiddle, .clearlooks2 .mceMiddle div {top:0}
+.clearlooks2 .mceMiddle {width:100%; height:100%; clip:rect(23px auto auto auto)}
+.clearlooks2 .mceMiddle .mceLeft {left:0; width:5px; height:100%; background:url(img/vertical.gif) -5px 0}
+.clearlooks2 .mceMiddle span {top:23px; left:5px; width:100%; height:100%; background:#FFF}
+.clearlooks2 .mceMiddle .mceRight {right:0; width:5px; height:100%; background:url(img/vertical.gif)}
+
+/* Bottom */
+.clearlooks2 .mceBottom, .clearlooks2 .mceBottom div {height:6px}
+.clearlooks2 .mceBottom {left:0; bottom:0; width:100%}
+.clearlooks2 .mceBottom div {top:0}
+.clearlooks2 .mceBottom .mceLeft {left:0; width:5px; background:url(img/corners.gif) -34px -6px}
+.clearlooks2 .mceBottom .mceCenter {left:5px; width:100%; background:url(img/horizontal.gif) 0 -46px}
+.clearlooks2 .mceBottom .mceRight {right:0; width:5px; background: url(img/corners.gif) -34px 0}
+.clearlooks2 .mceBottom span {display:none}
+.clearlooks2 .mceStatusbar .mceBottom, .clearlooks2 .mceStatusbar .mceBottom div {height:23px}
+.clearlooks2 .mceStatusbar .mceBottom .mceLeft {background:url(img/corners.gif) -29px 0}
+.clearlooks2 .mceStatusbar .mceBottom .mceCenter {background:url(img/horizontal.gif) 0 -52px}
+.clearlooks2 .mceStatusbar .mceBottom .mceRight {background:url(img/corners.gif) -24px 0}
+.clearlooks2 .mceStatusbar .mceBottom span {display:block; left:7px; font-family:Arial, Verdana; font-size:11px; line-height:23px}
+
+/* Actions */
+.clearlooks2 a {width:29px; height:16px; top:3px;}
+.clearlooks2 .mceClose {right:6px; background:url(img/buttons.gif) -87px 0}
+.clearlooks2 .mceMin {display:none; right:68px; background:url(img/buttons.gif) 0 0}
+.clearlooks2 .mceMed {display:none; right:37px; background:url(img/buttons.gif) -29px 0}
+.clearlooks2 .mceMax {display:none; right:37px; background:url(img/buttons.gif) -58px 0}
+.clearlooks2 .mceMove {display:none;width:100%;cursor:move;background:url(img/corners.gif) no-repeat -100px -100px}
+.clearlooks2 .mceMovable .mceMove {display:block}
+.clearlooks2 .mceFocus .mceClose {right:6px; background:url(img/buttons.gif) -87px -16px}
+.clearlooks2 .mceFocus .mceMin {right:68px; background:url(img/buttons.gif) 0 -16px}
+.clearlooks2 .mceFocus .mceMed {right:37px; background:url(img/buttons.gif) -29px -16px}
+.clearlooks2 .mceFocus .mceMax {right:37px; background:url(img/buttons.gif) -58px -16px}
+.clearlooks2 .mceFocus .mceClose:hover {right:6px; background:url(img/buttons.gif) -87px -32px}
+.clearlooks2 .mceFocus .mceClose:hover {right:6px; background:url(img/buttons.gif) -87px -32px}
+.clearlooks2 .mceFocus .mceMin:hover {right:68px; background:url(img/buttons.gif) 0 -32px}
+.clearlooks2 .mceFocus .mceMed:hover {right:37px; background:url(img/buttons.gif) -29px -32px}
+.clearlooks2 .mceFocus .mceMax:hover {right:37px; background:url(img/buttons.gif) -58px -32px}
+
+/* Resize */
+.clearlooks2 .mceResize {top:auto; left:auto; display:none; width:5px; height:5px; background:url(img/horizontal.gif) no-repeat 0 -75px}
+.clearlooks2 .mceResizable .mceResize {display:block}
+.clearlooks2 .mceResizable .mceMin, .clearlooks2 .mceMax {display:none}
+.clearlooks2 .mceMinimizable .mceMin {display:block}
+.clearlooks2 .mceMaximizable .mceMax {display:block}
+.clearlooks2 .mceMaximized .mceMed {display:block}
+.clearlooks2 .mceMaximized .mceMax {display:none}
+.clearlooks2 a.mceResizeN {top:0; left:0; width:100%; cursor:n-resize}
+.clearlooks2 a.mceResizeNW {top:0; left:0; cursor:nw-resize}
+.clearlooks2 a.mceResizeNE {top:0; right:0; cursor:ne-resize}
+.clearlooks2 a.mceResizeW {top:0; left:0; height:100%; cursor:w-resize;}
+.clearlooks2 a.mceResizeE {top:0; right:0; height:100%; cursor:e-resize}
+.clearlooks2 a.mceResizeS {bottom:0; left:0; width:100%; cursor:s-resize}
+.clearlooks2 a.mceResizeSW {bottom:0; left:0; cursor:sw-resize}
+.clearlooks2 a.mceResizeSE {bottom:0; right:0; cursor:se-resize}
+
+/* Alert/Confirm */
+.clearlooks2 .mceButton {font-weight:bold; bottom:10px; width:80px; height:30px; background:url(img/button.gif); line-height:30px; vertical-align:middle; text-align:center; outline:0}
+.clearlooks2 .mceMiddle .mceIcon {left:15px; top:35px; width:32px; height:32px}
+.clearlooks2 .mceAlert .mceMiddle span, .clearlooks2 .mceConfirm .mceMiddle span {background:transparent;left:60px; top:35px; width:320px; height:50px; font-weight:bold; overflow:auto; white-space:normal}
+.clearlooks2 a:hover {font-weight:bold;}
+.clearlooks2 .mceAlert .mceMiddle, .clearlooks2 .mceConfirm .mceMiddle {background:#D6D7D5}
+.clearlooks2 .mceAlert .mceOk {left:50%; top:auto; margin-left: -40px}
+.clearlooks2 .mceAlert .mceIcon {background:url(img/alert.gif)}
+.clearlooks2 .mceConfirm .mceOk {left:50%; top:auto; margin-left: -90px}
+.clearlooks2 .mceConfirm .mceCancel {left:50%; top:auto}
+.clearlooks2 .mceConfirm .mceIcon {background:url(img/confirm.gif)} 
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/inlinepopups/template.htm b/media/js/tinymce/plugins/inlinepopups/template.htm
new file mode 100644
index 0000000..f9ec642
--- /dev/null
+++ b/media/js/tinymce/plugins/inlinepopups/template.htm
@@ -0,0 +1,387 @@
+<!-- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Template for dialogs</title>
+<link rel="stylesheet" type="text/css" href="skins/clearlooks2/window.css" />
+</head>
+<body>
+
+<div class="mceEditor">
+	<div class="clearlooks2" style="width:400px; height:100px; left:10px;">
+		<div class="mceWrapper">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Blured</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>Content</span>
+				<div class="mceRight"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar text.</span>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceMin" href="#"></a>
+			<a class="mceMax" href="#"></a>
+			<a class="mceMed" href="#"></a>
+			<a class="mceClose" href="#"></a>
+			<a class="mceResize mceResizeN" href="#"></a>
+			<a class="mceResize mceResizeS" href="#"></a>
+			<a class="mceResize mceResizeW" href="#"></a>
+			<a class="mceResize mceResizeE" href="#"></a>
+			<a class="mceResize mceResizeNW" href="#"></a>
+			<a class="mceResize mceResizeNE" href="#"></a>
+			<a class="mceResize mceResizeSW" href="#"></a>
+			<a class="mceResize mceResizeSE" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:100px; left:420px;">
+		<div class="mceWrapper mceMovable mceFocus">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Focused</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>Content</span>
+				<div class="mceRight"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar text.</span>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceMin" href="#"></a>
+			<a class="mceMax" href="#"></a>
+			<a class="mceMed" href="#"></a>
+			<a class="mceClose" href="#"></a>
+			<a class="mceResize mceResizeN" href="#"></a>
+			<a class="mceResize mceResizeS" href="#"></a>
+			<a class="mceResize mceResizeW" href="#"></a>
+			<a class="mceResize mceResizeE" href="#"></a>
+			<a class="mceResize mceResizeNW" href="#"></a>
+			<a class="mceResize mceResizeNE" href="#"></a>
+			<a class="mceResize mceResizeSW" href="#"></a>
+			<a class="mceResize mceResizeSE" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:100px; left:10px; top:120px;">
+		<div class="mceWrapper mceMovable mceFocus mceStatusbar">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>Content</span>
+				<div class="mceRight"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar text.</span>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceMin" href="#"></a>
+			<a class="mceMax" href="#"></a>
+			<a class="mceMed" href="#"></a>
+			<a class="mceClose" href="#"></a>
+			<a class="mceResize mceResizeN" href="#"></a>
+			<a class="mceResize mceResizeS" href="#"></a>
+			<a class="mceResize mceResizeW" href="#"></a>
+			<a class="mceResize mceResizeE" href="#"></a>
+			<a class="mceResize mceResizeNW" href="#"></a>
+			<a class="mceResize mceResizeNE" href="#"></a>
+			<a class="mceResize mceResizeSW" href="#"></a>
+			<a class="mceResize mceResizeSE" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:100px; left:420px; top:120px;">
+		<div class="mceWrapper mceMovable mceFocus mceStatusbar mceResizable">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar, Resizable</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>Content</span>
+				<div class="mceRight"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar text.</span>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceMin" href="#"></a>
+			<a class="mceMax" href="#"></a>
+			<a class="mceMed" href="#"></a>
+			<a class="mceClose" href="#"></a>
+			<a class="mceResize mceResizeN" href="#"></a>
+			<a class="mceResize mceResizeS" href="#"></a>
+			<a class="mceResize mceResizeW" href="#"></a>
+			<a class="mceResize mceResizeE" href="#"></a>
+			<a class="mceResize mceResizeNW" href="#"></a>
+			<a class="mceResize mceResizeNE" href="#"></a>
+			<a class="mceResize mceResizeSW" href="#"></a>
+			<a class="mceResize mceResizeSE" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:100px; left:10px; top:230px;">
+		<div class="mceWrapper mceMovable mceFocus mceResizable mceMaximizable">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Resizable, Maximizable</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>Content</span>
+				<div class="mceRight"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar text.</span>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceMin" href="#"></a>
+			<a class="mceMax" href="#"></a>
+			<a class="mceMed" href="#"></a>
+			<a class="mceClose" href="#"></a>
+			<a class="mceResize mceResizeN" href="#"></a>
+			<a class="mceResize mceResizeS" href="#"></a>
+			<a class="mceResize mceResizeW" href="#"></a>
+			<a class="mceResize mceResizeE" href="#"></a>
+			<a class="mceResize mceResizeNW" href="#"></a>
+			<a class="mceResize mceResizeNE" href="#"></a>
+			<a class="mceResize mceResizeSW" href="#"></a>
+			<a class="mceResize mceResizeSE" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:100px; left:420px; top:230px;">
+		<div class="mceWrapper mceMovable mceStatusbar mceResizable mceMaximizable">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Blurred, Maximizable, Statusbar, Resizable</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>Content</span>
+				<div class="mceRight"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar text.</span>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceMin" href="#"></a>
+			<a class="mceMax" href="#"></a>
+			<a class="mceMed" href="#"></a>
+			<a class="mceClose" href="#"></a>
+			<a class="mceResize mceResizeN" href="#"></a>
+			<a class="mceResize mceResizeS" href="#"></a>
+			<a class="mceResize mceResizeW" href="#"></a>
+			<a class="mceResize mceResizeE" href="#"></a>
+			<a class="mceResize mceResizeNW" href="#"></a>
+			<a class="mceResize mceResizeNE" href="#"></a>
+			<a class="mceResize mceResizeSW" href="#"></a>
+			<a class="mceResize mceResizeSE" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:100px; left:10px; top:340px;">
+		<div class="mceWrapper mceMovable mceFocus mceResizable mceMaximized mceMinimizable mceMaximizable">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Maximized, Maximizable, Minimizable</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>Content</span>
+				<div class="mceRight"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar text.</span>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceMin" href="#"></a>
+			<a class="mceMax" href="#"></a>
+			<a class="mceMed" href="#"></a>
+			<a class="mceClose" href="#"></a>
+			<a class="mceResize mceResizeN" href="#"></a>
+			<a class="mceResize mceResizeS" href="#"></a>
+			<a class="mceResize mceResizeW" href="#"></a>
+			<a class="mceResize mceResizeE" href="#"></a>
+			<a class="mceResize mceResizeNW" href="#"></a>
+			<a class="mceResize mceResizeNE" href="#"></a>
+			<a class="mceResize mceResizeSW" href="#"></a>
+			<a class="mceResize mceResizeSE" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:100px; left:420px; top:340px;">
+		<div class="mceWrapper mceMovable mceStatusbar mceResizable mceMaximized mceMinimizable mceMaximizable">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Blured</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>Content</span>
+				<div class="mceRight"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Statusbar text.</span>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceMin" href="#"></a>
+			<a class="mceMax" href="#"></a>
+			<a class="mceMed" href="#"></a>
+			<a class="mceClose" href="#"></a>
+			<a class="mceResize mceResizeN" href="#"></a>
+			<a class="mceResize mceResizeS" href="#"></a>
+			<a class="mceResize mceResizeW" href="#"></a>
+			<a class="mceResize mceResizeE" href="#"></a>
+			<a class="mceResize mceResizeNW" href="#"></a>
+			<a class="mceResize mceResizeNE" href="#"></a>
+			<a class="mceResize mceResizeSW" href="#"></a>
+			<a class="mceResize mceResizeSE" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:130px; left:10px; top:450px;">
+		<div class="mceWrapper mceMovable mceFocus mceModal mceAlert">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Alert</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+				</span>
+				<div class="mceRight"></div>
+				<div class="mceIcon"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceButton mceOk" href="#">Ok</a>
+			<a class="mceClose" href="#"></a>
+		</div>
+	</div>
+
+	<div class="clearlooks2" style="width:400px; height:130px; left:420px; top:450px;">
+		<div class="mceWrapper mceMovable mceFocus mceModal mceConfirm">
+			<div class="mceTop">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+				<span>Confirm</span>
+			</div>
+
+			<div class="mceMiddle">
+				<div class="mceLeft"></div>
+				<span>
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					This is a very long error message. This is a very long error message.
+					</span>
+				<div class="mceRight"></div>
+				<div class="mceIcon"></div>
+			</div>
+
+			<div class="mceBottom">
+				<div class="mceLeft"></div>
+				<div class="mceCenter"></div>
+				<div class="mceRight"></div>
+			</div>
+
+			<a class="mceMove" href="#"></a>
+			<a class="mceButton mceOk" href="#">Ok</a>
+			<a class="mceButton mceCancel" href="#">Cancel</a>
+			<a class="mceClose" href="#"></a>
+		</div>
+	</div>
+</div>
+
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/insertdatetime/editor_plugin.js b/media/js/tinymce/plugins/insertdatetime/editor_plugin.js
new file mode 100644
index 0000000..938ce6b
--- /dev/null
+++ b/media/js/tinymce/plugins/insertdatetime/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.InsertDateTime",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceInsertDate",function(){var d=c._getDateTime(new Date(),a.getParam("plugin_insertdate_dateFormat",a.getLang("insertdatetime.date_fmt")));a.execCommand("mceInsertContent",false,d)});a.addCommand("mceInsertTime",function(){var d=c._getDateTime(new Date(),a.getParam("plugin_insertdate_timeFormat",a.getLang("insertdatetime.time_fmt")));a.execCommand("mceInsertContent",false,d [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/insertdatetime/editor_plugin_src.js b/media/js/tinymce/plugins/insertdatetime/editor_plugin_src.js
new file mode 100644
index 0000000..9ab3135
--- /dev/null
+++ b/media/js/tinymce/plugins/insertdatetime/editor_plugin_src.js
@@ -0,0 +1,80 @@
+/**
+ * $Id: editor_plugin_src.js 520 2008-01-07 16:30:32Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.InsertDateTime', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			ed.addCommand('mceInsertDate', function() {
+				var str = t._getDateTime(new Date(), ed.getParam("plugin_insertdate_dateFormat", ed.getLang('insertdatetime.date_fmt')));
+
+				ed.execCommand('mceInsertContent', false, str);
+			});
+
+			ed.addCommand('mceInsertTime', function() {
+				var str = t._getDateTime(new Date(), ed.getParam("plugin_insertdate_timeFormat", ed.getLang('insertdatetime.time_fmt')));
+
+				ed.execCommand('mceInsertContent', false, str);
+			});
+
+			ed.addButton('insertdate', {title : 'insertdatetime.insertdate_desc', cmd : 'mceInsertDate'});
+			ed.addButton('inserttime', {title : 'insertdatetime.inserttime_desc', cmd : 'mceInsertTime'});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Insert date/time',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/insertdatetime',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private methods
+
+		_getDateTime : function(d, fmt) {
+			var ed = this.editor;
+
+			function addZeros(value, len) {
+				value = "" + value;
+
+				if (value.length < len) {
+					for (var i=0; i<(len-value.length); i++)
+						value = "0" + value;
+				}
+
+				return value;
+			};
+
+			fmt = fmt.replace("%D", "%m/%d/%y");
+			fmt = fmt.replace("%r", "%I:%M:%S %p");
+			fmt = fmt.replace("%Y", "" + d.getFullYear());
+			fmt = fmt.replace("%y", "" + d.getYear());
+			fmt = fmt.replace("%m", addZeros(d.getMonth()+1, 2));
+			fmt = fmt.replace("%d", addZeros(d.getDate(), 2));
+			fmt = fmt.replace("%H", "" + addZeros(d.getHours(), 2));
+			fmt = fmt.replace("%M", "" + addZeros(d.getMinutes(), 2));
+			fmt = fmt.replace("%S", "" + addZeros(d.getSeconds(), 2));
+			fmt = fmt.replace("%I", "" + ((d.getHours() + 11) % 12 + 1));
+			fmt = fmt.replace("%p", "" + (d.getHours() < 12 ? "AM" : "PM"));
+			fmt = fmt.replace("%B", "" + ed.getLang("insertdatetime.months_long").split(',')[d.getMonth()]);
+			fmt = fmt.replace("%b", "" + ed.getLang("insertdatetime.months_short").split(',')[d.getMonth()]);
+			fmt = fmt.replace("%A", "" + ed.getLang("insertdatetime.day_long").split(',')[d.getDay()]);
+			fmt = fmt.replace("%a", "" + ed.getLang("insertdatetime.day_short").split(',')[d.getDay()]);
+			fmt = fmt.replace("%%", "%");
+
+			return fmt;
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('insertdatetime', tinymce.plugins.InsertDateTime);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/layer/editor_plugin.js b/media/js/tinymce/plugins/layer/editor_plugin.js
new file mode 100644
index 0000000..f88a6dd
--- /dev/null
+++ b/media/js/tinymce/plugins/layer/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.Layer",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceInsertLayer",c._insertLayer,c);a.addCommand("mceMoveForward",function(){c._move(1)});a.addCommand("mceMoveBackward",function(){c._move(-1)});a.addCommand("mceMakeAbsolute",function(){c._toggleAbsolute()});a.addButton("moveforward",{title:"layer.forward_desc",cmd:"mceMoveForward"});a.addButton("movebackward",{title:"layer.backward_desc",cmd:"mceMoveBackward"});a.addButton("absolut [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/layer/editor_plugin_src.js b/media/js/tinymce/plugins/layer/editor_plugin_src.js
new file mode 100644
index 0000000..a72f6c3
--- /dev/null
+++ b/media/js/tinymce/plugins/layer/editor_plugin_src.js
@@ -0,0 +1,209 @@
+/**
+ * $Id: editor_plugin_src.js 652 2008-02-29 13:09:46Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.Layer', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceInsertLayer', t._insertLayer, t);
+
+			ed.addCommand('mceMoveForward', function() {
+				t._move(1);
+			});
+
+			ed.addCommand('mceMoveBackward', function() {
+				t._move(-1);
+			});
+
+			ed.addCommand('mceMakeAbsolute', function() {
+				t._toggleAbsolute();
+			});
+
+			// Register buttons
+			ed.addButton('moveforward', {title : 'layer.forward_desc', cmd : 'mceMoveForward'});
+			ed.addButton('movebackward', {title : 'layer.backward_desc', cmd : 'mceMoveBackward'});
+			ed.addButton('absolute', {title : 'layer.absolute_desc', cmd : 'mceMakeAbsolute'});
+			ed.addButton('insertlayer', {title : 'layer.insertlayer_desc', cmd : 'mceInsertLayer'});
+
+			ed.onInit.add(function() {
+				if (tinymce.isIE)
+					ed.getDoc().execCommand('2D-Position', false, true);
+			});
+
+			ed.onNodeChange.add(t._nodeChange, t);
+			ed.onVisualAid.add(t._visualAid, t);
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Layer',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private methods
+
+		_nodeChange : function(ed, cm, n) {
+			var le, p;
+
+			le = this._getParentLayer(n);
+			p = ed.dom.getParent(n, 'DIV,P,IMG');
+
+			if (!p) {
+				cm.setDisabled('absolute', 1);
+				cm.setDisabled('moveforward', 1);
+				cm.setDisabled('movebackward', 1);
+			} else {
+				cm.setDisabled('absolute', 0);
+				cm.setDisabled('moveforward', !le);
+				cm.setDisabled('movebackward', !le);
+				cm.setActive('absolute', le && le.style.position.toLowerCase() == "absolute");
+			}
+		},
+
+		// Private methods
+
+		_visualAid : function(ed, e, s) {
+			var dom = ed.dom;
+
+			tinymce.each(dom.select('div,p', e), function(e) {
+				if (/^(absolute|relative|static)$/i.test(e.style.position)) {
+					if (s)
+						dom.addClass(e, 'mceItemVisualAid');
+					else
+						dom.removeClass(e, 'mceItemVisualAid');	
+				}
+			});
+		},
+
+		_move : function(d) {
+			var ed = this.editor, i, z = [], le = this._getParentLayer(ed.selection.getNode()), ci = -1, fi = -1, nl;
+
+			nl = [];
+			tinymce.walk(ed.getBody(), function(n) {
+				if (n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position))
+					nl.push(n); 
+			}, 'childNodes');
+
+			// Find z-indexes
+			for (i=0; i<nl.length; i++) {
+				z[i] = nl[i].style.zIndex ? parseInt(nl[i].style.zIndex) : 0;
+
+				if (ci < 0 && nl[i] == le)
+					ci = i;
+			}
+
+			if (d < 0) {
+				// Move back
+
+				// Try find a lower one
+				for (i=0; i<z.length; i++) {
+					if (z[i] < z[ci]) {
+						fi = i;
+						break;
+					}
+				}
+
+				if (fi > -1) {
+					nl[ci].style.zIndex = z[fi];
+					nl[fi].style.zIndex = z[ci];
+				} else {
+					if (z[ci] > 0)
+						nl[ci].style.zIndex = z[ci] - 1;
+				}
+			} else {
+				// Move forward
+
+				// Try find a higher one
+				for (i=0; i<z.length; i++) {
+					if (z[i] > z[ci]) {
+						fi = i;
+						break;
+					}
+				}
+
+				if (fi > -1) {
+					nl[ci].style.zIndex = z[fi];
+					nl[fi].style.zIndex = z[ci];
+				} else
+					nl[ci].style.zIndex = z[ci] + 1;
+			}
+
+			ed.execCommand('mceRepaint');
+		},
+
+		_getParentLayer : function(n) {
+			return this.editor.dom.getParent(n, function(n) {
+				return n.nodeType == 1 && /^(absolute|relative|static)$/i.test(n.style.position);
+			});
+		},
+
+		_insertLayer : function() {
+			var ed = this.editor, p = ed.dom.getPos(ed.dom.getParent(ed.selection.getNode(), '*'));
+
+			ed.dom.add(ed.getBody(), 'div', {
+				style : {
+					position : 'absolute',
+					left : p.x,
+					top : (p.y > 20 ? p.y : 20),
+					width : 100,
+					height : 100
+				},
+				'class' : 'mceItemVisualAid'
+			}, ed.selection.getContent() || ed.getLang('layer.content'));
+		},
+
+		_toggleAbsolute : function() {
+			var ed = this.editor, le = this._getParentLayer(ed.selection.getNode());
+
+			if (!le)
+				le = ed.dom.getParent(ed.selection.getNode(), 'DIV,P,IMG');
+
+			if (le) {
+				if (le.style.position.toLowerCase() == "absolute") {
+					ed.dom.setStyles(le, {
+						position : '',
+						left : '',
+						top : '',
+						width : '',
+						height : ''
+					});
+
+					ed.dom.removeClass(le, 'mceItemVisualAid');
+				} else {
+					if (le.style.left == "")
+						le.style.left = 20 + 'px';
+
+					if (le.style.top == "")
+						le.style.top = 20 + 'px';
+
+					if (le.style.width == "")
+						le.style.width = le.width ? (le.width + 'px') : '100px';
+
+					if (le.style.height == "")
+						le.style.height = le.height ? (le.height + 'px') : '100px';
+
+					le.style.position = "absolute";
+					ed.addVisual(ed.getBody());
+				}
+
+				ed.execCommand('mceRepaint');
+				ed.nodeChanged();
+			}
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('layer', tinymce.plugins.Layer);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/media/css/content.css b/media/js/tinymce/plugins/media/css/content.css
new file mode 100644
index 0000000..1bf6a75
--- /dev/null
+++ b/media/js/tinymce/plugins/media/css/content.css
@@ -0,0 +1,6 @@
+.mceItemFlash, .mceItemShockWave, .mceItemQuickTime, .mceItemWindowsMedia, .mceItemRealMedia {border:1px dotted #cc0000; background-position:center; background-repeat:no-repeat; background-color:#ffffcc;}
+.mceItemShockWave {background-image: url(../img/shockwave.gif);}
+.mceItemFlash {background-image:url(../img/flash.gif);}
+.mceItemQuickTime {background-image:url(../img/quicktime.gif);}
+.mceItemWindowsMedia {background-image:url(../img/windowsmedia.gif);}
+.mceItemRealMedia {background-image:url(../img/realmedia.gif);}
diff --git a/media/js/tinymce/plugins/media/css/media.css b/media/js/tinymce/plugins/media/css/media.css
new file mode 100644
index 0000000..2d08794
--- /dev/null
+++ b/media/js/tinymce/plugins/media/css/media.css
@@ -0,0 +1,16 @@
+#id, #name, #hspace, #vspace, #class_name, #align {	width: 100px }
+#hspace, #vspace { width: 50px }
+#flash_quality, #flash_align, #flash_scale, #flash_salign, #flash_wmode { width: 100px }
+#flash_base, #flash_flashvars { width: 240px }
+#width, #height { width: 40px }
+#src, #media_type { width: 250px }
+#class { width: 120px }
+#prev { margin: 0; border: 1px solid black; width: 380px; height: 230px; overflow: auto }
+.panel_wrapper div.current { height: 390px; overflow: auto }
+#flash_options, #shockwave_options, #qt_options, #wmp_options, #rmp_options { display: none }
+.mceAddSelectValue { background-color: #DDDDDD }
+#qt_starttime, #qt_endtime, #qt_fov, #qt_href, #qt_moveid, #qt_moviename, #qt_node, #qt_pan, #qt_qtsrc, #qt_qtsrcchokespeed, #qt_target, #qt_tilt, #qt_urlsubstituten, #qt_volume { width: 70px }
+#wmp_balance, #wmp_baseurl, #wmp_captioningid, #wmp_currentmarker, #wmp_currentposition, #wmp_defaultframe, #wmp_playcount, #wmp_rate, #wmp_uimode, #wmp_volume { width: 70px }
+#rmp_console, #rmp_numloop, #rmp_controls, #rmp_scriptcallbacks { width: 70px }
+#shockwave_swvolume, #shockwave_swframe, #shockwave_swurl, #shockwave_swstretchvalign, #shockwave_swstretchhalign, #shockwave_swstretchstyle { width: 90px }
+#qt_qtsrc { width: 200px }
diff --git a/media/js/tinymce/plugins/media/editor_plugin.js b/media/js/tinymce/plugins/media/editor_plugin.js
new file mode 100644
index 0000000..2889be5
--- /dev/null
+++ b/media/js/tinymce/plugins/media/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.MediaPlugin",{init:function(b,c){var e=this;e.editor=b;e.url=c;function f(g){return/^(mceItemFlash|mceItemShockWave|mceItemWindowsMedia|mceItemQuickTime|mceItemRealMedia)$/.test(g.className)}b.onPreInit.add(function(){b.serializer.addRules("param[name|value|_mce_value]")});b.addCommand("mceMedia",function(){b.windowManager.open({file:c+"/media.htm",width:430+parseInt(b.getLang("media.delta_width",0)),height:470+parseInt(b.get [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/media/editor_plugin_src.js b/media/js/tinymce/plugins/media/editor_plugin_src.js
new file mode 100644
index 0000000..2692e0a
--- /dev/null
+++ b/media/js/tinymce/plugins/media/editor_plugin_src.js
@@ -0,0 +1,411 @@
+/**
+ * $Id: editor_plugin_src.js 1222 2009-09-03 17:26:47Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var each = tinymce.each;
+
+	tinymce.create('tinymce.plugins.MediaPlugin', {
+		init : function(ed, url) {
+			var t = this;
+			
+			t.editor = ed;
+			t.url = url;
+
+			function isMediaElm(n) {
+				return /^(mceItemFlash|mceItemShockWave|mceItemWindowsMedia|mceItemQuickTime|mceItemRealMedia)$/.test(n.className);
+			};
+
+			ed.onPreInit.add(function() {
+				// Force in _value parameter this extra parameter is required for older Opera versions
+				ed.serializer.addRules('param[name|value|_mce_value]');
+			});
+
+			// Register commands
+			ed.addCommand('mceMedia', function() {
+				ed.windowManager.open({
+					file : url + '/media.htm',
+					width : 430 + parseInt(ed.getLang('media.delta_width', 0)),
+					height : 470 + parseInt(ed.getLang('media.delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			// Register buttons
+			ed.addButton('media', {title : 'media.desc', cmd : 'mceMedia'});
+
+			ed.onNodeChange.add(function(ed, cm, n) {
+				cm.setActive('media', n.nodeName == 'IMG' && isMediaElm(n));
+			});
+
+			ed.onInit.add(function() {
+				var lo = {
+					mceItemFlash : 'flash',
+					mceItemShockWave : 'shockwave',
+					mceItemWindowsMedia : 'windowsmedia',
+					mceItemQuickTime : 'quicktime',
+					mceItemRealMedia : 'realmedia'
+				};
+
+				ed.selection.onSetContent.add(function() {
+					t._spansToImgs(ed.getBody());
+				});
+
+				ed.selection.onBeforeSetContent.add(t._objectsToSpans, t);
+
+				if (ed.settings.content_css !== false)
+					ed.dom.loadCSS(url + "/css/content.css");
+
+				if (ed.theme && ed.theme.onResolveName) {
+					ed.theme.onResolveName.add(function(th, o) {
+						if (o.name == 'img') {
+							each(lo, function(v, k) {
+								if (ed.dom.hasClass(o.node, k)) {
+									o.name = v;
+									o.title = ed.dom.getAttrib(o.node, 'title');
+									return false;
+								}
+							});
+						}
+					});
+				}
+
+				if (ed && ed.plugins.contextmenu) {
+					ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
+						if (e.nodeName == 'IMG' && /mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(e.className)) {
+							m.add({title : 'media.edit', icon : 'media', cmd : 'mceMedia'});
+						}
+					});
+				}
+			});
+
+			ed.onBeforeSetContent.add(t._objectsToSpans, t);
+
+			ed.onSetContent.add(function() {
+				t._spansToImgs(ed.getBody());
+			});
+
+			ed.onPreProcess.add(function(ed, o) {
+				var dom = ed.dom;
+
+				if (o.set) {
+					t._spansToImgs(o.node);
+
+					each(dom.select('IMG', o.node), function(n) {
+						var p;
+
+						if (isMediaElm(n)) {
+							p = t._parse(n.title);
+							dom.setAttrib(n, 'width', dom.getAttrib(n, 'width', p.width || 100));
+							dom.setAttrib(n, 'height', dom.getAttrib(n, 'height', p.height || 100));
+						}
+					});
+				}
+
+				if (o.get) {
+					each(dom.select('IMG', o.node), function(n) {
+						var ci, cb, mt;
+
+						if (ed.getParam('media_use_script')) {
+							if (isMediaElm(n))
+								n.className = n.className.replace(/mceItem/g, 'mceTemp');
+
+							return;
+						}
+
+						switch (n.className) {
+							case 'mceItemFlash':
+								ci = 'd27cdb6e-ae6d-11cf-96b8-444553540000';
+								cb = 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0';
+								mt = 'application/x-shockwave-flash';
+								break;
+
+							case 'mceItemShockWave':
+								ci = '166b1bca-3f9c-11cf-8075-444553540000';
+								cb = 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0';
+								mt = 'application/x-director';
+								break;
+
+							case 'mceItemWindowsMedia':
+								ci = ed.getParam('media_wmp6_compatible') ? '05589fa1-c356-11ce-bf01-00aa0055595a' : '6bf52a52-394a-11d3-b153-00c04f79faa6';
+								cb = 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701';
+								mt = 'application/x-mplayer2';
+								break;
+
+							case 'mceItemQuickTime':
+								ci = '02bf25d5-8c17-4b23-bc80-d3488abddc6b';
+								cb = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0';
+								mt = 'video/quicktime';
+								break;
+
+							case 'mceItemRealMedia':
+								ci = 'cfcdaa03-8be4-11cf-b84b-0020afbbccfa';
+								cb = 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0';
+								mt = 'audio/x-pn-realaudio-plugin';
+								break;
+						}
+
+						if (ci) {
+							dom.replace(t._buildObj({
+								classid : ci,
+								codebase : cb,
+								type : mt
+							}, n), n);
+						}
+					});
+				}
+			});
+
+			ed.onPostProcess.add(function(ed, o) {
+				o.content = o.content.replace(/_mce_value=/g, 'value=');
+			});
+
+			function getAttr(s, n) {
+				n = new RegExp(n + '=\"([^\"]+)\"', 'g').exec(s);
+
+				return n ? ed.dom.decode(n[1]) : '';
+			};
+
+			ed.onPostProcess.add(function(ed, o) {
+				if (ed.getParam('media_use_script')) {
+					o.content = o.content.replace(/<img[^>]+>/g, function(im) {
+						var cl = getAttr(im, 'class');
+
+						if (/^(mceTempFlash|mceTempShockWave|mceTempWindowsMedia|mceTempQuickTime|mceTempRealMedia)$/.test(cl)) {
+							at = t._parse(getAttr(im, 'title'));
+							at.width = getAttr(im, 'width');
+							at.height = getAttr(im, 'height');
+							im = '<script type="text/javascript">write' + cl.substring(7) + '({' + t._serialize(at) + '});</script>';
+						}
+
+						return im;
+					});
+				}
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Media',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/media',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private methods
+		_objectsToSpans : function(ed, o) {
+			var t = this, h = o.content;
+
+			h = h.replace(/<script[^>]*>\s*write(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)\(\{([^\)]*)\}\);\s*<\/script>/gi, function(a, b, c) {
+				var o = t._parse(c);
+
+				return '<img class="mceItem' + b + '" title="' + ed.dom.encode(c) + '" src="' + t.url + '/img/trans.gif" width="' + o.width + '" height="' + o.height + '" />'
+			});
+
+			h = h.replace(/<object([^>]*)>/gi, '<span class="mceItemObject" $1>');
+			h = h.replace(/<embed([^>]*)\/?>/gi, '<span class="mceItemEmbed" $1></span>');
+			h = h.replace(/<embed([^>]*)>/gi, '<span class="mceItemEmbed" $1>');
+			h = h.replace(/<\/(object)([^>]*)>/gi, '</span>');
+			h = h.replace(/<\/embed>/gi, '');
+			h = h.replace(/<param([^>]*)>/gi, function(a, b) {return '<span ' + b.replace(/value=/gi, '_mce_value=') + ' class="mceItemParam"></span>'});
+			h = h.replace(/\/ class=\"mceItemParam\"><\/span>/gi, 'class="mceItemParam"></span>');
+
+			o.content = h;
+		},
+
+		_buildObj : function(o, n) {
+			var ob, ed = this.editor, dom = ed.dom, p = this._parse(n.title), stc;
+			
+			stc = ed.getParam('media_strict', true) && o.type == 'application/x-shockwave-flash';
+
+			p.width = o.width = dom.getAttrib(n, 'width') || 100;
+			p.height = o.height = dom.getAttrib(n, 'height') || 100;
+
+			if (p.src)
+				p.src = ed.convertURL(p.src, 'src', n);
+
+			if (stc) {
+				ob = dom.create('span', {
+					id : p.id,
+					mce_name : 'object',
+					type : 'application/x-shockwave-flash',
+					data : p.src,
+					style : dom.getAttrib(n, 'style'),
+					width : o.width,
+					height : o.height
+				});
+			} else {
+				ob = dom.create('span', {
+					id : p.id,
+					mce_name : 'object',
+					classid : "clsid:" + o.classid,
+					style : dom.getAttrib(n, 'style'),
+					codebase : o.codebase,
+					width : o.width,
+					height : o.height
+				});
+			}
+
+			each (p, function(v, k) {
+				if (!/^(width|height|codebase|classid|id|_cx|_cy)$/.test(k)) {
+					// Use url instead of src in IE for Windows media
+					if (o.type == 'application/x-mplayer2' && k == 'src' && !p.url)
+						k = 'url';
+
+					if (v)
+						dom.add(ob, 'span', {mce_name : 'param', name : k, '_mce_value' : v});
+				}
+			});
+
+			if (!stc)
+				dom.add(ob, 'span', tinymce.extend({mce_name : 'embed', type : o.type, style : dom.getAttrib(n, 'style')}, p));
+
+			return ob;
+		},
+
+		_spansToImgs : function(p) {
+			var t = this, dom = t.editor.dom, im, ci;
+
+			each(dom.select('span', p), function(n) {
+				// Convert object into image
+				if (dom.getAttrib(n, 'class') == 'mceItemObject') {
+					ci = dom.getAttrib(n, "classid").toLowerCase().replace(/\s+/g, '');
+
+					switch (ci) {
+						case 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000':
+							dom.replace(t._createImg('mceItemFlash', n), n);
+							break;
+
+						case 'clsid:166b1bca-3f9c-11cf-8075-444553540000':
+							dom.replace(t._createImg('mceItemShockWave', n), n);
+							break;
+
+						case 'clsid:6bf52a52-394a-11d3-b153-00c04f79faa6':
+						case 'clsid:22d6f312-b0f6-11d0-94ab-0080c74c7e95':
+						case 'clsid:05589fa1-c356-11ce-bf01-00aa0055595a':
+							dom.replace(t._createImg('mceItemWindowsMedia', n), n);
+							break;
+
+						case 'clsid:02bf25d5-8c17-4b23-bc80-d3488abddc6b':
+							dom.replace(t._createImg('mceItemQuickTime', n), n);
+							break;
+
+						case 'clsid:cfcdaa03-8be4-11cf-b84b-0020afbbccfa':
+							dom.replace(t._createImg('mceItemRealMedia', n), n);
+							break;
+
+						default:
+							dom.replace(t._createImg('mceItemFlash', n), n);
+					}
+					
+					return;
+				}
+
+				// Convert embed into image
+				if (dom.getAttrib(n, 'class') == 'mceItemEmbed') {
+					switch (dom.getAttrib(n, 'type')) {
+						case 'application/x-shockwave-flash':
+							dom.replace(t._createImg('mceItemFlash', n), n);
+							break;
+
+						case 'application/x-director':
+							dom.replace(t._createImg('mceItemShockWave', n), n);
+							break;
+
+						case 'application/x-mplayer2':
+							dom.replace(t._createImg('mceItemWindowsMedia', n), n);
+							break;
+
+						case 'video/quicktime':
+							dom.replace(t._createImg('mceItemQuickTime', n), n);
+							break;
+
+						case 'audio/x-pn-realaudio-plugin':
+							dom.replace(t._createImg('mceItemRealMedia', n), n);
+							break;
+
+						default:
+							dom.replace(t._createImg('mceItemFlash', n), n);
+					}
+				}			
+			});
+		},
+
+		_createImg : function(cl, n) {
+			var im, dom = this.editor.dom, pa = {}, ti = '', args;
+
+			args = ['id', 'name', 'width', 'height', 'bgcolor', 'align', 'flashvars', 'src', 'wmode', 'allowfullscreen', 'quality', 'data'];	
+
+			// Create image
+			im = dom.create('img', {
+				src : this.url + '/img/trans.gif',
+				width : dom.getAttrib(n, 'width') || 100,
+				height : dom.getAttrib(n, 'height') || 100,
+				style : dom.getAttrib(n, 'style'),
+				'class' : cl
+			});
+
+			// Setup base parameters
+			each(args, function(na) {
+				var v = dom.getAttrib(n, na);
+
+				if (v)
+					pa[na] = v;
+			});
+
+			// Add optional parameters
+			each(dom.select('span', n), function(n) {
+				if (dom.hasClass(n, 'mceItemParam'))
+					pa[dom.getAttrib(n, 'name')] = dom.getAttrib(n, '_mce_value');
+			});
+
+			// Use src not movie
+			if (pa.movie) {
+				pa.src = pa.movie;
+				delete pa.movie;
+			}
+
+			// No src try data
+			if (!pa.src) {
+				pa.src = pa.data;
+				delete pa.data;
+			}
+
+			// Merge with embed args
+			n = dom.select('.mceItemEmbed', n)[0];
+			if (n) {
+				each(args, function(na) {
+					var v = dom.getAttrib(n, na);
+
+					if (v && !pa[na])
+						pa[na] = v;
+				});
+			}
+
+			delete pa.width;
+			delete pa.height;
+
+			im.title = this._serialize(pa);
+
+			return im;
+		},
+
+		_parse : function(s) {
+			return tinymce.util.JSON.parse('{' + s + '}');
+		},
+
+		_serialize : function(o) {
+			return tinymce.util.JSON.serialize(o).replace(/[{}]/g, '');
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('media', tinymce.plugins.MediaPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/media/img/flash.gif b/media/js/tinymce/plugins/media/img/flash.gif
new file mode 100644
index 0000000..cb192e6
Binary files /dev/null and b/media/js/tinymce/plugins/media/img/flash.gif differ
diff --git a/media/js/tinymce/plugins/media/img/flv_player.swf b/media/js/tinymce/plugins/media/img/flv_player.swf
new file mode 100644
index 0000000..042c2ab
Binary files /dev/null and b/media/js/tinymce/plugins/media/img/flv_player.swf differ
diff --git a/media/js/tinymce/plugins/media/img/quicktime.gif b/media/js/tinymce/plugins/media/img/quicktime.gif
new file mode 100644
index 0000000..3b04991
Binary files /dev/null and b/media/js/tinymce/plugins/media/img/quicktime.gif differ
diff --git a/media/js/tinymce/plugins/media/img/realmedia.gif b/media/js/tinymce/plugins/media/img/realmedia.gif
new file mode 100644
index 0000000..fdfe0b9
Binary files /dev/null and b/media/js/tinymce/plugins/media/img/realmedia.gif differ
diff --git a/media/js/tinymce/plugins/media/img/shockwave.gif b/media/js/tinymce/plugins/media/img/shockwave.gif
new file mode 100644
index 0000000..5f235df
Binary files /dev/null and b/media/js/tinymce/plugins/media/img/shockwave.gif differ
diff --git a/media/js/tinymce/plugins/media/img/trans.gif b/media/js/tinymce/plugins/media/img/trans.gif
new file mode 100644
index 0000000..3884865
Binary files /dev/null and b/media/js/tinymce/plugins/media/img/trans.gif differ
diff --git a/media/js/tinymce/plugins/media/img/windowsmedia.gif b/media/js/tinymce/plugins/media/img/windowsmedia.gif
new file mode 100644
index 0000000..ab50f2d
Binary files /dev/null and b/media/js/tinymce/plugins/media/img/windowsmedia.gif differ
diff --git a/media/js/tinymce/plugins/media/js/embed.js b/media/js/tinymce/plugins/media/js/embed.js
new file mode 100644
index 0000000..f8dc810
--- /dev/null
+++ b/media/js/tinymce/plugins/media/js/embed.js
@@ -0,0 +1,73 @@
+/**
+ * This script contains embed functions for common plugins. This scripts are complety free to use for any purpose.
+ */
+
+function writeFlash(p) {
+	writeEmbed(
+		'D27CDB6E-AE6D-11cf-96B8-444553540000',
+		'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0',
+		'application/x-shockwave-flash',
+		p
+	);
+}
+
+function writeShockWave(p) {
+	writeEmbed(
+	'166B1BCA-3F9C-11CF-8075-444553540000',
+	'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0',
+	'application/x-director',
+		p
+	);
+}
+
+function writeQuickTime(p) {
+	writeEmbed(
+		'02BF25D5-8C17-4B23-BC80-D3488ABDDC6B',
+		'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0',
+		'video/quicktime',
+		p
+	);
+}
+
+function writeRealMedia(p) {
+	writeEmbed(
+		'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA',
+		'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0',
+		'audio/x-pn-realaudio-plugin',
+		p
+	);
+}
+
+function writeWindowsMedia(p) {
+	p.url = p.src;
+	writeEmbed(
+		'6BF52A52-394A-11D3-B153-00C04F79FAA6',
+		'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701',
+		'application/x-mplayer2',
+		p
+	);
+}
+
+function writeEmbed(cls, cb, mt, p) {
+	var h = '', n;
+
+	h += '<object classid="clsid:' + cls + '" codebase="' + cb + '"';
+	h += typeof(p.id) != "undefined" ? 'id="' + p.id + '"' : '';
+	h += typeof(p.name) != "undefined" ? 'name="' + p.name + '"' : '';
+	h += typeof(p.width) != "undefined" ? 'width="' + p.width + '"' : '';
+	h += typeof(p.height) != "undefined" ? 'height="' + p.height + '"' : '';
+	h += typeof(p.align) != "undefined" ? 'align="' + p.align + '"' : '';
+	h += '>';
+
+	for (n in p)
+		h += '<param name="' + n + '" value="' + p[n] + '">';
+
+	h += '<embed type="' + mt + '"';
+
+	for (n in p)
+		h += n + '="' + p[n] + '" ';
+
+	h += '></embed></object>';
+
+	document.write(h);
+}
diff --git a/media/js/tinymce/plugins/media/js/media.js b/media/js/tinymce/plugins/media/js/media.js
new file mode 100644
index 0000000..86cfa98
--- /dev/null
+++ b/media/js/tinymce/plugins/media/js/media.js
@@ -0,0 +1,630 @@
+tinyMCEPopup.requireLangPack();
+
+var oldWidth, oldHeight, ed, url;
+
+if (url = tinyMCEPopup.getParam("media_external_list_url"))
+	document.write('<script language="javascript" type="text/javascript" src="' + tinyMCEPopup.editor.documentBaseURI.toAbsolute(url) + '"></script>');
+
+function init() {
+	var pl = "", f, val;
+	var type = "flash", fe, i;
+
+	ed = tinyMCEPopup.editor;
+
+	tinyMCEPopup.resizeToInnerSize();
+	f = document.forms[0]
+
+	fe = ed.selection.getNode();
+	if (/mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(ed.dom.getAttrib(fe, 'class'))) {
+		pl = fe.title;
+
+		switch (ed.dom.getAttrib(fe, 'class')) {
+			case 'mceItemFlash':
+				type = 'flash';
+				break;
+
+			case 'mceItemFlashVideo':
+				type = 'flv';
+				break;
+
+			case 'mceItemShockWave':
+				type = 'shockwave';
+				break;
+
+			case 'mceItemWindowsMedia':
+				type = 'wmp';
+				break;
+
+			case 'mceItemQuickTime':
+				type = 'qt';
+				break;
+
+			case 'mceItemRealMedia':
+				type = 'rmp';
+				break;
+		}
+
+		document.forms[0].insert.value = ed.getLang('update', 'Insert', true); 
+	}
+
+	document.getElementById('filebrowsercontainer').innerHTML = getBrowserHTML('filebrowser','src','media','media');
+	document.getElementById('qtsrcfilebrowsercontainer').innerHTML = getBrowserHTML('qtsrcfilebrowser','qt_qtsrc','media','media');
+	document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor');
+
+	var html = getMediaListHTML('medialist','src','media','media');
+	if (html == "")
+		document.getElementById("linklistrow").style.display = 'none';
+	else
+		document.getElementById("linklistcontainer").innerHTML = html;
+
+	// Resize some elements
+	if (isVisible('filebrowser'))
+		document.getElementById('src').style.width = '230px';
+
+	// Setup form
+	if (pl != "") {
+		pl = tinyMCEPopup.editor.plugins.media._parse(pl);
+
+		switch (type) {
+			case "flash":
+				setBool(pl, 'flash', 'play');
+				setBool(pl, 'flash', 'loop');
+				setBool(pl, 'flash', 'menu');
+				setBool(pl, 'flash', 'swliveconnect');
+				setStr(pl, 'flash', 'quality');
+				setStr(pl, 'flash', 'scale');
+				setStr(pl, 'flash', 'salign');
+				setStr(pl, 'flash', 'wmode');
+				setStr(pl, 'flash', 'base');
+				setStr(pl, 'flash', 'flashvars');
+			break;
+
+			case "qt":
+				setBool(pl, 'qt', 'loop');
+				setBool(pl, 'qt', 'autoplay');
+				setBool(pl, 'qt', 'cache');
+				setBool(pl, 'qt', 'controller');
+				setBool(pl, 'qt', 'correction');
+				setBool(pl, 'qt', 'enablejavascript');
+				setBool(pl, 'qt', 'kioskmode');
+				setBool(pl, 'qt', 'autohref');
+				setBool(pl, 'qt', 'playeveryframe');
+				setBool(pl, 'qt', 'tarsetcache');
+				setStr(pl, 'qt', 'scale');
+				setStr(pl, 'qt', 'starttime');
+				setStr(pl, 'qt', 'endtime');
+				setStr(pl, 'qt', 'tarset');
+				setStr(pl, 'qt', 'qtsrcchokespeed');
+				setStr(pl, 'qt', 'volume');
+				setStr(pl, 'qt', 'qtsrc');
+			break;
+
+			case "shockwave":
+				setBool(pl, 'shockwave', 'sound');
+				setBool(pl, 'shockwave', 'progress');
+				setBool(pl, 'shockwave', 'autostart');
+				setBool(pl, 'shockwave', 'swliveconnect');
+				setStr(pl, 'shockwave', 'swvolume');
+				setStr(pl, 'shockwave', 'swstretchstyle');
+				setStr(pl, 'shockwave', 'swstretchhalign');
+				setStr(pl, 'shockwave', 'swstretchvalign');
+			break;
+
+			case "wmp":
+				setBool(pl, 'wmp', 'autostart');
+				setBool(pl, 'wmp', 'enabled');
+				setBool(pl, 'wmp', 'enablecontextmenu');
+				setBool(pl, 'wmp', 'fullscreen');
+				setBool(pl, 'wmp', 'invokeurls');
+				setBool(pl, 'wmp', 'mute');
+				setBool(pl, 'wmp', 'stretchtofit');
+				setBool(pl, 'wmp', 'windowlessvideo');
+				setStr(pl, 'wmp', 'balance');
+				setStr(pl, 'wmp', 'baseurl');
+				setStr(pl, 'wmp', 'captioningid');
+				setStr(pl, 'wmp', 'currentmarker');
+				setStr(pl, 'wmp', 'currentposition');
+				setStr(pl, 'wmp', 'defaultframe');
+				setStr(pl, 'wmp', 'playcount');
+				setStr(pl, 'wmp', 'rate');
+				setStr(pl, 'wmp', 'uimode');
+				setStr(pl, 'wmp', 'volume');
+			break;
+
+			case "rmp":
+				setBool(pl, 'rmp', 'autostart');
+				setBool(pl, 'rmp', 'loop');
+				setBool(pl, 'rmp', 'autogotourl');
+				setBool(pl, 'rmp', 'center');
+				setBool(pl, 'rmp', 'imagestatus');
+				setBool(pl, 'rmp', 'maintainaspect');
+				setBool(pl, 'rmp', 'nojava');
+				setBool(pl, 'rmp', 'prefetch');
+				setBool(pl, 'rmp', 'shuffle');
+				setStr(pl, 'rmp', 'console');
+				setStr(pl, 'rmp', 'controls');
+				setStr(pl, 'rmp', 'numloop');
+				setStr(pl, 'rmp', 'scriptcallbacks');
+			break;
+		}
+
+		setStr(pl, null, 'src');
+		setStr(pl, null, 'id');
+		setStr(pl, null, 'name');
+		setStr(pl, null, 'vspace');
+		setStr(pl, null, 'hspace');
+		setStr(pl, null, 'bgcolor');
+		setStr(pl, null, 'align');
+		setStr(pl, null, 'width');
+		setStr(pl, null, 'height');
+
+		if ((val = ed.dom.getAttrib(fe, "width")) != "")
+			pl.width = f.width.value = val;
+
+		if ((val = ed.dom.getAttrib(fe, "height")) != "")
+			pl.height = f.height.value = val;
+
+		oldWidth = pl.width ? parseInt(pl.width) : 0;
+		oldHeight = pl.height ? parseInt(pl.height) : 0;
+	} else
+		oldWidth = oldHeight = 0;
+
+	selectByValue(f, 'media_type', type);
+	changedType(type);
+	updateColor('bgcolor_pick', 'bgcolor');
+
+	TinyMCE_EditableSelects.init();
+	generatePreview();
+}
+
+function insertMedia() {
+	var fe, f = document.forms[0], h;
+
+	tinyMCEPopup.restoreSelection();
+
+	if (!AutoValidator.validate(f)) {
+		tinyMCEPopup.alert(ed.getLang('invalid_data'));
+		return false;
+	}
+
+	f.width.value = f.width.value == "" ? 100 : f.width.value;
+	f.height.value = f.height.value == "" ? 100 : f.height.value;
+
+	fe = ed.selection.getNode();
+	if (fe != null && /mceItem(Flash|ShockWave|WindowsMedia|QuickTime|RealMedia)/.test(ed.dom.getAttrib(fe, 'class'))) {
+		switch (f.media_type.options[f.media_type.selectedIndex].value) {
+			case "flash":
+				fe.className = "mceItemFlash";
+				break;
+
+			case "flv":
+				fe.className = "mceItemFlashVideo";
+				break;
+
+			case "shockwave":
+				fe.className = "mceItemShockWave";
+				break;
+
+			case "qt":
+				fe.className = "mceItemQuickTime";
+				break;
+
+			case "wmp":
+				fe.className = "mceItemWindowsMedia";
+				break;
+
+			case "rmp":
+				fe.className = "mceItemRealMedia";
+				break;
+		}
+
+		if (fe.width != f.width.value || fe.height != f.height.value)
+			ed.execCommand('mceRepaint');
+
+		fe.title = serializeParameters();
+		fe.width = f.width.value;
+		fe.height = f.height.value;
+		fe.style.width = f.width.value + (f.width.value.indexOf('%') == -1 ? 'px' : '');
+		fe.style.height = f.height.value + (f.height.value.indexOf('%') == -1 ? 'px' : '');
+		fe.align = f.align.options[f.align.selectedIndex].value;
+	} else {
+		h = '<img src="' + tinyMCEPopup.getWindowArg("plugin_url") + '/img/trans.gif"' ;
+
+		switch (f.media_type.options[f.media_type.selectedIndex].value) {
+			case "flash":
+				h += ' class="mceItemFlash"';
+				break;
+
+			case "flv":
+				h += ' class="mceItemFlashVideo"';
+				break;
+
+			case "shockwave":
+				h += ' class="mceItemShockWave"';
+				break;
+
+			case "qt":
+				h += ' class="mceItemQuickTime"';
+				break;
+
+			case "wmp":
+				h += ' class="mceItemWindowsMedia"';
+				break;
+
+			case "rmp":
+				h += ' class="mceItemRealMedia"';
+				break;
+		}
+
+		h += ' title="' + serializeParameters() + '"';
+		h += ' width="' + f.width.value + '"';
+		h += ' height="' + f.height.value + '"';
+		h += ' align="' + f.align.options[f.align.selectedIndex].value + '"';
+
+		h += ' />';
+
+		ed.execCommand('mceInsertContent', false, h);
+	}
+
+	tinyMCEPopup.close();
+}
+
+function updatePreview() {
+	var f = document.forms[0], type;
+
+	f.width.value = f.width.value || '320';
+	f.height.value = f.height.value || '240';
+
+	type = getType(f.src.value);
+	selectByValue(f, 'media_type', type);
+	changedType(type);
+	generatePreview();
+}
+
+function getMediaListHTML() {
+	if (typeof(tinyMCEMediaList) != "undefined" && tinyMCEMediaList.length > 0) {
+		var html = "";
+
+		html += '<select id="linklist" name="linklist" style="width: 250px" onchange="this.form.src.value=this.options[this.selectedIndex].value;updatePreview();">';
+		html += '<option value="">---</option>';
+
+		for (var i=0; i<tinyMCEMediaList.length; i++)
+			html += '<option value="' + tinyMCEMediaList[i][1] + '">' + tinyMCEMediaList[i][0] + '</option>';
+
+		html += '</select>';
+
+		return html;
+	}
+
+	return "";
+}
+
+function getType(v) {
+	var fo, i, c, el, x, f = document.forms[0];
+
+	fo = ed.getParam("media_types", "flash=swf;flv=flv;shockwave=dcr;qt=mov,qt,mpg,mp3,mp4,mpeg;shockwave=dcr;wmp=avi,wmv,wm,asf,asx,wmx,wvx;rmp=rm,ra,ram").split(';');
+
+	// YouTube
+	if (v.match(/watch\?v=(.+)(.*)/)) {
+		f.width.value = '425';
+		f.height.value = '350';
+		f.src.value = 'http://www.youtube.com/v/' + v.match(/v=(.*)(.*)/)[0].split('=')[1];
+		return 'flash';
+	}
+
+	// Google video
+	if (v.indexOf('http://video.google.com/videoplay?docid=') == 0) {
+		f.width.value = '425';
+		f.height.value = '326';
+		f.src.value = 'http://video.google.com/googleplayer.swf?docId=' + v.substring('http://video.google.com/videoplay?docid='.length) + '&hl=en';
+		return 'flash';
+	}
+
+	for (i=0; i<fo.length; i++) {
+		c = fo[i].split('=');
+
+		el = c[1].split(',');
+		for (x=0; x<el.length; x++)
+		if (v.indexOf('.' + el[x]) != -1)
+			return c[0];
+	}
+
+	return null;
+}
+
+function switchType(v) {
+	var t = getType(v), d = document, f = d.forms[0];
+
+	if (!t)
+		return;
+
+	selectByValue(d.forms[0], 'media_type', t);
+	changedType(t);
+
+	// Update qtsrc also
+	if (t == 'qt' && f.src.value.toLowerCase().indexOf('rtsp://') != -1) {
+		alert(ed.getLang("media_qt_stream_warn"));
+
+		if (f.qt_qtsrc.value == '')
+			f.qt_qtsrc.value = f.src.value;
+	}
+}
+
+function changedType(t) {
+	var d = document;
+
+	d.getElementById('flash_options').style.display = 'none';
+	d.getElementById('flv_options').style.display = 'none';
+	d.getElementById('qt_options').style.display = 'none';
+	d.getElementById('shockwave_options').style.display = 'none';
+	d.getElementById('wmp_options').style.display = 'none';
+	d.getElementById('rmp_options').style.display = 'none';
+
+	if (t)
+		d.getElementById(t + '_options').style.display = 'block';
+}
+
+function serializeParameters() {
+	var d = document, f = d.forms[0], s = '';
+
+	switch (f.media_type.options[f.media_type.selectedIndex].value) {
+		case "flash":
+			s += getBool('flash', 'play', true);
+			s += getBool('flash', 'loop', true);
+			s += getBool('flash', 'menu', true);
+			s += getBool('flash', 'swliveconnect', false);
+			s += getStr('flash', 'quality');
+			s += getStr('flash', 'scale');
+			s += getStr('flash', 'salign');
+			s += getStr('flash', 'wmode');
+			s += getStr('flash', 'base');
+			s += getStr('flash', 'flashvars');
+		break;
+
+		case "qt":
+			s += getBool('qt', 'loop', false);
+			s += getBool('qt', 'autoplay', true);
+			s += getBool('qt', 'cache', false);
+			s += getBool('qt', 'controller', true);
+			s += getBool('qt', 'correction', false, 'none', 'full');
+			s += getBool('qt', 'enablejavascript', false);
+			s += getBool('qt', 'kioskmode', false);
+			s += getBool('qt', 'autohref', false);
+			s += getBool('qt', 'playeveryframe', false);
+			s += getBool('qt', 'targetcache', false);
+			s += getStr('qt', 'scale');
+			s += getStr('qt', 'starttime');
+			s += getStr('qt', 'endtime');
+			s += getStr('qt', 'target');
+			s += getStr('qt', 'qtsrcchokespeed');
+			s += getStr('qt', 'volume');
+			s += getStr('qt', 'qtsrc');
+		break;
+
+		case "shockwave":
+			s += getBool('shockwave', 'sound');
+			s += getBool('shockwave', 'progress');
+			s += getBool('shockwave', 'autostart');
+			s += getBool('shockwave', 'swliveconnect');
+			s += getStr('shockwave', 'swvolume');
+			s += getStr('shockwave', 'swstretchstyle');
+			s += getStr('shockwave', 'swstretchhalign');
+			s += getStr('shockwave', 'swstretchvalign');
+		break;
+
+		case "wmp":
+			s += getBool('wmp', 'autostart', true);
+			s += getBool('wmp', 'enabled', false);
+			s += getBool('wmp', 'enablecontextmenu', true);
+			s += getBool('wmp', 'fullscreen', false);
+			s += getBool('wmp', 'invokeurls', true);
+			s += getBool('wmp', 'mute', false);
+			s += getBool('wmp', 'stretchtofit', false);
+			s += getBool('wmp', 'windowlessvideo', false);
+			s += getStr('wmp', 'balance');
+			s += getStr('wmp', 'baseurl');
+			s += getStr('wmp', 'captioningid');
+			s += getStr('wmp', 'currentmarker');
+			s += getStr('wmp', 'currentposition');
+			s += getStr('wmp', 'defaultframe');
+			s += getStr('wmp', 'playcount');
+			s += getStr('wmp', 'rate');
+			s += getStr('wmp', 'uimode');
+			s += getStr('wmp', 'volume');
+		break;
+
+		case "rmp":
+			s += getBool('rmp', 'autostart', false);
+			s += getBool('rmp', 'loop', false);
+			s += getBool('rmp', 'autogotourl', true);
+			s += getBool('rmp', 'center', false);
+			s += getBool('rmp', 'imagestatus', true);
+			s += getBool('rmp', 'maintainaspect', false);
+			s += getBool('rmp', 'nojava', false);
+			s += getBool('rmp', 'prefetch', false);
+			s += getBool('rmp', 'shuffle', false);
+			s += getStr('rmp', 'console');
+			s += getStr('rmp', 'controls');
+			s += getStr('rmp', 'numloop');
+			s += getStr('rmp', 'scriptcallbacks');
+		break;
+	}
+
+	s += getStr(null, 'id');
+	s += getStr(null, 'name');
+	s += getStr(null, 'src');
+	s += getStr(null, 'align');
+	s += getStr(null, 'bgcolor');
+	s += getInt(null, 'vspace');
+	s += getInt(null, 'hspace');
+	s += getStr(null, 'width');
+	s += getStr(null, 'height');
+
+	s = s.length > 0 ? s.substring(0, s.length - 1) : s;
+
+	return s;
+}
+
+function setBool(pl, p, n) {
+	if (typeof(pl[n]) == "undefined")
+		return;
+
+	document.forms[0].elements[p + "_" + n].checked = pl[n] != 'false';
+}
+
+function setStr(pl, p, n) {
+	var f = document.forms[0], e = f.elements[(p != null ? p + "_" : '') + n];
+
+	if (typeof(pl[n]) == "undefined")
+		return;
+
+	if (e.type == "text")
+		e.value = pl[n];
+	else
+		selectByValue(f, (p != null ? p + "_" : '') + n, pl[n]);
+}
+
+function getBool(p, n, d, tv, fv) {
+	var v = document.forms[0].elements[p + "_" + n].checked;
+
+	tv = typeof(tv) == 'undefined' ? 'true' : "'" + jsEncode(tv) + "'";
+	fv = typeof(fv) == 'undefined' ? 'false' : "'" + jsEncode(fv) + "'";
+
+	return (v == d) ? '' : n + (v ? ':' + tv + ',' : ":\'" + fv + "\',");
+}
+
+function getStr(p, n, d) {
+	var e = document.forms[0].elements[(p != null ? p + "_" : "") + n];
+	var v = e.type == "text" ? e.value : e.options[e.selectedIndex].value;
+
+	if (n == 'src')
+		v = tinyMCEPopup.editor.convertURL(v, 'src', null);
+
+	return ((n == d || v == '') ? '' : n + ":'" + jsEncode(v) + "',");
+}
+
+function getInt(p, n, d) {
+	var e = document.forms[0].elements[(p != null ? p + "_" : "") + n];
+	var v = e.type == "text" ? e.value : e.options[e.selectedIndex].value;
+
+	return ((n == d || v == '') ? '' : n + ":" + v.replace(/[^0-9]+/g, '') + ",");
+}
+
+function jsEncode(s) {
+	s = s.replace(new RegExp('\\\\', 'g'), '\\\\');
+	s = s.replace(new RegExp('"', 'g'), '\\"');
+	s = s.replace(new RegExp("'", 'g'), "\\'");
+
+	return s;
+}
+
+function generatePreview(c) {
+	var f = document.forms[0], p = document.getElementById('prev'), h = '', cls, pl, n, type, codebase, wp, hp, nw, nh;
+
+	p.innerHTML = '<!-- x --->';
+
+	nw = parseInt(f.width.value);
+	nh = parseInt(f.height.value);
+
+	if (f.width.value != "" && f.height.value != "") {
+		if (f.constrain.checked) {
+			if (c == 'width' && oldWidth != 0) {
+				wp = nw / oldWidth;
+				nh = Math.round(wp * nh);
+				f.height.value = nh;
+			} else if (c == 'height' && oldHeight != 0) {
+				hp = nh / oldHeight;
+				nw = Math.round(hp * nw);
+				f.width.value = nw;
+			}
+		}
+	}
+
+	if (f.width.value != "")
+		oldWidth = nw;
+
+	if (f.height.value != "")
+		oldHeight = nh;
+
+	// After constrain
+	pl = serializeParameters();
+
+	switch (f.media_type.options[f.media_type.selectedIndex].value) {
+		case "flash":
+			cls = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
+			codebase = 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0';
+			type = 'application/x-shockwave-flash';
+			break;
+
+		case "shockwave":
+			cls = 'clsid:166B1BCA-3F9C-11CF-8075-444553540000';
+			codebase = 'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0';
+			type = 'application/x-director';
+			break;
+
+		case "qt":
+			cls = 'clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B';
+			codebase = 'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0';
+			type = 'video/quicktime';
+			break;
+
+		case "wmp":
+			cls = ed.getParam('media_wmp6_compatible') ? 'clsid:05589FA1-C356-11CE-BF01-00AA0055595A' : 'clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6';
+			codebase = 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701';
+			type = 'application/x-mplayer2';
+			break;
+
+		case "rmp":
+			cls = 'clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA';
+			codebase = 'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701';
+			type = 'audio/x-pn-realaudio-plugin';
+			break;
+	}
+
+	if (pl == '') {
+		p.innerHTML = '';
+		return;
+	}
+
+	pl = tinyMCEPopup.editor.plugins.media._parse(pl);
+
+	if (!pl.src) {
+		p.innerHTML = '';
+		return;
+	}
+
+	pl.src = tinyMCEPopup.editor.documentBaseURI.toAbsolute(pl.src);
+	pl.width = !pl.width ? 100 : pl.width;
+	pl.height = !pl.height ? 100 : pl.height;
+	pl.id = !pl.id ? 'obj' : pl.id;
+	pl.name = !pl.name ? 'eobj' : pl.name;
+	pl.align = !pl.align ? '' : pl.align;
+
+	// Avoid annoying warning about insecure items
+	if (!tinymce.isIE || document.location.protocol != 'https:') {
+		h += '<object classid="' + cls + '" codebase="' + codebase + '" width="' + pl.width + '" height="' + pl.height + '" id="' + pl.id + '" name="' + pl.name + '" align="' + pl.align + '">';
+
+		for (n in pl) {
+			h += '<param name="' + n + '" value="' + pl[n] + '">';
+
+			// Add extra url parameter if it's an absolute URL
+			if (n == 'src' && pl[n].indexOf('://') != -1)
+				h += '<param name="url" value="' + pl[n] + '" />';
+		}
+	}
+
+	h += '<embed type="' + type + '" ';
+
+	for (n in pl)
+		h += n + '="' + pl[n] + '" ';
+
+	h += '></embed>';
+
+	// Avoid annoying warning about insecure items
+	if (!tinymce.isIE || document.location.protocol != 'https:')
+		h += '</object>';
+
+	p.innerHTML = "<!-- x --->" + h;
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/media/langs/en_dlg.js b/media/js/tinymce/plugins/media/langs/en_dlg.js
new file mode 100644
index 0000000..6d0a996
--- /dev/null
+++ b/media/js/tinymce/plugins/media/langs/en_dlg.js
@@ -0,0 +1,103 @@
+tinyMCE.addI18n('en.media_dlg',{
+title:"Insert / edit embedded media",
+general:"General",
+advanced:"Advanced",
+file:"File/URL",
+list:"List",
+size:"Dimensions",
+preview:"Preview",
+constrain_proportions:"Constrain proportions",
+type:"Type",
+id:"Id",
+name:"Name",
+class_name:"Class",
+vspace:"V-Space",
+hspace:"H-Space",
+play:"Auto play",
+loop:"Loop",
+menu:"Show menu",
+quality:"Quality",
+scale:"Scale",
+align:"Align",
+salign:"SAlign",
+wmode:"WMode",
+bgcolor:"Background",
+base:"Base",
+flashvars:"Flashvars",
+liveconnect:"SWLiveConnect",
+autohref:"AutoHREF",
+cache:"Cache",
+hidden:"Hidden",
+controller:"Controller",
+kioskmode:"Kiosk mode",
+playeveryframe:"Play every frame",
+targetcache:"Target cache",
+correction:"No correction",
+enablejavascript:"Enable JavaScript",
+starttime:"Start time",
+endtime:"End time",
+href:"Href",
+qtsrcchokespeed:"Choke speed",
+target:"Target",
+volume:"Volume",
+autostart:"Auto start",
+enabled:"Enabled",
+fullscreen:"Fullscreen",
+invokeurls:"Invoke URLs",
+mute:"Mute",
+stretchtofit:"Stretch to fit",
+windowlessvideo:"Windowless video",
+balance:"Balance",
+baseurl:"Base URL",
+captioningid:"Captioning id",
+currentmarker:"Current marker",
+currentposition:"Current position",
+defaultframe:"Default frame",
+playcount:"Play count",
+rate:"Rate",
+uimode:"UI Mode",
+flash_options:"Flash options",
+qt_options:"Quicktime options",
+wmp_options:"Windows media player options",
+rmp_options:"Real media player options",
+shockwave_options:"Shockwave options",
+autogotourl:"Auto goto URL",
+center:"Center",
+imagestatus:"Image status",
+maintainaspect:"Maintain aspect",
+nojava:"No java",
+prefetch:"Prefetch",
+shuffle:"Shuffle",
+console:"Console",
+numloop:"Num loops",
+controls:"Controls",
+scriptcallbacks:"Script callbacks",
+swstretchstyle:"Stretch style",
+swstretchhalign:"Stretch H-Align",
+swstretchvalign:"Stretch V-Align",
+sound:"Sound",
+progress:"Progress",
+qtsrc:"QT Src",
+qt_stream_warn:"Streamed rtsp resources should be added to the QT Src field under the advanced tab.\nYou should also add a non streamed version to the Src field..",
+align_top:"Top",
+align_right:"Right",
+align_bottom:"Bottom",
+align_left:"Left",
+align_center:"Center",
+align_top_left:"Top left",
+align_top_right:"Top right",
+align_bottom_left:"Bottom left",
+align_bottom_right:"Bottom right",
+flv_options:"Flash video options",
+flv_scalemode:"Scale mode",
+flv_buffer:"Buffer",
+flv_startimage:"Start image",
+flv_starttime:"Start time",
+flv_defaultvolume:"Default volumne",
+flv_hiddengui:"Hidden GUI",
+flv_autostart:"Auto start",
+flv_loop:"Loop",
+flv_showscalemodes:"Show scale modes",
+flv_smoothvideo:"Smooth video",
+flv_jscallback:"JS Callback"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/media/media.htm b/media/js/tinymce/plugins/media/media.htm
new file mode 100644
index 0000000..911c03d
--- /dev/null
+++ b/media/js/tinymce/plugins/media/media.htm
@@ -0,0 +1,822 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#media_dlg.title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/media.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/validate.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<link href="css/media.css" rel="stylesheet" type="text/css" />
+</head>
+<body style="display: none">
+    <form onsubmit="insertMedia();return false;" action="#">
+		<div class="tabs">
+			<ul>
+				<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');generatePreview();" onmousedown="return false;">{#media_dlg.general}</a></span></li>
+				<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#media_dlg.advanced}</a></span></li>
+			</ul>
+		</div>
+
+		<div class="panel_wrapper">
+			<div id="general_panel" class="panel current">
+				<fieldset>
+					<legend>{#media_dlg.general}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+							<tr>
+								<td><label for="media_type">{#media_dlg.type}</label></td>
+								<td>
+									<select id="media_type" name="media_type" onchange="changedType(this.value);generatePreview();">
+										<option value="flash">Flash</option>
+										<!-- <option value="flv">Flash video (FLV)</option> -->
+										<option value="qt">Quicktime</option>
+										<option value="shockwave">Shockwave</option>
+										<option value="wmp">Windows Media</option>
+										<option value="rmp">Real Media</option>
+									</select>
+								</td>
+							</tr>
+							<tr>
+							<td><label for="src">{#media_dlg.file}</label></td>
+							  <td>
+									<table border="0" cellspacing="0" cellpadding="0">
+									  <tr>
+										<td><input id="src" name="src" type="text" value="" class="mceFocus" onchange="switchType(this.value);generatePreview();" /></td>
+										<td id="filebrowsercontainer"> </td>
+									  </tr>
+									</table>
+								</td>
+							</tr>
+							<tr id="linklistrow">
+								<td><label for="linklist">{#media_dlg.list}</label></td>
+								<td id="linklistcontainer"><select id="linklist"><option value=""></option></select></td>
+							</tr>
+							<tr>
+								<td><label for="width">{#media_dlg.size}</label></td>
+								<td>
+									<table border="0" cellpadding="0" cellspacing="0">
+										<tr>
+											<td><input type="text" id="width" name="width" value="" class="size" onchange="generatePreview('width');" /> x <input type="text" id="height" name="height" value="" class="size"  onchange="generatePreview('height');" /></td>
+											<td>  <input id="constrain" type="checkbox" name="constrain" class="checkbox" /></td>
+											<td><label id="constrainlabel" for="constrain">{#media_dlg.constrain_proportions}</label></td>
+										</tr>
+									</table>
+								</td>
+							</tr>
+					</table>
+				</fieldset>
+
+				<fieldset>
+					<legend>{#media_dlg.preview}</legend>
+					<div id="prev"></div>
+				</fieldset>
+			</div>
+
+			<div id="advanced_panel" class="panel">
+				<fieldset>
+					<legend>{#media_dlg.advanced}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0" width="100%">
+						<tr>
+							<td><label for="id">{#media_dlg.id}</label></td>
+							<td><input type="text" id="id" name="id" onchange="generatePreview();" /></td>
+							<td><label for="name">{#media_dlg.name}</label></td>
+							<td><input type="text" id="name" name="name" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="align">{#media_dlg.align}</label></td>
+							<td>
+								<select id="align" name="align" onchange="generatePreview();">
+									<option value="">{#not_set}</option> 
+									<option value="top">{#media_dlg.align_top}</option>
+									<option value="right">{#media_dlg.align_right}</option>
+									<option value="bottom">{#media_dlg.align_bottom}</option>
+									<option value="left">{#media_dlg.align_left}</option>
+								</select>
+							</td>
+
+							<td><label for="bgcolor">{#media_dlg.bgcolor}</label></td>
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="bgcolor" name="bgcolor" type="text" value="" size="9" onchange="updateColor('bgcolor_pick','bgcolor');generatePreview();" /></td>
+										<td id="bgcolor_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="vspace">{#media_dlg.vspace}</label></td>
+							<td><input type="text" id="vspace" name="vspace" class="number" onchange="generatePreview();" /></td>
+							<td><label for="hspace">{#media_dlg.hspace}</label></td>
+							<td><input type="text" id="hspace" name="hspace" class="number" onchange="generatePreview();" /></td>
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset id="flash_options">
+					<legend>{#media_dlg.flash_options}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td><label for="flash_quality">{#media_dlg.quality}</label></td>
+							<td>
+								<select id="flash_quality" name="flash_quality" onchange="generatePreview();">
+									<option value="">{#not_set}</option> 
+									<option value="high">high</option>
+									<option value="low">low</option>
+									<option value="autolow">autolow</option>
+									<option value="autohigh">autohigh</option>
+									<option value="best">best</option>
+								</select>
+							</td>
+
+							<td><label for="flash_scale">{#media_dlg.scale}</label></td>
+							<td>
+								<select id="flash_scale" name="flash_scale" onchange="generatePreview();">
+									<option value="">{#not_set}</option> 
+									<option value="showall">showall</option>
+									<option value="noborder">noborder</option>
+									<option value="exactfit">exactfit</option>
+									<option value="noscale">noscale</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="flash_wmode">{#media_dlg.wmode}</label></td>
+							<td>
+								<select id="flash_wmode" name="flash_wmode" onchange="generatePreview();">
+									<option value="">{#not_set}</option> 
+									<option value="window">window</option>
+									<option value="opaque">opaque</option>
+									<option value="transparent">transparent</option>
+								</select>
+							</td>
+
+							<td><label for="flash_salign">{#media_dlg.salign}</label></td>
+							<td>
+								<select id="flash_salign" name="flash_salign" onchange="generatePreview();">
+									<option value="">{#not_set}</option> 
+									<option value="l">{#media_dlg.align_left}</option>
+									<option value="t">{#media_dlg.align_top}</option>
+									<option value="r">{#media_dlg.align_right}</option>
+									<option value="b">{#media_dlg.align_bottom}</option>
+									<option value="tl">{#media_dlg.align_top_left}</option>
+									<option value="tr">{#media_dlg.align_top_right}</option>
+									<option value="bl">{#media_dlg.align_bottom_left}</option>
+									<option value="br">{#media_dlg.align_bottom_right}</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flash_play" name="flash_play" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="flash_play">{#media_dlg.play}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flash_loop" name="flash_loop" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="flash_loop">{#media_dlg.loop}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flash_menu" name="flash_menu" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="flash_menu">{#media_dlg.menu}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flash_swliveconnect" name="flash_swliveconnect" onchange="generatePreview();" /></td>
+										<td><label for="flash_swliveconnect">{#media_dlg.liveconnect}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+					</table>
+
+					<table>
+						<tr>
+							<td><label for="flash_base">{#media_dlg.base}</label></td>
+							<td><input type="text" id="flash_base" name="flash_base" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="flash_flashvars">{#media_dlg.flashvars}</label></td>
+							<td><input type="text" id="flash_flashvars" name="flash_flashvars" onchange="generatePreview();" /></td>
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset id="flv_options">
+					<legend>{#media_dlg.flv_options}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td><label for="flv_scalemode">{#media_dlg.flv_scalemode}</label></td>
+							<td>
+								<select id="flv_scalemode" name="flv_scalemode" onchange="generatePreview();">
+									<option value="">{#not_set}</option> 
+									<option value="none">none</option>
+									<option value="double">double</option>
+									<option value="full">full</option>
+								</select>
+							</td>
+
+							<td><label for="flv_buffer">{#media_dlg.flv_buffer}</label></td>
+							<td><input type="text" id="flv_buffer" name="flv_buffer" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="flv_startimage">{#media_dlg.flv_startimage}</label></td>
+							<td><input type="text" id="flv_startimage" name="flv_startimage" onchange="generatePreview();" /></td>
+
+							<td><label for="flv_starttime">{#media_dlg.flv_starttime}</label></td>
+							<td><input type="text" id="flv_starttime" name="flv_starttime" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="flv_defaultvolume">{#media_dlg.flv_defaultvolume}</label></td>
+							<td><input type="text" id="flv_defaultvolume" name="flv_defaultvolume" onchange="generatePreview();" /></td>
+
+
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flv_hiddengui" name="flv_hiddengui" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="flv_hiddengui">{#media_dlg.flv_hiddengui}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flv_autostart" name="flv_autostart" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="flv_autostart">{#media_dlg.flv_autostart}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flv_loop" name="flv_loop" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="flv_loop">{#media_dlg.flv_loop}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flv_showscalemodes" name="flv_showscalemodes" onchange="generatePreview();" /></td>
+										<td><label for="flv_showscalemodes">{#media_dlg.flv_showscalemodes}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flv_smoothvideo" name="flash_flv_flv_smoothvideosmoothvideo" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="flv_smoothvideo">{#media_dlg.flv_smoothvideo}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="flv_jscallback" name="flv_jscallback" onchange="generatePreview();" /></td>
+										<td><label for="flv_jscallback">{#media_dlg.flv_jscallback}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset id="qt_options">
+					<legend>{#media_dlg.qt_options}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_loop" name="qt_loop" onchange="generatePreview();" /></td>
+										<td><label for="qt_loop">{#media_dlg.loop}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_autoplay" name="qt_autoplay" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="qt_autoplay">{#media_dlg.play}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_cache" name="qt_cache" onchange="generatePreview();" /></td>
+										<td><label for="qt_cache">{#media_dlg.cache}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_controller" name="qt_controller" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="qt_controller">{#media_dlg.controller}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_correction" name="qt_correction" onchange="generatePreview();" /></td>
+										<td><label for="qt_correction">{#media_dlg.correction}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_enablejavascript" name="qt_enablejavascript" onchange="generatePreview();" /></td>
+										<td><label for="qt_enablejavascript">{#media_dlg.enablejavascript}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_kioskmode" name="qt_kioskmode" onchange="generatePreview();" /></td>
+										<td><label for="qt_kioskmode">{#media_dlg.kioskmode}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_autohref" name="qt_autohref" onchange="generatePreview();" /></td>
+										<td><label for="qt_autohref">{#media_dlg.autohref}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_playeveryframe" name="qt_playeveryframe" onchange="generatePreview();" /></td>
+										<td><label for="qt_playeveryframe">{#media_dlg.playeveryframe}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="qt_targetcache" name="qt_targetcache" onchange="generatePreview();" /></td>
+										<td><label for="qt_targetcache">{#media_dlg.targetcache}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="qt_scale">{#media_dlg.scale}</label></td>
+							<td><select id="qt_scale" name="qt_scale" class="mceEditableSelect" onchange="generatePreview();">
+									<option value="">{#not_set}</option> 
+									<option value="tofit">tofit</option>
+									<option value="aspect">aspect</option>
+								</select>
+							</td>
+
+							<td colspan="2"> </td>
+						</tr>
+
+						<tr>
+							<td><label for="qt_starttime">{#media_dlg.starttime}</label></td>
+							<td><input type="text" id="qt_starttime" name="qt_starttime" onchange="generatePreview();" /></td>
+
+							<td><label for="qt_endtime">{#media_dlg.endtime}</label></td>
+							<td><input type="text" id="qt_endtime" name="qt_endtime" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="qt_target">{#media_dlg.target}</label></td>
+							<td><input type="text" id="qt_target" name="qt_target" onchange="generatePreview();" /></td>
+
+							<td><label for="qt_href">{#media_dlg.href}</label></td>
+							<td><input type="text" id="qt_href" name="qt_href" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="qt_qtsrcchokespeed">{#media_dlg.qtsrcchokespeed}</label></td>
+							<td><input type="text" id="qt_qtsrcchokespeed" name="qt_qtsrcchokespeed" onchange="generatePreview();" /></td>
+
+							<td><label for="qt_volume">{#media_dlg.volume}</label></td>
+							<td><input type="text" id="qt_volume" name="qt_volume" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="qt_qtsrc">{#media_dlg.qtsrc}</label></td>
+							<td colspan="4">
+							<table border="0" cellspacing="0" cellpadding="0">
+								  <tr>
+									<td><input type="text" id="qt_qtsrc" name="qt_qtsrc" onchange="generatePreview();" /></td>
+									<td id="qtsrcfilebrowsercontainer"> </td>
+								  </tr>
+							</table>
+							</td>
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset id="wmp_options">
+					<legend>{#media_dlg.wmp_options}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="wmp_autostart" name="wmp_autostart" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="wmp_autostart">{#media_dlg.autostart}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="wmp_enabled" name="wmp_enabled" onchange="generatePreview();" /></td>
+										<td><label for="wmp_enabled">{#media_dlg.enabled}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="wmp_enablecontextmenu" name="wmp_enablecontextmenu" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="wmp_enablecontextmenu">{#media_dlg.menu}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="wmp_fullscreen" name="wmp_fullscreen" onchange="generatePreview();" /></td>
+										<td><label for="wmp_fullscreen">{#media_dlg.fullscreen}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="wmp_invokeurls" name="wmp_invokeurls" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="wmp_invokeurls">{#media_dlg.invokeurls}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="wmp_mute" name="wmp_mute" onchange="generatePreview();" /></td>
+										<td><label for="wmp_mute">{#media_dlg.mute}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="wmp_stretchtofit" name="wmp_stretchtofit" onchange="generatePreview();" /></td>
+										<td><label for="wmp_stretchtofit">{#media_dlg.stretchtofit}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="wmp_windowlessvideo" name="wmp_windowlessvideo" onchange="generatePreview();" /></td>
+										<td><label for="wmp_windowlessvideo">{#media_dlg.windowlessvideo}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="wmp_balance">{#media_dlg.balance}</label></td>
+							<td><input type="text" id="wmp_balance" name="wmp_balance" onchange="generatePreview();" /></td>
+
+							<td><label for="wmp_baseurl">{#media_dlg.baseurl}</label></td>
+							<td><input type="text" id="wmp_baseurl" name="wmp_baseurl" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="wmp_captioningid">{#media_dlg.captioningid}</label></td>
+							<td><input type="text" id="wmp_captioningid" name="wmp_captioningid" onchange="generatePreview();" /></td>
+
+							<td><label for="wmp_currentmarker">{#media_dlg.currentmarker}</label></td>
+							<td><input type="text" id="wmp_currentmarker" name="wmp_currentmarker" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="wmp_currentposition">{#media_dlg.currentposition}</label></td>
+							<td><input type="text" id="wmp_currentposition" name="wmp_currentposition" onchange="generatePreview();" /></td>
+
+							<td><label for="wmp_defaultframe">{#media_dlg.defaultframe}</label></td>
+							<td><input type="text" id="wmp_defaultframe" name="wmp_defaultframe" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="wmp_playcount">{#media_dlg.playcount}</label></td>
+							<td><input type="text" id="wmp_playcount" name="wmp_playcount" onchange="generatePreview();" /></td>
+
+							<td><label for="wmp_rate">{#media_dlg.rate}</label></td>
+							<td><input type="text" id="wmp_rate" name="wmp_rate" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="wmp_uimode">{#media_dlg.uimode}</label></td>
+							<td><input type="text" id="wmp_uimode" name="wmp_uimode" onchange="generatePreview();" /></td>
+
+							<td><label for="wmp_volume">{#media_dlg.volume}</label></td>
+							<td><input type="text" id="wmp_volume" name="wmp_volume" onchange="generatePreview();" /></td>
+						</tr>
+
+					</table>
+				</fieldset>
+
+				<fieldset id="rmp_options">
+					<legend>{#media_dlg.rmp_options}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_autostart" name="rmp_autostart" onchange="generatePreview();" /></td>
+										<td><label for="rmp_autostart">{#media_dlg.autostart}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_loop" name="rmp_loop" onchange="generatePreview();" /></td>
+										<td><label for="rmp_loop">{#media_dlg.loop}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_autogotourl" name="rmp_autogotourl" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="rmp_autogotourl">{#media_dlg.autogotourl}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_center" name="rmp_center" onchange="generatePreview();" /></td>
+										<td><label for="rmp_center">{#media_dlg.center}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_imagestatus" name="rmp_imagestatus" checked="checked" onchange="generatePreview();" /></td>
+										<td><label for="rmp_imagestatus">{#media_dlg.imagestatus}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_maintainaspect" name="rmp_maintainaspect" onchange="generatePreview();" /></td>
+										<td><label for="rmp_maintainaspect">{#media_dlg.maintainaspect}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_nojava" name="rmp_nojava" onchange="generatePreview();" /></td>
+										<td><label for="rmp_nojava">{#media_dlg.nojava}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_prefetch" name="rmp_prefetch" onchange="generatePreview();" /></td>
+										<td><label for="rmp_prefetch">{#media_dlg.prefetch}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="rmp_shuffle" name="rmp_shuffle" onchange="generatePreview();" /></td>
+										<td><label for="rmp_shuffle">{#media_dlg.shuffle}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								 
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="rmp_console">{#media_dlg.console}</label></td>
+							<td><input type="text" id="rmp_console" name="rmp_console" onchange="generatePreview();" /></td>
+
+							<td><label for="rmp_controls">{#media_dlg.controls}</label></td>
+							<td><input type="text" id="rmp_controls" name="rmp_controls" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="rmp_numloop">{#media_dlg.numloop}</label></td>
+							<td><input type="text" id="rmp_numloop" name="rmp_numloop" onchange="generatePreview();" /></td>
+
+							<td><label for="rmp_scriptcallbacks">{#media_dlg.scriptcallbacks}</label></td>
+							<td><input type="text" id="rmp_scriptcallbacks" name="rmp_scriptcallbacks" onchange="generatePreview();" /></td>
+						</tr>
+					</table>
+				</fieldset>
+
+				<fieldset id="shockwave_options">
+					<legend>{#media_dlg.shockwave_options}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td><label for="shockwave_swstretchstyle">{#media_dlg.swstretchstyle}</label></td>
+							<td>
+								<select id="shockwave_swstretchstyle" name="shockwave_swstretchstyle" onchange="generatePreview();">
+									<option value="none">{#not_set}</option>
+									<option value="meet">Meet</option>
+									<option value="fill">Fill</option>
+									<option value="stage">Stage</option>
+								</select>
+							</td>
+
+							<td><label for="shockwave_swvolume">{#media_dlg.volume}</label></td>
+							<td><input type="text" id="shockwave_swvolume" name="shockwave_swvolume" onchange="generatePreview();" /></td>
+						</tr>
+
+						<tr>
+							<td><label for="shockwave_swstretchhalign">{#media_dlg.swstretchhalign}</label></td>
+							<td>
+								<select id="shockwave_swstretchhalign" name="shockwave_swstretchhalign" onchange="generatePreview();">
+									<option value="none">{#not_set}</option>
+									<option value="left">{#media_dlg.align_left}</option>
+									<option value="center">{#media_dlg.align_center}</option>
+									<option value="right">{#media_dlg.align_right}</option>
+								</select>
+							</td>
+
+							<td><label for="shockwave_swstretchvalign">{#media_dlg.swstretchvalign}</label></td>
+							<td>
+								<select id="shockwave_swstretchvalign" name="shockwave_swstretchvalign" onchange="generatePreview();">
+									<option value="none">{#not_set}</option>
+									<option value="meet">Meet</option>
+									<option value="fill">Fill</option>
+									<option value="stage">Stage</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="shockwave_autostart" name="shockwave_autostart" onchange="generatePreview();" checked="checked" /></td>
+										<td><label for="shockwave_autostart">{#media_dlg.autostart}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="shockwave_sound" name="shockwave_sound" onchange="generatePreview();" checked="checked" /></td>
+										<td><label for="shockwave_sound">{#media_dlg.sound}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+
+
+						<tr>
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="shockwave_swliveconnect" name="shockwave_swliveconnect" onchange="generatePreview();" /></td>
+										<td><label for="shockwave_swliveconnect">{#media_dlg.liveconnect}</label></td>
+									</tr>
+								</table>
+							</td>
+
+							<td colspan="2">
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input type="checkbox" class="checkbox" id="shockwave_progress" name="shockwave_progress" onchange="generatePreview();" checked="checked" /></td>
+										<td><label for="shockwave_progress">{#media_dlg.progress}</label></td>
+									</tr>
+								</table>
+							</td>
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+		</div>
+
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" id="insert" name="insert" value="{#insert}" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+	</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/nonbreaking/editor_plugin.js b/media/js/tinymce/plugins/nonbreaking/editor_plugin.js
new file mode 100644
index 0000000..f2dbbff
--- /dev/null
+++ b/media/js/tinymce/plugins/nonbreaking/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.Nonbreaking",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceNonBreaking",function(){a.execCommand("mceInsertContent",false,(a.plugins.visualchars&&a.plugins.visualchars.state)?'<span class="mceItemHidden mceVisualNbsp">·</span>':" ")});a.addButton("nonbreaking",{title:"nonbreaking.nonbreaking_desc",cmd:"mceNonBreaking"});if(a.getParam("nonbreaking_force_tab")){a.onKeyDown.add(function(d,f){if(tinymce.isIE&&f.keyCode==9){ [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/nonbreaking/editor_plugin_src.js b/media/js/tinymce/plugins/nonbreaking/editor_plugin_src.js
new file mode 100644
index 0000000..b723756
--- /dev/null
+++ b/media/js/tinymce/plugins/nonbreaking/editor_plugin_src.js
@@ -0,0 +1,50 @@
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.Nonbreaking', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceNonBreaking', function() {
+				ed.execCommand('mceInsertContent', false, (ed.plugins.visualchars && ed.plugins.visualchars.state) ? '<span class="mceItemHidden mceVisualNbsp">·</span>' : ' ');
+			});
+
+			// Register buttons
+			ed.addButton('nonbreaking', {title : 'nonbreaking.nonbreaking_desc', cmd : 'mceNonBreaking'});
+
+			if (ed.getParam('nonbreaking_force_tab')) {
+				ed.onKeyDown.add(function(ed, e) {
+					if (tinymce.isIE && e.keyCode == 9) {
+						ed.execCommand('mceNonBreaking');
+						ed.execCommand('mceNonBreaking');
+						ed.execCommand('mceNonBreaking');
+						tinymce.dom.Event.cancel(e);
+					}
+				});
+			}
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Nonbreaking space',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/nonbreaking',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+
+		// Private methods
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('nonbreaking', tinymce.plugins.Nonbreaking);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/noneditable/editor_plugin.js b/media/js/tinymce/plugins/noneditable/editor_plugin.js
new file mode 100644
index 0000000..9945cd8
--- /dev/null
+++ b/media/js/tinymce/plugins/noneditable/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.dom.Event;tinymce.create("tinymce.plugins.NonEditablePlugin",{init:function(d,e){var f=this,c,b;f.editor=d;c=d.getParam("noneditable_editable_class","mceEditable");b=d.getParam("noneditable_noneditable_class","mceNonEditable");d.onNodeChange.addToTop(function(h,g,k){var j,i;j=h.dom.getParent(h.selection.getStart(),function(l){return h.dom.hasClass(l,b)});i=h.dom.getParent(h.selection.getEnd(),function(l){return h.dom.hasClass(l,b)});if(j||i){f._setDisabled(1);re [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/noneditable/editor_plugin_src.js b/media/js/tinymce/plugins/noneditable/editor_plugin_src.js
new file mode 100644
index 0000000..77db577
--- /dev/null
+++ b/media/js/tinymce/plugins/noneditable/editor_plugin_src.js
@@ -0,0 +1,87 @@
+/**
+ * $Id: editor_plugin_src.js 743 2008-03-23 17:47:33Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var Event = tinymce.dom.Event;
+
+	tinymce.create('tinymce.plugins.NonEditablePlugin', {
+		init : function(ed, url) {
+			var t = this, editClass, nonEditClass;
+
+			t.editor = ed;
+			editClass = ed.getParam("noneditable_editable_class", "mceEditable");
+			nonEditClass = ed.getParam("noneditable_noneditable_class", "mceNonEditable");
+
+			ed.onNodeChange.addToTop(function(ed, cm, n) {
+				var sc, ec;
+
+				// Block if start or end is inside a non editable element
+				sc = ed.dom.getParent(ed.selection.getStart(), function(n) {
+					return ed.dom.hasClass(n, nonEditClass);
+				});
+
+				ec = ed.dom.getParent(ed.selection.getEnd(), function(n) {
+					return ed.dom.hasClass(n, nonEditClass);
+				});
+
+				// Block or unblock
+				if (sc || ec) {
+					t._setDisabled(1);
+					return false;
+				} else
+					t._setDisabled(0);
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Non editable elements',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/noneditable',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		_block : function(ed, e) {
+			var k = e.keyCode;
+
+			// Don't block arrow keys, pg up/down, and F1-F12
+			if ((k > 32 && k < 41) || (k > 111 && k < 124))
+				return;
+
+			return Event.cancel(e);
+		},
+
+		_setDisabled : function(s) {
+			var t = this, ed = t.editor;
+
+			tinymce.each(ed.controlManager.controls, function(c) {
+				c.setDisabled(s);
+			});
+
+			if (s !== t.disabled) {
+				if (s) {
+					ed.onKeyDown.addToTop(t._block);
+					ed.onKeyPress.addToTop(t._block);
+					ed.onKeyUp.addToTop(t._block);
+					ed.onPaste.addToTop(t._block);
+				} else {
+					ed.onKeyDown.remove(t._block);
+					ed.onKeyPress.remove(t._block);
+					ed.onKeyUp.remove(t._block);
+					ed.onPaste.remove(t._block);
+				}
+
+				t.disabled = s;
+			}
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('noneditable', tinymce.plugins.NonEditablePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/pagebreak/css/content.css b/media/js/tinymce/plugins/pagebreak/css/content.css
new file mode 100644
index 0000000..c949d58
--- /dev/null
+++ b/media/js/tinymce/plugins/pagebreak/css/content.css
@@ -0,0 +1 @@
+.mcePageBreak {display:block;border:0;width:100%;height:12px;border-top:1px dotted #ccc;margin-top:15px;background:#fff url(../img/pagebreak.gif) no-repeat center top;}
diff --git a/media/js/tinymce/plugins/pagebreak/editor_plugin.js b/media/js/tinymce/plugins/pagebreak/editor_plugin.js
new file mode 100644
index 0000000..a212f69
--- /dev/null
+++ b/media/js/tinymce/plugins/pagebreak/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.PageBreakPlugin",{init:function(b,d){var f='<img src="'+d+'/img/trans.gif" class="mcePageBreak mceItemNoResize" />',a="mcePageBreak",c=b.getParam("pagebreak_separator","<!-- pagebreak -->"),e;e=new RegExp(c.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,function(g){return"\\"+g}),"g");b.addCommand("mcePageBreak",function(){b.execCommand("mceInsertContent",0,f)});b.addButton("pagebreak",{title:"pagebreak.desc",cmd:a});b.onInit.add(function(){if(b.setti [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/pagebreak/editor_plugin_src.js b/media/js/tinymce/plugins/pagebreak/editor_plugin_src.js
new file mode 100644
index 0000000..16f5748
--- /dev/null
+++ b/media/js/tinymce/plugins/pagebreak/editor_plugin_src.js
@@ -0,0 +1,74 @@
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.PageBreakPlugin', {
+		init : function(ed, url) {
+			var pb = '<img src="' + url + '/img/trans.gif" class="mcePageBreak mceItemNoResize" />', cls = 'mcePageBreak', sep = ed.getParam('pagebreak_separator', '<!-- pagebreak -->'), pbRE;
+
+			pbRE = new RegExp(sep.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g, function(a) {return '\\' + a;}), 'g');
+
+			// Register commands
+			ed.addCommand('mcePageBreak', function() {
+				ed.execCommand('mceInsertContent', 0, pb);
+			});
+
+			// Register buttons
+			ed.addButton('pagebreak', {title : 'pagebreak.desc', cmd : cls});
+
+			ed.onInit.add(function() {
+				if (ed.settings.content_css !== false)
+					ed.dom.loadCSS(url + "/css/content.css");
+
+				if (ed.theme.onResolveName) {
+					ed.theme.onResolveName.add(function(th, o) {
+						if (o.node.nodeName == 'IMG' && ed.dom.hasClass(o.node, cls))
+							o.name = 'pagebreak';
+					});
+				}
+			});
+
+			ed.onClick.add(function(ed, e) {
+				e = e.target;
+
+				if (e.nodeName === 'IMG' && ed.dom.hasClass(e, cls))
+					ed.selection.select(e);
+			});
+
+			ed.onNodeChange.add(function(ed, cm, n) {
+				cm.setActive('pagebreak', n.nodeName === 'IMG' && ed.dom.hasClass(n, cls));
+			});
+
+			ed.onBeforeSetContent.add(function(ed, o) {
+				o.content = o.content.replace(pbRE, pb);
+			});
+
+			ed.onPostProcess.add(function(ed, o) {
+				if (o.get)
+					o.content = o.content.replace(/<img[^>]+>/g, function(im) {
+						if (im.indexOf('class="mcePageBreak') !== -1)
+							im = sep;
+
+						return im;
+					});
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'PageBreak',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/pagebreak',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('pagebreak', tinymce.plugins.PageBreakPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/pagebreak/img/pagebreak.gif b/media/js/tinymce/plugins/pagebreak/img/pagebreak.gif
new file mode 100644
index 0000000..acdf408
Binary files /dev/null and b/media/js/tinymce/plugins/pagebreak/img/pagebreak.gif differ
diff --git a/media/js/tinymce/plugins/pagebreak/img/trans.gif b/media/js/tinymce/plugins/pagebreak/img/trans.gif
new file mode 100644
index 0000000..3884865
Binary files /dev/null and b/media/js/tinymce/plugins/pagebreak/img/trans.gif differ
diff --git a/media/js/tinymce/plugins/paste/editor_plugin.js b/media/js/tinymce/plugins/paste/editor_plugin.js
new file mode 100644
index 0000000..7b2bbd9
--- /dev/null
+++ b/media/js/tinymce/plugins/paste/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.PastePlugin",{init:function(c,d){var e=this,b;e.editor=c;e.url=d;e.onPreProcess=new tinymce.util.Dispatcher(e);e.onPostProcess=new tinymce.util.Dispatcher(e);e.onPreProcess.add(e._preProcess);e.onPostProcess.add(e._postProcess);e.onPreProcess.add(function(h,i){c.execCallback("paste_preprocess",h,i)});e.onPostProcess.add(function(h,i){c.execCallback("paste_postprocess",h,i)});function g(i){var h=c.dom;e.onPreProcess.dispatch(e [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/paste/editor_plugin_src.js b/media/js/tinymce/plugins/paste/editor_plugin_src.js
new file mode 100644
index 0000000..8b6cae3
--- /dev/null
+++ b/media/js/tinymce/plugins/paste/editor_plugin_src.js
@@ -0,0 +1,531 @@
+/**
+ * $Id: editor_plugin_src.js 1225 2009-09-07 19:06:19Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var each = tinymce.each;
+
+	tinymce.create('tinymce.plugins.PastePlugin', {
+		init : function(ed, url) {
+			var t = this, cb;
+
+			t.editor = ed;
+			t.url = url;
+
+			// Setup plugin events
+			t.onPreProcess = new tinymce.util.Dispatcher(t);
+			t.onPostProcess = new tinymce.util.Dispatcher(t);
+
+			// Register default handlers
+			t.onPreProcess.add(t._preProcess);
+			t.onPostProcess.add(t._postProcess);
+
+			// Register optional preprocess handler
+			t.onPreProcess.add(function(pl, o) {
+				ed.execCallback('paste_preprocess', pl, o);
+			});
+
+			// Register optional postprocess
+			t.onPostProcess.add(function(pl, o) {
+				ed.execCallback('paste_postprocess', pl, o);
+			});
+
+			// This function executes the process handlers and inserts the contents
+			function process(o) {
+				var dom = ed.dom;
+
+				// Execute pre process handlers
+				t.onPreProcess.dispatch(t, o);
+
+				// Create DOM structure
+				o.node = dom.create('div', 0, o.content);
+
+				// Execute post process handlers
+				t.onPostProcess.dispatch(t, o);
+
+				// Serialize content
+				o.content = ed.serializer.serialize(o.node, {getInner : 1});
+
+				//  Insert cleaned content. We need to handle insertion of contents containing block elements separately
+				if (/<(p|h[1-6]|ul|ol)/.test(o.content))
+					t._insertBlockContent(ed, dom, o.content);
+				else
+					t._insert(o.content);
+			};
+
+			// Add command for external usage
+			ed.addCommand('mceInsertClipboardContent', function(u, o) {
+				process(o);
+			});
+
+			// This function grabs the contents from the clipboard by adding a
+			// hidden div and placing the caret inside it and after the browser paste
+			// is done it grabs that contents and processes that
+			function grabContent(e) {
+				var n, or, rng, sel = ed.selection, dom = ed.dom, body = ed.getBody(), posY;
+
+				if (dom.get('_mcePaste'))
+					return;
+
+				// Create container to paste into
+				n = dom.add(body, 'div', {id : '_mcePaste'}, '\uFEFF');
+
+				// If contentEditable mode we need to find out the position of the closest element
+				if (body != ed.getDoc().body)
+					posY = dom.getPos(ed.selection.getStart(), body).y;
+				else
+					posY = body.scrollTop;
+
+				// Styles needs to be applied after the element is added to the document since WebKit will otherwise remove all styles
+				dom.setStyles(n, {
+					position : 'absolute',
+					left : -10000,
+					top : posY,
+					width : 1,
+					height : 1,
+					overflow : 'hidden'
+				});
+
+				if (tinymce.isIE) {
+					// Select the container
+					rng = dom.doc.body.createTextRange();
+					rng.moveToElementText(n);
+					rng.execCommand('Paste');
+
+					// Remove container
+					dom.remove(n);
+
+					// Check if the contents was changed, if it wasn't then clipboard extraction failed probably due
+					// to IE security settings so we pass the junk though better than nothing right
+					if (n.innerHTML === '\uFEFF') {
+						ed.execCommand('mcePasteWord');
+						e.preventDefault();
+						return;
+					}
+
+					// Process contents
+					process({content : n.innerHTML});
+
+					// Block the real paste event
+					return tinymce.dom.Event.cancel(e);
+				} else {
+					or = ed.selection.getRng();
+
+					// Move caret into hidden div
+					n = n.firstChild;
+					rng = ed.getDoc().createRange();
+					rng.setStart(n, 0);
+					rng.setEnd(n, 1);
+					sel.setRng(rng);
+
+					// Wait a while and grab the pasted contents
+					window.setTimeout(function() {
+						var h = '', nl = dom.select('div[id=_mcePaste]');
+
+						// WebKit will split the div into multiple ones so this will loop through then all and join them to get the whole HTML string
+						each(nl, function(n) {
+							h += (dom.select('> span.Apple-style-span div', n)[0] || dom.select('> span.Apple-style-span', n)[0] || n).innerHTML;
+						});
+
+						// Remove the nodes
+						each(nl, function(n) {
+							dom.remove(n);
+						});
+
+						// Restore the old selection
+						if (or)
+							sel.setRng(or);
+
+						process({content : h});
+					}, 0);
+				}
+			};
+
+			// Check if we should use the new auto process method			
+			if (ed.getParam('paste_auto_cleanup_on_paste', true)) {
+				// Is it's Opera or older FF use key handler
+				if (tinymce.isOpera || /Firefox\/2/.test(navigator.userAgent)) {
+					ed.onKeyDown.add(function(ed, e) {
+						if (((tinymce.isMac ? e.metaKey : e.ctrlKey) && e.keyCode == 86) || (e.shiftKey && e.keyCode == 45))
+							grabContent(e);
+					});
+				} else {
+					// Grab contents on paste event on Gecko and WebKit
+					ed.onPaste.addToTop(function(ed, e) {
+						return grabContent(e);
+					});
+				}
+			}
+
+			// Block all drag/drop events
+			if (ed.getParam('paste_block_drop')) {
+				ed.onInit.add(function() {
+					ed.dom.bind(ed.getBody(), ['dragend', 'dragover', 'draggesture', 'dragdrop', 'drop', 'drag'], function(e) {
+						e.preventDefault();
+						e.stopPropagation();
+
+						return false;
+					});
+				});
+			}
+
+			// Add legacy support
+			t._legacySupport();
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Paste text/word',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		_preProcess : function(pl, o) {
+			var ed = this.editor, h = o.content, process, stripClass;
+
+			//console.log('Before preprocess:' + o.content);
+
+			function process(items) {
+				each(items, function(v) {
+					// Remove or replace
+					if (v.constructor == RegExp)
+						h = h.replace(v, '');
+					else
+						h = h.replace(v[0], v[1]);
+				});
+			};
+
+			// Detect Word content and process it more aggressive
+			if (/(class=\"?Mso|style=\"[^\"]*\bmso\-|w:WordDocument)/.test(h) || o.wordContent) {
+				o.wordContent = true; // Mark the pasted contents as word specific content
+				//console.log('Word contents detected.');
+
+				// Process away some basic content
+				process([
+					/^\s*( )+/g,											// nbsp entities at the start of contents
+					/( |<br[^>]*>)+\s*$/g									// nbsp entities at the end of contents
+				]);
+
+				if (ed.getParam('paste_convert_middot_lists', true)) {
+					process([
+						[/<!--\[if !supportLists\]-->/gi, '$&__MCE_ITEM__'],			// Convert supportLists to a list item marker
+						[/(<span[^>]+:\s*symbol[^>]+>)/gi, '$1__MCE_ITEM__'],				// Convert symbol spans to list items
+						[/(<span[^>]+mso-list:[^>]+>)/gi, '$1__MCE_ITEM__']				// Convert mso-list to item marker
+					]);
+				}
+
+				process([
+					/<!--[\s\S]+?-->/gi,												// Word comments
+					/<\/?(img|font|meta|link|style|div|v:\w+)[^>]*>/gi,					// Remove some tags including VML content
+					/<\\?\?xml[^>]*>/gi,												// XML namespace declarations
+					/<\/?o:[^>]*>/gi,													// MS namespaced elements <o:tag>
+					/ (id|name|language|type|on\w+|v:\w+)=\"([^\"]*)\"/gi,				// on.., class, style and language attributes with quotes
+					/ (id|name|language|type|on\w+|v:\w+)=(\w+)/gi,						// on.., class, style and language attributes without quotes (IE)
+					[/<(\/?)s>/gi, '<$1strike>'],										// Convert <s> into <strike> for line-though
+					/<script[^>]+>[\s\S]*?<\/script>/gi,								// All scripts elements for msoShowComment for example
+					[/ /g, '\u00a0']												// Replace nsbp entites to char since it's easier to handle
+				]);
+
+				// Remove all spans if no styles is to be retained
+				if (!ed.getParam('paste_retain_style_properties')) {
+					process([
+						/<\/?(span)[^>]*>/gi
+					]);
+				}
+			}
+
+			// Allow for class names to be retained if desired; either all, or just the ones from Word
+			// Note that the paste_strip_class_attributes: 'none, verify_css_classes: true is also a good variation.
+			stripClass = ed.getParam('paste_strip_class_attributes');
+			if (stripClass != 'none') {
+				// Cleans everything but mceItem... classes
+				function cleanClasses(str, cls) {
+					var i, out = '';
+
+					// Remove all classes
+					if (stripClass == 'all')
+						return '';
+
+					cls = tinymce.explode(cls, ' ');
+
+					for (i = cls.length - 1; i >= 0; i--) {
+						// Remove Mso classes
+						if (!/^(Mso)/i.test(cls[i]))
+							out += (!out ? '' : ' ') + cls[i];
+					}
+
+					return ' class="' + out + '"';
+				};
+
+				process([
+					[/ class=\"([^\"]*)\"/gi, cleanClasses],	// class attributes with quotes
+					[/ class=(\w+)/gi, cleanClasses]			// class attributes without quotes (IE)
+				]);
+			}
+
+			// Remove spans option
+			if (ed.getParam('paste_remove_spans')) {
+				process([
+					/<\/?(span)[^>]*>/gi
+				]);
+			}
+
+			//console.log('After preprocess:' + h);
+
+			o.content = h;
+		},
+
+		/**
+		 * Various post process items.
+		 */
+		_postProcess : function(pl, o) {
+			var t = this, ed = t.editor, dom = ed.dom, styleProps;
+
+			if (o.wordContent) {
+				// Remove named anchors or TOC links
+				each(dom.select('a', o.node), function(a) {
+					if (!a.href || a.href.indexOf('#_Toc') != -1)
+						dom.remove(a, 1);
+				});
+
+				if (t.editor.getParam('paste_convert_middot_lists', true))
+					t._convertLists(pl, o);
+
+				// Process styles
+				styleProps = ed.getParam('paste_retain_style_properties'); // retained properties
+
+				// If string property then split it
+				if (tinymce.is(styleProps, 'string'))
+					styleProps = tinymce.explode(styleProps);
+
+				// Retains some style properties
+				each(dom.select('*', o.node), function(el) {
+					var newStyle = {}, npc = 0, i, sp, sv;
+
+					// Store a subset of the existing styles
+					if (styleProps) {
+						for (i = 0; i < styleProps.length; i++) {
+							sp = styleProps[i];
+							sv = dom.getStyle(el, sp);
+
+							if (sv) {
+								newStyle[sp] = sv;
+								npc++;
+							}
+						}
+					}
+
+					// Remove all of the existing styles
+					dom.setAttrib(el, 'style', '');
+
+					if (styleProps && npc > 0)
+						dom.setStyles(el, newStyle); // Add back the stored subset of styles
+					else // Remove empty span tags that do not have class attributes
+						if (el.nodeName == 'SPAN' && !el.className)
+							dom.remove(el, true);
+				});
+			}
+
+			// Remove all style information or only specifically on WebKit to avoid the style bug on that browser
+			if (ed.getParam("paste_remove_styles") || (ed.getParam("paste_remove_styles_if_webkit") && tinymce.isWebKit)) {
+				each(dom.select('*[style]', o.node), function(el) {
+					el.removeAttribute('style');
+					el.removeAttribute('mce_style');
+				});
+			} else {
+				if (tinymce.isWebKit) {
+					// We need to compress the styles on WebKit since if you paste <img border="0" /> it will become <img border="0" style="... lots of junk ..." />
+					// Removing the mce_style that contains the real value will force the Serializer engine to compress the styles
+					each(dom.select('*', o.node), function(el) {
+						el.removeAttribute('mce_style');
+					});
+				}
+			}
+		},
+
+		/**
+		 * Converts the most common bullet and number formats in Office into a real semantic UL/LI list.
+		 */
+		_convertLists : function(pl, o) {
+			var dom = pl.editor.dom, listElm, li, lastMargin = -1, margin, levels = [], lastType, html;
+
+			// Convert middot lists into real semantic lists
+			each(dom.select('p', o.node), function(p) {
+				var sib, val = '', type, html, idx, parents;
+
+				// Get text node value at beginning of paragraph
+				for (sib = p.firstChild; sib && sib.nodeType == 3; sib = sib.nextSibling)
+					val += sib.nodeValue;
+
+				val = p.innerHTML.replace(/<\/?\w+[^>]*>/gi, '').replace(/ /g, '\u00a0');
+
+				// Detect unordered lists look for bullets
+				if (/^(__MCE_ITEM__)+[\u2022\u00b7\u00a7\u00d8o]\s*\u00a0*/.test(val))
+					type = 'ul';
+
+				// Detect ordered lists 1., a. or ixv.
+				if (/^__MCE_ITEM__\s*\w+\.\s*\u00a0{2,}/.test(val))
+					type = 'ol';
+
+				// Check if node value matches the list pattern: o  
+				if (type) {
+					margin = parseFloat(p.style.marginLeft || 0);
+
+					if (margin > lastMargin)
+						levels.push(margin);
+
+					if (!listElm || type != lastType) {
+						listElm = dom.create(type);
+						dom.insertAfter(listElm, p);
+					} else {
+						// Nested list element
+						if (margin > lastMargin) {
+							listElm = li.appendChild(dom.create(type));
+						} else if (margin < lastMargin) {
+							// Find parent level based on margin value
+							idx = tinymce.inArray(levels, margin);
+							parents = dom.getParents(listElm.parentNode, type);
+							listElm = parents[parents.length - 1 - idx] || listElm;
+						}
+					}
+
+					// Remove middot or number spans if they exists
+					each(dom.select('span', p), function(span) {
+						var html = span.innerHTML.replace(/<\/?\w+[^>]*>/gi, '');
+
+						// Remove span with the middot or the number
+						if (type == 'ul' && /^[\u2022\u00b7\u00a7\u00d8o]/.test(html))
+							dom.remove(span);
+						else if (/^[\s\S]*\w+\.( |\u00a0)*\s*/.test(html))
+							dom.remove(span);
+					});
+
+					html = p.innerHTML;
+
+					// Remove middot/list items
+					if (type == 'ul')
+						html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^[\u2022\u00b7\u00a7\u00d8o]\s*( |\u00a0)+\s*/, '');
+					else
+						html = p.innerHTML.replace(/__MCE_ITEM__/g, '').replace(/^\s*\w+\.( |\u00a0)+\s*/, '');
+
+					// Create li and add paragraph data into the new li
+					li = listElm.appendChild(dom.create('li', 0, html));
+					dom.remove(p);
+
+					lastMargin = margin;
+					lastType = type;
+				} else
+					listElm = lastMargin = 0; // End list element
+			});
+
+			// Remove any left over makers
+			html = o.node.innerHTML;
+			if (html.indexOf('__MCE_ITEM__') != -1)
+				o.node.innerHTML = html.replace(/__MCE_ITEM__/g, '');
+		},
+
+		/**
+		 * This method will split the current block parent and insert the contents inside the split position.
+		 * This logic can be improved so text nodes at the start/end remain in the start/end block elements
+		 */
+		_insertBlockContent : function(ed, dom, content) {
+			var parentBlock, marker, sel = ed.selection, last, elm, vp, y, elmHeight;
+
+			function select(n) {
+				var r;
+
+				if (tinymce.isIE) {
+					r = ed.getDoc().body.createTextRange();
+					r.moveToElementText(n);
+					r.collapse(false);
+					r.select();
+				} else {
+					sel.select(n, 1);
+					sel.collapse(false);
+				}
+			};
+
+			// Insert a marker for the caret position
+			this._insert('<span id="_marker"> </span>', 1);
+			marker = dom.get('_marker');
+			parentBlock = dom.getParent(marker, 'p,h1,h2,h3,h4,h5,h6,ul,ol,th,td');
+
+			// If it's a parent block but not a table cell
+			if (parentBlock && !/TD|TH/.test(parentBlock.nodeName)) {
+				// Split parent block
+				marker = dom.split(parentBlock, marker);
+
+				// Insert nodes before the marker
+				each(dom.create('div', 0, content).childNodes, function(n) {
+					last = marker.parentNode.insertBefore(n.cloneNode(true), marker);
+				});
+
+				// Move caret after marker
+				select(last);
+			} else {
+				dom.setOuterHTML(marker, content);
+				sel.select(ed.getBody(), 1);
+				sel.collapse(0);
+			}
+
+			dom.remove('_marker'); // Remove marker if it's left
+
+			// Get element, position and height
+			elm = sel.getStart();
+			vp = dom.getViewPort(ed.getWin());
+			y = ed.dom.getPos(elm).y;
+			elmHeight = elm.clientHeight;
+
+			// Is element within viewport if not then scroll it into view
+			if (y < vp.y || y + elmHeight > vp.y + vp.h)
+				ed.getDoc().body.scrollTop = y < vp.y ? y : y - vp.h + 25;
+		},
+
+		/**
+		 * Inserts the specified contents at the caret position.
+		 */
+		_insert : function(h, skip_undo) {
+			var ed = this.editor;
+
+			// First delete the contents seems to work better on WebKit
+			if (!ed.selection.isCollapsed())
+				ed.getDoc().execCommand('Delete', false, null);
+
+			// It's better to use the insertHTML method on Gecko since it will combine paragraphs correctly before inserting the contents
+			ed.execCommand(tinymce.isGecko ? 'insertHTML' : 'mceInsertContent', false, h, {skip_undo : skip_undo});
+		},
+
+		/**
+		 * This method will open the old style paste dialogs. Some users might want the old behavior but still use the new cleanup engine.
+		 */
+		_legacySupport : function() {
+			var t = this, ed = t.editor;
+
+			// Register commands for backwards compatibility
+			each(['mcePasteText', 'mcePasteWord'], function(cmd) {
+				ed.addCommand(cmd, function() {
+					ed.windowManager.open({
+						file : t.url + (cmd == 'mcePasteText' ? '/pastetext.htm' : '/pasteword.htm'),
+						width : parseInt(ed.getParam("paste_dialog_width", "450")),
+						height : parseInt(ed.getParam("paste_dialog_height", "400")),
+						inline : 1
+					});
+				});
+			});
+
+			// Register buttons for backwards compatibility
+			ed.addButton('pastetext', {title : 'paste.paste_text_desc', cmd : 'mcePasteText'});
+			ed.addButton('pasteword', {title : 'paste.paste_word_desc', cmd : 'mcePasteWord'});
+			ed.addButton('selectall', {title : 'paste.selectall_desc', cmd : 'selectall'});
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('paste', tinymce.plugins.PastePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/paste/js/pastetext.js b/media/js/tinymce/plugins/paste/js/pastetext.js
new file mode 100644
index 0000000..c524f9e
--- /dev/null
+++ b/media/js/tinymce/plugins/paste/js/pastetext.js
@@ -0,0 +1,36 @@
+tinyMCEPopup.requireLangPack();
+
+var PasteTextDialog = {
+	init : function() {
+		this.resize();
+	},
+
+	insert : function() {
+		var h = tinyMCEPopup.dom.encode(document.getElementById('content').value), lines;
+
+		// Convert linebreaks into paragraphs
+		if (document.getElementById('linebreaks').checked) {
+			lines = h.split(/\r?\n/);
+			if (lines.length > 1) {
+				h = '';
+				tinymce.each(lines, function(row) {
+					h += '<p>' + row + '</p>';
+				});
+			}
+		}
+
+		tinyMCEPopup.editor.execCommand('mceInsertClipboardContent', false, {content : h});
+		tinyMCEPopup.close();
+	},
+
+	resize : function() {
+		var vp = tinyMCEPopup.dom.getViewPort(window), el;
+
+		el = document.getElementById('content');
+
+		el.style.width  = (vp.w - 20) + 'px';
+		el.style.height = (vp.h - 90) + 'px';
+	}
+};
+
+tinyMCEPopup.onInit.add(PasteTextDialog.init, PasteTextDialog);
diff --git a/media/js/tinymce/plugins/paste/js/pasteword.js b/media/js/tinymce/plugins/paste/js/pasteword.js
new file mode 100644
index 0000000..a52731c
--- /dev/null
+++ b/media/js/tinymce/plugins/paste/js/pasteword.js
@@ -0,0 +1,51 @@
+tinyMCEPopup.requireLangPack();
+
+var PasteWordDialog = {
+	init : function() {
+		var ed = tinyMCEPopup.editor, el = document.getElementById('iframecontainer'), ifr, doc, css, cssHTML = '';
+
+		// Create iframe
+		el.innerHTML = '<iframe id="iframe" src="javascript:\'\';" frameBorder="0" style="border: 1px solid gray"></iframe>';
+		ifr = document.getElementById('iframe');
+		doc = ifr.contentWindow.document;
+
+		// Force absolute CSS urls
+		css = [ed.baseURI.toAbsolute("themes/" + ed.settings.theme + "/skins/" + ed.settings.skin + "/content.css")];
+		css = css.concat(tinymce.explode(ed.settings.content_css) || []);
+		tinymce.each(css, function(u) {
+			cssHTML += '<link href="' + ed.documentBaseURI.toAbsolute('' + u) + '" rel="stylesheet" type="text/css" />';
+		});
+
+		// Write content into iframe
+		doc.open();
+		doc.write('<html><head>' + cssHTML + '</head><body class="mceContentBody" spellcheck="false"></body></html>');
+		doc.close();
+
+		doc.designMode = 'on';
+		this.resize();
+
+		window.setTimeout(function() {
+			ifr.contentWindow.focus();
+		}, 10);
+	},
+
+	insert : function() {
+		var h = document.getElementById('iframe').contentWindow.document.body.innerHTML;
+
+		tinyMCEPopup.editor.execCommand('mceInsertClipboardContent', false, {content : h, wordContent : true});
+		tinyMCEPopup.close();
+	},
+
+	resize : function() {
+		var vp = tinyMCEPopup.dom.getViewPort(window), el;
+
+		el = document.getElementById('iframe');
+
+		if (el) {
+			el.style.width  = (vp.w - 20) + 'px';
+			el.style.height = (vp.h - 90) + 'px';
+		}
+	}
+};
+
+tinyMCEPopup.onInit.add(PasteWordDialog.init, PasteWordDialog);
diff --git a/media/js/tinymce/plugins/paste/langs/en_dlg.js b/media/js/tinymce/plugins/paste/langs/en_dlg.js
new file mode 100644
index 0000000..eeac778
--- /dev/null
+++ b/media/js/tinymce/plugins/paste/langs/en_dlg.js
@@ -0,0 +1,5 @@
+tinyMCE.addI18n('en.paste_dlg',{
+text_title:"Use CTRL+V on your keyboard to paste the text into the window.",
+text_linebreaks:"Keep linebreaks",
+word_title:"Use CTRL+V on your keyboard to paste the text into the window."
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/paste/pastetext.htm b/media/js/tinymce/plugins/paste/pastetext.htm
new file mode 100644
index 0000000..42c3d9c
--- /dev/null
+++ b/media/js/tinymce/plugins/paste/pastetext.htm
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#paste.paste_text_desc}</title>
+	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/pastetext.js"></script>
+</head>
+<body onresize="PasteTextDialog.resize();" style="display:none; overflow:hidden;">
+	<form name="source" onsubmit="return PasteTextDialog.insert();" action="#">
+		<div style="float: left" class="title">{#paste.paste_text_desc}</div>
+
+		<div style="float: right">
+			<input type="checkbox" name="linebreaks" id="linebreaks" class="wordWrapCode" checked="checked" /><label for="linebreaks">{#paste_dlg.text_linebreaks}</label>
+		</div>
+
+		<br style="clear: both" />
+
+		<div>{#paste_dlg.text_title}</div>
+
+		<textarea id="content" name="content" rows="15" cols="100" style="width: 100%; height: 100%; font-family: 'Courier New',Courier,mono; font-size: 12px;" dir="ltr" wrap="soft" class="mceFocus"></textarea>
+
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" name="insert" value="{#insert}" id="insert" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" id="cancel" />
+			</div>
+		</div>
+	</form>
+</body> 
+</html>
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/paste/pasteword.htm b/media/js/tinymce/plugins/paste/pasteword.htm
new file mode 100644
index 0000000..f4a9b3d
--- /dev/null
+++ b/media/js/tinymce/plugins/paste/pasteword.htm
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+	<title>{#paste.paste_word_desc}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/pasteword.js"></script>
+</head>
+<body onresize="PasteWordDialog.resize();" style="display:none; overflow:hidden;">
+	<form name="source" onsubmit="return PasteWordDialog.insert();" action="#">
+		<div class="title">{#paste.paste_word_desc}</div>
+
+		<div>{#paste_dlg.word_title}</div>
+
+		<div id="iframecontainer"></div>
+
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" id="insert" name="insert" value="{#insert}" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+	</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/preview/editor_plugin.js b/media/js/tinymce/plugins/preview/editor_plugin.js
new file mode 100644
index 0000000..507909c
--- /dev/null
+++ b/media/js/tinymce/plugins/preview/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.Preview",{init:function(a,b){var d=this,c=tinymce.explode(a.settings.content_css);d.editor=a;tinymce.each(c,function(f,e){c[e]=a.documentBaseURI.toAbsolute(f)});a.addCommand("mcePreview",function(){a.windowManager.open({file:a.getParam("plugin_preview_pageurl",b+"/preview.html"),width:parseInt(a.getParam("plugin_preview_width","550")),height:parseInt(a.getParam("plugin_preview_height","600")),resizable:"yes",scrollbars:"yes",popup_css:c?c.join( [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/preview/editor_plugin_src.js b/media/js/tinymce/plugins/preview/editor_plugin_src.js
new file mode 100644
index 0000000..0582ab8
--- /dev/null
+++ b/media/js/tinymce/plugins/preview/editor_plugin_src.js
@@ -0,0 +1,50 @@
+/**
+ * $Id: editor_plugin_src.js 1056 2009-03-13 12:47:03Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.Preview', {
+		init : function(ed, url) {
+			var t = this, css = tinymce.explode(ed.settings.content_css);
+
+			t.editor = ed;
+
+			// Force absolute CSS urls	
+			tinymce.each(css, function(u, k) {
+				css[k] = ed.documentBaseURI.toAbsolute(u);
+			});
+
+			ed.addCommand('mcePreview', function() {
+				ed.windowManager.open({
+					file : ed.getParam("plugin_preview_pageurl", url + "/preview.html"),
+					width : parseInt(ed.getParam("plugin_preview_width", "550")),
+					height : parseInt(ed.getParam("plugin_preview_height", "600")),
+					resizable : "yes",
+					scrollbars : "yes",
+					popup_css : css ? css.join(',') : ed.baseURI.toAbsolute("themes/" + ed.settings.theme + "/skins/" + ed.settings.skin + "/content.css"),
+					inline : ed.getParam("plugin_preview_inline", 1)
+				}, {
+					base : ed.documentBaseURI.getURI()
+				});
+			});
+
+			ed.addButton('preview', {title : 'preview.preview_desc', cmd : 'mcePreview'});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Preview',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/preview',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('preview', tinymce.plugins.Preview);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/preview/example.html b/media/js/tinymce/plugins/preview/example.html
new file mode 100644
index 0000000..b2c3d90
--- /dev/null
+++ b/media/js/tinymce/plugins/preview/example.html
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script language="javascript" src="../../tiny_mce_popup.js"></script>
+<script type="text/javascript" src="jscripts/embed.js"></script>
+<script type="text/javascript">
+tinyMCEPopup.onInit.add(function(ed) {
+	var dom = tinyMCEPopup.dom;
+
+	// Load editor content_css
+	tinymce.each(ed.settings.content_css.split(','), function(u) {
+		dom.loadCSS(ed.documentBaseURI.toAbsolute(u));
+	});
+
+	// Place contents inside div container
+	dom.setHTML('content', ed.getContent());
+});
+</script>
+<title>Example of a custom preview page</title>
+</head>
+<body>
+
+Editor contents: <br />
+<div id="content">
+<!-- Gets filled with editor contents -->
+</div>
+
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/preview/jscripts/embed.js b/media/js/tinymce/plugins/preview/jscripts/embed.js
new file mode 100644
index 0000000..f8dc810
--- /dev/null
+++ b/media/js/tinymce/plugins/preview/jscripts/embed.js
@@ -0,0 +1,73 @@
+/**
+ * This script contains embed functions for common plugins. This scripts are complety free to use for any purpose.
+ */
+
+function writeFlash(p) {
+	writeEmbed(
+		'D27CDB6E-AE6D-11cf-96B8-444553540000',
+		'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0',
+		'application/x-shockwave-flash',
+		p
+	);
+}
+
+function writeShockWave(p) {
+	writeEmbed(
+	'166B1BCA-3F9C-11CF-8075-444553540000',
+	'http://download.macromedia.com/pub/shockwave/cabs/director/sw.cab#version=8,5,1,0',
+	'application/x-director',
+		p
+	);
+}
+
+function writeQuickTime(p) {
+	writeEmbed(
+		'02BF25D5-8C17-4B23-BC80-D3488ABDDC6B',
+		'http://www.apple.com/qtactivex/qtplugin.cab#version=6,0,2,0',
+		'video/quicktime',
+		p
+	);
+}
+
+function writeRealMedia(p) {
+	writeEmbed(
+		'CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA',
+		'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0',
+		'audio/x-pn-realaudio-plugin',
+		p
+	);
+}
+
+function writeWindowsMedia(p) {
+	p.url = p.src;
+	writeEmbed(
+		'6BF52A52-394A-11D3-B153-00C04F79FAA6',
+		'http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701',
+		'application/x-mplayer2',
+		p
+	);
+}
+
+function writeEmbed(cls, cb, mt, p) {
+	var h = '', n;
+
+	h += '<object classid="clsid:' + cls + '" codebase="' + cb + '"';
+	h += typeof(p.id) != "undefined" ? 'id="' + p.id + '"' : '';
+	h += typeof(p.name) != "undefined" ? 'name="' + p.name + '"' : '';
+	h += typeof(p.width) != "undefined" ? 'width="' + p.width + '"' : '';
+	h += typeof(p.height) != "undefined" ? 'height="' + p.height + '"' : '';
+	h += typeof(p.align) != "undefined" ? 'align="' + p.align + '"' : '';
+	h += '>';
+
+	for (n in p)
+		h += '<param name="' + n + '" value="' + p[n] + '">';
+
+	h += '<embed type="' + mt + '"';
+
+	for (n in p)
+		h += n + '="' + p[n] + '" ';
+
+	h += '></embed></object>';
+
+	document.write(h);
+}
diff --git a/media/js/tinymce/plugins/preview/preview.html b/media/js/tinymce/plugins/preview/preview.html
new file mode 100644
index 0000000..67e7b14
--- /dev/null
+++ b/media/js/tinymce/plugins/preview/preview.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+<script type="text/javascript" src="jscripts/embed.js"></script>
+<script type="text/javascript"><!--
+document.write('<base href="' + tinyMCEPopup.getWindowArg("base") + '">');
+// -->
+</script>
+<title>{#preview.preview_desc}</title>
+</head>
+<body id="content">
+<script type="text/javascript">
+	document.write(tinyMCEPopup.editor.getContent());
+</script>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/print/editor_plugin.js b/media/js/tinymce/plugins/print/editor_plugin.js
new file mode 100644
index 0000000..b5b3a55
--- /dev/null
+++ b/media/js/tinymce/plugins/print/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.Print",{init:function(a,b){a.addCommand("mcePrint",function(){a.getWin().print()});a.addButton("print",{title:"print.print_desc",cmd:"mcePrint"})},getInfo:function(){return{longname:"Print",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/print",version:tinymce.majorVersion+"."+tinymce.minorVersion}}});tinymce.PluginManager.add("print",tinymce.plugins.Print)})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/print/editor_plugin_src.js b/media/js/tinymce/plugins/print/editor_plugin_src.js
new file mode 100644
index 0000000..51fe156
--- /dev/null
+++ b/media/js/tinymce/plugins/print/editor_plugin_src.js
@@ -0,0 +1,31 @@
+/**
+ * $Id: editor_plugin_src.js 520 2008-01-07 16:30:32Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.Print', {
+		init : function(ed, url) {
+			ed.addCommand('mcePrint', function() {
+				ed.getWin().print();
+			});
+
+			ed.addButton('print', {title : 'print.print_desc', cmd : 'mcePrint'});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Print',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/print',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('print', tinymce.plugins.Print);
+})();
diff --git a/media/js/tinymce/plugins/safari/blank.htm b/media/js/tinymce/plugins/safari/blank.htm
new file mode 100644
index 0000000..266808c
--- /dev/null
+++ b/media/js/tinymce/plugins/safari/blank.htm
@@ -0,0 +1 @@
+<!-- WebKit -->
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/safari/editor_plugin.js b/media/js/tinymce/plugins/safari/editor_plugin.js
new file mode 100644
index 0000000..794477c
--- /dev/null
+++ b/media/js/tinymce/plugins/safari/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.dom.Event,c=tinymce.grep,d=tinymce.each,b=tinymce.inArray;function e(j,i,h){var g,k;g=j.createTreeWalker(i,NodeFilter.SHOW_ALL,null,false);while(k=g.nextNode()){if(h){if(!h(k)){return false}}if(k.nodeType==3&&k.nodeValue&&/[^\s\u00a0]+/.test(k.nodeValue)){return false}if(k.nodeType==1&&/^(HR|IMG|TABLE)$/.test(k.nodeName)){return false}}return true}tinymce.create("tinymce.plugins.Safari",{init:function(f){var g=this,h;if(!tinymce.isWebKit){return}g.editor=f;g.web [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/safari/editor_plugin_src.js b/media/js/tinymce/plugins/safari/editor_plugin_src.js
new file mode 100644
index 0000000..6667b7c
--- /dev/null
+++ b/media/js/tinymce/plugins/safari/editor_plugin_src.js
@@ -0,0 +1,438 @@
+/**
+ * $Id: editor_plugin_src.js 264 2007-04-26 20:53:09Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var Event = tinymce.dom.Event, grep = tinymce.grep, each = tinymce.each, inArray = tinymce.inArray;
+
+	function isEmpty(d, e, f) {
+		var w, n;
+
+		w = d.createTreeWalker(e, NodeFilter.SHOW_ALL, null, false);
+		while (n = w.nextNode()) {
+			// Filter func
+			if (f) {
+				if (!f(n))
+					return false;
+			}
+
+			// Non whitespace text node
+			if (n.nodeType == 3 && n.nodeValue && /[^\s\u00a0]+/.test(n.nodeValue))
+				return false;
+
+			// Is non text element byt still content
+			if (n.nodeType == 1 && /^(HR|IMG|TABLE)$/.test(n.nodeName))
+				return false;
+		}
+
+		return true;
+	};
+
+	tinymce.create('tinymce.plugins.Safari', {
+		init : function(ed) {
+			var t = this, dom;
+
+			// Ignore on non webkit
+			if (!tinymce.isWebKit)
+				return;
+
+			t.editor = ed;
+			t.webKitFontSizes = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', '-webkit-xxx-large'];
+			t.namedFontSizes = ['xx-small', 'x-small','small','medium','large','x-large', 'xx-large'];
+
+			// Safari CreateLink command will not work correctly on images that is aligned
+			ed.addCommand('CreateLink', function(u, v) {
+				var n = ed.selection.getNode(), dom = ed.dom, a;
+
+				if (n && (/^(left|right)$/i.test(dom.getStyle(n, 'float', 1)) || /^(left|right)$/i.test(dom.getAttrib(n, 'align')))) {
+					a = dom.create('a', {href : v}, n.cloneNode());
+					n.parentNode.replaceChild(a, n);
+					ed.selection.select(a);
+				} else
+					ed.getDoc().execCommand("CreateLink", false, v);
+			});
+
+/*
+			// WebKit generates spans out of thin air this patch used to remove them but it will also remove styles we want so it's disabled for now
+			ed.onPaste.add(function(ed, e) {
+				function removeStyles(e) {
+					e = e.target;
+
+					if (e.nodeType == 1) {
+						e.style.cssText = '';
+
+						each(ed.dom.select('*', e), function(e) {
+							e.style.cssText = '';
+						});
+					}
+				};
+
+				Event.add(ed.getDoc(), 'DOMNodeInserted', removeStyles);
+
+				window.setTimeout(function() {
+					Event.remove(ed.getDoc(), 'DOMNodeInserted', removeStyles);
+				}, 0);
+			});
+*/
+			ed.onKeyUp.add(function(ed, e) {
+				var h, b, r, n, s;
+
+				// If backspace or delete key
+				if (e.keyCode == 46 || e.keyCode == 8) {
+					b = ed.getBody();
+					h = b.innerHTML;
+					s = ed.selection;
+
+					// If there is no text content or images or hr elements then remove everything
+					if (b.childNodes.length == 1 && !/<(img|hr)/.test(h) && tinymce.trim(h.replace(/<[^>]+>/g, '')).length == 0) {
+						// Inject paragrah and bogus br
+						ed.setContent('<p><br mce_bogus="1" /></p>', {format : 'raw'});
+
+						// Move caret before bogus br
+						n = b.firstChild;
+						r = s.getRng();
+						r.setStart(n, 0);
+						r.setEnd(n, 0);
+						s.setRng(r);
+					}
+				}
+			});
+
+			// Workaround for FormatBlock bug, http://bugs.webkit.org/show_bug.cgi?id=16004
+			ed.addCommand('FormatBlock', function(u, v) {
+				var dom = ed.dom, e = dom.getParent(ed.selection.getNode(), dom.isBlock);
+
+				if (e)
+					dom.replace(dom.create(v), e, 1);
+				else
+					ed.getDoc().execCommand("FormatBlock", false, v);
+			});
+
+			// Workaround for InsertHTML bug, http://bugs.webkit.org/show_bug.cgi?id=16382
+			ed.addCommand('mceInsertContent', function(u, v) {
+				ed.getDoc().execCommand("InsertText", false, 'mce_marker');
+				ed.getBody().innerHTML = ed.getBody().innerHTML.replace(/mce_marker/g, ed.dom.processHTML(v) + '<span id="_mce_tmp">XX</span>');
+				ed.selection.select(ed.dom.get('_mce_tmp'));
+				ed.getDoc().execCommand("Delete", false, ' ');
+			});
+	
+	/*		ed.onKeyDown.add(function(ed, e) {
+				// Ctrl+A select all will fail on WebKit since if you paste the contents you selected it will produce a odd div wrapper
+				if ((e.ctrlKey || e.metaKey) && e.keyCode == 65) {
+					ed.selection.select(ed.getBody(), 1);
+					return Event.cancel(e);
+				}
+			});*/
+
+			ed.onKeyPress.add(function(ed, e) {
+				var se, li, lic, r1, r2, n, sel, doc, be, af, pa;
+
+				if (e.keyCode == 13) {
+					sel = ed.selection;
+					se = sel.getNode();
+
+					// Workaround for missing shift+enter support, http://bugs.webkit.org/show_bug.cgi?id=16973
+					if (e.shiftKey || ed.settings.force_br_newlines && se.nodeName != 'LI') {
+						t._insertBR(ed);
+						Event.cancel(e);
+					}
+
+					// Workaround for DIV elements produced by Safari
+					if (li = dom.getParent(se, 'LI')) {
+						lic = dom.getParent(li, 'OL,UL');
+						doc = ed.getDoc();
+
+						pa = dom.create('p');
+						dom.add(pa, 'br', {mce_bogus : "1"});
+
+						if (isEmpty(doc, li)) {
+							// If list in list then use browser default behavior
+							if (n = dom.getParent(lic.parentNode, 'LI,OL,UL'))
+								return;
+
+							n = dom.getParent(lic, 'p,h1,h2,h3,h4,h5,h6,div') || lic;
+
+							// Create range from the start of block element to the list item
+							r1 = doc.createRange();
+							r1.setStartBefore(n);
+							r1.setEndBefore(li);
+
+							// Create range after the list to the end of block element
+							r2 = doc.createRange();
+							r2.setStartAfter(li);
+							r2.setEndAfter(n);
+
+							be = r1.cloneContents();
+							af = r2.cloneContents();
+
+							if (!isEmpty(doc, af))
+								dom.insertAfter(af, n);
+
+							dom.insertAfter(pa, n);
+
+							if (!isEmpty(doc, be))
+								dom.insertAfter(be, n);
+
+							dom.remove(n);
+
+							n = pa.firstChild;
+							r1 = doc.createRange();
+							r1.setStartBefore(n);
+							r1.setEndBefore(n);
+							sel.setRng(r1);
+
+							return Event.cancel(e);
+						}
+					}
+				}
+			});
+
+			// Safari doesn't place lists outside block elements
+			ed.onExecCommand.add(function(ed, cmd) {
+				var sel, dom, bl, bm;
+
+				if (cmd == 'InsertUnorderedList' || cmd == 'InsertOrderedList') {
+					sel = ed.selection;
+					dom = ed.dom;
+
+					if (bl = dom.getParent(sel.getNode(), function(n) {return /^(H[1-6]|P|ADDRESS|PRE)$/.test(n.nodeName);})) {
+						bm = sel.getBookmark();
+						dom.remove(bl, 1);
+						sel.moveToBookmark(bm);
+					}
+				}
+			});
+
+			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
+			ed.onClick.add(function(ed, e) {
+				e = e.target;
+
+				if (e.nodeName == 'IMG') {
+					t.selElm = e;
+					ed.selection.select(e);
+				} else
+					t.selElm = null;
+			});
+
+			ed.onInit.add(function() {
+				t._fixWebKitSpans();
+			});
+
+			ed.onSetContent.add(function() {
+				dom = ed.dom;
+
+				// Convert strong,b,em,u,strike to spans
+				each(['strong','b','em','u','strike','sub','sup','a'], function(v) {
+					each(grep(dom.select(v)).reverse(), function(n) {
+						var nn = n.nodeName.toLowerCase(), st;
+
+						// Convert anchors into images
+						if (nn == 'a') {
+							if (n.name)
+								dom.replace(dom.create('img', {mce_name : 'a', name : n.name, 'class' : 'mceItemAnchor'}), n);
+
+							return;
+						}
+
+						switch (nn) {
+							case 'b':
+							case 'strong':
+								if (nn == 'b')
+									nn = 'strong';
+
+								st = 'font-weight: bold;';
+								break;
+
+							case 'em':
+								st = 'font-style: italic;';
+								break;
+
+							case 'u':
+								st = 'text-decoration: underline;';
+								break;
+
+							case 'sub':
+								st = 'vertical-align: sub;';
+								break;
+
+							case 'sup':
+								st = 'vertical-align: super;';
+								break;
+
+							case 'strike':
+								st = 'text-decoration: line-through;';
+								break;
+						}
+
+						dom.replace(dom.create('span', {mce_name : nn, style : st, 'class' : 'Apple-style-span'}), n, 1);
+					});
+				});
+			});
+
+			ed.onPreProcess.add(function(ed, o) {
+				dom = ed.dom;
+
+				each(grep(o.node.getElementsByTagName('span')).reverse(), function(n) {
+					var v, bg;
+
+					if (o.get) {
+						if (dom.hasClass(n, 'Apple-style-span')) {
+							bg = n.style.backgroundColor;
+
+							switch (dom.getAttrib(n, 'mce_name')) {
+								case 'font':
+									if (!ed.settings.convert_fonts_to_spans)
+										dom.setAttrib(n, 'style', '');
+									break;
+
+								case 'strong':
+								case 'em':
+								case 'sub':
+								case 'sup':
+									dom.setAttrib(n, 'style', '');
+									break;
+
+								case 'strike':
+								case 'u':
+									if (!ed.settings.inline_styles)
+										dom.setAttrib(n, 'style', '');
+									else
+										dom.setAttrib(n, 'mce_name', '');
+
+									break;
+
+								default:
+									if (!ed.settings.inline_styles)
+										dom.setAttrib(n, 'style', '');
+							}
+
+
+							if (bg)
+								n.style.backgroundColor = bg;
+						}
+					}
+
+					if (dom.hasClass(n, 'mceItemRemoved'))
+						dom.remove(n, 1);
+				});
+			});
+
+			ed.onPostProcess.add(function(ed, o) {
+				// Safari adds BR at end of all block elements
+				o.content = o.content.replace(/<br \/><\/(h[1-6]|div|p|address|pre)>/g, '</$1>');
+
+				// Safari adds id="undefined" to HR elements
+				o.content = o.content.replace(/ id=\"undefined\"/g, '');
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Safari compatibility',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/safari',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Internal methods
+
+		_fixWebKitSpans : function() {
+			var t = this, ed = t.editor;
+
+			// Use mutator events on new WebKit
+			Event.add(ed.getDoc(), 'DOMNodeInserted', function(e) {
+				e = e.target;
+
+				if (e && e.nodeType == 1)
+					t._fixAppleSpan(e);
+			});
+		},
+
+		_fixAppleSpan : function(e) {
+			var ed = this.editor, dom = ed.dom, fz = this.webKitFontSizes, fzn = this.namedFontSizes, s = ed.settings, st, p;
+
+			if (dom.getAttrib(e, 'mce_fixed'))
+				return;
+
+			// Handle Apple style spans
+			if (e.nodeName == 'SPAN' && e.className == 'Apple-style-span') {
+				st = e.style;
+
+				if (!s.convert_fonts_to_spans) {
+					if (st.fontSize) {
+						dom.setAttrib(e, 'mce_name', 'font');
+						dom.setAttrib(e, 'size', inArray(fz, st.fontSize) + 1);
+					}
+
+					if (st.fontFamily) {
+						dom.setAttrib(e, 'mce_name', 'font');
+						dom.setAttrib(e, 'face', st.fontFamily);
+					}
+
+					if (st.color) {
+						dom.setAttrib(e, 'mce_name', 'font');
+						dom.setAttrib(e, 'color', dom.toHex(st.color));
+					}
+
+					if (st.backgroundColor) {
+						dom.setAttrib(e, 'mce_name', 'font');
+						dom.setStyle(e, 'background-color', st.backgroundColor);
+					}
+				} else {
+					if (st.fontSize)
+						dom.setStyle(e, 'fontSize', fzn[inArray(fz, st.fontSize)]);
+				}
+
+				if (st.fontWeight == 'bold')
+					dom.setAttrib(e, 'mce_name', 'strong');
+
+				if (st.fontStyle == 'italic')
+					dom.setAttrib(e, 'mce_name', 'em');
+
+				if (st.textDecoration == 'underline')
+					dom.setAttrib(e, 'mce_name', 'u');
+
+				if (st.textDecoration == 'line-through')
+					dom.setAttrib(e, 'mce_name', 'strike');
+
+				if (st.verticalAlign == 'super')
+					dom.setAttrib(e, 'mce_name', 'sup');
+
+				if (st.verticalAlign == 'sub')
+					dom.setAttrib(e, 'mce_name', 'sub');
+
+				dom.setAttrib(e, 'mce_fixed', '1');
+			}
+		},
+
+		_insertBR : function(ed) {
+			var dom = ed.dom, s = ed.selection, r = s.getRng(), br;
+
+			// Insert BR element
+			r.insertNode(br = dom.create('br'));
+
+			// Place caret after BR
+			r.setStartAfter(br);
+			r.setEndAfter(br);
+			s.setRng(r);
+
+			// Could not place caret after BR then insert an nbsp entity and move the caret
+			if (s.getSel().focusNode == br.previousSibling) {
+				s.select(dom.insertAfter(dom.doc.createTextNode('\u00a0'), br));
+				s.collapse(1);
+			}
+
+			// Scroll to new position, scrollIntoView can't be used due to bug: http://bugs.webkit.org/show_bug.cgi?id=16117
+			ed.getWin().scrollTo(0, dom.getPos(s.getRng().startContainer).y);
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('safari', tinymce.plugins.Safari);
+})();
+
diff --git a/media/js/tinymce/plugins/save/editor_plugin.js b/media/js/tinymce/plugins/save/editor_plugin.js
new file mode 100644
index 0000000..8e93996
--- /dev/null
+++ b/media/js/tinymce/plugins/save/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.Save",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceSave",c._save,c);a.addCommand("mceCancel",c._cancel,c);a.addButton("save",{title:"save.save_desc",cmd:"mceSave"});a.addButton("cancel",{title:"save.cancel_desc",cmd:"mceCancel"});a.onNodeChange.add(c._nodeChange,c);a.addShortcut("ctrl+s",a.getLang("save.save_desc"),"mceSave")},getInfo:function(){return{longname:"Save",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxieco [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/save/editor_plugin_src.js b/media/js/tinymce/plugins/save/editor_plugin_src.js
new file mode 100644
index 0000000..b38be4d
--- /dev/null
+++ b/media/js/tinymce/plugins/save/editor_plugin_src.js
@@ -0,0 +1,98 @@
+/**
+ * $Id: editor_plugin_src.js 851 2008-05-26 15:38:49Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.Save', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceSave', t._save, t);
+			ed.addCommand('mceCancel', t._cancel, t);
+
+			// Register buttons
+			ed.addButton('save', {title : 'save.save_desc', cmd : 'mceSave'});
+			ed.addButton('cancel', {title : 'save.cancel_desc', cmd : 'mceCancel'});
+
+			ed.onNodeChange.add(t._nodeChange, t);
+			ed.addShortcut('ctrl+s', ed.getLang('save.save_desc'), 'mceSave');
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Save',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/save',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private methods
+
+		_nodeChange : function(ed, cm, n) {
+			var ed = this.editor;
+
+			if (ed.getParam('save_enablewhendirty')) {
+				cm.setDisabled('save', !ed.isDirty());
+				cm.setDisabled('cancel', !ed.isDirty());
+			}
+		},
+
+		// Private methods
+
+		_save : function() {
+			var ed = this.editor, formObj, os, i, elementId;
+
+			formObj = tinymce.DOM.get(ed.id).form || tinymce.DOM.getParent(ed.id, 'form');
+
+			if (ed.getParam("save_enablewhendirty") && !ed.isDirty())
+				return;
+
+			tinyMCE.triggerSave();
+
+			// Use callback instead
+			if (os = ed.getParam("save_onsavecallback")) {
+				if (ed.execCallback('save_onsavecallback', ed)) {
+					ed.startContent = tinymce.trim(ed.getContent({format : 'raw'}));
+					ed.nodeChanged();
+				}
+
+				return;
+			}
+
+			if (formObj) {
+				ed.isNotDirty = true;
+
+				if (formObj.onsubmit == null || formObj.onsubmit() != false)
+					formObj.submit();
+
+				ed.nodeChanged();
+			} else
+				ed.windowManager.alert("Error: No form element found.");
+		},
+
+		_cancel : function() {
+			var ed = this.editor, os, h = tinymce.trim(ed.startContent);
+
+			// Use callback instead
+			if (os = ed.getParam("save_oncancelcallback")) {
+				ed.execCallback('save_oncancelcallback', ed);
+				return;
+			}
+
+			ed.setContent(h);
+			ed.undoManager.clear();
+			ed.nodeChanged();
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('save', tinymce.plugins.Save);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/searchreplace/css/searchreplace.css b/media/js/tinymce/plugins/searchreplace/css/searchreplace.css
new file mode 100644
index 0000000..ecdf58c
--- /dev/null
+++ b/media/js/tinymce/plugins/searchreplace/css/searchreplace.css
@@ -0,0 +1,6 @@
+.panel_wrapper {height:85px;}
+.panel_wrapper div.current {height:85px;}
+
+/* IE */
+* html .panel_wrapper {height:100px;}
+* html .panel_wrapper div.current {height:100px;}
diff --git a/media/js/tinymce/plugins/searchreplace/editor_plugin.js b/media/js/tinymce/plugins/searchreplace/editor_plugin.js
new file mode 100644
index 0000000..c3f8358
--- /dev/null
+++ b/media/js/tinymce/plugins/searchreplace/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.SearchReplacePlugin",{init:function(a,c){function b(d){a.windowManager.open({file:c+"/searchreplace.htm",width:420+parseInt(a.getLang("searchreplace.delta_width",0)),height:160+parseInt(a.getLang("searchreplace.delta_height",0)),inline:1,auto_focus:0},{mode:d,search_string:a.selection.getContent({format:"text"}),plugin_url:c})}a.addCommand("mceSearch",function(){b("search")});a.addCommand("mceReplace",function(){b("replace")});a.addButton("sear [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/searchreplace/editor_plugin_src.js b/media/js/tinymce/plugins/searchreplace/editor_plugin_src.js
new file mode 100644
index 0000000..59edc3b
--- /dev/null
+++ b/media/js/tinymce/plugins/searchreplace/editor_plugin_src.js
@@ -0,0 +1,54 @@
+/**
+ * $Id: editor_plugin_src.js 686 2008-03-09 18:13:49Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.SearchReplacePlugin', {
+		init : function(ed, url) {
+			function open(m) {
+				ed.windowManager.open({
+					file : url + '/searchreplace.htm',
+					width : 420 + parseInt(ed.getLang('searchreplace.delta_width', 0)),
+					height : 160 + parseInt(ed.getLang('searchreplace.delta_height', 0)),
+					inline : 1,
+					auto_focus : 0
+				}, {
+					mode : m,
+					search_string : ed.selection.getContent({format : 'text'}),
+					plugin_url : url
+				});
+			};
+
+			// Register commands
+			ed.addCommand('mceSearch', function() {
+				open('search');
+			});
+
+			ed.addCommand('mceReplace', function() {
+				open('replace');
+			});
+
+			// Register buttons
+			ed.addButton('search', {title : 'searchreplace.search_desc', cmd : 'mceSearch'});
+			ed.addButton('replace', {title : 'searchreplace.replace_desc', cmd : 'mceReplace'});
+
+			ed.addShortcut('ctrl+f', 'searchreplace.search_desc', 'mceSearch');
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Search/Replace',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('searchreplace', tinymce.plugins.SearchReplacePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/searchreplace/js/searchreplace.js b/media/js/tinymce/plugins/searchreplace/js/searchreplace.js
new file mode 100644
index 0000000..a8585cc
--- /dev/null
+++ b/media/js/tinymce/plugins/searchreplace/js/searchreplace.js
@@ -0,0 +1,126 @@
+tinyMCEPopup.requireLangPack();
+
+var SearchReplaceDialog = {
+	init : function(ed) {
+		var f = document.forms[0], m = tinyMCEPopup.getWindowArg("mode");
+
+		this.switchMode(m);
+
+		f[m + '_panel_searchstring'].value = tinyMCEPopup.getWindowArg("search_string");
+
+		// Focus input field
+		f[m + '_panel_searchstring'].focus();
+	},
+
+	switchMode : function(m) {
+		var f, lm = this.lastMode;
+
+		if (lm != m) {
+			f = document.forms[0];
+
+			if (lm) {
+				f[m + '_panel_searchstring'].value = f[lm + '_panel_searchstring'].value;
+				f[m + '_panel_backwardsu'].checked = f[lm + '_panel_backwardsu'].checked;
+				f[m + '_panel_backwardsd'].checked = f[lm + '_panel_backwardsd'].checked;
+				f[m + '_panel_casesensitivebox'].checked = f[lm + '_panel_casesensitivebox'].checked;
+			}
+
+			mcTabs.displayTab(m + '_tab',  m + '_panel');
+			document.getElementById("replaceBtn").style.display = (m == "replace") ? "inline" : "none";
+			document.getElementById("replaceAllBtn").style.display = (m == "replace") ? "inline" : "none";
+			this.lastMode = m;
+		}
+	},
+
+	searchNext : function(a) {
+		var ed = tinyMCEPopup.editor, se = ed.selection, r = se.getRng(), f, m = this.lastMode, s, b, fl = 0, w = ed.getWin(), wm = ed.windowManager, fo = 0;
+
+		// Get input
+		f = document.forms[0];
+		s = f[m + '_panel_searchstring'].value;
+		b = f[m + '_panel_backwardsu'].checked;
+		ca = f[m + '_panel_casesensitivebox'].checked;
+		rs = f['replace_panel_replacestring'].value;
+
+		if (s == '')
+			return;
+
+		function fix() {
+			// Correct Firefox graphics glitches
+			r = se.getRng().cloneRange();
+			ed.getDoc().execCommand('SelectAll', false, null);
+			se.setRng(r);
+		};
+
+		function replace() {
+			if (tinymce.isIE)
+				ed.selection.getRng().duplicate().pasteHTML(rs); // Needs to be duplicated due to selection bug in IE
+			else
+				ed.getDoc().execCommand('InsertHTML', false, rs);
+		};
+
+		// IE flags
+		if (ca)
+			fl = fl | 4;
+
+		switch (a) {
+			case 'all':
+				// Move caret to beginning of text
+				ed.execCommand('SelectAll');
+				ed.selection.collapse(true);
+
+				if (tinymce.isIE) {
+					while (r.findText(s, b ? -1 : 1, fl)) {
+						r.scrollIntoView();
+						r.select();
+						replace();
+						fo = 1;
+					}
+
+					tinyMCEPopup.storeSelection();
+				} else {
+					while (w.find(s, ca, b, false, false, false, false)) {
+						replace();
+						fo = 1;
+					}
+				}
+
+				if (fo)
+					tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.allreplaced'));
+				else
+					tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound'));
+
+				return;
+
+			case 'current':
+				if (!ed.selection.isCollapsed())
+					replace();
+
+				break;
+		}
+
+		se.collapse(b);
+		r = se.getRng();
+
+		// Whats the point
+		if (!s)
+			return;
+
+		if (tinymce.isIE) {
+			if (r.findText(s, b ? -1 : 1, fl)) {
+				r.scrollIntoView();
+				r.select();
+			} else
+				tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound'));
+
+			tinyMCEPopup.storeSelection();
+		} else {
+			if (!w.find(s, ca, b, false, false, false, false))
+				tinyMCEPopup.alert(ed.getLang('searchreplace_dlg.notfound'));
+			else
+				fix();
+		}
+	}
+};
+
+tinyMCEPopup.onInit.add(SearchReplaceDialog.init, SearchReplaceDialog);
diff --git a/media/js/tinymce/plugins/searchreplace/langs/en_dlg.js b/media/js/tinymce/plugins/searchreplace/langs/en_dlg.js
new file mode 100644
index 0000000..370959a
--- /dev/null
+++ b/media/js/tinymce/plugins/searchreplace/langs/en_dlg.js
@@ -0,0 +1,16 @@
+tinyMCE.addI18n('en.searchreplace_dlg',{
+searchnext_desc:"Find again",
+notfound:"The search has been completed. The search string could not be found.",
+search_title:"Find",
+replace_title:"Find/Replace",
+allreplaced:"All occurrences of the search string were replaced.",
+findwhat:"Find what",
+replacewith:"Replace with",
+direction:"Direction",
+up:"Up",
+down:"Down",
+mcase:"Match case",
+findnext:"Find next",
+replace:"Replace",
+replaceall:"Replace all"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/searchreplace/searchreplace.htm b/media/js/tinymce/plugins/searchreplace/searchreplace.htm
new file mode 100644
index 0000000..0b42486
--- /dev/null
+++ b/media/js/tinymce/plugins/searchreplace/searchreplace.htm
@@ -0,0 +1,104 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#searchreplace_dlg.replace_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="js/searchreplace.js"></script>
+	<link rel="stylesheet" type="text/css" href="css/searchreplace.css" />
+</head>
+<body style="display:none;">
+<form onsubmit="SearchReplaceDialog.searchNext('none');return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="search_tab"><span><a href="javascript:SearchReplaceDialog.switchMode('search');" onmousedown="return false;">{#searchreplace.search_desc}</a></span></li>
+			<li id="replace_tab"><span><a href="javascript:SearchReplaceDialog.switchMode('replace');" onmousedown="return false;">{#searchreplace_dlg.replace}</a></span></li>
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="search_panel" class="panel">
+			<table border="0" cellspacing="0" cellpadding="2">
+				<tr>
+					<td><label for="search_panel_searchstring">{#searchreplace_dlg.findwhat}</label></td>
+					<td><input type="text" id="search_panel_searchstring" name="search_panel_searchstring" style="width: 200px" /></td>
+				</tr>
+				<tr>
+					<td colspan="2">
+						<table border="0" cellspacing="0" cellpadding="0" class="direction">
+							<tr>
+								<td><label>{#searchreplace_dlg.direction}</label></td>
+								<td><input id="search_panel_backwardsu" name="search_panel_backwards" class="radio" type="radio" /></td>
+								<td><label for="search_panel_backwardsu">{#searchreplace_dlg.up}</label></td>
+								<td><input id="search_panel_backwardsd" name="search_panel_backwards" class="radio" type="radio" checked="checked" /></td>
+								<td><label for="search_panel_backwardsd">{#searchreplace_dlg.down}</label></td>
+							</tr>
+						</table>
+					</td>
+				</tr>
+				<tr>
+					<td colspan="2">
+						<table border="0" cellspacing="0" cellpadding="0">
+							<tr>
+								<td><input id="search_panel_casesensitivebox" name="search_panel_casesensitivebox" class="checkbox" type="checkbox" /></td>
+								<td><label for="search_panel_casesensitivebox">{#searchreplace_dlg.mcase}</label></td>
+							</tr>
+						</table>
+					</td>
+				</tr>
+			</table>
+		</div>
+
+		<div id="replace_panel" class="panel">
+			<table border="0" cellspacing="0" cellpadding="2">
+				<tr>
+					<td><label for="replace_panel_searchstring">{#searchreplace_dlg.findwhat}</label></td>
+					<td><input type="text" id="replace_panel_searchstring" name="replace_panel_searchstring" style="width: 200px" /></td>
+				</tr>
+				<tr>
+					<td><label for="replace_panel_replacestring">{#searchreplace_dlg.replacewith}</label></td>
+					<td><input type="text" id="replace_panel_replacestring" name="replace_panel_replacestring" style="width: 200px" /></td>
+				</tr>
+				<tr>
+					<td colspan="2">
+						<table border="0" cellspacing="0" cellpadding="0" class="direction">
+							<tr>
+								<td><label>{#searchreplace_dlg.direction}</label></td>
+								<td><input id="replace_panel_backwardsu" name="replace_panel_backwards" class="radio" type="radio" /></td>
+								<td><label for="replace_panel_backwardsu">{#searchreplace_dlg.up}</label></td>
+								<td><input id="replace_panel_backwardsd" name="replace_panel_backwards" class="radio" type="radio" checked="checked" /></td>
+								<td><label for="replace_panel_backwardsd">{#searchreplace_dlg.down}</label></td>
+							</tr>
+						</table>
+					</td>
+				</tr>
+				<tr>
+					<td colspan="2">
+						<table border="0" cellspacing="0" cellpadding="0">
+							<tr>
+								<td><input id="replace_panel_casesensitivebox" name="replace_panel_casesensitivebox" class="checkbox" type="checkbox" /></td>
+								<td><label for="replace_panel_casesensitivebox">{#searchreplace_dlg.mcase}</label></td>
+							</tr>
+						</table>
+					</td>
+				</tr>
+			</table>
+		</div>
+
+	</div>
+
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#searchreplace_dlg.findnext}" />
+			<input type="button" class="button" id="replaceBtn" name="replaceBtn" value="{#searchreplace_dlg.replace}" onclick="SearchReplaceDialog.searchNext('current');" />
+			<input type="button" class="button" id="replaceAllBtn" name="replaceAllBtn" value="{#searchreplace_dlg.replaceall}" onclick="SearchReplaceDialog.searchNext('all');" />
+		</div>
+
+		<div style="float: right">	
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/spellchecker/css/content.css b/media/js/tinymce/plugins/spellchecker/css/content.css
new file mode 100644
index 0000000..24efa02
--- /dev/null
+++ b/media/js/tinymce/plugins/spellchecker/css/content.css
@@ -0,0 +1 @@
+.mceItemHiddenSpellWord {background:url(../img/wline.gif) repeat-x bottom left; cursor:default;}
diff --git a/media/js/tinymce/plugins/spellchecker/editor_plugin.js b/media/js/tinymce/plugins/spellchecker/editor_plugin.js
new file mode 100644
index 0000000..377e4e8
--- /dev/null
+++ b/media/js/tinymce/plugins/spellchecker/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.util.JSONRequest,c=tinymce.each,b=tinymce.DOM;tinymce.create("tinymce.plugins.SpellcheckerPlugin",{getInfo:function(){return{longname:"Spellchecker",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker",version:tinymce.majorVersion+"."+tinymce.minorVersion}},init:function(e,f){var g=this,d;g.url=f;g.editor=e;e.addCommand("mceSpellCheck",function(){if(!g.active){e.setProgr [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/spellchecker/editor_plugin_src.js b/media/js/tinymce/plugins/spellchecker/editor_plugin_src.js
new file mode 100644
index 0000000..c913c46
--- /dev/null
+++ b/media/js/tinymce/plugins/spellchecker/editor_plugin_src.js
@@ -0,0 +1,338 @@
+/**
+ * $Id: editor_plugin_src.js 425 2007-11-21 15:17:39Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var JSONRequest = tinymce.util.JSONRequest, each = tinymce.each, DOM = tinymce.DOM;
+
+	tinymce.create('tinymce.plugins.SpellcheckerPlugin', {
+		getInfo : function() {
+			return {
+				longname : 'Spellchecker',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		init : function(ed, url) {
+			var t = this, cm;
+
+			t.url = url;
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceSpellCheck', function() {
+				if (!t.active) {
+					ed.setProgressState(1);
+					t._sendRPC('checkWords', [t.selectedLang, t._getWords()], function(r) {
+						if (r.length > 0) {
+							t.active = 1;
+							t._markWords(r);
+							ed.setProgressState(0);
+							ed.nodeChanged();
+						} else {
+							ed.setProgressState(0);
+							ed.windowManager.alert('spellchecker.no_mpell');
+						}
+					});
+				} else
+					t._done();
+			});
+
+			ed.onInit.add(function() {
+				if (ed.settings.content_css !== false)
+					ed.dom.loadCSS(url + '/css/content.css');
+			});
+
+			ed.onClick.add(t._showMenu, t);
+			ed.onContextMenu.add(t._showMenu, t);
+			ed.onBeforeGetContent.add(function() {
+				if (t.active)
+					t._removeWords();
+			});
+
+			ed.onNodeChange.add(function(ed, cm) {
+				cm.setActive('spellchecker', t.active);
+			});
+
+			ed.onSetContent.add(function() {
+				t._done();
+			});
+
+			ed.onBeforeGetContent.add(function() {
+				t._done();
+			});
+
+			ed.onBeforeExecCommand.add(function(ed, cmd) {
+				if (cmd == 'mceFullScreen')
+					t._done();
+			});
+
+			// Find selected language
+			t.languages = {};
+			each(ed.getParam('spellchecker_languages', '+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv', 'hash'), function(v, k) {
+				if (k.indexOf('+') === 0) {
+					k = k.substring(1);
+					t.selectedLang = v;
+				}
+
+				t.languages[k] = v;
+			});
+		},
+
+		createControl : function(n, cm) {
+			var t = this, c, ed = t.editor;
+
+			if (n == 'spellchecker') {
+				c = cm.createSplitButton(n, {title : 'spellchecker.desc', cmd : 'mceSpellCheck', scope : t});
+
+				c.onRenderMenu.add(function(c, m) {
+					m.add({title : 'spellchecker.langs', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
+					each(t.languages, function(v, k) {
+						var o = {icon : 1}, mi;
+
+						o.onclick = function() {
+							mi.setSelected(1);
+							t.selectedItem.setSelected(0);
+							t.selectedItem = mi;
+							t.selectedLang = v;
+						};
+
+						o.title = k;
+						mi = m.add(o);
+						mi.setSelected(v == t.selectedLang);
+
+						if (v == t.selectedLang)
+							t.selectedItem = mi;
+					})
+				});
+
+				return c;
+			}
+		},
+
+		// Internal functions
+
+		_walk : function(n, f) {
+			var d = this.editor.getDoc(), w;
+
+			if (d.createTreeWalker) {
+				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
+
+				while ((n = w.nextNode()) != null)
+					f.call(this, n);
+			} else
+				tinymce.walk(n, f, 'childNodes');
+		},
+
+		_getSeparators : function() {
+			var re = '', i, str = this.editor.getParam('spellchecker_word_separator_chars', '\\s!"#$%&()*+,-./:;<=>?@[\]^_{|}����������������\u201d\u201c');
+
+			// Build word separator regexp
+			for (i=0; i<str.length; i++)
+				re += '\\' + str.charAt(i);
+
+			return re;
+		},
+
+		_getWords : function() {
+			var ed = this.editor, wl = [], tx = '', lo = {};
+
+			// Get area text
+			this._walk(ed.getBody(), function(n) {
+				if (n.nodeType == 3)
+					tx += n.nodeValue + ' ';
+			});
+
+			// Split words by separator
+			tx = tx.replace(new RegExp('([0-9]|[' + this._getSeparators() + '])', 'g'), ' ');
+			tx = tinymce.trim(tx.replace(/(\s+)/g, ' '));
+
+			// Build word array and remove duplicates
+			each(tx.split(' '), function(v) {
+				if (!lo[v]) {
+					wl.push(v);
+					lo[v] = 1;
+				}
+			});
+
+			return wl;
+		},
+
+		_removeWords : function(w) {
+			var ed = this.editor, dom = ed.dom, se = ed.selection, b = se.getBookmark();
+
+			each(dom.select('span').reverse(), function(n) {
+				if (n && (dom.hasClass(n, 'mceItemHiddenSpellWord') || dom.hasClass(n, 'mceItemHidden'))) {
+					if (!w || dom.decode(n.innerHTML) == w)
+						dom.remove(n, 1);
+				}
+			});
+
+			se.moveToBookmark(b);
+		},
+
+		_markWords : function(wl) {
+			var r1, r2, r3, r4, r5, w = '', ed = this.editor, re = this._getSeparators(), dom = ed.dom, nl = [];
+			var se = ed.selection, b = se.getBookmark();
+
+			each(wl, function(v) {
+				w += (w ? '|' : '') + v;
+			});
+
+			r1 = new RegExp('([' + re + '])(' + w + ')([' + re + '])', 'g');
+			r2 = new RegExp('^(' + w + ')', 'g');
+			r3 = new RegExp('(' + w + ')([' + re + ']?)$', 'g');
+			r4 = new RegExp('^(' + w + ')([' + re + ']?)$', 'g');
+			r5 = new RegExp('(' + w + ')([' + re + '])', 'g');
+
+			// Collect all text nodes
+			this._walk(this.editor.getBody(), function(n) {
+				if (n.nodeType == 3) {
+					nl.push(n);
+				}
+			});
+
+			// Wrap incorrect words in spans
+			each(nl, function(n) {
+				var v;
+
+				if (n.nodeType == 3) {
+					v = n.nodeValue;
+
+					if (r1.test(v) || r2.test(v) || r3.test(v) || r4.test(v)) {
+						v = dom.encode(v);
+						v = v.replace(r5, '<span class="mceItemHiddenSpellWord">$1</span>$2');
+						v = v.replace(r3, '<span class="mceItemHiddenSpellWord">$1</span>$2');
+
+						dom.replace(dom.create('span', {'class' : 'mceItemHidden'}, v), n);
+					}
+				}
+			});
+
+			se.moveToBookmark(b);
+		},
+
+		_showMenu : function(ed, e) {
+			var t = this, ed = t.editor, m = t._menu, p1, dom = ed.dom, vp = dom.getViewPort(ed.getWin());
+
+			if (!m) {
+				p1 = DOM.getPos(ed.getContentAreaContainer());
+				//p2 = DOM.getPos(ed.getContainer());
+
+				m = ed.controlManager.createDropMenu('spellcheckermenu', {
+					offset_x : p1.x,
+					offset_y : p1.y,
+					'class' : 'mceNoIcons'
+				});
+
+				t._menu = m;
+			}
+
+			if (dom.hasClass(e.target, 'mceItemHiddenSpellWord')) {
+				m.removeAll();
+				m.add({title : 'spellchecker.wait', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
+
+				t._sendRPC('getSuggestions', [t.selectedLang, dom.decode(e.target.innerHTML)], function(r) {
+					m.removeAll();
+
+					if (r.length > 0) {
+						m.add({title : 'spellchecker.sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
+						each(r, function(v) {
+							m.add({title : v, onclick : function() {
+								dom.replace(ed.getDoc().createTextNode(v), e.target);
+								t._checkDone();
+							}});
+						});
+
+						m.addSeparator();
+					} else
+						m.add({title : 'spellchecker.no_sug', 'class' : 'mceMenuItemTitle'}).setDisabled(1);
+
+					m.add({
+						title : 'spellchecker.ignore_word',
+						onclick : function() {
+							dom.remove(e.target, 1);
+							t._checkDone();
+						}
+					});
+
+					m.add({
+						title : 'spellchecker.ignore_words',
+						onclick : function() {
+							t._removeWords(dom.decode(e.target.innerHTML));
+							t._checkDone();
+						}
+					});
+
+					m.update();
+				});
+
+				ed.selection.select(e.target);
+				p1 = dom.getPos(e.target);
+				m.showMenu(p1.x, p1.y + e.target.offsetHeight - vp.y);
+
+				return tinymce.dom.Event.cancel(e);
+			} else
+				m.hideMenu();
+		},
+
+		_checkDone : function() {
+			var t = this, ed = t.editor, dom = ed.dom, o;
+
+			each(dom.select('span'), function(n) {
+				if (n && dom.hasClass(n, 'mceItemHiddenSpellWord')) {
+					o = true;
+					return false;
+				}
+			});
+
+			if (!o)
+				t._done();
+		},
+
+		_done : function() {
+			var t = this, la = t.active;
+
+			if (t.active) {
+				t.active = 0;
+				t._removeWords();
+
+				if (t._menu)
+					t._menu.hideMenu();
+
+				if (la)
+					t.editor.nodeChanged();
+			}
+		},
+
+		_sendRPC : function(m, p, cb) {
+			var t = this, url = t.editor.getParam("spellchecker_rpc_url", "{backend}");
+
+			if (url == '{backend}') {
+				t.editor.setProgressState(0);
+				alert('Please specify: spellchecker_rpc_url');
+				return;
+			}
+
+			JSONRequest.sendRPC({
+				url : url,
+				method : m,
+				params : p,
+				success : cb,
+				error : function(e, x) {
+					t.editor.setProgressState(0);
+					t.editor.windowManager.alert(e.errstr || ('Error response: ' + x.responseText));
+				}
+			});
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('spellchecker', tinymce.plugins.SpellcheckerPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/spellchecker/img/wline.gif b/media/js/tinymce/plugins/spellchecker/img/wline.gif
new file mode 100644
index 0000000..7d0a4db
Binary files /dev/null and b/media/js/tinymce/plugins/spellchecker/img/wline.gif differ
diff --git a/media/js/tinymce/plugins/style/css/props.css b/media/js/tinymce/plugins/style/css/props.css
new file mode 100644
index 0000000..eb1f264
--- /dev/null
+++ b/media/js/tinymce/plugins/style/css/props.css
@@ -0,0 +1,13 @@
+#text_font {width:250px;}
+#text_size {width:70px;}
+.mceAddSelectValue {background:#DDD;}
+select, #block_text_indent, #box_width, #box_height, #box_padding_top, #box_padding_right, #box_padding_bottom, #box_padding_left {width:70px;}
+#box_margin_top, #box_margin_right, #box_margin_bottom, #box_margin_left, #positioning_width, #positioning_height, #positioning_zindex {width:70px;}
+#positioning_placement_top, #positioning_placement_right, #positioning_placement_bottom, #positioning_placement_left {width:70px;}
+#positioning_clip_top, #positioning_clip_right, #positioning_clip_bottom, #positioning_clip_left {width:70px;}
+.panel_wrapper div.current {padding-top:10px;height:230px;}
+.delim {border-left:1px solid gray;}
+.tdelim {border-bottom:1px solid gray;}
+#block_display {width:145px;}
+#list_type {width:115px;}
+.disabled {background:#EEE;}
diff --git a/media/js/tinymce/plugins/style/editor_plugin.js b/media/js/tinymce/plugins/style/editor_plugin.js
new file mode 100644
index 0000000..cab2153
--- /dev/null
+++ b/media/js/tinymce/plugins/style/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.StylePlugin",{init:function(a,b){a.addCommand("mceStyleProps",function(){a.windowManager.open({file:b+"/props.htm",width:480+parseInt(a.getLang("style.delta_width",0)),height:320+parseInt(a.getLang("style.delta_height",0)),inline:1},{plugin_url:b,style_text:a.selection.getNode().style.cssText})});a.addCommand("mceSetElementStyle",function(d,c){if(e=a.selection.getNode()){a.dom.setAttrib(e,"style",c);a.execCommand("mceRepaint")}});a.onNodeChange [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/style/editor_plugin_src.js b/media/js/tinymce/plugins/style/editor_plugin_src.js
new file mode 100644
index 0000000..6c817ce
--- /dev/null
+++ b/media/js/tinymce/plugins/style/editor_plugin_src.js
@@ -0,0 +1,52 @@
+/**
+ * $Id: editor_plugin_src.js 787 2008-04-10 11:40:57Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.StylePlugin', {
+		init : function(ed, url) {
+			// Register commands
+			ed.addCommand('mceStyleProps', function() {
+				ed.windowManager.open({
+					file : url + '/props.htm',
+					width : 480 + parseInt(ed.getLang('style.delta_width', 0)),
+					height : 320 + parseInt(ed.getLang('style.delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url,
+					style_text : ed.selection.getNode().style.cssText
+				});
+			});
+
+			ed.addCommand('mceSetElementStyle', function(ui, v) {
+				if (e = ed.selection.getNode()) {
+					ed.dom.setAttrib(e, 'style', v);
+					ed.execCommand('mceRepaint');
+				}
+			});
+
+			ed.onNodeChange.add(function(ed, cm, n) {
+				cm.setDisabled('styleprops', n.nodeName === 'BODY');
+			});
+
+			// Register buttons
+			ed.addButton('styleprops', {title : 'style.desc', cmd : 'mceStyleProps'});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Style',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/style',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('style', tinymce.plugins.StylePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/style/js/props.js b/media/js/tinymce/plugins/style/js/props.js
new file mode 100644
index 0000000..a8dd93d
--- /dev/null
+++ b/media/js/tinymce/plugins/style/js/props.js
@@ -0,0 +1,641 @@
+tinyMCEPopup.requireLangPack();
+
+var defaultFonts = "" + 
+	"Arial, Helvetica, sans-serif=Arial, Helvetica, sans-serif;" + 
+	"Times New Roman, Times, serif=Times New Roman, Times, serif;" + 
+	"Courier New, Courier, mono=Courier New, Courier, mono;" + 
+	"Times New Roman, Times, serif=Times New Roman, Times, serif;" + 
+	"Georgia, Times New Roman, Times, serif=Georgia, Times New Roman, Times, serif;" + 
+	"Verdana, Arial, Helvetica, sans-serif=Verdana, Arial, Helvetica, sans-serif;" + 
+	"Geneva, Arial, Helvetica, sans-serif=Geneva, Arial, Helvetica, sans-serif";
+
+var defaultSizes = "9;10;12;14;16;18;24;xx-small;x-small;small;medium;large;x-large;xx-large;smaller;larger";
+var defaultMeasurement = "+pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%";
+var defaultSpacingMeasurement = "pixels=px;points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;+ems=em;exs=ex;%";
+var defaultIndentMeasurement = "pixels=px;+points=pt;inches=in;centimetres=cm;millimetres=mm;picas=pc;ems=em;exs=ex;%";
+var defaultWeight = "normal;bold;bolder;lighter;100;200;300;400;500;600;700;800;900";
+var defaultTextStyle = "normal;italic;oblique";
+var defaultVariant = "normal;small-caps";
+var defaultLineHeight = "normal";
+var defaultAttachment = "fixed;scroll";
+var defaultRepeat = "no-repeat;repeat;repeat-x;repeat-y";
+var defaultPosH = "left;center;right";
+var defaultPosV = "top;center;bottom";
+var defaultVAlign = "baseline;sub;super;top;text-top;middle;bottom;text-bottom";
+var defaultDisplay = "inline;block;list-item;run-in;compact;marker;table;inline-table;table-row-group;table-header-group;table-footer-group;table-row;table-column-group;table-column;table-cell;table-caption;none";
+var defaultBorderStyle = "none;solid;dashed;dotted;double;groove;ridge;inset;outset";
+var defaultBorderWidth = "thin;medium;thick";
+var defaultListType = "disc;circle;square;decimal;lower-roman;upper-roman;lower-alpha;upper-alpha;none";
+
+function init() {
+	var ce = document.getElementById('container'), h;
+
+	ce.style.cssText = tinyMCEPopup.getWindowArg('style_text');
+
+	h = getBrowserHTML('background_image_browser','background_image','image','advimage');
+	document.getElementById("background_image_browser").innerHTML = h;
+
+	document.getElementById('text_color_pickcontainer').innerHTML = getColorPickerHTML('text_color_pick','text_color');
+	document.getElementById('background_color_pickcontainer').innerHTML = getColorPickerHTML('background_color_pick','background_color');
+	document.getElementById('border_color_top_pickcontainer').innerHTML = getColorPickerHTML('border_color_top_pick','border_color_top');
+	document.getElementById('border_color_right_pickcontainer').innerHTML = getColorPickerHTML('border_color_right_pick','border_color_right');
+	document.getElementById('border_color_bottom_pickcontainer').innerHTML = getColorPickerHTML('border_color_bottom_pick','border_color_bottom');
+	document.getElementById('border_color_left_pickcontainer').innerHTML = getColorPickerHTML('border_color_left_pick','border_color_left');
+
+	fillSelect(0, 'text_font', 'style_font', defaultFonts, ';', true);
+	fillSelect(0, 'text_size', 'style_font_size', defaultSizes, ';', true);
+	fillSelect(0, 'text_size_measurement', 'style_font_size_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'text_case', 'style_text_case', "capitalize;uppercase;lowercase", ';', true);
+	fillSelect(0, 'text_weight', 'style_font_weight', defaultWeight, ';', true);
+	fillSelect(0, 'text_style', 'style_font_style', defaultTextStyle, ';', true);
+	fillSelect(0, 'text_variant', 'style_font_variant', defaultVariant, ';', true);
+	fillSelect(0, 'text_lineheight', 'style_font_line_height', defaultLineHeight, ';', true);
+	fillSelect(0, 'text_lineheight_measurement', 'style_font_line_height_measurement', defaultMeasurement, ';', true);
+
+	fillSelect(0, 'background_attachment', 'style_background_attachment', defaultAttachment, ';', true);
+	fillSelect(0, 'background_repeat', 'style_background_repeat', defaultRepeat, ';', true);
+
+	fillSelect(0, 'background_hpos_measurement', 'style_background_hpos_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'background_vpos_measurement', 'style_background_vpos_measurement', defaultMeasurement, ';', true);
+
+	fillSelect(0, 'background_hpos', 'style_background_hpos', defaultPosH, ';', true);
+	fillSelect(0, 'background_vpos', 'style_background_vpos', defaultPosV, ';', true);
+
+	fillSelect(0, 'block_wordspacing', 'style_wordspacing', 'normal', ';', true);
+	fillSelect(0, 'block_wordspacing_measurement', 'style_wordspacing_measurement', defaultSpacingMeasurement, ';', true);
+	fillSelect(0, 'block_letterspacing', 'style_letterspacing', 'normal', ';', true);
+	fillSelect(0, 'block_letterspacing_measurement', 'style_letterspacing_measurement', defaultSpacingMeasurement, ';', true);
+	fillSelect(0, 'block_vertical_alignment', 'style_vertical_alignment', defaultVAlign, ';', true);
+	fillSelect(0, 'block_text_align', 'style_text_align', "left;right;center;justify", ';', true);
+	fillSelect(0, 'block_whitespace', 'style_whitespace', "normal;pre;nowrap", ';', true);
+	fillSelect(0, 'block_display', 'style_display', defaultDisplay, ';', true);
+	fillSelect(0, 'block_text_indent_measurement', 'style_text_indent_measurement', defaultIndentMeasurement, ';', true);
+
+	fillSelect(0, 'box_width_measurement', 'style_box_width_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_height_measurement', 'style_box_height_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_float', 'style_float', 'left;right;none', ';', true);
+	fillSelect(0, 'box_clear', 'style_clear', 'left;right;both;none', ';', true);
+	fillSelect(0, 'box_padding_left_measurement', 'style_padding_left_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_padding_top_measurement', 'style_padding_top_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_padding_bottom_measurement', 'style_padding_bottom_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_padding_right_measurement', 'style_padding_right_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_margin_left_measurement', 'style_margin_left_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_margin_top_measurement', 'style_margin_top_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_margin_bottom_measurement', 'style_margin_bottom_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'box_margin_right_measurement', 'style_margin_right_measurement', defaultMeasurement, ';', true);
+
+	fillSelect(0, 'border_style_top', 'style_border_style_top', defaultBorderStyle, ';', true);
+	fillSelect(0, 'border_style_right', 'style_border_style_right', defaultBorderStyle, ';', true);
+	fillSelect(0, 'border_style_bottom', 'style_border_style_bottom', defaultBorderStyle, ';', true);
+	fillSelect(0, 'border_style_left', 'style_border_style_left', defaultBorderStyle, ';', true);
+
+	fillSelect(0, 'border_width_top', 'style_border_width_top', defaultBorderWidth, ';', true);
+	fillSelect(0, 'border_width_right', 'style_border_width_right', defaultBorderWidth, ';', true);
+	fillSelect(0, 'border_width_bottom', 'style_border_width_bottom', defaultBorderWidth, ';', true);
+	fillSelect(0, 'border_width_left', 'style_border_width_left', defaultBorderWidth, ';', true);
+
+	fillSelect(0, 'border_width_top_measurement', 'style_border_width_top_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'border_width_right_measurement', 'style_border_width_right_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'border_width_bottom_measurement', 'style_border_width_bottom_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'border_width_left_measurement', 'style_border_width_left_measurement', defaultMeasurement, ';', true);
+
+	fillSelect(0, 'list_type', 'style_list_type', defaultListType, ';', true);
+	fillSelect(0, 'list_position', 'style_list_position', "inside;outside", ';', true);
+
+	fillSelect(0, 'positioning_type', 'style_positioning_type', "absolute;relative;static", ';', true);
+	fillSelect(0, 'positioning_visibility', 'style_positioning_visibility', "inherit;visible;hidden", ';', true);
+
+	fillSelect(0, 'positioning_width_measurement', 'style_positioning_width_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'positioning_height_measurement', 'style_positioning_height_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'positioning_overflow', 'style_positioning_overflow', "visible;hidden;scroll;auto", ';', true);
+
+	fillSelect(0, 'positioning_placement_top_measurement', 'style_positioning_placement_top_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'positioning_placement_right_measurement', 'style_positioning_placement_right_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'positioning_placement_bottom_measurement', 'style_positioning_placement_bottom_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'positioning_placement_left_measurement', 'style_positioning_placement_left_measurement', defaultMeasurement, ';', true);
+
+	fillSelect(0, 'positioning_clip_top_measurement', 'style_positioning_clip_top_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'positioning_clip_right_measurement', 'style_positioning_clip_right_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'positioning_clip_bottom_measurement', 'style_positioning_clip_bottom_measurement', defaultMeasurement, ';', true);
+	fillSelect(0, 'positioning_clip_left_measurement', 'style_positioning_clip_left_measurement', defaultMeasurement, ';', true);
+
+	TinyMCE_EditableSelects.init();
+	setupFormData();
+	showDisabledControls();
+}
+
+function setupFormData() {
+	var ce = document.getElementById('container'), f = document.forms[0], s, b, i;
+
+	// Setup text fields
+
+	selectByValue(f, 'text_font', ce.style.fontFamily, true, true);
+	selectByValue(f, 'text_size', getNum(ce.style.fontSize), true, true);
+	selectByValue(f, 'text_size_measurement', getMeasurement(ce.style.fontSize));
+	selectByValue(f, 'text_weight', ce.style.fontWeight, true, true);
+	selectByValue(f, 'text_style', ce.style.fontStyle, true, true);
+	selectByValue(f, 'text_lineheight', getNum(ce.style.lineHeight), true, true);
+	selectByValue(f, 'text_lineheight_measurement', getMeasurement(ce.style.lineHeight));
+	selectByValue(f, 'text_case', ce.style.textTransform, true, true);
+	selectByValue(f, 'text_variant', ce.style.fontVariant, true, true);
+	f.text_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.color);
+	updateColor('text_color_pick', 'text_color');
+	f.text_underline.checked = inStr(ce.style.textDecoration, 'underline');
+	f.text_overline.checked = inStr(ce.style.textDecoration, 'overline');
+	f.text_linethrough.checked = inStr(ce.style.textDecoration, 'line-through');
+	f.text_blink.checked = inStr(ce.style.textDecoration, 'blink');
+
+	// Setup background fields
+
+	f.background_color.value = tinyMCEPopup.editor.dom.toHex(ce.style.backgroundColor);
+	updateColor('background_color_pick', 'background_color');
+	f.background_image.value = ce.style.backgroundImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");
+	selectByValue(f, 'background_repeat', ce.style.backgroundRepeat, true, true);
+	selectByValue(f, 'background_attachment', ce.style.backgroundAttachment, true, true);
+	selectByValue(f, 'background_hpos', getNum(getVal(ce.style.backgroundPosition, 0)), true, true);
+	selectByValue(f, 'background_hpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 0)));
+	selectByValue(f, 'background_vpos', getNum(getVal(ce.style.backgroundPosition, 1)), true, true);
+	selectByValue(f, 'background_vpos_measurement', getMeasurement(getVal(ce.style.backgroundPosition, 1)));
+
+	// Setup block fields
+
+	selectByValue(f, 'block_wordspacing', getNum(ce.style.wordSpacing), true, true);
+	selectByValue(f, 'block_wordspacing_measurement', getMeasurement(ce.style.wordSpacing));
+	selectByValue(f, 'block_letterspacing', getNum(ce.style.letterSpacing), true, true);
+	selectByValue(f, 'block_letterspacing_measurement', getMeasurement(ce.style.letterSpacing));
+	selectByValue(f, 'block_vertical_alignment', ce.style.verticalAlign, true, true);
+	selectByValue(f, 'block_text_align', ce.style.textAlign, true, true);
+	f.block_text_indent.value = getNum(ce.style.textIndent);
+	selectByValue(f, 'block_text_indent_measurement', getMeasurement(ce.style.textIndent));
+	selectByValue(f, 'block_whitespace', ce.style.whiteSpace, true, true);
+	selectByValue(f, 'block_display', ce.style.display, true, true);
+
+	// Setup box fields
+
+	f.box_width.value = getNum(ce.style.width);
+	selectByValue(f, 'box_width_measurement', getMeasurement(ce.style.width));
+
+	f.box_height.value = getNum(ce.style.height);
+	selectByValue(f, 'box_height_measurement', getMeasurement(ce.style.height));
+
+	if (tinymce.isGecko)
+		selectByValue(f, 'box_float', ce.style.cssFloat, true, true);
+	else
+		selectByValue(f, 'box_float', ce.style.styleFloat, true, true);
+
+	selectByValue(f, 'box_clear', ce.style.clear, true, true);
+
+	setupBox(f, ce, 'box_padding', 'padding', '');
+	setupBox(f, ce, 'box_margin', 'margin', '');
+
+	// Setup border fields
+
+	setupBox(f, ce, 'border_style', 'border', 'Style');
+	setupBox(f, ce, 'border_width', 'border', 'Width');
+	setupBox(f, ce, 'border_color', 'border', 'Color');
+
+	updateColor('border_color_top_pick', 'border_color_top');
+	updateColor('border_color_right_pick', 'border_color_right');
+	updateColor('border_color_bottom_pick', 'border_color_bottom');
+	updateColor('border_color_left_pick', 'border_color_left');
+
+	f.elements.border_color_top.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_top.value);
+	f.elements.border_color_right.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_right.value);
+	f.elements.border_color_bottom.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_bottom.value);
+	f.elements.border_color_left.value = tinyMCEPopup.editor.dom.toHex(f.elements.border_color_left.value);
+
+	// Setup list fields
+
+	selectByValue(f, 'list_type', ce.style.listStyleType, true, true);
+	selectByValue(f, 'list_position', ce.style.listStylePosition, true, true);
+	f.list_bullet_image.value = ce.style.listStyleImage.replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");
+
+	// Setup box fields
+
+	selectByValue(f, 'positioning_type', ce.style.position, true, true);
+	selectByValue(f, 'positioning_visibility', ce.style.visibility, true, true);
+	selectByValue(f, 'positioning_overflow', ce.style.overflow, true, true);
+	f.positioning_zindex.value = ce.style.zIndex ? ce.style.zIndex : "";
+
+	f.positioning_width.value = getNum(ce.style.width);
+	selectByValue(f, 'positioning_width_measurement', getMeasurement(ce.style.width));
+
+	f.positioning_height.value = getNum(ce.style.height);
+	selectByValue(f, 'positioning_height_measurement', getMeasurement(ce.style.height));
+
+	setupBox(f, ce, 'positioning_placement', '', '', ['top', 'right', 'bottom', 'left']);
+
+	s = ce.style.clip.replace(new RegExp("rect\\('?([^']*)'?\\)", 'gi'), "$1");
+	s = s.replace(/,/g, ' ');
+
+	if (!hasEqualValues([getVal(s, 0), getVal(s, 1), getVal(s, 2), getVal(s, 3)])) {
+		f.positioning_clip_top.value = getNum(getVal(s, 0));
+		selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0)));
+		f.positioning_clip_right.value = getNum(getVal(s, 1));
+		selectByValue(f, 'positioning_clip_right_measurement', getMeasurement(getVal(s, 1)));
+		f.positioning_clip_bottom.value = getNum(getVal(s, 2));
+		selectByValue(f, 'positioning_clip_bottom_measurement', getMeasurement(getVal(s, 2)));
+		f.positioning_clip_left.value = getNum(getVal(s, 3));
+		selectByValue(f, 'positioning_clip_left_measurement', getMeasurement(getVal(s, 3)));
+	} else {
+		f.positioning_clip_top.value = getNum(getVal(s, 0));
+		selectByValue(f, 'positioning_clip_top_measurement', getMeasurement(getVal(s, 0)));
+		f.positioning_clip_right.value = f.positioning_clip_bottom.value = f.positioning_clip_left.value;
+	}
+
+//	setupBox(f, ce, '', 'border', 'Color');
+}
+
+function getMeasurement(s) {
+	return s.replace(/^([0-9.]+)(.*)$/, "$2");
+}
+
+function getNum(s) {
+	if (new RegExp('^(?:[0-9.]+)(?:[a-z%]+)$', 'gi').test(s))
+		return s.replace(/[^0-9.]/g, '');
+
+	return s;
+}
+
+function inStr(s, n) {
+	return new RegExp(n, 'gi').test(s);
+}
+
+function getVal(s, i) {
+	var a = s.split(' ');
+
+	if (a.length > 1)
+		return a[i];
+
+	return "";
+}
+
+function setValue(f, n, v) {
+	if (f.elements[n].type == "text")
+		f.elements[n].value = v;
+	else
+		selectByValue(f, n, v, true, true);
+}
+
+function setupBox(f, ce, fp, pr, sf, b) {
+	if (typeof(b) == "undefined")
+		b = ['Top', 'Right', 'Bottom', 'Left'];
+
+	if (isSame(ce, pr, sf, b)) {
+		f.elements[fp + "_same"].checked = true;
+
+		setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf]));
+		f.elements[fp + "_top"].disabled = false;
+
+		f.elements[fp + "_right"].value = "";
+		f.elements[fp + "_right"].disabled = true;
+		f.elements[fp + "_bottom"].value = "";
+		f.elements[fp + "_bottom"].disabled = true;
+		f.elements[fp + "_left"].value = "";
+		f.elements[fp + "_left"].disabled = true;
+
+		if (f.elements[fp + "_top_measurement"]) {
+			selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf]));
+			f.elements[fp + "_left_measurement"].disabled = true;
+			f.elements[fp + "_bottom_measurement"].disabled = true;
+			f.elements[fp + "_right_measurement"].disabled = true;
+		}
+	} else {
+		f.elements[fp + "_same"].checked = false;
+
+		setValue(f, fp + "_top", getNum(ce.style[pr + b[0] + sf]));
+		f.elements[fp + "_top"].disabled = false;
+
+		setValue(f, fp + "_right", getNum(ce.style[pr + b[1] + sf]));
+		f.elements[fp + "_right"].disabled = false;
+
+		setValue(f, fp + "_bottom", getNum(ce.style[pr + b[2] + sf]));
+		f.elements[fp + "_bottom"].disabled = false;
+
+		setValue(f, fp + "_left", getNum(ce.style[pr + b[3] + sf]));
+		f.elements[fp + "_left"].disabled = false;
+
+		if (f.elements[fp + "_top_measurement"]) {
+			selectByValue(f, fp + '_top_measurement', getMeasurement(ce.style[pr + b[0] + sf]));
+			selectByValue(f, fp + '_right_measurement', getMeasurement(ce.style[pr + b[1] + sf]));
+			selectByValue(f, fp + '_bottom_measurement', getMeasurement(ce.style[pr + b[2] + sf]));
+			selectByValue(f, fp + '_left_measurement', getMeasurement(ce.style[pr + b[3] + sf]));
+			f.elements[fp + "_left_measurement"].disabled = false;
+			f.elements[fp + "_bottom_measurement"].disabled = false;
+			f.elements[fp + "_right_measurement"].disabled = false;
+		}
+	}
+}
+
+function isSame(e, pr, sf, b) {
+	var a = [], i, x;
+
+	if (typeof(b) == "undefined")
+		b = ['Top', 'Right', 'Bottom', 'Left'];
+
+	if (typeof(sf) == "undefined" || sf == null)
+		sf = "";
+
+	a[0] = e.style[pr + b[0] + sf];
+	a[1] = e.style[pr + b[1] + sf];
+	a[2] = e.style[pr + b[2] + sf];
+	a[3] = e.style[pr + b[3] + sf];
+
+	for (i=0; i<a.length; i++) {
+		if (a[i] == null)
+			return false;
+
+		for (x=0; x<a.length; x++) {
+			if (a[x] != a[i])
+				return false;
+		}
+	}
+
+	return true;
+};
+
+function hasEqualValues(a) {
+	var i, x;
+
+	for (i=0; i<a.length; i++) {
+		if (a[i] == null)
+			return false;
+
+		for (x=0; x<a.length; x++) {
+			if (a[x] != a[i])
+				return false;
+		}
+	}
+
+	return true;
+}
+
+function applyAction() {
+	var ce = document.getElementById('container'), ed = tinyMCEPopup.editor;
+
+	generateCSS();
+
+	tinyMCEPopup.restoreSelection();
+	ed.dom.setAttrib(ed.selection.getNode(), 'style', tinyMCEPopup.editor.dom.serializeStyle(tinyMCEPopup.editor.dom.parseStyle(ce.style.cssText)));
+}
+
+function updateAction() {
+	applyAction();
+	tinyMCEPopup.close();
+}
+
+function generateCSS() {
+	var ce = document.getElementById('container'), f = document.forms[0], num = new RegExp('[0-9]+', 'g'), s, t;
+
+	ce.style.cssText = "";
+
+	// Build text styles
+	ce.style.fontFamily = f.text_font.value;
+	ce.style.fontSize = f.text_size.value + (isNum(f.text_size.value) ? (f.text_size_measurement.value || 'px') : "");
+	ce.style.fontStyle = f.text_style.value;
+	ce.style.lineHeight = f.text_lineheight.value + (isNum(f.text_lineheight.value) ? f.text_lineheight_measurement.value : "");
+	ce.style.textTransform = f.text_case.value;
+	ce.style.fontWeight = f.text_weight.value;
+	ce.style.fontVariant = f.text_variant.value;
+	ce.style.color = f.text_color.value;
+
+	s = "";
+	s += f.text_underline.checked ? " underline" : "";
+	s += f.text_overline.checked ? " overline" : "";
+	s += f.text_linethrough.checked ? " line-through" : "";
+	s += f.text_blink.checked ? " blink" : "";
+	s = s.length > 0 ? s.substring(1) : s;
+
+	if (f.text_none.checked)
+		s = "none";
+
+	ce.style.textDecoration = s;
+
+	// Build background styles
+
+	ce.style.backgroundColor = f.background_color.value;
+	ce.style.backgroundImage = f.background_image.value != "" ? "url(" + f.background_image.value + ")" : "";
+	ce.style.backgroundRepeat = f.background_repeat.value;
+	ce.style.backgroundAttachment = f.background_attachment.value;
+
+	if (f.background_hpos.value != "") {
+		s = "";
+		s += f.background_hpos.value + (isNum(f.background_hpos.value) ? f.background_hpos_measurement.value : "") + " ";
+		s += f.background_vpos.value + (isNum(f.background_vpos.value) ? f.background_vpos_measurement.value : "");
+		ce.style.backgroundPosition = s;
+	}
+
+	// Build block styles
+
+	ce.style.wordSpacing = f.block_wordspacing.value + (isNum(f.block_wordspacing.value) ? f.block_wordspacing_measurement.value : "");
+	ce.style.letterSpacing = f.block_letterspacing.value + (isNum(f.block_letterspacing.value) ? f.block_letterspacing_measurement.value : "");
+	ce.style.verticalAlign = f.block_vertical_alignment.value;
+	ce.style.textAlign = f.block_text_align.value;
+	ce.style.textIndent = f.block_text_indent.value + (isNum(f.block_text_indent.value) ? f.block_text_indent_measurement.value : "");
+	ce.style.whiteSpace = f.block_whitespace.value;
+	ce.style.display = f.block_display.value;
+
+	// Build box styles
+
+	ce.style.width = f.box_width.value + (isNum(f.box_width.value) ? f.box_width_measurement.value : "");
+	ce.style.height = f.box_height.value + (isNum(f.box_height.value) ? f.box_height_measurement.value : "");
+	ce.style.styleFloat = f.box_float.value;
+
+	if (tinymce.isGecko)
+		ce.style.cssFloat = f.box_float.value;
+
+	ce.style.clear = f.box_clear.value;
+
+	if (!f.box_padding_same.checked) {
+		ce.style.paddingTop = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : "");
+		ce.style.paddingRight = f.box_padding_right.value + (isNum(f.box_padding_right.value) ? f.box_padding_right_measurement.value : "");
+		ce.style.paddingBottom = f.box_padding_bottom.value + (isNum(f.box_padding_bottom.value) ? f.box_padding_bottom_measurement.value : "");
+		ce.style.paddingLeft = f.box_padding_left.value + (isNum(f.box_padding_left.value) ? f.box_padding_left_measurement.value : "");
+	} else
+		ce.style.padding = f.box_padding_top.value + (isNum(f.box_padding_top.value) ? f.box_padding_top_measurement.value : "");		
+
+	if (!f.box_margin_same.checked) {
+		ce.style.marginTop = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : "");
+		ce.style.marginRight = f.box_margin_right.value + (isNum(f.box_margin_right.value) ? f.box_margin_right_measurement.value : "");
+		ce.style.marginBottom = f.box_margin_bottom.value + (isNum(f.box_margin_bottom.value) ? f.box_margin_bottom_measurement.value : "");
+		ce.style.marginLeft = f.box_margin_left.value + (isNum(f.box_margin_left.value) ? f.box_margin_left_measurement.value : "");
+	} else
+		ce.style.margin = f.box_margin_top.value + (isNum(f.box_margin_top.value) ? f.box_margin_top_measurement.value : "");		
+
+	// Build border styles
+
+	if (!f.border_style_same.checked) {
+		ce.style.borderTopStyle = f.border_style_top.value;
+		ce.style.borderRightStyle = f.border_style_right.value;
+		ce.style.borderBottomStyle = f.border_style_bottom.value;
+		ce.style.borderLeftStyle = f.border_style_left.value;
+	} else
+		ce.style.borderStyle = f.border_style_top.value;
+
+	if (!f.border_width_same.checked) {
+		ce.style.borderTopWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : "");
+		ce.style.borderRightWidth = f.border_width_right.value + (isNum(f.border_width_right.value) ? f.border_width_right_measurement.value : "");
+		ce.style.borderBottomWidth = f.border_width_bottom.value + (isNum(f.border_width_bottom.value) ? f.border_width_bottom_measurement.value : "");
+		ce.style.borderLeftWidth = f.border_width_left.value + (isNum(f.border_width_left.value) ? f.border_width_left_measurement.value : "");
+	} else
+		ce.style.borderWidth = f.border_width_top.value + (isNum(f.border_width_top.value) ? f.border_width_top_measurement.value : "");
+
+	if (!f.border_color_same.checked) {
+		ce.style.borderTopColor = f.border_color_top.value;
+		ce.style.borderRightColor = f.border_color_right.value;
+		ce.style.borderBottomColor = f.border_color_bottom.value;
+		ce.style.borderLeftColor = f.border_color_left.value;
+	} else
+		ce.style.borderColor = f.border_color_top.value;
+
+	// Build list styles
+
+	ce.style.listStyleType = f.list_type.value;
+	ce.style.listStylePosition = f.list_position.value;
+	ce.style.listStyleImage = f.list_bullet_image.value != "" ? "url(" + f.list_bullet_image.value + ")" : "";
+
+	// Build positioning styles
+
+	ce.style.position = f.positioning_type.value;
+	ce.style.visibility = f.positioning_visibility.value;
+
+	if (ce.style.width == "")
+		ce.style.width = f.positioning_width.value + (isNum(f.positioning_width.value) ? f.positioning_width_measurement.value : "");
+
+	if (ce.style.height == "")
+		ce.style.height = f.positioning_height.value + (isNum(f.positioning_height.value) ? f.positioning_height_measurement.value : "");
+
+	ce.style.zIndex = f.positioning_zindex.value;
+	ce.style.overflow = f.positioning_overflow.value;
+
+	if (!f.positioning_placement_same.checked) {
+		ce.style.top = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : "");
+		ce.style.right = f.positioning_placement_right.value + (isNum(f.positioning_placement_right.value) ? f.positioning_placement_right_measurement.value : "");
+		ce.style.bottom = f.positioning_placement_bottom.value + (isNum(f.positioning_placement_bottom.value) ? f.positioning_placement_bottom_measurement.value : "");
+		ce.style.left = f.positioning_placement_left.value + (isNum(f.positioning_placement_left.value) ? f.positioning_placement_left_measurement.value : "");
+	} else {
+		s = f.positioning_placement_top.value + (isNum(f.positioning_placement_top.value) ? f.positioning_placement_top_measurement.value : "");
+		ce.style.top = s;
+		ce.style.right = s;
+		ce.style.bottom = s;
+		ce.style.left = s;
+	}
+
+	if (!f.positioning_clip_same.checked) {
+		s = "rect(";
+		s += (isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto") + " ";
+		s += (isNum(f.positioning_clip_right.value) ? f.positioning_clip_right.value + f.positioning_clip_right_measurement.value : "auto") + " ";
+		s += (isNum(f.positioning_clip_bottom.value) ? f.positioning_clip_bottom.value + f.positioning_clip_bottom_measurement.value : "auto") + " ";
+		s += (isNum(f.positioning_clip_left.value) ? f.positioning_clip_left.value + f.positioning_clip_left_measurement.value : "auto");
+		s += ")";
+
+		if (s != "rect(auto auto auto auto)")
+			ce.style.clip = s;
+	} else {
+		s = "rect(";
+		t = isNum(f.positioning_clip_top.value) ? f.positioning_clip_top.value + f.positioning_clip_top_measurement.value : "auto";
+		s += t + " ";
+		s += t + " ";
+		s += t + " ";
+		s += t + ")";
+
+		if (s != "rect(auto auto auto auto)")
+			ce.style.clip = s;
+	}
+
+	ce.style.cssText = ce.style.cssText;
+}
+
+function isNum(s) {
+	return new RegExp('[0-9]+', 'g').test(s);
+}
+
+function showDisabledControls() {
+	var f = document.forms, i, a;
+
+	for (i=0; i<f.length; i++) {
+		for (a=0; a<f[i].elements.length; a++) {
+			if (f[i].elements[a].disabled)
+				tinyMCEPopup.editor.dom.addClass(f[i].elements[a], "disabled");
+			else
+				tinyMCEPopup.editor.dom.removeClass(f[i].elements[a], "disabled");
+		}
+	}
+}
+
+function fillSelect(f, s, param, dval, sep, em) {
+	var i, ar, p, se;
+
+	f = document.forms[f];
+	sep = typeof(sep) == "undefined" ? ";" : sep;
+
+	if (em)
+		addSelectValue(f, s, "", "");
+
+	ar = tinyMCEPopup.getParam(param, dval).split(sep);
+	for (i=0; i<ar.length; i++) {
+		se = false;
+
+		if (ar[i].charAt(0) == '+') {
+			ar[i] = ar[i].substring(1);
+			se = true;
+		}
+
+		p = ar[i].split('=');
+
+		if (p.length > 1) {
+			addSelectValue(f, s, p[0], p[1]);
+
+			if (se)
+				selectByValue(f, s, p[1]);
+		} else {
+			addSelectValue(f, s, p[0], p[0]);
+
+			if (se)
+				selectByValue(f, s, p[0]);
+		}
+	}
+}
+
+function toggleSame(ce, pre) {
+	var el = document.forms[0].elements, i;
+
+	if (ce.checked) {
+		el[pre + "_top"].disabled = false;
+		el[pre + "_right"].disabled = true;
+		el[pre + "_bottom"].disabled = true;
+		el[pre + "_left"].disabled = true;
+
+		if (el[pre + "_top_measurement"]) {
+			el[pre + "_top_measurement"].disabled = false;
+			el[pre + "_right_measurement"].disabled = true;
+			el[pre + "_bottom_measurement"].disabled = true;
+			el[pre + "_left_measurement"].disabled = true;
+		}
+	} else {
+		el[pre + "_top"].disabled = false;
+		el[pre + "_right"].disabled = false;
+		el[pre + "_bottom"].disabled = false;
+		el[pre + "_left"].disabled = false;
+
+		if (el[pre + "_top_measurement"]) {
+			el[pre + "_top_measurement"].disabled = false;
+			el[pre + "_right_measurement"].disabled = false;
+			el[pre + "_bottom_measurement"].disabled = false;
+			el[pre + "_left_measurement"].disabled = false;
+		}
+	}
+
+	showDisabledControls();
+}
+
+function synch(fr, to) {
+	var f = document.forms[0];
+
+	f.elements[to].value = f.elements[fr].value;
+
+	if (f.elements[fr + "_measurement"])
+		selectByValue(f, to + "_measurement", f.elements[fr + "_measurement"].value);
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/style/langs/en_dlg.js b/media/js/tinymce/plugins/style/langs/en_dlg.js
new file mode 100644
index 0000000..5026313
--- /dev/null
+++ b/media/js/tinymce/plugins/style/langs/en_dlg.js
@@ -0,0 +1,63 @@
+tinyMCE.addI18n('en.style_dlg',{
+title:"Edit CSS Style",
+apply:"Apply",
+text_tab:"Text",
+background_tab:"Background",
+block_tab:"Block",
+box_tab:"Box",
+border_tab:"Border",
+list_tab:"List",
+positioning_tab:"Positioning",
+text_props:"Text",
+text_font:"Font",
+text_size:"Size",
+text_weight:"Weight",
+text_style:"Style",
+text_variant:"Variant",
+text_lineheight:"Line height",
+text_case:"Case",
+text_color:"Color",
+text_decoration:"Decoration",
+text_overline:"overline",
+text_underline:"underline",
+text_striketrough:"strikethrough",
+text_blink:"blink",
+text_none:"none",
+background_color:"Background color",
+background_image:"Background image",
+background_repeat:"Repeat",
+background_attachment:"Attachment",
+background_hpos:"Horizontal position",
+background_vpos:"Vertical position",
+block_wordspacing:"Word spacing",
+block_letterspacing:"Letter spacing",
+block_vertical_alignment:"Vertical alignment",
+block_text_align:"Text align",
+block_text_indent:"Text indent",
+block_whitespace:"Whitespace",
+block_display:"Display",
+box_width:"Width",
+box_height:"Height",
+box_float:"Float",
+box_clear:"Clear",
+padding:"Padding",
+same:"Same for all",
+top:"Top",
+right:"Right",
+bottom:"Bottom",
+left:"Left",
+margin:"Margin",
+style:"Style",
+width:"Width",
+height:"Height",
+color:"Color",
+list_type:"Type",
+bullet_image:"Bullet image",
+position:"Position",
+positioning_type:"Type",
+visibility:"Visibility",
+zindex:"Z-index",
+overflow:"Overflow",
+placement:"Placement",
+clip:"Clip"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/style/props.htm b/media/js/tinymce/plugins/style/props.htm
new file mode 100644
index 0000000..cb74ec6
--- /dev/null
+++ b/media/js/tinymce/plugins/style/props.htm
@@ -0,0 +1,730 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#style_dlg.title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="js/props.js"></script>
+	<link href="css/props.css" rel="stylesheet" type="text/css" />
+</head>
+
+<body id="styleprops" style="display: none">
+<form onsubmit="updateAction();return false;" action="#">
+<div class="tabs">
+	<ul>
+		<li id="text_tab" class="current"><span><a href="javascript:mcTabs.displayTab('text_tab','text_panel');" onMouseDown="return false;">{#style_dlg.text_tab}</a></span></li>
+		<li id="background_tab"><span><a href="javascript:mcTabs.displayTab('background_tab','background_panel');" onMouseDown="return false;">{#style_dlg.background_tab}</a></span></li>
+		<li id="block_tab"><span><a href="javascript:mcTabs.displayTab('block_tab','block_panel');" onMouseDown="return false;">{#style_dlg.block_tab}</a></span></li>
+		<li id="box_tab"><span><a href="javascript:mcTabs.displayTab('box_tab','box_panel');" onMouseDown="return false;">{#style_dlg.box_tab}</a></span></li>
+		<li id="border_tab"><span><a href="javascript:mcTabs.displayTab('border_tab','border_panel');" onMouseDown="return false;">{#style_dlg.border_tab}</a></span></li>
+		<li id="list_tab"><span><a href="javascript:mcTabs.displayTab('list_tab','list_panel');" onMouseDown="return false;">{#style_dlg.list_tab}</a></span></li>
+		<li id="positioning_tab"><span><a href="javascript:mcTabs.displayTab('positioning_tab','positioning_panel');" onMouseDown="return false;">{#style_dlg.positioning_tab}</a></span></li>
+	</ul>
+</div>
+
+<div class="panel_wrapper">
+<div id="text_panel" class="panel current">
+	<table border="0" width="100%">
+		<tr>
+			<td><label for="text_font">{#style_dlg.text_font}</label></td>
+			<td colspan="3">
+				<select id="text_font" name="text_font" class="mceEditableSelect mceFocus"></select>
+			</td>
+		</tr>
+		<tr>
+			<td><label for="text_size">{#style_dlg.text_size}</label></td>
+			<td>
+				<table border="0" cellspacing="0" cellpadding="0">
+					<tr>
+						<td><select id="text_size" name="text_size" class="mceEditableSelect"></select></td>
+						<td> </td>
+      <td><select id="text_size_measurement" name="text_size_measurement"></select></td>
+					</tr>
+				</table>
+			</td>
+			<td><label for="text_weight">{#style_dlg.text_weight}</label></td>
+			<td>
+				<select id="text_weight" name="text_weight"></select>
+			</td>
+		</tr>
+		<tr>
+			<td><label for="text_style">{#style_dlg.text_style}</label></td>
+			<td>
+				<select id="text_style" name="text_style" class="mceEditableSelect"></select>
+			</td>
+			<td><label for="text_variant">{#style_dlg.text_variant}</label></td>
+			<td>
+				<select id="text_variant" name="text_variant"></select>
+			</td>
+		</tr>
+		<tr>
+			<td><label for="text_lineheight">{#style_dlg.text_lineheight}</label></td>
+			<td>
+				<table border="0" cellspacing="0" cellpadding="0">
+					<tr>
+						<td>
+							<select id="text_lineheight" name="text_lineheight" class="mceEditableSelect"></select>
+						</td>
+						<td> </td>
+						<td><select id="text_lineheight_measurement" name="text_lineheight_measurement"></select></td>
+					</tr>
+				</table>
+			</td>
+			<td><label for="text_case">{#style_dlg.text_case}</label></td>
+			<td>
+				<select id="text_case" name="text_case"></select>
+			</td>
+		</tr>
+		<tr>
+			<td><label for="text_color">{#style_dlg.text_color}</label></td>
+			<td colspan="2">
+				<table border="0" cellpadding="0" cellspacing="0">
+					<tr>
+						<td><input id="text_color" name="text_color" type="text" value="" size="9" onchange="updateColor('text_color_pick','text_color');" /></td>
+						<td id="text_color_pickcontainer"> </td>
+					</tr>
+				</table>
+			</td>
+		</tr>
+		<tr>
+			<td valign="top" style="vertical-align: top; padding-top: 3px;">{#style_dlg.text_decoration}</td>
+			<td colspan="2">
+				<table border="0" cellspacing="0" cellpadding="0">
+					<tr>
+						<td><input id="text_underline" name="text_underline" class="checkbox" type="checkbox" /></td>
+						<td><label for="text_underline">{#style_dlg.text_underline}</label></td>
+					</tr>
+					<tr>
+						<td><input id="text_overline" name="text_overline" class="checkbox" type="checkbox" /></td>
+						<td><label for="text_overline">{#style_dlg.text_overline}</label></td>
+					</tr>
+					<tr>
+						<td><input id="text_linethrough" name="text_linethrough" class="checkbox" type="checkbox" /></td>
+						<td><label for="text_linethrough">{#style_dlg.text_striketrough}</label></td>
+					</tr>
+					<tr>
+						<td><input id="text_blink" name="text_blink" class="checkbox" type="checkbox" /></td>
+						<td><label for="text_blink">{#style_dlg.text_blink}</label></td>
+					</tr>
+					<tr>
+						<td><input id="text_none" name="text_none" class="checkbox" type="checkbox" /></td>
+						<td><label for="text_none">{#style_dlg.text_none}</label></td>
+					</tr>
+				</table>
+			</td>
+		</tr>
+	</table>
+</div>
+
+<div id="background_panel" class="panel">
+	<table border="0">
+		<tr>
+			<td><label for="background_color">{#style_dlg.background_color}</label></td>
+			<td>
+				<table border="0" cellpadding="0" cellspacing="0">
+					<tr>
+						<td><input id="background_color" name="background_color" type="text" value="" size="9" onchange="updateColor('background_color_pick','background_color');" /></td>
+						<td id="background_color_pickcontainer"> </td>
+					</tr>
+				</table>
+			</td>
+		</tr>
+
+		<tr>
+			<td><label for="background_image">{#style_dlg.background_image}</label></td>
+			<td><table border="0" cellspacing="0" cellpadding="0">
+				<tr> 
+				  <td><input id="background_image" name="background_image" type="text" /></td> 
+				  <td id="background_image_browser"> </td>
+				</tr>
+				</table>
+			</td>
+		</tr>
+
+		<tr>
+			<td><label for="background_repeat">{#style_dlg.background_repeat}</label></td>
+			<td><select id="background_repeat" name="background_repeat" class="mceEditableSelect"></select></td>
+		</tr>
+
+		<tr>
+			<td><label for="background_attachment">{#style_dlg.background_attachment}</label></td>
+			<td><select id="background_attachment" name="background_attachment" class="mceEditableSelect"></select></td>
+		</tr>
+
+		<tr>
+			<td><label for="background_hpos">{#style_dlg.background_hpos}</label></td>
+			<td>
+				<table border="0" cellspacing="0" cellpadding="0">
+					<tr>
+						<td><select id="background_hpos" name="background_hpos" class="mceEditableSelect"></select></td>
+						<td> </td>
+						<td><select id="background_hpos_measurement" name="background_hpos_measurement"></select></td>
+					</tr>
+				</table>
+			</td>
+		</tr>
+
+		<tr>
+			<td><label for="background_vpos">{#style_dlg.background_vpos}</label></td>
+			<td>
+				<table border="0" cellspacing="0" cellpadding="0">
+					<tr>
+						<td><select id="background_vpos" name="background_vpos" class="mceEditableSelect"></select></td>
+						<td> </td>
+						<td><select id="background_vpos_measurement" name="background_vpos_measurement"></select></td>
+					</tr>
+				</table>
+			</td>
+		</tr>
+	</table>
+</div>
+
+<div id="block_panel" class="panel">
+	<table border="0">
+		<tr>
+			<td><label for="block_wordspacing">{#style_dlg.block_wordspacing}</label></td>
+			<td>
+				<table border="0" cellspacing="0" cellpadding="0">
+					<tr>
+						<td><select id="block_wordspacing" name="block_wordspacing" class="mceEditableSelect"></select></td>
+						<td> </td>
+						<td><select id="block_wordspacing_measurement" name="block_wordspacing_measurement"></select></td>
+					</tr>
+				</table>
+			</td>
+		</tr>
+
+		<tr>
+			<td><label for="block_letterspacing">{#style_dlg.block_letterspacing}</label></td>
+			<td>
+				<table border="0" cellspacing="0" cellpadding="0">
+					<tr>
+						<td><select id="block_letterspacing" name="block_letterspacing" class="mceEditableSelect"></select></td>
+						<td> </td>
+						<td><select id="block_letterspacing_measurement" name="block_letterspacing_measurement"></select></td>
+					</tr>
+				</table>
+			</td>
+		</tr>
+
+		<tr>
+			<td><label for="block_vertical_alignment">{#style_dlg.block_vertical_alignment}</label></td>
+			<td><select id="block_vertical_alignment" name="block_vertical_alignment" class="mceEditableSelect"></select></td>
+		</tr>
+
+		<tr>
+			<td><label for="block_text_align">{#style_dlg.block_text_align}</label></td>
+			<td><select id="block_text_align" name="block_text_align" class="mceEditableSelect"></select></td>
+		</tr>
+
+		<tr>
+			<td><label for="block_text_indent">{#style_dlg.block_text_indent}</label></td>
+			<td>
+				<table border="0" cellspacing="0" cellpadding="0">
+					<tr>
+						<td><input type="text" id="block_text_indent" name="block_text_indent" /></td>
+						<td> </td>
+						<td><select id="block_text_indent_measurement" name="block_text_indent_measurement"></select></td>
+					</tr>
+				</table>
+			</td>
+		</tr>
+
+		<tr>
+			<td><label for="block_whitespace">{#style_dlg.block_whitespace}</label></td>
+			<td><select id="block_whitespace" name="block_whitespace" class="mceEditableSelect"></select></td>
+		</tr>
+
+		<tr>
+			<td><label for="block_display">{#style_dlg.block_display}</label></td>
+			<td><select id="block_display" name="block_display" class="mceEditableSelect"></select></td>
+		</tr>
+	</table>
+</div>
+
+<div id="box_panel" class="panel">
+<table border="0">
+	<tr>
+		<td><label for="box_width">{#style_dlg.box_width}</label></td>
+		<td>
+			<table border="0" cellspacing="0" cellpadding="0">
+				<tr>
+					<td><input type="text" id="box_width" name="box_width" class="mceEditableSelect" onchange="synch('box_width','positioning_width');" /></td>
+					<td> </td>
+					<td><select id="box_width_measurement" name="box_width_measurement"></select></td>
+				</tr>
+			</table>
+		</td>
+		<td>   <label for="box_float">{#style_dlg.box_float}</label></td>
+		<td><select id="box_float" name="box_float" class="mceEditableSelect"></select></td>
+	</tr>
+
+	<tr>
+		<td><label for="box_height">{#style_dlg.box_height}</label></td>
+		<td>
+			<table border="0" cellspacing="0" cellpadding="0">
+				<tr>
+					<td><input type="text" id="box_height" name="box_height" class="mceEditableSelect" onchange="synch('box_height','positioning_height');" /></td>
+					<td> </td>
+					<td><select id="box_height_measurement" name="box_height_measurement"></select></td>
+				</tr>
+			</table>
+		</td>
+		<td>   <label for="box_clear">{#style_dlg.box_clear}</label></td>
+		<td><select id="box_clear" name="box_clear" class="mceEditableSelect"></select></td>
+	</tr>
+</table>
+<div style="float: left; width: 49%">
+	<fieldset>
+		<legend>{#style_dlg.padding}</legend>
+
+		<table border="0">
+			<tr>
+				<td> </td>
+				<td><input type="checkbox" id="box_padding_same" name="box_padding_same" class="checkbox" checked="checked" onClick="toggleSame(this,'box_padding');" /> <label for="box_padding_same">{#style_dlg.same}</label></td>
+			</tr>
+			<tr>
+				<td><label for="box_padding_top">{#style_dlg.top}</label></td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="box_padding_top" name="box_padding_top" class="mceEditableSelect" /></td>
+							<td> </td>
+							<td><select id="box_padding_top_measurement" name="box_padding_top_measurement"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td><label for="box_padding_right">{#style_dlg.right}</label></td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="box_padding_right" name="box_padding_right" class="mceEditableSelect" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="box_padding_right_measurement" name="box_padding_right_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td><label for="box_padding_bottom">{#style_dlg.bottom}</label></td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="box_padding_bottom" name="box_padding_bottom" class="mceEditableSelect" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="box_padding_bottom_measurement" name="box_padding_bottom_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td><label for="box_padding_left">{#style_dlg.left}</label></td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="box_padding_left" name="box_padding_left" class="mceEditableSelect" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="box_padding_left_measurement" name="box_padding_left_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+		</table>
+	</fieldset>
+</div>
+
+<div style="float: right; width: 49%">
+	<fieldset>
+		<legend>{#style_dlg.margin}</legend>
+
+		<table border="0">
+			<tr>
+				<td> </td>
+				<td><input type="checkbox" id="box_margin_same" name="box_margin_same" class="checkbox" checked="checked" onClick="toggleSame(this,'box_margin');" /> <label for="box_margin_same">{#style_dlg.same}</label></td>
+			</tr>
+			<tr>
+				<td><label for="box_margin_top">{#style_dlg.top}</label></td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="box_margin_top" name="box_margin_top" class="mceEditableSelect" /></td>
+							<td> </td>
+							<td><select id="box_margin_top_measurement" name="box_margin_top_measurement"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td><label for="box_margin_right">{#style_dlg.right}</label></td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="box_margin_right" name="box_margin_right" class="mceEditableSelect" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="box_margin_right_measurement" name="box_margin_right_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td><label for="box_margin_bottom">{#style_dlg.bottom}</label></td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="box_margin_bottom" name="box_margin_bottom" class="mceEditableSelect" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="box_margin_bottom_measurement" name="box_margin_bottom_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td><label for="box_margin_left">{#style_dlg.left}</label></td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="box_margin_left" name="box_margin_left" class="mceEditableSelect" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="box_margin_left_measurement" name="box_margin_left_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+		</table>
+	</fieldset>
+</div>
+<br style="clear: both" />
+</div>
+
+<div id="border_panel" class="panel">
+<table border="0" cellspacing="0" cellpadding="0" width="100%">
+<tr>
+	<td class="tdelim"> </td>
+	<td class="tdelim delim"> </td>
+	<td class="tdelim">{#style_dlg.style}</td>
+	<td class="tdelim delim"> </td>
+	<td class="tdelim">{#style_dlg.width}</td>
+	<td class="tdelim delim"> </td>
+	<td class="tdelim">{#style_dlg.color}</td>
+</tr>
+
+<tr>
+	<td> </td>
+	<td class="delim"> </td>
+	<td><input type="checkbox" id="border_style_same" name="border_style_same" class="checkbox" checked="checked" onClick="toggleSame(this,'border_style');" /> <label for="border_style_same">{#style_dlg.same}</label></td>
+	<td class="delim"> </td>
+	<td><input type="checkbox" id="border_width_same" name="border_width_same" class="checkbox" checked="checked" onClick="toggleSame(this,'border_width');" /> <label for="border_width_same">{#style_dlg.same}</label></td>
+	<td class="delim"> </td>
+	<td><input type="checkbox" id="border_color_same" name="border_color_same" class="checkbox" checked="checked" onClick="toggleSame(this,'border_color');" /> <label for="border_color_same">{#style_dlg.same}</label></td>
+</tr>
+
+<tr>
+	<td>{#style_dlg.top}</td>
+	<td class="delim"> </td>
+	<td><select id="border_style_top" name="border_style_top" class="mceEditableSelect"></select></td>
+	<td class="delim"> </td>
+	<td>
+		<table border="0" cellspacing="0" cellpadding="0">
+			<tr>
+				<td><select id="border_width_top" name="border_width_top" class="mceEditableSelect"></select></td>
+				<td> </td>
+				<td><select id="border_width_top_measurement" name="border_width_top_measurement"></select></td>
+			</tr>
+		</table>
+	</td>
+	<td class="delim"> </td>
+	<td>
+		<table border="0" cellpadding="0" cellspacing="0">
+			<tr>
+				<td><input id="border_color_top" name="border_color_top" type="text" value="" size="9" onchange="updateColor('border_color_top_pick','border_color_top');" /></td>
+				<td id="border_color_top_pickcontainer"> </td>
+			</tr>
+		</table>
+	</td>
+</tr>
+
+<tr>
+	<td>{#style_dlg.right}</td>
+	<td class="delim"> </td>
+	<td><select id="border_style_right" name="border_style_right" class="mceEditableSelect" disabled="disabled"></select></td>
+	<td class="delim"> </td>
+	<td>
+		<table border="0" cellspacing="0" cellpadding="0">
+			<tr>
+				<td><select id="border_width_right" name="border_width_right" class="mceEditableSelect" disabled="disabled"></select></td>
+				<td> </td>
+				<td><select id="border_width_right_measurement" name="border_width_right_measurement" disabled="disabled"></select></td>
+			</tr>
+		</table>
+	</td>
+	<td class="delim"> </td>
+	<td>
+		<table border="0" cellpadding="0" cellspacing="0">
+			<tr>
+				<td><input id="border_color_right" name="border_color_right" type="text" value="" size="9" onchange="updateColor('border_color_right_pick','border_color_right');" disabled="disabled" /></td>
+				<td id="border_color_right_pickcontainer"> </td>
+			</tr>
+		</table>
+	</td>
+</tr>
+
+<tr>
+	<td>{#style_dlg.bottom}</td>
+	<td class="delim"> </td>
+	<td><select id="border_style_bottom" name="border_style_bottom" class="mceEditableSelect" disabled="disabled"></select></td>
+	<td class="delim"> </td>
+	<td>
+		<table border="0" cellspacing="0" cellpadding="0">
+			<tr>
+				<td><select id="border_width_bottom" name="border_width_bottom" class="mceEditableSelect" disabled="disabled"></select></td>
+				<td> </td>
+				<td><select id="border_width_bottom_measurement" name="border_width_bottom_measurement" disabled="disabled"></select></td>
+			</tr>
+		</table>
+	</td>
+	<td class="delim"> </td>
+	<td>
+		<table border="0" cellpadding="0" cellspacing="0">
+			<tr>
+				<td><input id="border_color_bottom" name="border_color_bottom" type="text" value="" size="9" onchange="updateColor('border_color_bottom_pick','border_color_bottom');" disabled="disabled" /></td>
+				<td id="border_color_bottom_pickcontainer"> </td>
+			</tr>
+		</table>
+	</td>
+</tr>
+
+<tr>
+	<td>{#style_dlg.left}</td>
+	<td class="delim"> </td>
+	<td><select id="border_style_left" name="border_style_left" class="mceEditableSelect" disabled="disabled"></select></td>
+	<td class="delim"> </td>
+	<td>
+		<table border="0" cellspacing="0" cellpadding="0">
+			<tr>
+				<td><select id="border_width_left" name="border_width_left" class="mceEditableSelect" disabled="disabled"></select></td>
+				<td> </td>
+				<td><select id="border_width_left_measurement" name="border_width_left_measurement" disabled="disabled"></select></td>
+			</tr>
+		</table>
+	</td>
+	<td class="delim"> </td>
+	<td>
+		<table border="0" cellpadding="0" cellspacing="0">
+			<tr>
+				<td><input id="border_color_left" name="border_color_left" type="text" value="" size="9" onchange="updateColor('border_color_left_pick','border_color_left');" disabled="disabled" /></td>
+				<td id="border_color_left_pickcontainer"> </td>
+			</tr>
+		</table>
+	</td>
+</tr>
+</table>
+</div>
+
+<div id="list_panel" class="panel">
+	<table border="0">
+		<tr>
+			<td><label for="list_type">{#style_dlg.list_type}</label></td>
+			<td><select id="list_type" name="list_type" class="mceEditableSelect"></select></td>
+		</tr>
+
+		<tr>
+			<td><label for="list_bullet_image">{#style_dlg.bullet_image}</label></td>
+			<td><input id="list_bullet_image" name="list_bullet_image" type="text" /></td>
+		</tr>
+
+		<tr>
+			<td><label for="list_position">{#style_dlg.position}</label></td>
+			<td><select id="list_position" name="list_position" class="mceEditableSelect"></select></td>
+		</tr>
+	</table>
+</div>
+
+<div id="positioning_panel" class="panel">
+<table border="0">
+	<tr>
+		<td><label for="positioning_type">{#style_dlg.positioning_type}</label></td>
+		<td><select id="positioning_type" name="positioning_type" class="mceEditableSelect"></select></td>
+		<td>   <label for="positioning_visibility">{#style_dlg.visibility}</label></td>
+		<td><select id="positioning_visibility" name="positioning_visibility" class="mceEditableSelect"></select></td>
+	</tr>
+
+	<tr>
+		<td><label for="positioning_width">{#style_dlg.width}</label></td>
+		<td>
+			<table border="0" cellspacing="0" cellpadding="0">
+				<tr>
+					<td><input type="text" id="positioning_width" name="positioning_width" onchange="synch('positioning_width','box_width');" /></td>
+					<td> </td>
+					<td><select id="positioning_width_measurement" name="positioning_width_measurement"></select></td>
+				</tr>
+			</table>
+		</td>
+		<td>   <label for="positioning_zindex">{#style_dlg.zindex}</label></td>
+		<td><input type="text" id="positioning_zindex" name="positioning_zindex" /></td>
+	</tr>
+
+	<tr>
+		<td><label for="positioning_height">{#style_dlg.height}</label></td>
+		<td>
+			<table border="0" cellspacing="0" cellpadding="0">
+				<tr>
+					<td><input type="text" id="positioning_height" name="positioning_height" onchange="synch('positioning_height','box_height');" /></td>
+					<td> </td>
+					<td><select id="positioning_height_measurement" name="positioning_height_measurement"></select></td>
+				</tr>
+			</table>
+		</td>
+		<td>   <label for="positioning_overflow">{#style_dlg.overflow}</label></td>
+		<td><select id="positioning_overflow" name="positioning_overflow" class="mceEditableSelect"></select></td>
+	</tr>
+</table>
+
+<div style="float: left; width: 49%">
+	<fieldset>
+		<legend>{#style_dlg.placement}</legend>
+
+		<table border="0">
+			<tr>
+				<td> </td>
+				<td><input type="checkbox" id="positioning_placement_same" name="positioning_placement_same" class="checkbox" checked="checked" onClick="toggleSame(this,'positioning_placement');" /> <label for="positioning_placement_same">{#style_dlg.same}</label></td>
+			</tr>
+			<tr>
+				<td>{#style_dlg.top}</td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="positioning_placement_top" name="positioning_placement_top" /></td>
+							<td> </td>
+							<td><select id="positioning_placement_top_measurement" name="positioning_placement_top_measurement"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td>{#style_dlg.right}</td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="positioning_placement_right" name="positioning_placement_right" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="positioning_placement_right_measurement" name="positioning_placement_right_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td>{#style_dlg.bottom}</td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="positioning_placement_bottom" name="positioning_placement_bottom" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="positioning_placement_bottom_measurement" name="positioning_placement_bottom_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td>{#style_dlg.left}</td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="positioning_placement_left" name="positioning_placement_left" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="positioning_placement_left_measurement" name="positioning_placement_left_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+		</table>
+	</fieldset>
+</div>
+
+<div style="float: right; width: 49%">
+	<fieldset>
+		<legend>{#style_dlg.clip}</legend>
+
+		<table border="0">
+			<tr>
+				<td> </td>
+				<td><input type="checkbox" id="positioning_clip_same" name="positioning_clip_same" class="checkbox" checked="checked" onClick="toggleSame(this,'positioning_clip');" /> <label for="positioning_clip_same">{#style_dlg.same}</label></td>
+			</tr>
+			<tr>
+				<td>{#style_dlg.top}</td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="positioning_clip_top" name="positioning_clip_top" /></td>
+							<td> </td>
+							<td><select id="positioning_clip_top_measurement" name="positioning_clip_top_measurement"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td>{#style_dlg.right}</td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="positioning_clip_right" name="positioning_clip_right" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="positioning_clip_right_measurement" name="positioning_clip_right_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td>{#style_dlg.bottom}</td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="positioning_clip_bottom" name="positioning_clip_bottom" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="positioning_clip_bottom_measurement" name="positioning_clip_bottom_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+			<tr>
+				<td>{#style_dlg.left}</td>
+				<td>
+					<table border="0" cellspacing="0" cellpadding="0">
+						<tr>
+							<td><input type="text" id="positioning_clip_left" name="positioning_clip_left" disabled="disabled" /></td>
+							<td> </td>
+							<td><select id="positioning_clip_left_measurement" name="positioning_clip_left_measurement" disabled="disabled"></select></td>
+						</tr>
+					</table>
+				</td>
+			</tr>
+		</table>
+	</fieldset>
+</div>
+<br style="clear: both" />
+</div>
+</div>
+
+<div class="mceActionPanel">
+	<div style="float: left">
+		<div style="float: left"><input type="submit" id="insert" name="insert" value="{#update}" /></div>
+
+		<div style="float: left"> <input type="button" class="button" id="apply" name="apply" value="{#style_dlg.apply}" onClick="applyAction();" /></div>
+		<br style="clear: both" />
+	</div>
+
+	<div style="float: right">
+		<input type="button" id="cancel" name="cancel" value="{#cancel}" onClick="tinyMCEPopup.close();" />
+	</div>
+</div>
+</form>
+
+<div style="display: none">
+	<div id="container"></div>
+</div>
+
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/tabfocus/editor_plugin.js b/media/js/tinymce/plugins/tabfocus/editor_plugin.js
new file mode 100644
index 0000000..7f1fe26
--- /dev/null
+++ b/media/js/tinymce/plugins/tabfocus/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var c=tinymce.DOM,a=tinymce.dom.Event,d=tinymce.each,b=tinymce.explode;tinymce.create("tinymce.plugins.TabFocusPlugin",{init:function(f,g){function e(i,j){if(j.keyCode===9){return a.cancel(j)}}function h(l,p){var j,m,o,n,k;function q(i){o=c.getParent(l.id,"form");n=o.elements;if(o){d(n,function(s,r){if(s.id==l.id){j=r;return false}});if(i>0){for(m=j+1;m<n.length;m++){if(n[m].type!="hidden"){return n[m]}}}else{for(m=j-1;m>=0;m--){if(n[m].type!="hidden"){return n[m]}}}}return n [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/tabfocus/editor_plugin_src.js b/media/js/tinymce/plugins/tabfocus/editor_plugin_src.js
new file mode 100644
index 0000000..0fa8d81
--- /dev/null
+++ b/media/js/tinymce/plugins/tabfocus/editor_plugin_src.js
@@ -0,0 +1,109 @@
+/**
+ * $Id: editor_plugin_src.js 787 2008-04-10 11:40:57Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, explode = tinymce.explode;
+
+	tinymce.create('tinymce.plugins.TabFocusPlugin', {
+		init : function(ed, url) {
+			function tabCancel(ed, e) {
+				if (e.keyCode === 9)
+					return Event.cancel(e);
+			};
+
+			function tabHandler(ed, e) {
+				var x, i, f, el, v;
+
+				function find(d) {
+					f = DOM.getParent(ed.id, 'form');
+					el = f.elements;
+
+					if (f) {
+						each(el, function(e, i) {
+							if (e.id == ed.id) {
+								x = i;
+								return false;
+							}
+						});
+
+						if (d > 0) {
+							for (i = x + 1; i < el.length; i++) {
+								if (el[i].type != 'hidden')
+									return el[i];
+							}
+						} else {
+							for (i = x - 1; i >= 0; i--) {
+								if (el[i].type != 'hidden')
+									return el[i];
+							}
+						}
+					}
+
+					return null;
+				};
+
+				if (e.keyCode === 9) {
+					v = explode(ed.getParam('tab_focus', ed.getParam('tabfocus_elements', ':prev,:next')));
+
+					if (v.length == 1) {
+						v[1] = v[0];
+						v[0] = ':prev';
+					}
+
+					// Find element to focus
+					if (e.shiftKey) {
+						if (v[0] == ':prev')
+							el = find(-1);
+						else
+							el = DOM.get(v[0]);
+					} else {
+						if (v[1] == ':next')
+							el = find(1);
+						else
+							el = DOM.get(v[1]);
+					}
+
+					if (el) {
+						if (ed = tinymce.EditorManager.get(el.id || el.name))
+							ed.focus();
+						else
+							window.setTimeout(function() {window.focus();el.focus();}, 10);
+
+						return Event.cancel(e);
+					}
+				}
+			};
+
+			ed.onKeyUp.add(tabCancel);
+
+			if (tinymce.isGecko) {
+				ed.onKeyPress.add(tabHandler);
+				ed.onKeyDown.add(tabCancel);
+			} else
+				ed.onKeyDown.add(tabHandler);
+
+			ed.onInit.add(function() {
+				each(DOM.select('a:first,a:last', ed.getContainer()), function(n) {
+					Event.add(n, 'focus', function() {ed.focus();});
+				});
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Tabfocus',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/tabfocus',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('tabfocus', tinymce.plugins.TabFocusPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/table/cell.htm b/media/js/tinymce/plugins/table/cell.htm
new file mode 100644
index 0000000..1fabc8d
--- /dev/null
+++ b/media/js/tinymce/plugins/table/cell.htm
@@ -0,0 +1,183 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#table_dlg.cell_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/cell.js"></script>
+	<link href="css/cell.css" rel="stylesheet" type="text/css" />
+</head>
+<body id="tablecell" style="display: none">
+	<form onsubmit="updateAction();return false;" action="#">
+		<div class="tabs">
+			<ul>
+				<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#table_dlg.general_tab}</a></span></li>
+				<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#table_dlg.advanced_tab}</a></span></li>
+			</ul>
+		</div>
+
+		<div class="panel_wrapper">
+			<div id="general_panel" class="panel current">
+				<fieldset>
+					<legend>{#table_dlg.general_props}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td><label for="align">{#table_dlg.align}</label></td>
+							<td>
+								<select id="align" name="align" class="mceFocus">
+									<option value="">{#not_set}</option>
+									<option value="center">{#table_dlg.align_middle}</option>
+									<option value="left">{#table_dlg.align_left}</option>
+									<option value="right">{#table_dlg.align_right}</option>
+								</select>
+							</td>
+		
+							<td><label for="celltype">{#table_dlg.cell_type}</label></td>
+							<td>
+								<select id="celltype" name="celltype">
+									<option value="td">{#table_dlg.td}</option>
+									<option value="th">{#table_dlg.th}</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="valign">{#table_dlg.valign}</label></td>
+							<td>
+								<select id="valign" name="valign">
+									<option value="">{#not_set}</option>
+									<option value="top">{#table_dlg.align_top}</option>
+									<option value="middle">{#table_dlg.align_middle}</option>
+									<option value="bottom">{#table_dlg.align_bottom}</option>
+								</select>
+							</td>
+
+							<td><label for="scope">{#table_dlg.scope}</label></td>
+							<td>
+								<select id="scope" name="scope">
+									<option value="">{#not_set}</option>
+									<option value="col">{#table.col}</option>
+									<option value="row">{#table.row}</option>
+									<option value="rowgroup">{#table_dlg.rowgroup}</option>
+									<option value="colgroup">{#table_dlg.colgroup}</option>
+								</select>
+							</td>
+
+						</tr>
+
+						<tr>
+							<td><label for="width">{#table_dlg.width}</label></td>
+							<td><input id="width" name="width" type="text" value="" size="4" maxlength="4" onchange="changedSize();" /></td>
+
+							<td><label for="height">{#table_dlg.height}</label></td>
+							<td><input id="height" name="height" type="text" value="" size="4" maxlength="4" onchange="changedSize();" /></td>
+						</tr>
+
+						<tr id="styleSelectRow">
+							<td><label for="class">{#class_name}</label></td>
+							<td colspan="3">
+								<select id="class" name="class" class="mceEditableSelect">
+									<option value="" selected="selected">{#not_set}</option>
+								</select>
+							</td>
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+
+			<div id="advanced_panel" class="panel">
+				<fieldset>
+					<legend>{#table_dlg.advanced_props}</legend>
+
+					<table border="0" cellpadding="0" cellspacing="4">
+						<tr>
+							<td class="column1"><label for="id">{#table_dlg.id}</label></td> 
+							<td><input id="id" name="id" type="text" value="" style="width: 200px" /></td> 
+						</tr>
+
+						<tr>
+							<td><label for="style">{#table_dlg.style}</label></td>
+							<td><input type="text" id="style" name="style" value="" style="width: 200px;" onchange="changedStyle();" /></td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="dir">{#table_dlg.langdir}</label></td> 
+							<td>
+								<select id="dir" name="dir" style="width: 200px"> 
+										<option value="">{#not_set}</option> 
+										<option value="ltr">{#table_dlg.ltr}</option> 
+										<option value="rtl">{#table_dlg.rtl}</option> 
+								</select>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="lang">{#table_dlg.langcode}</label></td> 
+							<td>
+								<input id="lang" name="lang" type="text" value="" style="width: 200px" />
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="backgroundimage">{#table_dlg.bgimage}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="backgroundimage" name="backgroundimage" type="text" value="" style="width: 200px" onchange="changedBackgroundImage();" /></td>
+										<td id="backgroundimagebrowsercontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="bordercolor">{#table_dlg.bordercolor}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="bordercolor" name="bordercolor" type="text" value="" size="9" onchange="updateColor('bordercolor_pick','bordercolor');changedColor();" /></td>
+										<td id="bordercolor_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="bgcolor">{#table_dlg.bgcolor}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="bgcolor" name="bgcolor" type="text" value="" size="9" onchange="updateColor('bgcolor_pick','bgcolor');changedColor();" /></td>
+										<td id="bgcolor_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+		</div>
+
+		<div class="mceActionPanel">
+			<div>
+				<select id="action" name="action">
+					<option value="cell">{#table_dlg.cell_cell}</option>
+					<option value="row">{#table_dlg.cell_row}</option>
+					<option value="all">{#table_dlg.cell_all}</option>
+				</select>
+			</div>
+
+			<div style="float: left">
+				<div><input type="submit" id="insert" name="insert" value="{#update}" /></div>
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+	</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/table/css/cell.css b/media/js/tinymce/plugins/table/css/cell.css
new file mode 100644
index 0000000..a067ecd
--- /dev/null
+++ b/media/js/tinymce/plugins/table/css/cell.css
@@ -0,0 +1,17 @@
+/* CSS file for cell dialog in the table plugin */
+
+.panel_wrapper div.current {
+	height: 200px;
+}
+
+.advfield {
+	width: 200px;
+}
+
+#action {
+	margin-bottom: 3px;
+}
+
+#class {
+	width: 150px;
+}
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/table/css/row.css b/media/js/tinymce/plugins/table/css/row.css
new file mode 100644
index 0000000..1f7755d
--- /dev/null
+++ b/media/js/tinymce/plugins/table/css/row.css
@@ -0,0 +1,25 @@
+/* CSS file for row dialog in the table plugin */
+
+.panel_wrapper div.current {
+	height: 200px;
+}
+
+.advfield {
+	width: 200px;
+}
+
+#action {
+	margin-bottom: 3px;
+}
+
+#rowtype,#align,#valign,#class,#height {
+	width: 150px;
+}
+
+#height {
+	width: 50px;	
+}
+
+.col2 {
+	padding-left: 20px;
+}
diff --git a/media/js/tinymce/plugins/table/css/table.css b/media/js/tinymce/plugins/table/css/table.css
new file mode 100644
index 0000000..d11c3f6
--- /dev/null
+++ b/media/js/tinymce/plugins/table/css/table.css
@@ -0,0 +1,13 @@
+/* CSS file for table dialog in the table plugin */
+
+.panel_wrapper div.current {
+	height: 245px;
+}
+
+.advfield {
+	width: 200px;
+}
+
+#class {
+	width: 150px;
+}
diff --git a/media/js/tinymce/plugins/table/editor_plugin.js b/media/js/tinymce/plugins/table/editor_plugin.js
new file mode 100644
index 0000000..806ef28
--- /dev/null
+++ b/media/js/tinymce/plugins/table/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var b=tinymce.each;function a(d,e){var f=e.ownerDocument,c=f.createRange(),g;c.setStartBefore(e);c.setEnd(d.endContainer,d.endOffset);g=f.createElement("body");g.appendChild(c.cloneContents());return g.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi,"-").replace(/<[^>]+>/g,"").length==0}tinymce.create("tinymce.plugins.TablePlugin",{init:function(c,d){var e=this;e.editor=c;e.url=d;b([["table","table.desc","mceInsertTable",true],["delete_table","table.del","mc [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/table/editor_plugin_src.js b/media/js/tinymce/plugins/table/editor_plugin_src.js
new file mode 100644
index 0000000..87b1055
--- /dev/null
+++ b/media/js/tinymce/plugins/table/editor_plugin_src.js
@@ -0,0 +1,1202 @@
+/**
+ * $Id: editor_plugin_src.js 1209 2009-08-20 12:35:10Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var each = tinymce.each;
+
+	// Checks if the selection/caret is at the start of the specified block element
+	function isAtStart(rng, par) {
+		var doc = par.ownerDocument, rng2 = doc.createRange(), elm;
+
+		rng2.setStartBefore(par);
+		rng2.setEnd(rng.endContainer, rng.endOffset);
+
+		elm = doc.createElement('body');
+		elm.appendChild(rng2.cloneContents());
+
+		// Check for text characters of other elements that should be treated as content
+		return elm.innerHTML.replace(/<(br|img|object|embed|input|textarea)[^>]*>/gi, '-').replace(/<[^>]+>/g, '').length == 0;
+	};
+
+	tinymce.create('tinymce.plugins.TablePlugin', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+			t.url = url;
+
+			// Register buttons
+			each([
+				['table', 'table.desc', 'mceInsertTable', true],
+				['delete_table', 'table.del', 'mceTableDelete'],
+				['delete_col', 'table.delete_col_desc', 'mceTableDeleteCol'],
+				['delete_row', 'table.delete_row_desc', 'mceTableDeleteRow'],
+				['col_after', 'table.col_after_desc', 'mceTableInsertColAfter'],
+				['col_before', 'table.col_before_desc', 'mceTableInsertColBefore'],
+				['row_after', 'table.row_after_desc', 'mceTableInsertRowAfter'],
+				['row_before', 'table.row_before_desc', 'mceTableInsertRowBefore'],
+				['row_props', 'table.row_desc', 'mceTableRowProps', true],
+				['cell_props', 'table.cell_desc', 'mceTableCellProps', true],
+				['split_cells', 'table.split_cells_desc', 'mceTableSplitCells', true],
+				['merge_cells', 'table.merge_cells_desc', 'mceTableMergeCells', true]
+			], function(c) {
+				ed.addButton(c[0], {title : c[1], cmd : c[2], ui : c[3]});
+			});
+
+			if (ed.getParam('inline_styles')) {
+				// Force move of attribs to styles in strict mode
+				ed.onPreProcess.add(function(ed, o) {
+					var dom = ed.dom;
+
+					each(dom.select('table', o.node), function(n) {
+						var v;
+
+						if (v = dom.getAttrib(n, 'width')) {
+							dom.setStyle(n, 'width', v);
+							dom.setAttrib(n, 'width');
+						}
+
+						if (v = dom.getAttrib(n, 'height')) {
+							dom.setStyle(n, 'height', v);
+							dom.setAttrib(n, 'height');
+						}
+					});
+				});
+			}
+
+			ed.onInit.add(function() {
+				// Fixes an issue on Gecko where it's impossible to place the caret behind a table
+				// This fix will force a paragraph element after the table but only when the forced_root_block setting is enabled
+				if (!tinymce.isIE && ed.getParam('forced_root_block')) {
+					function fixTableCaretPos() {
+						var last = ed.getBody().lastChild;
+
+						if (last && last.nodeName == 'TABLE')
+							ed.dom.add(ed.getBody(), 'p', null, '<br mce_bogus="1" />');
+					};
+
+					// Fixes an bug where it's impossible to place the caret before a table in Gecko
+					// this fix solves it by detecting when the caret is at the beginning of such a table
+					// and then manually moves the caret infront of the table
+					if (tinymce.isGecko) {
+						ed.onKeyDown.add(function(ed, e) {
+							var rng, table, dom = ed.dom;
+
+							// On gecko it's not possible to place the caret before a table
+							if (e.keyCode == 37 || e.keyCode == 38) {
+								rng = ed.selection.getRng();
+								table = dom.getParent(rng.startContainer, 'table');
+
+								if (table && ed.getBody().firstChild == table) {
+									if (isAtStart(rng, table)) {
+										rng = dom.createRng();
+
+										rng.setStartBefore(table);
+										rng.setEndBefore(table);
+
+										ed.selection.setRng(rng);
+
+										e.preventDefault();
+									}
+								}
+							}
+						});
+					}
+
+					ed.onKeyUp.add(fixTableCaretPos);
+					ed.onSetContent.add(fixTableCaretPos);
+					ed.onVisualAid.add(fixTableCaretPos);
+
+					ed.onPreProcess.add(function(ed, o) {
+						var last = o.node.lastChild;
+
+						if (last && last.childNodes.length == 1 && last.firstChild.nodeName == 'BR')
+							ed.dom.remove(last);
+					});
+
+					fixTableCaretPos();
+				}
+
+				if (ed && ed.plugins.contextmenu) {
+					ed.plugins.contextmenu.onContextMenu.add(function(th, m, e) {
+						var sm, se = ed.selection, el = se.getNode() || ed.getBody();
+
+						if (ed.dom.getParent(e, 'td') || ed.dom.getParent(e, 'th')) {
+							m.removeAll();
+
+							if (el.nodeName == 'A' && !ed.dom.getAttrib(el, 'name')) {
+								m.add({title : 'advanced.link_desc', icon : 'link', cmd : ed.plugins.advlink ? 'mceAdvLink' : 'mceLink', ui : true});
+								m.add({title : 'advanced.unlink_desc', icon : 'unlink', cmd : 'UnLink'});
+								m.addSeparator();
+							}
+
+							if (el.nodeName == 'IMG' && el.className.indexOf('mceItem') == -1) {
+								m.add({title : 'advanced.image_desc', icon : 'image', cmd : ed.plugins.advimage ? 'mceAdvImage' : 'mceImage', ui : true});
+								m.addSeparator();
+							}
+
+							m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', ui : true, value : {action : 'insert'}});
+							m.add({title : 'table.props_desc', icon : 'table_props', cmd : 'mceInsertTable', ui : true});
+							m.add({title : 'table.del', icon : 'delete_table', cmd : 'mceTableDelete', ui : true});
+							m.addSeparator();
+
+							// Cell menu
+							sm = m.addMenu({title : 'table.cell'});
+							sm.add({title : 'table.cell_desc', icon : 'cell_props', cmd : 'mceTableCellProps', ui : true});
+							sm.add({title : 'table.split_cells_desc', icon : 'split_cells', cmd : 'mceTableSplitCells', ui : true});
+							sm.add({title : 'table.merge_cells_desc', icon : 'merge_cells', cmd : 'mceTableMergeCells', ui : true});
+
+							// Row menu
+							sm = m.addMenu({title : 'table.row'});
+							sm.add({title : 'table.row_desc', icon : 'row_props', cmd : 'mceTableRowProps', ui : true});
+							sm.add({title : 'table.row_before_desc', icon : 'row_before', cmd : 'mceTableInsertRowBefore'});
+							sm.add({title : 'table.row_after_desc', icon : 'row_after', cmd : 'mceTableInsertRowAfter'});
+							sm.add({title : 'table.delete_row_desc', icon : 'delete_row', cmd : 'mceTableDeleteRow'});
+							sm.addSeparator();
+							sm.add({title : 'table.cut_row_desc', icon : 'cut', cmd : 'mceTableCutRow'});
+							sm.add({title : 'table.copy_row_desc', icon : 'copy', cmd : 'mceTableCopyRow'});
+							sm.add({title : 'table.paste_row_before_desc', icon : 'paste', cmd : 'mceTablePasteRowBefore'});
+							sm.add({title : 'table.paste_row_after_desc', icon : 'paste', cmd : 'mceTablePasteRowAfter'});
+
+							// Column menu
+							sm = m.addMenu({title : 'table.col'});
+							sm.add({title : 'table.col_before_desc', icon : 'col_before', cmd : 'mceTableInsertColBefore'});
+							sm.add({title : 'table.col_after_desc', icon : 'col_after', cmd : 'mceTableInsertColAfter'});
+							sm.add({title : 'table.delete_col_desc', icon : 'delete_col', cmd : 'mceTableDeleteCol'});
+						} else
+							m.add({title : 'table.desc', icon : 'table', cmd : 'mceInsertTable', ui : true});
+					});
+				}
+			});
+
+			// Add undo level when new rows are created using the tab key
+			ed.onKeyDown.add(function(ed, e) {
+				if (e.keyCode == 9 && ed.dom.getParent(ed.selection.getNode(), 'TABLE')) {
+					if (!tinymce.isGecko && !tinymce.isOpera) {
+						tinyMCE.execInstanceCommand(ed.editorId, "mceTableMoveToNextRow", true);
+						return tinymce.dom.Event.cancel(e);
+					}
+
+					ed.undoManager.add();
+				}
+			});
+
+			// Select whole table is a table border is clicked
+			if (!tinymce.isIE) {
+				if (ed.getParam('table_selection', true)) {
+					ed.onClick.add(function(ed, e) {
+						e = e.target;
+
+						if (e.nodeName === 'TABLE')
+							ed.selection.select(e);
+					});
+				}
+			}
+
+			ed.onNodeChange.add(function(ed, cm, n) {
+				var p = ed.dom.getParent(n, 'td,th,caption');
+
+				cm.setActive('table', n.nodeName === 'TABLE' || !!p);
+				if (p && p.nodeName === 'CAPTION')
+					p = null;
+
+				cm.setDisabled('delete_table', !p);
+				cm.setDisabled('delete_col', !p);
+				cm.setDisabled('delete_table', !p);
+				cm.setDisabled('delete_row', !p);
+				cm.setDisabled('col_after', !p);
+				cm.setDisabled('col_before', !p);
+				cm.setDisabled('row_after', !p);
+				cm.setDisabled('row_before', !p);
+				cm.setDisabled('row_props', !p);
+				cm.setDisabled('cell_props', !p);
+				cm.setDisabled('split_cells', !p || (parseInt(ed.dom.getAttrib(p, 'colspan', '1')) < 2 && parseInt(ed.dom.getAttrib(p, 'rowspan', '1')) < 2));
+				cm.setDisabled('merge_cells', !p);
+			});
+
+			// Padd empty table cells
+			if (!tinymce.isIE) {
+				ed.onBeforeSetContent.add(function(ed, o) {
+					if (o.initial)
+						o.content = o.content.replace(/<(td|th)([^>]+|)>\s*<\/(td|th)>/g, tinymce.isOpera ? '<$1$2> </$1>' : '<$1$2><br mce_bogus="1" /></$1>');
+				});
+			}
+		},
+
+		execCommand : function(cmd, ui, val) {
+			var ed = this.editor, b;
+
+			// Is table command
+			switch (cmd) {
+				case "mceTableMoveToNextRow":
+				case "mceInsertTable":
+				case "mceTableRowProps":
+				case "mceTableCellProps":
+				case "mceTableSplitCells":
+				case "mceTableMergeCells":
+				case "mceTableInsertRowBefore":
+				case "mceTableInsertRowAfter":
+				case "mceTableDeleteRow":
+				case "mceTableInsertColBefore":
+				case "mceTableInsertColAfter":
+				case "mceTableDeleteCol":
+				case "mceTableCutRow":
+				case "mceTableCopyRow":
+				case "mceTablePasteRowBefore":
+				case "mceTablePasteRowAfter":
+				case "mceTableDelete":
+					ed.execCommand('mceBeginUndoLevel');
+					this._doExecCommand(cmd, ui, val);
+					ed.execCommand('mceEndUndoLevel');
+
+					return true;
+			}
+
+			// Pass to next handler in chain
+			return false;
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Tables',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/table',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private plugin internal methods
+
+		/**
+		 * Executes the table commands.
+		 */
+		_doExecCommand : function(command, user_interface, value) {
+			var inst = this.editor, ed = inst, url = this.url;
+			var focusElm = inst.selection.getNode();
+			var trElm = inst.dom.getParent(focusElm, "tr");
+			var tdElm = inst.dom.getParent(focusElm, "td,th");
+			var tableElm = inst.dom.getParent(focusElm, "table");
+			var doc = inst.contentWindow.document;
+			var tableBorder = tableElm ? tableElm.getAttribute("border") : "";
+
+			// Get first TD if no TD found
+			if (trElm && tdElm == null)
+				tdElm = trElm.cells[0];
+
+			function inArray(ar, v) {
+				for (var i=0; i<ar.length; i++) {
+					// Is array
+					if (ar[i].length > 0 && inArray(ar[i], v))
+						return true;
+
+					// Found value
+					if (ar[i] == v)
+						return true;
+				}
+
+				return false;
+			}
+
+			function select(dx, dy) {
+				var td;
+
+				grid = getTableGrid(tableElm);
+				dx = dx || 0;
+				dy = dy || 0;
+				dx = Math.max(cpos.cellindex + dx, 0);
+				dy = Math.max(cpos.rowindex + dy, 0);
+
+				// Recalculate grid and select
+				inst.execCommand('mceRepaint');
+				td = getCell(grid, dy, dx);
+
+				if (td) {
+					inst.selection.select(td.firstChild || td);
+					inst.selection.collapse(1);
+				}
+			};
+
+			function makeTD() {
+				var newTD = doc.createElement("td");
+
+				if (!tinymce.isIE)
+					newTD.innerHTML = '<br mce_bogus="1"/>';
+			}
+
+			function getColRowSpan(td) {
+				var colspan = inst.dom.getAttrib(td, "colspan");
+				var rowspan = inst.dom.getAttrib(td, "rowspan");
+
+				colspan = colspan == "" ? 1 : parseInt(colspan);
+				rowspan = rowspan == "" ? 1 : parseInt(rowspan);
+
+				return {colspan : colspan, rowspan : rowspan};
+			}
+
+			function getCellPos(grid, td) {
+				var x, y;
+
+				for (y=0; y<grid.length; y++) {
+					for (x=0; x<grid[y].length; x++) {
+						if (grid[y][x] == td)
+							return {cellindex : x, rowindex : y};
+					}
+				}
+
+				return null;
+			}
+
+			function getCell(grid, row, col) {
+				if (grid[row] && grid[row][col])
+					return grid[row][col];
+
+				return null;
+			}
+
+			function getNextCell(table, cell) {
+				var cells = [], x = 0, i, j, cell, nextCell;
+
+				for (i = 0; i < table.rows.length; i++)
+					for (j = 0; j < table.rows[i].cells.length; j++, x++)
+						cells[x] = table.rows[i].cells[j];
+
+				for (i = 0; i < cells.length; i++)
+					if (cells[i] == cell)
+						if (nextCell = cells[i+1])
+							return nextCell;
+			}
+
+			function getTableGrid(table) {
+				var grid = [], rows = table.rows, x, y, td, sd, xstart, x2, y2;
+
+				for (y=0; y<rows.length; y++) {
+					for (x=0; x<rows[y].cells.length; x++) {
+						td = rows[y].cells[x];
+						sd = getColRowSpan(td);
+
+						// All ready filled
+						for (xstart = x; grid[y] && grid[y][xstart]; xstart++) ;
+
+						// Fill box
+						for (y2=y; y2<y+sd['rowspan']; y2++) {
+							if (!grid[y2])
+								grid[y2] = [];
+
+							for (x2=xstart; x2<xstart+sd['colspan']; x2++)
+								grid[y2][x2] = td;
+						}
+					}
+				}
+
+				return grid;
+			}
+
+			function trimRow(table, tr, td, new_tr) {
+				var grid = getTableGrid(table), cpos = getCellPos(grid, td);
+				var cells, lastElm;
+
+				// Time to crop away some
+				if (new_tr.cells.length != tr.childNodes.length) {
+					cells = tr.childNodes;
+					lastElm = null;
+
+					for (var x=0; td = getCell(grid, cpos.rowindex, x); x++) {
+						var remove = true;
+						var sd = getColRowSpan(td);
+
+						// Remove due to rowspan
+						if (inArray(cells, td)) {
+							new_tr.childNodes[x]._delete = true;
+						} else if ((lastElm == null || td != lastElm) && sd.colspan > 1) { // Remove due to colspan
+							for (var i=x; i<x+td.colSpan; i++)
+								new_tr.childNodes[i]._delete = true;
+						}
+
+						if ((lastElm == null || td != lastElm) && sd.rowspan > 1)
+							td.rowSpan = sd.rowspan + 1;
+
+						lastElm = td;
+					}
+
+					deleteMarked(tableElm);
+				}
+			}
+
+			function prevElm(node, name) {
+				while ((node = node.previousSibling) != null) {
+					if (node.nodeName == name)
+						return node;
+				}
+
+				return null;
+			}
+
+			function nextElm(node, names) {
+				var namesAr = names.split(',');
+
+				while ((node = node.nextSibling) != null) {
+					for (var i=0; i<namesAr.length; i++) {
+						if (node.nodeName.toLowerCase() == namesAr[i].toLowerCase() )
+							return node;
+					}
+				}
+
+				return null;
+			}
+
+			function deleteMarked(tbl) {
+				if (tbl.rows == 0)
+					return;
+
+				var tr = tbl.rows[0];
+				do {
+					var next = nextElm(tr, "TR");
+
+					// Delete row
+					if (tr._delete) {
+						tr.parentNode.removeChild(tr);
+						continue;
+					}
+
+					// Delete cells
+					var td = tr.cells[0];
+					if (td.cells > 1) {
+						do {
+							var nexttd = nextElm(td, "TD,TH");
+
+							if (td._delete)
+								td.parentNode.removeChild(td);
+						} while ((td = nexttd) != null);
+					}
+				} while ((tr = next) != null);
+			}
+
+			function addRows(td_elm, tr_elm, rowspan) {
+				// Add rows
+				td_elm.rowSpan = 1;
+				var trNext = nextElm(tr_elm, "TR");
+				for (var i=1; i<rowspan && trNext; i++) {
+					var newTD = doc.createElement("td");
+
+					if (!tinymce.isIE)
+						newTD.innerHTML = '<br mce_bogus="1"/>';
+
+					if (tinymce.isIE)
+						trNext.insertBefore(newTD, trNext.cells(td_elm.cellIndex));
+					else
+						trNext.insertBefore(newTD, trNext.cells[td_elm.cellIndex]);
+
+					trNext = nextElm(trNext, "TR");
+				}
+			}
+
+			function copyRow(doc, table, tr) {
+				var grid = getTableGrid(table);
+				var newTR = tr.cloneNode(false);
+				var cpos = getCellPos(grid, tr.cells[0]);
+				var lastCell = null;
+				var tableBorder = inst.dom.getAttrib(table, "border");
+				var tdElm = null;
+
+				for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
+					var newTD = null;
+
+					if (lastCell != tdElm) {
+						for (var i=0; i<tr.cells.length; i++) {
+							if (tdElm == tr.cells[i]) {
+								newTD = tdElm.cloneNode(true);
+								break;
+							}
+						}
+					}
+
+					if (newTD == null) {
+						newTD = doc.createElement("td");
+
+						if (!tinymce.isIE)
+							newTD.innerHTML = '<br mce_bogus="1"/>';
+					}
+
+					// Reset col/row span
+					newTD.colSpan = 1;
+					newTD.rowSpan = 1;
+
+					newTR.appendChild(newTD);
+
+					lastCell = tdElm;
+				}
+
+				return newTR;
+			}
+
+			// ---- Commands -----
+
+			// Handle commands
+			switch (command) {
+				case "mceTableMoveToNextRow":
+					var nextCell = getNextCell(tableElm, tdElm);
+
+					if (!nextCell) {
+						inst.execCommand("mceTableInsertRowAfter", tdElm);
+						nextCell = getNextCell(tableElm, tdElm);
+					}
+
+					inst.selection.select(nextCell);
+					inst.selection.collapse(true);
+
+					return true;
+
+				case "mceTableRowProps":
+					if (trElm == null)
+						return true;
+
+					if (user_interface) {
+						inst.windowManager.open({
+							url : url + '/row.htm',
+							width : 400 + parseInt(inst.getLang('table.rowprops_delta_width', 0)),
+							height : 295 + parseInt(inst.getLang('table.rowprops_delta_height', 0)),
+							inline : 1
+						}, {
+							plugin_url : url
+						});
+					}
+
+					return true;
+
+				case "mceTableCellProps":
+					if (tdElm == null)
+						return true;
+
+					if (user_interface) {
+						inst.windowManager.open({
+							url : url + '/cell.htm',
+							width : 400 + parseInt(inst.getLang('table.cellprops_delta_width', 0)),
+							height : 295 + parseInt(inst.getLang('table.cellprops_delta_height', 0)),
+							inline : 1
+						}, {
+							plugin_url : url
+						});
+					}
+
+					return true;
+
+				case "mceInsertTable":
+					if (user_interface) {
+						inst.windowManager.open({
+							url : url + '/table.htm',
+							width : 400 + parseInt(inst.getLang('table.table_delta_width', 0)),
+							height : 320 + parseInt(inst.getLang('table.table_delta_height', 0)),
+							inline : 1
+						}, {
+							plugin_url : url,
+							action : value ? value.action : 0
+						});
+					}
+
+					return true;
+
+				case "mceTableDelete":
+					var table = inst.dom.getParent(inst.selection.getNode(), "table");
+					if (table) {
+						table.parentNode.removeChild(table);
+						inst.execCommand('mceRepaint');
+					}
+					return true;
+
+				case "mceTableSplitCells":
+				case "mceTableMergeCells":
+				case "mceTableInsertRowBefore":
+				case "mceTableInsertRowAfter":
+				case "mceTableDeleteRow":
+				case "mceTableInsertColBefore":
+				case "mceTableInsertColAfter":
+				case "mceTableDeleteCol":
+				case "mceTableCutRow":
+				case "mceTableCopyRow":
+				case "mceTablePasteRowBefore":
+				case "mceTablePasteRowAfter":
+					// No table just return (invalid command)
+					if (!tableElm)
+						return true;
+
+					// Table has a tbody use that reference
+					// Changed logic by ApTest 2005.07.12 (www.aptest.com)
+					// Now lookk at the focused element and take its parentNode.  That will be a tbody or a table.
+					if (trElm && tableElm != trElm.parentNode)
+						tableElm = trElm.parentNode;
+
+					if (tableElm && trElm) {
+						switch (command) {
+							case "mceTableCutRow":
+								if (!trElm || !tdElm)
+									return true;
+
+								inst.tableRowClipboard = copyRow(doc, tableElm, trElm);
+								inst.execCommand("mceTableDeleteRow");
+								break;
+
+							case "mceTableCopyRow":
+								if (!trElm || !tdElm)
+									return true;
+
+								inst.tableRowClipboard = copyRow(doc, tableElm, trElm);
+								break;
+
+							case "mceTablePasteRowBefore":
+								if (!trElm || !tdElm)
+									return true;
+
+								var newTR = inst.tableRowClipboard.cloneNode(true);
+
+								var prevTR = prevElm(trElm, "TR");
+								if (prevTR != null)
+									trimRow(tableElm, prevTR, prevTR.cells[0], newTR);
+
+								trElm.parentNode.insertBefore(newTR, trElm);
+								break;
+
+							case "mceTablePasteRowAfter":
+								if (!trElm || !tdElm)
+									return true;
+								
+								var nextTR = nextElm(trElm, "TR");
+								var newTR = inst.tableRowClipboard.cloneNode(true);
+
+								trimRow(tableElm, trElm, tdElm, newTR);
+
+								if (nextTR == null)
+									trElm.parentNode.appendChild(newTR);
+								else
+									nextTR.parentNode.insertBefore(newTR, nextTR);
+
+								break;
+
+							case "mceTableInsertRowBefore":
+								if (!trElm || !tdElm)
+									return true;
+
+								var grid = getTableGrid(tableElm);
+								var cpos = getCellPos(grid, tdElm);
+								var newTR = doc.createElement("tr");
+								var lastTDElm = null;
+
+								cpos.rowindex--;
+								if (cpos.rowindex < 0)
+									cpos.rowindex = 0;
+
+								// Create cells
+								for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
+									if (tdElm != lastTDElm) {
+										var sd = getColRowSpan(tdElm);
+
+										if (sd['rowspan'] == 1) {
+											var newTD = doc.createElement("td");
+
+											if (!tinymce.isIE)
+												newTD.innerHTML = '<br mce_bogus="1"/>';
+
+											newTD.colSpan = tdElm.colSpan;
+
+											newTR.appendChild(newTD);
+										} else
+											tdElm.rowSpan = sd['rowspan'] + 1;
+
+										lastTDElm = tdElm;
+									}
+								}
+
+								trElm.parentNode.insertBefore(newTR, trElm);
+								select(0, 1);
+							break;
+
+							case "mceTableInsertRowAfter":
+								if (!trElm || !tdElm)
+									return true;
+
+								var grid = getTableGrid(tableElm);
+								var cpos = getCellPos(grid, tdElm);
+								var newTR = doc.createElement("tr");
+								var lastTDElm = null;
+
+								// Create cells
+								for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
+									if (tdElm != lastTDElm) {
+										var sd = getColRowSpan(tdElm);
+
+										if (sd['rowspan'] == 1) {
+											var newTD = doc.createElement("td");
+
+											if (!tinymce.isIE)
+												newTD.innerHTML = '<br mce_bogus="1"/>';
+
+											newTD.colSpan = tdElm.colSpan;
+
+											newTR.appendChild(newTD);
+										} else
+											tdElm.rowSpan = sd['rowspan'] + 1;
+
+										lastTDElm = tdElm;
+									}
+								}
+
+								if (newTR.hasChildNodes()) {
+									var nextTR = nextElm(trElm, "TR");
+									if (nextTR)
+										nextTR.parentNode.insertBefore(newTR, nextTR);
+									else
+										tableElm.appendChild(newTR);
+								}
+
+								select(0, 1);
+							break;
+
+							case "mceTableDeleteRow":
+								if (!trElm || !tdElm)
+									return true;
+
+								var grid = getTableGrid(tableElm);
+								var cpos = getCellPos(grid, tdElm);
+
+								// Only one row, remove whole table
+								if (grid.length == 1 && tableElm.nodeName == 'TBODY') {
+									inst.dom.remove(inst.dom.getParent(tableElm, "table"));
+									return true;
+								}
+
+								// Move down row spanned cells
+								var cells = trElm.cells;
+								var nextTR = nextElm(trElm, "TR");
+								for (var x=0; x<cells.length; x++) {
+									if (cells[x].rowSpan > 1) {
+										var newTD = cells[x].cloneNode(true);
+										var sd = getColRowSpan(cells[x]);
+
+										newTD.rowSpan = sd.rowspan - 1;
+
+										var nextTD = nextTR.cells[x];
+
+										if (nextTD == null)
+											nextTR.appendChild(newTD);
+										else
+											nextTR.insertBefore(newTD, nextTD);
+									}
+								}
+
+								// Delete cells
+								var lastTDElm = null;
+								for (var x=0; tdElm = getCell(grid, cpos.rowindex, x); x++) {
+									if (tdElm != lastTDElm) {
+										var sd = getColRowSpan(tdElm);
+
+										if (sd.rowspan > 1) {
+											tdElm.rowSpan = sd.rowspan - 1;
+										} else {
+											trElm = tdElm.parentNode;
+
+											if (trElm.parentNode)
+												trElm._delete = true;
+										}
+
+										lastTDElm = tdElm;
+									}
+								}
+
+								deleteMarked(tableElm);
+
+								select(0, -1);
+							break;
+
+							case "mceTableInsertColBefore":
+								if (!trElm || !tdElm)
+									return true;
+
+								var grid = getTableGrid(inst.dom.getParent(tableElm, "table"));
+								var cpos = getCellPos(grid, tdElm);
+								var lastTDElm = null;
+
+								for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
+									if (tdElm != lastTDElm) {
+										var sd = getColRowSpan(tdElm);
+
+										if (sd['colspan'] == 1) {
+											var newTD = doc.createElement(tdElm.nodeName);
+
+											if (!tinymce.isIE)
+												newTD.innerHTML = '<br mce_bogus="1"/>';
+
+											newTD.rowSpan = tdElm.rowSpan;
+
+											tdElm.parentNode.insertBefore(newTD, tdElm);
+										} else
+											tdElm.colSpan++;
+
+										lastTDElm = tdElm;
+									}
+								}
+
+								select();
+							break;
+
+							case "mceTableInsertColAfter":
+								if (!trElm || !tdElm)
+									return true;
+
+								var grid = getTableGrid(inst.dom.getParent(tableElm, "table"));
+								var cpos = getCellPos(grid, tdElm);
+								var lastTDElm = null;
+
+								for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
+									if (tdElm != lastTDElm) {
+										var sd = getColRowSpan(tdElm);
+
+										if (sd['colspan'] == 1) {
+											var newTD = doc.createElement(tdElm.nodeName);
+
+											if (!tinymce.isIE)
+												newTD.innerHTML = '<br mce_bogus="1"/>';
+
+											newTD.rowSpan = tdElm.rowSpan;
+
+											var nextTD = nextElm(tdElm, "TD,TH");
+											if (nextTD == null)
+												tdElm.parentNode.appendChild(newTD);
+											else
+												nextTD.parentNode.insertBefore(newTD, nextTD);
+										} else
+											tdElm.colSpan++;
+
+										lastTDElm = tdElm;
+									}
+								}
+
+								select(1);
+							break;
+
+							case "mceTableDeleteCol":
+								if (!trElm || !tdElm)
+									return true;
+
+								var grid = getTableGrid(tableElm);
+								var cpos = getCellPos(grid, tdElm);
+								var lastTDElm = null;
+
+								// Only one col, remove whole table
+								if ((grid.length > 1 && grid[0].length <= 1) && tableElm.nodeName == 'TBODY') {
+									inst.dom.remove(inst.dom.getParent(tableElm, "table"));
+									return true;
+								}
+
+								// Delete cells
+								for (var y=0; tdElm = getCell(grid, y, cpos.cellindex); y++) {
+									if (tdElm != lastTDElm) {
+										var sd = getColRowSpan(tdElm);
+
+										if (sd['colspan'] > 1)
+											tdElm.colSpan = sd['colspan'] - 1;
+										else {
+											if (tdElm.parentNode)
+												tdElm.parentNode.removeChild(tdElm);
+										}
+
+										lastTDElm = tdElm;
+									}
+								}
+
+								select(-1);
+							break;
+
+						case "mceTableSplitCells":
+							if (!trElm || !tdElm)
+								return true;
+
+							var spandata = getColRowSpan(tdElm);
+
+							var colspan = spandata["colspan"];
+							var rowspan = spandata["rowspan"];
+
+							// Needs splitting
+							if (colspan > 1 || rowspan > 1) {
+								// Generate cols
+								tdElm.colSpan = 1;
+								for (var i=1; i<colspan; i++) {
+									var newTD = doc.createElement("td");
+
+									if (!tinymce.isIE)
+										newTD.innerHTML = '<br mce_bogus="1"/>';
+
+									trElm.insertBefore(newTD, nextElm(tdElm, "TD,TH"));
+
+									if (rowspan > 1)
+										addRows(newTD, trElm, rowspan);
+								}
+
+								addRows(tdElm, trElm, rowspan);
+							}
+
+							// Apply visual aids
+							tableElm = inst.dom.getParent(inst.selection.getNode(), "table");
+							break;
+
+						case "mceTableMergeCells":
+							var rows = [];
+							var sel = inst.selection.getSel();
+							var grid = getTableGrid(tableElm);
+
+							if (tinymce.isIE || sel.rangeCount == 1) {
+								if (user_interface) {
+									// Setup template
+									var sp = getColRowSpan(tdElm);
+
+									inst.windowManager.open({
+										url : url + '/merge_cells.htm',
+										width : 240 + parseInt(inst.getLang('table.merge_cells_delta_width', 0)),
+										height : 110 + parseInt(inst.getLang('table.merge_cells_delta_height', 0)),
+										inline : 1
+									}, {
+										action : "update",
+										numcols : sp.colspan,
+										numrows : sp.rowspan,
+										plugin_url : url
+									});
+
+									return true;
+								} else {
+									var numRows = parseInt(value['numrows']);
+									var numCols = parseInt(value['numcols']);
+									var cpos = getCellPos(grid, tdElm);
+
+									if (("" + numRows) == "NaN")
+										numRows = 1;
+
+									if (("" + numCols) == "NaN")
+										numCols = 1;
+
+									// Get rows and cells
+									var tRows = tableElm.rows;
+									for (var y=cpos.rowindex; y<grid.length; y++) {
+										var rowCells = [];
+
+										for (var x=cpos.cellindex; x<grid[y].length; x++) {
+											var td = getCell(grid, y, x);
+
+											if (td && !inArray(rows, td) && !inArray(rowCells, td)) {
+												var cp = getCellPos(grid, td);
+
+												// Within range
+												if (cp.cellindex < cpos.cellindex+numCols && cp.rowindex < cpos.rowindex+numRows)
+													rowCells[rowCells.length] = td;
+											}
+										}
+
+										if (rowCells.length > 0)
+											rows[rows.length] = rowCells;
+
+										var td = getCell(grid, cpos.rowindex, cpos.cellindex);
+										each(ed.dom.select('br', td), function(e, i) {
+											if (i > 0 && ed.dom.getAttrib('mce_bogus'))
+												ed.dom.remove(e);
+										});
+									}
+
+									//return true;
+								}
+							} else {
+								var cells = [];
+								var sel = inst.selection.getSel();
+								var lastTR = null;
+								var curRow = null;
+								var x1 = -1, y1 = -1, x2, y2;
+
+								// Only one cell selected, whats the point?
+								if (sel.rangeCount < 2)
+									return true;
+
+								// Get all selected cells
+								for (var i=0; i<sel.rangeCount; i++) {
+									var rng = sel.getRangeAt(i);
+									var tdElm = rng.startContainer.childNodes[rng.startOffset];
+
+									if (!tdElm)
+										break;
+
+									if (tdElm.nodeName == "TD" || tdElm.nodeName == "TH")
+										cells[cells.length] = tdElm;
+								}
+
+								// Get rows and cells
+								var tRows = tableElm.rows;
+								for (var y=0; y<tRows.length; y++) {
+									var rowCells = [];
+
+									for (var x=0; x<tRows[y].cells.length; x++) {
+										var td = tRows[y].cells[x];
+
+										for (var i=0; i<cells.length; i++) {
+											if (td == cells[i]) {
+												rowCells[rowCells.length] = td;
+											}
+										}
+									}
+
+									if (rowCells.length > 0)
+										rows[rows.length] = rowCells;
+								}
+
+								// Find selected cells in grid and box
+								var curRow = [];
+								var lastTR = null;
+								for (var y=0; y<grid.length; y++) {
+									for (var x=0; x<grid[y].length; x++) {
+										grid[y][x]._selected = false;
+
+										for (var i=0; i<cells.length; i++) {
+											if (grid[y][x] == cells[i]) {
+												// Get start pos
+												if (x1 == -1) {
+													x1 = x;
+													y1 = y;
+												}
+
+												// Get end pos
+												x2 = x;
+												y2 = y;
+
+												grid[y][x]._selected = true;
+											}
+										}
+									}
+								}
+
+								// Is there gaps, if so deny
+								for (var y=y1; y<=y2; y++) {
+									for (var x=x1; x<=x2; x++) {
+										if (!grid[y][x]._selected) {
+											alert("Invalid selection for merge.");
+											return true;
+										}
+									}
+								}
+							}
+
+							// Validate selection and get total rowspan and colspan
+							var rowSpan = 1, colSpan = 1;
+
+							// Validate horizontal and get total colspan
+							var lastRowSpan = -1;
+							for (var y=0; y<rows.length; y++) {
+								var rowColSpan = 0;
+
+								for (var x=0; x<rows[y].length; x++) {
+									var sd = getColRowSpan(rows[y][x]);
+
+									rowColSpan += sd['colspan'];
+
+									if (lastRowSpan != -1 && sd['rowspan'] != lastRowSpan) {
+										alert("Invalid selection for merge.");
+										return true;
+									}
+
+									lastRowSpan = sd['rowspan'];
+								}
+
+								if (rowColSpan > colSpan)
+									colSpan = rowColSpan;
+
+								lastRowSpan = -1;
+							}
+
+							// Validate vertical and get total rowspan
+							var lastColSpan = -1;
+							for (var x=0; x<rows[0].length; x++) {
+								var colRowSpan = 0;
+
+								for (var y=0; y<rows.length; y++) {
+									var sd = getColRowSpan(rows[y][x]);
+
+									colRowSpan += sd['rowspan'];
+
+									if (lastColSpan != -1 && sd['colspan'] != lastColSpan) {
+										alert("Invalid selection for merge.");
+										return true;
+									}
+
+									lastColSpan = sd['colspan'];
+								}
+
+								if (colRowSpan > rowSpan)
+									rowSpan = colRowSpan;
+
+								lastColSpan = -1;
+							}
+
+							// Setup td
+							tdElm = rows[0][0];
+							tdElm.rowSpan = rowSpan;
+							tdElm.colSpan = colSpan;
+
+							// Merge cells
+							for (var y=0; y<rows.length; y++) {
+								for (var x=0; x<rows[y].length; x++) {
+									var html = rows[y][x].innerHTML;
+									var chk = html.replace(/[ \t\r\n]/g, "");
+
+									if (chk != "<br/>" && chk != "<br>" && chk != '<br mce_bogus="1"/>' && (x+y > 0))
+										tdElm.innerHTML += html;
+
+									// Not current cell
+									if (rows[y][x] != tdElm && !rows[y][x]._deleted) {
+										var cpos = getCellPos(grid, rows[y][x]);
+										var tr = rows[y][x].parentNode;
+
+										tr.removeChild(rows[y][x]);
+										rows[y][x]._deleted = true;
+
+										// Empty TR, remove it
+										if (!tr.hasChildNodes()) {
+											tr.parentNode.removeChild(tr);
+
+											var lastCell = null;
+											for (var x=0; cellElm = getCell(grid, cpos.rowindex, x); x++) {
+												if (cellElm != lastCell && cellElm.rowSpan > 1)
+													cellElm.rowSpan--;
+
+												lastCell = cellElm;
+											}
+
+											if (tdElm.rowSpan > 1)
+												tdElm.rowSpan--;
+										}
+									}
+								}
+							}
+
+							// Remove all but one bogus br
+							each(ed.dom.select('br', tdElm), function(e, i) {
+								if (i > 0 && ed.dom.getAttrib(e, 'mce_bogus'))
+									ed.dom.remove(e);
+							});
+
+							break;
+						}
+
+						tableElm = inst.dom.getParent(inst.selection.getNode(), "table");
+						inst.addVisual(tableElm);
+						inst.nodeChanged();
+					}
+
+				return true;
+			}
+
+			// Pass to next handler in chain
+			return false;
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('table', tinymce.plugins.TablePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/table/js/cell.js b/media/js/tinymce/plugins/table/js/cell.js
new file mode 100644
index 0000000..f23b067
--- /dev/null
+++ b/media/js/tinymce/plugins/table/js/cell.js
@@ -0,0 +1,269 @@
+tinyMCEPopup.requireLangPack();
+
+var ed;
+
+function init() {
+	ed = tinyMCEPopup.editor;
+	tinyMCEPopup.resizeToInnerSize();
+
+	document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table');
+	document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor');
+	document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor')
+
+	var inst = ed;
+	var tdElm = ed.dom.getParent(ed.selection.getNode(), "td,th");
+	var formObj = document.forms[0];
+	var st = ed.dom.parseStyle(ed.dom.getAttrib(tdElm, "style"));
+
+	// Get table cell data
+	var celltype = tdElm.nodeName.toLowerCase();
+	var align = ed.dom.getAttrib(tdElm, 'align');
+	var valign = ed.dom.getAttrib(tdElm, 'valign');
+	var width = trimSize(getStyle(tdElm, 'width', 'width'));
+	var height = trimSize(getStyle(tdElm, 'height', 'height'));
+	var bordercolor = convertRGBToHex(getStyle(tdElm, 'bordercolor', 'borderLeftColor'));
+	var bgcolor = convertRGBToHex(getStyle(tdElm, 'bgcolor', 'backgroundColor'));
+	var className = ed.dom.getAttrib(tdElm, 'class');
+	var backgroundimage = getStyle(tdElm, 'background', 'backgroundImage').replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");;
+	var id = ed.dom.getAttrib(tdElm, 'id');
+	var lang = ed.dom.getAttrib(tdElm, 'lang');
+	var dir = ed.dom.getAttrib(tdElm, 'dir');
+	var scope = ed.dom.getAttrib(tdElm, 'scope');
+
+	// Setup form
+	addClassesToList('class', 'table_cell_styles');
+	TinyMCE_EditableSelects.init();
+
+	formObj.bordercolor.value = bordercolor;
+	formObj.bgcolor.value = bgcolor;
+	formObj.backgroundimage.value = backgroundimage;
+	formObj.width.value = width;
+	formObj.height.value = height;
+	formObj.id.value = id;
+	formObj.lang.value = lang;
+	formObj.style.value = ed.dom.serializeStyle(st);
+	selectByValue(formObj, 'align', align);
+	selectByValue(formObj, 'valign', valign);
+	selectByValue(formObj, 'class', className, true, true);
+	selectByValue(formObj, 'celltype', celltype);
+	selectByValue(formObj, 'dir', dir);
+	selectByValue(formObj, 'scope', scope);
+
+	// Resize some elements
+	if (isVisible('backgroundimagebrowser'))
+		document.getElementById('backgroundimage').style.width = '180px';
+
+	updateColor('bordercolor_pick', 'bordercolor');
+	updateColor('bgcolor_pick', 'bgcolor');
+}
+
+function updateAction() {
+	var el, inst = ed, tdElm, trElm, tableElm, formObj = document.forms[0];
+
+	tinyMCEPopup.restoreSelection();
+	el = ed.selection.getNode();
+	tdElm = ed.dom.getParent(el, "td,th");
+	trElm = ed.dom.getParent(el, "tr");
+	tableElm = ed.dom.getParent(el, "table");
+
+	ed.execCommand('mceBeginUndoLevel');
+
+	switch (getSelectValue(formObj, 'action')) {
+		case "cell":
+			var celltype = getSelectValue(formObj, 'celltype');
+			var scope = getSelectValue(formObj, 'scope');
+
+			function doUpdate(s) {
+				if (s) {
+					updateCell(tdElm);
+
+					ed.addVisual();
+					ed.nodeChanged();
+					inst.execCommand('mceEndUndoLevel');
+					tinyMCEPopup.close();
+				}
+			};
+
+			if (ed.getParam("accessibility_warnings", 1)) {
+				if (celltype == "th" && scope == "")
+					tinyMCEPopup.confirm(ed.getLang('table_dlg.missing_scope', '', true), doUpdate);
+				else
+					doUpdate(1);
+
+				return;
+			}
+
+			updateCell(tdElm);
+			break;
+
+		case "row":
+			var cell = trElm.firstChild;
+
+			if (cell.nodeName != "TD" && cell.nodeName != "TH")
+				cell = nextCell(cell);
+
+			do {
+				cell = updateCell(cell, true);
+			} while ((cell = nextCell(cell)) != null);
+
+			break;
+
+		case "all":
+			var rows = tableElm.getElementsByTagName("tr");
+
+			for (var i=0; i<rows.length; i++) {
+				var cell = rows[i].firstChild;
+
+				if (cell.nodeName != "TD" && cell.nodeName != "TH")
+					cell = nextCell(cell);
+
+				do {
+					cell = updateCell(cell, true);
+				} while ((cell = nextCell(cell)) != null);
+			}
+
+			break;
+	}
+
+	ed.addVisual();
+	ed.nodeChanged();
+	inst.execCommand('mceEndUndoLevel');
+	tinyMCEPopup.close();
+}
+
+function nextCell(elm) {
+	while ((elm = elm.nextSibling) != null) {
+		if (elm.nodeName == "TD" || elm.nodeName == "TH")
+			return elm;
+	}
+
+	return null;
+}
+
+function updateCell(td, skip_id) {
+	var inst = ed;
+	var formObj = document.forms[0];
+	var curCellType = td.nodeName.toLowerCase();
+	var celltype = getSelectValue(formObj, 'celltype');
+	var doc = inst.getDoc();
+	var dom = ed.dom;
+
+	if (!skip_id)
+		td.setAttribute('id', formObj.id.value);
+
+	td.setAttribute('align', formObj.align.value);
+	td.setAttribute('vAlign', formObj.valign.value);
+	td.setAttribute('lang', formObj.lang.value);
+	td.setAttribute('dir', getSelectValue(formObj, 'dir'));
+	td.setAttribute('style', ed.dom.serializeStyle(ed.dom.parseStyle(formObj.style.value)));
+	td.setAttribute('scope', formObj.scope.value);
+	ed.dom.setAttrib(td, 'class', getSelectValue(formObj, 'class'));
+
+	// Clear deprecated attributes
+	ed.dom.setAttrib(td, 'width', '');
+	ed.dom.setAttrib(td, 'height', '');
+	ed.dom.setAttrib(td, 'bgColor', '');
+	ed.dom.setAttrib(td, 'borderColor', '');
+	ed.dom.setAttrib(td, 'background', '');
+
+	// Set styles
+	td.style.width = getCSSSize(formObj.width.value);
+	td.style.height = getCSSSize(formObj.height.value);
+	if (formObj.bordercolor.value != "") {
+		td.style.borderColor = formObj.bordercolor.value;
+		td.style.borderStyle = td.style.borderStyle == "" ? "solid" : td.style.borderStyle;
+		td.style.borderWidth = td.style.borderWidth == "" ? "1px" : td.style.borderWidth;
+	} else
+		td.style.borderColor = '';
+
+	td.style.backgroundColor = formObj.bgcolor.value;
+
+	if (formObj.backgroundimage.value != "")
+		td.style.backgroundImage = "url('" + formObj.backgroundimage.value + "')";
+	else
+		td.style.backgroundImage = '';
+
+	if (curCellType != celltype) {
+		// changing to a different node type
+		var newCell = doc.createElement(celltype);
+
+		for (var c=0; c<td.childNodes.length; c++)
+			newCell.appendChild(td.childNodes[c].cloneNode(1));
+
+		for (var a=0; a<td.attributes.length; a++)
+			ed.dom.setAttrib(newCell, td.attributes[a].name, ed.dom.getAttrib(td, td.attributes[a].name));
+
+		td.parentNode.replaceChild(newCell, td);
+		td = newCell;
+	}
+
+	dom.setAttrib(td, 'style', dom.serializeStyle(dom.parseStyle(td.style.cssText)));
+
+	return td;
+}
+
+function changedBackgroundImage() {
+	var formObj = document.forms[0];
+	var st = ed.dom.parseStyle(formObj.style.value);
+
+	st['background-image'] = "url('" + formObj.backgroundimage.value + "')";
+
+	formObj.style.value = ed.dom.serializeStyle(st);
+}
+
+function changedSize() {
+	var formObj = document.forms[0];
+	var st = ed.dom.parseStyle(formObj.style.value);
+
+	var width = formObj.width.value;
+	if (width != "")
+		st['width'] = getCSSSize(width);
+	else
+		st['width'] = "";
+
+	var height = formObj.height.value;
+	if (height != "")
+		st['height'] = getCSSSize(height);
+	else
+		st['height'] = "";
+
+	formObj.style.value = ed.dom.serializeStyle(st);
+}
+
+function changedColor() {
+	var formObj = document.forms[0];
+	var st = ed.dom.parseStyle(formObj.style.value);
+
+	st['background-color'] = formObj.bgcolor.value;
+	st['border-color'] = formObj.bordercolor.value;
+
+	formObj.style.value = ed.dom.serializeStyle(st);
+}
+
+function changedStyle() {
+	var formObj = document.forms[0];
+	var st = ed.dom.parseStyle(formObj.style.value);
+
+	if (st['background-image'])
+		formObj.backgroundimage.value = st['background-image'].replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");
+	else
+		formObj.backgroundimage.value = '';
+
+	if (st['width'])
+		formObj.width.value = trimSize(st['width']);
+
+	if (st['height'])
+		formObj.height.value = trimSize(st['height']);
+
+	if (st['background-color']) {
+		formObj.bgcolor.value = st['background-color'];
+		updateColor('bgcolor_pick','bgcolor');
+	}
+
+	if (st['border-color']) {
+		formObj.bordercolor.value = st['border-color'];
+		updateColor('bordercolor_pick','bordercolor');
+	}
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/table/js/merge_cells.js b/media/js/tinymce/plugins/table/js/merge_cells.js
new file mode 100644
index 0000000..31d6df0
--- /dev/null
+++ b/media/js/tinymce/plugins/table/js/merge_cells.js
@@ -0,0 +1,29 @@
+tinyMCEPopup.requireLangPack();
+
+function init() {
+	var f = document.forms[0], v;
+
+	tinyMCEPopup.resizeToInnerSize();
+
+	f.numcols.value = tinyMCEPopup.getWindowArg('numcols', 1);
+	f.numrows.value = tinyMCEPopup.getWindowArg('numrows', 1);
+}
+
+function mergeCells() {
+	var args = [], f = document.forms[0];
+
+	tinyMCEPopup.restoreSelection();
+
+	if (!AutoValidator.validate(f)) {
+		tinyMCEPopup.alert(tinyMCEPopup.getLang('invalid_data'));
+		return false;
+	}
+
+	args["numcols"] = f.numcols.value;
+	args["numrows"] = f.numrows.value;
+
+	tinyMCEPopup.execCommand("mceTableMergeCells", false, args);
+	tinyMCEPopup.close();
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/table/js/row.js b/media/js/tinymce/plugins/table/js/row.js
new file mode 100644
index 0000000..d25f635
--- /dev/null
+++ b/media/js/tinymce/plugins/table/js/row.js
@@ -0,0 +1,212 @@
+tinyMCEPopup.requireLangPack();
+
+function init() {
+	tinyMCEPopup.resizeToInnerSize();
+
+	document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table');
+	document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor');
+
+	var inst = tinyMCEPopup.editor;
+	var dom = inst.dom;
+	var trElm = dom.getParent(inst.selection.getNode(), "tr");
+	var formObj = document.forms[0];
+	var st = dom.parseStyle(dom.getAttrib(trElm, "style"));
+
+	// Get table row data
+	var rowtype = trElm.parentNode.nodeName.toLowerCase();
+	var align = dom.getAttrib(trElm, 'align');
+	var valign = dom.getAttrib(trElm, 'valign');
+	var height = trimSize(getStyle(trElm, 'height', 'height'));
+	var className = dom.getAttrib(trElm, 'class');
+	var bgcolor = convertRGBToHex(getStyle(trElm, 'bgcolor', 'backgroundColor'));
+	var backgroundimage = getStyle(trElm, 'background', 'backgroundImage').replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");;
+	var id = dom.getAttrib(trElm, 'id');
+	var lang = dom.getAttrib(trElm, 'lang');
+	var dir = dom.getAttrib(trElm, 'dir');
+
+	// Setup form
+	addClassesToList('class', 'table_row_styles');
+	TinyMCE_EditableSelects.init();
+
+	formObj.bgcolor.value = bgcolor;
+	formObj.backgroundimage.value = backgroundimage;
+	formObj.height.value = height;
+	formObj.id.value = id;
+	formObj.lang.value = lang;
+	formObj.style.value = dom.serializeStyle(st);
+	selectByValue(formObj, 'align', align);
+	selectByValue(formObj, 'valign', valign);
+	selectByValue(formObj, 'class', className, true, true);
+	selectByValue(formObj, 'rowtype', rowtype);
+	selectByValue(formObj, 'dir', dir);
+
+	// Resize some elements
+	if (isVisible('backgroundimagebrowser'))
+		document.getElementById('backgroundimage').style.width = '180px';
+
+	updateColor('bgcolor_pick', 'bgcolor');
+}
+
+function updateAction() {
+	var inst = tinyMCEPopup.editor, dom = inst.dom, trElm, tableElm, formObj = document.forms[0];
+	var action = getSelectValue(formObj, 'action');
+
+	tinyMCEPopup.restoreSelection();
+	trElm = dom.getParent(inst.selection.getNode(), "tr");
+	tableElm = dom.getParent(inst.selection.getNode(), "table");
+
+	inst.execCommand('mceBeginUndoLevel');
+
+	switch (action) {
+		case "row":
+			updateRow(trElm);
+			break;
+
+		case "all":
+			var rows = tableElm.getElementsByTagName("tr");
+
+			for (var i=0; i<rows.length; i++)
+				updateRow(rows[i], true);
+
+			break;
+
+		case "odd":
+		case "even":
+			var rows = tableElm.getElementsByTagName("tr");
+
+			for (var i=0; i<rows.length; i++) {
+				if ((i % 2 == 0 && action == "odd") || (i % 2 != 0 && action == "even"))
+					updateRow(rows[i], true, true);
+			}
+
+			break;
+	}
+
+	inst.addVisual();
+	inst.nodeChanged();
+	inst.execCommand('mceEndUndoLevel');
+	tinyMCEPopup.close();
+}
+
+function updateRow(tr_elm, skip_id, skip_parent) {
+	var inst = tinyMCEPopup.editor;
+	var formObj = document.forms[0];
+	var dom = inst.dom;
+	var curRowType = tr_elm.parentNode.nodeName.toLowerCase();
+	var rowtype = getSelectValue(formObj, 'rowtype');
+	var doc = inst.getDoc();
+
+	// Update row element
+	if (!skip_id)
+		tr_elm.setAttribute('id', formObj.id.value);
+
+	tr_elm.setAttribute('align', getSelectValue(formObj, 'align'));
+	tr_elm.setAttribute('vAlign', getSelectValue(formObj, 'valign'));
+	tr_elm.setAttribute('lang', formObj.lang.value);
+	tr_elm.setAttribute('dir', getSelectValue(formObj, 'dir'));
+	tr_elm.setAttribute('style', dom.serializeStyle(dom.parseStyle(formObj.style.value)));
+	dom.setAttrib(tr_elm, 'class', getSelectValue(formObj, 'class'));
+
+	// Clear deprecated attributes
+	tr_elm.setAttribute('background', '');
+	tr_elm.setAttribute('bgColor', '');
+	tr_elm.setAttribute('height', '');
+
+	// Set styles
+	tr_elm.style.height = getCSSSize(formObj.height.value);
+	tr_elm.style.backgroundColor = formObj.bgcolor.value;
+
+	if (formObj.backgroundimage.value != "")
+		tr_elm.style.backgroundImage = "url('" + formObj.backgroundimage.value + "')";
+	else
+		tr_elm.style.backgroundImage = '';
+
+	// Setup new rowtype
+	if (curRowType != rowtype && !skip_parent) {
+		// first, clone the node we are working on
+		var newRow = tr_elm.cloneNode(1);
+
+		// next, find the parent of its new destination (creating it if necessary)
+		var theTable = dom.getParent(tr_elm, "table");
+		var dest = rowtype;
+		var newParent = null;
+		for (var i = 0; i < theTable.childNodes.length; i++) {
+			if (theTable.childNodes[i].nodeName.toLowerCase() == dest)
+				newParent = theTable.childNodes[i];
+		}
+
+		if (newParent == null) {
+			newParent = doc.createElement(dest);
+
+			if (dest == "thead") {
+				if (theTable.firstChild.nodeName == 'CAPTION')
+					inst.dom.insertAfter(newParent, theTable.firstChild);
+				else
+					theTable.insertBefore(newParent, theTable.firstChild);
+			} else
+				theTable.appendChild(newParent);
+		}
+
+		// append the row to the new parent
+		newParent.appendChild(newRow);
+
+		// remove the original
+		tr_elm.parentNode.removeChild(tr_elm);
+
+		// set tr_elm to the new node
+		tr_elm = newRow;
+	}
+
+	dom.setAttrib(tr_elm, 'style', dom.serializeStyle(dom.parseStyle(tr_elm.style.cssText)));
+}
+
+function changedBackgroundImage() {
+	var formObj = document.forms[0], dom = tinyMCEPopup.editor.dom;
+	var st = dom.parseStyle(formObj.style.value);
+
+	st['background-image'] = "url('" + formObj.backgroundimage.value + "')";
+
+	formObj.style.value = dom.serializeStyle(st);
+}
+
+function changedStyle() {
+	var formObj = document.forms[0], dom = tinyMCEPopup.editor.dom;
+	var st = dom.parseStyle(formObj.style.value);
+
+	if (st['background-image'])
+		formObj.backgroundimage.value = st['background-image'].replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");
+	else
+		formObj.backgroundimage.value = '';
+
+	if (st['height'])
+		formObj.height.value = trimSize(st['height']);
+
+	if (st['background-color']) {
+		formObj.bgcolor.value = st['background-color'];
+		updateColor('bgcolor_pick','bgcolor');
+	}
+}
+
+function changedSize() {
+	var formObj = document.forms[0], dom = tinyMCEPopup.editor.dom;
+	var st = dom.parseStyle(formObj.style.value);
+
+	var height = formObj.height.value;
+	if (height != "")
+		st['height'] = getCSSSize(height);
+	else
+		st['height'] = "";
+
+	formObj.style.value = dom.serializeStyle(st);
+}
+
+function changedColor() {
+	var formObj = document.forms[0], dom = tinyMCEPopup.editor.dom;
+	var st = dom.parseStyle(formObj.style.value);
+
+	st['background-color'] = formObj.bgcolor.value;
+
+	formObj.style.value = dom.serializeStyle(st);
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/table/js/table.js b/media/js/tinymce/plugins/table/js/table.js
new file mode 100644
index 0000000..182589d
--- /dev/null
+++ b/media/js/tinymce/plugins/table/js/table.js
@@ -0,0 +1,440 @@
+tinyMCEPopup.requireLangPack();
+
+var action, orgTableWidth, orgTableHeight, dom = tinyMCEPopup.editor.dom;
+
+function insertTable() {
+	var formObj = document.forms[0];
+	var inst = tinyMCEPopup.editor, dom = inst.dom;
+	var cols = 2, rows = 2, border = 0, cellpadding = -1, cellspacing = -1, align, width, height, className, caption, frame, rules;
+	var html = '', capEl, elm;
+	var cellLimit, rowLimit, colLimit;
+
+	tinyMCEPopup.restoreSelection();
+
+	if (!AutoValidator.validate(formObj)) {
+		tinyMCEPopup.alert(inst.getLang('invalid_data'));
+		return false;
+	}
+
+	elm = dom.getParent(inst.selection.getNode(), 'table');
+
+	// Get form data
+	cols = formObj.elements['cols'].value;
+	rows = formObj.elements['rows'].value;
+	border = formObj.elements['border'].value != "" ? formObj.elements['border'].value  : 0;
+	cellpadding = formObj.elements['cellpadding'].value != "" ? formObj.elements['cellpadding'].value : "";
+	cellspacing = formObj.elements['cellspacing'].value != "" ? formObj.elements['cellspacing'].value : "";
+	align = getSelectValue(formObj, "align");
+	frame = getSelectValue(formObj, "tframe");
+	rules = getSelectValue(formObj, "rules");
+	width = formObj.elements['width'].value;
+	height = formObj.elements['height'].value;
+	bordercolor = formObj.elements['bordercolor'].value;
+	bgcolor = formObj.elements['bgcolor'].value;
+	className = getSelectValue(formObj, "class");
+	id = formObj.elements['id'].value;
+	summary = formObj.elements['summary'].value;
+	style = formObj.elements['style'].value;
+	dir = formObj.elements['dir'].value;
+	lang = formObj.elements['lang'].value;
+	background = formObj.elements['backgroundimage'].value;
+	caption = formObj.elements['caption'].checked;
+
+	cellLimit = tinyMCEPopup.getParam('table_cell_limit', false);
+	rowLimit = tinyMCEPopup.getParam('table_row_limit', false);
+	colLimit = tinyMCEPopup.getParam('table_col_limit', false);
+
+	// Validate table size
+	if (colLimit && cols > colLimit) {
+		tinyMCEPopup.alert(inst.getLang('table_dlg.col_limit').replace(/\{\$cols\}/g, colLimit));
+		return false;
+	} else if (rowLimit && rows > rowLimit) {
+		tinyMCEPopup.alert(inst.getLang('table_dlg.row_limit').replace(/\{\$rows\}/g, rowLimit));
+		return false;
+	} else if (cellLimit && cols * rows > cellLimit) {
+		tinyMCEPopup.alert(inst.getLang('table_dlg.cell_limit').replace(/\{\$cells\}/g, cellLimit));
+		return false;
+	}
+
+	// Update table
+	if (action == "update") {
+		inst.execCommand('mceBeginUndoLevel');
+
+		dom.setAttrib(elm, 'cellPadding', cellpadding, true);
+		dom.setAttrib(elm, 'cellSpacing', cellspacing, true);
+		dom.setAttrib(elm, 'border', border);
+		dom.setAttrib(elm, 'align', align);
+		dom.setAttrib(elm, 'frame', frame);
+		dom.setAttrib(elm, 'rules', rules);
+		dom.setAttrib(elm, 'class', className);
+		dom.setAttrib(elm, 'style', style);
+		dom.setAttrib(elm, 'id', id);
+		dom.setAttrib(elm, 'summary', summary);
+		dom.setAttrib(elm, 'dir', dir);
+		dom.setAttrib(elm, 'lang', lang);
+
+		capEl = inst.dom.select('caption', elm)[0];
+
+		if (capEl && !caption)
+			capEl.parentNode.removeChild(capEl);
+
+		if (!capEl && caption) {
+			capEl = elm.ownerDocument.createElement('caption');
+
+			if (!tinymce.isIE)
+				capEl.innerHTML = '<br mce_bogus="1"/>';
+
+			elm.insertBefore(capEl, elm.firstChild);
+		}
+
+		if (width && inst.settings.inline_styles) {
+			dom.setStyle(elm, 'width', width);
+			dom.setAttrib(elm, 'width', '');
+		} else {
+			dom.setAttrib(elm, 'width', width, true);
+			dom.setStyle(elm, 'width', '');
+		}
+
+		// Remove these since they are not valid XHTML
+		dom.setAttrib(elm, 'borderColor', '');
+		dom.setAttrib(elm, 'bgColor', '');
+		dom.setAttrib(elm, 'background', '');
+
+		if (height && inst.settings.inline_styles) {
+			dom.setStyle(elm, 'height', height);
+			dom.setAttrib(elm, 'height', '');
+		} else {
+			dom.setAttrib(elm, 'height', height, true);
+			dom.setStyle(elm, 'height', '');
+ 		}
+
+		if (background != '')
+			elm.style.backgroundImage = "url('" + background + "')";
+		else
+			elm.style.backgroundImage = '';
+
+/*		if (tinyMCEPopup.getParam("inline_styles")) {
+			if (width != '')
+				elm.style.width = getCSSSize(width);
+		}*/
+
+		if (bordercolor != "") {
+			elm.style.borderColor = bordercolor;
+			elm.style.borderStyle = elm.style.borderStyle == "" ? "solid" : elm.style.borderStyle;
+			elm.style.borderWidth = border == "" ? "1px" : border;
+		} else
+			elm.style.borderColor = '';
+
+		elm.style.backgroundColor = bgcolor;
+		elm.style.height = getCSSSize(height);
+
+		inst.addVisual();
+
+		// Fix for stange MSIE align bug
+		//elm.outerHTML = elm.outerHTML;
+
+		inst.nodeChanged();
+		inst.execCommand('mceEndUndoLevel');
+
+		// Repaint if dimensions changed
+		if (formObj.width.value != orgTableWidth || formObj.height.value != orgTableHeight)
+			inst.execCommand('mceRepaint');
+
+		tinyMCEPopup.close();
+		return true;
+	}
+
+	// Create new table
+	html += '<table';
+
+	html += makeAttrib('id', id);
+	html += makeAttrib('border', border);
+	html += makeAttrib('cellpadding', cellpadding);
+	html += makeAttrib('cellspacing', cellspacing);
+
+	if (width && inst.settings.inline_styles) {
+		if (style)
+			style += '; ';
+
+		// Force px
+		if (/^[0-9\.]+$/.test(width))
+			width += 'px';
+
+		style += 'width: ' + width;
+	} else
+		html += makeAttrib('width', width);
+
+/*	if (height) {
+		if (style)
+			style += '; ';
+
+		style += 'height: ' + height;
+	}*/
+
+	//html += makeAttrib('height', height);
+	//html += makeAttrib('bordercolor', bordercolor);
+	//html += makeAttrib('bgcolor', bgcolor);
+	html += makeAttrib('align', align);
+	html += makeAttrib('frame', frame);
+	html += makeAttrib('rules', rules);
+	html += makeAttrib('class', className);
+	html += makeAttrib('style', style);
+	html += makeAttrib('summary', summary);
+	html += makeAttrib('dir', dir);
+	html += makeAttrib('lang', lang);
+	html += '>';
+
+	if (caption) {
+		if (!tinymce.isIE)
+			html += '<caption><br mce_bogus="1"/></caption>';
+		else
+			html += '<caption></caption>';
+	}
+
+	for (var y=0; y<rows; y++) {
+		html += "<tr>";
+
+		for (var x=0; x<cols; x++) {
+			if (!tinymce.isIE)
+				html += '<td><br mce_bogus="1"/></td>';
+			else
+				html += '<td></td>';
+		}
+
+		html += "</tr>";
+	}
+
+	html += "</table>";
+
+	inst.execCommand('mceBeginUndoLevel');
+
+	// Move table
+	if (inst.settings.fix_table_elements) {
+		var bm = inst.selection.getBookmark(), patt = '';
+
+		inst.execCommand('mceInsertContent', false, '<br class="_mce_marker" />');
+
+		tinymce.each('h1,h2,h3,h4,h5,h6,p'.split(','), function(n) {
+			if (patt)
+				patt += ',';
+
+			patt += n + ' ._mce_marker';
+		});
+
+		tinymce.each(inst.dom.select(patt), function(n) {
+			inst.dom.split(inst.dom.getParent(n, 'h1,h2,h3,h4,h5,h6,p'), n);
+		});
+
+		dom.setOuterHTML(dom.select('._mce_marker')[0], html);
+
+		inst.selection.moveToBookmark(bm);
+	} else
+		inst.execCommand('mceInsertContent', false, html);
+
+	inst.addVisual();
+	inst.execCommand('mceEndUndoLevel');
+
+	tinyMCEPopup.close();
+}
+
+function makeAttrib(attrib, value) {
+	var formObj = document.forms[0];
+	var valueElm = formObj.elements[attrib];
+
+	if (typeof(value) == "undefined" || value == null) {
+		value = "";
+
+		if (valueElm)
+			value = valueElm.value;
+	}
+
+	if (value == "")
+		return "";
+
+	// XML encode it
+	value = value.replace(/&/g, '&');
+	value = value.replace(/\"/g, '"');
+	value = value.replace(/</g, '<');
+	value = value.replace(/>/g, '>');
+
+	return ' ' + attrib + '="' + value + '"';
+}
+
+function init() {
+	tinyMCEPopup.resizeToInnerSize();
+
+	document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table');
+	document.getElementById('backgroundimagebrowsercontainer').innerHTML = getBrowserHTML('backgroundimagebrowser','backgroundimage','image','table');
+	document.getElementById('bordercolor_pickcontainer').innerHTML = getColorPickerHTML('bordercolor_pick','bordercolor');
+	document.getElementById('bgcolor_pickcontainer').innerHTML = getColorPickerHTML('bgcolor_pick','bgcolor');
+
+	var cols = 2, rows = 2, border = tinyMCEPopup.getParam('table_default_border', '0'), cellpadding = tinyMCEPopup.getParam('table_default_cellpadding', ''), cellspacing = tinyMCEPopup.getParam('table_default_cellspacing', '');
+	var align = "", width = "", height = "", bordercolor = "", bgcolor = "", className = "";
+	var id = "", summary = "", style = "", dir = "", lang = "", background = "", bgcolor = "", bordercolor = "", rules, frame;
+	var inst = tinyMCEPopup.editor, dom = inst.dom;
+	var formObj = document.forms[0];
+	var elm = dom.getParent(inst.selection.getNode(), "table");
+
+	action = tinyMCEPopup.getWindowArg('action');
+
+	if (!action)
+		action = elm ? "update" : "insert";
+
+	if (elm && action != "insert") {
+		var rowsAr = elm.rows;
+		var cols = 0;
+		for (var i=0; i<rowsAr.length; i++)
+			if (rowsAr[i].cells.length > cols)
+				cols = rowsAr[i].cells.length;
+
+		cols = cols;
+		rows = rowsAr.length;
+
+		st = dom.parseStyle(dom.getAttrib(elm, "style"));
+		border = trimSize(getStyle(elm, 'border', 'borderWidth'));
+		cellpadding = dom.getAttrib(elm, 'cellpadding', "");
+		cellspacing = dom.getAttrib(elm, 'cellspacing', "");
+		width = trimSize(getStyle(elm, 'width', 'width'));
+		height = trimSize(getStyle(elm, 'height', 'height'));
+		bordercolor = convertRGBToHex(getStyle(elm, 'bordercolor', 'borderLeftColor'));
+		bgcolor = convertRGBToHex(getStyle(elm, 'bgcolor', 'backgroundColor'));
+		align = dom.getAttrib(elm, 'align', align);
+		frame = dom.getAttrib(elm, 'frame');
+		rules = dom.getAttrib(elm, 'rules');
+		className = tinymce.trim(dom.getAttrib(elm, 'class').replace(/mceItem.+/g, ''));
+		id = dom.getAttrib(elm, 'id');
+		summary = dom.getAttrib(elm, 'summary');
+		style = dom.serializeStyle(st);
+		dir = dom.getAttrib(elm, 'dir');
+		lang = dom.getAttrib(elm, 'lang');
+		background = getStyle(elm, 'background', 'backgroundImage').replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");
+		formObj.caption.checked = elm.getElementsByTagName('caption').length > 0;
+
+		orgTableWidth = width;
+		orgTableHeight = height;
+
+		action = "update";
+		formObj.insert.value = inst.getLang('update');
+	}
+
+	addClassesToList('class', "table_styles");
+	TinyMCE_EditableSelects.init();
+
+	// Update form
+	selectByValue(formObj, 'align', align);
+	selectByValue(formObj, 'tframe', frame);
+	selectByValue(formObj, 'rules', rules);
+	selectByValue(formObj, 'class', className, true, true);
+	formObj.cols.value = cols;
+	formObj.rows.value = rows;
+	formObj.border.value = border;
+	formObj.cellpadding.value = cellpadding;
+	formObj.cellspacing.value = cellspacing;
+	formObj.width.value = width;
+	formObj.height.value = height;
+	formObj.bordercolor.value = bordercolor;
+	formObj.bgcolor.value = bgcolor;
+	formObj.id.value = id;
+	formObj.summary.value = summary;
+	formObj.style.value = style;
+	formObj.dir.value = dir;
+	formObj.lang.value = lang;
+	formObj.backgroundimage.value = background;
+
+	updateColor('bordercolor_pick', 'bordercolor');
+	updateColor('bgcolor_pick', 'bgcolor');
+
+	// Resize some elements
+	if (isVisible('backgroundimagebrowser'))
+		document.getElementById('backgroundimage').style.width = '180px';
+
+	// Disable some fields in update mode
+	if (action == "update") {
+		formObj.cols.disabled = true;
+		formObj.rows.disabled = true;
+	}
+}
+
+function changedSize() {
+	var formObj = document.forms[0];
+	var st = dom.parseStyle(formObj.style.value);
+
+/*	var width = formObj.width.value;
+	if (width != "")
+		st['width'] = tinyMCEPopup.getParam("inline_styles") ? getCSSSize(width) : "";
+	else
+		st['width'] = "";*/
+
+	var height = formObj.height.value;
+	if (height != "")
+		st['height'] = getCSSSize(height);
+	else
+		st['height'] = "";
+
+	formObj.style.value = dom.serializeStyle(st);
+}
+
+function changedBackgroundImage() {
+	var formObj = document.forms[0];
+	var st = dom.parseStyle(formObj.style.value);
+
+	st['background-image'] = "url('" + formObj.backgroundimage.value + "')";
+
+	formObj.style.value = dom.serializeStyle(st);
+}
+
+function changedBorder() {
+	var formObj = document.forms[0];
+	var st = dom.parseStyle(formObj.style.value);
+
+	// Update border width if the element has a color
+	if (formObj.border.value != "" && formObj.bordercolor.value != "")
+		st['border-width'] = formObj.border.value + "px";
+
+	formObj.style.value = dom.serializeStyle(st);
+}
+
+function changedColor() {
+	var formObj = document.forms[0];
+	var st = dom.parseStyle(formObj.style.value);
+
+	st['background-color'] = formObj.bgcolor.value;
+
+	if (formObj.bordercolor.value != "") {
+		st['border-color'] = formObj.bordercolor.value;
+
+		// Add border-width if it's missing
+		if (!st['border-width'])
+			st['border-width'] = formObj.border.value == "" ? "1px" : formObj.border.value + "px";
+	}
+
+	formObj.style.value = dom.serializeStyle(st);
+}
+
+function changedStyle() {
+	var formObj = document.forms[0];
+	var st = dom.parseStyle(formObj.style.value);
+
+	if (st['background-image'])
+		formObj.backgroundimage.value = st['background-image'].replace(new RegExp("url\\('?([^']*)'?\\)", 'gi'), "$1");
+	else
+		formObj.backgroundimage.value = '';
+
+	if (st['width'])
+		formObj.width.value = trimSize(st['width']);
+
+	if (st['height'])
+		formObj.height.value = trimSize(st['height']);
+
+	if (st['background-color']) {
+		formObj.bgcolor.value = st['background-color'];
+		updateColor('bgcolor_pick','bgcolor');
+	}
+
+	if (st['border-color']) {
+		formObj.bordercolor.value = st['border-color'];
+		updateColor('bordercolor_pick','bordercolor');
+	}
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/table/langs/en_dlg.js b/media/js/tinymce/plugins/table/langs/en_dlg.js
new file mode 100644
index 0000000..000332a
--- /dev/null
+++ b/media/js/tinymce/plugins/table/langs/en_dlg.js
@@ -0,0 +1,74 @@
+tinyMCE.addI18n('en.table_dlg',{
+general_tab:"General",
+advanced_tab:"Advanced",
+general_props:"General properties",
+advanced_props:"Advanced properties",
+rowtype:"Row in table part",
+title:"Insert/Modify table",
+width:"Width",
+height:"Height",
+cols:"Cols",
+rows:"Rows",
+cellspacing:"Cellspacing",
+cellpadding:"Cellpadding",
+border:"Border",
+align:"Alignment",
+align_default:"Default",
+align_left:"Left",
+align_right:"Right",
+align_middle:"Center",
+row_title:"Table row properties",
+cell_title:"Table cell properties",
+cell_type:"Cell type",
+valign:"Vertical alignment",
+align_top:"Top",
+align_bottom:"Bottom",
+bordercolor:"Border color",
+bgcolor:"Background color",
+merge_cells_title:"Merge table cells",
+id:"Id",
+style:"Style",
+langdir:"Language direction",
+langcode:"Language code",
+mime:"Target MIME type",
+ltr:"Left to right",
+rtl:"Right to left",
+bgimage:"Background image",
+summary:"Summary",
+td:"Data",
+th:"Header",
+cell_cell:"Update current cell",
+cell_row:"Update all cells in row",
+cell_all:"Update all cells in table",
+row_row:"Update current row",
+row_odd:"Update odd rows in table",
+row_even:"Update even rows in table",
+row_all:"Update all rows in table",
+thead:"Table Head",
+tbody:"Table Body",
+tfoot:"Table Foot",
+scope:"Scope",
+rowgroup:"Row Group",
+colgroup:"Col Group",
+col_limit:"You've exceeded the maximum number of columns of {$cols}.",
+row_limit:"You've exceeded the maximum number of rows of {$rows}.",
+cell_limit:"You've exceeded the maximum number of cells of {$cells}.",
+missing_scope:"Are you sure you want to continue without specifying a scope for this table header cell. Without it, it may be difficult for some users with disabilities to understand the content or data displayed of the table.",
+caption:"Table caption",
+frame:"Frame",
+frame_none:"none",
+frame_groups:"groups",
+frame_rows:"rows",
+frame_cols:"cols",
+frame_all:"all",
+rules:"Rules",
+rules_void:"void",
+rules_above:"above",
+rules_below:"below",
+rules_hsides:"hsides",
+rules_lhs:"lhs",
+rules_rhs:"rhs",
+rules_vsides:"vsides",
+rules_box:"box",
+rules_border:"border"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/table/merge_cells.htm b/media/js/tinymce/plugins/table/merge_cells.htm
new file mode 100644
index 0000000..25d42eb
--- /dev/null
+++ b/media/js/tinymce/plugins/table/merge_cells.htm
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#table_dlg.merge_cells_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/validate.js"></script>
+	<script type="text/javascript" src="js/merge_cells.js"></script>
+</head>
+<body style="margin: 8px">
+<form onsubmit="mergeCells();return false;" action="#">
+	<fieldset>
+		<legend>{#table_dlg.merge_cells_title}</legend>
+		  <table border="0" cellpadding="0" cellspacing="3" width="100%">
+			  <tr>
+				<td>{#table_dlg.cols}:</td>
+				<td align="right"><input type="text" name="numcols" value="" class="number min1 mceFocus" style="width: 30px" /></td>
+			  </tr>
+			  <tr>
+				<td>{#table_dlg.rows}:</td>
+				<td align="right"><input type="text" name="numrows" value="" class="number min1" style="width: 30px" /></td>
+			  </tr>
+		  </table>
+	</fieldset>
+
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#update}" />
+		</div>
+
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/table/row.htm b/media/js/tinymce/plugins/table/row.htm
new file mode 100644
index 0000000..07ca13c
--- /dev/null
+++ b/media/js/tinymce/plugins/table/row.htm
@@ -0,0 +1,160 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#table_dlg.row_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/row.js"></script>
+	<link href="css/row.css" rel="stylesheet" type="text/css" />
+</head>
+<body id="tablerow" style="display: none">
+	<form onsubmit="updateAction();return false;" action="#">
+		<div class="tabs">
+			<ul>
+				<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#table_dlg.general_tab}</a></span></li>
+				<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#table_dlg.advanced_tab}</a></span></li>
+			</ul>
+		</div>
+
+		<div class="panel_wrapper">
+			<div id="general_panel" class="panel current">
+				<fieldset>
+					<legend>{#table_dlg.general_props}</legend>
+
+					<table border="0" cellpadding="4" cellspacing="0">
+						<tr>
+							<td><label for="rowtype">{#table_dlg.rowtype}</label></td>
+							<td class="col2">
+								<select id="rowtype" name="rowtype" class="mceFocus">
+									<option value="thead">{#table_dlg.thead}</option>
+									<option value="tbody">{#table_dlg.tbody}</option>
+									<option value="tfoot">{#table_dlg.tfoot}</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="align">{#table_dlg.align}</label></td>
+							<td class="col2">
+								<select id="align" name="align">
+									<option value="">{#not_set}</option>
+									<option value="center">{#table_dlg.align_middle}</option>
+									<option value="left">{#table_dlg.align_left}</option>
+									<option value="right">{#table_dlg.align_right}</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="valign">{#table_dlg.valign}</label></td>
+							<td class="col2">
+								<select id="valign" name="valign">
+									<option value="">{#not_set}</option>
+									<option value="top">{#table_dlg.align_top}</option>
+									<option value="middle">{#table_dlg.align_middle}</option>
+									<option value="bottom">{#table_dlg.align_bottom}</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr id="styleSelectRow">
+							<td><label for="class">{#class_name}</label></td>
+							<td class="col2">
+								<select id="class" name="class" class="mceEditableSelect">
+									<option value="" selected="selected">{#not_set}</option>
+								</select>
+							</td>
+						</tr>
+
+						<tr>
+							<td><label for="height">{#table_dlg.height}</label></td>
+							<td class="col2"><input name="height" type="text" id="height" value="" size="4" maxlength="4" onchange="changedSize();" /></td>
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+
+			<div id="advanced_panel" class="panel">
+				<fieldset>
+					<legend>{#table_dlg.advanced_props}</legend>
+
+					<table border="0" cellpadding="0" cellspacing="4">
+						<tr>
+							<td class="column1"><label for="id">{#table_dlg.id}</label></td> 
+							<td><input id="id" name="id" type="text" value="" style="width: 200px" /></td> 
+						</tr>
+
+						<tr>
+							<td><label for="style">{#table_dlg.style}</label></td>
+							<td><input type="text" id="style" name="style" value="" style="width: 200px;" onchange="changedStyle();" /></td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="dir">{#table_dlg.langdir}</label></td> 
+							<td>
+								<select id="dir" name="dir" style="width: 200px"> 
+										<option value="">{#not_set}</option> 
+										<option value="ltr">{#table_dlg.ltr}</option> 
+										<option value="rtl">{#table_dlg.rtl}</option> 
+								</select>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="lang">{#table_dlg.langcode}</label></td> 
+							<td>
+								<input id="lang" name="lang" type="text" value="" style="width: 200px" />
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="backgroundimage">{#table_dlg.bgimage}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="backgroundimage" name="backgroundimage" type="text" value="" style="width: 200px" onchange="changedBackgroundImage();" /></td>
+										<td id="backgroundimagebrowsercontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="bgcolor">{#table_dlg.bgcolor}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="bgcolor" name="bgcolor" type="text" value="" size="9" onchange="updateColor('bgcolor_pick','bgcolor');changedColor();" /></td>
+										<td id="bgcolor_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+		</div>
+
+		<div class="mceActionPanel">
+			<div>
+				<select id="action" name="action">
+					<option value="row">{#table_dlg.row_row}</option>
+					<option value="odd">{#table_dlg.row_odd}</option>
+					<option value="even">{#table_dlg.row_even}</option>
+					<option value="all">{#table_dlg.row_all}</option>
+				</select>
+			</div>
+
+			<div style="float: left">
+				<div><input type="submit" id="insert" name="insert" value="{#update}" /></div>
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+	</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/table/table.htm b/media/js/tinymce/plugins/table/table.htm
new file mode 100644
index 0000000..37e6159
--- /dev/null
+++ b/media/js/tinymce/plugins/table/table.htm
@@ -0,0 +1,192 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#table_dlg.title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/validate.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/table.js"></script>
+	<link href="css/table.css" rel="stylesheet" type="text/css" />
+</head>
+<body id="table" style="display: none">
+	<form onsubmit="insertTable();return false;" action="#">
+		<div class="tabs">
+			<ul>
+				<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#table_dlg.general_tab}</a></span></li>
+				<li id="advanced_tab"><span><a href="javascript:mcTabs.displayTab('advanced_tab','advanced_panel');" onmousedown="return false;">{#table_dlg.advanced_tab}</a></span></li>
+			</ul>
+		</div>
+
+		<div class="panel_wrapper">
+			<div id="general_panel" class="panel current">
+				<fieldset>
+					<legend>{#table_dlg.general_props}</legend>
+					  <table border="0" cellpadding="4" cellspacing="0" width="100%">
+							  <tr>
+								<td><label id="colslabel" for="cols">{#table_dlg.cols}</label></td>
+								<td><input id="cols" name="cols" type="text" value="" size="3" maxlength="3" class="required number min1 mceFocus" /></td>
+								<td><label id="rowslabel" for="rows">{#table_dlg.rows}</label></td>
+								<td><input id="rows" name="rows" type="text" value="" size="3" maxlength="3" class="required number min1" /></td>
+							  </tr>
+							  <tr>
+								<td><label id="cellpaddinglabel" for="cellpadding">{#table_dlg.cellpadding}</label></td>
+								<td><input id="cellpadding" name="cellpadding" type="text" value="" size="3" maxlength="3" class="number" /></td>
+								<td><label id="cellspacinglabel" for="cellspacing">{#table_dlg.cellspacing}</label></td>
+								<td><input id="cellspacing" name="cellspacing" type="text" value="" size="3" maxlength="3" class="number" /></td>
+							  </tr>
+							  <tr>
+								<td><label id="alignlabel" for="align">{#table_dlg.align}</label></td>
+								<td><select id="align" name="align">
+									<option value="">{#not_set}</option>
+									<option value="center">{#table_dlg.align_middle}</option>
+									<option value="left">{#table_dlg.align_left}</option>
+									<option value="right">{#table_dlg.align_right}</option>
+								  </select></td>
+								<td><label id="borderlabel" for="border">{#table_dlg.border}</label></td>
+								<td><input id="border" name="border" type="text" value="" size="3" maxlength="3" onchange="changedBorder();" class="number" /></td>
+							  </tr>
+							  <tr id="width_row">
+								<td><label id="widthlabel" for="width">{#table_dlg.width}</label></td>
+								<td><input name="width" type="text" id="width" value="" size="4" maxlength="4" onchange="changedSize();" class="size" /></td>
+								<td><label id="heightlabel" for="height">{#table_dlg.height}</label></td>
+								<td><input name="height" type="text" id="height" value="" size="4" maxlength="4" onchange="changedSize();" class="size" /></td>
+							  </tr>
+							  <tr id="styleSelectRow">
+								<td><label id="classlabel" for="class">{#class_name}</label></td>
+								<td colspan="3">
+								 <select id="class" name="class" class="mceEditableSelect">
+									<option value="" selected="selected">{#not_set}</option>
+								 </select></td>
+							  </tr>
+							  <tr>
+								<td class="column1"><label for="caption">{#table_dlg.caption}</label></td> 
+								<td><input id="caption" name="caption" type="checkbox" class="checkbox" value="true" /></td> 
+							  </tr>
+							</table>
+				</fieldset>
+			</div>
+
+			<div id="advanced_panel" class="panel">
+				<fieldset>
+					<legend>{#table_dlg.advanced_props}</legend>
+
+					<table border="0" cellpadding="0" cellspacing="4">
+						<tr>
+							<td class="column1"><label for="id">{#table_dlg.id}</label></td> 
+							<td><input id="id" name="id" type="text" value="" class="advfield" /></td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="summary">{#table_dlg.summary}</label></td> 
+							<td><input id="summary" name="summary" type="text" value="" class="advfield" /></td> 
+						</tr>
+
+						<tr>
+							<td><label for="style">{#table_dlg.style}</label></td>
+							<td><input type="text" id="style" name="style" value="" class="advfield" onchange="changedStyle();" /></td>
+						</tr>
+
+						<tr>
+							<td class="column1"><label id="langlabel" for="lang">{#table_dlg.langcode}</label></td> 
+							<td>
+								<input id="lang" name="lang" type="text" value="" class="advfield" />
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="backgroundimage">{#table_dlg.bgimage}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="backgroundimage" name="backgroundimage" type="text" value="" class="advfield" onchange="changedBackgroundImage();" /></td>
+										<td id="backgroundimagebrowsercontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="tframe">{#table_dlg.frame}</label></td> 
+							<td>
+								<select id="tframe" name="tframe" class="advfield"> 
+										<option value="">{#not_set}</option>
+										<option value="void">{#table_dlg.rules_void}</option>
+										<option value="above">{#table_dlg.rules_above}</option> 
+										<option value="below">{#table_dlg.rules_below}</option> 
+										<option value="hsides">{#table_dlg.rules_hsides}</option> 
+										<option value="lhs">{#table_dlg.rules_lhs}</option> 
+										<option value="rhs">{#table_dlg.rules_rhs}</option> 
+										<option value="vsides">{#table_dlg.rules_vsides}</option> 
+										<option value="box">{#table_dlg.rules_box}</option> 
+										<option value="border">{#table_dlg.rules_border}</option> 
+								</select>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="rules">{#table_dlg.rules}</label></td> 
+							<td>
+								<select id="rules" name="rules" class="advfield"> 
+										<option value="">{#not_set}</option> 
+										<option value="none">{#table_dlg.frame_none}</option>
+										<option value="groups">{#table_dlg.frame_groups}</option>
+										<option value="rows">{#table_dlg.frame_rows}</option>
+										<option value="cols">{#table_dlg.frame_cols}</option>
+										<option value="all">{#table_dlg.frame_all}</option>
+									</select>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="dir">{#table_dlg.langdir}</label></td> 
+							<td>
+								<select id="dir" name="dir" class="advfield"> 
+										<option value="">{#not_set}</option> 
+										<option value="ltr">{#table_dlg.ltr}</option> 
+										<option value="rtl">{#table_dlg.rtl}</option> 
+								</select>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="bordercolor">{#table_dlg.bordercolor}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="bordercolor" name="bordercolor" type="text" value="" size="9" onchange="updateColor('bordercolor_pick','bordercolor');changedColor();" /></td>
+										<td id="bordercolor_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+
+						<tr>
+							<td class="column1"><label for="bgcolor">{#table_dlg.bgcolor}</label></td> 
+							<td>
+								<table border="0" cellpadding="0" cellspacing="0">
+									<tr>
+										<td><input id="bgcolor" name="bgcolor" type="text" value="" size="9" onchange="updateColor('bgcolor_pick','bgcolor');changedColor();" /></td>
+										<td id="bgcolor_pickcontainer"> </td>
+									</tr>
+								</table>
+							</td> 
+						</tr>
+					</table>
+				</fieldset>
+			</div>
+		</div>
+
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" id="insert" name="insert" value="{#insert}" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+	</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/template/blank.htm b/media/js/tinymce/plugins/template/blank.htm
new file mode 100644
index 0000000..ecde53f
--- /dev/null
+++ b/media/js/tinymce/plugins/template/blank.htm
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>blank_page</title>
+	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+	<script type="text/javascript">
+		parent.TemplateDialog.loadCSSFiles(document);
+	</script>
+</head>
+<body id="mceTemplatePreview" class="mceContentBody">
+
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/template/css/template.css b/media/js/tinymce/plugins/template/css/template.css
new file mode 100644
index 0000000..2d23a49
--- /dev/null
+++ b/media/js/tinymce/plugins/template/css/template.css
@@ -0,0 +1,23 @@
+#frmbody {
+	padding: 10px;
+	background-color: #FFF;
+	border: 1px solid #CCC;
+}
+
+.frmRow {
+	margin-bottom: 10px;
+}
+
+#templatesrc {
+	border: none;
+	width: 320px;
+	height: 240px;
+}
+
+.title {
+	padding-bottom: 5px;
+}
+
+.mceActionPanel {
+	padding-top: 5px;
+}
diff --git a/media/js/tinymce/plugins/template/editor_plugin.js b/media/js/tinymce/plugins/template/editor_plugin.js
new file mode 100644
index 0000000..ebe3c27
--- /dev/null
+++ b/media/js/tinymce/plugins/template/editor_plugin.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.each;tinymce.create("tinymce.plugins.TemplatePlugin",{init:function(b,c){var d=this;d.editor=b;b.addCommand("mceTemplate",function(e){b.windowManager.open({file:c+"/template.htm",width:b.getParam("template_popup_width",750),height:b.getParam("template_popup_height",600),inline:1},{plugin_url:c})});b.addCommand("mceInsertTemplate",d._insertTemplate,d);b.addButton("template",{title:"template.desc",cmd:"mceTemplate"});b.onPreProcess.add(function(e,g){var f=e.dom;a( [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/template/editor_plugin_src.js b/media/js/tinymce/plugins/template/editor_plugin_src.js
new file mode 100644
index 0000000..09057ec
--- /dev/null
+++ b/media/js/tinymce/plugins/template/editor_plugin_src.js
@@ -0,0 +1,156 @@
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var each = tinymce.each;
+
+	tinymce.create('tinymce.plugins.TemplatePlugin', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceTemplate', function(ui) {
+				ed.windowManager.open({
+					file : url + '/template.htm',
+					width : ed.getParam('template_popup_width', 750),
+					height : ed.getParam('template_popup_height', 600),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			ed.addCommand('mceInsertTemplate', t._insertTemplate, t);
+
+			// Register buttons
+			ed.addButton('template', {title : 'template.desc', cmd : 'mceTemplate'});
+
+			ed.onPreProcess.add(function(ed, o) {
+				var dom = ed.dom;
+
+				each(dom.select('div', o.node), function(e) {
+					if (dom.hasClass(e, 'mceTmpl')) {
+						each(dom.select('*', e), function(e) {
+							if (dom.hasClass(e, ed.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|')))
+								e.innerHTML = t._getDateTime(new Date(), ed.getParam("template_mdate_format", ed.getLang("template.mdate_format")));
+						});
+
+						t._replaceVals(e);
+					}
+				});
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Template plugin',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://www.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/template',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		_insertTemplate : function(ui, v) {
+			var t = this, ed = t.editor, h, el, dom = ed.dom, sel = ed.selection.getContent();
+
+			h = v.content;
+
+			each(t.editor.getParam('template_replace_values'), function(v, k) {
+				if (typeof(v) != 'function')
+					h = h.replace(new RegExp('\\{\\$' + k + '\\}', 'g'), v);
+			});
+
+			el = dom.create('div', null, h);
+
+			// Find template element within div
+			n = dom.select('.mceTmpl', el);
+			if (n && n.length > 0) {
+				el = dom.create('div', null);
+				el.appendChild(n[0].cloneNode(true));
+			}
+
+			function hasClass(n, c) {
+				return new RegExp('\\b' + c + '\\b', 'g').test(n.className);
+			};
+
+			each(dom.select('*', el), function(n) {
+				// Replace cdate
+				if (hasClass(n, ed.getParam('template_cdate_classes', 'cdate').replace(/\s+/g, '|')))
+					n.innerHTML = t._getDateTime(new Date(), ed.getParam("template_cdate_format", ed.getLang("template.cdate_format")));
+
+				// Replace mdate
+				if (hasClass(n, ed.getParam('template_mdate_classes', 'mdate').replace(/\s+/g, '|')))
+					n.innerHTML = t._getDateTime(new Date(), ed.getParam("template_mdate_format", ed.getLang("template.mdate_format")));
+
+				// Replace selection
+				if (hasClass(n, ed.getParam('template_selected_content_classes', 'selcontent').replace(/\s+/g, '|')))
+					n.innerHTML = sel;
+			});
+
+			t._replaceVals(el);
+
+			ed.execCommand('mceInsertContent', false, el.innerHTML);
+			ed.addVisual();
+		},
+
+		_replaceVals : function(e) {
+			var dom = this.editor.dom, vl = this.editor.getParam('template_replace_values');
+
+			each(dom.select('*', e), function(e) {
+				each(vl, function(v, k) {
+					if (dom.hasClass(e, k)) {
+						if (typeof(vl[k]) == 'function')
+							vl[k](e);
+					}
+				});
+			});
+		},
+
+		_getDateTime : function(d, fmt) {
+				if (!fmt)
+					return "";
+
+				function addZeros(value, len) {
+					var i;
+
+					value = "" + value;
+
+					if (value.length < len) {
+						for (i=0; i<(len-value.length); i++)
+							value = "0" + value;
+					}
+
+					return value;
+				}
+
+				fmt = fmt.replace("%D", "%m/%d/%y");
+				fmt = fmt.replace("%r", "%I:%M:%S %p");
+				fmt = fmt.replace("%Y", "" + d.getFullYear());
+				fmt = fmt.replace("%y", "" + d.getYear());
+				fmt = fmt.replace("%m", addZeros(d.getMonth()+1, 2));
+				fmt = fmt.replace("%d", addZeros(d.getDate(), 2));
+				fmt = fmt.replace("%H", "" + addZeros(d.getHours(), 2));
+				fmt = fmt.replace("%M", "" + addZeros(d.getMinutes(), 2));
+				fmt = fmt.replace("%S", "" + addZeros(d.getSeconds(), 2));
+				fmt = fmt.replace("%I", "" + ((d.getHours() + 11) % 12 + 1));
+				fmt = fmt.replace("%p", "" + (d.getHours() < 12 ? "AM" : "PM"));
+				fmt = fmt.replace("%B", "" + this.editor.getLang("template_months_long").split(',')[d.getMonth()]);
+				fmt = fmt.replace("%b", "" + this.editor.getLang("template_months_short").split(',')[d.getMonth()]);
+				fmt = fmt.replace("%A", "" + this.editor.getLang("template_day_long").split(',')[d.getDay()]);
+				fmt = fmt.replace("%a", "" + this.editor.getLang("template_day_short").split(',')[d.getDay()]);
+				fmt = fmt.replace("%%", "%");
+
+				return fmt;
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('template', tinymce.plugins.TemplatePlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/template/js/template.js b/media/js/tinymce/plugins/template/js/template.js
new file mode 100644
index 0000000..24045d7
--- /dev/null
+++ b/media/js/tinymce/plugins/template/js/template.js
@@ -0,0 +1,106 @@
+tinyMCEPopup.requireLangPack();
+
+var TemplateDialog = {
+	preInit : function() {
+		var url = tinyMCEPopup.getParam("template_external_list_url");
+
+		if (url != null)
+			document.write('<sc'+'ript language="javascript" type="text/javascript" src="' + tinyMCEPopup.editor.documentBaseURI.toAbsolute(url) + '"></sc'+'ript>');
+	},
+
+	init : function() {
+		var ed = tinyMCEPopup.editor, tsrc, sel, x, u;
+
+ 		tsrc = ed.getParam("template_templates", false);
+ 		sel = document.getElementById('tpath');
+
+		// Setup external template list
+		if (!tsrc && typeof(tinyMCETemplateList) != 'undefined') {
+			for (x=0, tsrc = []; x<tinyMCETemplateList.length; x++)
+				tsrc.push({title : tinyMCETemplateList[x][0], src : tinyMCETemplateList[x][1], description : tinyMCETemplateList[x][2]});
+		}
+
+		for (x=0; x<tsrc.length; x++)
+			sel.options[sel.options.length] = new Option(tsrc[x].title, tinyMCEPopup.editor.documentBaseURI.toAbsolute(tsrc[x].src));
+
+		this.resize();
+		this.tsrc = tsrc;
+	},
+
+	resize : function() {
+		var w, h, e;
+
+		if (!self.innerWidth) {
+			w = document.body.clientWidth - 50;
+			h = document.body.clientHeight - 160;
+		} else {
+			w = self.innerWidth - 50;
+			h = self.innerHeight - 170;
+		}
+
+		e = document.getElementById('templatesrc');
+
+		if (e) {
+			e.style.height = Math.abs(h) + 'px';
+			e.style.width  = Math.abs(w - 5) + 'px';
+		}
+	},
+
+	loadCSSFiles : function(d) {
+		var ed = tinyMCEPopup.editor;
+
+		tinymce.each(ed.getParam("content_css", '').split(','), function(u) {
+			d.write('<link href="' + ed.documentBaseURI.toAbsolute(u) + '" rel="stylesheet" type="text/css" />');
+		});
+	},
+
+	selectTemplate : function(u, ti) {
+		var d = window.frames['templatesrc'].document, x, tsrc = this.tsrc;
+
+		if (!u)
+			return;
+
+		d.body.innerHTML = this.templateHTML = this.getFileContents(u);
+
+		for (x=0; x<tsrc.length; x++) {
+			if (tsrc[x].title == ti)
+				document.getElementById('tmpldesc').innerHTML = tsrc[x].description || '';
+		}
+	},
+
+ 	insert : function() {
+		tinyMCEPopup.execCommand('mceInsertTemplate', false, {
+			content : this.templateHTML,
+			selection : tinyMCEPopup.editor.selection.getContent()
+		});
+
+		tinyMCEPopup.close();
+	},
+
+	getFileContents : function(u) {
+		var x, d, t = 'text/plain';
+
+		function g(s) {
+			x = 0;
+
+			try {
+				x = new ActiveXObject(s);
+			} catch (s) {
+			}
+
+			return x;
+		};
+
+		x = window.ActiveXObject ? g('Msxml2.XMLHTTP') || g('Microsoft.XMLHTTP') : new XMLHttpRequest();
+
+		// Synchronous AJAX load file
+		x.overrideMimeType && x.overrideMimeType(t);
+		x.open("GET", u, false);
+		x.send(null);
+
+		return x.responseText;
+	}
+};
+
+TemplateDialog.preInit();
+tinyMCEPopup.onInit.add(TemplateDialog.init, TemplateDialog);
diff --git a/media/js/tinymce/plugins/template/langs/en_dlg.js b/media/js/tinymce/plugins/template/langs/en_dlg.js
new file mode 100644
index 0000000..2471c3f
--- /dev/null
+++ b/media/js/tinymce/plugins/template/langs/en_dlg.js
@@ -0,0 +1,15 @@
+tinyMCE.addI18n('en.template_dlg',{
+title:"Templates",
+label:"Template",
+desc_label:"Description",
+desc:"Insert predefined template content",
+select:"Select a template",
+preview:"Preview",
+warning:"Warning: Updating a template with a different one may cause data loss.",
+mdate_format:"%Y-%m-%d %H:%M:%S",
+cdate_format:"%Y-%m-%d %H:%M:%S",
+months_long:"January,February,March,April,May,June,July,August,September,October,November,December",
+months_short:"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
+day_long:"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday",
+day_short:"Sun,Mon,Tue,Wed,Thu,Fri,Sat,Sun"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/template/template.htm b/media/js/tinymce/plugins/template/template.htm
new file mode 100644
index 0000000..f7bb044
--- /dev/null
+++ b/media/js/tinymce/plugins/template/template.htm
@@ -0,0 +1,38 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#template_dlg.title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/template.js"></script>
+	<link href="css/template.css" rel="stylesheet" type="text/css" />
+</head>
+<body onresize="TemplateDialog.resize();"> 
+	<form onsubmit="TemplateDialog.insert();return false;">
+		<div id="frmbody">
+			<div class="title">{#template_dlg.desc}</div>
+			<div class="frmRow"><label for="tpath" title="{#template_dlg.select}">{#template_dlg.label}:</label>
+			<select id="tpath" name="tpath" onchange="TemplateDialog.selectTemplate(this.options[this.selectedIndex].value, this.options[this.selectedIndex].text);" class="mceFocus">
+				<option value="">{#template_dlg.select}...</option>
+			</select>
+			<span id="warning"></span></div>
+			<div class="frmRow"><label for="tdesc">{#template_dlg.desc_label}:</label>
+			<span id="tmpldesc"></span></div>
+			<fieldset>
+				<legend>{#template_dlg.preview}</legend>
+				<iframe id="templatesrc" name="templatesrc" src="blank.htm" width="690" height="400" frameborder="0"></iframe>
+			</fieldset>
+		</div>
+		
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" id="insert" name="insert" value="{#insert}" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+			</div>
+
+			<br style="clear:both" />
+		</div>
+	</form>
+</body> 
+</html> 
diff --git a/media/js/tinymce/plugins/visualchars/editor_plugin.js b/media/js/tinymce/plugins/visualchars/editor_plugin.js
new file mode 100644
index 0000000..53d31c4
--- /dev/null
+++ b/media/js/tinymce/plugins/visualchars/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.VisualChars",{init:function(a,b){var c=this;c.editor=a;a.addCommand("mceVisualChars",c._toggleVisualChars,c);a.addButton("visualchars",{title:"visualchars.desc",cmd:"mceVisualChars"});a.onBeforeGetContent.add(function(d,e){if(c.state){c.state=true;c._toggleVisualChars()}})},getInfo:function(){return{longname:"Visual characters",author:"Moxiecode Systems AB",authorurl:"http://tinymce.moxiecode.com",infourl:"http://wiki.moxiecode.com/index.php/Ti [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/visualchars/editor_plugin_src.js b/media/js/tinymce/plugins/visualchars/editor_plugin_src.js
new file mode 100644
index 0000000..02ec4e6
--- /dev/null
+++ b/media/js/tinymce/plugins/visualchars/editor_plugin_src.js
@@ -0,0 +1,73 @@
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.VisualChars', {
+		init : function(ed, url) {
+			var t = this;
+
+			t.editor = ed;
+
+			// Register commands
+			ed.addCommand('mceVisualChars', t._toggleVisualChars, t);
+
+			// Register buttons
+			ed.addButton('visualchars', {title : 'visualchars.desc', cmd : 'mceVisualChars'});
+
+			ed.onBeforeGetContent.add(function(ed, o) {
+				if (t.state) {
+					t.state = true;
+					t._toggleVisualChars();
+				}
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Visual characters',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/visualchars',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		},
+
+		// Private methods
+
+		_toggleVisualChars : function() {
+			var t = this, ed = t.editor, nl, i, h, d = ed.getDoc(), b = ed.getBody(), nv, s = ed.selection, bo;
+
+			t.state = !t.state;
+			ed.controlManager.setActive('visualchars', t.state);
+
+			if (t.state) {
+				nl = [];
+				tinymce.walk(b, function(n) {
+					if (n.nodeType == 3 && n.nodeValue && n.nodeValue.indexOf('\u00a0') != -1)
+						nl.push(n);
+				}, 'childNodes');
+
+				for (i=0; i<nl.length; i++) {
+					nv = nl[i].nodeValue;
+					nv = nv.replace(/(\u00a0+)/g, '<span class="mceItemHidden mceVisualNbsp">$1</span>');
+					nv = nv.replace(/\u00a0/g, '\u00b7');
+					ed.dom.setOuterHTML(nl[i], nv, d);
+				}
+			} else {
+				nl = tinymce.grep(ed.dom.select('span', b), function(n) {
+					return ed.dom.hasClass(n, 'mceVisualNbsp');
+				});
+
+				for (i=0; i<nl.length; i++)
+					ed.dom.setOuterHTML(nl[i], nl[i].innerHTML.replace(/(·|\u00b7)/g, ' '), d);
+			}
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('visualchars', tinymce.plugins.VisualChars);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/wordcount/editor_plugin.js b/media/js/tinymce/plugins/wordcount/editor_plugin.js
new file mode 100644
index 0000000..f192835
--- /dev/null
+++ b/media/js/tinymce/plugins/wordcount/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.WordCount",{block:0,id:null,countre:null,cleanre:null,init:function(a,b){var c=this,d=0;c.countre=a.getParam("wordcount_countregex",/\S\s+/g);c.cleanre=a.getParam("wordcount_cleanregex",/[0-9.(),;:!?%#$�'"_+=\\/-]*/g);c.id=a.id+"-word-count";a.onPostRender.add(function(f,e){var g,h;h=f.getParam("wordcount_target_id");if(!h){g=tinymce.DOM.get(f.id+"_path_row");if(g){tinymce.DOM.add(g.parentNode,"div",{style:"float: right"},f.getLang("wordcount.w [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/wordcount/editor_plugin_src.js b/media/js/tinymce/plugins/wordcount/editor_plugin_src.js
new file mode 100644
index 0000000..41b78a9
--- /dev/null
+++ b/media/js/tinymce/plugins/wordcount/editor_plugin_src.js
@@ -0,0 +1,95 @@
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+    tinymce.create('tinymce.plugins.WordCount', {
+		block : 0,
+		id : null,
+		countre : null,
+		cleanre : null,
+
+		init : function(ed, url) {
+			var t = this, last = 0;
+
+			t.countre = ed.getParam('wordcount_countregex', /\S\s+/g);
+			t.cleanre = ed.getParam('wordcount_cleanregex', /[0-9.(),;:!?%#$�'"_+=\\/-]*/g);
+			t.id = ed.id + '-word-count';
+
+			ed.onPostRender.add(function(ed, cm) {
+				var row, id;
+
+				// Add it to the specified id or the theme advanced path
+				id = ed.getParam('wordcount_target_id');
+				if (!id) {
+					row = tinymce.DOM.get(ed.id + '_path_row');
+
+					if (row)
+						tinymce.DOM.add(row.parentNode, 'div', {'style': 'float: right'}, ed.getLang('wordcount.words', 'Words: ') + '<span id="' + t.id + '">0</span>');
+				} else
+					tinymce.DOM.add(id, 'span', {}, '<span id="' + t.id + '">0</span>');
+			});
+
+            ed.onInit.add(function(ed) {
+				ed.selection.onSetContent.add(function() {
+					t._count(ed);
+				});
+
+				t._count(ed);
+			});
+
+			ed.onSetContent.add(function(ed) {
+				t._count(ed);
+			});
+
+			ed.onKeyUp.add(function(ed, e) {
+				if (e.keyCode == last)
+					return;
+
+				if (13 == e.keyCode || 8 == last || 46 == last)
+					t._count(ed);
+
+				last = e.keyCode;
+			});
+		},
+
+		_count : function(ed) {
+			var t = this, tc = 0;
+
+			// Keep multiple calls from happening at the same time
+			if (t.block)
+				return;
+
+			t.block = 1;
+
+			setTimeout(function() {
+				var tx = ed.getContent({format : 'raw'});
+
+				if (tx) {
+					tx = tx.replace(/<.[^<>]*?>/g, ' ').replace(/ | /gi, ' '); // remove html tags and space chars
+					tx = tx.replace(t.cleanre, ''); // remove numbers and punctuation
+					tx.replace(t.countre, function() {tc++;}); // count the words
+				}
+
+				tinymce.DOM.setHTML(t.id, tc.toString());
+
+				setTimeout(function() {t.block = 0;}, 2000);
+			}, 1);
+		},
+
+        getInfo: function() {
+			return {
+				longname : 'Word Count plugin',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+        }
+    });
+
+    tinymce.PluginManager.add('wordcount', tinymce.plugins.WordCount);
+})();
diff --git a/media/js/tinymce/plugins/xhtmlxtras/abbr.htm b/media/js/tinymce/plugins/xhtmlxtras/abbr.htm
new file mode 100644
index 0000000..3928a17
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/abbr.htm
@@ -0,0 +1,148 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#xhtmlxtras_dlg.title_abbr_element}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/element_common.js"></script>
+	<script type="text/javascript" src="js/abbr.js"></script>
+	<link rel="stylesheet" type="text/css" href="css/popup.css" />
+</head>
+<body style="display: none">
+<form onsubmit="insertAbbr();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.general_tab}</a></span></li>
+			<!-- <li id="events_tab"><span><a href="javascript:mcTabs.displayTab('events_tab','events_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.events_tab}</a></span></li> -->
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_attrib_tab}</legend>
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label id="titlelabel" for="title">{#xhtmlxtras_dlg.attribute_label_title}</label>:</td> 
+						<td><input id="title" name="title" type="text" value="" class="field mceFocus" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="idlabel" for="id">{#xhtmlxtras_dlg.attribute_label_id}</label>:</td> 
+						<td><input id="id" name="id" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="classlabel" for="class">{#xhtmlxtras_dlg.attribute_label_class}</label>:</td> 
+						<td>
+							<select id="class" name="class" class="field mceEditableSelect">
+								<option value="">{#not_set}</option> 
+							</select>
+						</td>
+					</tr>
+					<tr>
+						<td class="label"><label id="stylelabel" for="class">{#xhtmlxtras_dlg.attribute_label_style}</label>:</td> 
+						<td><input id="style" name="style" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="dirlabel" for="dir">{#xhtmlxtras_dlg.attribute_label_langdir}</label>:</td> 
+						<td>
+							<select id="dir" name="dir" class="field"> 
+								<option value="">{#not_set}</option> 
+								<option value="ltr">{#xhtmlxtras_dlg.attribute_option_ltr}</option> 
+								<option value="rtl">{#xhtmlxtras_dlg.attribute_option_rtl}</option> 
+							</select>
+						</td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="langlabel" for="lang">{#xhtmlxtras_dlg.attribute_label_langcode}</label>:</td> 
+						<td>
+							<input id="lang" name="lang" type="text" value="" class="field" />
+						</td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+		<div id="events_panel" class="panel">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_events_tab}</legend>
+
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label for="onfocus">onfocus</label>:</td> 
+						<td><input id="onfocus" name="onfocus" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onblur">onblur</label>:</td> 
+						<td><input id="onblur" name="onblur" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onclick">onclick</label>:</td> 
+						<td><input id="onclick" name="onclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="ondblclick">ondblclick</label>:</td> 
+						<td><input id="ondblclick" name="ondblclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousedown">onmousedown</label>:</td> 
+						<td><input id="onmousedown" name="onmousedown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseup">onmouseup</label>:</td> 
+						<td><input id="onmouseup" name="onmouseup" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseover">onmouseover</label>:</td> 
+						<td><input id="onmouseover" name="onmouseover" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousemove">onmousemove</label>:</td> 
+						<td><input id="onmousemove" name="onmousemove" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseout">onmouseout</label>:</td> 
+						<td><input id="onmouseout" name="onmouseout" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeypress">onkeypress</label>:</td> 
+						<td><input id="onkeypress" name="onkeypress" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeydown">onkeydown</label>:</td> 
+						<td><input id="onkeydown" name="onkeydown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeyup">onkeyup</label>:</td> 
+						<td><input id="onkeyup" name="onkeyup" type="text" value="" class="field" /></td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+	</div>
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#update}" />
+		</div>
+		<div style="float: left">
+			<input type="button" id="remove" name="remove" class="button" value="{#xhtmlxtras_dlg.remove}" onclick="removeAbbr();" style="display: none;" />
+		</div>
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/xhtmlxtras/acronym.htm b/media/js/tinymce/plugins/xhtmlxtras/acronym.htm
new file mode 100644
index 0000000..4d4ebaa
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/acronym.htm
@@ -0,0 +1,148 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#xhtmlxtras_dlg.title_acronym_element}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/element_common.js"></script>
+	<script type="text/javascript" src="js/acronym.js"></script>
+	<link rel="stylesheet" type="text/css" href="css/popup.css" />
+</head>
+<body style="display: none">
+<form onsubmit="insertAcronym();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.general_tab}</a></span></li>
+			<!-- <li id="events_tab"><span><a href="javascript:mcTabs.displayTab('events_tab','events_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.events_tab}</a></span></li> -->
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_attrib_tab}</legend>
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label id="titlelabel" for="title">{#xhtmlxtras_dlg.attribute_label_title}</label>:</td> 
+						<td><input id="title" name="title" type="text" value="" class="field mceFocus" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="idlabel" for="id">{#xhtmlxtras_dlg.attribute_label_id}</label>:</td> 
+						<td><input id="id" name="id" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="classlabel" for="class">{#xhtmlxtras_dlg.attribute_label_class}</label>:</td> 
+						<td>
+							<select id="class" name="class" class="field mceEditableSelect">
+								<option value="">{#not_set}</option> 
+							</select>
+						</td>
+					</tr>
+					<tr>
+						<td class="label"><label id="stylelabel" for="class">{#xhtmlxtras_dlg.attribute_label_style}</label>:</td> 
+						<td><input id="style" name="style" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="dirlabel" for="dir">{#xhtmlxtras_dlg.attribute_label_langdir}</label>:</td> 
+						<td>
+							<select id="dir" name="dir" class="field"> 
+								<option value="">{#not_set}</option> 
+								<option value="ltr">{#xhtmlxtras_dlg.attribute_option_ltr}</option> 
+								<option value="rtl">{#xhtmlxtras_dlg.attribute_option_rtl}</option> 
+							</select>
+						</td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="langlabel" for="lang">{#xhtmlxtras_dlg.attribute_label_langcode}</label>:</td> 
+						<td>
+							<input id="lang" name="lang" type="text" value="" class="field" />
+						</td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+		<div id="events_panel" class="panel">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_events_tab}</legend>
+
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label for="onfocus">onfocus</label>:</td> 
+						<td><input id="onfocus" name="onfocus" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onblur">onblur</label>:</td> 
+						<td><input id="onblur" name="onblur" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onclick">onclick</label>:</td> 
+						<td><input id="onclick" name="onclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="ondblclick">ondblclick</label>:</td> 
+						<td><input id="ondblclick" name="ondblclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousedown">onmousedown</label>:</td> 
+						<td><input id="onmousedown" name="onmousedown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseup">onmouseup</label>:</td> 
+						<td><input id="onmouseup" name="onmouseup" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseover">onmouseover</label>:</td> 
+						<td><input id="onmouseover" name="onmouseover" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousemove">onmousemove</label>:</td> 
+						<td><input id="onmousemove" name="onmousemove" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseout">onmouseout</label>:</td> 
+						<td><input id="onmouseout" name="onmouseout" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeypress">onkeypress</label>:</td> 
+						<td><input id="onkeypress" name="onkeypress" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeydown">onkeydown</label>:</td> 
+						<td><input id="onkeydown" name="onkeydown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeyup">onkeyup</label>:</td> 
+						<td><input id="onkeyup" name="onkeyup" type="text" value="" class="field" /></td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+	</div>
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#update}" />
+		</div>
+		<div style="float: left">
+			<input type="button" id="remove" name="remove" class="button" value="{#xhtmlxtras_dlg.remove}" onclick="removeAcronym();" style="display: none;" />
+		</div>
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/xhtmlxtras/attributes.htm b/media/js/tinymce/plugins/xhtmlxtras/attributes.htm
new file mode 100644
index 0000000..322b468
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/attributes.htm
@@ -0,0 +1,153 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#xhtmlxtras_dlg.attribs_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="js/attributes.js"></script>
+	<link rel="stylesheet" type="text/css" href="css/attributes.css" />
+</head>
+<body style="display: none">
+<form onsubmit="insertAction();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.attribute_attrib_tab}</a></span></li>
+			<li id="events_tab"><span><a href="javascript:mcTabs.displayTab('events_tab','events_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.attribute_events_tab}</a></span></li>
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.attribute_attrib_tab}</legend>
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label id="titlelabel" for="title">{#xhtmlxtras_dlg.attribute_label_title}</label>:</td> 
+						<td><input id="title" name="title" type="text" value="" class="mceFocus" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="idlabel" for="id">{#xhtmlxtras_dlg.attribute_label_id}</label>:</td> 
+						<td><input id="id" name="id" type="text" value="" /></td> 
+					</tr>
+					<tr>
+						<td><label id="classlabel" for="classlist">{#class_name}</label></td>
+						<td>
+							<select id="classlist" name="classlist" class="mceEditableSelect">
+								<option value="" selected="selected">{#not_set}</option>
+							</select>
+						</td>
+					</tr>
+					<tr>
+						<td class="label"><label id="stylelabel" for="style">{#xhtmlxtras_dlg.attribute_label_style}</label>:</td> 
+						<td><input id="style" name="style" type="text" value="" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="dirlabel" for="dir">{#xhtmlxtras_dlg.attribute_label_langdir}</label>:</td> 
+						<td>
+							<select id="dir" name="dir"> 
+								<option value="">{#not_set}</option> 
+								<option value="ltr">{#xhtmlxtras_dlg.option_ltr}</option> 
+								<option value="rtl">{#xhtmlxtras_dlg.option_rtl}</option> 
+							</select>
+						</td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="langlabel" for="lang">{#xhtmlxtras_dlg.attribute_label_langcode}</label>:</td> 
+						<td>
+							<input id="lang" name="lang" type="text" value="" />
+						</td> 
+					</tr>
+					<tr>
+							<td><label id="tabindexlabel" for="tabindex">{#xhtmlxtras_dlg.attribute_label_tabindex}</label></td>
+							<td><input type="text" id="tabindex" name="tabindex" value="" /></td>
+						</tr>
+
+						<tr>
+							<td><label id="accesskeylabel" for="accesskey">{#xhtmlxtras_dlg.attribute_label_accesskey}</label></td>
+							<td><input type="text" id="accesskey" name="accesskey" value="" /></td>
+						</tr>
+				</table>
+			</fieldset>
+		</div>
+		<div id="events_panel" class="panel">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.attribute_events_tab}</legend>
+
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label for="onfocus">onfocus</label>:</td> 
+						<td><input id="onfocus" name="onfocus" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onblur">onblur</label>:</td> 
+						<td><input id="onblur" name="onblur" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onclick">onclick</label>:</td> 
+						<td><input id="onclick" name="onclick" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="ondblclick">ondblclick</label>:</td> 
+						<td><input id="ondblclick" name="ondblclick" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousedown">onmousedown</label>:</td> 
+						<td><input id="onmousedown" name="onmousedown" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseup">onmouseup</label>:</td> 
+						<td><input id="onmouseup" name="onmouseup" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseover">onmouseover</label>:</td> 
+						<td><input id="onmouseover" name="onmouseover" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousemove">onmousemove</label>:</td> 
+						<td><input id="onmousemove" name="onmousemove" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseout">onmouseout</label>:</td> 
+						<td><input id="onmouseout" name="onmouseout" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeypress">onkeypress</label>:</td> 
+						<td><input id="onkeypress" name="onkeypress" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeydown">onkeydown</label>:</td> 
+						<td><input id="onkeydown" name="onkeydown" type="text" value="" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeyup">onkeyup</label>:</td> 
+						<td><input id="onkeyup" name="onkeyup" type="text" value="" /></td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+	</div>
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#insert}" />
+		</div>
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/xhtmlxtras/cite.htm b/media/js/tinymce/plugins/xhtmlxtras/cite.htm
new file mode 100644
index 0000000..cdfaf4e
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/cite.htm
@@ -0,0 +1,148 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#xhtmlxtras_dlg.title_cite_element}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/element_common.js"></script>
+	<script type="text/javascript" src="js/cite.js"></script>
+	<link rel="stylesheet" type="text/css" href="css/popup.css" />
+</head>
+<body style="display: none">
+<form onsubmit="insertCite();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.general_tab}</a></span></li>
+			<!-- <li id="events_tab"><span><a href="javascript:mcTabs.displayTab('events_tab','events_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.events_tab}</a></span></li> -->
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_attrib_tab}</legend>
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label id="titlelabel" for="title">{#xhtmlxtras_dlg.attribute_label_title}</label>:</td> 
+						<td><input id="title" name="title" type="text" value="" class="field mceFocus" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="idlabel" for="id">{#xhtmlxtras_dlg.attribute_label_id}</label>:</td> 
+						<td><input id="id" name="id" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="classlabel" for="class">{#xhtmlxtras_dlg.attribute_label_class}</label>:</td> 
+						<td>
+							<select id="class" name="class" class="field mceEditableSelect">
+								<option value="">{#not_set}</option> 
+							</select>
+						</td>
+					</tr>
+					<tr>
+						<td class="label"><label id="stylelabel" for="class">{#xhtmlxtras_dlg.attribute_label_style}</label>:</td> 
+						<td><input id="style" name="style" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="dirlabel" for="dir">{#xhtmlxtras_dlg.attribute_label_langdir}</label>:</td> 
+						<td>
+							<select id="dir" name="dir" class="field"> 
+								<option value="">{#not_set}</option> 
+								<option value="ltr">{#xhtmlxtras_dlg.attribute_option_ltr}</option> 
+								<option value="rtl">{#xhtmlxtras_dlg.attribute_option_rtl}</option> 
+							</select>
+						</td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="langlabel" for="lang">{#xhtmlxtras_dlg.attribute_label_langcode}</label>:</td> 
+						<td>
+							<input id="lang" name="lang" type="text" value="" class="field" />
+						</td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+		<div id="events_panel" class="panel">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_events_tab}</legend>
+
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label for="onfocus">onfocus</label>:</td> 
+						<td><input id="onfocus" name="onfocus" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onblur">onblur</label>:</td> 
+						<td><input id="onblur" name="onblur" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onclick">onclick</label>:</td> 
+						<td><input id="onclick" name="onclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="ondblclick">ondblclick</label>:</td> 
+						<td><input id="ondblclick" name="ondblclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousedown">onmousedown</label>:</td> 
+						<td><input id="onmousedown" name="onmousedown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseup">onmouseup</label>:</td> 
+						<td><input id="onmouseup" name="onmouseup" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseover">onmouseover</label>:</td> 
+						<td><input id="onmouseover" name="onmouseover" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousemove">onmousemove</label>:</td> 
+						<td><input id="onmousemove" name="onmousemove" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseout">onmouseout</label>:</td> 
+						<td><input id="onmouseout" name="onmouseout" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeypress">onkeypress</label>:</td> 
+						<td><input id="onkeypress" name="onkeypress" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeydown">onkeydown</label>:</td> 
+						<td><input id="onkeydown" name="onkeydown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeyup">onkeyup</label>:</td> 
+						<td><input id="onkeyup" name="onkeyup" type="text" value="" class="field" /></td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+	</div>
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#update}" />
+		</div>
+		<div style="float: left">
+			<input type="button" id="remove" name="remove" class="button" value="{#xhtmlxtras_dlg.remove}" onclick="removeCite();" style="display: none;" />
+		</div>
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/xhtmlxtras/css/attributes.css b/media/js/tinymce/plugins/xhtmlxtras/css/attributes.css
new file mode 100644
index 0000000..9a6a235
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/css/attributes.css
@@ -0,0 +1,11 @@
+.panel_wrapper div.current {
+	height: 290px;
+}
+
+#id, #style, #title, #dir, #hreflang, #lang, #classlist, #tabindex, #accesskey {
+	width: 200px;
+}
+
+#events_panel input {
+	width: 200px;
+}
diff --git a/media/js/tinymce/plugins/xhtmlxtras/css/popup.css b/media/js/tinymce/plugins/xhtmlxtras/css/popup.css
new file mode 100644
index 0000000..e67114d
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/css/popup.css
@@ -0,0 +1,9 @@
+input.field, select.field {width:200px;}
+input.picker {width:179px; margin-left: 5px;}
+input.disabled {border-color:#F2F2F2;}
+img.picker {vertical-align:text-bottom; cursor:pointer;}
+h1 {padding: 0 0 5px 0;}
+.panel_wrapper div.current {height:160px;}
+#xhtmlxtrasdel .panel_wrapper div.current, #xhtmlxtrasins .panel_wrapper div.current {height: 230px;}
+a.browse span {display:block; width:20px; height:20px; background:url('../../../themes/advanced/img/icons.gif') -140px -20px;}
+#datetime {width:180px;}
diff --git a/media/js/tinymce/plugins/xhtmlxtras/del.htm b/media/js/tinymce/plugins/xhtmlxtras/del.htm
new file mode 100644
index 0000000..f45676e
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/del.htm
@@ -0,0 +1,169 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#xhtmlxtras_dlg.title_del_element}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/element_common.js"></script>
+	<script type="text/javascript" src="js/del.js"></script>
+	<link rel="stylesheet" type="text/css" href="css/popup.css" />
+</head>
+<body id="xhtmlxtrasins" style="display: none">
+<form onsubmit="insertDel();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.general_tab}</a></span></li>
+			<!-- <li id="events_tab"><span><a href="javascript:mcTabs.displayTab('events_tab','events_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.events_tab}</a></span></li> -->
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_general_tab}</legend>
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label id="datetimelabel" for="datetime">{#xhtmlxtras_dlg.attribute_label_datetime}</label>:</td>
+						<td>
+							<table border="0" cellspacing="0" cellpadding="0">
+								<tr> 
+									<td><input id="datetime" name="datetime" type="text" value="" maxlength="19" class="field mceFocus" /></td> 
+									<td><a href="javascript:insertDateTime('datetime');" onmousedown="return false;" class="browse"><span class="datetime" title="{#xhtmlxtras_dlg.insert_date}"></span></a></td>
+								</tr>
+							</table>
+						</td>
+					</tr>
+					<tr>
+						<td class="label"><label id="citelabel" for="cite">{#xhtmlxtras_dlg.attribute_label_cite}</label>:</td>
+						<td><input id="cite" name="cite" type="text" value="" class="field" /></td> 
+					</tr>
+				</table>
+			</fieldset>
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_attrib_tab}</legend>
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label id="titlelabel" for="title">{#xhtmlxtras_dlg.attribute_label_title}</label>:</td> 
+						<td><input id="title" name="title" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="idlabel" for="id">{#xhtmlxtras_dlg.attribute_label_id}</label>:</td> 
+						<td><input id="id" name="id" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="classlabel" for="class">{#xhtmlxtras_dlg.attribute_label_class}</label>:</td> 
+						<td>
+							<select id="class" name="class" class="field mceEditableSelect">
+								<option value="">{#not_set}</option> 
+							</select>
+						</td>
+					</tr>
+					<tr>
+						<td class="label"><label id="stylelabel" for="class">{#xhtmlxtras_dlg.attribute_label_style}</label>:</td> 
+						<td><input id="style" name="style" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="dirlabel" for="dir">{#xhtmlxtras_dlg.attribute_label_langdir}</label>:</td> 
+						<td>
+							<select id="dir" name="dir" class="field"> 
+								<option value="">{#not_set}</option> 
+								<option value="ltr">{#xhtmlxtras_dlg.attribute_option_ltr}</option> 
+								<option value="rtl">{#xhtmlxtras_dlg.attribute_option_rtl}</option> 
+							</select>
+						</td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="langlabel" for="lang">{#xhtmlxtras_dlg.attribute_label_langcode}</label>:</td> 
+						<td>
+							<input id="lang" name="lang" type="text" value="" class="field" />
+						</td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+		<div id="events_panel" class="panel">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_events_tab}</legend>
+
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label for="onfocus">onfocus</label>:</td> 
+						<td><input id="onfocus" name="onfocus" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onblur">onblur</label>:</td> 
+						<td><input id="onblur" name="onblur" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onclick">onclick</label>:</td> 
+						<td><input id="onclick" name="onclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="ondblclick">ondblclick</label>:</td> 
+						<td><input id="ondblclick" name="ondblclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousedown">onmousedown</label>:</td> 
+						<td><input id="onmousedown" name="onmousedown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseup">onmouseup</label>:</td> 
+						<td><input id="onmouseup" name="onmouseup" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseover">onmouseover</label>:</td> 
+						<td><input id="onmouseover" name="onmouseover" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousemove">onmousemove</label>:</td> 
+						<td><input id="onmousemove" name="onmousemove" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseout">onmouseout</label>:</td> 
+						<td><input id="onmouseout" name="onmouseout" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeypress">onkeypress</label>:</td> 
+						<td><input id="onkeypress" name="onkeypress" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeydown">onkeydown</label>:</td> 
+						<td><input id="onkeydown" name="onkeydown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeyup">onkeyup</label>:</td> 
+						<td><input id="onkeyup" name="onkeyup" type="text" value="" class="field" /></td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+	</div>
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#update}" />
+		</div>
+		<div style="float: left">
+			<input type="button" id="remove" name="remove" class="button" value="{#xhtmlxtras_dlg.remove}" onclick="removeDel();" style="display: none;" />
+		</div>
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+
+</form>
+
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/xhtmlxtras/editor_plugin.js b/media/js/tinymce/plugins/xhtmlxtras/editor_plugin.js
new file mode 100644
index 0000000..8c7f48e
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/editor_plugin.js
@@ -0,0 +1 @@
+(function(){tinymce.create("tinymce.plugins.XHTMLXtrasPlugin",{init:function(b,c){b.addCommand("mceCite",function(){b.windowManager.open({file:c+"/cite.htm",width:350+parseInt(b.getLang("xhtmlxtras.cite_delta_width",0)),height:250+parseInt(b.getLang("xhtmlxtras.cite_delta_height",0)),inline:1},{plugin_url:c})});b.addCommand("mceAcronym",function(){b.windowManager.open({file:c+"/acronym.htm",width:350+parseInt(b.getLang("xhtmlxtras.acronym_delta_width",0)),height:250+parseInt(b.getLang("x [...]
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/xhtmlxtras/editor_plugin_src.js b/media/js/tinymce/plugins/xhtmlxtras/editor_plugin_src.js
new file mode 100644
index 0000000..bef06f2
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/editor_plugin_src.js
@@ -0,0 +1,136 @@
+/**
+ * $Id: editor_plugin_src.js 201 2007-02-12 15:56:56Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	tinymce.create('tinymce.plugins.XHTMLXtrasPlugin', {
+		init : function(ed, url) {
+			// Register commands
+			ed.addCommand('mceCite', function() {
+				ed.windowManager.open({
+					file : url + '/cite.htm',
+					width : 350 + parseInt(ed.getLang('xhtmlxtras.cite_delta_width', 0)),
+					height : 250 + parseInt(ed.getLang('xhtmlxtras.cite_delta_height', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			ed.addCommand('mceAcronym', function() {
+				ed.windowManager.open({
+					file : url + '/acronym.htm',
+					width : 350 + parseInt(ed.getLang('xhtmlxtras.acronym_delta_width', 0)),
+					height : 250 + parseInt(ed.getLang('xhtmlxtras.acronym_delta_width', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			ed.addCommand('mceAbbr', function() {
+				ed.windowManager.open({
+					file : url + '/abbr.htm',
+					width : 350 + parseInt(ed.getLang('xhtmlxtras.abbr_delta_width', 0)),
+					height : 250 + parseInt(ed.getLang('xhtmlxtras.abbr_delta_width', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			ed.addCommand('mceDel', function() {
+				ed.windowManager.open({
+					file : url + '/del.htm',
+					width : 340 + parseInt(ed.getLang('xhtmlxtras.del_delta_width', 0)),
+					height : 310 + parseInt(ed.getLang('xhtmlxtras.del_delta_width', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			ed.addCommand('mceIns', function() {
+				ed.windowManager.open({
+					file : url + '/ins.htm',
+					width : 340 + parseInt(ed.getLang('xhtmlxtras.ins_delta_width', 0)),
+					height : 310 + parseInt(ed.getLang('xhtmlxtras.ins_delta_width', 0)),
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			ed.addCommand('mceAttributes', function() {
+				ed.windowManager.open({
+					file : url + '/attributes.htm',
+					width : 380,
+					height : 370,
+					inline : 1
+				}, {
+					plugin_url : url
+				});
+			});
+
+			// Register buttons
+			ed.addButton('cite', {title : 'xhtmlxtras.cite_desc', cmd : 'mceCite'});
+			ed.addButton('acronym', {title : 'xhtmlxtras.acronym_desc', cmd : 'mceAcronym'});
+			ed.addButton('abbr', {title : 'xhtmlxtras.abbr_desc', cmd : 'mceAbbr'});
+			ed.addButton('del', {title : 'xhtmlxtras.del_desc', cmd : 'mceDel'});
+			ed.addButton('ins', {title : 'xhtmlxtras.ins_desc', cmd : 'mceIns'});
+			ed.addButton('attribs', {title : 'xhtmlxtras.attribs_desc', cmd : 'mceAttributes'});
+
+			if (tinymce.isIE) {
+				function fix(ed, o) {
+					if (o.set) {
+						o.content = o.content.replace(/<abbr([^>]+)>/gi, '<html:abbr $1>');
+						o.content = o.content.replace(/<\/abbr>/gi, '</html:abbr>');
+					}
+				};
+
+				ed.onBeforeSetContent.add(fix);
+				ed.onPostProcess.add(fix);
+			}
+
+			ed.onNodeChange.add(function(ed, cm, n, co) {
+				n = ed.dom.getParent(n, 'CITE,ACRONYM,ABBR,DEL,INS');
+
+				cm.setDisabled('cite', co);
+				cm.setDisabled('acronym', co);
+				cm.setDisabled('abbr', co);
+				cm.setDisabled('del', co);
+				cm.setDisabled('ins', co);
+				cm.setDisabled('attribs', n && n.nodeName == 'BODY');
+				cm.setActive('cite', 0);
+				cm.setActive('acronym', 0);
+				cm.setActive('abbr', 0);
+				cm.setActive('del', 0);
+				cm.setActive('ins', 0);
+
+				// Activate all
+				if (n) {
+					do {
+						cm.setDisabled(n.nodeName.toLowerCase(), 0);
+						cm.setActive(n.nodeName.toLowerCase(), 1);
+					} while (n = n.parentNode);
+				}
+			});
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'XHTML Xtras Plugin',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				infourl : 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			};
+		}
+	});
+
+	// Register plugin
+	tinymce.PluginManager.add('xhtmlxtras', tinymce.plugins.XHTMLXtrasPlugin);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/plugins/xhtmlxtras/ins.htm b/media/js/tinymce/plugins/xhtmlxtras/ins.htm
new file mode 100644
index 0000000..9fa21c4
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/ins.htm
@@ -0,0 +1,169 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#xhtmlxtras_dlg.title_ins_element}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/editable_selects.js"></script>
+	<script type="text/javascript" src="js/element_common.js"></script>
+	<script type="text/javascript" src="js/ins.js"></script>
+	<link rel="stylesheet" type="text/css" href="css/popup.css" />
+</head>
+<body id="xhtmlxtrasins" style="display: none">
+<form onsubmit="insertIns();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.general_tab}</a></span></li>
+			<!-- <li id="events_tab"><span><a href="javascript:mcTabs.displayTab('events_tab','events_panel');" onmousedown="return false;">{#xhtmlxtras_dlg.events_tab}</a></span></li> -->
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_general_tab}</legend>
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label id="datetimelabel" for="datetime">{#xhtmlxtras_dlg.attribute_label_datetime}</label>:</td> 
+						<td>
+							<table border="0" cellspacing="0" cellpadding="0">
+								<tr> 
+									<td><input id="datetime" name="datetime" type="text" value="" maxlength="19" class="field mceFocus" /></td> 
+									<td><a href="javascript:insertDateTime('datetime');" onmousedown="return false;" class="browse"><span class="datetime" title="{#xhtmlxtras_dlg.insert_date}"></span></a></td>
+								</tr>
+							</table>
+						</td>
+					</tr>
+					<tr>
+						<td class="label"><label id="citelabel" for="cite">{#xhtmlxtras_dlg.attribute_label_cite}</label>:</td> 
+						<td><input id="cite" name="cite" type="text" value="" class="field" /></td> 
+					</tr>
+				</table>
+			</fieldset>
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_attrib_tab}</legend>
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label id="titlelabel" for="title">{#xhtmlxtras_dlg.attribute_label_title}</label>:</td> 
+						<td><input id="title" name="title" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="idlabel" for="id">{#xhtmlxtras_dlg.attribute_label_id}</label>:</td> 
+						<td><input id="id" name="id" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="classlabel" for="class">{#xhtmlxtras_dlg.attribute_label_class}</label>:</td> 
+						<td>
+							<select id="class" name="class" class="field mceEditableSelect">
+								<option value="">{#not_set}</option> 
+							</select>
+						</td>
+					</tr>
+					<tr>
+						<td class="label"><label id="stylelabel" for="class">{#xhtmlxtras_dlg.attribute_label_style}</label>:</td> 
+						<td><input id="style" name="style" type="text" value="" class="field" /></td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="dirlabel" for="dir">{#xhtmlxtras_dlg.attribute_label_langdir}</label>:</td> 
+						<td>
+							<select id="dir" name="dir" class="field"> 
+								<option value="">{#not_set}</option> 
+								<option value="ltr">{#xhtmlxtras_dlg.attribute_option_ltr}</option> 
+								<option value="rtl">{#xhtmlxtras_dlg.attribute_option_rtl}</option> 
+							</select>
+						</td> 
+					</tr>
+					<tr>
+						<td class="label"><label id="langlabel" for="lang">{#xhtmlxtras_dlg.attribute_label_langcode}</label>:</td> 
+						<td>
+							<input id="lang" name="lang" type="text" value="" class="field" />
+						</td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+		<div id="events_panel" class="panel">
+			<fieldset>
+				<legend>{#xhtmlxtras_dlg.fieldset_events_tab}</legend>
+
+				<table border="0" cellpadding="0" cellspacing="4">
+					<tr>
+						<td class="label"><label for="onfocus">onfocus</label>:</td> 
+						<td><input id="onfocus" name="onfocus" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onblur">onblur</label>:</td> 
+						<td><input id="onblur" name="onblur" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onclick">onclick</label>:</td> 
+						<td><input id="onclick" name="onclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="ondblclick">ondblclick</label>:</td> 
+						<td><input id="ondblclick" name="ondblclick" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousedown">onmousedown</label>:</td> 
+						<td><input id="onmousedown" name="onmousedown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseup">onmouseup</label>:</td> 
+						<td><input id="onmouseup" name="onmouseup" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseover">onmouseover</label>:</td> 
+						<td><input id="onmouseover" name="onmouseover" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmousemove">onmousemove</label>:</td> 
+						<td><input id="onmousemove" name="onmousemove" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onmouseout">onmouseout</label>:</td> 
+						<td><input id="onmouseout" name="onmouseout" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeypress">onkeypress</label>:</td> 
+						<td><input id="onkeypress" name="onkeypress" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeydown">onkeydown</label>:</td> 
+						<td><input id="onkeydown" name="onkeydown" type="text" value="" class="field" /></td> 
+					</tr>
+
+					<tr>
+						<td class="label"><label for="onkeyup">onkeyup</label>:</td> 
+						<td><input id="onkeyup" name="onkeyup" type="text" value="" class="field" /></td> 
+					</tr>
+				</table>
+			</fieldset>
+		</div>
+	</div>
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#update}" />
+		</div>
+		<div style="float: left">
+			<input type="button" id="remove" name="remove" class="button" value="{#xhtmlxtras_dlg.remove}" onclick="removeIns();" style="display: none;" />
+		</div>
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+
+</form>
+
+</body>
+</html>
diff --git a/media/js/tinymce/plugins/xhtmlxtras/js/abbr.js b/media/js/tinymce/plugins/xhtmlxtras/js/abbr.js
new file mode 100644
index 0000000..e84b6a8
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/js/abbr.js
@@ -0,0 +1,25 @@
+ /**
+ * $Id: editor_plugin_src.js 42 2006-08-08 14:32:24Z spocke $
+ *
+ * @author Moxiecode - based on work by Andrew Tetlaw
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+function init() {
+	SXE.initElementDialog('abbr');
+	if (SXE.currentAction == "update") {
+		SXE.showRemoveButton();
+	}
+}
+
+function insertAbbr() {
+	SXE.insertElement(tinymce.isIE ? 'html:abbr' : 'abbr');
+	tinyMCEPopup.close();
+}
+
+function removeAbbr() {
+	SXE.removeElement('abbr');
+	tinyMCEPopup.close();
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/xhtmlxtras/js/acronym.js b/media/js/tinymce/plugins/xhtmlxtras/js/acronym.js
new file mode 100644
index 0000000..933d122
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/js/acronym.js
@@ -0,0 +1,25 @@
+ /**
+ * $Id: editor_plugin_src.js 42 2006-08-08 14:32:24Z spocke $
+ *
+ * @author Moxiecode - based on work by Andrew Tetlaw
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+function init() {
+	SXE.initElementDialog('acronym');
+	if (SXE.currentAction == "update") {
+		SXE.showRemoveButton();
+	}
+}
+
+function insertAcronym() {
+	SXE.insertElement('acronym');
+	tinyMCEPopup.close();
+}
+
+function removeAcronym() {
+	SXE.removeElement('acronym');
+	tinyMCEPopup.close();
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/xhtmlxtras/js/attributes.js b/media/js/tinymce/plugins/xhtmlxtras/js/attributes.js
new file mode 100644
index 0000000..23c7fa4
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/js/attributes.js
@@ -0,0 +1,123 @@
+ /**
+ * $Id: editor_plugin_src.js 42 2006-08-08 14:32:24Z spocke $
+ *
+ * @author Moxiecode - based on work by Andrew Tetlaw
+ * @copyright Copyright � 2004-2006, Moxiecode Systems AB, All rights reserved.
+ */
+
+function init() {
+	tinyMCEPopup.resizeToInnerSize();
+	var inst = tinyMCEPopup.editor;
+	var dom = inst.dom;
+	var elm = inst.selection.getNode();
+	var f = document.forms[0];
+	var onclick = dom.getAttrib(elm, 'onclick');
+
+	setFormValue('title', dom.getAttrib(elm, 'title'));
+	setFormValue('id', dom.getAttrib(elm, 'id'));
+	setFormValue('style', dom.getAttrib(elm, "style"));
+	setFormValue('dir', dom.getAttrib(elm, 'dir'));
+	setFormValue('lang', dom.getAttrib(elm, 'lang'));
+	setFormValue('tabindex', dom.getAttrib(elm, 'tabindex', typeof(elm.tabindex) != "undefined" ? elm.tabindex : ""));
+	setFormValue('accesskey', dom.getAttrib(elm, 'accesskey', typeof(elm.accesskey) != "undefined" ? elm.accesskey : ""));
+	setFormValue('onfocus', dom.getAttrib(elm, 'onfocus'));
+	setFormValue('onblur', dom.getAttrib(elm, 'onblur'));
+	setFormValue('onclick', onclick);
+	setFormValue('ondblclick', dom.getAttrib(elm, 'ondblclick'));
+	setFormValue('onmousedown', dom.getAttrib(elm, 'onmousedown'));
+	setFormValue('onmouseup', dom.getAttrib(elm, 'onmouseup'));
+	setFormValue('onmouseover', dom.getAttrib(elm, 'onmouseover'));
+	setFormValue('onmousemove', dom.getAttrib(elm, 'onmousemove'));
+	setFormValue('onmouseout', dom.getAttrib(elm, 'onmouseout'));
+	setFormValue('onkeypress', dom.getAttrib(elm, 'onkeypress'));
+	setFormValue('onkeydown', dom.getAttrib(elm, 'onkeydown'));
+	setFormValue('onkeyup', dom.getAttrib(elm, 'onkeyup'));
+	className = dom.getAttrib(elm, 'class');
+
+	addClassesToList('classlist', 'advlink_styles');
+	selectByValue(f, 'classlist', className, true);
+
+	TinyMCE_EditableSelects.init();
+}
+
+function setFormValue(name, value) {
+	if(value && document.forms[0].elements[name]){
+		document.forms[0].elements[name].value = value;
+	}
+}
+
+function insertAction() {
+	var inst = tinyMCEPopup.editor;
+	var elm = inst.selection.getNode();
+
+	tinyMCEPopup.execCommand("mceBeginUndoLevel");	
+	setAllAttribs(elm);
+	tinyMCEPopup.execCommand("mceEndUndoLevel");
+	tinyMCEPopup.close();
+}
+
+function setAttrib(elm, attrib, value) {
+	var formObj = document.forms[0];
+	var valueElm = formObj.elements[attrib.toLowerCase()];
+	var inst = tinyMCEPopup.editor;
+	var dom = inst.dom;
+
+	if (typeof(value) == "undefined" || value == null) {
+		value = "";
+
+		if (valueElm)
+			value = valueElm.value;
+	}
+
+	if (value != "") {
+		dom.setAttrib(elm, attrib.toLowerCase(), value);
+
+		if (attrib == "style")
+			attrib = "style.cssText";
+
+		if (attrib.substring(0, 2) == 'on')
+			value = 'return true;' + value;
+
+		if (attrib == "class")
+			attrib = "className";
+
+		elm[attrib]=value;
+	} else
+		elm.removeAttribute(attrib);
+}
+
+function setAllAttribs(elm) {
+	var f = document.forms[0];
+
+	setAttrib(elm, 'title');
+	setAttrib(elm, 'id');
+	setAttrib(elm, 'style');
+	setAttrib(elm, 'class', getSelectValue(f, 'classlist'));
+	setAttrib(elm, 'dir');
+	setAttrib(elm, 'lang');
+	setAttrib(elm, 'tabindex');
+	setAttrib(elm, 'accesskey');
+	setAttrib(elm, 'onfocus');
+	setAttrib(elm, 'onblur');
+	setAttrib(elm, 'onclick');
+	setAttrib(elm, 'ondblclick');
+	setAttrib(elm, 'onmousedown');
+	setAttrib(elm, 'onmouseup');
+	setAttrib(elm, 'onmouseover');
+	setAttrib(elm, 'onmousemove');
+	setAttrib(elm, 'onmouseout');
+	setAttrib(elm, 'onkeypress');
+	setAttrib(elm, 'onkeydown');
+	setAttrib(elm, 'onkeyup');
+
+	// Refresh in old MSIE
+//	if (tinyMCE.isMSIE5)
+//		elm.outerHTML = elm.outerHTML;
+}
+
+function insertAttribute() {
+	tinyMCEPopup.close();
+}
+
+tinyMCEPopup.onInit.add(init);
+tinyMCEPopup.requireLangPack();
diff --git a/media/js/tinymce/plugins/xhtmlxtras/js/cite.js b/media/js/tinymce/plugins/xhtmlxtras/js/cite.js
new file mode 100644
index 0000000..c36f7fd
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/js/cite.js
@@ -0,0 +1,25 @@
+ /**
+ * $Id: editor_plugin_src.js 42 2006-08-08 14:32:24Z spocke $
+ *
+ * @author Moxiecode - based on work by Andrew Tetlaw
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+function init() {
+	SXE.initElementDialog('cite');
+	if (SXE.currentAction == "update") {
+		SXE.showRemoveButton();
+	}
+}
+
+function insertCite() {
+	SXE.insertElement('cite');
+	tinyMCEPopup.close();
+}
+
+function removeCite() {
+	SXE.removeElement('cite');
+	tinyMCEPopup.close();
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/xhtmlxtras/js/del.js b/media/js/tinymce/plugins/xhtmlxtras/js/del.js
new file mode 100644
index 0000000..7049f2b
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/js/del.js
@@ -0,0 +1,60 @@
+ /**
+ * $Id: editor_plugin_src.js 42 2006-08-08 14:32:24Z spocke $
+ *
+ * @author Moxiecode - based on work by Andrew Tetlaw
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+function init() {
+	SXE.initElementDialog('del');
+	if (SXE.currentAction == "update") {
+		setFormValue('datetime', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'datetime'));
+		setFormValue('cite', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'cite'));
+		SXE.showRemoveButton();
+	}
+}
+
+function setElementAttribs(elm) {
+	setAllCommonAttribs(elm);
+	setAttrib(elm, 'datetime');
+	setAttrib(elm, 'cite');
+}
+
+function insertDel() {
+	var elm = tinyMCEPopup.editor.dom.getParent(SXE.focusElement, 'DEL');
+
+	tinyMCEPopup.execCommand('mceBeginUndoLevel');
+	if (elm == null) {
+		var s = SXE.inst.selection.getContent();
+		if(s.length > 0) {
+			insertInlineElement('del');
+			var elementArray = tinymce.grep(SXE.inst.dom.select('del'), function(n) {return n.id == '#sxe_temp_del#';});
+			for (var i=0; i<elementArray.length; i++) {
+				var elm = elementArray[i];
+				setElementAttribs(elm);
+			}
+		}
+	} else {
+		setElementAttribs(elm);
+	}
+	tinyMCEPopup.editor.nodeChanged();
+	tinyMCEPopup.execCommand('mceEndUndoLevel');
+	tinyMCEPopup.close();
+}
+
+function insertInlineElement(en) {
+	var ed = tinyMCEPopup.editor, dom = ed.dom;
+
+	ed.getDoc().execCommand('FontName', false, 'mceinline');
+	tinymce.each(dom.select(tinymce.isWebKit ? 'span' : 'font'), function(n) {
+		if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline')
+			dom.replace(dom.create(en), n, 1);
+	});
+}
+
+function removeDel() {
+	SXE.removeElement('del');
+	tinyMCEPopup.close();
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/xhtmlxtras/js/element_common.js b/media/js/tinymce/plugins/xhtmlxtras/js/element_common.js
new file mode 100644
index 0000000..70f168a
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/js/element_common.js
@@ -0,0 +1,231 @@
+ /**
+ * $Id: editor_plugin_src.js 42 2006-08-08 14:32:24Z spocke $
+ *
+ * @author Moxiecode - based on work by Andrew Tetlaw
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+tinyMCEPopup.requireLangPack();
+
+function initCommonAttributes(elm) {
+	var formObj = document.forms[0], dom = tinyMCEPopup.editor.dom;
+
+	// Setup form data for common element attributes
+	setFormValue('title', dom.getAttrib(elm, 'title'));
+	setFormValue('id', dom.getAttrib(elm, 'id'));
+	selectByValue(formObj, 'class', dom.getAttrib(elm, 'class'), true);
+	setFormValue('style', dom.getAttrib(elm, 'style'));
+	selectByValue(formObj, 'dir', dom.getAttrib(elm, 'dir'));
+	setFormValue('lang', dom.getAttrib(elm, 'lang'));
+	setFormValue('onfocus', dom.getAttrib(elm, 'onfocus'));
+	setFormValue('onblur', dom.getAttrib(elm, 'onblur'));
+	setFormValue('onclick', dom.getAttrib(elm, 'onclick'));
+	setFormValue('ondblclick', dom.getAttrib(elm, 'ondblclick'));
+	setFormValue('onmousedown', dom.getAttrib(elm, 'onmousedown'));
+	setFormValue('onmouseup', dom.getAttrib(elm, 'onmouseup'));
+	setFormValue('onmouseover', dom.getAttrib(elm, 'onmouseover'));
+	setFormValue('onmousemove', dom.getAttrib(elm, 'onmousemove'));
+	setFormValue('onmouseout', dom.getAttrib(elm, 'onmouseout'));
+	setFormValue('onkeypress', dom.getAttrib(elm, 'onkeypress'));
+	setFormValue('onkeydown', dom.getAttrib(elm, 'onkeydown'));
+	setFormValue('onkeyup', dom.getAttrib(elm, 'onkeyup'));
+}
+
+function setFormValue(name, value) {
+	if(document.forms[0].elements[name]) document.forms[0].elements[name].value = value;
+}
+
+function insertDateTime(id) {
+	document.getElementById(id).value = getDateTime(new Date(), "%Y-%m-%dT%H:%M:%S");
+}
+
+function getDateTime(d, fmt) {
+	fmt = fmt.replace("%D", "%m/%d/%y");
+	fmt = fmt.replace("%r", "%I:%M:%S %p");
+	fmt = fmt.replace("%Y", "" + d.getFullYear());
+	fmt = fmt.replace("%y", "" + d.getYear());
+	fmt = fmt.replace("%m", addZeros(d.getMonth()+1, 2));
+	fmt = fmt.replace("%d", addZeros(d.getDate(), 2));
+	fmt = fmt.replace("%H", "" + addZeros(d.getHours(), 2));
+	fmt = fmt.replace("%M", "" + addZeros(d.getMinutes(), 2));
+	fmt = fmt.replace("%S", "" + addZeros(d.getSeconds(), 2));
+	fmt = fmt.replace("%I", "" + ((d.getHours() + 11) % 12 + 1));
+	fmt = fmt.replace("%p", "" + (d.getHours() < 12 ? "AM" : "PM"));
+	fmt = fmt.replace("%%", "%");
+
+	return fmt;
+}
+
+function addZeros(value, len) {
+	var i;
+
+	value = "" + value;
+
+	if (value.length < len) {
+		for (i=0; i<(len-value.length); i++)
+			value = "0" + value;
+	}
+
+	return value;
+}
+
+function selectByValue(form_obj, field_name, value, add_custom, ignore_case) {
+	if (!form_obj || !form_obj.elements[field_name])
+		return;
+
+	var sel = form_obj.elements[field_name];
+
+	var found = false;
+	for (var i=0; i<sel.options.length; i++) {
+		var option = sel.options[i];
+
+		if (option.value == value || (ignore_case && option.value.toLowerCase() == value.toLowerCase())) {
+			option.selected = true;
+			found = true;
+		} else
+			option.selected = false;
+	}
+
+	if (!found && add_custom && value != '') {
+		var option = new Option('Value: ' + value, value);
+		option.selected = true;
+		sel.options[sel.options.length] = option;
+	}
+
+	return found;
+}
+
+function setAttrib(elm, attrib, value) {
+	var formObj = document.forms[0];
+	var valueElm = formObj.elements[attrib.toLowerCase()];
+	tinyMCEPopup.editor.dom.setAttrib(elm, attrib, value || valueElm.value);
+}
+
+function setAllCommonAttribs(elm) {
+	setAttrib(elm, 'title');
+	setAttrib(elm, 'id');
+	setAttrib(elm, 'class');
+	setAttrib(elm, 'style');
+	setAttrib(elm, 'dir');
+	setAttrib(elm, 'lang');
+	/*setAttrib(elm, 'onfocus');
+	setAttrib(elm, 'onblur');
+	setAttrib(elm, 'onclick');
+	setAttrib(elm, 'ondblclick');
+	setAttrib(elm, 'onmousedown');
+	setAttrib(elm, 'onmouseup');
+	setAttrib(elm, 'onmouseover');
+	setAttrib(elm, 'onmousemove');
+	setAttrib(elm, 'onmouseout');
+	setAttrib(elm, 'onkeypress');
+	setAttrib(elm, 'onkeydown');
+	setAttrib(elm, 'onkeyup');*/
+}
+
+SXE = {
+	currentAction : "insert",
+	inst : tinyMCEPopup.editor,
+	updateElement : null
+}
+
+SXE.focusElement = SXE.inst.selection.getNode();
+
+SXE.initElementDialog = function(element_name) {
+	addClassesToList('class', 'xhtmlxtras_styles');
+	TinyMCE_EditableSelects.init();
+
+	element_name = element_name.toLowerCase();
+	var elm = SXE.inst.dom.getParent(SXE.focusElement, element_name.toUpperCase());
+	if (elm != null && elm.nodeName.toUpperCase() == element_name.toUpperCase()) {
+		SXE.currentAction = "update";
+	}
+
+	if (SXE.currentAction == "update") {
+		initCommonAttributes(elm);
+		SXE.updateElement = elm;
+	}
+
+	document.forms[0].insert.value = tinyMCEPopup.getLang(SXE.currentAction, 'Insert', true); 
+}
+
+SXE.insertElement = function(element_name) {
+	var elm = SXE.inst.dom.getParent(SXE.focusElement, element_name.toUpperCase()), h, tagName;
+
+	tinyMCEPopup.execCommand('mceBeginUndoLevel');
+	if (elm == null) {
+		var s = SXE.inst.selection.getContent();
+		if(s.length > 0) {
+			tagName = element_name;
+
+			if (tinymce.isIE && element_name.indexOf('html:') == 0)
+				element_name = element_name.substring(5).toLowerCase();
+
+			insertInlineElement(element_name);
+			var elementArray = tinymce.grep(SXE.inst.dom.select(element_name));
+			for (var i=0; i<elementArray.length; i++) {
+				var elm = elementArray[i];
+
+				if (SXE.inst.dom.getAttrib(elm, '_mce_new')) {
+					elm.id = '';
+					elm.setAttribute('id', '');
+					elm.removeAttribute('id');
+					elm.removeAttribute('_mce_new');
+
+					setAllCommonAttribs(elm);
+				}
+			}
+		}
+	} else {
+		setAllCommonAttribs(elm);
+	}
+	SXE.inst.nodeChanged();
+	tinyMCEPopup.execCommand('mceEndUndoLevel');
+}
+
+SXE.removeElement = function(element_name){
+	element_name = element_name.toLowerCase();
+	elm = SXE.inst.dom.getParent(SXE.focusElement, element_name.toUpperCase());
+	if(elm && elm.nodeName.toUpperCase() == element_name.toUpperCase()){
+		tinyMCEPopup.execCommand('mceBeginUndoLevel');
+		tinyMCE.execCommand('mceRemoveNode', false, elm);
+		SXE.inst.nodeChanged();
+		tinyMCEPopup.execCommand('mceEndUndoLevel');
+	}
+}
+
+SXE.showRemoveButton = function() {
+		document.getElementById("remove").style.display = 'block';
+}
+
+SXE.containsClass = function(elm,cl) {
+	return (elm.className.indexOf(cl) > -1) ? true : false;
+}
+
+SXE.removeClass = function(elm,cl) {
+	if(elm.className == null || elm.className == "" || !SXE.containsClass(elm,cl)) {
+		return true;
+	}
+	var classNames = elm.className.split(" ");
+	var newClassNames = "";
+	for (var x = 0, cnl = classNames.length; x < cnl; x++) {
+		if (classNames[x] != cl) {
+			newClassNames += (classNames[x] + " ");
+		}
+	}
+	elm.className = newClassNames.substring(0,newClassNames.length-1); //removes extra space at the end
+}
+
+SXE.addClass = function(elm,cl) {
+	if(!SXE.containsClass(elm,cl)) elm.className ? elm.className += " " + cl : elm.className = cl;
+	return true;
+}
+
+function insertInlineElement(en) {
+	var ed = tinyMCEPopup.editor, dom = ed.dom;
+
+	ed.getDoc().execCommand('FontName', false, 'mceinline');
+	tinymce.each(dom.select('span,font'), function(n) {
+		if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline')
+			dom.replace(dom.create(en, {_mce_new : 1}), n, 1);
+	});
+}
diff --git a/media/js/tinymce/plugins/xhtmlxtras/js/ins.js b/media/js/tinymce/plugins/xhtmlxtras/js/ins.js
new file mode 100644
index 0000000..4fcc998
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/js/ins.js
@@ -0,0 +1,59 @@
+ /**
+ * $Id: editor_plugin_src.js 42 2006-08-08 14:32:24Z spocke $
+ *
+ * @author Moxiecode - based on work by Andrew Tetlaw
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+function init() {
+	SXE.initElementDialog('ins');
+	if (SXE.currentAction == "update") {
+		setFormValue('datetime', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'datetime'));
+		setFormValue('cite', tinyMCEPopup.editor.dom.getAttrib(SXE.updateElement, 'cite'));
+		SXE.showRemoveButton();
+	}
+}
+
+function setElementAttribs(elm) {
+	setAllCommonAttribs(elm);
+	setAttrib(elm, 'datetime');
+	setAttrib(elm, 'cite');
+}
+
+function insertIns() {
+	var elm = tinyMCEPopup.editor.dom.getParent(SXE.focusElement, 'INS');
+	tinyMCEPopup.execCommand('mceBeginUndoLevel');
+	if (elm == null) {
+		var s = SXE.inst.selection.getContent();
+		if(s.length > 0) {
+			insertInlineElement('INS');
+			var elementArray = tinymce.grep(SXE.inst.dom.select('ins'), function(n) {return n.id == '#sxe_temp_ins#';});
+			for (var i=0; i<elementArray.length; i++) {
+				var elm = elementArray[i];
+				setElementAttribs(elm);
+			}
+		}
+	} else {
+		setElementAttribs(elm);
+	}
+	tinyMCEPopup.editor.nodeChanged();
+	tinyMCEPopup.execCommand('mceEndUndoLevel');
+	tinyMCEPopup.close();
+}
+
+function removeIns() {
+	SXE.removeElement('ins');
+	tinyMCEPopup.close();
+}
+
+function insertInlineElement(en) {
+	var ed = tinyMCEPopup.editor, dom = ed.dom;
+
+	ed.getDoc().execCommand('FontName', false, 'mceinline');
+	tinymce.each(dom.select(tinymce.isWebKit ? 'span' : 'font'), function(n) {
+		if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline')
+			dom.replace(dom.create(en), n, 1);
+	});
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/plugins/xhtmlxtras/langs/en_dlg.js b/media/js/tinymce/plugins/xhtmlxtras/langs/en_dlg.js
new file mode 100644
index 0000000..45b6b26
--- /dev/null
+++ b/media/js/tinymce/plugins/xhtmlxtras/langs/en_dlg.js
@@ -0,0 +1,32 @@
+tinyMCE.addI18n('en.xhtmlxtras_dlg',{
+attribute_label_title:"Title",
+attribute_label_id:"ID",
+attribute_label_class:"Class",
+attribute_label_style:"Style",
+attribute_label_cite:"Cite",
+attribute_label_datetime:"Date/Time",
+attribute_label_langdir:"Text Direction",
+attribute_option_ltr:"Left to right",
+attribute_option_rtl:"Right to left",
+attribute_label_langcode:"Language",
+attribute_label_tabindex:"TabIndex",
+attribute_label_accesskey:"AccessKey",
+attribute_events_tab:"Events",
+attribute_attrib_tab:"Attributes",
+general_tab:"General",
+attrib_tab:"Attributes",
+events_tab:"Events",
+fieldset_general_tab:"General Settings",
+fieldset_attrib_tab:"Element Attributes",
+fieldset_events_tab:"Element Events",
+title_ins_element:"Insertion Element",
+title_del_element:"Deletion Element",
+title_acronym_element:"Acronym Element",
+title_abbr_element:"Abbreviation Element",
+title_cite_element:"Citation Element",
+remove:"Remove",
+insert_date:"Insert current date/time",
+option_ltr:"Left to right",
+option_rtl:"Right to left",
+attribs_title:"Insert/Edit Attributes"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/themes/advanced/about.htm b/media/js/tinymce/themes/advanced/about.htm
new file mode 100644
index 0000000..e5df7aa
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/about.htm
@@ -0,0 +1,56 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advanced_dlg.about_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="js/about.js"></script>
+</head>
+<body id="about" style="display: none">
+		<div class="tabs">
+			<ul>
+				<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advanced_dlg.about_general}</a></span></li>
+				<li id="help_tab" style="display:none"><span><a href="javascript:mcTabs.displayTab('help_tab','help_panel');" onmousedown="return false;">{#advanced_dlg.about_help}</a></span></li>
+				<li id="plugins_tab"><span><a href="javascript:mcTabs.displayTab('plugins_tab','plugins_panel');" onmousedown="return false;">{#advanced_dlg.about_plugins}</a></span></li>
+			</ul>
+		</div>
+
+		<div class="panel_wrapper">
+			<div id="general_panel" class="panel current">
+				<h3>{#advanced_dlg.about_title}</h3>
+				<p>Version: <span id="version"></span> (<span id="date"></span>)</p>
+				<p>TinyMCE is a platform independent web based Javascript HTML WYSIWYG editor control released as Open Source under <a href="../../license.txt" target="_blank">LGPL</a>
+				by Moxiecode Systems AB. It has the ability to convert HTML TEXTAREA fields or other HTML elements to editor instances.</p>
+				<p>Copyright © 2003-2008, <a href="http://www.moxiecode.com" target="_blank">Moxiecode Systems AB</a>, All rights reserved.</p>
+				<p>For more information about this software visit the <a href="http://tinymce.moxiecode.com" target="_blank">TinyMCE website</a>.</p>
+
+				<div id="buttoncontainer">
+					<a href="http://www.moxiecode.com" target="_blank"><img src="http://tinymce.moxiecode.com/images/gotmoxie.png" alt="Got Moxie?" border="0" /></a>
+					<a href="http://sourceforge.net/projects/tinymce/" target="_blank"><img src="http://sourceforge.net/sflogo.php?group_id=103281" alt="Hosted By Sourceforge" border="0" /></a>
+					<a href="http://www.freshmeat.net/projects/tinymce" target="_blank"><img src="http://tinymce.moxiecode.com/images/fm.gif" alt="Also on freshmeat" border="0" /></a>
+				</div>
+			</div>
+
+			<div id="plugins_panel" class="panel">
+				<div id="pluginscontainer">
+					<h3>{#advanced_dlg.about_loaded}</h3>
+
+					<div id="plugintablecontainer">
+					</div>
+
+					<p> </p>
+				</div>
+			</div>
+
+			<div id="help_panel" class="panel noscroll" style="overflow: visible;">
+				<div id="iframecontainer"></div>
+			</div>
+		</div>
+
+		<div class="mceActionPanel">
+			<div style="float: right">
+				<input type="button" id="cancel" name="cancel" value="{#close}" onclick="tinyMCEPopup.close();" />
+			</div>
+		</div>
+</body>
+</html>
diff --git a/media/js/tinymce/themes/advanced/anchor.htm b/media/js/tinymce/themes/advanced/anchor.htm
new file mode 100644
index 0000000..42095a1
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/anchor.htm
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advanced_dlg.anchor_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/anchor.js"></script>
+</head>
+<body style="display: none">
+<form onsubmit="AnchorDialog.update();return false;" action="#">
+	<table border="0" cellpadding="4" cellspacing="0">
+		<tr>
+			<td colspan="2" class="title">{#advanced_dlg.anchor_title}</td>
+		</tr>
+		<tr>
+			<td class="nowrap">{#advanced_dlg.anchor_name}:</td>
+			<td><input name="anchorName" type="text" class="mceFocus" id="anchorName" value="" style="width: 200px" /></td>
+		</tr>
+	</table>
+
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#update}" />
+		</div>
+
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/themes/advanced/charmap.htm b/media/js/tinymce/themes/advanced/charmap.htm
new file mode 100644
index 0000000..f11a38a
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/charmap.htm
@@ -0,0 +1,53 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advanced_dlg.charmap_title}</title>
+	<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/charmap.js"></script>
+</head>
+<body id="charmap" style="display:none">
+<table align="center" border="0" cellspacing="0" cellpadding="2">
+    <tr>
+        <td colspan="2" class="title">{#advanced_dlg.charmap_title}</td>
+    </tr>
+    <tr>
+        <td id="charmapView" rowspan="2" align="left" valign="top">
+			<!-- Chars will be rendered here -->
+        </td>
+        <td width="100" align="center" valign="top">
+            <table border="0" cellpadding="0" cellspacing="0" width="100" style="height:100px">
+                <tr>
+                    <td id="codeV"> </td>
+                </tr>
+                <tr>
+                    <td id="codeN"> </td>
+                </tr>
+            </table>
+        </td>
+    </tr>
+    <tr>
+        <td valign="bottom" style="padding-bottom: 3px;">
+            <table width="100" align="center" border="0" cellpadding="2" cellspacing="0">
+                <tr>
+                    <td align="center" style="border-left: 1px solid #666699; border-top: 1px solid #666699; border-right: 1px solid #666699;">HTML-Code</td>
+                </tr>
+                <tr>
+                    <td style="font-size: 16px; font-weight: bold; border-left: 1px solid #666699; border-bottom: 1px solid #666699; border-right: 1px solid #666699;" id="codeA" align="center"> </td>
+                </tr>
+                <tr>
+                    <td style="font-size: 1px;"> </td>
+                </tr>
+                <tr>
+                    <td align="center" style="border-left: 1px solid #666699; border-top: 1px solid #666699; border-right: 1px solid #666699;">NUM-Code</td>
+                </tr>
+                <tr>
+                    <td style="font-size: 16px; font-weight: bold; border-left: 1px solid #666699; border-bottom: 1px solid #666699; border-right: 1px solid #666699;" id="codeB" align="center"> </td>
+                </tr>
+            </table>
+        </td>
+    </tr>
+</table>
+
+</body>
+</html>
diff --git a/media/js/tinymce/themes/advanced/color_picker.htm b/media/js/tinymce/themes/advanced/color_picker.htm
new file mode 100644
index 0000000..cbd6b88
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/color_picker.htm
@@ -0,0 +1,75 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advanced_dlg.colorpicker_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="js/color_picker.js"></script>
+</head>
+<body id="colorpicker" style="display: none">
+<form onsubmit="insertAction();return false" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="picker_tab" class="current"><span><a href="javascript:mcTabs.displayTab('picker_tab','picker_panel');" onmousedown="return false;">{#advanced_dlg.colorpicker_picker_tab}</a></span></li>
+			<li id="rgb_tab"><span><a href="javascript:;" onclick="generateWebColors();mcTabs.displayTab('rgb_tab','rgb_panel');" onmousedown="return false;">{#advanced_dlg.colorpicker_palette_tab}</a></span></li>
+			<li id="named_tab"><span><a  href="javascript:;" onclick="generateNamedColors();javascript:mcTabs.displayTab('named_tab','named_panel');" onmousedown="return false;">{#advanced_dlg.colorpicker_named_tab}</a></span></li>
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="picker_panel" class="panel current">
+			<fieldset>
+				<legend>{#advanced_dlg.colorpicker_picker_title}</legend>
+				<div id="picker">
+					<img id="colors" src="img/colorpicker.jpg" onclick="computeColor(event)" onmousedown="isMouseDown = true;return false;" onmouseup="isMouseDown = false;" onmousemove="if (isMouseDown && isMouseOver) computeColor(event); return false;" onmouseover="isMouseOver=true;" onmouseout="isMouseOver=false;" alt="" />
+
+					<div id="light">
+						<!-- Will be filled with divs -->
+					</div>
+
+					<br style="clear: both" />
+				</div>
+			</fieldset>
+		</div>
+
+		<div id="rgb_panel" class="panel">
+			<fieldset>
+				<legend>{#advanced_dlg.colorpicker_palette_title}</legend>
+				<div id="webcolors">
+					<!-- Gets filled with web safe colors-->
+				</div>
+
+				<br style="clear: both" />
+			</fieldset>
+		</div>
+
+		<div id="named_panel" class="panel">
+			<fieldset>
+				<legend>{#advanced_dlg.colorpicker_named_title}</legend>
+				<div id="namedcolors">
+					<!-- Gets filled with named colors-->
+				</div>
+
+				<br style="clear: both" />
+
+				<div id="colornamecontainer">
+					{#advanced_dlg.colorpicker_name} <span id="colorname"></span>
+				</div>
+			</fieldset>
+		</div>
+	</div>
+
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#apply}" />
+		</div>
+
+		<div id="preview"></div>
+
+		<div id="previewblock">
+			<label for="color">{#advanced_dlg.colorpicker_color}</label> <input id="color" type="text" size="8" maxlength="8" class="text mceFocus" />
+		</div>
+	</div>
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/themes/advanced/editor_template.js b/media/js/tinymce/themes/advanced/editor_template.js
new file mode 100644
index 0000000..628c793
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/editor_template.js
@@ -0,0 +1 @@
+(function(e){var d=e.DOM,b=e.dom.Event,h=e.extend,f=e.each,a=e.util.Cookie,g,c=e.explode;e.ThemeManager.requireLangPack("advanced");e.create("tinymce.themes.AdvancedTheme",{sizes:[8,10,12,14,18,24,36],controls:{bold:["bold_desc","Bold"],italic:["italic_desc","Italic"],underline:["underline_desc","Underline"],strikethrough:["striketrough_desc","Strikethrough"],justifyleft:["justifyleft_desc","JustifyLeft"],justifycenter:["justifycenter_desc","JustifyCenter"],justifyright:["justifyright_de [...]
\ No newline at end of file
diff --git a/media/js/tinymce/themes/advanced/editor_template_src.js b/media/js/tinymce/themes/advanced/editor_template_src.js
new file mode 100644
index 0000000..21eb259
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/editor_template_src.js
@@ -0,0 +1,1153 @@
+/**
+ * $Id: editor_template_src.js 1045 2009-03-04 20:03:18Z spocke $
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function(tinymce) {
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, each = tinymce.each, Cookie = tinymce.util.Cookie, lastExtID, explode = tinymce.explode;
+
+	// Tell it to load theme specific language pack(s)
+	tinymce.ThemeManager.requireLangPack('advanced');
+
+	tinymce.create('tinymce.themes.AdvancedTheme', {
+		sizes : [8, 10, 12, 14, 18, 24, 36],
+
+		// Control name lookup, format: title, command
+		controls : {
+			bold : ['bold_desc', 'Bold'],
+			italic : ['italic_desc', 'Italic'],
+			underline : ['underline_desc', 'Underline'],
+			strikethrough : ['striketrough_desc', 'Strikethrough'],
+			justifyleft : ['justifyleft_desc', 'JustifyLeft'],
+			justifycenter : ['justifycenter_desc', 'JustifyCenter'],
+			justifyright : ['justifyright_desc', 'JustifyRight'],
+			justifyfull : ['justifyfull_desc', 'JustifyFull'],
+			bullist : ['bullist_desc', 'InsertUnorderedList'],
+			numlist : ['numlist_desc', 'InsertOrderedList'],
+			outdent : ['outdent_desc', 'Outdent'],
+			indent : ['indent_desc', 'Indent'],
+			cut : ['cut_desc', 'Cut'],
+			copy : ['copy_desc', 'Copy'],
+			paste : ['paste_desc', 'Paste'],
+			undo : ['undo_desc', 'Undo'],
+			redo : ['redo_desc', 'Redo'],
+			link : ['link_desc', 'mceLink'],
+			unlink : ['unlink_desc', 'unlink'],
+			image : ['image_desc', 'mceImage'],
+			cleanup : ['cleanup_desc', 'mceCleanup'],
+			help : ['help_desc', 'mceHelp'],
+			code : ['code_desc', 'mceCodeEditor'],
+			hr : ['hr_desc', 'InsertHorizontalRule'],
+			removeformat : ['removeformat_desc', 'RemoveFormat'],
+			sub : ['sub_desc', 'subscript'],
+			sup : ['sup_desc', 'superscript'],
+			forecolor : ['forecolor_desc', 'ForeColor'],
+			forecolorpicker : ['forecolor_desc', 'mceForeColor'],
+			backcolor : ['backcolor_desc', 'HiliteColor'],
+			backcolorpicker : ['backcolor_desc', 'mceBackColor'],
+			charmap : ['charmap_desc', 'mceCharMap'],
+			visualaid : ['visualaid_desc', 'mceToggleVisualAid'],
+			anchor : ['anchor_desc', 'mceInsertAnchor'],
+			newdocument : ['newdocument_desc', 'mceNewDocument'],
+			blockquote : ['blockquote_desc', 'mceBlockQuote']
+		},
+
+		stateControls : ['bold', 'italic', 'underline', 'strikethrough', 'bullist', 'numlist', 'justifyleft', 'justifycenter', 'justifyright', 'justifyfull', 'sub', 'sup', 'blockquote'],
+
+		init : function(ed, url) {
+			var t = this, s, v, o;
+	
+			t.editor = ed;
+			t.url = url;
+			t.onResolveName = new tinymce.util.Dispatcher(this);
+
+			// Default settings
+			t.settings = s = extend({
+				theme_advanced_path : true,
+				theme_advanced_toolbar_location : 'bottom',
+				theme_advanced_buttons1 : "bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect",
+				theme_advanced_buttons2 : "bullist,numlist,|,outdent,indent,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code",
+				theme_advanced_buttons3 : "hr,removeformat,visualaid,|,sub,sup,|,charmap",
+				theme_advanced_blockformats : "p,address,pre,h1,h2,h3,h4,h5,h6",
+				theme_advanced_toolbar_align : "center",
+				theme_advanced_fonts : "Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings; [...]
+				theme_advanced_more_colors : 1,
+				theme_advanced_row_height : 23,
+				theme_advanced_resize_horizontal : 1,
+				theme_advanced_resizing_use_cookie : 1,
+				theme_advanced_font_sizes : "1,2,3,4,5,6,7",
+				readonly : ed.settings.readonly
+			}, ed.settings);
+
+			// Setup default font_size_style_values
+			if (!s.font_size_style_values)
+				s.font_size_style_values = "8pt,10pt,12pt,14pt,18pt,24pt,36pt";
+
+			if (tinymce.is(s.theme_advanced_font_sizes, 'string')) {
+				s.font_size_style_values = tinymce.explode(s.font_size_style_values);
+				s.font_size_classes = tinymce.explode(s.font_size_classes || '');
+
+				// Parse string value
+				o = {};
+				ed.settings.theme_advanced_font_sizes = s.theme_advanced_font_sizes;
+				each(ed.getParam('theme_advanced_font_sizes', '', 'hash'), function(v, k) {
+					var cl;
+
+					if (k == v && v >= 1 && v <= 7) {
+						k = v + ' (' + t.sizes[v - 1] + 'pt)';
+
+						if (ed.settings.convert_fonts_to_spans) {
+							cl = s.font_size_classes[v - 1];
+							v = s.font_size_style_values[v - 1] || (t.sizes[v - 1] + 'pt');
+						}
+					}
+
+					if (/^\s*\./.test(v))
+						cl = v.replace(/\./g, '');
+
+					o[k] = cl ? {'class' : cl} : {fontSize : v};
+				});
+
+				s.theme_advanced_font_sizes = o;
+			}
+
+			if ((v = s.theme_advanced_path_location) && v != 'none')
+				s.theme_advanced_statusbar_location = s.theme_advanced_path_location;
+
+			if (s.theme_advanced_statusbar_location == 'none')
+				s.theme_advanced_statusbar_location = 0;
+
+			// Init editor
+			ed.onInit.add(function() {
+				ed.onNodeChange.add(t._nodeChanged, t);
+
+				if (ed.settings.content_css !== false)
+					ed.dom.loadCSS(ed.baseURI.toAbsolute("themes/advanced/skins/" + ed.settings.skin + "/content.css"));
+			});
+
+			ed.onSetProgressState.add(function(ed, b, ti) {
+				var co, id = ed.id, tb;
+
+				if (b) {
+					t.progressTimer = setTimeout(function() {
+						co = ed.getContainer();
+						co = co.insertBefore(DOM.create('DIV', {style : 'position:relative'}), co.firstChild);
+						tb = DOM.get(ed.id + '_tbl');
+
+						DOM.add(co, 'div', {id : id + '_blocker', 'class' : 'mceBlocker', style : {width : tb.clientWidth + 2, height : tb.clientHeight + 2}});
+						DOM.add(co, 'div', {id : id + '_progress', 'class' : 'mceProgress', style : {left : tb.clientWidth / 2, top : tb.clientHeight / 2}});
+					}, ti || 0);
+				} else {
+					DOM.remove(id + '_blocker');
+					DOM.remove(id + '_progress');
+					clearTimeout(t.progressTimer);
+				}
+			});
+
+			DOM.loadCSS(s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : url + "/skins/" + ed.settings.skin + "/ui.css");
+
+			if (s.skin_variant)
+				DOM.loadCSS(url + "/skins/" + ed.settings.skin + "/ui_" + s.skin_variant + ".css");
+		},
+
+		createControl : function(n, cf) {
+			var cd, c;
+
+			if (c = cf.createControl(n))
+				return c;
+
+			switch (n) {
+				case "styleselect":
+					return this._createStyleSelect();
+
+				case "formatselect":
+					return this._createBlockFormats();
+
+				case "fontselect":
+					return this._createFontSelect();
+
+				case "fontsizeselect":
+					return this._createFontSizeSelect();
+
+				case "forecolor":
+					return this._createForeColorMenu();
+
+				case "backcolor":
+					return this._createBackColorMenu();
+			}
+
+			if ((cd = this.controls[n]))
+				return cf.createButton(n, {title : "advanced." + cd[0], cmd : cd[1], ui : cd[2], value : cd[3]});
+		},
+
+		execCommand : function(cmd, ui, val) {
+			var f = this['_' + cmd];
+
+			if (f) {
+				f.call(this, ui, val);
+				return true;
+			}
+
+			return false;
+		},
+
+		_importClasses : function(e) {
+			var ed = this.editor, c = ed.controlManager.get('styleselect');
+
+			if (c.getLength() == 0) {
+				each(ed.dom.getClasses(), function(o) {
+					c.add(o['class'], o['class']);
+				});
+			}
+		},
+
+		_createStyleSelect : function(n) {
+			var t = this, ed = t.editor, cf = ed.controlManager, c = cf.createListBox('styleselect', {
+				title : 'advanced.style_select',
+				onselect : function(v) {
+					if (c.selectedValue === v) {
+						ed.execCommand('mceSetStyleInfo', 0, {command : 'removeformat'});
+						c.select();
+						return false;
+					} else
+						ed.execCommand('mceSetCSSClass', 0, v);
+				}
+			});
+
+			if (c) {
+				each(ed.getParam('theme_advanced_styles', '', 'hash'), function(v, k) {
+					if (v)
+						c.add(t.editor.translate(k), v);
+				});
+
+				c.onPostRender.add(function(ed, n) {
+					if (!c.NativeListBox) {
+						Event.add(n.id + '_text', 'focus', t._importClasses, t);
+						Event.add(n.id + '_text', 'mousedown', t._importClasses, t);
+						Event.add(n.id + '_open', 'focus', t._importClasses, t);
+						Event.add(n.id + '_open', 'mousedown', t._importClasses, t);
+					} else
+						Event.add(n.id, 'focus', t._importClasses, t);
+				});
+			}
+
+			return c;
+		},
+
+		_createFontSelect : function() {
+			var c, t = this, ed = t.editor;
+
+			c = ed.controlManager.createListBox('fontselect', {title : 'advanced.fontdefault', cmd : 'FontName'});
+			if (c) {
+				each(ed.getParam('theme_advanced_fonts', t.settings.theme_advanced_fonts, 'hash'), function(v, k) {
+					c.add(ed.translate(k), v, {style : v.indexOf('dings') == -1 ? 'font-family:' + v : ''});
+				});
+			}
+
+			return c;
+		},
+
+		_createFontSizeSelect : function() {
+			var t = this, ed = t.editor, c, i = 0, cl = [];
+
+			c = ed.controlManager.createListBox('fontsizeselect', {title : 'advanced.font_size', onselect : function(v) {
+				if (v.fontSize)
+					ed.execCommand('FontSize', false, v.fontSize);
+				else {
+					each(t.settings.theme_advanced_font_sizes, function(v, k) {
+						if (v['class'])
+							cl.push(v['class']);
+					});
+
+					ed.editorCommands._applyInlineStyle('span', {'class' : v['class']}, {check_classes : cl});
+				}
+			}});
+
+			if (c) {
+				each(t.settings.theme_advanced_font_sizes, function(v, k) {
+					var fz = v.fontSize;
+
+					if (fz >= 1 && fz <= 7)
+						fz = t.sizes[parseInt(fz) - 1] + 'pt';
+
+					c.add(k, v, {'style' : 'font-size:' + fz, 'class' : 'mceFontSize' + (i++) + (' ' + (v['class'] || ''))});
+				});
+			}
+
+			return c;
+		},
+
+		_createBlockFormats : function() {
+			var c, fmts = {
+				p : 'advanced.paragraph',
+				address : 'advanced.address',
+				pre : 'advanced.pre',
+				h1 : 'advanced.h1',
+				h2 : 'advanced.h2',
+				h3 : 'advanced.h3',
+				h4 : 'advanced.h4',
+				h5 : 'advanced.h5',
+				h6 : 'advanced.h6',
+				div : 'advanced.div',
+				blockquote : 'advanced.blockquote',
+				code : 'advanced.code',
+				dt : 'advanced.dt',
+				dd : 'advanced.dd',
+				samp : 'advanced.samp'
+			}, t = this;
+
+			c = t.editor.controlManager.createListBox('formatselect', {title : 'advanced.block', cmd : 'FormatBlock'});
+			if (c) {
+				each(t.editor.getParam('theme_advanced_blockformats', t.settings.theme_advanced_blockformats, 'hash'), function(v, k) {
+					c.add(t.editor.translate(k != v ? k : fmts[v]), v, {'class' : 'mce_formatPreview mce_' + v});
+				});
+			}
+
+			return c;
+		},
+
+		_createForeColorMenu : function() {
+			var c, t = this, s = t.settings, o = {}, v;
+
+			if (s.theme_advanced_more_colors) {
+				o.more_colors_func = function() {
+					t._mceColorPicker(0, {
+						color : c.value,
+						func : function(co) {
+							c.setColor(co);
+						}
+					});
+				};
+			}
+
+			if (v = s.theme_advanced_text_colors)
+				o.colors = v;
+
+			if (s.theme_advanced_default_foreground_color)
+				o.default_color = s.theme_advanced_default_foreground_color;
+
+			o.title = 'advanced.forecolor_desc';
+			o.cmd = 'ForeColor';
+			o.scope = this;
+
+			c = t.editor.controlManager.createColorSplitButton('forecolor', o);
+
+			return c;
+		},
+
+		_createBackColorMenu : function() {
+			var c, t = this, s = t.settings, o = {}, v;
+
+			if (s.theme_advanced_more_colors) {
+				o.more_colors_func = function() {
+					t._mceColorPicker(0, {
+						color : c.value,
+						func : function(co) {
+							c.setColor(co);
+						}
+					});
+				};
+			}
+
+			if (v = s.theme_advanced_background_colors)
+				o.colors = v;
+
+			if (s.theme_advanced_default_background_color)
+				o.default_color = s.theme_advanced_default_background_color;
+
+			o.title = 'advanced.backcolor_desc';
+			o.cmd = 'HiliteColor';
+			o.scope = this;
+
+			c = t.editor.controlManager.createColorSplitButton('backcolor', o);
+
+			return c;
+		},
+
+		renderUI : function(o) {
+			var n, ic, tb, t = this, ed = t.editor, s = t.settings, sc, p, nl;
+
+			n = p = DOM.create('span', {id : ed.id + '_parent', 'class' : 'mceEditor ' + ed.settings.skin + 'Skin' + (s.skin_variant ? ' ' + ed.settings.skin + 'Skin' + t._ufirst(s.skin_variant) : '')});
+
+			if (!DOM.boxModel)
+				n = DOM.add(n, 'div', {'class' : 'mceOldBoxModel'});
+
+			n = sc = DOM.add(n, 'table', {id : ed.id + '_tbl', 'class' : 'mceLayout', cellSpacing : 0, cellPadding : 0});
+			n = tb = DOM.add(n, 'tbody');
+
+			switch ((s.theme_advanced_layout_manager || '').toLowerCase()) {
+				case "rowlayout":
+					ic = t._rowLayout(s, tb, o);
+					break;
+
+				case "customlayout":
+					ic = ed.execCallback("theme_advanced_custom_layout", s, tb, o, p);
+					break;
+
+				default:
+					ic = t._simpleLayout(s, tb, o, p);
+			}
+
+			n = o.targetNode;
+
+			// Add classes to first and last TRs
+			nl = DOM.stdMode ? sc.getElementsByTagName('tr') : sc.rows; // Quick fix for IE 8
+			DOM.addClass(nl[0], 'mceFirst');
+			DOM.addClass(nl[nl.length - 1], 'mceLast');
+
+			// Add classes to first and last TDs
+			each(DOM.select('tr', tb), function(n) {
+				DOM.addClass(n.firstChild, 'mceFirst');
+				DOM.addClass(n.childNodes[n.childNodes.length - 1], 'mceLast');
+			});
+
+			if (DOM.get(s.theme_advanced_toolbar_container))
+				DOM.get(s.theme_advanced_toolbar_container).appendChild(p);
+			else
+				DOM.insertAfter(p, n);
+
+			Event.add(ed.id + '_path_row', 'click', function(e) {
+				e = e.target;
+
+				if (e.nodeName == 'A') {
+					t._sel(e.className.replace(/^.*mcePath_([0-9]+).*$/, '$1'));
+
+					return Event.cancel(e);
+				}
+			});
+/*
+			if (DOM.get(ed.id + '_path_row')) {
+				Event.add(ed.id + '_tbl', 'mouseover', function(e) {
+					var re;
+	
+					e = e.target;
+
+					if (e.nodeName == 'SPAN' && DOM.hasClass(e.parentNode, 'mceButton')) {
+						re = DOM.get(ed.id + '_path_row');
+						t.lastPath = re.innerHTML;
+						DOM.setHTML(re, e.parentNode.title);
+					}
+				});
+
+				Event.add(ed.id + '_tbl', 'mouseout', function(e) {
+					if (t.lastPath) {
+						DOM.setHTML(ed.id + '_path_row', t.lastPath);
+						t.lastPath = 0;
+					}
+				});
+			}
+*/
+
+			if (!ed.getParam('accessibility_focus'))
+				Event.add(DOM.add(p, 'a', {href : '#'}, '<!-- IE -->'), 'focus', function() {tinyMCE.get(ed.id).focus();});
+
+			if (s.theme_advanced_toolbar_location == 'external')
+				o.deltaHeight = 0;
+
+			t.deltaHeight = o.deltaHeight;
+			o.targetNode = null;
+
+			return {
+				iframeContainer : ic,
+				editorContainer : ed.id + '_parent',
+				sizeContainer : sc,
+				deltaHeight : o.deltaHeight
+			};
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Advanced theme',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			}
+		},
+
+		resizeBy : function(dw, dh) {
+			var e = DOM.get(this.editor.id + '_tbl');
+
+			this.resizeTo(e.clientWidth + dw, e.clientHeight + dh);
+		},
+
+		resizeTo : function(w, h) {
+			var ed = this.editor, s = ed.settings, e = DOM.get(ed.id + '_tbl'), ifr = DOM.get(ed.id + '_ifr'), dh;
+
+			// Boundery fix box
+			w = Math.max(s.theme_advanced_resizing_min_width || 100, w);
+			h = Math.max(s.theme_advanced_resizing_min_height || 100, h);
+			w = Math.min(s.theme_advanced_resizing_max_width || 0xFFFF, w);
+			h = Math.min(s.theme_advanced_resizing_max_height || 0xFFFF, h);
+
+			// Calc difference between iframe and container
+			dh = e.clientHeight - ifr.clientHeight;
+
+			// Resize iframe and container
+			DOM.setStyle(ifr, 'height', h - dh);
+			DOM.setStyles(e, {width : w, height : h});
+		},
+
+		destroy : function() {
+			var id = this.editor.id;
+
+			Event.clear(id + '_resize');
+			Event.clear(id + '_path_row');
+			Event.clear(id + '_external_close');
+		},
+
+		// Internal functions
+
+		_simpleLayout : function(s, tb, o, p) {
+			var t = this, ed = t.editor, lo = s.theme_advanced_toolbar_location, sl = s.theme_advanced_statusbar_location, n, ic, etb, c;
+
+			if (s.readonly) {
+				n = DOM.add(tb, 'tr');
+				n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'});
+				return ic;
+			}
+
+			// Create toolbar container at top
+			if (lo == 'top')
+				t._addToolbars(tb, o);
+
+			// Create external toolbar
+			if (lo == 'external') {
+				n = c = DOM.create('div', {style : 'position:relative'});
+				n = DOM.add(n, 'div', {id : ed.id + '_external', 'class' : 'mceExternalToolbar'});
+				DOM.add(n, 'a', {id : ed.id + '_external_close', href : 'javascript:;', 'class' : 'mceExternalClose'});
+				n = DOM.add(n, 'table', {id : ed.id + '_tblext', cellSpacing : 0, cellPadding : 0});
+				etb = DOM.add(n, 'tbody');
+
+				if (p.firstChild.className == 'mceOldBoxModel')
+					p.firstChild.appendChild(c);
+				else
+					p.insertBefore(c, p.firstChild);
+
+				t._addToolbars(etb, o);
+
+				ed.onMouseUp.add(function() {
+					var e = DOM.get(ed.id + '_external');
+					DOM.show(e);
+
+					DOM.hide(lastExtID);
+
+					var f = Event.add(ed.id + '_external_close', 'click', function() {
+						DOM.hide(ed.id + '_external');
+						Event.remove(ed.id + '_external_close', 'click', f);
+					});
+
+					DOM.show(e);
+					DOM.setStyle(e, 'top', 0 - DOM.getRect(ed.id + '_tblext').h - 1);
+
+					// Fixes IE rendering bug
+					DOM.hide(e);
+					DOM.show(e);
+					e.style.filter = '';
+
+					lastExtID = ed.id + '_external';
+
+					e = null;
+				});
+			}
+
+			if (sl == 'top')
+				t._addStatusBar(tb, o);
+
+			// Create iframe container
+			if (!s.theme_advanced_toolbar_container) {
+				n = DOM.add(tb, 'tr');
+				n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'});
+			}
+
+			// Create toolbar container at bottom
+			if (lo == 'bottom')
+				t._addToolbars(tb, o);
+
+			if (sl == 'bottom')
+				t._addStatusBar(tb, o);
+
+			return ic;
+		},
+
+		_rowLayout : function(s, tb, o) {
+			var t = this, ed = t.editor, dc, da, cf = ed.controlManager, n, ic, to, a;
+
+			dc = s.theme_advanced_containers_default_class || '';
+			da = s.theme_advanced_containers_default_align || 'center';
+
+			each(explode(s.theme_advanced_containers || ''), function(c, i) {
+				var v = s['theme_advanced_container_' + c] || '';
+
+				switch (v.toLowerCase()) {
+					case 'mceeditor':
+						n = DOM.add(tb, 'tr');
+						n = ic = DOM.add(n, 'td', {'class' : 'mceIframeContainer'});
+						break;
+
+					case 'mceelementpath':
+						t._addStatusBar(tb, o);
+						break;
+
+					default:
+						a = (s['theme_advanced_container_' + c + '_align'] || da).toLowerCase();
+						a = 'mce' + t._ufirst(a);
+
+						n = DOM.add(DOM.add(tb, 'tr'), 'td', {
+							'class' : 'mceToolbar ' + (s['theme_advanced_container_' + c + '_class'] || dc) + ' ' + a || da
+						});
+
+						to = cf.createToolbar("toolbar" + i);
+						t._addControls(v, to);
+						DOM.setHTML(n, to.renderHTML());
+						o.deltaHeight -= s.theme_advanced_row_height;
+				}
+			});
+
+			return ic;
+		},
+
+		_addControls : function(v, tb) {
+			var t = this, s = t.settings, di, cf = t.editor.controlManager;
+
+			if (s.theme_advanced_disable && !t._disabled) {
+				di = {};
+
+				each(explode(s.theme_advanced_disable), function(v) {
+					di[v] = 1;
+				});
+
+				t._disabled = di;
+			} else
+				di = t._disabled;
+
+			each(explode(v), function(n) {
+				var c;
+
+				if (di && di[n])
+					return;
+
+				// Compatiblity with 2.x
+				if (n == 'tablecontrols') {
+					each(["table","|","row_props","cell_props","|","row_before","row_after","delete_row","|","col_before","col_after","delete_col","|","split_cells","merge_cells"], function(n) {
+						n = t.createControl(n, cf);
+
+						if (n)
+							tb.add(n);
+					});
+
+					return;
+				}
+
+				c = t.createControl(n, cf);
+
+				if (c)
+					tb.add(c);
+			});
+		},
+
+		_addToolbars : function(c, o) {
+			var t = this, i, tb, ed = t.editor, s = t.settings, v, cf = ed.controlManager, di, n, h = [], a;
+
+			a = s.theme_advanced_toolbar_align.toLowerCase();
+			a = 'mce' + t._ufirst(a);
+
+			n = DOM.add(DOM.add(c, 'tr'), 'td', {'class' : 'mceToolbar ' + a});
+
+			if (!ed.getParam('accessibility_focus'))
+				h.push(DOM.createHTML('a', {href : '#', onfocus : 'tinyMCE.get(\'' + ed.id + '\').focus();'}, '<!-- IE -->'));
+
+			h.push(DOM.createHTML('a', {href : '#', accesskey : 'q', title : ed.getLang("advanced.toolbar_focus")}, '<!-- IE -->'));
+
+			// Create toolbar and add the controls
+			for (i=1; (v = s['theme_advanced_buttons' + i]); i++) {
+				tb = cf.createToolbar("toolbar" + i, {'class' : 'mceToolbarRow' + i});
+
+				if (s['theme_advanced_buttons' + i + '_add'])
+					v += ',' + s['theme_advanced_buttons' + i + '_add'];
+
+				if (s['theme_advanced_buttons' + i + '_add_before'])
+					v = s['theme_advanced_buttons' + i + '_add_before'] + ',' + v;
+
+				t._addControls(v, tb);
+
+				//n.appendChild(n = tb.render());
+				h.push(tb.renderHTML());
+
+				o.deltaHeight -= s.theme_advanced_row_height;
+			}
+
+			h.push(DOM.createHTML('a', {href : '#', accesskey : 'z', title : ed.getLang("advanced.toolbar_focus"), onfocus : 'tinyMCE.getInstanceById(\'' + ed.id + '\').focus();'}, '<!-- IE -->'));
+			DOM.setHTML(n, h.join(''));
+		},
+
+		_addStatusBar : function(tb, o) {
+			var n, t = this, ed = t.editor, s = t.settings, r, mf, me, td;
+
+			n = DOM.add(tb, 'tr');
+			n = td = DOM.add(n, 'td', {'class' : 'mceStatusbar'});
+			n = DOM.add(n, 'div', {id : ed.id + '_path_row'}, s.theme_advanced_path ? ed.translate('advanced.path') + ': ' : ' ');
+			DOM.add(n, 'a', {href : '#', accesskey : 'x'});
+
+			if (s.theme_advanced_resizing) {
+				DOM.add(td, 'a', {id : ed.id + '_resize', href : 'javascript:;', onclick : "return false;", 'class' : 'mceResize'});
+
+				if (s.theme_advanced_resizing_use_cookie) {
+					ed.onPostRender.add(function() {
+						var o = Cookie.getHash("TinyMCE_" + ed.id + "_size"), c = DOM.get(ed.id + '_tbl');
+
+						if (!o)
+							return;
+
+						if (s.theme_advanced_resize_horizontal)
+							c.style.width = Math.max(10, o.cw) + 'px';
+
+						c.style.height = Math.max(10, o.ch) + 'px';
+						DOM.get(ed.id + '_ifr').style.height = Math.max(10, parseInt(o.ch) + t.deltaHeight) + 'px';
+					});
+				}
+
+				ed.onPostRender.add(function() {
+					Event.add(ed.id + '_resize', 'mousedown', function(e) {
+						var c, p, w, h, n, pa;
+
+						// Measure container
+						c = DOM.get(ed.id + '_tbl');
+						w = c.clientWidth;
+						h = c.clientHeight;
+
+						miw = s.theme_advanced_resizing_min_width || 100;
+						mih = s.theme_advanced_resizing_min_height || 100;
+						maw = s.theme_advanced_resizing_max_width || 0xFFFF;
+						mah = s.theme_advanced_resizing_max_height || 0xFFFF;
+
+						// Setup placeholder
+						p = DOM.add(DOM.get(ed.id + '_parent'), 'div', {'class' : 'mcePlaceHolder'});
+						DOM.setStyles(p, {width : w, height : h});
+
+						// Replace with placeholder
+						DOM.hide(c);
+						DOM.show(p);
+
+						// Create internal resize obj
+						r = {
+							x : e.screenX,
+							y : e.screenY,
+							w : w,
+							h : h,
+							dx : null,
+							dy : null
+						};
+
+						// Start listening
+						mf = Event.add(DOM.doc, 'mousemove', function(e) {
+							var w, h;
+
+							// Calc delta values
+							r.dx = e.screenX - r.x;
+							r.dy = e.screenY - r.y;
+
+							// Boundery fix box
+							w = Math.max(miw, r.w + r.dx);
+							h = Math.max(mih, r.h + r.dy);
+							w = Math.min(maw, w);
+							h = Math.min(mah, h);
+
+							// Resize placeholder
+							if (s.theme_advanced_resize_horizontal)
+								p.style.width = w + 'px';
+
+							p.style.height = h + 'px';
+
+							return Event.cancel(e);
+						});
+
+						me = Event.add(DOM.doc, 'mouseup', function(e) {
+							var ifr;
+
+							// Stop listening
+							Event.remove(DOM.doc, 'mousemove', mf);
+							Event.remove(DOM.doc, 'mouseup', me);
+
+							c.style.display = '';
+							DOM.remove(p);
+
+							if (r.dx === null)
+								return;
+
+							ifr = DOM.get(ed.id + '_ifr');
+
+							if (s.theme_advanced_resize_horizontal)
+								c.style.width = Math.max(10, r.w + r.dx) + 'px';
+
+							c.style.height = Math.max(10, r.h + r.dy) + 'px';
+							ifr.style.height = Math.max(10, ifr.clientHeight + r.dy) + 'px';
+
+							if (s.theme_advanced_resizing_use_cookie) {
+								Cookie.setHash("TinyMCE_" + ed.id + "_size", {
+									cw : r.w + r.dx,
+									ch : r.h + r.dy
+								});
+							}
+						});
+
+						return Event.cancel(e);
+					});
+				});
+			}
+
+			o.deltaHeight -= 21;
+			n = tb = null;
+		},
+
+		_nodeChanged : function(ed, cm, n, co) {
+			var t = this, p, de = 0, v, c, s = t.settings, cl, fz, fn;
+
+			if (s.readonly)
+				return;
+
+			tinymce.each(t.stateControls, function(c) {
+				cm.setActive(c, ed.queryCommandState(t.controls[c][1]));
+			});
+
+			cm.setActive('visualaid', ed.hasVisual);
+			cm.setDisabled('undo', !ed.undoManager.hasUndo() && !ed.typing);
+			cm.setDisabled('redo', !ed.undoManager.hasRedo());
+			cm.setDisabled('outdent', !ed.queryCommandState('Outdent'));
+
+			p = DOM.getParent(n, 'A');
+			if (c = cm.get('link')) {
+				if (!p || !p.name) {
+					c.setDisabled(!p && co);
+					c.setActive(!!p);
+				}
+			}
+
+			if (c = cm.get('unlink')) {
+				c.setDisabled(!p && co);
+				c.setActive(!!p && !p.name);
+			}
+
+			if (c = cm.get('anchor')) {
+				c.setActive(!!p && p.name);
+
+				if (tinymce.isWebKit) {
+					p = DOM.getParent(n, 'IMG');
+					c.setActive(!!p && DOM.getAttrib(p, 'mce_name') == 'a');
+				}
+			}
+
+			p = DOM.getParent(n, 'IMG');
+			if (c = cm.get('image'))
+				c.setActive(!!p && n.className.indexOf('mceItem') == -1);
+
+			if (c = cm.get('styleselect')) {
+				if (n.className) {
+					t._importClasses();
+					c.select(n.className);
+				} else
+					c.select();
+			}
+
+			if (c = cm.get('formatselect')) {
+				p = DOM.getParent(n, DOM.isBlock);
+
+				if (p)
+					c.select(p.nodeName.toLowerCase());
+			}
+
+			if (ed.settings.convert_fonts_to_spans) {
+				ed.dom.getParent(n, function(n) {
+					if (n.nodeName === 'SPAN') {
+						if (!cl && n.className)
+							cl = n.className;
+
+						if (!fz && n.style.fontSize)
+							fz = n.style.fontSize;
+
+						if (!fn && n.style.fontFamily)
+							fn = n.style.fontFamily.replace(/[\"\']+/g, '').replace(/^([^,]+).*/, '$1').toLowerCase();
+					}
+
+					return false;
+				});
+
+				if (c = cm.get('fontselect')) {
+					c.select(function(v) {
+						return v.replace(/^([^,]+).*/, '$1').toLowerCase() == fn;
+					});
+				}
+
+				if (c = cm.get('fontsizeselect')) {
+					c.select(function(v) {
+						if (v.fontSize && v.fontSize === fz)
+							return true;
+
+						if (v['class'] && v['class'] === cl)
+							return true;
+					});
+				}
+			} else {
+				if (c = cm.get('fontselect'))
+					c.select(ed.queryCommandValue('FontName'));
+
+				if (c = cm.get('fontsizeselect')) {
+					v = ed.queryCommandValue('FontSize');
+					c.select(function(iv) {
+						return iv.fontSize == v;
+					});
+				}
+			}
+
+			if (s.theme_advanced_path && s.theme_advanced_statusbar_location) {
+				p = DOM.get(ed.id + '_path') || DOM.add(ed.id + '_path_row', 'span', {id : ed.id + '_path'});
+				DOM.setHTML(p, '');
+
+				ed.dom.getParent(n, function(n) {
+					var na = n.nodeName.toLowerCase(), u, pi, ti = '';
+
+					// Ignore non element and hidden elements
+					if (n.nodeType != 1 || n.nodeName === 'BR' || (DOM.hasClass(n, 'mceItemHidden') || DOM.hasClass(n, 'mceItemRemoved')))
+						return;
+
+					// Fake name
+					if (v = DOM.getAttrib(n, 'mce_name'))
+						na = v;
+
+					// Handle prefix
+					if (tinymce.isIE && n.scopeName !== 'HTML')
+						na = n.scopeName + ':' + na;
+
+					// Remove internal prefix
+					na = na.replace(/mce\:/g, '');
+
+					// Handle node name
+					switch (na) {
+						case 'b':
+							na = 'strong';
+							break;
+
+						case 'i':
+							na = 'em';
+							break;
+
+						case 'img':
+							if (v = DOM.getAttrib(n, 'src'))
+								ti += 'src: ' + v + ' ';
+
+							break;
+
+						case 'a':
+							if (v = DOM.getAttrib(n, 'name')) {
+								ti += 'name: ' + v + ' ';
+								na += '#' + v;
+							}
+
+							if (v = DOM.getAttrib(n, 'href'))
+								ti += 'href: ' + v + ' ';
+
+							break;
+
+						case 'font':
+							if (s.convert_fonts_to_spans)
+								na = 'span';
+
+							if (v = DOM.getAttrib(n, 'face'))
+								ti += 'font: ' + v + ' ';
+
+							if (v = DOM.getAttrib(n, 'size'))
+								ti += 'size: ' + v + ' ';
+
+							if (v = DOM.getAttrib(n, 'color'))
+								ti += 'color: ' + v + ' ';
+
+							break;
+
+						case 'span':
+							if (v = DOM.getAttrib(n, 'style'))
+								ti += 'style: ' + v + ' ';
+
+							break;
+					}
+
+					if (v = DOM.getAttrib(n, 'id'))
+						ti += 'id: ' + v + ' ';
+
+					if (v = n.className) {
+						v = v.replace(/(webkit-[\w\-]+|Apple-[\w\-]+|mceItem\w+|mceVisualAid)/g, '');
+
+						if (v && v.indexOf('mceItem') == -1) {
+							ti += 'class: ' + v + ' ';
+
+							if (DOM.isBlock(n) || na == 'img' || na == 'span')
+								na += '.' + v;
+						}
+					}
+
+					na = na.replace(/(html:)/g, '');
+					na = {name : na, node : n, title : ti};
+					t.onResolveName.dispatch(t, na);
+					ti = na.title;
+					na = na.name;
+
+					//u = "javascript:tinymce.EditorManager.get('" + ed.id + "').theme._sel('" + (de++) + "');";
+					pi = DOM.create('a', {'href' : "javascript:;", onmousedown : "return false;", title : ti, 'class' : 'mcePath_' + (de++)}, na);
+
+					if (p.hasChildNodes()) {
+						p.insertBefore(DOM.doc.createTextNode(' \u00bb '), p.firstChild);
+						p.insertBefore(pi, p.firstChild);
+					} else
+						p.appendChild(pi);
+				}, ed.getBody());
+			}
+		},
+
+		// Commands gets called by execCommand
+
+		_sel : function(v) {
+			this.editor.execCommand('mceSelectNodeDepth', false, v);
+		},
+
+		_mceInsertAnchor : function(ui, v) {
+			var ed = this.editor;
+
+			ed.windowManager.open({
+				url : tinymce.baseURL + '/themes/advanced/anchor.htm',
+				width : 320 + parseInt(ed.getLang('advanced.anchor_delta_width', 0)),
+				height : 90 + parseInt(ed.getLang('advanced.anchor_delta_height', 0)),
+				inline : true
+			}, {
+				theme_url : this.url
+			});
+		},
+
+		_mceCharMap : function() {
+			var ed = this.editor;
+
+			ed.windowManager.open({
+				url : tinymce.baseURL + '/themes/advanced/charmap.htm',
+				width : 550 + parseInt(ed.getLang('advanced.charmap_delta_width', 0)),
+				height : 250 + parseInt(ed.getLang('advanced.charmap_delta_height', 0)),
+				inline : true
+			}, {
+				theme_url : this.url
+			});
+		},
+
+		_mceHelp : function() {
+			var ed = this.editor;
+
+			ed.windowManager.open({
+				url : tinymce.baseURL + '/themes/advanced/about.htm',
+				width : 480,
+				height : 380,
+				inline : true
+			}, {
+				theme_url : this.url
+			});
+		},
+
+		_mceColorPicker : function(u, v) {
+			var ed = this.editor;
+
+			v = v || {};
+
+			ed.windowManager.open({
+				url : tinymce.baseURL + '/themes/advanced/color_picker.htm',
+				width : 375 + parseInt(ed.getLang('advanced.colorpicker_delta_width', 0)),
+				height : 250 + parseInt(ed.getLang('advanced.colorpicker_delta_height', 0)),
+				close_previous : false,
+				inline : true
+			}, {
+				input_color : v.color,
+				func : v.func,
+				theme_url : this.url
+			});
+		},
+
+		_mceCodeEditor : function(ui, val) {
+			var ed = this.editor;
+
+			ed.windowManager.open({
+				url : tinymce.baseURL + '/themes/advanced/source_editor.htm',
+				width : parseInt(ed.getParam("theme_advanced_source_editor_width", 720)),
+				height : parseInt(ed.getParam("theme_advanced_source_editor_height", 580)),
+				inline : true,
+				resizable : true,
+				maximizable : true
+			}, {
+				theme_url : this.url
+			});
+		},
+
+		_mceImage : function(ui, val) {
+			var ed = this.editor;
+
+			// Internal image object like a flash placeholder
+			if (ed.dom.getAttrib(ed.selection.getNode(), 'class').indexOf('mceItem') != -1)
+				return;
+
+			ed.windowManager.open({
+				url : tinymce.baseURL + '/themes/advanced/image.htm',
+				width : 355 + parseInt(ed.getLang('advanced.image_delta_width', 0)),
+				height : 275 + parseInt(ed.getLang('advanced.image_delta_height', 0)),
+				inline : true
+			}, {
+				theme_url : this.url
+			});
+		},
+
+		_mceLink : function(ui, val) {
+			var ed = this.editor;
+
+			ed.windowManager.open({
+				url : tinymce.baseURL + '/themes/advanced/link.htm',
+				width : 310 + parseInt(ed.getLang('advanced.link_delta_width', 0)),
+				height : 200 + parseInt(ed.getLang('advanced.link_delta_height', 0)),
+				inline : true
+			}, {
+				theme_url : this.url
+			});
+		},
+
+		_mceNewDocument : function() {
+			var ed = this.editor;
+
+			ed.windowManager.confirm('advanced.newdocument', function(s) {
+				if (s)
+					ed.execCommand('mceSetContent', false, '');
+			});
+		},
+
+		_mceForeColor : function() {
+			var t = this;
+
+			this._mceColorPicker(0, {
+				color: t.fgColor,
+				func : function(co) {
+					t.fgColor = co;
+					t.editor.execCommand('ForeColor', false, co);
+				}
+			});
+		},
+
+		_mceBackColor : function() {
+			var t = this;
+
+			this._mceColorPicker(0, {
+				color: t.bgColor,
+				func : function(co) {
+					t.bgColor = co;
+					t.editor.execCommand('HiliteColor', false, co);
+				}
+			});
+		},
+
+		_ufirst : function(s) {
+			return s.substring(0, 1).toUpperCase() + s.substring(1);
+		}
+	});
+
+	tinymce.ThemeManager.add('advanced', tinymce.themes.AdvancedTheme);
+}(tinymce));
\ No newline at end of file
diff --git a/media/js/tinymce/themes/advanced/image.htm b/media/js/tinymce/themes/advanced/image.htm
new file mode 100644
index 0000000..7ec1052
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/image.htm
@@ -0,0 +1,85 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advanced_dlg.image_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="js/image.js"></script>
+</head>
+<body id="image" style="display: none">
+<form onsubmit="ImageDialog.update();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advanced_dlg.image_title}</a></span></li>
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+     <table border="0" cellpadding="4" cellspacing="0">
+          <tr>
+            <td class="nowrap"><label for="src">{#advanced_dlg.image_src}</label></td>
+            <td><table border="0" cellspacing="0" cellpadding="0">
+                <tr>
+                  <td><input id="src" name="src" type="text" class="mceFocus" value="" style="width: 200px" onchange="ImageDialog.getImageData();" /></td>
+                  <td id="srcbrowsercontainer"> </td>
+                </tr>
+              </table></td>
+          </tr>
+		  <tr>
+			<td><label for="image_list">{#advanced_dlg.image_list}</label></td>
+			<td><select id="image_list" name="image_list" onchange="document.getElementById('src').value=this.options[this.selectedIndex].value;document.getElementById('alt').value=this.options[this.selectedIndex].text;"></select></td>
+		  </tr>
+          <tr>
+            <td class="nowrap"><label for="alt">{#advanced_dlg.image_alt}</label></td>
+            <td><input id="alt" name="alt" type="text" value="" style="width: 200px" /></td>
+          </tr>
+          <tr>
+            <td class="nowrap"><label for="align">{#advanced_dlg.image_align}</label></td>
+            <td><select id="align" name="align" onchange="ImageDialog.updateStyle();">
+                <option value="">{#not_set}</option>
+                <option value="baseline">{#advanced_dlg.image_align_baseline}</option>
+                <option value="top">{#advanced_dlg.image_align_top}</option>
+                <option value="middle">{#advanced_dlg.image_align_middle}</option>
+                <option value="bottom">{#advanced_dlg.image_align_bottom}</option>
+                <option value="text-top">{#advanced_dlg.image_align_texttop}</option>
+                <option value="text-bottom">{#advanced_dlg.image_align_textbottom}</option>
+                <option value="left">{#advanced_dlg.image_align_left}</option>
+                <option value="right">{#advanced_dlg.image_align_right}</option>
+              </select></td>
+          </tr>
+          <tr>
+            <td class="nowrap"><label for="width">{#advanced_dlg.image_dimensions}</label></td>
+            <td><input id="width" name="width" type="text" value="" size="3" maxlength="5" />
+              x
+              <input id="height" name="height" type="text" value="" size="3" maxlength="5" /></td>
+          </tr>
+          <tr>
+            <td class="nowrap"><label for="border">{#advanced_dlg.image_border}</label></td>
+            <td><input id="border" name="border" type="text" value="" size="3" maxlength="3" onchange="ImageDialog.updateStyle();" /></td>
+          </tr>
+          <tr>
+            <td class="nowrap"><label for="vspace">{#advanced_dlg.image_vspace}</label></td>
+            <td><input id="vspace" name="vspace" type="text" value="" size="3" maxlength="3" onchange="ImageDialog.updateStyle();" /></td>
+          </tr>
+          <tr>
+            <td class="nowrap"><label for="hspace">{#advanced_dlg.image_hspace}</label></td>
+            <td><input id="hspace" name="hspace" type="text" value="" size="3" maxlength="3" onchange="ImageDialog.updateStyle();" /></td>
+          </tr>
+        </table>
+		</div>
+	</div>
+
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#insert}" />
+		</div>
+
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/themes/advanced/img/colorpicker.jpg b/media/js/tinymce/themes/advanced/img/colorpicker.jpg
new file mode 100644
index 0000000..b4c542d
Binary files /dev/null and b/media/js/tinymce/themes/advanced/img/colorpicker.jpg differ
diff --git a/media/js/tinymce/themes/advanced/img/icons.gif b/media/js/tinymce/themes/advanced/img/icons.gif
new file mode 100644
index 0000000..ccac36f
Binary files /dev/null and b/media/js/tinymce/themes/advanced/img/icons.gif differ
diff --git a/media/js/tinymce/themes/advanced/js/about.js b/media/js/tinymce/themes/advanced/js/about.js
new file mode 100644
index 0000000..5cee9ed
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/js/about.js
@@ -0,0 +1,72 @@
+tinyMCEPopup.requireLangPack();
+
+function init() {
+	var ed, tcont;
+
+	tinyMCEPopup.resizeToInnerSize();
+	ed = tinyMCEPopup.editor;
+
+	// Give FF some time
+	window.setTimeout(insertHelpIFrame, 10);
+
+	tcont = document.getElementById('plugintablecontainer');
+	document.getElementById('plugins_tab').style.display = 'none';
+
+	var html = "";
+	html += '<table id="plugintable">';
+	html += '<thead>';
+	html += '<tr>';
+	html += '<td>' + ed.getLang('advanced_dlg.about_plugin') + '</td>';
+	html += '<td>' + ed.getLang('advanced_dlg.about_author') + '</td>';
+	html += '<td>' + ed.getLang('advanced_dlg.about_version') + '</td>';
+	html += '</tr>';
+	html += '</thead>';
+	html += '<tbody>';
+
+	tinymce.each(ed.plugins, function(p, n) {
+		var info;
+
+		if (!p.getInfo)
+			return;
+
+		html += '<tr>';
+
+		info = p.getInfo();
+
+		if (info.infourl != null && info.infourl != '')
+			html += '<td width="50%" title="' + n + '"><a href="' + info.infourl + '" target="_blank">' + info.longname + '</a></td>';
+		else
+			html += '<td width="50%" title="' + n + '">' + info.longname + '</td>';
+
+		if (info.authorurl != null && info.authorurl != '')
+			html += '<td width="35%"><a href="' + info.authorurl + '" target="_blank">' + info.author + '</a></td>';
+		else
+			html += '<td width="35%">' + info.author + '</td>';
+
+		html += '<td width="15%">' + info.version + '</td>';
+		html += '</tr>';
+
+		document.getElementById('plugins_tab').style.display = '';
+
+	});
+
+	html += '</tbody>';
+	html += '</table>';
+
+	tcont.innerHTML = html;
+
+	tinyMCEPopup.dom.get('version').innerHTML = tinymce.majorVersion + "." + tinymce.minorVersion;
+	tinyMCEPopup.dom.get('date').innerHTML = tinymce.releaseDate;
+}
+
+function insertHelpIFrame() {
+	var html;
+
+	if (tinyMCEPopup.getParam('docs_url')) {
+		html = '<iframe width="100%" height="300" src="' + tinyMCEPopup.editor.baseURI.toAbsolute(tinyMCEPopup.getParam('docs_url')) + '"></iframe>';
+		document.getElementById('iframecontainer').innerHTML = html;
+		document.getElementById('help_tab').style.display = 'block';
+	}
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/themes/advanced/js/anchor.js b/media/js/tinymce/themes/advanced/js/anchor.js
new file mode 100644
index 0000000..b5efd1e
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/js/anchor.js
@@ -0,0 +1,37 @@
+tinyMCEPopup.requireLangPack();
+
+var AnchorDialog = {
+	init : function(ed) {
+		var action, elm, f = document.forms[0];
+
+		this.editor = ed;
+		elm = ed.dom.getParent(ed.selection.getNode(), 'A,IMG');
+		v = ed.dom.getAttrib(elm, 'name');
+
+		if (v) {
+			this.action = 'update';
+			f.anchorName.value = v;
+		}
+
+		f.insert.value = ed.getLang(elm ? 'update' : 'insert');
+	},
+
+	update : function() {
+		var ed = this.editor;
+		
+		tinyMCEPopup.restoreSelection();
+
+		if (this.action != 'update')
+			ed.selection.collapse(1);
+
+		// Webkit acts weird if empty inline element is inserted so we need to use a image instead
+		if (tinymce.isWebKit)
+			ed.execCommand('mceInsertContent', 0, ed.dom.createHTML('img', {mce_name : 'a', name : document.forms[0].anchorName.value, 'class' : 'mceItemAnchor'}));
+		else
+			ed.execCommand('mceInsertContent', 0, ed.dom.createHTML('a', {name : document.forms[0].anchorName.value, 'class' : 'mceItemAnchor'}, ''));
+
+		tinyMCEPopup.close();
+	}
+};
+
+tinyMCEPopup.onInit.add(AnchorDialog.init, AnchorDialog);
diff --git a/media/js/tinymce/themes/advanced/js/charmap.js b/media/js/tinymce/themes/advanced/js/charmap.js
new file mode 100644
index 0000000..8467ef6
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/js/charmap.js
@@ -0,0 +1,325 @@
+tinyMCEPopup.requireLangPack();
+
+var charmap = [
+	[' ',    ' ',  true, 'no-break space'],
+	['&',     '&',   true, 'ampersand'],
+	['"',    '"',   true, 'quotation mark'],
+// finance
+	['¢',    '¢',  true, 'cent sign'],
+	['€',    '€', true, 'euro sign'],
+	['£',   '£',  true, 'pound sign'],
+	['¥',     '¥',  true, 'yen sign'],
+// signs
+	['©',    '©',  true, 'copyright sign'],
+	['®',     '®',  true, 'registered sign'],
+	['™',   '™', true, 'trade mark sign'],
+	['‰',  '‰', true, 'per mille sign'],
+	['µ',   'µ',  true, 'micro sign'],
+	['·',  '·',  true, 'middle dot'],
+	['•',    '•', true, 'bullet'],
+	['…',  '…', true, 'three dot leader'],
+	['′',   '′', true, 'minutes / feet'],
+	['″',   '″', true, 'seconds / inches'],
+	['§',    '§',  true, 'section sign'],
+	['¶',    '¶',  true, 'paragraph sign'],
+	['ß',   'ß',  true, 'sharp s / ess-zed'],
+// quotations
+	['‹',  '‹', true, 'single left-pointing angle quotation mark'],
+	['›',  '›', true, 'single right-pointing angle quotation mark'],
+	['«',   '«',  true, 'left pointing guillemet'],
+	['»',   '»',  true, 'right pointing guillemet'],
+	['‘',   '‘', true, 'left single quotation mark'],
+	['’',   '’', true, 'right single quotation mark'],
+	['“',   '“', true, 'left double quotation mark'],
+	['”',   '”', true, 'right double quotation mark'],
+	['‚',   '‚', true, 'single low-9 quotation mark'],
+	['„',   '„', true, 'double low-9 quotation mark'],
+	['<',      '<',   true, 'less-than sign'],
+	['>',      '>',   true, 'greater-than sign'],
+	['≤',      '≤', true, 'less-than or equal to'],
+	['≥',      '≥', true, 'greater-than or equal to'],
+	['–',   '–', true, 'en dash'],
+	['—',   '—', true, 'em dash'],
+	['¯',    '¯',  true, 'macron'],
+	['‾',   '‾', true, 'overline'],
+	['¤',  '¤',  true, 'currency sign'],
+	['¦',  '¦',  true, 'broken bar'],
+	['¨',     '¨',  true, 'diaeresis'],
+	['¡',   '¡',  true, 'inverted exclamation mark'],
+	['¿',  '¿',  true, 'turned question mark'],
+	['ˆ',    'ˆ',  true, 'circumflex accent'],
+	['˜',   '˜',  true, 'small tilde'],
+	['°',     '°',  true, 'degree sign'],
+	['−',   '−', true, 'minus sign'],
+	['±',  '±',  true, 'plus-minus sign'],
+	['÷',  '÷',  true, 'division sign'],
+	['⁄',   '⁄', true, 'fraction slash'],
+	['×',   '×',  true, 'multiplication sign'],
+	['&sup1;',    '¹',  true, 'superscript one'],
+	['&sup2;',    '²',  true, 'superscript two'],
+	['&sup3;',    '³',  true, 'superscript three'],
+	['&frac14;',  '¼',  true, 'fraction one quarter'],
+	['&frac12;',  '½',  true, 'fraction one half'],
+	['&frac34;',  '¾',  true, 'fraction three quarters'],
+// math / logical
+	['ƒ',    'ƒ',  true, 'function / florin'],
+	['∫',     '∫', true, 'integral'],
+	['∑',     '∑', true, 'n-ary sumation'],
+	['∞',   '∞', true, 'infinity'],
+	['√',   '√', true, 'square root'],
+	['∼',     '∼', false,'similar to'],
+	['≅',    '≅', false,'approximately equal to'],
+	['≈',   '≈', true, 'almost equal to'],
+	['≠',      '≠', true, 'not equal to'],
+	['≡',   '≡', true, 'identical to'],
+	['∈',    '∈', false,'element of'],
+	['∉',   '∉', false,'not an element of'],
+	['∋',      '∋', false,'contains as member'],
+	['∏',    '∏', true, 'n-ary product'],
+	['∧',     '∧', false,'logical and'],
+	['∨',      '∨', false,'logical or'],
+	['¬',     '¬',  true, 'not sign'],
+	['∩',     '∩', true, 'intersection'],
+	['∪',     '∪', false,'union'],
+	['∂',    '∂', true, 'partial differential'],
+	['∀',  '∀', false,'for all'],
+	['∃',   '∃', false,'there exists'],
+	['∅',   '∅', false,'diameter'],
+	['∇',   '∇', false,'backward difference'],
+	['∗',  '∗', false,'asterisk operator'],
+	['∝',    '∝', false,'proportional to'],
+	['∠',     '∠', false,'angle'],
+// undefined
+	['´',   '´',  true, 'acute accent'],
+	['¸',   '¸',  true, 'cedilla'],
+	['ª',    'ª',  true, 'feminine ordinal indicator'],
+	['º',    'º',  true, 'masculine ordinal indicator'],
+	['†',  '†', true, 'dagger'],
+	['‡',  '‡', true, 'double dagger'],
+// alphabetical special chars
+	['À',  'À',  true, 'A - grave'],
+	['Á',  'Á',  true, 'A - acute'],
+	['Â',   'Â',  true, 'A - circumflex'],
+	['Ã',  'Ã',  true, 'A - tilde'],
+	['Ä',    'Ä',  true, 'A - diaeresis'],
+	['Å',   'Å',  true, 'A - ring above'],
+	['Æ',   'Æ',  true, 'ligature AE'],
+	['Ç',  'Ç',  true, 'C - cedilla'],
+	['È',  'È',  true, 'E - grave'],
+	['É',  'É',  true, 'E - acute'],
+	['Ê',   'Ê',  true, 'E - circumflex'],
+	['Ë',    'Ë',  true, 'E - diaeresis'],
+	['Ì',  'Ì',  true, 'I - grave'],
+	['Í',  'Í',  true, 'I - acute'],
+	['Î',   'Î',  true, 'I - circumflex'],
+	['Ï',    'Ï',  true, 'I - diaeresis'],
+	['Ð',     'Ð',  true, 'ETH'],
+	['Ñ',  'Ñ',  true, 'N - tilde'],
+	['Ò',  'Ò',  true, 'O - grave'],
+	['Ó',  'Ó',  true, 'O - acute'],
+	['Ô',   'Ô',  true, 'O - circumflex'],
+	['Õ',  'Õ',  true, 'O - tilde'],
+	['Ö',    'Ö',  true, 'O - diaeresis'],
+	['Ø',  'Ø',  true, 'O - slash'],
+	['Œ',   'Œ',  true, 'ligature OE'],
+	['Š',  'Š',  true, 'S - caron'],
+	['Ù',  'Ù',  true, 'U - grave'],
+	['Ú',  'Ú',  true, 'U - acute'],
+	['Û',   'Û',  true, 'U - circumflex'],
+	['Ü',    'Ü',  true, 'U - diaeresis'],
+	['Ý',  'Ý',  true, 'Y - acute'],
+	['Ÿ',    'Ÿ',  true, 'Y - diaeresis'],
+	['Þ',   'Þ',  true, 'THORN'],
+	['à',  'à',  true, 'a - grave'],
+	['á',  'á',  true, 'a - acute'],
+	['â',   'â',  true, 'a - circumflex'],
+	['ã',  'ã',  true, 'a - tilde'],
+	['ä',    'ä',  true, 'a - diaeresis'],
+	['å',   'å',  true, 'a - ring above'],
+	['æ',   'æ',  true, 'ligature ae'],
+	['ç',  'ç',  true, 'c - cedilla'],
+	['è',  'è',  true, 'e - grave'],
+	['é',  'é',  true, 'e - acute'],
+	['ê',   'ê',  true, 'e - circumflex'],
+	['ë',    'ë',  true, 'e - diaeresis'],
+	['ì',  'ì',  true, 'i - grave'],
+	['í',  'í',  true, 'i - acute'],
+	['î',   'î',  true, 'i - circumflex'],
+	['ï',    'ï',  true, 'i - diaeresis'],
+	['ð',     'ð',  true, 'eth'],
+	['ñ',  'ñ',  true, 'n - tilde'],
+	['ò',  'ò',  true, 'o - grave'],
+	['ó',  'ó',  true, 'o - acute'],
+	['ô',   'ô',  true, 'o - circumflex'],
+	['õ',  'õ',  true, 'o - tilde'],
+	['ö',    'ö',  true, 'o - diaeresis'],
+	['ø',  'ø',  true, 'o slash'],
+	['œ',   'œ',  true, 'ligature oe'],
+	['š',  'š',  true, 's - caron'],
+	['ù',  'ù',  true, 'u - grave'],
+	['ú',  'ú',  true, 'u - acute'],
+	['û',   'û',  true, 'u - circumflex'],
+	['ü',    'ü',  true, 'u - diaeresis'],
+	['ý',  'ý',  true, 'y - acute'],
+	['þ',   'þ',  true, 'thorn'],
+	['ÿ',    'ÿ',  true, 'y - diaeresis'],
+    ['Α',   'Α',  true, 'Alpha'],
+	['Β',    'Β',  true, 'Beta'],
+	['Γ',   'Γ',  true, 'Gamma'],
+	['Δ',   'Δ',  true, 'Delta'],
+	['Ε', 'Ε',  true, 'Epsilon'],
+	['Ζ',    'Ζ',  true, 'Zeta'],
+	['Η',     'Η',  true, 'Eta'],
+	['Θ',   'Θ',  true, 'Theta'],
+	['Ι',    'Ι',  true, 'Iota'],
+	['Κ',   'Κ',  true, 'Kappa'],
+	['Λ',  'Λ',  true, 'Lambda'],
+	['Μ',      'Μ',  true, 'Mu'],
+	['Ν',      'Ν',  true, 'Nu'],
+	['Ξ',      'Ξ',  true, 'Xi'],
+	['Ο', 'Ο',  true, 'Omicron'],
+	['Π',      'Π',  true, 'Pi'],
+	['Ρ',     'Ρ',  true, 'Rho'],
+	['Σ',   'Σ',  true, 'Sigma'],
+	['Τ',     'Τ',  true, 'Tau'],
+	['Υ', 'Υ',  true, 'Upsilon'],
+	['Φ',     'Φ',  true, 'Phi'],
+	['Χ',     'Χ',  true, 'Chi'],
+	['Ψ',     'Ψ',  true, 'Psi'],
+	['Ω',   'Ω',  true, 'Omega'],
+	['α',   'α',  true, 'alpha'],
+	['β',    'β',  true, 'beta'],
+	['γ',   'γ',  true, 'gamma'],
+	['δ',   'δ',  true, 'delta'],
+	['ε', 'ε',  true, 'epsilon'],
+	['ζ',    'ζ',  true, 'zeta'],
+	['η',     'η',  true, 'eta'],
+	['θ',   'θ',  true, 'theta'],
+	['ι',    'ι',  true, 'iota'],
+	['κ',   'κ',  true, 'kappa'],
+	['λ',  'λ',  true, 'lambda'],
+	['μ',      'μ',  true, 'mu'],
+	['ν',      'ν',  true, 'nu'],
+	['ξ',      'ξ',  true, 'xi'],
+	['ο', 'ο',  true, 'omicron'],
+	['π',      'π',  true, 'pi'],
+	['ρ',     'ρ',  true, 'rho'],
+	['ς',  'ς',  true, 'final sigma'],
+	['σ',   'σ',  true, 'sigma'],
+	['τ',     'τ',  true, 'tau'],
+	['υ', 'υ',  true, 'upsilon'],
+	['φ',     'φ',  true, 'phi'],
+	['χ',     'χ',  true, 'chi'],
+	['ψ',     'ψ',  true, 'psi'],
+	['ω',   'ω',  true, 'omega'],
+// symbols
+	['ℵ', 'ℵ', false,'alef symbol'],
+	['ϖ',     'ϖ',  false,'pi symbol'],
+	['ℜ',    'ℜ', false,'real part symbol'],
+	['ϑ','ϑ',  false,'theta symbol'],
+	['ϒ',   'ϒ',  false,'upsilon - hook symbol'],
+	['℘',  '℘', false,'Weierstrass p'],
+	['ℑ',   'ℑ', false,'imaginary part'],
+// arrows
+	['←',    '←', true, 'leftwards arrow'],
+	['↑',    '↑', true, 'upwards arrow'],
+	['→',    '→', true, 'rightwards arrow'],
+	['↓',    '↓', true, 'downwards arrow'],
+	['↔',    '↔', true, 'left right arrow'],
+	['↵',   '↵', false,'carriage return'],
+	['⇐',    '⇐', false,'leftwards double arrow'],
+	['⇑',    '⇑', false,'upwards double arrow'],
+	['⇒',    '⇒', false,'rightwards double arrow'],
+	['⇓',    '⇓', false,'downwards double arrow'],
+	['⇔',    '⇔', false,'left right double arrow'],
+	['&there4;',  '∴', false,'therefore'],
+	['⊂',     '⊂', false,'subset of'],
+	['⊃',     '⊃', false,'superset of'],
+	['⊄',    '⊄', false,'not a subset of'],
+	['⊆',    '⊆', false,'subset of or equal to'],
+	['⊇',    '⊇', false,'superset of or equal to'],
+	['⊕',   '⊕', false,'circled plus'],
+	['⊗',  '⊗', false,'circled times'],
+	['⊥',    '⊥', false,'perpendicular'],
+	['⋅',    '⋅', false,'dot operator'],
+	['⌈',   '⌈', false,'left ceiling'],
+	['⌉',   '⌉', false,'right ceiling'],
+	['⌊',  '⌊', false,'left floor'],
+	['⌋',  '⌋', false,'right floor'],
+	['⟨',    '〈', false,'left-pointing angle bracket'],
+	['⟩',    '〉', false,'right-pointing angle bracket'],
+	['◊',     '◊', true,'lozenge'],
+	['♠',  '♠', false,'black spade suit'],
+	['♣',   '♣', true, 'black club suit'],
+	['♥',  '♥', true, 'black heart suit'],
+	['♦',   '♦', true, 'black diamond suit'],
+	[' ',    ' ', false,'en space'],
+	[' ',    ' ', false,'em space'],
+	[' ',  ' ', false,'thin space'],
+	['‌',    '‌', false,'zero width non-joiner'],
+	['‍',     '‍', false,'zero width joiner'],
+	['‎',     '‎', false,'left-to-right mark'],
+	['‏',     '‏', false,'right-to-left mark'],
+	['­',     '­',  false,'soft hyphen']
+];
+
+tinyMCEPopup.onInit.add(function() {
+	tinyMCEPopup.dom.setHTML('charmapView', renderCharMapHTML());
+});
+
+function renderCharMapHTML() {
+	var charsPerRow = 20, tdWidth=20, tdHeight=20, i;
+	var html = '<table border="0" cellspacing="1" cellpadding="0" width="' + (tdWidth*charsPerRow) + '"><tr height="' + tdHeight + '">';
+	var cols=-1;
+
+	for (i=0; i<charmap.length; i++) {
+		if (charmap[i][2]==true) {
+			cols++;
+			html += ''
+				+ '<td class="charmap">'
+				+ '<a onmouseover="previewChar(\'' + charmap[i][1].substring(1,charmap[i][1].length) + '\',\'' + charmap[i][0].substring(1,charmap[i][0].length) + '\',\'' + charmap[i][3] + '\');" onfocus="previewChar(\'' + charmap[i][1].substring(1,charmap[i][1].length) + '\',\'' + charmap[i][0].substring(1,charmap[i][0].length) + '\',\'' + charmap[i][3] + '\');" href="javascript:void(0)" onclick="insertChar(\'' + charmap[i][1].substring(2,charmap[i][1].length-1) + '\');" onclick="return false;" onm [...]
+				+ charmap[i][1]
+				+ '</a></td>';
+			if ((cols+1) % charsPerRow == 0)
+				html += '</tr><tr height="' + tdHeight + '">';
+		}
+	 }
+
+	if (cols % charsPerRow > 0) {
+		var padd = charsPerRow - (cols % charsPerRow);
+		for (var i=0; i<padd-1; i++)
+			html += '<td width="' + tdWidth + '" height="' + tdHeight + '" class="charmap"> </td>';
+	}
+
+	html += '</tr></table>';
+
+	return html;
+}
+
+function insertChar(chr) {
+	tinyMCEPopup.execCommand('mceInsertContent', false, '&#' + chr + ';');
+
+	// Refocus in window
+	if (tinyMCEPopup.isWindow)
+		window.focus();
+
+	tinyMCEPopup.editor.focus();
+	tinyMCEPopup.close();
+}
+
+function previewChar(codeA, codeB, codeN) {
+	var elmA = document.getElementById('codeA');
+	var elmB = document.getElementById('codeB');
+	var elmV = document.getElementById('codeV');
+	var elmN = document.getElementById('codeN');
+
+	if (codeA=='#160;') {
+		elmV.innerHTML = '__';
+	} else {
+		elmV.innerHTML = '&' + codeA;
+	}
+
+	elmB.innerHTML = '&' + codeA;
+	elmA.innerHTML = '&' + codeB;
+	elmN.innerHTML = codeN;
+}
diff --git a/media/js/tinymce/themes/advanced/js/color_picker.js b/media/js/tinymce/themes/advanced/js/color_picker.js
new file mode 100644
index 0000000..fd9700f
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/js/color_picker.js
@@ -0,0 +1,253 @@
+tinyMCEPopup.requireLangPack();
+
+var detail = 50, strhex = "0123456789abcdef", i, isMouseDown = false, isMouseOver = false;
+
+var colors = [
+	"#000000","#000033","#000066","#000099","#0000cc","#0000ff","#330000","#330033",
+	"#330066","#330099","#3300cc","#3300ff","#660000","#660033","#660066","#660099",
+	"#6600cc","#6600ff","#990000","#990033","#990066","#990099","#9900cc","#9900ff",
+	"#cc0000","#cc0033","#cc0066","#cc0099","#cc00cc","#cc00ff","#ff0000","#ff0033",
+	"#ff0066","#ff0099","#ff00cc","#ff00ff","#003300","#003333","#003366","#003399",
+	"#0033cc","#0033ff","#333300","#333333","#333366","#333399","#3333cc","#3333ff",
+	"#663300","#663333","#663366","#663399","#6633cc","#6633ff","#993300","#993333",
+	"#993366","#993399","#9933cc","#9933ff","#cc3300","#cc3333","#cc3366","#cc3399",
+	"#cc33cc","#cc33ff","#ff3300","#ff3333","#ff3366","#ff3399","#ff33cc","#ff33ff",
+	"#006600","#006633","#006666","#006699","#0066cc","#0066ff","#336600","#336633",
+	"#336666","#336699","#3366cc","#3366ff","#666600","#666633","#666666","#666699",
+	"#6666cc","#6666ff","#996600","#996633","#996666","#996699","#9966cc","#9966ff",
+	"#cc6600","#cc6633","#cc6666","#cc6699","#cc66cc","#cc66ff","#ff6600","#ff6633",
+	"#ff6666","#ff6699","#ff66cc","#ff66ff","#009900","#009933","#009966","#009999",
+	"#0099cc","#0099ff","#339900","#339933","#339966","#339999","#3399cc","#3399ff",
+	"#669900","#669933","#669966","#669999","#6699cc","#6699ff","#999900","#999933",
+	"#999966","#999999","#9999cc","#9999ff","#cc9900","#cc9933","#cc9966","#cc9999",
+	"#cc99cc","#cc99ff","#ff9900","#ff9933","#ff9966","#ff9999","#ff99cc","#ff99ff",
+	"#00cc00","#00cc33","#00cc66","#00cc99","#00cccc","#00ccff","#33cc00","#33cc33",
+	"#33cc66","#33cc99","#33cccc","#33ccff","#66cc00","#66cc33","#66cc66","#66cc99",
+	"#66cccc","#66ccff","#99cc00","#99cc33","#99cc66","#99cc99","#99cccc","#99ccff",
+	"#cccc00","#cccc33","#cccc66","#cccc99","#cccccc","#ccccff","#ffcc00","#ffcc33",
+	"#ffcc66","#ffcc99","#ffcccc","#ffccff","#00ff00","#00ff33","#00ff66","#00ff99",
+	"#00ffcc","#00ffff","#33ff00","#33ff33","#33ff66","#33ff99","#33ffcc","#33ffff",
+	"#66ff00","#66ff33","#66ff66","#66ff99","#66ffcc","#66ffff","#99ff00","#99ff33",
+	"#99ff66","#99ff99","#99ffcc","#99ffff","#ccff00","#ccff33","#ccff66","#ccff99",
+	"#ccffcc","#ccffff","#ffff00","#ffff33","#ffff66","#ffff99","#ffffcc","#ffffff"
+];
+
+var named = {
+	'#F0F8FF':'AliceBlue','#FAEBD7':'AntiqueWhite','#00FFFF':'Aqua','#7FFFD4':'Aquamarine','#F0FFFF':'Azure','#F5F5DC':'Beige',
+	'#FFE4C4':'Bisque','#000000':'Black','#FFEBCD':'BlanchedAlmond','#0000FF':'Blue','#8A2BE2':'BlueViolet','#A52A2A':'Brown',
+	'#DEB887':'BurlyWood','#5F9EA0':'CadetBlue','#7FFF00':'Chartreuse','#D2691E':'Chocolate','#FF7F50':'Coral','#6495ED':'CornflowerBlue',
+	'#FFF8DC':'Cornsilk','#DC143C':'Crimson','#00FFFF':'Cyan','#00008B':'DarkBlue','#008B8B':'DarkCyan','#B8860B':'DarkGoldenRod',
+	'#A9A9A9':'DarkGray','#A9A9A9':'DarkGrey','#006400':'DarkGreen','#BDB76B':'DarkKhaki','#8B008B':'DarkMagenta','#556B2F':'DarkOliveGreen',
+	'#FF8C00':'Darkorange','#9932CC':'DarkOrchid','#8B0000':'DarkRed','#E9967A':'DarkSalmon','#8FBC8F':'DarkSeaGreen','#483D8B':'DarkSlateBlue',
+	'#2F4F4F':'DarkSlateGray','#2F4F4F':'DarkSlateGrey','#00CED1':'DarkTurquoise','#9400D3':'DarkViolet','#FF1493':'DeepPink','#00BFFF':'DeepSkyBlue',
+	'#696969':'DimGray','#696969':'DimGrey','#1E90FF':'DodgerBlue','#B22222':'FireBrick','#FFFAF0':'FloralWhite','#228B22':'ForestGreen',
+	'#FF00FF':'Fuchsia','#DCDCDC':'Gainsboro','#F8F8FF':'GhostWhite','#FFD700':'Gold','#DAA520':'GoldenRod','#808080':'Gray','#808080':'Grey',
+	'#008000':'Green','#ADFF2F':'GreenYellow','#F0FFF0':'HoneyDew','#FF69B4':'HotPink','#CD5C5C':'IndianRed','#4B0082':'Indigo','#FFFFF0':'Ivory',
+	'#F0E68C':'Khaki','#E6E6FA':'Lavender','#FFF0F5':'LavenderBlush','#7CFC00':'LawnGreen','#FFFACD':'LemonChiffon','#ADD8E6':'LightBlue',
+	'#F08080':'LightCoral','#E0FFFF':'LightCyan','#FAFAD2':'LightGoldenRodYellow','#D3D3D3':'LightGray','#D3D3D3':'LightGrey','#90EE90':'LightGreen',
+	'#FFB6C1':'LightPink','#FFA07A':'LightSalmon','#20B2AA':'LightSeaGreen','#87CEFA':'LightSkyBlue','#778899':'LightSlateGray','#778899':'LightSlateGrey',
+	'#B0C4DE':'LightSteelBlue','#FFFFE0':'LightYellow','#00FF00':'Lime','#32CD32':'LimeGreen','#FAF0E6':'Linen','#FF00FF':'Magenta','#800000':'Maroon',
+	'#66CDAA':'MediumAquaMarine','#0000CD':'MediumBlue','#BA55D3':'MediumOrchid','#9370D8':'MediumPurple','#3CB371':'MediumSeaGreen','#7B68EE':'MediumSlateBlue',
+	'#00FA9A':'MediumSpringGreen','#48D1CC':'MediumTurquoise','#C71585':'MediumVioletRed','#191970':'MidnightBlue','#F5FFFA':'MintCream','#FFE4E1':'MistyRose','#FFE4B5':'Moccasin',
+	'#FFDEAD':'NavajoWhite','#000080':'Navy','#FDF5E6':'OldLace','#808000':'Olive','#6B8E23':'OliveDrab','#FFA500':'Orange','#FF4500':'OrangeRed','#DA70D6':'Orchid',
+	'#EEE8AA':'PaleGoldenRod','#98FB98':'PaleGreen','#AFEEEE':'PaleTurquoise','#D87093':'PaleVioletRed','#FFEFD5':'PapayaWhip','#FFDAB9':'PeachPuff',
+	'#CD853F':'Peru','#FFC0CB':'Pink','#DDA0DD':'Plum','#B0E0E6':'PowderBlue','#800080':'Purple','#FF0000':'Red','#BC8F8F':'RosyBrown','#4169E1':'RoyalBlue',
+	'#8B4513':'SaddleBrown','#FA8072':'Salmon','#F4A460':'SandyBrown','#2E8B57':'SeaGreen','#FFF5EE':'SeaShell','#A0522D':'Sienna','#C0C0C0':'Silver',
+	'#87CEEB':'SkyBlue','#6A5ACD':'SlateBlue','#708090':'SlateGray','#708090':'SlateGrey','#FFFAFA':'Snow','#00FF7F':'SpringGreen',
+	'#4682B4':'SteelBlue','#D2B48C':'Tan','#008080':'Teal','#D8BFD8':'Thistle','#FF6347':'Tomato','#40E0D0':'Turquoise','#EE82EE':'Violet',
+	'#F5DEB3':'Wheat','#FFFFFF':'White','#F5F5F5':'WhiteSmoke','#FFFF00':'Yellow','#9ACD32':'YellowGreen'
+};
+
+function init() {
+	var inputColor = convertRGBToHex(tinyMCEPopup.getWindowArg('input_color'));
+
+	tinyMCEPopup.resizeToInnerSize();
+
+	generatePicker();
+
+	if (inputColor) {
+		changeFinalColor(inputColor);
+
+		col = convertHexToRGB(inputColor);
+
+		if (col)
+			updateLight(col.r, col.g, col.b);
+	}
+}
+
+function insertAction() {
+	var color = document.getElementById("color").value, f = tinyMCEPopup.getWindowArg('func');
+
+	tinyMCEPopup.restoreSelection();
+
+	if (f)
+		f(color);
+
+	tinyMCEPopup.close();
+}
+
+function showColor(color, name) {
+	if (name)
+		document.getElementById("colorname").innerHTML = name;
+
+	document.getElementById("preview").style.backgroundColor = color;
+	document.getElementById("color").value = color.toLowerCase();
+}
+
+function convertRGBToHex(col) {
+	var re = new RegExp("rgb\\s*\\(\\s*([0-9]+).*,\\s*([0-9]+).*,\\s*([0-9]+).*\\)", "gi");
+
+	if (!col)
+		return col;
+
+	var rgb = col.replace(re, "$1,$2,$3").split(',');
+	if (rgb.length == 3) {
+		r = parseInt(rgb[0]).toString(16);
+		g = parseInt(rgb[1]).toString(16);
+		b = parseInt(rgb[2]).toString(16);
+
+		r = r.length == 1 ? '0' + r : r;
+		g = g.length == 1 ? '0' + g : g;
+		b = b.length == 1 ? '0' + b : b;
+
+		return "#" + r + g + b;
+	}
+
+	return col;
+}
+
+function convertHexToRGB(col) {
+	if (col.indexOf('#') != -1) {
+		col = col.replace(new RegExp('[^0-9A-F]', 'gi'), '');
+
+		r = parseInt(col.substring(0, 2), 16);
+		g = parseInt(col.substring(2, 4), 16);
+		b = parseInt(col.substring(4, 6), 16);
+
+		return {r : r, g : g, b : b};
+	}
+
+	return null;
+}
+
+function generatePicker() {
+	var el = document.getElementById('light'), h = '', i;
+
+	for (i = 0; i < detail; i++){
+		h += '<div id="gs'+i+'" style="background-color:#000000; width:15px; height:3px; border-style:none; border-width:0px;"'
+		+ ' onclick="changeFinalColor(this.style.backgroundColor)"'
+		+ ' onmousedown="isMouseDown = true; return false;"'
+		+ ' onmouseup="isMouseDown = false;"'
+		+ ' onmousemove="if (isMouseDown && isMouseOver) changeFinalColor(this.style.backgroundColor); return false;"'
+		+ ' onmouseover="isMouseOver = true;"'
+		+ ' onmouseout="isMouseOver = false;"'
+		+ '></div>';
+	}
+
+	el.innerHTML = h;
+}
+
+function generateWebColors() {
+	var el = document.getElementById('webcolors'), h = '', i;
+
+	if (el.className == 'generated')
+		return;
+
+	h += '<table border="0" cellspacing="1" cellpadding="0">'
+		+ '<tr>';
+
+	for (i=0; i<colors.length; i++) {
+		h += '<td bgcolor="' + colors[i] + '" width="10" height="10">'
+			+ '<a href="javascript:insertAction();" onfocus="showColor(\'' + colors[i] +  '\');" onmouseover="showColor(\'' + colors[i] +  '\');" style="display:block;width:10px;height:10px;overflow:hidden;">'
+			+ '</a></td>';
+		if ((i+1) % 18 == 0)
+			h += '</tr><tr>';
+	}
+
+	h += '</table>';
+
+	el.innerHTML = h;
+	el.className = 'generated';
+}
+
+function generateNamedColors() {
+	var el = document.getElementById('namedcolors'), h = '', n, v, i = 0;
+
+	if (el.className == 'generated')
+		return;
+
+	for (n in named) {
+		v = named[n];
+		h += '<a href="javascript:insertAction();" onmouseover="showColor(\'' + n +  '\',\'' + v + '\');" style="background-color: ' + n + '"><!-- IE --></a>'
+	}
+
+	el.innerHTML = h;
+	el.className = 'generated';
+}
+
+function dechex(n) {
+	return strhex.charAt(Math.floor(n / 16)) + strhex.charAt(n % 16);
+}
+
+function computeColor(e) {
+	var x, y, partWidth, partDetail, imHeight, r, g, b, coef, i, finalCoef, finalR, finalG, finalB;
+
+	x = e.offsetX ? e.offsetX : (e.target ? e.clientX - e.target.x : 0);
+	y = e.offsetY ? e.offsetY : (e.target ? e.clientY - e.target.y : 0);
+
+	partWidth = document.getElementById('colors').width / 6;
+	partDetail = detail / 2;
+	imHeight = document.getElementById('colors').height;
+
+	r = (x >= 0)*(x < partWidth)*255 + (x >= partWidth)*(x < 2*partWidth)*(2*255 - x * 255 / partWidth) + (x >= 4*partWidth)*(x < 5*partWidth)*(-4*255 + x * 255 / partWidth) + (x >= 5*partWidth)*(x < 6*partWidth)*255;
+	g = (x >= 0)*(x < partWidth)*(x * 255 / partWidth) + (x >= partWidth)*(x < 3*partWidth)*255	+ (x >= 3*partWidth)*(x < 4*partWidth)*(4*255 - x * 255 / partWidth);
+	b = (x >= 2*partWidth)*(x < 3*partWidth)*(-2*255 + x * 255 / partWidth) + (x >= 3*partWidth)*(x < 5*partWidth)*255 + (x >= 5*partWidth)*(x < 6*partWidth)*(6*255 - x * 255 / partWidth);
+
+	coef = (imHeight - y) / imHeight;
+	r = 128 + (r - 128) * coef;
+	g = 128 + (g - 128) * coef;
+	b = 128 + (b - 128) * coef;
+
+	changeFinalColor('#' + dechex(r) + dechex(g) + dechex(b));
+	updateLight(r, g, b);
+}
+
+function updateLight(r, g, b) {
+	var i, partDetail = detail / 2, finalCoef, finalR, finalG, finalB, color;
+
+	for (i=0; i<detail; i++) {
+		if ((i>=0) && (i<partDetail)) {
+			finalCoef = i / partDetail;
+			finalR = dechex(255 - (255 - r) * finalCoef);
+			finalG = dechex(255 - (255 - g) * finalCoef);
+			finalB = dechex(255 - (255 - b) * finalCoef);
+		} else {
+			finalCoef = 2 - i / partDetail;
+			finalR = dechex(r * finalCoef);
+			finalG = dechex(g * finalCoef);
+			finalB = dechex(b * finalCoef);
+		}
+
+		color = finalR + finalG + finalB;
+
+		setCol('gs' + i, '#'+color);
+	}
+}
+
+function changeFinalColor(color) {
+	if (color.indexOf('#') == -1)
+		color = convertRGBToHex(color);
+
+	setCol('preview', color);
+	document.getElementById('color').value = color;
+}
+
+function setCol(e, c) {
+	try {
+		document.getElementById(e).style.backgroundColor = c;
+	} catch (ex) {
+		// Ignore IE warning
+	}
+}
+
+tinyMCEPopup.onInit.add(init);
diff --git a/media/js/tinymce/themes/advanced/js/image.js b/media/js/tinymce/themes/advanced/js/image.js
new file mode 100644
index 0000000..4982ce0
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/js/image.js
@@ -0,0 +1,245 @@
+var ImageDialog = {
+	preInit : function() {
+		var url;
+
+		tinyMCEPopup.requireLangPack();
+
+		if (url = tinyMCEPopup.getParam("external_image_list_url"))
+			document.write('<script language="javascript" type="text/javascript" src="' + tinyMCEPopup.editor.documentBaseURI.toAbsolute(url) + '"></script>');
+	},
+
+	init : function() {
+		var f = document.forms[0], ed = tinyMCEPopup.editor;
+
+		// Setup browse button
+		document.getElementById('srcbrowsercontainer').innerHTML = getBrowserHTML('srcbrowser','src','image','theme_advanced_image');
+		if (isVisible('srcbrowser'))
+			document.getElementById('src').style.width = '180px';
+
+		e = ed.selection.getNode();
+
+		this.fillFileList('image_list', 'tinyMCEImageList');
+
+		if (e.nodeName == 'IMG') {
+			f.src.value = ed.dom.getAttrib(e, 'src');
+			f.alt.value = ed.dom.getAttrib(e, 'alt');
+			f.border.value = this.getAttrib(e, 'border');
+			f.vspace.value = this.getAttrib(e, 'vspace');
+			f.hspace.value = this.getAttrib(e, 'hspace');
+			f.width.value = ed.dom.getAttrib(e, 'width');
+			f.height.value = ed.dom.getAttrib(e, 'height');
+			f.insert.value = ed.getLang('update');
+			this.styleVal = ed.dom.getAttrib(e, 'style');
+			selectByValue(f, 'image_list', f.src.value);
+			selectByValue(f, 'align', this.getAttrib(e, 'align'));
+			this.updateStyle();
+		}
+	},
+
+	fillFileList : function(id, l) {
+		var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl;
+
+		l = window[l];
+
+		if (l && l.length > 0) {
+			lst.options[lst.options.length] = new Option('', '');
+
+			tinymce.each(l, function(o) {
+				lst.options[lst.options.length] = new Option(o[0], o[1]);
+			});
+		} else
+			dom.remove(dom.getParent(id, 'tr'));
+	},
+
+	update : function() {
+		var f = document.forms[0], nl = f.elements, ed = tinyMCEPopup.editor, args = {}, el;
+
+		tinyMCEPopup.restoreSelection();
+
+		if (f.src.value === '') {
+			if (ed.selection.getNode().nodeName == 'IMG') {
+				ed.dom.remove(ed.selection.getNode());
+				ed.execCommand('mceRepaint');
+			}
+
+			tinyMCEPopup.close();
+			return;
+		}
+
+		if (!ed.settings.inline_styles) {
+			args = tinymce.extend(args, {
+				vspace : nl.vspace.value,
+				hspace : nl.hspace.value,
+				border : nl.border.value,
+				align : getSelectValue(f, 'align')
+			});
+		} else
+			args.style = this.styleVal;
+
+		tinymce.extend(args, {
+			src : f.src.value,
+			alt : f.alt.value,
+			width : f.width.value,
+			height : f.height.value
+		});
+
+		el = ed.selection.getNode();
+
+		if (el && el.nodeName == 'IMG') {
+			ed.dom.setAttribs(el, args);
+		} else {
+			ed.execCommand('mceInsertContent', false, '<img id="__mce_tmp" />', {skip_undo : 1});
+			ed.dom.setAttribs('__mce_tmp', args);
+			ed.dom.setAttrib('__mce_tmp', 'id', '');
+			ed.undoManager.add();
+		}
+
+		tinyMCEPopup.close();
+	},
+
+	updateStyle : function() {
+		var dom = tinyMCEPopup.dom, st, v, f = document.forms[0];
+
+		if (tinyMCEPopup.editor.settings.inline_styles) {
+			st = tinyMCEPopup.dom.parseStyle(this.styleVal);
+
+			// Handle align
+			v = getSelectValue(f, 'align');
+			if (v) {
+				if (v == 'left' || v == 'right') {
+					st['float'] = v;
+					delete st['vertical-align'];
+				} else {
+					st['vertical-align'] = v;
+					delete st['float'];
+				}
+			} else {
+				delete st['float'];
+				delete st['vertical-align'];
+			}
+
+			// Handle border
+			v = f.border.value;
+			if (v || v == '0') {
+				if (v == '0')
+					st['border'] = '0';
+				else
+					st['border'] = v + 'px solid black';
+			} else
+				delete st['border'];
+
+			// Handle hspace
+			v = f.hspace.value;
+			if (v) {
+				delete st['margin'];
+				st['margin-left'] = v + 'px';
+				st['margin-right'] = v + 'px';
+			} else {
+				delete st['margin-left'];
+				delete st['margin-right'];
+			}
+
+			// Handle vspace
+			v = f.vspace.value;
+			if (v) {
+				delete st['margin'];
+				st['margin-top'] = v + 'px';
+				st['margin-bottom'] = v + 'px';
+			} else {
+				delete st['margin-top'];
+				delete st['margin-bottom'];
+			}
+
+			// Merge
+			st = tinyMCEPopup.dom.parseStyle(dom.serializeStyle(st));
+			this.styleVal = dom.serializeStyle(st);
+		}
+	},
+
+	getAttrib : function(e, at) {
+		var ed = tinyMCEPopup.editor, dom = ed.dom, v, v2;
+
+		if (ed.settings.inline_styles) {
+			switch (at) {
+				case 'align':
+					if (v = dom.getStyle(e, 'float'))
+						return v;
+
+					if (v = dom.getStyle(e, 'vertical-align'))
+						return v;
+
+					break;
+
+				case 'hspace':
+					v = dom.getStyle(e, 'margin-left')
+					v2 = dom.getStyle(e, 'margin-right');
+					if (v && v == v2)
+						return parseInt(v.replace(/[^0-9]/g, ''));
+
+					break;
+
+				case 'vspace':
+					v = dom.getStyle(e, 'margin-top')
+					v2 = dom.getStyle(e, 'margin-bottom');
+					if (v && v == v2)
+						return parseInt(v.replace(/[^0-9]/g, ''));
+
+					break;
+
+				case 'border':
+					v = 0;
+
+					tinymce.each(['top', 'right', 'bottom', 'left'], function(sv) {
+						sv = dom.getStyle(e, 'border-' + sv + '-width');
+
+						// False or not the same as prev
+						if (!sv || (sv != v && v !== 0)) {
+							v = 0;
+							return false;
+						}
+
+						if (sv)
+							v = sv;
+					});
+
+					if (v)
+						return parseInt(v.replace(/[^0-9]/g, ''));
+
+					break;
+			}
+		}
+
+		if (v = dom.getAttrib(e, at))
+			return v;
+
+		return '';
+	},
+
+	resetImageData : function() {
+		var f = document.forms[0];
+
+		f.width.value = f.height.value = "";	
+	},
+
+	updateImageData : function() {
+		var f = document.forms[0], t = ImageDialog;
+
+		if (f.width.value == "")
+			f.width.value = t.preloadImg.width;
+
+		if (f.height.value == "")
+			f.height.value = t.preloadImg.height;
+	},
+
+	getImageData : function() {
+		var f = document.forms[0];
+
+		this.preloadImg = new Image();
+		this.preloadImg.onload = this.updateImageData;
+		this.preloadImg.onerror = this.resetImageData;
+		this.preloadImg.src = tinyMCEPopup.editor.documentBaseURI.toAbsolute(f.src.value);
+	}
+};
+
+ImageDialog.preInit();
+tinyMCEPopup.onInit.add(ImageDialog.init, ImageDialog);
diff --git a/media/js/tinymce/themes/advanced/js/link.js b/media/js/tinymce/themes/advanced/js/link.js
new file mode 100644
index 0000000..f67a5bc
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/js/link.js
@@ -0,0 +1,156 @@
+tinyMCEPopup.requireLangPack();
+
+var LinkDialog = {
+	preInit : function() {
+		var url;
+
+		if (url = tinyMCEPopup.getParam("external_link_list_url"))
+			document.write('<script language="javascript" type="text/javascript" src="' + tinyMCEPopup.editor.documentBaseURI.toAbsolute(url) + '"></script>');
+	},
+
+	init : function() {
+		var f = document.forms[0], ed = tinyMCEPopup.editor;
+
+		// Setup browse button
+		document.getElementById('hrefbrowsercontainer').innerHTML = getBrowserHTML('hrefbrowser', 'href', 'file', 'theme_advanced_link');
+		if (isVisible('hrefbrowser'))
+			document.getElementById('href').style.width = '180px';
+
+		this.fillClassList('class_list');
+		this.fillFileList('link_list', 'tinyMCELinkList');
+		this.fillTargetList('target_list');
+
+		if (e = ed.dom.getParent(ed.selection.getNode(), 'A')) {
+			f.href.value = ed.dom.getAttrib(e, 'href');
+			f.linktitle.value = ed.dom.getAttrib(e, 'title');
+			f.insert.value = ed.getLang('update');
+			selectByValue(f, 'link_list', f.href.value);
+			selectByValue(f, 'target_list', ed.dom.getAttrib(e, 'target'));
+			selectByValue(f, 'class_list', ed.dom.getAttrib(e, 'class'));
+		}
+	},
+
+	update : function() {
+		var f = document.forms[0], ed = tinyMCEPopup.editor, e, b;
+
+		tinyMCEPopup.restoreSelection();
+		e = ed.dom.getParent(ed.selection.getNode(), 'A');
+
+		// Remove element if there is no href
+		if (!f.href.value) {
+			if (e) {
+				tinyMCEPopup.execCommand("mceBeginUndoLevel");
+				b = ed.selection.getBookmark();
+				ed.dom.remove(e, 1);
+				ed.selection.moveToBookmark(b);
+				tinyMCEPopup.execCommand("mceEndUndoLevel");
+				tinyMCEPopup.close();
+				return;
+			}
+		}
+
+		tinyMCEPopup.execCommand("mceBeginUndoLevel");
+
+		// Create new anchor elements
+		if (e == null) {
+			ed.getDoc().execCommand("unlink", false, null);
+			tinyMCEPopup.execCommand("CreateLink", false, "#mce_temp_url#", {skip_undo : 1});
+
+			tinymce.each(ed.dom.select("a"), function(n) {
+				if (ed.dom.getAttrib(n, 'href') == '#mce_temp_url#') {
+					e = n;
+
+					ed.dom.setAttribs(e, {
+						href : f.href.value,
+						title : f.linktitle.value,
+						target : f.target_list ? getSelectValue(f, "target_list") : null,
+						'class' : f.class_list ? getSelectValue(f, "class_list") : null
+					});
+				}
+			});
+		} else {
+			ed.dom.setAttribs(e, {
+				href : f.href.value,
+				title : f.linktitle.value,
+				target : f.target_list ? getSelectValue(f, "target_list") : null,
+				'class' : f.class_list ? getSelectValue(f, "class_list") : null
+			});
+		}
+
+		// Don't move caret if selection was image
+		if (e.childNodes.length != 1 || e.firstChild.nodeName != 'IMG') {
+			ed.focus();
+			ed.selection.select(e);
+			ed.selection.collapse(0);
+			tinyMCEPopup.storeSelection();
+		}
+
+		tinyMCEPopup.execCommand("mceEndUndoLevel");
+		tinyMCEPopup.close();
+	},
+
+	checkPrefix : function(n) {
+		if (n.value && Validator.isEmail(n) && !/^\s*mailto:/i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_email')))
+			n.value = 'mailto:' + n.value;
+
+		if (/^\s*www\./i.test(n.value) && confirm(tinyMCEPopup.getLang('advanced_dlg.link_is_external')))
+			n.value = 'http://' + n.value;
+	},
+
+	fillFileList : function(id, l) {
+		var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl;
+
+		l = window[l];
+
+		if (l && l.length > 0) {
+			lst.options[lst.options.length] = new Option('', '');
+
+			tinymce.each(l, function(o) {
+				lst.options[lst.options.length] = new Option(o[0], o[1]);
+			});
+		} else
+			dom.remove(dom.getParent(id, 'tr'));
+	},
+
+	fillClassList : function(id) {
+		var dom = tinyMCEPopup.dom, lst = dom.get(id), v, cl;
+
+		if (v = tinyMCEPopup.getParam('theme_advanced_styles')) {
+			cl = [];
+
+			tinymce.each(v.split(';'), function(v) {
+				var p = v.split('=');
+
+				cl.push({'title' : p[0], 'class' : p[1]});
+			});
+		} else
+			cl = tinyMCEPopup.editor.dom.getClasses();
+
+		if (cl.length > 0) {
+			lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), '');
+
+			tinymce.each(cl, function(o) {
+				lst.options[lst.options.length] = new Option(o.title || o['class'], o['class']);
+			});
+		} else
+			dom.remove(dom.getParent(id, 'tr'));
+	},
+
+	fillTargetList : function(id) {
+		var dom = tinyMCEPopup.dom, lst = dom.get(id), v;
+
+		lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('not_set'), '');
+		lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_same'), '_self');
+		lst.options[lst.options.length] = new Option(tinyMCEPopup.getLang('advanced_dlg.link_target_blank'), '_blank');
+
+		if (v = tinyMCEPopup.getParam('theme_advanced_link_targets')) {
+			tinymce.each(v.split(','), function(v) {
+				v = v.split('=');
+				lst.options[lst.options.length] = new Option(v[0], v[1]);
+			});
+		}
+	}
+};
+
+LinkDialog.preInit();
+tinyMCEPopup.onInit.add(LinkDialog.init, LinkDialog);
diff --git a/media/js/tinymce/themes/advanced/js/source_editor.js b/media/js/tinymce/themes/advanced/js/source_editor.js
new file mode 100644
index 0000000..2793286
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/js/source_editor.js
@@ -0,0 +1,62 @@
+tinyMCEPopup.requireLangPack();
+tinyMCEPopup.onInit.add(onLoadInit);
+
+function saveContent() {
+	tinyMCEPopup.editor.setContent(document.getElementById('htmlSource').value, {source_view : true});
+	tinyMCEPopup.close();
+}
+
+function onLoadInit() {
+	tinyMCEPopup.resizeToInnerSize();
+
+	// Remove Gecko spellchecking
+	if (tinymce.isGecko)
+		document.body.spellcheck = tinyMCEPopup.editor.getParam("gecko_spellcheck");
+
+	document.getElementById('htmlSource').value = tinyMCEPopup.editor.getContent({source_view : true});
+
+	if (tinyMCEPopup.editor.getParam("theme_advanced_source_editor_wrap", true)) {
+		setWrap('soft');
+		document.getElementById('wraped').checked = true;
+	}
+
+	resizeInputs();
+}
+
+function setWrap(val) {
+	var v, n, s = document.getElementById('htmlSource');
+
+	s.wrap = val;
+
+	if (!tinymce.isIE) {
+		v = s.value;
+		n = s.cloneNode(false);
+		n.setAttribute("wrap", val);
+		s.parentNode.replaceChild(n, s);
+		n.value = v;
+	}
+}
+
+function toggleWordWrap(elm) {
+	if (elm.checked)
+		setWrap('soft');
+	else
+		setWrap('off');
+}
+
+var wHeight=0, wWidth=0, owHeight=0, owWidth=0;
+
+function resizeInputs() {
+	var el = document.getElementById('htmlSource');
+
+	if (!tinymce.isIE) {
+		 wHeight = self.innerHeight - 65;
+		 wWidth = self.innerWidth - 16;
+	} else {
+		 wHeight = document.body.clientHeight - 70;
+		 wWidth = document.body.clientWidth - 16;
+	}
+
+	el.style.height = Math.abs(wHeight) + 'px';
+	el.style.width  = Math.abs(wWidth) + 'px';
+}
diff --git a/media/js/tinymce/themes/advanced/langs/en.js b/media/js/tinymce/themes/advanced/langs/en.js
new file mode 100644
index 0000000..69694b1
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/langs/en.js
@@ -0,0 +1,62 @@
+tinyMCE.addI18n('en.advanced',{
+style_select:"Styles",
+font_size:"Font size",
+fontdefault:"Font family",
+block:"Format",
+paragraph:"Paragraph",
+div:"Div",
+address:"Address",
+pre:"Preformatted",
+h1:"Heading 1",
+h2:"Heading 2",
+h3:"Heading 3",
+h4:"Heading 4",
+h5:"Heading 5",
+h6:"Heading 6",
+blockquote:"Blockquote",
+code:"Code",
+samp:"Code sample",
+dt:"Definition term ",
+dd:"Definition description",
+bold_desc:"Bold (Ctrl+B)",
+italic_desc:"Italic (Ctrl+I)",
+underline_desc:"Underline (Ctrl+U)",
+striketrough_desc:"Strikethrough",
+justifyleft_desc:"Align left",
+justifycenter_desc:"Align center",
+justifyright_desc:"Align right",
+justifyfull_desc:"Align full",
+bullist_desc:"Unordered list",
+numlist_desc:"Ordered list",
+outdent_desc:"Outdent",
+indent_desc:"Indent",
+undo_desc:"Undo (Ctrl+Z)",
+redo_desc:"Redo (Ctrl+Y)",
+link_desc:"Insert/edit link",
+unlink_desc:"Unlink",
+image_desc:"Insert/edit image",
+cleanup_desc:"Cleanup messy code",
+code_desc:"Edit HTML Source",
+sub_desc:"Subscript",
+sup_desc:"Superscript",
+hr_desc:"Insert horizontal ruler",
+removeformat_desc:"Remove formatting",
+custom1_desc:"Your custom description here",
+forecolor_desc:"Select text color",
+backcolor_desc:"Select background color",
+charmap_desc:"Insert custom character",
+visualaid_desc:"Toggle guidelines/invisible elements",
+anchor_desc:"Insert/edit anchor",
+cut_desc:"Cut",
+copy_desc:"Copy",
+paste_desc:"Paste",
+image_props_desc:"Image properties",
+newdocument_desc:"New document",
+help_desc:"Help",
+blockquote_desc:"Blockquote",
+clipboard_msg:"Copy/Cut/Paste is not available in Mozilla and Firefox.\r\nDo you want more information about this issue?",
+path:"Path",
+newdocument:"Are you sure you want clear all contents?",
+toolbar_focus:"Jump to tool buttons - Alt+Q, Jump to editor - Alt-Z, Jump to element path - Alt-X",
+more_colors:"More colors"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/themes/advanced/langs/en_dlg.js b/media/js/tinymce/themes/advanced/langs/en_dlg.js
new file mode 100644
index 0000000..9d124d7
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/langs/en_dlg.js
@@ -0,0 +1,51 @@
+tinyMCE.addI18n('en.advanced_dlg',{
+about_title:"About TinyMCE",
+about_general:"About",
+about_help:"Help",
+about_license:"License",
+about_plugins:"Plugins",
+about_plugin:"Plugin",
+about_author:"Author",
+about_version:"Version",
+about_loaded:"Loaded plugins",
+anchor_title:"Insert/edit anchor",
+anchor_name:"Anchor name",
+code_title:"HTML Source Editor",
+code_wordwrap:"Word wrap",
+colorpicker_title:"Select a color",
+colorpicker_picker_tab:"Picker",
+colorpicker_picker_title:"Color picker",
+colorpicker_palette_tab:"Palette",
+colorpicker_palette_title:"Palette colors",
+colorpicker_named_tab:"Named",
+colorpicker_named_title:"Named colors",
+colorpicker_color:"Color:",
+colorpicker_name:"Name:",
+charmap_title:"Select custom character",
+image_title:"Insert/edit image",
+image_src:"Image URL",
+image_alt:"Image description",
+image_list:"Image list",
+image_border:"Border",
+image_dimensions:"Dimensions",
+image_vspace:"Vertical space",
+image_hspace:"Horizontal space",
+image_align:"Alignment",
+image_align_baseline:"Baseline",
+image_align_top:"Top",
+image_align_middle:"Middle",
+image_align_bottom:"Bottom",
+image_align_texttop:"Text top",
+image_align_textbottom:"Text bottom",
+image_align_left:"Left",
+image_align_right:"Right",
+link_title:"Insert/edit link",
+link_url:"Link URL",
+link_target:"Target",
+link_target_same:"Open link in the same window",
+link_target_blank:"Open link in a new window",
+link_titlefield:"Title",
+link_is_email:"The URL you entered seems to be an email address, do you want to add the required mailto: prefix?",
+link_is_external:"The URL you entered seems to external link, do you want to add the required http:// prefix?",
+link_list:"Link list"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/themes/advanced/link.htm b/media/js/tinymce/themes/advanced/link.htm
new file mode 100644
index 0000000..a78bd33
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/link.htm
@@ -0,0 +1,63 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<title>{#advanced_dlg.link_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="../../utils/mctabs.js"></script>
+	<script type="text/javascript" src="../../utils/form_utils.js"></script>
+	<script type="text/javascript" src="../../utils/validate.js"></script>
+	<script type="text/javascript" src="js/link.js"></script>
+</head>
+<body id="link" style="display: none">
+<form onsubmit="LinkDialog.update();return false;" action="#">
+	<div class="tabs">
+		<ul>
+			<li id="general_tab" class="current"><span><a href="javascript:mcTabs.displayTab('general_tab','general_panel');" onmousedown="return false;">{#advanced_dlg.link_title}</a></span></li>
+		</ul>
+	</div>
+
+	<div class="panel_wrapper">
+		<div id="general_panel" class="panel current">
+
+		<table border="0" cellpadding="4" cellspacing="0">
+          <tr>
+            <td class="nowrap"><label for="href">{#advanced_dlg.link_url}</label></td>
+            <td><table border="0" cellspacing="0" cellpadding="0"> 
+				  <tr> 
+					<td><input id="href" name="href" type="text" class="mceFocus" value="" style="width: 200px" onchange="LinkDialog.checkPrefix(this);" /></td> 
+					<td id="hrefbrowsercontainer"> </td>
+				  </tr> 
+				</table></td>
+          </tr>
+		  <tr>
+			<td><label for="link_list">{#advanced_dlg.link_list}</label></td>
+			<td><select id="link_list" name="link_list" onchange="document.getElementById('href').value=this.options[this.selectedIndex].value;"></select></td>
+		  </tr>
+		<tr>
+			<td><label id="targetlistlabel" for="targetlist">{#advanced_dlg.link_target}</label></td>
+			<td><select id="target_list" name="target_list"></select></td>
+		</tr>
+          <tr>
+            <td class="nowrap"><label for="linktitle">{#advanced_dlg.link_titlefield}</label></td>
+            <td><input id="linktitle" name="linktitle" type="text" value="" style="width: 200px" /></td>
+          </tr>
+			<tr>
+				<td><label for="class_list">{#class_name}</label></td>
+				<td><select id="class_list" name="class_list"></select></td>
+			</tr>
+        </table>
+		</div>
+	</div>
+
+	<div class="mceActionPanel">
+		<div style="float: left">
+			<input type="submit" id="insert" name="insert" value="{#insert}" />
+		</div>
+
+		<div style="float: right">
+			<input type="button" id="cancel" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" />
+		</div>
+	</div>
+</form>
+</body>
+</html>
diff --git a/media/js/tinymce/themes/advanced/skins/default/content.css b/media/js/tinymce/themes/advanced/skins/default/content.css
new file mode 100644
index 0000000..19da194
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/skins/default/content.css
@@ -0,0 +1,32 @@
+body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;}
+body {background:#FFF;}
+body.mceForceColors {background:#FFF; color:#000;}
+h1 {font-size: 2em}
+h2 {font-size: 1.5em}
+h3 {font-size: 1.17em}
+h4 {font-size: 1em}
+h5 {font-size: .83em}
+h6 {font-size: .75em}
+.mceItemTable, .mceItemTable td, .mceItemTable th, .mceItemTable caption, .mceItemVisualAid {border: 1px dashed #BBB;}
+a.mceItemAnchor {width:12px; line-height:6px; overflow:hidden; padding-left:12px; background:url(img/items.gif) no-repeat bottom left;}
+img.mceItemAnchor {width:12px; height:12px; background:url(img/items.gif) no-repeat;}
+img {border:0;}
+table {cursor:default}
+table td, table th {cursor:text}
+ins {border-bottom:1px solid green; text-decoration: none; color:green}
+del {color:red; text-decoration:line-through}
+cite {border-bottom:1px dashed blue}
+acronym {border-bottom:1px dotted #CCC; cursor:help}
+abbr, html\:abbr {border-bottom:1px dashed #CCC; cursor:help}
+
+/* IE */
+* html body {
+scrollbar-3dlight-color:#F0F0EE;
+scrollbar-arrow-color:#676662;
+scrollbar-base-color:#F0F0EE;
+scrollbar-darkshadow-color:#DDD;
+scrollbar-face-color:#E0E0DD;
+scrollbar-highlight-color:#F0F0EE;
+scrollbar-shadow-color:#F0F0EE;
+scrollbar-track-color:#F5F5F5;
+}
diff --git a/media/js/tinymce/themes/advanced/skins/default/dialog.css b/media/js/tinymce/themes/advanced/skins/default/dialog.css
new file mode 100644
index 0000000..873c67e
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/skins/default/dialog.css
@@ -0,0 +1,116 @@
+/* Generic */
+body {
+font-family:Verdana, Arial, Helvetica, sans-serif; font-size:11px;
+scrollbar-3dlight-color:#F0F0EE;
+scrollbar-arrow-color:#676662;
+scrollbar-base-color:#F0F0EE;
+scrollbar-darkshadow-color:#DDDDDD;
+scrollbar-face-color:#E0E0DD;
+scrollbar-highlight-color:#F0F0EE;
+scrollbar-shadow-color:#F0F0EE;
+scrollbar-track-color:#F5F5F5;
+background:#F0F0EE;
+padding:0;
+margin:8px 8px 0 8px;
+}
+
+html {background:#F0F0EE;}
+td {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;}
+textarea {resize:none;outline:none;}
+a:link, a:visited {color:black;}
+a:hover {color:#2B6FB6;}
+.nowrap {white-space: nowrap}
+
+/* Forms */
+fieldset {margin:0; padding:4px; border:1px solid #919B9C; font-family:Verdana, Arial; font-size:10px;}
+legend {color:#2B6FB6; font-weight:bold;}
+label.msg {display:none;}
+label.invalid {color:#EE0000; display:inline;}
+input.invalid {border:1px solid #EE0000;}
+input {background:#FFF; border:1px solid #CCC;}
+input, select, textarea {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;}
+input, select, textarea {border:1px solid #808080;}
+input.radio {border:1px none #000000; background:transparent; vertical-align:middle;}
+input.checkbox {border:1px none #000000; background:transparent; vertical-align:middle;}
+.input_noborder {border:0;}
+
+/* Buttons */
+#insert, #cancel, input.button, .updateButton {
+border:0; margin:0; padding:0;
+font-weight:bold;
+width:94px; height:26px;
+background:url(img/buttons.png) 0 -26px;
+cursor:pointer;
+padding-bottom:2px;
+}
+
+#insert {background:url(img/buttons.png) 0 -52px;}
+#cancel {background:url(img/buttons.png) 0 0;}
+
+/* Browse */
+a.pickcolor, a.browse {text-decoration:none}
+a.browse span {display:block; width:20px; height:18px; background:url(../../img/icons.gif) -860px 0; border:1px solid #FFF; margin-left:1px;}
+.mceOldBoxModel a.browse span {width:22px; height:20px;}
+a.browse:hover span {border:1px solid #0A246A; background-color:#B2BBD0;}
+a.browse span.disabled {border:1px solid white; opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
+a.browse:hover span.disabled {border:1px solid white; background-color:transparent;}
+a.pickcolor span {display:block; width:20px; height:16px; background:url(../../img/icons.gif) -840px 0; margin-left:2px;}
+.mceOldBoxModel a.pickcolor span {width:21px; height:17px;}
+a.pickcolor:hover span {background-color:#B2BBD0;}
+a.pickcolor:hover span.disabled {}
+
+/* Charmap */
+table.charmap {border:1px solid #AAA; text-align:center}
+td.charmap, #charmap a {width:18px; height:18px; color:#000; border:1px solid #AAA; text-align:center; font-size:12px; vertical-align:middle; line-height: 18px;}
+#charmap a {display:block; color:#000; text-decoration:none; border:0}
+#charmap a:hover {background:#CCC;color:#2B6FB6}
+#charmap #codeN {font-size:10px; font-family:Arial,Helvetica,sans-serif; text-align:center}
+#charmap #codeV {font-size:40px; height:80px; border:1px solid #AAA; text-align:center}
+
+/* Source */
+.wordWrapCode {vertical-align:middle; border:1px none #000000; background:transparent;}
+.mceActionPanel {margin-top:5px;}
+
+/* Tabs classes */
+.tabs {width:100%; height:18px; line-height:normal; background:url(img/tabs.gif) repeat-x 0 -72px;}
+.tabs ul {margin:0; padding:0; list-style:none;}
+.tabs li {float:left; background:url(img/tabs.gif) no-repeat 0 0; margin:0 2px 0 0; padding:0 0 0 10px; line-height:17px; height:18px; display:block;}
+.tabs li.current {background:url(img/tabs.gif) no-repeat 0 -18px; margin-right:2px;}
+.tabs span {float:left; display:block; background:url(img/tabs.gif) no-repeat right -36px; padding:0px 10px 0 0;}
+.tabs .current span {background:url(img/tabs.gif) no-repeat right -54px;}
+.tabs a {text-decoration:none; font-family:Verdana, Arial; font-size:10px;}
+.tabs a:link, .tabs a:visited, .tabs a:hover {color:black;}
+
+/* Panels */
+.panel_wrapper div.panel {display:none;}
+.panel_wrapper div.current {display:block; width:100%; height:300px; overflow:visible;}
+.panel_wrapper {border:1px solid #919B9C; border-top:0px; padding:10px; padding-top:5px; clear:both; background:white;}
+
+/* Columns */
+.column {float:left;}
+.properties {width:100%;}
+.properties .column1 {}
+.properties .column2 {text-align:left;}
+
+/* Titles */
+h1, h2, h3, h4 {color:#2B6FB6; margin:0; padding:0; padding-top:5px;}
+h3 {font-size:14px;}
+.title {font-size:12px; font-weight:bold; color:#2B6FB6;}
+
+/* Dialog specific */
+#link .panel_wrapper, #link div.current {height:125px;}
+#image .panel_wrapper, #image div.current {height:200px;}
+#plugintable thead {font-weight:bold; background:#DDD;}
+#plugintable, #about #plugintable td {border:1px solid #919B9C;}
+#plugintable {width:96%; margin-top:10px;}
+#pluginscontainer {height:290px; overflow:auto;}
+#colorpicker #preview {float:right; width:50px; height:14px;line-height:1px; border:1px solid black; margin-left:5px;}
+#colorpicker #colors {float:left; border:1px solid gray; cursor:crosshair;}
+#colorpicker #light {border:1px solid gray; margin-left:5px; float:left;width:15px; height:150px; cursor:crosshair;}
+#colorpicker #light div {overflow:hidden;}
+#colorpicker #previewblock {float:right; padding-left:10px; height:20px;}
+#colorpicker .panel_wrapper div.current {height:175px;}
+#colorpicker #namedcolors {width:150px;}
+#colorpicker #namedcolors a {display:block; float:left; width:10px; height:10px; margin:1px 1px 0 0; overflow:hidden;}
+#colorpicker #colornamecontainer {margin-top:5px;}
+#colorpicker #picker_panel fieldset {margin:auto;width:325px;}
\ No newline at end of file
diff --git a/media/js/tinymce/themes/advanced/skins/default/img/buttons.png b/media/js/tinymce/themes/advanced/skins/default/img/buttons.png
new file mode 100644
index 0000000..7dd5841
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/default/img/buttons.png differ
diff --git a/media/js/tinymce/themes/advanced/skins/default/img/items.gif b/media/js/tinymce/themes/advanced/skins/default/img/items.gif
new file mode 100644
index 0000000..2eafd79
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/default/img/items.gif differ
diff --git a/media/js/tinymce/themes/advanced/skins/default/img/menu_arrow.gif b/media/js/tinymce/themes/advanced/skins/default/img/menu_arrow.gif
new file mode 100644
index 0000000..85e31df
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/default/img/menu_arrow.gif differ
diff --git a/media/js/tinymce/themes/advanced/skins/default/img/menu_check.gif b/media/js/tinymce/themes/advanced/skins/default/img/menu_check.gif
new file mode 100644
index 0000000..adfdddc
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/default/img/menu_check.gif differ
diff --git a/media/js/tinymce/themes/advanced/skins/default/img/progress.gif b/media/js/tinymce/themes/advanced/skins/default/img/progress.gif
new file mode 100644
index 0000000..5bb90fd
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/default/img/progress.gif differ
diff --git a/media/js/tinymce/themes/advanced/skins/default/img/tabs.gif b/media/js/tinymce/themes/advanced/skins/default/img/tabs.gif
new file mode 100644
index 0000000..ce4be63
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/default/img/tabs.gif differ
diff --git a/media/js/tinymce/themes/advanced/skins/default/ui.css b/media/js/tinymce/themes/advanced/skins/default/ui.css
new file mode 100644
index 0000000..230a2ee
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/skins/default/ui.css
@@ -0,0 +1,214 @@
+/* Reset */
+.defaultSkin table, .defaultSkin tbody, .defaultSkin a, .defaultSkin img, .defaultSkin tr, .defaultSkin div, .defaultSkin td, .defaultSkin iframe, .defaultSkin span, .defaultSkin *, .defaultSkin .mceText {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000; vertical-align:baseline; width:auto; border-collapse:separate; text-align:left}
+.defaultSkin a:hover, .defaultSkin a:link, .defaultSkin a:visited, .defaultSkin a:active {text-decoration:none; font-weight:normal; cursor:default; color:#000}
+.defaultSkin table td {vertical-align:middle}
+
+/* Containers */
+.defaultSkin table {background:#F0F0EE}
+.defaultSkin iframe {display:block; background:#FFF}
+.defaultSkin .mceToolbar {height:26px}
+.defaultSkin .mceLeft {text-align:left}
+.defaultSkin .mceRight {text-align:right}
+
+/* External */
+.defaultSkin .mceExternalToolbar {position:absolute; border:1px solid #CCC; border-bottom:0; display:none;}
+.defaultSkin .mceExternalToolbar td.mceToolbar {padding-right:13px;}
+.defaultSkin .mceExternalClose {position:absolute; top:3px; right:3px; width:7px; height:7px; background:url(../../img/icons.gif) -820px 0}
+
+/* Layout */
+.defaultSkin table.mceLayout {border:0; border-left:1px solid #CCC; border-right:1px solid #CCC}
+.defaultSkin table.mceLayout tr.mceFirst td {border-top:1px solid #CCC}
+.defaultSkin table.mceLayout tr.mceLast td {border-bottom:1px solid #CCC}
+.defaultSkin table.mceToolbar, .defaultSkin tr.mceFirst .mceToolbar tr td, .defaultSkin tr.mceLast .mceToolbar tr td {border:0; margin:0; padding:0;}
+.defaultSkin td.mceToolbar {padding-top:1px; vertical-align:top}
+.defaultSkin .mceIframeContainer {border-top:1px solid #CCC; border-bottom:1px solid #CCC}
+.defaultSkin .mceStatusbar {font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:9pt; line-height:16px; overflow:visible; color:#000; display:block; height:20px}
+.defaultSkin .mceStatusbar div {float:left; margin:2px}
+.defaultSkin .mceStatusbar a.mceResize {display:block; float:right; background:url(../../img/icons.gif) -800px 0; width:20px; height:20px; cursor:se-resize}
+.defaultSkin .mceStatusbar a:hover {text-decoration:underline}
+.defaultSkin table.mceToolbar {margin-left:3px}
+.defaultSkin span.mceIcon, .defaultSkin img.mceIcon {display:block; width:20px; height:20px}
+.defaultSkin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px}
+.defaultSkin td.mceCenter {text-align:center;}
+.defaultSkin td.mceCenter table {margin:0 auto; text-align:left;}
+.defaultSkin td.mceRight table {margin:0 0 0 auto;}
+
+/* Button */
+.defaultSkin .mceButton {display:block; border:1px solid #F0F0EE; width:20px; height:20px; margin-right:1px}
+.defaultSkin a.mceButtonEnabled:hover {border:1px solid #0A246A; background-color:#B2BBD0}
+.defaultSkin a.mceButtonActive, .defaultSkin a.mceButtonSelected {border:1px solid #0A246A; background-color:#C2CBE0}
+.defaultSkin .mceButtonDisabled .mceIcon {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
+.defaultSkin .mceButtonLabeled {width:auto}
+.defaultSkin .mceButtonLabeled span.mceIcon {float:left}
+.defaultSkin span.mceButtonLabel {display:block; font-size:10px; padding:4px 6px 0 22px; font-family:Tahoma,Verdana,Arial,Helvetica}
+.defaultSkin .mceButtonDisabled .mceButtonLabel {color:#888}
+
+/* Separator */
+.defaultSkin .mceSeparator {display:block; background:url(../../img/icons.gif) -180px 0; width:2px; height:20px; margin:2px 2px 0 4px}
+
+/* ListBox */
+.defaultSkin .mceListBox {direction:ltr}
+.defaultSkin .mceListBox, .defaultSkin .mceListBox a {display:block}
+.defaultSkin .mceListBox .mceText {padding-left:4px; width:70px; text-align:left; border:1px solid #CCC; border-right:0; background:#FFF; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden}
+.defaultSkin .mceListBox .mceOpen {width:9px; height:20px; background:url(../../img/icons.gif) -741px 0; margin-right:2px; border:1px solid #CCC;}
+.defaultSkin table.mceListBoxEnabled:hover .mceText, .defaultSkin .mceListBoxHover .mceText, .defaultSkin .mceListBoxSelected .mceText {border:1px solid #A2ABC0; border-right:0; background:#FFF}
+.defaultSkin table.mceListBoxEnabled:hover .mceOpen, .defaultSkin .mceListBoxHover .mceOpen, .defaultSkin .mceListBoxSelected .mceOpen {background-color:#FFF; border:1px solid #A2ABC0}
+.defaultSkin .mceListBoxDisabled a.mceText {color:gray; background-color:transparent;}
+.defaultSkin .mceListBoxMenu {overflow:auto; overflow-x:hidden}
+.defaultSkin .mceOldBoxModel .mceListBox .mceText {height:22px}
+.defaultSkin .mceOldBoxModel .mceListBox .mceOpen {width:11px; height:22px;}
+.defaultSkin select.mceNativeListBox {font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:7pt; background:#F0F0EE; border:1px solid gray; margin-right:2px;}
+
+/* SplitButton */
+.defaultSkin .mceSplitButton {width:32px; height:20px; direction:ltr}
+.defaultSkin .mceSplitButton a, .defaultSkin .mceSplitButton span {height:20px; display:block}
+.defaultSkin .mceSplitButton a.mceAction {width:20px; border:1px solid #F0F0EE; border-right:0;}
+.defaultSkin .mceSplitButton span.mceAction {width:20px; background:url(../../img/icons.gif) 20px 20px;}
+.defaultSkin .mceSplitButton a.mceOpen {width:9px; background:url(../../img/icons.gif) -741px 0; border:1px solid #F0F0EE;}
+.defaultSkin .mceSplitButton span.mceOpen {display:none}
+.defaultSkin table.mceSplitButtonEnabled:hover a.mceAction, .defaultSkin .mceSplitButtonHover a.mceAction, .defaultSkin .mceSplitButtonSelected a.mceAction {border:1px solid #0A246A; border-right:0; background-color:#B2BBD0}
+.defaultSkin table.mceSplitButtonEnabled:hover a.mceOpen, .defaultSkin .mceSplitButtonHover a.mceOpen, .defaultSkin .mceSplitButtonSelected a.mceOpen {background-color:#B2BBD0; border:1px solid #0A246A;}
+.defaultSkin .mceSplitButtonDisabled .mceAction, .defaultSkin .mceSplitButtonDisabled a.mceOpen {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
+.defaultSkin .mceSplitButtonActive a.mceAction {border:1px solid #0A246A; background-color:#C2CBE0}
+.defaultSkin .mceSplitButtonActive a.mceOpen {border-left:0;}
+
+/* ColorSplitButton */
+.defaultSkin div.mceColorSplitMenu table {background:#FFF; border:1px solid gray}
+.defaultSkin .mceColorSplitMenu td {padding:2px}
+.defaultSkin .mceColorSplitMenu a {display:block; width:9px; height:9px; overflow:hidden; border:1px solid #808080}
+.defaultSkin .mceColorSplitMenu td.mceMoreColors {padding:1px 3px 1px 1px}
+.defaultSkin .mceColorSplitMenu a.mceMoreColors {width:100%; height:auto; text-align:center; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; line-height:20px; border:1px solid #FFF}
+.defaultSkin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid #0A246A; background-color:#B6BDD2}
+.defaultSkin a.mceMoreColors:hover {border:1px solid #0A246A}
+.defaultSkin .mceColorPreview {margin-left:2px; width:16px; height:4px; overflow:hidden; background:#9a9b9a}
+.defaultSkin .mce_forecolor span.mceAction, .defaultSkin .mce_backcolor span.mceAction {overflow:hidden; height:16px}
+
+/* Menu */
+.defaultSkin .mceMenu {position:absolute; left:0; top:0; z-index:1000; border:1px solid #D4D0C8}
+.defaultSkin .mceNoIcons span.mceIcon {width:0;}
+.defaultSkin .mceNoIcons a .mceText {padding-left:10px}
+.defaultSkin .mceMenu table {background:#FFF}
+.defaultSkin .mceMenu a, .defaultSkin .mceMenu span, .defaultSkin .mceMenu {display:block}
+.defaultSkin .mceMenu td {height:20px}
+.defaultSkin .mceMenu a {position:relative;padding:3px 0 4px 0}
+.defaultSkin .mceMenu .mceText {position:relative; display:block; font-family:Tahoma,Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block}
+.defaultSkin .mceMenu span.mceText, .defaultSkin .mceMenu .mcePreview {font-size:11px}
+.defaultSkin .mceMenu pre.mceText {font-family:Monospace}
+.defaultSkin .mceMenu .mceIcon {position:absolute; top:0; left:0; width:22px;}
+.defaultSkin .mceMenu .mceMenuItemEnabled a:hover, .defaultSkin .mceMenu .mceMenuItemActive {background-color:#dbecf3}
+.defaultSkin td.mceMenuItemSeparator {background:#DDD; height:1px}
+.defaultSkin .mceMenuItemTitle a {border:0; background:#EEE; border-bottom:1px solid #DDD}
+.defaultSkin .mceMenuItemTitle span.mceText {color:#000; font-weight:bold; padding-left:4px}
+.defaultSkin .mceMenuItemDisabled .mceText {color:#888}
+.defaultSkin .mceMenuItemSelected .mceIcon {background:url(img/menu_check.gif)}
+.defaultSkin .mceNoIcons .mceMenuItemSelected a {background:url(img/menu_arrow.gif) no-repeat -6px center}
+.defaultSkin .mceMenu span.mceMenuLine {display:none}
+.defaultSkin .mceMenuItemSub a {background:url(img/menu_arrow.gif) no-repeat top right;}
+
+/* Progress,Resize */
+.defaultSkin .mceBlocker {position:absolute; left:0; top:0; z-index:1000; opacity:0.5; -ms-filter:'alpha(opacity=50)'; filter:alpha(opacity=50); background:#FFF}
+.defaultSkin .mceProgress {position:absolute; left:0; top:0; z-index:1001; background:url(img/progress.gif) no-repeat; width:32px; height:32px; margin:-16px 0 0 -16px}
+.defaultSkin .mcePlaceHolder {border:1px dotted gray}
+
+/* Formats */
+.defaultSkin .mce_formatPreview a {font-size:10px}
+.defaultSkin .mce_p span.mceText {}
+.defaultSkin .mce_address span.mceText {font-style:italic}
+.defaultSkin .mce_pre span.mceText {font-family:monospace}
+.defaultSkin .mce_h1 span.mceText {font-weight:bolder; font-size: 2em}
+.defaultSkin .mce_h2 span.mceText {font-weight:bolder; font-size: 1.5em}
+.defaultSkin .mce_h3 span.mceText {font-weight:bolder; font-size: 1.17em}
+.defaultSkin .mce_h4 span.mceText {font-weight:bolder; font-size: 1em}
+.defaultSkin .mce_h5 span.mceText {font-weight:bolder; font-size: .83em}
+.defaultSkin .mce_h6 span.mceText {font-weight:bolder; font-size: .75em}
+
+/* Theme */
+.defaultSkin span.mce_bold {background-position:0 0}
+.defaultSkin span.mce_italic {background-position:-60px 0}
+.defaultSkin span.mce_underline {background-position:-140px 0}
+.defaultSkin span.mce_strikethrough {background-position:-120px 0}
+.defaultSkin span.mce_undo {background-position:-160px 0}
+.defaultSkin span.mce_redo {background-position:-100px 0}
+.defaultSkin span.mce_cleanup {background-position:-40px 0}
+.defaultSkin span.mce_bullist {background-position:-20px 0}
+.defaultSkin span.mce_numlist {background-position:-80px 0}
+.defaultSkin span.mce_justifyleft {background-position:-460px 0}
+.defaultSkin span.mce_justifyright {background-position:-480px 0}
+.defaultSkin span.mce_justifycenter {background-position:-420px 0}
+.defaultSkin span.mce_justifyfull {background-position:-440px 0}
+.defaultSkin span.mce_anchor {background-position:-200px 0}
+.defaultSkin span.mce_indent {background-position:-400px 0}
+.defaultSkin span.mce_outdent {background-position:-540px 0}
+.defaultSkin span.mce_link {background-position:-500px 0}
+.defaultSkin span.mce_unlink {background-position:-640px 0}
+.defaultSkin span.mce_sub {background-position:-600px 0}
+.defaultSkin span.mce_sup {background-position:-620px 0}
+.defaultSkin span.mce_removeformat {background-position:-580px 0}
+.defaultSkin span.mce_newdocument {background-position:-520px 0}
+.defaultSkin span.mce_image {background-position:-380px 0}
+.defaultSkin span.mce_help {background-position:-340px 0}
+.defaultSkin span.mce_code {background-position:-260px 0}
+.defaultSkin span.mce_hr {background-position:-360px 0}
+.defaultSkin span.mce_visualaid {background-position:-660px 0}
+.defaultSkin span.mce_charmap {background-position:-240px 0}
+.defaultSkin span.mce_paste {background-position:-560px 0}
+.defaultSkin span.mce_copy {background-position:-700px 0}
+.defaultSkin span.mce_cut {background-position:-680px 0}
+.defaultSkin span.mce_blockquote {background-position:-220px 0}
+.defaultSkin .mce_forecolor span.mceAction {background-position:-720px 0}
+.defaultSkin .mce_backcolor span.mceAction {background-position:-760px 0}
+.defaultSkin span.mce_forecolorpicker {background-position:-720px 0}
+.defaultSkin span.mce_backcolorpicker {background-position:-760px 0}
+
+/* Plugins */
+.defaultSkin span.mce_advhr {background-position:-0px -20px}
+.defaultSkin span.mce_ltr {background-position:-20px -20px}
+.defaultSkin span.mce_rtl {background-position:-40px -20px}
+.defaultSkin span.mce_emotions {background-position:-60px -20px}
+.defaultSkin span.mce_fullpage {background-position:-80px -20px}
+.defaultSkin span.mce_fullscreen {background-position:-100px -20px}
+.defaultSkin span.mce_iespell {background-position:-120px -20px}
+.defaultSkin span.mce_insertdate {background-position:-140px -20px}
+.defaultSkin span.mce_inserttime {background-position:-160px -20px}
+.defaultSkin span.mce_absolute {background-position:-180px -20px}
+.defaultSkin span.mce_backward {background-position:-200px -20px}
+.defaultSkin span.mce_forward {background-position:-220px -20px}
+.defaultSkin span.mce_insert_layer {background-position:-240px -20px}
+.defaultSkin span.mce_insertlayer {background-position:-260px -20px}
+.defaultSkin span.mce_movebackward {background-position:-280px -20px}
+.defaultSkin span.mce_moveforward {background-position:-300px -20px}
+.defaultSkin span.mce_media {background-position:-320px -20px}
+.defaultSkin span.mce_nonbreaking {background-position:-340px -20px}
+.defaultSkin span.mce_pastetext {background-position:-360px -20px}
+.defaultSkin span.mce_pasteword {background-position:-380px -20px}
+.defaultSkin span.mce_selectall {background-position:-400px -20px}
+.defaultSkin span.mce_preview {background-position:-420px -20px}
+.defaultSkin span.mce_print {background-position:-440px -20px}
+.defaultSkin span.mce_cancel {background-position:-460px -20px}
+.defaultSkin span.mce_save {background-position:-480px -20px}
+.defaultSkin span.mce_replace {background-position:-500px -20px}
+.defaultSkin span.mce_search {background-position:-520px -20px}
+.defaultSkin span.mce_styleprops {background-position:-560px -20px}
+.defaultSkin span.mce_table {background-position:-580px -20px}
+.defaultSkin span.mce_cell_props {background-position:-600px -20px}
+.defaultSkin span.mce_delete_table {background-position:-620px -20px}
+.defaultSkin span.mce_delete_col {background-position:-640px -20px}
+.defaultSkin span.mce_delete_row {background-position:-660px -20px}
+.defaultSkin span.mce_col_after {background-position:-680px -20px}
+.defaultSkin span.mce_col_before {background-position:-700px -20px}
+.defaultSkin span.mce_row_after {background-position:-720px -20px}
+.defaultSkin span.mce_row_before {background-position:-740px -20px}
+.defaultSkin span.mce_merge_cells {background-position:-760px -20px}
+.defaultSkin span.mce_table_props {background-position:-980px -20px}
+.defaultSkin span.mce_row_props {background-position:-780px -20px}
+.defaultSkin span.mce_split_cells {background-position:-800px -20px}
+.defaultSkin span.mce_template {background-position:-820px -20px}
+.defaultSkin span.mce_visualchars {background-position:-840px -20px}
+.defaultSkin span.mce_abbr {background-position:-860px -20px}
+.defaultSkin span.mce_acronym {background-position:-880px -20px}
+.defaultSkin span.mce_attribs {background-position:-900px -20px}
+.defaultSkin span.mce_cite {background-position:-920px -20px}
+.defaultSkin span.mce_del {background-position:-940px -20px}
+.defaultSkin span.mce_ins {background-position:-960px -20px}
+.defaultSkin span.mce_pagebreak {background-position:0 -40px}
+.defaultSkin .mce_spellchecker span.mceAction {background-position:-540px -20px}
diff --git a/media/js/tinymce/themes/advanced/skins/o2k7/content.css b/media/js/tinymce/themes/advanced/skins/o2k7/content.css
new file mode 100644
index 0000000..b8431d1
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/skins/o2k7/content.css
@@ -0,0 +1,32 @@
+body, td, pre {color:#000; font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px; margin:8px;}
+body {background:#FFF;}
+body.mceForceColors {background:#FFF; color:#000;}
+h1 {font-size: 2em}
+h2 {font-size: 1.5em}
+h3 {font-size: 1.17em}
+h4 {font-size: 1em}
+h5 {font-size: .83em}
+h6 {font-size: .75em}
+.mceItemTable, .mceItemTable td, .mceItemTable th, .mceItemTable caption, .mceItemVisualAid {border: 1px dashed #BBB;}
+a.mceItemAnchor {width:12px; line-height:6px; overflow:hidden; padding-left:12px; background:url(../default/img/items.gif) no-repeat bottom left;}
+img.mceItemAnchor {width:12px; height:12px; background:url(../default/img/items.gif) no-repeat;}
+img {border:0;}
+table {cursor:default}
+table td, table th {cursor:text}
+ins {border-bottom:1px solid green; text-decoration: none; color:green}
+del {color:red; text-decoration:line-through}
+cite {border-bottom:1px dashed blue}
+acronym {border-bottom:1px dotted #CCC; cursor:help}
+abbr, html\:abbr {border-bottom:1px dashed #CCC; cursor:help}
+
+/* IE */
+* html body {
+scrollbar-3dlight-color:#F0F0EE;
+scrollbar-arrow-color:#676662;
+scrollbar-base-color:#F0F0EE;
+scrollbar-darkshadow-color:#DDD;
+scrollbar-face-color:#E0E0DD;
+scrollbar-highlight-color:#F0F0EE;
+scrollbar-shadow-color:#F0F0EE;
+scrollbar-track-color:#F5F5F5;
+}
diff --git a/media/js/tinymce/themes/advanced/skins/o2k7/dialog.css b/media/js/tinymce/themes/advanced/skins/o2k7/dialog.css
new file mode 100644
index 0000000..6c37d6f
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/skins/o2k7/dialog.css
@@ -0,0 +1,115 @@
+/* Generic */
+body {
+font-family:Verdana, Arial, Helvetica, sans-serif; font-size:11px;
+scrollbar-3dlight-color:#F0F0EE;
+scrollbar-arrow-color:#676662;
+scrollbar-base-color:#F0F0EE;
+scrollbar-darkshadow-color:#DDDDDD;
+scrollbar-face-color:#E0E0DD;
+scrollbar-highlight-color:#F0F0EE;
+scrollbar-shadow-color:#F0F0EE;
+scrollbar-track-color:#F5F5F5;
+background:#F0F0EE;
+padding:0;
+margin:8px 8px 0 8px;
+}
+
+html {background:#F0F0EE;}
+td {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;}
+textarea {resize:none;outline:none;}
+a:link, a:visited {color:black;}
+a:hover {color:#2B6FB6;}
+.nowrap {white-space: nowrap}
+
+/* Forms */
+fieldset {margin:0; padding:4px; border:1px solid #919B9C; font-family:Verdana, Arial; font-size:10px;}
+legend {color:#2B6FB6; font-weight:bold;}
+label.msg {display:none;}
+label.invalid {color:#EE0000; display:inline;}
+input.invalid {border:1px solid #EE0000;}
+input {background:#FFF; border:1px solid #CCC;}
+input, select, textarea {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;}
+input, select, textarea {border:1px solid #808080;}
+input.radio {border:1px none #000000; background:transparent; vertical-align:middle;}
+input.checkbox {border:1px none #000000; background:transparent; vertical-align:middle;}
+.input_noborder {border:0;}
+
+/* Buttons */
+#insert, #cancel, input.button, .updateButton {
+border:0; margin:0; padding:0;
+font-weight:bold;
+width:94px; height:26px;
+background:url(../default/img/buttons.png) 0 -26px;
+cursor:pointer;
+padding-bottom:2px;
+}
+
+#insert {background:url(../default/img/buttons.png) 0 -52px;}
+#cancel {background:url(../default/img/buttons.png) 0 0;}
+
+/* Browse */
+a.pickcolor, a.browse {text-decoration:none}
+a.browse span {display:block; width:20px; height:18px; background:url(../../img/icons.gif) -860px 0; border:1px solid #FFF; margin-left:1px;}
+.mceOldBoxModel a.browse span {width:22px; height:20px;}
+a.browse:hover span {border:1px solid #0A246A; background-color:#B2BBD0;}
+a.browse span.disabled {border:1px solid white; opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
+a.browse:hover span.disabled {border:1px solid white; background-color:transparent;}
+a.pickcolor span {display:block; width:20px; height:16px; background:url(../../img/icons.gif) -840px 0; margin-left:2px;}
+.mceOldBoxModel a.pickcolor span {width:21px; height:17px;}
+a.pickcolor:hover span {background-color:#B2BBD0;}
+a.pickcolor:hover span.disabled {}
+
+/* Charmap */
+table.charmap {border:1px solid #AAA; text-align:center}
+td.charmap, #charmap a {width:18px; height:18px; color:#000; border:1px solid #AAA; text-align:center; font-size:12px; vertical-align:middle; line-height: 18px;}
+#charmap a {display:block; color:#000; text-decoration:none; border:0}
+#charmap a:hover {background:#CCC;color:#2B6FB6}
+#charmap #codeN {font-size:10px; font-family:Arial,Helvetica,sans-serif; text-align:center}
+#charmap #codeV {font-size:40px; height:80px; border:1px solid #AAA; text-align:center}
+
+/* Source */
+.wordWrapCode {vertical-align:middle; border:1px none #000000; background:transparent;}
+.mceActionPanel {margin-top:5px;}
+
+/* Tabs classes */
+.tabs {width:100%; height:18px; line-height:normal; background:url(../default/img/tabs.gif) repeat-x 0 -72px;}
+.tabs ul {margin:0; padding:0; list-style:none;}
+.tabs li {float:left; background:url(../default/img/tabs.gif) no-repeat 0 0; margin:0 2px 0 0; padding:0 0 0 10px; line-height:17px; height:18px; display:block;}
+.tabs li.current {background:url(../default/img/tabs.gif) no-repeat 0 -18px; margin-right:2px;}
+.tabs span {float:left; display:block; background:url(../default/img/tabs.gif) no-repeat right -36px; padding:0px 10px 0 0;}
+.tabs .current span {background:url(../default/img/tabs.gif) no-repeat right -54px;}
+.tabs a {text-decoration:none; font-family:Verdana, Arial; font-size:10px;}
+.tabs a:link, .tabs a:visited, .tabs a:hover {color:black;}
+
+/* Panels */
+.panel_wrapper div.panel {display:none;}
+.panel_wrapper div.current {display:block; width:100%; height:300px; overflow:visible;}
+.panel_wrapper {border:1px solid #919B9C; border-top:0px; padding:10px; padding-top:5px; clear:both; background:white;}
+
+/* Columns */
+.column {float:left;}
+.properties {width:100%;}
+.properties .column1 {}
+.properties .column2 {text-align:left;}
+
+/* Titles */
+h1, h2, h3, h4 {color:#2B6FB6; margin:0; padding:0; padding-top:5px;}
+h3 {font-size:14px;}
+.title {font-size:12px; font-weight:bold; color:#2B6FB6;}
+
+/* Dialog specific */
+#link .panel_wrapper, #link div.current {height:125px;}
+#image .panel_wrapper, #image div.current {height:200px;}
+#plugintable thead {font-weight:bold; background:#DDD;}
+#plugintable, #about #plugintable td {border:1px solid #919B9C;}
+#plugintable {width:96%; margin-top:10px;}
+#pluginscontainer {height:290px; overflow:auto;}
+#colorpicker #preview {float:right; width:50px; height:14px;line-height:1px; border:1px solid black; margin-left:5px;}
+#colorpicker #colors {float:left; border:1px solid gray; cursor:crosshair;}
+#colorpicker #light {border:1px solid gray; margin-left:5px; float:left;width:15px; height:150px; cursor:crosshair;}
+#colorpicker #light div {overflow:hidden;}
+#colorpicker #previewblock {float:right; padding-left:10px; height:20px;}
+#colorpicker .panel_wrapper div.current {height:175px;}
+#colorpicker #namedcolors {width:150px;}
+#colorpicker #namedcolors a {display:block; float:left; width:10px; height:10px; margin:1px 1px 0 0; overflow:hidden;}
+#colorpicker #colornamecontainer {margin-top:5px;}
diff --git a/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg.png b/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg.png
new file mode 100644
index 0000000..12cfb41
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg.png differ
diff --git a/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg_black.png b/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg_black.png
new file mode 100644
index 0000000..8996c74
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg_black.png differ
diff --git a/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg_silver.png b/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg_silver.png
new file mode 100644
index 0000000..bd5d255
Binary files /dev/null and b/media/js/tinymce/themes/advanced/skins/o2k7/img/button_bg_silver.png differ
diff --git a/media/js/tinymce/themes/advanced/skins/o2k7/ui.css b/media/js/tinymce/themes/advanced/skins/o2k7/ui.css
new file mode 100644
index 0000000..c10a3f0
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/skins/o2k7/ui.css
@@ -0,0 +1,215 @@
+/* Reset */
+.o2k7Skin table, .o2k7Skin tbody, .o2k7Skin a, .o2k7Skin img, .o2k7Skin tr, .o2k7Skin div, .o2k7Skin td, .o2k7Skin iframe, .o2k7Skin span, .o2k7Skin *, .o2k7Skin .mceText {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000; vertical-align:baseline; width:auto; border-collapse:separate; text-align:left}
+.o2k7Skin a:hover, .o2k7Skin a:link, .o2k7Skin a:visited, .o2k7Skin a:active {text-decoration:none; font-weight:normal; cursor:default; color:#000}
+.o2k7Skin table td {vertical-align:middle}
+
+/* Containers */
+.o2k7Skin table {background:#E5EFFD}
+.o2k7Skin iframe {display:block; background:#FFF}
+.o2k7Skin .mceToolbar {height:26px}
+
+/* External */
+.o2k7Skin .mceExternalToolbar {position:absolute; border:1px solid #ABC6DD; border-bottom:0; display:none}
+.o2k7Skin .mceExternalToolbar td.mceToolbar {padding-right:13px;}
+.o2k7Skin .mceExternalClose {position:absolute; top:3px; right:3px; width:7px; height:7px; background:url(../../img/icons.gif) -820px 0}
+
+/* Layout */
+.o2k7Skin table.mceLayout {border:0; border-left:1px solid #ABC6DD; border-right:1px solid #ABC6DD}
+.o2k7Skin table.mceLayout tr.mceFirst td {border-top:1px solid #ABC6DD}
+.o2k7Skin table.mceLayout tr.mceLast td {border-bottom:1px solid #ABC6DD}
+.o2k7Skin table.mceToolbar, .o2k7Skin tr.mceFirst .mceToolbar tr td, .o2k7Skin tr.mceLast .mceToolbar tr td {border:0; margin:0; padding:0}
+.o2k7Skin .mceIframeContainer {border-top:1px solid #ABC6DD; border-bottom:1px solid #ABC6DD}
+.o2k7Skin .mceStatusbar {display:block; font-family:'MS Sans Serif',sans-serif,Verdana,Arial; font-size:9pt; line-height:16px; overflow:visible; color:#000; height:20px}
+.o2k7Skin .mceStatusbar div {float:left; padding:2px}
+.o2k7Skin .mceStatusbar a.mceResize {display:block; float:right; background:url(../../img/icons.gif) -800px 0; width:20px; height:20px; cursor:se-resize}
+.o2k7Skin .mceStatusbar a:hover {text-decoration:underline}
+.o2k7Skin table.mceToolbar {margin-left:3px}
+.o2k7Skin .mceToolbar .mceToolbarStart span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px; margin-left:3px;}
+.o2k7Skin .mceToolbar td.mceFirst span {margin:0}
+.o2k7Skin .mceToolbar .mceToolbarEnd span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px}
+.o2k7Skin .mceToolbar .mceToolbarEndListBox span, .o2k7Skin .mceToolbar .mceToolbarStartListBox span {display:none}
+.o2k7Skin span.mceIcon, .o2k7Skin img.mceIcon {display:block; width:20px; height:20px}
+.o2k7Skin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px}
+.o2k7Skin td.mceCenter {text-align:center;}
+.o2k7Skin td.mceCenter table {margin:0 auto; text-align:left;}
+.o2k7Skin td.mceRight table {margin:0 0 0 auto;}
+
+/* Button */
+.o2k7Skin .mceButton {display:block; background:url(img/button_bg.png); width:22px; height:22px}
+.o2k7Skin a.mceButton span, .o2k7Skin a.mceButton img {margin-left:1px}
+.o2k7Skin .mceOldBoxModel a.mceButton span, .o2k7Skin .mceOldBoxModel a.mceButton img {margin:0 0 0 1px}
+.o2k7Skin a.mceButtonEnabled:hover {background-color:#B2BBD0; background-position:0 -22px}
+.o2k7Skin a.mceButtonActive, .o2k7Skin a.mceButtonSelected {background-position:0 -44px}
+.o2k7Skin .mceButtonDisabled .mceIcon {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
+.o2k7Skin .mceButtonLabeled {width:auto}
+.o2k7Skin .mceButtonLabeled span.mceIcon {float:left}
+.o2k7Skin span.mceButtonLabel {display:block; font-size:10px; padding:4px 6px 0 22px; font-family:Tahoma,Verdana,Arial,Helvetica}
+.o2k7Skin .mceButtonDisabled .mceButtonLabel {color:#888}
+
+/* Separator */
+.o2k7Skin .mceSeparator {display:block; background:url(img/button_bg.png) -22px 0; width:5px; height:22px}
+
+/* ListBox */
+.o2k7Skin .mceListBox {margin-left:3px}
+.o2k7Skin .mceListBox, .o2k7Skin .mceListBox a {display:block}
+.o2k7Skin .mceListBox .mceText {padding-left:4px; text-align:left; width:70px; border:1px solid #b3c7e1; border-right:0; background:#eaf2fb; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; height:20px; line-height:20px; overflow:hidden}
+.o2k7Skin .mceListBox .mceOpen {width:14px; height:22px; background:url(img/button_bg.png) -66px 0}
+.o2k7Skin table.mceListBoxEnabled:hover .mceText, .o2k7Skin .mceListBoxHover .mceText, .o2k7Skin .mceListBoxSelected .mceText {background:#FFF}
+.o2k7Skin table.mceListBoxEnabled:hover .mceOpen, .o2k7Skin .mceListBoxHover .mceOpen, .o2k7Skin .mceListBoxSelected .mceOpen {background-position:-66px -22px}
+.o2k7Skin .mceListBoxDisabled .mceText {color:gray}
+.o2k7Skin .mceListBoxMenu {overflow:auto; overflow-x:hidden}
+.o2k7Skin .mceOldBoxModel .mceListBox .mceText {height:22px}
+.o2k7Skin select.mceListBox {font-family:Tahoma,Verdana,Arial,Helvetica; font-size:12px; border:1px solid #b3c7e1; background:#FFF;}
+
+/* SplitButton */
+.o2k7Skin .mceSplitButton, .o2k7Skin .mceSplitButton a, .o2k7Skin .mceSplitButton span {display:block; height:22px}
+.o2k7Skin .mceSplitButton {background:url(img/button_bg.png)}
+.o2k7Skin .mceSplitButton a.mceAction {width:22px}
+.o2k7Skin .mceSplitButton span.mceAction {width:22px; background:url(../../img/icons.gif) 20px 20px}
+.o2k7Skin .mceSplitButton a.mceOpen {width:10px; background:url(img/button_bg.png) -44px 0}
+.o2k7Skin .mceSplitButton span.mceOpen {display:none}
+.o2k7Skin table.mceSplitButtonEnabled:hover a.mceAction, .o2k7Skin .mceSplitButtonHover a.mceAction, .o2k7Skin .mceSplitButtonSelected {background:url(img/button_bg.png) 0 -22px}
+.o2k7Skin table.mceSplitButtonEnabled:hover a.mceOpen, .o2k7Skin .mceSplitButtonHover a.mceOpen, .o2k7Skin .mceSplitButtonSelected a.mceOpen {background-position:-44px -44px}
+.o2k7Skin .mceSplitButtonDisabled .mceAction {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
+.o2k7Skin .mceSplitButtonActive {background-position:0 -44px}
+
+/* ColorSplitButton */
+.o2k7Skin div.mceColorSplitMenu table {background:#FFF; border:1px solid gray}
+.o2k7Skin .mceColorSplitMenu td {padding:2px}
+.o2k7Skin .mceColorSplitMenu a {display:block; width:9px; height:9px; overflow:hidden; border:1px solid #808080}
+.o2k7Skin .mceColorSplitMenu td.mceMoreColors {padding:1px 3px 1px 1px}
+.o2k7Skin .mceColorSplitMenu a.mceMoreColors {width:100%; height:auto; text-align:center; font-family:Tahoma,Verdana,Arial,Helvetica; font-size:11px; line-height:20px; border:1px solid #FFF}
+.o2k7Skin .mceColorSplitMenu a.mceMoreColors:hover {border:1px solid #0A246A; background-color:#B6BDD2}
+.o2k7Skin a.mceMoreColors:hover {border:1px solid #0A246A}
+.o2k7Skin .mceColorPreview {margin-left:2px; width:16px; height:4px; overflow:hidden; background:#9a9b9a;overflow:hidden}
+.o2k7Skin .mce_forecolor span.mceAction, .o2k7Skin .mce_backcolor span.mceAction {height:15px;overflow:hidden}
+
+/* Menu */
+.o2k7Skin .mceMenu {position:absolute; left:0; top:0; z-index:1000; border:1px solid #ABC6DD}
+.o2k7Skin .mceNoIcons span.mceIcon {width:0;}
+.o2k7Skin .mceNoIcons a .mceText {padding-left:10px}
+.o2k7Skin .mceMenu table {background:#FFF}
+.o2k7Skin .mceMenu a, .o2k7Skin .mceMenu span, .o2k7Skin .mceMenu {display:block}
+.o2k7Skin .mceMenu td {height:20px}
+.o2k7Skin .mceMenu a {position:relative;padding:3px 0 4px 0}
+.o2k7Skin .mceMenu .mceText {position:relative; display:block; font-family:Tahoma,Verdana,Arial,Helvetica; color:#000; cursor:default; margin:0; padding:0 25px 0 25px; display:block}
+.o2k7Skin .mceMenu span.mceText, .o2k7Skin .mceMenu .mcePreview {font-size:11px}
+.o2k7Skin .mceMenu pre.mceText {font-family:Monospace}
+.o2k7Skin .mceMenu .mceIcon {position:absolute; top:0; left:0; width:22px;}
+.o2k7Skin .mceMenu .mceMenuItemEnabled a:hover, .o2k7Skin .mceMenu .mceMenuItemActive {background-color:#dbecf3}
+.o2k7Skin td.mceMenuItemSeparator {background:#DDD; height:1px}
+.o2k7Skin .mceMenuItemTitle a {border:0; background:#E5EFFD; border-bottom:1px solid #ABC6DD}
+.o2k7Skin .mceMenuItemTitle span.mceText {color:#000; font-weight:bold; padding-left:4px}
+.o2k7Skin .mceMenuItemDisabled .mceText {color:#888}
+.o2k7Skin .mceMenuItemSelected .mceIcon {background:url(../default/img/menu_check.gif)}
+.o2k7Skin .mceNoIcons .mceMenuItemSelected a {background:url(../default/img/menu_arrow.gif) no-repeat -6px center}
+.o2k7Skin .mceMenu span.mceMenuLine {display:none}
+.o2k7Skin .mceMenuItemSub a {background:url(../default/img/menu_arrow.gif) no-repeat top right;}
+
+/* Progress,Resize */
+.o2k7Skin .mceBlocker {position:absolute; left:0; top:0; z-index:1000; opacity:0.5; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=50); background:#FFF}
+.o2k7Skin .mceProgress {position:absolute; left:0; top:0; z-index:1001; background:url(../default/img/progress.gif) no-repeat; width:32px; height:32px; margin:-16px 0 0 -16px}
+.o2k7Skin .mcePlaceHolder {border:1px dotted gray}
+
+/* Formats */
+.o2k7Skin .mce_formatPreview a {font-size:10px}
+.o2k7Skin .mce_p span.mceText {}
+.o2k7Skin .mce_address span.mceText {font-style:italic}
+.o2k7Skin .mce_pre span.mceText {font-family:monospace}
+.o2k7Skin .mce_h1 span.mceText {font-weight:bolder; font-size: 2em}
+.o2k7Skin .mce_h2 span.mceText {font-weight:bolder; font-size: 1.5em}
+.o2k7Skin .mce_h3 span.mceText {font-weight:bolder; font-size: 1.17em}
+.o2k7Skin .mce_h4 span.mceText {font-weight:bolder; font-size: 1em}
+.o2k7Skin .mce_h5 span.mceText {font-weight:bolder; font-size: .83em}
+.o2k7Skin .mce_h6 span.mceText {font-weight:bolder; font-size: .75em}
+
+/* Theme */
+.o2k7Skin span.mce_bold {background-position:0 0}
+.o2k7Skin span.mce_italic {background-position:-60px 0}
+.o2k7Skin span.mce_underline {background-position:-140px 0}
+.o2k7Skin span.mce_strikethrough {background-position:-120px 0}
+.o2k7Skin span.mce_undo {background-position:-160px 0}
+.o2k7Skin span.mce_redo {background-position:-100px 0}
+.o2k7Skin span.mce_cleanup {background-position:-40px 0}
+.o2k7Skin span.mce_bullist {background-position:-20px 0}
+.o2k7Skin span.mce_numlist {background-position:-80px 0}
+.o2k7Skin span.mce_justifyleft {background-position:-460px 0}
+.o2k7Skin span.mce_justifyright {background-position:-480px 0}
+.o2k7Skin span.mce_justifycenter {background-position:-420px 0}
+.o2k7Skin span.mce_justifyfull {background-position:-440px 0}
+.o2k7Skin span.mce_anchor {background-position:-200px 0}
+.o2k7Skin span.mce_indent {background-position:-400px 0}
+.o2k7Skin span.mce_outdent {background-position:-540px 0}
+.o2k7Skin span.mce_link {background-position:-500px 0}
+.o2k7Skin span.mce_unlink {background-position:-640px 0}
+.o2k7Skin span.mce_sub {background-position:-600px 0}
+.o2k7Skin span.mce_sup {background-position:-620px 0}
+.o2k7Skin span.mce_removeformat {background-position:-580px 0}
+.o2k7Skin span.mce_newdocument {background-position:-520px 0}
+.o2k7Skin span.mce_image {background-position:-380px 0}
+.o2k7Skin span.mce_help {background-position:-340px 0}
+.o2k7Skin span.mce_code {background-position:-260px 0}
+.o2k7Skin span.mce_hr {background-position:-360px 0}
+.o2k7Skin span.mce_visualaid {background-position:-660px 0}
+.o2k7Skin span.mce_charmap {background-position:-240px 0}
+.o2k7Skin span.mce_paste {background-position:-560px 0}
+.o2k7Skin span.mce_copy {background-position:-700px 0}
+.o2k7Skin span.mce_cut {background-position:-680px 0}
+.o2k7Skin span.mce_blockquote {background-position:-220px 0}
+.o2k7Skin .mce_forecolor span.mceAction {background-position:-720px 0}
+.o2k7Skin .mce_backcolor span.mceAction {background-position:-760px 0}
+.o2k7Skin span.mce_forecolorpicker {background-position:-720px 0}
+.o2k7Skin span.mce_backcolorpicker {background-position:-760px 0}
+
+/* Plugins */
+.o2k7Skin span.mce_advhr {background-position:-0px -20px}
+.o2k7Skin span.mce_ltr {background-position:-20px -20px}
+.o2k7Skin span.mce_rtl {background-position:-40px -20px}
+.o2k7Skin span.mce_emotions {background-position:-60px -20px}
+.o2k7Skin span.mce_fullpage {background-position:-80px -20px}
+.o2k7Skin span.mce_fullscreen {background-position:-100px -20px}
+.o2k7Skin span.mce_iespell {background-position:-120px -20px}
+.o2k7Skin span.mce_insertdate {background-position:-140px -20px}
+.o2k7Skin span.mce_inserttime {background-position:-160px -20px}
+.o2k7Skin span.mce_absolute {background-position:-180px -20px}
+.o2k7Skin span.mce_backward {background-position:-200px -20px}
+.o2k7Skin span.mce_forward {background-position:-220px -20px}
+.o2k7Skin span.mce_insert_layer {background-position:-240px -20px}
+.o2k7Skin span.mce_insertlayer {background-position:-260px -20px}
+.o2k7Skin span.mce_movebackward {background-position:-280px -20px}
+.o2k7Skin span.mce_moveforward {background-position:-300px -20px}
+.o2k7Skin span.mce_media {background-position:-320px -20px}
+.o2k7Skin span.mce_nonbreaking {background-position:-340px -20px}
+.o2k7Skin span.mce_pastetext {background-position:-360px -20px}
+.o2k7Skin span.mce_pasteword {background-position:-380px -20px}
+.o2k7Skin span.mce_selectall {background-position:-400px -20px}
+.o2k7Skin span.mce_preview {background-position:-420px -20px}
+.o2k7Skin span.mce_print {background-position:-440px -20px}
+.o2k7Skin span.mce_cancel {background-position:-460px -20px}
+.o2k7Skin span.mce_save {background-position:-480px -20px}
+.o2k7Skin span.mce_replace {background-position:-500px -20px}
+.o2k7Skin span.mce_search {background-position:-520px -20px}
+.o2k7Skin span.mce_styleprops {background-position:-560px -20px}
+.o2k7Skin span.mce_table {background-position:-580px -20px}
+.o2k7Skin span.mce_cell_props {background-position:-600px -20px}
+.o2k7Skin span.mce_delete_table {background-position:-620px -20px}
+.o2k7Skin span.mce_delete_col {background-position:-640px -20px}
+.o2k7Skin span.mce_delete_row {background-position:-660px -20px}
+.o2k7Skin span.mce_col_after {background-position:-680px -20px}
+.o2k7Skin span.mce_col_before {background-position:-700px -20px}
+.o2k7Skin span.mce_row_after {background-position:-720px -20px}
+.o2k7Skin span.mce_row_before {background-position:-740px -20px}
+.o2k7Skin span.mce_merge_cells {background-position:-760px -20px}
+.o2k7Skin span.mce_table_props {background-position:-980px -20px}
+.o2k7Skin span.mce_row_props {background-position:-780px -20px}
+.o2k7Skin span.mce_split_cells {background-position:-800px -20px}
+.o2k7Skin span.mce_template {background-position:-820px -20px}
+.o2k7Skin span.mce_visualchars {background-position:-840px -20px}
+.o2k7Skin span.mce_abbr {background-position:-860px -20px}
+.o2k7Skin span.mce_acronym {background-position:-880px -20px}
+.o2k7Skin span.mce_attribs {background-position:-900px -20px}
+.o2k7Skin span.mce_cite {background-position:-920px -20px}
+.o2k7Skin span.mce_del {background-position:-940px -20px}
+.o2k7Skin span.mce_ins {background-position:-960px -20px}
+.o2k7Skin span.mce_pagebreak {background-position:0 -40px}
+.o2k7Skin .mce_spellchecker span.mceAction {background-position:-540px -20px}
diff --git a/media/js/tinymce/themes/advanced/skins/o2k7/ui_black.css b/media/js/tinymce/themes/advanced/skins/o2k7/ui_black.css
new file mode 100644
index 0000000..153f0c3
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/skins/o2k7/ui_black.css
@@ -0,0 +1,8 @@
+/* Black */
+.o2k7SkinBlack .mceToolbar .mceToolbarStart span, .o2k7SkinBlack .mceToolbar .mceToolbarEnd span, .o2k7SkinBlack .mceButton, .o2k7SkinBlack .mceSplitButton, .o2k7SkinBlack .mceSeparator, .o2k7SkinBlack .mceSplitButton a.mceOpen, .o2k7SkinBlack .mceListBox a.mceOpen {background-image:url(img/button_bg_black.png)}
+.o2k7SkinBlack table, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack .mceMenuItemTitle span.mceText, .o2k7SkinBlack .mceStatusbar div, .o2k7SkinBlack .mceStatusbar span, .o2k7SkinBlack .mceStatusbar a {background:#535353; color:#FFF}
+.o2k7SkinBlack table.mceListBoxEnabled .mceText, o2k7SkinBlack .mceListBox .mceText {background:#FFF; border:1px solid #CBCFD4; border-bottom-color:#989FA9; border-right:0}
+.o2k7SkinBlack table.mceListBoxEnabled:hover .mceText, .o2k7SkinBlack .mceListBoxHover .mceText, .o2k7SkinBlack .mceListBoxSelected .mceText {background:#FFF; border:1px solid #FFBD69; border-right:0}
+.o2k7SkinBlack .mceExternalToolbar, .o2k7SkinBlack .mceListBox .mceText, .o2k7SkinBlack div.mceMenu, .o2k7SkinBlack table.mceLayout, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack table.mceLayout tr.mceFirst td, .o2k7SkinBlack table.mceLayout, .o2k7SkinBlack .mceMenuItemTitle a, .o2k7SkinBlack table.mceLayout tr.mceLast td, .o2k7SkinBlack .mceIframeContainer {border-color: #535353;}
+.o2k7SkinBlack table.mceSplitButtonEnabled:hover a.mceAction, .o2k7SkinBlack .mceSplitButtonHover a.mceAction, .o2k7SkinBlack .mceSplitButtonSelected {background-image:url(img/button_bg_black.png)}
+.o2k7SkinBlack .mceMenu .mceMenuItemEnabled a:hover, .o2k7SkinBlack .mceMenu .mceMenuItemActive {background-color:#FFE7A1}
\ No newline at end of file
diff --git a/media/js/tinymce/themes/advanced/skins/o2k7/ui_silver.css b/media/js/tinymce/themes/advanced/skins/o2k7/ui_silver.css
new file mode 100644
index 0000000..7fe3b45
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/skins/o2k7/ui_silver.css
@@ -0,0 +1,5 @@
+/* Silver */
+.o2k7SkinSilver .mceToolbar .mceToolbarStart span, .o2k7SkinSilver .mceButton, .o2k7SkinSilver .mceSplitButton, .o2k7SkinSilver .mceSeparator, .o2k7SkinSilver .mceSplitButton a.mceOpen, .o2k7SkinSilver .mceListBox a.mceOpen {background-image:url(img/button_bg_silver.png)}
+.o2k7SkinSilver table, .o2k7SkinSilver .mceMenuItemTitle a {background:#eee}
+.o2k7SkinSilver .mceListBox .mceText {background:#FFF}
+.o2k7SkinSilver .mceExternalToolbar, .o2k7SkinSilver .mceListBox .mceText, .o2k7SkinSilver div.mceMenu, .o2k7SkinSilver table.mceLayout, .o2k7SkinSilver .mceMenuItemTitle a, .o2k7SkinSilver table.mceLayout tr.mceFirst td, .o2k7SkinSilver table.mceLayout, .o2k7SkinSilver .mceMenuItemTitle a, .o2k7SkinSilver table.mceLayout tr.mceLast td, .o2k7SkinSilver .mceIframeContainer {border-color: #bbb}
diff --git a/media/js/tinymce/themes/advanced/source_editor.htm b/media/js/tinymce/themes/advanced/source_editor.htm
new file mode 100644
index 0000000..553e7bb
--- /dev/null
+++ b/media/js/tinymce/themes/advanced/source_editor.htm
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+	<title>{#advanced_dlg.code_title}</title>
+	<script type="text/javascript" src="../../tiny_mce_popup.js"></script>
+	<script type="text/javascript" src="js/source_editor.js"></script>
+</head>
+<body onresize="resizeInputs();" style="display:none; overflow:hidden;">
+	<form name="source" onsubmit="saveContent();return false;" action="#">
+		<div style="float: left" class="title">{#advanced_dlg.code_title}</div>
+
+		<div id="wrapline" style="float: right">
+			<input type="checkbox" name="wraped" id="wraped" onclick="toggleWordWrap(this);" class="wordWrapCode" /><label for="wraped">{#advanced_dlg.code_wordwrap}</label>
+		</div>
+
+		<br style="clear: both" />
+
+		<textarea name="htmlSource" id="htmlSource" rows="15" cols="100" style="width: 100%; height: 100%; font-family: 'Courier New',Courier,monospace; font-size: 12px;" dir="ltr" wrap="off" class="mceFocus"></textarea>
+
+		<div class="mceActionPanel">
+			<div style="float: left">
+				<input type="submit" name="insert" value="{#update}" id="insert" />
+			</div>
+
+			<div style="float: right">
+				<input type="button" name="cancel" value="{#cancel}" onclick="tinyMCEPopup.close();" id="cancel" />
+			</div>
+		</div>
+	</form>
+</body>
+</html>
diff --git a/media/js/tinymce/themes/simple/editor_template.js b/media/js/tinymce/themes/simple/editor_template.js
new file mode 100644
index 0000000..ed89abc
--- /dev/null
+++ b/media/js/tinymce/themes/simple/editor_template.js
@@ -0,0 +1 @@
+(function(){var a=tinymce.DOM;tinymce.ThemeManager.requireLangPack("simple");tinymce.create("tinymce.themes.SimpleTheme",{init:function(c,d){var e=this,b=["Bold","Italic","Underline","Strikethrough","InsertUnorderedList","InsertOrderedList"],f=c.settings;e.editor=c;c.onInit.add(function(){c.onNodeChange.add(function(h,g){tinymce.each(b,function(i){g.get(i.toLowerCase()).setActive(h.queryCommandState(i))})});c.dom.loadCSS(d+"/skins/"+f.skin+"/content.css")});a.loadCSS((f.editor_css?c.docu [...]
\ No newline at end of file
diff --git a/media/js/tinymce/themes/simple/editor_template_src.js b/media/js/tinymce/themes/simple/editor_template_src.js
new file mode 100644
index 0000000..fb0bd78
--- /dev/null
+++ b/media/js/tinymce/themes/simple/editor_template_src.js
@@ -0,0 +1,85 @@
+/**
+ * $Id: editor_template_src.js 920 2008-09-09 14:05:33Z spocke $
+ *
+ * This file is meant to showcase how to create a simple theme. The advanced
+ * theme is more suitable for production use.
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+(function() {
+	var DOM = tinymce.DOM;
+
+	// Tell it to load theme specific language pack(s)
+	tinymce.ThemeManager.requireLangPack('simple');
+
+	tinymce.create('tinymce.themes.SimpleTheme', {
+		init : function(ed, url) {
+			var t = this, states = ['Bold', 'Italic', 'Underline', 'Strikethrough', 'InsertUnorderedList', 'InsertOrderedList'], s = ed.settings;
+
+			t.editor = ed;
+
+			ed.onInit.add(function() {
+				ed.onNodeChange.add(function(ed, cm) {
+					tinymce.each(states, function(c) {
+						cm.get(c.toLowerCase()).setActive(ed.queryCommandState(c));
+					});
+				});
+
+				ed.dom.loadCSS(url + "/skins/" + s.skin + "/content.css");
+			});
+
+			DOM.loadCSS((s.editor_css ? ed.documentBaseURI.toAbsolute(s.editor_css) : '') || url + "/skins/" + s.skin + "/ui.css");
+		},
+
+		renderUI : function(o) {
+			var t = this, n = o.targetNode, ic, tb, ed = t.editor, cf = ed.controlManager, sc;
+
+			n = DOM.insertAfter(DOM.create('span', {id : ed.id + '_container', 'class' : 'mceEditor ' + ed.settings.skin + 'SimpleSkin'}), n);
+			n = sc = DOM.add(n, 'table', {cellPadding : 0, cellSpacing : 0, 'class' : 'mceLayout'});
+			n = tb = DOM.add(n, 'tbody');
+
+			// Create iframe container
+			n = DOM.add(tb, 'tr');
+			n = ic = DOM.add(DOM.add(n, 'td'), 'div', {'class' : 'mceIframeContainer'});
+
+			// Create toolbar container
+			n = DOM.add(DOM.add(tb, 'tr', {'class' : 'last'}), 'td', {'class' : 'mceToolbar mceLast', align : 'center'});
+
+			// Create toolbar
+			tb = t.toolbar = cf.createToolbar("tools1");
+			tb.add(cf.createButton('bold', {title : 'simple.bold_desc', cmd : 'Bold'}));
+			tb.add(cf.createButton('italic', {title : 'simple.italic_desc', cmd : 'Italic'}));
+			tb.add(cf.createButton('underline', {title : 'simple.underline_desc', cmd : 'Underline'}));
+			tb.add(cf.createButton('strikethrough', {title : 'simple.striketrough_desc', cmd : 'Strikethrough'}));
+			tb.add(cf.createSeparator());
+			tb.add(cf.createButton('undo', {title : 'simple.undo_desc', cmd : 'Undo'}));
+			tb.add(cf.createButton('redo', {title : 'simple.redo_desc', cmd : 'Redo'}));
+			tb.add(cf.createSeparator());
+			tb.add(cf.createButton('cleanup', {title : 'simple.cleanup_desc', cmd : 'mceCleanup'}));
+			tb.add(cf.createSeparator());
+			tb.add(cf.createButton('insertunorderedlist', {title : 'simple.bullist_desc', cmd : 'InsertUnorderedList'}));
+			tb.add(cf.createButton('insertorderedlist', {title : 'simple.numlist_desc', cmd : 'InsertOrderedList'}));
+			tb.renderTo(n);
+
+			return {
+				iframeContainer : ic,
+				editorContainer : ed.id + '_container',
+				sizeContainer : sc,
+				deltaHeight : -20
+			};
+		},
+
+		getInfo : function() {
+			return {
+				longname : 'Simple theme',
+				author : 'Moxiecode Systems AB',
+				authorurl : 'http://tinymce.moxiecode.com',
+				version : tinymce.majorVersion + "." + tinymce.minorVersion
+			}
+		}
+	});
+
+	tinymce.ThemeManager.add('simple', tinymce.themes.SimpleTheme);
+})();
\ No newline at end of file
diff --git a/media/js/tinymce/themes/simple/img/icons.gif b/media/js/tinymce/themes/simple/img/icons.gif
new file mode 100644
index 0000000..16af141
Binary files /dev/null and b/media/js/tinymce/themes/simple/img/icons.gif differ
diff --git a/media/js/tinymce/themes/simple/langs/en.js b/media/js/tinymce/themes/simple/langs/en.js
new file mode 100644
index 0000000..9f08f10
--- /dev/null
+++ b/media/js/tinymce/themes/simple/langs/en.js
@@ -0,0 +1,11 @@
+tinyMCE.addI18n('en.simple',{
+bold_desc:"Bold (Ctrl+B)",
+italic_desc:"Italic (Ctrl+I)",
+underline_desc:"Underline (Ctrl+U)",
+striketrough_desc:"Strikethrough",
+bullist_desc:"Unordered list",
+numlist_desc:"Ordered list",
+undo_desc:"Undo (Ctrl+Z)",
+redo_desc:"Redo (Ctrl+Y)",
+cleanup_desc:"Cleanup messy code"
+});
\ No newline at end of file
diff --git a/media/js/tinymce/themes/simple/skins/default/content.css b/media/js/tinymce/themes/simple/skins/default/content.css
new file mode 100644
index 0000000..2506c80
--- /dev/null
+++ b/media/js/tinymce/themes/simple/skins/default/content.css
@@ -0,0 +1,25 @@
+body, td, pre {
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 10px;
+}
+
+body {
+	background-color: #FFFFFF;
+}
+
+.mceVisualAid {
+	border: 1px dashed #BBBBBB;
+}
+
+/* MSIE specific */
+
+* html body {
+	scrollbar-3dlight-color: #F0F0EE;
+	scrollbar-arrow-color: #676662;
+	scrollbar-base-color: #F0F0EE;
+	scrollbar-darkshadow-color: #DDDDDD;
+	scrollbar-face-color: #E0E0DD;
+	scrollbar-highlight-color: #F0F0EE;
+	scrollbar-shadow-color: #F0F0EE;
+	scrollbar-track-color: #F5F5F5;	
+}
diff --git a/media/js/tinymce/themes/simple/skins/default/ui.css b/media/js/tinymce/themes/simple/skins/default/ui.css
new file mode 100644
index 0000000..076fe84
--- /dev/null
+++ b/media/js/tinymce/themes/simple/skins/default/ui.css
@@ -0,0 +1,32 @@
+/* Reset */
+.defaultSimpleSkin table, .defaultSimpleSkin tbody, .defaultSimpleSkin a, .defaultSimpleSkin img, .defaultSimpleSkin tr, .defaultSimpleSkin div, .defaultSimpleSkin td, .defaultSimpleSkin iframe, .defaultSimpleSkin span, .defaultSimpleSkin * {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000}
+
+/* Containers */
+.defaultSimpleSkin {position:relative}
+.defaultSimpleSkin table.mceLayout {background:#F0F0EE; border:1px solid #CCC;}
+.defaultSimpleSkin iframe {display:block; background:#FFF; border-bottom:1px solid #CCC;}
+.defaultSimpleSkin .mceToolbar {height:24px;}
+
+/* Layout */
+.defaultSimpleSkin span.mceIcon, .defaultSimpleSkin img.mceIcon {display:block; width:20px; height:20px}
+.defaultSimpleSkin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px}
+
+/* Button */
+.defaultSimpleSkin .mceButton {display:block; border:1px solid #F0F0EE; width:20px; height:20px}
+.defaultSimpleSkin a.mceButtonEnabled:hover {border:1px solid #0A246A; background-color:#B2BBD0}
+.defaultSimpleSkin a.mceButtonActive {border:1px solid #0A246A; background-color:#C2CBE0}
+.defaultSimpleSkin .mceButtonDisabled span {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
+
+/* Separator */
+.defaultSimpleSkin .mceSeparator {display:block; background:url(../../img/icons.gif) -180px 0; width:2px; height:20px; margin:0 2px 0 4px}
+
+/* Theme */
+.defaultSimpleSkin span.mce_bold {background-position:0 0}
+.defaultSimpleSkin span.mce_italic {background-position:-60px 0}
+.defaultSimpleSkin span.mce_underline {background-position:-140px 0}
+.defaultSimpleSkin span.mce_strikethrough {background-position:-120px 0}
+.defaultSimpleSkin span.mce_undo {background-position:-160px 0}
+.defaultSimpleSkin span.mce_redo {background-position:-100px 0}
+.defaultSimpleSkin span.mce_cleanup {background-position:-40px 0}
+.defaultSimpleSkin span.mce_insertunorderedlist {background-position:-20px 0}
+.defaultSimpleSkin span.mce_insertorderedlist {background-position:-80px 0}
diff --git a/media/js/tinymce/themes/simple/skins/o2k7/content.css b/media/js/tinymce/themes/simple/skins/o2k7/content.css
new file mode 100644
index 0000000..595809f
--- /dev/null
+++ b/media/js/tinymce/themes/simple/skins/o2k7/content.css
@@ -0,0 +1,17 @@
+body, td, pre {font-family:Verdana, Arial, Helvetica, sans-serif; font-size:10px;}
+
+body {background: #FFF;}
+.mceVisualAid {border: 1px dashed #BBB;}
+
+/* IE */
+
+* html body {
+scrollbar-3dlight-color: #F0F0EE;
+scrollbar-arrow-color: #676662;
+scrollbar-base-color: #F0F0EE;
+scrollbar-darkshadow-color: #DDDDDD;
+scrollbar-face-color: #E0E0DD;
+scrollbar-highlight-color: #F0F0EE;
+scrollbar-shadow-color: #F0F0EE;
+scrollbar-track-color: #F5F5F5;	
+}
diff --git a/media/js/tinymce/themes/simple/skins/o2k7/img/button_bg.png b/media/js/tinymce/themes/simple/skins/o2k7/img/button_bg.png
new file mode 100644
index 0000000..527e349
Binary files /dev/null and b/media/js/tinymce/themes/simple/skins/o2k7/img/button_bg.png differ
diff --git a/media/js/tinymce/themes/simple/skins/o2k7/ui.css b/media/js/tinymce/themes/simple/skins/o2k7/ui.css
new file mode 100644
index 0000000..cf6c35d
--- /dev/null
+++ b/media/js/tinymce/themes/simple/skins/o2k7/ui.css
@@ -0,0 +1,35 @@
+/* Reset */
+.o2k7SimpleSkin table, .o2k7SimpleSkin tbody, .o2k7SimpleSkin a, .o2k7SimpleSkin img, .o2k7SimpleSkin tr, .o2k7SimpleSkin div, .o2k7SimpleSkin td, .o2k7SimpleSkin iframe, .o2k7SimpleSkin span, .o2k7SimpleSkin * {border:0; margin:0; padding:0; background:transparent; white-space:nowrap; text-decoration:none; font-weight:normal; cursor:default; color:#000}
+
+/* Containers */
+.o2k7SimpleSkin {position:relative}
+.o2k7SimpleSkin table.mceLayout {background:#E5EFFD; border:1px solid #ABC6DD;}
+.o2k7SimpleSkin iframe {display:block; background:#FFF; border-bottom:1px solid #ABC6DD;}
+.o2k7SimpleSkin .mceToolbar {height:26px;}
+
+/* Layout */
+.o2k7SimpleSkin .mceToolbar .mceToolbarStart span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px; }
+.o2k7SimpleSkin .mceToolbar .mceToolbarEnd span {display:block; background:url(img/button_bg.png) -22px 0; width:1px; height:22px}
+.o2k7SimpleSkin span.mceIcon, .o2k7SimpleSkin img.mceIcon {display:block; width:20px; height:20px}
+.o2k7SimpleSkin .mceIcon {background:url(../../img/icons.gif) no-repeat 20px 20px}
+
+/* Button */
+.o2k7SimpleSkin .mceButton {display:block; background:url(img/button_bg.png); width:22px; height:22px}
+.o2k7SimpleSkin a.mceButton span, .o2k7SimpleSkin a.mceButton img {margin:1px 0 0 1px}
+.o2k7SimpleSkin a.mceButtonEnabled:hover {background-color:#B2BBD0; background-position:0 -22px}
+.o2k7SimpleSkin a.mceButtonActive {background-position:0 -44px}
+.o2k7SimpleSkin .mceButtonDisabled span {opacity:0.3; -ms-filter:'alpha(opacity=30)'; filter:alpha(opacity=30)}
+
+/* Separator */
+.o2k7SimpleSkin .mceSeparator {display:block; background:url(img/button_bg.png) -22px 0; width:5px; height:22px}
+
+/* Theme */
+.o2k7SimpleSkin span.mce_bold {background-position:0 0}
+.o2k7SimpleSkin span.mce_italic {background-position:-60px 0}
+.o2k7SimpleSkin span.mce_underline {background-position:-140px 0}
+.o2k7SimpleSkin span.mce_strikethrough {background-position:-120px 0}
+.o2k7SimpleSkin span.mce_undo {background-position:-160px 0}
+.o2k7SimpleSkin span.mce_redo {background-position:-100px 0}
+.o2k7SimpleSkin span.mce_cleanup {background-position:-40px 0}
+.o2k7SimpleSkin span.mce_insertunorderedlist {background-position:-20px 0}
+.o2k7SimpleSkin span.mce_insertorderedlist {background-position:-80px 0}
diff --git a/media/js/tinymce/tiny_mce.js b/media/js/tinymce/tiny_mce.js
new file mode 100644
index 0000000..6b9393c
--- /dev/null
+++ b/media/js/tinymce/tiny_mce.js
@@ -0,0 +1 @@
+var tinymce={majorVersion:"3",minorVersion:"2.7",releaseDate:"2009-09-22",_init:function(){var o=this,k=document,l=window,j=navigator,b=j.userAgent,h,a,g,f,e,m;o.isOpera=l.opera&&opera.buildNumber;o.isWebKit=/WebKit/.test(b);o.isIE=!o.isWebKit&&!o.isOpera&&(/MSIE/gi).test(b)&&(/Explorer/gi).test(j.appName);o.isIE6=o.isIE&&/MSIE [56]/.test(b);o.isGecko=!o.isWebKit&&/Gecko/.test(b);o.isMac=b.indexOf("Mac")!=-1;o.isAir=/adobeair/i.test(b);if(l.tinyMCEPreInit){o.suffix=tinyMCEPreInit.suffix; [...]
\ No newline at end of file
diff --git a/media/js/tinymce/tiny_mce_popup.js b/media/js/tinymce/tiny_mce_popup.js
new file mode 100644
index 0000000..c9bf1fe
--- /dev/null
+++ b/media/js/tinymce/tiny_mce_popup.js
@@ -0,0 +1,5 @@
+
+// Uncomment and change this document.domain value if you are loading the script cross subdomains
+// document.domain = 'moxiecode.com';
+
+var tinymce=null,tinyMCEPopup,tinyMCE;tinyMCEPopup={init:function(){var b=this,a,c;a=b.getWin();tinymce=a.tinymce;tinyMCE=a.tinyMCE;b.editor=tinymce.EditorManager.activeEditor;b.params=b.editor.windowManager.params;b.features=b.editor.windowManager.features;b.dom=b.editor.windowManager.createInstance("tinymce.dom.DOMUtils",document);if(b.features.popup_css!==false){b.dom.loadCSS(b.features.popup_css||b.editor.settings.popup_css)}b.listeners=[];b.onInit={add:function(e,d){b.listeners.push [...]
\ No newline at end of file
diff --git a/media/js/tinymce/tiny_mce_src.js b/media/js/tinymce/tiny_mce_src.js
new file mode 100644
index 0000000..3fe768b
--- /dev/null
+++ b/media/js/tinymce/tiny_mce_src.js
@@ -0,0 +1,12428 @@
+var tinymce = {
+	majorVersion : '3',
+	minorVersion : '2.7',
+	releaseDate : '2009-09-22',
+
+	_init : function() {
+		var t = this, d = document, w = window, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
+
+		t.isOpera = w.opera && opera.buildNumber;
+
+		t.isWebKit = /WebKit/.test(ua);
+
+		t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
+
+		t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
+
+		t.isGecko = !t.isWebKit && /Gecko/.test(ua);
+
+		t.isMac = ua.indexOf('Mac') != -1;
+
+		t.isAir = /adobeair/i.test(ua);
+
+		// TinyMCE .NET webcontrol might be setting the values for TinyMCE
+		if (w.tinyMCEPreInit) {
+			t.suffix = tinyMCEPreInit.suffix;
+			t.baseURL = tinyMCEPreInit.base;
+			t.query = tinyMCEPreInit.query;
+			return;
+		}
+
+		// Get suffix and base
+		t.suffix = '';
+
+		// If base element found, add that infront of baseURL
+		nl = d.getElementsByTagName('base');
+		for (i=0; i<nl.length; i++) {
+			if (v = nl[i].href) {
+				// Host only value like http://site.com or http://site.com:8008
+				if (/^https?:\/\/[^\/]+$/.test(v))
+					v += '/';
+
+				base = v ? v.match(/.*\//)[0] : ''; // Get only directory
+			}
+		}
+
+		function getBase(n) {
+			if (n.src && /tiny_mce(|_gzip|_jquery|_prototype)(_dev|_src)?.js/.test(n.src)) {
+				if (/_(src|dev)\.js/g.test(n.src))
+					t.suffix = '_src';
+
+				if ((p = n.src.indexOf('?')) != -1)
+					t.query = n.src.substring(p + 1);
+
+				t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
+
+				// If path to script is relative and a base href was found add that one infront
+				// the src property will always be an absolute one on non IE browsers and IE 8
+				// so this logic will basically only be executed on older IE versions
+				if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
+					t.baseURL = base + t.baseURL;
+
+				return t.baseURL;
+			}
+
+			return null;
+		};
+
+		// Check document
+		nl = d.getElementsByTagName('script');
+		for (i=0; i<nl.length; i++) {
+			if (getBase(nl[i]))
+				return;
+		}
+
+		// Check head
+		n = d.getElementsByTagName('head')[0];
+		if (n) {
+			nl = n.getElementsByTagName('script');
+			for (i=0; i<nl.length; i++) {
+				if (getBase(nl[i]))
+					return;
+			}
+		}
+
+		return;
+	},
+
+	is : function(o, t) {
+		var n = typeof(o);
+
+		if (!t)
+			return n != 'undefined';
+
+		if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
+			return true;
+
+		return n == t;
+	},
+
+	each : function(o, cb, s) {
+		var n, l;
+
+		if (!o)
+			return 0;
+
+		s = s || o;
+
+		if (typeof(o.length) != 'undefined') {
+			// Indexed arrays, needed for Safari
+			for (n=0, l = o.length; n<l; n++) {
+				if (cb.call(s, o[n], n, o) === false)
+					return 0;
+			}
+		} else {
+			// Hashtables
+			for (n in o) {
+				if (o.hasOwnProperty(n)) {
+					if (cb.call(s, o[n], n, o) === false)
+						return 0;
+				}
+			}
+		}
+
+		return 1;
+	},
+
+
+	trim : function(s) {
+		return (s ? '' + s : '').replace(/^\s*|\s*$/g, '');
+	},
+
+	create : function(s, p) {
+		var t = this, sp, ns, cn, scn, c, de = 0;
+
+		// Parse : <prefix> <class>:<super class>
+		s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
+		cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
+
+		// Create namespace for new class
+		ns = t.createNS(s[3].replace(/\.\w+$/, ''));
+
+		// Class already exists
+		if (ns[cn])
+			return;
+
+		// Make pure static class
+		if (s[2] == 'static') {
+			ns[cn] = p;
+
+			if (this.onCreate)
+				this.onCreate(s[2], s[3], ns[cn]);
+
+			return;
+		}
+
+		// Create default constructor
+		if (!p[cn]) {
+			p[cn] = function() {};
+			de = 1;
+		}
+
+		// Add constructor and methods
+		ns[cn] = p[cn];
+		t.extend(ns[cn].prototype, p);
+
+		// Extend
+		if (s[5]) {
+			sp = t.resolve(s[5]).prototype;
+			scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
+
+			// Extend constructor
+			c = ns[cn];
+			if (de) {
+				// Add passthrough constructor
+				ns[cn] = function() {
+					return sp[scn].apply(this, arguments);
+				};
+			} else {
+				// Add inherit constructor
+				ns[cn] = function() {
+					this.parent = sp[scn];
+					return c.apply(this, arguments);
+				};
+			}
+			ns[cn].prototype[cn] = ns[cn];
+
+			// Add super methods
+			t.each(sp, function(f, n) {
+				ns[cn].prototype[n] = sp[n];
+			});
+
+			// Add overridden methods
+			t.each(p, function(f, n) {
+				// Extend methods if needed
+				if (sp[n]) {
+					ns[cn].prototype[n] = function() {
+						this.parent = sp[n];
+						return f.apply(this, arguments);
+					};
+				} else {
+					if (n != cn)
+						ns[cn].prototype[n] = f;
+				}
+			});
+		}
+
+		// Add static methods
+		t.each(p['static'], function(f, n) {
+			ns[cn][n] = f;
+		});
+
+		if (this.onCreate)
+			this.onCreate(s[2], s[3], ns[cn].prototype);
+	},
+
+	walk : function(o, f, n, s) {
+		s = s || this;
+
+		if (o) {
+			if (n)
+				o = o[n];
+
+			tinymce.each(o, function(o, i) {
+				if (f.call(s, o, i, n) === false)
+					return false;
+
+				tinymce.walk(o, f, n, s);
+			});
+		}
+	},
+
+	createNS : function(n, o) {
+		var i, v;
+
+		o = o || window;
+
+		n = n.split('.');
+		for (i=0; i<n.length; i++) {
+			v = n[i];
+
+			if (!o[v])
+				o[v] = {};
+
+			o = o[v];
+		}
+
+		return o;
+	},
+
+	resolve : function(n, o) {
+		var i, l;
+
+		o = o || window;
+
+		n = n.split('.');
+		for (i = 0, l = n.length; i < l; i++) {
+			o = o[n[i]];
+
+			if (!o)
+				break;
+		}
+
+		return o;
+	},
+
+	addUnload : function(f, s) {
+		var t = this, w = window;
+
+		f = {func : f, scope : s || this};
+
+		if (!t.unloads) {
+			function unload() {
+				var li = t.unloads, o, n;
+
+				if (li) {
+					// Call unload handlers
+					for (n in li) {
+						o = li[n];
+
+						if (o && o.func)
+							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
+					}
+
+					// Detach unload function
+					if (w.detachEvent) {
+						w.detachEvent('onbeforeunload', fakeUnload);
+						w.detachEvent('onunload', unload);
+					} else if (w.removeEventListener)
+						w.removeEventListener('unload', unload, false);
+
+					// Destroy references
+					t.unloads = o = li = w = unload = 0;
+
+					// Run garbarge collector on IE
+					if (window.CollectGarbage)
+						window.CollectGarbage();
+				}
+			};
+
+			function fakeUnload() {
+				var d = document;
+
+				// Is there things still loading, then do some magic
+				if (d.readyState == 'interactive') {
+					function stop() {
+						// Prevent memory leak
+						d.detachEvent('onstop', stop);
+
+						// Call unload handler
+						if (unload)
+							unload();
+
+						d = 0;
+					};
+
+					// Fire unload when the currently loading page is stopped
+					if (d)
+						d.attachEvent('onstop', stop);
+
+					// Remove onstop listener after a while to prevent the unload function
+					// to execute if the user presses cancel in an onbeforeunload
+					// confirm dialog and then presses the browser stop button
+					window.setTimeout(function() {
+						if (d)
+							d.detachEvent('onstop', stop);
+					}, 0);
+				}
+			};
+
+			// Attach unload handler
+			if (w.attachEvent) {
+				w.attachEvent('onunload', unload);
+				w.attachEvent('onbeforeunload', fakeUnload);
+			} else if (w.addEventListener)
+				w.addEventListener('unload', unload, false);
+
+			// Setup initial unload handler array
+			t.unloads = [f];
+		} else
+			t.unloads.push(f);
+
+		return f;
+	},
+
+	removeUnload : function(f) {
+		var u = this.unloads, r = null;
+
+		tinymce.each(u, function(o, i) {
+			if (o && o.func == f) {
+				u.splice(i, 1);
+				r = f;
+				return false;
+			}
+		});
+
+		return r;
+	},
+
+	explode : function(s, d) {
+		return s ? tinymce.map(s.split(d || ','), tinymce.trim) : s;
+	},
+
+	_addVer : function(u) {
+		var v;
+
+		if (!this.query)
+			return u;
+
+		v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
+
+		if (u.indexOf('#') == -1)
+			return u + v;
+
+		return u.replace('#', v + '#');
+	}
+
+	};
+
+// Required for GZip AJAX loading
+window.tinymce = tinymce;
+
+// Initialize the API
+tinymce._init();
+
+(function($, tinymce) {
+	var is = tinymce.is;
+
+	if (!window.jQuery)
+		return alert("Load jQuery first!");
+
+	// Patch in core NS functions
+	tinymce.extend = $.extend;
+	tinymce.extend(tinymce, {
+		map : $.map,
+		grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
+		inArray : function(a, v) {return $.inArray(v, a || []);}
+
+		/* Didn't iterate stylesheets
+		each : function(o, cb, s) {
+			if (!o)
+				return 0;
+
+			var r = 1;
+
+			$.each(o, function(nr, el){
+				if (cb.call(s, el, nr, o) === false) {
+					r = 0;
+					return false;
+				}
+			});
+
+			return r;
+		}*/
+	});
+
+	// Patch in functions in various clases
+	// Add a "#ifndefjquery" statement around each core API function you add below
+	var patches = {
+		'tinymce.dom.DOMUtils' : {
+			/*
+			addClass : function(e, c) {
+				if (is(e, 'array') && is(e[0], 'string'))
+					e = e.join(',#');
+				return (e && $(is(e, 'string') ? '#' + e : e)
+					.addClass(c)
+					.attr('class')) || false;
+			},
+
+			hasClass : function(n, c) {
+				return $(is(n, 'string') ? '#' + n : n).hasClass(c);
+			},
+
+			removeClass : function(e, c) {
+				if (!e)
+					return false;
+
+				var r = [];
+
+				$(is(e, 'string') ? '#' + e : e)
+					.removeClass(c)
+					.each(function(){
+						r.push(this.className);
+					});
+
+				return r.length == 1 ? r[0] : r;
+			},
+			*/
+
+			select : function(pattern, scope) {
+				var t = this;
+
+				return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
+			},
+
+			is : function(n, patt) {
+				return $(this.get(n)).is(patt);
+			}
+
+			/*
+			show : function(e) {
+				if (is(e, 'array') && is(e[0], 'string'))
+					e = e.join(',#');
+
+				$(is(e, 'string') ? '#' + e : e).css('display', 'block');
+			},
+
+			hide : function(e) {
+				if (is(e, 'array') && is(e[0], 'string'))
+					e = e.join(',#');
+
+				$(is(e, 'string') ? '#' + e : e).css('display', 'none');
+			},
+
+			isHidden : function(e) {
+				return $(is(e, 'string') ? '#' + e : e).is(':hidden');
+			},
+
+			insertAfter : function(n, e) {
+				return $(is(e, 'string') ? '#' + e : e).after(n);
+			},
+
+			replace : function(o, n, k) {
+				n = $(is(n, 'string') ? '#' + n : n);
+
+				if (k)
+					n.children().appendTo(o);
+
+				n.replaceWith(o);
+			},
+
+			setStyle : function(n, na, v) {
+				if (is(n, 'array') && is(n[0], 'string'))
+					n = n.join(',#');
+
+				$(is(n, 'string') ? '#' + n : n).css(na, v);
+			},
+
+			getStyle : function(n, na, c) {
+				return $(is(n, 'string') ? '#' + n : n).css(na);
+			},
+
+			setStyles : function(e, o) {
+				if (is(e, 'array') && is(e[0], 'string'))
+					e = e.join(',#');
+				$(is(e, 'string') ? '#' + e : e).css(o);
+			},
+
+			setAttrib : function(e, n, v) {
+				var t = this, s = t.settings;
+
+				if (is(e, 'array') && is(e[0], 'string'))
+					e = e.join(',#');
+
+				e = $(is(e, 'string') ? '#' + e : e);
+
+				switch (n) {
+					case "style":
+						e.each(function(i, v){
+							if (s.keep_values)
+								$(v).attr('mce_style', v);
+
+							v.style.cssText = v;
+						});
+						break;
+
+					case "class":
+						e.each(function(){
+							this.className = v;
+						});
+						break;
+
+					case "src":
+					case "href":
+						e.each(function(i, v){
+							if (s.keep_values) {
+								if (s.url_converter)
+									v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
+
+								t.setAttrib(v, 'mce_' + n, v);
+							}
+						});
+
+						break;
+				}
+
+				if (v !== null && v.length !== 0)
+					e.attr(n, '' + v);
+				else
+					e.removeAttr(n);
+			},
+
+			setAttribs : function(e, o) {
+				var t = this;
+
+				$.each(o, function(n, v){
+					t.setAttrib(e,n,v);
+				});
+			}
+			*/
+		}
+
+/*
+		'tinymce.dom.Event' : {
+			add : function (o, n, f, s) {
+				var lo, cb;
+
+				cb = function(e) {
+					e.target = e.target || this;
+					f.call(s || this, e);
+				};
+
+				if (is(o, 'array') && is(o[0], 'string'))
+					o = o.join(',#');
+				o = $(is(o, 'string') ? '#' + o : o);
+				if (n == 'init') {
+					o.ready(cb, s);
+				} else {
+					if (s) {
+						o.bind(n, s, cb);
+					} else {
+						o.bind(n, cb);
+					}
+				}
+
+				lo = this._jqLookup || (this._jqLookup = []);
+				lo.push({func : f, cfunc : cb});
+
+				return cb;
+			},
+
+			remove : function(o, n, f) {
+				// Find cfunc
+				$(this._jqLookup).each(function() {
+					if (this.func === f)
+						f = this.cfunc;
+				});
+
+				if (is(o, 'array') && is(o[0], 'string'))
+					o = o.join(',#');
+
+				$(is(o, 'string') ? '#' + o : o).unbind(n,f);
+
+				return true;
+			}
+		}
+*/
+	};
+
+	// Patch functions after a class is created
+	tinymce.onCreate = function(ty, c, p) {
+		tinymce.extend(p, patches[c]);
+	};
+})(jQuery, tinymce);
+
+tinymce.create('tinymce.util.Dispatcher', {
+	scope : null,
+	listeners : null,
+
+	Dispatcher : function(s) {
+		this.scope = s || this;
+		this.listeners = [];
+	},
+
+	add : function(cb, s) {
+		this.listeners.push({cb : cb, scope : s || this.scope});
+
+		return cb;
+	},
+
+	addToTop : function(cb, s) {
+		this.listeners.unshift({cb : cb, scope : s || this.scope});
+
+		return cb;
+	},
+
+	remove : function(cb) {
+		var l = this.listeners, o = null;
+
+		tinymce.each(l, function(c, i) {
+			if (cb == c.cb) {
+				o = cb;
+				l.splice(i, 1);
+				return false;
+			}
+		});
+
+		return o;
+	},
+
+	dispatch : function() {
+		var s, a = arguments, i, li = this.listeners, c;
+
+		// Needs to be a real loop since the listener count might change while looping
+		// And this is also more efficient
+		for (i = 0; i<li.length; i++) {
+			c = li[i];
+			s = c.cb.apply(c.scope, a);
+
+			if (s === false)
+				break;
+		}
+
+		return s;
+	}
+
+	});
+(function() {
+	var each = tinymce.each;
+
+	tinymce.create('tinymce.util.URI', {
+		URI : function(u, s) {
+			var t = this, o, a, b;
+
+			// Trim whitespace
+			u = tinymce.trim(u);
+
+			// Default settings
+			s = t.settings = s || {};
+
+			// Strange app protocol or local anchor
+			if (/^(mailto|tel|news|javascript|about|data):/i.test(u) || /^\s*#/.test(u)) {
+				t.source = u;
+				return;
+			}
+
+			// Absolute path with no host, fake host and protocol
+			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
+				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
+
+			// Relative path http:// or protocol relative //path
+			if (!/^\w*:?\/\//.test(u))
+				u = (s.base_uri.protocol || 'http') + '://mce_host' + t.toAbsPath(s.base_uri.path, u);
+
+			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
+			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
+			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
+			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
+				var s = u[i];
+
+				// Zope 3 workaround, they use @@something
+				if (s)
+					s = s.replace(/\(mce_at\)/g, '@@');
+
+				t[v] = s;
+			});
+
+			if (b = s.base_uri) {
+				if (!t.protocol)
+					t.protocol = b.protocol;
+
+				if (!t.userInfo)
+					t.userInfo = b.userInfo;
+
+				if (!t.port && t.host == 'mce_host')
+					t.port = b.port;
+
+				if (!t.host || t.host == 'mce_host')
+					t.host = b.host;
+
+				t.source = '';
+			}
+
+			//t.path = t.path || '/';
+		},
+
+		setPath : function(p) {
+			var t = this;
+
+			p = /^(.*?)\/?(\w+)?$/.exec(p);
+
+			// Update path parts
+			t.path = p[0];
+			t.directory = p[1];
+			t.file = p[2];
+
+			// Rebuild source
+			t.source = '';
+			t.getURI();
+		},
+
+		toRelative : function(u) {
+			var t = this, o;
+
+			if (u === "./")
+				return u;
+
+			u = new tinymce.util.URI(u, {base_uri : t});
+
+			// Not on same domain/port or protocol
+			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
+				return u.getURI();
+
+			o = t.toRelPath(t.path, u.path);
+
+			// Add query
+			if (u.query)
+				o += '?' + u.query;
+
+			// Add anchor
+			if (u.anchor)
+				o += '#' + u.anchor;
+
+			return o;
+		},
+	
+		toAbsolute : function(u, nh) {
+			var u = new tinymce.util.URI(u, {base_uri : this});
+
+			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
+		},
+
+		toRelPath : function(base, path) {
+			var items, bp = 0, out = '', i, l;
+
+			// Split the paths
+			base = base.substring(0, base.lastIndexOf('/'));
+			base = base.split('/');
+			items = path.split('/');
+
+			if (base.length >= items.length) {
+				for (i = 0, l = base.length; i < l; i++) {
+					if (i >= items.length || base[i] != items[i]) {
+						bp = i + 1;
+						break;
+					}
+				}
+			}
+
+			if (base.length < items.length) {
+				for (i = 0, l = items.length; i < l; i++) {
+					if (i >= base.length || base[i] != items[i]) {
+						bp = i + 1;
+						break;
+					}
+				}
+			}
+
+			if (bp == 1)
+				return path;
+
+			for (i = 0, l = base.length - (bp - 1); i < l; i++)
+				out += "../";
+
+			for (i = bp - 1, l = items.length; i < l; i++) {
+				if (i != bp - 1)
+					out += "/" + items[i];
+				else
+					out += items[i];
+			}
+
+			return out;
+		},
+
+		toAbsPath : function(base, path) {
+			var i, nb = 0, o = [], tr, outPath;
+
+			// Split paths
+			tr = /\/$/.test(path) ? '/' : '';
+			base = base.split('/');
+			path = path.split('/');
+
+			// Remove empty chunks
+			each(base, function(k) {
+				if (k)
+					o.push(k);
+			});
+
+			base = o;
+
+			// Merge relURLParts chunks
+			for (i = path.length - 1, o = []; i >= 0; i--) {
+				// Ignore empty or .
+				if (path[i].length == 0 || path[i] == ".")
+					continue;
+
+				// Is parent
+				if (path[i] == '..') {
+					nb++;
+					continue;
+				}
+
+				// Move up
+				if (nb > 0) {
+					nb--;
+					continue;
+				}
+
+				o.push(path[i]);
+			}
+
+			i = base.length - nb;
+
+			// If /a/b/c or /
+			if (i <= 0)
+				outPath = o.reverse().join('/');
+			else
+				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
+
+			// Add front / if it's needed
+			if (outPath.indexOf('/') !== 0)
+				outPath = '/' + outPath;
+
+			// Add traling / if it's needed
+			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
+				outPath += tr;
+
+			return outPath;
+		},
+
+		getURI : function(nh) {
+			var s, t = this;
+
+			// Rebuild source
+			if (!t.source || nh) {
+				s = '';
+
+				if (!nh) {
+					if (t.protocol)
+						s += t.protocol + '://';
+
+					if (t.userInfo)
+						s += t.userInfo + '@';
+
+					if (t.host)
+						s += t.host;
+
+					if (t.port)
+						s += ':' + t.port;
+				}
+
+				if (t.path)
+					s += t.path;
+
+				if (t.query)
+					s += '?' + t.query;
+
+				if (t.anchor)
+					s += '#' + t.anchor;
+
+				t.source = s;
+			}
+
+			return t.source;
+		}
+	});
+})();
+(function() {
+	var each = tinymce.each;
+
+	tinymce.create('static tinymce.util.Cookie', {
+		getHash : function(n) {
+			var v = this.get(n), h;
+
+			if (v) {
+				each(v.split('&'), function(v) {
+					v = v.split('=');
+					h = h || {};
+					h[unescape(v[0])] = unescape(v[1]);
+				});
+			}
+
+			return h;
+		},
+
+		setHash : function(n, v, e, p, d, s) {
+			var o = '';
+
+			each(v, function(v, k) {
+				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
+			});
+
+			this.set(n, o, e, p, d, s);
+		},
+
+		get : function(n) {
+			var c = document.cookie, e, p = n + "=", b;
+
+			// Strict mode
+			if (!c)
+				return;
+
+			b = c.indexOf("; " + p);
+
+			if (b == -1) {
+				b = c.indexOf(p);
+
+				if (b != 0)
+					return null;
+			} else
+				b += 2;
+
+			e = c.indexOf(";", b);
+
+			if (e == -1)
+				e = c.length;
+
+			return unescape(c.substring(b + p.length, e));
+		},
+
+		set : function(n, v, e, p, d, s) {
+			document.cookie = n + "=" + escape(v) +
+				((e) ? "; expires=" + e.toGMTString() : "") +
+				((p) ? "; path=" + escape(p) : "") +
+				((d) ? "; domain=" + d : "") +
+				((s) ? "; secure" : "");
+		},
+
+		remove : function(n, p) {
+			var d = new Date();
+
+			d.setTime(d.getTime() - 1000);
+
+			this.set(n, '', d, p, d);
+		}
+	});
+})();
+tinymce.create('static tinymce.util.JSON', {
+	serialize : function(o) {
+		var i, v, s = tinymce.util.JSON.serialize, t;
+
+		if (o == null)
+			return 'null';
+
+		t = typeof o;
+
+		if (t == 'string') {
+			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
+
+			return '"' + o.replace(/([\u0080-\uFFFF\x00-\x1f\"])/g, function(a, b) {
+				i = v.indexOf(b);
+
+				if (i + 1)
+					return '\\' + v.charAt(i + 1);
+
+				a = b.charCodeAt().toString(16);
+
+				return '\\u' + '0000'.substring(a.length) + a;
+			}) + '"';
+		}
+
+		if (t == 'object') {
+			if (o.hasOwnProperty && o instanceof Array) {
+					for (i=0, v = '['; i<o.length; i++)
+						v += (i > 0 ? ',' : '') + s(o[i]);
+
+					return v + ']';
+				}
+
+				v = '{';
+
+				for (i in o)
+					v += typeof o[i] != 'function' ? (v.length > 1 ? ',"' : '"') + i + '":' + s(o[i]) : '';
+
+				return v + '}';
+		}
+
+		return '' + o;
+	},
+
+	parse : function(s) {
+		try {
+			return eval('(' + s + ')');
+		} catch (ex) {
+			// Ignore
+		}
+	}
+
+	});
+tinymce.create('static tinymce.util.XHR', {
+	send : function(o) {
+		var x, t, w = window, c = 0;
+
+		// Default settings
+		o.scope = o.scope || this;
+		o.success_scope = o.success_scope || o.scope;
+		o.error_scope = o.error_scope || o.scope;
+		o.async = o.async === false ? false : true;
+		o.data = o.data || '';
+
+		function get(s) {
+			x = 0;
+
+			try {
+				x = new ActiveXObject(s);
+			} catch (ex) {
+			}
+
+			return x;
+		};
+
+		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
+
+		if (x) {
+			if (x.overrideMimeType)
+				x.overrideMimeType(o.content_type);
+
+			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
+
+			if (o.content_type)
+				x.setRequestHeader('Content-Type', o.content_type);
+
+			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+
+			x.send(o.data);
+
+			function ready() {
+				if (!o.async || x.readyState == 4 || c++ > 10000) {
+					if (o.success && c < 10000 && x.status == 200)
+						o.success.call(o.success_scope, '' + x.responseText, x, o);
+					else if (o.error)
+						o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
+
+					x = null;
+				} else
+					w.setTimeout(ready, 10);
+			};
+
+			// Syncronous request
+			if (!o.async)
+				return ready();
+
+			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
+			t = w.setTimeout(ready, 10);
+		}
+	}
+});
+(function() {
+	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
+
+	tinymce.create('tinymce.util.JSONRequest', {
+		JSONRequest : function(s) {
+			this.settings = extend({
+			}, s);
+			this.count = 0;
+		},
+
+		send : function(o) {
+			var ecb = o.error, scb = o.success;
+
+			o = extend(this.settings, o);
+
+			o.success = function(c, x) {
+				c = JSON.parse(c);
+
+				if (typeof(c) == 'undefined') {
+					c = {
+						error : 'JSON Parse error.'
+					};
+				}
+
+				if (c.error)
+					ecb.call(o.error_scope || o.scope, c.error, x);
+				else
+					scb.call(o.success_scope || o.scope, c.result);
+			};
+
+			o.error = function(ty, x) {
+				ecb.call(o.error_scope || o.scope, ty, x);
+			};
+
+			o.data = JSON.serialize({
+				id : o.id || 'c' + (this.count++),
+				method : o.method,
+				params : o.params
+			});
+
+			// JSON content type for Ruby on rails. Bug: #1883287
+			o.content_type = 'application/json';
+
+			XHR.send(o);
+		},
+
+		'static' : {
+			sendRPC : function(o) {
+				return new tinymce.util.JSONRequest().send(o);
+			}
+		}
+	});
+}());(function(tinymce) {
+	// Shorten names
+	var each = tinymce.each, is = tinymce.is;
+	var isWebKit = tinymce.isWebKit, isIE = tinymce.isIE;
+
+	tinymce.create('tinymce.dom.DOMUtils', {
+		doc : null,
+		root : null,
+		files : null,
+		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
+		props : {
+			"for" : "htmlFor",
+			"class" : "className",
+			className : "className",
+			checked : "checked",
+			disabled : "disabled",
+			maxlength : "maxLength",
+			readonly : "readOnly",
+			selected : "selected",
+			value : "value",
+			id : "id",
+			name : "name",
+			type : "type"
+		},
+
+		DOMUtils : function(d, s) {
+			var t = this;
+
+			t.doc = d;
+			t.win = window;
+			t.files = {};
+			t.cssFlicker = false;
+			t.counter = 0;
+			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat"; 
+			t.stdMode = d.documentMode === 8;
+
+			t.settings = s = tinymce.extend({
+				keep_values : false,
+				hex_colors : 1,
+				process_html : 1
+			}, s);
+
+			// Fix IE6SP2 flicker and check it failed for pre SP2
+			if (tinymce.isIE6) {
+				try {
+					d.execCommand('BackgroundImageCache', false, true);
+				} catch (e) {
+					t.cssFlicker = true;
+				}
+			}
+
+			tinymce.addUnload(t.destroy, t);
+		},
+
+		getRoot : function() {
+			var t = this, s = t.settings;
+
+			return (s && t.get(s.root_element)) || t.doc.body;
+		},
+
+		getViewPort : function(w) {
+			var d, b;
+
+			w = !w ? this.win : w;
+			d = w.document;
+			b = this.boxModel ? d.documentElement : d.body;
+
+			// Returns viewport size excluding scrollbars
+			return {
+				x : w.pageXOffset || b.scrollLeft,
+				y : w.pageYOffset || b.scrollTop,
+				w : w.innerWidth || b.clientWidth,
+				h : w.innerHeight || b.clientHeight
+			};
+		},
+
+		getRect : function(e) {
+			var p, t = this, sr;
+
+			e = t.get(e);
+			p = t.getPos(e);
+			sr = t.getSize(e);
+
+			return {
+				x : p.x,
+				y : p.y,
+				w : sr.w,
+				h : sr.h
+			};
+		},
+
+		getSize : function(e) {
+			var t = this, w, h;
+
+			e = t.get(e);
+			w = t.getStyle(e, 'width');
+			h = t.getStyle(e, 'height');
+
+			// Non pixel value, then force offset/clientWidth
+			if (w.indexOf('px') === -1)
+				w = 0;
+
+			// Non pixel value, then force offset/clientWidth
+			if (h.indexOf('px') === -1)
+				h = 0;
+
+			return {
+				w : parseInt(w) || e.offsetWidth || e.clientWidth,
+				h : parseInt(h) || e.offsetHeight || e.clientHeight
+			};
+		},
+
+		getParent : function(n, f, r) {
+			return this.getParents(n, f, r, false);
+		},
+
+		getParents : function(n, f, r, c) {
+			var t = this, na, se = t.settings, o = [];
+
+			n = t.get(n);
+			c = c === undefined;
+
+			if (se.strict_root)
+				r = r || t.getRoot();
+
+			// Wrap node name as func
+			if (is(f, 'string')) {
+				na = f;
+
+				if (f === '*') {
+					f = function(n) {return n.nodeType == 1;};
+				} else {
+					f = function(n) {
+						return t.is(n, na);
+					};
+				}
+			}
+
+			while (n) {
+				if (n == r || !n.nodeType || n.nodeType === 9)
+					break;
+
+				if (!f || f(n)) {
+					if (c)
+						o.push(n);
+					else
+						return n;
+				}
+
+				n = n.parentNode;
+			}
+
+			return c ? o : null;
+		},
+
+		get : function(e) {
+			var n;
+
+			if (e && this.doc && typeof(e) == 'string') {
+				n = e;
+				e = this.doc.getElementById(e);
+
+				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
+				if (e && e.id !== n)
+					return this.doc.getElementsByName(n)[1];
+			}
+
+			return e;
+		},
+
+		getNext : function(node, selector) {
+			return this._findSib(node, selector, 'nextSibling');
+		},
+
+		getPrev : function(node, selector) {
+			return this._findSib(node, selector, 'previousSibling');
+		},
+
+
+		add : function(p, n, a, h, c) {
+			var t = this;
+
+			return this.run(p, function(p) {
+				var e, k;
+
+				e = is(n, 'string') ? t.doc.createElement(n) : n;
+				t.setAttribs(e, a);
+
+				if (h) {
+					if (h.nodeType)
+						e.appendChild(h);
+					else
+						t.setHTML(e, h);
+				}
+
+				return !c ? p.appendChild(e) : e;
+			});
+		},
+
+		create : function(n, a, h) {
+			return this.add(this.doc.createElement(n), n, a, h, 1);
+		},
+
+		createHTML : function(n, a, h) {
+			var o = '', t = this, k;
+
+			o += '<' + n;
+
+			for (k in a) {
+				if (a.hasOwnProperty(k))
+					o += ' ' + k + '="' + t.encode(a[k]) + '"';
+			}
+
+			if (tinymce.is(h))
+				return o + '>' + h + '</' + n + '>';
+
+			return o + ' />';
+		},
+
+		remove : function(n, k) {
+			var t = this;
+
+			return this.run(n, function(n) {
+				var p, g, i;
+
+				p = n.parentNode;
+
+				if (!p)
+					return null;
+
+				if (k) {
+					for (i = n.childNodes.length - 1; i >= 0; i--)
+						t.insertAfter(n.childNodes[i], n);
+
+					//each(n.childNodes, function(c) {
+					//	p.insertBefore(c.cloneNode(true), n);
+					//});
+				}
+
+				// Fix IE psuedo leak
+				if (t.fixPsuedoLeaks) {
+					p = n.cloneNode(true);
+					k = 'IELeakGarbageBin';
+					g = t.get(k) || t.add(t.doc.body, 'div', {id : k, style : 'display:none'});
+					g.appendChild(n);
+					g.innerHTML = '';
+
+					return p;
+				}
+
+				return p.removeChild(n);
+			});
+		},
+
+		setStyle : function(n, na, v) {
+			var t = this;
+
+			return t.run(n, function(e) {
+				var s, i;
+
+				s = e.style;
+
+				// Camelcase it, if needed
+				na = na.replace(/-(\D)/g, function(a, b){
+					return b.toUpperCase();
+				});
+
+				// Default px suffix on these
+				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
+					v += 'px';
+
+				switch (na) {
+					case 'opacity':
+						// IE specific opacity
+						if (isIE) {
+							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
+
+							if (!n.currentStyle || !n.currentStyle.hasLayout)
+								s.display = 'inline-block';
+						}
+
+						// Fix for older browsers
+						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
+						break;
+
+					case 'float':
+						isIE ? s.styleFloat = v : s.cssFloat = v;
+						break;
+					
+					default:
+						s[na] = v || '';
+				}
+
+				// Force update of the style data
+				if (t.settings.update_styles)
+					t.setAttrib(e, 'mce_style');
+			});
+		},
+
+		getStyle : function(n, na, c) {
+			n = this.get(n);
+
+			if (!n)
+				return false;
+
+			// Gecko
+			if (this.doc.defaultView && c) {
+				// Remove camelcase
+				na = na.replace(/[A-Z]/g, function(a){
+					return '-' + a;
+				});
+
+				try {
+					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
+				} catch (ex) {
+					// Old safari might fail
+					return null;
+				}
+			}
+
+			// Camelcase it, if needed
+			na = na.replace(/-(\D)/g, function(a, b){
+				return b.toUpperCase();
+			});
+
+			if (na == 'float')
+				na = isIE ? 'styleFloat' : 'cssFloat';
+
+			// IE & Opera
+			if (n.currentStyle && c)
+				return n.currentStyle[na];
+
+			return n.style[na];
+		},
+
+		setStyles : function(e, o) {
+			var t = this, s = t.settings, ol;
+
+			ol = s.update_styles;
+			s.update_styles = 0;
+
+			each(o, function(v, n) {
+				t.setStyle(e, n, v);
+			});
+
+			// Update style info
+			s.update_styles = ol;
+			if (s.update_styles)
+				t.setAttrib(e, s.cssText);
+		},
+
+		setAttrib : function(e, n, v) {
+			var t = this;
+
+			// Whats the point
+			if (!e || !n)
+				return;
+
+			// Strict XML mode
+			if (t.settings.strict)
+				n = n.toLowerCase();
+
+			return this.run(e, function(e) {
+				var s = t.settings;
+
+				switch (n) {
+					case "style":
+						if (!is(v, 'string')) {
+							each(v, function(v, n) {
+								t.setStyle(e, n, v);
+							});
+
+							return;
+						}
+
+						// No mce_style for elements with these since they might get resized by the user
+						if (s.keep_values) {
+							if (v && !t._isRes(v))
+								e.setAttribute('mce_style', v, 2);
+							else
+								e.removeAttribute('mce_style', 2);
+						}
+
+						e.style.cssText = v;
+						break;
+
+					case "class":
+						e.className = v || ''; // Fix IE null bug
+						break;
+
+					case "src":
+					case "href":
+						if (s.keep_values) {
+							if (s.url_converter)
+								v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
+
+							t.setAttrib(e, 'mce_' + n, v, 2);
+						}
+
+						break;
+					
+					case "shape":
+						e.setAttribute('mce_style', v);
+						break;
+				}
+
+				if (is(v) && v !== null && v.length !== 0)
+					e.setAttribute(n, '' + v, 2);
+				else
+					e.removeAttribute(n, 2);
+			});
+		},
+
+		setAttribs : function(e, o) {
+			var t = this;
+
+			return this.run(e, function(e) {
+				each(o, function(v, n) {
+					t.setAttrib(e, n, v);
+				});
+			});
+		},
+
+		getAttrib : function(e, n, dv) {
+			var v, t = this;
+
+			e = t.get(e);
+
+			if (!e || e.nodeType !== 1)
+				return false;
+
+			if (!is(dv))
+				dv = '';
+
+			// Try the mce variant for these
+			if (/^(src|href|style|coords|shape)$/.test(n)) {
+				v = e.getAttribute("mce_" + n);
+
+				if (v)
+					return v;
+			}
+
+			if (isIE && t.props[n]) {
+				v = e[t.props[n]];
+				v = v && v.nodeValue ? v.nodeValue : v;
+			}
+
+			if (!v)
+				v = e.getAttribute(n, 2);
+
+			// Check boolean attribs
+			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
+				if (e[t.props[n]] === true && v === '')
+					return n;
+
+				return v ? n : '';
+			}
+
+			// Inner input elements will override attributes on form elements
+			if (e.nodeName === "FORM" && e.getAttributeNode(n))
+				return e.getAttributeNode(n).nodeValue;
+
+			if (n === 'style') {
+				v = v || e.style.cssText;
+
+				if (v) {
+					v = t.serializeStyle(t.parseStyle(v));
+
+					if (t.settings.keep_values && !t._isRes(v))
+						e.setAttribute('mce_style', v);
+				}
+			}
+
+			// Remove Apple and WebKit stuff
+			if (isWebKit && n === "class" && v)
+				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
+
+			// Handle IE issues
+			if (isIE) {
+				switch (n) {
+					case 'rowspan':
+					case 'colspan':
+						// IE returns 1 as default value
+						if (v === 1)
+							v = '';
+
+						break;
+
+					case 'size':
+						// IE returns +0 as default value for size
+						if (v === '+0' || v === 20 || v === 0)
+							v = '';
+
+						break;
+
+					case 'width':
+					case 'height':
+					case 'vspace':
+					case 'checked':
+					case 'disabled':
+					case 'readonly':
+						if (v === 0)
+							v = '';
+
+						break;
+
+					case 'hspace':
+						// IE returns -1 as default value
+						if (v === -1)
+							v = '';
+
+						break;
+
+					case 'maxlength':
+					case 'tabindex':
+						// IE returns default value
+						if (v === 32768 || v === 2147483647 || v === '32768')
+							v = '';
+
+						break;
+
+					case 'multiple':
+					case 'compact':
+					case 'noshade':
+					case 'nowrap':
+						if (v === 65535)
+							return n;
+
+						return dv;
+
+					case 'shape':
+						v = v.toLowerCase();
+						break;
+
+					default:
+						// IE has odd anonymous function for event attributes
+						if (n.indexOf('on') === 0 && v)
+							v = ('' + v).replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1');
+				}
+			}
+
+			return (v !== undefined && v !== null && v !== '') ? '' + v : dv;
+		},
+
+		getPos : function(n, ro) {
+			var t = this, x = 0, y = 0, e, d = t.doc, r;
+
+			n = t.get(n);
+			ro = ro || d.body;
+
+			if (n) {
+				// Use getBoundingClientRect on IE, Opera has it but it's not perfect
+				if (isIE && !t.stdMode) {
+					n = n.getBoundingClientRect();
+					e = t.boxModel ? d.documentElement : d.body;
+					x = t.getStyle(t.select('html')[0], 'borderWidth'); // Remove border
+					x = (x == 'medium' || t.boxModel && !t.isIE6) && 2 || x;
+					n.top += t.win.self != t.win.top ? 2 : 0; // IE adds some strange extra cord if used in a frameset
+
+					return {x : n.left + e.scrollLeft - x, y : n.top + e.scrollTop - x};
+				}
+
+				r = n;
+				while (r && r != ro && r.nodeType) {
+					x += r.offsetLeft || 0;
+					y += r.offsetTop || 0;
+					r = r.offsetParent;
+				}
+
+				r = n.parentNode;
+				while (r && r != ro && r.nodeType) {
+					x -= r.scrollLeft || 0;
+					y -= r.scrollTop || 0;
+					r = r.parentNode;
+				}
+			}
+
+			return {x : x, y : y};
+		},
+
+		parseStyle : function(st) {
+			var t = this, s = t.settings, o = {};
+
+			if (!st)
+				return o;
+
+			function compress(p, s, ot) {
+				var t, r, b, l;
+
+				// Get values and check it it needs compressing
+				t = o[p + '-top' + s];
+				if (!t)
+					return;
+
+				r = o[p + '-right' + s];
+				if (t != r)
+					return;
+
+				b = o[p + '-bottom' + s];
+				if (r != b)
+					return;
+
+				l = o[p + '-left' + s];
+				if (b != l)
+					return;
+
+				// Compress
+				o[ot] = l;
+				delete o[p + '-top' + s];
+				delete o[p + '-right' + s];
+				delete o[p + '-bottom' + s];
+				delete o[p + '-left' + s];
+			};
+
+			function compress2(ta, a, b, c) {
+				var t;
+
+				t = o[a];
+				if (!t)
+					return;
+
+				t = o[b];
+				if (!t)
+					return;
+
+				t = o[c];
+				if (!t)
+					return;
+
+				// Compress
+				o[ta] = o[a] + ' ' + o[b] + ' ' + o[c];
+				delete o[a];
+				delete o[b];
+				delete o[c];
+			};
+
+			st = st.replace(/&(#?[a-z0-9]+);/g, '&$1_MCE_SEMI_'); // Protect entities
+
+			each(st.split(';'), function(v) {
+				var sv, ur = [];
+
+				if (v) {
+					v = v.replace(/_MCE_SEMI_/g, ';'); // Restore entities
+					v = v.replace(/url\([^\)]+\)/g, function(v) {ur.push(v);return 'url(' + ur.length + ')';});
+					v = v.split(':');
+					sv = tinymce.trim(v[1]);
+					sv = sv.replace(/url\(([^\)]+)\)/g, function(a, b) {return ur[parseInt(b) - 1];});
+
+					sv = sv.replace(/rgb\([^\)]+\)/g, function(v) {
+						return t.toHex(v);
+					});
+
+					if (s.url_converter) {
+						sv = sv.replace(/url\([\'\"]?([^\)\'\"]+)[\'\"]?\)/g, function(x, c) {
+							return 'url(' + s.url_converter.call(s.url_converter_scope || t, t.decode(c), 'style', null) + ')';
+						});
+					}
+
+					o[tinymce.trim(v[0]).toLowerCase()] = sv;
+				}
+			});
+
+			compress("border", "", "border");
+			compress("border", "-width", "border-width");
+			compress("border", "-color", "border-color");
+			compress("border", "-style", "border-style");
+			compress("padding", "", "padding");
+			compress("margin", "", "margin");
+			compress2('border', 'border-width', 'border-style', 'border-color');
+
+			if (isIE) {
+				// Remove pointless border
+				if (o.border == 'medium none')
+					o.border = '';
+			}
+
+			return o;
+		},
+
+		serializeStyle : function(o) {
+			var s = '';
+
+			each(o, function(v, k) {
+				if (k && v) {
+					if (tinymce.isGecko && k.indexOf('-moz-') === 0)
+						return;
+
+					switch (k) {
+						case 'color':
+						case 'background-color':
+							v = v.toLowerCase();
+							break;
+					}
+
+					s += (s ? ' ' : '') + k + ': ' + v + ';';
+				}
+			});
+
+			return s;
+		},
+
+		loadCSS : function(u) {
+			var t = this, d = t.doc, head;
+
+			if (!u)
+				u = '';
+
+			head = t.select('head')[0];
+
+			each(u.split(','), function(u) {
+				var link;
+
+				if (t.files[u])
+					return;
+
+				t.files[u] = true;
+				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
+
+				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
+				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
+				// It's ugly but it seems to work fine.
+				if (isIE && d.documentMode) {
+					link.onload = function() {
+						d.recalc();
+						link.onload = null;
+					};
+				}
+
+				head.appendChild(link);
+			});
+		},
+
+		addClass : function(e, c) {
+			return this.run(e, function(e) {
+				var o;
+
+				if (!c)
+					return 0;
+
+				if (this.hasClass(e, c))
+					return e.className;
+
+				o = this.removeClass(e, c);
+
+				return e.className = (o != '' ? (o + ' ') : '') + c;
+			});
+		},
+
+		removeClass : function(e, c) {
+			var t = this, re;
+
+			return t.run(e, function(e) {
+				var v;
+
+				if (t.hasClass(e, c)) {
+					if (!re)
+						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
+
+					v = e.className.replace(re, ' ');
+
+					return e.className = tinymce.trim(v != ' ' ? v : '');
+				}
+
+				return e.className;
+			});
+		},
+
+		hasClass : function(n, c) {
+			n = this.get(n);
+
+			if (!n || !c)
+				return false;
+
+			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
+		},
+
+		show : function(e) {
+			return this.setStyle(e, 'display', 'block');
+		},
+
+		hide : function(e) {
+			return this.setStyle(e, 'display', 'none');
+		},
+
+		isHidden : function(e) {
+			e = this.get(e);
+
+			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
+		},
+
+		uniqueId : function(p) {
+			return (!p ? 'mce_' : p) + (this.counter++);
+		},
+
+		setHTML : function(e, h) {
+			var t = this;
+
+			return this.run(e, function(e) {
+				var x, i, nl, n, p, x;
+
+				h = t.processHTML(h);
+
+				if (isIE) {
+					function set() {
+						try {
+							// IE will remove comments from the beginning
+							// unless you padd the contents with something
+							e.innerHTML = '<br />' + h;
+							e.removeChild(e.firstChild);
+						} catch (ex) {
+							// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
+							// This seems to fix this problem
+
+							// Remove all child nodes
+							while (e.firstChild)
+								e.firstChild.removeNode();
+
+							// Create new div with HTML contents and a BR infront to keep comments
+							x = t.create('div');
+							x.innerHTML = '<br />' + h;
+
+							// Add all children from div to target
+							each (x.childNodes, function(n, i) {
+								// Skip br element
+								if (i)
+									e.appendChild(n);
+							});
+						}
+					};
+
+					// IE has a serious bug when it comes to paragraphs it can produce an invalid
+					// DOM tree if contents like this <p><ul><li>Item 1</li></ul></p> is inserted
+					// It seems to be that IE doesn't like a root block element placed inside another root block element
+					if (t.settings.fix_ie_paragraphs)
+						h = h.replace(/<p><\/p>|<p([^>]+)><\/p>|<p[^\/+]\/>/gi, '<p$1 mce_keep="true"> </p>');
+
+					set();
+
+					if (t.settings.fix_ie_paragraphs) {
+						// Check for odd paragraphs this is a sign of a broken DOM
+						nl = e.getElementsByTagName("p");
+						for (i = nl.length - 1, x = 0; i >= 0; i--) {
+							n = nl[i];
+
+							if (!n.hasChildNodes()) {
+								if (!n.mce_keep) {
+									x = 1; // Is broken
+									break;
+								}
+
+								n.removeAttribute('mce_keep');
+							}
+						}
+					}
+
+					// Time to fix the madness IE left us
+					if (x) {
+						// So if we replace the p elements with divs and mark them and then replace them back to paragraphs
+						// after we use innerHTML we can fix the DOM tree
+						h = h.replace(/<p ([^>]+)>|<p>/ig, '<div $1 mce_tmp="1">');
+						h = h.replace(/<\/p>/g, '</div>');
+
+						// Set the new HTML with DIVs
+						set();
+
+						// Replace all DIV elements with he mce_tmp attibute back to paragraphs
+						// This is needed since IE has a annoying bug see above for details
+						// This is a slow process but it has to be done. :(
+						if (t.settings.fix_ie_paragraphs) {
+							nl = e.getElementsByTagName("DIV");
+							for (i = nl.length - 1; i >= 0; i--) {
+								n = nl[i];
+
+								// Is it a temp div
+								if (n.mce_tmp) {
+									// Create new paragraph
+									p = t.doc.createElement('p');
+
+									// Copy all attributes
+									n.cloneNode(false).outerHTML.replace(/([a-z0-9\-_]+)=/gi, function(a, b) {
+										var v;
+
+										if (b !== 'mce_tmp') {
+											v = n.getAttribute(b);
+
+											if (!v && b === 'class')
+												v = n.className;
+
+											p.setAttribute(b, v);
+										}
+									});
+
+									// Append all children to new paragraph
+									for (x = 0; x<n.childNodes.length; x++)
+										p.appendChild(n.childNodes[x].cloneNode(true));
+
+									// Replace div with new paragraph
+									n.swapNode(p);
+								}
+							}
+						}
+					}
+				} else
+					e.innerHTML = h;
+
+				return h;
+			});
+		},
+
+		processHTML : function(h) {
+			var t = this, s = t.settings, codeBlocks = [];
+
+			if (!s.process_html)
+				return h;
+
+			// Convert strong and em to b and i in FF since it can't handle them
+			if (tinymce.isGecko) {
+				h = h.replace(/<(\/?)strong>|<strong( [^>]+)>/gi, '<$1b$2>');
+				h = h.replace(/<(\/?)em>|<em( [^>]+)>/gi, '<$1i$2>');
+			} else if (isIE) {
+				h = h.replace(/'/g, '''); // IE can't handle apos
+				h = h.replace(/\s+(disabled|checked|readonly|selected)\s*=\s*[\"\']?(false|0)[\"\']?/gi, ''); // IE doesn't handle default values correct
+			}
+
+			// Fix some issues
+			h = h.replace(/<a( )([^>]+)\/>|<a\/>/gi, '<a$1$2></a>'); // Force open
+
+			// Store away src and href in mce_src and mce_href since browsers mess them up
+			if (s.keep_values) {
+				// Wrap scripts and styles in comments for serialization purposes
+				if (/<script|noscript|style/i.test(h)) {
+					function trim(s) {
+						// Remove prefix and suffix code for element
+						s = s.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n');
+						s = s.replace(/^[\r\n]*|[\r\n]*$/g, '');
+						s = s.replace(/^\s*(\/\/\s*<!--|\/\/\s*<!\[CDATA\[|<!--|<!\[CDATA\[)[\r\n]*/g, '');
+						s = s.replace(/\s*(\/\/\s*\]\]>|\/\/\s*-->|\]\]>|-->|\]\]-->)\s*$/g, '');
+
+						return s;
+					};
+
+					// Wrap the script contents in CDATA and keep them from executing
+					h = h.replace(/<script([^>]+|)>([\s\S]*?)<\/script>/gi, function(v, attribs, text) {
+						// Force type attribute
+						if (!attribs)
+							attribs = ' type="text/javascript"';
+
+						// Convert the src attribute of the scripts
+						attribs = attribs.replace(/src=\"([^\"]+)\"?/i, function(a, url) {
+							if (s.url_converter)
+								url = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(url), 'src', 'script'));
+
+							return 'mce_src="' + url + '"';
+						});
+
+						// Wrap text contents
+						if (tinymce.trim(text)) {
+							codeBlocks.push(trim(text));
+							text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n// -->';
+						}
+
+						return '<mce:script' + attribs + '>' + text + '</mce:script>';
+					});
+
+					// Wrap style elements
+					h = h.replace(/<style([^>]+|)>([\s\S]*?)<\/style>/gi, function(v, attribs, text) {
+						// Wrap text contents
+						if (text) {
+							codeBlocks.push(trim(text));
+							text = '<!--\nMCE_SCRIPT:' + (codeBlocks.length - 1) + '\n-->';
+						}
+
+						return '<mce:style' + attribs + '>' + text + '</mce:style><style ' + attribs + ' mce_bogus="1">' + text + '</style>';
+					});
+
+					// Wrap noscript elements
+					h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
+						return '<mce:noscript' + attribs + '><!--' + t.encode(text).replace(/--/g, '--') + '--></mce:noscript>';
+					});
+				}
+
+				h = h.replace(/<!\[CDATA\[([\s\S]+)\]\]>/g, '<!--[CDATA[$1]]-->');
+
+				// Remove false bool attributes and force attributes into xhtml style attr="attr"
+				h = h.replace(/<([\w:]+) [^>]*(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)[^>]*>/gi, function(val) {
+					function handle(val, name, value) {
+						// Remove false/0 attribs
+						if (value === 'false' || value === '0')
+							return '';
+
+						return ' ' + name + '="' + name + '"';
+					};
+
+					val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=[\"]([^\"]+)[\"]/gi, handle); // W3C
+					val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=[\']([^\']+)[\']/gi, handle); // W3C
+					val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)=([^\s\"\'>]+)/gi, handle); // IE
+					val = val.replace(/ (checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)([\s>])/gi, ' $1="$1"$2'); // Force attr="attr"
+
+					return val;
+				});
+
+				// Process all tags with src, href or style
+				h = h.replace(/<([\w:]+) [^>]*(src|href|style|shape|coords)[^>]*>/gi, function(a, n) {
+					function handle(m, b, c) {
+						var u = c;
+
+						// Tag already got a mce_ version
+						if (a.indexOf('mce_' + b) != -1)
+							return m;
+
+						if (b == 'style') {
+							// No mce_style for elements with these since they might get resized by the user
+							if (t._isRes(c))
+								return m;
+
+							// Parse and serialize the style to convert for example uppercase styles like "BORDER: 1px"
+							u = t.encode(t.serializeStyle(t.parseStyle(u)));
+						} else if (b != 'coords' && b != 'shape') {
+							if (s.url_converter)
+								u = t.encode(s.url_converter.call(s.url_converter_scope || t, t.decode(c), b, n));
+						}
+
+						return ' ' + b + '="' + c + '" mce_' + b + '="' + u + '"';
+					};
+
+					a = a.replace(/ (src|href|style|coords|shape)=[\"]([^\"]+)[\"]/gi, handle); // W3C
+					a = a.replace(/ (src|href|style|coords|shape)=[\']([^\']+)[\']/gi, handle); // W3C
+
+					return a.replace(/ (src|href|style|coords|shape)=([^\s\"\'>]+)/gi, handle); // IE
+				});
+
+				// Restore script blocks
+				h = h.replace(/MCE_SCRIPT:([0-9]+)/g, function(val, idx) {
+					return codeBlocks[idx];
+				});
+			}
+
+			return h;
+		},
+
+		getOuterHTML : function(e) {
+			var d;
+
+			e = this.get(e);
+
+			if (!e)
+				return null;
+
+			if (e.outerHTML !== undefined)
+				return e.outerHTML;
+
+			d = (e.ownerDocument || this.doc).createElement("body");
+			d.appendChild(e.cloneNode(true));
+
+			return d.innerHTML;
+		},
+
+		setOuterHTML : function(e, h, d) {
+			var t = this;
+
+			function setHTML(e, h, d) {
+				var n, tp;
+				
+				tp = d.createElement("body");
+				tp.innerHTML = h;
+
+				n = tp.lastChild;
+				while (n) {
+					t.insertAfter(n.cloneNode(true), e);
+					n = n.previousSibling;
+				}
+
+				t.remove(e);
+			};
+
+			return this.run(e, function(e) {
+				e = t.get(e);
+
+				// Only set HTML on elements
+				if (e.nodeType == 1) {
+					d = d || e.ownerDocument || t.doc;
+
+					if (isIE) {
+						try {
+							// Try outerHTML for IE it sometimes produces an unknown runtime error
+							if (isIE && e.nodeType == 1)
+								e.outerHTML = h;
+							else
+								setHTML(e, h, d);
+						} catch (ex) {
+							// Fix for unknown runtime error
+							setHTML(e, h, d);
+						}
+					} else
+						setHTML(e, h, d);
+				}
+			});
+		},
+
+		decode : function(s) {
+			var e, n, v;
+
+			// Look for entities to decode
+			if (/&[^;]+;/.test(s)) {
+				// Decode the entities using a div element not super efficient but less code
+				e = this.doc.createElement("div");
+				e.innerHTML = s;
+				n = e.firstChild;
+				v = '';
+
+				if (n) {
+					do {
+						v += n.nodeValue;
+					} while (n.nextSibling);
+				}
+
+				return v || s;
+			}
+
+			return s;
+		},
+
+		encode : function(s) {
+			return s ? ('' + s).replace(/[<>&\"]/g, function (c, b) {
+				switch (c) {
+					case '&':
+						return '&';
+
+					case '"':
+						return '"';
+
+					case '<':
+						return '<';
+
+					case '>':
+						return '>';
+				}
+
+				return c;
+			}) : s;
+		},
+
+		insertAfter : function(n, r) {
+			var t = this;
+
+			r = t.get(r);
+
+			return this.run(n, function(n) {
+				var p, ns;
+
+				p = r.parentNode;
+				ns = r.nextSibling;
+
+				if (ns)
+					p.insertBefore(n, ns);
+				else
+					p.appendChild(n);
+
+				return n;
+			});
+		},
+
+		isBlock : function(n) {
+			if (n.nodeType && n.nodeType !== 1)
+				return false;
+
+			n = n.nodeName || n;
+
+			return /^(H[1-6]|HR|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TH|TBODY|TR|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP)$/.test(n);
+		},
+
+		replace : function(n, o, k) {
+			var t = this;
+
+			if (is(o, 'array'))
+				n = n.cloneNode(true);
+
+			return t.run(o, function(o) {
+				if (k) {
+					each(o.childNodes, function(c) {
+						n.appendChild(c.cloneNode(true));
+					});
+				}
+
+				// Fix IE psuedo leak for elements since replacing elements if fairly common
+				// Will break parentNode for some unknown reason
+				if (t.fixPsuedoLeaks && o.nodeType === 1) {
+					o.parentNode.insertBefore(n, o);
+					t.remove(o);
+					return n;
+				}
+
+				return o.parentNode.replaceChild(n, o);
+			});
+		},
+
+		findCommonAncestor : function(a, b) {
+			var ps = a, pe;
+
+			while (ps) {
+				pe = b;
+
+				while (pe && ps != pe)
+					pe = pe.parentNode;
+
+				if (ps == pe)
+					break;
+
+				ps = ps.parentNode;
+			}
+
+			if (!ps && a.ownerDocument)
+				return a.ownerDocument.documentElement;
+
+			return ps;
+		},
+
+		toHex : function(s) {
+			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
+
+			function hex(s) {
+				s = parseInt(s).toString(16);
+
+				return s.length > 1 ? s : '0' + s; // 0 -> 00
+			};
+
+			if (c) {
+				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
+
+				return s;
+			}
+
+			return s;
+		},
+
+		getClasses : function() {
+			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
+
+			if (t.classes)
+				return t.classes;
+
+			function addClasses(s) {
+				// IE style imports
+				each(s.imports, function(r) {
+					addClasses(r);
+				});
+
+				each(s.cssRules || s.rules, function(r) {
+					// Real type or fake it on IE
+					switch (r.type || 1) {
+						// Rule
+						case 1:
+							if (r.selectorText) {
+								each(r.selectorText.split(','), function(v) {
+									v = v.replace(/^\s*|\s*$|^\s\./g, "");
+
+									// Is internal or it doesn't contain a class
+									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
+										return;
+
+									// Remove everything but class name
+									ov = v;
+									v = v.replace(/.*\.([a-z0-9_\-]+).*/i, '$1');
+
+									// Filter classes
+									if (f && !(v = f(v, ov)))
+										return;
+
+									if (!lo[v]) {
+										cl.push({'class' : v});
+										lo[v] = 1;
+									}
+								});
+							}
+							break;
+
+						// Import
+						case 3:
+							addClasses(r.styleSheet);
+							break;
+					}
+				});
+			};
+
+			try {
+				each(t.doc.styleSheets, addClasses);
+			} catch (ex) {
+				// Ignore
+			}
+
+			if (cl.length > 0)
+				t.classes = cl;
+
+			return cl;
+		},
+
+		run : function(e, f, s) {
+			var t = this, o;
+
+			if (t.doc && typeof(e) === 'string')
+				e = t.get(e);
+
+			if (!e)
+				return false;
+
+			s = s || this;
+			if (!e.nodeType && (e.length || e.length === 0)) {
+				o = [];
+
+				each(e, function(e, i) {
+					if (e) {
+						if (typeof(e) == 'string')
+							e = t.doc.getElementById(e);
+
+						o.push(f.call(s, e, i));
+					}
+				});
+
+				return o;
+			}
+
+			return f.call(s, e);
+		},
+
+		getAttribs : function(n) {
+			var o;
+
+			n = this.get(n);
+
+			if (!n)
+				return [];
+
+			if (isIE) {
+				o = [];
+
+				// Object will throw exception in IE
+				if (n.nodeName == 'OBJECT')
+					return n.attributes;
+
+				// IE doesn't keep the selected attribute if you clone option elements
+				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
+					o.push({specified : 1, nodeName : 'selected'});
+
+				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
+				n.cloneNode(false).outerHTML.replace(/<\/?[\w:]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=\w+|>/gi, '').replace(/[\w:]+/gi, function(a) {
+					o.push({specified : 1, nodeName : a});
+				});
+
+				return o;
+			}
+
+			return n.attributes;
+		},
+
+		destroy : function(s) {
+			var t = this;
+
+			if (t.events)
+				t.events.destroy();
+
+			t.win = t.doc = t.root = t.events = null;
+
+			// Manual destroy then remove unload handler
+			if (!s)
+				tinymce.removeUnload(t.destroy);
+		},
+
+		createRng : function() {
+			var d = this.doc;
+
+			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
+		},
+
+		split : function(pe, e, re) {
+			var t = this, r = t.createRng(), bef, aft, pa;
+
+			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sence
+			// but we don't want that in our code since it serves no purpose
+			// For example if this is chopped:
+			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
+			// would produce:
+			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
+			// this function will then trim of empty edges and produce:
+			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
+			function trimEdge(n, na) {
+				n = n[na];
+
+				if (n && n[na] && n[na].nodeType == 1 && isEmpty(n[na]))
+					t.remove(n[na]);
+			};
+
+			function isEmpty(n) {
+				n = t.getOuterHTML(n);
+				n = n.replace(/<(img|hr|table)/gi, '-'); // Keep these convert them to - chars
+				n = n.replace(/<[^>]+>/g, ''); // Remove all tags
+
+				return n.replace(/[ \t\r\n]+| | /g, '') == '';
+			};
+
+			// Added until Gecko can create real HTML documents using implementation.createHTMLDocument
+			// this is to future proof it if Gecko decides to implement the error checking for range methods.
+			function nodeIndex(n) {
+				var i = 0;
+
+				while (n.previousSibling) {
+					i++;
+					n = n.previousSibling;
+				}
+
+				return i;
+			};
+
+			if (pe && e) {
+				// Get before chunk
+				r.setStart(pe.parentNode, nodeIndex(pe));
+				r.setEnd(e.parentNode, nodeIndex(e));
+				bef = r.extractContents();
+
+				// Get after chunk
+				r = t.createRng();
+				r.setStart(e.parentNode, nodeIndex(e) + 1);
+				r.setEnd(pe.parentNode, nodeIndex(pe) + 1);
+				aft = r.extractContents();
+
+				// Insert chunks and remove parent
+				pa = pe.parentNode;
+
+				// Remove right side edge of the before contents
+				trimEdge(bef, 'lastChild');
+
+				if (!isEmpty(bef))
+					pa.insertBefore(bef, pe);
+
+				if (re)
+					pa.replaceChild(re, e);
+				else
+					pa.insertBefore(e, pe);
+
+				// Remove left site edge of the after contents
+				trimEdge(aft, 'firstChild');
+
+				if (!isEmpty(aft))
+					pa.insertBefore(aft, pe);
+
+				t.remove(pe);
+
+				return re || e;
+			}
+		},
+
+		bind : function(target, name, func, scope) {
+			var t = this;
+
+			if (!t.events)
+				t.events = new tinymce.dom.EventUtils();
+
+			return t.events.add(target, name, func, scope || this);
+		},
+
+		unbind : function(target, name, func) {
+			var t = this;
+
+			if (!t.events)
+				t.events = new tinymce.dom.EventUtils();
+
+			return t.events.remove(target, name, func);
+		},
+
+
+		_findSib : function(node, selector, name) {
+			var t = this, f = selector;
+
+			if (node) {
+				// If expression make a function of it using is
+				if (is(f, 'string')) {
+					f = function(node) {
+						return t.is(node, selector);
+					};
+				}
+
+				// Loop all siblings
+				for (node = node[name]; node; node = node[name]) {
+					if (f(node))
+						return node;
+				}
+			}
+
+			return null;
+		},
+
+		_isRes : function(c) {
+			// Is live resizble element
+			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
+		}
+
+		/*
+		walk : function(n, f, s) {
+			var d = this.doc, w;
+
+			if (d.createTreeWalker) {
+				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
+
+				while ((n = w.nextNode()) != null)
+					f.call(s || this, n);
+			} else
+				tinymce.walk(n, f, 'childNodes', s);
+		}
+		*/
+
+		/*
+		toRGB : function(s) {
+			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
+
+			if (c) {
+				// #FFF -> #FFFFFF
+				if (!is(c[3]))
+					c[3] = c[2] = c[1];
+
+				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
+			}
+
+			return s;
+		}
+		*/
+	});
+
+	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
+})(tinymce);
+(function(ns) {
+	// Traverse constants
+	var EXTRACT = 0, CLONE = 1, DELETE = 2, extend = tinymce.extend;
+
+	function indexOf(child, parent) {
+		var i, node;
+
+		if (child.parentNode != parent)
+			return -1;
+
+		for (node = parent.firstChild, i = 0; node != child; node = node.nextSibling)
+			i++;
+
+		return i;
+	};
+
+	function nodeIndex(n) {
+		var i = 0;
+
+		while (n.previousSibling) {
+			i++;
+			n = n.previousSibling;
+		}
+
+		return i;
+	};
+
+	function getSelectedNode(container, offset) {
+		var child;
+
+		if (container.nodeType == 3 /* TEXT_NODE */)
+			return container;
+
+		if (offset < 0)
+			return container;
+
+		child = container.firstChild;
+		while (child != null && offset > 0) {
+			--offset;
+			child = child.nextSibling;
+		}
+
+		if (child != null)
+			return child;
+
+		return container;
+	};
+
+	// Range constructor
+	function Range(dom) {
+		var d = dom.doc;
+
+		extend(this, {
+			dom : dom,
+
+			// Inital states
+			startContainer : d,
+			startOffset : 0,
+			endContainer : d,
+			endOffset : 0,
+			collapsed : true,
+			commonAncestorContainer : d,
+
+			// Range constants
+			START_TO_START : 0,
+			START_TO_END : 1,
+			END_TO_END : 2,
+			END_TO_START : 3
+		});
+	};
+
+	// Add range methods
+	extend(Range.prototype, {
+		setStart : function(n, o) {
+			this._setEndPoint(true, n, o);
+		},
+
+		setEnd : function(n, o) {
+			this._setEndPoint(false, n, o);
+		},
+
+		setStartBefore : function(n) {
+			this.setStart(n.parentNode, nodeIndex(n));
+		},
+
+		setStartAfter : function(n) {
+			this.setStart(n.parentNode, nodeIndex(n) + 1);
+		},
+
+		setEndBefore : function(n) {
+			this.setEnd(n.parentNode, nodeIndex(n));
+		},
+
+		setEndAfter : function(n) {
+			this.setEnd(n.parentNode, nodeIndex(n) + 1);
+		},
+
+		collapse : function(ts) {
+			var t = this;
+
+			if (ts) {
+				t.endContainer = t.startContainer;
+				t.endOffset = t.startOffset;
+			} else {
+				t.startContainer = t.endContainer;
+				t.startOffset = t.endOffset;
+			}
+
+			t.collapsed = true;
+		},
+
+		selectNode : function(n) {
+			this.setStartBefore(n);
+			this.setEndAfter(n);
+		},
+
+		selectNodeContents : function(n) {
+			this.setStart(n, 0);
+			this.setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
+		},
+
+		compareBoundaryPoints : function(h, r) {
+			var t = this, sc = t.startContainer, so = t.startOffset, ec = t.endContainer, eo = t.endOffset;
+
+			// Check START_TO_START
+			if (h === 0)
+				return t._compareBoundaryPoints(sc, so, sc, so);
+
+			// Check START_TO_END
+			if (h === 1)
+				return t._compareBoundaryPoints(sc, so, ec, eo);
+
+			// Check END_TO_END
+			if (h === 2)
+				return t._compareBoundaryPoints(ec, eo, ec, eo);
+
+			// Check END_TO_START
+			if (h === 3)
+				return t._compareBoundaryPoints(ec, eo, sc, so);
+		},
+
+		deleteContents : function() {
+			this._traverse(DELETE);
+		},
+
+		extractContents : function() {
+			return this._traverse(EXTRACT);
+		},
+
+		cloneContents : function() {
+			return this._traverse(CLONE);
+		},
+
+		insertNode : function(n) {
+			var t = this, nn, o;
+
+			// Node is TEXT_NODE or CDATA
+			if (n.nodeType === 3 || n.nodeType === 4) {
+				nn = t.startContainer.splitText(t.startOffset);
+				t.startContainer.parentNode.insertBefore(n, nn);
+			} else {
+				// Insert element node
+				if (t.startContainer.childNodes.length > 0)
+					o = t.startContainer.childNodes[t.startOffset];
+
+				t.startContainer.insertBefore(n, o);
+			}
+		},
+
+		surroundContents : function(n) {
+			var t = this, f = t.extractContents();
+
+			t.insertNode(n);
+			n.appendChild(f);
+			t.selectNode(n);
+		},
+
+		cloneRange : function() {
+			var t = this;
+
+			return extend(new Range(t.dom), {
+				startContainer : t.startContainer,
+				startOffset : t.startOffset,
+				endContainer : t.endContainer,
+				endOffset : t.endOffset,
+				collapsed : t.collapsed,
+				commonAncestorContainer : t.commonAncestorContainer
+			});
+		},
+
+/*
+		detach : function() {
+			// Not implemented
+		},
+*/
+		// Internal methods
+
+		_isCollapsed : function() {
+			return (this.startContainer == this.endContainer && this.startOffset == this.endOffset);
+		},
+
+		_compareBoundaryPoints : function (containerA, offsetA, containerB, offsetB) {
+			var c, offsetC, n, cmnRoot, childA, childB;
+
+			// In the first case the boundary-points have the same container. A is before B 
+			// if its offset is less than the offset of B, A is equal to B if its offset is 
+			// equal to the offset of B, and A is after B if its offset is greater than the 
+			// offset of B.
+			if (containerA == containerB) {
+				if (offsetA == offsetB) {
+					return 0; // equal
+				} else if (offsetA < offsetB) {
+					return -1; // before
+				} else {
+					return 1; // after
+				}
+			}
+
+			// In the second case a child node C of the container of A is an ancestor 
+			// container of B. In this case, A is before B if the offset of A is less than or 
+			// equal to the index of the child node C and A is after B otherwise.
+			c = containerB;
+			while (c && c.parentNode != containerA) {
+				c = c.parentNode;
+			}
+			if (c) {
+				offsetC = 0;
+				n = containerA.firstChild;
+
+				while (n != c && offsetC < offsetA) {
+					offsetC++;
+					n = n.nextSibling;
+				}
+
+				if (offsetA <= offsetC) {
+					return -1; // before
+				} else {
+					return 1; // after
+				}
+			}
+
+			// In the third case a child node C of the container of B is an ancestor container 
+			// of A. In this case, A is before B if the index of the child node C is less than 
+			// the offset of B and A is after B otherwise.
+			c = containerA;
+			while (c && c.parentNode != containerB) {
+				c = c.parentNode;
+			}
+
+			if (c) {
+				offsetC = 0;
+				n = containerB.firstChild;
+
+				while (n != c && offsetC < offsetB) {
+					offsetC++;
+					n = n.nextSibling;
+				}
+
+				if (offsetC < offsetB) {
+					return -1; // before
+				} else {
+					return 1; // after
+				}
+			}
+
+			// In the fourth case, none of three other cases hold: the containers of A and B 
+			// are siblings or descendants of sibling nodes. In this case, A is before B if 
+			// the container of A is before the container of B in a pre-order traversal of the
+			// Ranges' context tree and A is after B otherwise.
+			cmnRoot = this.dom.findCommonAncestor(containerA, containerB);
+			childA = containerA;
+
+			while (childA && childA.parentNode != cmnRoot) {
+				childA = childA.parentNode;  
+			}
+
+			if (!childA) {
+				childA = cmnRoot;
+			}
+
+			childB = containerB;
+			while (childB && childB.parentNode != cmnRoot) {
+				childB = childB.parentNode;
+			}
+
+			if (!childB) {
+				childB = cmnRoot;
+			}
+
+			if (childA == childB) {
+				return 0; // equal
+			}
+
+			n = cmnRoot.firstChild;
+			while (n) {
+				if (n == childA) {
+					return -1; // before
+				}
+
+				if (n == childB) {
+					return 1; // after
+				}
+
+				n = n.nextSibling;
+			}
+		},
+
+		_setEndPoint : function(st, n, o) {
+			var t = this, ec, sc;
+
+			if (st) {
+				t.startContainer = n;
+				t.startOffset = o;
+			} else {
+				t.endContainer = n;
+				t.endOffset = o;
+			}
+
+			// If one boundary-point of a Range is set to have a root container 
+			// other than the current one for the Range, the Range is collapsed to 
+			// the new position. This enforces the restriction that both boundary-
+			// points of a Range must have the same root container.
+			ec = t.endContainer;
+			while (ec.parentNode)
+				ec = ec.parentNode;
+
+			sc = t.startContainer;
+			while (sc.parentNode)
+				sc = sc.parentNode;
+
+			if (sc != ec) {
+				t.collapse(st);
+			} else {
+				// The start position of a Range is guaranteed to never be after the 
+				// end position. To enforce this restriction, if the start is set to 
+				// be at a position after the end, the Range is collapsed to that 
+				// position.
+				if (t._compareBoundaryPoints(t.startContainer, t.startOffset, t.endContainer, t.endOffset) > 0)
+					t.collapse(st);
+			}
+
+			t.collapsed = t._isCollapsed();
+			t.commonAncestorContainer = t.dom.findCommonAncestor(t.startContainer, t.endContainer);
+		},
+
+		// This code is heavily "inspired" by the Apache Xerces implementation. I hope they don't mind. :)
+
+		_traverse : function(how) {
+			var t = this, c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
+
+			if (t.startContainer == t.endContainer)
+				return t._traverseSameContainer(how);
+
+			for (c = t.endContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) {
+				if (p == t.startContainer)
+					return t._traverseCommonStartContainer(c, how);
+
+				++endContainerDepth;
+			}
+
+			for (c = t.startContainer, p = c.parentNode; p != null; c = p, p = p.parentNode) {
+				if (p == t.endContainer)
+					return t._traverseCommonEndContainer(c, how);
+
+				++startContainerDepth;
+			}
+
+			depthDiff = startContainerDepth - endContainerDepth;
+
+			startNode = t.startContainer;
+			while (depthDiff > 0) {
+				startNode = startNode.parentNode;
+				depthDiff--;
+			}
+
+			endNode = t.endContainer;
+			while (depthDiff < 0) {
+				endNode = endNode.parentNode;
+				depthDiff++;
+			}
+
+			// ascend the ancestor hierarchy until we have a common parent.
+			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
+				startNode = sp;
+				endNode = ep;
+			}
+
+			return t._traverseCommonAncestors(startNode, endNode, how);
+		},
+
+		_traverseSameContainer : function(how) {
+			var t = this, frag, s, sub, n, cnt, sibling, xferNode;
+
+			if (how != DELETE)
+				frag = t.dom.doc.createDocumentFragment();
+
+			// If selection is empty, just return the fragment
+			if (t.startOffset == t.endOffset)
+				return frag;
+
+			// Text node needs special case handling
+			if (t.startContainer.nodeType == 3 /* TEXT_NODE */) {
+				// get the substring
+				s = t.startContainer.nodeValue;
+				sub = s.substring(t.startOffset, t.endOffset);
+
+				// set the original text node to its new value
+				if (how != CLONE) {
+					t.startContainer.deleteData(t.startOffset, t.endOffset - t.startOffset);
+
+					// Nothing is partially selected, so collapse to start point
+					t.collapse(true);
+				}
+
+				if (how == DELETE)
+					return null;
+
+				frag.appendChild(t.dom.doc.createTextNode(sub));
+				return frag;
+			}
+
+			// Copy nodes between the start/end offsets.
+			n = getSelectedNode(t.startContainer, t.startOffset);
+			cnt = t.endOffset - t.startOffset;
+
+			while (cnt > 0) {
+				sibling = n.nextSibling;
+				xferNode = t._traverseFullySelected(n, how);
+
+				if (frag)
+					frag.appendChild( xferNode );
+
+				--cnt;
+				n = sibling;
+			}
+
+			// Nothing is partially selected, so collapse to start point
+			if (how != CLONE)
+				t.collapse(true);
+
+			return frag;
+		},
+
+		_traverseCommonStartContainer : function(endAncestor, how) {
+			var t = this, frag, n, endIdx, cnt, sibling, xferNode;
+
+			if (how != DELETE)
+				frag = t.dom.doc.createDocumentFragment();
+
+			n = t._traverseRightBoundary(endAncestor, how);
+
+			if (frag)
+				frag.appendChild(n);
+
+			endIdx = indexOf(endAncestor, t.startContainer);
+			cnt = endIdx - t.startOffset;
+
+			if (cnt <= 0) {
+				// Collapse to just before the endAncestor, which 
+				// is partially selected.
+				if (how != CLONE) {
+					t.setEndBefore(endAncestor);
+					t.collapse(false);
+				}
+
+				return frag;
+			}
+
+			n = endAncestor.previousSibling;
+			while (cnt > 0) {
+				sibling = n.previousSibling;
+				xferNode = t._traverseFullySelected(n, how);
+
+				if (frag)
+					frag.insertBefore(xferNode, frag.firstChild);
+
+				--cnt;
+				n = sibling;
+			}
+
+			// Collapse to just before the endAncestor, which 
+			// is partially selected.
+			if (how != CLONE) {
+				t.setEndBefore(endAncestor);
+				t.collapse(false);
+			}
+
+			return frag;
+		},
+
+		_traverseCommonEndContainer : function(startAncestor, how) {
+			var t = this, frag, startIdx, n, cnt, sibling, xferNode;
+
+			if (how != DELETE)
+				frag = t.dom.doc.createDocumentFragment();
+
+			n = t._traverseLeftBoundary(startAncestor, how);
+			if (frag)
+				frag.appendChild(n);
+
+			startIdx = indexOf(startAncestor, t.endContainer);
+			++startIdx;  // Because we already traversed it....
+
+			cnt = t.endOffset - startIdx;
+			n = startAncestor.nextSibling;
+			while (cnt > 0) {
+				sibling = n.nextSibling;
+				xferNode = t._traverseFullySelected(n, how);
+
+				if (frag)
+					frag.appendChild(xferNode);
+
+				--cnt;
+				n = sibling;
+			}
+
+			if (how != CLONE) {
+				t.setStartAfter(startAncestor);
+				t.collapse(true);
+			}
+
+			return frag;
+		},
+
+		_traverseCommonAncestors : function(startAncestor, endAncestor, how) {
+			var t = this, n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
+
+			if (how != DELETE)
+				frag = t.dom.doc.createDocumentFragment();
+
+			n = t._traverseLeftBoundary(startAncestor, how);
+			if (frag)
+				frag.appendChild(n);
+
+			commonParent = startAncestor.parentNode;
+			startOffset = indexOf(startAncestor, commonParent);
+			endOffset = indexOf(endAncestor, commonParent);
+			++startOffset;
+
+			cnt = endOffset - startOffset;
+			sibling = startAncestor.nextSibling;
+
+			while (cnt > 0) {
+				nextSibling = sibling.nextSibling;
+				n = t._traverseFullySelected(sibling, how);
+
+				if (frag)
+					frag.appendChild(n);
+
+				sibling = nextSibling;
+				--cnt;
+			}
+
+			n = t._traverseRightBoundary(endAncestor, how);
+
+			if (frag)
+				frag.appendChild(n);
+
+			if (how != CLONE) {
+				t.setStartAfter(startAncestor);
+				t.collapse(true);
+			}
+
+			return frag;
+		},
+
+		_traverseRightBoundary : function(root, how) {
+			var t = this, next = getSelectedNode(t.endContainer, t.endOffset - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent;
+			var isFullySelected = next != t.endContainer;
+
+			if (next == root)
+				return t._traverseNode(next, isFullySelected, false, how);
+
+			parent = next.parentNode;
+			clonedParent = t._traverseNode(parent, false, false, how);
+
+			while (parent != null) {
+				while (next != null) {
+					prevSibling = next.previousSibling;
+					clonedChild = t._traverseNode(next, isFullySelected, false, how);
+
+					if (how != DELETE)
+						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
+
+					isFullySelected = true;
+					next = prevSibling;
+				}
+
+				if (parent == root)
+					return clonedParent;
+
+				next = parent.previousSibling;
+				parent = parent.parentNode;
+
+				clonedGrandParent = t._traverseNode(parent, false, false, how);
+
+				if (how != DELETE)
+					clonedGrandParent.appendChild(clonedParent);
+
+				clonedParent = clonedGrandParent;
+			}
+
+			// should never occur
+			return null;
+		},
+
+		_traverseLeftBoundary : function(root, how) {
+			var t = this, next = getSelectedNode(t.startContainer, t.startOffset);
+			var isFullySelected = next != t.startContainer, parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
+
+			if (next == root)
+				return t._traverseNode(next, isFullySelected, true, how);
+
+			parent = next.parentNode;
+			clonedParent = t._traverseNode(parent, false, true, how);
+
+			while (parent != null) {
+				while (next != null) {
+					nextSibling = next.nextSibling;
+					clonedChild = t._traverseNode(next, isFullySelected, true, how);
+
+					if (how != DELETE)
+						clonedParent.appendChild(clonedChild);
+
+					isFullySelected = true;
+					next = nextSibling;
+				}
+
+				if (parent == root)
+					return clonedParent;
+
+				next = parent.nextSibling;
+				parent = parent.parentNode;
+
+				clonedGrandParent = t._traverseNode(parent, false, true, how);
+
+				if (how != DELETE)
+					clonedGrandParent.appendChild(clonedParent);
+
+				clonedParent = clonedGrandParent;
+			}
+
+			// should never occur
+			return null;
+		},
+
+		_traverseNode : function(n, isFullySelected, isLeft, how) {
+			var t = this, txtValue, newNodeValue, oldNodeValue, offset, newNode;
+
+			if (isFullySelected)
+				return t._traverseFullySelected(n, how);
+
+			if (n.nodeType == 3 /* TEXT_NODE */) {
+				txtValue = n.nodeValue;
+
+				if (isLeft) {
+					offset = t.startOffset;
+					newNodeValue = txtValue.substring(offset);
+					oldNodeValue = txtValue.substring(0, offset);
+				} else {
+					offset = t.endOffset;
+					newNodeValue = txtValue.substring(0, offset);
+					oldNodeValue = txtValue.substring(offset);
+				}
+
+				if (how != CLONE)
+					n.nodeValue = oldNodeValue;
+
+				if (how == DELETE)
+					return null;
+
+				newNode = n.cloneNode(false);
+				newNode.nodeValue = newNodeValue;
+
+				return newNode;
+			}
+
+			if (how == DELETE)
+				return null;
+
+			return n.cloneNode(false);
+		},
+
+		_traverseFullySelected : function(n, how) {
+			var t = this;
+
+			if (how != DELETE)
+				return how == CLONE ? n.cloneNode(true) : n;
+
+			n.parentNode.removeChild(n);
+			return null;
+		}
+	});
+
+	ns.Range = Range;
+})(tinymce.dom);
+(function() {
+	function Selection(selection) {
+		var t = this, invisibleChar = '\uFEFF', range, lastIERng;
+
+		function compareRanges(rng1, rng2) {
+			if (rng1 && rng2) {
+				// Both are control ranges and the selected element matches
+				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
+					return 1;
+
+				// Both are text ranges and the range matches
+				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
+					return 1;
+			}
+
+			return 0;
+		};
+
+		function getRange() {
+			var dom = selection.dom, ieRange = selection.getRng(), domRange = dom.createRng(), startPos, endPos, element, sc, ec, collapsed;
+
+			function findIndex(element) {
+				var nl = element.parentNode.childNodes, i;
+
+				for (i = nl.length - 1; i >= 0; i--) {
+					if (nl[i] == element)
+						return i;
+				}
+
+				return -1;
+			};
+
+			function findEndPoint(start) {
+				var rng = ieRange.duplicate(), parent, i, nl, n, offset = 0, index = 0, pos, tmpRng;
+
+				// Insert marker character
+				rng.collapse(start);
+				parent = rng.parentElement();
+				rng.pasteHTML(invisibleChar); // Needs to be a pasteHTML instead of .text = since IE has a bug with nodeValue
+
+				// Find marker character
+				nl = parent.childNodes;
+				for (i = 0; i < nl.length; i++) {
+					n = nl[i];
+
+					// Calculate node index excluding text node fragmentation
+					if (i > 0 && (n.nodeType !== 3 || nl[i - 1].nodeType !== 3))
+						index++;
+
+					// If text node then calculate offset
+					if (n.nodeType === 3) {
+						// Look for marker
+						pos = n.nodeValue.indexOf(invisibleChar);
+						if (pos !== -1) {
+							offset += pos;
+							break;
+						}
+
+						offset += n.nodeValue.length;
+					} else
+						offset = 0;
+				}
+
+				// Remove marker character
+				rng.moveStart('character', -1);
+				rng.text = '';
+
+				return {index : index, offset : offset, parent : parent};
+			};
+
+			// If selection is outside the current document just return an empty range
+			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
+			if (element.ownerDocument != dom.doc)
+				return domRange;
+
+			// Handle control selection or text selection of a image
+			if (ieRange.item || !element.hasChildNodes()) {
+				domRange.setStart(element.parentNode, findIndex(element));
+				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
+
+				return domRange;
+			}
+
+			// Check collapsed state
+			collapsed = selection.isCollapsed();
+
+			// Find start and end pos index and offset
+			startPos = findEndPoint(true);
+			endPos = findEndPoint(false);
+
+			// Normalize the elements to avoid fragmented dom
+			startPos.parent.normalize();
+			endPos.parent.normalize();
+
+			// Set start container and offset
+			sc = startPos.parent.childNodes[Math.min(startPos.index, startPos.parent.childNodes.length - 1)];
+
+			if (sc.nodeType != 3)
+				domRange.setStart(startPos.parent, startPos.index);
+			else
+				domRange.setStart(startPos.parent.childNodes[startPos.index], startPos.offset);
+
+			// Set end container and offset
+			ec = endPos.parent.childNodes[Math.min(endPos.index, endPos.parent.childNodes.length - 1)];
+
+			if (ec.nodeType != 3) {
+				if (!collapsed)
+					endPos.index++;
+
+				domRange.setEnd(endPos.parent, endPos.index);
+			} else
+				domRange.setEnd(endPos.parent.childNodes[endPos.index], endPos.offset);
+
+			// If not collapsed then make sure offsets are valid
+			if (!collapsed) {
+				sc = domRange.startContainer;
+				if (sc.nodeType == 1)
+					domRange.setStart(sc, Math.min(domRange.startOffset, sc.childNodes.length));
+
+				ec = domRange.endContainer;
+				if (ec.nodeType == 1)
+					domRange.setEnd(ec, Math.min(domRange.endOffset, ec.childNodes.length));
+			}
+
+			// Restore selection to new range
+			t.addRange(domRange);
+
+			return domRange;
+		};
+
+		this.addRange = function(rng) {
+			var ieRng, body = selection.dom.doc.body, startPos, endPos, sc, so, ec, eo;
+
+			// Setup some shorter versions
+			sc = rng.startContainer;
+			so = rng.startOffset;
+			ec = rng.endContainer;
+			eo = rng.endOffset;
+			ieRng = body.createTextRange();
+
+			// Find element
+			sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc;
+			ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec;
+
+			// Single element selection
+			if (sc == ec && sc.nodeType == 1) {
+				// Make control selection for some elements
+				if (/^(IMG|TABLE)$/.test(sc.nodeName) && so != eo) {
+					ieRng = body.createControlRange();
+					ieRng.addElement(sc);
+				} else {
+					ieRng = body.createTextRange();
+
+					// Padd empty elements with invisible character
+					if (!sc.hasChildNodes() && sc.canHaveHTML)
+						sc.innerHTML = invisibleChar;
+
+					// Select element contents
+					ieRng.moveToElementText(sc);
+
+					// If it's only containing a padding remove it so the caret remains
+					if (sc.innerHTML == invisibleChar) {
+						ieRng.collapse(true);
+						sc.removeChild(sc.firstChild);
+					}
+				}
+
+				if (so == eo)
+					ieRng.collapse(eo <= rng.endContainer.childNodes.length - 1);
+
+				ieRng.select();
+
+				return;
+			}
+
+			function getCharPos(container, offset) {
+				var nodeVal, rng, pos;
+
+				if (container.nodeType != 3)
+					return -1;
+
+				nodeVal = container.nodeValue;
+				rng = body.createTextRange();
+
+				// Insert marker at offset position
+				container.nodeValue = nodeVal.substring(0, offset) + invisibleChar + nodeVal.substring(offset);
+
+				// Find char pos of marker and remove it
+				rng.moveToElementText(container.parentNode);
+				rng.findText(invisibleChar);
+				pos = Math.abs(rng.moveStart('character', -0xFFFFF));
+				container.nodeValue = nodeVal;
+
+				return pos;
+			};
+
+			// Collapsed range
+			if (rng.collapsed) {
+				pos = getCharPos(sc, so);
+
+				ieRng = body.createTextRange();
+				ieRng.move('character', pos);
+				ieRng.select();
+
+				return;
+			} else {
+				// If same text container
+				if (sc == ec && sc.nodeType == 3) {
+					startPos = getCharPos(sc, so);
+
+					ieRng = body.createTextRange();
+					ieRng.move('character', startPos);
+					ieRng.moveEnd('character', eo - so);
+					ieRng.select();
+
+					return;
+				}
+
+				// Get caret positions
+				startPos = getCharPos(sc, so);
+				endPos = getCharPos(ec, eo);
+				ieRng = body.createTextRange();
+
+				// Move start of range to start character position or start element
+				if (startPos == -1) {
+					ieRng.moveToElementText(sc);
+					startPos = 0;
+				} else
+					ieRng.move('character', startPos);
+
+				// Move end of range to end character position or end element
+				tmpRng = body.createTextRange();
+
+				if (endPos == -1)
+					tmpRng.moveToElementText(ec);
+				else
+					tmpRng.move('character', endPos);
+
+				ieRng.setEndPoint('EndToEnd', tmpRng);
+				ieRng.select();
+
+				return;
+			}
+		};
+
+		this.getRangeAt = function() {
+			// Setup new range if the cache is empty
+			if (!range || !compareRanges(lastIERng, selection.getRng())) {
+				range = getRange();
+
+				// Store away text range for next call
+				lastIERng = selection.getRng();
+			}
+
+			// Return cached range
+			return range;
+		};
+
+		this.destroy = function() {
+			// Destroy cached range and last IE range to avoid memory leaks
+			lastIERng = range = null;
+		};
+	};
+
+	// Expose the selection object
+	tinymce.dom.TridentSelection = Selection;
+})();
+(function(tinymce) {
+	// Shorten names
+	var each = tinymce.each, DOM = tinymce.DOM, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, Event;
+
+	tinymce.create('tinymce.dom.EventUtils', {
+		EventUtils : function() {
+			this.inits = [];
+			this.events = [];
+		},
+
+		add : function(o, n, f, s) {
+			var cb, t = this, el = t.events, r;
+
+			if (n instanceof Array) {
+				r = [];
+
+				each(n, function(n) {
+					r.push(t.add(o, n, f, s));
+				});
+
+				return r;
+			}
+
+			// Handle array
+			if (o && o.hasOwnProperty && o instanceof Array) {
+				r = [];
+
+				each(o, function(o) {
+					o = DOM.get(o);
+					r.push(t.add(o, n, f, s));
+				});
+
+				return r;
+			}
+
+			o = DOM.get(o);
+
+			if (!o)
+				return;
+
+			// Setup event callback
+			cb = function(e) {
+				// Is all events disabled
+				if (t.disabled)
+					return;
+
+				e = e || window.event;
+
+				// Patch in target, preventDefault and stopPropagation in IE it's W3C valid
+				if (e && isIE) {
+					if (!e.target)
+						e.target = e.srcElement;
+
+					// Patch in preventDefault, stopPropagation methods for W3C compatibility
+					tinymce.extend(e, t._stoppers);
+				}
+
+				if (!s)
+					return f(e);
+
+				return f.call(s, e);
+			};
+
+			if (n == 'unload') {
+				tinymce.unloads.unshift({func : cb});
+				return cb;
+			}
+
+			if (n == 'init') {
+				if (t.domLoaded)
+					cb();
+				else
+					t.inits.push(cb);
+
+				return cb;
+			}
+
+			// Store away listener reference
+			el.push({
+				obj : o,
+				name : n,
+				func : f,
+				cfunc : cb,
+				scope : s
+			});
+
+			t._add(o, n, cb);
+
+			return f;
+		},
+
+		remove : function(o, n, f) {
+			var t = this, a = t.events, s = false, r;
+
+			// Handle array
+			if (o && o.hasOwnProperty && o instanceof Array) {
+				r = [];
+
+				each(o, function(o) {
+					o = DOM.get(o);
+					r.push(t.remove(o, n, f));
+				});
+
+				return r;
+			}
+
+			o = DOM.get(o);
+
+			each(a, function(e, i) {
+				if (e.obj == o && e.name == n && (!f || (e.func == f || e.cfunc == f))) {
+					a.splice(i, 1);
+					t._remove(o, n, e.cfunc);
+					s = true;
+					return false;
+				}
+			});
+
+			return s;
+		},
+
+		clear : function(o) {
+			var t = this, a = t.events, i, e;
+
+			if (o) {
+				o = DOM.get(o);
+
+				for (i = a.length - 1; i >= 0; i--) {
+					e = a[i];
+
+					if (e.obj === o) {
+						t._remove(e.obj, e.name, e.cfunc);
+						e.obj = e.cfunc = null;
+						a.splice(i, 1);
+					}
+				}
+			}
+		},
+
+		cancel : function(e) {
+			if (!e)
+				return false;
+
+			this.stop(e);
+
+			return this.prevent(e);
+		},
+
+		stop : function(e) {
+			if (e.stopPropagation)
+				e.stopPropagation();
+			else
+				e.cancelBubble = true;
+
+			return false;
+		},
+
+		prevent : function(e) {
+			if (e.preventDefault)
+				e.preventDefault();
+			else
+				e.returnValue = false;
+
+			return false;
+		},
+
+		destroy : function() {
+			var t = this;
+
+			each(t.events, function(e, i) {
+				t._remove(e.obj, e.name, e.cfunc);
+				e.obj = e.cfunc = null;
+			});
+
+			t.events = [];
+			t = null;
+		},
+
+		_add : function(o, n, f) {
+			if (o.attachEvent)
+				o.attachEvent('on' + n, f);
+			else if (o.addEventListener)
+				o.addEventListener(n, f, false);
+			else
+				o['on' + n] = f;
+		},
+
+		_remove : function(o, n, f) {
+			if (o) {
+				try {
+					if (o.detachEvent)
+						o.detachEvent('on' + n, f);
+					else if (o.removeEventListener)
+						o.removeEventListener(n, f, false);
+					else
+						o['on' + n] = null;
+				} catch (ex) {
+					// Might fail with permission denined on IE so we just ignore that
+				}
+			}
+		},
+
+		_pageInit : function(win) {
+			var t = this;
+
+			// Keep it from running more than once
+			if (t.domLoaded)
+				return;
+
+			t.domLoaded = true;
+
+			each(t.inits, function(c) {
+				c();
+			});
+
+			t.inits = [];
+		},
+
+		_wait : function(win) {
+			var t = this, doc = win.document;
+
+			// No need since the document is already loaded
+			if (win.tinyMCE_GZ && tinyMCE_GZ.loaded) {
+				t.domLoaded = 1;
+				return;
+			}
+
+			// Use IE method
+			if (doc.attachEvent) {
+				doc.attachEvent("onreadystatechange", function() {
+					if (doc.readyState === "complete") {
+						doc.detachEvent("onreadystatechange", arguments.callee);
+						t._pageInit(win);
+					}
+				});
+
+				if (doc.documentElement.doScroll && win == win.top) {
+					(function() {
+						if (t.domLoaded)
+							return;
+
+						try {
+							// If IE is used, use the trick by Diego Perini
+							// http://javascript.nwbox.com/IEContentLoaded/
+							doc.documentElement.doScroll("left");
+						} catch (ex) {
+							setTimeout(arguments.callee, 0);
+							return;
+						}
+
+						t._pageInit(win);
+					})();
+				}
+			} else if (doc.addEventListener) {
+				t._add(win, 'DOMContentLoaded', function() {
+					t._pageInit(win);
+				});
+			}
+
+			t._add(win, 'load', function() {
+				t._pageInit(win);
+			});
+		},
+
+		_stoppers : {
+			preventDefault :  function() {
+				this.returnValue = false;
+			},
+
+			stopPropagation : function() {
+				this.cancelBubble = true;
+			}
+		}
+	});
+
+	Event = tinymce.dom.Event = new tinymce.dom.EventUtils();
+
+	// Dispatch DOM content loaded event for IE and Safari
+	Event._wait(window);
+
+	tinymce.addUnload(function() {
+		Event.destroy();
+	});
+})(tinymce);
+(function(tinymce) {
+	var each = tinymce.each;
+
+	tinymce.create('tinymce.dom.Element', {
+		Element : function(id, s) {
+			var t = this, dom, el;
+
+			s = s || {};
+			t.id = id;
+			t.dom = dom = s.dom || tinymce.DOM;
+			t.settings = s;
+
+			// Only IE leaks DOM references, this is a lot faster
+			if (!tinymce.isIE)
+				el = t.dom.get(t.id);
+
+			each([
+				'getPos',
+				'getRect',
+				'getParent',
+				'add',
+				'setStyle',
+				'getStyle',
+				'setStyles',
+				'setAttrib',
+				'setAttribs',
+				'getAttrib',
+				'addClass',
+				'removeClass',
+				'hasClass',
+				'getOuterHTML',
+				'setOuterHTML',
+				'remove',
+				'show',
+				'hide',
+				'isHidden',
+				'setHTML',
+				'get'
+			], function(k) {
+				t[k] = function() {
+					var a = [id], i;
+
+					for (i = 0; i < arguments.length; i++)
+						a.push(arguments[i]);
+
+					a = dom[k].apply(dom, a);
+					t.update(k);
+
+					return a;
+				};
+			});
+		},
+
+		on : function(n, f, s) {
+			return tinymce.dom.Event.add(this.id, n, f, s);
+		},
+
+		getXY : function() {
+			return {
+				x : parseInt(this.getStyle('left')),
+				y : parseInt(this.getStyle('top'))
+			};
+		},
+
+		getSize : function() {
+			var n = this.dom.get(this.id);
+
+			return {
+				w : parseInt(this.getStyle('width') || n.clientWidth),
+				h : parseInt(this.getStyle('height') || n.clientHeight)
+			};
+		},
+
+		moveTo : function(x, y) {
+			this.setStyles({left : x, top : y});
+		},
+
+		moveBy : function(x, y) {
+			var p = this.getXY();
+
+			this.moveTo(p.x + x, p.y + y);
+		},
+
+		resizeTo : function(w, h) {
+			this.setStyles({width : w, height : h});
+		},
+
+		resizeBy : function(w, h) {
+			var s = this.getSize();
+
+			this.resizeTo(s.w + w, s.h + h);
+		},
+
+		update : function(k) {
+			var t = this, b, dom = t.dom;
+
+			if (tinymce.isIE6 && t.settings.blocker) {
+				k = k || '';
+
+				// Ignore getters
+				if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
+					return;
+
+				// Remove blocker on remove
+				if (k == 'remove') {
+					dom.remove(t.blocker);
+					return;
+				}
+
+				if (!t.blocker) {
+					t.blocker = dom.uniqueId();
+					b = dom.add(t.settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
+					dom.setStyle(b, 'opacity', 0);
+				} else
+					b = dom.get(t.blocker);
+
+				dom.setStyle(b, 'left', t.getStyle('left', 1));
+				dom.setStyle(b, 'top', t.getStyle('top', 1));
+				dom.setStyle(b, 'width', t.getStyle('width', 1));
+				dom.setStyle(b, 'height', t.getStyle('height', 1));
+				dom.setStyle(b, 'display', t.getStyle('display', 1));
+				dom.setStyle(b, 'zIndex', parseInt(t.getStyle('zIndex', 1) || 0) - 1);
+			}
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	function trimNl(s) {
+		return s.replace(/[\n\r]+/g, '');
+	};
+
+	// Shorten names
+	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each;
+
+	tinymce.create('tinymce.dom.Selection', {
+		Selection : function(dom, win, serializer) {
+			var t = this;
+
+			t.dom = dom;
+			t.win = win;
+			t.serializer = serializer;
+
+			// Add events
+			each([
+				'onBeforeSetContent',
+				'onBeforeGetContent',
+				'onSetContent',
+				'onGetContent'
+			], function(e) {
+				t[e] = new tinymce.util.Dispatcher(t);
+			});
+
+			// No W3C Range support
+			if (!t.win.getSelection)
+				t.tridentSel = new tinymce.dom.TridentSelection(t);
+
+			// Prevent leaks
+			tinymce.addUnload(t.destroy, t);
+		},
+
+		getContent : function(s) {
+			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
+
+			s = s || {};
+			wb = wa = '';
+			s.get = true;
+			s.format = s.format || 'html';
+			t.onBeforeGetContent.dispatch(t, s);
+
+			if (s.format == 'text')
+				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
+
+			if (r.cloneContents) {
+				n = r.cloneContents();
+
+				if (n)
+					e.appendChild(n);
+			} else if (is(r.item) || is(r.htmlText))
+				e.innerHTML = r.item ? r.item(0).outerHTML : r.htmlText;
+			else
+				e.innerHTML = r.toString();
+
+			// Keep whitespace before and after
+			if (/^\s/.test(e.innerHTML))
+				wb = ' ';
+
+			if (/\s+$/.test(e.innerHTML))
+				wa = ' ';
+
+			s.getInner = true;
+
+			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
+			t.onGetContent.dispatch(t, s);
+
+			return s.content;
+		},
+
+		setContent : function(h, s) {
+			var t = this, r = t.getRng(), c, d = t.win.document;
+
+			s = s || {format : 'html'};
+			s.set = true;
+			h = s.content = t.dom.processHTML(h);
+
+			// Dispatch before set content event
+			t.onBeforeSetContent.dispatch(t, s);
+			h = s.content;
+
+			if (r.insertNode) {
+				// Make caret marker since insertNode places the caret in the beginning of text after insert
+				h += '<span id="__caret">_</span>';
+
+				// Delete and insert new node
+				r.deleteContents();
+				r.insertNode(t.getRng().createContextualFragment(h));
+
+				// Move to caret marker
+				c = t.dom.get('__caret');
+
+				// Make sure we wrap it compleatly, Opera fails with a simple select call
+				r = d.createRange();
+				r.setStartBefore(c);
+				r.setEndAfter(c);
+				t.setRng(r);
+
+				// Delete the marker, and hopefully the caret gets placed in the right location
+				// Removed this since it seems to remove   in FF and simply deleting it
+				// doesn't seem to affect the caret position in any browser
+				//d.execCommand('Delete', false, null);
+
+				// Remove the caret position
+				t.dom.remove('__caret');
+			} else {
+				if (r.item) {
+					// Delete content and get caret text selection
+					d.execCommand('Delete', false, null);
+					r = t.getRng();
+				}
+
+				r.pasteHTML(h);
+			}
+
+			// Dispatch set content event
+			t.onSetContent.dispatch(t, s);
+		},
+
+		getStart : function() {
+			var t = this, r = t.getRng(), e;
+
+			if (isIE) {
+				if (r.item)
+					return r.item(0);
+
+				r = r.duplicate();
+				r.collapse(1);
+				e = r.parentElement();
+
+				if (e && e.nodeName == 'BODY')
+					return e.firstChild;
+
+				return e;
+			} else {
+				e = r.startContainer;
+
+				if (e.nodeName == 'BODY')
+					return e.firstChild;
+
+				return t.dom.getParent(e, '*');
+			}
+		},
+
+		getEnd : function() {
+			var t = this, r = t.getRng(), e;
+
+			if (isIE) {
+				if (r.item)
+					return r.item(0);
+
+				r = r.duplicate();
+				r.collapse(0);
+				e = r.parentElement();
+
+				if (e && e.nodeName == 'BODY')
+					return e.lastChild;
+
+				return e;
+			} else {
+				e = r.endContainer;
+
+				if (e.nodeName == 'BODY')
+					return e.lastChild;
+
+				return t.dom.getParent(e, '*');
+			}
+		},
+
+		getBookmark : function(si) {
+			var t = this, r = t.getRng(), tr, sx, sy, vp = t.dom.getViewPort(t.win), e, sp, bp, le, c = -0xFFFFFF, s, ro = t.dom.getRoot(), wb = 0, wa = 0, nv;
+			sx = vp.x;
+			sy = vp.y;
+
+			// Simple bookmark fast but not as persistent
+			if (si)
+				return {rng : r, scrollX : sx, scrollY : sy};
+
+			// Handle IE
+			if (isIE) {
+				// Control selection
+				if (r.item) {
+					e = r.item(0);
+
+					each(t.dom.select(e.nodeName), function(n, i) {
+						if (e == n) {
+							sp = i;
+							return false;
+						}
+					});
+
+					return {
+						tag : e.nodeName,
+						index : sp,
+						scrollX : sx,
+						scrollY : sy
+					};
+				}
+
+				// Text selection
+				tr = t.dom.doc.body.createTextRange();
+				tr.moveToElementText(ro);
+				tr.collapse(true);
+				bp = Math.abs(tr.move('character', c));
+
+				tr = r.duplicate();
+				tr.collapse(true);
+				sp = Math.abs(tr.move('character', c));
+
+				tr = r.duplicate();
+				tr.collapse(false);
+				le = Math.abs(tr.move('character', c)) - sp;
+
+				return {
+					start : sp - bp,
+					length : le,
+					scrollX : sx,
+					scrollY : sy
+				};
+			}
+
+			// Handle W3C
+			e = t.getNode();
+			s = t.getSel();
+
+			if (!s)
+				return null;
+
+			// Image selection
+			if (e && e.nodeName == 'IMG') {
+				return {
+					scrollX : sx,
+					scrollY : sy
+				};
+			}
+
+			// Text selection
+
+			function getPos(r, sn, en) {
+				var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {};
+
+				while ((n = w.nextNode()) != null) {
+					if (n == sn)
+						d.start = p;
+
+					if (n == en) {
+						d.end = p;
+						return d;
+					}
+
+					p += trimNl(n.nodeValue || '').length;
+				}
+
+				return null;
+			};
+
+			// Caret or selection
+			if (s.anchorNode == s.focusNode && s.anchorOffset == s.focusOffset) {
+				e = getPos(ro, s.anchorNode, s.focusNode);
+
+				if (!e)
+					return {scrollX : sx, scrollY : sy};
+
+				// Count whitespace before
+				trimNl(s.anchorNode.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;});
+
+				return {
+					start : Math.max(e.start + s.anchorOffset - wb, 0),
+					end : Math.max(e.end + s.focusOffset - wb, 0),
+					scrollX : sx,
+					scrollY : sy,
+					beg : s.anchorOffset - wb == 0
+				};
+			} else {
+				e = getPos(ro, r.startContainer, r.endContainer);
+
+				// Count whitespace before start and end container
+				//(r.startContainer.nodeValue || '').replace(/^\s+/, function(a) {wb = a.length;});
+				//(r.endContainer.nodeValue || '').replace(/^\s+/, function(a) {wa = a.length;});
+
+				if (!e)
+					return {scrollX : sx, scrollY : sy};
+
+				return {
+					start : Math.max(e.start + r.startOffset - wb, 0),
+					end : Math.max(e.end + r.endOffset - wa, 0),
+					scrollX : sx,
+					scrollY : sy,
+					beg : r.startOffset - wb == 0
+				};
+			}
+		},
+
+		moveToBookmark : function(b) {
+			var t = this, r = t.getRng(), s = t.getSel(), ro = t.dom.getRoot(), sd, nvl, nv;
+
+			function getPos(r, sp, ep) {
+				var w = t.dom.doc.createTreeWalker(r, NodeFilter.SHOW_TEXT, null, false), n, p = 0, d = {}, o, v, wa, wb;
+
+				while ((n = w.nextNode()) != null) {
+					wa = wb = 0;
+
+					nv = n.nodeValue || '';
+					//nv.replace(/^\s+[^\s]/, function(a) {wb = a.length - 1;});
+					//nv.replace(/[^\s]\s+$/, function(a) {wa = a.length - 1;});
+
+					nvl = trimNl(nv).length;
+					p += nvl;
+
+					if (p >= sp && !d.startNode) {
+						o = sp - (p - nvl);
+
+						// Fix for odd quirk in FF
+						if (b.beg && o >= nvl)
+							continue;
+
+						d.startNode = n;
+						d.startOffset = o + wb;
+					}
+
+					if (p >= ep) {
+						d.endNode = n;
+						d.endOffset = ep - (p - nvl) + wb;
+						return d;
+					}
+				}
+
+				return null;
+			};
+
+			if (!b)
+				return false;
+
+			t.win.scrollTo(b.scrollX, b.scrollY);
+
+			// Handle explorer
+			if (isIE) {
+				t.tridentSel.destroy();
+
+				// Handle simple
+				if (r = b.rng) {
+					try {
+						r.select();
+					} catch (ex) {
+						// Ignore
+					}
+
+					return true;
+				}
+
+				t.win.focus();
+
+				// Handle control bookmark
+				if (b.tag) {
+					r = ro.createControlRange();
+
+					each(t.dom.select(b.tag), function(n, i) {
+						if (i == b.index)
+							r.addElement(n);
+					});
+				} else {
+					// Try/catch needed since this operation breaks when TinyMCE is placed in hidden divs/tabs
+					try {
+						// Incorrect bookmark
+						if (b.start < 0)
+							return true;
+
+						r = s.createRange();
+						r.moveToElementText(ro);
+						r.collapse(true);
+						r.moveStart('character', b.start);
+						r.moveEnd('character', b.length);
+					} catch (ex2) {
+						return true;
+					}
+				}
+
+				try {
+					r.select();
+				} catch (ex) {
+					// Needed for some odd IE bug #1843306
+				}
+
+				return true;
+			}
+
+			// Handle W3C
+			if (!s)
+				return false;
+
+			// Handle simple
+			if (b.rng) {
+				s.removeAllRanges();
+				s.addRange(b.rng);
+			} else {
+				if (is(b.start) && is(b.end)) {
+					try {
+						sd = getPos(ro, b.start, b.end);
+
+						if (sd) {
+							r = t.dom.doc.createRange();
+							r.setStart(sd.startNode, sd.startOffset);
+							r.setEnd(sd.endNode, sd.endOffset);
+							s.removeAllRanges();
+							s.addRange(r);
+						}
+
+						if (!tinymce.isOpera)
+							t.win.focus();
+					} catch (ex) {
+						// Ignore
+					}
+				}
+			}
+		},
+
+		select : function(n, c) {
+			var t = this, r = t.getRng(), s = t.getSel(), b, fn, ln, d = t.win.document;
+
+			function find(n, start) {
+				var walker, o;
+
+				if (n) {
+					walker = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
+
+					// Find first/last non empty text node
+					while (n = walker.nextNode()) {
+						o = n;
+
+						if (tinymce.trim(n.nodeValue).length != 0) {
+							if (start)
+								return n;
+							else
+								o = n;
+						}
+					}
+				}
+
+				return o;
+			};
+
+			if (isIE) {
+				try {
+					b = d.body;
+
+					if (/^(IMG|TABLE)$/.test(n.nodeName)) {
+						r = b.createControlRange();
+						r.addElement(n);
+					} else {
+						r = b.createTextRange();
+						r.moveToElementText(n);
+					}
+
+					r.select();
+				} catch (ex) {
+					// Throws illigal agrument in IE some times
+				}
+			} else {
+				if (c) {
+					fn = find(n, 1) || t.dom.select('br:first', n)[0];
+					ln = find(n, 0) || t.dom.select('br:last', n)[0];
+
+					if (fn && ln) {
+						r = d.createRange();
+
+						if (fn.nodeName == 'BR')
+							r.setStartBefore(fn);
+						else
+							r.setStart(fn, 0);
+
+						if (ln.nodeName == 'BR')
+							r.setEndBefore(ln);
+						else
+							r.setEnd(ln, ln.nodeValue.length);
+					} else
+						r.selectNode(n);
+				} else
+					r.selectNode(n);
+
+				t.setRng(r);
+			}
+
+			return n;
+		},
+
+		isCollapsed : function() {
+			var t = this, r = t.getRng(), s = t.getSel();
+
+			if (!r || r.item)
+				return false;
+
+			return !s || r.boundingWidth == 0 || r.collapsed;
+		},
+
+		collapse : function(b) {
+			var t = this, r = t.getRng(), n;
+
+			// Control range on IE
+			if (r.item) {
+				n = r.item(0);
+				r = this.win.document.body.createTextRange();
+				r.moveToElementText(n);
+			}
+
+			r.collapse(!!b);
+			t.setRng(r);
+		},
+
+		getSel : function() {
+			var t = this, w = this.win;
+
+			return w.getSelection ? w.getSelection() : w.document.selection;
+		},
+
+		getRng : function(w3c) {
+			var t = this, s, r;
+
+			// Found tridentSel object then we need to use that one
+			if (w3c && t.tridentSel)
+				return t.tridentSel.getRangeAt(0);
+
+			try {
+				if (s = t.getSel())
+					r = s.rangeCount > 0 ? s.getRangeAt(0) : (s.createRange ? s.createRange() : t.win.document.createRange());
+			} catch (ex) {
+				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
+			}
+
+			// No range found then create an empty one
+			// This can occur when the editor is placed in a hidden container element on Gecko
+			// Or on IE when there was an exception
+			if (!r)
+				r = isIE ? t.win.document.body.createTextRange() : t.win.document.createRange();
+
+			return r;
+		},
+
+		setRng : function(r) {
+			var s, t = this;
+
+			if (!t.tridentSel) {
+				s = t.getSel();
+
+				if (s) {
+					s.removeAllRanges();
+					s.addRange(r);
+				}
+			} else {
+				// Is W3C Range
+				if (r.cloneRange) {
+					t.tridentSel.addRange(r);
+					return;
+				}
+
+				// Is IE specific range
+				try {
+					r.select();
+				} catch (ex) {
+					// Needed for some odd IE bug #1843306
+				}
+			}
+		},
+
+		setNode : function(n) {
+			var t = this;
+
+			t.setContent(t.dom.getOuterHTML(n));
+
+			return n;
+		},
+
+		getNode : function() {
+			var t = this, r = t.getRng(), s = t.getSel(), e;
+
+			if (!isIE) {
+				// Range maybe lost after the editor is made visible again
+				if (!r)
+					return t.dom.getRoot();
+
+				e = r.commonAncestorContainer;
+
+				// Handle selection a image or other control like element such as anchors
+				if (!r.collapsed) {
+					// If the anchor node is a element instead of a text node then return this element
+					if (tinymce.isWebKit && s.anchorNode && s.anchorNode.nodeType == 1) 
+						return s.anchorNode.childNodes[s.anchorOffset]; 
+
+					if (r.startContainer == r.endContainer) {
+						if (r.startOffset - r.endOffset < 2) {
+							if (r.startContainer.hasChildNodes())
+								e = r.startContainer.childNodes[r.startOffset];
+						}
+					}
+				}
+
+				return t.dom.getParent(e, '*');
+			}
+
+			return r.item ? r.item(0) : r.parentElement();
+		},
+
+		getSelectedBlocks : function(st, en) {
+			var t = this, dom = t.dom, sb, eb, n, bl = [];
+
+			sb = dom.getParent(st || t.getStart(), dom.isBlock);
+			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
+
+			if (sb)
+				bl.push(sb);
+
+			if (sb && eb && sb != eb) {
+				n = sb;
+
+				while ((n = n.nextSibling) && n != eb) {
+					if (dom.isBlock(n))
+						bl.push(n);
+				}
+			}
+
+			if (eb && sb != eb)
+				bl.push(eb);
+
+			return bl;
+		},
+
+		destroy : function(s) {
+			var t = this;
+
+			t.win = null;
+
+			if (t.tridentSel)
+				t.tridentSel.destroy();
+
+			// Manual destroy then remove unload handler
+			if (!s)
+				tinymce.removeUnload(t.destroy);
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	tinymce.create('tinymce.dom.XMLWriter', {
+		node : null,
+
+		XMLWriter : function(s) {
+			// Get XML document
+			function getXML() {
+				var i = document.implementation;
+
+				if (!i || !i.createDocument) {
+					// Try IE objects
+					try {return new ActiveXObject('MSXML2.DOMDocument');} catch (ex) {}
+					try {return new ActiveXObject('Microsoft.XmlDom');} catch (ex) {}
+				} else
+					return i.createDocument('', '', null);
+			};
+
+			this.doc = getXML();
+			
+			// Since Opera and WebKit doesn't escape > into > we need to do it our self to normalize the output for all browsers
+			this.valid = tinymce.isOpera || tinymce.isWebKit;
+
+			this.reset();
+		},
+
+		reset : function() {
+			var t = this, d = t.doc;
+
+			if (d.firstChild)
+				d.removeChild(d.firstChild);
+
+			t.node = d.appendChild(d.createElement("html"));
+		},
+
+		writeStartElement : function(n) {
+			var t = this;
+
+			t.node = t.node.appendChild(t.doc.createElement(n));
+		},
+
+		writeAttribute : function(n, v) {
+			if (this.valid)
+				v = v.replace(/>/g, '%MCGT%');
+
+			this.node.setAttribute(n, v);
+		},
+
+		writeEndElement : function() {
+			this.node = this.node.parentNode;
+		},
+
+		writeFullEndElement : function() {
+			var t = this, n = t.node;
+
+			n.appendChild(t.doc.createTextNode(""));
+			t.node = n.parentNode;
+		},
+
+		writeText : function(v) {
+			if (this.valid)
+				v = v.replace(/>/g, '%MCGT%');
+
+			this.node.appendChild(this.doc.createTextNode(v));
+		},
+
+		writeCDATA : function(v) {
+			this.node.appendChild(this.doc.createCDATASection(v));
+		},
+
+		writeComment : function(v) {
+			// Fix for bug #2035694
+			if (tinymce.isIE)
+				v = v.replace(/^\-|\-$/g, ' ');
+
+			this.node.appendChild(this.doc.createComment(v.replace(/\-\-/g, ' ')));
+		},
+
+		getContent : function() {
+			var h;
+
+			h = this.doc.xml || new XMLSerializer().serializeToString(this.doc);
+			h = h.replace(/<\?[^?]+\?>|<html>|<\/html>|<html\/>|<!DOCTYPE[^>]+>/g, '');
+			h = h.replace(/ ?\/>/g, ' />');
+
+			if (this.valid)
+				h = h.replace(/\%MCGT%/g, '>');
+
+			return h;
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	tinymce.create('tinymce.dom.StringWriter', {
+		str : null,
+		tags : null,
+		count : 0,
+		settings : null,
+		indent : null,
+
+		StringWriter : function(s) {
+			this.settings = tinymce.extend({
+				indent_char : ' ',
+				indentation : 0
+			}, s);
+
+			this.reset();
+		},
+
+		reset : function() {
+			this.indent = '';
+			this.str = "";
+			this.tags = [];
+			this.count = 0;
+		},
+
+		writeStartElement : function(n) {
+			this._writeAttributesEnd();
+			this.writeRaw('<' + n);
+			this.tags.push(n);
+			this.inAttr = true;
+			this.count++;
+			this.elementCount = this.count;
+		},
+
+		writeAttribute : function(n, v) {
+			var t = this;
+
+			t.writeRaw(" " + t.encode(n) + '="' + t.encode(v) + '"');
+		},
+
+		writeEndElement : function() {
+			var n;
+
+			if (this.tags.length > 0) {
+				n = this.tags.pop();
+
+				if (this._writeAttributesEnd(1))
+					this.writeRaw('</' + n + '>');
+
+				if (this.settings.indentation > 0)
+					this.writeRaw('\n');
+			}
+		},
+
+		writeFullEndElement : function() {
+			if (this.tags.length > 0) {
+				this._writeAttributesEnd();
+				this.writeRaw('</' + this.tags.pop() + '>');
+
+				if (this.settings.indentation > 0)
+					this.writeRaw('\n');
+			}
+		},
+
+		writeText : function(v) {
+			this._writeAttributesEnd();
+			this.writeRaw(this.encode(v));
+			this.count++;
+		},
+
+		writeCDATA : function(v) {
+			this._writeAttributesEnd();
+			this.writeRaw('<![CDATA[' + v + ']]>');
+			this.count++;
+		},
+
+		writeComment : function(v) {
+			this._writeAttributesEnd();
+			this.writeRaw('<!-- ' + v + '-->');
+			this.count++;
+		},
+
+		writeRaw : function(v) {
+			this.str += v;
+		},
+
+		encode : function(s) {
+			return s.replace(/[<>&"]/g, function(v) {
+				switch (v) {
+					case '<':
+						return '<';
+
+					case '>':
+						return '>';
+
+					case '&':
+						return '&';
+
+					case '"':
+						return '"';
+				}
+
+				return v;
+			});
+		},
+
+		getContent : function() {
+			return this.str;
+		},
+
+		_writeAttributesEnd : function(s) {
+			if (!this.inAttr)
+				return;
+
+			this.inAttr = false;
+
+			if (s && this.elementCount == this.count) {
+				this.writeRaw(' />');
+				return false;
+			}
+
+			this.writeRaw('>');
+
+			return true;
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	// Shorten names
+	var extend = tinymce.extend, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, isIE = tinymce.isIE, isGecko = tinymce.isGecko;
+
+	function wildcardToRE(s) {
+		return s.replace(/([?+*])/g, '.$1');
+	};
+
+	tinymce.create('tinymce.dom.Serializer', {
+		Serializer : function(s) {
+			var t = this;
+
+			t.key = 0;
+			t.onPreProcess = new Dispatcher(t);
+			t.onPostProcess = new Dispatcher(t);
+
+			try {
+				t.writer = new tinymce.dom.XMLWriter();
+			} catch (ex) {
+				// IE might throw exception if ActiveX is disabled so we then switch to the slightly slower StringWriter
+				t.writer = new tinymce.dom.StringWriter();
+			}
+
+			// Default settings
+			t.settings = s = extend({
+				dom : tinymce.DOM,
+				valid_nodes : 0,
+				node_filter : 0,
+				attr_filter : 0,
+				invalid_attrs : /^(mce_|_moz_|sizset|sizcache)/,
+				closed : /^(br|hr|input|meta|img|link|param|area)$/,
+				entity_encoding : 'named',
+				entities : '160,nbsp,161,iexcl,162,cent,163,pound,164,curren,165,yen,166,brvbar,167,sect,168,uml,169,copy,170,ordf,171,laquo,172,not,173,shy,174,reg,175,macr,176,deg,177,plusmn,178,sup2,179,sup3,180,acute,181,micro,182,para,183,middot,184,cedil,185,sup1,186,ordm,187,raquo,188,frac14,189,frac12,190,frac34,191,iquest,192,Agrave,193,Aacute,194,Acirc,195,Atilde,196,Auml,197,Aring,198,AElig,199,Ccedil,200,Egrave,201,Eacute,202,Ecirc,203,Euml,204,Igrave,205,Iacute,206,Icirc,207,Iuml,208,ET [...]
+				valid_elements : '*[*]',
+				extended_valid_elements : 0,
+				valid_child_elements : 0,
+				invalid_elements : 0,
+				fix_table_elements : 1,
+				fix_list_elements : true,
+				fix_content_duplication : true,
+				convert_fonts_to_spans : false,
+				font_size_classes : 0,
+				font_size_style_values : 0,
+				apply_source_formatting : 0,
+				indent_mode : 'simple',
+				indent_char : '\t',
+				indent_levels : 1,
+				remove_linebreaks : 1,
+				remove_redundant_brs : 1,
+				element_format : 'xhtml'
+			}, s);
+
+			t.dom = s.dom;
+
+			if (s.remove_redundant_brs) {
+				t.onPostProcess.add(function(se, o) {
+					// Remove single BR at end of block elements since they get rendered
+					o.content = o.content.replace(/(<br \/>\s*)+<\/(p|h[1-6]|div|li)>/gi, function(a, b, c) {
+						// Check if it's a single element
+						if (/^<br \/>\s*<\//.test(a))
+							return '</' + c + '>';
+
+						return a;
+					});
+				});
+			}
+
+			// Remove XHTML element endings i.e. produce crap :) XHTML is better
+			if (s.element_format == 'html') {
+				t.onPostProcess.add(function(se, o) {
+					o.content = o.content.replace(/<([^>]+) \/>/g, '<$1>');
+				});
+			}
+
+			if (s.fix_list_elements) {
+				t.onPreProcess.add(function(se, o) {
+					var nl, x, a = ['ol', 'ul'], i, n, p, r = /^(OL|UL)$/, np;
+
+					function prevNode(e, n) {
+						var a = n.split(','), i;
+
+						while ((e = e.previousSibling) != null) {
+							for (i=0; i<a.length; i++) {
+								if (e.nodeName == a[i])
+									return e;
+							}
+						}
+
+						return null;
+					};
+
+					for (x=0; x<a.length; x++) {
+						nl = t.dom.select(a[x], o.node);
+
+						for (i=0; i<nl.length; i++) {
+							n = nl[i];
+							p = n.parentNode;
+
+							if (r.test(p.nodeName)) {
+								np = prevNode(n, 'LI');
+
+								if (!np) {
+									np = t.dom.create('li');
+									np.innerHTML = ' ';
+									np.appendChild(n);
+									p.insertBefore(np, p.firstChild);
+								} else
+									np.appendChild(n);
+							}
+						}
+					}
+				});
+			}
+
+			if (s.fix_table_elements) {
+				t.onPreProcess.add(function(se, o) {
+					// Since Opera will crash if you attach the node to a dynamic document we need to brrowser sniff a specific build
+					// so Opera users with an older version will have to live with less compaible output not much we can do here
+					if (!tinymce.isOpera || opera.buildNumber() >= 1767) {
+						each(t.dom.select('p table', o.node).reverse(), function(n) {
+							var parent = t.dom.getParent(n.parentNode, 'table,p');
+
+							if (parent.nodeName != 'TABLE') {
+								try {
+									t.dom.split(parent, n);
+								} catch (ex) {
+									// IE can sometimes fire an unknown runtime error so we just ignore it
+								}
+							}
+						});
+					}
+				});
+			}
+		},
+
+		setEntities : function(s) {
+			var t = this, a, i, l = {}, re = '', v;
+
+			// No need to setup more than once
+			if (t.entityLookup)
+				return;
+
+			// Build regex and lookup array
+			a = s.split(',');
+			for (i = 0; i < a.length; i += 2) {
+				v = a[i];
+
+				// Don't add default & " etc.
+				if (v == 34 || v == 38 || v == 60 || v == 62)
+					continue;
+
+				l[String.fromCharCode(a[i])] = a[i + 1];
+
+				v = parseInt(a[i]).toString(16);
+				re += '\\u' + '0000'.substring(v.length) + v;
+			}
+
+			if (!re) {
+				t.settings.entity_encoding = 'raw';
+				return;
+			}
+
+			t.entitiesRE = new RegExp('[' + re + ']', 'g');
+			t.entityLookup = l;
+		},
+
+		setValidChildRules : function(s) {
+			this.childRules = null;
+			this.addValidChildRules(s);
+		},
+
+		addValidChildRules : function(s) {
+			var t = this, inst, intr, bloc;
+
+			if (!s)
+				return;
+
+			inst = 'A|BR|SPAN|BDO|MAP|OBJECT|IMG|TT|I|B|BIG|SMALL|EM|STRONG|DFN|CODE|Q|SAMP|KBD|VAR|CITE|ABBR|ACRONYM|SUB|SUP|#text|#comment';
+			intr = 'A|BR|SPAN|BDO|OBJECT|APPLET|IMG|MAP|IFRAME|TT|I|B|U|S|STRIKE|BIG|SMALL|FONT|BASEFONT|EM|STRONG|DFN|CODE|Q|SAMP|KBD|VAR|CITE|ABBR|ACRONYM|SUB|SUP|INPUT|SELECT|TEXTAREA|LABEL|BUTTON|#text|#comment';
+			bloc = 'H[1-6]|P|DIV|ADDRESS|PRE|FORM|TABLE|LI|OL|UL|TD|CAPTION|BLOCKQUOTE|CENTER|DL|DT|DD|DIR|FIELDSET|FORM|NOSCRIPT|NOFRAMES|MENU|ISINDEX|SAMP';
+
+			each(s.split(','), function(s) {
+				var p = s.split(/\[|\]/), re;
+
+				s = '';
+				each(p[1].split('|'), function(v) {
+					if (s)
+						s += '|';
+
+					switch (v) {
+						case '%itrans':
+							v = intr;
+							break;
+
+						case '%itrans_na':
+							v = intr.substring(2);
+							break;
+
+						case '%istrict':
+							v = inst;
+							break;
+
+						case '%istrict_na':
+							v = inst.substring(2);
+							break;
+
+						case '%btrans':
+							v = bloc;
+							break;
+
+						case '%bstrict':
+							v = bloc;
+							break;
+					}
+
+					s += v;
+				});
+				re = new RegExp('^(' + s.toLowerCase() + ')$', 'i');
+
+				each(p[0].split('/'), function(s) {
+					t.childRules = t.childRules || {};
+					t.childRules[s] = re;
+				});
+			});
+
+			// Build regex
+			s = '';
+			each(t.childRules, function(v, k) {
+				if (s)
+					s += '|';
+
+				s += k;
+			});
+
+			t.parentElementsRE = new RegExp('^(' + s.toLowerCase() + ')$', 'i');
+
+			/*console.debug(t.parentElementsRE.toString());
+			each(t.childRules, function(v) {
+				console.debug(v.toString());
+			});*/
+		},
+
+		setRules : function(s) {
+			var t = this;
+
+			t._setup();
+			t.rules = {};
+			t.wildRules = [];
+			t.validElements = {};
+
+			return t.addRules(s);
+		},
+
+		addRules : function(s) {
+			var t = this, dr;
+
+			if (!s)
+				return;
+
+			t._setup();
+
+			each(s.split(','), function(s) {
+				var p = s.split(/\[|\]/), tn = p[0].split('/'), ra, at, wat, va = [];
+
+				// Extend with default rules
+				if (dr)
+					at = tinymce.extend([], dr.attribs);
+
+				// Parse attributes
+				if (p.length > 1) {
+					each(p[1].split('|'), function(s) {
+						var ar = {}, i;
+
+						at = at || [];
+
+						// Parse attribute rule
+						s = s.replace(/::/g, '~');
+						s = /^([!\-])?([\w*.?~_\-]+|)([=:<])?(.+)?$/.exec(s);
+						s[2] = s[2].replace(/~/g, ':');
+
+						// Add required attributes
+						if (s[1] == '!') {
+							ra = ra || [];
+							ra.push(s[2]);
+						}
+
+						// Remove inherited attributes
+						if (s[1] == '-') {
+							for (i = 0; i <at.length; i++) {
+								if (at[i].name == s[2]) {
+									at.splice(i, 1);
+									return;
+								}
+							}
+						}
+
+						switch (s[3]) {
+							// Add default attrib values
+							case '=':
+								ar.defaultVal = s[4] || '';
+								break;
+
+							// Add forced attrib values
+							case ':':
+								ar.forcedVal = s[4];
+								break;
+
+							// Add validation values
+							case '<':
+								ar.validVals = s[4].split('?');
+								break;
+						}
+
+						if (/[*.?]/.test(s[2])) {
+							wat = wat || [];
+							ar.nameRE = new RegExp('^' + wildcardToRE(s[2]) + '$');
+							wat.push(ar);
+						} else {
+							ar.name = s[2];
+							at.push(ar);
+						}
+
+						va.push(s[2]);
+					});
+				}
+
+				// Handle element names
+				each(tn, function(s, i) {
+					var pr = s.charAt(0), x = 1, ru = {};
+
+					// Extend with default rule data
+					if (dr) {
+						if (dr.noEmpty)
+							ru.noEmpty = dr.noEmpty;
+
+						if (dr.fullEnd)
+							ru.fullEnd = dr.fullEnd;
+
+						if (dr.padd)
+							ru.padd = dr.padd;
+					}
+
+					// Handle prefixes
+					switch (pr) {
+						case '-':
+							ru.noEmpty = true;
+							break;
+
+						case '+':
+							ru.fullEnd = true;
+							break;
+
+						case '#':
+							ru.padd = true;
+							break;
+
+						default:
+							x = 0;
+					}
+
+					tn[i] = s = s.substring(x);
+					t.validElements[s] = 1;
+
+					// Add element name or element regex
+					if (/[*.?]/.test(tn[0])) {
+						ru.nameRE = new RegExp('^' + wildcardToRE(tn[0]) + '$');
+						t.wildRules = t.wildRules || {};
+						t.wildRules.push(ru);
+					} else {
+						ru.name = tn[0];
+
+						// Store away default rule
+						if (tn[0] == '@')
+							dr = ru;
+
+						t.rules[s] = ru;
+					}
+
+					ru.attribs = at;
+
+					if (ra)
+						ru.requiredAttribs = ra;
+
+					if (wat) {
+						// Build valid attributes regexp
+						s = '';
+						each(va, function(v) {
+							if (s)
+								s += '|';
+
+							s += '(' + wildcardToRE(v) + ')';
+						});
+						ru.validAttribsRE = new RegExp('^' + s.toLowerCase() + '$');
+						ru.wildAttribs = wat;
+					}
+				});
+			});
+
+			// Build valid elements regexp
+			s = '';
+			each(t.validElements, function(v, k) {
+				if (s)
+					s += '|';
+
+				if (k != '@')
+					s += k;
+			});
+			t.validElementsRE = new RegExp('^(' + wildcardToRE(s.toLowerCase()) + ')$');
+
+			//console.debug(t.validElementsRE.toString());
+			//console.dir(t.rules);
+			//console.dir(t.wildRules);
+		},
+
+		findRule : function(n) {
+			var t = this, rl = t.rules, i, r;
+
+			t._setup();
+
+			// Exact match
+			r = rl[n];
+			if (r)
+				return r;
+
+			// Try wildcards
+			rl = t.wildRules;
+			for (i = 0; i < rl.length; i++) {
+				if (rl[i].nameRE.test(n))
+					return rl[i];
+			}
+
+			return null;
+		},
+
+		findAttribRule : function(ru, n) {
+			var i, wa = ru.wildAttribs;
+
+			for (i = 0; i < wa.length; i++) {
+				if (wa[i].nameRE.test(n))
+					return wa[i];
+			}
+
+			return null;
+		},
+
+		serialize : function(n, o) {
+			var h, t = this, doc, oldDoc, impl, selected;
+
+			t._setup();
+			o = o || {};
+			o.format = o.format || 'html';
+			t.processObj = o;
+
+			// IE looses the selected attribute on option elements so we need to store it
+			// See: http://support.microsoft.com/kb/829907
+			if (isIE) {
+				selected = [];
+				each(n.getElementsByTagName('option'), function(n) {
+					var v = t.dom.getAttrib(n, 'selected');
+
+					selected.push(v ? v : null);
+				});
+			}
+
+			n = n.cloneNode(true);
+
+			// IE looses the selected attribute on option elements so we need to restore it
+			if (isIE) {
+				each(n.getElementsByTagName('option'), function(n, i) {
+					t.dom.setAttrib(n, 'selected', selected[i]);
+				});
+			}
+
+			// Nodes needs to be attached to something in WebKit/Opera
+			// Older builds of Opera crashes if you attach the node to an document created dynamically
+			// and since we can't feature detect a crash we need to sniff the acutal build number
+			// This fix will make DOM ranges and make Sizzle happy!
+			impl = n.ownerDocument.implementation;
+			if (impl.createHTMLDocument && (tinymce.isOpera && opera.buildNumber() >= 1767)) {
+				// Create an empty HTML document
+				doc = impl.createHTMLDocument("");
+
+				// Add the element or it's children if it's a body element to the new document
+				each(n.nodeName == 'BODY' ? n.childNodes : [n], function(node) {
+					doc.body.appendChild(doc.importNode(node, true));
+				});
+
+				// Grab first child or body element for serialization
+				if (n.nodeName != 'BODY')
+					n = doc.body.firstChild;
+				else
+					n = doc.body;
+
+				// set the new document in DOMUtils so createElement etc works
+				oldDoc = t.dom.doc;
+				t.dom.doc = doc;
+			}
+
+			t.key = '' + (parseInt(t.key) + 1);
+
+			// Pre process
+			if (!o.no_events) {
+				o.node = n;
+				t.onPreProcess.dispatch(t, o);
+			}
+
+			// Serialize HTML DOM into a string
+			t.writer.reset();
+			t._serializeNode(n, o.getInner);
+
+			// Post process
+			o.content = t.writer.getContent();
+
+			// Restore the old document if it was changed
+			if (oldDoc)
+				t.dom.doc = oldDoc;
+
+			if (!o.no_events)
+				t.onPostProcess.dispatch(t, o);
+
+			t._postProcess(o);
+			o.node = null;
+
+			return tinymce.trim(o.content);
+		},
+
+		// Internal functions
+
+		_postProcess : function(o) {
+			var t = this, s = t.settings, h = o.content, sc = [], p;
+
+			if (o.format == 'html') {
+				// Protect some elements
+				p = t._protect({
+					content : h,
+					patterns : [
+						{pattern : /(<script[^>]*>)(.*?)(<\/script>)/g},
+						{pattern : /(<noscript[^>]*>)(.*?)(<\/noscript>)/g},
+						{pattern : /(<style[^>]*>)(.*?)(<\/style>)/g},
+						{pattern : /(<pre[^>]*>)(.*?)(<\/pre>)/g, encode : 1},
+						{pattern : /(<!--\[CDATA\[)(.*?)(\]\]-->)/g}
+					]
+				});
+
+				h = p.content;
+
+				// Entity encode
+				if (s.entity_encoding !== 'raw')
+					h = t._encode(h);
+
+				// Use BR instead of   padded P elements inside editor and use <p> </p> outside editor
+/*				if (o.set)
+					h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p><br /></p>');
+				else
+					h = h.replace(/<p>\s+( | |\u00a0|<br \/>)\s+<\/p>/g, '<p>$1</p>');*/
+
+				// Since Gecko and Safari keeps whitespace in the DOM we need to
+				// remove it inorder to match other browsers. But I think Gecko and Safari is right.
+				// This process is only done when getting contents out from the editor.
+				if (!o.set) {
+					// We need to replace paragraph whitespace with an nbsp before indentation to keep the \u00a0 char
+					h = h.replace(/<p>\s+<\/p>|<p([^>]+)>\s+<\/p>/g, s.entity_encoding == 'numeric' ? '<p$1> </p>' : '<p$1> </p>');
+
+					if (s.remove_linebreaks) {
+						h = h.replace(/\r?\n|\r/g, ' ');
+						h = h.replace(/(<[^>]+>)\s+/g, '$1 ');
+						h = h.replace(/\s+(<\/[^>]+>)/g, ' $1');
+						h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object) ([^>]+)>\s+/g, '<$1 $2>'); // Trim block start
+						h = h.replace(/<(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>\s+/g, '<$1>'); // Trim block start
+						h = h.replace(/\s+<\/(p|h[1-6]|blockquote|hr|div|table|tbody|tr|td|body|head|html|title|meta|style|pre|script|link|object)>/g, '</$1>'); // Trim block end
+					}
+
+					// Simple indentation
+					if (s.apply_source_formatting && s.indent_mode == 'simple') {
+						// Add line breaks before and after block elements
+						h = h.replace(/<(\/?)(ul|hr|table|meta|link|tbody|tr|object|body|head|html|map)(|[^>]+)>\s*/g, '\n<$1$2$3>\n');
+						h = h.replace(/\s*<(p|h[1-6]|blockquote|div|title|style|pre|script|td|li|area)(|[^>]+)>/g, '\n<$1$2>');
+						h = h.replace(/<\/(p|h[1-6]|blockquote|div|title|style|pre|script|td|li)>\s*/g, '</$1>\n');
+						h = h.replace(/\n\n/g, '\n');
+					}
+				}
+
+				h = t._unprotect(h, p);
+
+				// Restore CDATA sections
+				h = h.replace(/<!--\[CDATA\[([\s\S]+)\]\]-->/g, '<![CDATA[$1]]>');
+
+				// Restore the \u00a0 character if raw mode is enabled
+				if (s.entity_encoding == 'raw')
+					h = h.replace(/<p> <\/p>|<p([^>]+)> <\/p>/g, '<p$1>\u00a0</p>');
+
+				// Restore noscript elements
+				h = h.replace(/<noscript([^>]+|)>([\s\S]*?)<\/noscript>/g, function(v, attribs, text) {
+					return '<noscript' + attribs + '>' + t.dom.decode(text.replace(/<!--|-->/g, '')) + '</noscript>';
+				});
+			}
+
+			o.content = h;
+		},
+
+		_serializeNode : function(n, inn) {
+			var t = this, s = t.settings, w = t.writer, hc, el, cn, i, l, a, at, no, v, nn, ru, ar, iv, closed;
+
+			if (!s.node_filter || s.node_filter(n)) {
+				switch (n.nodeType) {
+					case 1: // Element
+						if (n.hasAttribute ? n.hasAttribute('mce_bogus') : n.getAttribute('mce_bogus'))
+							return;
+
+						iv = false;
+						hc = n.hasChildNodes();
+						nn = n.getAttribute('mce_name') || n.nodeName.toLowerCase();
+
+						// Add correct prefix on IE
+						if (isIE) {
+							if (n.scopeName !== 'HTML' && n.scopeName !== 'html')
+								nn = n.scopeName + ':' + nn;
+						}
+
+						// Remove mce prefix on IE needed for the abbr element
+						if (nn.indexOf('mce:') === 0)
+							nn = nn.substring(4);
+
+						// Check if valid
+						if (!t.validElementsRE || !t.validElementsRE.test(nn) || (t.invalidElementsRE && t.invalidElementsRE.test(nn)) || inn) {
+							iv = true;
+							break;
+						}
+
+						if (isIE) {
+							// Fix IE content duplication (DOM can have multiple copies of the same node)
+							if (s.fix_content_duplication) {
+								if (n.mce_serialized == t.key)
+									return;
+
+								n.mce_serialized = t.key;
+							}
+
+							// IE sometimes adds a / infront of the node name
+							if (nn.charAt(0) == '/')
+								nn = nn.substring(1);
+						} else if (isGecko) {
+							// Ignore br elements
+							if (n.nodeName === 'BR' && n.getAttribute('type') == '_moz')
+								return;
+						}
+
+						// Check if valid child
+						if (t.childRules) {
+							if (t.parentElementsRE.test(t.elementName)) {
+								if (!t.childRules[t.elementName].test(nn)) {
+									iv = true;
+									break;
+								}
+							}
+
+							t.elementName = nn;
+						}
+
+						ru = t.findRule(nn);
+						nn = ru.name || nn;
+						closed = s.closed.test(nn);
+
+						// Skip empty nodes or empty node name in IE
+						if ((!hc && ru.noEmpty) || (isIE && !nn)) {
+							iv = true;
+							break;
+						}
+
+						// Check required
+						if (ru.requiredAttribs) {
+							a = ru.requiredAttribs;
+
+							for (i = a.length - 1; i >= 0; i--) {
+								if (this.dom.getAttrib(n, a[i]) !== '')
+									break;
+							}
+
+							// None of the required was there
+							if (i == -1) {
+								iv = true;
+								break;
+							}
+						}
+
+						w.writeStartElement(nn);
+
+						// Add ordered attributes
+						if (ru.attribs) {
+							for (i=0, at = ru.attribs, l = at.length; i<l; i++) {
+								a = at[i];
+								v = t._getAttrib(n, a);
+
+								if (v !== null)
+									w.writeAttribute(a.name, v);
+							}
+						}
+
+						// Add wild attributes
+						if (ru.validAttribsRE) {
+							at = t.dom.getAttribs(n);
+							for (i=at.length-1; i>-1; i--) {
+								no = at[i];
+
+								if (no.specified) {
+									a = no.nodeName.toLowerCase();
+
+									if (s.invalid_attrs.test(a) || !ru.validAttribsRE.test(a))
+										continue;
+
+									ar = t.findAttribRule(ru, a);
+									v = t._getAttrib(n, ar, a);
+
+									if (v !== null)
+										w.writeAttribute(a, v);
+								}
+							}
+						}
+
+						// Write text from script
+						if (nn === 'script' && tinymce.trim(n.innerHTML)) {
+							w.writeText('// '); // Padd it with a comment so it will parse on older browsers
+							w.writeCDATA(n.innerHTML.replace(/<!--|-->|<\[CDATA\[|\]\]>/g, '')); // Remove comments and cdata stuctures
+							hc = false;
+							break;
+						}
+
+						// Padd empty nodes with a  
+						if (ru.padd) {
+							// If it has only one bogus child, padd it anyway workaround for <td><br /></td> bug
+							if (hc && (cn = n.firstChild) && cn.nodeType === 1 && n.childNodes.length === 1) {
+								if (cn.hasAttribute ? cn.hasAttribute('mce_bogus') : cn.getAttribute('mce_bogus'))
+									w.writeText('\u00a0');
+							} else if (!hc)
+								w.writeText('\u00a0'); // No children then padd it
+						}
+
+						break;
+
+					case 3: // Text
+						// Check if valid child
+						if (t.childRules && t.parentElementsRE.test(t.elementName)) {
+							if (!t.childRules[t.elementName].test(n.nodeName))
+								return;
+						}
+
+						return w.writeText(n.nodeValue);
+
+					case 4: // CDATA
+						return w.writeCDATA(n.nodeValue);
+
+					case 8: // Comment
+						return w.writeComment(n.nodeValue);
+				}
+			} else if (n.nodeType == 1)
+				hc = n.hasChildNodes();
+
+			if (hc && !closed) {
+				cn = n.firstChild;
+
+				while (cn) {
+					t._serializeNode(cn);
+					t.elementName = nn;
+					cn = cn.nextSibling;
+				}
+			}
+
+			// Write element end
+			if (!iv) {
+				if (!closed)
+					w.writeFullEndElement();
+				else
+					w.writeEndElement();
+			}
+		},
+
+		_protect : function(o) {
+			var t = this;
+
+			o.items = o.items || [];
+
+			function enc(s) {
+				return s.replace(/[\r\n\\]/g, function(c) {
+					if (c === '\n')
+						return '\\n';
+					else if (c === '\\')
+						return '\\\\';
+
+					return '\\r';
+				});
+			};
+
+			function dec(s) {
+				return s.replace(/\\[\\rn]/g, function(c) {
+					if (c === '\\n')
+						return '\n';
+					else if (c === '\\\\')
+						return '\\';
+
+					return '\r';
+				});
+			};
+
+			each(o.patterns, function(p) {
+				o.content = dec(enc(o.content).replace(p.pattern, function(x, a, b, c) {
+					b = dec(b);
+
+					if (p.encode)
+						b = t._encode(b);
+
+					o.items.push(b);
+					return a + '<!--mce:' + (o.items.length - 1) + '-->' + c;
+				}));
+			});
+
+			return o;
+		},
+
+		_unprotect : function(h, o) {
+			h = h.replace(/\<!--mce:([0-9]+)--\>/g, function(a, b) {
+				return o.items[parseInt(b)];
+			});
+
+			o.items = [];
+
+			return h;
+		},
+
+		_encode : function(h) {
+			var t = this, s = t.settings, l;
+
+			// Entity encode
+			if (s.entity_encoding !== 'raw') {
+				if (s.entity_encoding.indexOf('named') != -1) {
+					t.setEntities(s.entities);
+					l = t.entityLookup;
+
+					h = h.replace(t.entitiesRE, function(a) {
+						var v;
+
+						if (v = l[a])
+							a = '&' + v + ';';
+
+						return a;
+					});
+				}
+
+				if (s.entity_encoding.indexOf('numeric') != -1) {
+					h = h.replace(/[\u007E-\uFFFF]/g, function(a) {
+						return '&#' + a.charCodeAt(0) + ';';
+					});
+				}
+			}
+
+			return h;
+		},
+
+		_setup : function() {
+			var t = this, s = this.settings;
+
+			if (t.done)
+				return;
+
+			t.done = 1;
+
+			t.setRules(s.valid_elements);
+			t.addRules(s.extended_valid_elements);
+			t.addValidChildRules(s.valid_child_elements);
+
+			if (s.invalid_elements)
+				t.invalidElementsRE = new RegExp('^(' + wildcardToRE(s.invalid_elements.replace(/,/g, '|').toLowerCase()) + ')$');
+
+			if (s.attrib_value_filter)
+				t.attribValueFilter = s.attribValueFilter;
+		},
+
+		_getAttrib : function(n, a, na) {
+			var i, v;
+
+			na = na || a.name;
+
+			if (a.forcedVal && (v = a.forcedVal)) {
+				if (v === '{$uid}')
+					return this.dom.uniqueId();
+
+				return v;
+			}
+
+			v = this.dom.getAttrib(n, na);
+
+			switch (na) {
+				case 'rowspan':
+				case 'colspan':
+					// Whats the point? Remove usless attribute value
+					if (v == '1')
+						v = '';
+
+					break;
+			}
+
+			if (this.attribValueFilter)
+				v = this.attribValueFilter(na, v, n);
+
+			if (a.validVals) {
+				for (i = a.validVals.length - 1; i >= 0; i--) {
+					if (v == a.validVals[i])
+						break;
+				}
+
+				if (i == -1)
+					return null;
+			}
+
+			if (v === '' && typeof(a.defaultVal) != 'undefined') {
+				v = a.defaultVal;
+
+				if (v === '{$uid}')
+					return this.dom.uniqueId();
+
+				return v;
+			} else {
+				// Remove internal mceItemXX classes when content is extracted from editor
+				if (na == 'class' && this.processObj.get)
+					v = v.replace(/\s?mceItem\w+\s?/g, '');
+			}
+
+			if (v === '')
+				return null;
+
+
+			return v;
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	var each = tinymce.each, Event = tinymce.dom.Event;
+
+	tinymce.create('tinymce.dom.ScriptLoader', {
+		ScriptLoader : function(s) {
+			this.settings = s || {};
+			this.queue = [];
+			this.lookup = {};
+		},
+
+		isDone : function(u) {
+			return this.lookup[u] ? this.lookup[u].state == 2 : 0;
+		},
+
+		markDone : function(u) {
+			this.lookup[u] = {state : 2, url : u};
+		},
+
+		add : function(u, cb, s, pr) {
+			var t = this, lo = t.lookup, o;
+
+			if (o = lo[u]) {
+				// Is loaded fire callback
+				if (cb && o.state == 2)
+					cb.call(s || this);
+
+				return o;
+			}
+
+			o = {state : 0, url : u, func : cb, scope : s || this};
+
+			if (pr)
+				t.queue.unshift(o);
+			else
+				t.queue.push(o);
+
+			lo[u] = o;
+
+			return o;
+		},
+
+		load : function(u, cb, s) {
+			var t = this, o;
+
+			if (o = t.lookup[u]) {
+				// Is loaded fire callback
+				if (cb && o.state == 2)
+					cb.call(s || t);
+
+				return o;
+			}
+
+			function loadScript(u) {
+				if (Event.domLoaded || t.settings.strict_mode) {
+					tinymce.util.XHR.send({
+						url : tinymce._addVer(u),
+						error : t.settings.error,
+						async : false,
+						success : function(co) {
+							t.eval(co);
+						}
+					});
+				} else
+					document.write('<script type="text/javascript" src="' + tinymce._addVer(u) + '"></script>');
+			};
+
+			if (!tinymce.is(u, 'string')) {
+				each(u, function(u) {
+					loadScript(u);
+				});
+
+				if (cb)
+					cb.call(s || t);
+			} else {
+				loadScript(u);
+
+				if (cb)
+					cb.call(s || t);
+			}
+		},
+
+		loadQueue : function(cb, s) {
+			var t = this;
+
+			if (!t.queueLoading) {
+				t.queueLoading = 1;
+				t.queueCallbacks = [];
+
+				t.loadScripts(t.queue, function() {
+					t.queueLoading = 0;
+
+					if (cb)
+						cb.call(s || t);
+
+					each(t.queueCallbacks, function(o) {
+						o.func.call(o.scope);
+					});
+				});
+			} else if (cb)
+				t.queueCallbacks.push({func : cb, scope : s || t});
+		},
+
+		eval : function(co) {
+			var w = window;
+
+			// Evaluate script
+			if (!w.execScript) {
+				try {
+					eval.call(w, co);
+				} catch (ex) {
+					eval(co, w); // Firefox 3.0a8
+				}
+			} else
+				w.execScript(co); // IE
+		},
+
+		loadScripts : function(sc, cb, s) {
+			var t = this, lo = t.lookup;
+
+			function done(o) {
+				o.state = 2; // Has been loaded
+
+				// Run callback
+				if (o.func)
+					o.func.call(o.scope || t);
+			};
+
+			function allDone() {
+				var l;
+
+				// Check if all files are loaded
+				l = sc.length;
+				each(sc, function(o) {
+					o = lo[o.url];
+
+					if (o.state === 2) {// It has finished loading
+						done(o);
+						l--;
+					} else
+						load(o);
+				});
+
+				// They are all loaded
+				if (l === 0 && cb) {
+					cb.call(s || t);
+					cb = 0;
+				}
+			};
+
+			function load(o) {
+				if (o.state > 0)
+					return;
+
+				o.state = 1; // Is loading
+
+				tinymce.dom.ScriptLoader.loadScript(o.url, function() {
+					done(o);
+					allDone();
+				});
+
+				/*
+				tinymce.util.XHR.send({
+					url : o.url,
+					error : t.settings.error,
+					success : function(co) {
+						t.eval(co);
+						done(o);
+						allDone();
+					}
+				});
+				*/
+			};
+
+			each(sc, function(o) {
+				var u = o.url;
+
+				// Add to queue if needed
+				if (!lo[u]) {
+					lo[u] = o;
+					t.queue.push(o);
+				} else
+					o = lo[u];
+
+				// Is already loading or has been loaded
+				if (o.state > 0)
+					return;
+
+				if (!Event.domLoaded && !t.settings.strict_mode) {
+					var ix, ol = '';
+
+					// Add onload events
+					if (cb || o.func) {
+						o.state = 1; // Is loading
+
+						ix = tinymce.dom.ScriptLoader._addOnLoad(function() {
+							done(o);
+							allDone();
+						});
+
+						if (tinymce.isIE)
+							ol = ' onreadystatechange="';
+						else
+							ol = ' onload="';
+
+						ol += 'tinymce.dom.ScriptLoader._onLoad(this,\'' + u + '\',' + ix + ');"';
+					}
+
+					document.write('<script type="text/javascript" src="' + tinymce._addVer(u) + '"' + ol + '></script>');
+
+					if (!o.func)
+						done(o);
+				} else
+					load(o);
+			});
+
+			allDone();
+		},
+
+		// Static methods
+		'static' : {
+			_addOnLoad : function(f) {
+				var t = this;
+
+				t._funcs = t._funcs || [];
+				t._funcs.push(f);
+
+				return t._funcs.length - 1;
+			},
+
+			_onLoad : function(e, u, ix) {
+				if (!tinymce.isIE || e.readyState == 'complete')
+					this._funcs[ix].call(this);
+			},
+
+			loadScript : function(u, cb) {
+				var id = tinymce.DOM.uniqueId(), e;
+
+				function done() {
+					Event.clear(id);
+					tinymce.DOM.remove(id);
+
+					if (cb) {
+						cb.call(document, u);
+						cb = 0;
+					}
+				};
+
+				if (tinymce.isIE) {
+/*					Event.add(e, 'readystatechange', function(e) {
+						if (e.target && e.target.readyState == 'complete')
+							done();
+					});*/
+
+					tinymce.util.XHR.send({
+						url : tinymce._addVer(u),
+						async : false,
+						success : function(co) {
+							window.execScript(co);
+							done();
+						}
+					});
+				} else {
+					e = tinymce.DOM.create('script', {id : id, type : 'text/javascript', src : tinymce._addVer(u)});
+					Event.add(e, 'load', done);
+
+					// Check for head or body
+					(document.getElementsByTagName('head')[0] || document.body).appendChild(e);
+				}
+			}
+		}
+	});
+
+	// Global script loader
+	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
+})(tinymce);
+(function(tinymce) {
+	// Shorten class names
+	var DOM = tinymce.DOM, is = tinymce.is;
+
+	tinymce.create('tinymce.ui.Control', {
+		Control : function(id, s) {
+			this.id = id;
+			this.settings = s = s || {};
+			this.rendered = false;
+			this.onRender = new tinymce.util.Dispatcher(this);
+			this.classPrefix = '';
+			this.scope = s.scope || this;
+			this.disabled = 0;
+			this.active = 0;
+		},
+
+		setDisabled : function(s) {
+			var e;
+
+			if (s != this.disabled) {
+				e = DOM.get(this.id);
+
+				// Add accessibility title for unavailable actions
+				if (e && this.settings.unavailable_prefix) {
+					if (s) {
+						this.prevTitle = e.title;
+						e.title = this.settings.unavailable_prefix + ": " + e.title;
+					} else
+						e.title = this.prevTitle;
+				}
+
+				this.setState('Disabled', s);
+				this.setState('Enabled', !s);
+				this.disabled = s;
+			}
+		},
+
+		isDisabled : function() {
+			return this.disabled;
+		},
+
+		setActive : function(s) {
+			if (s != this.active) {
+				this.setState('Active', s);
+				this.active = s;
+			}
+		},
+
+		isActive : function() {
+			return this.active;
+		},
+
+		setState : function(c, s) {
+			var n = DOM.get(this.id);
+
+			c = this.classPrefix + c;
+
+			if (s)
+				DOM.addClass(n, c);
+			else
+				DOM.removeClass(n, c);
+		},
+
+		isRendered : function() {
+			return this.rendered;
+		},
+
+		renderHTML : function() {
+		},
+
+		renderTo : function(n) {
+			DOM.setHTML(n, this.renderHTML());
+		},
+
+		postRender : function() {
+			var t = this, b;
+
+			// Set pending states
+			if (is(t.disabled)) {
+				b = t.disabled;
+				t.disabled = -1;
+				t.setDisabled(b);
+			}
+
+			if (is(t.active)) {
+				b = t.active;
+				t.active = -1;
+				t.setActive(b);
+			}
+		},
+
+		remove : function() {
+			DOM.remove(this.id);
+			this.destroy();
+		},
+
+		destroy : function() {
+			tinymce.dom.Event.clear(this.id);
+		}
+	});
+})(tinymce);tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
+	Container : function(id, s) {
+		this.parent(id, s);
+
+		this.controls = [];
+
+		this.lookup = {};
+	},
+
+	add : function(c) {
+		this.lookup[c.id] = c;
+		this.controls.push(c);
+
+		return c;
+	},
+
+	get : function(n) {
+		return this.lookup[n];
+	}
+});
+
+tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
+	Separator : function(id, s) {
+		this.parent(id, s);
+		this.classPrefix = 'mceSeparator';
+	},
+
+	renderHTML : function() {
+		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix});
+	}
+});
+(function(tinymce) {
+	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
+
+	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
+		MenuItem : function(id, s) {
+			this.parent(id, s);
+			this.classPrefix = 'mceMenuItem';
+		},
+
+		setSelected : function(s) {
+			this.setState('Selected', s);
+			this.selected = s;
+		},
+
+		isSelected : function() {
+			return this.selected;
+		},
+
+		postRender : function() {
+			var t = this;
+			
+			t.parent();
+
+			// Set pending state
+			if (is(t.selected))
+				t.setSelected(t.selected);
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
+
+	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
+		Menu : function(id, s) {
+			var t = this;
+
+			t.parent(id, s);
+			t.items = {};
+			t.collapsed = false;
+			t.menuCount = 0;
+			t.onAddItem = new tinymce.util.Dispatcher(this);
+		},
+
+		expand : function(d) {
+			var t = this;
+
+			if (d) {
+				walk(t, function(o) {
+					if (o.expand)
+						o.expand();
+				}, 'items', t);
+			}
+
+			t.collapsed = false;
+		},
+
+		collapse : function(d) {
+			var t = this;
+
+			if (d) {
+				walk(t, function(o) {
+					if (o.collapse)
+						o.collapse();
+				}, 'items', t);
+			}
+
+			t.collapsed = true;
+		},
+
+		isCollapsed : function() {
+			return this.collapsed;
+		},
+
+		add : function(o) {
+			if (!o.settings)
+				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
+
+			this.onAddItem.dispatch(this, o);
+
+			return this.items[o.id] = o;
+		},
+
+		addSeparator : function() {
+			return this.add({separator : true});
+		},
+
+		addMenu : function(o) {
+			if (!o.collapse)
+				o = this.createMenu(o);
+
+			this.menuCount++;
+
+			return this.add(o);
+		},
+
+		hasMenus : function() {
+			return this.menuCount !== 0;
+		},
+
+		remove : function(o) {
+			delete this.items[o.id];
+		},
+
+		removeAll : function() {
+			var t = this;
+
+			walk(t, function(o) {
+				if (o.removeAll)
+					o.removeAll();
+				else
+					o.remove();
+
+				o.destroy();
+			}, 'items', t);
+
+			t.items = {};
+		},
+
+		createMenu : function(o) {
+			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
+
+			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
+
+			return m;
+		}
+	});
+})(tinymce);(function(tinymce) {
+	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
+
+	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
+		DropMenu : function(id, s) {
+			s = s || {};
+			s.container = s.container || DOM.doc.body;
+			s.offset_x = s.offset_x || 0;
+			s.offset_y = s.offset_y || 0;
+			s.vp_offset_x = s.vp_offset_x || 0;
+			s.vp_offset_y = s.vp_offset_y || 0;
+
+			if (is(s.icons) && !s.icons)
+				s['class'] += ' mceNoIcons';
+
+			this.parent(id, s);
+			this.onShowMenu = new tinymce.util.Dispatcher(this);
+			this.onHideMenu = new tinymce.util.Dispatcher(this);
+			this.classPrefix = 'mceMenu';
+		},
+
+		createMenu : function(s) {
+			var t = this, cs = t.settings, m;
+
+			s.container = s.container || cs.container;
+			s.parent = t;
+			s.constrain = s.constrain || cs.constrain;
+			s['class'] = s['class'] || cs['class'];
+			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
+			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
+			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
+
+			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
+
+			return m;
+		},
+
+		update : function() {
+			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
+
+			tw = s.max_width ? Math.min(tb.clientWidth, s.max_width) : tb.clientWidth;
+			th = s.max_height ? Math.min(tb.clientHeight, s.max_height) : tb.clientHeight;
+
+			if (!DOM.boxModel)
+				t.element.setStyles({width : tw + 2, height : th + 2});
+			else
+				t.element.setStyles({width : tw, height : th});
+
+			if (s.max_width)
+				DOM.setStyle(co, 'width', tw);
+
+			if (s.max_height) {
+				DOM.setStyle(co, 'height', th);
+
+				if (tb.clientHeight < s.max_height)
+					DOM.setStyle(co, 'overflow', 'hidden');
+			}
+		},
+
+		showMenu : function(x, y, px) {
+			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
+
+			t.collapse(1);
+
+			if (t.isMenuVisible)
+				return;
+
+			if (!t.rendered) {
+				co = DOM.add(t.settings.container, t.renderNode());
+
+				each(t.items, function(o) {
+					o.postRender();
+				});
+
+				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
+			} else
+				co = DOM.get('menu_' + t.id);
+
+			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
+			if (!tinymce.isOpera)
+				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
+
+			DOM.show(co);
+			t.update();
+
+			x += s.offset_x || 0;
+			y += s.offset_y || 0;
+			vp.w -= 4;
+			vp.h -= 4;
+
+			// Move inside viewport if not submenu
+			if (s.constrain) {
+				w = co.clientWidth - ot;
+				h = co.clientHeight - ot;
+				mx = vp.x + vp.w;
+				my = vp.y + vp.h;
+
+				if ((x + s.vp_offset_x + w) > mx)
+					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
+
+				if ((y + s.vp_offset_y + h) > my)
+					y = Math.max(0, (my - s.vp_offset_y) - h);
+			}
+
+			DOM.setStyles(co, {left : x , top : y});
+			t.element.update();
+
+			t.isMenuVisible = 1;
+			t.mouseClickFunc = Event.add(co, 'click', function(e) {
+				var m;
+
+				e = e.target;
+
+				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
+					m = t.items[e.id];
+
+					if (m.isDisabled())
+						return;
+
+					dm = t;
+
+					while (dm) {
+						if (dm.hideMenu)
+							dm.hideMenu();
+
+						dm = dm.settings.parent;
+					}
+
+					if (m.settings.onclick)
+						m.settings.onclick(e);
+
+					return Event.cancel(e); // Cancel to fix onbeforeunload problem
+				}
+			});
+
+			if (t.hasMenus()) {
+				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
+					var m, r, mi;
+
+					e = e.target;
+					if (e && (e = DOM.getParent(e, 'tr'))) {
+						m = t.items[e.id];
+
+						if (t.lastMenu)
+							t.lastMenu.collapse(1);
+
+						if (m.isDisabled())
+							return;
+
+						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
+							//p = DOM.getPos(s.container);
+							r = DOM.getRect(e);
+							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
+							t.lastMenu = m;
+							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
+						}
+					}
+				});
+			}
+
+			t.onShowMenu.dispatch(t);
+
+			if (s.keyboard_focus) {
+				Event.add(co, 'keydown', t._keyHandler, t);
+				DOM.select('a', 'menu_' + t.id)[0].focus(); // Select first link
+				t._focusIdx = 0;
+			}
+		},
+
+		hideMenu : function(c) {
+			var t = this, co = DOM.get('menu_' + t.id), e;
+
+			if (!t.isMenuVisible)
+				return;
+
+			Event.remove(co, 'mouseover', t.mouseOverFunc);
+			Event.remove(co, 'click', t.mouseClickFunc);
+			Event.remove(co, 'keydown', t._keyHandler);
+			DOM.hide(co);
+			t.isMenuVisible = 0;
+
+			if (!c)
+				t.collapse(1);
+
+			if (t.element)
+				t.element.hide();
+
+			if (e = DOM.get(t.id))
+				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
+
+			t.onHideMenu.dispatch(t);
+		},
+
+		add : function(o) {
+			var t = this, co;
+
+			o = t.parent(o);
+
+			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
+				t._add(DOM.select('tbody', co)[0], o);
+
+			return o;
+		},
+
+		collapse : function(d) {
+			this.parent(d);
+			this.hideMenu(1);
+		},
+
+		remove : function(o) {
+			DOM.remove(o.id);
+			this.destroy();
+
+			return this.parent(o);
+		},
+
+		destroy : function() {
+			var t = this, co = DOM.get('menu_' + t.id);
+
+			Event.remove(co, 'mouseover', t.mouseOverFunc);
+			Event.remove(co, 'click', t.mouseClickFunc);
+
+			if (t.element)
+				t.element.remove();
+
+			DOM.remove(co);
+		},
+
+		renderNode : function() {
+			var t = this, s = t.settings, n, tb, co, w;
+
+			w = DOM.create('div', {id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000'});
+			co = DOM.add(w, 'div', {id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
+			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
+
+			if (s.menu_line)
+				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
+
+//			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
+			n = DOM.add(co, 'table', {id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
+			tb = DOM.add(n, 'tbody');
+
+			each(t.items, function(o) {
+				t._add(tb, o);
+			});
+
+			t.rendered = true;
+
+			return w;
+		},
+
+		// Internal functions
+
+		_keyHandler : function(e) {
+			var t = this, kc = e.keyCode;
+
+			function focus(d) {
+				var i = t._focusIdx + d, e = DOM.select('a', 'menu_' + t.id)[i];
+
+				if (e) {
+					t._focusIdx = i;
+					e.focus();
+				}
+			};
+
+			switch (kc) {
+				case 38:
+					focus(-1); // Select first link
+					return;
+
+				case 40:
+					focus(1);
+					return;
+
+				case 13:
+					return;
+
+				case 27:
+					return this.hideMenu();
+			}
+		},
+
+		_add : function(tb, o) {
+			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
+
+			if (s.separator) {
+				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
+				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
+
+				if (n = ro.previousSibling)
+					DOM.addClass(n, 'mceLast');
+
+				return;
+			}
+
+			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
+			n = it = DOM.add(n, 'td');
+			n = a = DOM.add(n, 'a', {href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
+
+			DOM.addClass(it, s['class']);
+//			n = DOM.add(n, 'span', {'class' : 'item'});
+
+			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
+
+			if (s.icon_src)
+				DOM.add(ic, 'img', {src : s.icon_src});
+
+			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
+
+			if (o.settings.style)
+				DOM.setAttrib(n, 'style', o.settings.style);
+
+			if (tb.childNodes.length == 1)
+				DOM.addClass(ro, 'mceFirst');
+
+			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
+				DOM.addClass(ro, 'mceFirst');
+
+			if (o.collapse)
+				DOM.addClass(ro, cp + 'ItemSub');
+
+			if (n = ro.previousSibling)
+				DOM.removeClass(n, 'mceLast');
+
+			DOM.addClass(ro, 'mceLast');
+		}
+	});
+})(tinymce);(function(tinymce) {
+	var DOM = tinymce.DOM;
+
+	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
+		Button : function(id, s) {
+			this.parent(id, s);
+			this.classPrefix = 'mceButton';
+		},
+
+		renderHTML : function() {
+			var cp = this.classPrefix, s = this.settings, h, l;
+
+			l = DOM.encode(s.label || '');
+			h = '<a id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" title="' + DOM.encode(s.title) + '">';
+
+			if (s.image)
+				h += '<img class="mceIcon" src="' + s.image + '" />' + l + '</a>';
+			else
+				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '') + '</a>';
+
+			return h;
+		},
+
+		postRender : function() {
+			var t = this, s = t.settings;
+
+			tinymce.dom.Event.add(t.id, 'click', function(e) {
+				if (!t.isDisabled())
+					return s.onclick.call(s.scope, e);
+			});
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
+
+	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
+		ListBox : function(id, s) {
+			var t = this;
+
+			t.parent(id, s);
+
+			t.items = [];
+
+			t.onChange = new Dispatcher(t);
+
+			t.onPostRender = new Dispatcher(t);
+
+			t.onAdd = new Dispatcher(t);
+
+			t.onRenderMenu = new tinymce.util.Dispatcher(this);
+
+			t.classPrefix = 'mceListBox';
+		},
+
+		select : function(va) {
+			var t = this, fv, f;
+
+			if (va == undefined)
+				return t.selectByIndex(-1);
+
+			// Is string or number make function selector
+			if (va && va.call)
+				f = va;
+			else {
+				f = function(v) {
+					return v == va;
+				};
+			}
+
+			// Do we need to do something?
+			if (va != t.selectedValue) {
+				// Find item
+				each(t.items, function(o, i) {
+					if (f(o.value)) {
+						fv = 1;
+						t.selectByIndex(i);
+						return false;
+					}
+				});
+
+				if (!fv)
+					t.selectByIndex(-1);
+			}
+		},
+
+		selectByIndex : function(idx) {
+			var t = this, e, o;
+
+			if (idx != t.selectedIndex) {
+				e = DOM.get(t.id + '_text');
+				o = t.items[idx];
+
+				if (o) {
+					t.selectedValue = o.value;
+					t.selectedIndex = idx;
+					DOM.setHTML(e, DOM.encode(o.title));
+					DOM.removeClass(e, 'mceTitle');
+				} else {
+					DOM.setHTML(e, DOM.encode(t.settings.title));
+					DOM.addClass(e, 'mceTitle');
+					t.selectedValue = t.selectedIndex = null;
+				}
+
+				e = 0;
+			}
+		},
+
+		add : function(n, v, o) {
+			var t = this;
+
+			o = o || {};
+			o = tinymce.extend(o, {
+				title : n,
+				value : v
+			});
+
+			t.items.push(o);
+			t.onAdd.dispatch(t, o);
+		},
+
+		getLength : function() {
+			return this.items.length;
+		},
+
+		renderHTML : function() {
+			var h = '', t = this, s = t.settings, cp = t.classPrefix;
+
+			h = '<table id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
+			h += '<td>' + DOM.createHTML('a', {id : t.id + '_text', href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
+			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span></span>') + '</td>';
+			h += '</tr></tbody></table>';
+
+			return h;
+		},
+
+		showMenu : function() {
+			var t = this, p1, p2, e = DOM.get(this.id), m;
+
+			if (t.isDisabled() || t.items.length == 0)
+				return;
+
+			if (t.menu && t.menu.isMenuVisible)
+				return t.hideMenu();
+
+			if (!t.isMenuRendered) {
+				t.renderMenu();
+				t.isMenuRendered = true;
+			}
+
+			p1 = DOM.getPos(this.settings.menu_container);
+			p2 = DOM.getPos(e);
+
+			m = t.menu;
+			m.settings.offset_x = p2.x;
+			m.settings.offset_y = p2.y;
+			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
+
+			// Select in menu
+			if (t.oldID)
+				m.items[t.oldID].setSelected(0);
+
+			each(t.items, function(o) {
+				if (o.value === t.selectedValue) {
+					m.items[o.id].setSelected(1);
+					t.oldID = o.id;
+				}
+			});
+
+			m.showMenu(0, e.clientHeight);
+
+			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
+			DOM.addClass(t.id, t.classPrefix + 'Selected');
+
+			//DOM.get(t.id + '_text').focus();
+		},
+
+		hideMenu : function(e) {
+			var t = this;
+
+			// Prevent double toogles by canceling the mouse click event to the button
+			if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
+				return;
+
+			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
+				DOM.removeClass(t.id, t.classPrefix + 'Selected');
+				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
+
+				if (t.menu)
+					t.menu.hideMenu();
+			}
+		},
+
+		renderMenu : function() {
+			var t = this, m;
+
+			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
+				menu_line : 1,
+				'class' : t.classPrefix + 'Menu mceNoIcons',
+				max_width : 150,
+				max_height : 150
+			});
+
+			m.onHideMenu.add(t.hideMenu, t);
+
+			m.add({
+				title : t.settings.title,
+				'class' : 'mceMenuItemTitle',
+				onclick : function() {
+					if (t.settings.onselect('') !== false)
+						t.select(''); // Must be runned after
+				}
+			});
+
+			each(t.items, function(o) {
+				o.id = DOM.uniqueId();
+				o.onclick = function() {
+					if (t.settings.onselect(o.value) !== false)
+						t.select(o.value); // Must be runned after
+				};
+
+				m.add(o);
+			});
+
+			t.onRenderMenu.dispatch(t, m);
+			t.menu = m;
+		},
+
+		postRender : function() {
+			var t = this, cp = t.classPrefix;
+
+			Event.add(t.id, 'click', t.showMenu, t);
+			Event.add(t.id + '_text', 'focus', function(e) {
+				if (!t._focused) {
+					t.keyDownHandler = Event.add(t.id + '_text', 'keydown', function(e) {
+						var idx = -1, v, kc = e.keyCode;
+
+						// Find current index
+						each(t.items, function(v, i) {
+							if (t.selectedValue == v.value)
+								idx = i;
+						});
+
+						// Move up/down
+						if (kc == 38)
+							v = t.items[idx - 1];
+						else if (kc == 40)
+							v = t.items[idx + 1];
+						else if (kc == 13) {
+							// Fake select on enter
+							v = t.selectedValue;
+							t.selectedValue = null; // Needs to be null to fake change
+							t.settings.onselect(v);
+							return Event.cancel(e);
+						}
+
+						if (v) {
+							t.hideMenu();
+							t.select(v.value);
+						}
+					});
+				}
+
+				t._focused = 1;
+			});
+			Event.add(t.id + '_text', 'blur', function() {Event.remove(t.id + '_text', 'keydown', t.keyDownHandler); t._focused = 0;});
+
+			// Old IE doesn't have hover on all elements
+			if (tinymce.isIE6 || !DOM.boxModel) {
+				Event.add(t.id, 'mouseover', function() {
+					if (!DOM.hasClass(t.id, cp + 'Disabled'))
+						DOM.addClass(t.id, cp + 'Hover');
+				});
+
+				Event.add(t.id, 'mouseout', function() {
+					if (!DOM.hasClass(t.id, cp + 'Disabled'))
+						DOM.removeClass(t.id, cp + 'Hover');
+				});
+			}
+
+			t.onPostRender.dispatch(t, DOM.get(t.id));
+		},
+
+		destroy : function() {
+			this.parent();
+
+			Event.clear(this.id + '_text');
+			Event.clear(this.id + '_open');
+		}
+	});
+})(tinymce);(function(tinymce) {
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher;
+
+	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
+		NativeListBox : function(id, s) {
+			this.parent(id, s);
+			this.classPrefix = 'mceNativeListBox';
+		},
+
+		setDisabled : function(s) {
+			DOM.get(this.id).disabled = s;
+		},
+
+		isDisabled : function() {
+			return DOM.get(this.id).disabled;
+		},
+
+		select : function(va) {
+			var t = this, fv, f;
+
+			if (va == undefined)
+				return t.selectByIndex(-1);
+
+			// Is string or number make function selector
+			if (va && va.call)
+				f = va;
+			else {
+				f = function(v) {
+					return v == va;
+				};
+			}
+
+			// Do we need to do something?
+			if (va != t.selectedValue) {
+				// Find item
+				each(t.items, function(o, i) {
+					if (f(o.value)) {
+						fv = 1;
+						t.selectByIndex(i);
+						return false;
+					}
+				});
+
+				if (!fv)
+					t.selectByIndex(-1);
+			}
+		},
+
+		selectByIndex : function(idx) {
+			DOM.get(this.id).selectedIndex = idx + 1;
+			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
+		},
+
+		add : function(n, v, a) {
+			var o, t = this;
+
+			a = a || {};
+			a.value = v;
+
+			if (t.isRendered())
+				DOM.add(DOM.get(this.id), 'option', a, n);
+
+			o = {
+				title : n,
+				value : v,
+				attribs : a
+			};
+
+			t.items.push(o);
+			t.onAdd.dispatch(t, o);
+		},
+
+		getLength : function() {
+			return DOM.get(this.id).options.length - 1;
+		},
+
+		renderHTML : function() {
+			var h, t = this;
+
+			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
+
+			each(t.items, function(it) {
+				h += DOM.createHTML('option', {value : it.value}, it.title);
+			});
+
+			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox'}, h);
+
+			return h;
+		},
+
+		postRender : function() {
+			var t = this, ch;
+
+			t.rendered = true;
+
+			function onChange(e) {
+				var v = t.items[e.target.selectedIndex - 1];
+
+				if (v && (v = v.value)) {
+					t.onChange.dispatch(t, v);
+
+					if (t.settings.onselect)
+						t.settings.onselect(v);
+				}
+			};
+
+			Event.add(t.id, 'change', onChange);
+
+			// Accessibility keyhandler
+			Event.add(t.id, 'keydown', function(e) {
+				var bf;
+
+				Event.remove(t.id, 'change', ch);
+
+				bf = Event.add(t.id, 'blur', function() {
+					Event.add(t.id, 'change', onChange);
+					Event.remove(t.id, 'blur', bf);
+				});
+
+				if (e.keyCode == 13 || e.keyCode == 32) {
+					onChange(e);
+					return Event.cancel(e);
+				}
+			});
+
+			t.onPostRender.dispatch(t, DOM.get(t.id));
+		}
+	});
+})(tinymce);(function(tinymce) {
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
+
+	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
+		MenuButton : function(id, s) {
+			this.parent(id, s);
+
+			this.onRenderMenu = new tinymce.util.Dispatcher(this);
+
+			s.menu_container = s.menu_container || DOM.doc.body;
+		},
+
+		showMenu : function() {
+			var t = this, p1, p2, e = DOM.get(t.id), m;
+
+			if (t.isDisabled())
+				return;
+
+			if (!t.isMenuRendered) {
+				t.renderMenu();
+				t.isMenuRendered = true;
+			}
+
+			if (t.isMenuVisible)
+				return t.hideMenu();
+
+			p1 = DOM.getPos(t.settings.menu_container);
+			p2 = DOM.getPos(e);
+
+			m = t.menu;
+			m.settings.offset_x = p2.x;
+			m.settings.offset_y = p2.y;
+			m.settings.vp_offset_x = p2.x;
+			m.settings.vp_offset_y = p2.y;
+			m.settings.keyboard_focus = t._focused;
+			m.showMenu(0, e.clientHeight);
+
+			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
+			t.setState('Selected', 1);
+
+			t.isMenuVisible = 1;
+		},
+
+		renderMenu : function() {
+			var t = this, m;
+
+			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
+				menu_line : 1,
+				'class' : this.classPrefix + 'Menu',
+				icons : t.settings.icons
+			});
+
+			m.onHideMenu.add(t.hideMenu, t);
+
+			t.onRenderMenu.dispatch(t, m);
+			t.menu = m;
+		},
+
+		hideMenu : function(e) {
+			var t = this;
+
+			// Prevent double toogles by canceling the mouse click event to the button
+			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
+				return;
+
+			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
+				t.setState('Selected', 0);
+				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
+				if (t.menu)
+					t.menu.hideMenu();
+			}
+
+			t.isMenuVisible = 0;
+		},
+
+		postRender : function() {
+			var t = this, s = t.settings;
+
+			Event.add(t.id, 'click', function() {
+				if (!t.isDisabled()) {
+					if (s.onclick)
+						s.onclick(t.value);
+
+					t.showMenu();
+				}
+			});
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
+
+	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
+		SplitButton : function(id, s) {
+			this.parent(id, s);
+			this.classPrefix = 'mceSplitButton';
+		},
+
+		renderHTML : function() {
+			var h, t = this, s = t.settings, h1;
+
+			h = '<tbody><tr>';
+
+			if (s.image)
+				h1 = DOM.createHTML('img ', {src : s.image, 'class' : 'mceAction ' + s['class']});
+			else
+				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
+
+			h += '<td>' + DOM.createHTML('a', {id : t.id + '_action', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
+	
+			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']});
+			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
+
+			h += '</tr></tbody>';
+
+			return DOM.createHTML('table', {id : t.id, 'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', onmousedown : 'return false;', title : s.title}, h);
+		},
+
+		postRender : function() {
+			var t = this, s = t.settings;
+
+			if (s.onclick) {
+				Event.add(t.id + '_action', 'click', function() {
+					if (!t.isDisabled())
+						s.onclick(t.value);
+				});
+			}
+
+			Event.add(t.id + '_open', 'click', t.showMenu, t);
+			Event.add(t.id + '_open', 'focus', function() {t._focused = 1;});
+			Event.add(t.id + '_open', 'blur', function() {t._focused = 0;});
+
+			// Old IE doesn't have hover on all elements
+			if (tinymce.isIE6 || !DOM.boxModel) {
+				Event.add(t.id, 'mouseover', function() {
+					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
+						DOM.addClass(t.id, 'mceSplitButtonHover');
+				});
+
+				Event.add(t.id, 'mouseout', function() {
+					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
+						DOM.removeClass(t.id, 'mceSplitButtonHover');
+				});
+			}
+		},
+
+		destroy : function() {
+			this.parent();
+
+			Event.clear(this.id + '_action');
+			Event.clear(this.id + '_open');
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
+
+	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
+		ColorSplitButton : function(id, s) {
+			var t = this;
+
+			t.parent(id, s);
+
+			t.settings = s = tinymce.extend({
+				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
+				grid_width : 8,
+				default_color : '#888888'
+			}, t.settings);
+
+			t.onShowMenu = new tinymce.util.Dispatcher(t);
+
+			t.onHideMenu = new tinymce.util.Dispatcher(t);
+
+			t.value = s.default_color;
+		},
+
+		showMenu : function() {
+			var t = this, r, p, e, p2;
+
+			if (t.isDisabled())
+				return;
+
+			if (!t.isMenuRendered) {
+				t.renderMenu();
+				t.isMenuRendered = true;
+			}
+
+			if (t.isMenuVisible)
+				return t.hideMenu();
+
+			e = DOM.get(t.id);
+			DOM.show(t.id + '_menu');
+			DOM.addClass(e, 'mceSplitButtonSelected');
+			p2 = DOM.getPos(e);
+			DOM.setStyles(t.id + '_menu', {
+				left : p2.x,
+				top : p2.y + e.clientHeight,
+				zIndex : 200000
+			});
+			e = 0;
+
+			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
+			t.onShowMenu.dispatch(t);
+
+			if (t._focused) {
+				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
+					if (e.keyCode == 27)
+						t.hideMenu();
+				});
+
+				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
+			}
+
+			t.isMenuVisible = 1;
+		},
+
+		hideMenu : function(e) {
+			var t = this;
+
+			// Prevent double toogles by canceling the mouse click event to the button
+			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
+				return;
+
+			if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
+				DOM.removeClass(t.id, 'mceSplitButtonSelected');
+				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
+				Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
+				DOM.hide(t.id + '_menu');
+			}
+
+			t.onHideMenu.dispatch(t);
+
+			t.isMenuVisible = 0;
+		},
+
+		renderMenu : function() {
+			var t = this, m, i = 0, s = t.settings, n, tb, tr, w;
+
+			w = DOM.add(s.menu_container, 'div', {id : t.id + '_menu', 'class' : s['menu_class'] + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
+			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
+			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
+
+			n = DOM.add(m, 'table', {'class' : 'mceColorSplitMenu'});
+			tb = DOM.add(n, 'tbody');
+
+			// Generate color grid
+			i = 0;
+			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
+				c = c.replace(/^#/, '');
+
+				if (!i--) {
+					tr = DOM.add(tb, 'tr');
+					i = s.grid_width - 1;
+				}
+
+				n = DOM.add(tr, 'td');
+
+				n = DOM.add(n, 'a', {
+					href : 'javascript:;',
+					style : {
+						backgroundColor : '#' + c
+					},
+					mce_color : '#' + c
+				});
+			});
+
+			if (s.more_colors_func) {
+				n = DOM.add(tb, 'tr');
+				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
+				n = DOM.add(n, 'a', {id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
+
+				Event.add(n, 'click', function(e) {
+					s.more_colors_func.call(s.more_colors_scope || this);
+					return Event.cancel(e); // Cancel to fix onbeforeunload problem
+				});
+			}
+
+			DOM.addClass(m, 'mceColorSplitMenu');
+
+			Event.add(t.id + '_menu', 'click', function(e) {
+				var c;
+
+				e = e.target;
+
+				if (e.nodeName == 'A' && (c = e.getAttribute('mce_color')))
+					t.setColor(c);
+
+				return Event.cancel(e); // Prevent IE auto save warning
+			});
+
+			return w;
+		},
+
+		setColor : function(c) {
+			var t = this;
+
+			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
+
+			t.value = c;
+			t.hideMenu();
+			t.settings.onselect(c);
+		},
+
+		postRender : function() {
+			var t = this, id = t.id;
+
+			t.parent();
+			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
+			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
+		},
+
+		destroy : function() {
+			this.parent();
+
+			Event.clear(this.id + '_menu');
+			Event.clear(this.id + '_more');
+			DOM.remove(this.id + '_menu');
+		}
+	});
+})(tinymce);
+tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
+	renderHTML : function() {
+		var t = this, h = '', c, co, dom = tinymce.DOM, s = t.settings, i, pr, nx, cl;
+
+		cl = t.controls;
+		for (i=0; i<cl.length; i++) {
+			// Get current control, prev control, next control and if the control is a list box or not
+			co = cl[i];
+			pr = cl[i - 1];
+			nx = cl[i + 1];
+
+			// Add toolbar start
+			if (i === 0) {
+				c = 'mceToolbarStart';
+
+				if (co.Button)
+					c += ' mceToolbarStartButton';
+				else if (co.SplitButton)
+					c += ' mceToolbarStartSplitButton';
+				else if (co.ListBox)
+					c += ' mceToolbarStartListBox';
+
+				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
+			}
+
+			// Add toolbar end before list box and after the previous button
+			// This is to fix the o2k7 editor skins
+			if (pr && co.ListBox) {
+				if (pr.Button || pr.SplitButton)
+					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
+			}
+
+			// Render control HTML
+
+			// IE 8 quick fix, needed to propertly generate a hit area for anchors
+			if (dom.stdMode)
+				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
+			else
+				h += '<td>' + co.renderHTML() + '</td>';
+
+			// Add toolbar start after list box and before the next button
+			// This is to fix the o2k7 editor skins
+			if (nx && co.ListBox) {
+				if (nx.Button || nx.SplitButton)
+					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
+			}
+		}
+
+		c = 'mceToolbarEnd';
+
+		if (co.Button)
+			c += ' mceToolbarEndButton';
+		else if (co.SplitButton)
+			c += ' mceToolbarEndSplitButton';
+		else if (co.ListBox)
+			c += ' mceToolbarEndListBox';
+
+		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
+
+		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || ''}, '<tbody><tr>' + h + '</tr></tbody>');
+	}
+});
+(function(tinymce) {
+	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
+
+	tinymce.create('tinymce.AddOnManager', {
+		items : [],
+		urls : {},
+		lookup : {},
+
+		onAdd : new Dispatcher(this),
+
+		get : function(n) {
+			return this.lookup[n];
+		},
+
+		requireLangPack : function(n) {
+			var u, s = tinymce.EditorManager.settings;
+
+			if (s && s.language) {
+				u = this.urls[n] + '/langs/' + s.language + '.js';
+
+				if (!tinymce.dom.Event.domLoaded && !s.strict_mode)
+					tinymce.ScriptLoader.load(u);
+				else
+					tinymce.ScriptLoader.add(u);
+			}
+		},
+
+		add : function(id, o) {
+			this.items.push(o);
+			this.lookup[id] = o;
+			this.onAdd.dispatch(this, id, o);
+
+			return o;
+		},
+
+		load : function(n, u, cb, s) {
+			var t = this;
+
+			if (t.urls[n])
+				return;
+
+			if (u.indexOf('/') != 0 && u.indexOf('://') == -1)
+				u = tinymce.baseURL + '/' +  u;
+
+			t.urls[n] = u.substring(0, u.lastIndexOf('/'));
+			tinymce.ScriptLoader.add(u, cb, s);
+		}
+	});
+
+	// Create plugin and theme managers
+	tinymce.PluginManager = new tinymce.AddOnManager();
+	tinymce.ThemeManager = new tinymce.AddOnManager();
+}(tinymce));
+
+(function(tinymce) {
+	// Shorten names
+	var each = tinymce.each, extend = tinymce.extend, DOM = tinymce.DOM, Event = tinymce.dom.Event, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, explode = tinymce.explode;
+
+	tinymce.create('static tinymce.EditorManager', {
+		editors : {},
+
+		i18n : {},
+	
+		activeEditor : null,
+
+		preInit : function() {
+			var t = this, lo = window.location;
+
+			// Setup some URLs where the editor API is located and where the document is
+			tinymce.documentBaseURL = lo.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
+			if (!/[\/\\]$/.test(tinymce.documentBaseURL))
+				tinymce.documentBaseURL += '/';
+
+			tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
+			tinymce.EditorManager.baseURI = new tinymce.util.URI(tinymce.baseURL);
+
+			// Add before unload listener
+			// This was required since IE was leaking memory if you added and removed beforeunload listeners
+			// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
+			t.onBeforeUnload = new tinymce.util.Dispatcher(t);
+
+			// Must be on window or IE will leak if the editor is placed in frame or iframe
+			Event.add(window, 'beforeunload', function(e) {
+				t.onBeforeUnload.dispatch(t, e);
+			});
+		},
+
+		init : function(s) {
+			var t = this, pl, sl = tinymce.ScriptLoader, c, e, el = [], ed;
+
+			function execCallback(se, n, s) {
+				var f = se[n];
+
+				if (!f)
+					return;
+
+				if (tinymce.is(f, 'string')) {
+					s = f.replace(/\.\w+$/, '');
+					s = s ? tinymce.resolve(s) : 0;
+					f = tinymce.resolve(f);
+				}
+
+				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
+			};
+
+			s = extend({
+				theme : "simple",
+				language : "en",
+				strict_loading_mode : document.contentType == 'application/xhtml+xml'
+			}, s);
+
+			t.settings = s;
+
+			// If page not loaded and strict mode isn't enabled then load them
+			if (!Event.domLoaded && !s.strict_loading_mode) {
+				// Load language
+				if (s.language)
+					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
+
+				// Load theme
+				if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
+					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
+
+				// Load plugins
+				if (s.plugins) {
+					pl = explode(s.plugins);
+
+					// Load rest if plugins
+					each(pl, function(v) {
+						if (v && v.charAt(0) != '-' && !PluginManager.urls[v]) {
+							// Skip safari plugin for other browsers
+							if (!tinymce.isWebKit && v == 'safari')
+								return;
+
+							PluginManager.load(v, 'plugins/' + v + '/editor_plugin' + tinymce.suffix + '.js');
+						}
+					});
+				}
+
+				sl.loadQueue();
+			}
+
+			// Legacy call
+			Event.add(document, 'init', function() {
+				var l, co;
+
+				execCallback(s, 'onpageload');
+
+				// Verify that it's a valid browser
+				if (s.browsers) {
+					l = false;
+
+					each(explode(s.browsers), function(v) {
+						switch (v) {
+							case 'ie':
+							case 'msie':
+								if (tinymce.isIE)
+									l = true;
+								break;
+
+							case 'gecko':
+								if (tinymce.isGecko)
+									l = true;
+								break;
+
+							case 'safari':
+							case 'webkit':
+								if (tinymce.isWebKit)
+									l = true;
+								break;
+
+							case 'opera':
+								if (tinymce.isOpera)
+									l = true;
+
+								break;
+						}
+					});
+
+					// Not a valid one
+					if (!l)
+						return;
+				}
+
+				switch (s.mode) {
+					case "exact":
+						l = s.elements || '';
+
+						if(l.length > 0) {
+							each(explode(l), function(v) {
+								if (DOM.get(v)) {
+									ed = new tinymce.Editor(v, s);
+									el.push(ed);
+									ed.render(1);
+								} else {
+									c = 0;
+
+									each(document.forms, function(f) {
+										each(f.elements, function(e) {
+											if (e.name === v) {
+												v = 'mce_editor_' + c;
+												DOM.setAttrib(e, 'id', v);
+
+												ed = new tinymce.Editor(v, s);
+												el.push(ed);
+												ed.render(1);
+											}
+										});
+									});
+								}
+							});
+						}
+						break;
+
+					case "textareas":
+					case "specific_textareas":
+						function hasClass(n, c) {
+							return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
+						};
+
+						each(DOM.select('textarea'), function(v) {
+							if (s.editor_deselector && hasClass(v, s.editor_deselector))
+								return;
+
+							if (!s.editor_selector || hasClass(v, s.editor_selector)) {
+								// Can we use the name
+								e = DOM.get(v.name);
+								if (!v.id && !e)
+									v.id = v.name;
+
+								// Generate unique name if missing or already exists
+								if (!v.id || t.get(v.id))
+									v.id = DOM.uniqueId();
+
+								ed = new tinymce.Editor(v.id, s);
+								el.push(ed);
+								ed.render(1);
+							}
+						});
+						break;
+				}
+
+				// Call onInit when all editors are initialized
+				if (s.oninit) {
+					l = co = 0;
+
+					each (el, function(ed) {
+						co++;
+
+						if (!ed.initialized) {
+							// Wait for it
+							ed.onInit.add(function() {
+								l++;
+
+								// All done
+								if (l == co)
+									execCallback(s, 'oninit');
+							});
+						} else
+							l++;
+
+						// All done
+						if (l == co)
+							execCallback(s, 'oninit');					
+					});
+				}
+			});
+		},
+
+		get : function(id) {
+			return this.editors[id];
+		},
+
+		getInstanceById : function(id) {
+			return this.get(id);
+		},
+
+		add : function(e) {
+			this.editors[e.id] = e;
+			this._setActive(e);
+
+			return e;
+		},
+
+		remove : function(e) {
+			var t = this;
+
+			// Not in the collection
+			if (!t.editors[e.id])
+				return null;
+
+			delete t.editors[e.id];
+
+			// Select another editor since the active one was removed
+			if (t.activeEditor == e) {
+				t._setActive(null);
+
+				each(t.editors, function(e) {
+					t._setActive(e);
+					return false; // Break
+				});
+			}
+
+			e.destroy();
+
+			return e;
+		},
+
+		execCommand : function(c, u, v) {
+			var t = this, ed = t.get(v), w;
+
+			// Manager commands
+			switch (c) {
+				case "mceFocus":
+					ed.focus();
+					return true;
+
+				case "mceAddEditor":
+				case "mceAddControl":
+					if (!t.get(v))
+						new tinymce.Editor(v, t.settings).render();
+
+					return true;
+
+				case "mceAddFrameControl":
+					w = v.window;
+
+					// Add tinyMCE global instance and tinymce namespace to specified window
+					w.tinyMCE = tinyMCE;
+					w.tinymce = tinymce;
+
+					tinymce.DOM.doc = w.document;
+					tinymce.DOM.win = w;
+
+					ed = new tinymce.Editor(v.element_id, v);
+					ed.render();
+
+					// Fix IE memory leaks
+					if (tinymce.isIE) {
+						function clr() {
+							ed.destroy();
+							w.detachEvent('onunload', clr);
+							w = w.tinyMCE = w.tinymce = null; // IE leak
+						};
+
+						w.attachEvent('onunload', clr);
+					}
+
+					v.page_window = null;
+
+					return true;
+
+				case "mceRemoveEditor":
+				case "mceRemoveControl":
+					if (ed)
+						ed.remove();
+
+					return true;
+
+				case 'mceToggleEditor':
+					if (!ed) {
+						t.execCommand('mceAddControl', 0, v);
+						return true;
+					}
+
+					if (ed.isHidden())
+						ed.show();
+					else
+						ed.hide();
+
+					return true;
+			}
+
+			// Run command on active editor
+			if (t.activeEditor)
+				return t.activeEditor.execCommand(c, u, v);
+
+			return false;
+		},
+
+		execInstanceCommand : function(id, c, u, v) {
+			var ed = this.get(id);
+
+			if (ed)
+				return ed.execCommand(c, u, v);
+
+			return false;
+		},
+
+		triggerSave : function() {
+			each(this.editors, function(e) {
+				e.save();
+			});
+		},
+
+		addI18n : function(p, o) {
+			var lo, i18n = this.i18n;
+
+			if (!tinymce.is(p, 'string')) {
+				each(p, function(o, lc) {
+					each(o, function(o, g) {
+						each(o, function(o, k) {
+							if (g === 'common')
+								i18n[lc + '.' + k] = o;
+							else
+								i18n[lc + '.' + g + '.' + k] = o;
+						});
+					});
+				});
+			} else {
+				each(o, function(o, k) {
+					i18n[p + '.' + k] = o;
+				});
+			}
+		},
+
+		// Private methods
+
+		_setActive : function(e) {
+			this.selectedInstance = this.activeEditor = e;
+		}
+	});
+
+	tinymce.EditorManager.preInit();
+})(tinymce);
+
+var tinyMCE = window.tinyMCE = tinymce.EditorManager;
+(function(tinymce) {
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend, Dispatcher = tinymce.util.Dispatcher;
+	var each = tinymce.each, isGecko = tinymce.isGecko, isIE = tinymce.isIE, isWebKit = tinymce.isWebKit;
+	var is = tinymce.is, ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager, EditorManager = tinymce.EditorManager;
+	var inArray = tinymce.inArray, grep = tinymce.grep, explode = tinymce.explode;
+
+	tinymce.create('tinymce.Editor', {
+		Editor : function(id, s) {
+			var t = this;
+
+			t.id = t.editorId = id;
+
+			t.execCommands = {};
+			t.queryStateCommands = {};
+			t.queryValueCommands = {};
+
+			t.isNotDirty = false;
+
+			t.plugins = {};
+
+			// Add events to the editor
+			each([
+				'onPreInit',
+
+				'onBeforeRenderUI',
+
+				'onPostRender',
+
+				'onInit',
+
+				'onRemove',
+
+				'onActivate',
+
+				'onDeactivate',
+
+				'onClick',
+
+				'onEvent',
+
+				'onMouseUp',
+
+				'onMouseDown',
+
+				'onDblClick',
+
+				'onKeyDown',
+
+				'onKeyUp',
+
+				'onKeyPress',
+
+				'onContextMenu',
+
+				'onSubmit',
+
+				'onReset',
+
+				'onPaste',
+
+				'onPreProcess',
+
+				'onPostProcess',
+
+				'onBeforeSetContent',
+
+				'onBeforeGetContent',
+
+				'onSetContent',
+
+				'onGetContent',
+
+				'onLoadContent',
+
+				'onSaveContent',
+
+				'onNodeChange',
+
+				'onChange',
+
+				'onBeforeExecCommand',
+
+				'onExecCommand',
+
+				'onUndo',
+
+				'onRedo',
+
+				'onVisualAid',
+
+				'onSetProgressState'
+			], function(e) {
+				t[e] = new Dispatcher(t);
+			});
+
+			t.settings = s = extend({
+				id : id,
+				language : 'en',
+				docs_language : 'en',
+				theme : 'simple',
+				skin : 'default',
+				delta_width : 0,
+				delta_height : 0,
+				popup_css : '',
+				plugins : '',
+				document_base_url : tinymce.documentBaseURL,
+				add_form_submit_trigger : 1,
+				submit_patch : 1,
+				add_unload_trigger : 1,
+				convert_urls : 1,
+				relative_urls : 1,
+				remove_script_host : 1,
+				table_inline_editing : 0,
+				object_resizing : 1,
+				cleanup : 1,
+				accessibility_focus : 1,
+				custom_shortcuts : 1,
+				custom_undo_redo_keyboard_shortcuts : 1,
+				custom_undo_redo_restore_selection : 1,
+				custom_undo_redo : 1,
+				doctype : '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">',
+				visual_table_class : 'mceItemTable',
+				visual : 1,
+				inline_styles : true,
+				convert_fonts_to_spans : true,
+				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
+				apply_source_formatting : 1,
+				directionality : 'ltr',
+				forced_root_block : 'p',
+				valid_elements : '@[id|class|style|title|dir<ltr?rtl|lang|xml::lang|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],a[rel|rev|charset|hreflang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur],strong/b,em/i,strike,u,#p,-ol[type|compact],-ul[type|compact],-li,br,img[longdesc|usemap|src|border|alt=|title|hspace|vspace|width|height|align],-sub,-sup,-blockquote[cite],-table[border|cellspacing|cellpadding|width|f [...]
+				hidden_input : 1,
+				padd_empty_editor : 1,
+				render_ui : 1,
+				init_theme : 1,
+				force_p_newlines : 1,
+				indentation : '30px',
+				keep_styles : 1,
+				fix_table_elements : 1,
+				removeformat_selector : 'span,b,strong,em,i,font,u,strike'
+			}, s);
+
+			t.documentBaseURI = new tinymce.util.URI(s.document_base_url || tinymce.documentBaseURL, {
+				base_uri : tinyMCE.baseURI
+			});
+
+			t.baseURI = EditorManager.baseURI;
+
+			// Call setup
+			t.execCallback('setup', t);
+		},
+
+		render : function(nst) {
+			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
+
+			// Page is not loaded yet, wait for it
+			if (!Event.domLoaded) {
+				Event.add(document, 'init', function() {
+					t.render();
+				});
+				return;
+			}
+
+			// Force strict loading mode if render us called by user and not internally
+			if (!nst) {
+				s.strict_loading_mode = 1;
+				tinyMCE.settings = s;
+			}
+
+			// Element not found, then skip initialization
+			if (!t.getElement())
+				return;
+
+			if (s.strict_loading_mode) {
+				sl.settings.strict_mode = s.strict_loading_mode;
+				tinymce.DOM.settings.strict = 1;
+			}
+
+			// Add hidden input for non input elements inside form elements
+			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
+				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
+
+			if (tinymce.WindowManager)
+				t.windowManager = new tinymce.WindowManager(t);
+
+			if (s.encoding == 'xml') {
+				t.onGetContent.add(function(ed, o) {
+					if (o.save)
+						o.content = DOM.encode(o.content);
+				});
+			}
+
+			if (s.add_form_submit_trigger) {
+				t.onSubmit.addToTop(function() {
+					if (t.initialized) {
+						t.save();
+						t.isNotDirty = 1;
+					}
+				});
+			}
+
+			if (s.add_unload_trigger) {
+				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
+					if (t.initialized && !t.destroyed && !t.isHidden())
+						t.save({format : 'raw', no_events : true});
+				});
+			}
+
+			tinymce.addUnload(t.destroy, t);
+
+			if (s.submit_patch) {
+				t.onBeforeRenderUI.add(function() {
+					var n = t.getElement().form;
+
+					if (!n)
+						return;
+
+					// Already patched
+					if (n._mceOldSubmit)
+						return;
+
+					// Check page uses id="submit" or name="submit" for it's submit button
+					if (!n.submit.nodeType && !n.submit.length) {
+						t.formElement = n;
+						n._mceOldSubmit = n.submit;
+						n.submit = function() {
+							// Save all instances
+							EditorManager.triggerSave();
+							t.isNotDirty = 1;
+
+							return t.formElement._mceOldSubmit(t.formElement);
+						};
+					}
+
+					n = null;
+				});
+			}
+
+			// Load scripts
+			function loadScripts() {
+				if (s.language)
+					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
+
+				if (s.theme && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
+					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
+
+				each(explode(s.plugins), function(p) {
+					if (p && p.charAt(0) != '-' && !PluginManager.urls[p]) {
+						// Skip safari plugin for other browsers
+						if (!isWebKit && p == 'safari')
+							return;
+
+						PluginManager.load(p, 'plugins/' + p + '/editor_plugin' + tinymce.suffix + '.js');
+					}
+				});
+
+				// Init when que is loaded
+				sl.loadQueue(function() {
+					if (!t.removed)
+						t.init();
+				});
+			};
+
+			loadScripts();
+		},
+
+		init : function() {
+			var n, t = this, s = t.settings, w, h, e = t.getElement(), o, ti, u, bi, bc, re;
+
+			EditorManager.add(t);
+
+			if (s.theme) {
+				s.theme = s.theme.replace(/-/, '');
+				o = ThemeManager.get(s.theme);
+				t.theme = new o();
+
+				if (t.theme.init && s.init_theme)
+					t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
+			}
+
+			// Create all plugins
+			each(explode(s.plugins.replace(/\-/g, '')), function(p) {
+				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
+
+				if (c) {
+					po = new c(t, u);
+
+					t.plugins[p] = po;
+
+					if (po.init)
+						po.init(t, u);
+				}
+			});
+
+			// Setup popup CSS path(s)
+			if (s.popup_css !== false) {
+				if (s.popup_css)
+					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
+				else
+					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
+			}
+
+			if (s.popup_css_add)
+				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
+
+			t.controlManager = new tinymce.ControlManager(t);
+
+			t.undoManager = new tinymce.UndoManager(t);
+
+			// Pass through
+			t.undoManager.onAdd.add(function(um, l) {
+				if (!l.initial)
+					return t.onChange.dispatch(t, l, um);
+			});
+
+			t.undoManager.onUndo.add(function(um, l) {
+				return t.onUndo.dispatch(t, l, um);
+			});
+
+			t.undoManager.onRedo.add(function(um, l) {
+				return t.onRedo.dispatch(t, l, um);
+			});
+
+			if (s.custom_undo_redo) {
+				t.onExecCommand.add(function(ed, cmd, ui, val, a) {
+					if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!a || !a.skip_undo))
+						t.undoManager.add();
+				});
+			}
+
+			t.onExecCommand.add(function(ed, c) {
+				// Don't refresh the select lists until caret move
+				if (!/^(FontName|FontSize)$/.test(c))
+					t.nodeChanged();
+			});
+
+			// Remove ghost selections on images and tables in Gecko
+			if (isGecko) {
+				function repaint(a, o) {
+					if (!o || !o.initial)
+						t.execCommand('mceRepaint');
+				};
+
+				t.onUndo.add(repaint);
+				t.onRedo.add(repaint);
+				t.onSetContent.add(repaint);
+			}
+
+			// Enables users to override the control factory
+			t.onBeforeRenderUI.dispatch(t, t.controlManager);
+
+			// Measure box
+			if (s.render_ui) {
+				w = s.width || e.style.width || e.offsetWidth;
+				h = s.height || e.style.height || e.offsetHeight;
+				t.orgDisplay = e.style.display;
+				re = /^[0-9\.]+(|px)$/i;
+
+				if (re.test('' + w))
+					w = Math.max(parseInt(w) + (o.deltaWidth || 0), 100);
+
+				if (re.test('' + h))
+					h = Math.max(parseInt(h) + (o.deltaHeight || 0), 100);
+
+				// Render UI
+				o = t.theme.renderUI({
+					targetNode : e,
+					width : w,
+					height : h,
+					deltaWidth : s.delta_width,
+					deltaHeight : s.delta_height
+				});
+
+				t.editorContainer = o.editorContainer;
+			}
+
+
+			// User specified a document.domain value
+			if (document.domain && location.hostname != document.domain)
+				tinymce.relaxedDomain = document.domain;
+
+			// Resize editor
+			DOM.setStyles(o.sizeContainer || o.editorContainer, {
+				width : w,
+				height : h
+			});
+
+			h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
+			if (h < 100)
+				h = 100;
+
+			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
+
+			// We only need to override paths if we have to
+			// IE has a bug where it remove site absolute urls to relative ones if this is specified
+			if (s.document_base_url != tinymce.documentBaseURL)
+				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
+
+			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
+
+			if (tinymce.relaxedDomain)
+				t.iframeHTML += '<script type="text/javascript">document.domain = "' + tinymce.relaxedDomain + '";</script>';
+
+			bi = s.body_id || 'tinymce';
+			if (bi.indexOf('=') != -1) {
+				bi = t.getParam('body_id', '', 'hash');
+				bi = bi[t.id] || bi;
+			}
+
+			bc = s.body_class || '';
+			if (bc.indexOf('=') != -1) {
+				bc = t.getParam('body_class', '', 'hash');
+				bc = bc[t.id] || '';
+			}
+
+			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '"></body></html>';
+
+			// Domain relaxing enabled, then set document domain
+			if (tinymce.relaxedDomain) {
+				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
+				if (isIE || (tinymce.isOpera && parseFloat(opera.version()) >= 9.5))
+					u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.setupIframe();})()';
+				else if (tinymce.isOpera)
+					u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";document.close();ed.setupIframe();})()';					
+			}
+
+			// Create iframe
+			n = DOM.add(o.iframeContainer, 'iframe', {
+				id : t.id + "_ifr",
+				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
+				frameBorder : '0',
+				style : {
+					width : '100%',
+					height : h
+				}
+			});
+
+			t.contentAreaContainer = o.iframeContainer;
+			DOM.get(o.editorContainer).style.display = t.orgDisplay;
+			DOM.get(t.id).style.display = 'none';
+
+			if (!isIE || !tinymce.relaxedDomain)
+				t.setupIframe();
+
+			e = n = o = null; // Cleanup
+		},
+
+		setupIframe : function() {
+			var t = this, s = t.settings, e = DOM.get(t.id), d = t.getDoc(), h, b;
+
+			// Setup iframe body
+			if (!isIE || !tinymce.relaxedDomain) {
+				d.open();
+				d.write(t.iframeHTML);
+				d.close();
+			}
+
+			// Design mode needs to be added here Ctrl+A will fail otherwise
+			if (!isIE) {
+				try {
+					if (!s.readonly)
+						d.designMode = 'On';
+				} catch (ex) {
+					// Will fail on Gecko if the editor is placed in an hidden container element
+					// The design mode will be set ones the editor is focused
+				}
+			}
+
+			// IE needs to use contentEditable or it will display non secure items for HTTPS
+			if (isIE) {
+				// It will not steal focus if we hide it while setting contentEditable
+				b = t.getBody();
+				DOM.hide(b);
+
+				if (!s.readonly)
+					b.contentEditable = true;
+
+				DOM.show(b);
+			}
+
+			t.dom = new tinymce.dom.DOMUtils(t.getDoc(), {
+				keep_values : true,
+				url_converter : t.convertURL,
+				url_converter_scope : t,
+				hex_colors : s.force_hex_style_colors,
+				class_filter : s.class_filter,
+				update_styles : 1,
+				fix_ie_paragraphs : 1
+			});
+
+			t.serializer = new tinymce.dom.Serializer(extend(s, {
+				valid_elements : s.verify_html === false ? '*[*]' : s.valid_elements,
+				dom : t.dom
+			}));
+
+			t.selection = new tinymce.dom.Selection(t.dom, t.getWin(), t.serializer);
+
+			t.forceBlocks = new tinymce.ForceBlocks(t, {
+				forced_root_block : s.forced_root_block
+			});
+			t.editorCommands = new tinymce.EditorCommands(t);
+
+			// Pass through
+			t.serializer.onPreProcess.add(function(se, o) {
+				return t.onPreProcess.dispatch(t, o, se);
+			});
+
+			t.serializer.onPostProcess.add(function(se, o) {
+				return t.onPostProcess.dispatch(t, o, se);
+			});
+
+			t.onPreInit.dispatch(t);
+
+			if (!s.gecko_spellcheck)
+				t.getBody().spellcheck = 0;
+
+			if (!s.readonly)
+				t._addEvents();
+
+			t.controlManager.onPostRender.dispatch(t, t.controlManager);
+			t.onPostRender.dispatch(t);
+
+			if (s.directionality)
+				t.getBody().dir = s.directionality;
+
+			if (s.nowrap)
+				t.getBody().style.whiteSpace = "nowrap";
+
+			if (s.custom_elements) {
+				function handleCustom(ed, o) {
+					each(explode(s.custom_elements), function(v) {
+						var n;
+
+						if (v.indexOf('~') === 0) {
+							v = v.substring(1);
+							n = 'span';
+						} else
+							n = 'div';
+
+						o.content = o.content.replace(new RegExp('<(' + v + ')([^>]*)>', 'g'), '<' + n + ' mce_name="$1"$2>');
+						o.content = o.content.replace(new RegExp('</(' + v + ')>', 'g'), '</' + n + '>');
+					});
+				};
+
+				t.onBeforeSetContent.add(handleCustom);
+				t.onPostProcess.add(function(ed, o) {
+					if (o.set)
+						handleCustom(ed, o);
+				});
+			}
+
+			if (s.handle_node_change_callback) {
+				t.onNodeChange.add(function(ed, cm, n) {
+					t.execCallback('handle_node_change_callback', t.id, n, -1, -1, true, t.selection.isCollapsed());
+				});
+			}
+
+			if (s.save_callback) {
+				t.onSaveContent.add(function(ed, o) {
+					var h = t.execCallback('save_callback', t.id, o.content, t.getBody());
+
+					if (h)
+						o.content = h;
+				});
+			}
+
+			if (s.onchange_callback) {
+				t.onChange.add(function(ed, l) {
+					t.execCallback('onchange_callback', t, l);
+				});
+			}
+
+			if (s.convert_newlines_to_brs) {
+				t.onBeforeSetContent.add(function(ed, o) {
+					if (o.initial)
+						o.content = o.content.replace(/\r?\n/g, '<br />');
+				});
+			}
+
+			if (s.fix_nesting && isIE) {
+				t.onBeforeSetContent.add(function(ed, o) {
+					o.content = t._fixNesting(o.content);
+				});
+			}
+
+			if (s.preformatted) {
+				t.onPostProcess.add(function(ed, o) {
+					o.content = o.content.replace(/^\s*<pre.*?>/, '');
+					o.content = o.content.replace(/<\/pre>\s*$/, '');
+
+					if (o.set)
+						o.content = '<pre class="mceItemHidden">' + o.content + '</pre>';
+				});
+			}
+
+			if (s.verify_css_classes) {
+				t.serializer.attribValueFilter = function(n, v) {
+					var s, cl;
+
+					if (n == 'class') {
+						// Build regexp for classes
+						if (!t.classesRE) {
+							cl = t.dom.getClasses();
+
+							if (cl.length > 0) {
+								s = '';
+
+								each (cl, function(o) {
+									s += (s ? '|' : '') + o['class'];
+								});
+
+								t.classesRE = new RegExp('(' + s + ')', 'gi');
+							}
+						}
+
+						return !t.classesRE || /(\bmceItem\w+\b|\bmceTemp\w+\b)/g.test(v) || t.classesRE.test(v) ? v : '';
+					}
+
+					return v;
+				};
+			}
+
+			if (s.convert_fonts_to_spans)
+				t._convertFonts();
+
+			if (s.inline_styles)
+				t._convertInlineElements();
+
+			if (s.cleanup_callback) {
+				t.onBeforeSetContent.add(function(ed, o) {
+					o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
+				});
+
+				t.onPreProcess.add(function(ed, o) {
+					if (o.set)
+						t.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
+
+					if (o.get)
+						t.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
+				});
+
+				t.onPostProcess.add(function(ed, o) {
+					if (o.set)
+						o.content = t.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
+
+					if (o.get)						
+						o.content = t.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
+				});
+			}
+
+			if (s.save_callback) {
+				t.onGetContent.add(function(ed, o) {
+					if (o.save)
+						o.content = t.execCallback('save_callback', t.id, o.content, t.getBody());
+				});
+			}
+
+			if (s.handle_event_callback) {
+				t.onEvent.add(function(ed, e, o) {
+					if (t.execCallback('handle_event_callback', e, ed, o) === false)
+						Event.cancel(e);
+				});
+			}
+
+			// Add visual aids when new contents is added
+			t.onSetContent.add(function() {
+				t.addVisual(t.getBody());
+			});
+
+			// Remove empty contents
+			if (s.padd_empty_editor) {
+				t.onPostProcess.add(function(ed, o) {
+					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
+				});
+			}
+
+			if (isGecko) {
+				// Fix gecko link bug, when a link is placed at the end of block elements there is
+				// no way to move the caret behind the link. This fix adds a bogus br element after the link
+				function fixLinks(ed, o) {
+					each(ed.dom.select('a'), function(n) {
+						var pn = n.parentNode;
+
+						if (ed.dom.isBlock(pn) && pn.lastChild === n)
+							ed.dom.add(pn, 'br', {'mce_bogus' : 1});
+					});
+				};
+
+				t.onExecCommand.add(function(ed, cmd) {
+					if (cmd === 'CreateLink')
+						fixLinks(ed);
+				});
+
+				t.onSetContent.add(t.selection.onSetContent.add(fixLinks));
+
+				if (!s.readonly) {
+					try {
+						// Design mode must be set here once again to fix a bug where
+						// Ctrl+A/Delete/Backspace didn't work if the editor was added using mceAddControl then removed then added again
+						d.designMode = 'Off';
+						d.designMode = 'On';
+					} catch (ex) {
+						// Will fail on Gecko if the editor is placed in an hidden container element
+						// The design mode will be set ones the editor is focused
+					}
+				}
+			}
+
+			// A small timeout was needed since firefox will remove. Bug: #1838304
+			setTimeout(function () {
+				if (t.removed)
+					return;
+
+				t.load({initial : true, format : (s.cleanup_on_startup ? 'html' : 'raw')});
+				t.startContent = t.getContent({format : 'raw'});
+				t.undoManager.add({initial : true});
+				t.initialized = true;
+
+				t.onInit.dispatch(t);
+				t.execCallback('setupcontent_callback', t.id, t.getBody(), t.getDoc());
+				t.execCallback('init_instance_callback', t);
+				t.focus(true);
+				t.nodeChanged({initial : 1});
+
+				// Load specified content CSS last
+				if (s.content_css) {
+					tinymce.each(explode(s.content_css), function(u) {
+						t.dom.loadCSS(t.documentBaseURI.toAbsolute(u));
+					});
+				}
+
+				// Handle auto focus
+				if (s.auto_focus) {
+					setTimeout(function () {
+						var ed = EditorManager.get(s.auto_focus);
+
+						ed.selection.select(ed.getBody(), 1);
+						ed.selection.collapse(1);
+						ed.getWin().focus();
+					}, 100);
+				}
+			}, 1);
+	
+			e = null;
+		},
+
+
+		focus : function(sf) {
+			var oed, t = this, ce = t.settings.content_editable;
+
+			if (!sf) {
+				// Is not content editable or the selection is outside the area in IE
+				// the IE statement is needed to avoid bluring if element selections inside layers since
+				// the layer is like it's own document in IE
+				if (!ce && (!isIE || t.selection.getNode().ownerDocument != t.getDoc()))
+					t.getWin().focus();
+
+			}
+
+			if (EditorManager.activeEditor != t) {
+				if ((oed = EditorManager.activeEditor) != null)
+					oed.onDeactivate.dispatch(oed, t);
+
+				t.onActivate.dispatch(t, oed);
+			}
+
+			EditorManager._setActive(t);
+		},
+
+		execCallback : function(n) {
+			var t = this, f = t.settings[n], s;
+
+			if (!f)
+				return;
+
+			// Look through lookup
+			if (t.callbackLookup && (s = t.callbackLookup[n])) {
+				f = s.func;
+				s = s.scope;
+			}
+
+			if (is(f, 'string')) {
+				s = f.replace(/\.\w+$/, '');
+				s = s ? tinymce.resolve(s) : 0;
+				f = tinymce.resolve(f);
+				t.callbackLookup = t.callbackLookup || {};
+				t.callbackLookup[n] = {func : f, scope : s};
+			}
+
+			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
+		},
+
+		translate : function(s) {
+			var c = this.settings.language || 'en', i18n = EditorManager.i18n;
+
+			if (!s)
+				return '';
+
+			return i18n[c + '.' + s] || s.replace(/{\#([^}]+)\}/g, function(a, b) {
+				return i18n[c + '.' + b] || '{#' + b + '}';
+			});
+		},
+
+		getLang : function(n, dv) {
+			return EditorManager.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
+		},
+
+		getParam : function(n, dv, ty) {
+			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
+
+			if (ty === 'hash') {
+				o = {};
+
+				if (is(v, 'string')) {
+					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
+						v = v.split('=');
+
+						if (v.length > 1)
+							o[tr(v[0])] = tr(v[1]);
+						else
+							o[tr(v[0])] = tr(v);
+					});
+				} else
+					o = v;
+
+				return o;
+			}
+
+			return v;
+		},
+
+		nodeChanged : function(o) {
+			var t = this, s = t.selection, n = s.getNode() || t.getBody();
+
+			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
+			if (t.initialized) {
+				t.onNodeChange.dispatch(
+					t,
+					o ? o.controlManager || t.controlManager : t.controlManager,
+					isIE && n.ownerDocument != t.getDoc() ? t.getBody() : n, // Fix for IE initial state
+					s.isCollapsed(),
+					o
+				);
+			}
+		},
+
+		addButton : function(n, s) {
+			var t = this;
+
+			t.buttons = t.buttons || {};
+			t.buttons[n] = s;
+		},
+
+		addCommand : function(n, f, s) {
+			this.execCommands[n] = {func : f, scope : s || this};
+		},
+
+		addQueryStateHandler : function(n, f, s) {
+			this.queryStateCommands[n] = {func : f, scope : s || this};
+		},
+
+		addQueryValueHandler : function(n, f, s) {
+			this.queryValueCommands[n] = {func : f, scope : s || this};
+		},
+
+		addShortcut : function(pa, desc, cmd_func, sc) {
+			var t = this, c;
+
+			if (!t.settings.custom_shortcuts)
+				return false;
+
+			t.shortcuts = t.shortcuts || {};
+
+			if (is(cmd_func, 'string')) {
+				c = cmd_func;
+
+				cmd_func = function() {
+					t.execCommand(c, false, null);
+				};
+			}
+
+			if (is(cmd_func, 'object')) {
+				c = cmd_func;
+
+				cmd_func = function() {
+					t.execCommand(c[0], c[1], c[2]);
+				};
+			}
+
+			each(explode(pa), function(pa) {
+				var o = {
+					func : cmd_func,
+					scope : sc || this,
+					desc : desc,
+					alt : false,
+					ctrl : false,
+					shift : false
+				};
+
+				each(explode(pa, '+'), function(v) {
+					switch (v) {
+						case 'alt':
+						case 'ctrl':
+						case 'shift':
+							o[v] = true;
+							break;
+
+						default:
+							o.charCode = v.charCodeAt(0);
+							o.keyCode = v.toUpperCase().charCodeAt(0);
+					}
+				});
+
+				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
+			});
+
+			return true;
+		},
+
+		execCommand : function(cmd, ui, val, a) {
+			var t = this, s = 0, o, st;
+
+			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
+				t.focus();
+
+			o = {};
+			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, o);
+			if (o.terminate)
+				return false;
+
+			// Command callback
+			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
+				t.onExecCommand.dispatch(t, cmd, ui, val, a);
+				return true;
+			}
+
+			// Registred commands
+			if (o = t.execCommands[cmd]) {
+				st = o.func.call(o.scope, ui, val);
+
+				// Fall through on true
+				if (st !== true) {
+					t.onExecCommand.dispatch(t, cmd, ui, val, a);
+					return st;
+				}
+			}
+
+			// Plugin commands
+			each(t.plugins, function(p) {
+				if (p.execCommand && p.execCommand(cmd, ui, val)) {
+					t.onExecCommand.dispatch(t, cmd, ui, val, a);
+					s = 1;
+					return false;
+				}
+			});
+
+			if (s)
+				return true;
+
+			// Theme commands
+			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
+				t.onExecCommand.dispatch(t, cmd, ui, val, a);
+				return true;
+			}
+
+			// Execute global commands
+			if (tinymce.GlobalCommands.execCommand(t, cmd, ui, val)) {
+				t.onExecCommand.dispatch(t, cmd, ui, val, a);
+				return true;
+			}
+
+			// Editor commands
+			if (t.editorCommands.execCommand(cmd, ui, val)) {
+				t.onExecCommand.dispatch(t, cmd, ui, val, a);
+				return true;
+			}
+
+			// Browser commands
+			t.getDoc().execCommand(cmd, ui, val);
+			t.onExecCommand.dispatch(t, cmd, ui, val, a);
+		},
+
+		queryCommandState : function(c) {
+			var t = this, o, s;
+
+			// Is hidden then return undefined
+			if (t._isHidden())
+				return;
+
+			// Registred commands
+			if (o = t.queryStateCommands[c]) {
+				s = o.func.call(o.scope);
+
+				// Fall though on true
+				if (s !== true)
+					return s;
+			}
+
+			// Registred commands
+			o = t.editorCommands.queryCommandState(c);
+			if (o !== -1)
+				return o;
+
+			// Browser commands
+			try {
+				return this.getDoc().queryCommandState(c);
+			} catch (ex) {
+				// Fails sometimes see bug: 1896577
+			}
+		},
+
+		queryCommandValue : function(c) {
+			var t = this, o, s;
+
+			// Is hidden then return undefined
+			if (t._isHidden())
+				return;
+
+			// Registred commands
+			if (o = t.queryValueCommands[c]) {
+				s = o.func.call(o.scope);
+
+				// Fall though on true
+				if (s !== true)
+					return s;
+			}
+
+			// Registred commands
+			o = t.editorCommands.queryCommandValue(c);
+			if (is(o))
+				return o;
+
+			// Browser commands
+			try {
+				return this.getDoc().queryCommandValue(c);
+			} catch (ex) {
+				// Fails sometimes see bug: 1896577
+			}
+		},
+
+		show : function() {
+			var t = this;
+
+			DOM.show(t.getContainer());
+			DOM.hide(t.id);
+			t.load();
+		},
+
+		hide : function() {
+			var t = this, d = t.getDoc();
+
+			// Fixed bug where IE has a blinking cursor left from the editor
+			if (isIE && d)
+				d.execCommand('SelectAll');
+
+			// We must save before we hide so Safari doesn't crash
+			t.save();
+			DOM.hide(t.getContainer());
+			DOM.setStyle(t.id, 'display', t.orgDisplay);
+		},
+
+		isHidden : function() {
+			return !DOM.isHidden(this.id);
+		},
+
+		setProgressState : function(b, ti, o) {
+			this.onSetProgressState.dispatch(this, b, ti, o);
+
+			return b;
+		},
+
+		load : function(o) {
+			var t = this, e = t.getElement(), h;
+
+			if (e) {
+				o = o || {};
+				o.load = true;
+
+				// Double encode existing entities in the value
+				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
+				o.element = e;
+
+				if (!o.no_events)
+					t.onLoadContent.dispatch(t, o);
+
+				o.element = e = null;
+
+				return h;
+			}
+		},
+
+		save : function(o) {
+			var t = this, e = t.getElement(), h, f;
+
+			if (!e || !t.initialized)
+				return;
+
+			o = o || {};
+			o.save = true;
+
+			// Add undo level will trigger onchange event
+			if (!o.no_events) {
+				t.undoManager.typing = 0;
+				t.undoManager.add();
+			}
+
+			o.element = e;
+			h = o.content = t.getContent(o);
+
+			if (!o.no_events)
+				t.onSaveContent.dispatch(t, o);
+
+			h = o.content;
+
+			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
+				e.innerHTML = h;
+
+				// Update hidden form element
+				if (f = DOM.getParent(t.id, 'form')) {
+					each(f.elements, function(e) {
+						if (e.name == t.id) {
+							e.value = h;
+							return false;
+						}
+					});
+				}
+			} else
+				e.value = h;
+
+			o.element = e = null;
+
+			return h;
+		},
+
+		setContent : function(h, o) {
+			var t = this;
+
+			o = o || {};
+			o.format = o.format || 'html';
+			o.set = true;
+			o.content = h;
+
+			if (!o.no_events)
+				t.onBeforeSetContent.dispatch(t, o);
+
+			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
+			// It will also be impossible to place the caret in the editor unless there is a BR element present
+			if (!tinymce.isIE && (h.length === 0 || /^\s+$/.test(h))) {
+				o.content = t.dom.setHTML(t.getBody(), '<br mce_bogus="1" />');
+				o.format = 'raw';
+			}
+
+			o.content = t.dom.setHTML(t.getBody(), tinymce.trim(o.content));
+
+			if (o.format != 'raw' && t.settings.cleanup) {
+				o.getInner = true;
+				o.content = t.dom.setHTML(t.getBody(), t.serializer.serialize(t.getBody(), o));
+			}
+
+			if (!o.no_events)
+				t.onSetContent.dispatch(t, o);
+
+			return o.content;
+		},
+
+		getContent : function(o) {
+			var t = this, h;
+
+			o = o || {};
+			o.format = o.format || 'html';
+			o.get = true;
+
+			if (!o.no_events)
+				t.onBeforeGetContent.dispatch(t, o);
+
+			if (o.format != 'raw' && t.settings.cleanup) {
+				o.getInner = true;
+				h = t.serializer.serialize(t.getBody(), o);
+			} else
+				h = t.getBody().innerHTML;
+
+			h = h.replace(/^\s*|\s*$/g, '');
+			o.content = h;
+
+			if (!o.no_events)
+				t.onGetContent.dispatch(t, o);
+
+			return o.content;
+		},
+
+		isDirty : function() {
+			var t = this;
+
+			return tinymce.trim(t.startContent) != tinymce.trim(t.getContent({format : 'raw', no_events : 1})) && !t.isNotDirty;
+		},
+
+		getContainer : function() {
+			var t = this;
+
+			if (!t.container)
+				t.container = DOM.get(t.editorContainer || t.id + '_parent');
+
+			return t.container;
+		},
+
+		getContentAreaContainer : function() {
+			return this.contentAreaContainer;
+		},
+
+		getElement : function() {
+			return DOM.get(this.settings.content_element || this.id);
+		},
+
+		getWin : function() {
+			var t = this, e;
+
+			if (!t.contentWindow) {
+				e = DOM.get(t.id + "_ifr");
+
+				if (e)
+					t.contentWindow = e.contentWindow;
+			}
+
+			return t.contentWindow;
+		},
+
+		getDoc : function() {
+			var t = this, w;
+
+			if (!t.contentDocument) {
+				w = t.getWin();
+
+				if (w)
+					t.contentDocument = w.document;
+			}
+
+			return t.contentDocument;
+		},
+
+		getBody : function() {
+			return this.bodyElement || this.getDoc().body;
+		},
+
+		convertURL : function(u, n, e) {
+			var t = this, s = t.settings;
+
+			// Use callback instead
+			if (s.urlconverter_callback)
+				return t.execCallback('urlconverter_callback', u, e, true, n);
+
+			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
+			if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
+				return u;
+
+			// Convert to relative
+			if (s.relative_urls)
+				return t.documentBaseURI.toRelative(u);
+
+			// Convert to absolute
+			u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
+
+			return u;
+		},
+
+		addVisual : function(e) {
+			var t = this, s = t.settings;
+
+			e = e || t.getBody();
+
+			if (!is(t.hasVisual))
+				t.hasVisual = s.visual;
+
+			each(t.dom.select('table,a', e), function(e) {
+				var v;
+
+				switch (e.nodeName) {
+					case 'TABLE':
+						v = t.dom.getAttrib(e, 'border');
+
+						if (!v || v == '0') {
+							if (t.hasVisual)
+								t.dom.addClass(e, s.visual_table_class);
+							else
+								t.dom.removeClass(e, s.visual_table_class);
+						}
+
+						return;
+
+					case 'A':
+						v = t.dom.getAttrib(e, 'name');
+
+						if (v) {
+							if (t.hasVisual)
+								t.dom.addClass(e, 'mceItemAnchor');
+							else
+								t.dom.removeClass(e, 'mceItemAnchor');
+						}
+
+						return;
+				}
+			});
+
+			t.onVisualAid.dispatch(t, e, t.hasVisual);
+		},
+
+		remove : function() {
+			var t = this, e = t.getContainer();
+
+			t.removed = 1; // Cancels post remove event execution
+			t.hide();
+
+			t.execCallback('remove_instance_callback', t);
+			t.onRemove.dispatch(t);
+
+			// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
+			t.onExecCommand.listeners = [];
+
+			EditorManager.remove(t);
+			DOM.remove(e);
+		},
+
+		destroy : function(s) {
+			var t = this;
+
+			// One time is enough
+			if (t.destroyed)
+				return;
+
+			if (!s) {
+				tinymce.removeUnload(t.destroy);
+				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
+
+				// Manual destroy
+				if (t.theme && t.theme.destroy)
+					t.theme.destroy();
+
+				// Destroy controls, selection and dom
+				t.controlManager.destroy();
+				t.selection.destroy();
+				t.dom.destroy();
+
+				// Remove all events
+
+				// Don't clear the window or document if content editable
+				// is enabled since other instances might still be present
+				if (!t.settings.content_editable) {
+					Event.clear(t.getWin());
+					Event.clear(t.getDoc());
+				}
+
+				Event.clear(t.getBody());
+				Event.clear(t.formElement);
+			}
+
+			if (t.formElement) {
+				t.formElement.submit = t.formElement._mceOldSubmit;
+				t.formElement._mceOldSubmit = null;
+			}
+
+			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
+
+			if (t.selection)
+				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
+
+			t.destroyed = 1;
+		},
+
+		// Internal functions
+
+		_addEvents : function() {
+			// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
+			var t = this, i, s = t.settings, lo = {
+				mouseup : 'onMouseUp',
+				mousedown : 'onMouseDown',
+				click : 'onClick',
+				keyup : 'onKeyUp',
+				keydown : 'onKeyDown',
+				keypress : 'onKeyPress',
+				submit : 'onSubmit',
+				reset : 'onReset',
+				contextmenu : 'onContextMenu',
+				dblclick : 'onDblClick',
+				paste : 'onPaste' // Doesn't work in all browsers yet
+			};
+
+			function eventHandler(e, o) {
+				var ty = e.type;
+
+				// Don't fire events when it's removed
+				if (t.removed)
+					return;
+
+				// Generic event handler
+				if (t.onEvent.dispatch(t, e, o) !== false) {
+					// Specific event handler
+					t[lo[e.fakeType || e.type]].dispatch(t, e, o);
+				}
+			};
+
+			// Add DOM events
+			each(lo, function(v, k) {
+				switch (k) {
+					case 'contextmenu':
+						if (tinymce.isOpera) {
+							// Fake contextmenu on Opera
+							t.dom.bind(t.getBody(), 'mousedown', function(e) {
+								if (e.ctrlKey) {
+									e.fakeType = 'contextmenu';
+									eventHandler(e);
+								}
+							});
+						} else
+							t.dom.bind(t.getBody(), k, eventHandler);
+						break;
+
+					case 'paste':
+						t.dom.bind(t.getBody(), k, function(e) {
+							eventHandler(e);
+						});
+						break;
+
+					case 'submit':
+					case 'reset':
+						t.dom.bind(t.getElement().form || DOM.getParent(t.id, 'form'), k, eventHandler);
+						break;
+
+					default:
+						t.dom.bind(s.content_editable ? t.getBody() : t.getDoc(), k, eventHandler);
+				}
+			});
+
+			t.dom.bind(s.content_editable ? t.getBody() : (isGecko ? t.getDoc() : t.getWin()), 'focus', function(e) {
+				t.focus(true);
+			});
+
+
+			// Fixes bug where a specified document_base_uri could result in broken images
+			// This will also fix drag drop of images in Gecko
+			if (tinymce.isGecko) {
+				// Convert all images to absolute URLs
+/*				t.onSetContent.add(function(ed, o) {
+					each(ed.dom.select('img'), function(e) {
+						var v;
+
+						if (v = e.getAttribute('mce_src'))
+							e.src = t.documentBaseURI.toAbsolute(v);
+					})
+				});*/
+
+				t.dom.bind(t.getDoc(), 'DOMNodeInserted', function(e) {
+					var v;
+
+					e = e.target;
+
+					if (e.nodeType === 1 && e.nodeName === 'IMG' && (v = e.getAttribute('mce_src')))
+						e.src = t.documentBaseURI.toAbsolute(v);
+				});
+			}
+
+			// Set various midas options in Gecko
+			if (isGecko) {
+				function setOpts() {
+					var t = this, d = t.getDoc(), s = t.settings;
+
+					if (isGecko && !s.readonly) {
+						if (t._isHidden()) {
+							try {
+								if (!s.content_editable)
+									d.designMode = 'On';
+							} catch (ex) {
+								// Fails if it's hidden
+							}
+						}
+
+						try {
+							// Try new Gecko method
+							d.execCommand("styleWithCSS", 0, false);
+						} catch (ex) {
+							// Use old method
+							if (!t._isHidden())
+								try {d.execCommand("useCSS", 0, true);} catch (ex) {}
+						}
+
+						if (!s.table_inline_editing)
+							try {d.execCommand('enableInlineTableEditing', false, false);} catch (ex) {}
+
+						if (!s.object_resizing)
+							try {d.execCommand('enableObjectResizing', false, false);} catch (ex) {}
+					}
+				};
+
+				t.onBeforeExecCommand.add(setOpts);
+				t.onMouseDown.add(setOpts);
+			}
+
+			// Add node change handlers
+			t.onMouseUp.add(t.nodeChanged);
+			t.onClick.add(t.nodeChanged);
+			t.onKeyUp.add(function(ed, e) {
+				var c = e.keyCode;
+
+				if ((c >= 33 && c <= 36) || (c >= 37 && c <= 40) || c == 13 || c == 45 || c == 46 || c == 8 || (tinymce.isMac && (c == 91 || c == 93)) || e.ctrlKey)
+					t.nodeChanged();
+			});
+
+			// Add reset handler
+			t.onReset.add(function() {
+				t.setContent(t.startContent, {format : 'raw'});
+			});
+
+			// Add shortcuts
+			if (s.custom_shortcuts) {
+				if (s.custom_undo_redo_keyboard_shortcuts) {
+					t.addShortcut('ctrl+z', t.getLang('undo_desc'), 'Undo');
+					t.addShortcut('ctrl+y', t.getLang('redo_desc'), 'Redo');
+				}
+
+				// Add default shortcuts for gecko
+				if (isGecko) {
+					t.addShortcut('ctrl+b', t.getLang('bold_desc'), 'Bold');
+					t.addShortcut('ctrl+i', t.getLang('italic_desc'), 'Italic');
+					t.addShortcut('ctrl+u', t.getLang('underline_desc'), 'Underline');
+				}
+
+				// BlockFormat shortcuts keys
+				for (i=1; i<=6; i++)
+					t.addShortcut('ctrl+' + i, '', ['FormatBlock', false, '<h' + i + '>']);
+
+				t.addShortcut('ctrl+7', '', ['FormatBlock', false, '<p>']);
+				t.addShortcut('ctrl+8', '', ['FormatBlock', false, '<div>']);
+				t.addShortcut('ctrl+9', '', ['FormatBlock', false, '<address>']);
+
+				function find(e) {
+					var v = null;
+
+					if (!e.altKey && !e.ctrlKey && !e.metaKey)
+						return v;
+
+					each(t.shortcuts, function(o) {
+						if (tinymce.isMac && o.ctrl != e.metaKey)
+							return;
+						else if (!tinymce.isMac && o.ctrl != e.ctrlKey)
+							return;
+
+						if (o.alt != e.altKey)
+							return;
+
+						if (o.shift != e.shiftKey)
+							return;
+
+						if (e.keyCode == o.keyCode || (e.charCode && e.charCode == o.charCode)) {
+							v = o;
+							return false;
+						}
+					});
+
+					return v;
+				};
+
+				t.onKeyUp.add(function(ed, e) {
+					var o = find(e);
+
+					if (o)
+						return Event.cancel(e);
+				});
+
+				t.onKeyPress.add(function(ed, e) {
+					var o = find(e);
+
+					if (o)
+						return Event.cancel(e);
+				});
+
+				t.onKeyDown.add(function(ed, e) {
+					var o = find(e);
+
+					if (o) {
+						o.func.call(o.scope);
+						return Event.cancel(e);
+					}
+				});
+			}
+
+			if (tinymce.isIE) {
+				// Fix so resize will only update the width and height attributes not the styles of an image
+				// It will also block mceItemNoResize items
+				t.dom.bind(t.getDoc(), 'controlselect', function(e) {
+					var re = t.resizeInfo, cb;
+
+					e = e.target;
+
+					// Don't do this action for non image elements
+					if (e.nodeName !== 'IMG')
+						return;
+
+					if (re)
+						t.dom.unbind(re.node, re.ev, re.cb);
+
+					if (!t.dom.hasClass(e, 'mceItemNoResize')) {
+						ev = 'resizeend';
+						cb = t.dom.bind(e, ev, function(e) {
+							var v;
+
+							e = e.target;
+
+							if (v = t.dom.getStyle(e, 'width')) {
+								t.dom.setAttrib(e, 'width', v.replace(/[^0-9%]+/g, ''));
+								t.dom.setStyle(e, 'width', '');
+							}
+
+							if (v = t.dom.getStyle(e, 'height')) {
+								t.dom.setAttrib(e, 'height', v.replace(/[^0-9%]+/g, ''));
+								t.dom.setStyle(e, 'height', '');
+							}
+						});
+					} else {
+						ev = 'resizestart';
+						cb = t.dom.bind(e, 'resizestart', Event.cancel, Event);
+					}
+
+					re = t.resizeInfo = {
+						node : e,
+						ev : ev,
+						cb : cb
+					};
+				});
+
+				t.onKeyDown.add(function(ed, e) {
+					switch (e.keyCode) {
+						case 8:
+							// Fix IE control + backspace browser bug
+							if (t.selection.getRng().item) {
+								t.selection.getRng().item(0).removeNode();
+								return Event.cancel(e);
+							}
+					}
+				});
+
+				/*if (t.dom.boxModel) {
+					t.getBody().style.height = '100%';
+
+					Event.add(t.getWin(), 'resize', function(e) {
+						var docElm = t.getDoc().documentElement;
+
+						docElm.style.height = (docElm.offsetHeight - 10) + 'px';
+					});
+				}*/
+			}
+
+			if (tinymce.isOpera) {
+				t.onClick.add(function(ed, e) {
+					Event.prevent(e);
+				});
+			}
+
+			// Add custom undo/redo handlers
+			if (s.custom_undo_redo) {
+				function addUndo() {
+					t.undoManager.typing = 0;
+					t.undoManager.add();
+				};
+
+				// Add undo level on editor blur
+				if (tinymce.isIE) {
+					t.dom.bind(t.getWin(), 'blur', function(e) {
+						var n;
+
+						// Check added for fullscreen bug
+						if (t.selection) {
+							n = t.selection.getNode();
+
+							// Add undo level is selection was lost to another document
+							if (!t.removed && n.ownerDocument && n.ownerDocument != t.getDoc())
+								addUndo();
+						}
+					});
+				} else {
+					t.dom.bind(t.getDoc(), 'blur', function() {
+						if (t.selection && !t.removed)
+							addUndo();
+					});
+				}
+
+				t.onMouseDown.add(addUndo);
+
+				t.onKeyUp.add(function(ed, e) {
+					if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45 || e.ctrlKey) {
+						t.undoManager.typing = 0;
+						t.undoManager.add();
+					}
+				});
+
+				t.onKeyDown.add(function(ed, e) {
+					// Is caracter positon keys
+					if ((e.keyCode >= 33 && e.keyCode <= 36) || (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode == 13 || e.keyCode == 45) {
+						if (t.undoManager.typing) {
+							t.undoManager.add();
+							t.undoManager.typing = 0;
+						}
+
+						return;
+					}
+
+					if (!t.undoManager.typing) {
+						t.undoManager.add();
+						t.undoManager.typing = 1;
+					}
+				});
+			}
+		},
+
+		_convertInlineElements : function() {
+			var t = this, s = t.settings, dom = t.dom, v, e, na, st, sp;
+
+			function convert(ed, o) {
+				if (!s.inline_styles)
+					return;
+
+				if (o.get) {
+					each(t.dom.select('table,u,strike', o.node), function(n) {
+						switch (n.nodeName) {
+							case 'TABLE':
+								if (v = dom.getAttrib(n, 'height')) {
+									dom.setStyle(n, 'height', v);
+									dom.setAttrib(n, 'height', '');
+								}
+								break;
+
+							case 'U':
+							case 'STRIKE':
+								//sp = dom.create('span', {style : dom.getAttrib(n, 'style')});
+								n.style.textDecoration = n.nodeName == 'U' ? 'underline' : 'line-through';
+								dom.setAttrib(n, 'mce_style', '');
+								dom.setAttrib(n, 'mce_name', 'span');
+								break;
+						}
+					});
+				} else if (o.set) {
+					each(t.dom.select('table,span', o.node).reverse(), function(n) {
+						if (n.nodeName == 'TABLE') {
+							if (v = dom.getStyle(n, 'height'))
+								dom.setAttrib(n, 'height', v.replace(/[^0-9%]+/g, ''));
+						} else {
+							// Convert spans to elements
+							if (n.style.textDecoration == 'underline')
+								na = 'u';
+							else if (n.style.textDecoration == 'line-through')
+								na = 'strike';
+							else
+								na = '';
+
+							if (na) {
+								n.style.textDecoration = '';
+								dom.setAttrib(n, 'mce_style', '');
+
+								e = dom.create(na, {
+									style : dom.getAttrib(n, 'style')
+								});
+
+								dom.replace(e, n, 1);
+							}
+						}
+					});
+				}
+			};
+
+			t.onPreProcess.add(convert);
+
+			if (!s.cleanup_on_startup) {
+				t.onSetContent.add(function(ed, o) {
+					if (o.initial)
+						convert(t, {node : t.getBody(), set : 1});
+				});
+			}
+		},
+
+		_convertFonts : function() {
+			var t = this, s = t.settings, dom = t.dom, fz, fzn, sl, cl;
+
+			// No need
+			if (!s.inline_styles)
+				return;
+
+			// Font pt values and font size names
+			fz = [8, 10, 12, 14, 18, 24, 36];
+			fzn = ['xx-small', 'x-small','small','medium','large','x-large', 'xx-large'];
+
+			if (sl = s.font_size_style_values)
+				sl = explode(sl);
+
+			if (cl = s.font_size_classes)
+				cl = explode(cl);
+
+			function process(no) {
+				var n, sp, nl, x;
+
+				// Keep unit tests happy
+				if (!s.inline_styles)
+					return;
+
+				nl = t.dom.select('font', no);
+				for (x = nl.length - 1; x >= 0; x--) {
+					n = nl[x];
+
+					sp = dom.create('span', {
+						style : dom.getAttrib(n, 'style'),
+						'class' : dom.getAttrib(n, 'class')
+					});
+
+					dom.setStyles(sp, {
+						fontFamily : dom.getAttrib(n, 'face'),
+						color : dom.getAttrib(n, 'color'),
+						backgroundColor : n.style.backgroundColor
+					});
+
+					if (n.size) {
+						if (sl)
+							dom.setStyle(sp, 'fontSize', sl[parseInt(n.size) - 1]);
+						else
+							dom.setAttrib(sp, 'class', cl[parseInt(n.size) - 1]);
+					}
+
+					dom.setAttrib(sp, 'mce_style', '');
+					dom.replace(sp, n, 1);
+				}
+			};
+
+			// Run on cleanup
+			t.onPreProcess.add(function(ed, o) {
+				if (o.get)
+					process(o.node);
+			});
+
+			t.onSetContent.add(function(ed, o) {
+				if (o.initial)
+					process(o.node);
+			});
+		},
+
+		_isHidden : function() {
+			var s;
+
+			if (!isGecko)
+				return 0;
+
+			// Weird, wheres that cursor selection?
+			s = this.selection.getSel();
+			return (!s || !s.rangeCount || s.rangeCount == 0);
+		},
+
+		// Fix for bug #1867292
+		_fixNesting : function(s) {
+			var d = [], i;
+
+			s = s.replace(/<(\/)?([^\s>]+)[^>]*?>/g, function(a, b, c) {
+				var e;
+
+				// Handle end element
+				if (b === '/') {
+					if (!d.length)
+						return '';
+
+					if (c !== d[d.length - 1].tag) {
+						for (i=d.length - 1; i>=0; i--) {
+							if (d[i].tag === c) {
+								d[i].close = 1;
+								break;
+							}
+						}
+
+						return '';
+					} else {
+						d.pop();
+
+						if (d.length && d[d.length - 1].close) {
+							a = a + '</' + d[d.length - 1].tag + '>';
+							d.pop();
+						}
+					}
+				} else {
+					// Ignore these
+					if (/^(br|hr|input|meta|img|link|param)$/i.test(c))
+						return a;
+
+					// Ignore closed ones
+					if (/\/>$/.test(a))
+						return a;
+
+					d.push({tag : c}); // Push start element
+				}
+
+				return a;
+			});
+
+			// End all open tags
+			for (i=d.length - 1; i>=0; i--)
+				s += '</' + d[i].tag + '>';
+
+			return s;
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	var each = tinymce.each, isIE = tinymce.isIE, isGecko = tinymce.isGecko, isOpera = tinymce.isOpera, isWebKit = tinymce.isWebKit;
+
+	tinymce.create('tinymce.EditorCommands', {
+		EditorCommands : function(ed) {
+			this.editor = ed;
+		},
+
+		execCommand : function(cmd, ui, val) {
+			var t = this, ed = t.editor, f;
+
+			switch (cmd) {
+				// Ignore these
+				case 'mceResetDesignMode':
+				case 'mceBeginUndoLevel':
+					return true;
+
+				// Ignore these
+				case 'unlink':
+					t.UnLink();
+					return true;
+
+				// Bundle these together
+				case 'JustifyLeft':
+				case 'JustifyCenter':
+				case 'JustifyRight':
+				case 'JustifyFull':
+					t.mceJustify(cmd, cmd.substring(7).toLowerCase());
+					return true;
+
+				default:
+					f = this[cmd];
+
+					if (f) {
+						f.call(this, ui, val);
+						return true;
+					}
+			}
+
+			return false;
+		},
+
+		Indent : function() {
+			var ed = this.editor, d = ed.dom, s = ed.selection, e, iv, iu;
+
+			// Setup indent level
+			iv = ed.settings.indentation;
+			iu = /[a-z%]+$/i.exec(iv);
+			iv = parseInt(iv);
+
+			if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) {
+				each(s.getSelectedBlocks(), function(e) {
+					d.setStyle(e, 'paddingLeft', (parseInt(e.style.paddingLeft || 0) + iv) + iu);
+				});
+
+				return;
+			}
+
+			ed.getDoc().execCommand('Indent', false, null);
+
+			if (isIE) {
+				d.getParent(s.getNode(), function(n) {
+					if (n.nodeName == 'BLOCKQUOTE') {
+						n.dir = n.style.cssText = '';
+					}
+				});
+			}
+		},
+
+		Outdent : function() {
+			var ed = this.editor, d = ed.dom, s = ed.selection, e, v, iv, iu;
+
+			// Setup indent level
+			iv = ed.settings.indentation;
+			iu = /[a-z%]+$/i.exec(iv);
+			iv = parseInt(iv);
+
+			if (ed.settings.inline_styles && (!this.queryStateInsertUnorderedList() && !this.queryStateInsertOrderedList())) {
+				each(s.getSelectedBlocks(), function(e) {
+					v = Math.max(0, parseInt(e.style.paddingLeft || 0) - iv);
+					d.setStyle(e, 'paddingLeft', v ? v + iu : '');
+				});
+
+				return;
+			}
+
+			ed.getDoc().execCommand('Outdent', false, null);
+		},
+
+/*
+		mceSetAttribute : function(u, v) {
+			var ed = this.editor, d = ed.dom, e;
+
+			if (e = d.getParent(ed.selection.getNode(), d.isBlock))
+				d.setAttrib(e, v.name, v.value);
+		},
+*/
+		mceSetContent : function(u, v) {
+			this.editor.setContent(v);
+		},
+
+		mceToggleVisualAid : function() {
+			var ed = this.editor;
+
+			ed.hasVisual = !ed.hasVisual;
+			ed.addVisual();
+		},
+
+		mceReplaceContent : function(u, v) {
+			var s = this.editor.selection;
+
+			s.setContent(v.replace(/\{\$selection\}/g, s.getContent({format : 'text'})));
+		},
+
+		mceInsertLink : function(u, v) {
+			var ed = this.editor, s = ed.selection, e = ed.dom.getParent(s.getNode(), 'a');
+
+			if (tinymce.is(v, 'string'))
+				v = {href : v};
+
+			function set(e) {
+				each(v, function(v, k) {
+					ed.dom.setAttrib(e, k, v);
+				});
+			};
+
+			if (!e) {
+				ed.execCommand('CreateLink', false, 'javascript:mctmp(0);');
+				each(ed.dom.select('a[href=javascript:mctmp(0);]'), function(e) {
+					set(e);
+				});
+			} else {
+				if (v.href)
+					set(e);
+				else
+					ed.dom.remove(e, 1);
+			}
+		},
+
+		UnLink : function() {
+			var ed = this.editor, s = ed.selection;
+
+			if (s.isCollapsed())
+				s.select(s.getNode());
+
+			ed.getDoc().execCommand('unlink', false, null);
+			s.collapse(0);
+		},
+
+		FontName : function(u, v) {
+			var t = this, ed = t.editor, s = ed.selection, e;
+
+			if (!v) {
+				if (s.isCollapsed())
+					s.select(s.getNode());
+			} else {
+				if (ed.settings.convert_fonts_to_spans)
+					t._applyInlineStyle('span', {style : {fontFamily : v}});
+				else
+					ed.getDoc().execCommand('FontName', false, v);
+			}
+		},
+
+		FontSize : function(u, v) {
+			var ed = this.editor, s = ed.settings, fc, fs;
+
+			// Use style options instead
+			if (s.convert_fonts_to_spans && v >= 1 && v <= 7) {
+				fs = tinymce.explode(s.font_size_style_values);
+				fc = tinymce.explode(s.font_size_classes);
+
+				if (fc)
+					v = fc[v - 1] || v;
+				else
+					v = fs[v - 1] || v;
+			}
+
+			if (v >= 1 && v <= 7)
+				ed.getDoc().execCommand('FontSize', false, v);
+			else
+				this._applyInlineStyle('span', {style : {fontSize : v}});
+		},
+
+		queryCommandValue : function(c) {
+			var f = this['queryValue' + c];
+
+			if (f)
+				return f.call(this, c);
+
+			return false;
+		},
+
+		queryCommandState : function(cmd) {
+			var f;
+
+			switch (cmd) {
+				// Bundle these together
+				case 'JustifyLeft':
+				case 'JustifyCenter':
+				case 'JustifyRight':
+				case 'JustifyFull':
+					return this.queryStateJustify(cmd, cmd.substring(7).toLowerCase());
+
+				default:
+					if (f = this['queryState' + cmd])
+						return f.call(this, cmd);
+			}
+
+			return -1;
+		},
+
+		_queryState : function(c) {
+			try {
+				return this.editor.getDoc().queryCommandState(c);
+			} catch (ex) {
+				// Ignore exception
+			}
+		},
+
+		_queryVal : function(c) {
+			try {
+				return this.editor.getDoc().queryCommandValue(c);
+			} catch (ex) {
+				// Ignore exception
+			}
+		},
+
+		queryValueFontSize : function() {
+			var ed = this.editor, v = 0, p;
+
+			if (p = ed.dom.getParent(ed.selection.getNode(), 'span'))
+				v = p.style.fontSize;
+
+			if (!v && (isOpera || isWebKit)) {
+				if (p = ed.dom.getParent(ed.selection.getNode(), 'font'))
+					v = p.size;
+
+				return v;
+			}
+
+			return v || this._queryVal('FontSize');
+		},
+
+		queryValueFontName : function() {
+			var ed = this.editor, v = 0, p;
+
+			if (p = ed.dom.getParent(ed.selection.getNode(), 'font'))
+				v = p.face;
+
+			if (p = ed.dom.getParent(ed.selection.getNode(), 'span'))
+				v = p.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
+
+			if (!v)
+				v = this._queryVal('FontName');
+
+			return v;
+		},
+
+		mceJustify : function(c, v) {
+			var ed = this.editor, se = ed.selection, n = se.getNode(), nn = n.nodeName, bl, nb, dom = ed.dom, rm;
+
+			if (ed.settings.inline_styles && this.queryStateJustify(c, v))
+				rm = 1;
+
+			bl = dom.getParent(n, ed.dom.isBlock);
+
+			if (nn == 'IMG') {
+				if (v == 'full')
+					return;
+
+				if (rm) {
+					if (v == 'center')
+						dom.setStyle(bl || n.parentNode, 'textAlign', '');
+
+					dom.setStyle(n, 'float', '');
+					this.mceRepaint();
+					return;
+				}
+
+				if (v == 'center') {
+					// Do not change table elements
+					if (bl && /^(TD|TH)$/.test(bl.nodeName))
+						bl = 0;
+
+					if (!bl || bl.childNodes.length > 1) {
+						nb = dom.create('p');
+						nb.appendChild(n.cloneNode(false));
+
+						if (bl)
+							dom.insertAfter(nb, bl);
+						else
+							dom.insertAfter(nb, n);
+
+						dom.remove(n);
+						n = nb.firstChild;
+						bl = nb;
+					}
+
+					dom.setStyle(bl, 'textAlign', v);
+					dom.setStyle(n, 'float', '');
+				} else {
+					dom.setStyle(n, 'float', v);
+					dom.setStyle(bl || n.parentNode, 'textAlign', '');
+				}
+
+				this.mceRepaint();
+				return;
+			}
+
+			// Handle the alignment outselfs, less quirks in all browsers
+			if (ed.settings.inline_styles && ed.settings.forced_root_block) {
+				if (rm)
+					v = '';
+
+				each(se.getSelectedBlocks(dom.getParent(se.getStart(), dom.isBlock), dom.getParent(se.getEnd(), dom.isBlock)), function(e) {
+					dom.setAttrib(e, 'align', '');
+					dom.setStyle(e, 'textAlign', v == 'full' ? 'justify' : v);
+				});
+
+				return;
+			} else if (!rm)
+				ed.getDoc().execCommand(c, false, null);
+
+			if (ed.settings.inline_styles) {
+				if (rm) {
+					dom.getParent(ed.selection.getNode(), function(n) {
+						if (n.style && n.style.textAlign)
+							dom.setStyle(n, 'textAlign', '');
+					});
+
+					return;
+				}
+
+				each(dom.select('*'), function(n) {
+					var v = n.align;
+
+					if (v) {
+						if (v == 'full')
+							v = 'justify';
+
+						dom.setStyle(n, 'textAlign', v);
+						dom.setAttrib(n, 'align', '');
+					}
+				});
+			}
+		},
+
+		mceSetCSSClass : function(u, v) {
+			this.mceSetStyleInfo(0, {command : 'setattrib', name : 'class', value : v});
+		},
+
+		getSelectedElement : function() {
+			var t = this, ed = t.editor, dom = ed.dom, se = ed.selection, r = se.getRng(), r1, r2, sc, ec, so, eo, e, sp, ep, re;
+
+			if (se.isCollapsed() || r.item)
+				return se.getNode();
+
+			// Setup regexp
+			re = ed.settings.merge_styles_invalid_parents;
+			if (tinymce.is(re, 'string'))
+				re = new RegExp(re, 'i');
+
+			if (isIE) {
+				r1 = r.duplicate();
+				r1.collapse(true);
+				sc = r1.parentElement();
+
+				r2 = r.duplicate();
+				r2.collapse(false);
+				ec = r2.parentElement();
+
+				if (sc != ec) {
+					r1.move('character', 1);
+					sc = r1.parentElement();
+				}
+
+				if (sc == ec) {
+					r1 = r.duplicate();
+					r1.moveToElementText(sc);
+
+					if (r1.compareEndPoints('StartToStart', r) == 0 && r1.compareEndPoints('EndToEnd', r) == 0)
+						return re && re.test(sc.nodeName) ? null : sc;
+				}
+			} else {
+				function getParent(n) {
+					return dom.getParent(n, '*');
+				};
+
+				sc = r.startContainer;
+				ec = r.endContainer;
+				so = r.startOffset;
+				eo = r.endOffset;
+
+				if (!r.collapsed) {
+					if (sc == ec) {
+						if (so - eo < 2) {
+							if (sc.hasChildNodes()) {
+								sp = sc.childNodes[so];
+								return re && re.test(sp.nodeName) ? null : sp;
+							}
+						}
+					}
+				}
+
+				if (sc.nodeType != 3 || ec.nodeType != 3)
+					return null;
+
+				if (so == 0) {
+					sp = getParent(sc);
+
+					if (sp && sp.firstChild != sc)
+						sp = null;
+				}
+
+				if (so == sc.nodeValue.length) {
+					e = sc.nextSibling;
+
+					if (e && e.nodeType == 1)
+						sp = sc.nextSibling;
+				}
+
+				if (eo == 0) {
+					e = ec.previousSibling;
+
+					if (e && e.nodeType == 1)
+						ep = e;
+				}
+
+				if (eo == ec.nodeValue.length) {
+					ep = getParent(ec);
+
+					if (ep && ep.lastChild != ec)
+						ep = null;
+				}
+
+				// Same element
+				if (sp == ep)
+					return re && sp && re.test(sp.nodeName) ? null : sp;
+			}
+
+			return null;
+		},
+
+		mceSetStyleInfo : function(u, v) {
+			var t = this, ed = t.editor, d = ed.getDoc(), dom = ed.dom, e, b, s = ed.selection, nn = v.wrapper || 'span', b = s.getBookmark(), re;
+
+			function set(n, e) {
+				if (n.nodeType == 1) {
+					switch (v.command) {
+						case 'setattrib':
+							return dom.setAttrib(n, v.name, v.value);
+
+						case 'setstyle':
+							return dom.setStyle(n, v.name, v.value);
+
+						case 'removeformat':
+							return dom.setAttrib(n, 'class', '');
+					}
+				}
+			};
+
+			// Setup regexp
+			re = ed.settings.merge_styles_invalid_parents;
+			if (tinymce.is(re, 'string'))
+				re = new RegExp(re, 'i');
+
+			// Set style info on selected element
+			if ((e = t.getSelectedElement()) && !ed.settings.force_span_wrappers)
+				set(e, 1);
+			else {
+				// Generate wrappers and set styles on them
+				d.execCommand('FontName', false, '__');
+				each(dom.select('span,font'), function(n) {
+					var sp, e;
+
+					if (dom.getAttrib(n, 'face') == '__' || n.style.fontFamily === '__') {
+						sp = dom.create(nn, {mce_new : '1'});
+
+						set(sp);
+
+						each (n.childNodes, function(n) {
+							sp.appendChild(n.cloneNode(true));
+						});
+
+						dom.replace(sp, n);
+					}
+				});
+			}
+
+			// Remove wrappers inside new ones
+			each(dom.select(nn).reverse(), function(n) {
+				var p = n.parentNode;
+
+				// Check if it's an old span in a new wrapper
+				if (!dom.getAttrib(n, 'mce_new')) {
+					// Find new wrapper
+					p = dom.getParent(n, '*[mce_new]');
+
+					if (p)
+						dom.remove(n, 1);
+				}
+			});
+
+			// Merge wrappers with parent wrappers
+			each(dom.select(nn).reverse(), function(n) {
+				var p = n.parentNode;
+
+				if (!p || !dom.getAttrib(n, 'mce_new'))
+					return;
+
+				if (ed.settings.force_span_wrappers && p.nodeName != 'SPAN')
+					return;
+
+				// Has parent of the same type and only child
+				if (p.nodeName == nn.toUpperCase() && p.childNodes.length == 1)
+					return dom.remove(p, 1);
+
+				// Has parent that is more suitable to have the class and only child
+				if (n.nodeType == 1 && (!re || !re.test(p.nodeName)) && p.childNodes.length == 1) {
+					set(p); // Set style info on parent instead
+					dom.setAttrib(n, 'class', '');
+				}
+			});
+
+			// Remove empty wrappers
+			each(dom.select(nn).reverse(), function(n) {
+				if (dom.getAttrib(n, 'mce_new') || (dom.getAttribs(n).length <= 1 && n.className === '')) {
+					if (!dom.getAttrib(n, 'class') && !dom.getAttrib(n, 'style'))
+						return dom.remove(n, 1);
+
+					dom.setAttrib(n, 'mce_new', ''); // Remove mce_new marker
+				}
+			});
+
+			s.moveToBookmark(b);
+		},
+
+		queryStateJustify : function(c, v) {
+			var ed = this.editor, n = ed.selection.getNode(), dom = ed.dom;
+
+			if (n && n.nodeName == 'IMG') {
+				if (dom.getStyle(n, 'float') == v)
+					return 1;
+
+				return n.parentNode.style.textAlign == v;
+			}
+
+			n = dom.getParent(ed.selection.getStart(), function(n) {
+				return n.nodeType == 1 && n.style.textAlign;
+			});
+
+			if (v == 'full')
+				v = 'justify';
+
+			if (ed.settings.inline_styles)
+				return (n && n.style.textAlign == v);
+
+			return this._queryState(c);
+		},
+
+		ForeColor : function(ui, v) {
+			var ed = this.editor;
+
+			if (ed.settings.convert_fonts_to_spans) {
+				this._applyInlineStyle('span', {style : {color : v}});
+				return;
+			} else
+				ed.getDoc().execCommand('ForeColor', false, v);
+		},
+
+		HiliteColor : function(ui, val) {
+			var t = this, ed = t.editor, d = ed.getDoc();
+
+			if (ed.settings.convert_fonts_to_spans) {
+				this._applyInlineStyle('span', {style : {backgroundColor : val}});
+				return;
+			}
+
+			function set(s) {
+				if (!isGecko)
+					return;
+
+				try {
+					// Try new Gecko method
+					d.execCommand("styleWithCSS", 0, s);
+				} catch (ex) {
+					// Use old
+					d.execCommand("useCSS", 0, !s);
+				}
+			};
+
+			if (isGecko || isOpera) {
+				set(true);
+				d.execCommand('hilitecolor', false, val);
+				set(false);
+			} else
+				d.execCommand('BackColor', false, val);
+		},
+
+		FormatBlock : function(ui, val) {
+			var t = this, ed = t.editor, s = ed.selection, dom = ed.dom, bl, nb, b;
+
+			function isBlock(n) {
+				return /^(P|DIV|H[1-6]|ADDRESS|BLOCKQUOTE|PRE)$/.test(n.nodeName);
+			};
+
+			bl = dom.getParent(s.getNode(), function(n) {
+				return isBlock(n);
+			});
+
+			// IE has an issue where it removes the parent div if you change format on the paragrah in <div><p>Content</p></div>
+			// FF and Opera doesn't change parent DIV elements if you switch format
+			if (bl) {
+				if ((isIE && isBlock(bl.parentNode)) || bl.nodeName == 'DIV') {
+					// Rename block element
+					nb = ed.dom.create(val);
+
+					each(dom.getAttribs(bl), function(v) {
+						dom.setAttrib(nb, v.nodeName, dom.getAttrib(bl, v.nodeName));
+					});
+
+					b = s.getBookmark();
+					dom.replace(nb, bl, 1);
+					s.moveToBookmark(b);
+					ed.nodeChanged();
+					return;
+				}
+			}
+
+			val = ed.settings.forced_root_block ? (val || '<p>') : val;
+
+			if (val.indexOf('<') == -1)
+				val = '<' + val + '>';
+
+			if (tinymce.isGecko)
+				val = val.replace(/<(div|blockquote|code|dt|dd|dl|samp)>/gi, '$1');
+
+			ed.getDoc().execCommand('FormatBlock', false, val);
+		},
+
+		mceCleanup : function() {
+			var ed = this.editor, s = ed.selection, b = s.getBookmark();
+			ed.setContent(ed.getContent());
+			s.moveToBookmark(b);
+		},
+
+		mceRemoveNode : function(ui, val) {
+			var ed = this.editor, s = ed.selection, b, n = val || s.getNode();
+
+			// Make sure that the body node isn't removed
+			if (n == ed.getBody())
+				return;
+
+			b = s.getBookmark();
+			ed.dom.remove(n, 1);
+			s.moveToBookmark(b);
+			ed.nodeChanged();
+		},
+
+		mceSelectNodeDepth : function(ui, val) {
+			var ed = this.editor, s = ed.selection, c = 0;
+
+			ed.dom.getParent(s.getNode(), function(n) {
+				if (n.nodeType == 1 && c++ == val) {
+					s.select(n);
+					ed.nodeChanged();
+					return false;
+				}
+			}, ed.getBody());
+		},
+
+		mceSelectNode : function(u, v) {
+			this.editor.selection.select(v);
+		},
+
+		mceInsertContent : function(ui, val) {
+			this.editor.selection.setContent(val);
+		},
+
+		mceInsertRawHTML : function(ui, val) {
+			var ed = this.editor;
+
+			ed.selection.setContent('tiny_mce_marker');
+			ed.setContent(ed.getContent().replace(/tiny_mce_marker/g, val));
+		},
+
+		mceRepaint : function() {
+			var s, b, e = this.editor;
+
+			if (tinymce.isGecko) {
+				try {
+					s = e.selection;
+					b = s.getBookmark(true);
+
+					if (s.getSel())
+						s.getSel().selectAllChildren(e.getBody());
+
+					s.collapse(true);
+					s.moveToBookmark(b);
+				} catch (ex) {
+					// Ignore
+				}
+			}
+		},
+
+		queryStateUnderline : function() {
+			var ed = this.editor, n = ed.selection.getNode();
+
+			if (n && n.nodeName == 'A')
+				return false;
+
+			return this._queryState('Underline');
+		},
+
+		queryStateOutdent : function() {
+			var ed = this.editor, n;
+
+			if (ed.settings.inline_styles) {
+				if ((n = ed.dom.getParent(ed.selection.getStart(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0)
+					return true;
+
+				if ((n = ed.dom.getParent(ed.selection.getEnd(), ed.dom.isBlock)) && parseInt(n.style.paddingLeft) > 0)
+					return true;
+			}
+
+			return this.queryStateInsertUnorderedList() || this.queryStateInsertOrderedList() || (!ed.settings.inline_styles && !!ed.dom.getParent(ed.selection.getNode(), 'BLOCKQUOTE'));
+		},
+
+		queryStateInsertUnorderedList : function() {
+			return this.editor.dom.getParent(this.editor.selection.getNode(), 'UL');
+		},
+
+		queryStateInsertOrderedList : function() {
+			return this.editor.dom.getParent(this.editor.selection.getNode(), 'OL');
+		},
+
+		queryStatemceBlockQuote : function() {
+			return !!this.editor.dom.getParent(this.editor.selection.getStart(), function(n) {return n.nodeName === 'BLOCKQUOTE';});
+		},
+
+		_applyInlineStyle : function(na, at, op) {
+			var t = this, ed = t.editor, dom = ed.dom, bm, lo = {}, kh, found;
+
+			na = na.toUpperCase();
+
+			if (op && op.check_classes && at['class'])
+				op.check_classes.push(at['class']);
+
+			function removeEmpty() {
+				each(dom.select(na).reverse(), function(n) {
+					var c = 0;
+
+					// Check if there is any attributes
+					each(dom.getAttribs(n), function(an) {
+						if (an.nodeName.substring(0, 1) != '_' && dom.getAttrib(n, an.nodeName) != '') {
+							//console.log(dom.getOuterHTML(n), dom.getAttrib(n, an.nodeName));
+							c++;
+						}
+					});
+
+					// No attributes then remove the element and keep the children
+					if (c == 0)
+						dom.remove(n, 1);
+				});
+			};
+
+			function replaceFonts() {
+				var bm;
+
+				each(dom.select('span,font'), function(n) {
+					if (n.style.fontFamily == 'mceinline' || n.face == 'mceinline') {
+						if (!bm)
+							bm = ed.selection.getBookmark();
+
+						at._mce_new = '1';
+						dom.replace(dom.create(na, at), n, 1);
+					}
+				});
+
+				// Remove redundant elements
+				each(dom.select(na + '[_mce_new]'), function(n) {
+					function removeStyle(n) {
+						if (n.nodeType == 1) {
+							each(at.style, function(v, k) {
+								dom.setStyle(n, k, '');
+							});
+
+							// Remove spans with the same class or marked classes
+							if (at['class'] && n.className && op) {
+								each(op.check_classes, function(c) {
+									if (dom.hasClass(n, c))
+										dom.removeClass(n, c);
+								});
+							}
+						}
+					};
+
+					// Remove specified style information from child elements
+					each(dom.select(na, n), removeStyle);
+
+					// Remove the specified style information on parent if current node is only child (IE)
+					if (n.parentNode && n.parentNode.nodeType == 1 && n.parentNode.childNodes.length == 1)
+						removeStyle(n.parentNode);
+
+					// Remove the child elements style info if a parent already has it
+					dom.getParent(n.parentNode, function(pn) {
+						if (pn.nodeType == 1) {
+							if (at.style) {
+								each(at.style, function(v, k) {
+									var sv;
+
+									if (!lo[k] && (sv = dom.getStyle(pn, k))) {
+										if (sv === v)
+											dom.setStyle(n, k, '');
+
+										lo[k] = 1;
+									}
+								});
+							}
+
+							// Remove spans with the same class or marked classes
+							if (at['class'] && pn.className && op) {
+								each(op.check_classes, function(c) {
+									if (dom.hasClass(pn, c))
+										dom.removeClass(n, c);
+								});
+							}
+						}
+
+						return false;
+					});
+
+					n.removeAttribute('_mce_new');
+				});
+
+				removeEmpty();
+				ed.selection.moveToBookmark(bm);
+
+				return !!bm;
+			};
+
+			// Create inline elements
+			ed.focus();
+			ed.getDoc().execCommand('FontName', false, 'mceinline');
+			replaceFonts();
+
+			if (kh = t._applyInlineStyle.keyhandler) {
+				ed.onKeyUp.remove(kh);
+				ed.onKeyPress.remove(kh);
+				ed.onKeyDown.remove(kh);
+				ed.onSetContent.remove(t._applyInlineStyle.chandler);
+			}
+
+			if (ed.selection.isCollapsed()) {
+				// IE will format the current word so this code can't be executed on that browser
+				if (!isIE) {
+					each(dom.getParents(ed.selection.getNode(), 'span'), function(n) {
+						each(at.style, function(v, k) {
+							var kv;
+
+							if (kv = dom.getStyle(n, k)) {
+								if (kv == v) {
+									dom.setStyle(n, k, '');
+									found = 2;
+									return false;
+								}
+
+								found = 1;
+								return false;
+							}
+						});
+
+						if (found)
+							return false;
+					});
+
+					if (found == 2) {
+						bm = ed.selection.getBookmark();
+
+						removeEmpty();
+
+						ed.selection.moveToBookmark(bm);
+
+						// Node change needs to be detached since the onselect event
+						// for the select box will run the onclick handler after onselect call. Todo: Add a nicer fix!
+						window.setTimeout(function() {
+							ed.nodeChanged();
+						}, 1);
+
+						return;
+					}
+				}
+
+				// Start collecting styles
+				t._pendingStyles = tinymce.extend(t._pendingStyles || {}, at.style);
+
+				t._applyInlineStyle.chandler = ed.onSetContent.add(function() {
+					delete t._pendingStyles;
+				});
+
+				t._applyInlineStyle.keyhandler = kh = function(e) {
+					// Use pending styles
+					if (t._pendingStyles) {
+						at.style = t._pendingStyles;
+						delete t._pendingStyles;
+					}
+
+					if (replaceFonts()) {
+						ed.onKeyDown.remove(t._applyInlineStyle.keyhandler);
+						ed.onKeyPress.remove(t._applyInlineStyle.keyhandler);
+					}
+
+					if (e.type == 'keyup')
+						ed.onKeyUp.remove(t._applyInlineStyle.keyhandler);
+				};
+
+				ed.onKeyDown.add(kh);
+				ed.onKeyPress.add(kh);
+				ed.onKeyUp.add(kh);
+			} else
+				t._pendingStyles = 0;
+		}
+	});
+})(tinymce);(function(tinymce) {
+	tinymce.create('tinymce.UndoManager', {
+		index : 0,
+		data : null,
+		typing : 0,
+
+		UndoManager : function(ed) {
+			var t = this, Dispatcher = tinymce.util.Dispatcher;
+
+			t.editor = ed;
+			t.data = [];
+			t.onAdd = new Dispatcher(this);
+			t.onUndo = new Dispatcher(this);
+			t.onRedo = new Dispatcher(this);
+		},
+
+		add : function(l) {
+			var t = this, i, ed = t.editor, b, s = ed.settings, la;
+
+			l = l || {};
+			l.content = l.content || ed.getContent({format : 'raw', no_events : 1});
+
+			// Add undo level if needed
+			l.content = l.content.replace(/^\s*|\s*$/g, '');
+			la = t.data[t.index > 0 && (t.index == 0 || t.index == t.data.length) ? t.index - 1 : t.index];
+			if (!l.initial && la && l.content == la.content)
+				return null;
+
+			// Time to compress
+			if (s.custom_undo_redo_levels) {
+				if (t.data.length > s.custom_undo_redo_levels) {
+					for (i = 0; i < t.data.length - 1; i++)
+						t.data[i] = t.data[i + 1];
+
+					t.data.length--;
+					t.index = t.data.length;
+				}
+			}
+
+			if (s.custom_undo_redo_restore_selection && !l.initial)
+				l.bookmark = b = l.bookmark || ed.selection.getBookmark();
+
+			if (t.index < t.data.length)
+				t.index++;
+
+			// Only initial marked undo levels should be allowed as first item
+			// This to workaround a bug with Firefox and the blur event
+			if (t.data.length === 0 && !l.initial)
+				return null;
+
+			// Add level
+			t.data.length = t.index + 1;
+			t.data[t.index++] = l;
+
+			if (l.initial)
+				t.index = 0;
+
+			// Set initial bookmark use first real undo level
+			if (t.data.length == 2 && t.data[0].initial)
+				t.data[0].bookmark = b;
+
+			t.onAdd.dispatch(t, l);
+			ed.isNotDirty = 0;
+
+			//console.dir(t.data);
+
+			return l;
+		},
+
+		undo : function() {
+			var t = this, ed = t.editor, l = l, i;
+
+			if (t.typing) {
+				t.add();
+				t.typing = 0;
+			}
+
+			if (t.index > 0) {
+				// If undo on last index then take snapshot
+				if (t.index == t.data.length && t.index > 1) {
+					i = t.index;
+					t.typing = 0;
+
+					if (!t.add())
+						t.index = i;
+
+					--t.index;
+				}
+
+				l = t.data[--t.index];
+				ed.setContent(l.content, {format : 'raw'});
+				ed.selection.moveToBookmark(l.bookmark);
+
+				t.onUndo.dispatch(t, l);
+			}
+
+			return l;
+		},
+
+		redo : function() {
+			var t = this, ed = t.editor, l = null;
+
+			if (t.index < t.data.length - 1) {
+				l = t.data[++t.index];
+				ed.setContent(l.content, {format : 'raw'});
+				ed.selection.moveToBookmark(l.bookmark);
+
+				t.onRedo.dispatch(t, l);
+			}
+
+			return l;
+		},
+
+		clear : function() {
+			var t = this;
+
+			t.data = [];
+			t.index = 0;
+			t.typing = 0;
+			t.add({initial : true});
+		},
+
+		hasUndo : function() {
+			return this.index != 0 || this.typing;
+		},
+
+		hasRedo : function() {
+			return this.index < this.data.length - 1;
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	// Shorten names
+	var Event, isIE, isGecko, isOpera, each, extend;
+
+	Event = tinymce.dom.Event;
+	isIE = tinymce.isIE;
+	isGecko = tinymce.isGecko;
+	isOpera = tinymce.isOpera;
+	each = tinymce.each;
+	extend = tinymce.extend;
+
+	// Checks if the selection/caret is at the end of the specified block element
+	function isAtEnd(rng, par) {
+		var rng2 = par.ownerDocument.createRange();
+
+		rng2.setStart(rng.endContainer, rng.endOffset);
+		rng2.setEndAfter(par);
+
+		// Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
+		return rng2.cloneContents().textContent.length == 0;
+	};
+
+	function isEmpty(n) {
+		n = n.innerHTML;
+
+		n = n.replace(/<(img|hr|table|input|select|textarea)[ \>]/gi, '-'); // Keep these convert them to - chars
+		n = n.replace(/<[^>]+>/g, ''); // Remove all tags
+
+		return n.replace(/[ \t\r\n]+/g, '') == '';
+	};
+
+	tinymce.create('tinymce.ForceBlocks', {
+		ForceBlocks : function(ed) {
+			var t = this, s = ed.settings, elm;
+
+			t.editor = ed;
+			t.dom = ed.dom;
+			elm = (s.forced_root_block || 'p').toLowerCase();
+			s.element = elm.toUpperCase();
+
+			ed.onPreInit.add(t.setup, t);
+
+			t.reOpera = new RegExp('(\\u00a0| | )<\/' + elm + '>', 'gi');
+			t.rePadd = new RegExp('<p( )([^>]+)><\\\/p>|<p( )([^>]+)\\\/>|<p( )([^>]+)>\\s+<\\\/p>|<p><\\\/p>|<p\\\/>|<p>\\s+<\\\/p>'.replace(/p/g, elm), 'gi');
+			t.reNbsp2BR1 = new RegExp('<p( )([^>]+)>[\\s\\u00a0]+<\\\/p>|<p>[\\s\\u00a0]+<\\\/p>'.replace(/p/g, elm), 'gi');
+			t.reNbsp2BR2 = new RegExp('<%p()([^>]+)>( | )<\\\/%p>|<%p>( | )<\\\/%p>'.replace(/%p/g, elm), 'gi');
+			t.reBR2Nbsp = new RegExp('<p( )([^>]+)>\\s*<br \\\/>\\s*<\\\/p>|<p>\\s*<br \\\/>\\s*<\\\/p>'.replace(/p/g, elm), 'gi');
+
+			function padd(ed, o) {
+				if (isOpera)
+					o.content = o.content.replace(t.reOpera, '</' + elm + '>');
+
+				o.content = o.content.replace(t.rePadd, '<' + elm + '$1$2$3$4$5$6>\u00a0</' + elm + '>');
+
+				if (!isIE && !isOpera && o.set) {
+					// Use   instead of BR in padded paragraphs
+					o.content = o.content.replace(t.reNbsp2BR1, '<' + elm + '$1$2><br /></' + elm + '>');
+					o.content = o.content.replace(t.reNbsp2BR2, '<' + elm + '$1$2><br /></' + elm + '>');
+				} else
+					o.content = o.content.replace(t.reBR2Nbsp, '<' + elm + '$1$2>\u00a0</' + elm + '>');
+			};
+
+			ed.onBeforeSetContent.add(padd);
+			ed.onPostProcess.add(padd);
+
+			if (s.forced_root_block) {
+				ed.onInit.add(t.forceRoots, t);
+				ed.onSetContent.add(t.forceRoots, t);
+				ed.onBeforeGetContent.add(t.forceRoots, t);
+			}
+		},
+
+		setup : function() {
+			var t = this, ed = t.editor, s = ed.settings;
+
+			// Force root blocks when typing and when getting output
+			if (s.forced_root_block) {
+				ed.onKeyUp.add(t.forceRoots, t);
+				ed.onPreProcess.add(t.forceRoots, t);
+			}
+
+			if (s.force_br_newlines) {
+				// Force IE to produce BRs on enter
+				if (isIE) {
+					ed.onKeyPress.add(function(ed, e) {
+						var n, s = ed.selection;
+
+						if (e.keyCode == 13 && s.getNode().nodeName != 'LI') {
+							s.setContent('<br id="__" /> ', {format : 'raw'});
+							n = ed.dom.get('__');
+							n.removeAttribute('id');
+							s.select(n);
+							s.collapse();
+							return Event.cancel(e);
+						}
+					});
+				}
+
+				return;
+			}
+
+			if (!isIE && s.force_p_newlines) {
+/*				ed.onPreProcess.add(function(ed, o) {
+					each(ed.dom.select('br', o.node), function(n) {
+						var p = n.parentNode;
+
+						// Replace <p><br /></p> with <p> </p>
+						if (p && p.nodeName == 'p' && (p.childNodes.length == 1 || p.lastChild == n)) {
+							p.replaceChild(ed.getDoc().createTextNode('\u00a0'), n);
+						}
+					});
+				});*/
+
+				ed.onKeyPress.add(function(ed, e) {
+					if (e.keyCode == 13 && !e.shiftKey) {
+						if (!t.insertPara(e))
+							Event.cancel(e);
+					}
+				});
+
+				if (isGecko) {
+					ed.onKeyDown.add(function(ed, e) {
+						if ((e.keyCode == 8 || e.keyCode == 46) && !e.shiftKey)
+							t.backspaceDelete(e, e.keyCode == 8);
+					});
+				}
+			}
+
+			function ren(rn, na) {
+				var ne = ed.dom.create(na);
+
+				each(rn.attributes, function(a) {
+					if (a.specified && a.nodeValue)
+						ne.setAttribute(a.nodeName.toLowerCase(), a.nodeValue);
+				});
+
+				each(rn.childNodes, function(n) {
+					ne.appendChild(n.cloneNode(true));
+				});
+
+				rn.parentNode.replaceChild(ne, rn);
+
+				return ne;
+			};
+
+			// Padd empty inline elements within block elements
+			// For example: <p><strong><em></em></strong></p> becomes <p><strong><em> </em></strong></p>
+			ed.onPreProcess.add(function(ed, o) {
+				each(ed.dom.select('p,h1,h2,h3,h4,h5,h6,div', o.node), function(p) {
+					if (isEmpty(p)) {
+						each(ed.dom.select('span,em,strong,b,i', o.node), function(n) {
+							if (!n.hasChildNodes()) {
+								n.appendChild(ed.getDoc().createTextNode('\u00a0'));
+								return false; // Break the loop one padding is enough
+							}
+						});
+					}
+				});
+			});
+
+			// IE specific fixes
+			if (isIE) {
+				// Replaces IE:s auto generated paragraphs with the specified element name
+				if (s.element != 'P') {
+					ed.onKeyPress.add(function(ed, e) {
+						t.lastElm = ed.selection.getNode().nodeName;
+					});
+
+					ed.onKeyUp.add(function(ed, e) {
+						var bl, sel = ed.selection, n = sel.getNode(), b = ed.getBody();
+
+						if (b.childNodes.length === 1 && n.nodeName == 'P') {
+							n = ren(n, s.element);
+							sel.select(n);
+							sel.collapse();
+							ed.nodeChanged();
+						} else if (e.keyCode == 13 && !e.shiftKey && t.lastElm != 'P') {
+							bl = ed.dom.getParent(n, 'p');
+
+							if (bl) {
+								ren(bl, s.element);
+								ed.nodeChanged();
+							}
+						}
+					});
+				}
+			}
+		},
+
+		find : function(n, t, s) {
+			var ed = this.editor, w = ed.getDoc().createTreeWalker(n, 4, null, false), c = -1;
+
+			while (n = w.nextNode()) {
+				c++;
+
+				// Index by node
+				if (t == 0 && n == s)
+					return c;
+
+				// Node by index
+				if (t == 1 && c == s)
+					return n;
+			}
+
+			return -1;
+		},
+
+		forceRoots : function(ed, e) {
+			var t = this, ed = t.editor, b = ed.getBody(), d = ed.getDoc(), se = ed.selection, s = se.getSel(), r = se.getRng(), si = -2, ei, so, eo, tr, c = -0xFFFFFF;
+			var nx, bl, bp, sp, le, nl = b.childNodes, i, n, eid;
+
+			// Fix for bug #1863847
+			//if (e && e.keyCode == 13)
+			//	return true;
+
+			// Wrap non blocks into blocks
+			for (i = nl.length - 1; i >= 0; i--) {
+				nx = nl[i];
+
+				// Is text or non block element
+				if (nx.nodeType === 3 || (!t.dom.isBlock(nx) && nx.nodeType !== 8 && !/^(script|mce:script|style|mce:style)$/i.test(nx.nodeName))) {
+					if (!bl) {
+						// Create new block but ignore whitespace
+						if (nx.nodeType != 3 || /[^\s]/g.test(nx.nodeValue)) {
+							// Store selection
+							if (si == -2 && r) {
+								if (!isIE) {
+									// If selection is element then mark it
+									if (r.startContainer.nodeType == 1 && (n = r.startContainer.childNodes[r.startOffset]) && n.nodeType == 1) {
+										// Save the id of the selected element
+										eid = n.getAttribute("id");
+										n.setAttribute("id", "__mce");
+									} else {
+										// If element is inside body, might not be the case in contentEdiable mode
+										if (ed.dom.getParent(r.startContainer, function(e) {return e === b;})) {
+											so = r.startOffset;
+											eo = r.endOffset;
+											si = t.find(b, 0, r.startContainer);
+											ei = t.find(b, 0, r.endContainer);
+										}
+									}
+								} else {
+									tr = d.body.createTextRange();
+									tr.moveToElementText(b);
+									tr.collapse(1);
+									bp = tr.move('character', c) * -1;
+
+									tr = r.duplicate();
+									tr.collapse(1);
+									sp = tr.move('character', c) * -1;
+
+									tr = r.duplicate();
+									tr.collapse(0);
+									le = (tr.move('character', c) * -1) - sp;
+
+									si = sp - bp;
+									ei = le;
+								}
+							}
+
+							// Uses replaceChild instead of cloneNode since it removes selected attribute from option elements on IE
+							// See: http://support.microsoft.com/kb/829907
+							bl = ed.dom.create(ed.settings.forced_root_block);
+							nx.parentNode.replaceChild(bl, nx);
+							bl.appendChild(nx);
+						}
+					} else {
+						if (bl.hasChildNodes())
+							bl.insertBefore(nx, bl.firstChild);
+						else
+							bl.appendChild(nx);
+					}
+				} else
+					bl = null; // Time to create new block
+			}
+
+			// Restore selection
+			if (si != -2) {
+				if (!isIE) {
+					bl = b.getElementsByTagName(ed.settings.element)[0];
+					r = d.createRange();
+
+					// Select last location or generated block
+					if (si != -1)
+						r.setStart(t.find(b, 1, si), so);
+					else
+						r.setStart(bl, 0);
+
+					// Select last location or generated block
+					if (ei != -1)
+						r.setEnd(t.find(b, 1, ei), eo);
+					else
+						r.setEnd(bl, 0);
+
+					if (s) {
+						s.removeAllRanges();
+						s.addRange(r);
+					}
+				} else {
+					try {
+						r = s.createRange();
+						r.moveToElementText(b);
+						r.collapse(1);
+						r.moveStart('character', si);
+						r.moveEnd('character', ei);
+						r.select();
+					} catch (ex) {
+						// Ignore
+					}
+				}
+			} else if (!isIE && (n = ed.dom.get('__mce'))) {
+				// Restore the id of the selected element
+				if (eid)
+					n.setAttribute('id', eid);
+				else
+					n.removeAttribute('id');
+
+				// Move caret before selected element
+				r = d.createRange();
+				r.setStartBefore(n);
+				r.setEndBefore(n);
+				se.setRng(r);
+			}
+		},
+
+		getParentBlock : function(n) {
+			var d = this.dom;
+
+			return d.getParent(n, d.isBlock);
+		},
+
+		insertPara : function(e) {
+			var t = this, ed = t.editor, dom = ed.dom, d = ed.getDoc(), se = ed.settings, s = ed.selection.getSel(), r = s.getRangeAt(0), b = d.body;
+			var rb, ra, dir, sn, so, en, eo, sb, eb, bn, bef, aft, sc, ec, n, vp = dom.getViewPort(ed.getWin()), y, ch, car;
+
+			// If root blocks are forced then use Operas default behavior since it's really good
+// Removed due to bug: #1853816
+//			if (se.forced_root_block && isOpera)
+//				return true;
+
+			// Setup before range
+			rb = d.createRange();
+
+			// If is before the first block element and in body, then move it into first block element
+			rb.setStart(s.anchorNode, s.anchorOffset);
+			rb.collapse(true);
+
+			// Setup after range
+			ra = d.createRange();
+
+			// If is before the first block element and in body, then move it into first block element
+			ra.setStart(s.focusNode, s.focusOffset);
+			ra.collapse(true);
+
+			// Setup start/end points
+			dir = rb.compareBoundaryPoints(rb.START_TO_END, ra) < 0;
+			sn = dir ? s.anchorNode : s.focusNode;
+			so = dir ? s.anchorOffset : s.focusOffset;
+			en = dir ? s.focusNode : s.anchorNode;
+			eo = dir ? s.focusOffset : s.anchorOffset;
+
+			// If selection is in empty table cell
+			if (sn === en && /^(TD|TH)$/.test(sn.nodeName)) {
+				if (sn.firstChild.nodeName == 'BR')
+					dom.remove(sn.firstChild); // Remove BR
+
+				// Create two new block elements
+				if (sn.childNodes.length == 0) {
+					ed.dom.add(sn, se.element, null, '<br />');
+					aft = ed.dom.add(sn, se.element, null, '<br />');
+				} else {
+					n = sn.innerHTML;
+					sn.innerHTML = '';
+					ed.dom.add(sn, se.element, null, n);
+					aft = ed.dom.add(sn, se.element, null, '<br />');
+				}
+
+				// Move caret into the last one
+				r = d.createRange();
+				r.selectNodeContents(aft);
+				r.collapse(1);
+				ed.selection.setRng(r);
+
+				return false;
+			}
+
+			// If the caret is in an invalid location in FF we need to move it into the first block
+			if (sn == b && en == b && b.firstChild && ed.dom.isBlock(b.firstChild)) {
+				sn = en = sn.firstChild;
+				so = eo = 0;
+				rb = d.createRange();
+				rb.setStart(sn, 0);
+				ra = d.createRange();
+				ra.setStart(en, 0);
+			}
+
+			// Never use body as start or end node
+			sn = sn.nodeName == "HTML" ? d.body : sn; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
+			sn = sn.nodeName == "BODY" ? sn.firstChild : sn;
+			en = en.nodeName == "HTML" ? d.body : en; // Fix for Opera bug: https://bugs.opera.com/show_bug.cgi?id=273224&comments=yes
+			en = en.nodeName == "BODY" ? en.firstChild : en;
+
+			// Get start and end blocks
+			sb = t.getParentBlock(sn);
+			eb = t.getParentBlock(en);
+			bn = sb ? sb.nodeName : se.element; // Get block name to create
+
+			// Return inside list use default browser behavior
+			if (t.dom.getParent(sb, 'ol,ul,pre'))
+				return true;
+
+			// If caption or absolute layers then always generate new blocks within
+			if (sb && (sb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
+				bn = se.element;
+				sb = null;
+			}
+
+			// If caption or absolute layers then always generate new blocks within
+			if (eb && (eb.nodeName == 'CAPTION' || /absolute|relative|fixed/gi.test(dom.getStyle(sb, 'position', 1)))) {
+				bn = se.element;
+				eb = null;
+			}
+
+			// Use P instead
+			if (/(TD|TABLE|TH|CAPTION)/.test(bn) || (sb && bn == "DIV" && /left|right/gi.test(dom.getStyle(sb, 'float', 1)))) {
+				bn = se.element;
+				sb = eb = null;
+			}
+
+			// Setup new before and after blocks
+			bef = (sb && sb.nodeName == bn) ? sb.cloneNode(0) : ed.dom.create(bn);
+			aft = (eb && eb.nodeName == bn) ? eb.cloneNode(0) : ed.dom.create(bn);
+
+			// Remove id from after clone
+			aft.removeAttribute('id');
+
+			// Is header and cursor is at the end, then force paragraph under
+			if (/^(H[1-6])$/.test(bn) && isAtEnd(r, sb))
+				aft = ed.dom.create(se.element);
+
+			// Find start chop node
+			n = sc = sn;
+			do {
+				if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
+					break;
+
+				sc = n;
+			} while ((n = n.previousSibling ? n.previousSibling : n.parentNode));
+
+			// Find end chop node
+			n = ec = en;
+			do {
+				if (n == b || n.nodeType == 9 || t.dom.isBlock(n) || /(TD|TABLE|TH|CAPTION)/.test(n.nodeName))
+					break;
+
+				ec = n;
+			} while ((n = n.nextSibling ? n.nextSibling : n.parentNode));
+
+			// Place first chop part into before block element
+			if (sc.nodeName == bn)
+				rb.setStart(sc, 0);
+			else
+				rb.setStartBefore(sc);
+
+			rb.setEnd(sn, so);
+			bef.appendChild(rb.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
+
+			// Place secnd chop part within new block element
+			try {
+				ra.setEndAfter(ec);
+			} catch(ex) {
+				//console.debug(s.focusNode, s.focusOffset);
+			}
+
+			ra.setStart(en, eo);
+			aft.appendChild(ra.cloneContents() || d.createTextNode('')); // Empty text node needed for Safari
+
+			// Create range around everything
+			r = d.createRange();
+			if (!sc.previousSibling && sc.parentNode.nodeName == bn) {
+				r.setStartBefore(sc.parentNode);
+			} else {
+				if (rb.startContainer.nodeName == bn && rb.startOffset == 0)
+					r.setStartBefore(rb.startContainer);
+				else
+					r.setStart(rb.startContainer, rb.startOffset);
+			}
+
+			if (!ec.nextSibling && ec.parentNode.nodeName == bn)
+				r.setEndAfter(ec.parentNode);
+			else
+				r.setEnd(ra.endContainer, ra.endOffset);
+
+			// Delete and replace it with new block elements
+			r.deleteContents();
+
+			if (isOpera)
+				ed.getWin().scrollTo(0, vp.y);
+
+			// Never wrap blocks in blocks
+			if (bef.firstChild && bef.firstChild.nodeName == bn)
+				bef.innerHTML = bef.firstChild.innerHTML;
+
+			if (aft.firstChild && aft.firstChild.nodeName == bn)
+				aft.innerHTML = aft.firstChild.innerHTML;
+
+			// Padd empty blocks
+			if (isEmpty(bef))
+				bef.innerHTML = '<br />';
+
+			function appendStyles(e, en) {
+				var nl = [], nn, n, i;
+
+				e.innerHTML = '';
+
+				// Make clones of style elements
+				if (se.keep_styles) {
+					n = en;
+					do {
+						// We only want style specific elements
+						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(n.nodeName)) {
+							nn = n.cloneNode(false);
+							dom.setAttrib(nn, 'id', ''); // Remove ID since it needs to be unique
+							nl.push(nn);
+						}
+					} while (n = n.parentNode);
+				}
+
+				// Append style elements to aft
+				if (nl.length > 0) {
+					for (i = nl.length - 1, nn = e; i >= 0; i--)
+						nn = nn.appendChild(nl[i]);
+
+					// Padd most inner style element
+					nl[0].innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
+					return nl[0]; // Move caret to most inner element
+				} else
+					e.innerHTML = isOpera ? ' ' : '<br />'; // Extra space for Opera so that the caret can move there
+			};
+
+			// Fill empty afterblook with current style
+			if (isEmpty(aft))
+				car = appendStyles(aft, en);
+
+			// Opera needs this one backwards for older versions
+			if (isOpera && parseFloat(opera.version()) < 9.5) {
+				r.insertNode(bef);
+				r.insertNode(aft);
+			} else {
+				r.insertNode(aft);
+				r.insertNode(bef);
+			}
+
+			// Normalize
+			aft.normalize();
+			bef.normalize();
+
+			function first(n) {
+				return d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false).nextNode() || n;
+			};
+
+			// Move cursor and scroll into view
+			r = d.createRange();
+			r.selectNodeContents(isGecko ? first(car || aft) : car || aft);
+			r.collapse(1);
+			s.removeAllRanges();
+			s.addRange(r);
+
+			// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
+			y = ed.dom.getPos(aft).y;
+			ch = aft.clientHeight;
+
+			// Is element within viewport
+			if (y < vp.y || y + ch > vp.y + vp.h) {
+				ed.getWin().scrollTo(0, y < vp.y ? y : y - vp.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
+				//console.debug('SCROLL!', 'vp.y: ' + vp.y, 'y' + y, 'vp.h' + vp.h, 'clientHeight' + aft.clientHeight, 'yyy: ' + (y < vp.y ? y : y - vp.h + aft.clientHeight));
+			}
+
+			return false;
+		},
+
+		backspaceDelete : function(e, bs) {
+			var t = this, ed = t.editor, b = ed.getBody(), dom = ed.dom, n, se = ed.selection, r = se.getRng(), sc = r.startContainer, n, w, tn;
+
+			/*
+			var par, rng, nextBlock;
+
+			// Delete key will not merge paragraphs on Gecko so we need to do this manually
+			// Hitting the delete key at the following caret position doesn't merge the elements <p>A|</p><p>B</p>
+			// This logic will merge them into this: <p>A|B</p>
+			if (e.keyCode == 46) {
+				if (r.collapsed) {
+					par = dom.getParent(sc, 'p,h1,h2,h3,h4,h5,h6,div');
+
+					if (par) {
+						rng = dom.createRng();
+
+						rng.setStart(sc, r.startOffset);
+						rng.setEndAfter(par);
+
+						// Get number of characters to the right of the cursor if it's zero then we are at the end and need to merge the next block element
+						if (dom.getOuterHTML(rng.cloneContents()).replace(/<[^>]+>/g, '').length == 0) {
+							nextBlock = dom.getNext(par, 'p,h1,h2,h3,h4,h5,h6,div');
+
+							// Copy all children from next sibling block and remove it
+							if (nextBlock) {
+								each(nextBlock.childNodes, function(node) {
+									par.appendChild(node.cloneNode(true));
+								});
+
+								dom.remove(nextBlock);
+							}
+
+							// Block the default even since the Gecko team might eventually fix this
+							// We will remove this logic once they do we can't feature detect this one
+							e.preventDefault();
+							return;
+						}
+					}
+				}
+			}
+			*/
+
+			// The caret sometimes gets stuck in Gecko if you delete empty paragraphs
+			// This workaround removes the element by hand and moves the caret to the previous element
+			if (sc && ed.dom.isBlock(sc) && !/^(TD|TH)$/.test(sc.nodeName) && bs) {
+				if (sc.childNodes.length == 0 || (sc.childNodes.length == 1 && sc.firstChild.nodeName == 'BR')) {
+					// Find previous block element
+					n = sc;
+					while ((n = n.previousSibling) && !ed.dom.isBlock(n)) ;
+
+					if (n) {
+						if (sc != b.firstChild) {
+							// Find last text node
+							w = ed.dom.doc.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
+							while (tn = w.nextNode())
+								n = tn;
+
+							// Place caret at the end of last text node
+							r = ed.getDoc().createRange();
+							r.setStart(n, n.nodeValue ? n.nodeValue.length : 0);
+							r.setEnd(n, n.nodeValue ? n.nodeValue.length : 0);
+							se.setRng(r);
+
+							// Remove the target container
+							ed.dom.remove(sc);
+						}
+
+						return Event.cancel(e);
+					}
+				}
+			}
+
+			// Gecko generates BR elements here and there, we don't like those so lets remove them
+			function handler(e) {
+				var pr;
+
+				e = e.target;
+
+				// A new BR was created in a block element, remove it
+				if (e && e.parentNode && e.nodeName == 'BR' && (n = t.getParentBlock(e))) {
+					pr = e.previousSibling;
+
+					Event.remove(b, 'DOMNodeInserted', handler);
+
+					// Is there whitespace at the end of the node before then we might need the pesky BR
+					// to place the caret at a correct location see bug: #2013943
+					if (pr && pr.nodeType == 3 && /\s+$/.test(pr.nodeValue))
+						return;
+
+					// Only remove BR elements that got inserted in the middle of the text
+					if (e.previousSibling || e.nextSibling)
+						ed.dom.remove(e);
+				}
+			};
+
+			// Listen for new nodes
+			Event._add(b, 'DOMNodeInserted', handler);
+
+			// Remove listener
+			window.setTimeout(function() {
+				Event._remove(b, 'DOMNodeInserted', handler);
+			}, 1);
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	// Shorten names
+	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
+
+	tinymce.create('tinymce.ControlManager', {
+		ControlManager : function(ed, s) {
+			var t = this, i;
+
+			s = s || {};
+			t.editor = ed;
+			t.controls = {};
+			t.onAdd = new tinymce.util.Dispatcher(t);
+			t.onPostRender = new tinymce.util.Dispatcher(t);
+			t.prefix = s.prefix || ed.id + '_';
+			t._cls = {};
+
+			t.onPostRender.add(function() {
+				each(t.controls, function(c) {
+					c.postRender();
+				});
+			});
+		},
+
+		get : function(id) {
+			return this.controls[this.prefix + id] || this.controls[id];
+		},
+
+		setActive : function(id, s) {
+			var c = null;
+
+			if (c = this.get(id))
+				c.setActive(s);
+
+			return c;
+		},
+
+		setDisabled : function(id, s) {
+			var c = null;
+
+			if (c = this.get(id))
+				c.setDisabled(s);
+
+			return c;
+		},
+
+		add : function(c) {
+			var t = this;
+
+			if (c) {
+				t.controls[c.id] = c;
+				t.onAdd.dispatch(c, t);
+			}
+
+			return c;
+		},
+
+		createControl : function(n) {
+			var c, t = this, ed = t.editor;
+
+			each(ed.plugins, function(p) {
+				if (p.createControl) {
+					c = p.createControl(n, t);
+
+					if (c)
+						return false;
+				}
+			});
+
+			switch (n) {
+				case "|":
+				case "separator":
+					return t.createSeparator();
+			}
+
+			if (!c && ed.buttons && (c = ed.buttons[n]))
+				return t.createButton(n, c);
+
+			return t.add(c);
+		},
+
+		createDropMenu : function(id, s, cc) {
+			var t = this, ed = t.editor, c, bm, v, cls;
+
+			s = extend({
+				'class' : 'mceDropDown',
+				constrain : ed.settings.constrain_menus
+			}, s);
+
+			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
+			if (v = ed.getParam('skin_variant'))
+				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
+
+			id = t.prefix + id;
+			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
+			c = t.controls[id] = new cls(id, s);
+			c.onAddItem.add(function(c, o) {
+				var s = o.settings;
+
+				s.title = ed.getLang(s.title, s.title);
+
+				if (!s.onclick) {
+					s.onclick = function(v) {
+						ed.execCommand(s.cmd, s.ui || false, s.value);
+					};
+				}
+			});
+
+			ed.onRemove.add(function() {
+				c.destroy();
+			});
+
+			// Fix for bug #1897785, #1898007
+			if (tinymce.isIE) {
+				c.onShowMenu.add(function() {
+					// IE 8 needs focus in order to store away a range with the current collapsed caret location
+					ed.focus();
+
+					bm = ed.selection.getBookmark(1);
+				});
+
+				c.onHideMenu.add(function() {
+					if (bm) {
+						ed.selection.moveToBookmark(bm);
+						bm = 0;
+					}
+				});
+			}
+
+			return t.add(c);
+		},
+
+		createListBox : function(id, s, cc) {
+			var t = this, ed = t.editor, cmd, c, cls;
+
+			if (t.get(id))
+				return null;
+
+			s.title = ed.translate(s.title);
+			s.scope = s.scope || ed;
+
+			if (!s.onselect) {
+				s.onselect = function(v) {
+					ed.execCommand(s.cmd, s.ui || false, v || s.value);
+				};
+			}
+
+			s = extend({
+				title : s.title,
+				'class' : 'mce_' + id,
+				scope : s.scope,
+				control_manager : t
+			}, s);
+
+			id = t.prefix + id;
+
+			if (ed.settings.use_native_selects)
+				c = new tinymce.ui.NativeListBox(id, s);
+			else {
+				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
+				c = new cls(id, s);
+			}
+
+			t.controls[id] = c;
+
+			// Fix focus problem in Safari
+			if (tinymce.isWebKit) {
+				c.onPostRender.add(function(c, n) {
+					// Store bookmark on mousedown
+					Event.add(n, 'mousedown', function() {
+						ed.bookmark = ed.selection.getBookmark(1);
+					});
+
+					// Restore on focus, since it might be lost
+					Event.add(n, 'focus', function() {
+						ed.selection.moveToBookmark(ed.bookmark);
+						ed.bookmark = null;
+					});
+				});
+			}
+
+			if (c.hideMenu)
+				ed.onMouseDown.add(c.hideMenu, c);
+
+			return t.add(c);
+		},
+
+		createButton : function(id, s, cc) {
+			var t = this, ed = t.editor, o, c, cls;
+
+			if (t.get(id))
+				return null;
+
+			s.title = ed.translate(s.title);
+			s.label = ed.translate(s.label);
+			s.scope = s.scope || ed;
+
+			if (!s.onclick && !s.menu_button) {
+				s.onclick = function() {
+					ed.execCommand(s.cmd, s.ui || false, s.value);
+				};
+			}
+
+			s = extend({
+				title : s.title,
+				'class' : 'mce_' + id,
+				unavailable_prefix : ed.getLang('unavailable', ''),
+				scope : s.scope,
+				control_manager : t
+			}, s);
+
+			id = t.prefix + id;
+
+			if (s.menu_button) {
+				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
+				c = new cls(id, s);
+				ed.onMouseDown.add(c.hideMenu, c);
+			} else {
+				cls = t._cls.button || tinymce.ui.Button;
+				c = new cls(id, s);
+			}
+
+			return t.add(c);
+		},
+
+		createMenuButton : function(id, s, cc) {
+			s = s || {};
+			s.menu_button = 1;
+
+			return this.createButton(id, s, cc);
+		},
+
+		createSplitButton : function(id, s, cc) {
+			var t = this, ed = t.editor, cmd, c, cls;
+
+			if (t.get(id))
+				return null;
+
+			s.title = ed.translate(s.title);
+			s.scope = s.scope || ed;
+
+			if (!s.onclick) {
+				s.onclick = function(v) {
+					ed.execCommand(s.cmd, s.ui || false, v || s.value);
+				};
+			}
+
+			if (!s.onselect) {
+				s.onselect = function(v) {
+					ed.execCommand(s.cmd, s.ui || false, v || s.value);
+				};
+			}
+
+			s = extend({
+				title : s.title,
+				'class' : 'mce_' + id,
+				scope : s.scope,
+				control_manager : t
+			}, s);
+
+			id = t.prefix + id;
+			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
+			c = t.add(new cls(id, s));
+			ed.onMouseDown.add(c.hideMenu, c);
+
+			return c;
+		},
+
+		createColorSplitButton : function(id, s, cc) {
+			var t = this, ed = t.editor, cmd, c, cls, bm;
+
+			if (t.get(id))
+				return null;
+
+			s.title = ed.translate(s.title);
+			s.scope = s.scope || ed;
+
+			if (!s.onclick) {
+				s.onclick = function(v) {
+					if (tinymce.isIE)
+						bm = ed.selection.getBookmark(1);
+
+					ed.execCommand(s.cmd, s.ui || false, v || s.value);
+				};
+			}
+
+			if (!s.onselect) {
+				s.onselect = function(v) {
+					ed.execCommand(s.cmd, s.ui || false, v || s.value);
+				};
+			}
+
+			s = extend({
+				title : s.title,
+				'class' : 'mce_' + id,
+				'menu_class' : ed.getParam('skin') + 'Skin',
+				scope : s.scope,
+				more_colors_title : ed.getLang('more_colors')
+			}, s);
+
+			id = t.prefix + id;
+			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
+			c = new cls(id, s);
+			ed.onMouseDown.add(c.hideMenu, c);
+
+			// Remove the menu element when the editor is removed
+			ed.onRemove.add(function() {
+				c.destroy();
+			});
+
+			// Fix for bug #1897785, #1898007
+			if (tinymce.isIE) {
+				c.onShowMenu.add(function() {
+					// IE 8 needs focus in order to store away a range with the current collapsed caret location
+					ed.focus();
+					bm = ed.selection.getBookmark(1);
+				});
+
+				c.onHideMenu.add(function() {
+					if (bm) {
+						ed.selection.moveToBookmark(bm);
+						bm = 0;
+					}
+				});
+			}
+
+			return t.add(c);
+		},
+
+		createToolbar : function(id, s, cc) {
+			var c, t = this, cls;
+
+			id = t.prefix + id;
+			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
+			c = new cls(id, s);
+
+			if (t.get(id))
+				return null;
+
+			return t.add(c);
+		},
+
+		createSeparator : function(cc) {
+			var cls = cc || this._cls.separator || tinymce.ui.Separator;
+
+			return new cls();
+		},
+
+		setControlType : function(n, c) {
+			return this._cls[n.toLowerCase()] = c;
+		},
+	
+		destroy : function() {
+			each(this.controls, function(c) {
+				c.destroy();
+			});
+
+			this.controls = null;
+		}
+	});
+})(tinymce);
+(function(tinymce) {
+	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
+
+	tinymce.create('tinymce.WindowManager', {
+		WindowManager : function(ed) {
+			var t = this;
+
+			t.editor = ed;
+			t.onOpen = new Dispatcher(t);
+			t.onClose = new Dispatcher(t);
+			t.params = {};
+			t.features = {};
+		},
+
+		open : function(s, p) {
+			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
+
+			// Default some options
+			s = s || {};
+			p = p || {};
+			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
+			sh = isOpera ? vp.h : screen.height;
+			s.name = s.name || 'mc_' + new Date().getTime();
+			s.width = parseInt(s.width || 320);
+			s.height = parseInt(s.height || 240);
+			s.resizable = true;
+			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
+			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
+			p.inline = false;
+			p.mce_width = s.width;
+			p.mce_height = s.height;
+			p.mce_auto_focus = s.auto_focus;
+
+			if (mo) {
+				if (isIE) {
+					s.center = true;
+					s.help = false;
+					s.dialogWidth = s.width + 'px';
+					s.dialogHeight = s.height + 'px';
+					s.scroll = s.scrollbars || false;
+				}
+			}
+
+			// Build features string
+			each(s, function(v, k) {
+				if (tinymce.is(v, 'boolean'))
+					v = v ? 'yes' : 'no';
+
+				if (!/^(name|url)$/.test(k)) {
+					if (isIE && mo)
+						f += (f ? ';' : '') + k + ':' + v;
+					else
+						f += (f ? ',' : '') + k + '=' + v;
+				}
+			});
+
+			t.features = s;
+			t.params = p;
+			t.onOpen.dispatch(t, s, p);
+
+			u = s.url || s.file;
+			u = tinymce._addVer(u);
+
+			try {
+				if (isIE && mo) {
+					w = 1;
+					window.showModalDialog(u, window, f);
+				} else
+					w = window.open(u, s.name, f);
+			} catch (ex) {
+				// Ignore
+			}
+
+			if (!w)
+				alert(t.editor.getLang('popup_blocked'));
+		},
+
+		close : function(w) {
+			w.close();
+			this.onClose.dispatch(this);
+		},
+
+		createInstance : function(cl, a, b, c, d, e) {
+			var f = tinymce.resolve(cl);
+
+			return new f(a, b, c, d, e);
+		},
+
+		confirm : function(t, cb, s, w) {
+			w = w || window;
+
+			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
+		},
+
+		alert : function(tx, cb, s, w) {
+			var t = this;
+
+			w = w || window;
+			w.alert(t._decode(t.editor.getLang(tx, tx)));
+
+			if (cb)
+				cb.call(s || t);
+		},
+
+		// Internal functions
+
+		_decode : function(s) {
+			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
+		}
+	});
+}(tinymce));(function(tinymce) {
+	tinymce.CommandManager = function() {
+		var execCommands = {}, queryStateCommands = {}, queryValueCommands = {};
+
+		function add(collection, cmd, func, scope) {
+			if (typeof(cmd) == 'string')
+				cmd = [cmd];
+
+			tinymce.each(cmd, function(cmd) {
+				collection[cmd.toLowerCase()] = {func : func, scope : scope};
+			});
+		};
+
+		tinymce.extend(this, {
+			add : function(cmd, func, scope) {
+				add(execCommands, cmd, func, scope);
+			},
+
+			addQueryStateHandler : function(cmd, func, scope) {
+				add(queryStateCommands, cmd, func, scope);
+			},
+
+			addQueryValueHandler : function(cmd, func, scope) {
+				add(queryValueCommands, cmd, func, scope);
+			},
+
+			execCommand : function(scope, cmd, ui, value, args) {
+				if (cmd = execCommands[cmd.toLowerCase()]) {
+					if (cmd.func.call(scope || cmd.scope, ui, value, args) !== false)
+						return true;
+				}
+			},
+
+			queryCommandValue : function() {
+				if (cmd = queryValueCommands[cmd.toLowerCase()])
+					return cmd.func.call(scope || cmd.scope, ui, value, args);
+			},
+
+			queryCommandState : function() {
+				if (cmd = queryStateCommands[cmd.toLowerCase()])
+					return cmd.func.call(scope || cmd.scope, ui, value, args);
+			}
+		});
+	};
+
+	tinymce.GlobalCommands = new tinymce.CommandManager();
+})(tinymce);(function(tinymce) {
+	function processRange(dom, start, end, callback) {
+		var ancestor, n, startPoint, endPoint, sib;
+
+		function findEndPoint(n, c) {
+			do {
+				if (n.parentNode == c)
+					return n;
+
+				n = n.parentNode;
+			} while(n);
+		};
+
+		function process(n) {
+			callback(n);
+			tinymce.walk(n, callback, 'childNodes');
+		};
+
+		// Find common ancestor and end points
+		ancestor = dom.findCommonAncestor(start, end);
+		startPoint = findEndPoint(start, ancestor) || start;
+		endPoint = findEndPoint(end, ancestor) || end;
+
+		// Process left leaf
+		for (n = start; n && n != startPoint; n = n.parentNode) {
+			for (sib = n.nextSibling; sib; sib = sib.nextSibling)
+				process(sib);
+		}
+
+		// Process middle from start to end point
+		if (startPoint != endPoint) {
+			for (n = startPoint.nextSibling; n && n != endPoint; n = n.nextSibling)
+				process(n);
+		} else
+			process(startPoint);
+
+		// Process right leaf
+		for (n = end; n && n != endPoint; n = n.parentNode) {
+			for (sib = n.previousSibling; sib; sib = sib.previousSibling)
+				process(sib);
+		}
+	};
+
+	tinymce.GlobalCommands.add('RemoveFormat', function() {
+		var ed = this, dom = ed.dom, s = ed.selection, r = s.getRng(1), nodes = [], bm, start, end, sc, so, ec, eo, n;
+
+		function findFormatRoot(n) {
+			var sp;
+
+			dom.getParent(n, function(n) {
+				if (dom.is(n, ed.getParam('removeformat_selector')))
+					sp = n;
+
+				return dom.isBlock(n);
+			}, ed.getBody());
+
+			return sp;
+		};
+
+		function collect(n) {
+			if (dom.is(n, ed.getParam('removeformat_selector')))
+				nodes.push(n);
+		};
+
+		function walk(n) {
+			collect(n);
+			tinymce.walk(n, collect, 'childNodes');
+		};
+
+		bm = s.getBookmark();
+		sc = r.startContainer;
+		ec = r.endContainer;
+		so = r.startOffset;
+		eo = r.endOffset;
+		sc = sc.nodeType == 1 ? sc.childNodes[Math.min(so, sc.childNodes.length - 1)] : sc;
+		ec = ec.nodeType == 1 ? ec.childNodes[Math.min(so == eo ? eo : eo - 1, ec.childNodes.length - 1)] : ec;
+
+		// Same container
+		if (sc == ec) { // TEXT_NODE
+			start = findFormatRoot(sc);
+
+			// Handle single text node
+			if (sc.nodeType == 3) {
+				if (start && start.nodeType == 1) { // ELEMENT
+					n = sc.splitText(so);
+					n.splitText(eo - so);
+					dom.split(start, n);
+
+					s.moveToBookmark(bm);
+				}
+
+				return;
+			}
+
+			// Handle single element
+			walk(dom.split(start, sc) || sc);
+		} else {
+			// Find start/end format root
+			start = findFormatRoot(sc);
+			end = findFormatRoot(ec);
+
+			// Split start text node
+			if (start) {
+				if (sc.nodeType == 3) { // TEXT
+					// Since IE doesn't support white space nodes in the DOM we need to
+					// add this invisible character so that the splitText function can split the contents
+					if (so == sc.nodeValue.length)
+						sc.nodeValue += '\uFEFF'; // Yet another pesky IE fix
+
+					sc = sc.splitText(so);
+				}
+			}
+
+			// Split end text node
+			if (end) {
+				if (ec.nodeType == 3) // TEXT
+					ec.splitText(eo);
+			}
+
+			// If the start and end format root is the same then we need to wrap
+			// the end node in a span since the split calls might change the reference
+			// Example: <p><b><em>x[yz<span>---</span>12]3</em></b></p>
+			if (start && start == end)
+				dom.replace(dom.create('span', {id : '__end'}, ec.cloneNode(true)), ec);
+
+			// Split all start containers down to the format root
+			if (start)
+				start = dom.split(start, sc);
+			else
+				start = sc;
+
+			// If there is a span wrapper use that one instead
+			if (n = dom.get('__end')) {
+				ec = n;
+				end = findFormatRoot(ec);
+			}
+
+			// Split all end containers down to the format root
+			if (end)
+				end = dom.split(end, ec);
+			else
+				end = ec;
+
+			// Collect nodes in between
+			processRange(dom, start, end, collect);
+
+			// Remove invisible character for IE workaround if we find it
+			if (sc.nodeValue == '\uFEFF')
+				sc.nodeValue = '';
+
+			// Process start/end container elements
+			walk(ec);
+			walk(sc);
+		}
+
+		// Remove all collected nodes
+		tinymce.each(nodes, function(n) {
+			dom.remove(n, 1);
+		});
+
+		// Remove leftover wrapper
+		dom.remove('__end', 1);
+
+		s.moveToBookmark(bm);
+	});
+})(tinymce);
+(function(tinymce) {
+	tinymce.GlobalCommands.add('mceBlockQuote', function() {
+		var ed = this, s = ed.selection, dom = ed.dom, sb, eb, n, bm, bq, r, bq2, i, nl;
+
+		function getBQ(e) {
+			return dom.getParent(e, function(n) {return n.nodeName === 'BLOCKQUOTE';});
+		};
+
+		// Get start/end block
+		sb = dom.getParent(s.getStart(), dom.isBlock);
+		eb = dom.getParent(s.getEnd(), dom.isBlock);
+
+		// Remove blockquote(s)
+		if (bq = getBQ(sb)) {
+			if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR'))
+				bm = s.getBookmark();
+
+			// Move all elements after the end block into new bq
+			if (getBQ(eb)) {
+				bq2 = bq.cloneNode(false);
+
+				while (n = eb.nextSibling)
+					bq2.appendChild(n.parentNode.removeChild(n));
+			}
+
+			// Add new bq after
+			if (bq2)
+				dom.insertAfter(bq2, bq);
+
+			// Move all selected blocks after the current bq
+			nl = s.getSelectedBlocks(sb, eb);
+			for (i = nl.length - 1; i >= 0; i--) {
+				dom.insertAfter(nl[i], bq);
+			}
+
+			// Empty bq, then remove it
+			if (/^\s*$/.test(bq.innerHTML))
+				dom.remove(bq, 1); // Keep children so boomark restoration works correctly
+
+			// Empty bq, then remote it
+			if (bq2 && /^\s*$/.test(bq2.innerHTML))
+				dom.remove(bq2, 1); // Keep children so boomark restoration works correctly
+
+			if (!bm) {
+				// Move caret inside empty block element
+				if (!tinymce.isIE) {
+					r = ed.getDoc().createRange();
+					r.setStart(sb, 0);
+					r.setEnd(sb, 0);
+					s.setRng(r);
+				} else {
+					s.select(sb);
+					s.collapse(0);
+
+					// IE misses the empty block some times element so we must move back the caret
+					if (dom.getParent(s.getStart(), dom.isBlock) != sb) {
+						r = s.getRng();
+						r.move('character', -1);
+						r.select();
+					}
+				}
+			} else
+				ed.selection.moveToBookmark(bm);
+
+			return;
+		}
+
+		// Since IE can start with a totally empty document we need to add the first bq and paragraph
+		if (tinymce.isIE && !sb && !eb) {
+			ed.getDoc().execCommand('Indent');
+			n = getBQ(s.getNode());
+			n.style.margin = n.dir = ''; // IE adds margin and dir to bq
+			return;
+		}
+
+		if (!sb || !eb)
+			return;
+
+		// If empty paragraph node then do not use bookmark
+		if (sb != eb || sb.childNodes.length > 1 || (sb.childNodes.length == 1 && sb.firstChild.nodeName != 'BR'))
+			bm = s.getBookmark();
+
+		// Move selected block elements into a bq
+		tinymce.each(s.getSelectedBlocks(getBQ(s.getStart()), getBQ(s.getEnd())), function(e) {
+			// Found existing BQ add to this one
+			if (e.nodeName == 'BLOCKQUOTE' && !bq) {
+				bq = e;
+				return;
+			}
+
+			// No BQ found, create one
+			if (!bq) {
+				bq = dom.create('blockquote');
+				e.parentNode.insertBefore(bq, e);
+			}
+
+			// Add children from existing BQ
+			if (e.nodeName == 'BLOCKQUOTE' && bq) {
+				n = e.firstChild;
+
+				while (n) {
+					bq.appendChild(n.cloneNode(true));
+					n = n.nextSibling;
+				}
+
+				dom.remove(e);
+				return;
+			}
+
+			// Add non BQ element to BQ
+			bq.appendChild(dom.remove(e));
+		});
+
+		if (!bm) {
+			// Move caret inside empty block element
+			if (!tinymce.isIE) {
+				r = ed.getDoc().createRange();
+				r.setStart(sb, 0);
+				r.setEnd(sb, 0);
+				s.setRng(r);
+			} else {
+				s.select(sb);
+				s.collapse(1);
+			}
+		} else
+			s.moveToBookmark(bm);
+	});
+})(tinymce);
+(function(tinymce) {
+	tinymce.each(['Cut', 'Copy', 'Paste'], function(cmd) {
+		tinymce.GlobalCommands.add(cmd, function() {
+			var ed = this, doc = ed.getDoc();
+
+			try {
+				doc.execCommand(cmd, false, null);
+
+				// On WebKit the command will just be ignored if it's not enabled
+				if (!doc.queryCommandEnabled(cmd))
+					throw 'Error';
+			} catch (ex) {
+				if (tinymce.isGecko) {
+					ed.windowManager.confirm(ed.getLang('clipboard_msg'), function(s) {
+						if (s)
+							open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
+					});
+				} else
+					ed.windowManager.alert(ed.getLang('clipboard_no_support'));
+			}
+		});
+	});
+})(tinymce);
+(function(tinymce) {
+	tinymce.GlobalCommands.add('InsertHorizontalRule', function() {
+		if (tinymce.isOpera)
+			return this.getDoc().execCommand('InsertHorizontalRule', false, '');
+
+		this.selection.setContent('<hr />');
+	});
+})(tinymce);
+(function() {
+	var cmds = tinymce.GlobalCommands;
+
+	cmds.add(['mceEndUndoLevel', 'mceAddUndoLevel'], function() {
+		this.undoManager.add();
+	});
+
+	cmds.add('Undo', function() {
+		var ed = this;
+
+		if (ed.settings.custom_undo_redo) {
+			ed.undoManager.undo();
+			ed.nodeChanged();
+			return true;
+		}
+
+		return false; // Run browser command
+	});
+
+	cmds.add('Redo', function() {
+		var ed = this;
+
+		if (ed.settings.custom_undo_redo) {
+			ed.undoManager.redo();
+			ed.nodeChanged();
+			return true;
+		}
+
+		return false; // Run browser command
+	});
+})();
diff --git a/media/js/tinymce/utils/editable_selects.js b/media/js/tinymce/utils/editable_selects.js
new file mode 100644
index 0000000..fff4963
--- /dev/null
+++ b/media/js/tinymce/utils/editable_selects.js
@@ -0,0 +1,69 @@
+/**
+ * $Id: editable_selects.js 867 2008-06-09 20:33:40Z spocke $
+ *
+ * Makes select boxes editable.
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+var TinyMCE_EditableSelects = {
+	editSelectElm : null,
+
+	init : function() {
+		var nl = document.getElementsByTagName("select"), i, d = document, o;
+
+		for (i=0; i<nl.length; i++) {
+			if (nl[i].className.indexOf('mceEditableSelect') != -1) {
+				o = new Option('(value)', '__mce_add_custom__');
+
+				o.className = 'mceAddSelectValue';
+
+				nl[i].options[nl[i].options.length] = o;
+				nl[i].onchange = TinyMCE_EditableSelects.onChangeEditableSelect;
+			}
+		}
+	},
+
+	onChangeEditableSelect : function(e) {
+		var d = document, ne, se = window.event ? window.event.srcElement : e.target;
+
+		if (se.options[se.selectedIndex].value == '__mce_add_custom__') {
+			ne = d.createElement("input");
+			ne.id = se.id + "_custom";
+			ne.name = se.name + "_custom";
+			ne.type = "text";
+
+			ne.style.width = se.offsetWidth + 'px';
+			se.parentNode.insertBefore(ne, se);
+			se.style.display = 'none';
+			ne.focus();
+			ne.onblur = TinyMCE_EditableSelects.onBlurEditableSelectInput;
+			ne.onkeydown = TinyMCE_EditableSelects.onKeyDown;
+			TinyMCE_EditableSelects.editSelectElm = se;
+		}
+	},
+
+	onBlurEditableSelectInput : function() {
+		var se = TinyMCE_EditableSelects.editSelectElm;
+
+		if (se) {
+			if (se.previousSibling.value != '') {
+				addSelectValue(document.forms[0], se.id, se.previousSibling.value, se.previousSibling.value);
+				selectByValue(document.forms[0], se.id, se.previousSibling.value);
+			} else
+				selectByValue(document.forms[0], se.id, '');
+
+			se.style.display = 'inline';
+			se.parentNode.removeChild(se.previousSibling);
+			TinyMCE_EditableSelects.editSelectElm = null;
+		}
+	},
+
+	onKeyDown : function(e) {
+		e = e || window.event;
+
+		if (e.keyCode == 13)
+			TinyMCE_EditableSelects.onBlurEditableSelectInput();
+	}
+};
diff --git a/media/js/tinymce/utils/form_utils.js b/media/js/tinymce/utils/form_utils.js
new file mode 100644
index 0000000..9bc2bad
--- /dev/null
+++ b/media/js/tinymce/utils/form_utils.js
@@ -0,0 +1,199 @@
+/**
+ * $Id: form_utils.js 1184 2009-08-11 11:47:27Z spocke $
+ *
+ * Various form utilitiy functions.
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+var themeBaseURL = tinyMCEPopup.editor.baseURI.toAbsolute('themes/' + tinyMCEPopup.getParam("theme"));
+
+function getColorPickerHTML(id, target_form_element) {
+	var h = "";
+
+	h += '<a id="' + id + '_link" href="javascript:;" onclick="tinyMCEPopup.pickColor(event,\'' + target_form_element +'\');" onmousedown="return false;" class="pickcolor">';
+	h += '<span id="' + id + '" title="' + tinyMCEPopup.getLang('browse') + '"> </span></a>';
+
+	return h;
+}
+
+function updateColor(img_id, form_element_id) {
+	document.getElementById(img_id).style.backgroundColor = document.forms[0].elements[form_element_id].value;
+}
+
+function setBrowserDisabled(id, state) {
+	var img = document.getElementById(id);
+	var lnk = document.getElementById(id + "_link");
+
+	if (lnk) {
+		if (state) {
+			lnk.setAttribute("realhref", lnk.getAttribute("href"));
+			lnk.removeAttribute("href");
+			tinyMCEPopup.dom.addClass(img, 'disabled');
+		} else {
+			if (lnk.getAttribute("realhref"))
+				lnk.setAttribute("href", lnk.getAttribute("realhref"));
+
+			tinyMCEPopup.dom.removeClass(img, 'disabled');
+		}
+	}
+}
+
+function getBrowserHTML(id, target_form_element, type, prefix) {
+	var option = prefix + "_" + type + "_browser_callback", cb, html;
+
+	cb = tinyMCEPopup.getParam(option, tinyMCEPopup.getParam("file_browser_callback"));
+
+	if (!cb)
+		return "";
+
+	html = "";
+	html += '<a id="' + id + '_link" href="javascript:openBrowser(\'' + id + '\',\'' + target_form_element + '\', \'' + type + '\',\'' + option + '\');" onmousedown="return false;" class="browse">';
+	html += '<span id="' + id + '" title="' + tinyMCEPopup.getLang('browse') + '"> </span></a>';
+
+	return html;
+}
+
+function openBrowser(img_id, target_form_element, type, option) {
+	var img = document.getElementById(img_id);
+
+	if (img.className != "mceButtonDisabled")
+		tinyMCEPopup.openBrowser(target_form_element, type, option);
+}
+
+function selectByValue(form_obj, field_name, value, add_custom, ignore_case) {
+	if (!form_obj || !form_obj.elements[field_name])
+		return;
+
+	var sel = form_obj.elements[field_name];
+
+	var found = false;
+	for (var i=0; i<sel.options.length; i++) {
+		var option = sel.options[i];
+
+		if (option.value == value || (ignore_case && option.value.toLowerCase() == value.toLowerCase())) {
+			option.selected = true;
+			found = true;
+		} else
+			option.selected = false;
+	}
+
+	if (!found && add_custom && value != '') {
+		var option = new Option(value, value);
+		option.selected = true;
+		sel.options[sel.options.length] = option;
+		sel.selectedIndex = sel.options.length - 1;
+	}
+
+	return found;
+}
+
+function getSelectValue(form_obj, field_name) {
+	var elm = form_obj.elements[field_name];
+
+	if (elm == null || elm.options == null || elm.selectedIndex === -1)
+		return "";
+
+	return elm.options[elm.selectedIndex].value;
+}
+
+function addSelectValue(form_obj, field_name, name, value) {
+	var s = form_obj.elements[field_name];
+	var o = new Option(name, value);
+	s.options[s.options.length] = o;
+}
+
+function addClassesToList(list_id, specific_option) {
+	// Setup class droplist
+	var styleSelectElm = document.getElementById(list_id);
+	var styles = tinyMCEPopup.getParam('theme_advanced_styles', false);
+	styles = tinyMCEPopup.getParam(specific_option, styles);
+
+	if (styles) {
+		var stylesAr = styles.split(';');
+
+		for (var i=0; i<stylesAr.length; i++) {
+			if (stylesAr != "") {
+				var key, value;
+
+				key = stylesAr[i].split('=')[0];
+				value = stylesAr[i].split('=')[1];
+
+				styleSelectElm.options[styleSelectElm.length] = new Option(key, value);
+			}
+		}
+	} else {
+		tinymce.each(tinyMCEPopup.editor.dom.getClasses(), function(o) {
+			styleSelectElm.options[styleSelectElm.length] = new Option(o.title || o['class'], o['class']);
+		});
+	}
+}
+
+function isVisible(element_id) {
+	var elm = document.getElementById(element_id);
+
+	return elm && elm.style.display != "none";
+}
+
+function convertRGBToHex(col) {
+	var re = new RegExp("rgb\\s*\\(\\s*([0-9]+).*,\\s*([0-9]+).*,\\s*([0-9]+).*\\)", "gi");
+
+	var rgb = col.replace(re, "$1,$2,$3").split(',');
+	if (rgb.length == 3) {
+		r = parseInt(rgb[0]).toString(16);
+		g = parseInt(rgb[1]).toString(16);
+		b = parseInt(rgb[2]).toString(16);
+
+		r = r.length == 1 ? '0' + r : r;
+		g = g.length == 1 ? '0' + g : g;
+		b = b.length == 1 ? '0' + b : b;
+
+		return "#" + r + g + b;
+	}
+
+	return col;
+}
+
+function convertHexToRGB(col) {
+	if (col.indexOf('#') != -1) {
+		col = col.replace(new RegExp('[^0-9A-F]', 'gi'), '');
+
+		r = parseInt(col.substring(0, 2), 16);
+		g = parseInt(col.substring(2, 4), 16);
+		b = parseInt(col.substring(4, 6), 16);
+
+		return "rgb(" + r + "," + g + "," + b + ")";
+	}
+
+	return col;
+}
+
+function trimSize(size) {
+	return size.replace(/([0-9\.]+)px|(%|in|cm|mm|em|ex|pt|pc)/, '$1$2');
+}
+
+function getCSSSize(size) {
+	size = trimSize(size);
+
+	if (size == "")
+		return "";
+
+	// Add px
+	if (/^[0-9]+$/.test(size))
+		size += 'px';
+
+	return size;
+}
+
+function getStyle(elm, attrib, style) {
+	var val = tinyMCEPopup.dom.getAttrib(elm, attrib);
+
+	if (val != '')
+		return '' + val;
+
+	if (typeof(style) == 'undefined')
+		style = attrib;
+
+	return tinyMCEPopup.dom.getStyle(elm, style);
+}
diff --git a/media/js/tinymce/utils/mctabs.js b/media/js/tinymce/utils/mctabs.js
new file mode 100644
index 0000000..284501e
--- /dev/null
+++ b/media/js/tinymce/utils/mctabs.js
@@ -0,0 +1,76 @@
+/**
+ * $Id: mctabs.js 758 2008-03-30 13:53:29Z spocke $
+ *
+ * Moxiecode DHTML Tabs script.
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+function MCTabs() {
+	this.settings = [];
+};
+
+MCTabs.prototype.init = function(settings) {
+	this.settings = settings;
+};
+
+MCTabs.prototype.getParam = function(name, default_value) {
+	var value = null;
+
+	value = (typeof(this.settings[name]) == "undefined") ? default_value : this.settings[name];
+
+	// Fix bool values
+	if (value == "true" || value == "false")
+		return (value == "true");
+
+	return value;
+};
+
+MCTabs.prototype.displayTab = function(tab_id, panel_id) {
+	var panelElm, panelContainerElm, tabElm, tabContainerElm, selectionClass, nodes, i;
+
+	panelElm= document.getElementById(panel_id);
+	panelContainerElm = panelElm ? panelElm.parentNode : null;
+	tabElm = document.getElementById(tab_id);
+	tabContainerElm = tabElm ? tabElm.parentNode : null;
+	selectionClass = this.getParam('selection_class', 'current');
+
+	if (tabElm && tabContainerElm) {
+		nodes = tabContainerElm.childNodes;
+
+		// Hide all other tabs
+		for (i = 0; i < nodes.length; i++) {
+			if (nodes[i].nodeName == "LI")
+				nodes[i].className = '';
+		}
+
+		// Show selected tab
+		tabElm.className = 'current';
+	}
+
+	if (panelElm && panelContainerElm) {
+		nodes = panelContainerElm.childNodes;
+
+		// Hide all other panels
+		for (i = 0; i < nodes.length; i++) {
+			if (nodes[i].nodeName == "DIV")
+				nodes[i].className = 'panel';
+		}
+
+		// Show selected panel
+		panelElm.className = 'current';
+	}
+};
+
+MCTabs.prototype.getAnchor = function() {
+	var pos, url = document.location.href;
+
+	if ((pos = url.lastIndexOf('#')) != -1)
+		return url.substring(pos + 1);
+
+	return "";
+};
+
+// Global instance
+var mcTabs = new MCTabs();
diff --git a/media/js/tinymce/utils/validate.js b/media/js/tinymce/utils/validate.js
new file mode 100644
index 0000000..cde4c97
--- /dev/null
+++ b/media/js/tinymce/utils/validate.js
@@ -0,0 +1,219 @@
+/**
+ * $Id: validate.js 758 2008-03-30 13:53:29Z spocke $
+ *
+ * Various form validation methods.
+ *
+ * @author Moxiecode
+ * @copyright Copyright � 2004-2008, Moxiecode Systems AB, All rights reserved.
+ */
+
+/**
+	// String validation:
+
+	if (!Validator.isEmail('myemail'))
+		alert('Invalid email.');
+
+	// Form validation:
+
+	var f = document.forms['myform'];
+
+	if (!Validator.isEmail(f.myemail))
+		alert('Invalid email.');
+*/
+
+var Validator = {
+	isEmail : function(s) {
+		return this.test(s, '^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+@[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$');
+	},
+
+	isAbsUrl : function(s) {
+		return this.test(s, '^(news|telnet|nttp|file|http|ftp|https)://[-A-Za-z0-9\\.]+\\/?.*$');
+	},
+
+	isSize : function(s) {
+		return this.test(s, '^[0-9]+(%|in|cm|mm|em|ex|pt|pc|px)?$');
+	},
+
+	isId : function(s) {
+		return this.test(s, '^[A-Za-z_]([A-Za-z0-9_])*$');
+	},
+
+	isEmpty : function(s) {
+		var nl, i;
+
+		if (s.nodeName == 'SELECT' && s.selectedIndex < 1)
+			return true;
+
+		if (s.type == 'checkbox' && !s.checked)
+			return true;
+
+		if (s.type == 'radio') {
+			for (i=0, nl = s.form.elements; i<nl.length; i++) {
+				if (nl[i].type == "radio" && nl[i].name == s.name && nl[i].checked)
+					return false;
+			}
+
+			return true;
+		}
+
+		return new RegExp('^\\s*$').test(s.nodeType == 1 ? s.value : s);
+	},
+
+	isNumber : function(s, d) {
+		return !isNaN(s.nodeType == 1 ? s.value : s) && (!d || !this.test(s, '^-?[0-9]*\\.[0-9]*$'));
+	},
+
+	test : function(s, p) {
+		s = s.nodeType == 1 ? s.value : s;
+
+		return s == '' || new RegExp(p).test(s);
+	}
+};
+
+var AutoValidator = {
+	settings : {
+		id_cls : 'id',
+		int_cls : 'int',
+		url_cls : 'url',
+		number_cls : 'number',
+		email_cls : 'email',
+		size_cls : 'size',
+		required_cls : 'required',
+		invalid_cls : 'invalid',
+		min_cls : 'min',
+		max_cls : 'max'
+	},
+
+	init : function(s) {
+		var n;
+
+		for (n in s)
+			this.settings[n] = s[n];
+	},
+
+	validate : function(f) {
+		var i, nl, s = this.settings, c = 0;
+
+		nl = this.tags(f, 'label');
+		for (i=0; i<nl.length; i++)
+			this.removeClass(nl[i], s.invalid_cls);
+
+		c += this.validateElms(f, 'input');
+		c += this.validateElms(f, 'select');
+		c += this.validateElms(f, 'textarea');
+
+		return c == 3;
+	},
+
+	invalidate : function(n) {
+		this.mark(n.form, n);
+	},
+
+	reset : function(e) {
+		var t = ['label', 'input', 'select', 'textarea'];
+		var i, j, nl, s = this.settings;
+
+		if (e == null)
+			return;
+
+		for (i=0; i<t.length; i++) {
+			nl = this.tags(e.form ? e.form : e, t[i]);
+			for (j=0; j<nl.length; j++)
+				this.removeClass(nl[j], s.invalid_cls);
+		}
+	},
+
+	validateElms : function(f, e) {
+		var nl, i, n, s = this.settings, st = true, va = Validator, v;
+
+		nl = this.tags(f, e);
+		for (i=0; i<nl.length; i++) {
+			n = nl[i];
+
+			this.removeClass(n, s.invalid_cls);
+
+			if (this.hasClass(n, s.required_cls) && va.isEmpty(n))
+				st = this.mark(f, n);
+
+			if (this.hasClass(n, s.number_cls) && !va.isNumber(n))
+				st = this.mark(f, n);
+
+			if (this.hasClass(n, s.int_cls) && !va.isNumber(n, true))
+				st = this.mark(f, n);
+
+			if (this.hasClass(n, s.url_cls) && !va.isAbsUrl(n))
+				st = this.mark(f, n);
+
+			if (this.hasClass(n, s.email_cls) && !va.isEmail(n))
+				st = this.mark(f, n);
+
+			if (this.hasClass(n, s.size_cls) && !va.isSize(n))
+				st = this.mark(f, n);
+
+			if (this.hasClass(n, s.id_cls) && !va.isId(n))
+				st = this.mark(f, n);
+
+			if (this.hasClass(n, s.min_cls, true)) {
+				v = this.getNum(n, s.min_cls);
+
+				if (isNaN(v) || parseInt(n.value) < parseInt(v))
+					st = this.mark(f, n);
+			}
+
+			if (this.hasClass(n, s.max_cls, true)) {
+				v = this.getNum(n, s.max_cls);
+
+				if (isNaN(v) || parseInt(n.value) > parseInt(v))
+					st = this.mark(f, n);
+			}
+		}
+
+		return st;
+	},
+
+	hasClass : function(n, c, d) {
+		return new RegExp('\\b' + c + (d ? '[0-9]+' : '') + '\\b', 'g').test(n.className);
+	},
+
+	getNum : function(n, c) {
+		c = n.className.match(new RegExp('\\b' + c + '([0-9]+)\\b', 'g'))[0];
+		c = c.replace(/[^0-9]/g, '');
+
+		return c;
+	},
+
+	addClass : function(n, c, b) {
+		var o = this.removeClass(n, c);
+		n.className = b ? c + (o != '' ? (' ' + o) : '') : (o != '' ? (o + ' ') : '') + c;
+	},
+
+	removeClass : function(n, c) {
+		c = n.className.replace(new RegExp("(^|\\s+)" + c + "(\\s+|$)"), ' ');
+		return n.className = c != ' ' ? c : '';
+	},
+
+	tags : function(f, s) {
+		return f.getElementsByTagName(s);
+	},
+
+	mark : function(f, n) {
+		var s = this.settings;
+
+		this.addClass(n, s.invalid_cls);
+		this.markLabels(f, n, s.invalid_cls);
+
+		return false;
+	},
+
+	markLabels : function(f, n, ic) {
+		var nl, i;
+
+		nl = this.tags(f, "label");
+		for (i=0; i<nl.length; i++) {
+			if (nl[i].getAttribute("for") == n.id || nl[i].htmlFor == n.id)
+				this.addClass(nl[i], ic);
+		}
+
+		return null;
+	}
+};
diff --git a/media/js/ushahidi.js b/media/js/ushahidi.js
new file mode 100644
index 0000000..2be969f
--- /dev/null
+++ b/media/js/ushahidi.js
@@ -0,0 +1,1193 @@
+/**
+ * Copyright (c) 2008-2012 by Ushahidi Dev Team
+ * Published under the LGPL license. See License.txt for the
+ * full text of the license
+ *
+ * @requires media/js/OpenLayers.js
+ */
+(function(){
+	
+	/**
+	 * Namespace: Ushahidi
+	 * The Ushahidi object provides a namespace for all things Ushahidi
+	 */
+	 window.Ushahidi = {
+
+	 	/**
+	 	 * APIProperty: proj_4326
+	 	 * Projection for representing latitude and longitude coordinates
+	 	 */
+	 	proj_4326: new OpenLayers.Projection('EPSG:4326'),
+
+	 	/**
+	 	 * APIProperty: proj_900913
+	 	 * An unofficial code but maintained nonetheless - for describing
+	 	 * coordinates in meters in x/y
+	 	 */
+	 	proj_900913: new OpenLayers.Projection('EPSG:900913'),
+
+	 	/**
+	 	 * APIProperty: markerOpacity
+	 	 * Default opacity for the markers
+	 	 */
+	 	markerOpacity: 0.9,
+
+	 	/**
+	 	 * APIProperty: markerRadius
+	 	 * Default radius for the markers
+	 	 */
+	 	markerRadius: 5,
+
+	 	/**
+	 	 * APIProperty: markerStrokeWidth
+	 	 * Stroke width for the markers
+	 	 */
+	 	markerStrokeWidth: 2,
+
+	 	/**
+	 	 * APIProperty: markerStrokeOpacity
+	 	 */
+	 	markerStrokeOpacity: 0.9,
+
+		// Layer types
+		GEOJSON: "GeoJSON",
+
+		KML: "KML",
+
+		DEFAULT: "default",
+
+		/**
+		 * APIProperty: baseURL
+		 * Base URL for the application
+		 */
+		baseURL: '',
+
+	 	/**
+	 	 * APIProperty: geoJSONStyle
+	 	 * Default styling for GeoJSON data
+	 	 * the map
+	 	 */
+	 	 GeoJSONStyle: function() {
+	 	 	var style = new OpenLayers.Style({
+	 	 		'externalGraphic': "${icon}",
+	 	 		'graphicTitle': "${cluster_count}",
+				pointRadius: "${radius}",
+				fillColor: "${color}",
+				fillOpacity: "${opacity}",
+				strokeColor: "${strokeColor}",
+				strokeWidth: "${strokeWidth}",
+				strokeOpacity: "${strokeOpacity}",
+				label:"${clusterCount}",
+				fontWeight: "${fontweight}",
+				fontColor: "#ffffff",
+				fontSize: "${fontsize}",
+				title: "${title}"    
+			},
+			{
+				context: {
+					title: function(feature) {
+            			if (feature.attributes.count >= 2) {
+              				return feature.attributes.count + " reports";
+            			} else {
+              				return feature.attributes.title;
+            			}
+          			},
+					count: function(feature) {
+						if (feature.attributes.count < 2) {
+							return 2 * Ushahidi.markerRadius;
+						}  else if (feature.attributes.count == 2) {
+							return (Math.min(feature.attributes.count, 7) + 1) *
+							(Ushahidi.markerRadius * 0.8);
+						} else {
+							return (Math.min(feature.attributes.count, 7) + 1) *
+							(Ushahidi.markerRadius * 0.6);
+						}
+					},
+					fontsize: function(feature) {
+						feature_icon = feature.attributes.icon;
+						if (feature_icon !== "") {
+							return "9px";
+						}
+						else {
+							feature_count = feature.attributes.count;
+							if (feature_count > 1000) {
+								return "20px";
+							} else if (feature_count > 500) {
+								return "18px";
+							} else if (feature_count > 100) {
+								return "14px";
+							} else if (feature_count > 10) {
+								return "12px";
+							} else if (feature_count >= 2) {
+								return "10px";
+							} else {
+								return "";
+							}
+						}
+					},
+					fontweight: function(feature) {
+						feature_icon = feature.attributes.icon;
+						if (feature_icon!=="") {
+							return "normal";
+						} else {
+							return "bold";
+						}
+					},
+					radius: function(feature) {
+						if (typeof(feature.attributes.radius) != 'undefined' && 
+							feature.attributes.radius != '') {
+							return feature.attributes.radius;
+						} else {
+							feature_count = feature.attributes.count;
+							if (feature_count > 10000)
+							{
+								return Ushahidi.markerRadius * 17;
+							}
+							else if (feature_count > 5000)
+							{
+								return Ushahidi.markerRadius * 10;
+							}
+							else if (feature_count > 1000)
+							{
+								return Ushahidi.markerRadius * 8;
+							}
+							else if (feature_count > 500)
+							{
+								return Ushahidi.markerRadius * 7;
+							}
+							else if (feature_count > 100)
+							{
+								return Ushahidi.markerRadius * 6;
+							}
+							else if (feature_count > 10)
+							{
+								return Ushahidi.markerRadius * 5;
+							}
+							else if (feature_count >= 2)
+							{
+								return Ushahidi.markerRadius * 3;
+							}
+							else
+							{
+								return Ushahidi.markerRadius * 2;
+							}
+						}
+					},
+					strokeWidth: function(feature) {
+						if ( typeof(feature.attributes.strokewidth) != 'undefined' && 
+							feature.attributes.strokewidth != '')
+						{
+							return feature.attributes.strokewidth;
+						}
+						else
+						{
+							feature_count = feature.attributes.count;
+							if (feature_count > 10000)
+							{
+								return 45;
+							}
+							else if (feature_count > 5000)
+							{
+								return 30;
+							}
+							else if (feature_count > 1000)
+							{
+								return 22;
+							}
+							else if (feature_count > 100)
+							{
+								return 15;
+							}
+							else if (feature_count > 10)
+							{
+								return 10;
+							}
+							else if (feature_count >= 2)
+							{
+								return 5;
+							}
+							else
+							{
+								return 1;
+							}
+						}
+					},
+					color: function(feature) {
+						return "#" + feature.attributes.color;
+					},
+					strokeColor: function(feature) {
+						if ( typeof(feature.attributes.strokecolor) != 'undefined' && 
+							feature.attributes.strokecolor != '')
+						{
+							return "#"+feature.attributes.strokecolor;
+						}
+						else
+						{
+							return "#"+feature.attributes.color;
+						}
+					},
+					icon: function(feature) {
+						feature_icon = feature.attributes.icon;
+						
+						return (feature_icon !== "") ? feature_icon : "";
+					},
+					clusterCount: function(feature) {
+						if (feature.attributes.count > 1)
+						{
+							if ($.browser.msie && $.browser.version=="6.0")
+							{ // IE6 Bug with Labels
+								return "";
+							}
+
+							return feature.attributes.count;
+						}
+						else
+						{
+							return "";
+						}
+					},
+					opacity: function(feature) {
+						feature_icon = feature.attributes.icon;
+						if (typeof(feature.attributes.opacity) != 'undefined' && 
+							feature.attributes.opacity != '')
+						{
+							return feature.attributes.opacity
+						}
+						else if (feature_icon!=="")
+						{
+							return "1";
+						}
+						else
+						{
+							return Ushahidi.markerOpacity;
+						}
+					},
+					strokeOpacity: function(feature) {
+						if(typeof(feature.attributes.strokeopacity) != 'undefined' && 
+							feature.attributes.strokeopacity != '')
+						{
+							return feature.attributes.strokeopacity;
+						}
+						else
+						{
+							return Ushahidi.markerStrokeOpacity;
+						}
+					},
+					labelalign: function(feature) {
+						return "c";
+					}
+				}
+			});
+
+			return style;
+		}
+
+	 };
+
+	 /**
+	  * Constructor: Ushahidi.Map
+	  * Base class used to construct all OpenLayers.Map objects
+	  *
+	  * Parameters:
+	  * div - {DOMElement|String} The element or id of an element in the page
+	  *       that will contain the map
+	  * config = {Object} Optional object with the configuration to be used for
+	  *          generating the map
+	  *
+	  * Valid config options are:
+	  * zoom - Initial zoom level of the map
+	  * center - {Object} The default initial center of the map
+	  * redrawOnZoom - {Boolean} Whether to redraw the layers when the map zoom changes.
+	  *                Default is false
+	  * detectMapZoom - {Boolean} Whether to detect change in the zoom level. If the
+	  *                 redrawOnZoom property is true, this option is ignored
+	  * mapControls - {Array(OpenLayers.Control)} The list of controls to add to the map
+	  * reportFilters - Initial report filters to be passed in URL
+	  */
+	 Ushahidi.Map = function(div, config) {
+	 	// Internal registry for the marker layers
+	 	this._registry = [];
+	 	
+	 	// Internal list of layers to keep at the top
+	 	this._onTop = [];
+
+	 	// Markers are not yet loaded on the map
+	 	this._isLoaded = 0;
+
+	 	// Timeouts for layers about to be loaded, keyed by layer name
+	 	this._addLayerTimeouts = {};
+
+	 	// List of supported events
+	 	this._EVENTS = [
+	 		// When the report filters change
+	 	    "filterschanged",
+
+	 	    // Map's zoom level changes
+	 	    "zoomchanged",
+
+	 	    // When the size of the map's viewport changes
+	 	    "resize",
+
+	 	    // When a layer is deleted
+	 	    "deletelayer",
+
+	 	    // Maker position changed
+	 	    "markerpositionchanged",
+
+	 	    // Base layer is changed
+	 	    "baselayerchanged",
+
+	 	    // Map center changed
+	 	    "mapcenterchanged"
+	 	];
+
+	 	// Register for the callbacks to be invoked when the report filters
+	 	// are updated. The updated paramters will passed to the callback
+	 	// as a parameter
+	 	this._callbacks = {};
+
+	 	// Tracks the current marker position
+	 	this._currentMarkerPosition = {};
+
+	 	// Check for the mapDiv
+	 	if (div == undefined) {
+	 		throw "The element or id of an element that will contain the map must be specified";
+	 	}
+
+	 	// Sanitize input parameters
+	 	var config = config || {};
+	 	if (config.zoom == undefined) config.zoom = 8;
+
+	 	// Internally track the current zoom
+	 	this.currentZoom = config.zoom;
+
+	 	// The set of filters/parameters to pass to the URL that fetches
+	 	// overlay data - not applicable to KMLs
+	 	this._reportFilters = config.reportFilters || {};
+
+	 	// Update the report filters with the zoom level
+	 	this._reportFilters.z = config.zoom;
+
+	 	// Latitude and longitude
+	 	if (config.center == undefined) {
+	 		// Set the default latitude and longitude to Nairobi - no place like ground Ø
+	 		config.center = {latitude: -1.286449478924, longitude: 36.822838050049};
+		}
+
+		// Map options
+		var mapOptions = {
+			units: "dd",
+			numZoomLevels: 18,
+			theme: false,
+			controls: [],
+			projection: Ushahidi.proj_900913,
+			'displayProjection': Ushahidi.proj_4326,
+			maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 
+			                                 20037508.34, 20037508.34),
+			maxResolution: 156543.0339,
+			// Shrink the popup padding so popups don't land under zoom control
+			paddingForPopups: new OpenLayers.Bounds(40,15,15,15),
+			eventListeners: { 
+				// Trigger keepOnTop fn whenever new layers are added
+				addlayer: this.keepOnTop,
+				scope: this
+			}
+		};
+
+		// Are the layers to be redrawn on zoom change
+		if (config.redrawOnZoom !== undefined && config.redrawOnZoom) {
+			mapOptions.eventListeners.zoomend = this.refresh;
+		}
+
+		// Zoom detection is enabled and redrawOnZoom has not been specified or is false
+		if ((config.redrawOnZoom == undefined || !config.redrawOnZoom) && config.detectMapZoom) {
+			this.register("zoomchanged", this.updateZoom, this);
+			mapOptions.eventListeners.zoomend = this.triggerZoomChanged;
+		}
+
+		// Check for the controls to add to the map
+		if (config.mapControls == undefined) {
+			// Default map controls
+			mapOptions.controls = [
+				new OpenLayers.Control.Navigation({ dragPanOptions: { enableKinetic: true } }),
+				new OpenLayers.Control.Zoom(),
+				new OpenLayers.Control.Attribution(),
+				new OpenLayers.Control.MousePosition({
+					formatOutput: Ushahidi.convertLongLat
+				}),
+				new OpenLayers.Control.LayerSwitcher()
+			];
+		} else {
+			mapOptions.controls = config.mapControls;
+		}
+
+		// Create the map
+		this._olMap = new OpenLayers.Map(div, mapOptions);
+		if (config.baseLayers != undefined && config.baseLayers.length > 0) {
+			this._olMap.addLayers(config.baseLayers);
+		}
+
+		var point = new OpenLayers.LonLat(config.center.longitude, config.center.latitude);
+		point.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
+
+		this._currentCenter = point;
+
+		// Set the map center
+		this._olMap.setCenter(point, config.zoom);
+
+		// Display the map projection
+		if (config.displayProjection != undefined && config.displayProjection) {
+			document.getElementById('mapProjection').innerHTML = this._olMap.projection;
+		}
+
+		// Register this instance in the namespace
+		// NOTE: Only one map is allowed in the namespace
+		Ushahidi._currentMap = this;
+
+		// Register events
+		this.register("resize", this.resize, this);
+		this.register("deletelayer", this.deleteLayer, this);
+		this.register("baselayerchanged", this.updateBaseLayer, this);
+		this.register("mapcenterchanged", this.updateMapCenter, this);
+		
+		// Pre-load the background image for the popup so that it is 
+		// fetched from the cache when when popup is displayed
+		var popupBgImage = new Image();
+		popupBgImage.src = OpenLayers.Util.getImagesLocation() + 'cloud-popup-relative.png';
+
+		return this;
+	};
+
+	/**
+	 * APIMethod addLayers
+	 *
+	 * Parameters:
+	 * layerType - {String} Type of marker to be added and could be one of the following 
+	 *             (Ushahidi.REPORTS, Ushahidi.KML, Ushahidi.SHARES. Ushahidi.DEFAULT)
+	 * options   - {Object} Optional object key/value pairs of the markers to be added to the map
+	 *
+	 * Valid options are:
+	 * name - {String} Name of the Layer being added
+	 * url  - {String} Fetch URL for the layer data
+	 * styleMap - {OpenLayers.StyleMap} Styling for the layer
+	 * detectMapClicks - {Boolean} - When true, registers a callback function to detect
+	 *               click events on the map. This option is only used with the default
+	 *               layer (Ushahidi.DEFAULT). The default value is true
+	 * transform - {Boolean} When true, transforms the featur geometry to spherical mercator
+	 *             The default value is false
+	 * features - {Array(OpenLayers.Feature.Vector)} Features to add to the layer 
+	 *            When the features ar specified, the protocol property is omitted from the
+	 *            options passed to the layer constructor. The features property is used instead
+	 *
+	 * save -      {bool} Whether to save the layer in the internal registry of Ushahidi.Map This
+	 *             parameter should be set to true, if the layer being added is new so as to ensure
+	 *             that it is redrawn when the map is zoomed in/out or the report filters are updated   
+	 * keepOnTop - {bool} Whether to keep this layer above others.
+	 */
+	Ushahidi.Map.prototype.addLayer = function(layerType, options, save, keepOnTop) {
+		// Default markers layer
+		if (layerType == Ushahidi.DEFAULT) {
+			this.deleteLayer("default");
+			
+			var markers = null;
+			
+			if (options == undefined) {
+				options = {};
+			}
+			
+			// Check for the style map
+			if (options.styleMap != undefined) {
+
+				// Create vector layer
+				markers = new OpenLayers.Layer.Vector("default", {
+					styleMap: styleMap
+				});
+
+				// Add features to the vector layer
+				makers.addFeatures(new OpenLayers.Feature.Vector(this._olMap.getCenter()));
+
+			} else {
+				markers = new OpenLayers.Layer.Markers("default");
+				markers.addMarker(new OpenLayers.Marker(this._olMap.getCenter()));
+			}
+
+			// Add the layer to the map
+			this._olMap.addLayer(markers);
+
+			// Is map-click detection enabled?
+			if (options.detectMapClicks == undefined || options.detectMapClicks) {
+				var context = this;
+				context._olMap.events.register("click", context._olMap, function(e){
+					var point = context._olMap.getLonLatFromViewPortPx(e.xy);
+					markers.clearMarkers();
+					markers.addMarker(new OpenLayers.Marker(point));
+
+					point.transform(Ushahidi.proj_900913, Ushahidi.proj_4326);
+					
+					var coords = {latitude: point.lat, longitude: point.lon};
+					context.trigger("markerpositionchanged", coords);
+
+					// Update the current map center
+					var newCenter = new OpenLayers.LonLat(coords.longitude, coords.latitude);
+					newCenter.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
+					context._currentCenter = newCenter;
+				});
+			}
+
+			this._isLoaded = 1;
+
+			return this;
+		}
+		
+		// Setup default protocol format
+		var protocolFormat = new OpenLayers.Format.GeoJSON();
+		// Switch protocol format if layer is KML
+		if (layerType == Ushahidi.KML) {
+			protocolFormat = new OpenLayers.Format.KML({
+				extractStyles: true,
+				extractAttributes: true,
+				maxDepth: 5
+			});
+		}
+
+		// No options defined - where layerType !== Ushahidi.DEFAULT
+		if (options == undefined) {
+			throw "Invalid layer options";
+		}
+
+		// Save the layer data in the internal registry
+		if (save !== undefined && save) {
+			this._registry.push({layerType: layerType, options: options});
+		}
+
+		// Save the layer name to keep on top
+		if (keepOnTop !== undefined && keepOnTop) {
+			this._onTop.push(options.name);
+		}
+
+		// Transform feature geometry to Spherical Mercator
+		preFeatureInsert = function(feature) {
+			if (feature.geometry) {
+				var point = new OpenLayers.Geometry.Point(feature.geometry.x, feature.geometry.y);
+				OpenLayers.Projection.transform(point, Ushahidi.proj_4326, Ushahidi.proj_900913);
+				feature.geometry.x = point.x;
+				feature.geometry.y = point.y;
+			}
+		}
+
+		// Layer options
+		var layerOptions = {
+			projection: Ushahidi.proj_4326,
+			formatOptions: {
+				extractStyles: true,
+				extractAttributes: true
+			}
+		};
+
+		if (options.transform) {
+			// Transform the feature geometry to spherical mercator
+			layerOptions.preFeatureInsert = preFeatureInsert;
+		}
+
+		// Build out the fetch url
+		var fetchURL = Ushahidi.baseURL + options.url;
+
+		// Apply the report filters to the layer fetch URL except where
+		// the layer type is KML
+		if (layerType !== Ushahidi.KML) {
+			var params = [];
+			for (var _key in this._reportFilters) {
+				params.push(_key + '=' + this._reportFilters[_key]);
+			}
+
+			if (fetchURL.indexOf("?") !== -1) {
+				var index = fetchURL.indexOf("?");
+				var args = fetchURL.substr(index+1, fetchURL.length).split("&");
+				fetchURL = fetchURL.substring(0, index);
+
+				for (var i=0; i<args.length; i++) {
+					params.push(args[i]);
+				}
+			}
+
+			// Update the fetch URL with parameters
+			fetchURL += (params.length > 0) ? '?' + params.join('&') : '';
+
+			// Get the styling to use
+			var styleMap = null;
+			if (options.styleMap !== undefined) {
+				styleMap = options.styleMap;
+			} else {
+				var style = Ushahidi.GeoJSONStyle();
+				styleMap = new OpenLayers.StyleMap({
+					"default": style,
+					"select": style
+				});
+			}
+
+			// Update the layer options with the style map
+			layerOptions.styleMap = styleMap;
+		}
+
+		// If no features were passed in layer options,
+		// set up protocal and strategy to grab GeoJSON
+		if (options.features === undefined) {
+			layerOptions.strategies = [new OpenLayers.Strategy.Fixed({preload: true})];
+
+			// Set the protocol
+			layerOptions.protocol = new OpenLayers.Protocol.HTTP({
+				url: fetchURL,
+				format: protocolFormat
+			});
+		}
+
+		// Create the layer
+		var layer = new OpenLayers.Layer.Vector(options.name, layerOptions);
+		
+		// Store context for callbacks
+		var context = this;
+		
+		// Hide the layer until its loaded
+		// only delete the old layer on loadend
+		layer.display(false);
+		// Hack to start with opacity 0 then fade in
+		layer.div.style['opacity'] = 0;
+		var oldLayer = this._olMap.getLayersByName(options.name);
+		var displayLayer = function () {
+			// Delete the old layers
+			this.deleteLayer(oldLayer);
+			// Display the new layer, fading in if we've got CSS3
+			layer.display(true);
+			layer.div.className += " olVectorLayerDiv";
+			layer.div.style['opacity'] = 1;
+
+			// Update / Create SelectFeature Control
+			if (typeof this._selectControl == "object")
+			{
+				// Update SelectFeature control with all vector layers
+				this._selectControl.setLayer(this._olMap.getLayersByClass("OpenLayers.Layer.Vector"));
+			}
+			else
+			{
+				// Select Feature control
+				this._selectControl = new OpenLayers.Control.SelectFeature(
+					this._olMap.getLayersByClass("OpenLayers.Layer.Vector"),
+					{ clickout: true, toggle: false, multiple: false, hover: false }
+				);
+				this._olMap.addControl(this._selectControl);
+				this._selectControl.activate();
+			}
+			
+			// Bind popup events for select/unselect
+			layer.events.on({
+				"featureselected": this.onFeatureSelect,
+				"featureunselected": this.onFeatureUnselect,
+				scope: this
+			});
+		}
+		// Register display layer fn to run on load end
+		layer.events.register('loadend', this, displayLayer);
+		
+		// If features were passed in layer options
+		// Add features to layer and register display layer on layer added
+		if (options.features !== undefined && options.features.length > 0) {
+			layer.addFeatures(options.features);
+			layer.events.register('added', this, displayLayer);
+		}
+
+		// Add the layer to the map
+		// We do this after a delay in case someone zooms multiple times
+		clearTimeout(this._addLayerTimeouts[options.name]);
+		this._addLayerTimeouts[options.name] = setTimeout(function(){ context._olMap.addLayer(layer); }, 100);
+
+		this._isLoaded = 1;
+
+		return this;
+	}
+
+	/**
+	 * APIMethod: updateReportFilters
+	 * This method updates the set of parameters used to filter out
+	 * the map content and then redraws the map
+	 *
+	 * Parameters:
+	 * filters - {Object} Set of filter to apply to the map.
+	 * Allowable filters are:
+	 *     z - {Number} The zoom level
+	 *     c - {Number} The category id
+	 *     m - {Number} The media type (0 - All, 1 - Pictures, 2 - Video 4 - News)
+	 *     s - {Number} Start date - Earliest date by which to filter the reports
+	 *     e - {Number} End date - Latest date by which to filter the reports
+	 */
+	Ushahidi.Map.prototype.updateReportFilters = function(filters) {
+		if (filters == undefined) {
+			throw "Missing filters";
+		}
+
+		var hasChanged = false;
+		var context = this;
+
+		// Overwrite the current set of filters with the new values
+		$.each(filters, function(filter, value) {
+			var currentValue = context._reportFilters[filter];
+			if ((currentValue == undefined && currentValue == null) ||
+				currentValue !== value) {
+				hasChanged = true;
+				context._reportFilters[filter] = value;
+			}
+		});
+
+		// Have the filters changed
+		if (hasChanged) {
+			context.redraw();
+
+			setTimeout(function() {
+				context.trigger("filterschanged", context._reportFilters);
+			}, 800);
+		}
+	}
+
+	/**
+	 * APIMethod: getReportFilters
+	 * Gets the set of filters used to filter the reports on the map
+	 */
+	Ushahidi.Map.prototype.getReportFilters = function() {
+		return this._reportFilters;
+	}
+
+	/**
+	 * APIMethod: refresh
+	 *
+	 * Deletes the markers and layers from the map and adds them afresh. This
+	 * method should be called when zoom level of the map changes
+	 */
+	Ushahidi.Map.prototype.refresh = function(e) {
+		if (this._isLoaded) {
+			// Update the zoom and redraw
+			this._reportFilters.z = this._olMap.getZoom();
+			this.redraw();
+		}
+		return this;
+	}
+
+	/**
+	 * APIMethod: redraw
+	 *
+	 * Redraws the layers on the map using the updated report filters
+	 * This method is automatically invoked each time the map is zoomed
+	 * or when the report filters are updated
+	 */
+	Ushahidi.Map.prototype.redraw = function() {
+		// Close any open popups
+		this.closePopups();
+
+		for (var i=0; i<this._registry.length; i++) {
+			var layer = this._registry[i];
+			this.addLayer(layer.layerType, layer.options);
+		}
+		return this;
+	}
+
+	/**
+	 * APIMethod: onFeatureSelect
+	 *
+	 * Callback that is executed when a feature that is on the map is
+	 * selected. When executed, it displays a popup with the content/information
+	 * about the selected feature
+	 */
+	Ushahidi.Map.prototype.onFeatureSelect = function(event) {
+		this._selectedFeature = event.feature;
+
+		zoom_point = event.feature.geometry.getBounds().getCenterLonLat();
+		lon = zoom_point.lon;
+		lat = zoom_point.lat;
+
+		// Image to display within the popup
+		var image = "";
+		if (event.feature.attributes.thumb !== undefined && event.feature.attributes.thumb != '') {
+			image = "<div class=\"infowindow_image\"><a href='"+event.feature.attributes.link+"'>";
+			image += "<img src=\""+event.feature.attributes.thumb+"\" height=\"59\" width=\"89\" /></a></div>";
+		} else if (event.feature.attributes.image !== undefined && event.feature.attributes.image != '') {
+			image = "<div class=\"infowindow_image\">";
+			image += "<a href=\""+event.feature.attributes.link+"\" title=\""+event.feature.attributes.name+"\">";
+			image += "<img src=\""+event.feature.attributes.image+"\" />";
+			image += "</a>";
+			image += "</div>";
+		}
+
+		var content = "<div class=\"infowindow\">" + image +
+		    "<div class=\"infowindow_content\">"+
+		    "<div class=\"infowindow_list\">"+event.feature.attributes.name+"</div>\n" +
+		    "<div class=\"infowindow_meta\">";
+
+		if (typeof(event.feature.attributes.link) != 'undefined' &&
+		    event.feature.attributes.link != '') {
+
+		    content += "<a href='"+event.feature.attributes.link+"'>" +
+			    "More Information</a><br/>";
+		}
+
+		content += "<a id=\"zoomIn\">";
+		content += "Zoom In</a>";
+		content += "  |  ";
+		content += "<a id=\"zoomOut\">";
+		content += "Zoom Out</a></div>";
+		content += "</div><div style=\"clear:both;\"></div></div>";		
+
+		if (content.search("<script") != -1) {
+			content = "Content contained Javascript! Escaped content " +
+			    "below.<br />" + content.replace(/</g, "<");
+		}
+		  
+		// Destroy existing popups before opening a new one
+		if (event.feature.popup != null) {
+			map.removePopup(event.feature.popup);
+		}
+
+		// Create the popup
+		var popup = new OpenLayers.Popup.FramedCloud("chicken", 
+			event.feature.geometry.getBounds().getCenterLonLat(),
+			new OpenLayers.Size(100,100),
+			content,
+			null, true, this.onPopupClose);
+
+		event.feature.popup = popup;
+		this._olMap.addPopup(popup);
+		popup.show();
+
+		// Register zoom in/out events
+		$("#zoomIn", popup.contentDiv).click(
+			{context: this, latitude: lat, longitude: lon, zoomFactor: 1}, 
+			this.zoomToSelectedFeature);
+
+		$("#zoomOut", popup.contentDiv).click(
+			{context: this, latitude: lat, longitude: lon, zoomFactor: -1}, 
+			this.zoomToSelectedFeature);
+	}
+
+	/**
+	 * APIMethod: onFeatureUnselect
+	 * Callback to be executed when a feature is unselected - when a click
+	 * is registered outside the feature's viewport
+	 */
+	Ushahidi.Map.prototype.onFeatureUnselect = function(e) {
+		if (e.feature.popup != null) {
+			this._olMap.removePopup(e.feature.popup);
+			e.feature.popup.destroy();
+			e.feature.popup = null;
+		}
+	}
+
+	/**
+	 * APIMethod: onPopupClose
+	 * Callback to be executed when the "close" button in the 
+	 * popup is clicked
+	 */
+	Ushahidi.Map.prototype.onPopupClose = function(e) {
+		Ushahidi._currentMap.closePopups();
+	}
+
+	/**
+	 * APIMethod: closePopups
+	 *
+	 * Closes all all open popups
+	 */
+	Ushahidi.Map.prototype.closePopups = function() {
+		if (this._selectedFeature !== undefined && this._selectedFeature != null) {
+			this._selectControl.unselect(this._selectedFeature);
+			this._selectedFeature = null;
+		}
+	}
+
+	/**
+	 * APIMethod: zoomToSelectedFeature
+	 * Callback to be executed when zooming in/out of a report
+	 */
+	Ushahidi.Map.prototype.zoomToSelectedFeature = function(e) {
+		// Get the event data
+		var data = e.data;
+		
+		var point = new OpenLayers.LonLat(data.longitude, data.latitude);
+		var zoomLevel = data.zoomFactor + data.context._olMap.getZoom();
+
+		// Center and zoom
+		data.context._olMap.zoomTo(zoomLevel);
+		data.context._olMap.panTo(point);
+
+		// Close any open popups
+		data.context.closePopups();
+
+		// Halt further event processing
+		return false;
+	}
+
+	/**
+	 * APIMethod: register
+	 * Registers a callback to be invoked when a specified event is triggered - the
+	 * report filters are passed to each of the registered callbacks as a parameter.
+	 *
+	 * Parameters:
+	 * eventName - {String} Name of the event for which to register the callback
+	 * callback  - {Function} Function to be executed when the event in 'eventName' is triggered
+	 * context   - {Object} Execution context of the callback function. This is necessary where
+	 *             the callback function is an object method
+	 */
+	Ushahidi.Map.prototype.register = function(eventName, callback, context) {
+
+		// Is the event known? i.e. in the internal event registry
+		if ($.inArray(eventName, this._EVENTS) === -1)
+			return;
+
+		// Has the event already been registered
+		if (this._callbacks[eventName] == undefined) {
+			this._callbacks[eventName] = [];
+		}
+
+		// Subscribe to the event
+		this._callbacks[eventName].push({
+			callback: callback,
+			context: context
+		});
+	}
+
+	/**
+	 * APIMethod: trigger
+	 * Triggers the event specified specified in the eventName parameter
+	 *
+	 * Parameters:
+	 * eventName - {String} Name of the event to be triggered. No error will be returned
+	 *              if the event is not found
+	 * data - {Object} Data to be passed to the subscribers of the event specified in eventName
+	 */
+	Ushahidi.Map.prototype.trigger = function(eventName, data) {
+		// If the event is unknown, exit without throwing an error
+		if (this._callbacks.length == 0 || this._callbacks[eventName] == undefined) {
+			return;
+		}
+
+		// Get the subscribers for the event
+		var subscribers = this._callbacks[eventName];
+
+		for (var i=0; i < subscribers.length; i++) {
+			var subscriber = subscribers[i];
+			if (subscriber.context == undefined || subscriber.context == null) {
+				subscriber.callback(data);
+			} else {
+				subscriber.callback.call(subscriber.context, data);
+			}
+		}
+	}
+
+	/**
+	 * APIMethod: resize
+	 * Resizes the map when the size of its container changes
+	 */
+	Ushahidi.Map.prototype.resize = function() {
+		this._olMap.updateSize();
+		this._olMap.pan(0, 1);
+	}
+
+	/**
+	 * APIMethod: deleteLayer
+	 * Deletes a layer from the map
+	 *
+	 * Parameters:
+	 * name - {String} Name of the layer to be deleted
+	 */
+	Ushahidi.Map.prototype.deleteLayer = function(name) {
+		var layers = name;
+		if (typeof name === 'string')
+			layers = this._olMap.getLayersByName(name);
+		
+		for (var i=0; i < layers.length; i++) {
+			// Set opacity to 0 then hide, using CSS3 transitions to fade the layer out
+			layers[i].div.style['opacity'] = 0.2;
+			layers[i].display(false);
+			// Skip layer if its not on the map
+			if (layers[i].map == null || this._olMap.getLayerIndex(layers[i]) == -1) continue;
+			
+			this._olMap.removeLayer(layers[i]);
+			if (layers[i].destroyFeatures !== undefined)
+				layers[i].destroyFeatures();
+		}
+	}
+
+	/**
+	 * APIMethod: createRadiusLayer
+	 * Creates a radius layer
+	 * Parameters:
+	 * config - {Object} The properties to be used to create the radius layer
+	 */
+	Ushahidi.Map.prototype.addRadiusLayer = function(config) {
+
+		// Check if the Layer Switcher control has been added to the map
+		// and delete all instances of such
+		var layerSwitchers = this._olMap.getControlsByClass("OpenLayers.Control.LayerSwitcher");
+		for (var i = 0; i < layerSwitchers.length; i++) {
+			this._olMap.removeControl(layerSwitchers[i]);
+		}
+
+		this.updateRadius(config);
+		var context = this;
+
+		// Detect click events on the map
+		this._olMap.events.register("click", context._olMap, function(e) {
+			var lonlat = context._olMap.getLonLatFromViewPortPx(e.xy);
+			lonlat.transform(Ushahidi.proj_900913, Ushahidi.proj_4326);
+
+			var coords = {latitude: lonlat.lat, longitude: lonlat.lon};
+			context.updateRadius(coords);
+			context.trigger("markerpositionchanged", coords);
+		});
+		
+	}
+
+	/**
+	 * APIMethod: updateRadius
+	 * Updates the location and size of the radius layer
+	 *
+	 * Parameters:
+	 * options - {Object} Properties for updating the radius layer
+	 *
+	 * Valid options are:
+	 * latitude - Latitude for the alert center
+	 * longitude - Longitude for the alert center
+	 * radius - {Number} Alert radius
+	 */
+	Ushahidi.Map.prototype.updateRadius = function(options) {
+		// Get the radius
+		this._radius = (options.radius == undefined) ? 20000 : options.radius;
+
+		if (options.latitude !== undefined && options.longitude !== undefined) {
+			this._currentMarkerPosition = {latitude: options.latitude, longitude: options.longitude};
+		} 
+
+		if (this._currentMarkerPosition.latitude == undefined ||
+			this._currentMarkerPosition.longitude == undefined) {
+			throw "Missing latitude and longitude in the options";
+		}
+
+		// Update the options with latitude and longitude
+		var latitude = this._currentMarkerPosition.latitude;
+		var longitude = this._currentMarkerPosition.longitude;
+
+		// Delete the layers and re-add them
+		this.deleteLayer("radius");
+		this.deleteLayer("radiusmarker");
+
+		// Get the current map center
+		var point  = new OpenLayers.LonLat(longitude, latitude);
+		point.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
+
+		// Move the map to the new center
+		this._olMap.moveTo(point);
+
+		var circleOrigin = new OpenLayers.Geometry.Point(longitude, latitude);
+		circleOrigin.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
+
+		// Radius feature
+		var radiusFeature = new OpenLayers.Feature.Vector(
+			OpenLayers.Geometry.Polygon.createRegularPolygon(circleOrigin, this._radius, 40, 0),
+			null,
+			OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style["default"])
+		);
+
+		var radiusLayer = new OpenLayers.Layer.Vector("radius");
+		radiusLayer.addFeatures([radiusFeature]);
+
+		// Create marker to be used for adjusting the center
+		// point for the radius
+		var markers = new OpenLayers.Layer.Markers("radiusmarker");
+		markers.addMarker(new OpenLayers.Marker(point));
+
+		this._olMap.addLayers([radiusLayer, markers]);
+
+	}
+
+	/**
+	 * APIMethod: updateBaseLayer
+	 * Changes the basealyer of the map
+	 * Parameters:
+	 * layerName - {String} Name of the new base layer
+	 */
+	Ushahidi.Map.prototype.updateBaseLayer = function(layerName) {
+		var layers =  this._olMap.getLayersByName(layerName);
+		if (layers.length < 1)
+			throw "Layer " + layerName + " not found";
+
+		layers[0].setVisibility(true);
+		this._olMap.setBaseLayer(layers[0]);
+	}
+
+	/**
+	 * APIMethod: updateZoom
+	 * Updates the zoom level of the map
+	 * Parameters:
+	 * zoom - {Number} The zoom level to adjust to
+	 */
+	Ushahidi.Map.prototype.updateZoom = function(zoom) {
+		if (this.currentZoom !== zoom) {
+			this.currentZoom = zoom;
+			this._olMap.setCenter(this._currentCenter, zoom);
+		}
+	}
+
+	/**
+	 * APIMethod: updateMapCenter
+	 * Updates the center of the map
+	 */
+	Ushahidi.Map.prototype.updateMapCenter = function(center) {
+		var point = new OpenLayers.LonLat(center.longitude, center.latitude);
+		point.transform(Ushahidi.proj_4326, Ushahidi.proj_900913);
+		this._olMap.panTo(point);
+
+		// Re-add the default layer
+		this.addLayer(Ushahidi.DEFAULT);
+
+		// Map center has changed, trigger 
+		this.trigger("markerpositionchanged", center);
+	}
+
+	/**
+	 * APIMethod: triggerZoomChanged
+	 * Callback that triggers the "zoomchanged" event. This method
+	 * is called by OpenLayers when the map zoom changes and the
+	 * Ushahidi.Map object was initialized with the detectMapZoom
+	 * option set to TRUE
+	 */
+	Ushahidi.Map.prototype.triggerZoomChanged = function(e) {
+		if (this._isLoaded) {
+			this.trigger("zoomchanged", this._olMap.getZoom());
+
+			// EK <emmanuel(at)ushahidi.com>
+			// Dirty hack to add back the default layer
+			// The assumption here is that this method is only called
+			// when we're using the default layer
+			this.addLayer(Ushahidi.DEFAULT);
+		}
+	}
+	
+	/**
+	 * APIMethod: keepOnTop
+	 * Forces specified layer(s) to the top of the stack
+	 */
+	Ushahidi.Map.prototype.keepOnTop = function(layer) {
+		for (var i=0; i<this._onTop.length; i++) {
+			var layerName = this._onTop[i];
+			var layers = this._olMap.getLayersByName(layerName);
+			
+			for (var j=0; j<layers.length; j++) {
+				this._olMap.raiseLayer(layers[j], this._olMap.getNumLayers());
+			}
+		}
+	}
+
+
+	/**
+	 * Helper method: convert LongLat
+	 * Converts LongLat coordinates from Open Layers to "lat, long"
+	 */
+	Ushahidi.convertLongLat = function(longLat) {
+		return longLat.lat.toFixed(5) + ", " + longLat.lon.toFixed(5)
+	}
+})();
diff --git a/media/uploads/.gitignore b/media/uploads/.gitignore
new file mode 100755
index 0000000..e69de29
diff --git a/modules/auth/config/auth.php b/modules/auth/config/auth.php
new file mode 100644
index 0000000..e7274bd
--- /dev/null
+++ b/modules/auth/config/auth.php
@@ -0,0 +1,55 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Auth library configuration. By default, Auth will use the controller
+ * database connection. If Database is not loaded, it will use use the default
+ * database group.
+ *
+ * In order to log a user in, a user must have the `login` role. You may create
+ * and assign any other role to your users.
+ */
+
+/**
+ * Driver to use for authentication. By default, LDAP and ORM are available.
+ */
+$config['driver'] = 'ORM';
+
+/**
+ * Type of hash to use for passwords. Any algorithm supported by the hash function
+ * can be used here. Note that the length of your password is determined by the
+ * hash type + the number of salt characters.
+ * @see http://php.net/hash
+ * @see http://php.net/hash_algos
+ */
+$config['hash_method'] = 'sha1';
+
+/**
+ * Defines the hash offsets to insert the salt at. The password hash length
+ * will be increased by the total number of offsets.
+ */
+$config['salt_pattern'] = '1, 3, 5, 9, 14, 15, 20, 21, 28, 30';
+
+/**
+ * Set the auto-login (remember me) cookie lifetime, in seconds. The default
+ * lifetime is two weeks.
+ */
+$config['lifetime'] = 1209600;
+
+/**
+ * Set the session key that will be used to store the current user.
+ */
+$config['session_key'] = 'auth_user';
+
+/**
+ * Usernames (keys) and hashed passwords (values) used by the File driver.
+ * Default admin password is "admin". You are encouraged to change this.
+ */
+$config['users'] = array
+(
+	// 'admin' => 'b3154acf3a344170077d11bdb5fff31532f679a1919e716a02',
+);
+
+/**
+ * Allowed Password Length. 127 is the upper limit.
+ * ie. '8,127' says passwords must be between 8 and 127 characters long
+ */
+$config['password_length'] = '1,127';
diff --git a/modules/auth/libraries/Auth.php b/modules/auth/libraries/Auth.php
new file mode 100644
index 0000000..01fc04c
--- /dev/null
+++ b/modules/auth/libraries/Auth.php
@@ -0,0 +1,262 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * User authorization library. Handles user login and logout, as well as secure
+ * password hashing.
+ *
+ * @package    Auth
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Auth_Core {
+
+	// Session instance
+	protected $session;
+
+	// Configuration
+	protected $config;
+
+	/**
+	 * Create an instance of Auth.
+	 *
+	 * @return  object
+	 */
+	public static function factory($config = array())
+	{
+		return new Auth($config);
+	}
+
+	/**
+	 * Return a static instance of Auth.
+	 *
+	 * @return  object
+	 */
+	public static function instance($config = array())
+	{
+		static $instance;
+
+		// Load the Auth instance
+		empty($instance) and $instance = new Auth($config);
+
+		return $instance;
+	}
+
+	/**
+	 * Loads Session and configuration options.
+	 *
+	 * @return  void
+	 */
+	public function __construct($config = array())
+	{
+		// Append default auth configuration
+		$config += Kohana::config('auth');
+
+		// Clean up the salt pattern and split it into an array
+		$config['salt_pattern'] = preg_split('/,\s*/', Kohana::config('auth.salt_pattern'));
+
+		// Save the config in the object
+		$this->config = $config;
+
+		// Set the driver class name
+		$driver = 'Auth_'.$config['driver'].'_Driver';
+
+		if ( ! Kohana::auto_load($driver))
+			throw new Kohana_Exception('core.driver_not_found', $config['driver'], get_class($this));
+
+		// Load the driver
+		$driver = new $driver($config);
+
+		if ( ! ($driver instanceof Auth_Driver))
+			throw new Kohana_Exception('core.driver_implements', $config['driver'], get_class($this), 'Auth_Driver');
+
+		// Load the driver for access
+		$this->driver = $driver;
+
+		Kohana::log('debug', 'Auth Library loaded');
+	}
+
+	/**
+	 * Check if there is an active session. Optionally allows checking for a
+	 * specific role.
+	 *
+	 * @param   string   role name
+	 * @return  boolean
+	 */
+	public function logged_in($role = NULL)
+	{
+		return $this->driver->logged_in($role);
+	}
+
+	/**
+	 * Returns the currently logged in user, or FALSE.
+	 *
+	 * @return  mixed
+	 */
+	public function get_user()
+	{
+		return $this->driver->get_user();
+	}
+
+	/**
+	 * Attempt to log in a user by using an ORM object and plain-text password.
+	 *
+	 * @param   string   username to log in
+	 * @param   string   password to check against
+	 * @param   boolean  enable auto-login
+	 * @param   string   email
+	 * @return  bool
+	 */
+	public function login($username, $password, $remember = FALSE, $email = FALSE)
+	{
+		if (empty($password))
+			return FALSE;
+
+		// Hash the password only if we are not using RiverID
+		if (is_string($password) AND Kohana::config('riverid.enable') != TRUE)
+		{
+			// Get the salt from the stored password
+			$salt = $this->find_salt($this->driver->password($username));
+
+			// Create a hashed password using the salt from the stored password
+			$password = $this->hash_password($password, $salt);
+		}
+
+		return $this->driver->login($username, $password, $remember, $email);
+	}
+
+	/**
+	 * Simply check to see if a password is valid for a user. NOT COMPATIBLE WITH RIVERID.
+	 *
+	 * @param   integer  user id of the user to check
+	 * @param   string   password to check against
+	 * @return  boolean
+	 */
+	public function check_password($user_id, $password)
+	{
+		if (empty($password))
+			return FALSE;
+
+		// Hash the password
+		if (is_string($password))
+		{
+			$user = ORM::factory('user',$user_id);
+
+			// Get the salt from the stored password
+			$salt = $this->find_salt($this->driver->password($user->email));
+
+			// Create a hashed password using the salt from the stored password
+			$password = $this->hash_password($password, $salt);
+		}
+
+		return $this->driver->check_password($user_id, $password);
+	}
+
+	/**
+	 * Attempt to automatically log a user in.
+	 *
+	 * @return  boolean
+	 */
+	public function auto_login()
+	{
+		return $this->driver->auto_login();
+	}
+
+	/**
+	 * Force a login for a specific username.
+	 *
+	 * @param   mixed    username
+	 * @return  boolean
+	 */
+	public function force_login($username)
+	{
+		return $this->driver->force_login($username);
+	}
+
+	/**
+	 * Log out a user by removing the related session variables.
+	 *
+	 * @param   boolean  completely destroy the session
+	 * @return  boolean
+	 */
+	public function logout($destroy = FALSE)
+	{
+		return $this->driver->logout($destroy);
+	}
+
+	/**
+	 * Creates a hashed password from a plaintext password, inserting salt
+	 * based on the configured salt pattern.
+	 *
+	 * @param   string  plaintext password
+	 * @return  string  hashed password string
+	 */
+	public function hash_password($password, $salt = FALSE)
+	{
+		if ($salt === FALSE)
+		{
+			// Create a salt seed, same length as the number of offsets in the pattern
+			$salt = substr($this->hash(uniqid(NULL, TRUE)), 0, count($this->config['salt_pattern']));
+		}
+
+		// Password hash that the salt will be inserted into
+		$hash = $this->hash($salt.$password);
+
+		// Change salt to an array
+		$salt = str_split($salt, 1);
+
+		// Returned password
+		$password = '';
+
+		// Used to calculate the length of splits
+		$last_offset = 0;
+
+		foreach ($this->config['salt_pattern'] as $offset)
+		{
+			// Split a new part of the hash off
+			$part = substr($hash, 0, $offset - $last_offset);
+
+			// Cut the current part out of the hash
+			$hash = substr($hash, $offset - $last_offset);
+
+			// Add the part to the password, appending the salt character
+			$password .= $part.array_shift($salt);
+
+			// Set the last offset to the current offset
+			$last_offset = $offset;
+		}
+
+		// Return the password, with the remaining hash appended
+		return $password.$hash;
+	}
+
+	/**
+	 * Perform a hash, using the configured method.
+	 *
+	 * @param   string  string to hash
+	 * @return  string
+	 */
+	public function hash($str)
+	{
+		return hash($this->config['hash_method'], $str);
+	}
+
+	/**
+	 * Finds the salt from a password, based on the configured salt pattern.
+	 *
+	 * @param   string  hashed password
+	 * @return  string
+	 */
+	public function find_salt($password)
+	{
+		$salt = '';
+
+		foreach ($this->config['salt_pattern'] as $i => $offset)
+		{
+			// Find salt characters, take a good long look...
+			$salt .= substr($password, $offset + $i, 1);
+		}
+
+		return $salt;
+	}
+
+} // End Auth
\ No newline at end of file
diff --git a/modules/auth/libraries/drivers/Auth.php b/modules/auth/libraries/drivers/Auth.php
new file mode 100644
index 0000000..f29865d
--- /dev/null
+++ b/modules/auth/libraries/drivers/Auth.php
@@ -0,0 +1,142 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Abstract Auth driver, must be extended by all drivers.
+ *
+ * $Id: Auth.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Auth
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Auth_Driver {
+
+	// Session instance
+	protected $session;
+
+	// Configuration
+	protected $config;
+
+	/**
+	 * Creates a new driver instance, loading the session and storing config.
+	 *
+	 * @param   array  configuration
+	 * @return  void
+	 */
+	public function __construct(array $config)
+	{
+		// Load Session
+		$this->session = Session::instance();
+
+		// Store config
+		$this->config = $config;
+	}
+
+	/**
+	 * Checks if a session is active.
+	 *
+	 * @param   string   role name (not supported)
+	 * @return  boolean
+	 */
+	public function logged_in($role)
+	{
+		return isset($_SESSION[$this->config['session_key']]);
+	}
+
+	/**
+	 * Gets the currently logged in user from the session.
+	 * Returns FALSE if no user is currently logged in.
+	 *
+	 * @return  mixed
+	 */
+	public function get_user()
+	{
+		if ($this->logged_in(NULL))
+		{
+			return $_SESSION[$this->config['session_key']];
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Logs a user in.
+	 *
+	 * @param   string   username
+	 * @param   string   password
+	 * @param   boolean  enable auto-login
+	 * @return  boolean
+	 */
+	abstract public function login($username, $password, $remember);
+
+	/**
+	 * Forces a user to be logged in, without specifying a password.
+	 *
+	 * @param   mixed    username
+	 * @return  boolean
+	 */
+	abstract public function force_login($username);
+
+	/**
+	 * Logs a user in, based on stored credentials, typically cookies.
+	 * Not supported by default.
+	 *
+	 * @return  boolean
+	 */
+	public function auto_login()
+	{
+		return FALSE;
+	}
+
+	/**
+	 * Log a user out.
+	 *
+	 * @param   boolean  completely destroy the session
+	 * @return  boolean
+	 */
+	public function logout($destroy)
+	{
+		if ($destroy === TRUE)
+		{
+			// Destroy the session completely
+			Session::instance()->destroy();
+		}
+		else
+		{
+			// Remove the user from the session
+			$this->session->delete($this->config['session_key']);
+
+			// Regenerate session_id
+			$this->session->regenerate();
+		}
+
+		// Double check
+		return ! $this->logged_in(NULL);
+	}
+
+	/**
+	 * Get the stored password for a username.
+	 *
+	 * @param   mixed   username
+	 * @return  string
+	 */
+	abstract public function password($username);
+
+	/**
+	 * Completes a login by assigning the user to the session key.
+	 *
+	 * @param   string   username
+	 * @return  TRUE
+	 */
+	protected function complete_login($user)
+	{
+		// Regenerate session_id
+		$this->session->regenerate();
+
+		// Store username in session
+		$_SESSION[$this->config['session_key']] = $user;
+
+		return TRUE;
+	}
+
+} // End Auth_Driver
\ No newline at end of file
diff --git a/modules/auth/libraries/drivers/Auth/File.php b/modules/auth/libraries/drivers/Auth/File.php
new file mode 100644
index 0000000..8c18a27
--- /dev/null
+++ b/modules/auth/libraries/drivers/Auth/File.php
@@ -0,0 +1,73 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * File Auth driver.
+ * Note: this Auth driver does not support roles nor auto-login.
+ *
+ * $Id: File.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Auth
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Auth_File_Driver extends Auth_Driver {
+
+	// User list
+	protected $users;
+
+	/**
+	 * Constructor loads the user list into the class.
+	 */
+	public function __construct(array $config)
+	{
+		parent::__construct($config);
+
+		// Load user list
+		$this->users = empty($config['users']) ? array() : $config['users'];
+	}
+
+	/**
+	 * Logs a user in.
+	 *
+	 * @param   string   username
+	 * @param   string   password
+	 * @param   boolean  enable auto-login (not supported)
+	 * @param   string   email (ignored but kept for consistency across login driver functions)
+	 * @return  boolean
+	 */
+	public function login($username, $password, $remember, $email)
+	{
+		if (isset($this->users[$username]) AND $this->users[$username] === $password)
+		{
+			// Complete the login
+			return $this->complete_login($username);
+		}
+
+		// Login failed
+		return FALSE;
+	}
+
+	/**
+	 * Forces a user to be logged in, without specifying a password.
+	 *
+	 * @param   mixed    username
+	 * @return  boolean
+	 */
+	public function force_login($username)
+	{
+		// Complete the login
+		return $this->complete_login($username);
+	}
+
+	/**
+	 * Get the stored password for a username.
+	 *
+	 * @param   mixed   username
+	 * @return  string
+	 */
+	public function password($username)
+	{
+		return isset($this->users[$username]) ? $this->users[$username] : FALSE;
+	}
+
+} // End Auth_File_Driver
\ No newline at end of file
diff --git a/modules/auth/libraries/drivers/Auth/ORM.php b/modules/auth/libraries/drivers/Auth/ORM.php
new file mode 100644
index 0000000..982e20d
--- /dev/null
+++ b/modules/auth/libraries/drivers/Auth/ORM.php
@@ -0,0 +1,542 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * ORM Auth driver.
+ *
+ * $Id: ORM.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Auth
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Auth_ORM_Driver extends Auth_Driver {
+
+	/**
+	 * Checks if a session is active.
+	 *
+	 * @param   string   role name
+	 * @param   array    collection of role names
+	 * @return  boolean
+	 */
+	public function logged_in($role)
+	{
+		$status = FALSE;
+
+		if(kohana::config('riverid.enable') == true)
+		{
+			// RiverID is being used so we need to go through some extra
+			//   steps to authenticate before moving forward
+			self::auto_login();
+		}
+
+		// Get the user from the session
+		$user = $this->session->get($this->config['session_key']);
+
+		if (is_object($user) AND $user instanceof User_Model AND $user->loaded)
+		{
+			// Everything is okay so far
+			$status = TRUE;
+
+			if ( ! empty($role))
+			{
+
+				// If role is an array
+				if (is_array($role))
+				{
+					// Check each role
+					foreach ($role as $role_iteration)
+					{
+						if ( ! is_object($role_iteration))
+						{
+							$role_iteration = ORM::factory('role', $role_iteration);
+						}
+						// If the user doesn't have the role
+						if( ! $user->has($role_iteration))
+						{
+							// Set the status false and get outta here
+							$status = FALSE;
+							break;
+						}
+					}
+				}
+				else
+				{
+				// Else just check the one supplied roles
+					if ( ! is_object($role))
+					{
+						// Load the role
+						$role = ORM::factory('role', $role);
+					}
+
+					// Check that the user has the given role
+					$status = $user->has($role);
+				}
+			}
+		}
+
+		return $status;
+	}
+
+	/**
+	 * Determines the function to use when logging in a user
+	 *
+	 * @param   string   username
+	 * @param   string   password
+	 * @param   boolean  enable auto-login
+	 * @param   string   email
+	 * @return  boolean
+	 */
+	public function login($user, $password, $remember, $email = FALSE)
+	{
+		if(kohana::config('riverid.enable') == true)
+		{
+			return $this->login_riverid($user, $password, $remember, $email);
+		}
+		else
+		{
+			return $this->login_standard($user, $password, $remember);
+		}
+	}
+
+	/**
+	 * Performs a login by setting token, etc.
+	 *
+	 * @param   string   username
+	 * @param   string   password
+	 * @param   boolean  enable auto-login
+	 * @return  boolean
+	 */
+	public function perform_login($user,$remember,$riverid=false)
+	{
+		// In case we need to check if the user has confirmed their address, do that here
+		if (Kohana::config('settings.require_email_confirmation') == 1)
+		{
+			if ($user->confirmed == 0)
+			{
+				// User has not confirmed email so kill auth cookies and fail login
+				$this->logout(TRUE);
+				
+				return FALSE;
+			}
+		}
+
+		if ($remember === TRUE)
+		{
+			// Create a new autologin token
+			$token = ORM::factory('user_token');
+
+			// Set token data
+			$token->user_id = $user->id;
+			$token->expires = time() + $this->config['lifetime'];
+			$token->save();
+
+			// Set the autologin cookie
+			cookie::set('authautologin', $token->token, $this->config['lifetime']);
+		}
+
+		// If we are using RiverID, we want to save the object so other sites
+		//   on the same domain can use it for single signon
+		if (kohana::config('riverid.enable') == true)
+		{
+			session::set('riverid',$riverid);
+		}
+
+		// Finish the login
+		$this->complete_login($user);
+
+		return TRUE;
+	}
+
+	/**
+	 * Logs a user in using the standard method.
+	 *
+	 * @param   string   username
+	 * @param   string   password
+	 * @param   boolean  enable auto-login
+	 * @return  boolean
+	 */
+	public function login_standard($user, $password, $remember)
+	{
+		if ( ! is_object($user))
+		{
+			// Load the user
+			$user = ORM::factory('user', $user);
+		}
+
+		if ( ! $user->id)
+		{
+			// user doesn't exist. Login Failed.
+			return FALSE;
+		}
+
+		// If the passwords match, perform a login
+		if ($user->has(ORM::factory('role', 'login')) AND $user->password === $password)
+		{
+			return $this->perform_login($user,$remember);
+		}
+
+		// Passwords don't match. Login Failed.
+		return FALSE;
+	}
+
+	/**
+	 * Logs a user in using the RiverID method.
+	 *
+	 * @param   string   username
+	 * @param   string   password
+	 * @param   boolean  enable auto-login
+	 * @param   string   email
+	 * @param   object   a riverid object, not required
+	 * @return  boolean
+	 */
+	public function login_riverid($user, $password, $remember, $email, $riverid=false)
+	{
+		// First check for exemptions
+
+		if ( ! is_object($user))
+		{
+			// Load the user
+			$user = ORM::factory('user', $user);
+		}
+
+		if (isset($user->id) AND in_array($user->id,kohana::config('riverid.exempt')))
+		{
+			// Looks like this is an exempted account
+			return $this->login_standard($user, $password, $remember);
+		}
+
+		// Get down to business since there were no exemptions
+
+		if ($riverid == false)
+		{
+			$riverid = new RiverID;
+			$riverid->email = $email;
+			$riverid->password = $password;
+		}
+
+		$is_registered = $riverid->is_registered();
+
+		// See if the request even fired off.
+		if ($riverid->error)
+		{
+			throw new Exception($riverid->error[0]);
+		}
+
+		if($is_registered == true)
+		{
+			// RiverID is registered on RiverID Server
+
+			if ($riverid->authenticated != true)
+			{
+				// Attempt to sign in if our riverid object hasn't already authenticated
+				$riverid->signin();
+			}
+
+			if ($riverid->authenticated == true)
+			{
+				// Correct email/pass
+
+				// Collect the RiverID user_id and connect that with a user in the local system
+
+				$user = User_Model::get_user_by_river_id($riverid->user_id);
+
+				if ( ! $user->id)
+				{
+					// User not found locally with that RiverID, we need to see if they are already registered
+					//   and convert their account or add them as a new user to the system
+
+					// This may be a brand new user, but we need to figure out if
+					//    the email has already been registered
+					$user = User_Model::get_user_by_email($riverid->email);
+
+					if ( ! $user->id)
+					{
+						// Email isn't in our system, create a new user.
+						$user = User_Model::create_user($riverid->email,$riverid->password,$riverid->user_id);
+					}
+					else
+					{
+						// Email already exists. Put the RiverID on that account.
+						$user->riverid = $riverid->user_id;
+						$user->save();
+					}
+
+				}
+				else
+				{
+					// We authenticated and we matched a RiverID, lets just makes sure the email
+					//   addresses are both up to date
+
+					if ($user->email != $riverid->email)
+					{
+						// We don't have a match for this user account. We need to see if we should
+						//   be updating this account by first checking to see if another account
+						//   already uses this email address
+						$user_check = User_Model::get_user_by_email($riverid->email);
+						if ( ! $user_check->id)
+						{
+							$user->email = $riverid->email;
+							$user->username = $riverid->email;
+							$user->save();
+						}
+						else
+						{
+							// Conflicting accounts
+
+							// TODO: Figure out what to do when we need to update an email address on
+							//   one account but it's already in use on another.
+						}
+					}
+				}
+
+				// Now that we have our user account tied to their RiverID, approve their authentication
+
+				return $this->perform_login($user,$remember,$riverid);
+
+			}
+			else
+			{
+				// Incorrect email/pass, but registered on RiverID. Failed login.
+
+				if ($riverid->error)
+				{
+					throw new Exception($riverid->error[0]);
+				}
+
+				return FALSE;
+			}
+		}
+		else
+		{
+
+			// Email is not registerd on RiverID Server, could be registered locally
+
+			// First see if they used the correct user/pass on their local account
+
+			$user = User_Model::get_user_by_email($riverid->email);
+
+			if ( ! $user->id)
+			{
+				// User doesn't exist locally or on RiverID. Fail login.
+
+				if ($riverid->error)
+				{
+					throw new Exception($riverid->error[0]);
+				}
+
+				return FALSE;
+			}
+			else
+			{
+				// User exists locally but doesn't yet exist on the RiverID server
+
+				// Check if they got the password correct
+
+				if ($user->has(ORM::factory('role', 'login'))
+					AND User_Model::check_password($user->id,$password,TRUE))
+				{
+					// Correct password! Create RiverID account
+					$riverid->register();
+
+					// If something went wrong with registration, catch it here
+					if ($riverid->error)
+					{
+						throw new Exception($riverid->error[0]);
+					}
+
+					// Our user is now registered, let's assign the riverid user to the db.
+					$user->riverid = $riverid->user_id;
+
+					// Now lets sign them in
+					$riverid->signin();
+
+					// If something went wrong with signin, catch it here
+					if ($riverid->error)
+					{
+						throw new Exception($riverid->error[0]);
+					}
+
+					return $this->perform_login($user,$remember,$riverid);
+
+				}
+				else
+				{
+					// Incorrect user/pass. Fail login.
+
+					if ($riverid->error)
+					{
+						throw new Exception($riverid->error[0]);
+					}
+
+					return FALSE;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Simply check to see if a password is valid for a user. NOT COMPATIBLE WITH RIVERID.
+	 *
+	 * @param   integer  user id of the user to check
+	 * @param   string   password to check against
+	 * @return  boolean
+	 */
+	public function check_password($user_id, $password)
+	{
+		$user = ORM::factory('user', $user_id);
+
+		if ( ! $user->id OR empty($password) )
+		{
+			return FALSE;
+		}
+
+		// If the passwords match, return true
+		if ($user->password === $password)
+		{
+			return TRUE;
+		}else{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Forces a user to be logged in, without specifying a password.
+	 *
+	 * @param   mixed    username
+	 * @return  boolean
+	 */
+	public function force_login($user)
+	{
+		if ( ! is_object($user))
+		{
+			// Load the user
+			$user = ORM::factory('user', $user);
+		}
+
+		// Mark the session as forced, to prevent users from changing account information
+		$_SESSION['auth_forced'] = TRUE;
+
+		// Run the standard completion
+		$this->complete_login($user);
+	}
+
+	/**
+	 * Logs a user in, based on the authautologin cookie.
+	 *
+	 * @param bool Set to true to force standard login
+	 * @return  boolean
+	 */
+	public function auto_login($force_standard=false)
+	{
+		// If we are using RiverID
+
+		if (kohana::config('riverid.enable') == true
+		    AND $force_standard != true)
+		{
+			$riverid = session::get('riverid');
+
+			// Check if we have the RiverID model, fail auto login if we don't
+			if ( ! $riverid )
+			{
+				return FALSE;
+			}
+
+			// user, password, remember, email, riverid object
+			return $this->login_riverid($riverid->email, false, true, $riverid->email, $riverid);
+
+		}
+
+		// If we are not using RiverID
+
+		if ($token = cookie::get('authautologin'))
+		{
+			// Load the token and user
+			$token = ORM::factory('user_token', $token);
+
+			if ($token->loaded AND $token->user->loaded)
+			{
+				if ($token->user_agent === sha1(Kohana::$user_agent))
+				{
+					// Save the token to create a new unique token
+					$token->save();
+
+					// Set the new token
+					cookie::set('authautologin', $token->token, $token->expires - time());
+
+					// Complete the login with the found data
+					$this->complete_login($token->user);
+
+					// Automatic login was successful
+					return TRUE;
+				}
+
+				// Token is invalid
+				$token->delete();
+			}
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Log a user out and remove any auto-login cookies.
+	 *
+	 * @param   boolean  completely destroy the session
+	 * @return  boolean
+	 */
+	public function logout($destroy)
+	{
+		if (cookie::get('authautologin'))
+		{
+			// Delete the autologin cookie to prevent re-login
+			cookie::delete('authautologin');
+		}
+
+		if (session::get('riverid'))
+		{
+			// Delete the riverid object in case it's set
+			session::delete('riverid');
+		}
+
+		return parent::logout($destroy);
+	}
+
+	/**
+	 * Get the stored password for a username.
+	 *
+	 * @param   mixed   username
+	 * @return  string
+	 */
+	public function password($user)
+	{
+		if ( ! is_object($user))
+		{
+			// Load the user
+			$user = ORM::factory('user', $user);
+		}
+
+		return $user->password;
+	}
+
+	/**
+	 * Complete the login for a user by incrementing the logins and setting
+	 * session data: user_id, username, roles
+	 *
+	 * @param   object   user model object
+	 * @return  void
+	 */
+	protected function complete_login(User_Model $user)
+	{
+		// Update the number of logins
+		$user->logins += 1;
+
+		// Set the last login date
+		$user->last_login = time();
+
+		// Save the user
+		$user->save();
+
+		return parent::complete_login($user);
+	}
+
+} // End Auth_ORM_Driver
\ No newline at end of file
diff --git a/modules/auth/models/auth_role.php b/modules/auth/models/auth_role.php
new file mode 100644
index 0000000..3f21461
--- /dev/null
+++ b/modules/auth/models/auth_role.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class Auth_Role_Model extends ORM {
+
+	protected $has_and_belongs_to_many = array('users');
+
+	/**
+	 * Validates and optionally saves a role record from an array.
+	 *
+	 * @param  array    values to check
+	 * @param  boolean  save the record when validation succeeds
+	 * @return boolean
+	 */
+	public function validate(array & $array, $save = FALSE)
+	{
+		$array = Validation::factory($array)
+			->pre_filter('trim')
+			->add_rules('name', 'required', 'length[4,32]')
+			->add_rules('description', 'length[0,255]');
+
+		return parent::validate($array, $save);
+	}
+
+	/**
+	 * Allows finding roles by name.
+	 */
+	public function unique_key($id)
+	{
+		if ( ! empty($id) AND is_string($id) AND ! ctype_digit($id))
+		{
+			return 'name';
+		}
+
+		return parent::unique_key($id);
+	}
+
+} // End Auth Role Model
\ No newline at end of file
diff --git a/modules/auth/models/auth_user.php b/modules/auth/models/auth_user.php
new file mode 100644
index 0000000..84b27bc
--- /dev/null
+++ b/modules/auth/models/auth_user.php
@@ -0,0 +1,155 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class Auth_User_Model extends ORM {
+
+	// Relationships
+	protected $has_many = array('user_tokens');
+	protected $has_and_belongs_to_many = array('roles');
+
+	// Columns to ignore
+	protected $ignored_columns = array('password_confirm');
+
+	public function __set($key, $value)
+	{
+		if ($key === 'password')
+		{
+			// Use Auth to hash the password
+			$value = Auth::instance()->hash_password($value);
+		}
+
+		parent::__set($key, $value);
+	}
+
+	/**
+	 * Validates and optionally saves a new user record from an array.
+	 *
+	 * @param  array    values to check
+	 * @param  boolean  save the record when validation succeeds
+	 * @return boolean
+	 */
+	public function validate(array & $array, $save = FALSE)
+	{
+		$array = Validation::factory($array)
+			->pre_filter('trim')
+			->add_rules('email', 'required', 'length[4,127]', 'valid::email')
+			->add_rules('username', 'required', 'length[4,32]', 'chars[a-zA-Z0-9_.]', array($this, 'username_exists'))
+			->add_rules('password', 'required', 'length['.kohana::config('auth.password_length').']')
+			->add_rules('password_confirm', 'matches[password]');
+
+		return parent::validate($array, $save);
+	}
+
+	/**
+	 * Validates login information from an array, and optionally redirects
+	 * after a successful login.
+	 *
+	 * @param  array    values to check
+	 * @param  string   URI or URL to redirect to
+	 * @return boolean
+	 */
+	public function login(array & $array, $redirect = FALSE)
+	{
+		$array = Validation::factory($array)
+			->pre_filter('trim')
+			->add_rules('username', 'required', 'length[4,127]')
+			->add_rules('password', 'required', 'length['.kohana::config('auth.password_length').']');
+
+		// Login starts out invalid
+		$status = FALSE;
+
+		if ($array->validate())
+		{
+			// Attempt to load the user
+			$this->find($array['username']);
+
+			if ($this->loaded AND Auth::instance()->login($this, $array['password']))
+			{
+				if (is_string($redirect))
+				{
+					// Redirect after a successful login
+					url::redirect($redirect);
+				}
+
+				// Login is successful
+				$status = TRUE;
+			}
+			else
+			{
+				$array->add_error('username', 'invalid');
+			}
+		}
+
+		return $status;
+	}
+
+	/**
+	 * Validates an array for a matching password and password_confirm field.
+	 *
+	 * @param  array    values to check
+	 * @param  string   save the user if
+	 * @return boolean
+	 */
+	public function change_password(array & $array, $save = FALSE)
+	{
+		$array = Validation::factory($array)
+			->pre_filter('trim')
+			->add_rules('password', 'required', 'length['.kohana::config('auth.password_length').']')
+			->add_rules('password_confirm', 'matches[password]');
+
+		if ($status = $array->validate())
+		{
+			// Change the password
+			$this->password = $array['password'];
+
+			if ($save !== FALSE AND $status = $this->save())
+			{
+				if (is_string($save))
+				{
+					// Redirect to the success page
+					url::redirect($save);
+				}
+			}
+		}
+
+		return $status;
+	}
+
+	/**
+	 * Tests if a user email already exists in the database.
+	 *
+	 * @param   string   email to check
+	 * @return  bool
+	 */
+	public function email_exists($email)
+	{
+		return (bool) $this->db->where('email', $email)->count_records($this->table_name);
+	}
+
+	/**
+	 * Tests if a username exists in the database. This can be used as a
+	 * Validation rule.
+	 *
+	 * @param   mixed    id to check
+	 * @return  boolean
+	 */
+	public function username_exists($id)
+	{
+		return (bool) $this->db
+			->where($this->unique_key($id), $id)
+			->count_records($this->table_name);
+	}
+
+	/**
+	 * Allows a model to be loaded by username or email address.
+	 */
+	public function unique_key($id)
+	{
+		if ( ! empty($id) AND is_string($id) AND ! ctype_digit($id))
+		{
+			return valid::email($id) ? 'email' : 'username';
+		}
+
+		return parent::unique_key($id);
+	}
+
+} // End Auth User Model
\ No newline at end of file
diff --git a/modules/auth/models/auth_user_token.php b/modules/auth/models/auth_user_token.php
new file mode 100644
index 0000000..b0c8bd8
--- /dev/null
+++ b/modules/auth/models/auth_user_token.php
@@ -0,0 +1,102 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class Auth_User_Token_Model extends ORM {
+
+	// Relationships
+	protected $belongs_to = array('user');
+
+	// Current timestamp
+	protected $now;
+
+	/**
+	 * Handles garbage collection and deleting of expired objects.
+	 */
+	public function __construct($id = NULL)
+	{
+		parent::__construct($id);
+
+		// Set the now, we use this a lot
+		$this->now = time();
+
+		if (mt_rand(1, 100) === 1)
+		{
+			// Do garbage collection
+			$this->delete_expired();
+		}
+
+		if ($this->expires < $this->now)
+		{
+			// This object has expired
+			$this->delete();
+		}
+	}
+
+	/**
+	 * Overload saving to set the created time and to create a new token
+	 * when the object is saved.
+	 */
+	public function save()
+	{
+		if ($this->loaded === FALSE)
+		{
+			// Set the created time, token, and hash of the user agent
+			$this->created = $this->now;
+			$this->user_agent = sha1(Kohana::$user_agent);
+		}
+
+		// Create a new token each time the token is saved
+		$this->token = $this->create_token();
+
+		return parent::save();
+	}
+
+	/**
+	 * Deletes all expired tokens.
+	 *
+	 * @return  void
+	 */
+	public function delete_expired()
+	{
+		// Delete all expired tokens
+		$this->db->where('expires <', $this->now)->delete($this->table_name);
+
+		return $this;
+	}
+
+	/**
+	 * Finds a new unique token, using a loop to make sure that the token does
+	 * not already exist in the database. This could potentially become an
+	 * infinite loop, but the chances of that happening are very unlikely.
+	 *
+	 * @return  string
+	 */
+	protected function create_token()
+	{
+		while (TRUE)
+		{
+			// Create a random token
+			$token = text::random('alnum', 32);
+
+			// Make sure the token does not already exist
+			if ($this->db->select('id')->where('token', $token)->get($this->table_name)->count() === 0)
+			{
+				// A unique token has been found
+				return $token;
+			}
+		}
+	}
+
+	/**
+	 * Allows loading by token string.
+	 */
+	public function unique_key($id)
+	{
+		if ( ! empty($id) AND is_string($id) AND ! ctype_digit($id))
+		{
+			return 'token';
+		}
+
+		return parent::unique_key($id);
+	}
+
+} // End Auth User Token Model
\ No newline at end of file
diff --git a/modules/auth/models/role.php b/modules/auth/models/role.php
new file mode 100644
index 0000000..94f6f7d
--- /dev/null
+++ b/modules/auth/models/role.php
@@ -0,0 +1,7 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class Role_Model extends Auth_Role_Model {
+
+	// This class can be replaced or extended
+
+} // End Role Model
\ No newline at end of file
diff --git a/modules/auth/models/user.php b/modules/auth/models/user.php
new file mode 100644
index 0000000..82eef61
--- /dev/null
+++ b/modules/auth/models/user.php
@@ -0,0 +1,7 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class User_Model extends Auth_User_Model {
+	
+	// This class can be replaced or extended
+	
+} // End User Model
\ No newline at end of file
diff --git a/modules/auth/models/user_token.php b/modules/auth/models/user_token.php
new file mode 100644
index 0000000..aea6c67
--- /dev/null
+++ b/modules/auth/models/user_token.php
@@ -0,0 +1,7 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+class User_Token_Model extends Auth_User_Token_Model {
+
+	// This class can be replaced or extended
+
+} // End User Token Model
\ No newline at end of file
diff --git a/modules/csrf/helpers/MY_form.php b/modules/csrf/helpers/MY_form.php
new file mode 100644
index 0000000..21a17af
--- /dev/null
+++ b/modules/csrf/helpers/MY_form.php
@@ -0,0 +1,68 @@
+<?php defined('SYSPATH') or die('No direct script access');
+/**
+ * A helper class for generating CSRF-enabled forms - forms with
+ * a hidden field that contains a randomly generated token that
+ * is used for CSRF protection
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to GPLv3 license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://github.com/ushahidi/Ushahidi_Web
+ * @category   Helpers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU General Public License v3 (GPLv3) 
+ */
+class form extends form_Core {
+
+	/**
+	 * Generates an opening HTML form tag.
+	 *
+	 * @param   string  form action attribute
+	 * @param   array   extra attributes
+	 * @param   array   hidden fields to be created immediately after the form tag
+	 * @return  string
+	 */
+	public static function open($action = NULL, $attr = array(), $hidden = NULL)
+	{
+		// Make sure that the method is always set
+		empty($attr['method']) and $attr['method'] = 'post';
+
+		if ($attr['method'] !== 'post' AND $attr['method'] !== 'get')
+		{
+			// If the method is invalid, use post
+			$attr['method'] = 'post';
+		}
+
+		if ($action === NULL)
+		{
+			// Use the current URL as the default action
+			$action = url::site(Router::$complete_uri);
+		}
+		elseif (strpos($action, '://') === FALSE)
+		{
+			// Make the action URI into a URL
+			$action = url::site($action);
+		}
+
+		// Set action
+		$attr['action'] = $action;
+
+		// Only show the CSRF field when form method is POST
+		$hidden_field = ($attr['method'] === 'post')
+		    ? form::hidden('form_auth_token', csrf::token())."\n"
+		    : '';
+
+		// Form opening tag
+		$form = '<form'.form::attributes($attr).'>'."\n"
+		    . $hidden_field;
+
+		// Add hidden fields immediate after opening tag
+		empty($hidden) or $form .= form::hidden($hidden);
+
+		return $form;
+	}
+}
+?>
\ No newline at end of file
diff --git a/modules/csrf/helpers/csrf.php b/modules/csrf/helpers/csrf.php
new file mode 100644
index 0000000..4fead5a
--- /dev/null
+++ b/modules/csrf/helpers/csrf.php
@@ -0,0 +1,61 @@
+<?php defined('SYSPATH') or die('No direct script access');
+/**
+ * CSRF token generation and validation helper library
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to GPLv3 license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://github.com/ushahidi/Ushahidi_Web
+ * @category   Helpers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU General Public License v3 (GPLv3) 
+ */
+class csrf_Core {
+
+	/**
+	 * Session key for the CSRF token
+	 * @var string
+	 */
+	private static $_csrf_session_key = 'csrf-token';
+
+	/**
+	 * Generates an returns a randon token for CSRF
+	 * prevention
+	 *
+	 * @param bool $replace Whether to replace the current token
+	 * @return string
+	 */	
+	public static function token($replace = FALSE)
+	{
+		$token = Session::instance()->get(self::$_csrf_session_key);
+
+		if ( ! $token OR $replace)
+		{
+			// Generates a hash of variable length random alpha-numeric string
+			$token = hash('sha256', text::random('alnum', rand(25, 32)));
+			Session::instance()->set('csrf-token', $token);
+			Kohana::log('debug', 'Regenerated CSRF token: '.$token);
+		}
+
+		return $token;
+	}
+
+	/**
+	 * Validates the specified token against the current
+	 * session value
+	 *
+	 * @return bool TRUE if match, FALSE otherwise
+	 */
+	public static function valid($token)
+	{
+		// Get the current token and destroy the session value
+		$current_token = self::token();
+
+		return $token === $current_token;
+	}
+}
+
+?>
diff --git a/modules/csrf/i18n/en_US/csrf.php b/modules/csrf/i18n/en_US/csrf.php
new file mode 100644
index 0000000..fd54e3f
--- /dev/null
+++ b/modules/csrf/i18n/en_US/csrf.php
@@ -0,0 +1,13 @@
+<?php defined('SYSPATH') or die('No direct script access'); 
+
+/**
+ * CSRF i18n file
+ */
+
+$lang = array(
+	'form_auth_token' => array(
+		'error' => 'The request could not be validated. Possible CSRF attack'
+	)
+);
+
+?>
\ No newline at end of file
diff --git a/plugins/clickatell/controllers/admin/clickatell_settings.php b/plugins/clickatell/controllers/admin/clickatell_settings.php
new file mode 100644
index 0000000..fd623b3
--- /dev/null
+++ b/plugins/clickatell/controllers/admin/clickatell_settings.php
@@ -0,0 +1,156 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Clickatell Settings Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Clickatell Settings Controller	
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+* 
+*/
+
+class Clickatell_Settings_Controller extends Admin_Controller {
+	public function index()
+	{
+		$this->template->this_page = 'addons';
+		
+		// Standard Settings View
+		$this->template->content = new View("admin/addons/plugin_settings");
+		$this->template->content->title = "Clickatell Settings";
+		
+		// Settings Form View
+		$this->template->content->settings_form = new View("clickatell/admin/clickatell_settings");
+		
+		// JS Header Stuff
+        $this->template->js = new View('clickatell/admin/clickatell_settings_js');
+		
+		// setup and initialize form field names
+        $form = array
+        (
+            'clickatell_api' => '',
+            'clickatell_username' => '',
+            'clickatell_password' => ''
+        );
+        //  Copy the form as errors, so the errors will be stored with keys
+        //  corresponding to the form field names
+        $errors = $form;
+        $form_error = FALSE;
+        $form_saved = FALSE;
+
+        // check, has the form been submitted, if so, setup validation
+        if ($_POST)
+        {
+            // Instantiate Validation, use $post, so we don't overwrite $_POST
+            // fields with our own things
+            $post = new Validation($_POST);
+
+            // Add some filters
+            $post->pre_filter('trim', TRUE);
+
+            // Add some rules, the input field, followed by a list of checks, carried out in order
+
+            $post->add_rules('clickatell_api','required', 'length[4,20]');
+            $post->add_rules('clickatell_username', 'required', 'length[3,50]');
+            $post->add_rules('clickatell_password', 'required', 'length[5,50]');
+
+            // Test to see if things passed the rule checks
+            if ($post->validate())
+            {
+                // Yes! everything is valid
+                $clickatell = new Clickatell_Model(1);
+                $clickatell->clickatell_api = $post->clickatell_api;
+                $clickatell->clickatell_username = $post->clickatell_username;
+                $clickatell->clickatell_password = $post->clickatell_password;
+                $clickatell->save();
+
+                // Everything is A-Okay!
+                $form_saved = TRUE;
+
+                // repopulate the form fields
+                $form = arr::overwrite($form, $post->as_array());
+
+            }
+
+            // No! We have validation errors, we need to show the form again,
+            // with the errors
+            else
+            {
+                // repopulate the form fields
+                $form = arr::overwrite($form, $post->as_array());
+
+                // populate the error fields, if any
+                $errors = arr::overwrite($errors, $post->errors('settings'));
+                $form_error = TRUE;
+            }
+        }
+        else
+        {
+            // Retrieve Current Settings
+            $clickatell = ORM::factory('clickatell', 1);
+
+            $form = array
+            (
+                'clickatell_api' => $clickatell->clickatell_api,
+                'clickatell_username' => $clickatell->clickatell_username,
+                'clickatell_password' => $clickatell->clickatell_password
+            );
+        }
+		
+		// Pass the $form on to the settings_form variable in the view
+		$this->template->content->settings_form->form = $form;
+		
+		
+		// Do we have a frontlineSMS Key? If not create and save one on the fly
+        $clickatell = ORM::factory('clickatell', 1);
+		
+		if ($clickatell->loaded AND $clickatell->clickatell_key)
+		{
+			$clickatell_key = $clickatell->clickatell_key;
+		}
+		else
+		{
+			$clickatell_key = strtoupper(text::random('alnum',8));
+            $clickatell->clickatell_key = $clickatell_key;
+            $clickatell->save();
+		}
+
+		$this->template->content->settings_form->clickatell_key = $clickatell_key;
+		$this->template->content->settings_form->clickatell_link = url::site()."clickatell/index/".$clickatell_key;
+		
+		// Other variables
+	    $this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+	}
+	
+	/**
+     * Retrieves Clickatell Balance using Clickatell Library
+     */
+    function smsbalance()
+    {
+        $this->template = "";
+        $this->auto_render = FALSE;
+
+        $clickatell = ORM::factory("clickatell")->find(1);
+        if ($clickatell->loaded)
+		{
+            $clickatell_api = $clickatell->clickatell_api;
+            $clickatell_username = $clickatell->clickatell_username;
+            $clickatell_password = $clickatell->clickatell_password;
+
+            $testsms = new Clickatell_API();
+            $testsms->api_id = $clickatell_api;
+            $testsms->user = $clickatell_username;
+            $testsms->password = $clickatell_password;
+            $testsms->use_ssl = false;
+            $testsms->sms();
+            // echo $mysms->session;
+            echo $testsms->getbalance();
+        }
+    }
+}
\ No newline at end of file
diff --git a/plugins/clickatell/controllers/clickatell.php b/plugins/clickatell/controllers/clickatell.php
new file mode 100644
index 0000000..c69dc34
--- /dev/null
+++ b/plugins/clickatell/controllers/clickatell.php
@@ -0,0 +1,67 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Clickatell HTTP Post Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module	   Clickatell Controller	
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+*/
+
+class Clickatell_Controller extends Controller {
+	
+	private $request = array();
+	
+	public function __construct()
+    {
+        $this->request = ($_SERVER['REQUEST_METHOD'] == 'POST')
+            ? $_POST
+            : $_GET;
+    }
+	
+	/**
+     * Clickatell 2 way callback handler
+     * @param string $key (Unique key that prevents unauthorized access)
+     * @return void
+     */
+	function index($key = NULL)
+	{
+		if (isset($this->request['from']))
+		{
+			$message_from = $this->request['from'];
+			// Remove non-numeric characters from string
+			$message_from = preg_replace("#[^0-9]#", "", $message_from);
+		}
+		
+		if (isset($this->request['to']))
+		{
+			$message_to = $this->request['to'];
+			// Remove non-numeric characters from string
+			$message_to = preg_replace("#[^0-9]#", "", $message_to);
+		}
+		
+		if (isset($this->request['text']))
+		{
+			$message_description = $this->request['text'];
+		}
+		
+		if ( ! empty($message_from) AND ! empty($message_description))
+		{
+			// Is this a valid Clickatell Key?
+			$keycheck = ORM::factory('clickatell')
+				->where('clickatell_key', $key)
+				->find(1);
+
+			if ($keycheck->loaded == TRUE)
+			{
+					sms::add($message_from, $message_description, $message_to);
+
+			}
+		}
+	}
+}
diff --git a/plugins/clickatell/hooks/clickatell.php b/plugins/clickatell/hooks/clickatell.php
new file mode 100644
index 0000000..cce02c7
--- /dev/null
+++ b/plugins/clickatell/hooks/clickatell.php
@@ -0,0 +1,36 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Clickatell Hook
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class clickatell {
+	
+	/**
+	 * Registers the main event add method
+	 */
+	public function __construct()
+	{	
+		// Hook into routing
+		Event::add('system.pre_controller', array($this, 'add'));
+	}
+	
+	/**
+	 * Adds all the events to the main Ushahidi application
+	 */
+	public function add()
+	{
+		// SMS Provider
+		plugin::add_sms_provider("clickatell");
+	}
+}
+
+new clickatell;
\ No newline at end of file
diff --git a/plugins/clickatell/libraries/Clickatell_API.php b/plugins/clickatell/libraries/Clickatell_API.php
new file mode 100644
index 0000000..ea9a0fa
--- /dev/null
+++ b/plugins/clickatell/libraries/Clickatell_API.php
@@ -0,0 +1,389 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * CLICKATELL SMS API
+ *
+ * This class is meant to send SMS messages (with unicode support) via 
+ * the Clickatell gateway and provides support to authenticate to this service, 
+ * spending an vouchers and also query for the current account balance. This class
+ * use the fopen or CURL module to communicate with the gateway via HTTP/S.
+ *
+ * For more information about CLICKATELL service visit http://www.clickatell.com
+ *
+ * @version 1.6
+ * @package sms_api
+ * @author Aleksandar Markovic <mikikg at gmail.com>
+ * @copyright Copyright (c) 2004 - 2007 Aleksandar Markovic
+ * @link http://sourceforge.net/projects/sms-api/ SMS-API Sourceforge project page
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
+ *
+ */
+
+/**
+ * Main SMS-API class
+ *
+ * Example:
+ * <code>
+ * <?php
+ * require_once ("sms_api.php");
+ * $mysms = new sms();
+ * echo $mysms->session;
+ * echo $mysms->getbalance();
+ * // $mysms->token_pay("1234567890123456"); //spend voucher with SMS credits
+ * $mysms->send ("38160123", "netsector", "TEST MESSAGE");
+ * ?>
+ * </code>
+ * @package sms_api
+ */
+
+class Clickatell_API_Core {
+
+    /**
+    * Clickatell API-ID
+    * @link http://sourceforge.net/forum/forum.php?thread_id=1005106&forum_id=344522 How to get CLICKATELL API ID?
+    * @var integer
+    */
+    var $api_id = "YOUR_CLICKATELL_API_NUMBER";
+
+    /**
+    * Clickatell username
+    * @var mixed
+    */
+    var $user = "YOUR_CLICKATELL_USERNAME";
+
+    /**
+    * Clickatell password
+    * @var mixed
+    */
+    var $password = "YOUR_CLICKATELL_PASSWORD";
+
+    /**
+    * Use SSL (HTTPS) protocol
+    * @var bool
+    */
+    var $use_ssl = false;
+
+    /**
+    * Define SMS balance limit below class will not work
+    * @var integer
+    */
+    var $balace_limit = 0;
+
+    /**
+    * Gateway command sending method (curl,fopen)
+    * @var mixed
+    */
+    var $sending_method = "fopen";
+
+    /**
+    * Does to use facility for delivering Unicode messages
+    * @var bool
+    */
+    var $unicode = false;
+
+    /**
+    * Optional CURL Proxy
+    * @var bool
+    */
+    var $curl_use_proxy = false;
+
+    /**
+    * Proxy URL and PORT
+    * @var mixed
+    */
+    var $curl_proxy = "http://127.0.0.1:8080";
+
+    /**
+    * Proxy username and password
+    * @var mixed
+    */
+    var $curl_proxyuserpwd = "login:secretpass";
+
+    /**
+    * Callback
+    * 0 - Off
+    * 1 - Returns only intermediate statuses
+    * 2 - Returns only final statuses
+    * 3 - Returns both intermediate and final statuses
+    * @var integer
+    */
+    var $callback = 0;
+
+    /**
+    * Session variable
+    * @var mixed
+    */
+    var $session;
+
+    /**
+    * Class constructor
+    * Create SMS object and authenticate SMS gateway
+    * @return object New SMS object.
+    * @access public
+    */
+    function sms () {
+        if ($this->use_ssl) {
+            $this->base   = "http://api.clickatell.com/http";
+            $this->base_s = "https://api.clickatell.com/http";
+        } else {
+            $this->base   = "http://api.clickatell.com/http";
+            $this->base_s = $this->base;
+        }
+
+        $this->_auth();
+    }
+
+    /**
+    * Authenticate SMS gateway
+    * @return mixed  "OK" or script die
+    * @access private
+    */
+    function _auth() {
+    	$comm = sprintf ("%s/auth?api_id=%s&user=%s&password=%s", $this->base_s, $this->api_id, $this->user, $this->password);
+        $this->session = $this->_parse_auth ($this->_execgw($comm));
+    }
+
+    /**
+    * Query SMS credis balance
+    * @return integer  number of SMS credits
+    * @access public
+    */
+    function getbalance() {
+    	$comm = sprintf ("%s/getbalance?session_id=%s", $this->base, $this->session);
+        return $this->_parse_getbalance ($this->_execgw($comm));
+    }
+
+    /**
+    * Send SMS message
+    * @param to mixed  The destination address.
+    * @param from mixed  The source/sender address
+    * @param text mixed  The text content of the message
+    * @return mixed  "OK" or script die
+    * @access public
+    */
+    function send($to=null, $from=null, $text=null) {
+
+    	/* Check SMS credits balance */
+    	if ($this->getbalance() < $this->balace_limit) {
+    	    return "You have reach the SMS credit limit!";
+    	};
+
+    	/* Check SMS $text length */
+        if ($this->unicode == true) {
+            $this->_chk_mbstring();
+            if (mb_strlen ($text) > 210) {
+        	    return Kohana::lang('libraries.clickatell_unicode_message_too_long').mb_strlen ($text).")";
+        	}
+        	/* Does message need to be concatenate */
+            if (mb_strlen ($text) > 70) {
+                $concat = "&concat=3";
+        	} else {
+                $concat = "";
+            }
+        } else {
+            if (strlen ($text) > 459) {
+    	        return Kohana::lang('libraries.clickatell_message_too_long').strlen ($text).")";
+    	    }
+        	/* Does message need to be concatenate */
+            if (strlen ($text) > 160) {
+                $concat = "&concat=3";
+        	} else {
+                $concat = "";
+            }
+        }
+
+    	/* Check $to and $from is not empty */
+        if (empty ($to)) {
+    	    return Kohana::lang('libraries.clickatell_no_destination_address');
+    	}
+        if (empty ($from)) {
+    	    return Kohana::lang('libraries.clickatell_no_sender_address');
+    	}
+
+    	/* Reformat $to number */
+        $cleanup_chr = array ("+", " ", "(", ")", "\r", "\n", "\r\n");
+        $to = str_replace($cleanup_chr, "", $to);
+
+    	/* Send SMS now */
+    	$comm = sprintf ("%s/sendmsg?session_id=%s&to=%s&from=%s&text=%s&callback=%s&unicode=%s%s&mo=1",
+            $this->base,
+            $this->session,
+            rawurlencode($to),
+            rawurlencode($from),
+            $this->encode_message($text),
+            $this->callback,
+            $this->unicode,
+            $concat
+        );
+        return $this->_parse_send ($this->_execgw($comm));
+    }
+
+    /**
+    * Encode message text according to required standard
+    * @param text mixed  Input text of message.
+    * @return mixed  Return encoded text of message.
+    * @access public
+    */
+    function encode_message ($text) {
+        if ($this->unicode != true) {
+            //standard encoding
+            return rawurlencode($text);
+        } else {
+            //unicode encoding
+            $uni_text_len = mb_strlen ($text, "UTF-8");
+            $out_text = "";
+
+            //encode each character in text
+            for ($i=0; $i<$uni_text_len; $i++) {
+                $out_text .= $this->uniord(mb_substr ($text, $i, 1, "UTF-8"));
+            }
+
+            return $out_text;
+        }
+    }
+
+    /**
+    * Unicode function replacement for ord()
+    * @param c mixed  Unicode character.
+    * @return mixed  Return HEX value (with leading zero) of unicode character.
+    * @access public
+    */
+    function uniord($c) {
+        $ud = 0;
+        if (ord($c{0})>=0 && ord($c{0})<=127)
+            $ud = ord($c{0});
+        if (ord($c{0})>=192 && ord($c{0})<=223)
+            $ud = (ord($c{0})-192)*64 + (ord($c{1})-128);
+        if (ord($c{0})>=224 && ord($c{0})<=239)
+            $ud = (ord($c{0})-224)*4096 + (ord($c{1})-128)*64 + (ord($c{2})-128);
+        if (ord($c{0})>=240 && ord($c{0})<=247)
+            $ud = (ord($c{0})-240)*262144 + (ord($c{1})-128)*4096 + (ord($c{2})-128)*64 + (ord($c{3})-128);
+        if (ord($c{0})>=248 && ord($c{0})<=251)
+            $ud = (ord($c{0})-248)*16777216 + (ord($c{1})-128)*262144 + (ord($c{2})-128)*4096 + (ord($c{3})-128)*64 + (ord($c{4})-128);
+        if (ord($c{0})>=252 && ord($c{0})<=253)
+            $ud = (ord($c{0})-252)*1073741824 + (ord($c{1})-128)*16777216 + (ord($c{2})-128)*262144 + (ord($c{3})-128)*4096 + (ord($c{4})-128)*64 + (ord($c{5})-128);
+        if (ord($c{0})>=254 && ord($c{0})<=255) //error
+            $ud = false;
+        return sprintf("%04x", $ud);
+    }
+
+    /**
+    * Spend voucher with sms credits
+    * @param token mixed  The 16 character voucher number.
+    * @return mixed  Status code
+    * @access public
+    */
+    function token_pay ($token) {
+        $comm = sprintf ("%s/http/token_pay?session_id=%s&token=%s",
+        $this->base,
+        $this->session,
+        $token);
+
+        return $this->_execgw($comm);
+    }
+
+    /**
+    * Execute gateway commands
+    * @access private
+    */
+    function _execgw($command) {
+        if ($this->sending_method == "curl")
+            return $this->_curl($command);
+        if ($this->sending_method == "fopen")
+            return $this->_fopen($command);
+        return Kohana::lang('libraries.clickatell_unsupported_method');
+    }
+
+    /**
+    * CURL sending method
+    * @access private
+    */
+    function _curl($command) {
+        $this->_chk_curl();
+        $ch = curl_init ($command);
+        curl_setopt ($ch, CURLOPT_HEADER, 0);
+        curl_setopt ($ch, CURLOPT_RETURNTRANSFER,1);
+        curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER,0);
+        if ($this->curl_use_proxy) {
+            curl_setopt ($ch, CURLOPT_PROXY, $this->curl_proxy);
+            curl_setopt ($ch, CURLOPT_PROXYUSERPWD, $this->curl_proxyuserpwd);
+        }
+        $result=curl_exec ($ch);
+        curl_close ($ch);
+        return $result;
+    }
+
+    /**
+    * fopen sending method
+    * @access private
+    */
+    function _fopen($command) {
+        $result = '';
+        $handler = @fopen ($command, 'r');
+        if ($handler) {
+            while ($line = @fgets($handler,1024)) {
+                $result .= $line;
+            }
+            fclose ($handler);
+            return $result;
+        } else {
+            return Kohana::lang('libraries.clickatell_fopen_error');
+        }
+    }
+
+    /**
+    * Parse authentication command response text
+    * @access private
+    */
+    function _parse_auth ($result) {
+    	$session = substr($result, 4);
+        $code = substr($result, 0, 2);
+        if ($code!="OK") {
+            return "Error in SMS authorization! ($result)";
+        }
+        return $session;
+    }
+
+    /**
+    * Parse send command response text
+    * @access private
+    */
+    function _parse_send ($result) {
+    	$code = substr($result, 0, 2);
+    	if ($code!="ID") {
+    	    return "Error sending SMS! ($result)";
+    	} else {
+    	    $code = "OK";
+    	}
+        return $code;
+    }
+
+    /**
+    * Parse getbalance command response text
+    * @access private
+    */
+    function _parse_getbalance ($result) {
+    	$result = substr($result, 8);
+        return (int)$result;
+    }
+
+    /**
+    * Check for CURL PHP module
+    * @access private
+    */
+    function _chk_curl() {
+        if (!extension_loaded('curl')) {
+            return "This SMS API class can not work without CURL PHP module! Try using fopen sending method.";
+        }
+    }
+
+    /**
+    * Check for Multibyte String Functions PHP module - mbstring
+    * @access private
+    */
+    function _chk_mbstring() {
+        if (!extension_loaded('mbstring')) {
+            return "Error. This SMS API class is setup to use Multibyte String Functions module - mbstring, but module not found. Please try to set unicode=false in class or install mbstring module into PHP.";
+        }
+    }
+
+}
diff --git a/plugins/clickatell/libraries/Clickatell_Install.php b/plugins/clickatell/libraries/Clickatell_Install.php
new file mode 100644
index 0000000..97d9766
--- /dev/null
+++ b/plugins/clickatell/libraries/Clickatell_Install.php
@@ -0,0 +1,51 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Performs install/uninstall methods for the Clickatell Plugin
+ *
+ * @package    Ushahidi
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class Clickatell_Install {
+	
+	/**
+	 * Constructor to load the shared database library
+	 */
+	public function __construct()
+	{
+		$this->db =  new Database();
+	}
+
+	/**
+	 * Creates the required columns for the Clickatell Plugin
+	 */
+	public function run_install()
+	{
+		
+		// ****************************************
+		// DATABASE STUFF
+		$this->db->query("
+			CREATE TABLE IF NOT EXISTS `".Kohana::config('database.default.table_prefix')."clickatell`
+			(
+				id int(11) unsigned NOT NULL AUTO_INCREMENT,
+				clickatell_key varchar(100) DEFAULT NULL,
+				clickatell_api varchar(100) DEFAULT NULL,
+				clickatell_username varchar(100) DEFAULT NULL,
+				clickatell_password varchar(100) DEFAULT NULL,
+				PRIMARY KEY (`id`)
+			);
+		");
+		// ****************************************
+	}
+
+	/**
+	 * Drops the Clickatell Tables
+	 */
+	public function uninstall()
+	{
+		$this->db->query("
+			DROP TABLE ".Kohana::config('database.default.table_prefix')."clickatell;
+			");
+	}
+}
\ No newline at end of file
diff --git a/plugins/clickatell/libraries/Clickatell_Sms_Provider.php b/plugins/clickatell/libraries/Clickatell_Sms_Provider.php
new file mode 100644
index 0000000..c99fc36
--- /dev/null
+++ b/plugins/clickatell/libraries/Clickatell_Sms_Provider.php
@@ -0,0 +1,44 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * Helper library for the Clickatell API
+ *
+ * @package     Clickatell SMS 
+ * @category    Plugins
+ * @author      Ushahidi Team
+ * @copyright   (c) 2008-2011 Ushahidi Team
+ * @license     http://www.gnu.org/copyleft/lesser.html GNU Less Public General License (LGPL)
+ */
+class Clickatell_Sms_Provider implements Sms_Provider_Core {
+	
+	/**
+	 * Sends a text message (SMS) using the Clickatell API
+	 *
+	 * @param string $to
+	 * @param string $from
+	 * @param string $to
+	 */
+	public function send($to = NULL, $from = NULL, $message = NULL)
+	{
+		// Get Current Clickatell Settings
+		$clickatell = ORM::factory("clickatell", 1)->find();
+		
+		if ($clickatell->loaded)
+		{
+			// Create Clickatell Object
+			$new_sms = new Clickatell_API();
+			$new_sms->api_id = $clickatell->clickatell_api;
+			$new_sms->user = $clickatell->clickatell_username;
+			$new_sms->password = $clickatell->clickatell_password;
+			$new_sms->use_ssl = Kohana::config('core.site_protocol') == 'https';
+			$new_sms->sms();
+			$response = $new_sms->send($to, $from, $message);
+			
+			// Message Went Through??
+			return ($response == "OK")? TRUE : $response;
+		}
+		
+		return "Clickatell Is Not Set Up!";
+	}
+	
+}
\ No newline at end of file
diff --git a/plugins/clickatell/models/clickatell.php b/plugins/clickatell/models/clickatell.php
new file mode 100644
index 0000000..17842bd
--- /dev/null
+++ b/plugins/clickatell/models/clickatell.php
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Clickatell Model
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Clickatell Model  
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Clickatell_Model extends ORM
+{
+	
+	// Database table name
+	protected $table_name = 'clickatell';
+}
diff --git a/plugins/clickatell/readme.txt b/plugins/clickatell/readme.txt
new file mode 100644
index 0000000..a9fa744
--- /dev/null
+++ b/plugins/clickatell/readme.txt
@@ -0,0 +1,19 @@
+=== About ===
+name: Clickatell
+website: http://www.ushahidi.com
+description: Send and Receive Text Messages Using Clickatell
+version: 0.5
+requires: 2.0
+tested up to: 2.0
+author: David Kobia
+author website: http://www.dkfactor.com
+
+== Description ==
+Send and Receive Text Messages Using Clickatell.
+
+== Installation ==
+1. Copy the entire /clickatell/ directory into your /plugins/ directory.
+2. Activate the plugin.
+3. Click on the [settings] link to add your clickatell information
+
+== Changelog ==
\ No newline at end of file
diff --git a/plugins/clickatell/views/clickatell/admin/clickatell_settings.php b/plugins/clickatell/views/clickatell/admin/clickatell_settings.php
new file mode 100644
index 0000000..e281387
--- /dev/null
+++ b/plugins/clickatell/views/clickatell/admin/clickatell_settings.php
@@ -0,0 +1,55 @@
+<table style="width: 630px;" class="my_table">
+	<tr>
+		<td style="width:60px;">
+			<span class="big_blue_span"><?php echo Kohana::lang('ui_main.step');?> 1:</span>
+		</td>
+		<td>
+			<h4 class="fix"><?php echo Kohana::lang('settings.sms.clickatell_text_1');?>. <sup><a href="#">?</a></sup></h4>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			<span class="big_blue_span"><?php echo Kohana::lang('ui_main.step');?> 2:</span>
+		</td>
+		<td>
+			<h4 class="fix"><?php echo Kohana::lang('settings.sms.clickatell_text_2');?>. <sup><a href="#">?</a></sup></h4>
+			<div class="row">
+				<h4><?php echo Kohana::lang('settings.sms.clickatell_api');?>:</h4>
+				<?php print form::input('clickatell_api', $form['clickatell_api'], ' class="text title_2"'); ?>
+			</div>
+			<div class="row">
+				<h4><?php echo Kohana::lang('settings.sms.clickatell_username');?>:</h4>
+				<?php print form::input('clickatell_username', $form['clickatell_username'], ' class="text title_2"'); ?>
+			</div>
+			<div class="row">
+				<h4><?php echo Kohana::lang('settings.sms.clickatell_password');?>:</h4>
+				<?php print form::password('clickatell_password', $form['clickatell_password'], ' class="text title_2"'); ?>
+			</div>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			<span class="big_blue_span">Step 3:</span>
+		</td>
+		<td>
+			<h4 class="fix"><?php echo Kohana::lang('settings.sms.clickatell_check_balance');?>. <sup><a href="#">?</a></sup></h4>
+			<div class="row">
+				<h4><a href="javascript:clickatellBalance()"><?php echo Kohana::lang('settings.sms.clickatell_load_balance');?></a> <span id="balance_loading"></span></h4>
+			</div>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			<span class="big_blue_span"><?php echo Kohana::lang('ui_main.step');?> 4:</span>
+		</td>
+		<td>
+			<h4 class="fix"><a href="#" class="tooltip" title="">Working with Clickatell 2-Way</a></h4>
+			<p>
+				If you sign up for Clickatell 2-Way service they will ask you for a 'Primary Callback URL'. Use the URL below as the 'Target Address' and select 'HTTP POST' from the drop down.
+			</p>
+			<p class="sync_key">
+				<span><?php echo $clickatell_link; ?></span>
+			</p>
+		</td>
+	</tr>								
+</table>
\ No newline at end of file
diff --git a/plugins/clickatell/views/clickatell/admin/clickatell_settings_js.php b/plugins/clickatell/views/clickatell/admin/clickatell_settings_js.php
new file mode 100644
index 0000000..a70a57f
--- /dev/null
+++ b/plugins/clickatell/views/clickatell/admin/clickatell_settings_js.php
@@ -0,0 +1,9 @@
+// Retrieve Clickatell Balance (AJAX)
+function clickatellBalance()
+{
+	$('#balance_loading').html('<img src="<?php echo url::base() . "media/img/loading_g.gif"; ?>">');
+	$.get("<?php echo url::site() . 'admin/clickatell_settings/smsbalance/' ?>", function(data){
+		alert("RESPONSE: " + data);
+		$('#balance_loading').html('');
+	});
+}
\ No newline at end of file
diff --git a/plugins/frontlinesms/controllers/admin/frontlinesms_settings.php b/plugins/frontlinesms/controllers/admin/frontlinesms_settings.php
new file mode 100644
index 0000000..ae28732
--- /dev/null
+++ b/plugins/frontlinesms/controllers/admin/frontlinesms_settings.php
@@ -0,0 +1,46 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * FrontlineSMS Settings Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Clickatell Settings Controller	
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+* 
+*/
+
+class Frontlinesms_Settings_Controller extends Admin_Controller {
+	public function index()
+	{
+		$this->template->this_page = 'addons';
+		
+		// Standard Settings View
+		$this->template->content = new View("admin/addons/plugin_settings");
+		$this->template->content->title = "FrontlineSMS Settings";
+		
+		// Settings Form View
+		$this->template->content->settings_form = new View("frontlinesms/admin/frontlinesms_settings");
+		
+		// Do we have a frontlineSMS Key? If not create and save one on the fly
+        $frontlinesms = ORM::factory('frontlinesms', 1);
+		
+		if ($frontlinesms->loaded AND $frontlinesms->frontlinesms_key)
+		{
+			$frontlinesms_key = $frontlinesms->frontlinesms_key;
+		}
+		else
+		{
+			$frontlinesms_key = strtoupper(text::random('alnum',8));
+            $frontlinesms->frontlinesms_key = $frontlinesms_key;
+            $frontlinesms->save();
+		}
+
+		$this->template->content->settings_form->frontlinesms_key = $frontlinesms_key;
+		$this->template->content->settings_form->frontlinesms_link = url::site()."frontlinesms/?key=".$frontlinesms_key."&s=\${sender_number}&m=\${message_content}";
+	}
+}
\ No newline at end of file
diff --git a/plugins/frontlinesms/controllers/frontlinesms.php b/plugins/frontlinesms/controllers/frontlinesms.php
new file mode 100644
index 0000000..0e9ab0a
--- /dev/null
+++ b/plugins/frontlinesms/controllers/frontlinesms.php
@@ -0,0 +1,52 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+* FrontlineSMS HTTP Post Controller
+* Gets HTTP Post data from a FrontlineSMS Installation
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module	   FrontlineSMS Controller	
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+*/
+
+class Frontlinesms_Controller extends Controller
+{
+	function index()
+	{
+		if (isset($_GET['key']))
+		{
+			$frontlinesms_key = $_GET['key'];
+		}
+		
+		if (isset($_GET['s']))
+		{
+			$message_from = $_GET['s'];
+			// Remove non-numeric characters from string
+			$message_from = preg_replace("#[^0-9]#", "", $message_from);
+		}
+		
+		if (isset($_GET['m']))
+		{
+			$message_description = $_GET['m'];
+		}
+		
+		if ( ! empty($frontlinesms_key) AND ! empty($message_from) AND ! empty($message_description))
+		{
+			
+			// Is this a valid FrontlineSMS Key?
+			$keycheck = ORM::factory('frontlinesms')
+				->where('frontlinesms_key', $frontlinesms_key)
+				->find(1);
+
+			if ($keycheck->loaded == TRUE)
+			{
+					sms::add($message_from, $message_description);
+			}
+		}
+	}
+}
diff --git a/plugins/frontlinesms/hooks/frontlinesms.php b/plugins/frontlinesms/hooks/frontlinesms.php
new file mode 100644
index 0000000..9c010f6
--- /dev/null
+++ b/plugins/frontlinesms/hooks/frontlinesms.php
@@ -0,0 +1,36 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * FrontlineSMS Hook
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class frontlinesms {
+	
+	/**
+	 * Registers the main event add method
+	 */
+	public function __construct()
+	{	
+		// Hook into routing
+		Event::add('system.pre_controller', array($this, 'add'));
+	}
+	
+	/**
+	 * Adds all the events to the main Ushahidi application
+	 */
+	public function add()
+	{
+		// SMS Provider
+		plugin::add_sms_provider("frontlinesms");
+	}
+}
+
+new frontlinesms;
diff --git a/plugins/frontlinesms/libraries/Frontlinesms_Install.php b/plugins/frontlinesms/libraries/Frontlinesms_Install.php
new file mode 100644
index 0000000..147a4e9
--- /dev/null
+++ b/plugins/frontlinesms/libraries/Frontlinesms_Install.php
@@ -0,0 +1,48 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Performs install/uninstall methods for the FrontlineSMS Plugin
+ *
+ * @package    Ushahidi
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class Frontlinesms_Install {
+	
+	/**
+	 * Constructor to load the shared database library
+	 */
+	public function __construct()
+	{
+		$this->db =  new Database();
+	}
+
+	/**
+	 * Creates the required columns for the FrontlineSMS Plugin
+	 */
+	public function run_install()
+	{
+		
+		// ****************************************
+		// DATABASE STUFF
+		$this->db->query("
+			CREATE TABLE IF NOT EXISTS `".Kohana::config('database.default.table_prefix')."frontlinesms`
+			(
+				id int(11) unsigned NOT NULL AUTO_INCREMENT,
+				frontlinesms_key varchar(100) DEFAULT NULL,
+				PRIMARY KEY (`id`)
+			);
+		");
+		// ****************************************
+	}
+
+	/**
+	 * Drops the FrontlineSMS Tables
+	 */
+	public function uninstall()
+	{
+		$this->db->query("
+			DROP TABLE ".Kohana::config('database.default.table_prefix')."frontlinesms;
+			");
+	}
+}
\ No newline at end of file
diff --git a/plugins/frontlinesms/models/frontlinesms.php b/plugins/frontlinesms/models/frontlinesms.php
new file mode 100644
index 0000000..31f0fee
--- /dev/null
+++ b/plugins/frontlinesms/models/frontlinesms.php
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * FrontlineSMS Model
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     FrontlineSMS Model  
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Frontlinesms_Model extends ORM
+{
+	
+	// Database table name
+	protected $table_name = 'frontlinesms';
+}
diff --git a/plugins/frontlinesms/readme.txt b/plugins/frontlinesms/readme.txt
new file mode 100644
index 0000000..19d2436
--- /dev/null
+++ b/plugins/frontlinesms/readme.txt
@@ -0,0 +1,19 @@
+=== About ===
+name: FrontlineSMS
+website: http://www.frontlinesms.com
+description: Receive Messages from a FrontlineSMS Installations
+version: 0.5
+requires: 2.0
+tested up to: 2.0
+author: David Kobia
+author website: http://www.dkfactor.com
+
+== Description ==
+Receive Messages from a FrontlineSMS Installations.
+
+== Installation ==
+1. Copy the entire /frontlinesms/ directory into your /plugins/ directory.
+2. Activate the plugin.
+3. Click on the [settings] link to get your FrontlineSMS URL
+
+== Changelog ==
\ No newline at end of file
diff --git a/plugins/frontlinesms/views/frontlinesms/admin/frontlinesms_settings.php b/plugins/frontlinesms/views/frontlinesms/admin/frontlinesms_settings.php
new file mode 100644
index 0000000..8b39c49
--- /dev/null
+++ b/plugins/frontlinesms/views/frontlinesms/admin/frontlinesms_settings.php
@@ -0,0 +1,31 @@
+<table style="width: 630px;" class="my_table">
+	<tr>
+		<td style="width:60px;">
+			<span class="big_blue_span"><?php echo Kohana::lang('ui_main.step');?> 1:</span>
+		</td>
+		<td>
+			<h4 class="fix"><?php echo Kohana::lang('settings.sms.flsms_download');?></h4>
+			<p>
+				<?php echo Kohana::lang('settings.sms.flsms_description');?>
+			</p>
+			<a href="http://www.frontlinesms.com/the-software/download/" class="no_border">
+				<img src="<?php echo url::base() ?>media/img/admin/download_frontline_engine.gif" />
+			</a>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			<span class="big_blue_span"><?php echo Kohana::lang('ui_main.step');?> 2:</span>
+		</td>
+		<td>
+			<h4 class="fix"><?php echo Kohana::lang('settings.sms.flsms_synchronize');?></h4>
+			<p>
+				<?php echo Kohana::lang('settings.sms.flsms_instructions');?>
+			</p>
+			<p class="sync_key">
+				<?php echo Kohana::lang('settings.sms.flsms_link');?>:<br /><span><?php echo $frontlinesms_link; ?></span><br /><br />
+				<?php echo Kohana::lang('settings.sms.flsms_key');?>:<br /><span><?php echo $frontlinesms_key; ?></span>
+			</p>
+		</td>
+	</tr>	
+</table>
diff --git a/plugins/sharing/controllers/admin/manage/sharing.php b/plugins/sharing/controllers/admin/manage/sharing.php
new file mode 100644
index 0000000..9584826
--- /dev/null
+++ b/plugins/sharing/controllers/admin/manage/sharing.php
@@ -0,0 +1,210 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Sharing Controller
+ * Add/Edit Ushahidi Instance Shares
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Admin
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Sharing_Controller extends Admin_Controller
+{
+
+	function __construct()
+	{
+		parent::__construct();
+		$this->template->this_page = 'settings';
+		
+		// If user doesn't have access, redirect to dashboard
+		if ( ! $this->auth->has_permission("manage"))
+		{
+			url::redirect(url::site().'admin/dashboard');
+		}
+	}
+	
+	
+	function index()
+	{
+		$this->template->content = new View('admin/manage/sharing/main');
+		$this->template->content->title = Kohana::lang('ui_admin.settings');
+		
+		// What to display
+		if (isset($_GET['status']) && !empty($_GET['status']))
+		{
+			$status = $_GET['status'];
+			
+			if (strtolower($status) == 's')
+			{
+				$filter = 'sharing_type = 2';
+			}
+			elseif (strtolower($status) == 'r')
+			{
+				$filter = 'sharing_type = 1';
+			}
+			else
+			{
+				$status = "0";
+				$filter = '1=1';
+			}
+		}
+		else
+		{
+			$status = "0";
+			$filter = "1=1";
+		}	
+		
+		// Setup and initialize form field names
+		$form = array
+	    (
+			'sharing_name' => '',
+			'sharing_url' => '',
+			'sharing_color' => '',
+			'sharing_active' => ''
+	    );
+		//  Copy the form as errors, so the errors will be stored with keys corresponding to the form field names
+	    $errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+		$form_action = "";
+		$sharing_id = "";
+		
+		
+		if( $_POST ) 
+		{
+			$post = Validation::factory($_POST);
+			
+			 //  Add some filters
+			$post->pre_filter('trim', TRUE);
+	
+			// Add Action
+			if ($post->action == 'a')
+			{
+				// Clean the url before we do anything else
+				$post->sharing_url = sharing_helper::clean_url($post->sharing_url);
+				// Add some rules, the input field, followed by a list of checks, carried out in order
+				$post->add_rules('sharing_name','required', 'length[3,150]');
+				$post->add_rules('sharing_url','required', 'url', 'length[3,255]');
+				$post->add_rules('sharing_color','required', 'length[6,6]');
+				$post->add_callbacks('sharing_url', array($this,'url_exists_chk'));
+			}
+			
+			if( $post->validate() )
+			{
+				$sharing_id = $post->sharing_id;
+				
+				$sharing = new Sharing_Model($sharing_id);
+				
+				// Delete Action
+				if ( $post->action == 'd' )
+				{ 
+					$sharing->delete( $sharing_id );
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.deleted'));
+				}
+				
+				// Hide Action
+				else if ($post->action=='h')
+				{
+					if($sharing->loaded)
+					{
+						$sharing->sharing_active = 0;
+						$sharing->save();
+						$form_saved = TRUE;
+						$form_action = utf8::strtoupper(Kohana::lang('ui_main.hidden'));
+					}	
+				}
+				
+				// Show Action
+				else if ($post->action == 'v')
+				{ 
+					if ($sharing->loaded)
+					{
+						$sharing->sharing_active = 1;
+						$sharing->save();
+						$form_saved = TRUE;
+						$form_action = utf8::strtoupper(Kohana::lang('ui_admin.shown'));
+					}
+				}
+				
+				// Save Action
+				// Must check for action here otherwise passing no action param would also means no validation
+				// See validation code above.
+				elseif ($post->action == 'a')
+				{ 
+					$sharing->sharing_name = $post->sharing_name;
+					$sharing->sharing_url = $post->sharing_url;
+					$sharing->sharing_color = $post->sharing_color;
+					$sharing->save();
+					
+					$form_saved = TRUE;
+					$form_action = utf8::strtoupper(Kohana::lang('ui_admin.created_edited'));
+				}
+				
+			}
+			else
+			{
+				// Repopulate the form fields
+				$form = arr::overwrite($form, $post->as_array());
+
+				// Populate the error fields, if any
+				$errors = arr::overwrite($errors, $post->errors('sharing'));
+				$form_error = TRUE;
+			}
+		}
+		
+		// Pagination
+		$pagination = new Pagination(array(
+			'query_string' => 'page',
+			'items_per_page' => $this->items_per_page,
+			'total_items'  => ORM::factory('sharing')->where($filter)->count_all()
+		));
+		
+		$shares = ORM::factory('sharing')
+			->where($filter)
+			->orderby('sharing_name', 'asc')
+			->find_all($this->items_per_page,  $pagination->sql_offset);
+		
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+		$this->template->content->form_action = $form_action;
+		$this->template->content->pagination = $pagination;
+		$this->template->content->total_items = $pagination->total_items;
+		$this->template->content->shares = $shares;
+		$this->template->content->errors = $errors;
+		
+		// Status Tab
+		$this->template->content->status = $status;
+		
+		// Javascript Header
+		$this->template->colorpicker_enabled = TRUE;
+		$this->template->js = new View('admin/manage/sharing/sharing_js');
+	}
+	
+	
+	/**
+	 * Checks if url already exists.
+     * @param Validation $post $_POST variable with validation rules 
+	 */
+	public function url_exists_chk(Validation $post)
+	{
+		// If add->rules validation found any errors, get me out of here!
+		if (array_key_exists('sharing_url', $post->errors()))
+			return;
+		
+		$share_exists = ORM::factory('sharing')
+			->where('sharing_url', $post->sharing_url)
+			->find();
+		
+		if ($share_exists->loaded)
+		{
+			$post->add_error( 'sharing_url', 'exists');
+		}
+	}
+}
diff --git a/plugins/sharing/controllers/json/share.php b/plugins/sharing/controllers/json/share.php
new file mode 100644
index 0000000..bfb67b4
--- /dev/null
+++ b/plugins/sharing/controllers/json/share.php
@@ -0,0 +1,236 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Json Controller
+ * Generates Map GeoJSON File
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @subpackage Controllers
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+
+class Share_Controller extends Json_Controller {
+
+	/**
+	 * Read in new layer JSON from shared connection
+	 * @param int $sharing_id - ID of the new Share Layer
+	 */
+	public function index( $sharing_id = FALSE )
+	{
+		$json = '';
+		$json_item = array();
+		$json_features = array();
+		$sharing_data = "";
+		$clustering = Kohana::config('settings.allow_clustering');
+		
+		if ($sharing_id)
+		{
+			// Get This Sharing ID Color
+			$sharing = ORM::factory('sharing')
+				->find($sharing_id);
+
+			if( ! $sharing->loaded)
+				throw new Kohana_404_Exception();
+
+			$sharing_url = sharing_helper::clean_url($sharing->sharing_url);
+			$sharing_color = $sharing->sharing_color;
+			
+			if ($clustering)
+			{
+				// Database
+				$db = new Database();
+				
+				// Start Date
+				$start_date = (isset($_GET['s']) AND !empty($_GET['s'])) ?
+					(int) $_GET['s'] : "0";
+
+				// End Date
+				$end_date = (isset($_GET['e']) AND !empty($_GET['e'])) ?
+					(int) $_GET['e'] : "0";
+
+				// SouthWest Bound
+				$southwest = (isset($_GET['sw']) AND !empty($_GET['sw'])) ?
+					$_GET['sw'] : "0";
+
+				$northeast = (isset($_GET['ne']) AND !empty($_GET['ne'])) ?
+					$_GET['ne'] : "0";
+				
+				// Get Zoom Level
+				$zoomLevel = (isset($_GET['z']) AND !empty($_GET['z'])) ?
+					(int) $_GET['z'] : 8;
+
+				//$distance = 60;
+				$distance = (10000000 >> $zoomLevel) / 100000;
+				
+				$filter = "";
+				$filter .= ($start_date) ? 
+					" AND incident_date >= '" . date("Y-m-d H:i:s", $start_date) . "'" : "";
+				$filter .= ($end_date) ? 
+					" AND incident_date <= '" . date("Y-m-d H:i:s", $end_date) . "'" : "";
+
+				if ($southwest AND $northeast)
+				{
+					list($latitude_min, $longitude_min) = explode(',', $southwest);
+					list($latitude_max, $longitude_max) = explode(',', $northeast);
+
+					$filter .= " AND latitude >=".(float) $latitude_min.
+						" AND latitude <=".(float) $latitude_max;
+					$filter .= " AND longitude >=".(float) $longitude_min.
+						" AND longitude <=".(float) $longitude_max;
+				}
+				
+				$filter .= " AND sharing_id = ".(int)$sharing_id;
+
+				$query = $db->query("SELECT * FROM `".$this->table_prefix."sharing_incident` WHERE 1=1 $filter ORDER BY incident_id ASC "); 
+
+				$markers = $query->result_array(FALSE);
+
+				$clusters = array();	// Clustered
+				$singles = array();		// Non Clustered
+
+				// Loop until all markers have been compared
+				while (count($markers))
+				{
+					$marker	 = array_pop($markers);
+					$cluster = array();
+
+					// Compare marker against all remaining markers.
+					foreach ($markers as $key => $target)
+					{
+						// This function returns the distance between two markers, at a defined zoom level.
+						// $pixels = $this->_pixelDistance($marker['latitude'], $marker['longitude'], 
+						// $target['latitude'], $target['longitude'], $zoomLevel);
+
+						$pixels = abs($marker['longitude']-$target['longitude']) + 
+							abs($marker['latitude']-$target['latitude']);
+
+						// echo $pixels."<BR>";
+						// If two markers are closer than defined distance, remove compareMarker from array and add to cluster.
+						if ($pixels < $distance)
+						{
+							unset($markers[$key]);
+							$target['distance'] = $pixels;
+							$cluster[] = $target;
+						}
+					}
+
+					// If a marker was added to cluster, also add the marker we were comparing to.
+					if (count($cluster) > 0)
+					{
+						$cluster[] = $marker;
+						$clusters[] = $cluster;
+					}
+					else
+					{
+						$singles[] = $marker;
+					}
+				}
+
+				// Create Json
+				foreach ($clusters as $cluster)
+				{
+					// Calculate cluster center
+					$bounds = $this->calculate_center($cluster);
+					$cluster_center = array_values($bounds['center']);
+					$southwest = $bounds['sw']['longitude'].','.$bounds['sw']['latitude'];
+					$northeast = $bounds['ne']['longitude'].','.$bounds['ne']['latitude'];
+
+					// Number of Items in Cluster
+					$cluster_count = count($cluster);
+					$link = $sharing_url."/reports/index/?c=0&sw=".$southwest."&ne=".$northeast;
+					$item_name = $this->get_title(Kohana::lang('ui_main.reports_count', $cluster_count), $link);
+					
+					$json_item = array();
+					$json_item['type'] = 'Feature';
+					$json_item['properties'] = array(
+						'name' => $item_name,
+						'link' => $link,
+						'category' => array(0),
+						'color' => $sharing_color,
+						'icon' => '',
+						'thumb' => '',
+						'timestamp' => 0,
+						'count' => $cluster_count,
+					);
+					$json_item['geometry'] = array(
+						'type' => 'Point',
+						'coordinates' => $cluster_center
+					);
+
+					array_push($json_features, $json_item);
+				}
+				
+				foreach ($singles as $single)
+				{
+					$link = $sharing_url."/reports/view/".$single['id'];
+					$item_name = $this->get_title(html::strip_tags($single['incident_title']), $link);
+		
+					$json_item = array();
+					$json_item['type'] = 'Feature';
+					$json_item['properties'] = array(
+						'name' => $item_name,
+						'link' => $link,
+						'category' => array(0),
+						'color' => $sharing_color,
+						'icon' => '',
+						'timestamp' => 0,
+						'count' => 1
+					);
+					$json_item['geometry'] = array(
+						'type' => 'Point',
+						'coordinates' => array($single['longitude'],$single['latitude'])
+					);
+
+					array_push($json_features, $json_item);
+				}
+				
+			}
+			else
+			{
+				// Retrieve all markers
+				$markers = ORM::factory('sharing_incident')
+										->where('sharing_id', $sharing_id)
+										->find_all();
+
+				foreach ($markers as $marker)
+				{
+					$link = $sharing_url."/reports/view/".$marker->incident_id;
+					$item_name = $this->get_title($marker->incident_title, $link);
+
+					$json_item = array();
+					$json_item['type'] = 'Feature';
+					$json_item['properties'] = array(
+						'id' => $marker->incident_id,
+						'name' => $item_name,
+						'link' => $link,
+						'color' => $sharing_color,
+						'icon' => '',
+						'timestamp' => strtotime($marker->incident_date)
+					);
+					$json_item['geometry'] = array(
+						'type' => 'Point',
+						'coordinates' => array($marker->longitude, $marker->latitude)
+					);
+
+					array_push($json_features, $json_item);
+				}
+			}
+
+			Event::run('ushahidi_filter.json_share_features', $json_features);
+			
+			$json = json_encode(array(
+				"type" => "FeatureCollection",
+				"features" => $json_features
+			));
+		}
+		
+		header('Content-type: application/json; charset=utf-8');
+		echo $json;
+	}
+
+}
diff --git a/plugins/sharing/controllers/scheduler/s_sharing.php b/plugins/sharing/controllers/scheduler/s_sharing.php
new file mode 100644
index 0000000..545f2cf
--- /dev/null
+++ b/plugins/sharing/controllers/scheduler/s_sharing.php
@@ -0,0 +1,125 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Sharing Scheduler Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Scheduler
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+*/
+
+class S_Sharing_Controller extends Controller {
+
+	public function __construct()
+    {
+        parent::__construct();
+	}
+
+	public function index()
+	{
+		// Get all currently active shares
+		$shares = ORM::factory('sharing')
+			->where('sharing_active', 1)
+			->find_all();
+
+		foreach ($shares as $share)
+		{
+			$sharing_url = $share->sharing_url;
+
+			$this->_parse_json($share->id, $sharing_url);
+		}
+	}
+
+	/**
+	 * Use remote Ushahidi deployments API to get Incident Data
+	 * Limit to 20 not to kill remote server
+	 */
+	private function _parse_json($sharing_id = NULL, $sharing_url = NULL)
+	{
+		if ( ! $sharing_id OR ! $sharing_url)
+		{
+			return false;
+		}
+
+		$timeout = 5;
+		$limit = 20;
+		$since_id = 0;
+		$modified_ids = array(); // this is an array of our primary keys
+		$more_reports_to_pull = TRUE;
+
+		while($more_reports_to_pull == TRUE)
+		{
+			$api_url = "/api?task=incidents&limit=".$limit."&resp=json&orderfield=incidentid&sort=0&by=sinceid&id=".$since_id;
+
+			$request = new HttpClient(sharing_helper::clean_url($sharing_url) . $api_url);
+			$json = $request->execute();
+
+			$all_data = json_decode($json, false);
+			if ( ! $all_data)
+			{
+				return false;
+			}
+
+			if ( ! isset($all_data->payload->incidents))
+			{
+				return false;
+			}
+
+			// Parse Incidents Into Database
+
+			$count = 0;
+			foreach($all_data->payload->incidents as $incident)
+			{
+				// See if this incident already exists so we can edit it
+
+				$item_check = ORM::factory('sharing_incident')
+							->where('sharing_id', $sharing_id)
+							->where('incident_id',$incident->incident->incidentid)
+							->find();
+
+				if ($item_check->loaded==TRUE)
+				{
+					$item = ORM::factory('sharing_incident',$item_check->id);
+				}else{
+					$item = ORM::factory('sharing_incident');
+				}
+				$item->sharing_id = $sharing_id;
+				$item->incident_id = $incident->incident->incidentid;
+				$item->incident_title = $incident->incident->incidenttitle;
+				$item->latitude = $incident->incident->locationlatitude;
+				$item->longitude = $incident->incident->locationlongitude;
+				$item->incident_date = $incident->incident->incidentdate;
+				$item->save();
+
+				// Save the primary key of the row we touched. We will be deleting ones that weren't touched.
+
+				$modified_ids[] = $item->id;
+
+				// Save the highest pulled incident id so we can grab the next set from that id on
+
+				$since_id = $incident->incident->incidentid;
+
+				// Save count so we know if we need to pull any more reports or not
+
+				$count++;
+			}
+
+			if($count < $limit)
+			{
+				$more_reports_to_pull = FALSE;
+			}
+		}
+
+		// Delete the reports that are no longer being displayed on the shared site
+
+		ORM::factory('sharing_incident')
+			->notin('id',$modified_ids)
+			->where('sharing_id', $sharing_id)
+			->delete_all();
+	}
+}
diff --git a/plugins/sharing/helpers/sharing_helper.php b/plugins/sharing/helpers/sharing_helper.php
new file mode 100644
index 0000000..705836f
--- /dev/null
+++ b/plugins/sharing/helpers/sharing_helper.php
@@ -0,0 +1,29 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Sharing helper class.
+ *
+ * @package	   Sharing
+ * @author	   Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license	   http://www.ushahidi.com/license.html
+ */
+class sharing_helper_Core {
+	/**
+    * Clean Urls
+		* We want to standardize urls to prevent duplication
+    */
+	public static function clean_url($url)
+	{
+		// Assume http if not included (but don't remove http since it could be https)
+		if (stripos($url, 'http') === FALSE)
+		{
+			$url = 'http://'.$url;
+		}
+
+		// Remove trailing slash/s
+		$url = preg_replace('{/$}', '', $url);
+
+		return $url;
+	}
+}
+	
\ No newline at end of file
diff --git a/plugins/sharing/hooks/sharing.php b/plugins/sharing/hooks/sharing.php
new file mode 100644
index 0000000..d64aac5
--- /dev/null
+++ b/plugins/sharing/hooks/sharing.php
@@ -0,0 +1,81 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Sharing Hook - Load All Events
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class sharing {
+	
+	/**
+	 * Registers the main event add method
+	 */
+
+	protected $user;
+
+	public function __construct()
+	{
+		// Hook into routing
+		Event::add('system.pre_controller', array($this, 'add'));
+	}
+	
+	/**
+	 * Adds all the events to the main Ushahidi application
+	 */
+	public function add()
+	{
+		// Only add the events if we are on that controller
+		if (Router::$controller == 'manage' or Router::$controller == 'sharing')
+		{
+			Event::add('ushahidi_action.nav_admin_manage', array($this,'_sharing'));
+		}
+		elseif (strripos(Router::$current_uri, "main") !== false)
+		{
+			Event::add('ushahidi_action.header_scripts', array($this, 'sharing_js'));
+			Event::add('ushahidi_action.main_sidebar', array($this, 'sharing_bar'));
+		}
+	}
+
+	public function _sharing()
+	{
+		$this_sub_page = Event::$data;
+		echo ($this_sub_page == "sharing") ? "Sharing" : "<a href=\"".url::site()."admin/manage/sharing\">Sharing</a>";
+	}
+
+	/**
+	 * Loads the sharing bar on the side bar on the main page
+	 */
+	public function sharing_bar()
+	{
+		// Get all active Shares
+		$shares = array();
+		foreach (ORM::factory('sharing')
+					->where('sharing_active', 1)
+					->find_all() as $share)
+		{
+			$shares[$share->id] = array($share->sharing_name, $share->sharing_color);
+		}
+
+		$sharing_bar = View::factory('sharing/sharing_bar');
+
+		$sharing_bar->shares = $shares;
+		$sharing_bar->render(TRUE);
+	}
+	
+	/**
+	 * Loads the JavaScript for the sharing sidebar
+	 */
+	public function sharing_js()
+	{
+		$js = View::factory('js/sharing_bar_js');
+		$js->render(TRUE);
+	}
+}
+new sharing;
diff --git a/plugins/sharing/i18n/en_US/sharing.php b/plugins/sharing/i18n/en_US/sharing.php
new file mode 100755
index 0000000..a23fe19
--- /dev/null
+++ b/plugins/sharing/i18n/en_US/sharing.php
@@ -0,0 +1,20 @@
+<?php
+	$lang = array(
+	'date' => 'Ingress date',
+	'date_added' => 'Date Added',
+	'last_access' => 'Last Access',
+	'sharing_color' => array(
+		'length' => 'The color field must be 6 characters long.',
+		'required' => 'The color field is required.',
+	),
+	'sharing_name' => array(
+		'length' => 'The Sharing name does not appear to be valid.',
+		'required' => 'A Sharing name is required.',
+	),
+	'sharing_url' => array(
+		'exists' => 'The site URL already exists.',
+		'length' => 'The site URL does not appear to be valid.',
+		'required' => 'The site URL is required.',
+		'url' => 'The site URL field does not appear to contain a valid URL.',
+	));
+?>
diff --git a/plugins/sharing/libraries/sharing_install.php b/plugins/sharing/libraries/sharing_install.php
new file mode 100644
index 0000000..52278e8
--- /dev/null
+++ b/plugins/sharing/libraries/sharing_install.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * Performs install/uninstall methods for the Sharing Plugin
+ *
+ * @package    Ushahidi
+ * @author     Ushahidi Team
+ * @copyright  (c) 2008 Ushahidi Team
+ * @license    http://www.ushahidi.com/license.html
+ */
+class Sharing_Install {
+
+	/**
+	 * Constructor to load the shared database library
+	 */
+	public function __construct()
+	{
+		$this->db =  new Database();
+	}
+
+	/**
+	 * Creates the required database tables for the sharing module
+	 */
+	public function run_install()
+	{
+		// Create the database tables
+		// Include the table_prefix
+		$this->db->query("
+			CREATE TABLE IF NOT EXISTS `".Kohana::config('database.default.table_prefix')."sharing`
+			(
+				`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+				`sharing_name` varchar(150) NOT NULL COMMENT 'name that appears on the front end',
+				`sharing_url` varchar(255) NOT NULL COMMENT 'url of the deployment to share with',
+				`sharing_color` varchar(20) DEFAULT 'CC0000' COMMENT 'color that shows the shared reports',
+				`sharing_active` tinyint(4) NOT NULL DEFAULT '1' COMMENT 'sharing active or inactive ',
+				`sharing_date` datetime DEFAULT NULL COMMENT 'date the sharing was initiated',
+				PRIMARY KEY (id)
+			);");
+
+			$this->db->query("
+			CREATE TABLE IF NOT EXISTS `".Kohana::config('database.default.table_prefix')."sharing_incident`
+			(
+				`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+				`sharing_id` INT UNSIGNED NOT NULL,
+				`incident_id` INT(10) NOT NULL,
+				`incident_title` varchar(255) NOT NULL COMMENT 'title of the report',
+				`latitude` double NOT NULL COMMENT 'latitude of the report',
+				`longitude` double NOT NULL COMMENT 'longitude of the report',
+				`incident_date` datetime DEFAULT NULL,
+				PRIMARY KEY (id)
+			);");
+
+			$this->db->query("
+			CREATE TABLE IF NOT EXISTS `".Kohana::config('database.default.table_prefix')."sharing_log`
+			(
+			  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+			  `sharing_id` int(11) NOT NULL,
+			  `sharing_log_date` int(10) unsigned DEFAULT NULL,
+			   PRIMARY KEY (`id`)
+			);");
+
+			//Dump the sharing scheduler item from bundled SQL dump file
+			$this->db->query("DELETE FROM `".Kohana::config('database.default.table_prefix')."scheduler` where scheduler_name = 'Sharing' ");
+			
+			// Add sharing in to scheduler table
+			$this->db->query("INSERT IGNORE INTO `".Kohana::config('database.default.table_prefix')."scheduler`
+				(`scheduler_name`,`scheduler_last`,`scheduler_weekday`,`scheduler_day`,`scheduler_hour`,`scheduler_minute`,`scheduler_controller`,`scheduler_active`) VALUES
+				('Sharing','0','-1','-1','-1','-1','s_sharing','1')"
+			);
+			
+			// Update sharing urls to include http://
+			$this->db->query("
+			UPDATE `".Kohana::config('database.default.table_prefix')."sharing` SET sharing_url = CONCAT('http://', sharing_url) WHERE sharing_url NOT LIKE '%http%';
+			");
+
+    
+	}
+
+	/**
+	 * Deletes the database tables for the sharing module
+	 */
+	public function uninstall()
+	{
+		$this->db->query("
+			DROP TABLE ".Kohana::config('database.default.table_prefix')."sharing;
+			");
+		$this->db->query("
+			DROP TABLE ".Kohana::config('database.default.table_prefix')."sharing_incident;
+			");
+		$this->db->query("
+			DROP TABLE ".Kohana::config('database.default.table_prefix')."sharing_log;
+			");
+
+	}
+}
diff --git a/plugins/sharing/models/sharing.php b/plugins/sharing/models/sharing.php
new file mode 100644
index 0000000..0f41fd0
--- /dev/null
+++ b/plugins/sharing/models/sharing.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Sharing
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Sharing_Model extends ORM
+{	
+	protected $has_many = array('sharing_incident');
+	
+	// Database table name
+	protected $table_name = 'sharing';
+}
diff --git a/plugins/sharing/models/sharing_incident.php b/plugins/sharing/models/sharing_incident.php
new file mode 100644
index 0000000..4ab5e89
--- /dev/null
+++ b/plugins/sharing/models/sharing_incident.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+* Model for Sharing_Incident
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Models
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Sharing_Incident_Model extends ORM
+{	
+	protected $belongs_to = array('sharing');
+	
+	// Database table name
+	protected $table_name = 'sharing_incident';
+}
diff --git a/plugins/sharing/readme.txt b/plugins/sharing/readme.txt
new file mode 100644
index 0000000..7e79b15
--- /dev/null
+++ b/plugins/sharing/readme.txt
@@ -0,0 +1,17 @@
+=== About ===
+name: Sharing
+website: http://www.ushahidi.com
+description: Share reports among deployments
+version: 0.1
+requires: 2.1
+tested up to: 2.1
+author: Ushahidi Team
+author website: http://www.ushahidi.com
+
+== Description ==
+Share reports among deployments
+
+== Installation ==
+1. Copy the entire /sharing/ directory into your /plugins/ directory.
+2. Activate the plugin.
+
diff --git a/plugins/sharing/views/admin/manage/sharing/main.php b/plugins/sharing/views/admin/manage/sharing/main.php
new file mode 100644
index 0000000..eff941f
--- /dev/null
+++ b/plugins/sharing/views/admin/manage/sharing/main.php
@@ -0,0 +1,183 @@
+<?php 
+/**
+ * Sharing view page.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Sharing view
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+			<div class="bg">
+				<h2>
+					<?php admin::manage_subtabs("sharing"); ?>
+				</h2>
+				<?php
+				if ($form_error) {
+				?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+						<ul>
+						<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							// print "<li>" . $error_description . "</li>";
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+						?>
+						</ul>
+					</div>
+				<?php
+				}
+
+				if ($form_saved) {
+				?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo $form_action; ?>!</h3>
+					</div>
+				<?php
+				}
+				?>
+				<!-- report-table -->
+				<div class="report-form">
+					<?php print form::open(NULL,array('id' => 'sharingListing',
+					 	'name' => 'sharingListing')); ?>
+						<input type="hidden" name="action" id="action" value="">
+						<input type="hidden" name="sharing_id" id="sharing_id_action" value="">
+						<div class="table-holder">
+							<table class="table">
+								<thead>
+									<tr>
+										<th class="col-1"> </th>
+										<th class="col-2"><?php echo Kohana::lang('ui_main.sharing');?></th>
+										<th class="col-3"><?php echo Kohana::lang('ui_main.color');?></th>
+										<th class="col-4"><?php echo Kohana::lang('ui_main.actions');?></th>
+									</tr>
+								</thead>
+								<tfoot>
+									<tr class="foot">
+										<td colspan="4">
+											<?php echo $pagination; ?>
+										</td>
+									</tr>
+								</tfoot>
+								<tbody>
+									<?php
+									if ($total_items == 0)
+									{
+									?>
+										<tr>
+											<td colspan="4" class="col">
+												<h3><?php echo Kohana::lang('ui_main.no_results');?></h3>
+											</td>
+										</tr>
+									<?php	
+									}
+									foreach ($shares as $share)
+									{
+										$sharing_id = $share->id;
+										$sharing_name = $share->sharing_name;
+										$sharing_color = $share->sharing_color;
+										$sharing_url = $share->sharing_url;
+										$sharing_active = $share->sharing_active;
+										?>
+										<tr>
+											<td class="col-1"> </td>
+											<td class="col-2">
+												<div class="post">
+													<h4><?php echo $sharing_name; ?></h4>
+												</div>
+												<ul class="links">
+													<?php
+													if($sharing_url)
+													{
+														?><li class="none-separator"><strong><?php echo text::auto_link($sharing_url); ?></strong></li><?php
+													}
+													?>
+												</ul>
+											</td>
+											<td class="col-3">
+											<?php echo "<img src=\"".url::base()."swatch/?c=".$sharing_color."&w=30&h=30\">"; ?>
+											</td>
+											<td class="col-4">
+												<ul>
+													<li class="none-separator"><a href="#add" onClick="fillFields('<?php echo(rawurlencode($sharing_id)); ?>','<?php echo(rawurlencode($sharing_url)); ?>','<?php echo(rawurlencode($sharing_name)); ?>','<?php echo(rawurlencode($sharing_color)); ?>')"><?php echo Kohana::lang('ui_main.edit');?></a></li>
+													<li class="none-separator">
+													<?php if($sharing_active) {?>
+													<a href="javascript:sharingAction('h','HIDE',<?php echo rawurlencode($sharing_id);?>)" class="status_yes"><?php echo Kohana::lang('ui_main.visible');?></a>
+													<?php } else {?>
+													<a href="javascript:sharingAction('v','SHOW',<?php echo rawurlencode($sharing_id);?>)" class="status_yes"><?php echo Kohana::lang('ui_main.hidden');?></a>
+													<?php } ?>
+													</li>
+<li><a href="javascript:sharingAction('d','DELETE','<?php echo(rawurlencode($sharing_id)); ?>')" class="del"><?php echo Kohana::lang('ui_main.delete');?></a></li>
+												</ul>
+											</td>
+										</tr>
+										<?php									
+									}
+									?>
+								</tbody>
+							</table>
+						</div>
+					<?php print form::close(); ?>
+				</div>
+				
+				<!-- tabs -->
+				<div class="tabs">
+					<!-- tabset -->
+					<a name="add"></a>
+					<ul class="tabset">
+						<li><a href="#" class="active"><?php echo Kohana::lang('ui_main.add_edit');?></a></li>
+					</ul>
+					<!-- tab -->
+					<div class="tab">
+						<?php print form::open(NULL,array('id' => 'sharingMain', 'name' => 'sharingMain')); ?>
+						<input type="hidden" id="sharing_id" 
+							name="sharing_id" value="" />
+						<input type="hidden" name="action" 
+							id="action" value="a"/>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.name');?>:</strong><br />
+							<?php print form::input('sharing_name', '', ' class="text"'); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.site_url');?>:</strong><br />
+							<?php print form::input('sharing_url', '', ' class="text long"'); ?>
+						</div>
+						<div class="tab_form_item">
+							<strong><?php echo Kohana::lang('ui_main.color');?>:</strong><br />
+							<?php print form::input('sharing_color', '', ' class="text"'); ?>
+							<script type="text/javascript" charset="utf-8">
+								$(document).ready(function() {
+									$('#sharing_color').ColorPicker({
+										onSubmit: function(hsb, hex, rgb) {
+											$('#sharing_color').val(hex);
+										},
+										onChange: function(hsb, hex, rgb) {
+											$('#sharing_color').val(hex);
+										},
+										onBeforeShow: function () {
+											$(this).ColorPickerSetColor(this.value);
+										}
+									})
+									.bind('keyup', function(){
+										$(this).ColorPickerSetColor(this.value);
+									});
+								});
+							</script>
+						</div>
+						<div style="clear:both"></div>
+						<div class="tab_form_item">
+							<input type="submit" class="save-rep-btn" value="<?php echo Kohana::lang('ui_main.save');?>" />
+						</div>
+						<?php print form::close(); ?>			
+					</div>
+				</div>
+			</div>
diff --git a/plugins/sharing/views/admin/manage/sharing/sharing_js.php b/plugins/sharing/views/admin/manage/sharing/sharing_js.php
new file mode 100644
index 0000000..93ef3ad
--- /dev/null
+++ b/plugins/sharing/views/admin/manage/sharing/sharing_js.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Sharing js file.
+ * 
+ * Handles javascript stuff related to sharing controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Sharing JS View
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+// Sharing JS
+function fillFields(id, sharing_url, sharing_name, sharing_color)
+{
+	$("#sharing_id").attr("value", decodeURIComponent(id));
+	$("#sharing_name").attr("value", decodeURIComponent(sharing_name));
+	$("#sharing_url").attr("value", decodeURIComponent(sharing_url));
+	$("#sharing_color").attr("value", decodeURIComponent(sharing_color));
+}
+
+// Ajax Submission
+function sharingAction ( action, confirmAction, id )
+{
+	var statusMessage;
+	var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> ' + confirmAction + '?')
+	if (answer){
+		// Set Category ID
+		$("#sharing_id_action").attr("value", id);
+		// Set Submit Type
+		$("#action").attr("value", action);		
+		// Submit Form
+		$("#sharingListing").submit();
+	}
+}
\ No newline at end of file
diff --git a/plugins/sharing/views/js/sharing_bar_js.php b/plugins/sharing/views/js/sharing_bar_js.php
new file mode 100644
index 0000000..8121078
--- /dev/null
+++ b/plugins/sharing/views/js/sharing_bar_js.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * Sharing_bar js file.
+ * 
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Sharing Module
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+<script type="text/javascript">
+$(document).ready(function() {
+	
+	// Sharing Layer[s] Switch Action
+	$("a[id^='share_']").click(function() {
+		var shareID = this.id.substring(6);
+	
+		if ( $("#share_" + shareID).hasClass("active")) {
+			map.deleteLayer($("#share_" + shareID).html());
+			$("#share_" + shareID).removeClass("active");
+		
+		}  else {
+			$("#share_" + shareID).addClass("active");
+			map.addLayer(Ushahidi.SHARES, {
+							name: $("#share_" + shareID).html(),
+							url: "json/share/index/" + shareID
+						});
+		}
+		
+		return false;
+	});
+});
+</script>
\ No newline at end of file
diff --git a/plugins/sharing/views/sharing/sharing_bar.php b/plugins/sharing/views/sharing/sharing_bar.php
new file mode 100644
index 0000000..252235c
--- /dev/null
+++ b/plugins/sharing/views/sharing/sharing_bar.php
@@ -0,0 +1,15 @@
+<div class="cat-filters clearingfix" style="margin-top:20px;">
+			<strong><?php echo Kohana::lang('ui_main.other_ushahidi_instances');?> <span>[<a href="javascript:toggleLayer('sharing_switch_link','sharing_switch')" id="sharing_switch_link"><?php echo Kohana::lang('ui_main.hide'); ?></a>]</span></strong>
+</div>
+		<ul id="sharing_switch" class="category-filters">
+			<?php
+			
+			foreach ($shares as $share => $share_info)
+			{
+				$sharing_name = $share_info[0];
+				$sharing_color = $share_info[1];
+				echo '<li><a href="#" id="share_'. $share .'"><div class="swatch" style="background-color:#'.$sharing_color.'"></div>
+				<div>'.$sharing_name.'</div></a></li>';
+			}
+			?>
+		</ul>
diff --git a/plugins/smssync/controllers/admin/smssync_settings.php b/plugins/smssync/controllers/admin/smssync_settings.php
new file mode 100644
index 0000000..e00a580
--- /dev/null
+++ b/plugins/smssync/controllers/admin/smssync_settings.php
@@ -0,0 +1,102 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * SMSSync Settings Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   SMSSync Settings Controller	
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+* 
+*/
+
+class Smssync_Settings_Controller extends Admin_Controller
+{
+	public function index()
+	{
+		$this->template->this_page = 'addons';
+		
+		// Standard Settings View
+		$this->template->content = new View("admin/addons/plugin_settings");
+		$this->template->content->title = "SMSSync Settings";
+		
+		// Settings Form View
+		$this->template->content->settings_form = new View("smssync/admin/smssync_settings");
+		
+		// setup and initialize form field names
+		$form = array
+	    (
+			'smssync_secret' => ''
+	    );
+        //  Copy the form as errors, so the errors will be stored with keys
+        //  corresponding to the form field names
+        $errors = $form;
+		$form_error = FALSE;
+		$form_saved = FALSE;
+
+		// check, has the form been submitted, if so, setup validation
+	    if ($_POST)
+	    {			
+            // Instantiate Validation, use $post, so we don't overwrite $_POST
+            // fields with our own things
+            $post = new Validation($_POST);
+
+	        // Add some filters
+	        $post->pre_filter('trim', TRUE);
+
+	        // Add some rules, the input field, followed by a list of checks, carried out in order
+			$post->add_rules('smssync_secret', 'length[0,100]');
+
+			// Test to see if things passed the rule checks
+	        if ($post->validate())
+	        {
+	            // Yes! everything is valid
+				$settings = ORM::factory('smssync_settings', 1);
+				$settings->smssync_secret = $post->smssync_secret;
+				$settings->save();
+
+				// Everything is A-Okay!
+				$form_saved = TRUE;
+
+				// repopulate the form fields
+	            $form = arr::overwrite($form, $post->as_array());
+	        }
+
+            // No! We have validation errors, we need to show the form again,
+            // with the errors
+            else
+	        {
+	            // repopulate the form fields
+	            $form = arr::overwrite($form, $post->as_array());
+
+	            // populate the error fields, if any
+	            $errors = arr::overwrite($errors, $post->errors('smssync'));
+				$form_error = TRUE;
+	        }
+	    }
+		else
+		{
+			// Retrieve Current Settings
+			$settings = ORM::factory('smssync_settings', 1);
+
+			$form = array
+		    (
+		        'smssync_secret' => $settings->smssync_secret
+		    );
+		}
+		
+		// Pass the $form on to the settings_form variable in the view
+		$this->template->content->settings_form->form = $form;
+		
+		$this->template->content->settings_form->smssync_url = url::site()."smssync";
+		
+		// Other variables
+	    $this->template->content->errors = $errors;
+		$this->template->content->form_error = $form_error;
+		$this->template->content->form_saved = $form_saved;
+	}
+}
diff --git a/plugins/smssync/controllers/smssync.php b/plugins/smssync/controllers/smssync.php
new file mode 100644
index 0000000..07c8d41
--- /dev/null
+++ b/plugins/smssync/controllers/smssync.php
@@ -0,0 +1,167 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * SMSSync HTTP Post Controller
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @module	   SMSSync Controller	
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+*/
+
+class Smssync_Controller extends Controller {
+	
+	private $request = array();
+	
+	public function __construct()
+	{
+		$this->request = ($_SERVER['REQUEST_METHOD'] == 'POST')
+			? $_POST
+			: $_GET;
+	}
+	
+	function index()
+	{
+		$task = (isset($this->request['task'])) ? $this->request['task'] : "";
+		
+		switch ($task) {
+			// Send
+			case "send":
+				$this->_send();
+				break;
+			
+			// Receive
+			default:
+				$this->_receive();
+				break;
+		}
+	}
+	
+	private function _receive()
+	{
+		$secret = "";
+		$success = "false";
+		
+		//Sometimes user send blank SMSs or GSM operators will
+		//send promotional SMSs with no phone number, so this way
+		//these messages will always end up on the backend and not float around
+		//on the phones forever.
+		$message_description = Kohana::lang("ui_main.empty");		
+		$message_from = "00000000";
+		$non_numeric_source = false;
+		
+		
+		if (isset($this->request['secret']))
+		{
+			$secret = $this->request['secret'];
+		}
+		
+		if(isset($this->request['from']) &&  strlen($this->request['from']) > 0)
+		{
+			$message_from = $this->request['from'];
+			$original_from = $message_from;
+			$message_from = preg_replace("#[^0-9]#", "", $message_from);
+
+			if(strlen($message_from) == 0)
+			{
+				$message_from = "00000000";
+				$non_numeric_source = true;
+			}
+		}
+		
+		if (isset($this->request['message']) && strlen($this->request['message']) > 0)
+		{
+			$message_description = $this->request['message'];
+		}
+		
+		if($non_numeric_source)
+		{
+			$message_description = '<div style="color:red;">'.Kohana::lang("ui_main.message_non_numeric_source")." \"".$original_from."\" </div>".$message_description;			
+		}
+		
+		if ( ! empty($message_from) AND ! empty($message_description))
+		{
+			$secret_match = TRUE;
+			
+			// Is this a valid Secret?
+			$smssync = ORM::factory('smssync_settings')
+				->find(1);
+			
+			if ($smssync->loaded)
+			{
+				$smssync_secret = $smssync->smssync_secret;
+				if ($smssync_secret AND $secret != $smssync_secret)
+				{ // A Secret has been set and they don't match
+					$secret_match = FALSE;
+				}
+			}
+			else
+			{ // No Secret Set
+				$secret_match = TRUE;
+			}
+			
+			if ($secret_match)
+			{
+				if(stristr($message_description,"alert"))
+				{
+					alert::mobile_alerts_register($message_from, $message_description);
+					$success = "true";
+				}
+				elseif(stristr($message_description,"off"))
+				{
+					alert::mobile_alerts_unsubscribe($message_from, $message_description);
+					$success = "true";
+				}
+				else
+				{
+					sms::add($message_from, $message_description);
+					$success = "true";
+				}
+			}
+		}
+		
+		echo json_encode(array("payload"=>array("success"=>$success)));
+	}
+	
+	private function _send()
+	{
+		$all_messages =  array();
+		
+		// Find all unsent messages
+		$messages = ORM::factory("smssync_message")
+			->where("smssync_sent", 0)
+			->find_all();
+			
+		foreach ($messages as $message)
+		{
+			$all_messages[] = array(
+				"to"=>$message->smssync_to,
+				"message"=>$message->smssync_message
+			);
+			
+			$message->smssync_sent = 1;
+			$message->smssync_sent_date = date("Y-m-d H:i:s",time());
+			$message->save();
+        }
+        
+        //get the secret key
+		$smssync = ORM::factory('smssync_settings')->find(1);
+			
+		if ($smssync->loaded)
+		{
+		    $smssync_secret = $smssync->smssync_secret;
+		} 
+        else 
+        {
+            //set to empty, because secret key wasn't set.
+            $smssync_secret = "";
+        }
+		
+        echo json_encode(array("payload"=>array("task"=>"send", 
+            "secret"=>$smssync_secret,"messages"=>$all_messages)));
+	}
+}
diff --git a/plugins/smssync/hooks/smssync.php b/plugins/smssync/hooks/smssync.php
new file mode 100644
index 0000000..3559462
--- /dev/null
+++ b/plugins/smssync/hooks/smssync.php
@@ -0,0 +1,36 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * SMSSync Hook
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class smssync {
+	
+	/**
+	 * Registers the main event add method
+	 */
+	public function __construct()
+	{	
+		// Hook into routing
+		Event::add('system.pre_controller', array($this, 'add'));
+	}
+	
+	/**
+	 * Adds all the events to the main Ushahidi application
+	 */
+	public function add()
+	{
+		// SMS Provider
+		plugin::add_sms_provider("smssync");
+	}
+}
+
+new smssync;
\ No newline at end of file
diff --git a/plugins/smssync/libraries/Smssync_Sms_Provider.php b/plugins/smssync/libraries/Smssync_Sms_Provider.php
new file mode 100644
index 0000000..0a5a0f3
--- /dev/null
+++ b/plugins/smssync/libraries/Smssync_Sms_Provider.php
@@ -0,0 +1,20 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+
+/**
+ * The smssync sender
+ */
+class Smssync_Sms_Provider implements Sms_Provider_Core {
+	
+	public function send($to = NULL, $from = NULL, $message = NULL)
+	{
+		$smssync = ORM::factory("smssync_message");
+		$smssync->smssync_to = "+".$to;
+		$smssync->smssync_from = $from;
+		$smssync->smssync_message = $message;
+		$smssync->smssync_message_date = date("Y-m-d H:i:s",time());
+		$smssync->save();
+		
+		return TRUE;
+	}
+	
+}
diff --git a/plugins/smssync/libraries/smssync_install.php b/plugins/smssync/libraries/smssync_install.php
new file mode 100644
index 0000000..2e4425f
--- /dev/null
+++ b/plugins/smssync/libraries/smssync_install.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Performs install/uninstall methods for the SMSSync plugin
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   SMSSync Installer
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Smssync_Install {
+
+	/**
+	 * Constructor to load the shared database library
+	 */
+	public function __construct()
+	{
+		$this->db = Database::instance();
+	}
+
+	/**
+	 * Creates the required database tables for the smssync plugin
+	 */
+	public function run_install()
+	{
+		// Create the database tables.
+		// Also include table_prefix in name
+		$this->db->query("
+			CREATE TABLE IF NOT EXISTS `".Kohana::config('database.default.table_prefix')."smssync_settings` (
+				id int(11) unsigned NOT NULL AUTO_INCREMENT,
+				smssync_secret varchar(100) DEFAULT NULL,
+				PRIMARY KEY (`id`)
+			);
+		");
+		
+		$this->db->query("
+			CREATE TABLE IF NOT EXISTS `".Kohana::config('database.default.table_prefix')."smssync_message` (
+				id int(11) unsigned NOT NULL AUTO_INCREMENT,
+				smssync_to varchar(100) DEFAULT NULL,
+				smssync_from varchar(100) DEFAULT NULL,
+				smssync_message text,
+				smssync_message_date datetime DEFAULT NULL,
+				smssync_sent tinyint(4) NOT NULL DEFAULT '0',
+				smssync_sent_date datetime DEFAULT NULL,
+				PRIMARY KEY (id)
+			);
+		");
+	}
+
+	/**
+	 * Deletes the database tables for the actionable module
+	 */
+	public function uninstall()
+	{
+		$this->db->query('DROP TABLE `'.Kohana::config('database.default.table_prefix').'smssync_settings`');
+		$this->db->query('DROP TABLE `'.Kohana::config('database.default.table_prefix').'smssync_message`');
+	}
+}
\ No newline at end of file
diff --git a/plugins/smssync/models/smssync_message.php b/plugins/smssync/models/smssync_message.php
new file mode 100644
index 0000000..1e37f4e
--- /dev/null
+++ b/plugins/smssync/models/smssync_message.php
@@ -0,0 +1,20 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * SMSSync_Message Model
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     SMSSync_Settings Model  
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Smssync_Message_Model extends ORM
+{
+	// Database table name
+	protected $table_name = 'smssync_message';
+}
\ No newline at end of file
diff --git a/plugins/smssync/models/smssync_settings.php b/plugins/smssync/models/smssync_settings.php
new file mode 100644
index 0000000..c21dcb1
--- /dev/null
+++ b/plugins/smssync/models/smssync_settings.php
@@ -0,0 +1,20 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * SMSSync_Settings Model
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     SMSSync_Settings Model  
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Smssync_Settings_Model extends ORM
+{
+	// Database table name
+	protected $table_name = 'smssync_settings';
+}
\ No newline at end of file
diff --git a/plugins/smssync/readme.txt b/plugins/smssync/readme.txt
new file mode 100644
index 0000000..5c4d9f2
--- /dev/null
+++ b/plugins/smssync/readme.txt
@@ -0,0 +1,22 @@
+=== About ===
+name: SMSSync
+website: http://www.ushahidi.com
+description: Receive text messages from the SMSSync SMS Gateway App for Android
+version: 0.8
+requires: 2.0
+tested up to: 2.0
+author: Ushahidi Team
+author website: http://www.ushahidi.com
+
+== Description ==
+Receive text messages from the SMSSync SMS Gateway App for Android
+
+== Installation ==
+1. Copy the entire /smssync/ directory into your /plugins/ directory.
+2. Activate the plugin.
+3. Click on the [settings] to set the 'secret'
+
+== Changelog ==
+0.8
+* Added sendsms task -- allows you to use sms gateway as sender
+* Other bugfixes
\ No newline at end of file
diff --git a/plugins/smssync/views/images/smssync.png b/plugins/smssync/views/images/smssync.png
new file mode 100644
index 0000000..83c227d
Binary files /dev/null and b/plugins/smssync/views/images/smssync.png differ
diff --git a/plugins/smssync/views/smssync/admin/smssync_settings.php b/plugins/smssync/views/smssync/admin/smssync_settings.php
new file mode 100644
index 0000000..069e057
--- /dev/null
+++ b/plugins/smssync/views/smssync/admin/smssync_settings.php
@@ -0,0 +1,41 @@
+<table style="width: 630px;" class="my_table">
+	<tr>
+		<td style="width:60px;">
+			<span class="big_blue_span"><?php echo Kohana::lang('ui_main.step');?> 1:</span>
+		</td>
+		<td>
+			<h4 class="fix">Download the "SMSSync" app from the Android Market</h4>
+			<p>
+				Scan this QR Code with your phone to download the app from the Android Market
+			</p>
+			<div><img src="<?php echo url::base();?>plugins/smssync/views/images/smssync.png"></div>
+		</td>
+	</tr>
+	
+	<tr>
+		<td>
+			<span class="big_blue_span"><?php echo Kohana::lang('ui_main.step');?> 2:</span>
+		</td>
+		<td>
+			<h4 class="fix">Android App Settings</h4>
+			<p>
+				Turn on SMSSync and use the following link as the website
+			</p>
+			<p class="sync_key">
+				<span><?php echo $smssync_url; ?></span>
+			</p>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			<span class="big_blue_span"><?php echo Kohana::lang('ui_main.step');?> 3:</span>
+		</td>
+		<td>
+			<h4 class="fix">*Optional - Specify a secret so that only authorized users can use the SMSSync Gateway.</h4>
+			<div class="row">
+				<h4>SMSSync Secret</h4>
+				<?php print form::input('smssync_secret', $form['smssync_secret'], ' class="text title_2"'); ?>
+			</div>
+		</td>
+	</tr>							
+</table>
\ No newline at end of file
diff --git a/sql/upgrade.sql b/sql/upgrade.sql
new file mode 100644
index 0000000..417d533
--- /dev/null
+++ b/sql/upgrade.sql
@@ -0,0 +1,288 @@
+ALTER TABLE category ENGINE = MyISAM;
+ALTER TABLE feedback ENGINE = MyISAM;
+ALTER TABLE feedback_person ENGINE = MyISAM;
+ALTER TABLE idp ENGINE = MyISAM;
+ALTER TABLE incident ENGINE = MyISAM;
+ALTER TABLE incident_category ENGINE = MyISAM;
+ALTER TABLE incident_person ENGINE = MyISAM;
+ALTER TABLE level ENGINE = MyISAM;
+ALTER TABLE location ENGINE = MyISAM;
+ALTER TABLE media ENGINE = MyISAM;
+ALTER TABLE organization ENGINE = MyISAM;
+ALTER TABLE organization_incident ENGINE = MyISAM;
+ALTER TABLE pending_users ENGINE = MyISAM;
+ALTER TABLE reporter ENGINE = MyISAM;
+ALTER TABLE service ENGINE = MyISAM;
+ALTER TABLE sessions ENGINE = MyISAM;
+ALTER TABLE sharing ENGINE = MyISAM;
+ALTER TABLE sharing_log ENGINE = MyISAM;
+ALTER TABLE verified ENGINE = MyISAM;
+ 
+ALTER TABLE alert_sent CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE category_lang CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE cluster CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE comment CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE feed CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE feedback CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE feedback_person CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE feed_item CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE incident_lang CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE laconica CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE level CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE message CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE rating CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE reporter CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE scheduler CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE scheduler_log CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE service CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ALTER TABLE twitter CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
+ 
+ALTER TABLE `settings` ADD `db_version` VARCHAR( 20 ) NOT NULL ,
+ADD `ushahidi_version` VARCHAR( 20 ) NOT NULL ;
+ 
+UPDATE `settings` SET `db_version` = '11',
+`ushahidi_version` = '0.9' WHERE `id` =1 LIMIT 1 ;
+
+
+--- Take care of very old ushahidi installs.
+ALTER TABLE `incident` ADD `form_id` INT( 11 ) NOT NULL DEFAULT '1' AFTER `location_id`,
+   ADD  `incident_source` varchar(5) default NULL,
+   ADD  `incident_information` varchar(5) default NULL;
+
+
+
+   CREATE TABLE IF NOT EXISTS `form` (
+     `id` int(11) NOT NULL auto_increment,
+     `form_title` varchar(200) NOT NULL,
+     `form_description` text,
+     `form_active` tinyint(4) default '1',
+     PRIMARY KEY  (`id`)
+   ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+
+
+   INSERT INTO `form` (`id`, `form_title`, `form_description`, `form_active`) VALUES
+   (1, 'Default Form', 'Default form, for report entry', 1);
+
+
+
+   CREATE TABLE IF NOT EXISTS `form_field` (
+     `id` int(11) NOT NULL auto_increment,
+     `form_id` int(11) NOT NULL default '0',
+     `field_name` varchar(200) default NULL,
+     `field_type` tinyint(4) NOT NULL default '1' COMMENT '1 - TEXTFIELD, 2 - TEXTAREA (FREETEXT), 3 - DATE, 4 - PASSWORD, 5 - RADIO, 6 - CHECKBOX',
+     `field_required` tinyint(4) default '0',
+     `field_options` text,
+     `field_position` tinyint(4) NOT NULL default '0',
+     `field_default` varchar(200) default NULL,
+     `field_maxlength` int(11) NOT NULL default '0',
+     `field_width` smallint(6) NOT NULL default '0',
+     `field_height` tinyint(4) default '5',
+     `field_isdate` tinyint(4) NOT NULL default '0',
+     PRIMARY KEY  (`id`),
+     KEY `fk_form_id` (`form_id`)
+   ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+
+
+   CREATE TABLE IF NOT EXISTS `form_response` (
+     `id` bigint(20) NOT NULL auto_increment,
+     `form_field_id` int(11) NOT NULL,
+     `incident_id` bigint(20) NOT NULL,
+     `form_response` text NOT NULL,
+     PRIMARY KEY  (`id`),
+     KEY `fk_form_field_id` (`form_field_id`)
+   ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+   --
+   -- Constraints for table `form_field`
+   --
+   ALTER TABLE `form_field`
+     ADD CONSTRAINT `form_field_ibfk_1` FOREIGN KEY (`form_id`) REFERENCES `form` (`id`) ON DELETE CASCADE;
+
+   --
+   -- Constraints for table `form_response`
+   --
+   ALTER TABLE `form_response`
+     ADD CONSTRAINT `form_response_ibfk_1` FOREIGN KEY (`form_field_id`) REFERENCES `form_field` (`id`) ON DELETE CASCADE;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `level`
+--
+
+CREATE TABLE `level` (
+  `id` bigint(20) unsigned NOT NULL auto_increment,
+  `level_title` varchar(200) default NULL,
+  `level_description` varchar(200) default NULL,
+  `level_weight` tinyint(4) NOT NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=InnoDB;
+
+--
+-- Dumping data for table `level`
+--
+
+INSERT INTO `level` (`id`, `level_title`, `level_description`, `level_weight`) VALUES
+(1, 'SPAM + Delete', 'SPAM + Delete', -2),
+(2, 'SPAM', 'SPAM', -1),
+(3, 'Untrusted', 'Untrusted', 0),
+(4, 'Trusted', 'Trusted', 1),
+(5, 'Trusted + Verifiy', 'Trusted + Verify', 2);
+
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `reporter`
+--
+
+CREATE TABLE `reporter` (
+  `id` bigint(20) unsigned NOT NULL auto_increment,
+  `incident_id` bigint(20) default NULL,
+  `location_id` bigint(20) default NULL,
+  `user_id` int(11) default NULL,
+  `service_id` int(11) default NULL,
+  `service_userid` varchar(255) default NULL,
+  `service_account` varchar(255) default NULL,
+  `reporter_level` tinyint(4) default '3',
+  `reporter_first` varchar(200) default NULL,
+  `reporter_last` varchar(200) default NULL,
+  `reporter_email` varchar(120) default NULL,
+  `reporter_phone` varchar(60) default NULL,
+  `reporter_ip` varchar(50) default NULL,
+  `reporter_date` datetime default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=InnoDB;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `service`
+--
+
+CREATE TABLE `service` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `service_name` varchar(100) default NULL,
+  `service_description` varchar(255) default NULL,
+  `service_url` varchar(255) default NULL,
+  `service_api` varchar(255) default NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=InnoDB;
+
+--
+-- Dumping data for table `service`
+--
+
+INSERT INTO `service` (`id`, `service_name`, `service_description`, `service_url`, `service_api`) VALUES
+(1, 'SMS', 'Text messages from phones', NULL, NULL),
+(2, 'Email', 'Text messages from phones', NULL, NULL),
+(3, 'Twitter', 'Tweets tweets tweets', 'http://twitter.com', NULL),
+(4, 'Laconica', 'Tweets tweets tweets', NULL, NULL);
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `mhi_category`
+--
+
+CREATE TABLE `mhi_category` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) unsigned DEFAULT NULL,
+  `category_title` varchar(100) CHARACTER SET utf8 NOT NULL,
+  `category_active` tinyint(4) DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `mhi_site`
+--
+
+CREATE TABLE `mhi_site` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL,
+  `site_domain` varchar(100) NOT NULL,
+  `site_privacy` tinyint(4) NOT NULL DEFAULT '0',
+  `site_active` tinyint(4) DEFAULT '1',
+  `site_dateadd` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `mhi_site_category`
+--
+
+CREATE TABLE `mhi_site_category` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `site_id` int(11) unsigned NOT NULL,
+  `category_id` int(11) unsigned NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `mhi_site_database`
+--
+
+CREATE TABLE `mhi_site_database` (
+  `mhi_id` int(11) NOT NULL AUTO_INCREMENT,
+  `user` varchar(50) CHARACTER SET utf8 NOT NULL,
+  `pass` varchar(50) CHARACTER SET utf8 NOT NULL,
+  `host` varchar(100) CHARACTER SET utf8 NOT NULL,
+  `port` smallint(6) NOT NULL,
+  `database` varchar(30) CHARACTER SET utf8 NOT NULL,
+  PRIMARY KEY (`mhi_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 COMMENT='This table holds DB credentials for MHI instances';
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `mhi_users`
+--
+
+CREATE TABLE `mhi_users` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `email` varchar(50) CHARACTER SET utf8 NOT NULL,
+  `firstname` varchar(30) CHARACTER SET utf8 NOT NULL,
+  `lastname` varchar(30) CHARACTER SET utf8 NOT NULL,
+  `password` varchar(40) CHARACTER SET utf8 NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+--
+-- Update table `message`
+--
+
+ALTER TABLE `message`
+ADD `reporter_id` bigint(20) default NULL AFTER `user_id`,
+ADD `service_messageid` varchar(100) default NULL AFTER `reporter_id`,
+ADD `message_detail` text NULL AFTER `message`;
+
+
+-- --------------------------------------------------------
+
+--
+-- Update table `alert`
+--
+
+ALTER TABLE `alert`
+ADD UNIQUE KEY `uniq_alert_code` (`alert_code`);
+
+
+-- --------------------------------------------------------
+
+--
+-- Update table `alert_sent`
+--
+
+ALTER TABLE `alert_sent`
+ADD UNIQUE KEY `uniq_incident_id` (`incident_id`);
+
diff --git a/sql/upgrade100-101.sql b/sql/upgrade100-101.sql
new file mode 100755
index 0000000..23fd15a
--- /dev/null
+++ b/sql/upgrade100-101.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 101 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.6' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade101-102.sql b/sql/upgrade101-102.sql
new file mode 100644
index 0000000..67ab3e6
--- /dev/null
+++ b/sql/upgrade101-102.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 102 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.6.1' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade102-103.sql b/sql/upgrade102-103.sql
new file mode 100644
index 0000000..63865f2
--- /dev/null
+++ b/sql/upgrade102-103.sql
@@ -0,0 +1,9 @@
+-- Alter form field field_name index to accomodate form_id
+ALTER TABLE `form_field` DROP INDEX  `field_name` ,
+ADD UNIQUE `field_name` (  `field_name` ,  `form_id` );
+
+-- Add form_title unique constraint
+ALTER TABLE `form` ADD UNIQUE (`form_title`);
+
+-- Update DB Version
+UPDATE `settings` SET `value` = 103 WHERE `key` = 'db_version';
diff --git a/sql/upgrade103-104.sql b/sql/upgrade103-104.sql
new file mode 100644
index 0000000..7c85000
--- /dev/null
+++ b/sql/upgrade103-104.sql
@@ -0,0 +1,21 @@
+-- Location indexes
+ALTER TABLE `location`
+	ADD INDEX `latitude` (`latitude`),
+	ADD INDEX `longitude` (`longitude`);
+
+-- Category indexes
+ALTER TABLE `category`
+	ADD INDEX `parent_id` (`parent_id`);
+
+-- Incident_category indexes
+ALTER TABLE `incident_category`
+	ADD INDEX `incident_id` (`incident_id`),
+	ADD INDEX `category_id` (`category_id`);
+
+-- Incident indexes
+ALTER TABLE `incident`
+	ADD INDEX `incident_mode` (`incident_mode`),
+	ADD INDEX `incident_verified` (`incident_verified`);
+
+-- Update DB Version
+UPDATE `settings` SET `value` = 104 WHERE `key` = 'db_version';
diff --git a/sql/upgrade104-105.sql b/sql/upgrade104-105.sql
new file mode 100644
index 0000000..e859561
--- /dev/null
+++ b/sql/upgrade104-105.sql
@@ -0,0 +1,14 @@
+-- Add twitter api key column
+INSERT IGNORE INTO `settings` VALUES (' ','twitter_api_key',null);
+
+-- Add twitter api key secret column
+INSERT IGNORE INTO `settings` VALUES (' ','twitter_api_key_secret',null);
+
+-- Add twitter token column
+INSERT IGNORE INTO `settings` VALUES (' ','twitter_token',null);
+
+-- Add twitter token secret column
+INSERT IGNORE INTO `settings` VALUES (' ','twitter_token_secret',null);
+
+-- Update DB Version
+UPDATE `settings` SET `value` = 105 WHERE `key` = 'db_version';
diff --git a/sql/upgrade105-106.sql b/sql/upgrade105-106.sql
new file mode 100644
index 0000000..20f76f4
--- /dev/null
+++ b/sql/upgrade105-106.sql
@@ -0,0 +1,9 @@
+-- Alter form_field.field_default to TEXT column
+-- Alter field_ispublic fields to match role id
+ALTER TABLE `form_field`
+	MODIFY COLUMN `field_default` TEXT,
+	MODIFY COLUMN `field_ispublic_visible` INT(11) NOT NULL DEFAULT '0',
+	MODIFY COLUMN `field_ispublic_submit` INT(11) NOT NULL DEFAULT '0';
+
+-- Update DB Version
+UPDATE `settings` SET `value` = 106 WHERE `key` = 'db_version';
diff --git a/sql/upgrade106-107.sql b/sql/upgrade106-107.sql
new file mode 100644
index 0000000..080ecac
--- /dev/null
+++ b/sql/upgrade106-107.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 107 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.7' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade107-108.sql b/sql/upgrade107-108.sql
new file mode 100644
index 0000000..bc25a29
--- /dev/null
+++ b/sql/upgrade107-108.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 108 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.7.1' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade108-109.sql b/sql/upgrade108-109.sql
new file mode 100644
index 0000000..3208d7d
--- /dev/null
+++ b/sql/upgrade108-109.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 109 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.7.1' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade109-110.sql b/sql/upgrade109-110.sql
new file mode 100644
index 0000000..1db9100
--- /dev/null
+++ b/sql/upgrade109-110.sql
@@ -0,0 +1,11 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 110 WHERE `key` = 'db_version';
+
+-- Not including id in case current instalation already has a custom permissions.
+INSERT INTO `permissions` (`name`) VALUES ('delete_all_reports');
+
+-- Adding permission to superadmin role - @Robbie: will the ORM pick up on the subqueries?
+INSERT INTO `permissions_roles` (`role_id`, `permission_id`) VALUES (
+	(SELECT `id` FROM `roles` WHERE `name` = 'superadmin' LIMIT 1),
+	(SELECT `id` FROM `permissions` WHERE `name` = 'delete_all_reports' LIMIT 1)
+);
diff --git a/sql/upgrade11-12.sql b/sql/upgrade11-12.sql
new file mode 100644
index 0000000..79d00b4
--- /dev/null
+++ b/sql/upgrade11-12.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `category` ADD `parent_id` INT NOT NULL DEFAULT '0' AFTER `id`;
+
+UPDATE `settings` SET `db_version` = '12' WHERE `id` =1 LIMIT 1 ;
\ No newline at end of file
diff --git a/sql/upgrade110-111.sql b/sql/upgrade110-111.sql
new file mode 100644
index 0000000..047b4c7
--- /dev/null
+++ b/sql/upgrade110-111.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 111 WHERE `key` = 'db_version';
+
+-- Setting default theme for any themes that might be using the removed check in theme
+UPDATE `settings` SET `value` = 'default' WHERE `key` = 'site_style' AND `value` = 'ci_cumulus';
\ No newline at end of file
diff --git a/sql/upgrade111-112.sql b/sql/upgrade111-112.sql
new file mode 100755
index 0000000..d822b6a
--- /dev/null
+++ b/sql/upgrade111-112.sql
@@ -0,0 +1,17 @@
+/**
+ * Table structure for table `feed_item_category`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `feed_item_category` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `feed_item_id` bigint(20) unsigned NOT NULL DEFAULT '0',
+  `category_id` int(11) unsigned NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `feed_item_category_ids` (`feed_item_id`,`category_id`),
+  KEY `feed_item_id` (`feed_item_id`),
+  KEY `category_id` (`category_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores fetched feed items categories' AUTO_INCREMENT=1 ;
+
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 112 WHERE `key` = 'db_version';
diff --git a/sql/upgrade112-113.sql b/sql/upgrade112-113.sql
new file mode 100644
index 0000000..1fa21c0
--- /dev/null
+++ b/sql/upgrade112-113.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 113 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.7.2' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade113-114.sql b/sql/upgrade113-114.sql
new file mode 100644
index 0000000..be63e16
--- /dev/null
+++ b/sql/upgrade113-114.sql
@@ -0,0 +1,5 @@
+-- Add feed_category as a setting
+INSERT into settings(`key`, `value`) values('allow_feed_category',0);
+
+-- Update DB Version --
+UPDATE `settings` SET `value` = 114 WHERE `key` = 'db_version';
diff --git a/sql/upgrade114-115.sql b/sql/upgrade114-115.sql
new file mode 100644
index 0000000..5df8fd4
--- /dev/null
+++ b/sql/upgrade114-115.sql
@@ -0,0 +1,7 @@
+-- Add Kosovo and South Sudan
+INSERT INTO `country` (`id`, `iso`, `country`, `capital`, `cities`) VALUES
+(248, 'XK', 'Kosovo', 'Pristina', 0),
+(249, 'SS', 'South Sudan', 'Juba', 0);
+
+-- Update DB Version --
+UPDATE `settings` SET `value` = '115' WHERE `key` = 'db_version';
diff --git a/sql/upgrade115-116.sql b/sql/upgrade115-116.sql
new file mode 100644
index 0000000..cfc1f67
--- /dev/null
+++ b/sql/upgrade115-116.sql
@@ -0,0 +1,5 @@
+-- Add feed_geolocation_user as a setting
+INSERT into settings(`key`, `value`) values('feed_geolocation_user', '');
+
+-- Update DB Version --
+UPDATE `settings` SET `value` = '116' WHERE `key` = 'db_version';
diff --git a/sql/upgrade116-117.sql b/sql/upgrade116-117.sql
new file mode 100644
index 0000000..d9336e9
--- /dev/null
+++ b/sql/upgrade116-117.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 117 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.7.3' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade117-118.sql b/sql/upgrade117-118.sql
new file mode 100644
index 0000000..9c8c617
--- /dev/null
+++ b/sql/upgrade117-118.sql
@@ -0,0 +1,5 @@
+-- Add max_upload_size as a setting
+INSERT into settings(`key`, `value`) values('max_upload_size', '10');
+
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 118 WHERE `key` = 'db_version';
diff --git a/sql/upgrade118-119.sql b/sql/upgrade118-119.sql
new file mode 100644
index 0000000..621eee9
--- /dev/null
+++ b/sql/upgrade118-119.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 119 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.7.4' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade12-13.sql b/sql/upgrade12-13.sql
new file mode 100644
index 0000000..335f472
--- /dev/null
+++ b/sql/upgrade12-13.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `settings` ADD `site_style` VARCHAR( 50 ) NOT NULL DEFAULT 'default' AFTER `site_language` ;
+
+UPDATE `settings` SET `db_version` = '13' WHERE `id` =1 LIMIT 1 ;
\ No newline at end of file
diff --git a/sql/upgrade13-14.sql b/sql/upgrade13-14.sql
new file mode 100644
index 0000000..f16cc85
--- /dev/null
+++ b/sql/upgrade13-14.sql
@@ -0,0 +1,6 @@
+CREATE INDEX incident_active ON incident (incident_active);
+CREATE INDEX incident_date ON incident (incident_date);
+CREATE UNIQUE INDEX incident_category_ids ON incident_category (incident_id,category_id);
+CREATE INDEX category_visible ON category (category_visible);
+
+UPDATE `settings` SET `db_version` = '14' WHERE `id` = 1 LIMIT 1;
diff --git a/sql/upgrade14-15.sql b/sql/upgrade14-15.sql
new file mode 100644
index 0000000..6038850
--- /dev/null
+++ b/sql/upgrade14-15.sql
@@ -0,0 +1,13 @@
+CREATE TABLE IF NOT EXISTS `page` (
+  `id` int(11) NOT NULL auto_increment,
+  `page_title` varchar(255) NOT NULL,
+  `page_description` longtext,
+  `page_tab` varchar(100) NOT NULL,
+  `page_active` tinyint(4) NOT NULL default '0',
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
+
+INSERT INTO `page` (`id`, `page_title`, `page_description`, `page_tab`, `page_active`) VALUES
+(1, 'About Us', '<p>This is the default about us page.</p>', 'About Us', 1);
+
+UPDATE `settings` SET `db_version` = '15' WHERE `id` =1 LIMIT 1 ;
\ No newline at end of file
diff --git a/sql/upgrade15-16.sql b/sql/upgrade15-16.sql
new file mode 100644
index 0000000..e5c639d
--- /dev/null
+++ b/sql/upgrade15-16.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `settings` ADD `site_contact_page` TINYINT NOT NULL DEFAULT '1' AFTER `site_timezone` ;
+ALTER TABLE `settings` ADD `site_help_page` TINYINT NOT NULL DEFAULT '1' AFTER `site_contact_page` ;
+
+UPDATE `settings` SET `db_version` = '16' WHERE `id` =1 LIMIT 1 ;
\ No newline at end of file
diff --git a/sql/upgrade16-17.sql b/sql/upgrade16-17.sql
new file mode 100644
index 0000000..3e2ced9
--- /dev/null
+++ b/sql/upgrade16-17.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `users` ADD `notify` tinyint(1) NOT NULL default '0' COMMENT 'Flag incase admin opts in for email notifications' AFTER `last_login` ;
+
+UPDATE `settings` SET `db_version` = '17' WHERE `id` =1 LIMIT 1 ;
\ No newline at end of file
diff --git a/sql/upgrade17-18.sql b/sql/upgrade17-18.sql
new file mode 100644
index 0000000..97f1212
--- /dev/null
+++ b/sql/upgrade17-18.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `alert_sent` DROP INDEX `uniq_incident_id`;
+ALTER TABLE `alert` ADD `alert_radius` TINYINT NOT NULL DEFAULT '20' AFTER `alert_lon`;
+ALTER TABLE `incident` ADD `incident_alert_status` TINYINT NOT NULL DEFAULT '0' COMMENT '0 - Not Tagged for Sending, 1 - Tagged for Sending, 2 - Alerts Have Been Sent' AFTER `incident_datemodify`;
+
+ALTER TABLE `settings` ADD `stat_key` VARCHAR( 30 ) NOT NULL AFTER `stat_id` ;
+
+UPDATE `settings` SET `db_version` = '18' WHERE `id` =1 LIMIT 1 ;
\ No newline at end of file
diff --git a/sql/upgrade18-19.sql b/sql/upgrade18-19.sql
new file mode 100644
index 0000000..77f3409
--- /dev/null
+++ b/sql/upgrade18-19.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `level` CHANGE `id` `id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT;
+ALTER TABLE `reporter` DROP `reporter_level`;
+ALTER TABLE `reporter` ADD `level_id` INT( 11 ) NULL AFTER `service_id`;
+
+UPDATE `settings` SET `db_version` = '19' WHERE `id` =1 LIMIT 1 ;
\ No newline at end of file
diff --git a/sql/upgrade19-20.sql b/sql/upgrade19-20.sql
new file mode 100644
index 0000000..d82bdf1
--- /dev/null
+++ b/sql/upgrade19-20.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `feed_item` ADD `incident_id` INT( 11 ) NOT NULL DEFAULT '0' AFTER `location_id`;
+ALTER TABLE `feed_item` CHANGE `location_id` `location_id` BIGINT( 20 ) NULL DEFAULT '0';
+
+UPDATE `settings` SET `db_version` = '20' WHERE `id` =1 LIMIT 1 ;
\ No newline at end of file
diff --git a/sql/upgrade20-21.sql b/sql/upgrade20-21.sql
new file mode 100644
index 0000000..85a2544
--- /dev/null
+++ b/sql/upgrade20-21.sql
@@ -0,0 +1,15 @@
+--
+-- Table structure for table `layer`
+--
+
+CREATE TABLE `layer` (
+	`id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+	`layer_name` VARCHAR( 255 ) NULL ,
+	`layer_url` VARCHAR( 255 ) NULL ,
+	`layer_file` VARCHAR( 100 ) NULL ,
+	`layer_color` VARCHAR( 20 ) NULL ,
+	`layer_visible` TINYINT NOT NULL DEFAULT '1'
+) ENGINE = MYISAM  DEFAULT CHARSET=utf8;
+
+
+UPDATE `settings` SET `db_version` = '21' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade21-22.sql b/sql/upgrade21-22.sql
new file mode 100644
index 0000000..cc2c52d
--- /dev/null
+++ b/sql/upgrade21-22.sql
@@ -0,0 +1,26 @@
+--
+-- Table structure for table `api_banned`
+--
+
+CREATE TABLE IF NOT EXISTS `api_banned` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `banned_ipaddress` varchar(50) NOT NULL,
+  `banned_date` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='For logging banned API IP addresses' AUTO_INCREMENT=8 ;
+
+--
+-- Table structure for table `api_log`
+--
+
+CREATE TABLE IF NOT EXISTS `api_log` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `api_task` varchar(10) NOT NULL,
+  `api_parameters` varchar(50) NOT NULL,
+  `api_records` tinyint(11) NOT NULL,
+  `api_ipaddress` varchar(50) NOT NULL,
+  `api_date` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='For logging API activities' AUTO_INCREMENT=19 ;
+
+UPDATE `settings` SET `db_version` = '22' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade22-23.sql b/sql/upgrade22-23.sql
new file mode 100644
index 0000000..e0ad9f5
--- /dev/null
+++ b/sql/upgrade22-23.sql
@@ -0,0 +1,46 @@
+CREATE TABLE `mhi_category` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) unsigned DEFAULT NULL,
+  `category_title` varchar(100) CHARACTER SET utf8 NOT NULL,
+  `category_active` tinyint(4) DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+CREATE TABLE `mhi_site` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL,
+  `site_domain` varchar(32) NOT NULL,
+  `site_privacy` tinyint(4) NOT NULL DEFAULT '0',
+  `site_active` tinyint(4) DEFAULT '1',
+  `site_dateadd` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+CREATE TABLE `mhi_site_category` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `site_id` int(11) unsigned NOT NULL,
+  `category_id` int(11) unsigned NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+CREATE TABLE `mhi_site_database` (
+  `mhi_id` int(11) NOT NULL AUTO_INCREMENT,
+  `user` varchar(50) CHARACTER SET utf8 NOT NULL,
+  `pass` varchar(50) CHARACTER SET utf8 NOT NULL,
+  `host` varchar(100) CHARACTER SET utf8 NOT NULL,
+  `port` smallint(6) NOT NULL,
+  `database` varchar(100) CHARACTER SET utf8 NOT NULL,
+  PRIMARY KEY (`mhi_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 COMMENT='This table holds DB credentials for MHI instances';
+
+CREATE TABLE `mhi_users` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `email` varchar(50) CHARACTER SET utf8 NOT NULL,
+  `firstname` varchar(30) CHARACTER SET utf8 NOT NULL,
+  `lastname` varchar(30) CHARACTER SET utf8 NOT NULL,
+  `password` varchar(40) CHARACTER SET utf8 NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+UPDATE `settings` SET `db_version` = '23' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `ushahidi_version` = '1.0.1' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade23-24.sql b/sql/upgrade23-24.sql
new file mode 100644
index 0000000..9fbe4e3
--- /dev/null
+++ b/sql/upgrade23-24.sql
@@ -0,0 +1,8 @@
+UPDATE `scheduler` SET `scheduler_controller`='s_feeds' WHERE `id` = '1';
+UPDATE `scheduler` SET `scheduler_controller`='s_alerts' WHERE `id` = '2';
+UPDATE `scheduler` SET `scheduler_controller`='s_email' WHERE `id` = '3';
+UPDATE `scheduler` SET `scheduler_controller`='s_twitter' WHERE `id` = '4';
+UPDATE `scheduler` SET `scheduler_controller`='s_sharing' WHERE `id` = '5';
+
+UPDATE `settings` SET `db_version` = '24' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `ushahidi_version` = '1.0.1' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade24-25.sql b/sql/upgrade24-25.sql
new file mode 100644
index 0000000..d991ab9
--- /dev/null
+++ b/sql/upgrade24-25.sql
@@ -0,0 +1,27 @@
+CREATE TABLE `mhi_log` (
+  `id` bigint(20) NOT NULL auto_increment,
+  `user_id` int(11) NOT NULL,
+  `action_id` int(11) NOT NULL,
+  `notes` varchar(255) NOT NULL,
+  `ip` int(10) NOT NULL,
+  `time` timestamp NOT NULL default CURRENT_TIMESTAMP,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+CREATE TABLE `mhi_log_actions` (
+  `id` int(11) NOT NULL,
+  `description` text NOT NULL,
+  PRIMARY KEY  (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+INSERT INTO `mhi_log_actions` (`id`, `description`) VALUES
+(1, 'Logged in'),
+(2, 'Logged out'),
+(3, 'Created a deployment.'),
+(4, 'Disabled a deployment.'),
+(5, 'Password reset.'),
+(6, 'New user created.'),
+(7, 'Updated account information.');
+
+UPDATE `settings` SET `db_version` = '25' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `ushahidi_version` = '1.0.1' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade25-26.sql b/sql/upgrade25-26.sql
new file mode 100644
index 0000000..7a43539
--- /dev/null
+++ b/sql/upgrade25-26.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `settings` CHANGE `default_map` `default_map` varchar(100) NOT NULL DEFAULT 'google_normal';
+
+UPDATE `settings` SET `db_version` = '26' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `ushahidi_version` = '1.1.0' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade26-27.sql b/sql/upgrade26-27.sql
new file mode 100644
index 0000000..81ec42b
--- /dev/null
+++ b/sql/upgrade26-27.sql
@@ -0,0 +1,34 @@
+/**
+* Table structure for table `sharing`
+* 
+*/
+DROP TABLE IF EXISTS `sharing`;
+
+CREATE TABLE IF NOT EXISTS `sharing` (                                              -- table description
+	`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+	`sharing_name` VARCHAR(150) NOT NULL,				-- name of the sharing website
+	`sharing_url` VARCHAR(255) NOT NULL,				-- main url of the sharing website
+	`sharing_color` VARCHAR(20) DEFAULT 'CC0000',		-- color for the map layer selector
+	`sharing_active` TINYINT DEFAULT 1 NOT NULL,		-- sharing layer active?
+	PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+
+/**
+* Table structure for table `sharing_log`
+* 
+*/
+DROP TABLE IF EXISTS `sharing_incident`;
+
+CREATE TABLE IF NOT EXISTS `sharing_incident` (                                          -- table description
+	`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+	`sharing_id` INT UNSIGNED NOT NULL,
+	`incident_id` INT NOT NULL,							-- remote website incident ID
+	`incident_title` VARCHAR(255) NOT NULL,				-- remote incident title
+	`latitude` DOUBLE NOT NULL,							-- remote incident latitude
+	`longitude` DOUBLE NOT NULL,						-- remote incident longitude
+	`incident_date` DATETIME,							-- remote incident date
+	PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+UPDATE `settings` SET `db_version` = '27' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade27-28.sql b/sql/upgrade27-28.sql
new file mode 100644
index 0000000..f896279
--- /dev/null
+++ b/sql/upgrade27-28.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `settings` DROP `twitter_username`;
+ALTER TABLE `settings` DROP `twitter_password`;
+UPDATE `settings` SET `db_version` = '28' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade28-29.sql b/sql/upgrade28-29.sql
new file mode 100644
index 0000000..9b4c95d
--- /dev/null
+++ b/sql/upgrade28-29.sql
@@ -0,0 +1,29 @@
+ALTER TABLE `roles` ADD `reports_view` tinyint(4) NOT NULL default '0' AFTER `description`;
+ALTER TABLE `roles` ADD `reports_edit` tinyint(4) NOT NULL default '0' AFTER `reports_view`;
+ALTER TABLE `roles` ADD `reports_evaluation` tinyint(4) NOT NULL default '0' AFTER `reports_edit`;
+ALTER TABLE `roles` ADD `reports_comments` tinyint(4) NOT NULL default '0' AFTER `reports_evaluation`;
+ALTER TABLE `roles` ADD `reports_download` tinyint(4) NOT NULL default '0' AFTER `reports_comments`;
+ALTER TABLE `roles` ADD `reports_upload` tinyint(4) NOT NULL default '0' AFTER `reports_download`;
+ALTER TABLE `roles` ADD `messages` tinyint(4) NOT NULL default '0' AFTER `reports_upload`;
+ALTER TABLE `roles` ADD `messages_reporters` tinyint(4) NOT NULL default '0' AFTER `messages`;
+ALTER TABLE `roles` ADD `stats` tinyint(4) NOT NULL default '0' AFTER `messages_reporters`;
+ALTER TABLE `roles` ADD `settings` tinyint(4) NOT NULL default '0' AFTER `stats`;
+ALTER TABLE `roles` ADD `manage` tinyint(4) NOT NULL default '0' AFTER `settings`;
+ALTER TABLE `roles` ADD `users` tinyint(4) NOT NULL default '0' AFTER `manage`;
+
+UPDATE `roles` SET
+	`reports_view` = 1,
+	`reports_edit` = 1,
+	`reports_evaluation` = 1,
+	`reports_comments` = 1,
+	`reports_download` = 1,
+	`reports_upload` = 1,
+	`messages` = 1,
+	`messages_reporters` = 1,
+	`stats` = 1,
+	`settings` = 1,
+	`manage` = 1,
+	`users` = 1
+WHERE `id` = 2 OR `id` = 3;
+
+UPDATE `settings` SET `db_version` = '29' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade29-30.sql b/sql/upgrade29-30.sql
new file mode 100644
index 0000000..588b28c
--- /dev/null
+++ b/sql/upgrade29-30.sql
@@ -0,0 +1,14 @@
+ALTER TABLE `settings` ADD `site_message` TEXT NOT NULL AFTER `site_help_page`;
+
+CREATE TABLE IF NOT EXISTS `plugin` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `plugin_name` varchar(100) NOT NULL,
+  `plugin_url` varchar(250) NULL,
+  `plugin_description` text NULL,
+  `plugin_active` tinyint(4) DEFAULT '0',
+  `plugin_installed` tinyint(4) DEFAULT '0',
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `plugin_name` (`plugin_name`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
+
+UPDATE `settings` SET `db_version` = '30' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade30-31.sql b/sql/upgrade30-31.sql
new file mode 100644
index 0000000..5bb9897
--- /dev/null
+++ b/sql/upgrade30-31.sql
@@ -0,0 +1,13 @@
+/* Remove "UNIQUE" Index Key that prevented multiple reports from sharing one location */
+ALTER TABLE `incident` DROP INDEX `location_id`;
+ALTER TABLE `incident` ADD INDEX `location_id` (`location_id`);
+
+/* Connect Attached Images to Message via Message_ID */
+ALTER TABLE `media` ADD `message_id` bigint(20) NULL DEFAULT NULL  AFTER `incident_id`;
+
+/* Added trusted category column */
+ALTER TABLE `category` ADD `category_trusted` tinyint(4) NOT NULL default '0' AFTER `category_visible`;
+
+INSERT INTO `category` (`category_type`, `category_title`, `category_description`, `category_color`, `category_visible`, `category_trusted`) VALUES (5, 'Trusted Reports', 'Reports from trusted reporters', '339900', 1, 1);
+
+UPDATE `settings` SET `db_version` = '31' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade31-32.sql b/sql/upgrade31-32.sql
new file mode 100644
index 0000000..0192c8b
--- /dev/null
+++ b/sql/upgrade31-32.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `category` ADD `category_image_thumb` varchar(100) NULL DEFAULT NULL  AFTER `category_image`;
+
+UPDATE `settings` SET `db_version` = '32' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade32-33.sql b/sql/upgrade32-33.sql
new file mode 100644
index 0000000..c81f9d5
--- /dev/null
+++ b/sql/upgrade32-33.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `settings` ADD `cache_pages` tinyint(4) NOT NULL DEFAULT '0'  AFTER `allow_clustering`;
+ALTER TABLE `settings` ADD `cache_pages_lifetime` int(4) NOT NULL DEFAULT '1800'  AFTER `cache_pages`;
+
+UPDATE `settings` SET `db_version` = '33' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade33-34.sql b/sql/upgrade33-34.sql
new file mode 100644
index 0000000..94c2d65
--- /dev/null
+++ b/sql/upgrade33-34.sql
@@ -0,0 +1,8 @@
+ALTER TABLE `settings` ADD `sms_provider` varchar(100) NULL DEFAULT NULL  AFTER `items_per_page_admin`;
+
+ALTER TABLE `settings` DROP `clickatell_api`;
+ALTER TABLE `settings` DROP `clickatell_username`;
+ALTER TABLE `settings` DROP `clickatell_password`;
+ALTER TABLE `settings` DROP `frontlinesms_key`;
+
+UPDATE `settings` SET `db_version` = '34' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade34-35.sql b/sql/upgrade34-35.sql
new file mode 100644
index 0000000..4ef5a2c
--- /dev/null
+++ b/sql/upgrade34-35.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `media` ADD `media_medium` varchar(255) default NULL  AFTER `media_link`;
+
+UPDATE `settings` SET `db_version` = '35' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade35-36.sql b/sql/upgrade35-36.sql
new file mode 100644
index 0000000..ac11143
--- /dev/null
+++ b/sql/upgrade35-36.sql
@@ -0,0 +1,15 @@
+--
+-- Definition of table `api_settings`
+--
+
+CREATE TABLE  IF NOT EXISTS `api_settings` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `default_record_limit` int(11) NOT NULL DEFAULT '20',
+  `max_record_limit` int(11) DEFAULT NULL,
+  `max_requests_per_ip_address` int(11) DEFAULT NULL,
+  `max_requests_quota_basis` int(11) DEFAULT NULL,
+  `modification_date` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='For storing API logging settings';
+
+UPDATE `settings` SET `db_version` = '36' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade36-37.sql b/sql/upgrade36-37.sql
new file mode 100644
index 0000000..200e375
--- /dev/null
+++ b/sql/upgrade36-37.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `settings` ADD `site_copyright_statement` TEXT DEFAULT NULL AFTER `site_message`; 
+
+UPDATE `settings` SET `db_version` = '37' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade37-38.sql b/sql/upgrade37-38.sql
new file mode 100644
index 0000000..7618444
--- /dev/null
+++ b/sql/upgrade37-38.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `plugin` ADD `plugin_priority` tinyint(4) DEFAULT '0' AFTER `plugin_description`; 
+
+UPDATE `settings` SET `db_version` = '38' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade38-39.sql b/sql/upgrade38-39.sql
new file mode 100644
index 0000000..732ea12
--- /dev/null
+++ b/sql/upgrade38-39.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `settings` ADD `ftp_server` varchar(100) NULL DEFAULT NULL  AFTER `email_ssl`;
+ALTER TABLE `settings` ADD `ftp_user_name` varchar(100) NULL DEFAULT NULL  AFTER `ftp_server`;
+
+UPDATE `settings` SET `db_version` = '39' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade39-40.sql b/sql/upgrade39-40.sql
new file mode 100644
index 0000000..0b338f8
--- /dev/null
+++ b/sql/upgrade39-40.sql
@@ -0,0 +1,83 @@
+/* This update converts user and roles tables to MyISAM */
+
+/* UPDATE ROLES TABLES */
+
+CREATE TABLE IF NOT EXISTS `roles_temporary` (
+    `id` int(11) unsigned NOT NULL auto_increment,
+    `name` varchar(32) NOT NULL,
+    `description` varchar(255) NOT NULL,
+	`reports_view` tinyint(4) NOT NULL default '0',
+	`reports_edit` tinyint(4) NOT NULL default '0',
+	`reports_evaluation` tinyint(4) NOT NULL default '0',
+	`reports_comments` tinyint(4) NOT NULL default '0',
+	`reports_download` tinyint(4) NOT NULL default '0',
+	`reports_upload` tinyint(4) NOT NULL default '0',
+	`messages` tinyint(4) NOT NULL default '0',
+	`messages_reporters` tinyint(4) NOT NULL default '0',
+	`stats` tinyint(4) NOT NULL default '0',
+	`settings` tinyint(4) NOT NULL default '0',
+	`manage` tinyint(4) NOT NULL default '0',
+	`users` tinyint(4) NOT NULL default '0',
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `uniq_name` (`name`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+
+CREATE TABLE IF NOT EXISTS `roles_users_temporary` (
+    `user_id` int(11) unsigned NOT NULL,
+    `role_id` int(11) unsigned NOT NULL,
+  PRIMARY KEY  (`user_id`,`role_id`),
+  KEY `fk_role_id` (`role_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+
+INSERT INTO `roles_temporary` SELECT * FROM `roles`;
+INSERT INTO `roles_users_temporary` SELECT * FROM `roles_users`;
+
+DROP TABLE `roles_users`;
+DROP TABLE `roles`;
+
+RENAME TABLE `roles_temporary` TO `roles`;
+RENAME TABLE `roles_users_temporary` TO `roles_users`;
+
+/* UPDATE USERS TABLES */
+
+CREATE TABLE IF NOT EXISTS `users_temporary` (
+    `id` int(11) unsigned NOT NULL auto_increment,
+    `name` varchar(200) default NULL,
+    `email` varchar(127) NOT NULL,
+    `username` varchar(31) NOT NULL default '',
+    `password` char(50) NOT NULL,
+    `logins` int(10) unsigned NOT NULL default '0',
+    `last_login` int(10) unsigned default NULL,
+    `notify` tinyint(1) NOT NULL default '0' COMMENT 'Flag incase admin opts in for email notifications',
+    `updated` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `uniq_username` (`username`),
+  UNIQUE KEY `uniq_email` (`email`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE IF NOT EXISTS `user_tokens_temporary` (
+    `id` int(11) unsigned NOT NULL auto_increment,
+    `user_id` int(11) unsigned NOT NULL,
+    `user_agent` varchar(40) NOT NULL,
+    `token` varchar(32) NOT NULL,
+    `created` int(10) unsigned NOT NULL,
+    `expires` int(10) unsigned NOT NULL,
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `uniq_token` (`token`),
+  KEY `fk_user_id` (`user_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+INSERT INTO `users_temporary` SELECT * FROM `users`;
+INSERT INTO `user_tokens_temporary` SELECT * FROM `user_tokens`;
+
+DROP TABLE `user_tokens`;
+DROP TABLE `users`;
+
+RENAME TABLE `users_temporary` TO `users`;
+RENAME TABLE `user_tokens_temporary` TO `user_tokens`;
+
+/* Step up the database version number */
+
+UPDATE `settings` SET `db_version` = '40' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade40-41.sql b/sql/upgrade40-41.sql
new file mode 100644
index 0000000..e43ebc3
--- /dev/null
+++ b/sql/upgrade40-41.sql
@@ -0,0 +1,13 @@
+/**
+* Table structure for table 'alert_category'
+*/
+CREATE TABLE IF NOT EXISTS `alert_category` (
+  `id` int(11) NOT NULL auto_increment,
+  `alert_id` int(11),
+  `category_id` int(11),
+  PRIMARY KEY (`id`),
+  KEY `alert_id` (`alert_id`),
+  KEY `category_id` (`category_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
+
+UPDATE `settings` SET `db_version` = '41' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade41-42.sql b/sql/upgrade41-42.sql
new file mode 100644
index 0000000..bfc56d2
--- /dev/null
+++ b/sql/upgrade41-42.sql
@@ -0,0 +1,4 @@
+/** Increase the size of the api_parameters column in api_log */
+ALTER TABLE `api_log` MODIFY COLUMN `api_parameters` VARCHAR(100) NOT NULL;
+
+UPDATE `settings` SET `db_version` = '42' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade42-43.sql b/sql/upgrade42-43.sql
new file mode 100644
index 0000000..9bf380e
--- /dev/null
+++ b/sql/upgrade42-43.sql
@@ -0,0 +1,8 @@
+/** Add a new column to roles that shall differentiate superadmins from normal admins */
+ALTER TABLE `roles` ADD COLUMN `manage_roles` tinyint(4) NOT NULL default 0;
+
+-- Enable this privilege for the super admin role
+UPDATE `roles` SET `manage_roles` = 1 WHERE `name` = 'superadmin';
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = 43 WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade43-44.sql b/sql/upgrade43-44.sql
new file mode 100644
index 0000000..d771aa9
--- /dev/null
+++ b/sql/upgrade43-44.sql
@@ -0,0 +1,14 @@
+/**
+* Table structure for table `geometry`
+*/
+CREATE TABLE IF NOT EXISTS `geometry` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `incident_id` bigint(20) NOT NULL,
+  `geometry` geometry NOT NULL,
+  `geometry_color` varchar(20) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  SPATIAL KEY `geometry` (`geometry`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = 44 WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade44-45.sql b/sql/upgrade44-45.sql
new file mode 100644
index 0000000..fcee331
--- /dev/null
+++ b/sql/upgrade44-45.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `incident` ADD `incident_zoom` tinyint NULL DEFAULT NULL  AFTER `incident_alert_status`;
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = 45 WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade45-46.sql b/sql/upgrade45-46.sql
new file mode 100644
index 0000000..209b1f8
--- /dev/null
+++ b/sql/upgrade45-46.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `settings` ADD `private_deployment` tinyint(4) NOT NULL DEFAULT '0'  AFTER `cache_pages_lifetime`;
+
+UPDATE `settings` SET `db_version` = '46' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade46-47.sql b/sql/upgrade46-47.sql
new file mode 100644
index 0000000..d2b03f6
--- /dev/null
+++ b/sql/upgrade46-47.sql
@@ -0,0 +1,32 @@
+# Checkins will be going here
+CREATE TABLE IF NOT EXISTS `checkin`
+(
+`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
+`user_id` INT UNSIGNED NOT NULL,
+`location_id` BIGINT UNSIGNED NOT NULL,
+`checkin_description` VARCHAR(255),
+`checkin_date` DATETIME NOT NULL,
+`checkin_auto` ENUM('0','1') DEFAULT '0',
+PRIMARY KEY (`id`)
+);
+
+# Mobile devices will be going here
+CREATE TABLE IF NOT EXISTS `user_devices` (
+  `id` varchar(255) NOT NULL,
+  `user_id` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+# Add new column to roles and media tables
+ALTER TABLE `roles` ADD `checkin` TINYINT NOT NULL DEFAULT '0';
+ALTER TABLE `roles` ADD `checkin_admin` TINYINT NOT NULL DEFAULT '0';
+ALTER TABLE `media` ADD `checkin_id` BIGINT NULL DEFAULT NULL AFTER `incident_id`;
+
+# set roles for existing accounts, if they exist
+UPDATE `roles` SET `checkin` = '1', `checkin_admin` = '1' WHERE `name` = 'superadmin';
+UPDATE `roles` SET `checkin` = '1', `checkin_admin` = '1' WHERE `name` = 'admin';
+UPDATE `roles` SET `checkin` = '1' WHERE `name` = 'login';
+
+# Bump up version
+UPDATE `settings` SET `db_version` = '47' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `ushahidi_version` = '2.0.2' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade47-48.sql b/sql/upgrade47-48.sql
new file mode 100644
index 0000000..63ceeea
--- /dev/null
+++ b/sql/upgrade47-48.sql
@@ -0,0 +1,6 @@
+ALTER TABLE `geometry` ADD `geometry_label` varchar(150) NULL DEFAULT NULL  AFTER `geometry`;
+ALTER TABLE `geometry` ADD `geometry_comment` varchar(255) NULL DEFAULT NULL  AFTER `geometry_label`;
+ALTER TABLE `geometry` ADD `geometry_strokewidth` varchar(5) NULL DEFAULT NULL  AFTER `geometry_color`;
+
+# Bump up version
+UPDATE `settings` SET `db_version` = '48' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade48-49.sql b/sql/upgrade48-49.sql
new file mode 100644
index 0000000..1021b8b
--- /dev/null
+++ b/sql/upgrade48-49.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `users` ADD `color` VARCHAR( 6 ) NOT NULL DEFAULT 'FF0000';
+
+# Bump up version
+UPDATE `settings` SET `db_version` = '49' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade49-50.sql b/sql/upgrade49-50.sql
new file mode 100644
index 0000000..7bb5e5f
--- /dev/null
+++ b/sql/upgrade49-50.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `settings` ADD `checkins` tinyint(4) NOT NULL DEFAULT '0'  AFTER `alerts_email`;
+
+UPDATE `settings` SET `db_version` = '50' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade50-51.sql b/sql/upgrade50-51.sql
new file mode 100644
index 0000000..d3dddfe
--- /dev/null
+++ b/sql/upgrade50-51.sql
@@ -0,0 +1,13 @@
+-- Drop  the Laconica table
+DROP TABLE IF EXISTS `laconica`;
+
+-- Remove Laconica service from the list of message services
+DELETE FROM `service` WHERE `id` = 4;
+
+-- Remove all Laconica-related columns from the settings table
+ALTER TABLE `settings` DROP COLUMN `laconica_username`;
+ALTER TABLE `settings` DROP COLUMN `laconica_password`;
+ALTER TABLE `settings` DROP COLUMN `laconica_site`;
+
+-- Update the database version
+UPDATE `settings` SET db_version = '51' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade51-52.sql b/sql/upgrade51-52.sql
new file mode 100644
index 0000000..db1422f
--- /dev/null
+++ b/sql/upgrade51-52.sql
@@ -0,0 +1,17 @@
+-- Drop  the feedback table
+DROP TABLE IF EXISTS `feedback`;
+
+-- Drop  the feedback_person table
+DROP TABLE IF EXISTS `feedback_person`;
+
+-- Drop  the idp table
+DROP TABLE IF EXISTS `idp`;
+
+-- Drop  the pending_users table
+DROP TABLE IF EXISTS `pending_users`;
+
+-- Remove idp-related column from the verified table
+ALTER TABLE `verified` DROP COLUMN `idp_id`;
+
+-- Update the database version
+UPDATE `settings` SET db_version = '52' WHERE `id` = 1 LIMIT 1;
diff --git a/sql/upgrade52-53.sql b/sql/upgrade52-53.sql
new file mode 100644
index 0000000..21e2462
--- /dev/null
+++ b/sql/upgrade52-53.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `category` ADD `category_position` tinyint(4) NOT NULL DEFAULT '0'  AFTER `category_type`;
+UPDATE `settings` SET `db_version` = '53' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade53-54.sql b/sql/upgrade53-54.sql
new file mode 100644
index 0000000..bbd4080
--- /dev/null
+++ b/sql/upgrade53-54.sql
@@ -0,0 +1,8 @@
+/**
+* Drop organization-related tables
+*/
+DROP TABLE IF EXISTS `organization_incident`;
+DROP TABLE IF EXISTS `organization`;
+
+
+UPDATE `settings` SET `db_version` = 54 WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade54-55.sql b/sql/upgrade54-55.sql
new file mode 100644
index 0000000..f1e8afd
--- /dev/null
+++ b/sql/upgrade54-55.sql
@@ -0,0 +1,61 @@
+/**
+* Add new column to Alert table. Associates alert to user
+*/
+ALTER TABLE `alert` ADD `user_id` int(11) DEFAULT '0'  AFTER `id`;
+
+/**
+* Add new column to Ratings table. Associates alert to user
+*/
+ALTER TABLE `rating` ADD `user_id` int(11) DEFAULT '0'  AFTER `id`;
+
+/**
+* Add new column to Checkin table. Associates checkin to incident
+*/
+ALTER TABLE `checkin` ADD `incident_id` int(11) NULL DEFAULT '0'  AFTER `location_id`;
+
+
+/**
+* Create new Member Role
+*/
+INSERT INTO `roles` (`name`,`description`, `reports_view`, `reports_edit`, `reports_evaluation`, `reports_comments`, `reports_download`, `reports_upload`, `messages`, `messages_reporters`, `stats`, `settings`, `manage`, `users`, `manage_roles`, `checkin`, `checkin_admin`) VALUES
+('member','Regular user with access only to the member area', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
+
+/**
+* Table structure for table `openid`
+*/
+CREATE TABLE IF NOT EXISTS `openid` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL,
+  `openid` varchar(255) NOT NULL,
+  `openid_email` varchar(127) NOT NULL,
+  `openid_server` varchar(255) NOT NULL,
+  `openid_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `openid` (`openid`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
+
+/**
+* Table structure for table `private_message`
+*/
+CREATE TABLE IF NOT EXISTS `private_message` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) NOT NULL DEFAULT '0',
+  `user_id` int(11) NOT NULL,
+  `from_user_id` int(11) DEFAULT '0',
+  `private_subject` varchar(255) NOT NULL,
+  `private_message` text NOT NULL,
+  `private_message_date` datetime NOT NULL,
+  `private_message_new` tinyint(4) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+
+/**
+* Add new columns for blocks!
+*/
+ALTER TABLE `settings` ADD `blocks` text  AFTER `twitter_hashtags`;
+ALTER TABLE `settings` ADD `blocks_per_row` tinyint NOT NULL DEFAULT '2'  AFTER `blocks`;
+UPDATE `settings` SET `blocks`='reports_block|news_block' WHERE `id` = '1';
+
+
+UPDATE `settings` SET `db_version` = 55 WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade55-56.sql b/sql/upgrade55-56.sql
new file mode 100644
index 0000000..27ba076
--- /dev/null
+++ b/sql/upgrade55-56.sql
@@ -0,0 +1,3 @@
+# Removed update - replacing semi-colon with pipe, it broke the installer
+
+UPDATE `settings` SET `db_version` = 56 WHERE `id` = 1 LIMIT 1;
diff --git a/sql/upgrade56-57.sql b/sql/upgrade56-57.sql
new file mode 100644
index 0000000..a207e1f
--- /dev/null
+++ b/sql/upgrade56-57.sql
@@ -0,0 +1,3 @@
+INSERT INTO `scheduler` (`scheduler_name`,`scheduler_last`,`scheduler_weekday`,`scheduler_day`,`scheduler_hour`,`scheduler_minute`,`scheduler_controller`,`scheduler_active`) VALUES ('Cleanup','0','-1','-1','-1','0','s_cleanup','1');
+
+UPDATE `settings` SET `db_version` = 57 WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade57-58.sql b/sql/upgrade57-58.sql
new file mode 100644
index 0000000..b958489
--- /dev/null
+++ b/sql/upgrade57-58.sql
@@ -0,0 +1,16 @@
+CREATE TABLE IF NOT EXISTS `form_field_option` (
+	`id` int(11) NOT NULL auto_increment,
+	`form_field_id` int(11) NOT NULL default '0',
+	`option_name` varchar(200) default NULL,
+	`option_value` text default NULL,
+	PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+ALTER TABLE `roles` ADD `access_level` tinyint(4) NOT NULL default '0';
+
+ALTER TABLE `form_field` ADD `field_ispublic_visible` tinyint(4) NOT NULL default '0';
+ 
+ALTER TABLE `form_field` ADD  `field_ispublic_submit` tinyint(4) NOT NULL default '0';
+
+
+UPDATE `settings` SET `db_version` = '58' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade58-59.sql b/sql/upgrade58-59.sql
new file mode 100644
index 0000000..3ffb2a4
--- /dev/null
+++ b/sql/upgrade58-59.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `users` ADD `code` VARCHAR(30) NULL DEFAULT NULL AFTER `color`;
+ALTER TABLE `users` ADD `confirmed` TINYINT(1) NOT NULL DEFAULT '0' AFTER `code`;
+UPDATE `settings` SET `db_version` = '59' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade59-60.sql b/sql/upgrade59-60.sql
new file mode 100644
index 0000000..a2ecd0a
--- /dev/null
+++ b/sql/upgrade59-60.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `settings` ADD `facebook_appid` VARCHAR(150)  NULL  DEFAULT NULL  AFTER `checkins`;
+ALTER TABLE `settings` ADD `facebook_appsecret` VARCHAR(150)  NULL  DEFAULT NULL  AFTER `facebook_appid`;
+UPDATE `settings` SET `db_version` = '60' WHERE `id`=1 LIMIT 1;
+
diff --git a/sql/upgrade60-61.sql b/sql/upgrade60-61.sql
new file mode 100644
index 0000000..c2fb8fb
--- /dev/null
+++ b/sql/upgrade60-61.sql
@@ -0,0 +1,11 @@
+CREATE TABLE `actions` (
+	`action_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+	`action` VARCHAR( 75 ) NOT NULL ,
+	`qualifiers` TEXT NOT NULL ,
+	`response` VARCHAR( 75 ) NOT NULL ,
+	`response_vars` TEXT NOT NULL,
+	`active` TINYINT NOT NULL
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+UPDATE `settings` SET `db_version` = '61' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `ushahidi_version` = '2.1' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade61-62.sql b/sql/upgrade61-62.sql
new file mode 100644
index 0000000..4a12c52
--- /dev/null
+++ b/sql/upgrade61-62.sql
@@ -0,0 +1,8 @@
+CREATE TABLE `actions_log` (
+`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+`action_id` INT NOT NULL ,
+`user_id` INT NOT NULL ,
+`time` INT( 10 ) NOT NULL
+) ENGINE = MYISAM ;
+
+UPDATE `settings` SET `db_version` = '62' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade62-63.sql b/sql/upgrade62-63.sql
new file mode 100644
index 0000000..6a2e550
--- /dev/null
+++ b/sql/upgrade62-63.sql
@@ -0,0 +1,18 @@
+ALTER TABLE `media` ADD `badge_id` int(11) default NULL AFTER `message_id`;
+
+ALTER TABLE `users` ADD `public_profile` TINYINT( 1 ) NOT NULL DEFAULT '1';
+
+CREATE TABLE `badge` (
+`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+`name` VARCHAR( 250 ) NOT NULL ,
+`description` TEXT NOT NULL
+) ENGINE = MYISAM DEFAULT CHARSET=utf8;
+
+CREATE TABLE `badge_users` (
+`user_id` INT NOT NULL ,
+`badge_id` INT NOT NULL ,
+PRIMARY KEY ( `user_id` , `badge_id` )
+) ENGINE=MYISAM DEFAULT CHARSET=utf8;
+
+
+UPDATE `settings` SET `db_version` = '63' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade63-64.sql b/sql/upgrade63-64.sql
new file mode 100644
index 0000000..a5cc0aa
--- /dev/null
+++ b/sql/upgrade63-64.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `settings` ADD `site_submit_report_message` TEXT NOT NULL AFTER `site_copyright_statement`;
+ALTER TABLE `settings` ADD `site_banner_id` int(11) default NULL AFTER `site_tagline`;
+
+UPDATE `settings` SET `db_version` = '64' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade64-65.sql b/sql/upgrade64-65.sql
new file mode 100644
index 0000000..ac1fbd8
--- /dev/null
+++ b/sql/upgrade64-65.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `location` MODIFY `country_id` int(11) NOT NULL default '0';
+
+UPDATE `settings` SET `db_version` = '65' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade65-66.sql b/sql/upgrade65-66.sql
new file mode 100644
index 0000000..1b59b97
--- /dev/null
+++ b/sql/upgrade65-66.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `settings` ADD `allow_alerts` tinyint(4) NOT NULL DEFAULT '0';
+
+UPDATE `settings` SET `db_version` = '66' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade66-67.sql b/sql/upgrade66-67.sql
new file mode 100644
index 0000000..62ed985
--- /dev/null
+++ b/sql/upgrade66-67.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `category` MODIFY `category_image` VARCHAR(255);
+ALTER TABLE `category` MODIFY `category_image_thumb` VARCHAR(255);
+
+UPDATE `settings` SET `db_version` = '67' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade67-68.sql b/sql/upgrade67-68.sql
new file mode 100644
index 0000000..31c6e8b
--- /dev/null
+++ b/sql/upgrade67-68.sql
@@ -0,0 +1,6 @@
+-- Remove incident_source and incident_information columns from the incident table
+ALTER TABLE `incident` DROP COLUMN `incident_source`;
+ALTER TABLE `incident` DROP COLUMN `incident_information`;
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '68' WHERE `id` = 1 LIMIT 1;
diff --git a/sql/upgrade68-69.sql b/sql/upgrade68-69.sql
new file mode 100644
index 0000000..6b5cdfe
--- /dev/null
+++ b/sql/upgrade68-69.sql
@@ -0,0 +1,5 @@
+-- Remove incident_source and incident_information columns from the incident table
+ALTER TABLE `users` CHANGE `username` `username` VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '';
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '69' WHERE `id` = 1 LIMIT 1;
diff --git a/sql/upgrade69-70.sql b/sql/upgrade69-70.sql
new file mode 100644
index 0000000..a61c972
--- /dev/null
+++ b/sql/upgrade69-70.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `comment` CHANGE `incident_id` `incident_id` BIGINT NULL DEFAULT NULL;
+ALTER TABLE `comment` ADD `checkin_id` BIGINT NULL DEFAULT NULL AFTER `incident_id`;
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '70' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade70-71.sql b/sql/upgrade70-71.sql
new file mode 100644
index 0000000..c673532
--- /dev/null
+++ b/sql/upgrade70-71.sql
@@ -0,0 +1,7 @@
+ALTER TABLE `users` ADD `approved` TINYINT NOT NULL ;
+ALTER TABLE `users` ADD `riverid` VARCHAR( 128 ) NOT NULL AFTER `id`;
+ALTER TABLE `users` ADD `needinfo` TINYINT NOT NULL ;
+ALTER TABLE `user_tokens` CHANGE `token` `token` VARCHAR( 64 ) NOT NULL;
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '71' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade71-72.sql b/sql/upgrade71-72.sql
new file mode 100644
index 0000000..e0c5035
--- /dev/null
+++ b/sql/upgrade71-72.sql
@@ -0,0 +1,10 @@
+CREATE TABLE IF NOT EXISTS `externalapp` (
+`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+`name` VARCHAR( 255 ) NOT NULL ,
+`url` VARCHAR( 255 ) NOT NULL
+) ENGINE = MYISAM ;
+
+INSERT INTO `externalapp` (`id`, `name`, `url`) VALUES (NULL, 'iPhone', 'http://download.ushahidi.com/track_download.php?download=ios'), (NULL, 'Android', 'http://download.ushahidi.com/track_download.php?download=android');
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '72' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade72-73.sql b/sql/upgrade72-73.sql
new file mode 100644
index 0000000..44271fd
--- /dev/null
+++ b/sql/upgrade72-73.sql
@@ -0,0 +1,38 @@
+-- If there happens to be a category with id 5, assign it a different id
+UPDATE `category` SET `id` = '999' WHERE `id` = '5';
+
+-- Added Query: Update any incident tied to recently altered category with the new id
+UPDATE `incident_category` set `category_id` = '999' where `category_id` = '5';
+
+-- Check to ensure that child categories of recently altered parent category are also updated
+UPDATE `category` set `parent_id` = '999' where `parent_id` = '5';
+
+-- Insert orphaned reports category
+INSERT INTO `category` (`id`, `category_type`, `category_title`, `category_description`, `category_color`, `category_visible`, `category_trusted`) VALUES
+(5, 5, 'NONE', 'Holds orphaned reports', '009887', 1, 1);
+
+-- Change incident_category table structure and set default value for category_id to orphaned reports category i.e 5
+ALTER TABLE `incident_category` CHANGE `category_id` `category_id` int(11) NOT NULL default '5';
+
+-- Remove incident category links that link to no category
+DELETE FROM `incident_category` WHERE NOT EXISTS (select `category_title` from `category` where `id` = category_id);
+
+-- Add incidents with no categories deleted above, and assign them to orphaned reports category	
+INSERT into `incident_category` (`incident_id`) SELECT `e`.`id` FROM `incident` e	
+WHERE NOT EXISTS ( SELECT DISTINCT (`i`.`id`) FROM `incident` i JOIN `incident_category` ic ON `ic`.`incident_id` = `i`.`id` WHERE `e`.`id` = `ic`.`incident_id`);
+
+-- Delete updated entries tied to a non-orphaned report i.e a report with multiple categories
+DELETE FROM `incident_category` WHERE `category_id` =5 AND `incident_id` IN (
+SELECT `incident_id` FROM (
+	SELECT `ic`.`incident_id`
+	FROM `incident_category` ic
+	GROUP BY `ic`.`incident_id`
+	HAVING COUNT( `ic`.`category_id` ) >1
+	) 
+	AS X);
+
+-- Unapprove orphaned reports
+UPDATE `incident` SET `incident_active` = 0 WHERE `id` in (SELECT `incident_id` FROM `incident_category` WHERE `category_id` = 5);
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '73' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade73-74.sql b/sql/upgrade73-74.sql
new file mode 100644
index 0000000..51d9b93
--- /dev/null
+++ b/sql/upgrade73-74.sql
@@ -0,0 +1,9 @@
+-- Check for incidents that have no categories, and assign them to orphaned reports category
+INSERT into `incident_category` (`incident_id`) SELECT `e`.`id` FROM `incident` e
+WHERE NOT EXISTS ( SELECT DISTINCT (`i`.`id`) FROM `incident` i JOIN `incident_category` ic ON `ic`.`incident_id` = `i`.`id` WHERE `e`.`id` = `ic`.`incident_id`);
+
+-- Unapprove orphaned reports that were imported
+UPDATE `incident` SET `incident_active` = 0 WHERE `id` in (SELECT `incident_id` FROM `incident_category` WHERE `category_id` = 5);
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '74' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade74-75.sql b/sql/upgrade74-75.sql
new file mode 100644
index 0000000..d0cab07
--- /dev/null
+++ b/sql/upgrade74-75.sql
@@ -0,0 +1,11 @@
+-- Supports the ability to require approval of new users
+ALTER TABLE `settings` ADD `manually_approve_users` TINYINT( 4 ) NOT NULL DEFAULT '0' AFTER `allow_alerts`;
+
+-- Supports ability to require confirmation of email addresses
+ALTER TABLE `settings` ADD `require_email_confirmation` TINYINT( 4 ) NOT NULL DEFAULT '0' AFTER `allow_alerts`;
+
+-- Confirm the account of main admin user in case there's an invalid address and confirm addresses is enabled so they can log in
+UPDATE `users` SET `confirmed` = 1 WHERE `id` = 1;
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '75' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade75-76.sql b/sql/upgrade75-76.sql
new file mode 100644
index 0000000..f545fea
--- /dev/null
+++ b/sql/upgrade75-76.sql
@@ -0,0 +1,7 @@
+CREATE TABLE `maintenance` (
+`allowed_ip` VARCHAR( 15 ) NOT NULL ,
+PRIMARY KEY ( `allowed_ip` )
+) ENGINE = MYISAM ;
+
+-- Update the database version
+UPDATE `settings` SET `db_version` = '76' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade76-77.sql b/sql/upgrade76-77.sql
new file mode 100644
index 0000000..3a684d1
--- /dev/null
+++ b/sql/upgrade76-77.sql
@@ -0,0 +1,2 @@
+UPDATE `settings` SET `ushahidi_version` = '2.2' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `db_version` = '77' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade77-78.sql b/sql/upgrade77-78.sql
new file mode 100644
index 0000000..577bdcf
--- /dev/null
+++ b/sql/upgrade77-78.sql
@@ -0,0 +1,4 @@
+-- Check to ensure that child categories of recently altered parent category are also updated
+UPDATE `category` set `parent_id` = '999' where `parent_id` = '5';
+
+UPDATE `settings` SET `db_version` = '78' WHERE `id`=1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade78-79.sql b/sql/upgrade78-79.sql
new file mode 100644
index 0000000..9b64ea7
--- /dev/null
+++ b/sql/upgrade78-79.sql
@@ -0,0 +1,2 @@
+UPDATE `settings` SET `ushahidi_version` = '2.2.1' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `db_version` = '79' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade79-80.sql b/sql/upgrade79-80.sql
new file mode 100644
index 0000000..1ccbc7e
--- /dev/null
+++ b/sql/upgrade79-80.sql
@@ -0,0 +1,189 @@
+-- Altering table structures for the following tables based on recommendations from http://lopad.org/6aOddOZx72
+
+-- actions_log
+ALTER TABLE `actions_log` ADD INDEX `action_id` (`action_id`);
+
+-- alert
+ALTER TABLE  `alert` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NULL DEFAULT  '0';
+ALTER TABLE  `alert` ADD INDEX  `user_id` (  `user_id` );
+
+-- alert_category
+ALTER TABLE  `alert_category` CHANGE  `alert_id`  `alert_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `alert_category` CHANGE  `category_id`  `category_id` INT( 11 ) UNSIGNED NULL DEFAULT NULL;
+
+-- alert_sent
+ALTER TABLE  `alert_sent` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NOT NULL;
+ALTER TABLE  `alert_sent` CHANGE  `alert_id`  `alert_id` BIGINT( 20 ) UNSIGNED NOT NULL;
+
+ALTER TABLE `alert_sent` ADD INDEX  `incident_id` (  `incident_id` );
+ALTER TABLE `alert_sent` ADD INDEX  `alert_id` (  `alert_id` );
+
+-- badge_users
+ALTER TABLE  `badge_users` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NOT NULL;
+
+-- category
+ALTER TABLE  `category` DROP  `category_type` ,
+DROP  `category_image_shadow` ;
+
+-- category_lang
+ALTER TABLE  `category_lang` CHANGE  `category_id`  `category_id` INT( 11 ) UNSIGNED NOT NULL;
+ALTER TABLE  `category_lang` ADD INDEX  `category_id` (  `category_id` );
+
+-- checkin
+ALTER TABLE  `checkin` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NOT NULL;
+ALTER TABLE  `checkin` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT  '0';
+ALTER TABLE  `checkin` CHANGE  `location_id`  `location_id` BIGINT( 20 ) UNSIGNED NOT NULL;
+
+ALTER TABLE `checkin` ADD INDEX  `incident_id` (  `incident_id` );
+ALTER TABLE `checkin` ADD INDEX  `user_id` (  `user_id` );
+ALTER TABLE `checkin` ADD INDEX  `location_id` (  `location_id` );
+
+-- city
+ALTER TABLE  `city` ADD INDEX  `country_id` (  `country_id` );
+
+-- cluster
+ALTER TABLE  `cluster` DROP  `incident_title` ,
+DROP  `incident_date` ,
+DROP  `category_color` ;
+
+ALTER TABLE  `cluster` CHANGE  `location_id`  `location_id` BIGINT( 20 ) UNSIGNED NOT NULL DEFAULT  '0';
+ALTER TABLE  `cluster` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NOT NULL DEFAULT  '0';
+
+ALTER TABLE  `cluster` ADD INDEX  `location_id` (  `location_id` );
+ALTER TABLE  `cluster` ADD INDEX  `incident_id` (  `incident_id` );
+ALTER TABLE  `cluster` ADD INDEX  `category_id` (  `category_id` );
+
+-- comment
+ALTER TABLE  `comment` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `comment` CHANGE  `checkin_id`  `checkin_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `comment` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NULL DEFAULT  '0';
+
+ALTER TABLE  `comment` ADD INDEX  `incident_id` (  `incident_id` );
+ALTER TABLE  `comment` ADD INDEX  `checkin_id` (  `checkin_id` );
+ALTER TABLE  `comment` ADD INDEX  `user_id` (  `user_id` );
+
+ALTER TABLE  `comment` DROP  `comment_rating`;
+
+-- feed_item
+ALTER TABLE  `feed_item` CHANGE  `feed_id`	`feed_id` int(11) unsigned NOT NULL;	
+ALTER TABLE  `feed_item` CHANGE  `location_id`  `location_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT  '0';
+ALTER TABLE  `feed_item` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NOT NULL DEFAULT  '0';
+
+ALTER TABLE  `feed_item` ADD INDEX  `feed_id` (  `feed_id` );
+ALTER TABLE  `feed_item` ADD INDEX  `incident_id` (  `incident_id` );
+ALTER TABLE  `feed_item` ADD INDEX  `location_id` (  `location_id` );
+
+-- form_field_option
+ALTER TABLE  `form_field_option` ADD INDEX  `form_field_id` (  `form_field_id` );
+
+-- form_response
+ALTER TABLE  `form_response` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NOT NULL;
+ALTER TABLE  `form_response` ADD INDEX  `incident_id` (  `incident_id` );
+
+
+-- geometry
+ALTER TABLE  `geometry` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NOT NULL;
+ALTER TABLE  `geometry` ADD INDEX  `incident_id` (  `incident_id` );
+
+-- incident
+ALTER TABLE  `incident` CHANGE  `location_id`  `location_id` BIGINT( 20 ) UNSIGNED NOT NULL;
+ALTER TABLE  `incident` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE  `incident` ADD INDEX  `form_id` (  `form_id` );
+ALTER TABLE  `incident` ADD INDEX  `user_id` (  `user_id` );
+
+ALTER TABLE `incident` DROP `incident_rating`;
+
+
+-- incident_category
+ALTER TABLE  `incident_category` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NOT NULL DEFAULT  '0';
+ALTER TABLE  `incident_category` CHANGE  `category_id`  `category_id` INT( 11 ) UNSIGNED NOT NULL DEFAULT  '5';
+
+-- incident_lang
+ALTER TABLE  `incident_lang` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NOT NULL;
+ALTER TABLE  `incident_lang` ADD INDEX  `incident_id` (  `incident_id` );
+
+-- incident_person
+ALTER TABLE  `incident_person` DROP  `location_id`;
+ALTER TABLE  `incident_person` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `incident_person` ADD INDEX  `incident_id` (  `incident_id` );
+
+-- location
+ALTER TABLE  `location` ADD INDEX  `country_id` (  `country_id` );
+
+-- maintenance
+ALTER TABLE  `maintenance` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
+
+-- media
+ALTER TABLE  `media` CHANGE  `location_id`  `location_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `media` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `media` CHANGE  `checkin_id`  `checkin_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `media` CHANGE  `message_id`  `message_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+
+ALTER TABLE  `media` ADD INDEX  `incident_id` (  `incident_id` );
+ALTER TABLE  `media` ADD INDEX  `location_id` (  `location_id` );
+ALTER TABLE  `media` ADD INDEX  `checkin_id` (  `checkin_id` );
+ALTER TABLE  `media` ADD INDEX  `badge_id` (  `badge_id` );
+ALTER TABLE  `media` ADD INDEX  `message_id` (  `message_id` );
+
+-- message
+ALTER TABLE  `message` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT  '0';
+ALTER TABLE  `message` CHANGE  `reporter_id`  `reporter_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `message` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NULL DEFAULT  '0';
+
+ALTER TABLE  `message` ADD INDEX  `user_id` (  `user_id` );
+ALTER TABLE  `message` ADD INDEX  `incident_id` (  `incident_id` );
+ALTER TABLE  `message` ADD INDEX  `reporter_id` (  `reporter_id` );
+
+-- openid
+ALTER TABLE  `openid` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NOT NULL;
+ALTER TABLE  `openid` ADD INDEX  `user_id` (  `user_id` );
+
+-- private_message
+ALTER TABLE  `private_message` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NOT NULL;
+ALTER TABLE  `private_message` ADD INDEX  `user_id` (  `user_id` );
+
+-- rating
+ALTER TABLE  `rating` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NULL DEFAULT  '0';
+ALTER TABLE  `rating` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `rating` CHANGE  `comment_id`  `comment_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+
+
+ALTER TABLE  `rating` ADD INDEX  `user_id` (  `user_id` );
+ALTER TABLE  `rating` ADD INDEX  `incident_id` (  `incident_id` );
+ALTER TABLE  `rating` ADD INDEX  `comment_id` (  `comment_id` );
+
+-- reporter
+ALTER TABLE  `reporter` DROP  `incident_id` ,
+DROP  `service_userid` ;
+
+ALTER TABLE  `reporter` CHANGE  `location_id`  `location_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `reporter` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `reporter` CHANGE  `service_id`  `service_id` INT( 10 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE  `reporter` CHANGE  `level_id`  `level_id` INT( 11 ) UNSIGNED NULL DEFAULT NULL;
+
+
+ALTER TABLE  `reporter` ADD INDEX  `user_id` (  `user_id` );
+ALTER TABLE  `reporter` ADD INDEX  `location_id` (  `location_id` );
+ALTER TABLE  `reporter` ADD INDEX  `service_id` (  `service_id` );
+ALTER TABLE  `reporter` ADD INDEX  `level_id` (  `level_id` );
+
+-- service
+UPDATE `service` SET  `service_description` =  'Email messages sent to your deployment' WHERE  `service`.`id` =2;
+
+-- scheduler log
+ALTER TABLE  `scheduler_log` DROP  `scheduler_name` ;
+
+ALTER TABLE  `scheduler_log` CHANGE  `scheduler_id`  `scheduler_id` INT( 10 ) UNSIGNED NOT NULL;
+ALTER TABLE  `scheduler_log` ADD INDEX  `scheduler_id` (  `scheduler_id` );
+
+-- user_devices
+ALTER TABLE  `user_devices` CHANGE  `user_id`  `user_id` INT( 11 ) UNSIGNED NOT NULL;
+ALTER TABLE  `user_devices` ADD INDEX  `user_id` (  `user_id` );
+
+-- verified
+ALTER TABLE `verified` DROP `verified_comment`;
+ALTER TABLE `verified` CHANGE  `incident_id`  `incident_id` BIGINT( 20 ) UNSIGNED NULL DEFAULT NULL;
+ALTER TABLE `verified` ADD INDEX  `incident_id` (  `incident_id` );
+
+UPDATE `settings` SET `db_version` = '80' WHERE `id` = 1 LIMIT 1;
\ No newline at end of file
diff --git a/sql/upgrade80-81.sql b/sql/upgrade80-81.sql
new file mode 100644
index 0000000..5a93145
--- /dev/null
+++ b/sql/upgrade80-81.sql
@@ -0,0 +1,53 @@
+-- Adding comments to describe each table in the ushahidi schema
+
+ALTER TABLE  `actions` COMMENT =  'Stores user defined actions triggered by certain events';
+ALTER TABLE  `actions_log` COMMENT =  'Stores a log of triggered actions';
+ALTER TABLE  `alert` COMMENT =  'Stores alerts subscribers information';
+ALTER TABLE  `alert_category` COMMENT =  'Stores subscriber alert categories';
+ALTER TABLE  `alert_sent` COMMENT =  'Stores a log of alerts sent out to subscribers';
+ALTER TABLE  `badge` COMMENT =  'Stores description of badges to be assigned';
+ALTER TABLE  `badge_users` COMMENT =  'Stores assigned badge information';
+ALTER TABLE  `category` COMMENT =  'Holds information about categories defined for a deployment';
+ALTER TABLE  `category_lang` COMMENT =  'Holds translations for category titles and descriptions';
+ALTER TABLE  `checkin` COMMENT =  'Stores checkin information';
+ALTER TABLE  `city` COMMENT =  'Stores cities of countries retrieved by user.';
+ALTER TABLE  `cluster` COMMENT =  'Stores information used for clustering of reports on the map.';
+ALTER TABLE  `comment` COMMENT =  'Stores comments made on reports/checkins';
+ALTER TABLE  `country` COMMENT =  'Stores a list of all countries and their capital cities';
+ALTER TABLE  `externalapp` COMMENT =  'Info on external apps(mobile) that work with your deployment';
+ALTER TABLE  `feed` COMMENT =  'Information about RSS Feeds a deployment subscribes to';
+ALTER TABLE  `feed_item` COMMENT =  'Stores feed items pulled from each RSS Feed';
+ALTER TABLE  `form` COMMENT =  'Stores all report submission forms created(default+custom)';
+ALTER TABLE  `form_field` COMMENT =  'Stores all custom form fields created by users';
+ALTER TABLE  `form_field_option` COMMENT =  'Options related to custom form fields';
+ALTER TABLE  `form_response` COMMENT =  'Stores responses to custom form fields';
+ALTER TABLE  `geometry` COMMENT =  'Stores map geometries i.e polygons, lines etc';
+ALTER TABLE  `incident` COMMENT =  'Stores reports submitted';
+ALTER TABLE  `incident_category` COMMENT =  'Stores submitted reports categories';
+ALTER TABLE  `incident_lang` COMMENT =  'Holds translations for report titles and descriptions';
+ALTER TABLE  `incident_person` COMMENT =  'Holds information provided by people who submit reports';
+ALTER TABLE  `layer` COMMENT =  'Holds static layer information';
+ALTER TABLE  `level` COMMENT =  'Stores level of trust assigned to reporters of the platform';
+ALTER TABLE  `location` COMMENT =  'Stores location information';
+ALTER TABLE  `maintenance` COMMENT =  'Puts a site in maintenance mode if data exists in this table';
+ALTER TABLE  `media` COMMENT =  'Stores any media submitted along with a report/checkin';
+ALTER TABLE  `message` COMMENT =  'Stores tweets, emails and SMS messages';
+ALTER TABLE  `openid` COMMENT =  'Stores users’ openid information';
+ALTER TABLE  `page` COMMENT =  'Stores user created pages';
+ALTER TABLE  `plugin` COMMENT =  'Holds a list of all plugins installed on a deployment';
+ALTER TABLE  `private_message` COMMENT =  'Stores private messages sent between Members';
+ALTER TABLE  `rating` COMMENT =  'Stores credibility ratings for reports and comments';
+ALTER TABLE  `reporter` COMMENT =  'Information on report submitters via email, twitter and sms';
+ALTER TABLE  `roles` COMMENT =  'Defines user access levels and privileges on a deployment';
+ALTER TABLE  `roles_users` COMMENT =  'Stores roles assigned to users registered on a deployment';
+ALTER TABLE  `scheduler` COMMENT =  'Stores schedules for cron jobs';
+ALTER TABLE  `scheduler_log` COMMENT =  'Stores a log of scheduler actions';
+ALTER TABLE  `service` COMMENT =  'Info on input sources i.e SMS, Email, Twitter';
+ALTER TABLE  `sessions` COMMENT =  'Stores session information';
+ALTER TABLE  `settings` COMMENT =  'Stores a deployment’s general settings';
+ALTER TABLE  `users` COMMENT =  'Stores registered users’ information';
+ALTER TABLE  `user_devices` COMMENT =  'Works with checkins';
+ALTER TABLE  `user_tokens` COMMENT =  'Stores browser tokens assigned to users';
+ALTER TABLE  `verified` COMMENT =  'Stores all verified reports';
+
+UPDATE `settings` SET `db_version` = '81' WHERE `id` = 1 LIMIT 1;
diff --git a/sql/upgrade81-82.sql b/sql/upgrade81-82.sql
new file mode 100644
index 0000000..e2c11b6
--- /dev/null
+++ b/sql/upgrade81-82.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `settings` ADD `default_map_all_icon_id` INT( 11 ) DEFAULT NULL AFTER `default_map_all`;
+
+UPDATE `settings` SET `db_version` = '82' WHERE `id` = 1 LIMIT 1;
diff --git a/sql/upgrade82-83.sql b/sql/upgrade82-83.sql
new file mode 100644
index 0000000..d72a859
--- /dev/null
+++ b/sql/upgrade82-83.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `message` ADD `latitude` DOUBLE NULL DEFAULT NULL;
+
+ALTER TABLE `message` ADD `longitude` DOUBLE NULL DEFAULT NULL;
+
+UPDATE `settings` SET `db_version` = '83' WHERE `id` = 1 LIMIT 1;
diff --git a/sql/upgrade83-84.sql b/sql/upgrade83-84.sql
new file mode 100644
index 0000000..d3dd37f
--- /dev/null
+++ b/sql/upgrade83-84.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `settings` DROP `api_yahoo`;
+
+UPDATE `settings` SET `api_live` = 'Apumcka0uPOF2lKLorq8aeo4nuqfVVeNRqJjqOcLMJ9iMCTsnMsNd9_OvpA8gR0i' WHERE `id` = '1';
+
+UPDATE `settings` SET `db_version` = 84;
\ No newline at end of file
diff --git a/sql/upgrade84-85.sql b/sql/upgrade84-85.sql
new file mode 100644
index 0000000..3725fcb
--- /dev/null
+++ b/sql/upgrade84-85.sql
@@ -0,0 +1,3 @@
+UPDATE `category` SET  `category_description` =  'Holds uncategorized reports' WHERE  `category`.`id` =5;
+
+UPDATE `settings` SET `db_version` = 85;
\ No newline at end of file
diff --git a/sql/upgrade85-86.sql b/sql/upgrade85-86.sql
new file mode 100644
index 0000000..b96ba71
--- /dev/null
+++ b/sql/upgrade85-86.sql
@@ -0,0 +1,2 @@
+UPDATE `settings` SET `ushahidi_version` = '2.3' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `db_version` = '86' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade86-87.sql b/sql/upgrade86-87.sql
new file mode 100644
index 0000000..08c025f
--- /dev/null
+++ b/sql/upgrade86-87.sql
@@ -0,0 +1,4 @@
+ALTER TABLE `sessions` CHANGE COLUMN `session_id` `session_id` VARCHAR(127) NOT NULL  ;
+UPDATE `settings` SET `db_version` = '87' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `ushahidi_version` = '2.3.1' WHERE `id`=1 LIMIT 1;
+
diff --git a/sql/upgrade87-88.sql b/sql/upgrade87-88.sql
new file mode 100644
index 0000000..732e954
--- /dev/null
+++ b/sql/upgrade87-88.sql
@@ -0,0 +1,3 @@
+UPDATE `settings` SET `db_version` = '88' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `ushahidi_version` = '2.3.2' WHERE `id`=1 LIMIT 1;
+
diff --git a/sql/upgrade88-89.sql b/sql/upgrade88-89.sql
new file mode 100644
index 0000000..cd5cf7a
--- /dev/null
+++ b/sql/upgrade88-89.sql
@@ -0,0 +1,4 @@
+UPDATE `roles` SET `access_level` = '100' WHERE `name` = 'superadmin';
+UPDATE `roles` SET `access_level` = '90' WHERE `name` = 'admin';
+UPDATE `roles` SET `access_level` = '10' WHERE `name` = 'member';
+UPDATE `settings` SET `db_version` = '89' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade89-90.sql b/sql/upgrade89-90.sql
new file mode 100644
index 0000000..eb4f8a2
--- /dev/null
+++ b/sql/upgrade89-90.sql
@@ -0,0 +1,2 @@
+UPDATE `settings` SET `ushahidi_version` = '2.4' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `db_version` = '90' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade90-91.sql b/sql/upgrade90-91.sql
new file mode 100644
index 0000000..03961e0
--- /dev/null
+++ b/sql/upgrade90-91.sql
@@ -0,0 +1,2 @@
+UPDATE `settings` SET `ushahidi_version` = '2.4.1' WHERE `id`=1 LIMIT 1;
+UPDATE `settings` SET `db_version` = '91' WHERE `id`=1 LIMIT 1;
diff --git a/sql/upgrade91-92.sql b/sql/upgrade91-92.sql
new file mode 100644
index 0000000..45ebed4
--- /dev/null
+++ b/sql/upgrade91-92.sql
@@ -0,0 +1,145 @@
+-- Create the new_settings table
+CREATE TABLE IF NOT EXISTS `new_settings` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `key` varchar(100) NOT NULL DEFAULT '' COMMENT 'Unique identifier for the configuration parameter',
+  `value` text COMMENT 'Value for the settings parameter',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uq_settings_key` (`key`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+-- Populate
+INSERT INTO `new_settings`(`key`, `value`) 
+SELECT 'site_name' AS `key`, `site_name` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_tagline' AS `key`, `site_tagline` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_banner_id' AS `key`, `site_banner_id` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_email' AS `key`, `site_email` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_key' AS `key`, `site_key` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_language' AS `key`, `site_language` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_style' AS `key`, `site_style` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_timezone' AS `key`, `site_timezone` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_contact_page' AS `key`, `site_contact_page` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_help_page' AS `key`, `site_help_page` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_message' AS `key`, `site_message` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_copyright_statement' AS `key`, `site_copyright_statement` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'site_submit_report_message' AS `key`, `site_submit_report_message` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'allow_reports' AS `key`, `allow_reports` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'allow_comments' AS `key`, `allow_comments` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'allow_feed' AS `key`, `allow_feed` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'allow_stat_sharing' AS `key`, `allow_stat_sharing` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'allow_clustering' AS `key`, `allow_clustering` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'cache_pages' AS `key`, `cache_pages` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'cache_pages_lifetime' AS `key`, `cache_pages_lifetime` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'private_deployment' AS `key`, `private_deployment` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'default_map' AS `key`, `default_map` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'default_map_all' AS `key`, `default_map_all` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'default_map_all_icon_id' AS `key`, `default_map_all_icon_id` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'api_google' AS `key`, `api_google` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'api_live' AS `key`, `api_live` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'api_akismet' AS `key`, `api_akismet` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'default_country' AS `key`, `default_country` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'multi_country' AS `key`, `multi_country` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'default_city' AS `key`, `default_city` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'default_lat' AS `key`, `default_lat` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'default_lon' AS `key`, `default_lon` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'default_zoom' AS `key`, `default_zoom` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'items_per_page' AS `key`, `items_per_page` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'items_per_page_admin' AS `key`, `items_per_page_admin` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'sms_provider' AS `key`, `sms_provider` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'sms_no1' AS `key`, `sms_no1` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'sms_no2' AS `key`, `sms_no2` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'sms_no3' AS `key`, `sms_no3` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'google_analytics' AS `key`, `google_analytics` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'twitter_hashtags' AS `key`, `twitter_hashtags` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'blocks' AS `key`, `blocks` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'blocks_per_row' AS `key`, `blocks_per_row` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'date_modify' AS `key`, `date_modify` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'stat_id' AS `key`, `stat_id` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'stat_key' AS `key`, `stat_key` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'email_username' AS `key`, `email_username` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'email_password' AS `key`, `email_password` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'email_port' AS `key`, `email_port` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'email_host' AS `key`, `email_host` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'email_servertype' AS `key`, `email_servertype` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'email_ssl' AS `key`, `email_ssl` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'ftp_server' AS `key`, `ftp_server` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'ftp_user_name' AS `key`, `ftp_user_name` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'alerts_email' AS `key`, `alerts_email` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'checkins' AS `key`, `checkins` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'facebook_appid' AS `key`, `facebook_appid` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'facebook_appsecret' AS `key`, `facebook_appsecret` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'db_version' AS `key`, `db_version` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'ushahidi_version' AS `key`, `ushahidi_version` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'allow_alerts' AS `key`, `allow_alerts` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'require_email_confirmation' AS `key`, `require_email_confirmation` AS `value` FROM `settings` WHERE `id` = 1
+UNION
+SELECT 'manually_approve_users' AS `key`, `manually_approve_users` AS `value` FROM `settings` WHERE `id` = 1;
+
+-- Drop the existing settings table
+DROP TABLE IF EXISTS `settings`;
+
+-- Rename the new settings table to `settings`
+RENAME TABLE `new_settings` TO `settings`;
+
+-- Update the DB version
+UPDATE `settings` SET `value` = 92 WHERE `key` = 'db_version';
diff --git a/sql/upgrade92-93.sql b/sql/upgrade92-93.sql
new file mode 100644
index 0000000..282d450
--- /dev/null
+++ b/sql/upgrade92-93.sql
@@ -0,0 +1,94 @@
+/*
+ * Create new permissions tabls
+ */
+
+CREATE TABLE IF NOT EXISTS `permissions` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(32) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`name`)
+) ENGINE=MyISAM AUTO_INCREMENT=16 COMMENT='Stores permissions used for access control';
+
+/* Add existing permissions */
+INSERT IGNORE INTO `permissions` VALUES 
+(1,'reports_view'),
+(2,'reports_edit'),
+(3,'reports_evaluation'),
+(4,'reports_comments'),
+(5,'reports_download'),
+(6,'reports_upload'),
+(7,'messages'),
+(8,'messages_reporters'),
+(9,'stats'),
+(10,'settings'),
+(11,'manage'),
+(12,'users'),
+(13,'manage_roles'),
+(14,'checkin'),
+(15,'checkin_admin');
+
+CREATE TABLE IF NOT EXISTS `permissions_roles` (
+  `role_id` int(11) NOT NULL,
+  `permission_id` int(11) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`)
+) ENGINE=MyISAM COMMENT='Stores permissions assigned to roles';
+
+/* Grab existing permissions-roles matches */
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 1 as permission_id FROM `roles` WHERE reports_view = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 2 as permission_id FROM `roles` WHERE reports_edit = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 3 as permission_id FROM `roles` WHERE reports_evaluation = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 4 as permission_id FROM `roles` WHERE reports_comments = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 5 as permission_id FROM `roles` WHERE reports_download = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 6 as permission_id FROM `roles` WHERE reports_upload = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 7 as permission_id FROM `roles` WHERE messages = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 8 as permission_id FROM `roles` WHERE messages_reporters = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 9 as permission_id FROM `roles` WHERE stats = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 10 as permission_id FROM `roles` WHERE settings = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 11 as permission_id FROM `roles` WHERE manage = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 12 as permission_id FROM `roles` WHERE users = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 13 as permission_id FROM `roles` WHERE manage_roles = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 14 as permission_id FROM `roles` WHERE checkin = 1;
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id) SELECT id as role_id, 15 as permission_id FROM `roles` WHERE checkin_admin = 1;
+
+/* Backup old roles tables and then remove fields */
+CREATE TABLE IF NOT EXISTS `roles_old` LIKE `roles`; INSERT INTO `roles_old` SELECT * FROM `roles`;
+
+/* Remove permissions fields from roles */
+ALTER TABLE `roles` 
+DROP COLUMN `checkin_admin` ,
+DROP COLUMN `checkin` ,
+DROP COLUMN `manage_roles` ,
+DROP COLUMN `users` ,
+DROP COLUMN `manage` ,
+DROP COLUMN `settings` ,
+DROP COLUMN `stats` ,
+DROP COLUMN `messages_reporters` ,
+DROP COLUMN `messages` ,
+DROP COLUMN `reports_upload` ,
+DROP COLUMN `reports_download` ,
+DROP COLUMN `reports_comments` ,
+DROP COLUMN `reports_evaluation` ,
+DROP COLUMN `reports_edit` ,
+DROP COLUMN `reports_view` ;
+
+/* Remove report_evaluation permission */
+INSERT INTO `permissions` (id, name) VALUES (16,'reports_verify'),(17,'reports_approve');
+INSERT INTO `permissions_roles` (role_id, permission_id) SELECT role_id, 16 as permission_id FROM `permissions_roles` WHERE permission_id = 3;
+INSERT INTO `permissions_roles` (role_id, permission_id) SELECT role_id, 17 as permission_id FROM `permissions_roles` WHERE permission_id = 3;
+DELETE FROM `permissions` WHERE id = 3;
+DELETE FROM `permissions_roles` WHERE permission_id = 3;
+
+/* Add permission for members pages & admin pages */
+INSERT INTO `permissions` (id, name) VALUES (18, 'admin_ui'),(19,'member_ui');
+/* Grant admin to superadmin role*/
+INSERT INTO `permissions_roles` (role_id, permission_id)
+  SELECT id as role_id, 18 as permission_id FROM `roles` WHERE name = 'superadmin';
+/* Grant member to member role */
+INSERT INTO `permissions_roles` (role_id, permission_id)
+  SELECT id as role_id, 19 as permission_id FROM `roles` WHERE name = 'member';
+/* Grant admin to any role with permissions other than checkin */
+INSERT IGNORE INTO `permissions_roles` (role_id, permission_id)
+  SELECT DISTINCT role_id, 18 as permission_id FROM `permissions_roles` WHERE permission_id IN (1,2,3,4,5,6,7,8,9,10,11,12,13,15,16,17);
+
+-- Update the DB version
+UPDATE `settings` SET `value` = 93 WHERE `key` = 'db_version';
diff --git a/sql/upgrade93-94.sql b/sql/upgrade93-94.sql
new file mode 100644
index 0000000..1a50891
--- /dev/null
+++ b/sql/upgrade93-94.sql
@@ -0,0 +1,5 @@
+-- Change structure of foreign key role_id to match referenced field roles.id
+ALTER TABLE `permissions_roles` CHANGE  `role_id`  `role_id` INT( 11 ) UNSIGNED NOT NULL;
+
+-- Update the DB version
+UPDATE `settings` SET `value` = 94 WHERE `key` = 'db_version';
diff --git a/sql/upgrade94-95.sql b/sql/upgrade94-95.sql
new file mode 100644
index 0000000..76b35b0
--- /dev/null
+++ b/sql/upgrade94-95.sql
@@ -0,0 +1,5 @@
+-- Add enable_timeline as a setting to make it an off/on feature
+INSERT INTO `settings` (`key`, `value`) values ('enable_timeline','0');
+
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 95 WHERE `key` = 'db_version';
diff --git a/sql/upgrade95-96.sql b/sql/upgrade95-96.sql
new file mode 100644
index 0000000..1ed73eb
--- /dev/null
+++ b/sql/upgrade95-96.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 96 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.5b' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade96-97.sql b/sql/upgrade96-97.sql
new file mode 100644
index 0000000..ae79f7f
--- /dev/null
+++ b/sql/upgrade96-97.sql
@@ -0,0 +1,5 @@
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 97 WHERE `key` = 'db_version';
+
+-- UPDATE ushahidi_version
+UPDATE `settings` SET `value` = '2.5' WHERE `key` = 'ushahidi_version';
diff --git a/sql/upgrade97-98.sql b/sql/upgrade97-98.sql
new file mode 100644
index 0000000..e23906d
--- /dev/null
+++ b/sql/upgrade97-98.sql
@@ -0,0 +1,11 @@
+-- Delete orphaned reports category
+DELETE FROM `category` WHERE `id` = '5';
+
+-- Delete entries tied NONE category
+DELETE FROM `incident_category` WHERE `category_id` = 5;
+
+-- Change incident_category table structure and set default value for category_id to orphaned reports category i.e 5
+ALTER TABLE `incident_category` CHANGE `category_id` `category_id` int(11) NOT NULL default '0';
+
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 98 WHERE `key` = 'db_version';
diff --git a/sql/upgrade98-99.sql b/sql/upgrade98-99.sql
new file mode 100644
index 0000000..1dfb8d6
--- /dev/null
+++ b/sql/upgrade98-99.sql
@@ -0,0 +1,5 @@
+-- Add unique constraint on custom form field name/title
+ALTER TABLE  `form_field` ADD UNIQUE ( `field_name`);
+
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 99 WHERE `key` = 'db_version';
\ No newline at end of file
diff --git a/sql/upgrade99-100.sql b/sql/upgrade99-100.sql
new file mode 100644
index 0000000..d948a1e
--- /dev/null
+++ b/sql/upgrade99-100.sql
@@ -0,0 +1,9 @@
+-- Drop dormant field field_options
+ALTER TABLE `form_field`
+  DROP `field_options`;
+
+-- Change default form_id to 1 instead of 0
+ALTER TABLE  `form_field` CHANGE  `form_id`  `form_id` INT( 11 ) NOT NULL DEFAULT  '1';
+
+-- UPDATE db_version
+UPDATE `settings` SET `value` = 100 WHERE `key` = 'db_version';
\ No newline at end of file
diff --git a/sql/ushahidi.sql b/sql/ushahidi.sql
new file mode 100755
index 0000000..a5ecd82
--- /dev/null
+++ b/sql/ushahidi.sql
@@ -0,0 +1,1484 @@
+-- Ushahidi Engine
+-- version 115
+-- http://www.ushahidi.com
+
+
+SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `actions`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `actions` (
+  `action_id` int(11) NOT NULL AUTO_INCREMENT,
+  `action` varchar(75) NOT NULL,
+  `qualifiers` text NOT NULL,
+  `response` varchar(75) NOT NULL,
+  `response_vars` text NOT NULL,
+  `active` tinyint(4) NOT NULL,
+  PRIMARY KEY (`action_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores user defined actions triggered by certain events' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `actions_log`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `actions_log` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `action_id` int(11) NOT NULL,
+  `user_id` int(11) NOT NULL,
+  `time` int(10) NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `action_id` (`action_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores a log of triggered actions' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `alert`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `alert` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) unsigned DEFAULT '0',
+  `alert_type` tinyint(4) NOT NULL COMMENT '1 - MOBILE, 2 - EMAIL',
+  `alert_recipient` varchar(200) DEFAULT NULL,
+  `alert_code` varchar(30) DEFAULT NULL,
+  `alert_confirmed` tinyint(4) NOT NULL DEFAULT '0',
+  `alert_lat` varchar(150) DEFAULT NULL,
+  `alert_lon` varchar(150) DEFAULT NULL,
+  `alert_radius` tinyint(4) NOT NULL DEFAULT '20',
+  `alert_ip` varchar(100) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uniq_alert_code` (`alert_code`),
+  KEY `user_id` (`user_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores alerts subscribers information' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `alert_category`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `alert_category` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `alert_id` bigint(20) unsigned DEFAULT NULL,
+  `category_id` int(11) unsigned DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `alert_id` (`alert_id`),
+  KEY `category_id` (`category_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores subscriber alert categories' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `alert_sent`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `alert_sent` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `incident_id` bigint(20) unsigned NOT NULL,
+  `alert_id` bigint(20) unsigned NOT NULL,
+  `alert_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `incident_id` (`incident_id`),
+  KEY `alert_id` (`alert_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores a log of alerts sent out to subscribers' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `api_banned`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `api_banned` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `banned_ipaddress` varchar(50) NOT NULL,
+  `banned_date` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='For logging banned API IP addresses' AUTO_INCREMENT=8 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `api_log`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `api_log` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `api_task` varchar(10) NOT NULL,
+  `api_parameters` varchar(100) NOT NULL,
+  `api_records` tinyint(11) NOT NULL,
+  `api_ipaddress` varchar(50) NOT NULL,
+  `api_date` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='For logging API activities' AUTO_INCREMENT=19 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `api_settings`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `api_settings` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `default_record_limit` int(11) NOT NULL DEFAULT '20',
+  `max_record_limit` int(11) DEFAULT NULL,
+  `max_requests_per_ip_address` int(11) DEFAULT NULL,
+  `max_requests_quota_basis` int(11) DEFAULT NULL,
+  `modification_date` datetime NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='For storing API logging settings' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `badge`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `badge` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(250) NOT NULL,
+  `description` text NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores description of badges to be assigned' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `badge_users`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `badge_users` (
+  `user_id` int(11) unsigned NOT NULL,
+  `badge_id` int(11) NOT NULL,
+  PRIMARY KEY (`user_id`,`badge_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores assigned badge information';
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `category`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `category` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) NOT NULL DEFAULT '0',
+  `locale` varchar(10) NOT NULL DEFAULT 'en_US',
+  `category_position` tinyint(4) NOT NULL DEFAULT '0',
+  `category_title` varchar(255) DEFAULT NULL,
+  `category_description` text DEFAULT NULL,
+  `category_color` varchar(20) DEFAULT NULL,
+  `category_image` varchar(255) DEFAULT NULL,
+  `category_image_thumb` varchar(255) DEFAULT NULL,
+  `category_visible` tinyint(4) NOT NULL DEFAULT '1',
+  `category_trusted` tinyint(4) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `category_visible` (`category_visible`),
+  KEY `parent_id` (`parent_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Holds information about categories defined for a deployment' AUTO_INCREMENT=6 ;
+
+--
+-- Dumping data for table `category`
+--
+
+INSERT INTO `category` (`id`,`category_title`, `category_description`, `category_color`, `category_visible`, `category_trusted`, `category_position`) VALUES
+(1, 'Category 1', 'Category 1', '9900CC', 1, 0, 0),
+(2, 'Category 2', 'Category 2', '3300FF', 1, 0, 1),
+(3, 'Category 3', 'Category 3', '663300', 1, 0, 2),
+(4, 'Trusted Reports', 'Reports from trusted reporters', '339900', 1, 1, 3);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `category_lang`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `category_lang` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `category_id` int(11) unsigned NOT NULL,
+  `locale` varchar(10) DEFAULT NULL,
+  `category_title` varchar(255) DEFAULT NULL,
+  `category_description` text DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `category_id` (`category_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Holds translations for category titles and descriptions' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `city`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `city` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `country_id` int(11) DEFAULT NULL,
+  `city` varchar(200) DEFAULT NULL,
+  `city_lat` varchar(150) DEFAULT NULL,
+  `city_lon` varchar(200) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `country_id` (`country_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores cities of countries retrieved by user.' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `cluster`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `cluster` (
+  `id` int(11) NOT NULL,
+  `location_id` bigint(20) unsigned NOT NULL DEFAULT '0',
+  `latitude` double NOT NULL,
+  `longitude` double NOT NULL,
+  `latitude_min` double NOT NULL,
+  `longitude_min` double NOT NULL,
+  `latitude_max` double NOT NULL,
+  `longitude_max` double NOT NULL,
+  `child_count` int(11) NOT NULL,
+  `parent_id` int(11) NOT NULL,
+  `left_side` int(11) NOT NULL,
+  `right_side` int(11) NOT NULL,
+  `level` int(11) NOT NULL,
+  `incident_id` bigint(20) unsigned NOT NULL DEFAULT '0',
+  `category_id` int(11) unsigned NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `location_id` (`location_id`),
+  KEY `incident_id` (`incident_id`),
+  KEY `category_id` (`category_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores information used for clustering of reports on the map.';
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `comment`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `comment` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `incident_id` bigint(20) unsigned DEFAULT NULL,
+  `user_id` int(11) unsigned DEFAULT '0',
+  `comment_author` varchar(100) DEFAULT NULL,
+  `comment_email` varchar(120) DEFAULT NULL,
+  `comment_description` text,
+  `comment_ip` varchar(100) DEFAULT NULL,
+  `comment_spam` tinyint(4) NOT NULL DEFAULT '0',
+  `comment_active` tinyint(4) NOT NULL DEFAULT '0',
+  `comment_date` datetime DEFAULT NULL,
+  `comment_date_gmt` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `incident_id` (`incident_id`),
+  KEY `user_id` (`user_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores comments made on reports' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `country`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `country` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `iso` varchar(10) DEFAULT NULL,
+  `country` varchar(100) DEFAULT NULL,
+  `capital` varchar(100) DEFAULT NULL,
+  `cities` tinyint(4) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores a list of all countries and their capital cities' AUTO_INCREMENT=248 ;
+
+--
+-- Dumping data for table `country`
+--
+
+INSERT INTO `country` (`id`, `iso`, `country`, `capital`, `cities`) VALUES
+(1, 'AD', 'Andorra', 'Andorra la Vella', 0),
+(2, 'AE', 'United Arab Emirates', 'Abu Dhabi', 0),
+(3, 'AF', 'Afghanistan', 'Kabul', 0),
+(4, 'AG', 'Antigua and Barbuda', 'St. John''s', 0),
+(5, 'AI', 'Anguilla', 'The Valley', 0),
+(6, 'AL', 'Albania', 'Tirana', 0),
+(7, 'AM', 'Armenia', 'Yerevan', 0),
+(8, 'AN', 'Netherlands Antilles', 'Willemstad', 0),
+(9, 'AO', 'Angola', 'Luanda', 0),
+(10, 'AQ', 'Antarctica', '', 0),
+(11, 'AR', 'Argentina', 'Buenos Aires', 0),
+(12, 'AS', 'American Samoa', 'Pago Pago', 0),
+(13, 'AT', 'Austria', 'Vienna', 0),
+(14, 'AU', 'Australia', 'Canberra', 0),
+(15, 'AW', 'Aruba', 'Oranjestad', 0),
+(16, 'AX', 'Aland Islands', 'Mariehamn', 0),
+(17, 'AZ', 'Azerbaijan', 'Baku', 0),
+(18, 'BA', 'Bosnia and Herzegovina', 'Sarajevo', 0),
+(19, 'BB', 'Barbados', 'Bridgetown', 0),
+(20, 'BD', 'Bangladesh', 'Dhaka', 0),
+(21, 'BE', 'Belgium', 'Brussels', 0),
+(22, 'BF', 'Burkina Faso', 'Ouagadougou', 0),
+(23, 'BG', 'Bulgaria', 'Sofia', 0),
+(24, 'BH', 'Bahrain', 'Manama', 0),
+(25, 'BI', 'Burundi', 'Bujumbura', 0),
+(26, 'BJ', 'Benin', 'Porto-Novo', 0),
+(27, 'BL', 'Saint Barthélemy', 'Gustavia', 0),
+(28, 'BM', 'Bermuda', 'Hamilton', 0),
+(29, 'BN', 'Brunei', 'Bandar Seri Begawan', 0),
+(30, 'BO', 'Bolivia', 'La Paz', 0),
+(31, 'BR', 'Brazil', 'Brasília', 0),
+(32, 'BS', 'Bahamas', 'Nassau', 0),
+(33, 'BT', 'Bhutan', 'Thimphu', 0),
+(34, 'BV', 'Bouvet Island', '', 0),
+(35, 'BW', 'Botswana', 'Gaborone', 0),
+(36, 'BY', 'Belarus', 'Minsk', 0),
+(37, 'BZ', 'Belize', 'Belmopan', 0),
+(38, 'CA', 'Canada', 'Ottawa', 0),
+(39, 'CC', 'Cocos Islands', 'West Island', 0),
+(40, 'CD', 'Democratic Republic of the Congo', 'Kinshasa', 0),
+(41, 'CF', 'Central African Republic', 'Bangui', 0),
+(42, 'CG', 'Congo Brazzavile', 'Brazzaville', 0),
+(43, 'CH', 'Switzerland', 'Berne', 0),
+(44, 'CI', 'Ivory Coast', 'Yamoussoukro', 0),
+(45, 'CK', 'Cook Islands', 'Avarua', 0),
+(46, 'CL', 'Chile', 'Santiago', 0),
+(47, 'CM', 'Cameroon', 'Yaoundé', 0),
+(48, 'CN', 'China', 'Beijing', 0),
+(49, 'CO', 'Colombia', 'Bogotá', 0),
+(50, 'CR', 'Costa Rica', 'San José', 0),
+(51, 'CS', 'Serbia and Montenegro', 'Belgrade', 0),
+(52, 'CU', 'Cuba', 'Havana', 0),
+(53, 'CV', 'Cape Verde', 'Praia', 0),
+(54, 'CX', 'Christmas Island', 'Flying Fish Cove', 0),
+(55, 'CY', 'Cyprus', 'Nicosia', 0),
+(56, 'CZ', 'Czech Republic', 'Prague', 0),
+(57, 'DE', 'Germany', 'Berlin', 0),
+(58, 'DJ', 'Djibouti', 'Djibouti', 0),
+(59, 'DK', 'Denmark', 'Copenhagen', 0),
+(60, 'DM', 'Dominica', 'Roseau', 0),
+(61, 'DO', 'Dominican Republic', 'Santo Domingo', 0),
+(62, 'DZ', 'Algeria', 'Algiers', 0),
+(63, 'EC', 'Ecuador', 'Quito', 0),
+(64, 'EE', 'Estonia', 'Tallinn', 0),
+(65, 'EG', 'Egypt', 'Cairo', 0),
+(66, 'EH', 'Western Sahara', 'El-Aaiun', 0),
+(67, 'ER', 'Eritrea', 'Asmara', 0),
+(68, 'ES', 'Spain', 'Madrid', 0),
+(69, 'ET', 'Ethiopia', 'Addis Ababa', 0),
+(70, 'FI', 'Finland', 'Helsinki', 0),
+(71, 'FJ', 'Fiji', 'Suva', 0),
+(72, 'FK', 'Falkland Islands', 'Stanley', 0),
+(73, 'FM', 'Micronesia', 'Palikir', 0),
+(74, 'FO', 'Faroe Islands', 'Tórshavn', 0),
+(75, 'FR', 'France', 'Paris', 0),
+(76, 'GA', 'Gabon', 'Libreville', 0),
+(77, 'GB', 'United Kingdom', 'London', 0),
+(78, 'GD', 'Grenada', 'St. George''s', 0),
+(79, 'GE', 'Georgia', 'Tbilisi', 0),
+(80, 'GF', 'French Guiana', 'Cayenne', 0),
+(81, 'GG', 'Guernsey', 'St Peter Port', 0),
+(82, 'GH', 'Ghana', 'Accra', 0),
+(83, 'GI', 'Gibraltar', 'Gibraltar', 0),
+(84, 'GL', 'Greenland', 'Nuuk', 0),
+(85, 'GM', 'Gambia', 'Banjul', 0),
+(86, 'GN', 'Guinea', 'Conakry', 0),
+(87, 'GP', 'Guadeloupe', 'Basse-Terre', 0),
+(88, 'GQ', 'Equatorial Guinea', 'Malabo', 0),
+(89, 'GR', 'Greece', 'Athens', 0),
+(90, 'GS', 'South Georgia and the South Sandwich Islands', 'Grytviken', 0),
+(91, 'GT', 'Guatemala', 'Guatemala City', 0),
+(92, 'GU', 'Guam', 'Hagåtña', 0),
+(93, 'GW', 'Guinea-Bissau', 'Bissau', 0),
+(94, 'GY', 'Guyana', 'Georgetown', 0),
+(95, 'HK', 'Hong Kong', 'Hong Kong', 0),
+(96, 'HM', 'Heard Island and McDonald Islands', '', 0),
+(97, 'HN', 'Honduras', 'Tegucigalpa', 0),
+(98, 'HR', 'Croatia', 'Zagreb', 0),
+(99, 'HT', 'Haiti', 'Port-au-Prince', 0),
+(100, 'HU', 'Hungary', 'Budapest', 0),
+(101, 'ID', 'Indonesia', 'Jakarta', 0),
+(102, 'IE', 'Ireland', 'Dublin', 0),
+(103, 'IL', 'Israel', 'Jerusalem', 0),
+(104, 'IM', 'Isle of Man', 'Douglas, Isle of Man', 0),
+(105, 'IN', 'India', 'New Delhi', 0),
+(106, 'IO', 'British Indian Ocean Territory', 'Diego Garcia', 0),
+(107, 'IQ', 'Iraq', 'Baghdad', 0),
+(108, 'IR', 'Iran', 'Tehran', 0),
+(109, 'IS', 'Iceland', 'Reykjavík', 0),
+(110, 'IT', 'Italy', 'Rome', 0),
+(111, 'JE', 'Jersey', 'Saint Helier', 0),
+(112, 'JM', 'Jamaica', 'Kingston', 0),
+(113, 'JO', 'Jordan', 'Amman', 0),
+(114, 'JP', 'Japan', 'Tokyo', 0),
+(115, 'KE', 'Kenya', 'Nairobi', 0),
+(116, 'KG', 'Kyrgyzstan', 'Bishkek', 0),
+(117, 'KH', 'Cambodia', 'Phnom Penh', 0),
+(118, 'KI', 'Kiribati', 'South Tarawa', 0),
+(119, 'KM', 'Comoros', 'Moroni', 0),
+(120, 'KN', 'Saint Kitts and Nevis', 'Basseterre', 0),
+(121, 'KP', 'North Korea', 'Pyongyang', 0),
+(122, 'KR', 'South Korea', 'Seoul', 0),
+(123, 'KW', 'Kuwait', 'Kuwait City', 0),
+(124, 'KY', 'Cayman Islands', 'George Town', 0),
+(125, 'KZ', 'Kazakhstan', 'Astana', 0),
+(126, 'LA', 'Laos', 'Vientiane', 0),
+(127, 'LB', 'Lebanon', 'Beirut', 0),
+(128, 'LC', 'Saint Lucia', 'Castries', 0),
+(129, 'LI', 'Liechtenstein', 'Vaduz', 0),
+(130, 'LK', 'Sri Lanka', 'Colombo', 0),
+(131, 'LR', 'Liberia', 'Monrovia', 0),
+(132, 'LS', 'Lesotho', 'Maseru', 0),
+(133, 'LT', 'Lithuania', 'Vilnius', 0),
+(134, 'LU', 'Luxembourg', 'Luxembourg', 0),
+(135, 'LV', 'Latvia', 'Riga', 0),
+(136, 'LY', 'Libya', 'Tripolis', 0),
+(137, 'MA', 'Morocco', 'Rabat', 0),
+(138, 'MC', 'Monaco', 'Monaco', 0),
+(139, 'MD', 'Moldova', 'Chi_in_u', 0),
+(140, 'ME', 'Montenegro', 'Podgorica', 0),
+(141, 'MF', 'Saint Martin', 'Marigot', 0),
+(142, 'MG', 'Madagascar', 'Antananarivo', 0),
+(143, 'MH', 'Marshall Islands', 'Uliga', 0),
+(144, 'MK', 'Macedonia', 'Skopje', 0),
+(145, 'ML', 'Mali', 'Bamako', 0),
+(146, 'MM', 'Myanmar', 'Yangon', 0),
+(147, 'MN', 'Mongolia', 'Ulan Bator', 0),
+(148, 'MO', 'Macao', 'Macao', 0),
+(149, 'MP', 'Northern Mariana Islands', 'Saipan', 0),
+(150, 'MQ', 'Martinique', 'Fort-de-France', 0),
+(151, 'MR', 'Mauritania', 'Nouakchott', 0),
+(152, 'MS', 'Montserrat', 'Plymouth', 0),
+(153, 'MT', 'Malta', 'Valletta', 0),
+(154, 'MU', 'Mauritius', 'Port Louis', 0),
+(155, 'MV', 'Maldives', 'Malé', 0),
+(156, 'MW', 'Malawi', 'Lilongwe', 0),
+(157, 'MX', 'Mexico', 'Mexico City', 0),
+(158, 'MY', 'Malaysia', 'Kuala Lumpur', 0),
+(159, 'MZ', 'Mozambique', 'Maputo', 0),
+(160, 'NA', 'Namibia', 'Windhoek', 0),
+(161, 'NC', 'New Caledonia', 'Nouméa', 0),
+(162, 'NE', 'Niger', 'Niamey', 0),
+(163, 'NF', 'Norfolk Island', 'Kingston', 0),
+(164, 'NG', 'Nigeria', 'Abuja', 0),
+(165, 'NI', 'Nicaragua', 'Managua', 0),
+(166, 'NL', 'Netherlands', 'Amsterdam', 0),
+(167, 'NO', 'Norway', 'Oslo', 0),
+(168, 'NP', 'Nepal', 'Kathmandu', 0),
+(169, 'NR', 'Nauru', 'Yaren', 0),
+(170, 'NU', 'Niue', 'Alofi', 0),
+(171, 'NZ', 'New Zealand', 'Wellington', 0),
+(172, 'OM', 'Oman', 'Muscat', 0),
+(173, 'PA', 'Panama', 'Panama City', 0),
+(174, 'PE', 'Peru', 'Lima', 0),
+(175, 'PF', 'French Polynesia', 'Papeete', 0),
+(176, 'PG', 'Papua New Guinea', 'Port Moresby', 0),
+(177, 'PH', 'Philippines', 'Manila', 0),
+(178, 'PK', 'Pakistan', 'Islamabad', 0),
+(179, 'PL', 'Poland', 'Warsaw', 0),
+(180, 'PM', 'Saint Pierre and Miquelon', 'Saint-Pierre', 0),
+(181, 'PN', 'Pitcairn', 'Adamstown', 0),
+(182, 'PR', 'Puerto Rico', 'San Juan', 0),
+(183, 'PS', 'Palestinian Territory', 'East Jerusalem', 0),
+(184, 'PT', 'Portugal', 'Lisbon', 0),
+(185, 'PW', 'Palau', 'Koror', 0),
+(186, 'PY', 'Paraguay', 'Asunción', 0),
+(187, 'QA', 'Qatar', 'Doha', 0),
+(188, 'RE', 'Reunion', 'Saint-Denis', 0),
+(189, 'RO', 'Romania', 'Bucharest', 0),
+(190, 'RS', 'Serbia', 'Belgrade', 0),
+(191, 'RU', 'Russia', 'Moscow', 0),
+(192, 'RW', 'Rwanda', 'Kigali', 0),
+(193, 'SA', 'Saudi Arabia', 'Riyadh', 0),
+(194, 'SB', 'Solomon Islands', 'Honiara', 0),
+(195, 'SC', 'Seychelles', 'Victoria', 0),
+(196, 'SD', 'Sudan', 'Khartoum', 0),
+(197, 'SE', 'Sweden', 'Stockholm', 0),
+(198, 'SG', 'Singapore', 'Singapur', 0),
+(199, 'SH', 'Saint Helena', 'Jamestown', 0),
+(200, 'SI', 'Slovenia', 'Ljubljana', 0),
+(201, 'SJ', 'Svalbard and Jan Mayen', 'Longyearbyen', 0),
+(202, 'SK', 'Slovakia', 'Bratislava', 0),
+(203, 'SL', 'Sierra Leone', 'Freetown', 0),
+(204, 'SM', 'San Marino', 'San Marino', 0),
+(205, 'SN', 'Senegal', 'Dakar', 0),
+(206, 'SO', 'Somalia', 'Mogadishu', 0),
+(207, 'SR', 'Suriname', 'Paramaribo', 0),
+(208, 'ST', 'Sao Tome and Principe', 'São Tomé', 0),
+(209, 'SV', 'El Salvador', 'San Salvador', 0),
+(210, 'SY', 'Syria', 'Damascus', 0),
+(211, 'SZ', 'Swaziland', 'Mbabane', 0),
+(212, 'TC', 'Turks and Caicos Islands', 'Cockburn Town', 0),
+(213, 'TD', 'Chad', 'N''Djamena', 0),
+(214, 'TF', 'French Southern Territories', 'Martin-de-Viviès', 0),
+(215, 'TG', 'Togo', 'Lomé', 0),
+(216, 'TH', 'Thailand', 'Bangkok', 0),
+(217, 'TJ', 'Tajikistan', 'Dushanbe', 0),
+(218, 'TK', 'Tokelau', '', 0),
+(219, 'TL', 'East Timor', 'Dili', 0),
+(220, 'TM', 'Turkmenistan', 'Ashgabat', 0),
+(221, 'TN', 'Tunisia', 'Tunis', 0),
+(222, 'TO', 'Tonga', 'Nuku''alofa', 0),
+(223, 'TR', 'Turkey', 'Ankara', 0),
+(224, 'TT', 'Trinidad and Tobago', 'Port of Spain', 0),
+(225, 'TV', 'Tuvalu', 'Vaiaku', 0),
+(226, 'TW', 'Taiwan', 'Taipei', 0),
+(227, 'TZ', 'Tanzania', 'Dar es Salaam', 0),
+(228, 'UA', 'Ukraine', 'Kiev', 0),
+(229, 'UG', 'Uganda', 'Kampala', 0),
+(230, 'UM', 'United States Minor Outlying Islands', '', 0),
+(231, 'US', 'United States', 'Washington', 0),
+(232, 'UY', 'Uruguay', 'Montevideo', 0),
+(233, 'UZ', 'Uzbekistan', 'Tashkent', 0),
+(234, 'VA', 'Vatican', 'Vatican City', 0),
+(235, 'VC', 'Saint Vincent and the Grenadines', 'Kingstown', 0),
+(236, 'VE', 'Venezuela', 'Caracas', 0),
+(237, 'VG', 'British Virgin Islands', 'Road Town', 0),
+(238, 'VI', 'U.S. Virgin Islands', 'Charlotte Amalie', 0),
+(239, 'VN', 'Vietnam', 'Hanoi', 0),
+(240, 'VU', 'Vanuatu', 'Port Vila', 0),
+(241, 'WF', 'Wallis and Futuna', 'Matâ''Utu', 0),
+(242, 'WS', 'Samoa', 'Apia', 0),
+(243, 'YE', 'Yemen', 'San‘a’', 0),
+(244, 'YT', 'Mayotte', 'Mamoudzou', 0),
+(245, 'ZA', 'South Africa', 'Pretoria', 0),
+(246, 'ZM', 'Zambia', 'Lusaka', 0),
+(247, 'ZW', 'Zimbabwe', 'Harare', 0),
+(248, 'XK', 'Kosovo', 'Pristina', 0),
+(249, 'SS', 'South Sudan', 'Juba', 0);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `externalapp`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `externalapp` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(255) NOT NULL,
+  `url` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Info on external apps(mobile) that work with your deployment' AUTO_INCREMENT=3 ;
+
+--
+-- Dumping data for table `externalapp`
+--
+
+INSERT INTO `externalapp` (`id`, `name`, `url`) VALUES
+(1, 'iPhone', 'http://download.ushahidi.com/track_download.php?download=ios'),
+(2, 'Android', 'http://download.ushahidi.com/track_download.php?download=android');
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `feed`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `feed` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `feed_name` varchar(255) DEFAULT NULL,
+  `feed_url` varchar(255) DEFAULT NULL,
+  `feed_cache` text,
+  `feed_active` tinyint(4) DEFAULT '1',
+  `feed_update` int(11) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Information about RSS Feeds a deployment subscribes to' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `feed_item`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `feed_item` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `feed_id` int(11) unsigned NOT NULL,
+  `location_id` bigint(20) unsigned DEFAULT '0',
+  `incident_id` bigint(20) unsigned NOT NULL DEFAULT '0',
+  `item_title` varchar(255) DEFAULT NULL,
+  `item_description` text,
+  `item_link` varchar(255) DEFAULT NULL,
+  `item_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `feed_id` (`feed_id`),
+  KEY `incident_id` (`incident_id`),
+  KEY `location_id` (`location_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores feed items pulled from each RSS Feed' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `feed_item_category`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `feed_item_category` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `feed_item_id` bigint(20) unsigned NOT NULL DEFAULT '0',
+  `category_id` int(11) unsigned NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `feed_item_category_ids` (`feed_item_id`,`category_id`),
+  KEY `feed_item_id` (`feed_item_id`),
+  KEY `category_id` (`category_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores fetched feed items categories' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `form`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `form` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `form_title` varchar(200) NOT NULL,
+  `form_description` text,
+  `form_active` tinyint(4) DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `form_title` (`form_title`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores all report submission forms created(default+custom)' AUTO_INCREMENT=2 ;
+
+--
+-- Dumping data for table `form`
+--
+
+INSERT INTO `form` (`id`, `form_title`, `form_description`, `form_active`) VALUES
+(1, 'Default Form', 'Default form, for report entry', 1);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `form_field`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `form_field` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `form_id` int(11) NOT NULL DEFAULT '1',
+  `field_name` varchar(200) DEFAULT NULL,
+  `field_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1 - TEXTFIELD, 2 - TEXTAREA (FREETEXT), 3 - DATE, 4 - PASSWORD, 5 - RADIO, 6 - CHECKBOX',
+  `field_required` tinyint(4) DEFAULT '0',
+  `field_position` tinyint(4) NOT NULL DEFAULT '0',
+  `field_default` TEXT,
+  `field_maxlength` int(11) NOT NULL DEFAULT '0',
+  `field_width` smallint(6) NOT NULL DEFAULT '0',
+  `field_height` tinyint(4) DEFAULT '5',
+  `field_isdate` tinyint(4) NOT NULL DEFAULT '0',
+  `field_ispublic_visible` tinyint(4) NOT NULL DEFAULT '0',
+  `field_ispublic_submit` tinyint(4) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `field_name` (  `field_name` ,  `form_id` ),
+  KEY `fk_form_id` (`form_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores all custom form fields created by users' AUTO_INCREMENT=1 ;
+
+/**
+ * Constraints for table `form_field`
+ */
+
+ALTER TABLE `form_field`
+  ADD CONSTRAINT `form_field_ibfk_1` FOREIGN KEY (`form_id`) REFERENCES `form` (`id`) ON DELETE CASCADE;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `form_field_option`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `form_field_option` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `form_field_id` int(11) NOT NULL DEFAULT '0',
+  `option_name` varchar(200) DEFAULT NULL,
+  `option_value` text,
+  PRIMARY KEY (`id`),
+  KEY `form_field_id` (`form_field_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Options related to custom form fields' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `form_response`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `form_response` (
+  `id` bigint(20) NOT NULL AUTO_INCREMENT,
+  `form_field_id` int(11) NOT NULL,
+  `incident_id` bigint(20) unsigned NOT NULL,
+  `form_response` text NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `fk_form_field_id` (`form_field_id`),
+  KEY `incident_id` (`incident_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores responses to custom form fields' AUTO_INCREMENT=1 ;
+
+
+/**
+ * Constraints for table `form_response`
+ */
+
+ALTER TABLE `form_response`
+  ADD CONSTRAINT `form_response_ibfk_1` FOREIGN KEY (`form_field_id`) REFERENCES `form_field` (`id`) ON DELETE CASCADE;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `geometry`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `geometry` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `incident_id` bigint(20) unsigned NOT NULL,
+  `geometry` geometry NOT NULL,
+  `geometry_label` varchar(150) DEFAULT NULL,
+  `geometry_comment` varchar(255) DEFAULT NULL,
+  `geometry_color` varchar(20) DEFAULT NULL,
+  `geometry_strokewidth` varchar(5) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  SPATIAL KEY `geometry` (`geometry`),
+  KEY `incident_id` (`incident_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores map geometries i.e polygons, lines etc' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `incident`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `incident` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `location_id` bigint(20) unsigned NOT NULL,
+  `form_id` int(11) NOT NULL DEFAULT '1',
+  `locale` varchar(10) NOT NULL DEFAULT 'en_US',
+  `user_id` int(11) unsigned DEFAULT NULL,
+  `incident_title` varchar(255) DEFAULT NULL,
+  `incident_description` longtext,
+  `incident_date` datetime DEFAULT NULL,
+  `incident_mode` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1 - WEB, 2 - SMS, 3 - EMAIL, 4 - TWITTER',
+  `incident_active` tinyint(4) NOT NULL DEFAULT '0',
+  `incident_verified` tinyint(4) NOT NULL DEFAULT '0',
+  `incident_dateadd` datetime DEFAULT NULL,
+  `incident_dateadd_gmt` datetime DEFAULT NULL,
+  `incident_datemodify` datetime DEFAULT NULL,
+  `incident_alert_status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0 - Not Tagged for Sending, 1 - Tagged for Sending, 2 - Alerts Have Been Sent',
+  `incident_zoom` tinyint(4) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `location_id` (`location_id`),
+  KEY `incident_active` (`incident_active`),
+  KEY `incident_date` (`incident_date`),
+  KEY `form_id` (`form_id`),
+  KEY `user_id` (`user_id`),
+  KEY `incident_mode` (`incident_mode`),
+  KEY `incident_verified` (`incident_verified`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores reports submitted' AUTO_INCREMENT=2 ;
+
+--
+-- Dumping data for table `incident`
+--
+
+LOCK TABLES `incident` WRITE;
+/*!40000 ALTER TABLE `incident` DISABLE KEYS */;
+INSERT INTO `incident` (`id`, `location_id`, `form_id`, `locale`, `user_id`, `incident_title`, `incident_description`, `incident_date`, `incident_mode`, `incident_active`, `incident_verified`, `incident_dateadd`, `incident_dateadd_gmt`, `incident_datemodify`, `incident_alert_status`, `incident_zoom`) VALUES
+(1, 1, 1, 'en_US', 1, 'Hello Ushahidi!', 'Welcome to Ushahidi. Please replace this report with a valid incident', '2012-04-04 12:54:31', 1, 1, 1, NULL, NULL, NULL, 0, NULL);
+/*!40000 ALTER TABLE `incident` ENABLE KEYS */;
+UNLOCK TABLES;
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `incident_category`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `incident_category` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `incident_id` bigint(20) unsigned NOT NULL DEFAULT '0',
+  `category_id` int(11) unsigned NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `incident_category_ids` (`incident_id`,`category_id`),
+  KEY `incident_id` (`incident_id`),
+  KEY `category_id` (`category_id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores submitted reports categories' AUTO_INCREMENT=2 ;
+
+--
+-- Dumping data for table `incident_category`
+--
+LOCK TABLES `incident_category` WRITE;
+/*!40000 ALTER TABLE `incident_category` DISABLE KEYS */;
+INSERT INTO `incident_category` (`id`, `incident_id`, `category_id`) VALUES
+(1, 1, 1);
+/*!40000 ALTER TABLE `incident_category` ENABLE KEYS */;
+UNLOCK TABLES;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `incident_lang`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `incident_lang` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `incident_id` bigint(20) unsigned NOT NULL,
+  `locale` varchar(10) DEFAULT NULL,
+  `incident_title` varchar(255) DEFAULT NULL,
+  `incident_description` longtext,
+  PRIMARY KEY (`id`),
+  KEY `incident_id` (`incident_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Holds translations for report titles and descriptions' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `incident_person`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `incident_person` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `incident_id` bigint(20) unsigned DEFAULT NULL,
+  `person_first` varchar(200) DEFAULT NULL,
+  `person_last` varchar(200) DEFAULT NULL,
+  `person_email` varchar(120) DEFAULT NULL,
+  `person_phone` varchar(60) DEFAULT NULL,
+  `person_ip` varchar(50) DEFAULT NULL,
+  `person_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `incident_id` (`incident_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Holds information provided by people who submit reports' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `layer`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `layer` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `layer_name` varchar(255) DEFAULT NULL,
+  `layer_url` varchar(255) DEFAULT NULL,
+  `layer_file` varchar(100) DEFAULT NULL,
+  `layer_color` varchar(20) DEFAULT NULL,
+  `layer_visible` tinyint(4) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Holds static layer information' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `level`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `level` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `level_title` varchar(200) DEFAULT NULL,
+  `level_description` varchar(200) DEFAULT NULL,
+  `level_weight` tinyint(4) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores level of trust assigned to reporters of the platform' AUTO_INCREMENT=6 ;
+
+--
+-- Dumping data for table `level`
+--
+
+INSERT INTO `level` (`id`, `level_title`, `level_description`, `level_weight`) VALUES
+(1, 'SPAM + Delete', 'SPAM + Delete', -2),
+(2, 'SPAM', 'SPAM', -1),
+(3, 'Untrusted', 'Untrusted', 0),
+(4, 'Trusted', 'Trusted', 1),
+(5, 'Trusted + Verify', 'Trusted + Verify', 2);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `location`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `location` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `location_name` varchar(255) DEFAULT NULL,
+  `country_id` int(11) NOT NULL DEFAULT '0',
+  `latitude` double NOT NULL DEFAULT '0',
+  `longitude` double NOT NULL DEFAULT '0',
+  `location_visible` tinyint(4) NOT NULL DEFAULT '1',
+  `location_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `country_id` (`country_id`),
+  KEY `latitude` (`latitude`),
+  KEY `longitude` (`longitude`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores location information' AUTO_INCREMENT=2 ;
+
+--
+-- Dumping data for table `location`
+--
+LOCK TABLES `location` WRITE;
+/*!40000 ALTER TABLE `location` DISABLE KEYS */;
+INSERT INTO `location` (`id`, `location_name`, `country_id`, `latitude`, `longitude`, `location_visible`, `location_date`) VALUES
+(1, 'Nairobi', 115, -1.28730007070501, 36.8214511820082, 1, '2009-06-30 00:00:00');
+/*!40000 ALTER TABLE `location` ENABLE KEYS */;
+UNLOCK TABLES;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `maintenance`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `maintenance` (
+  `allowed_ip` varchar(15) NOT NULL,
+  PRIMARY KEY (`allowed_ip`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Puts a site in maintenance mode if data exists in this table';
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `media`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `media` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `location_id` bigint(20) unsigned DEFAULT NULL,
+  `incident_id` bigint(20) unsigned DEFAULT NULL,
+  `message_id` bigint(20) unsigned DEFAULT NULL,
+  `badge_id` int(11) DEFAULT NULL,
+  `media_type` tinyint(4) DEFAULT NULL COMMENT '1 - IMAGES, 2 - VIDEO, 3 - AUDIO, 4 - NEWS, 5 - PODCAST',
+  `media_title` varchar(255) DEFAULT NULL,
+  `media_description` longtext,
+  `media_link` varchar(255) DEFAULT NULL,
+  `media_medium` varchar(255) DEFAULT NULL,
+  `media_thumb` varchar(255) DEFAULT NULL,
+  `media_date` datetime DEFAULT NULL,
+  `media_active` tinyint(4) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `incident_id` (`incident_id`),
+  KEY `location_id` (`location_id`),
+  KEY `badge_id` (`badge_id`),
+  KEY `message_id` (`message_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores any media submitted along with a report' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `message`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `message` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `parent_id` bigint(20) DEFAULT '0',
+  `incident_id` bigint(20) unsigned DEFAULT '0',
+  `user_id` int(11) unsigned DEFAULT '0',
+  `reporter_id` bigint(20) unsigned DEFAULT NULL,
+  `service_messageid` varchar(100) DEFAULT NULL,
+  `message_from` varchar(100) DEFAULT NULL,
+  `message_to` varchar(100) DEFAULT NULL,
+  `message` text,
+  `message_detail` text,
+  `message_type` tinyint(4) DEFAULT '1' COMMENT '1 - INBOX, 2 - OUTBOX (From Admin), 3 - DELETED',
+  `message_date` datetime DEFAULT NULL,
+  `message_level` tinyint(4) DEFAULT '0' COMMENT '0 - UNREAD, 1 - READ, 99 - SPAM',
+  `latitude` double DEFAULT NULL,
+  `longitude` double DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `user_id` (`user_id`),
+  KEY `incident_id` (`incident_id`),
+  KEY `reporter_id` (`reporter_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores tweets, emails and SMS messages' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `openid`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `openid` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) unsigned NOT NULL,
+  `openid` varchar(255) NOT NULL,
+  `openid_email` varchar(127) NOT NULL,
+  `openid_server` varchar(255) NOT NULL,
+  `openid_date` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `openid` (`openid`),
+  KEY `user_id` (`user_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores users’ openid information' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `page`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `page` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `page_title` varchar(255) NOT NULL,
+  `page_description` longtext,
+  `page_tab` varchar(100) NOT NULL,
+  `page_active` tinyint(4) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores user created pages' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `permissions`
+ *
+ */
+CREATE TABLE IF NOT EXISTS `permissions` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(32) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name_UNIQUE` (`name`)
+) ENGINE=MyISAM AUTO_INCREMENT=16 COMMENT='Stores permissions used for access control';
+
+/* Data for permissions table */
+INSERT IGNORE INTO `permissions` VALUES
+(1,'reports_view'),
+(2,'reports_edit'),
+(4,'reports_comments'),
+(5,'reports_download'),
+(6,'reports_upload'),
+(7,'messages'),
+(8,'messages_reporters'),
+(9,'stats'),
+(10,'settings'),
+(11,'manage'),
+(12,'users'),
+(13,'manage_roles'),
+(16,'reports_verify'),
+(17,'reports_approve'),
+(18,'admin_ui'),
+(19,'member_ui'),
+(20,'delete_all_reports');
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `permissions_roles`
+ *
+ */
+CREATE TABLE IF NOT EXISTS `permissions_roles` (
+  `role_id` int(11) NOT NULL,
+  `permission_id` int(11) NOT NULL,
+  PRIMARY KEY (`role_id`,`permission_id`)
+) ENGINE=MyISAM COMMENT='Stores permissions assigned to roles';
+
+--
+-- Dumping data for table `permissions_roles`
+--
+
+INSERT INTO `permissions_roles` VALUES
+(1,14),
+(2,1),(2,2),(2,4),(2,5),(2,6),(2,7),(2,8),(2,9),(2,10),(2,11),(2,12),(2,14),(2,15),(2,16),(2,17),(2,18),
+(3,1),(3,2),(3,4),(3,5),(3,6),(3,7),(3,8),(3,9),(3,10),(3,11),(3,12),(3,13),(3,14),(3,15),(3,16),(3,17),(3,18),(3,20),
+(4,19);
+
+-- ---------------------------------------------------------
+
+/**
+ * Table structure for table `plugin`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `plugin` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `plugin_name` varchar(100) NOT NULL,
+  `plugin_url` varchar(250) DEFAULT NULL,
+  `plugin_description` text,
+  `plugin_priority` tinyint(4) DEFAULT '0',
+  `plugin_active` tinyint(4) DEFAULT '0',
+  `plugin_installed` tinyint(4) DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `plugin_name` (`plugin_name`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Holds a list of all plugins installed on a deployment' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `private_message`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `private_message` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) NOT NULL DEFAULT '0',
+  `user_id` int(11) unsigned NOT NULL,
+  `from_user_id` int(11) DEFAULT '0',
+  `private_subject` varchar(255) NOT NULL,
+  `private_message` text NOT NULL,
+  `private_message_date` datetime NOT NULL,
+  `private_message_new` tinyint(4) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  KEY `user_id` (`user_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores private messages sent between Members' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `rating`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `rating` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) unsigned DEFAULT '0',
+  `incident_id` bigint(20) unsigned DEFAULT NULL,
+  `comment_id` bigint(20) unsigned DEFAULT NULL,
+  `rating` tinyint(4) DEFAULT '0',
+  `rating_ip` varchar(100) DEFAULT NULL,
+  `rating_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `user_id` (`user_id`),
+  KEY `incident_id` (`incident_id`),
+  KEY `comment_id` (`comment_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores credibility ratings for reports and comments' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `reporter`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `reporter` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `location_id` bigint(20) unsigned DEFAULT NULL,
+  `user_id` int(11) unsigned DEFAULT NULL,
+  `service_id` int(10) unsigned DEFAULT NULL,
+  `level_id` int(11) unsigned DEFAULT NULL,
+  `service_account` varchar(255) DEFAULT NULL,
+  `reporter_first` varchar(200) DEFAULT NULL,
+  `reporter_last` varchar(200) DEFAULT NULL,
+  `reporter_email` varchar(120) DEFAULT NULL,
+  `reporter_phone` varchar(60) DEFAULT NULL,
+  `reporter_ip` varchar(50) DEFAULT NULL,
+  `reporter_date` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  KEY `user_id` (`user_id`),
+  KEY `location_id` (`location_id`),
+  KEY `service_id` (`service_id`),
+  KEY `level_id` (`level_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Information on report submitters via email, twitter and sms' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `roles`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `roles` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(32) NOT NULL,
+  `description` varchar(255) NOT NULL,
+  `access_level` tinyint(4) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uniq_name` (`name`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Defines user access levels and privileges on a deployment' AUTO_INCREMENT=5 ;
+
+--
+-- Dumping data for table `roles`
+--
+
+INSERT INTO `roles` (`id`, `name`, `description`, `access_level`) VALUES
+(1, 'login', 'Login privileges, granted after account confirmation', 0),
+(2, 'admin', 'Administrative user, has access to almost everything.', 90),
+(3, 'superadmin', 'Super administrative user, has access to everything.', 100),
+(4, 'member', 'Regular user with access only to the member area', 10);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `roles_users`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `roles_users` (
+  `user_id` int(11) unsigned NOT NULL,
+  `role_id` int(11) unsigned NOT NULL,
+  PRIMARY KEY (`user_id`,`role_id`),
+  KEY `fk_role_id` (`role_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores roles assigned to users registered on a deployment';
+
+--
+-- Dumping data for table `roles_users`
+--
+
+INSERT INTO `roles_users` (`user_id`, `role_id`) VALUES
+(1, 1),
+(1, 2),
+(1, 3);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `scheduler`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `scheduler` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `scheduler_name` varchar(100) NOT NULL,
+  `scheduler_last` int(10) unsigned NOT NULL DEFAULT '0',
+  `scheduler_weekday` smallint(6) NOT NULL DEFAULT '-1',
+  `scheduler_day` smallint(6) NOT NULL DEFAULT '-1',
+  `scheduler_hour` smallint(6) NOT NULL DEFAULT '-1',
+  `scheduler_minute` smallint(6) NOT NULL,
+  `scheduler_controller` varchar(100) NOT NULL,
+  `scheduler_active` tinyint(4) NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores schedules for cron jobs' AUTO_INCREMENT=6 ;
+
+--
+-- Dumping data for table `scheduler`
+--
+
+INSERT INTO `scheduler` (`id`, `scheduler_name`, `scheduler_last`, `scheduler_weekday`, `scheduler_day`, `scheduler_hour`, `scheduler_minute`, `scheduler_controller`, `scheduler_active`) VALUES
+(1, 'Feeds', 0, -1, -1, -1, 0, 's_feeds', 1),
+(2, 'Alerts', 0, -1, -1, -1, -1, 's_alerts', 1),
+(3, 'Email', 0, -1, -1, -1, 0, 's_email', 1),
+(4, 'Twitter', 0, -1, -1, -1, 0, 's_twitter', 1),
+(5, 'Cleanup', 0, -1, -1, -1, 0, 's_cleanup', 1);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `scheduler_log`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `scheduler_log` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `scheduler_id` int(10) unsigned NOT NULL,
+  `scheduler_status` varchar(20) DEFAULT NULL,
+  `scheduler_date` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  KEY `scheduler_id` (`scheduler_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores a log of scheduler actions' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `service`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `service` (
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+  `service_name` varchar(100) DEFAULT NULL,
+  `service_description` varchar(255) DEFAULT NULL,
+  `service_url` varchar(255) DEFAULT NULL,
+  `service_api` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Info on input sources i.e SMS, Email, Twitter' AUTO_INCREMENT=4 ;
+
+--
+-- Dumping data for table `service`
+--
+
+INSERT INTO `service` (`id`, `service_name`, `service_description`, `service_url`, `service_api`) VALUES
+(1, 'SMS', 'Text messages from phones', NULL, NULL),
+(2, 'Email', 'Email messages sent to your deployment', NULL, NULL),
+(3, 'Twitter', 'Tweets tweets tweets', 'http://twitter.com', NULL);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `sessions`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `sessions` (
+  `session_id` varchar(127) NOT NULL,
+  `last_activity` int(10) unsigned NOT NULL,
+  `data` text NOT NULL,
+  PRIMARY KEY (`session_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores session information';
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `settings`
+ *
+ */
+CREATE TABLE IF NOT EXISTS `settings` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `key` varchar(100) NOT NULL DEFAULT '' COMMENT 'Unique identifier for the configuration parameter',
+  `value` text COMMENT 'Value for the settings parameter',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uq_settings_key` (`key`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+/*!40000 ALTER TABLE `settings` DISABLE KEYS */;
+
+INSERT INTO `settings` (`id`, `key`, `value`)
+VALUES
+  (1,'site_name','Ushahidi'),
+  (2,'site_tagline',NULL),
+  (3,'site_banner_id',NULL),
+  (4,'site_email',NULL),
+  (5,'site_key',NULL),
+  (6,'site_language','en_US'),
+  (7,'site_style','default'),
+  (8,'site_timezone',NULL),
+  (9,'site_contact_page','1'),
+  (10,'site_help_page','1'),
+  (11,'site_message',NULL),
+  (12,'site_copyright_statement',NULL),
+  (13,'site_submit_report_message',NULL),
+  (14,'allow_reports','1'),
+  (15,'allow_comments','1'),
+  (16,'allow_feed','1'),
+  (17,'allow_stat_sharing','1'),
+  (18,'allow_clustering','1'),
+  (19,'cache_pages','0'),
+  (20,'cache_pages_lifetime','1800'),
+  (21,'private_deployment','0'),
+  (22,'default_map','osm_mapnik'),
+  (23,'default_map_all','CC0000'),
+  (24,'default_map_all_icon_id',NULL),
+  (25,'api_google','ABQIAAAAjsEM5UsvCPCIHp80spK1kBQnONNwnjgPbDSioH0X5rmWMjc4axQCaMN2CIvMUCsXGLs-5pQ8xAx5cw'),
+  (26,'api_live','Apumcka0uPOF2lKLorq8aeo4nuqfVVeNRqJjqOcLMJ9iMCTsnMsNd9_OvpA8gR0i'),
+  (27,'api_akismet',''),
+  (28,'default_country','115'),
+  (29,'multi_country','0'),
+  (30,'default_city','nairobi'),
+  (31,'default_lat','-1.2873000707050097'),
+  (32,'default_lon','36.821451182008204'),
+  (33,'default_zoom','13'),
+  (34,'items_per_page','5'),
+  (35,'items_per_page_admin','20'),
+  (36,'sms_provider',''),
+  (37,'sms_no1',NULL),
+  (38,'sms_no2',NULL),
+  (39,'sms_no3',NULL),
+  (40,'google_analytics',NULL),
+  (41,'twitter_hashtags',NULL),
+  (42,'blocks','news_block|reports_block'),
+  (43,'blocks_per_row','2'),
+  (44,'date_modify','2008-08-25 10:25:18'),
+  (45,'stat_id',NULL),
+  (46,'stat_key',NULL),
+  (47,'email_username',NULL),
+  (48,'email_password',NULL),
+  (49,'email_port',NULL),
+  (50,'email_host',NULL),
+  (51,'email_servertype',NULL),
+  (52,'email_ssl',NULL),
+  (53,'ftp_server',NULL),
+  (54,'ftp_user_name',NULL),
+  (55,'alerts_email',NULL),
+  (57,'facebook_appid',NULL),
+  (58,'facebook_appsecret',NULL),
+  (59,'db_version','97'),
+  (60,'ushahidi_version','2.5'),
+  (61,'allow_alerts','1'),
+  (62,'require_email_confirmation','0'),
+  (63,'manually_approve_users','0'),
+  (64,'enable_timeline','0'),
+  (65,'feed_geolocation_user', ''),
+  (66,'allow_feed_category', '0'),
+  (67, 'max_upload_size', '10');
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `users`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `users` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `riverid` varchar(128) NOT NULL,
+  `name` varchar(200) DEFAULT NULL,
+  `email` varchar(127) NOT NULL,
+  `username` varchar(100) NOT NULL DEFAULT '',
+  `password` char(50) NOT NULL,
+  `logins` int(10) unsigned NOT NULL DEFAULT '0',
+  `last_login` int(10) unsigned DEFAULT NULL,
+  `notify` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Flag incase admin opts in for email notifications',
+  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  `color` varchar(6) NOT NULL DEFAULT 'FF0000',
+  `code` varchar(30) DEFAULT NULL,
+  `confirmed` tinyint(1) NOT NULL DEFAULT '0',
+  `public_profile` tinyint(1) NOT NULL DEFAULT '1',
+  `approved` tinyint(1) NOT NULL DEFAULT '1',
+  `needinfo` tinyint(1) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uniq_username` (`username`),
+  UNIQUE KEY `uniq_email` (`email`)
+) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='Stores registered users’ information' AUTO_INCREMENT=2 ;
+
+--
+-- Dumping data for table `users`
+--
+
+INSERT INTO `users` (`id`, `name`, `email`, `username`, `password`, `logins`, `last_login`, `updated`, `public_profile`, `confirmed`) VALUES
+(1, 'Administrator', 'myemail at example.com', 'admin', 'bae4b17e9acbabf959654a4c496e577003e0b887c6f52803d7', 0, 1221420023, '2008-09-14 14:17:22', 0, 1);
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `user_tokens`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `user_tokens` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) unsigned NOT NULL,
+  `user_agent` varchar(40) NOT NULL,
+  `token` varchar(64) NOT NULL,
+  `created` int(10) unsigned NOT NULL,
+  `expires` int(10) unsigned NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uniq_token` (`token`),
+  KEY `fk_user_id` (`user_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores browser tokens assigned to users' AUTO_INCREMENT=1 ;
+
+-- --------------------------------------------------------
+
+/**
+ * Table structure for table `verified`
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `verified` (
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
+  `incident_id` bigint(20) unsigned DEFAULT NULL,
+  `user_id` int(11) DEFAULT NULL,
+  `verified_date` datetime DEFAULT NULL,
+  `verified_status` tinyint(4) NOT NULL DEFAULT '0',
+  PRIMARY KEY (`id`),
+  KEY `incident_id` (`incident_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='Stores all verified reports' AUTO_INCREMENT=1 ;
+
+/**
+ * Version information for table `settings`
+ *
+ */
+UPDATE `settings` SET `value` = '119' WHERE `key` = 'db_version';
+UPDATE `settings` SET `value` = '2.7.4' WHERE `key`= 'ushahidi_version';
diff --git a/system/config/cache.php b/system/config/cache.php
new file mode 100755
index 0000000..ccd3da4
--- /dev/null
+++ b/system/config/cache.php
@@ -0,0 +1,32 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Cache
+ *
+ * Cache settings, defined as arrays, or "groups". If no group name is
+ * used when loading the cache library, the group named "default" will be used.
+ *
+ * Each group can be used independently, and multiple groups can be used at once.
+ *
+ * Group Options:
+ *  driver   - Cache backend driver. Kohana comes with file, database, and memcache drivers.
+ *              > File cache is fast and reliable, but requires many filesystem lookups.
+ *              > Database cache can be used to cache items remotely, but is slower.
+ *              > Memcache is very high performance, but prevents cache tags from being used.
+ *
+ *  params   - Driver parameters, specific to each driver.
+ *
+ *  lifetime - Default lifetime of caches in seconds. By default caches are stored for
+ *             thirty minutes. Specific lifetime can also be set when creating a new cache.
+ *             Setting this to 0 will never automatically delete caches.
+ *
+ *  requests - Average number of cache requests that will processed before all expired
+ *             caches are deleted. This is commonly referred to as "garbage collection".
+ *             Setting this to 0 or a negative number will disable automatic garbage collection.
+ */
+$config['default'] = array
+(
+	'driver'   => 'file',
+	'params'   => APPPATH.'cache',
+	'lifetime' => 1800,
+	'requests' => 1000
+);
diff --git a/system/config/cache_memcache.php b/system/config/cache_memcache.php
new file mode 100755
index 0000000..43d8f20
--- /dev/null
+++ b/system/config/cache_memcache.php
@@ -0,0 +1,20 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Cache:Memcache
+ *
+ * memcache server configuration.
+ */
+$config['servers'] = array
+(
+	array
+	(
+		'host' => '127.0.0.1',
+		'port' => 11211,
+		'persistent' => FALSE,
+	)
+);
+
+/**
+ * Enable cache data compression.
+ */
+$config['compression'] = FALSE;
diff --git a/system/config/cache_sqlite.php b/system/config/cache_sqlite.php
new file mode 100755
index 0000000..918224c
--- /dev/null
+++ b/system/config/cache_sqlite.php
@@ -0,0 +1,11 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Cache:SQLite
+ */
+$config['schema'] =
+'CREATE TABLE caches(
+	id varchar(127) PRIMARY KEY,
+	hash char(40) NOT NULL,
+	tags varchar(255),
+	expiration int,
+	cache blob);';
\ No newline at end of file
diff --git a/system/config/cache_xcache.php b/system/config/cache_xcache.php
new file mode 100755
index 0000000..47aac25
--- /dev/null
+++ b/system/config/cache_xcache.php
@@ -0,0 +1,12 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Cache:Xcache
+ *
+ * Xcache administrator username.
+ */
+$config['PHP_AUTH_USER'] = 'kohana';
+
+/**
+ * Xcache administrator password.
+ */
+$config['PHP_AUTH_PW'] = 'kohana';
diff --git a/system/config/captcha.php b/system/config/captcha.php
new file mode 100755
index 0000000..9241ec6
--- /dev/null
+++ b/system/config/captcha.php
@@ -0,0 +1,29 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Captcha configuration is defined in groups which allows you to easily switch
+ * between different Captcha settings for different forms on your website.
+ * Note: all groups inherit and overwrite the default group.
+ *
+ * Group Options:
+ *  style      - Captcha type, e.g. basic, alpha, word, math, riddle
+ *  width      - Width of the Captcha image
+ *  height     - Height of the Captcha image
+ *  complexity - Difficulty level (0-10), usage depends on chosen style
+ *  background - Path to background image file
+ *  fontpath   - Path to font folder
+ *  fonts      - Font files
+ *  promote    - Valid response count threshold to promote user (FALSE to disable)
+ */
+$config['default'] = array
+(
+	'style'      => 'basic',
+	'width'      => 150,
+	'height'     => 50,
+	'complexity' => 4,
+	'background' => '',
+	'fontpath'   => SYSPATH.'fonts/',
+	'fonts'      => array('DejaVuSerif.ttf'),
+	'promote'    => FALSE,
+);
\ No newline at end of file
diff --git a/system/config/cookie.php b/system/config/cookie.php
new file mode 100755
index 0000000..b6ddfe4
--- /dev/null
+++ b/system/config/cookie.php
@@ -0,0 +1,32 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Domain, to restrict the cookie to a specific website domain. For security,
+ * you are encouraged to set this option. An empty setting allows the cookie
+ * to be read by any website domain.
+ */
+$config['domain'] = '';
+
+/**
+ * Restrict cookies to a specific path, typically the installation directory.
+ */
+$config['path'] = '/';
+
+/**
+ * Lifetime of the cookie. A setting of 0 makes the cookie active until the
+ * users browser is closed or the cookie is deleted.
+ */
+$config['expire'] = 0;
+
+/**
+ * Enable this option to only allow the cookie to be read when using the a
+ * secure protocol.
+ */
+$config['secure'] = FALSE;
+
+/**
+ * Enable this option to disable the cookie from being accessed when using a
+ * secure protocol. This option is only available in PHP 5.2 and above.
+ */
+$config['httponly'] = FALSE;
\ No newline at end of file
diff --git a/system/config/credit_cards.php b/system/config/credit_cards.php
new file mode 100755
index 0000000..bfc9842
--- /dev/null
+++ b/system/config/credit_cards.php
@@ -0,0 +1,60 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Credit card validation configuration.
+ * 
+ * Options for each credit card:
+ *  length - All the allowed card number lengths, in a comma separated string
+ *  prefix - The digits the card needs to start with, in regex format
+ *  luhn   - Enable or disable card number validation by the Luhn algorithm
+ */
+$config = array
+(
+	'default' => array
+	(
+		'length' => '13,14,15,16,17,18,19',
+		'prefix' => '',
+		'luhn'   => TRUE
+	),
+	'american express' => array
+	(
+		'length' => '15',
+		'prefix' => '3[47]',
+		'luhn'   => TRUE
+	),
+	'diners club' => array
+	(
+		'length' => '14,16',
+		'prefix' => '36|55|30[0-5]',
+		'luhn'   => TRUE
+	),
+	'discover' => array
+	(
+		'length' => '16',
+		'prefix' => '6(?:5|011)',
+		'luhn'   => TRUE,
+	),
+	'jcb' => array
+	(
+		'length' => '15,16',
+		'prefix' => '3|1800|2131',
+		'luhn'   => TRUE
+	),
+	'maestro' => array
+	(
+		'length' => '16,18',
+		'prefix' => '50(?:20|38)|6(?:304|759)',
+		'luhn'   => TRUE
+	),
+	'mastercard' => array
+	(
+		'length' => '16',
+		'prefix' => '5[1-5]',
+		'luhn'   => TRUE
+	),
+	'visa' => array
+	(
+		'length' => '13,16',
+		'prefix' => '4',
+		'luhn'   => TRUE
+	),
+);
\ No newline at end of file
diff --git a/system/config/email.php b/system/config/email.php
new file mode 100755
index 0000000..c768367
--- /dev/null
+++ b/system/config/email.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * SwiftMailer driver, used with the email helper.
+ *
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/nativemail
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/sendmail
+ * @see http://www.swiftmailer.org/wikidocs/v3/connections/smtp
+ *
+ * Valid drivers are: native, sendmail, smtp
+ */
+$config['driver'] = 'native';
+
+/**
+ * To use secure connections with SMTP, set "port" to 465 instead of 25.
+ * To enable TLS, set "encryption" to "tls".
+ *
+ * Driver options:
+ * @param   null    native: no options
+ * @param   string  sendmail: executable path, with -bs or equivalent attached
+ * @param   array   smtp: hostname, (username), (password), (port), (auth), (encryption)
+ */
+$config['options'] = NULL;
diff --git a/system/config/encryption.php b/system/config/encryption.php
new file mode 100755
index 0000000..589a9de
--- /dev/null
+++ b/system/config/encryption.php
@@ -0,0 +1,31 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Encrypt
+ *
+ * Encrypt configuration is defined in groups which allows you to easily switch
+ * between different encryption settings for different uses.
+ * Note: all groups inherit and overwrite the default group.
+ *
+ * Group Options:
+ *  key    - Encryption key used to do encryption and decryption. The default option
+ *           should never be used for a production website.
+ *
+ *           For best security, your encryption key should be at least 16 characters
+ *           long and contain letters, numbers, and symbols.
+ *           @note Do not use a hash as your key. This significantly lowers encryption entropy.
+ *
+ *  mode   - MCrypt encryption mode. By default, MCRYPT_MODE_NOFB is used. This mode
+ *           offers initialization vector support, is suited to short strings, and
+ *           produces the shortest encrypted output.
+ *           @see http://php.net/mcrypt
+ *
+ *  cipher - MCrypt encryption cipher. By default, the MCRYPT_RIJNDAEL_128 cipher is used.
+ *           This is also known as 128-bit AES.
+ *           @see http://php.net/mcrypt
+ */
+$config['default'] = array
+(
+	'key'    => 'K0H at NA+PHP_7hE-SW!FtFraM3w0R|<',
+	'mode'   => MCRYPT_MODE_NOFB,
+	'cipher' => MCRYPT_RIJNDAEL_128
+);
diff --git a/system/config/http.php b/system/config/http.php
new file mode 100755
index 0000000..3c4a86a
--- /dev/null
+++ b/system/config/http.php
@@ -0,0 +1,19 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+// HTTP-EQUIV type meta tags
+$config['meta_equiv'] = array
+(
+	'cache-control',
+	'content-type', 'content-script-type', 'content-style-type',
+	'content-disposition',
+	'content-language',
+	'default-style',
+	'expires',
+	'ext-cache',
+	'pics-label',
+	'pragma',
+	'refresh',
+	'set-cookie',
+	'vary',
+	'window-target',
+);
\ No newline at end of file
diff --git a/system/config/image.php b/system/config/image.php
new file mode 100755
index 0000000..e38b7cb
--- /dev/null
+++ b/system/config/image.php
@@ -0,0 +1,13 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Image
+ *
+ * Driver name. Default: GD
+ */
+$config['driver'] = 'GD';
+
+/**
+ * Driver parameters:
+ * ImageMagick - set the "directory" parameter to your ImageMagick installation directory
+ */
+$config['params'] = array();
\ No newline at end of file
diff --git a/system/config/inflector.php b/system/config/inflector.php
new file mode 100755
index 0000000..6dcfc2d
--- /dev/null
+++ b/system/config/inflector.php
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$config['uncountable'] = array
+(
+	'access',
+	'advice',
+	'art',
+	'baggage',
+	'dances',
+	'equipment',
+	'fish',
+	'fuel',
+	'furniture',
+	'food',
+	'heat',
+	'honey',
+	'homework',
+	'impatience',
+	'information',
+	'knowledge',
+	'luggage',
+	'money',
+	'music',
+	'news',
+	'patience',
+	'progress',
+	'pollution',
+	'research',
+	'rice',
+	'sand',
+	'series',
+	'sheep',
+	'sms',
+	'species',
+	'staff',
+	'toothpaste',
+	'traffic',
+	'understanding',
+	'water',
+	'weather',
+	'work',
+);
+
+$config['irregular'] = array
+(
+	'child' => 'children',
+	'clothes' => 'clothing',
+	'man' => 'men',
+	'movie' => 'movies',
+	'person' => 'people',
+	'woman' => 'women',
+	'mouse' => 'mice',
+	'goose' => 'geese',
+	'ox' => 'oxen',
+	'leaf' => 'leaves',
+	'course' => 'courses',
+	'size' => 'sizes',
+);
diff --git a/system/config/locale.php b/system/config/locale.php
new file mode 100755
index 0000000..3a26882
--- /dev/null
+++ b/system/config/locale.php
@@ -0,0 +1,16 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Default language locale name(s).
+ * First item must be a valid i18n directory name, subsequent items are alternative locales
+ * for OS's that don't support the first (e.g. Windows). The first valid locale in the array will be used.
+ * @see http://php.net/setlocale
+ */
+$config['language'] = array('en_US', 'English_United States');
+
+/**
+ * Locale timezone. Defaults to use the server timezone.
+ * @see http://php.net/timezones
+ */
+$config['timezone'] = '';
\ No newline at end of file
diff --git a/system/config/mimes.php b/system/config/mimes.php
new file mode 100755
index 0000000..6a960f2
--- /dev/null
+++ b/system/config/mimes.php
@@ -0,0 +1,224 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * A list of mime types. Our list is generally more complete and accurate than
+ * the operating system MIME list.
+ *
+ * If there are any missing options, please create a ticket on our issue tracker,
+ * http://kohanaphp.com/trac/newticket. Be sure to give the filename and
+ * expected MIME type, as well as any additional information you can provide.
+ */
+$config = array
+(
+	'323'   => array('text/h323'),
+	'7z'    => array('application/x-7z-compressed'),
+	'abw'   => array('application/x-abiword'),
+	'acx'   => array('application/internet-property-stream'),
+	'ai'    => array('application/postscript'),
+	'aif'   => array('audio/x-aiff'),
+	'aifc'  => array('audio/x-aiff'),
+	'aiff'  => array('audio/x-aiff'),
+	'asf'   => array('video/x-ms-asf'),
+	'asr'   => array('video/x-ms-asf'),
+	'asx'   => array('video/x-ms-asf'),
+	'atom'  => array('application/atom+xml'),
+	'avi'   => array('video/avi', 'video/msvideo', 'video/x-msvideo'),
+	'bin'   => array('application/octet-stream','application/macbinary'),
+	'bmp'   => array('image/bmp'),
+	'c'     => array('text/x-csrc'),
+	'c++'   => array('text/x-c++src'),
+	'cab'   => array('application/x-cab'),
+	'cc'    => array('text/x-c++src'),
+	'cda'   => array('application/x-cdf'),
+	'class' => array('application/octet-stream'),
+	'cpp'   => array('text/x-c++src'),
+	'cpt'   => array('application/mac-compactpro'),
+	'csh'   => array('text/x-csh'),
+	'css'   => array('text/css'),
+	'csv'   => array('text/x-comma-separated-values', 'application/vnd.ms-excel', 'text/comma-separated-values', 'text/csv'),
+	'dbk'   => array('application/docbook+xml'),
+	'dcr'   => array('application/x-director'),
+	'deb'   => array('application/x-debian-package'),
+	'diff'  => array('text/x-diff'),
+	'dir'   => array('application/x-director'),
+	'divx'  => array('video/divx'),
+	'dll'   => array('application/octet-stream', 'application/x-msdos-program'),
+	'dmg'   => array('application/x-apple-diskimage'),
+	'dms'   => array('application/octet-stream'),
+	'doc'   => array('application/msword'),
+	'dvi'   => array('application/x-dvi'),
+	'dxr'   => array('application/x-director'),
+	'eml'   => array('message/rfc822'),
+	'eps'   => array('application/postscript'),
+	'evy'   => array('application/envoy'),
+	'exe'   => array('application/x-msdos-program', 'application/octet-stream'),
+	'fla'   => array('application/octet-stream'),
+	'flac'  => array('application/x-flac'),
+	'flc'   => array('video/flc'),
+	'fli'   => array('video/fli'),
+	'flv'   => array('video/x-flv'),
+	'gif'   => array('image/gif'),
+	'gtar'  => array('application/x-gtar'),
+	'gz'    => array('application/x-gzip'),
+	'h'     => array('text/x-chdr'),
+	'h++'   => array('text/x-c++hdr'),
+	'hh'    => array('text/x-c++hdr'),
+	'hpp'   => array('text/x-c++hdr'),
+	'hqx'   => array('application/mac-binhex40'),
+	'hs'    => array('text/x-haskell'),
+	'htm'   => array('text/html'),
+	'html'  => array('text/html'),
+	'ico'   => array('image/x-icon'),
+	'ics'   => array('text/calendar'),
+	'iii'   => array('application/x-iphone'),
+	'ins'   => array('application/x-internet-signup'),
+	'iso'   => array('application/x-iso9660-image'),
+	'isp'   => array('application/x-internet-signup'),
+	'jar'   => array('application/java-archive'),
+	'java'  => array('application/x-java-applet'),
+	'jpe'   => array('image/jpeg', 'image/pjpeg'),
+	'jpeg'  => array('image/jpeg', 'image/pjpeg'),
+	'jpg'   => array('image/jpeg', 'image/pjpeg'),
+	'js'    => array('application/x-javascript'),
+	'json'  => array('application/json'),
+	'latex' => array('application/x-latex'),
+	'lha'   => array('application/octet-stream'),
+	'log'   => array('text/plain', 'text/x-log'),
+	'lzh'   => array('application/octet-stream'),
+	'm4a'   => array('audio/mpeg'),
+	'm4p'   => array('video/mp4v-es'),
+	'm4v'   => array('video/mp4'),
+	'man'   => array('application/x-troff-man'),
+	'mdb'   => array('application/x-msaccess'),
+	'midi'  => array('audio/midi'),
+	'mid'   => array('audio/midi'),
+	'mif'   => array('application/vnd.mif'),
+	'mka'   => array('audio/x-matroska'),
+	'mkv'   => array('video/x-matroska'),
+	'mov'   => array('video/quicktime'),
+	'movie' => array('video/x-sgi-movie'),
+	'mp2'   => array('audio/mpeg'),
+	'mp3'   => array('audio/mpeg'),
+	'mp4'   => array('application/mp4','audio/mp4','video/mp4'),
+	'mpa'   => array('video/mpeg'),
+	'mpe'   => array('video/mpeg'),
+	'mpeg'  => array('video/mpeg'),
+	'mpg'   => array('video/mpeg'),
+	'mpg4'  => array('video/mp4'),
+	'mpga'  => array('audio/mpeg'),
+	'mpp'   => array('application/vnd.ms-project'),
+	'mpv'   => array('video/x-matroska'),
+	'mpv2'  => array('video/mpeg'),
+	'ms'    => array('application/x-troff-ms'),
+	'msg'   => array('application/msoutlook','application/x-msg'),
+	'msi'   => array('application/x-msi'),
+	'nws'   => array('message/rfc822'),
+	'oda'   => array('application/oda'),
+	'odb'   => array('application/vnd.oasis.opendocument.database'),
+	'odc'   => array('application/vnd.oasis.opendocument.chart'),
+	'odf'   => array('application/vnd.oasis.opendocument.forumla'),
+	'odg'   => array('application/vnd.oasis.opendocument.graphics'),
+	'odi'   => array('application/vnd.oasis.opendocument.image'),
+	'odm'   => array('application/vnd.oasis.opendocument.text-master'),
+	'odp'   => array('application/vnd.oasis.opendocument.presentation'),
+	'ods'   => array('application/vnd.oasis.opendocument.spreadsheet'),
+	'odt'   => array('application/vnd.oasis.opendocument.text'),
+	'oga'   => array('audio/ogg'),
+	'ogg'   => array('application/ogg'),
+	'ogv'   => array('video/ogg'),
+	'otg'   => array('application/vnd.oasis.opendocument.graphics-template'),
+	'oth'   => array('application/vnd.oasis.opendocument.web'),
+	'otp'   => array('application/vnd.oasis.opendocument.presentation-template'),
+	'ots'   => array('application/vnd.oasis.opendocument.spreadsheet-template'),
+	'ott'   => array('application/vnd.oasis.opendocument.template'),
+	'p'     => array('text/x-pascal'),
+	'pas'   => array('text/x-pascal'),
+	'patch' => array('text/x-diff'),
+	'pbm'   => array('image/x-portable-bitmap'),
+	'pdf'   => array('application/pdf', 'application/x-download'),
+	'php'   => array('application/x-httpd-php'),
+	'php3'  => array('application/x-httpd-php'),
+	'php4'  => array('application/x-httpd-php'),
+	'php5'  => array('application/x-httpd-php'),
+	'phps'  => array('application/x-httpd-php-source'),
+	'phtml' => array('application/x-httpd-php'),
+	'pl'    => array('text/x-perl'),
+	'pm'    => array('text/x-perl'),
+	'png'   => array('image/png', 'image/x-png'),
+	'po'    => array('text/x-gettext-translation'),
+	'pot'   => array('application/vnd.ms-powerpoint'),
+	'pps'   => array('application/vnd.ms-powerpoint'),
+	'ppt'   => array('application/powerpoint'),
+	'ps'    => array('application/postscript'),
+	'psd'   => array('application/x-photoshop', 'image/x-photoshop'),
+	'pub'   => array('application/x-mspublisher'),
+	'py'    => array('text/x-python'),
+	'qt'    => array('video/quicktime'),
+	'ra'    => array('audio/x-realaudio'),
+	'ram'   => array('audio/x-realaudio', 'audio/x-pn-realaudio'),
+	'rar'   => array('application/rar'),
+	'rgb'   => array('image/x-rgb'),
+	'rm'    => array('audio/x-pn-realaudio'),
+	'rpm'   => array('audio/x-pn-realaudio-plugin', 'application/x-redhat-package-manager'),
+	'rss'   => array('application/rss+xml'),
+	'rtf'   => array('text/rtf'),
+	'rtx'   => array('text/richtext'),
+	'rv'    => array('video/vnd.rn-realvideo'),
+	'sea'   => array('application/octet-stream'),
+	'sh'    => array('text/x-sh'),
+	'shtml' => array('text/html'),
+	'sit'   => array('application/x-stuffit'),
+	'smi'   => array('application/smil'),
+	'smil'  => array('application/smil'),
+	'so'    => array('application/octet-stream'),
+	'src'   => array('application/x-wais-source'),
+	'svg'   => array('image/svg+xml'),
+	'swf'   => array('application/x-shockwave-flash'),
+	't'     => array('application/x-troff'),
+	'tar'   => array('application/x-tar'),
+	'tcl'   => array('text/x-tcl'),
+	'tex'   => array('application/x-tex'),
+	'text'  => array('text/plain'),
+	'texti' => array('application/x-texinfo'),
+	'textinfo' => array('application/x-texinfo'),
+	'tgz'   => array('application/x-tar'),
+	'tif'   => array('image/tiff'),
+	'tiff'  => array('image/tiff'),
+	'torrent' => array('application/x-bittorrent'),
+	'tr'    => array('application/x-troff'),
+	'tsv'   => array('text/tab-separated-values'),
+	'txt'   => array('text/plain'),
+	'wav'   => array('audio/x-wav'),
+	'wax'   => array('audio/x-ms-wax'),
+	'wbxml' => array('application/wbxml'),
+	'wm'    => array('video/x-ms-wm'),
+	'wma'   => array('audio/x-ms-wma'),
+	'wmd'   => array('application/x-ms-wmd'),
+	'wmlc'  => array('application/wmlc'),
+	'wmv'   => array('video/x-ms-wmv', 'application/octet-stream'),
+	'wmx'   => array('video/x-ms-wmx'),
+	'wmz'   => array('application/x-ms-wmz'),
+	'word'  => array('application/msword', 'application/octet-stream'),
+	'wp5'   => array('application/wordperfect5.1'),
+	'wpd'   => array('application/vnd.wordperfect'),
+	'wvx'   => array('video/x-ms-wvx'),
+	'xbm'   => array('image/x-xbitmap'),
+	'xcf'   => array('image/xcf'),
+	'xhtml' => array('application/xhtml+xml'),
+	'xht'   => array('application/xhtml+xml'),
+	'xl'    => array('application/excel', 'application/vnd.ms-excel'),
+	'xla'   => array('application/excel', 'application/vnd.ms-excel'),
+	'xlc'   => array('application/excel', 'application/vnd.ms-excel'),
+	'xlm'   => array('application/excel', 'application/vnd.ms-excel'),
+	'xls'   => array('application/excel', 'application/vnd.ms-excel'),
+	'xlt'   => array('application/excel', 'application/vnd.ms-excel'),
+	'xml'   => array('text/xml'),
+	'xof'   => array('x-world/x-vrml'),
+	'xpm'   => array('image/x-xpixmap'),
+	'xsl'   => array('text/xml'),
+	'xvid'  => array('video/x-xvid'),
+	'xwd'   => array('image/x-xwindowdump'),
+	'z'     => array('application/x-compress'),
+	'zip'   => array('application/x-zip', 'application/zip', 'application/x-zip-compressed')
+);
diff --git a/system/config/pagination.php b/system/config/pagination.php
new file mode 100755
index 0000000..808fc31
--- /dev/null
+++ b/system/config/pagination.php
@@ -0,0 +1,25 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Pagination
+ *
+ * Pagination configuration is defined in groups which allows you to easily switch
+ * between different pagination settings for different website sections.
+ * Note: all groups inherit and overwrite the default group.
+ *
+ * Group Options:
+ *  directory      - Views folder in which your pagination style templates reside
+ *  style          - Pagination style template (matches view filename)
+ *  uri_segment    - URI segment (int or 'label') in which the current page number can be found
+ *  query_string   - Alternative to uri_segment: query string key that contains the page number
+ *  items_per_page - Number of items to display per page
+ *  auto_hide      - Automatically hides pagination for single pages
+ */
+$config['default'] = array
+(
+	'directory'      => 'pagination',
+	'style'          => 'classic',
+	'uri_segment'    => 3,
+	'query_string'   => '',
+	'items_per_page' => 20,
+	'auto_hide'      => FALSE,
+);
diff --git a/system/config/profiler.php b/system/config/profiler.php
new file mode 100755
index 0000000..98ab5a4
--- /dev/null
+++ b/system/config/profiler.php
@@ -0,0 +1,8 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Profiler
+ *
+ * Array of section names to display in the Profiler, TRUE to display all of them.
+ * Built in sections are benchmarks, database, session, post and cookies, custom sections can be used too.
+ */
+$config['show'] = TRUE;
diff --git a/system/config/routes.php b/system/config/routes.php
new file mode 100755
index 0000000..c677fde
--- /dev/null
+++ b/system/config/routes.php
@@ -0,0 +1,7 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Sets the default route to "welcome"
+ */
+$config['_default'] = 'welcome';
diff --git a/system/config/session.php b/system/config/session.php
new file mode 100755
index 0000000..e287bae
--- /dev/null
+++ b/system/config/session.php
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package Session
+ *
+ * Session driver name.
+ */
+$config['driver'] = 'cookie';
+
+/**
+ * Session storage parameter, used by drivers.
+ */
+$config['storage'] = '';
+
+/**
+ * Session name.
+ * It must contain only alphanumeric characters and underscores. At least one letter must be present.
+ */
+$config['name'] = 'kohanasession';
+
+/**
+ * Session parameters to validate: user_agent, ip_address, expiration.
+ */
+$config['validate'] = array('user_agent');
+
+/**
+ * Enable or disable session encryption.
+ * Note: this has no effect on the native session driver.
+ * Note: the cookie driver always encrypts session data. Set to TRUE for stronger encryption.
+ */
+$config['encryption'] = FALSE;
+
+/**
+ * Session lifetime. Number of seconds that each session will last.
+ * A value of 0 will keep the session active until the browser is closed (with a limit of 24h).
+ */
+$config['expiration'] = 7200;
+
+/**
+ * Number of page loads before the session id is regenerated.
+ * A value of 0 will disable automatic session id regeneration.
+ */
+$config['regenerate'] = 3;
+
+/**
+ * Percentage probability that the gc (garbage collection) routine is started.
+ */
+$config['gc_probability'] = 2;
\ No newline at end of file
diff --git a/system/config/sql_types.php b/system/config/sql_types.php
new file mode 100755
index 0000000..90dd85a
--- /dev/null
+++ b/system/config/sql_types.php
@@ -0,0 +1,49 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Database
+ *
+ * SQL data types. If there are missing values, please report them:
+ *
+ * @link  http://trac.kohanaphp.com/newticket
+ */
+$config = array
+(
+	'tinyint'			=> array('type' => 'int', 'max' => 127),
+	'smallint'			=> array('type' => 'int', 'max' => 32767),
+	'mediumint'			=> array('type' => 'int', 'max' => 8388607),
+	'int'				=> array('type' => 'int', 'max' => 2147483647),
+	'integer'			=> array('type' => 'int', 'max' => 2147483647),
+	'bigint'			=> array('type' => 'int', 'max' => 9223372036854775807),
+	'float'				=> array('type' => 'float'),
+	'float unsigned'	=> array('type' => 'float', 'min' => 0),
+	'boolean'			=> array('type' => 'boolean'),
+	'time'				=> array('type' => 'string', 'format' => '00:00:00'),
+	'date'				=> array('type' => 'string', 'format' => '0000-00-00'),
+	'year'				=> array('type' => 'string', 'format' => '0000'),
+	'datetime'			=> array('type' => 'string', 'format' => '0000-00-00 00:00:00'),
+	'char'				=> array('type' => 'string', 'exact' => TRUE),
+	'binary'			=> array('type' => 'string', 'binary' => TRUE, 'exact' => TRUE),
+	'varchar'			=> array('type' => 'string'),
+	'varbinary'			=> array('type' => 'string', 'binary' => TRUE),
+	'blob'				=> array('type' => 'string', 'binary' => TRUE),
+	'text'				=> array('type' => 'string'),
+	'geometry'			=> array('type' => 'string', 'binary' => TRUE)
+);
+
+// DOUBLE
+$config['double'] = $config['double unsigned'] = $config['decimal'] = $config['real'] = $config['numeric'] = $config['float'];
+
+// BIT
+$config['bit'] = $config['boolean'];
+
+// TIMESTAMP
+$config['timestamp'] = $config['datetime'];
+
+// ENUM
+$config['enum'] = $config['set'] = $config['varchar'];
+
+// TEXT
+$config['tinytext'] = $config['mediumtext'] = $config['longtext'] = $config['text'];
+
+// BLOB
+$config['tinyblob'] = $config['mediumblob'] = $config['longblob'] = $config['clob'] = $config['blob'];
diff --git a/system/config/upload.php b/system/config/upload.php
new file mode 100755
index 0000000..df26a2d
--- /dev/null
+++ b/system/config/upload.php
@@ -0,0 +1,17 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * This path is relative to your index file. Absolute paths are also supported.
+ */
+$config['directory'] = DOCROOT.'upload';
+
+/**
+ * Enable or disable directory creation.
+ */
+$config['create_directories'] = FALSE;
+
+/**
+ * Remove spaces from uploaded filenames.
+ */
+$config['remove_spaces'] = TRUE;
\ No newline at end of file
diff --git a/system/config/user_agents.php b/system/config/user_agents.php
new file mode 100755
index 0000000..3432ba1
--- /dev/null
+++ b/system/config/user_agents.php
@@ -0,0 +1,109 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * This file contains four arrays of user agent data.  It is used by the
+ * User Agent library to help identify browser, platform, robot, and
+ * mobile device data. The array keys are used to identify the device
+ * and the array values are used to set the actual name of the item.
+ */
+$config['platform'] = array
+(
+	'windows nt 6.0' => 'Windows Vista',
+	'windows nt 5.2' => 'Windows 2003',
+	'windows nt 5.0' => 'Windows 2000',
+	'windows nt 5.1' => 'Windows XP',
+	'windows nt 4.0' => 'Windows NT',
+	'winnt4.0'       => 'Windows NT',
+	'winnt 4.0'      => 'Windows NT',
+	'winnt'          => 'Windows NT',
+	'windows 98'     => 'Windows 98',
+	'win98'          => 'Windows 98',
+	'windows 95'     => 'Windows 95',
+	'win95'          => 'Windows 95',
+	'windows'        => 'Unknown Windows OS',
+	'os x'           => 'Mac OS X',
+	'intel mac'      => 'Intel Mac',
+	'ppc mac'        => 'PowerPC Mac',
+	'powerpc'        => 'PowerPC',
+	'ppc'            => 'PowerPC',
+	'cygwin'         => 'Cygwin',
+	'linux'          => 'Linux',
+	'debian'         => 'Debian',
+	'openvms'        => 'OpenVMS',
+	'sunos'          => 'Sun Solaris',
+	'amiga'          => 'Amiga',
+	'beos'           => 'BeOS',
+	'apachebench'    => 'ApacheBench',
+	'freebsd'        => 'FreeBSD',
+	'netbsd'         => 'NetBSD',
+	'bsdi'           => 'BSDi',
+	'openbsd'        => 'OpenBSD',
+	'os/2'           => 'OS/2',
+	'warp'           => 'OS/2',
+	'aix'            => 'AIX',
+	'irix'           => 'Irix',
+	'osf'            => 'DEC OSF',
+	'hp-ux'          => 'HP-UX',
+	'hurd'           => 'GNU/Hurd',
+	'unix'           => 'Unknown Unix OS',
+);
+
+// The order of this array should NOT be changed. Many browsers return
+// multiple browser types so we want to identify the sub-type first.
+$config['browser'] = array
+(
+	'Opera'             => 'Opera',
+	'MSIE'              => 'Internet Explorer',
+	'Internet Explorer' => 'Internet Explorer',
+	'Shiira'            => 'Shiira',
+	'Firefox'           => 'Firefox',
+	'Chimera'           => 'Chimera',
+	'Phoenix'           => 'Phoenix',
+	'Firebird'          => 'Firebird',
+	'Camino'            => 'Camino',
+	'Netscape'          => 'Netscape',
+	'OmniWeb'           => 'OmniWeb',
+	'Safari'            => 'Safari',
+	'Konqueror'         => 'Konqueror',
+	'Epiphany'          => 'Epiphany',
+	'Galeon'            => 'Galeon',
+	'Mozilla'           => 'Mozilla',
+	'icab'              => 'iCab',
+	'lynx'              => 'Lynx',
+	'links'             => 'Links',
+	'hotjava'           => 'HotJava',
+	'amaya'             => 'Amaya',
+	'IBrowse'           => 'IBrowse',
+);
+
+$config['mobile'] = array
+(
+	'mobileexplorer' => 'Mobile Explorer',
+	'openwave'       => 'Open Wave',
+	'opera mini'     => 'Opera Mini',
+	'operamini'      => 'Opera Mini',
+	'elaine'         => 'Palm',
+	'palmsource'     => 'Palm',
+	'digital paths'  => 'Palm',
+	'avantgo'        => 'Avantgo',
+	'xiino'          => 'Xiino',
+	'palmscape'      => 'Palmscape',
+	'nokia'          => 'Nokia',
+	'ericsson'       => 'Ericsson',
+	'blackBerry'     => 'BlackBerry',
+	'motorola'       => 'Motorola',
+);
+
+// There are hundreds of bots but these are the most common.
+$config['robot'] = array
+(
+	'googlebot'   => 'Googlebot',
+	'msnbot'      => 'MSNBot',
+	'slurp'       => 'Inktomi Slurp',
+	'yahoo'       => 'Yahoo',
+	'askjeeves'   => 'AskJeeves',
+	'fastcrawler' => 'FastCrawler',
+	'infoseek'    => 'InfoSeek Robot 1.0',
+	'lycos'       => 'Lycos',
+);
\ No newline at end of file
diff --git a/system/config/view.php b/system/config/view.php
new file mode 100755
index 0000000..6bed22e
--- /dev/null
+++ b/system/config/view.php
@@ -0,0 +1,17 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * @package  Core
+ *
+ * Allowed non-php view types. Most file extensions are supported.
+ */
+$config['allowed_filetypes'] = array
+(
+	'gif',
+	'jpg', 'jpeg',
+	'png',
+	'tif', 'tiff',
+	'swf',
+	'htm', 'html',
+	'css',
+	'js'
+);
diff --git a/system/controllers/captcha.php b/system/controllers/captcha.php
new file mode 100755
index 0000000..1c1a208
--- /dev/null
+++ b/system/controllers/captcha.php
@@ -0,0 +1,23 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Outputs the dynamic Captcha resource.
+ * Usage: Call the Captcha controller from a view, e.g.
+ *        <img src="<?php echo url::site('captcha') ?>" />
+ *
+ * $Id: captcha.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Controller extends Controller {
+
+	public function __call($method, $args)
+	{
+		// Output the Captcha challenge resource (no html)
+		// Pull the config group name from the URL
+		Captcha::factory($this->uri->segment(2))->render(FALSE);
+	}
+
+} // End Captcha_Controller
\ No newline at end of file
diff --git a/system/controllers/template.php b/system/controllers/template.php
new file mode 100755
index 0000000..fbfdb23
--- /dev/null
+++ b/system/controllers/template.php
@@ -0,0 +1,54 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Allows a template to be automatically loaded and displayed. Display can be
+ * dynamically turned off in the controller methods, and the template file
+ * can be overloaded.
+ *
+ * To use it, declare your controller to extend this class:
+ * `class Your_Controller extends Template_Controller`
+ *
+ * $Id: template.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Template_Controller extends Controller {
+
+	// Template view name
+	public $template = 'template';
+
+	// Default to do auto-rendering
+	public $auto_render = TRUE;
+
+	/**
+	 * Template loading and setup routine.
+	 */
+	public function __construct()
+	{
+		parent::__construct();
+
+		// Load the template
+		$this->template = new View($this->template);
+
+		if ($this->auto_render == TRUE)
+		{
+			// Render the template immediately after the controller method
+			Event::add('system.post_controller', array($this, '_render'));
+		}
+	}
+
+	/**
+	 * Render the loaded template.
+	 */
+	public function _render()
+	{
+		if ($this->auto_render == TRUE)
+		{
+			// Render the template when the class is destroyed
+			$this->template->render(TRUE);
+		}
+	}
+
+} // End Template_Controller
\ No newline at end of file
diff --git a/system/core/Benchmark.php b/system/core/Benchmark.php
new file mode 100755
index 0000000..1c22c8d
--- /dev/null
+++ b/system/core/Benchmark.php
@@ -0,0 +1,165 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Simple benchmarking.
+ *
+ * $Id: Benchmark.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+final class Benchmark {
+
+	// Benchmark timestamps
+	private static $marks;
+
+	/**
+	 * Set a benchmark start point.
+	 *
+	 * @param   string  benchmark name
+	 * @return  void
+	 */
+	public static function start($name)
+	{
+		if ( ! isset(self::$marks[$name]))
+		{
+			self::$marks[$name] = array
+			(
+				'start'        => microtime(TRUE),
+				'stop'         => FALSE,
+				'memory_start' => function_exists('memory_get_usage') ? memory_get_usage() : 0,
+				'memory_stop'  => FALSE
+			);
+		}
+	}
+
+	/**
+	 * Config checked version of start()
+	 *
+	 * @param   string  benchmark name
+	 * @return  void
+	 */
+	public static function f_start($name)
+	{
+		if (Kohana::config('benchmark.enable') === TRUE)
+		{
+			self::start($name);
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Set a benchmark stop point.
+	 *
+	 * @param   string  benchmark name
+	 * @return  void
+	 */
+	public static function stop($name)
+	{
+		if (isset(self::$marks[$name]) AND self::$marks[$name]['stop'] === FALSE)
+		{
+			self::$marks[$name]['stop'] = microtime(TRUE);
+			self::$marks[$name]['memory_stop'] = function_exists('memory_get_usage') ? memory_get_usage() : 0;
+		}
+	}
+
+	/**
+	 * Config checked version of stop()
+	 *
+	 * @param   string  benchmark name
+	 * @return  void
+	 */
+	public static function f_stop($name)
+	{
+		if (Kohana::config('benchmark.enable') === TRUE)
+		{
+			self::stop($name);
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Get the elapsed time between a start and stop.
+	 *
+	 * @param   string   benchmark name, TRUE for all
+	 * @param   integer  number of decimal places to count to
+	 * @return  array
+	 */
+	public static function get($name, $decimals = 4)
+	{
+		if ($name === TRUE)
+		{
+			$times = array();
+			$names = array_keys(self::$marks);
+
+			foreach ($names as $name)
+			{
+				// Get each mark recursively
+				$times[$name] = self::get($name, $decimals);
+			}
+
+			// Return the array
+			return $times;
+		}
+
+		if ( ! isset(self::$marks[$name]))
+			return FALSE;
+
+		if (self::$marks[$name]['stop'] === FALSE)
+		{
+			// Stop the benchmark to prevent mis-matched results
+			self::stop($name);
+		}
+
+		// Return a string version of the time between the start and stop points
+		// Properly reading a float requires using number_format or sprintf
+		return array
+		(
+			'time'   => number_format(self::$marks[$name]['stop'] - self::$marks[$name]['start'], $decimals),
+			'memory' => (self::$marks[$name]['memory_stop'] - self::$marks[$name]['memory_start'])
+		);
+	}
+
+	/**
+	 * Saves the benchmark results to a database
+	 * @todo Use the database libraries for this instead of platform-specific DB calls
+	 *
+	 * @return void
+	 */
+	public static function save_results(){
+
+		// Ignore all of these actions if we have benchmarking disabled
+		if (Kohana::config('benchmark.enable') === FALSE) return FALSE;
+
+		// Connect to the benchmark database
+		$db = Kohana::config('benchmark.db');
+		$link = mysql_connect($db['host'], $db['user'], $db['pass']) or die('Could not connect to benchmark database.');
+		mysql_select_db($db['database']) or die('Could not select benchmark database.');
+		$table = mysql_real_escape_string($db['table_prefix']).'benchmark';
+
+		$benchmark_results = Benchmark::get(TRUE);
+		foreach ($benchmark_results as $name => $data)
+		{
+			// Don't save the generic system benchmark results
+			if (strstr($name,'system_benchmark_') === FALSE)
+			{
+				$query = 'INSERT INTO '.$table.' (`name`, `time`, `memory`) VALUES (\''.mysql_real_escape_string($name)
+				. '\', \''.mysql_real_escape_string($data['time']).'\', \''.mysql_real_escape_string($data['memory']).'\');';
+				
+				// Execute the query
+				mysql_query($query,$link);
+			}
+		}
+		
+		// Close the connection to the Benchmar DB
+		mysql_close($link);
+	}
+
+} // End Benchmark
diff --git a/system/core/Bootstrap.php b/system/core/Bootstrap.php
new file mode 100755
index 0000000..0c26c0d
--- /dev/null
+++ b/system/core/Bootstrap.php
@@ -0,0 +1,61 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana process control file, loaded by the front controller.
+ * 
+ * $Id: Bootstrap.php 3918 2009-01-21 03:15:53Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+
+define('KOHANA_VERSION',  '2.3.1');
+define('KOHANA_CODENAME', 'accipiter');
+
+// Test of Kohana is running in Windows
+define('KOHANA_IS_WIN', DIRECTORY_SEPARATOR === '\\');
+
+// Kohana benchmarks are prefixed to prevent collisions
+define('SYSTEM_BENCHMARK', 'system_benchmark');
+
+// Load benchmarking support
+require SYSPATH.'core/Benchmark'.EXT;
+
+// Start total_execution
+Benchmark::start(SYSTEM_BENCHMARK.'_total_execution');
+
+// Start kohana_loading
+Benchmark::start(SYSTEM_BENCHMARK.'_kohana_loading');
+
+// Load core files
+require SYSPATH.'core/utf8'.EXT;
+require SYSPATH.'core/Event'.EXT;
+require SYSPATH.'core/Kohana'.EXT;
+
+// Prepare the environment
+Kohana::setup();
+
+// End kohana_loading
+Benchmark::stop(SYSTEM_BENCHMARK.'_kohana_loading');
+
+// Start system_initialization
+Benchmark::start(SYSTEM_BENCHMARK.'_system_initialization');
+
+// Prepare the system
+Event::run('system.ready');
+
+// Determine routing
+Event::run('system.routing');
+
+// End system_initialization
+Benchmark::stop(SYSTEM_BENCHMARK.'_system_initialization');
+
+// Make the magic happen!
+Event::run('system.execute');
+
+// Save benchmark results
+Benchmark::save_results();
+
+// Clean up and exit
+Event::run('system.shutdown');
diff --git a/system/core/Event.php b/system/core/Event.php
new file mode 100755
index 0000000..00b4cdc
--- /dev/null
+++ b/system/core/Event.php
@@ -0,0 +1,232 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Process queuing/execution class. Allows an unlimited number of callbacks
+ * to be added to 'events'. Events can be run multiple times, and can also
+ * process event-specific data. By default, Kohana has several system events.
+ *
+ * $Id: Event.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ * @link       http://docs.kohanaphp.com/general/events
+ */
+final class Event {
+
+	// Event callbacks
+	private static $events = array();
+
+	// Cache of events that have been run
+	private static $has_run = array();
+
+	// Data that can be processed during events
+	public static $data;
+
+	/**
+	 * Add a callback to an event queue.
+	 *
+	 * @param   string   event name
+	 * @param   array    http://php.net/callback
+	 * @return  boolean
+	 */
+	public static function add($name, $callback)
+	{
+		if ( ! isset(self::$events[$name]))
+		{
+			// Create an empty event if it is not yet defined
+			self::$events[$name] = array();
+		}
+		elseif (in_array($callback, self::$events[$name], TRUE))
+		{
+			// The event already exists
+			return FALSE;
+		}
+
+		// Add the event
+		self::$events[$name][] = $callback;
+
+		return TRUE;
+	}
+
+	/**
+	 * Add a callback to an event queue, before a given event.
+	 *
+	 * @param   string   event name
+	 * @param   array    existing event callback
+	 * @param   array    event callback
+	 * @return  boolean
+	 */
+	public static function add_before($name, $existing, $callback)
+	{
+		if (empty(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name])) === FALSE)
+		{
+			// Just add the event if there are no events
+			return self::add($name, $callback);
+		}
+		else
+		{
+			// Insert the event immediately before the existing event
+			return self::insert_event($name, $key, $callback);
+		}
+	}
+
+	/**
+	 * Add a callback to an event queue, after a given event.
+	 *
+	 * @param   string   event name
+	 * @param   array    existing event callback
+	 * @param   array    event callback
+	 * @return  boolean
+	 */
+	public static function add_after($name, $existing, $callback)
+	{
+		if (empty(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name])) === FALSE)
+		{
+			// Just add the event if there are no events
+			return self::add($name, $callback);
+		}
+		else
+		{
+			// Insert the event immediately after the existing event
+			return self::insert_event($name, $key + 1, $callback);
+		}
+	}
+
+	/**
+	 * Inserts a new event at a specfic key location.
+	 *
+	 * @param   string   event name
+	 * @param   integer  key to insert new event at
+	 * @param   array    event callback
+	 * @return  void
+	 */
+	private static function insert_event($name, $key, $callback)
+	{
+		if (in_array($callback, self::$events[$name], TRUE))
+			return FALSE;
+
+		// Add the new event at the given key location
+		self::$events[$name] = array_merge
+		(
+			// Events before the key
+			array_slice(self::$events[$name], 0, $key),
+			// New event callback
+			array($callback),
+			// Events after the key
+			array_slice(self::$events[$name], $key)
+		);
+
+		return TRUE;
+	}
+
+	/**
+	 * Replaces an event with another event.
+	 *
+	 * @param   string   event name
+	 * @param   array    event to replace
+	 * @param   array    new callback
+	 * @return  boolean
+	 */
+	public static function replace($name, $existing, $callback)
+	{
+		if (empty(self::$events[$name]) OR ($key = array_search($existing, self::$events[$name])) === FALSE)
+			return FALSE;
+
+		if ( ! in_array($callback, self::$events[$name], TRUE))
+		{
+			// Replace the exisiting event with the new event
+			self::$events[$name][$key] = $callback;
+		}
+		else
+		{
+			// Remove the existing event from the queue
+			unset(self::$events[$name][$key]);
+
+			// Reset the array so the keys are ordered properly
+			self::$events[$name] = array_values(self::$events[$name]);
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Get all callbacks for an event.
+	 *
+	 * @param   string  event name
+	 * @return  array
+	 */
+	public static function get($name)
+	{
+		return empty(self::$events[$name]) ? array() : self::$events[$name];
+	}
+
+	/**
+	 * Clear some or all callbacks from an event.
+	 *
+	 * @param   string  event name
+	 * @param   array   specific callback to remove, FALSE for all callbacks
+	 * @return  void
+	 */
+	public static function clear($name, $callback = FALSE)
+	{
+		if ($callback === FALSE)
+		{
+			self::$events[$name] = array();
+		}
+		elseif (isset(self::$events[$name]))
+		{
+			// Loop through each of the event callbacks and compare it to the
+			// callback requested for removal. The callback is removed if it
+			// matches.
+			foreach (self::$events[$name] as $i => $event_callback)
+			{
+				if ($callback === $event_callback)
+				{
+					unset(self::$events[$name][$i]);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Execute all of the callbacks attached to an event.
+	 *
+	 * @param   string   event name
+	 * @param   array    data can be processed as Event::$data by the callbacks
+	 * @return  void
+	 */
+	public static function run($name, & $data = NULL)
+	{
+		if ( ! empty(self::$events[$name]))
+		{
+			// So callbacks can access Event::$data
+			self::$data =& $data;
+			$callbacks  =  self::get($name);
+
+			foreach ($callbacks as $callback)
+			{
+				call_user_func($callback);
+			}
+
+			// Do this to prevent data from getting 'stuck'
+			$clear_data = '';
+			self::$data =& $clear_data;
+
+			// The event has been run!
+			self::$has_run[$name] = $name;
+		}
+	}
+
+	/**
+	 * Check if a given event has been run.
+	 *
+	 * @param   string   event name
+	 * @return  boolean
+	 */
+	public static function has_run($name)
+	{
+		return isset(self::$has_run[$name]);
+	}
+
+} // End Event
diff --git a/system/core/Kohana.php b/system/core/Kohana.php
new file mode 100755
index 0000000..516bb76
--- /dev/null
+++ b/system/core/Kohana.php
@@ -0,0 +1,1745 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides Kohana-specific helper functions. This is where the magic happens!
+ *
+ * $Id: Kohana.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+final class Kohana {
+
+	// The singleton instance of the controller
+	public static $instance;
+
+	// Output buffering level
+	private static $buffer_level;
+
+	// Will be set to TRUE when an exception is caught
+	public static $has_error = FALSE;
+
+	// The final output that will displayed by Kohana
+	public static $output = '';
+
+	// The current user agent
+	public static $user_agent;
+
+	// The current locale
+	public static $locale;
+
+	// Configuration
+	private static $configuration;
+
+	// Include paths
+	private static $include_paths;
+
+	// Logged messages
+	private static $log;
+
+	// Cache lifetime
+	private static $cache_lifetime;
+
+	// Log levels
+	private static $log_levels = array
+	(
+		'error' => 1,
+		'alert' => 2,
+		'info'  => 3,
+		'debug' => 4,
+	);
+
+	// Internal caches and write status
+	private static $internal_cache = array();
+	private static $write_cache;
+
+	/**
+	 * Sets up the PHP environment. Adds error/exception handling, output
+	 * buffering, and adds an auto-loading method for loading classes.
+	 *
+	 * This method is run immediately when this file is loaded, and is
+	 * benchmarked as environment_setup.
+	 *
+	 * For security, this function also destroys the $_REQUEST global variable.
+	 * Using the proper global (GET, POST, COOKIE, etc) is inherently more secure.
+	 * The recommended way to fetch a global variable is using the Input library.
+	 * @see http://www.php.net/globals
+	 *
+	 * @return  void
+	 */
+	public static function setup()
+	{
+		static $run;
+
+		// This function can only be run once
+		if ($run === TRUE)
+			return;
+
+		// Start the environment setup benchmark
+		Benchmark::start(SYSTEM_BENCHMARK.'_environment_setup');
+
+		// Define Kohana error constant
+		define('E_KOHANA', 42);
+
+		// Define 404 error constant
+		define('E_PAGE_NOT_FOUND', 43);
+
+		// Define database error constant
+		define('E_DATABASE_ERROR', 44);
+
+		if (self::$cache_lifetime = self::config('core.internal_cache'))
+		{
+			// Load cached configuration and language files
+			self::$internal_cache['configuration'] = self::cache('configuration', self::$cache_lifetime);
+			self::$internal_cache['language']      = self::cache('language', self::$cache_lifetime);
+
+			// Load cached file paths
+			self::$internal_cache['find_file_paths'] = self::cache('find_file_paths', self::$cache_lifetime);
+
+			// Enable cache saving
+			Event::add('system.shutdown', array(__CLASS__, 'internal_cache_save'));
+		}
+
+		// Disable notices and "strict" errors
+		$ER = error_reporting(~E_NOTICE & ~E_STRICT);
+
+		// Set the user agent
+		self::$user_agent = trim($_SERVER['HTTP_USER_AGENT']);
+
+		if (function_exists('date_default_timezone_set'))
+		{
+			$timezone = self::config('locale.timezone');
+
+			// Set default timezone, due to increased validation of date settings
+			// which cause massive amounts of E_NOTICEs to be generated in PHP 5.2+
+			date_default_timezone_set(empty($timezone) ? 'UTC' : $timezone);
+		}
+
+		// Restore error reporting
+		error_reporting($ER);
+
+		// Start output buffering
+		// Hack to support PHP 5.4 - we're now calling Kohana::output_buffer() manually in Kohana::close_buffers() - rjmackay 20120914
+		ob_start(/*array(__CLASS__, 'output_buffer')*/);
+
+		// Save buffering level
+		self::$buffer_level = ob_get_level();
+
+		// Set autoloader
+		spl_autoload_register(array('Kohana', 'auto_load'));
+
+		// Set error handler
+		set_error_handler(array('Kohana', 'exception_handler'));
+
+		// Set exception handler
+		set_exception_handler(array('Kohana', 'exception_handler'));
+
+		// Send default text/html UTF-8 header
+		header('Content-Type: text/html; charset=UTF-8');
+
+		// Load locales
+		$locales = self::config('locale.language');
+
+		// Make first locale UTF-8
+		$locales[0] .= '.UTF-8';
+
+		// Set locale information
+		self::$locale = setlocale(LC_ALL, $locales);
+
+		if (self::$configuration['core']['log_threshold'] > 0)
+		{
+			// Set the log directory
+			self::log_directory(self::$configuration['core']['log_directory']);
+
+			// Enable log writing at shutdown
+			register_shutdown_function(array(__CLASS__, 'log_save'));
+		}
+
+		// Enable Kohana routing
+		Event::add('system.routing', array('Router', 'find_uri'));
+		Event::add('system.routing', array('Router', 'setup'));
+
+		// Enable Kohana controller initialization
+		Event::add('system.execute', array('Kohana', 'instance'));
+
+		// Enable Kohana 404 pages
+		Event::add('system.404', array('Kohana', 'show_404'));
+
+		// Enable Kohana output handling
+		Event::add('system.shutdown', array('Kohana', 'shutdown'));
+
+		if (self::config('core.enable_hooks') === TRUE)
+		{
+			// Find all the hook files
+			$hooks = self::list_files('hooks', TRUE);
+
+			foreach ($hooks as $file)
+			{
+				// Load the hook
+				include $file;
+			}
+		}
+
+		// Setup is complete, prevent it from being run again
+		$run = TRUE;
+
+		// Stop the environment setup routine
+		Benchmark::stop(SYSTEM_BENCHMARK.'_environment_setup');
+	}
+
+	/**
+	 * Loads the controller and initializes it. Runs the pre_controller,
+	 * post_controller_constructor, and post_controller events. Triggers
+	 * a system.404 event when the route cannot be mapped to a controller.
+	 *
+	 * This method is benchmarked as controller_setup and controller_execution.
+	 *
+	 * @return  object  instance of controller
+	 */
+	public static function & instance()
+	{
+		if (self::$instance === NULL)
+		{
+			Benchmark::start(SYSTEM_BENCHMARK.'_controller_setup');
+
+			if (Router::$method[0] === '_')
+			{
+				// Do not allow access to hidden methods
+				Event::run('system.404');
+			}
+
+			// Include the Controller file
+			require Router::$controller_path;
+
+			try
+			{
+				// Start validation of the controller
+				$class = new ReflectionClass(ucfirst(Router::$controller).'_Controller');
+			}
+			catch (ReflectionException $e)
+			{
+				// Controller does not exist
+				Event::run('system.404');
+			}
+
+			if ($class->isAbstract() OR (IN_PRODUCTION AND $class->getConstant('ALLOW_PRODUCTION') == FALSE))
+			{
+				// Controller is not allowed to run in production
+				Event::run('system.404');
+			}
+
+			// Run system.pre_controller
+			Event::run('system.pre_controller');
+
+			// Begin benchmark for the controller
+			Benchmark::f_start(ucfirst(Router::$controller).'_Controller');
+
+			// Create a new controller instance
+			$controller = $class->newInstance();
+
+			// End benchmark for the controller
+			Benchmark::f_stop(ucfirst(Router::$controller).'_Controller');
+
+			// Controller constructor has been executed
+			Event::run('system.post_controller_constructor');
+
+			try
+			{
+				// Load the controller method
+				$method = $class->getMethod(Router::$method);
+
+				if ($method->isProtected() or $method->isPrivate())
+				{
+					// Do not attempt to invoke protected methods
+					throw new ReflectionException('protected controller method');
+				}
+
+				// Default arguments
+				$arguments = Router::$arguments;
+			}
+			catch (ReflectionException $e)
+			{
+				// Use __call instead
+				$method = $class->getMethod('__call');
+
+				// Use arguments in __call format
+				$arguments = array(Router::$method, Router::$arguments);
+			}
+
+			// Stop the controller setup benchmark
+			Benchmark::stop(SYSTEM_BENCHMARK.'_controller_setup');
+
+			// Start the controller execution benchmark
+			Benchmark::start(SYSTEM_BENCHMARK.'_controller_execution');
+
+			// Execute the controller method
+			$method->invokeArgs($controller, $arguments);
+
+			// Controller method has been executed
+			Event::run('system.post_controller');
+
+			// Stop the controller execution benchmark
+			Benchmark::stop(SYSTEM_BENCHMARK.'_controller_execution');
+		}
+
+		return self::$instance;
+	}
+
+	/**
+	 * Get all include paths. APPPATH is the first path, followed by module
+	 * paths in the order they are configured, follow by the SYSPATH.
+	 *
+	 * @param   boolean  re-process the include paths
+	 * @return  array
+	 */
+	public static function include_paths($process = FALSE)
+	{
+		if ($process === TRUE)
+		{
+			// Add APPPATH as the first path
+			self::$include_paths = array(APPPATH);
+
+			foreach (self::$configuration['core']['modules'] as $path)
+			{
+				if ($path = str_replace('\\', '/', realpath($path)))
+				{
+					// Add a valid path
+					self::$include_paths[] = $path.'/';
+				}
+			}
+
+			// Add SYSPATH as the last path
+			self::$include_paths[] = SYSPATH;
+		}
+
+		return self::$include_paths;
+	}
+
+	/**
+	 * Get a config item or group.
+	 *
+	 * @param   string   item name
+	 * @param   boolean  force a forward slash (/) at the end of the item
+	 * @param   boolean  is the item required?
+	 * @return  mixed
+	 */
+	public static function config($key, $slash = FALSE, $required = TRUE)
+	{
+		if (self::$configuration === NULL)
+		{
+			// Load core configuration
+			self::$configuration['core'] = self::config_load('core');
+
+			// Re-parse the include paths
+			self::include_paths(TRUE);
+		}
+
+		// Get the group name from the key
+		$group = explode('.', $key, 2);
+		$group = $group[0];
+
+		if ( ! isset(self::$configuration[$group]))
+		{
+			// Load the configuration group
+			self::$configuration[$group] = self::config_load($group, $required);
+		}
+
+		// Get the value of the key string
+		$value = self::key_string(self::$configuration, $key);
+
+		if ($slash === TRUE AND is_string($value) AND $value !== '')
+		{
+			// Force the value to end with "/"
+			$value = rtrim($value, '/').'/';
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Sets a configuration item, if allowed.
+	 *
+	 * @param   string   config key string
+	 * @param   string   config value
+	 * @return  boolean
+	 */
+	public static function config_set($key, $value)
+	{
+		// Do this to make sure that the config array is already loaded
+		self::config($key);
+
+		if (substr($key, 0, 7) === 'routes.')
+		{
+			// Routes cannot contain sub keys due to possible dots in regex
+			$keys = explode('.', $key, 2);
+		}
+		else
+		{
+			// Convert dot-noted key string to an array
+			$keys = explode('.', $key);
+		}
+
+		// Used for recursion
+		$conf =& self::$configuration;
+		$last = count($keys) - 1;
+
+		foreach ($keys as $i => $k)
+		{
+			if ($i === $last)
+			{
+				$conf[$k] = $value;
+			}
+			else
+			{
+				$conf =& $conf[$k];
+			}
+		}
+
+		if ($key === 'core.modules')
+		{
+			// Reprocess the include paths
+			self::include_paths(TRUE);
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Load a config file.
+	 *
+	 * @param   string   config filename, without extension
+	 * @param   boolean  is the file required?
+	 * @return  array
+	 */
+	public static function config_load($name, $required = TRUE)
+	{
+		if ($name === 'core')
+		{
+			// Load the application configuration file
+			require APPPATH.'config/config'.EXT;
+
+			if ( ! isset($config['site_domain']))
+			{
+				// Invalid config file
+				die('Your Kohana application configuration file is not valid.');
+			}
+
+			return $config;
+		}
+
+		if (isset(self::$internal_cache['configuration'][$name]))
+			return self::$internal_cache['configuration'][$name];
+
+		// Load matching configs
+		$configuration = array();
+
+		if ($files = self::find_file('config', $name, $required))
+		{
+			foreach ($files as $file)
+			{
+				require $file;
+
+				if (isset($config) AND is_array($config))
+				{
+					// Merge in configuration
+					$configuration = array_merge($configuration, $config);
+				}
+			}
+		}
+
+		if ( ! isset(self::$write_cache['configuration']))
+		{
+			// Cache has changed
+			self::$write_cache['configuration'] = TRUE;
+		}
+
+		return self::$internal_cache['configuration'][$name] = $configuration;
+	}
+
+	/**
+	 * Clears a config group from the cached configuration.
+	 *
+	 * @param   string  config group
+	 * @return  void
+	 */
+	public static function config_clear($group)
+	{
+		// Remove the group from config
+		unset(self::$configuration[$group], self::$internal_cache['configuration'][$group]);
+
+		if ( ! isset(self::$write_cache['configuration']))
+		{
+			// Cache has changed
+			self::$write_cache['configuration'] = TRUE;
+		}
+	}
+
+	/**
+	 * Add a new message to the log.
+	 *
+	 * @param   string  type of message
+	 * @param   string  message text
+	 * @return  void
+	 */
+	public static function log($type, $message)
+	{
+		if (self::$log_levels[$type] <= self::$configuration['core']['log_threshold'])
+		{
+			$message = array(date('Y-m-d H:i:s P'), $type, $message);
+
+			// Run the system.log event
+			Event::run('system.log', $message);
+
+			self::$log[] = $message;
+		}
+	}
+
+	/**
+	 * Save all currently logged messages.
+	 *
+	 * @return  void
+	 */
+	public static function log_save()
+	{
+		if (empty(self::$log) OR self::$configuration['core']['log_threshold'] < 1)
+			return;
+
+		// Filename of the log
+		$filename = self::log_directory().date('Y-m-d').'.log'.EXT;
+
+		if ( ! is_file($filename))
+		{
+			// Write the SYSPATH checking header
+			file_put_contents($filename,
+				'<?php defined(\'SYSPATH\') or die(\'No direct script access.\'); ?>'.PHP_EOL.PHP_EOL);
+
+			// Prevent external writes
+			chmod($filename, 0644);
+		}
+
+		// Messages to write
+		$messages = array();
+
+		do
+		{
+			// Load the next mess
+			list ($date, $type, $text) = array_shift(self::$log);
+
+			// Add a new message line
+			$messages[] = $date.' --- '.$type.': '.$text;
+		}
+		while ( ! empty(self::$log));
+
+		// Write messages to log file
+		file_put_contents($filename, implode(PHP_EOL, $messages).PHP_EOL, FILE_APPEND);
+	}
+
+	/**
+	 * Get or set the logging directory.
+	 *
+	 * @param   string  new log directory
+	 * @return  string
+	 */
+	public static function log_directory($dir = NULL)
+	{
+		static $directory;
+
+		if ( ! empty($dir))
+		{
+			// Get the directory path
+			$dir = realpath($dir);
+
+			if (is_dir($dir) AND is_writable($dir))
+			{
+				// Change the log directory
+				$directory = str_replace('\\', '/', $dir).'/';
+			}
+			else
+			{
+				// Log directory is invalid
+				throw new Kohana_Exception('core.log_dir_unwritable', $dir);
+			}
+		}
+
+		return $directory;
+	}
+
+	/**
+	 * Load data from a simple cache file. This should only be used internally,
+	 * and is NOT a replacement for the Cache library.
+	 *
+	 * @param   string   unique name of cache
+	 * @param   integer  expiration in seconds
+	 * @return  mixed
+	 */
+	public static function cache($name, $lifetime)
+	{
+		if ($lifetime > 0)
+		{
+			$path = APPPATH.'cache/kohana_'.$name;
+
+			if (is_file($path))
+			{
+				// Check the file modification time
+				if ((time() - filemtime($path)) < $lifetime)
+				{
+					// Cache is valid
+					return unserialize(file_get_contents($path));
+				}
+				else
+				{
+					// Cache is invalid, delete it
+					unlink($path);
+				}
+			}
+		}
+
+		// No cache found
+		return NULL;
+	}
+
+	/**
+	 * Save data to a simple cache file. This should only be used internally, and
+	 * is NOT a replacement for the Cache library.
+	 *
+	 * @param   string   cache name
+	 * @param   mixed    data to cache
+	 * @param   integer  expiration in seconds
+	 * @return  boolean
+	 */
+	public static function cache_save($name, $data, $lifetime)
+	{
+		if ($lifetime < 1)
+			return FALSE;
+
+		$path = APPPATH.'cache/kohana_'.$name;
+
+		if ($data === NULL)
+		{
+			// Delete cache
+			return (is_file($path) and unlink($path));
+		}
+		else
+		{
+			// Write data to cache file
+			return (bool) file_put_contents($path, serialize($data));
+		}
+	}
+
+	/**
+	 * Kohana output handler.
+	 *
+	 * @param   string  current output buffer
+	 * @return  string
+	 */
+	public static function output_buffer($output)
+	{
+		if ( ! Event::has_run('system.send_headers'))
+		{
+			// Run the send_headers event, specifically for cookies being set
+			Event::run('system.send_headers');
+		}
+
+		// Set final output
+		self::$output = $output;
+
+		// Set and return the final output
+		return $output;
+	}
+
+	/**
+	 * Closes all open output buffers, either by flushing or cleaning all
+	 * open buffers, including the Kohana output buffer.
+	 *
+	 * @param   boolean  disable to clear buffers, rather than flushing
+	 * @return  void
+	 */
+	public static function close_buffers($flush = TRUE)
+	{
+		if (ob_get_level() >= self::$buffer_level)
+		{
+			// Set the close function
+			$close = ($flush === TRUE) ? 'ob_end_flush' : 'ob_end_clean';
+
+			while (ob_get_level() > self::$buffer_level)
+			{
+				// Flush or clean the buffer
+				$close();
+			}
+
+			// This will flush the Kohana buffer, which sets self::$output
+			// Hack to support PHP 5.4 - we're now calling Kohana::output_buffer() manually - rjmackay 20120914
+			self::output_buffer(ob_get_contents());
+			ob_end_clean();
+			
+			// Reset the buffer level
+			self::$buffer_level = ob_get_level();
+		}
+	}
+
+	/**
+	 * Triggers the shutdown of Kohana by closing the output buffer, runs the system.display event.
+	 *
+	 * @return  void
+	 */
+	public static function shutdown()
+	{
+		// Close output buffers
+		self::close_buffers(TRUE);
+
+		// Run the output event
+		Event::run('system.display', self::$output);
+
+		// Render the final output
+		self::render(self::$output);
+	}
+
+	/**
+	 * Inserts global Kohana variables into the generated output and prints it.
+	 *
+	 * @param   string  final output that will displayed
+	 * @return  void
+	 */
+	public static function render($output)
+	{
+		// Fetch memory usage in MB
+		$memory = function_exists('memory_get_usage') ? (memory_get_usage() / 1024 / 1024) : 0;
+
+		// Fetch benchmark for page execution time
+		$benchmark = Benchmark::get(SYSTEM_BENCHMARK.'_total_execution');
+
+		if (self::config('core.render_stats') === TRUE)
+		{
+			// Replace the global template variables
+			$output = str_replace(
+				array
+				(
+					'{kohana_version}',
+					'{kohana_codename}',
+					'{execution_time}',
+					'{memory_usage}',
+					'{included_files}',
+				),
+				array
+				(
+					KOHANA_VERSION,
+					KOHANA_CODENAME,
+					$benchmark['time'],
+					number_format($memory, 2).'MB',
+					count(get_included_files()),
+				),
+				$output
+			);
+		}
+
+		if ($level = self::config('core.output_compression') AND ini_get('output_handler') !== 'ob_gzhandler' AND (int) ini_get('zlib.output_compression') === 0)
+		{
+			if ($level < 1 OR $level > 9)
+			{
+				// Normalize the level to be an integer between 1 and 9. This
+				// step must be done to prevent gzencode from triggering an error
+				$level = max(1, min($level, 9));
+			}
+
+			if (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE)
+			{
+				$compress = 'gzip';
+			}
+			elseif (stripos(@$_SERVER['HTTP_ACCEPT_ENCODING'], 'deflate') !== FALSE)
+			{
+				$compress = 'deflate';
+			}
+		}
+
+		if (isset($compress) AND $level > 0)
+		{
+			switch ($compress)
+			{
+				case 'gzip':
+					// Compress output using gzip
+					$output = gzencode($output, $level);
+				break;
+				case 'deflate':
+					// Compress output using zlib (HTTP deflate)
+					$output = gzdeflate($output, $level);
+				break;
+			}
+
+			// This header must be sent with compressed content to prevent
+			// browser caches from breaking
+			header('Vary: Accept-Encoding');
+
+			// Send the content encoding header
+			header('Content-Encoding: '.$compress);
+
+			// Sending Content-Length in CGI can result in unexpected behavior
+			if (stripos(PHP_SAPI, 'cgi') === FALSE)
+			{
+				header('Content-Length: '.strlen($output));
+			}
+		}
+
+		echo $output;
+	}
+
+	/**
+	 * Displays a 404 page.
+	 *
+	 * @throws  Kohana_404_Exception
+	 * @param   string  URI of page
+	 * @param   string  custom template
+	 * @return  void
+	 */
+	public static function show_404($page = FALSE, $template = FALSE)
+	{
+		throw new Kohana_404_Exception($page, $template);
+	}
+
+	/**
+	 * Dual-purpose PHP error and exception handler. Uses the kohana_error_page
+	 * view to display the message.
+	 *
+	 * @param   integer|object  exception object or error code
+	 * @param   string          error message
+	 * @param   string          filename
+	 * @param   integer         line number
+	 * @return  void
+	 */
+	public static function exception_handler($exception, $message = NULL, $file = NULL, $line = NULL)
+	{
+		// PHP errors have 5 args, always
+		$PHP_ERROR = (func_num_args() === 5);
+
+		// Test to see if errors should be displayed
+		if ($PHP_ERROR AND (error_reporting() & $exception) === 0)
+			return;
+
+		// This is useful for hooks to determine if a page has an error
+		self::$has_error = TRUE;
+
+		// Error handling will use exactly 5 args, every time
+		if ($PHP_ERROR)
+		{
+			$code     = $exception;
+			$type     = 'PHP Error';
+			$template = 'kohana_error_page';
+		}
+		else
+		{
+			$code     = $exception->getCode();
+			$type     = get_class($exception);
+			$message  = $exception->getMessage();
+			$file     = $exception->getFile();
+			$line     = $exception->getLine();
+			$template = ($exception instanceof Kohana_Exception) ? $exception->getTemplate() : 'kohana_error_page';
+		}
+
+		if (is_numeric($code))
+		{
+			$codes = self::lang('errors');
+
+			if ( ! empty($codes[$code]))
+			{
+				list($level, $error, $description) = $codes[$code];
+			}
+			else
+			{
+				$level = 1;
+				$error = $PHP_ERROR ? 'Unknown Error' : get_class($exception);
+				$description = '';
+			}
+		}
+		else
+		{
+			// Custom error message, this will never be logged
+			$level = 5;
+			$error = $code;
+			$description = '';
+		}
+
+		// Remove the DOCROOT from the path, as a security precaution
+		$file = str_replace('\\', '/', realpath($file));
+		$file = preg_replace('|^'.preg_quote(DOCROOT).'|', '', $file);
+
+		if ($level <= self::$configuration['core']['log_threshold'])
+		{
+			// Log the error
+			self::log('error', self::lang('core.uncaught_exception', array($type, $message, $file, $line)));
+		}
+
+		if ($PHP_ERROR)
+		{
+			$description = self::lang('errors.'.E_RECOVERABLE_ERROR);
+			$description = is_array($description) ? $description[2] : '';
+
+			if ( ! headers_sent())
+			{
+				// Send the 500 header
+				header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error');
+			}
+		}
+		else
+		{
+			if (method_exists($exception, 'sendHeaders') AND ! headers_sent())
+			{
+				// Send the headers if they have not already been sent
+				$exception->sendHeaders();
+			}
+		}
+
+		while (ob_get_level() > self::$buffer_level)
+		{
+			// Close open buffers
+			ob_end_clean();
+		}
+
+		// Test if display_errors is on
+		if (self::$configuration['core']['display_errors'] === TRUE)
+		{
+			if ( ! IN_PRODUCTION AND $line != FALSE)
+			{
+				// Remove the first entry of debug_backtrace(), it is the exception_handler call
+				$trace = $PHP_ERROR ? array_slice(debug_backtrace(), 1) : $exception->getTrace();
+
+				// Beautify backtrace
+				$trace = self::backtrace($trace);
+			}
+
+			// Load the error
+			require self::find_file('views', empty($template) ? 'kohana_error_page' : $template);
+		}
+		else
+		{
+			// Get the i18n messages
+			$error   = self::lang('core.generic_error');
+			$message = self::lang('core.errors_disabled', url::site(), url::site(Router::$current_uri));
+
+			// Load the errors_disabled view
+			require self::find_file('views', 'kohana_error_disabled');
+		}
+
+		if ( ! Event::has_run('system.shutdown'))
+		{
+			// Run the shutdown even to ensure a clean exit
+			Event::run('system.shutdown');
+		}
+
+		// Turn off error reporting
+		error_reporting(0);
+		exit;
+	}
+
+	/**
+	 * Provides class auto-loading.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   string  name of class
+	 * @return  bool
+	 */
+	public static function auto_load($class)
+	{
+		if (class_exists($class, FALSE))
+			return TRUE;
+
+		if (($suffix = strrpos($class, '_')) > 0)
+		{
+			// Find the class suffix
+			$suffix = substr($class, $suffix + 1);
+		}
+		else
+		{
+			// No suffix
+			$suffix = FALSE;
+		}
+
+		if ($suffix === 'Core')
+		{
+			$type = 'libraries';
+			$file = substr($class, 0, -5);
+		}
+		elseif ($suffix === 'Controller')
+		{
+			$type = 'controllers';
+			// Lowercase filename
+			$file = strtolower(substr($class, 0, -11));
+		}
+		elseif ($suffix === 'Model')
+		{
+			$type = 'models';
+			// Lowercase filename
+			$file = strtolower(substr($class, 0, -6));
+		}
+		elseif ($suffix === 'Driver')
+		{
+			$type = 'libraries/drivers';
+			$file = str_replace('_', '/', substr($class, 0, -7));
+		}
+		else
+		{
+			// This could be either a library or a helper, but libraries must
+			// always be capitalized, so we check if the first character is
+			// uppercase. If it is, we are loading a library, not a helper.
+			$type = ($class[0] < 'a') ? 'libraries' : 'helpers';
+			$file = $class;
+		}
+
+		if ($filename = self::find_file($type, $file))
+		{
+			// Load the class
+			require $filename;
+		}
+		else
+		{
+			// The class could not be found
+			return FALSE;
+		}
+
+		if ($filename = self::find_file($type, self::$configuration['core']['extension_prefix'].$class))
+		{
+			// Load the class extension
+			require $filename;
+		}
+		elseif ($suffix !== 'Core' AND class_exists($class.'_Core', FALSE))
+		{
+			// Class extension to be evaluated
+			$extension = 'class '.$class.' extends '.$class.'_Core { }';
+
+			// Start class analysis
+			$core = new ReflectionClass($class.'_Core');
+
+			if ($core->isAbstract())
+			{
+				// Make the extension abstract
+				$extension = 'abstract '.$extension;
+			}
+
+			// Transparent class extensions are handled using eval. This is
+			// a disgusting hack, but it gets the job done.
+			eval($extension);
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Find a resource file in a given directory. Files will be located according
+	 * to the order of the include paths. Configuration and i18n files will be
+	 * returned in reverse order.
+	 *
+	 * @throws  Kohana_Exception  if file is required and not found
+	 * @param   string   directory to search in
+	 * @param   string   filename to look for (including extension only if 4th parameter is TRUE)
+	 * @param   boolean  file required
+	 * @param   string   file extension
+	 * @return  array    if the type is config, i18n or l10n
+	 * @return  string   if the file is found
+	 * @return  FALSE    if the file is not found
+	 */
+	public static function find_file($directory, $filename, $required = FALSE, $ext = FALSE)
+	{
+		// NOTE: This test MUST be not be a strict comparison (===), or empty
+		// extensions will be allowed!
+		if ($ext == '')
+		{
+			// Use the default extension
+			$ext = EXT;
+		}
+		else
+		{
+			// Add a period before the extension
+			$ext = '.'.$ext;
+		}
+
+		// Search path
+		$search = $directory.'/'.$filename.$ext;
+
+		if (isset(self::$internal_cache['find_file_paths'][$search]))
+			return self::$internal_cache['find_file_paths'][$search];
+
+		// Load include paths
+		$paths = self::$include_paths;
+
+		// Nothing found, yet
+		$found = NULL;
+
+		if ($directory === 'config' OR $directory === 'i18n')
+		{
+			// Search in reverse, for merging
+			$paths = array_reverse($paths);
+
+			foreach ($paths as $path)
+			{
+				if (is_file($path.$search))
+				{
+					// A matching file has been found
+					$found[] = $path.$search;
+				}
+			}
+		}
+		else
+		{
+			foreach ($paths as $path)
+			{
+				if (is_file($path.$search))
+				{
+					// A matching file has been found
+					$found = $path.$search;
+
+					// Stop searching
+					break;
+				}
+			}
+		}
+
+		if ($found === NULL)
+		{
+			if ($required === TRUE)
+			{
+				// Directory i18n key
+				$directory = 'core.'.inflector::singular($directory);
+
+				// If the file is required, throw an exception
+				throw new Kohana_Exception('core.resource_not_found', self::lang($directory), $filename);
+			}
+			else
+			{
+				// Nothing was found, return FALSE
+				$found = FALSE;
+			}
+		}
+
+		if ( ! isset(self::$write_cache['find_file_paths']))
+		{
+			// Write cache at shutdown
+			self::$write_cache['find_file_paths'] = TRUE;
+		}
+
+		return self::$internal_cache['find_file_paths'][$search] = $found;
+	}
+
+	/**
+	 * Lists all files and directories in a resource path.
+	 *
+	 * @param   string   directory to search
+	 * @param   boolean  list all files to the maximum depth?
+	 * @param   string   full path to search (used for recursion, *never* set this manually)
+	 * @return  array    filenames and directories
+	 */
+	public static function list_files($directory, $recursive = FALSE, $path = FALSE)
+	{
+		$files = array();
+
+		if ($path === FALSE)
+		{
+			$paths = array_reverse(self::include_paths());
+
+			foreach ($paths as $path)
+			{
+				// Recursively get and merge all files
+				$files = array_merge($files, self::list_files($directory, $recursive, $path.$directory));
+			}
+		}
+		else
+		{
+			$path = rtrim($path, '/').'/';
+
+			if (is_readable($path))
+			{
+				$items = (array) glob($path.'*');
+
+				foreach ($items as $index => $item)
+				{
+					$files[] = $item = str_replace('\\', '/', $item);
+
+					// Handle recursion
+					if (is_dir($item) AND $recursive == TRUE)
+					{
+						// Filename should only be the basename
+						$item = pathinfo($item, PATHINFO_BASENAME);
+
+						// Append sub-directory search
+						$files = array_merge($files, self::list_files($directory, TRUE, $path.$item));
+					}
+				}
+			}
+		}
+
+		return $files;
+	}
+
+	/**
+	 * Fetch an i18n language item.
+	 *
+	 * @param   string  language key to fetch
+	 * @param   array   additional information to insert into the line
+	 * @param   string  locale name to override settings
+	 * @return  string  i18n language string, or the requested key if the i18n item is not found
+	 */
+	public static function lang($key, $args = array(), $force_locale = NULL)
+	{
+		// Extract the main group from the key
+		$group = explode('.', $key, 2);
+		$group = $group[0];
+
+		// Get locale name
+		$locale = self::config('locale.language.0');
+
+		// If we are overriding the language settings then do it here.
+		if ($force_locale != NULL)
+		{
+			$locale = $force_locale;
+		}
+
+		if ( ! isset(self::$internal_cache['language'][$locale][$group]))
+		{
+			// Messages for this group
+			$messages = array();
+
+			if ($files = self::find_file('i18n', $locale.'/'.$group))
+			{
+				foreach ($files as $file)
+				{
+					include $file;
+
+					// Merge in configuration
+					if ( ! empty($lang) AND is_array($lang))
+					{
+						foreach ($lang as $k => $v)
+						{
+							$messages[$k] = $v;
+						}
+					}
+				}
+			}
+
+			if ( ! isset(self::$write_cache['language']))
+			{
+				// Write language cache
+				self::$write_cache['language'] = TRUE;
+			}
+
+			self::$internal_cache['language'][$locale][$group] = $messages;
+		}
+
+		// Get the line from cache
+		$line = self::key_string(self::$internal_cache['language'][$locale], $key);
+
+		if ($line === NULL)
+		{
+			// Only save error to log for en_US to the log since it's the default fallback
+			if ($locale == 'en_US')
+			{
+				self::log('info', 'Missing i18n entry '.$key.' for language '.$locale);
+			}
+
+			if ($force_locale != NULL)
+			{
+				// Return the key string as fallback
+				return $key;
+			}else{
+				// Try for English
+				return Kohana::lang($key, $args, 'en_US');
+			}
+		}
+
+		if (is_string($line) AND func_num_args() > 1)
+		{
+			$args = array_slice(func_get_args(), 1);
+
+			// Add the arguments into the line
+			$line = vsprintf($line, is_array($args[0]) ? $args[0] : $args);
+		}
+
+		return $line;
+	}
+
+	/**
+	 * Returns the value of a key, defined by a 'dot-noted' string, from an array.
+	 *
+	 * @param   array   array to search
+	 * @param   string  dot-noted string: foo.bar.baz
+	 * @return  string  if the key is found
+	 * @return  void    if the key is not found
+	 */
+	public static function key_string($array, $keys)
+	{
+		if (empty($array))
+			return NULL;
+
+		// Prepare for loop
+		$keys = explode('.', $keys);
+
+		do
+		{
+			// Get the next key
+			$key = array_shift($keys);
+
+			if (isset($array[$key]))
+			{
+				if (is_array($array[$key]) AND ! empty($keys))
+				{
+					// Dig down to prepare the next loop
+					$array = $array[$key];
+				}
+				else
+				{
+					// Requested key was found
+					return $array[$key];
+				}
+			}
+			else
+			{
+				// Requested key is not set
+				break;
+			}
+		}
+		while ( ! empty($keys));
+
+		return NULL;
+	}
+
+	/**
+	 * Sets values in an array by using a 'dot-noted' string.
+	 *
+	 * @param   array   array to set keys in (reference)
+	 * @param   string  dot-noted string: foo.bar.baz
+	 * @return  mixed   fill value for the key
+	 * @return  void
+	 */
+	public static function key_string_set( & $array, $keys, $fill = NULL)
+	{
+		if (is_object($array) AND ($array instanceof ArrayObject))
+		{
+			// Copy the array
+			$array_copy = $array->getArrayCopy();
+
+			// Is an object
+			$array_object = TRUE;
+		}
+		else
+		{
+			if ( ! is_array($array))
+			{
+				// Must always be an array
+				$array = (array) $array;
+			}
+
+			// Copy is a reference to the array
+			$array_copy =& $array;
+		}
+
+		if (empty($keys))
+			return $array;
+
+		// Create keys
+		$keys = explode('.', $keys);
+
+		// Create reference to the array
+		$row =& $array_copy;
+
+		for ($i = 0, $end = count($keys) - 1; $i <= $end; $i++)
+		{
+			// Get the current key
+			$key = $keys[$i];
+
+			if ( ! isset($row[$key]))
+			{
+				if (isset($keys[$i + 1]))
+				{
+					// Make the value an array
+					$row[$key] = array();
+				}
+				else
+				{
+					// Add the fill key
+					$row[$key] = $fill;
+				}
+			}
+			elseif (isset($keys[$i + 1]))
+			{
+				// Make the value an array
+				$row[$key] = (array) $row[$key];
+			}
+
+			// Go down a level, creating a new row reference
+			$row =& $row[$key];
+		}
+
+		if (isset($array_object))
+		{
+			// Swap the array back in
+			$array->exchangeArray($array_copy);
+		}
+	}
+
+	/**
+	 * Retrieves current user agent information:
+	 * keys:  browser, version, platform, mobile, robot, referrer, languages, charsets
+	 * tests: is_browser, is_mobile, is_robot, accept_lang, accept_charset
+	 *
+	 * @param   string   key or test name
+	 * @param   string   used with "accept" tests: user_agent(accept_lang, en)
+	 * @return  array    languages and charsets
+	 * @return  string   all other keys
+	 * @return  boolean  all tests
+	 */
+	public static function user_agent($key = 'agent', $compare = NULL)
+	{
+		static $info;
+
+		// Return the raw string
+		if ($key === 'agent')
+			return self::$user_agent;
+
+		if ($info === NULL)
+		{
+			// Parse the user agent and extract basic information
+			$agents = self::config('user_agents');
+
+			foreach ($agents as $type => $data)
+			{
+				foreach ($data as $agent => $name)
+				{
+					if (stripos(self::$user_agent, $agent) !== FALSE)
+					{
+						if ($type === 'browser' AND preg_match('|'.preg_quote($agent).'[^0-9.]*+([0-9.][0-9.a-z]*)|i', self::$user_agent, $match))
+						{
+							// Set the browser version
+							$info['version'] = $match[1];
+						}
+
+						// Set the agent name
+						$info[$type] = $name;
+						break;
+					}
+				}
+			}
+		}
+
+		if (empty($info[$key]))
+		{
+			switch ($key)
+			{
+				case 'is_robot':
+				case 'is_browser':
+				case 'is_mobile':
+					// A boolean result
+					$return = ! empty($info[substr($key, 3)]);
+				break;
+				case 'languages':
+					$return = array();
+					if ( ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE']))
+					{
+						if (preg_match_all('/[-a-z]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_LANGUAGE'])), $matches))
+						{
+							// Found a result
+							$return = $matches[0];
+						}
+					}
+				break;
+				case 'charsets':
+					$return = array();
+					if ( ! empty($_SERVER['HTTP_ACCEPT_CHARSET']))
+					{
+						if (preg_match_all('/[-a-z0-9]{2,}/', strtolower(trim($_SERVER['HTTP_ACCEPT_CHARSET'])), $matches))
+						{
+							// Found a result
+							$return = $matches[0];
+						}
+					}
+				break;
+				case 'referrer':
+					if ( ! empty($_SERVER['HTTP_REFERER']))
+					{
+						// Found a result
+						$return = trim($_SERVER['HTTP_REFERER']);
+					}
+				break;
+			}
+
+			// Cache the return value
+			isset($return) and $info[$key] = $return;
+		}
+
+		if ( ! empty($compare))
+		{
+			// The comparison must always be lowercase
+			$compare = strtolower($compare);
+
+			switch ($key)
+			{
+				case 'accept_lang':
+					// Check if the lange is accepted
+					return in_array($compare, self::user_agent('languages'));
+				break;
+				case 'accept_charset':
+					// Check if the charset is accepted
+					return in_array($compare, self::user_agent('charsets'));
+				break;
+				default:
+					// Invalid comparison
+					return FALSE;
+				break;
+			}
+		}
+
+		// Return the key, if set
+		return isset($info[$key]) ? $info[$key] : NULL;
+	}
+
+	/**
+	 * Quick debugging of any variable. Any number of parameters can be set.
+	 *
+	 * @return  string
+	 */
+	public static function debug()
+	{
+		if (func_num_args() === 0)
+			return;
+
+		// Get params
+		$params = func_get_args();
+		$output = array();
+
+		foreach ($params as $var)
+		{
+			$output[] = '<pre>('.gettype($var).') '.html::specialchars(print_r($var, TRUE)).'</pre>';
+		}
+
+		return implode("\n", $output);
+	}
+
+	/**
+	 * Displays nice backtrace information.
+	 * @see http://php.net/debug_backtrace
+	 *
+	 * @param   array   backtrace generated by an exception or debug_backtrace
+	 * @return  string
+	 */
+	public static function backtrace($trace)
+	{
+		if ( ! is_array($trace))
+			return;
+
+		// Final output
+		$output = array();
+
+		foreach ($trace as $entry)
+		{
+			$temp = '<li>';
+
+			if (isset($entry['file']))
+			{
+				// Hack this since kohana lang seems to fail here every time
+				$temp .= sprintf('<tt>%s <strong>[%s]:</strong></tt>', preg_replace('!^'.preg_quote(DOCROOT).'!', '', $entry['file']), $entry['line']);
+				//$temp .= self::lang('core.error_file_line', preg_replace('!^'.preg_quote(DOCROOT).'!', '', $entry['file']), $entry['line']);
+			}
+
+			$temp .= '<pre>';
+
+			if (isset($entry['class']))
+			{
+				// Add class and call type
+				$temp .= $entry['class'].$entry['type'];
+			}
+
+			// Add function
+			$temp .= $entry['function'].'( ';
+
+			// Add function args
+			if (isset($entry['args']) AND is_array($entry['args']))
+			{
+				// Separator starts as nothing
+				$sep = '';
+
+				while ($arg = array_shift($entry['args']))
+				{
+					if (is_string($arg) AND is_file($arg))
+					{
+						// Remove docroot from filename
+						$arg = preg_replace('!^'.preg_quote(DOCROOT).'!', '', $arg);
+					}
+
+					$temp .= $sep.html::specialchars(print_r($arg, TRUE));
+
+					// Change separator to a comma
+					$sep = ', ';
+				}
+			}
+
+			$temp .= ' )</pre></li>';
+
+			$output[] = $temp;
+		}
+
+		return '<ul class="backtrace">'.implode("\n", $output).'</ul>';
+	}
+
+	/**
+	 * Saves the internal caches: configuration, include paths, etc.
+	 *
+	 * @return  boolean
+	 */
+	public static function internal_cache_save()
+	{
+		if ( ! is_array(self::$write_cache))
+			return FALSE;
+
+		// Get internal cache names
+		$caches = array_keys(self::$write_cache);
+
+		// Nothing written
+		$written = FALSE;
+
+		foreach ($caches as $cache)
+		{
+			if (isset(self::$internal_cache[$cache]))
+			{
+				// Write the cache file
+				self::cache_save($cache, self::$internal_cache[$cache], self::$configuration['core']['internal_cache']);
+
+				// A cache has been written
+				$written = TRUE;
+			}
+		}
+
+		return $written;
+	}
+
+} // End Kohana
+
+/**
+ * Creates a generic i18n exception.
+ */
+class Kohana_Exception extends Exception {
+
+	// Template file
+	protected $template = 'kohana_error_page';
+
+	// Header
+	protected $header = FALSE;
+
+	// Error code
+	protected $code = E_KOHANA;
+
+	/**
+	 * Set exception message.
+	 *
+	 * @param  string  i18n language key for the message
+	 * @param  array   addition line parameters
+	 */
+	public function __construct($error)
+	{
+		$args = array_slice(func_get_args(), 1);
+
+		// Fetch the error message
+		$message = Kohana::lang($error, $args);
+
+		if ($message === $error OR empty($message))
+		{
+			// Unable to locate the message for the error
+			$message = 'Unknown Exception: '.$error;
+		}
+
+		// Sets $this->message the proper way
+		parent::__construct($message);
+	}
+
+	/**
+	 * Magic method for converting an object to a string.
+	 *
+	 * @return  string  i18n message
+	 */
+	public function __toString()
+	{
+		return (string) $this->message;
+	}
+
+	/**
+	 * Fetch the template name.
+	 *
+	 * @return  string
+	 */
+	public function getTemplate()
+	{
+		return $this->template;
+	}
+
+	/**
+	 * Sends an Internal Server Error header.
+	 *
+	 * @return  void
+	 */
+	public function sendHeaders()
+	{
+		// Send the 500 header
+		header($_SERVER['SERVER_PROTOCOL'] . ' 500 Internal Server Error');
+	}
+
+} // End Kohana Exception
+
+/**
+ * Creates a custom exception.
+ */
+class Kohana_User_Exception extends Kohana_Exception {
+
+	/**
+	 * Set exception title and message.
+	 *
+	 * @param   string  exception title string
+	 * @param   string  exception message string
+	 * @param   string  custom error template
+	 */
+	public function __construct($title, $message, $template = FALSE)
+	{
+		Exception::__construct($message);
+
+		$this->code = $title;
+
+		if ($template !== FALSE)
+		{
+			$this->template = $template;
+		}
+	}
+
+} // End Kohana PHP Exception
+
+/**
+ * Creates a Page Not Found exception.
+ */
+class Kohana_404_Exception extends Kohana_Exception {
+
+	protected $code = E_PAGE_NOT_FOUND;
+
+	/**
+	 * Set internal properties.
+	 *
+	 * @param  string  URL of page
+	 * @param  string  custom error template
+	 */
+	public function __construct($page = FALSE, $template = FALSE)
+	{
+		if ($page === FALSE)
+		{
+			// Construct the page URI using Router properties
+			$page = Router::$current_uri.Router::$url_suffix.Router::$query_string;
+		}
+
+		Exception::__construct(Kohana::lang('core.page_not_found', $page));
+
+		$this->template = $template;
+	}
+
+	/**
+	 * Sends "File Not Found" headers, to emulate server behavior.
+	 *
+	 * @return void
+	 */
+	public function sendHeaders()
+	{
+		// Send the 404 header
+		header($_SERVER['SERVER_PROTOCOL'] . ' 404 File Not Found');
+	}
+
+} // End Kohana 404 Exception
diff --git a/system/core/utf8.php b/system/core/utf8.php
new file mode 100755
index 0000000..1b4b86e
--- /dev/null
+++ b/system/core/utf8.php
@@ -0,0 +1,743 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * A port of phputf8 to a unified file/class. Checks PHP status to ensure that
+ * UTF-8 support is available and normalize global variables to UTF-8. It also
+ * provides multi-byte aware replacement string functions.
+ *
+ * This file is licensed differently from the rest of Kohana. As a port of
+ * phputf8, which is LGPL software, this file is released under the LGPL.
+ *
+ * PCRE needs to be compiled with UTF-8 support (--enable-utf8).
+ * Support for Unicode properties is highly recommended (--enable-unicode-properties).
+ * @see http://php.net/manual/reference.pcre.pattern.modifiers.php
+ *
+ * UTF-8 conversion will be much more reliable if the iconv extension is loaded.
+ * @see http://php.net/iconv
+ *
+ * The mbstring extension is highly recommended, but must not be overloading
+ * string functions.
+ * @see http://php.net/mbstring
+ *
+ * $Id: utf8.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+
+if ( ! preg_match('/^.$/u', 'ñ'))
+{
+	trigger_error
+	(
+		'<a href="http://php.net/pcre">PCRE</a> has not been compiled with UTF-8 support. '.
+		'See <a href="http://php.net/manual/reference.pcre.pattern.modifiers.php">PCRE Pattern Modifiers</a> '.
+		'for more information. This application cannot be run without UTF-8 support.',
+		E_USER_ERROR
+	);
+}
+
+if ( ! extension_loaded('iconv'))
+{
+	trigger_error
+	(
+		'The <a href="http://php.net/iconv">iconv</a> extension is not loaded. '.
+		'Without iconv, strings cannot be properly translated to UTF-8 from user input. '.
+		'This application cannot be run without UTF-8 support.',
+		E_USER_ERROR
+	);
+}
+
+if (extension_loaded('mbstring') AND (ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING))
+{
+	trigger_error
+	(
+		'The <a href="http://php.net/mbstring">mbstring</a> extension is overloading PHP\'s native string functions. '.
+		'Disable this by setting mbstring.func_overload to 0, 1, 4 or 5 in php.ini or a .htaccess file.'.
+		'This application cannot be run without UTF-8 support.',
+		E_USER_ERROR
+	);
+}
+
+// Check PCRE support for Unicode properties such as \p and \X.
+$ER = error_reporting(0);
+define('PCRE_UNICODE_PROPERTIES', (bool) preg_match('/^\pL$/u', 'ñ'));
+error_reporting($ER);
+
+// SERVER_UTF8 ? use mb_* functions : use non-native functions
+if (extension_loaded('mbstring'))
+{
+	mb_internal_encoding('UTF-8');
+	define('SERVER_UTF8', TRUE);
+}
+else
+{
+	define('SERVER_UTF8', FALSE);
+}
+
+// Convert all global variables to UTF-8.
+$_GET    = utf8::clean($_GET);
+$_POST   = utf8::clean($_POST);
+$_COOKIE = utf8::clean($_COOKIE);
+$_SERVER = utf8::clean($_SERVER);
+
+if (PHP_SAPI == 'cli')
+{
+	// Convert command line arguments
+	$_SERVER['argv'] = utf8::clean($_SERVER['argv']);
+}
+
+final class utf8 {
+
+	// Called methods
+	static $called = array();
+
+	/**
+	 * Recursively cleans arrays, objects, and strings. Removes ASCII control
+	 * codes and converts to UTF-8 while silently discarding incompatible
+	 * UTF-8 characters.
+	 *
+	 * @param   string  string to clean
+	 * @return  string
+	 */
+	public static function clean($str)
+	{
+		if (is_array($str) OR is_object($str))
+		{
+			foreach ($str as $key => $val)
+			{
+				// Recursion!
+				$str[self::clean($key)] = self::clean($val);
+			}
+		}
+		elseif (is_string($str) AND $str !== '')
+		{
+			// Remove control characters
+			$str = self::strip_ascii_ctrl($str);
+
+			if ( ! self::is_ascii($str))
+			{
+				// Disable notices
+				$ER = error_reporting(~E_NOTICE);
+
+				// iconv is expensive, so it is only used when needed
+				$str = iconv('UTF-8', 'UTF-8//IGNORE', $str);
+
+				// Turn notices back on
+				error_reporting($ER);
+			}
+		}
+
+		return $str;
+	}
+
+	/**
+	 * Tests whether a string contains only 7bit ASCII bytes. This is used to
+	 * determine when to use native functions or UTF-8 functions.
+	 *
+	 * @param   string  string to check
+	 * @return  bool
+	 */
+	public static function is_ascii($str)
+	{
+		return ! preg_match('/[^\x00-\x7F]/S', $str);
+	}
+
+	/**
+	 * Strips out device control codes in the ASCII range.
+	 *
+	 * @param   string  string to clean
+	 * @return  string
+	 */
+	public static function strip_ascii_ctrl($str)
+	{
+		return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+/S', '', $str);
+	}
+
+	/**
+	 * Strips out all non-7bit ASCII bytes.
+	 *
+	 * @param   string  string to clean
+	 * @return  string
+	 */
+	public static function strip_non_ascii($str)
+	{
+		return preg_replace('/[^\x00-\x7F]+/S', '', $str);
+	}
+
+	/**
+	 * Replaces special/accented UTF-8 characters by ASCII-7 'equivalents'.
+	 *
+	 * @author  Andreas Gohr <andi at splitbrain.org>
+	 *
+	 * @param   string   string to transliterate
+	 * @param   integer  -1 lowercase only, +1 uppercase only, 0 both cases
+	 * @return  string
+	 */
+	public static function transliterate_to_ascii($str, $case = 0)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _transliterate_to_ascii($str, $case);
+	}
+
+	/**
+	 * Returns the length of the given string.
+	 * @see http://php.net/strlen
+	 *
+	 * @param   string   string being measured for length
+	 * @return  integer
+	 */
+	public static function strlen($str)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strlen($str);
+	}
+
+	/**
+	 * Finds position of first occurrence of a UTF-8 string.
+	 * @see http://php.net/strlen
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   haystack
+	 * @param   string   needle
+	 * @param   integer  offset from which character in haystack to start searching
+	 * @return  integer  position of needle
+	 * @return  boolean  FALSE if the needle is not found
+	 */
+	public static function strpos($str, $search, $offset = 0)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strpos($str, $search, $offset);
+	}
+
+	/**
+	 * Finds position of last occurrence of a char in a UTF-8 string.
+	 * @see http://php.net/strrpos
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   haystack
+	 * @param   string   needle
+	 * @param   integer  offset from which character in haystack to start searching
+	 * @return  integer  position of needle
+	 * @return  boolean  FALSE if the needle is not found
+	 */
+	public static function strrpos($str, $search, $offset = 0)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strrpos($str, $search, $offset);
+	}
+
+	/**
+	 * Returns part of a UTF-8 string.
+	 * @see http://php.net/substr
+	 *
+	 * @author  Chris Smith <chris at jalakai.co.uk>
+	 *
+	 * @param   string   input string
+	 * @param   integer  offset
+	 * @param   integer  length limit
+	 * @return  string
+	 */
+	public static function substr($str, $offset, $length = NULL)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _substr($str, $offset, $length);
+	}
+
+	/**
+	 * Replaces text within a portion of a UTF-8 string.
+	 * @see http://php.net/substr_replace
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   input string
+	 * @param   string   replacement string
+	 * @param   integer  offset
+	 * @return  string
+	 */
+	public static function substr_replace($str, $replacement, $offset, $length = NULL)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _substr_replace($str, $replacement, $offset, $length);
+	}
+
+	/**
+	 * Makes a UTF-8 string lowercase.
+	 * @see http://php.net/strtolower
+	 *
+	 * @author  Andreas Gohr <andi at splitbrain.org>
+	 *
+	 * @param   string   mixed case string
+	 * @return  string
+	 */
+	public static function strtolower($str)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strtolower($str);
+	}
+
+	/**
+	 * Makes a UTF-8 string uppercase.
+	 * @see http://php.net/strtoupper
+	 *
+	 * @author  Andreas Gohr <andi at splitbrain.org>
+	 *
+	 * @param   string   mixed case string
+	 * @return  string
+	 */
+	public static function strtoupper($str)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strtoupper($str);
+	}
+
+	/**
+	 * Makes a UTF-8 string's first character uppercase.
+	 * @see http://php.net/ucfirst
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   mixed case string
+	 * @return  string
+	 */
+	public static function ucfirst($str)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _ucfirst($str);
+	}
+
+	/**
+	 * Makes the first character of every word in a UTF-8 string uppercase.
+	 * @see http://php.net/ucwords
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   mixed case string
+	 * @return  string
+	 */
+	public static function ucwords($str)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _ucwords($str);
+	}
+
+	/**
+	 * Case-insensitive UTF-8 string comparison.
+	 * @see http://php.net/strcasecmp
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   string to compare
+	 * @param   string   string to compare
+	 * @return  integer  less than 0 if str1 is less than str2
+	 * @return  integer  greater than 0 if str1 is greater than str2
+	 * @return  integer  0 if they are equal
+	 */
+	public static function strcasecmp($str1, $str2)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strcasecmp($str1, $str2);
+	}
+
+	/**
+	 * Returns a string or an array with all occurrences of search in subject (ignoring case).
+	 * replaced with the given replace value.
+	 * @see     http://php.net/str_ireplace
+	 *
+	 * @note    It's not fast and gets slower if $search and/or $replace are arrays.
+	 * @author  Harry Fuecks <hfuecks at gmail.com
+	 *
+	 * @param   string|array  text to replace
+	 * @param   string|array  replacement text
+	 * @param   string|array  subject text
+	 * @param   integer       number of matched and replaced needles will be returned via this parameter which is passed by reference
+	 * @return  string        if the input was a string
+	 * @return  array         if the input was an array
+	 */
+	public static function str_ireplace($search, $replace, $str, & $count = NULL)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _str_ireplace($search, $replace, $str, $count);
+	}
+
+	/**
+	 * Case-insenstive UTF-8 version of strstr. Returns all of input string
+	 * from the first occurrence of needle to the end.
+	 * @see http://php.net/stristr
+	 *
+	 * @author Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   input string
+	 * @param   string   needle
+	 * @return  string   matched substring if found
+	 * @return  boolean  FALSE if the substring was not found
+	 */
+	public static function stristr($str, $search)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _stristr($str, $search);
+	}
+
+	/**
+	 * Finds the length of the initial segment matching mask.
+	 * @see http://php.net/strspn
+	 *
+	 * @author Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   input string
+	 * @param   string   mask for search
+	 * @param   integer  start position of the string to examine
+	 * @param   integer  length of the string to examine
+	 * @return  integer  length of the initial segment that contains characters in the mask
+	 */
+	public static function strspn($str, $mask, $offset = NULL, $length = NULL)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strspn($str, $mask, $offset, $length);
+	}
+
+	/**
+	 * Finds the length of the initial segment not matching mask.
+	 * @see http://php.net/strcspn
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   input string
+	 * @param   string   mask for search
+	 * @param   integer  start position of the string to examine
+	 * @param   integer  length of the string to examine
+	 * @return  integer  length of the initial segment that contains characters not in the mask
+	 */
+	public static function strcspn($str, $mask, $offset = NULL, $length = NULL)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strcspn($str, $mask, $offset, $length);
+	}
+
+	/**
+	 * Pads a UTF-8 string to a certain length with another string.
+	 * @see http://php.net/str_pad
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   input string
+	 * @param   integer  desired string length after padding
+	 * @param   string   string to use as padding
+	 * @param   string   padding type: STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH
+	 * @return  string
+	 */
+	public static function str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _str_pad($str, $final_str_length, $pad_str, $pad_type);
+	}
+
+	/**
+	 * Converts a UTF-8 string to an array.
+	 * @see http://php.net/str_split
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   input string
+	 * @param   integer  maximum length of each chunk
+	 * @return  array
+	 */
+	public static function str_split($str, $split_length = 1)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _str_split($str, $split_length);
+	}
+
+	/**
+	 * Reverses a UTF-8 string.
+	 * @see http://php.net/strrev
+	 *
+	 * @author  Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   string to be reversed
+	 * @return  string
+	 */
+	public static function strrev($str)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _strrev($str);
+	}
+
+	/**
+	 * Strips whitespace (or other UTF-8 characters) from the beginning and
+	 * end of a string.
+	 * @see http://php.net/trim
+	 *
+	 * @author  Andreas Gohr <andi at splitbrain.org>
+	 *
+	 * @param   string   input string
+	 * @param   string   string of characters to remove
+	 * @return  string
+	 */
+	public static function trim($str, $charlist = NULL)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _trim($str, $charlist);
+	}
+
+	/**
+	 * Strips whitespace (or other UTF-8 characters) from the beginning of a string.
+	 * @see http://php.net/ltrim
+	 *
+	 * @author  Andreas Gohr <andi at splitbrain.org>
+	 *
+	 * @param   string   input string
+	 * @param   string   string of characters to remove
+	 * @return  string
+	 */
+	public static function ltrim($str, $charlist = NULL)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _ltrim($str, $charlist);
+	}
+
+	/**
+	 * Strips whitespace (or other UTF-8 characters) from the end of a string.
+	 * @see http://php.net/rtrim
+	 *
+	 * @author  Andreas Gohr <andi at splitbrain.org>
+	 *
+	 * @param   string   input string
+	 * @param   string   string of characters to remove
+	 * @return  string
+	 */
+	public static function rtrim($str, $charlist = NULL)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _rtrim($str, $charlist);
+	}
+
+	/**
+	 * Returns the unicode ordinal for a character.
+	 * @see http://php.net/ord
+	 *
+	 * @author Harry Fuecks <hfuecks at gmail.com>
+	 *
+	 * @param   string   UTF-8 encoded character
+	 * @return  integer
+	 */
+	public static function ord($chr)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _ord($chr);
+	}
+
+	/**
+	 * Takes an UTF-8 string and returns an array of ints representing the Unicode characters.
+	 * Astral planes are supported i.e. the ints in the output can be > 0xFFFF.
+	 * Occurrances of the BOM are ignored. Surrogates are not allowed.
+	 *
+	 * The Original Code is Mozilla Communicator client code.
+	 * The Initial Developer of the Original Code is Netscape Communications Corporation.
+	 * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+	 * Ported to PHP by Henri Sivonen <hsivonen at iki.fi>, see http://hsivonen.iki.fi/php-utf8/.
+	 * Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks at gmail.com>.
+	 *
+	 * @param   string   UTF-8 encoded string
+	 * @return  array    unicode code points
+	 * @return  boolean  FALSE if the string is invalid
+	 */
+	public static function to_unicode($str)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _to_unicode($str);
+	}
+
+	/**
+	 * Takes an array of ints representing the Unicode characters and returns a UTF-8 string.
+	 * Astral planes are supported i.e. the ints in the input can be > 0xFFFF.
+	 * Occurrances of the BOM are ignored. Surrogates are not allowed.
+	 *
+	 * The Original Code is Mozilla Communicator client code.
+	 * The Initial Developer of the Original Code is Netscape Communications Corporation.
+	 * Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
+	 * Ported to PHP by Henri Sivonen <hsivonen at iki.fi>, see http://hsivonen.iki.fi/php-utf8/.
+	 * Slight modifications to fit with phputf8 library by Harry Fuecks <hfuecks at gmail.com>.
+	 *
+	 * @param   array    unicode code points representing a string
+	 * @return  string   utf8 string of characters
+	 * @return  boolean  FALSE if a code point cannot be found
+	 */
+	public static function from_unicode($arr)
+	{
+		if ( ! isset(self::$called[__FUNCTION__]))
+		{
+			require SYSPATH.'core/utf8/'.__FUNCTION__.EXT;
+
+			// Function has been called
+			self::$called[__FUNCTION__] = TRUE;
+		}
+
+		return _from_unicode($arr);
+	}
+
+} // End utf8
\ No newline at end of file
diff --git a/system/core/utf8/from_unicode.php b/system/core/utf8/from_unicode.php
new file mode 100755
index 0000000..66c6742
--- /dev/null
+++ b/system/core/utf8/from_unicode.php
@@ -0,0 +1,68 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::from_unicode
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _from_unicode($arr)
+{
+	ob_start();
+
+	$keys = array_keys($arr);
+
+	foreach ($keys as $k)
+	{
+		// ASCII range (including control chars)
+		if (($arr[$k] >= 0) AND ($arr[$k] <= 0x007f))
+		{
+			echo chr($arr[$k]);
+		}
+		// 2 byte sequence
+		elseif ($arr[$k] <= 0x07ff)
+		{
+			echo chr(0xc0 | ($arr[$k] >> 6));
+			echo chr(0x80 | ($arr[$k] & 0x003f));
+		}
+		// Byte order mark (skip)
+		elseif ($arr[$k] == 0xFEFF)
+		{
+			// nop -- zap the BOM
+		}
+		// Test for illegal surrogates
+		elseif ($arr[$k] >= 0xD800 AND $arr[$k] <= 0xDFFF)
+		{
+			// Found a surrogate
+			trigger_error('utf8::from_unicode: Illegal surrogate at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+			return FALSE;
+		}
+		// 3 byte sequence
+		elseif ($arr[$k] <= 0xffff)
+		{
+			echo chr(0xe0 | ($arr[$k] >> 12));
+			echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
+			echo chr(0x80 | ($arr[$k] & 0x003f));
+		}
+		// 4 byte sequence
+		elseif ($arr[$k] <= 0x10ffff)
+		{
+			echo chr(0xf0 | ($arr[$k] >> 18));
+			echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
+			echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
+			echo chr(0x80 | ($arr[$k] & 0x3f));
+		}
+		// Out of range
+		else
+		{
+			trigger_error('utf8::from_unicode: Codepoint out of Unicode range at index: '.$k.', value: '.$arr[$k], E_USER_WARNING);
+			return FALSE;
+		}
+	}
+
+	$result = ob_get_contents();
+	ob_end_clean();
+	return $result;
+}
diff --git a/system/core/utf8/ltrim.php b/system/core/utf8/ltrim.php
new file mode 100755
index 0000000..556fe07
--- /dev/null
+++ b/system/core/utf8/ltrim.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::ltrim
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _ltrim($str, $charlist = NULL)
+{
+	if ($charlist === NULL)
+		return ltrim($str);
+
+	if (utf8::is_ascii($charlist))
+		return ltrim($str, $charlist);
+
+	$charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+	return preg_replace('/^['.$charlist.']+/u', '', $str);
+}
\ No newline at end of file
diff --git a/system/core/utf8/ord.php b/system/core/utf8/ord.php
new file mode 100755
index 0000000..29723b6
--- /dev/null
+++ b/system/core/utf8/ord.php
@@ -0,0 +1,88 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::ord
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _ord($chr)
+{
+	$ord0 = ord($chr);
+
+	if ($ord0 >= 0 AND $ord0 <= 127)
+	{
+		return $ord0;
+	}
+
+	if ( ! isset($chr[1]))
+	{
+		trigger_error('Short sequence - at least 2 bytes expected, only 1 seen', E_USER_WARNING);
+		return FALSE;
+	}
+
+	$ord1 = ord($chr[1]);
+
+	if ($ord0 >= 192 AND $ord0 <= 223)
+	{
+		return ($ord0 - 192) * 64 + ($ord1 - 128);
+	}
+
+	if ( ! isset($chr[2]))
+	{
+		trigger_error('Short sequence - at least 3 bytes expected, only 2 seen', E_USER_WARNING);
+		return FALSE;
+	}
+
+	$ord2 = ord($chr[2]);
+
+	if ($ord0 >= 224 AND $ord0 <= 239)
+	{
+		return ($ord0 - 224) * 4096 + ($ord1 - 128) * 64 + ($ord2 - 128);
+	}
+
+	if ( ! isset($chr[3]))
+	{
+		trigger_error('Short sequence - at least 4 bytes expected, only 3 seen', E_USER_WARNING);
+		return FALSE;
+	}
+
+	$ord3 = ord($chr[3]);
+
+	if ($ord0 >= 240 AND $ord0 <= 247)
+	{
+		return ($ord0 - 240) * 262144 + ($ord1 - 128) * 4096 + ($ord2-128) * 64 + ($ord3 - 128);
+	}
+
+	if ( ! isset($chr[4]))
+	{
+		trigger_error('Short sequence - at least 5 bytes expected, only 4 seen', E_USER_WARNING);
+		return FALSE;
+	}
+
+	$ord4 = ord($chr[4]);
+
+	if ($ord0 >= 248 AND $ord0 <= 251)
+	{
+		return ($ord0 - 248) * 16777216 + ($ord1-128) * 262144 + ($ord2 - 128) * 4096 + ($ord3 - 128) * 64 + ($ord4 - 128);
+	}
+
+	if ( ! isset($chr[5]))
+	{
+		trigger_error('Short sequence - at least 6 bytes expected, only 5 seen', E_USER_WARNING);
+		return FALSE;
+	}
+
+	if ($ord0 >= 252 AND $ord0 <= 253)
+	{
+		return ($ord0 - 252) * 1073741824 + ($ord1 - 128) * 16777216 + ($ord2 - 128) * 262144 + ($ord3 - 128) * 4096 + ($ord4 - 128) * 64 + (ord($c[5]) - 128);
+	}
+
+	if ($ord0 >= 254 AND $ord0 <= 255)
+	{
+		trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0, E_USER_WARNING);
+		return FALSE;
+	}
+}
\ No newline at end of file
diff --git a/system/core/utf8/rtrim.php b/system/core/utf8/rtrim.php
new file mode 100755
index 0000000..efa0e19
--- /dev/null
+++ b/system/core/utf8/rtrim.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::rtrim
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _rtrim($str, $charlist = NULL)
+{
+	if ($charlist === NULL)
+		return rtrim($str);
+
+	if (utf8::is_ascii($charlist))
+		return rtrim($str, $charlist);
+
+	$charlist = preg_replace('#[-\[\]:\\\\^/]#', '\\\\$0', $charlist);
+
+	return preg_replace('/['.$charlist.']++$/uD', '', $str);
+}
\ No newline at end of file
diff --git a/system/core/utf8/str_ireplace.php b/system/core/utf8/str_ireplace.php
new file mode 100755
index 0000000..97ad71a
--- /dev/null
+++ b/system/core/utf8/str_ireplace.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::str_ireplace
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _str_ireplace($search, $replace, $str, & $count = NULL)
+{
+	if (utf8::is_ascii($search) AND utf8::is_ascii($replace) AND utf8::is_ascii($str))
+		return str_ireplace($search, $replace, $str, $count);
+
+	if (is_array($str))
+	{
+		foreach ($str as $key => $val)
+		{
+			$str[$key] = utf8::str_ireplace($search, $replace, $val, $count);
+		}
+		return $str;
+	}
+
+	if (is_array($search))
+	{
+		$keys = array_keys($search);
+
+		foreach ($keys as $k)
+		{
+			if (is_array($replace))
+			{
+				if (array_key_exists($k, $replace))
+				{
+					$str = utf8::str_ireplace($search[$k], $replace[$k], $str, $count);
+				}
+				else
+				{
+					$str = utf8::str_ireplace($search[$k], '', $str, $count);
+				}
+			}
+			else
+			{
+				$str = utf8::str_ireplace($search[$k], $replace, $str, $count);
+			}
+		}
+		return $str;
+	}
+
+	$search = utf8::strtolower($search);
+	$str_lower = utf8::strtolower($str);
+
+	$total_matched_strlen = 0;
+	$i = 0;
+
+	while (preg_match('/(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches))
+	{
+		$matched_strlen = strlen($matches[0]);
+		$str_lower = substr($str_lower, $matched_strlen);
+
+		$offset = $total_matched_strlen + strlen($matches[1]) + ($i * (strlen($replace) - 1));
+		$str = substr_replace($str, $replace, $offset, strlen($search));
+
+		$total_matched_strlen += $matched_strlen;
+		$i++;
+	}
+
+	$count += $i;
+	return $str;
+}
diff --git a/system/core/utf8/str_pad.php b/system/core/utf8/str_pad.php
new file mode 100755
index 0000000..63e27f0
--- /dev/null
+++ b/system/core/utf8/str_pad.php
@@ -0,0 +1,54 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::str_pad
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _str_pad($str, $final_str_length, $pad_str = ' ', $pad_type = STR_PAD_RIGHT)
+{
+	if (utf8::is_ascii($str) AND utf8::is_ascii($pad_str))
+	{
+		return str_pad($str, $final_str_length, $pad_str, $pad_type);
+	}
+
+	$str_length = utf8::strlen($str);
+
+	if ($final_str_length <= 0 OR $final_str_length <= $str_length)
+	{
+		return $str;
+	}
+
+	$pad_str_length = utf8::strlen($pad_str);
+	$pad_length = $final_str_length - $str_length;
+
+	if ($pad_type == STR_PAD_RIGHT)
+	{
+		$repeat = ceil($pad_length / $pad_str_length);
+		return utf8::substr($str.str_repeat($pad_str, $repeat), 0, $final_str_length);
+	}
+
+	if ($pad_type == STR_PAD_LEFT)
+	{
+		$repeat = ceil($pad_length / $pad_str_length);
+		return utf8::substr(str_repeat($pad_str, $repeat), 0, floor($pad_length)).$str;
+	}
+
+	if ($pad_type == STR_PAD_BOTH)
+	{
+		$pad_length /= 2;
+		$pad_length_left = floor($pad_length);
+		$pad_length_right = ceil($pad_length);
+		$repeat_left = ceil($pad_length_left / $pad_str_length);
+		$repeat_right = ceil($pad_length_right / $pad_str_length);
+
+		$pad_left = utf8::substr(str_repeat($pad_str, $repeat_left), 0, $pad_length_left);
+		$pad_right = utf8::substr(str_repeat($pad_str, $repeat_right), 0, $pad_length_left);
+		return $pad_left.$str.$pad_right;
+	}
+
+	trigger_error('utf8::str_pad: Unknown padding type (' . $type . ')', E_USER_ERROR);
+}
\ No newline at end of file
diff --git a/system/core/utf8/str_split.php b/system/core/utf8/str_split.php
new file mode 100755
index 0000000..bc382cb
--- /dev/null
+++ b/system/core/utf8/str_split.php
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::str_split
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _str_split($str, $split_length = 1)
+{
+	$split_length = (int) $split_length;
+
+	if (utf8::is_ascii($str))
+	{
+		return str_split($str, $split_length);
+	}
+
+	if ($split_length < 1)
+	{
+		return FALSE;
+	}
+
+	if (utf8::strlen($str) <= $split_length)
+	{
+		return array($str);
+	}
+
+	preg_match_all('/.{'.$split_length.'}|[^\x00]{1,'.$split_length.'}$/us', $str, $matches);
+
+	return $matches[0];
+}
\ No newline at end of file
diff --git a/system/core/utf8/strcasecmp.php b/system/core/utf8/strcasecmp.php
new file mode 100755
index 0000000..d06d3eb
--- /dev/null
+++ b/system/core/utf8/strcasecmp.php
@@ -0,0 +1,19 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strcasecmp
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strcasecmp($str1, $str2)
+{
+	if (utf8::is_ascii($str1) AND utf8::is_ascii($str2))
+		return strcasecmp($str1, $str2);
+
+	$str1 = utf8::strtolower($str1);
+	$str2 = utf8::strtolower($str2);
+	return strcmp($str1, $str2);
+}
\ No newline at end of file
diff --git a/system/core/utf8/strcspn.php b/system/core/utf8/strcspn.php
new file mode 100755
index 0000000..abd93d5
--- /dev/null
+++ b/system/core/utf8/strcspn.php
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strcspn
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strcspn($str, $mask, $offset = NULL, $length = NULL)
+{
+	if ($str == '' OR $mask == '')
+		return 0;
+
+	if (utf8::is_ascii($str) AND utf8::is_ascii($mask))
+		return ($offset === NULL) ? strcspn($str, $mask) : (($length === NULL) ? strcspn($str, $mask, $offset) : strcspn($str, $mask, $offset, $length));
+
+	if ($start !== NULL OR $length !== NULL)
+	{
+		$str = utf8::substr($str, $offset, $length);
+	}
+
+	// Escape these characters:  - [ ] . : \ ^ /
+	// The . and : are escaped to prevent possible warnings about POSIX regex elements
+	$mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+	preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+	return isset($matches[0]) ? utf8::strlen($matches[0]) : 0;
+}
\ No newline at end of file
diff --git a/system/core/utf8/stristr.php b/system/core/utf8/stristr.php
new file mode 100755
index 0000000..73ea139
--- /dev/null
+++ b/system/core/utf8/stristr.php
@@ -0,0 +1,28 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::stristr
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _stristr($str, $search)
+{
+	if (utf8::is_ascii($str) AND utf8::is_ascii($search))
+		return stristr($str, $search);
+
+	if ($search == '')
+		return $str;
+
+	$str_lower = utf8::strtolower($str);
+	$search_lower = utf8::strtolower($search);
+
+	preg_match('/^(.*?)'.preg_quote($search, '/').'/s', $str_lower, $matches);
+
+	if (isset($matches[1]))
+		return substr($str, strlen($matches[1]));
+
+	return FALSE;
+}
\ No newline at end of file
diff --git a/system/core/utf8/strlen.php b/system/core/utf8/strlen.php
new file mode 100755
index 0000000..f186a9c
--- /dev/null
+++ b/system/core/utf8/strlen.php
@@ -0,0 +1,21 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strlen
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strlen($str)
+{
+	// Try mb_strlen() first because it's faster than combination of is_ascii() and strlen()
+	if (SERVER_UTF8)
+		return mb_strlen($str);
+
+	if (utf8::is_ascii($str))
+		return strlen($str);
+
+	return strlen(utf8_decode($str));
+}
\ No newline at end of file
diff --git a/system/core/utf8/strpos.php b/system/core/utf8/strpos.php
new file mode 100755
index 0000000..4a34dbe
--- /dev/null
+++ b/system/core/utf8/strpos.php
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strpos
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strpos($str, $search, $offset = 0)
+{
+	$offset = (int) $offset;
+
+	if (SERVER_UTF8)
+		return mb_strpos($str, $search, $offset);
+
+	if (utf8::is_ascii($str) AND utf8::is_ascii($search))
+		return strpos($str, $search, $offset);
+
+	if ($offset == 0)
+	{
+		$array = explode($search, $str, 2);
+		return isset($array[1]) ? utf8::strlen($array[0]) : FALSE;
+	}
+
+	$str = utf8::substr($str, $offset);
+	$pos = utf8::strpos($str, $search);
+	return ($pos === FALSE) ? FALSE : $pos + $offset;
+}
\ No newline at end of file
diff --git a/system/core/utf8/strrev.php b/system/core/utf8/strrev.php
new file mode 100755
index 0000000..89c428c
--- /dev/null
+++ b/system/core/utf8/strrev.php
@@ -0,0 +1,18 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strrev
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strrev($str)
+{
+	if (utf8::is_ascii($str))
+		return strrev($str);
+
+	preg_match_all('/./us', $str, $matches);
+	return implode('', array_reverse($matches[0]));
+}
\ No newline at end of file
diff --git a/system/core/utf8/strrpos.php b/system/core/utf8/strrpos.php
new file mode 100755
index 0000000..1ac8f9c
--- /dev/null
+++ b/system/core/utf8/strrpos.php
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strrpos
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strrpos($str, $search, $offset = 0)
+{
+	$offset = (int) $offset;
+
+	if (SERVER_UTF8)
+		return mb_strrpos($str, $search, $offset);
+
+	if (utf8::is_ascii($str) AND utf8::is_ascii($search))
+		return strrpos($str, $search, $offset);
+
+	if ($offset == 0)
+	{
+		$array = explode($search, $str, -1);
+		return isset($array[0]) ? utf8::strlen(implode($search, $array)) : FALSE;
+	}
+
+	$str = utf8::substr($str, $offset);
+	$pos = utf8::strrpos($str, $search);
+	return ($pos === FALSE) ? FALSE : $pos + $offset;
+}
\ No newline at end of file
diff --git a/system/core/utf8/strspn.php b/system/core/utf8/strspn.php
new file mode 100755
index 0000000..bc7e52f
--- /dev/null
+++ b/system/core/utf8/strspn.php
@@ -0,0 +1,30 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strspn
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strspn($str, $mask, $offset = NULL, $length = NULL)
+{
+	if ($str == '' OR $mask == '')
+		return 0;
+
+	if (utf8::is_ascii($str) AND utf8::is_ascii($mask))
+		return ($offset === NULL) ? strspn($str, $mask) : (($length === NULL) ? strspn($str, $mask, $offset) : strspn($str, $mask, $offset, $length));
+
+	if ($offset !== NULL OR $length !== NULL)
+	{
+		$str = utf8::substr($str, $offset, $length);
+	}
+
+	// Escape these characters:  - [ ] . : \ ^ /
+	// The . and : are escaped to prevent possible warnings about POSIX regex elements
+	$mask = preg_replace('#[-[\].:\\\\^/]#', '\\\\$0', $mask);
+	preg_match('/^[^'.$mask.']+/u', $str, $matches);
+
+	return isset($matches[0]) ? utf8::strlen($matches[0]) : 0;
+}
\ No newline at end of file
diff --git a/system/core/utf8/strtolower.php b/system/core/utf8/strtolower.php
new file mode 100755
index 0000000..ff4da85
--- /dev/null
+++ b/system/core/utf8/strtolower.php
@@ -0,0 +1,84 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strtolower
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strtolower($str)
+{
+	if (SERVER_UTF8)
+		return mb_strtolower($str);
+
+	if (utf8::is_ascii($str))
+		return strtolower($str);
+
+	static $UTF8_UPPER_TO_LOWER = NULL;
+
+	if ($UTF8_UPPER_TO_LOWER === NULL)
+	{
+		$UTF8_UPPER_TO_LOWER = array(
+			0x0041=>0x0061, 0x03A6=>0x03C6, 0x0162=>0x0163, 0x00C5=>0x00E5, 0x0042=>0x0062,
+			0x0139=>0x013A, 0x00C1=>0x00E1, 0x0141=>0x0142, 0x038E=>0x03CD, 0x0100=>0x0101,
+			0x0490=>0x0491, 0x0394=>0x03B4, 0x015A=>0x015B, 0x0044=>0x0064, 0x0393=>0x03B3,
+			0x00D4=>0x00F4, 0x042A=>0x044A, 0x0419=>0x0439, 0x0112=>0x0113, 0x041C=>0x043C,
+			0x015E=>0x015F, 0x0143=>0x0144, 0x00CE=>0x00EE, 0x040E=>0x045E, 0x042F=>0x044F,
+			0x039A=>0x03BA, 0x0154=>0x0155, 0x0049=>0x0069, 0x0053=>0x0073, 0x1E1E=>0x1E1F,
+			0x0134=>0x0135, 0x0427=>0x0447, 0x03A0=>0x03C0, 0x0418=>0x0438, 0x00D3=>0x00F3,
+			0x0420=>0x0440, 0x0404=>0x0454, 0x0415=>0x0435, 0x0429=>0x0449, 0x014A=>0x014B,
+			0x0411=>0x0431, 0x0409=>0x0459, 0x1E02=>0x1E03, 0x00D6=>0x00F6, 0x00D9=>0x00F9,
+			0x004E=>0x006E, 0x0401=>0x0451, 0x03A4=>0x03C4, 0x0423=>0x0443, 0x015C=>0x015D,
+			0x0403=>0x0453, 0x03A8=>0x03C8, 0x0158=>0x0159, 0x0047=>0x0067, 0x00C4=>0x00E4,
+			0x0386=>0x03AC, 0x0389=>0x03AE, 0x0166=>0x0167, 0x039E=>0x03BE, 0x0164=>0x0165,
+			0x0116=>0x0117, 0x0108=>0x0109, 0x0056=>0x0076, 0x00DE=>0x00FE, 0x0156=>0x0157,
+			0x00DA=>0x00FA, 0x1E60=>0x1E61, 0x1E82=>0x1E83, 0x00C2=>0x00E2, 0x0118=>0x0119,
+			0x0145=>0x0146, 0x0050=>0x0070, 0x0150=>0x0151, 0x042E=>0x044E, 0x0128=>0x0129,
+			0x03A7=>0x03C7, 0x013D=>0x013E, 0x0422=>0x0442, 0x005A=>0x007A, 0x0428=>0x0448,
+			0x03A1=>0x03C1, 0x1E80=>0x1E81, 0x016C=>0x016D, 0x00D5=>0x00F5, 0x0055=>0x0075,
+			0x0176=>0x0177, 0x00DC=>0x00FC, 0x1E56=>0x1E57, 0x03A3=>0x03C3, 0x041A=>0x043A,
+			0x004D=>0x006D, 0x016A=>0x016B, 0x0170=>0x0171, 0x0424=>0x0444, 0x00CC=>0x00EC,
+			0x0168=>0x0169, 0x039F=>0x03BF, 0x004B=>0x006B, 0x00D2=>0x00F2, 0x00C0=>0x00E0,
+			0x0414=>0x0434, 0x03A9=>0x03C9, 0x1E6A=>0x1E6B, 0x00C3=>0x00E3, 0x042D=>0x044D,
+			0x0416=>0x0436, 0x01A0=>0x01A1, 0x010C=>0x010D, 0x011C=>0x011D, 0x00D0=>0x00F0,
+			0x013B=>0x013C, 0x040F=>0x045F, 0x040A=>0x045A, 0x00C8=>0x00E8, 0x03A5=>0x03C5,
+			0x0046=>0x0066, 0x00DD=>0x00FD, 0x0043=>0x0063, 0x021A=>0x021B, 0x00CA=>0x00EA,
+			0x0399=>0x03B9, 0x0179=>0x017A, 0x00CF=>0x00EF, 0x01AF=>0x01B0, 0x0045=>0x0065,
+			0x039B=>0x03BB, 0x0398=>0x03B8, 0x039C=>0x03BC, 0x040C=>0x045C, 0x041F=>0x043F,
+			0x042C=>0x044C, 0x00DE=>0x00FE, 0x00D0=>0x00F0, 0x1EF2=>0x1EF3, 0x0048=>0x0068,
+			0x00CB=>0x00EB, 0x0110=>0x0111, 0x0413=>0x0433, 0x012E=>0x012F, 0x00C6=>0x00E6,
+			0x0058=>0x0078, 0x0160=>0x0161, 0x016E=>0x016F, 0x0391=>0x03B1, 0x0407=>0x0457,
+			0x0172=>0x0173, 0x0178=>0x00FF, 0x004F=>0x006F, 0x041B=>0x043B, 0x0395=>0x03B5,
+			0x0425=>0x0445, 0x0120=>0x0121, 0x017D=>0x017E, 0x017B=>0x017C, 0x0396=>0x03B6,
+			0x0392=>0x03B2, 0x0388=>0x03AD, 0x1E84=>0x1E85, 0x0174=>0x0175, 0x0051=>0x0071,
+			0x0417=>0x0437, 0x1E0A=>0x1E0B, 0x0147=>0x0148, 0x0104=>0x0105, 0x0408=>0x0458,
+			0x014C=>0x014D, 0x00CD=>0x00ED, 0x0059=>0x0079, 0x010A=>0x010B, 0x038F=>0x03CE,
+			0x0052=>0x0072, 0x0410=>0x0430, 0x0405=>0x0455, 0x0402=>0x0452, 0x0126=>0x0127,
+			0x0136=>0x0137, 0x012A=>0x012B, 0x038A=>0x03AF, 0x042B=>0x044B, 0x004C=>0x006C,
+			0x0397=>0x03B7, 0x0124=>0x0125, 0x0218=>0x0219, 0x00DB=>0x00FB, 0x011E=>0x011F,
+			0x041E=>0x043E, 0x1E40=>0x1E41, 0x039D=>0x03BD, 0x0106=>0x0107, 0x03AB=>0x03CB,
+			0x0426=>0x0446, 0x00DE=>0x00FE, 0x00C7=>0x00E7, 0x03AA=>0x03CA, 0x0421=>0x0441,
+			0x0412=>0x0432, 0x010E=>0x010F, 0x00D8=>0x00F8, 0x0057=>0x0077, 0x011A=>0x011B,
+			0x0054=>0x0074, 0x004A=>0x006A, 0x040B=>0x045B, 0x0406=>0x0456, 0x0102=>0x0103,
+			0x039B=>0x03BB, 0x00D1=>0x00F1, 0x041D=>0x043D, 0x038C=>0x03CC, 0x00C9=>0x00E9,
+			0x00D0=>0x00F0, 0x0407=>0x0457, 0x0122=>0x0123,
+		);
+	}
+
+	$uni = utf8::to_unicode($str);
+
+	if ($uni === FALSE)
+		return FALSE;
+
+	for ($i = 0, $c = count($uni); $i < $c; $i++)
+	{
+		if (isset($UTF8_UPPER_TO_LOWER[$uni[$i]]))
+		{
+			$uni[$i] = $UTF8_UPPER_TO_LOWER[$uni[$i]];
+		}
+	}
+
+	return utf8::from_unicode($uni);
+}
\ No newline at end of file
diff --git a/system/core/utf8/strtoupper.php b/system/core/utf8/strtoupper.php
new file mode 100755
index 0000000..f3ded73
--- /dev/null
+++ b/system/core/utf8/strtoupper.php
@@ -0,0 +1,84 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::strtoupper
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _strtoupper($str)
+{
+	if (SERVER_UTF8)
+		return mb_strtoupper($str);
+
+	if (utf8::is_ascii($str))
+		return strtoupper($str);
+
+	static $UTF8_LOWER_TO_UPPER = NULL;
+
+	if ($UTF8_LOWER_TO_UPPER === NULL)
+	{
+		$UTF8_LOWER_TO_UPPER = array(
+			0x0061=>0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
+			0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
+			0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
+			0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
+			0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
+			0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
+			0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
+			0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
+			0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
+			0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
+			0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
+			0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
+			0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
+			0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
+			0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
+			0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
+			0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
+			0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
+			0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
+			0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
+			0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
+			0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
+			0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
+			0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
+			0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
+			0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
+			0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
+			0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
+			0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
+			0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
+			0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
+			0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
+			0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
+			0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
+			0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
+			0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
+			0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
+			0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
+			0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
+			0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
+			0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
+			0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
+			0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122,
+		);
+	}
+
+	$uni = utf8::to_unicode($str);
+
+	if ($uni === FALSE)
+		return FALSE;
+
+	for ($i = 0, $c = count($uni); $i < $c; $i++)
+	{
+		if (isset($UTF8_LOWER_TO_UPPER[$uni[$i]]))
+		{
+			$uni[$i] = $UTF8_LOWER_TO_UPPER[$uni[$i]];
+		}
+	}
+
+	return utf8::from_unicode($uni);
+}
\ No newline at end of file
diff --git a/system/core/utf8/substr.php b/system/core/utf8/substr.php
new file mode 100755
index 0000000..daf66b8
--- /dev/null
+++ b/system/core/utf8/substr.php
@@ -0,0 +1,75 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::substr
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _substr($str, $offset, $length = NULL)
+{
+	if (SERVER_UTF8)
+		return ($length === NULL) ? mb_substr($str, $offset) : mb_substr($str, $offset, $length);
+
+	if (utf8::is_ascii($str))
+		return ($length === NULL) ? substr($str, $offset) : substr($str, $offset, $length);
+
+	// Normalize params
+	$str    = (string) $str;
+	$strlen = utf8::strlen($str);
+	$offset = (int) ($offset < 0) ? max(0, $strlen + $offset) : $offset; // Normalize to positive offset
+	$length = ($length === NULL) ? NULL : (int) $length;
+
+	// Impossible
+	if ($length === 0 OR $offset >= $strlen OR ($length < 0 AND $length <= $offset - $strlen))
+		return '';
+
+	// Whole string
+	if ($offset == 0 AND ($length === NULL OR $length >= $strlen))
+		return $str;
+
+	// Build regex
+	$regex = '^';
+
+	// Create an offset expression
+	if ($offset > 0)
+	{
+		// PCRE repeating quantifiers must be less than 65536, so repeat when necessary
+		$x = (int) ($offset / 65535);
+		$y = (int) ($offset % 65535);
+		$regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
+		$regex .= ($y == 0) ? '' : '.{'.$y.'}';
+	}
+
+	// Create a length expression
+	if ($length === NULL)
+	{
+		$regex .= '(.*)'; // No length set, grab it all
+	}
+	// Find length from the left (positive length)
+	elseif ($length > 0)
+	{
+		// Reduce length so that it can't go beyond the end of the string
+		$length = min($strlen - $offset, $length);
+
+		$x = (int) ($length / 65535);
+		$y = (int) ($length % 65535);
+		$regex .= '(';
+		$regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
+		$regex .= '.{'.$y.'})';
+	}
+	// Find length from the right (negative length)
+	else
+	{
+		$x = (int) (-$length / 65535);
+		$y = (int) (-$length % 65535);
+		$regex .= '(.*)';
+		$regex .= ($x == 0) ? '' : '(?:.{65535}){'.$x.'}';
+		$regex .= '.{'.$y.'}';
+	}
+
+	preg_match('/'.$regex.'/us', $str, $matches);
+	return $matches[1];
+}
\ No newline at end of file
diff --git a/system/core/utf8/substr_replace.php b/system/core/utf8/substr_replace.php
new file mode 100755
index 0000000..45e2d2a
--- /dev/null
+++ b/system/core/utf8/substr_replace.php
@@ -0,0 +1,22 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::substr_replace
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _substr_replace($str, $replacement, $offset, $length = NULL)
+{
+	if (utf8::is_ascii($str))
+		return ($length === NULL) ? substr_replace($str, $replacement, $offset) : substr_replace($str, $replacement, $offset, $length);
+
+	$length = ($length === NULL) ? utf8::strlen($str) : (int) $length;
+	preg_match_all('/./us', $str, $str_array);
+	preg_match_all('/./us', $replacement, $replacement_array);
+
+	array_splice($str_array[0], $offset, $length, $replacement_array[0]);
+	return implode('', $str_array[0]);
+}
\ No newline at end of file
diff --git a/system/core/utf8/to_unicode.php b/system/core/utf8/to_unicode.php
new file mode 100755
index 0000000..93f741a
--- /dev/null
+++ b/system/core/utf8/to_unicode.php
@@ -0,0 +1,141 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::to_unicode
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _to_unicode($str)
+{
+	$mState = 0; // cached expected number of octets after the current octet until the beginning of the next UTF8 character sequence
+	$mUcs4  = 0; // cached Unicode character
+	$mBytes = 1; // cached expected number of octets in the current sequence
+
+	$out = array();
+
+	$len = strlen($str);
+
+	for ($i = 0; $i < $len; $i++)
+	{
+		$in = ord($str[$i]);
+
+		if ($mState == 0)
+		{
+			// When mState is zero we expect either a US-ASCII character or a
+			// multi-octet sequence.
+			if (0 == (0x80 & $in))
+			{
+				// US-ASCII, pass straight through.
+				$out[] = $in;
+				$mBytes = 1;
+			}
+			elseif (0xC0 == (0xE0 & $in))
+			{
+				// First octet of 2 octet sequence
+				$mUcs4 = $in;
+				$mUcs4 = ($mUcs4 & 0x1F) << 6;
+				$mState = 1;
+				$mBytes = 2;
+			}
+			elseif (0xE0 == (0xF0 & $in))
+			{
+				// First octet of 3 octet sequence
+				$mUcs4 = $in;
+				$mUcs4 = ($mUcs4 & 0x0F) << 12;
+				$mState = 2;
+				$mBytes = 3;
+			}
+			elseif (0xF0 == (0xF8 & $in))
+			{
+				// First octet of 4 octet sequence
+				$mUcs4 = $in;
+				$mUcs4 = ($mUcs4 & 0x07) << 18;
+				$mState = 3;
+				$mBytes = 4;
+			}
+			elseif (0xF8 == (0xFC & $in))
+			{
+				// First octet of 5 octet sequence.
+				//
+				// This is illegal because the encoded codepoint must be either
+				// (a) not the shortest form or
+				// (b) outside the Unicode range of 0-0x10FFFF.
+				// Rather than trying to resynchronize, we will carry on until the end
+				// of the sequence and let the later error handling code catch it.
+				$mUcs4 = $in;
+				$mUcs4 = ($mUcs4 & 0x03) << 24;
+				$mState = 4;
+				$mBytes = 5;
+			}
+			elseif (0xFC == (0xFE & $in))
+			{
+				// First octet of 6 octet sequence, see comments for 5 octet sequence.
+				$mUcs4 = $in;
+				$mUcs4 = ($mUcs4 & 1) << 30;
+				$mState = 5;
+				$mBytes = 6;
+			}
+			else
+			{
+				// Current octet is neither in the US-ASCII range nor a legal first octet of a multi-octet sequence.
+				trigger_error('utf8::to_unicode: Illegal sequence identifier in UTF-8 at byte '.$i, E_USER_WARNING);
+				return FALSE;
+			}
+		}
+		else
+		{
+			// When mState is non-zero, we expect a continuation of the multi-octet sequence
+			if (0x80 == (0xC0 & $in))
+			{
+				// Legal continuation
+				$shift = ($mState - 1) * 6;
+				$tmp = $in;
+				$tmp = ($tmp & 0x0000003F) << $shift;
+				$mUcs4 |= $tmp;
+
+				// End of the multi-octet sequence. mUcs4 now contains the final Unicode codepoint to be output
+				if (0 == --$mState)
+				{
+					// Check for illegal sequences and codepoints
+
+					// From Unicode 3.1, non-shortest form is illegal
+					if (((2 == $mBytes) AND ($mUcs4 < 0x0080)) OR
+						((3 == $mBytes) AND ($mUcs4 < 0x0800)) OR
+						((4 == $mBytes) AND ($mUcs4 < 0x10000)) OR
+						(4 < $mBytes) OR
+						// From Unicode 3.2, surrogate characters are illegal
+						(($mUcs4 & 0xFFFFF800) == 0xD800) OR
+						// Codepoints outside the Unicode range are illegal
+						($mUcs4 > 0x10FFFF))
+					{
+						trigger_error('utf8::to_unicode: Illegal sequence or codepoint in UTF-8 at byte '.$i, E_USER_WARNING);
+						return FALSE;
+					}
+
+					if (0xFEFF != $mUcs4)
+					{
+						// BOM is legal but we don't want to output it
+						$out[] = $mUcs4;
+					}
+
+					// Initialize UTF-8 cache
+					$mState = 0;
+					$mUcs4  = 0;
+					$mBytes = 1;
+				}
+			}
+			else
+			{
+				// ((0xC0 & (*in) != 0x80) AND (mState != 0))
+				// Incomplete multi-octet sequence
+				trigger_error('utf8::to_unicode: Incomplete multi-octet sequence in UTF-8 at byte '.$i, E_USER_WARNING);
+				return FALSE;
+			}
+		}
+	}
+
+	return $out;
+}
\ No newline at end of file
diff --git a/system/core/utf8/transliterate_to_ascii.php b/system/core/utf8/transliterate_to_ascii.php
new file mode 100755
index 0000000..07461fb
--- /dev/null
+++ b/system/core/utf8/transliterate_to_ascii.php
@@ -0,0 +1,77 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::transliterate_to_ascii
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _transliterate_to_ascii($str, $case = 0)
+{
+	static $UTF8_LOWER_ACCENTS = NULL;
+	static $UTF8_UPPER_ACCENTS = NULL;
+
+	if ($case <= 0)
+	{
+		if ($UTF8_LOWER_ACCENTS === NULL)
+		{
+			$UTF8_LOWER_ACCENTS = array(
+				'à' => 'a',  'ô' => 'o',  'ď' => 'd',  'ḟ' => 'f',  'ë' => 'e',  'š' => 's',  'ơ' => 'o',
+				'ß' => 'ss', 'ă' => 'a',  'ř' => 'r',  'ț' => 't',  'ň' => 'n',  'ā' => 'a',  'ķ' => 'k',
+				'ŝ' => 's',  'ỳ' => 'y',  'ņ' => 'n',  'ĺ' => 'l',  'ħ' => 'h',  'ṗ' => 'p',  'ó' => 'o',
+				'ú' => 'u',  'ě' => 'e',  'é' => 'e',  'ç' => 'c',  'ẁ' => 'w',  'ċ' => 'c',  'õ' => 'o',
+				'ṡ' => 's',  'ø' => 'o',  'ģ' => 'g',  'ŧ' => 't',  'ș' => 's',  'ė' => 'e',  'ĉ' => 'c',
+				'ś' => 's',  'î' => 'i',  'ű' => 'u',  'ć' => 'c',  'ę' => 'e',  'ŵ' => 'w',  'ṫ' => 't',
+				'ū' => 'u',  'č' => 'c',  'ö' => 'o',  'è' => 'e',  'ŷ' => 'y',  'ą' => 'a',  'ł' => 'l',
+				'ų' => 'u',  'ů' => 'u',  'ş' => 's',  'ğ' => 'g',  'ļ' => 'l',  'ƒ' => 'f',  'ž' => 'z',
+				'ẃ' => 'w',  'ḃ' => 'b',  'å' => 'a',  'ì' => 'i',  'ï' => 'i',  'ḋ' => 'd',  'ť' => 't',
+				'ŗ' => 'r',  'ä' => 'a',  'í' => 'i',  'ŕ' => 'r',  'ê' => 'e',  'ü' => 'u',  'ò' => 'o',
+				'ē' => 'e',  'ñ' => 'n',  'ń' => 'n',  'ĥ' => 'h',  'ĝ' => 'g',  'đ' => 'd',  'ĵ' => 'j',
+				'ÿ' => 'y',  'ũ' => 'u',  'ŭ' => 'u',  'ư' => 'u',  'ţ' => 't',  'ý' => 'y',  'ő' => 'o',
+				'â' => 'a',  'ľ' => 'l',  'ẅ' => 'w',  'ż' => 'z',  'ī' => 'i',  'ã' => 'a',  'ġ' => 'g',
+				'ṁ' => 'm',  'ō' => 'o',  'ĩ' => 'i',  'ù' => 'u',  'į' => 'i',  'ź' => 'z',  'á' => 'a',
+				'û' => 'u',  'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u',  'ĕ' => 'e',
+			);
+		}
+
+		$str = str_replace(
+			array_keys($UTF8_LOWER_ACCENTS),
+			array_values($UTF8_LOWER_ACCENTS),
+			$str
+		);
+	}
+
+	if ($case >= 0)
+	{
+		if ($UTF8_UPPER_ACCENTS === NULL)
+		{
+			$UTF8_UPPER_ACCENTS = array(
+				'À' => 'A',  'Ô' => 'O',  'Ď' => 'D',  'Ḟ' => 'F',  'Ë' => 'E',  'Š' => 'S',  'Ơ' => 'O',
+				'Ă' => 'A',  'Ř' => 'R',  'Ț' => 'T',  'Ň' => 'N',  'Ā' => 'A',  'Ķ' => 'K',  'Ĕ' => 'E',
+				'Ŝ' => 'S',  'Ỳ' => 'Y',  'Ņ' => 'N',  'Ĺ' => 'L',  'Ħ' => 'H',  'Ṗ' => 'P',  'Ó' => 'O',
+				'Ú' => 'U',  'Ě' => 'E',  'É' => 'E',  'Ç' => 'C',  'Ẁ' => 'W',  'Ċ' => 'C',  'Õ' => 'O',
+				'Ṡ' => 'S',  'Ø' => 'O',  'Ģ' => 'G',  'Ŧ' => 'T',  'Ș' => 'S',  'Ė' => 'E',  'Ĉ' => 'C',
+				'Ś' => 'S',  'Î' => 'I',  'Ű' => 'U',  'Ć' => 'C',  'Ę' => 'E',  'Ŵ' => 'W',  'Ṫ' => 'T',
+				'Ū' => 'U',  'Č' => 'C',  'Ö' => 'O',  'È' => 'E',  'Ŷ' => 'Y',  'Ą' => 'A',  'Ł' => 'L',
+				'Ų' => 'U',  'Ů' => 'U',  'Ş' => 'S',  'Ğ' => 'G',  'Ļ' => 'L',  'Ƒ' => 'F',  'Ž' => 'Z',
+				'Ẃ' => 'W',  'Ḃ' => 'B',  'Å' => 'A',  'Ì' => 'I',  'Ï' => 'I',  'Ḋ' => 'D',  'Ť' => 'T',
+				'Ŗ' => 'R',  'Ä' => 'A',  'Í' => 'I',  'Ŕ' => 'R',  'Ê' => 'E',  'Ü' => 'U',  'Ò' => 'O',
+				'Ē' => 'E',  'Ñ' => 'N',  'Ń' => 'N',  'Ĥ' => 'H',  'Ĝ' => 'G',  'Đ' => 'D',  'Ĵ' => 'J',
+				'Ÿ' => 'Y',  'Ũ' => 'U',  'Ŭ' => 'U',  'Ư' => 'U',  'Ţ' => 'T',  'Ý' => 'Y',  'Ő' => 'O',
+				'Â' => 'A',  'Ľ' => 'L',  'Ẅ' => 'W',  'Ż' => 'Z',  'Ī' => 'I',  'Ã' => 'A',  'Ġ' => 'G',
+				'Ṁ' => 'M',  'Ō' => 'O',  'Ĩ' => 'I',  'Ù' => 'U',  'Į' => 'I',  'Ź' => 'Z',  'Á' => 'A',
+				'Û' => 'U',  'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae',
+			);
+		}
+
+		$str = str_replace(
+			array_keys($UTF8_UPPER_ACCENTS),
+			array_values($UTF8_UPPER_ACCENTS),
+			$str
+		);
+	}
+
+	return $str;
+}
\ No newline at end of file
diff --git a/system/core/utf8/trim.php b/system/core/utf8/trim.php
new file mode 100755
index 0000000..7434102
--- /dev/null
+++ b/system/core/utf8/trim.php
@@ -0,0 +1,17 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::trim
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _trim($str, $charlist = NULL)
+{
+	if ($charlist === NULL)
+		return trim($str);
+
+	return utf8::ltrim(utf8::rtrim($str, $charlist), $charlist);
+}
\ No newline at end of file
diff --git a/system/core/utf8/ucfirst.php b/system/core/utf8/ucfirst.php
new file mode 100755
index 0000000..81a4b38
--- /dev/null
+++ b/system/core/utf8/ucfirst.php
@@ -0,0 +1,18 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::ucfirst
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _ucfirst($str)
+{
+	if (utf8::is_ascii($str))
+		return ucfirst($str);
+
+	preg_match('/^(.?)(.*)$/us', $str, $matches);
+	return utf8::strtoupper($matches[1]).$matches[2];
+}
\ No newline at end of file
diff --git a/system/core/utf8/ucwords.php b/system/core/utf8/ucwords.php
new file mode 100755
index 0000000..2d4c94b
--- /dev/null
+++ b/system/core/utf8/ucwords.php
@@ -0,0 +1,26 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * utf8::ucwords
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @copyright  (c) 2005 Harry Fuecks
+ * @license    http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
+ */
+function _ucwords($str)
+{
+	if (SERVER_UTF8)
+		return mb_convert_case($str, MB_CASE_TITLE);
+
+	if (utf8::is_ascii($str))
+		return ucwords($str);
+
+	// [\x0c\x09\x0b\x0a\x0d\x20] matches form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns.
+	// This corresponds to the definition of a 'word' defined at http://php.net/ucwords
+	return preg_replace(
+		'/(?<=^|[\x0c\x09\x0b\x0a\x0d\x20])[^\x0c\x09\x0b\x0a\x0d\x20]/ue',
+		'utf8::strtoupper(\'$0\')',
+		$str
+	);
+}
\ No newline at end of file
diff --git a/system/fonts/DejaVuSerif.ttf b/system/fonts/DejaVuSerif.ttf
new file mode 100755
index 0000000..b7f4482
Binary files /dev/null and b/system/fonts/DejaVuSerif.ttf differ
diff --git a/system/fonts/LICENSE b/system/fonts/LICENSE
new file mode 100755
index 0000000..254e2cc
--- /dev/null
+++ b/system/fonts/LICENSE
@@ -0,0 +1,99 @@
+Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
+Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
+
+Bitstream Vera Fonts Copyright
+------------------------------
+
+Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
+a trademark of Bitstream, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of the fonts accompanying this license ("Fonts") and associated
+documentation files (the "Font Software"), to reproduce and distribute the
+Font Software, including without limitation the rights to use, copy, merge,
+publish, distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright and trademark notices and this permission notice shall
+be included in all copies of one or more of the Font Software typefaces.
+
+The Font Software may be modified, altered, or added to, and in particular
+the designs of glyphs or characters in the Fonts may be modified and
+additional glyphs or characters may be added to the Fonts, only if the fonts
+are renamed to names not containing either the words "Bitstream" or the word
+"Vera".
+
+This License becomes null and void to the extent applicable to Fonts or Font
+Software that has been modified and is distributed under the "Bitstream
+Vera" names.
+
+The Font Software may be sold as part of a larger software package but no
+copy of one or more of the Font Software typefaces may be sold by itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
+TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
+FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
+ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
+THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
+FONT SOFTWARE.
+
+Except as contained in this notice, the names of Gnome, the Gnome
+Foundation, and Bitstream Inc., shall not be used in advertising or
+otherwise to promote the sale, use or other dealings in this Font Software
+without prior written authorization from the Gnome Foundation or Bitstream
+Inc., respectively. For further information, contact: fonts at gnome dot
+org. 
+
+Arev Fonts Copyright
+------------------------------
+
+Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the fonts accompanying this license ("Fonts") and
+associated documentation files (the "Font Software"), to reproduce
+and distribute the modifications to the Bitstream Vera Font Software,
+including without limitation the rights to use, copy, merge, publish,
+distribute, and/or sell copies of the Font Software, and to permit
+persons to whom the Font Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright and trademark notices and this permission notice
+shall be included in all copies of one or more of the Font Software
+typefaces.
+
+The Font Software may be modified, altered, or added to, and in
+particular the designs of glyphs or characters in the Fonts may be
+modified and additional glyphs or characters may be added to the
+Fonts, only if the fonts are renamed to names not containing either
+the words "Tavmjong Bah" or the word "Arev".
+
+This License becomes null and void to the extent applicable to Fonts
+or Font Software that has been modified and is distributed under the 
+"Tavmjong Bah Arev" names.
+
+The Font Software may be sold as part of a larger software package but
+no copy of one or more of the Font Software typefaces may be sold by
+itself.
+
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
+TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+Except as contained in this notice, the name of Tavmjong Bah shall not
+be used in advertising or otherwise to promote the sale, use or other
+dealings in this Font Software without prior written authorization
+from Tavmjong Bah. For further information, contact: tavmjong @ free
+. fr.
+
+$Id: LICENSE 2133 2007-11-28 02:46:28Z lechimp $
diff --git a/system/helpers/arr.php b/system/helpers/arr.php
new file mode 100755
index 0000000..cbcd264
--- /dev/null
+++ b/system/helpers/arr.php
@@ -0,0 +1,316 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Array helper class.
+ *
+ * $Id: arr.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class arr_Core {
+
+	/**
+	 * Return a callback array from a string, eg: limit[10,20] would become
+	 * array('limit', array('10', '20'))
+	 *
+	 * @param   string  callback string
+	 * @return  array
+	 */
+	public static function callback_string($str)
+	{
+		// command[param,param]
+		if (preg_match('/([^\[]*+)\[(.+)\]/', (string) $str, $match))
+		{
+			// command
+			$command = $match[1];
+
+			// param,param
+			$params = preg_split('/(?<!\\\\),/', $match[2]);
+			$params = str_replace('\,', ',', $params);
+		}
+		else
+		{
+			// command
+			$command = $str;
+
+			// No params
+			$params = NULL;
+		}
+
+		return array($command, $params);
+	}
+
+	/**
+	 * Rotates a 2D array clockwise.
+	 * Example, turns a 2x3 array into a 3x2 array.
+	 *
+	 * @param   array    array to rotate
+	 * @param   boolean  keep the keys in the final rotated array. the sub arrays of the source array need to have the same key values.
+	 *                   if your subkeys might not match, you need to pass FALSE here!
+	 * @return  array
+	 */
+	public static function rotate($source_array, $keep_keys = TRUE)
+	{
+		$new_array = array();
+		foreach ($source_array as $key => $value)
+		{
+			$value = ($keep_keys === TRUE) ? $value : array_values($value);
+			foreach ($value as $k => $v)
+			{
+				$new_array[$k][$key] = $v;
+			}
+		}
+
+		return $new_array;
+	}
+
+	/**
+	 * Removes a key from an array and returns the value.
+	 *
+	 * @param   string  key to return
+	 * @param   array   array to work on
+	 * @return  mixed   value of the requested array key
+	 */
+	public static function remove($key, & $array)
+	{
+		if ( ! array_key_exists($key, $array))
+			return NULL;
+
+		$val = $array[$key];
+		unset($array[$key]);
+
+		return $val;
+	}
+
+	
+	/**
+	 * Extract one or more keys from an array. Each key given after the first
+	 * argument (the array) will be extracted. Keys that do not exist in the
+	 * search array will be NULL in the extracted data.
+	 *
+	 * @param   array   array to search
+	 * @param   string  key name
+	 * @return  array
+	 */
+	public static function extract(array $search, $keys)
+	{
+		// Get the keys, removing the $search array
+		$keys = array_slice(func_get_args(), 1);
+
+		$found = array();
+		foreach ($keys as $key)
+		{
+			if (isset($search[$key]))
+			{
+				$found[$key] = $search[$key];
+			}
+			else
+			{
+				$found[$key] = NULL;
+			}
+		}
+
+		return $found;
+	}
+
+	/**
+	 * Because PHP does not have this function.
+	 *
+	 * @param   array   array to unshift
+	 * @param   string  key to unshift
+	 * @param   mixed   value to unshift
+	 * @return  array
+	 */
+	public static function unshift_assoc( array & $array, $key, $val)
+	{
+		$array = array_reverse($array, TRUE);
+		$array[$key] = $val;
+		$array = array_reverse($array, TRUE);
+
+		return $array;
+	}
+
+	/**
+	 * Because PHP does not have this function, and array_walk_recursive creates
+	 * references in arrays and is not truly recursive.
+	 *
+	 * @param   mixed  callback to apply to each member of the array
+	 * @param   array  array to map to
+	 * @return  array
+	 */
+	public static function map_recursive($callback, array $array)
+	{
+		foreach ($array as $key => $val)
+		{
+			// Map the callback to the key
+			$array[$key] = is_array($val) ? arr::map_recursive($callback, $val) : call_user_func($callback, $val);
+		}
+
+		return $array;
+	}
+
+	/**
+	 * Binary search algorithm.
+	 *
+	 * @param   mixed    the value to search for
+	 * @param   array    an array of values to search in
+	 * @param   boolean  return false, or the nearest value
+	 * @param   mixed    sort the array before searching it
+	 * @return  integer
+	 */
+	public static function binary_search($needle, $haystack, $nearest = FALSE, $sort = FALSE)
+	{
+		if ($sort === TRUE)
+		{
+			sort($haystack);
+		}
+
+		$high = count($haystack);
+		$low = 0;
+
+		while ($high - $low > 1)
+		{
+			$probe = ($high + $low) / 2;
+			if ($haystack[$probe] < $needle)
+			{
+				$low = $probe;
+			}
+			else
+			{
+				$high = $probe;
+			}
+		}
+
+		if ($high == count($haystack) OR $haystack[$high] != $needle)
+		{
+			if ($nearest === FALSE)
+				return FALSE;
+
+			// return the nearest value
+			$high_distance = $haystack[ceil($low)] - $needle;
+			$low_distance = $needle - $haystack[floor($low)];
+
+			return ($high_distance >= $low_distance) ? $haystack[ceil($low)] : $haystack[floor($low)];
+		}
+
+		return $high;
+	}
+
+	/**
+	 * Emulates array_merge_recursive, but appends numeric keys and replaces
+	 * associative keys, instead of appending all keys.
+	 *
+	 * @param   array  any number of arrays
+	 * @return  array
+	 */
+	public static function merge()
+	{
+		$total = func_num_args();
+
+		$result = array();
+		for ($i = 0; $i < $total; $i++)
+		{
+			foreach (func_get_arg($i) as $key => $val)
+			{
+				if (isset($result[$key]))
+				{
+					if (is_array($val))
+					{
+						// Arrays are merged recursively
+						$result[$key] = arr::merge($result[$key], $val);
+					}
+					elseif (is_int($key))
+					{
+						// Indexed arrays are appended
+						array_push($result, $val);
+					}
+					else
+					{
+						// Associative arrays are replaced
+						$result[$key] = $val;
+					}
+				}
+				else
+				{
+					// New values are added
+					$result[$key] = $val;
+				}
+			}
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Overwrites an array with values from input array(s).
+	 * Non-existing keys will not be appended!
+	 *
+	 * @param   array   key array
+	 * @param   array   input array(s) that will overwrite key array values
+	 * @return  array
+	 */
+	public static function overwrite($array1)
+	{
+		foreach (array_slice(func_get_args(), 1) as $array2)
+		{
+			foreach ($array2 as $key => $value)
+			{
+				if (array_key_exists($key, $array1))
+				{
+					$array1[$key] = $value;
+				}
+			}
+		}
+
+		return $array1;
+	}
+
+	/**
+	 * Fill an array with a range of numbers.
+	 *
+	 * @param   integer  stepping
+	 * @param   integer  ending number
+	 * @return  array
+	 */
+	public static function range($step = 10, $max = 100)
+	{
+		if ($step < 1)
+			return array();
+
+		$array = array();
+		for ($i = $step; $i <= $max; $i += $step)
+		{
+			$array[$i] = $i;
+		}
+
+		return $array;
+	}
+
+	/**
+	 * Recursively convert an array to an object.
+	 *
+	 * @param   array   array to convert
+	 * @return  object
+	 */
+	public static function to_object(array $array, $class = 'stdClass')
+	{
+		$object = new $class;
+
+		foreach ($array as $key => $value)
+		{
+			if (is_array($value))
+			{
+				// Convert the array to an object
+				$value = arr::to_object($value, $class);
+			}
+
+			// Add the value to the object
+			$object->{$key} = $value;
+		}
+
+		return $object;
+	}
+
+} // End arr
\ No newline at end of file
diff --git a/system/helpers/cookie.php b/system/helpers/cookie.php
new file mode 100755
index 0000000..f875be7
--- /dev/null
+++ b/system/helpers/cookie.php
@@ -0,0 +1,84 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cookie helper class.
+ *
+ * $Id: cookie.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class cookie_Core {
+
+	/**
+	 * Sets a cookie with the given parameters.
+	 *
+	 * @param   string   cookie name or array of config options
+	 * @param   string   cookie value
+	 * @param   integer  number of seconds before the cookie expires
+	 * @param   string   URL path to allow
+	 * @param   string   URL domain to allow
+	 * @param   boolean  HTTPS only
+	 * @param   boolean  HTTP only (requires PHP 5.2 or higher)
+	 * @return  boolean
+	 */
+	public static function set($name, $value = NULL, $expire = NULL, $path = NULL, $domain = NULL, $secure = NULL, $httponly = NULL)
+	{
+		if (headers_sent())
+			return FALSE;
+
+		// If the name param is an array, we import it
+		is_array($name) and extract($name, EXTR_OVERWRITE);
+
+		// Fetch default options
+		$config = Kohana::config('cookie');
+
+		foreach (array('value', 'expire', 'domain', 'path', 'secure', 'httponly') as $item)
+		{
+			if ($$item === NULL AND isset($config[$item]))
+			{
+				$$item = $config[$item];
+			}
+		}
+
+		// Expiration timestamp
+		$expire = ($expire == 0) ? 0 : time() + (int) $expire;
+
+		return setcookie($name, $value, $expire, $path, $domain, $secure, $httponly);
+	}
+
+	/**
+	 * Fetch a cookie value, using the Input library.
+	 *
+	 * @param   string   cookie name
+	 * @param   mixed    default value
+	 * @param   boolean  use XSS cleaning on the value
+	 * @return  string
+	 */
+	public static function get($name, $default = NULL, $xss_clean = FALSE)
+	{
+		return Input::instance()->cookie($name, $default, $xss_clean);
+	}
+
+	/**
+	 * Nullify and unset a cookie.
+	 *
+	 * @param   string   cookie name
+	 * @param   string   URL path
+	 * @param   string   URL domain
+	 * @return  boolean
+	 */
+	public static function delete($name, $path = NULL, $domain = NULL)
+	{
+		if ( ! isset($_COOKIE[$name]))
+			return FALSE;
+
+		// Delete the cookie from globals
+		unset($_COOKIE[$name]);
+
+		// Sets the cookie value to an empty string, and the expiration to 24 hours ago
+		return cookie::set($name, '', -86400, $path, $domain, FALSE, FALSE);
+	}
+
+} // End cookie
\ No newline at end of file
diff --git a/system/helpers/date.php b/system/helpers/date.php
new file mode 100755
index 0000000..42ec316
--- /dev/null
+++ b/system/helpers/date.php
@@ -0,0 +1,399 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Date helper class.
+ *
+ * $Id: date.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class date_Core {
+
+	/**
+	 * Converts a UNIX timestamp to DOS format.
+	 *
+	 * @param   integer  UNIX timestamp
+	 * @return  integer
+	 */
+	public static function unix2dos($timestamp = FALSE)
+	{
+		$timestamp = ($timestamp === FALSE) ? getdate() : getdate($timestamp);
+
+		if ($timestamp['year'] < 1980)
+		{
+			return (1 << 21 | 1 << 16);
+		}
+
+		$timestamp['year'] -= 1980;
+
+		// What voodoo is this? I have no idea... Geert can explain it though,
+		// and that's good enough for me.
+		return ($timestamp['year']    << 25 | $timestamp['mon']     << 21 |
+		        $timestamp['mday']    << 16 | $timestamp['hours']   << 11 |
+		        $timestamp['minutes'] << 5  | $timestamp['seconds'] >> 1);
+	}
+
+	/**
+	 * Converts a DOS timestamp to UNIX format.
+	 *
+	 * @param   integer  DOS timestamp
+	 * @return  integer
+	 */
+	public static function dos2unix($timestamp = FALSE)
+	{
+		$sec  = 2 * ($timestamp & 0x1f);
+		$min  = ($timestamp >>  5) & 0x3f;
+		$hrs  = ($timestamp >> 11) & 0x1f;
+		$day  = ($timestamp >> 16) & 0x1f;
+		$mon  = ($timestamp >> 21) & 0x0f;
+		$year = ($timestamp >> 25) & 0x7f;
+
+		return mktime($hrs, $min, $sec, $mon, $day, $year + 1980);
+	}
+
+	/**
+	 * Returns the offset (in seconds) between two time zones.
+	 * @see     http://php.net/timezones
+	 *
+	 * @param   string          timezone that to find the offset of
+	 * @param   string|boolean  timezone used as the baseline
+	 * @return  integer
+	 */
+	public static function offset($remote, $local = TRUE)
+	{
+		static $offsets;
+
+		// Default values
+		$remote = (string) $remote;
+		$local  = ($local === TRUE) ? date_default_timezone_get() : (string) $local;
+
+		// Cache key name
+		$cache = $remote.$local;
+
+		if (empty($offsets[$cache]))
+		{
+			// Create timezone objects
+			$remote = new DateTimeZone($remote);
+			$local  = new DateTimeZone($local);
+
+			// Create date objects from timezones
+			$time_there = new DateTime('now', $remote);
+			$time_here  = new DateTime('now', $local);
+
+			// Find the offset
+			$offsets[$cache] = $remote->getOffset($time_there) - $local->getOffset($time_here);
+		}
+
+		return $offsets[$cache];
+	}
+
+	/**
+	 * Number of seconds in a minute, incrementing by a step.
+	 *
+	 * @param   integer  amount to increment each step by, 1 to 30
+	 * @param   integer  start value
+	 * @param   integer  end value
+	 * @return  array    A mirrored (foo => foo) array from 1-60.
+	 */
+	public static function seconds($step = 1, $start = 0, $end = 60)
+	{
+		// Always integer
+		$step = (int) $step;
+
+		$seconds = array();
+
+		for ($i = $start; $i < $end; $i += $step)
+		{
+			$seconds[$i] = ($i < 10) ? '0'.$i : $i;
+		}
+
+		return $seconds;
+	}
+
+	/**
+	 * Number of minutes in an hour, incrementing by a step.
+	 *
+	 * @param   integer  amount to increment each step by, 1 to 30
+	 * @return  array    A mirrored (foo => foo) array from 1-60.
+	 */
+	public static function minutes($step = 5)
+	{
+		// Because there are the same number of minutes as seconds in this set,
+		// we choose to re-use seconds(), rather than creating an entirely new
+		// function. Shhhh, it's cheating! ;) There are several more of these
+		// in the following methods.
+		return date::seconds($step);
+	}
+
+	/**
+	 * Number of hours in a day.
+	 *
+	 * @param   integer  amount to increment each step by
+	 * @param   boolean  use 24-hour time
+	 * @param   integer  the hour to start at
+	 * @return  array    A mirrored (foo => foo) array from start-12 or start-23.
+	 */
+	public static function hours($step = 1, $long = FALSE, $start = NULL)
+	{
+		// Default values
+		$step = (int) $step;
+		$long = (bool) $long;
+		$hours = array();
+
+		// Set the default start if none was specified.
+		if ($start === NULL)
+		{
+			$start = ($long === FALSE) ? 1 : 0;
+		}
+
+		$hours = array();
+
+		// 24-hour time has 24 hours, instead of 12
+		$size = ($long === TRUE) ? 23 : 12;
+
+		for ($i = $start; $i <= $size; $i += $step)
+		{
+			$hours[$i] = $i;
+		}
+
+		return $hours;
+	}
+
+	/**
+	 * Returns AM or PM, based on a given hour.
+	 *
+	 * @param   integer  number of the hour
+	 * @return  string
+	 */
+	public static function ampm($hour)
+	{
+		// Always integer
+		$hour = (int) $hour;
+
+		return ($hour > 11) ? 'PM' : 'AM';
+	}
+
+	/**
+	 * Adjusts a non-24-hour number into a 24-hour number.
+	 *
+	 * @param   integer  hour to adjust
+	 * @param   string   AM or PM
+	 * @return  string
+	 */
+	public static function adjust($hour, $ampm)
+	{
+		$hour = (int) $hour;
+		$ampm = strtolower($ampm);
+
+		switch ($ampm)
+		{
+			case 'am':
+				if ($hour == 12)
+					$hour = 0;
+			break;
+			case 'pm':
+				if ($hour < 12)
+					$hour += 12;
+			break;
+		}
+
+		return sprintf('%02s', $hour);
+	}
+
+	/**
+	 * Number of days in month.
+	 *
+	 * @param   integer  number of month
+	 * @param   integer  number of year to check month, defaults to the current year
+	 * @return  array    A mirrored (foo => foo) array of the days.
+	 */
+	public static function days($month, $year = FALSE)
+	{
+		static $months;
+
+		// Always integers
+		$month = (int) $month;
+		$year  = (int) $year;
+
+		// Use the current year by default
+		$year  = ($year == FALSE) ? date('Y') : $year;
+
+		// We use caching for months, because time functions are used
+		if (empty($months[$year][$month]))
+		{
+			$months[$year][$month] = array();
+
+			// Use date to find the number of days in the given month
+			$total = date('t', mktime(1, 0, 0, $month, 1, $year)) + 1;
+
+			for ($i = 1; $i < $total; $i++)
+			{
+				$months[$year][$month][$i] = $i;
+			}
+		}
+
+		return $months[$year][$month];
+	}
+
+	/**
+	 * Number of months in a year
+	 *
+	 * @return  array  A mirrored (foo => foo) array from 1-12.
+	 */
+	public static function months()
+	{
+		return date::hours();
+	}
+
+	/**
+	 * Returns an array of years between a starting and ending year.
+	 * Uses the current year +/- 5 as the max/min.
+	 *
+	 * @param   integer  starting year
+	 * @param   integer  ending year
+	 * @return  array
+	 */
+	public static function years($start = FALSE, $end = FALSE)
+	{
+		// Default values
+		$start = ($start === FALSE) ? date('Y') - 5 : (int) $start;
+		$end   = ($end   === FALSE) ? date('Y') + 5 : (int) $end;
+
+		$years = array();
+
+		// Add one, so that "less than" works
+		$end += 1;
+
+		for ($i = $start; $i < $end; $i++)
+		{
+			$years[$i] = $i;
+		}
+
+		return $years;
+	}
+
+	/**
+	 * Returns time difference between two timestamps, in human readable format.
+	 *
+	 * @param   integer       timestamp
+	 * @param   integer       timestamp, defaults to the current time
+	 * @param   string        formatting string
+	 * @return  string|array
+	 */
+	public static function timespan($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
+	{
+		// Array with the output formats
+		$output = preg_split('/[^a-z]+/', strtolower((string) $output));
+
+		// Invalid output
+		if (empty($output))
+			return FALSE;
+
+		// Make the output values into keys
+		extract(array_flip($output), EXTR_SKIP);
+
+		// Default values
+		$time1  = max(0, (int) $time1);
+		$time2  = empty($time2) ? time() : max(0, (int) $time2);
+
+		// Calculate timespan (seconds)
+		$timespan = abs($time1 - $time2);
+
+		// All values found using Google Calculator.
+		// Years and months do not match the formula exactly, due to leap years.
+
+		// Years ago, 60 * 60 * 24 * 365
+		isset($years) and $timespan -= 31556926 * ($years = (int) floor($timespan / 31556926));
+
+		// Months ago, 60 * 60 * 24 * 30
+		isset($months) and $timespan -= 2629744 * ($months = (int) floor($timespan / 2629743.83));
+
+		// Weeks ago, 60 * 60 * 24 * 7
+		isset($weeks) and $timespan -= 604800 * ($weeks = (int) floor($timespan / 604800));
+
+		// Days ago, 60 * 60 * 24
+		isset($days) and $timespan -= 86400 * ($days = (int) floor($timespan / 86400));
+
+		// Hours ago, 60 * 60
+		isset($hours) and $timespan -= 3600 * ($hours = (int) floor($timespan / 3600));
+
+		// Minutes ago, 60
+		isset($minutes) and $timespan -= 60 * ($minutes = (int) floor($timespan / 60));
+
+		// Seconds ago, 1
+		isset($seconds) and $seconds = $timespan;
+
+		// Remove the variables that cannot be accessed
+		unset($timespan, $time1, $time2);
+
+		// Deny access to these variables
+		$deny = array_flip(array('deny', 'key', 'difference', 'output'));
+
+		// Return the difference
+		$difference = array();
+		foreach ($output as $key)
+		{
+			if (isset($$key) AND ! isset($deny[$key]))
+			{
+				// Add requested key to the output
+				$difference[$key] = $$key;
+			}
+		}
+
+		// Invalid output formats string
+		if (empty($difference))
+			return FALSE;
+
+		// If only one output format was asked, don't put it in an array
+		if (count($difference) === 1)
+			return current($difference);
+
+		// Return array
+		return $difference;
+	}
+
+	/**
+	 * Returns time difference between two timestamps, in the format:
+	 * N year, N months, N weeks, N days, N hours, N minutes, and N seconds ago
+	 *
+	 * @param   integer       timestamp
+	 * @param   integer       timestamp, defaults to the current time
+	 * @param   string        formatting string
+	 * @return  string
+	 */
+	public static function timespan_string($time1, $time2 = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds')
+	{
+		if ($difference = date::timespan($time1, $time2, $output) AND is_array($difference))
+		{
+			// Determine the key of the last item in the array
+			$last = end($difference);
+			$last = key($difference);
+
+			$span = array();
+			foreach ($difference as $name => $amount)
+			{
+				if ($name !== $last AND $amount === 0)
+				{
+					// Skip empty amounts
+					continue;
+				}
+
+				// Add the amount to the span
+				$span[] = ($name === $last ? ' and ' : ', ').$amount.' '.($amount === 1 ? inflector::singular($name) : $name);
+			}
+
+			// Replace difference by making the span into a string
+			$difference = trim(implode('', $span), ',');
+		}
+		elseif (is_int($difference))
+		{
+			// Single-value return
+			$difference = $difference.' '.($difference === 1 ? inflector::singular($output) : $output);
+		}
+
+		return $difference;
+	}
+
+} // End date
\ No newline at end of file
diff --git a/system/helpers/download.php b/system/helpers/download.php
new file mode 100755
index 0000000..f9ea310
--- /dev/null
+++ b/system/helpers/download.php
@@ -0,0 +1,105 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Download helper class.
+ *
+ * $Id: download.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class download_Core {
+
+	/**
+	 * Force a download of a file to the user's browser. This function is
+	 * binary-safe and will work with any MIME type that Kohana is aware of.
+	 *
+	 * @param   string  a file path or file name
+	 * @param   mixed   data to be sent if the filename does not exist
+	 * @param   string  suggested filename to display in the download
+	 * @return  void
+	 */
+	public static function force($filename = NULL, $data = NULL, $nicename = NULL)
+	{
+		if (empty($filename))
+			return FALSE;
+
+		if (is_file($filename))
+		{
+			// Get the real path
+			$filepath = str_replace('\\', '/', realpath($filename));
+
+			// Set filesize
+			$filesize = filesize($filepath);
+
+			// Get filename
+			$filename = substr(strrchr('/'.$filepath, '/'), 1);
+
+			// Get extension
+			$extension = strtolower(substr(strrchr($filepath, '.'), 1));
+		}
+		else
+		{
+			// Get filesize
+			$filesize = strlen($data);
+
+			// Make sure the filename does not have directory info
+			$filename = substr(strrchr('/'.$filename, '/'), 1);
+
+			// Get extension
+			$extension = strtolower(substr(strrchr($filename, '.'), 1));
+		}
+
+		// Get the mime type of the file
+		$mime = Kohana::config('mimes.'.$extension);
+
+		if (empty($mime))
+		{
+			// Set a default mime if none was found
+			$mime = array('application/octet-stream');
+		}
+
+		// Generate the server headers
+		header('Content-Type: '.$mime[0]);
+		header('Content-Disposition: attachment; filename="'.(empty($nicename) ? $filename : $nicename).'"');
+		header('Content-Transfer-Encoding: binary');
+		header('Content-Length: '.sprintf('%d', $filesize));
+
+		// More caching prevention
+		header('Expires: 0');
+
+		if (Kohana::user_agent('browser') === 'Internet Explorer')
+		{
+			// Send IE headers
+			header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+			header('Pragma: public');
+		}
+		else
+		{
+			// Send normal headers
+			header('Pragma: no-cache');
+		}
+
+		// Clear the output buffer
+		Kohana::close_buffers(FALSE);
+
+		if (isset($filepath))
+		{
+			// Open the file
+			$handle = fopen($filepath, 'rb');
+
+			// Send the file data
+			fpassthru($handle);
+
+			// Close the file
+			fclose($handle);
+		}
+		else
+		{
+			// Send the file data
+			echo $data;
+		}
+	}
+
+} // End download
\ No newline at end of file
diff --git a/system/helpers/email.php b/system/helpers/email.php
new file mode 100755
index 0000000..e67ef8f
--- /dev/null
+++ b/system/helpers/email.php
@@ -0,0 +1,187 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Email helper class.
+ *
+ * $Id: email.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class email_Core {
+
+	// SwiftMailer instance
+	protected static $mail;
+
+	/**
+	 * Creates a SwiftMailer instance.
+	 *
+	 * @param   string  DSN connection string
+	 * @return  object  Swift object
+	 */
+	public static function connect($config = NULL)
+	{
+		if ( ! class_exists('Swift', FALSE))
+		{
+			// Load SwiftMailer
+			require Kohana::find_file('vendor', 'swift/Swift');
+
+			// Register the Swift ClassLoader as an autoload
+			spl_autoload_register(array('Swift_ClassLoader', 'load'));
+		}
+
+		// Load default configuration
+		($config === NULL) and $config = Kohana::config('email');
+
+		switch ($config['driver'])
+		{
+			case 'smtp':
+				// Set port
+				$port = empty($config['options']['port']) ? NULL : (int) $config['options']['port'];
+
+				if (empty($config['options']['encryption']))
+				{
+					// No encryption
+					$encryption = Swift_Connection_SMTP::ENC_OFF;
+				}
+				else
+				{
+					// Set encryption
+					switch (strtolower($config['options']['encryption']))
+					{
+						case 'tls': $encryption = Swift_Connection_SMTP::ENC_TLS; break;
+						case 'ssl': $encryption = Swift_Connection_SMTP::ENC_SSL; break;
+					}
+				}
+
+				// Create a SMTP connection
+				$connection = new Swift_Connection_SMTP($config['options']['hostname'], $port, $encryption);
+
+				// Do authentication, if part of the DSN
+				empty($config['options']['username']) or $connection->setUsername($config['options']['username']);
+				empty($config['options']['password']) or $connection->setPassword($config['options']['password']);
+
+				if ( ! empty($config['options']['auth']))
+				{
+					// Get the class name and params
+					list ($class, $params) = arr::callback_string($config['options']['auth']);
+
+					if ($class === 'PopB4Smtp')
+					{
+						// Load the PopB4Smtp class manually, due to its odd filename
+						require Kohana::find_file('vendor', 'swift/Swift/Authenticator/$PopB4Smtp$');
+					}
+
+					// Prepare the class name for auto-loading
+					$class = 'Swift_Authenticator_'.$class;
+
+					// Attach the authenticator
+					$connection->attachAuthenticator(($params === NULL) ? new $class : new $class($params[0]));
+				}
+
+				// Set the timeout to 5 seconds
+				$connection->setTimeout(empty($config['options']['timeout']) ? 5 : (int) $config['options']['timeout']);
+			break;
+			case 'sendmail':
+				// Create a sendmail connection
+				$connection = new Swift_Connection_Sendmail
+				(
+					empty($config['options']) ? Swift_Connection_Sendmail::AUTO_DETECT : $config['options']
+				);
+
+				// Set the timeout to 5 seconds
+				$connection->setTimeout(5);
+			break;
+			default:
+				// Use the native connection
+				$connection = new Swift_Connection_NativeMail($config['options']);
+			break;
+		}
+
+		// Create the SwiftMailer instance
+		return email::$mail = new Swift($connection);
+	}
+
+	/**
+	 * Send an email message.
+	 *
+	 * @param   string|array  recipient email (and name), or an array of To, Cc, Bcc names
+	 * @param   string|array  sender email (and name)
+	 * @param   string        message subject
+	 * @param   string        message body
+	 * @param   boolean       send email as HTML
+	 * @return  integer       number of emails sent
+	 */
+	public static function send($to, $from, $subject, $content, $html = FALSE)
+	{
+		// Connect to SwiftMailer
+		(email::$mail === NULL) and email::connect();
+
+		// Determine the message type
+		$header_type = ($html === TRUE) ? 'text/html' : 'text/plain';
+
+		// Create the message
+		$message = new Swift_Message($subject, $content, $header_type, '8bit', 'utf-8');
+
+		if ($html === TRUE)
+		{
+			$html2text = new Html2Text($content);
+			$message->attach(new Swift_Message_Part($html2text->get_text(), 'text/plain'));
+		}
+
+		if (is_string($to))
+		{
+			// Single recipient
+			$recipients = new Swift_Address($to);
+		}
+		elseif (is_array($to))
+		{
+			if (isset($to[0]) AND isset($to[1]))
+			{
+				// Create To: address set
+				$to = array('to' => $to);
+			}
+
+			// Create a list of recipients
+			$recipients = new Swift_RecipientList;
+
+			foreach ($to as $method => $set)
+			{
+				if ( ! in_array($method, array('to', 'cc', 'bcc')))
+				{
+					// Use To: by default
+					$method = 'to';
+				}
+
+				// Create method name
+				$method = 'add'.ucfirst($method);
+
+				if (is_array($set))
+				{
+					// Add a recipient with name
+					$recipients->$method($set[0], $set[1]);
+				}
+				else
+				{
+					// Add a recipient without name
+					$recipients->$method($set);
+				}
+			}
+		}
+
+		if (is_string($from))
+		{
+			// From without a name
+			$from = new Swift_Address($from);
+		}
+		elseif (is_array($from))
+		{
+			// From with a name
+			$from = new Swift_Address($from[0], $from[1]);
+		}
+
+		return email::$mail->send($message, $recipients, $from);
+	}
+
+} // End email
\ No newline at end of file
diff --git a/system/helpers/expires.php b/system/helpers/expires.php
new file mode 100755
index 0000000..c9dc927
--- /dev/null
+++ b/system/helpers/expires.php
@@ -0,0 +1,110 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Controls headers that effect client caching of pages
+ *
+ * $Id: expires.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class expires_Core {
+
+	/**
+	 * Sets the amount of time before a page expires
+	 *
+	 * @param  integer Seconds before the page expires 
+	 * @return boolean
+	 */
+	public static function set($seconds = 60)
+	{
+		if (expires::check_headers())
+		{
+			$now = $expires = time();
+
+			// Set the expiration timestamp
+			$expires += $seconds;
+
+			// Send headers
+			header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $now));
+			header('Expires: '.gmdate('D, d M Y H:i:s T', $expires));
+			header('Cache-Control: max-age='.$seconds);
+
+			return $expires;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Checks to see if a page should be updated or send Not Modified status
+	 *
+	 * @param   integer  Seconds added to the modified time received to calculate what should be sent
+	 * @return  bool     FALSE when the request needs to be updated
+	 */
+	public static function check($seconds = 60)
+	{
+		if ( ! empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) AND expires::check_headers())
+		{
+			if (($strpos = strpos($_SERVER['HTTP_IF_MODIFIED_SINCE'], ';')) !== FALSE)
+			{
+				// IE6 and perhaps other IE versions send length too, compensate here
+				$mod_time = substr($_SERVER['HTTP_IF_MODIFIED_SINCE'], 0, $strpos);
+			}
+			else
+			{
+				$mod_time = $_SERVER['HTTP_IF_MODIFIED_SINCE'];
+			}
+
+			$mod_time = strtotime($mod_time);
+			$mod_time_diff = $mod_time + $seconds - time();
+
+			if ($mod_time_diff > 0)
+			{
+				// Re-send headers
+				header('Last-Modified: '.gmdate('D, d M Y H:i:s T', $mod_time));
+				header('Expires: '.gmdate('D, d M Y H:i:s T', time() + $mod_time_diff));
+				header('Cache-Control: max-age='.$mod_time_diff);
+				header('Status: 304 Not Modified', TRUE, 304);
+
+				// Prevent any output
+				Event::add('system.display', array('expires', 'prevent_output'));
+
+				// Exit to prevent other output
+				exit;
+			}
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Check headers already created to not step on download or Img_lib's feet
+	 *
+	 * @return boolean
+	 */
+	public static function check_headers()
+	{
+		foreach (headers_list() as $header)
+		{
+			if (stripos($header, 'Last-Modified:') === 0 OR stripos($header, 'Expires:') === 0)
+			{
+				return FALSE;
+			}
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Prevent any output from being displayed. Executed during system.display.
+	 *
+	 * @return  void
+	 */
+	public static function prevent_output()
+	{
+		Kohana::$output = '';
+	}
+
+} // End expires
\ No newline at end of file
diff --git a/system/helpers/feed.php b/system/helpers/feed.php
new file mode 100755
index 0000000..a705842
--- /dev/null
+++ b/system/helpers/feed.php
@@ -0,0 +1,116 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Feed helper class.
+ *
+ * $Id: feed.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class feed_Core {
+
+	/**
+	 * Parses a remote feed into an array.
+	 *
+	 * @param   string   remote feed URL
+	 * @param   integer  item limit to fetch
+	 * @return  array
+	 */
+	public static function parse($feed, $limit = 0)
+	{
+		// Make limit an integer
+		$limit = (int) $limit;
+
+		// Disable error reporting while opening the feed
+		$ER = error_reporting(0);
+
+		// Allow loading by filename or raw XML string
+		$load = (is_file($feed) OR valid::url($feed)) ? 'simplexml_load_file' : 'simplexml_load_string';
+
+		// Load the feed
+		$feed = $load($feed, 'SimpleXMLElement', LIBXML_NOCDATA);
+
+		// Restore error reporting
+		error_reporting($ER);
+
+		// Feed could not be loaded
+		if ($feed === FALSE)
+			return array();
+
+		// Detect the feed type. RSS 1.0/2.0 and Atom 1.0 are supported.
+		$feed = isset($feed->channel) ? $feed->xpath('//item') : $feed->entry;
+
+		$i = 0;
+		$items = array();
+
+		foreach ($feed as $item)
+		{
+			if ($limit > 0 AND $i++ === $limit)
+				break;
+
+			$items[] = (array) $item;
+		}
+
+		return $items;
+	}
+
+	/**
+	 * Creates a feed from the given parameters.
+	 *
+	 * @param   array   feed information
+	 * @param   array   items to add to the feed
+	 * @return  string
+	 */
+	public static function create($info, $items, $format = 'rss2')
+	{
+		$info += array('title' => 'Generated Feed', 'link' => '', 'generator' => 'KohanaPHP');
+
+		$feed = '<?xml version="1.0"?><rss version="2.0"><channel></channel></rss>';
+		$feed = simplexml_load_string($feed);
+
+		foreach ($info as $name => $value)
+		{
+			if (($name === 'pubDate' OR $name === 'lastBuildDate') AND (is_int($value) OR ctype_digit($value)))
+			{
+				// Convert timestamps to RFC 822 formatted dates
+				$value = date(DATE_RFC822, $value);
+			}
+			elseif (($name === 'link' OR $name === 'docs') AND strpos($value, '://') === FALSE)
+			{
+				// Convert URIs to URLs
+				$value = url::site($value, 'http');
+			}
+
+			// Add the info to the channel
+			$feed->channel->addChild($name, $value);
+		}
+
+		foreach ($items as $item)
+		{
+			// Add the item to the channel
+			$row = $feed->channel->addChild('item');
+
+			foreach ($item as $name => $value)
+			{
+				if ($name === 'pubDate' AND (is_int($value) OR ctype_digit($value)))
+				{
+					// Convert timestamps to RFC 822 formatted dates
+					$value = date(DATE_RFC822, $value);
+				}
+				elseif (($name === 'link' OR $name === 'guid') AND strpos($value, '://') === FALSE)
+				{
+					// Convert URIs to URLs
+					$value = url::site($value, 'http');
+				}
+
+				// Add the info to the row
+				$row->addChild($name, $value);
+			}
+		}
+
+		return $feed->asXML();
+	}
+
+} // End feed
\ No newline at end of file
diff --git a/system/helpers/file.php b/system/helpers/file.php
new file mode 100755
index 0000000..9dede53
--- /dev/null
+++ b/system/helpers/file.php
@@ -0,0 +1,186 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * File helper class.
+ *
+ * $Id: file.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class file_Core {
+
+	/**
+	 * Attempt to get the mime type from a file. This method is horribly
+	 * unreliable, due to PHP being horribly unreliable when it comes to
+	 * determining the mime-type of a file.
+	 *
+	 * @param   string   filename
+	 * @return  string   mime-type, if found
+	 * @return  boolean  FALSE, if not found
+	 */
+	public static function mime($filename)
+	{
+		// Make sure the file is readable
+		if ( ! (is_file($filename) AND is_readable($filename)))
+			return FALSE;
+
+		// Get the extension from the filename
+		$extension = strtolower(substr(strrchr($filename, '.'), 1));
+
+		if (preg_match('/^(?:jpe?g|png|[gt]if|bmp|swf)$/', $extension))
+		{
+			// Disable error reporting
+			$ER = error_reporting(0);
+
+			// Use getimagesize() to find the mime type on images
+			$mime = getimagesize($filename);
+
+			// Turn error reporting back on
+			error_reporting($ER);
+
+			// Return the mime type
+			if (isset($mime['mime']))
+				return $mime['mime'];
+		}
+
+		if (function_exists('finfo_open'))
+		{
+			// Use the fileinfo extension
+			$finfo = finfo_open(FILEINFO_MIME);
+			$mime  = finfo_file($finfo, $filename);
+			finfo_close($finfo);
+
+			// Return the mime type
+			return $mime;
+		}
+
+		if (ini_get('mime_magic.magicfile') AND function_exists('mime_content_type'))
+		{
+			// Return the mime type using mime_content_type
+			return mime_content_type($filename);
+		}
+
+		if ( ! KOHANA_IS_WIN)
+		{
+			// Attempt to locate use the file command, checking the return value
+			if ($command = trim(exec('which file', $output, $return)) AND $return === 0)
+			{
+				return trim(exec($command.' -bi '.escapeshellarg($filename)));
+			}
+		}
+
+		if ( ! empty($extension) AND is_array($mime = Kohana::config('mimes.'.$extension)))
+		{
+			// Return the mime-type guess, based on the extension
+			return $mime[0];
+		}
+
+		// Unable to find the mime-type
+		return FALSE;
+	}
+
+	/**
+	 * Split a file into pieces matching a specific size.
+	 *
+	 * @param   string   file to be split
+	 * @param   string   directory to output to, defaults to the same directory as the file
+	 * @param   integer  size, in MB, for each chunk to be
+	 * @return  integer  The number of pieces that were created.
+	 */
+	public static function split($filename, $output_dir = FALSE, $piece_size = 10)
+	{
+		// Find output dir
+		$output_dir = ($output_dir == FALSE) ? pathinfo(str_replace('\\', '/', realpath($filename)), PATHINFO_DIRNAME) : str_replace('\\', '/', realpath($output_dir));
+		$output_dir = rtrim($output_dir, '/').'/';
+
+		// Open files for writing
+		$input_file = fopen($filename, 'rb');
+
+		// Change the piece size to bytes
+		$piece_size = 1024 * 1024 * (int) $piece_size; // Size in bytes
+
+		// Set up reading variables
+		$read  = 0; // Number of bytes read
+		$piece = 1; // Current piece
+		$chunk = 1024 * 8; // Chunk size to read
+
+		// Split the file
+		while ( ! feof($input_file))
+		{
+			// Open a new piece
+			$piece_name = $filename.'.'.str_pad($piece, 3, '0', STR_PAD_LEFT);
+			$piece_open = @fopen($piece_name, 'wb+') or die('Could not write piece '.$piece_name);
+
+			// Fill the current piece
+			while ($read < $piece_size AND $data = fread($input_file, $chunk))
+			{
+				fwrite($piece_open, $data) or die('Could not write to open piece '.$piece_name);
+				$read += $chunk;
+			}
+
+			// Close the current piece
+			fclose($piece_open);
+
+			// Prepare to open a new piece
+			$read = 0;
+			$piece++;
+
+			// Make sure that piece is valid
+			($piece < 999) or die('Maximum of 999 pieces exceeded, try a larger piece size');
+		}
+
+		// Close input file
+		fclose($input_file);
+
+		// Returns the number of pieces that were created
+		return ($piece - 1);
+	}
+
+	/**
+	 * Join a split file into a whole file.
+	 *
+	 * @param   string   split filename, without .000 extension
+	 * @param   string   output filename, if different then an the filename
+	 * @return  integer  The number of pieces that were joined.
+	 */
+	public static function join($filename, $output = FALSE)
+	{
+		if ($output == FALSE)
+			$output = $filename;
+
+		// Set up reading variables
+		$piece = 1; // Current piece
+		$chunk = 1024 * 8; // Chunk size to read
+
+		// Open output file
+		$output_file = @fopen($output, 'wb+') or die('Could not open output file '.$output);
+
+		// Read each piece
+		while ($piece_open = @fopen(($piece_name = $filename.'.'.str_pad($piece, 3, '0', STR_PAD_LEFT)), 'rb'))
+		{
+			// Write the piece into the output file
+			while ( ! feof($piece_open))
+			{
+				fwrite($output_file, fread($piece_open, $chunk));
+			}
+
+			// Close the current piece
+			fclose($piece_open);
+
+			// Prepare for a new piece
+			$piece++;
+
+			// Make sure piece is valid
+			($piece < 999) or die('Maximum of 999 pieces exceeded');
+		}
+
+		// Close the output file
+		fclose($output_file);
+
+		// Return the number of pieces joined
+		return ($piece - 1);
+	}
+
+} // End file
\ No newline at end of file
diff --git a/system/helpers/form.php b/system/helpers/form.php
new file mode 100755
index 0000000..eea4f7c
--- /dev/null
+++ b/system/helpers/form.php
@@ -0,0 +1,544 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Form helper class.
+ *
+ * $Id: form.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class form_Core {
+
+	/**
+	 * Generates an opening HTML form tag.
+	 *
+	 * @param   string  form action attribute
+	 * @param   array   extra attributes
+	 * @param   array   hidden fields to be created immediately after the form tag
+	 * @return  string
+	 */
+	public static function open($action = NULL, $attr = array(), $hidden = NULL)
+	{
+		// Make sure that the method is always set
+		empty($attr['method']) and $attr['method'] = 'post';
+
+		if ($attr['method'] !== 'post' AND $attr['method'] !== 'get')
+		{
+			// If the method is invalid, use post
+			$attr['method'] = 'post';
+		}
+
+		if ($action === NULL)
+		{
+			// Use the current URL as the default action
+			$action = url::site(Router::$complete_uri);
+		}
+		elseif (strpos($action, '://') === FALSE)
+		{
+			// Make the action URI into a URL
+			$action = url::site($action);
+		}
+
+		// Set action
+		$attr['action'] = $action;
+
+		// Form opening tag
+		$form = '<form'.form::attributes($attr).'>'."\n";
+
+		// Add hidden fields immediate after opening tag
+		empty($hidden) or $form .= form::hidden($hidden);
+
+		return $form;
+	}
+
+	/**
+	 * Generates an opening HTML form tag that can be used for uploading files.
+	 *
+	 * @param   string  form action attribute
+	 * @param   array   extra attributes
+	 * @param   array   hidden fields to be created immediately after the form tag
+	 * @return  string
+	 */
+	public static function open_multipart($action = NULL, $attr = array(), $hidden = array())
+	{
+		// Set multi-part form type
+		$attr['enctype'] = 'multipart/form-data';
+
+		return form::open($action, $attr, $hidden);
+	}
+
+	/**
+	 * Generates a fieldset opening tag.
+	 *
+	 * @param   array   html attributes
+	 * @param   string  a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function open_fieldset($data = NULL, $extra = '')
+	{
+		return '<fieldset'.html::attributes((array) $data).' '.$extra.'>'."\n";
+	}
+
+	/**
+	 * Generates a fieldset closing tag.
+	 *
+	 * @return  string
+	 */
+	public static function close_fieldset()
+	{
+		return '</fieldset>'."\n";
+	}
+
+	/**
+	 * Generates a legend tag for use with a fieldset.
+	 *
+	 * @param   string  legend text
+	 * @param   array   HTML attributes
+	 * @param   string  a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function legend($text = '', $data = NULL, $extra = '')
+	{
+		return '<legend'.form::attributes((array) $data).' '.$extra.'>'.$text.'</legend>'."\n";
+	}
+
+	/**
+	 * Generates hidden form fields.
+	 * You can pass a simple key/value string or an associative array with multiple values.
+	 *
+	 * @param   string|array  input name (string) or key/value pairs (array)
+	 * @param   string        input value, if using an input name
+	 * @return  string
+	 */
+	public static function hidden($data, $value = '')
+	{
+		if ( ! is_array($data))
+		{
+			$data = array
+			(
+				$data => $value
+			);
+		}
+
+		$input = '';
+		foreach ($data as $name => $value)
+		{
+			$attr = array
+			(
+				'type'  => 'hidden',
+				'name'  => $name,
+				'value' => $value
+			);
+
+			$input .= form::input($attr)."\n";
+		}
+
+		return $input;
+	}
+
+	/**
+	 * Creates an HTML form input tag. Defaults to a text type.
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   string        input value, when using a name
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function input($data, $value = '', $extra = '')
+	{
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+
+		// Type and value are required attributes
+		$data += array
+		(
+			'type'  => 'text',
+			'value' => $value
+		);
+
+		return '<input'.form::attributes($data).' '.$extra.' />';
+	}
+
+	/**
+	 * Creates a HTML form password input tag.
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   string        input value, when using a name
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function password($data, $value = '', $extra = '')
+	{
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+
+		$data['type'] = 'password';
+
+		return form::input($data, $value, $extra);
+	}
+
+	/**
+	 * Creates an HTML form upload input tag.
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   string        input value, when using a name
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function upload($data, $value = '', $extra = '')
+	{
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+
+		$data['type'] = 'file';
+
+		return form::input($data, $value, $extra);
+	}
+
+	/**
+	 * Creates an HTML form textarea tag.
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   string        input value, when using a name
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @param   boolean       encode existing entities
+	 * @return  string
+	 */
+	public static function textarea($data, $value = '', $extra = '', $double_encode = TRUE )
+	{
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+
+		// Use the value from $data if possible, or use $value
+		$value = isset($data['value']) ? $data['value'] : $value;
+
+		// Value is not part of the attributes
+		unset($data['value']);
+
+		return '<textarea'.form::attributes($data, 'textarea').' '.$extra.'>'.html::specialchars($value, $double_encode).'</textarea>';
+	}
+
+	/**
+	 * Creates an HTML form select tag, or "dropdown menu".
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   array         select options, when using a name
+	 * @param   string        option key that should be selected by default
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function dropdown($data, $options = NULL, $selected = NULL, $extra = '')
+	{
+
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+		else
+		{
+			if (isset($data['options']))
+			{
+				// Use data options
+				$options = $data['options'];
+			}
+
+			if (isset($data['selected']))
+			{
+				// Use data selected
+				$selected = $data['selected'];
+			}
+		}
+
+		$input = '<select'.form::attributes($data, 'select').' '.$extra.'>'."\n";
+		foreach ((array) $options as $key => $val)
+		{
+			// Key should always be a string
+			$key = (string) $key;
+
+			// Selected must always be a string
+			$selected = (string) $selected;
+
+			if (is_array($val))
+			{
+				$input .= '<optgroup label="'.$key.'">'."\n";
+				foreach ($val as $inner_key => $inner_val)
+				{
+					// Inner key should always be a string
+					$inner_key = (string) $inner_key;
+
+					if (is_array($selected))
+					{
+						$sel = in_array($inner_key, $selected, TRUE);
+					}
+					else
+					{
+						$sel = ($selected === $inner_key);
+					}
+
+					$sel = ($sel === TRUE) ? ' selected="selected"' : '';
+					$input .= '<option value="'.$inner_key.'"'.$sel.'>'.$inner_val.'</option>'."\n";
+				}
+				$input .= '</optgroup>'."\n";
+			}
+			else
+			{
+				$sel = ($selected === $key) ? ' selected="selected"' : '';
+				$input .= '<option value="'.$key.'"'.$sel.'>'.$val.'</option>'."\n";
+			}
+		}
+		$input .= '</select>';
+
+		return $input;
+	}
+
+	/**
+	 * Creates an HTML form checkbox input tag.
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   string        input value, when using a name
+	 * @param   boolean       make the checkbox checked by default
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function checkbox($data, $value = '', $checked = FALSE, $extra = '')
+	{
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+
+		$data['type'] = 'checkbox';
+
+		if ($checked == TRUE OR (isset($data['checked']) AND $data['checked'] == TRUE))
+		{
+			$data['checked'] = 'checked';
+		}
+		else
+		{
+			unset($data['checked']);
+		}
+
+		return form::input($data, $value, $extra);
+	}
+
+	/**
+	 * Creates an HTML form radio input tag.
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   string        input value, when using a name
+	 * @param   boolean       make the radio selected by default
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function radio($data = '', $value = '', $checked = FALSE, $extra = '')
+	{
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+
+		$data['type'] = 'radio';
+
+		if ($checked == TRUE OR (isset($data['checked']) AND $data['checked'] == TRUE))
+		{
+			$data['checked'] = 'checked';
+		}
+		else
+		{
+			unset($data['checked']);
+		}
+
+		return form::input($data, $value, $extra);
+	}
+
+	/**
+	 * Creates an HTML form submit input tag.
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   string        input value, when using a name
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function submit($data = '', $value = '', $extra = '')
+	{
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+
+		if (empty($data['name']))
+		{
+			// Remove the name if it is empty
+			unset($data['name']);
+		}
+
+		$data['type'] = 'submit';
+
+		return form::input($data, $value, $extra);
+	}
+
+	/**
+	 * Creates an HTML form button input tag.
+	 *
+	 * @param   string|array  input name or an array of HTML attributes
+	 * @param   string        input value, when using a name
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function button($data = '', $value = '', $extra = '')
+	{
+		if ( ! is_array($data))
+		{
+			$data = array('name' => $data);
+		}
+
+		if (empty($data['name']))
+		{
+			// Remove the name if it is empty
+			unset($data['name']);
+		}
+
+		if (isset($data['value']) AND empty($value))
+		{
+			$value = arr::remove('value', $data);
+		}
+
+		return '<button'.form::attributes($data, 'button').' '.$extra.'>'.$value.'</button>';
+	}
+
+	/**
+	 * Closes an open form tag.
+	 *
+	 * @param   string  string to be attached after the closing tag
+	 * @return  string
+	 */
+	public static function close($extra = '')
+	{
+		return '</form>'."\n".$extra;
+	}
+
+	/**
+	 * Creates an HTML form label tag.
+	 *
+	 * @param   string|array  label "for" name or an array of HTML attributes
+	 * @param   string        label text or HTML
+	 * @param   string        a string to be attached to the end of the attributes
+	 * @return  string
+	 */
+	public static function label($data = '', $text = NULL, $extra = '')
+	{
+		if ( ! is_array($data))
+		{
+			if (is_string($data))
+			{
+				// Specify the input this label is for
+				$data = array('for' => $data);
+			}
+			else
+			{
+				// No input specified
+				$data = array();
+			}
+		}
+
+		if ($text === NULL AND isset($data['for']))
+		{
+			// Make the text the human-readable input name
+			$text = ucwords(inflector::humanize($data['for']));
+		}
+
+		return '<label'.form::attributes($data).' '.$extra.'>'.$text.'</label>';
+	}
+
+	/**
+	 * Sorts a key/value array of HTML attributes, putting form attributes first,
+	 * and returns an attribute string.
+	 *
+	 * @param   array   HTML attributes array
+	 * @return  string
+	 */
+	public static function attributes($attr, $type = NULL)
+	{
+		if (empty($attr))
+			return '';
+
+		if (isset($attr['name']) AND empty($attr['id']) AND strpos($attr['name'], '[') === FALSE)
+		{
+			if ($type === NULL AND ! empty($attr['type']))
+			{
+				// Set the type by the attributes
+				$type = $attr['type'];
+			}
+
+			switch ($type)
+			{
+				case 'text':
+				case 'textarea':
+				case 'password':
+				case 'select':
+				case 'checkbox':
+				case 'file':
+				case 'image':
+				case 'button':
+				case 'submit':
+					// Only specific types of inputs use name to id matching
+					$attr['id'] = $attr['name'];
+				break;
+			}
+		}
+
+		$order = array
+		(
+			'action',
+			'method',
+			'type',
+			'id',
+			'name',
+			'value',
+			'src',
+			'size',
+			'maxlength',
+			'rows',
+			'cols',
+			'accept',
+			'tabindex',
+			'accesskey',
+			'align',
+			'alt',
+			'title',
+			'class',
+			'style',
+			'selected',
+			'checked',
+			'readonly',
+			'disabled'
+		);
+
+		$sorted = array();
+		foreach ($order as $key)
+		{
+			if (isset($attr[$key]))
+			{
+				// Move the attribute to the sorted array
+				$sorted[$key] = $attr[$key];
+
+				// Remove the attribute from unsorted array
+				unset($attr[$key]);
+			}
+		}
+
+		// Combine the sorted and unsorted attributes and create an HTML string
+		return html::attributes(array_merge($sorted, $attr));
+	}
+
+} // End form
\ No newline at end of file
diff --git a/system/helpers/format.php b/system/helpers/format.php
new file mode 100755
index 0000000..d99d2b7
--- /dev/null
+++ b/system/helpers/format.php
@@ -0,0 +1,66 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Format helper class.
+ *
+ * $Id: format.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class format_Core {
+
+	/**
+	 * Formats a phone number according to the specified format.
+	 *
+	 * @param   string  phone number
+	 * @param   string  format string
+	 * @return  string
+	 */
+	public static function phone($number, $format = '3-3-4')
+	{
+		// Get rid of all non-digit characters in number string
+		$number_clean = preg_replace('/\D+/', '', (string) $number);
+
+		// Array of digits we need for a valid format
+		$format_parts = preg_split('/[^1-9][^0-9]*/', $format, -1, PREG_SPLIT_NO_EMPTY);
+
+		// Number must match digit count of a valid format
+		if (strlen($number_clean) !== array_sum($format_parts))
+			return $number;
+
+		// Build regex
+		$regex = '(\d{'.implode('})(\d{', $format_parts).'})';
+
+		// Build replace string
+		for ($i = 1, $c = count($format_parts); $i <= $c; $i++)
+		{
+			$format = preg_replace('/(?<!\$)[1-9][0-9]*/', '\$'.$i, $format, 1);
+		}
+
+		// Hocus pocus!
+		return preg_replace('/^'.$regex.'$/', $format, $number_clean);
+	}
+
+	/**
+	 * Formats a URL to contain a protocol at the beginning.
+	 *
+	 * @param   string  possibly incomplete URL
+	 * @return  string
+	 */
+	public static function url($str = '')
+	{
+		// Clear protocol-only strings like "http://"
+		if ($str === '' OR substr($str, -3) === '://')
+			return '';
+
+		// If no protocol given, prepend "http://" by default
+		if (strpos($str, '://') === FALSE)
+			return 'http://'.$str;
+
+		// Return the original URL
+		return $str;
+	}
+
+} // End format
\ No newline at end of file
diff --git a/system/helpers/html.php b/system/helpers/html.php
new file mode 100755
index 0000000..c9712be
--- /dev/null
+++ b/system/helpers/html.php
@@ -0,0 +1,428 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * HTML helper class.
+ *
+ * $Id: html.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class html_Core {
+
+	// Enable or disable automatic setting of target="_blank"
+	public static $windowed_urls = FALSE;
+
+	/**
+	 * Convert special characters to HTML entities
+	 *
+	 * @param   string   string to convert
+	 * @param   boolean  encode existing entities
+	 * @return  string
+	 */
+	public static function specialchars($str, $double_encode = TRUE)
+	{
+		// Force the string to be a string
+		$str = (string) $str;
+
+		// Do encode existing HTML entities (default)
+		if ($double_encode === TRUE)
+		{
+			$str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
+		}
+		else
+		{
+			// Do not encode existing HTML entities
+			// From PHP 5.2.3 this functionality is built-in, otherwise use a regex
+			if (version_compare(PHP_VERSION, '5.2.3', '>='))
+			{
+				$str = htmlspecialchars($str, ENT_QUOTES, 'UTF-8', FALSE);
+			}
+			else
+			{
+				$str = preg_replace('/&(?!(?:#\d++|[a-z]++);)/ui', '&', $str);
+				$str = str_replace(array('<', '>', '\'', '"'), array('<', '>', ''', '"'), $str);
+			}
+		}
+
+		return $str;
+	}
+
+	/**
+	 * Create HTML link anchors.
+	 *
+	 * @param   string  URL or URI string
+	 * @param   string  link text
+	 * @param   array   HTML anchor attributes
+	 * @param   string  non-default protocol, eg: https
+	 * @return  string
+	 */
+	public static function anchor($uri, $title = NULL, $attributes = NULL, $protocol = NULL)
+	{
+		if ($uri === '')
+		{
+			$site_url = url::base(FALSE);
+		}
+		elseif (strpos($uri, '://') === FALSE AND strpos($uri, '#') !== 0)
+		{
+			$site_url = url::site($uri, $protocol);
+		}
+		else
+		{
+			if (html::$windowed_urls === TRUE AND empty($attributes['target']))
+			{
+				$attributes['target'] = '_blank';
+			}
+
+			$site_url = $uri;
+		}
+
+		return
+		// Parsed URL
+		'<a href="'.html::specialchars($site_url, FALSE).'"'
+		// Attributes empty? Use an empty string
+		.(is_array($attributes) ? html::attributes($attributes) : '').'>'
+		// Title empty? Use the parsed URL
+		.(($title === NULL) ? $site_url : $title).'</a>';
+	}
+
+	/**
+	 * Creates an HTML anchor to a file.
+	 *
+	 * @param   string  name of file to link to
+	 * @param   string  link text
+	 * @param   array   HTML anchor attributes
+	 * @param   string  non-default protocol, eg: ftp
+	 * @return  string
+	 */
+	public static function file_anchor($file, $title = NULL, $attributes = NULL, $protocol = NULL)
+	{
+		return
+		// Base URL + URI = full URL
+		'<a href="'.html::specialchars(url::base(FALSE, $protocol).$file, FALSE).'"'
+		// Attributes empty? Use an empty string
+		.(is_array($attributes) ? html::attributes($attributes) : '').'>'
+		// Title empty? Use the filename part of the URI
+		.(($title === NULL) ? end(explode('/', $file)) : $title) .'</a>';
+	}
+
+	/**
+	 * Similar to anchor, but with the protocol parameter first.
+	 *
+	 * @param   string  link protocol
+	 * @param   string  URI or URL to link to
+	 * @param   string  link text
+	 * @param   array   HTML anchor attributes
+	 * @return  string
+	 */
+	public static function panchor($protocol, $uri, $title = FALSE, $attributes = FALSE)
+	{
+		return html::anchor($uri, $title, $attributes, $protocol);
+	}
+
+	/**
+	 * Create an array of anchors from an array of link/title pairs.
+	 *
+	 * @param   array  link/title pairs
+	 * @return  array
+	 */
+	public static function anchor_array(array $array)
+	{
+		$anchors = array();
+		foreach ($array as $link => $title)
+		{
+			// Create list of anchors
+			$anchors[] = html::anchor($link, $title);
+		}
+		return $anchors;
+	}
+
+	/**
+	 * Generates an obfuscated version of an email address.
+	 *
+	 * @param   string  email address
+	 * @return  string
+	 */
+	public static function email($email)
+	{
+		$safe = '';
+		foreach (str_split($email) as $letter)
+		{
+			switch (($letter === '@') ? rand(1, 2) : rand(1, 3))
+			{
+				// HTML entity code
+				case 1: $safe .= '&#'.ord($letter).';'; break;
+				// Hex character code
+				case 2: $safe .= '&#x'.dechex(ord($letter)).';'; break;
+				// Raw (no) encoding
+				case 3: $safe .= $letter;
+			}
+		}
+
+		return $safe;
+	}
+
+	/**
+	 * Creates an email anchor.
+	 *
+	 * @param   string  email address to send to
+	 * @param   string  link text
+	 * @param   array   HTML anchor attributes
+	 * @return  string
+	 */
+	public static function mailto($email, $title = NULL, $attributes = NULL)
+	{
+		if (empty($email))
+			return $title;
+
+		// Remove the subject or other parameters that do not need to be encoded
+		if (strpos($email, '?') !== FALSE)
+		{
+			// Extract the parameters from the email address
+			list ($email, $params) = explode('?', $email, 2);
+
+			// Make the params into a query string, replacing spaces
+			$params = '?'.str_replace(' ', '%20', $params);
+		}
+		else
+		{
+			// No parameters
+			$params = '';
+		}
+
+		// Obfuscate email address
+		$safe = html::email($email);
+
+		// Title defaults to the encoded email address
+		empty($title) and $title = $safe;
+
+		// Parse attributes
+		empty($attributes) or $attributes = html::attributes($attributes);
+
+		// Encoded start of the href="" is a static encoded version of 'mailto:'
+		return '<a href="mailto:'.$safe.$params.'"'.$attributes.'>'.$title.'</a>';
+	}
+
+	/**
+	 * Generate a "breadcrumb" list of anchors representing the URI.
+	 *
+	 * @param   array   segments to use as breadcrumbs, defaults to using Router::$segments
+	 * @return  string
+	 */
+	public static function breadcrumb($segments = NULL)
+	{
+		empty($segments) and $segments = Router::$segments;
+
+		$array = array();
+		while ($segment = array_pop($segments))
+		{
+			$array[] = html::anchor
+			(
+				// Complete URI for the URL
+				implode('/', $segments).'/'.$segment,
+				// Title for the current segment
+				ucwords(inflector::humanize($segment))
+			);
+		}
+
+		// Retrun the array of all the segments
+		return array_reverse($array);
+	}
+
+	/**
+	 * Creates a meta tag.
+	 *
+	 * @param   string|array   tag name, or an array of tags
+	 * @param   string         tag "content" value
+	 * @return  string
+	 */
+	public static function meta($tag, $value = NULL)
+	{
+		if (is_array($tag))
+		{
+			$tags = array();
+			foreach ($tag as $t => $v)
+			{
+				// Build each tag and add it to the array
+				$tags[] = html::meta($t, $v);
+			}
+
+			// Return all of the tags as a string
+			return implode("\n", $tags);
+		}
+
+		// Set the meta attribute value
+		$attr = in_array(strtolower($tag), Kohana::config('http.meta_equiv')) ? 'http-equiv' : 'name';
+
+		return '<meta '.$attr.'="'.$tag.'" content="'.$value.'" />';
+	}
+
+	/**
+	 * Creates a stylesheet link.
+	 *
+	 * @param   string|array  filename, or array of filenames to match to array of medias
+	 * @param   string|array  media type of stylesheet, or array to match filenames
+	 * @param   boolean       include the index_page in the link
+	 * @return  string
+	 */
+	public static function stylesheet($style, $media = FALSE, $index = FALSE)
+	{
+		return html::link($style, 'stylesheet', 'text/css', '.css', $media, $index);
+	}
+
+	/**
+	 * Creates a link tag.
+	 *
+	 * @param   string|array  filename
+	 * @param   string|array  relationship
+	 * @param   string|array  mimetype
+	 * @param   string        specifies suffix of the file
+	 * @param   string|array  specifies on what device the document will be displayed
+	 * @param   boolean       include the index_page in the link
+	 * @return  string
+	 */
+	public static function link($href, $rel, $type, $suffix = FALSE, $media = FALSE, $index = FALSE)
+	{
+		$compiled = '';
+
+		if (is_array($href))
+		{
+			foreach ($href as $_href)
+			{
+				$_rel   = is_array($rel) ? array_shift($rel) : $rel;
+				$_type  = is_array($type) ? array_shift($type) : $type;
+				$_media = is_array($media) ? array_shift($media) : $media;
+
+				$compiled .= html::link($_href, $_rel, $_type, $suffix, $_media, $index);
+			}
+		}
+		else
+		{
+			if (strpos($href, '://') === FALSE)
+			{
+				// Make the URL absolute
+				$href = url::base($index).$href;
+			}
+
+			$length = strlen($suffix);
+
+			if ( $length > 0 AND substr_compare($href, $suffix, -$length, $length, FALSE) !== 0)
+			{
+				// Add the defined suffix
+				$href .= $suffix;
+			}
+
+			$attr = array
+			(
+				'rel' => $rel,
+				'type' => $type,
+				'href' => $href,
+			);
+
+			if ( ! empty($media))
+			{
+				// Add the media type to the attributes
+				$attr['media'] = $media;
+			}
+
+			$compiled = '<link'.html::attributes($attr).' />';
+		}
+
+		return $compiled."\n";
+	}
+
+	/**
+	 * Creates a script link.
+	 *
+	 * @param   string|array  filename
+	 * @param   boolean       include the index_page in the link
+	 * @return  string
+	 */
+	public static function script($script, $index = FALSE)
+	{
+		$compiled = '';
+
+		if (is_array($script))
+		{
+			foreach ($script as $name)
+			{
+				$compiled .= html::script($name, $index);
+			}
+		}
+		else
+		{
+			if (strpos($script, '://') === FALSE)
+			{
+				// Add the suffix only when it's not already present
+				$script = url::base((bool) $index).$script;
+			}
+
+			if (substr_compare($script, '.js', -3, 3, FALSE) !== 0)
+			{
+				// Add the javascript suffix
+				$script .= '.js';
+			}
+
+			$compiled = '<script type="text/javascript" src="'.$script.'"></script>';
+		}
+
+		return $compiled."\n";
+	}
+
+	/**
+	 * Creates a image link.
+	 *
+	 * @param   string        image source, or an array of attributes
+	 * @param   string|array  image alt attribute, or an array of attributes
+	 * @param   boolean       include the index_page in the link
+	 * @return  string
+	 */
+	public static function image($src = NULL, $alt = NULL, $index = FALSE)
+	{
+		// Create attribute list
+		$attributes = is_array($src) ? $src : array('src' => $src);
+
+		if (is_array($alt))
+		{
+			$attributes += $alt;
+		}
+		elseif ( ! empty($alt))
+		{
+			// Add alt to attributes
+			$attributes['alt'] = $alt;
+		}
+
+		if (strpos($attributes['src'], '://') === FALSE)
+		{
+			// Make the src attribute into an absolute URL
+			$attributes['src'] = url::base($index).$attributes['src'];
+		}
+
+		return '<img'.html::attributes($attributes).' />';
+	}
+
+	/**
+	 * Compiles an array of HTML attributes into an attribute string.
+	 *
+	 * @param   string|array  array of attributes
+	 * @return  string
+	 */
+	public static function attributes($attrs)
+	{
+		if (empty($attrs))
+			return '';
+
+		if (is_string($attrs))
+			return ' '.$attrs;
+
+		$compiled = '';
+		foreach ($attrs as $key => $val)
+		{
+			$compiled .= ' '.$key.'="'.html::specialchars($val).'"';
+		}
+
+		return $compiled;
+	}
+
+} // End html
diff --git a/system/helpers/inflector.php b/system/helpers/inflector.php
new file mode 100755
index 0000000..ccec515
--- /dev/null
+++ b/system/helpers/inflector.php
@@ -0,0 +1,193 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Inflector helper class.
+ *
+ * $Id: inflector.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class inflector_Core {
+
+	// Cached inflections
+	protected static $cache = array();
+
+	// Uncountable and irregular words
+	protected static $uncountable;
+	protected static $irregular;
+
+	/**
+	 * Checks if a word is defined as uncountable.
+	 *
+	 * @param   string   word to check
+	 * @return  boolean
+	 */
+	public static function uncountable($str)
+	{
+		if (self::$uncountable === NULL)
+		{
+			// Cache uncountables
+			self::$uncountable = Kohana::config('inflector.uncountable');
+
+			// Make uncountables mirroed
+			self::$uncountable = array_combine(self::$uncountable, self::$uncountable);
+		}
+
+		return isset(self::$uncountable[strtolower($str)]);
+	}
+
+	/**
+	 * Makes a plural word singular.
+	 *
+	 * @param   string   word to singularize
+	 * @param   integer  number of things
+	 * @return  string
+	 */
+	public static function singular($str, $count = NULL)
+	{
+		// Remove garbage
+		$str = strtolower(trim($str));
+
+		if (is_string($count))
+		{
+			// Convert to integer when using a digit string
+			$count = (int) $count;
+		}
+
+		// Do nothing with a single count
+		if ($count === 0 OR $count > 1)
+			return $str;
+
+		// Cache key name
+		$key = 'singular_'.$str.$count;
+
+		if (isset(self::$cache[$key]))
+			return self::$cache[$key];
+
+		if (inflector::uncountable($str))
+			return self::$cache[$key] = $str;
+
+		if (empty(self::$irregular))
+		{
+			// Cache irregular words
+			self::$irregular = Kohana::config('inflector.irregular');
+		}
+
+		if ($irregular = array_search($str, self::$irregular))
+		{
+			$str = $irregular;
+		}
+		elseif (preg_match('/[sxz]es$/', $str) OR preg_match('/[^aeioudgkprt]hes$/', $str))
+		{
+			// Remove "es"
+			$str = substr($str, 0, -2);
+		}
+		elseif (preg_match('/[^aeiou]ies$/', $str))
+		{
+			$str = substr($str, 0, -3).'y';
+		}
+		elseif (substr($str, -1) === 's' AND substr($str, -2) !== 'ss')
+		{
+			$str = substr($str, 0, -1);
+		}
+
+		return self::$cache[$key] = $str;
+	}
+
+	/**
+	 * Makes a singular word plural.
+	 *
+	 * @param   string  word to pluralize
+	 * @return  string
+	 */
+	public static function plural($str, $count = NULL)
+	{
+		// Remove garbage
+		$str = strtolower(trim($str));
+
+		if (is_string($count))
+		{
+			// Convert to integer when using a digit string
+			$count = (int) $count;
+		}
+
+		// Do nothing with singular
+		if ($count === 1)
+			return $str;
+
+		// Cache key name
+		$key = 'plural_'.$str.$count;
+
+		if (isset(self::$cache[$key]))
+			return self::$cache[$key];
+
+		if (inflector::uncountable($str))
+			return self::$cache[$key] = $str;
+
+		if (empty(self::$irregular))
+		{
+			// Cache irregular words
+			self::$irregular = Kohana::config('inflector.irregular');
+		}
+
+		if (isset(self::$irregular[$str]))
+		{
+			$str = self::$irregular[$str];
+		}
+		elseif (preg_match('/[sxz]$/', $str) OR preg_match('/[^aeioudgkprt]h$/', $str))
+		{
+			$str .= 'es';
+		}
+		elseif (preg_match('/[^aeiou]y$/', $str))
+		{
+			// Change "y" to "ies"
+			$str = substr_replace($str, 'ies', -1);
+		}
+		else
+		{
+			$str .= 's';
+		}
+
+		// Set the cache and return
+		return self::$cache[$key] = $str;
+	}
+
+	/**
+	 * Makes a phrase camel case.
+	 *
+	 * @param   string  phrase to camelize
+	 * @return  string
+	 */
+	public static function camelize($str)
+	{
+		$str = 'x'.strtolower(trim($str));
+		$str = ucwords(preg_replace('/[\s_]+/', ' ', $str));
+
+		return substr(str_replace(' ', '', $str), 1);
+	}
+
+	/**
+	 * Makes a phrase underscored instead of spaced.
+	 *
+	 * @param   string  phrase to underscore
+	 * @return  string
+	 */
+	public static function underscore($str)
+	{
+		return preg_replace('/\s+/', '_', trim($str));
+	}
+
+	/**
+	 * Makes an underscored or dashed phrase human-reable.
+	 *
+	 * @param   string  phrase to make human-reable
+	 * @return  string
+	 */
+	public static function humanize($str)
+	{
+		return preg_replace('/[_-]+/', ' ', trim($str));
+	}
+
+} // End inflector
\ No newline at end of file
diff --git a/system/helpers/num.php b/system/helpers/num.php
new file mode 100755
index 0000000..b7e48cf
--- /dev/null
+++ b/system/helpers/num.php
@@ -0,0 +1,26 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Number helper class.
+ *
+ * $Id: num.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class num_Core {
+
+	/**
+	 * Round a number to the nearest nth
+	 *
+	 * @param   integer  number to round
+	 * @param   integer  number to round to
+	 * @return  integer
+	 */
+	public static function round($number, $nearest = 5)
+	{
+		return round($number / $nearest) * $nearest;
+	}
+
+} // End num
\ No newline at end of file
diff --git a/system/helpers/remote.php b/system/helpers/remote.php
new file mode 100755
index 0000000..5d601a9
--- /dev/null
+++ b/system/helpers/remote.php
@@ -0,0 +1,71 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Remote url/file helper.
+ *
+ * $Id: remote.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class remote_Core {
+
+	public static function status($url)
+	{
+		if ( ! valid::url($url, 'http'))
+			return FALSE;
+
+		// Get the hostname and path
+		$url = parse_url($url);
+
+		if (empty($url['path']))
+		{
+			// Request the root document
+			$url['path'] = '/';
+		}
+
+		// Open a remote connection
+		if(isset($_SERVER["SERVER_PORT"])) {
+			$server_port = $_SERVER["SERVER_PORT"];
+		} else {
+			$server_port = '80';
+		}
+		$remote = fsockopen($url['host'], $server_port, $errno, $errstr, 5);
+
+		if ( ! is_resource($remote))
+			return FALSE;
+
+		// Set CRLF
+		$CRLF = "\r\n";
+
+		// Send request
+		fwrite($remote, 'HEAD '.$url['path'].' HTTP/1.0'.$CRLF);
+		fwrite($remote, 'Host: '.$url['host'].$CRLF);
+		fwrite($remote, 'Connection: close'.$CRLF);
+		fwrite($remote, 'User-Agent: Kohana Framework (+http://kohanaphp.com/)'.$CRLF);
+
+		// Send one more CRLF to terminate the headers
+		fwrite($remote, $CRLF);
+
+		while ( ! feof($remote))
+		{
+			// Get the line
+			$line = trim(fgets($remote, 512));
+
+			if ($line !== '' AND preg_match('#^HTTP/1\.[01] (\d{3})#', $line, $matches))
+			{
+				// Response code found
+				$response = (int) $matches[1];
+
+				break;
+			}
+		}
+
+		// Close the connection
+		fclose($remote);
+
+		return isset($response) ? $response : FALSE;
+	}
+
+} // End remote
\ No newline at end of file
diff --git a/system/helpers/request.php b/system/helpers/request.php
new file mode 100755
index 0000000..4f5e9c1
--- /dev/null
+++ b/system/helpers/request.php
@@ -0,0 +1,239 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Request helper class.
+ *
+ * $Id: request.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class request_Core {
+
+	// Possible HTTP methods
+	protected static $http_methods = array('get', 'head', 'options', 'post', 'put', 'delete');
+
+	// Content types from client's HTTP Accept request header (array)
+	protected static $accept_types;
+
+	/**
+	 * Returns the HTTP referrer, or the default if the referrer is not set.
+	 *
+	 * @param   mixed   default to return
+	 * @return  string
+	 */
+	public static function referrer($default = FALSE)
+	{
+		if ( ! empty($_SERVER['HTTP_REFERER']))
+		{
+			// Set referrer
+			$ref = $_SERVER['HTTP_REFERER'];
+
+			if (strpos($ref, url::base(FALSE)) === 0)
+			{
+				// Remove the base URL from the referrer
+				$ref = substr($ref, strlen(url::base(TRUE)));
+			}
+		}
+
+		return isset($ref) ? $ref : $default;
+	}
+
+	/**
+	 * Returns the current request protocol, based on $_SERVER['https']. In CLI
+	 * mode, NULL will be returned.
+	 *
+	 * @return  string
+	 */
+	public static function protocol()
+	{
+		if (PHP_SAPI === 'cli')
+		{
+			return NULL;
+		}
+		elseif ( ! empty($_SERVER['HTTPS']) AND $_SERVER['HTTPS'] === 'on')
+		{
+			return 'https';
+		}
+		else
+		{
+			return 'http';
+		}
+	}
+
+	/**
+	 * Tests if the current request is an AJAX request by checking the X-Requested-With HTTP
+	 * request header that most popular JS frameworks now set for AJAX calls.
+	 *
+	 * @return  boolean
+	 */
+	public static function is_ajax()
+	{
+		return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) AND strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
+	}
+
+	/**
+	 * Returns current request method.
+	 *
+	 * @throws  Kohana_Exception in case of an unknown request method
+	 * @return  string
+	 */
+	public static function method()
+	{
+		$method = strtolower($_SERVER['REQUEST_METHOD']);
+
+		if ( ! in_array($method, self::$http_methods))
+			throw new Kohana_Exception('request.unknown_method', $method);
+
+		return $method;
+	 }
+
+	/**
+	 * Returns boolean of whether client accepts content type.
+	 *
+	 * @param   string   content type
+	 * @param   boolean  set to TRUE to disable wildcard checking
+	 * @return  boolean
+	 */
+	public static function accepts($type = NULL, $explicit_check = FALSE)
+	{
+		request::parse_accept_header();
+
+		if ($type === NULL)
+			return self::$accept_types;
+
+		return (request::accepts_at_quality($type, $explicit_check) > 0);
+	}
+
+	/**
+	 * Compare the q values for given array of content types and return the one with the highest value.
+	 * If items are found to have the same q value, the first one encountered in the given array wins.
+	 * If all items in the given array have a q value of 0, FALSE is returned.
+	 *
+	 * @param   array    content types
+	 * @param   boolean  set to TRUE to disable wildcard checking
+	 * @return  mixed    string mime type with highest q value, FALSE if none of the given types are accepted
+	 */
+	public static function preferred_accept($types, $explicit_check = FALSE)
+	{
+		// Initialize
+		$mime_types = array();
+		$max_q = 0;
+		$preferred = FALSE;
+
+		// Load q values for all given content types
+		foreach (array_unique($types) as $type)
+		{
+			$mime_types[$type] = request::accepts_at_quality($type, $explicit_check);
+		}
+
+		// Look for the highest q value
+		foreach ($mime_types as $type => $q)
+		{
+			if ($q > $max_q)
+			{
+				$max_q = $q;
+				$preferred = $type;
+			}
+		}
+
+		return $preferred;
+	}
+
+	/**
+	 * Returns quality factor at which the client accepts content type.
+	 *
+	 * @param   string   content type (e.g. "image/jpg", "jpg")
+	 * @param   boolean  set to TRUE to disable wildcard checking
+	 * @return  integer|float
+	 */
+	public static function accepts_at_quality($type = NULL, $explicit_check = FALSE)
+	{
+		request::parse_accept_header();
+
+		// Normalize type
+		$type = strtolower((string) $type);
+
+		// General content type (e.g. "jpg")
+		if (strpos($type, '/') === FALSE)
+		{
+			// Don't accept anything by default
+			$q = 0;
+
+			// Look up relevant mime types
+			foreach ((array) Kohana::config('mimes.'.$type) as $type)
+			{
+				$q2 = request::accepts_at_quality($type, $explicit_check);
+				$q = ($q2 > $q) ? $q2 : $q;
+			}
+
+			return $q;
+		}
+
+		// Content type with subtype given (e.g. "image/jpg")
+		$type = explode('/', $type, 2);
+
+		// Exact match
+		if (isset(self::$accept_types[$type[0]][$type[1]]))
+			return self::$accept_types[$type[0]][$type[1]];
+        
+		// Wildcard match (if not checking explicitly)
+		if ($explicit_check === FALSE AND isset(self::$accept_types[$type[0]]['*']))
+			return self::$accept_types[$type[0]]['*'];
+
+		// Catch-all wildcard match (if not checking explicitly)
+		if ($explicit_check === FALSE AND isset(self::$accept_types['*']['*']))
+			return self::$accept_types['*']['*'];
+
+		// Content type not accepted
+		return 0;
+	}
+
+	/**
+	 * Parses client's HTTP Accept request header, and builds array structure representing it.
+	 *
+	 * @return  void
+	 */
+	protected static function parse_accept_header()
+	{
+		// Run this function just once
+		if (self::$accept_types !== NULL)
+			return;
+
+		// Initialize accept_types array
+		self::$accept_types = array();
+
+		// No HTTP Accept header found
+		if (empty($_SERVER['HTTP_ACCEPT']))
+		{
+			// Accept everything
+			self::$accept_types['*']['*'] = 1;
+			return;
+		}
+
+		// Remove linebreaks and parse the HTTP Accept header
+		foreach (explode(',', str_replace(array("\r", "\n"), '', $_SERVER['HTTP_ACCEPT'])) as $accept_entry)
+		{
+			// Explode each entry in content type and possible quality factor
+			$accept_entry = explode(';', trim($accept_entry), 2);
+
+			// Explode each content type (e.g. "text/html")
+			$type = explode('/', $accept_entry[0], 2);
+
+			// Skip invalid content types
+			if ( ! isset($type[1]))
+				continue;
+
+			// Assume a default quality factor of 1 if no custom q value found
+			$q = (isset($accept_entry[1]) AND preg_match('~\bq\s*+=\s*+([.0-9]+)~', $accept_entry[1], $match)) ? (float) $match[1] : 1;
+
+			// Populate accept_types array
+			if ( ! isset(self::$accept_types[$type[0]][$type[1]]) OR $q > self::$accept_types[$type[0]][$type[1]])
+			{
+				self::$accept_types[$type[0]][$type[1]] = $q;
+			}
+		}
+	}
+
+} // End request
\ No newline at end of file
diff --git a/system/helpers/security.php b/system/helpers/security.php
new file mode 100755
index 0000000..fa2632e
--- /dev/null
+++ b/system/helpers/security.php
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Security helper class.
+ *
+ * $Id: security.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class security_Core {
+
+	/**
+	 * Sanitize a string with the xss_clean method.
+	 *
+	 * @param   string  string to sanitize
+	 * @return  string
+	 */
+	public static function xss_clean($str)
+	{
+		return Input::instance()->xss_clean($str);
+	}
+
+	/**
+	 * Remove image tags from a string.
+	 *
+	 * @param   string  string to sanitize
+	 * @return  string
+	 */
+	public static function strip_image_tags($str)
+	{
+		return preg_replace('#<img\s.*?(?:src\s*=\s*["\']?([^"\'<>\s]*)["\']?[^>]*)?>#is', '$1', $str);
+	}
+
+	/**
+	 * Remove PHP tags from a string.
+	 *
+	 * @param   string  string to sanitize
+	 * @return  string
+	 */
+	public static function encode_php_tags($str)
+	{
+		return str_replace(array('<?', '?>'), array('<?', '?>'), $str);
+	}
+
+} // End security
\ No newline at end of file
diff --git a/system/helpers/text.php b/system/helpers/text.php
new file mode 100755
index 0000000..342013c
--- /dev/null
+++ b/system/helpers/text.php
@@ -0,0 +1,420 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Text helper class.
+ *
+ * $Id: text.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class text_Core {
+
+	/**
+	 * Limits a phrase to a given number of words.
+	 *
+	 * @param   string   phrase to limit words of
+	 * @param   integer  number of words to limit to
+	 * @param   string   end character or entity
+	 * @return  string
+	 */
+	public static function limit_words($str, $limit = 100, $end_char = NULL)
+	{
+		$limit = (int) $limit;
+		$end_char = ($end_char === NULL) ? '…' : $end_char;
+
+		if (trim($str) === '')
+			return $str;
+
+		if ($limit <= 0)
+			return $end_char;
+
+		preg_match('/^\s*+(?:\S++\s*+){1,'.$limit.'}/u', $str, $matches);
+
+		// Only attach the end character if the matched string is shorter
+		// than the starting string.
+		return rtrim($matches[0]).(strlen($matches[0]) === strlen($str) ? '' : $end_char);
+	}
+
+	/**
+	 * Limits a phrase to a given number of characters.
+	 *
+	 * @param   string   phrase to limit characters of
+	 * @param   integer  number of characters to limit to
+	 * @param   string   end character or entity
+	 * @param   boolean  enable or disable the preservation of words while limiting
+	 * @return  string
+	 */
+	public static function limit_chars($str, $limit = 100, $end_char = NULL, $preserve_words = FALSE)
+	{
+		$end_char = ($end_char === NULL) ? '…' : $end_char;
+
+		$limit = (int) $limit;
+
+		if (trim($str) === '' OR utf8::strlen($str) <= $limit)
+			return $str;
+
+		if ($limit <= 0)
+			return $end_char;
+
+		if ($preserve_words == FALSE)
+		{
+			return rtrim(utf8::substr($str, 0, $limit)).$end_char;
+		}
+
+		preg_match('/^.{'.($limit - 1).'}\S*/us', $str, $matches);
+
+		return rtrim($matches[0]).(strlen($matches[0]) == strlen($str) ? '' : $end_char);
+	}
+
+	/**
+	 * Alternates between two or more strings.
+	 *
+	 * @param   string  strings to alternate between
+	 * @return  string
+	 */
+	public static function alternate()
+	{
+		static $i;
+
+		if (func_num_args() === 0)
+		{
+			$i = 0;
+			return '';
+		}
+
+		$args = func_get_args();
+		return $args[($i++ % count($args))];
+	}
+
+	/**
+	 * Generates a random string of a given type and length.
+	 *
+	 * @param   string   a type of pool, or a string of characters to use as the pool
+	 * @param   integer  length of string to return
+	 * @return  string
+	 *
+	 * @tutorial  alnum     alpha-numeric characters
+	 * @tutorial  alpha     alphabetical characters
+	 * @tutorial  hexdec    hexadecimal characters, 0-9 plus a-f
+	 * @tutorial  numeric   digit characters, 0-9
+	 * @tutorial  nozero    digit characters, 1-9
+	 * @tutorial  distinct  clearly distinct alpha-numeric characters
+	 */
+	public static function random($type = 'alnum', $length = 8)
+	{
+		$utf8 = FALSE;
+
+		switch ($type)
+		{
+			case 'alnum':
+				$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+			break;
+			case 'alpha':
+				$pool = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+			break;
+			case 'hexdec':
+				$pool = '0123456789abcdef';
+			break;
+			case 'numeric':
+				$pool = '0123456789';
+			break;
+			case 'nozero':
+				$pool = '123456789';
+			break;
+			case 'distinct':
+				$pool = '2345679ACDEFHJKLMNPRSTUVWXYZ';
+			break;
+			default:
+				$pool = (string) $type;
+				$utf8 = ! utf8::is_ascii($pool);
+			break;
+		}
+
+		// Split the pool into an array of characters
+		$pool = ($utf8 === TRUE) ? utf8::str_split($pool, 1) : str_split($pool, 1);
+
+		// Largest pool key
+		$max = count($pool) - 1;
+
+		$str = '';
+		for ($i = 0; $i < $length; $i++)
+		{
+			// Select a random character from the pool and add it to the string
+			$str .= $pool[mt_rand(0, $max)];
+		}
+
+		// Make sure alnum strings contain at least one letter and one digit
+		if ($type === 'alnum' AND $length > 1)
+		{
+			if (ctype_alpha($str))
+			{
+				// Add a random digit
+				$str[mt_rand(0, $length - 1)] = chr(mt_rand(48, 57));
+			}
+			elseif (ctype_digit($str))
+			{
+				// Add a random letter
+				$str[mt_rand(0, $length - 1)] = chr(mt_rand(65, 90));
+			}
+		}
+
+		return $str;
+	}
+
+	/**
+	 * Reduces multiple slashes in a string to single slashes.
+	 *
+	 * @param   string  string to reduce slashes of
+	 * @return  string
+	 */
+	public static function reduce_slashes($str)
+	{
+		return preg_replace('#(?<!:)//+#', '/', $str);
+	}
+
+	/**
+	 * Replaces the given words with a string.
+	 *
+	 * @param   string   phrase to replace words in
+	 * @param   array    words to replace
+	 * @param   string   replacement string
+	 * @param   boolean  replace words across word boundries (space, period, etc)
+	 * @return  string
+	 */
+	public static function censor($str, $badwords, $replacement = '#', $replace_partial_words = FALSE)
+	{
+		foreach ((array) $badwords as $key => $badword)
+		{
+			$badwords[$key] = str_replace('\*', '\S*?', preg_quote((string) $badword));
+		}
+
+		$regex = '('.implode('|', $badwords).')';
+
+		if ($replace_partial_words == TRUE)
+		{
+			// Just using \b isn't sufficient when we need to replace a badword that already contains word boundaries itself
+			$regex = '(?<=\b|\s|^)'.$regex.'(?=\b|\s|$)';
+		}
+
+		$regex = '!'.$regex.'!ui';
+
+		if (utf8::strlen($replacement) == 1)
+		{
+			$regex .= 'e';
+			return preg_replace($regex, 'str_repeat($replacement, utf8::strlen(\'$1\'))', $str);
+		}
+
+		return preg_replace($regex, $replacement, $str);
+	}
+
+	/**
+	 * Finds the text that is similar between a set of words.
+	 *
+	 * @param   array   words to find similar text of
+	 * @return  string
+	 */
+	public static function similar(array $words)
+	{
+		// First word is the word to match against
+		$word = current($words);
+
+		for ($i = 0, $max = strlen($word); $i < $max; ++$i)
+		{
+			foreach ($words as $w)
+			{
+				// Once a difference is found, break out of the loops
+				if ( ! isset($w[$i]) OR $w[$i] !== $word[$i])
+					break 2;
+			}
+		}
+
+		// Return the similar text
+		return substr($word, 0, $i);
+	}
+
+	/**
+	 * Converts text email addresses and anchors into links.
+	 *
+	 * @param   string   text to auto link
+	 * @return  string
+	 */
+	public static function auto_link($text)
+	{
+		// Auto link emails first to prevent problems with "www.domain.com at example.com"
+		return text::auto_link_urls(text::auto_link_emails($text));
+	}
+
+	/**
+	 * Converts text anchors into links.
+	 *
+	 * @param   string   text to auto link
+	 * @return  string
+	 */
+	public static function auto_link_urls($text)
+	{
+
+		$regex = '~\\b'
+				.'((?:ht|f)tps?://)?' // protocol
+				.'(?:[-a-zA-Z0-9]{1,63}\.)+' // host name
+				.'(?:[0-9]{1,3}|aero|asia|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|l [...]
+				.'(?:/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?' // path
+				.'(?:\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // query
+				.'(?:#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?' // fragment
+				.'(?=[?.!,;:"]?(?:\s|$))~'; // punctuation and url end
+
+		$result = "";
+		$position = 0;
+
+		while (preg_match($regex, $text, $match, PREG_OFFSET_CAPTURE, $position))
+		{
+			list($url, $url_pos) = $match[0];
+
+			// Add the text before the url
+			$result  .= substr($text, $position, $url_pos - $position);
+
+			// Default to http://
+			$full_url = empty($match[1][0]) ? 'http://'.$url : $url;
+
+			// Add the hyperlink.
+			$result .= html::anchor($full_url, $url);
+
+			// New position to start parsing
+			$position = $url_pos + strlen($url);
+		}
+
+		return $result.substr($text, $position);
+	}
+
+	/**
+	 * Converts text email addresses into links.
+	 *
+	 * @param   string   text to auto link
+	 * @return  string
+	 */
+	public static function auto_link_emails($text)
+	{
+		// Finds all email addresses that are not part of an existing html mailto anchor
+		// Note: The "58;" negative lookbehind prevents matching of existing encoded html mailto anchors
+		//       The html entity for a colon (:) is : or : or : etc.
+		if (preg_match_all('~\b(?<!href="mailto:|">|58;)(?!\.)[-+_a-z0-9.]++(?<!\.)@(?![-.])[-a-z0-9.]+(?<!\.)\.[a-z]{2,6}\b~i', $text, $matches))
+		{
+			foreach ($matches[0] as $match)
+			{
+				// Replace each email with an encoded mailto
+				$text = str_replace($match, html::mailto($match), $text);
+			}
+		}
+
+		return $text;
+	}
+
+	/**
+	 * Automatically applies <p> and <br /> markup to text. Basically nl2br() on steroids.
+	 *
+	 * @param   string   subject
+	 * @return  string
+	 */
+	public static function auto_p($str)
+	{
+		// Trim whitespace
+		if (($str = trim($str)) === '')
+			return '';
+
+		// Standardize newlines
+		$str = str_replace(array("\r\n", "\r"), "\n", $str);
+
+		// Trim whitespace on each line
+		$str = preg_replace('~^[ \t]+~m', '', $str);
+		$str = preg_replace('~[ \t]+$~m', '', $str);
+
+		// The following regexes only need to be executed if the string contains html
+		if ($html_found = (strpos($str, '<') !== FALSE))
+		{
+			// Elements that should not be surrounded by p tags
+			$no_p = '(?:p|div|h[1-6r]|ul|ol|li|blockquote|d[dlt]|pre|t[dhr]|t(?:able|body|foot|head)|c(?:aption|olgroup)|form|s(?:elect|tyle)|a(?:ddress|rea)|ma(?:p|th))';
+
+			// Put at least two linebreaks before and after $no_p elements
+			$str = preg_replace('~^<'.$no_p.'[^>]*+>~im', "\n$0", $str);
+			$str = preg_replace('~</'.$no_p.'\s*+>$~im', "$0\n", $str);
+		}
+
+		// Do the <p> magic!
+		$str = '<p>'.trim($str).'</p>';
+		$str = preg_replace('~\n{2,}~', "</p>\n\n<p>", $str);
+
+		// The following regexes only need to be executed if the string contains html
+		if ($html_found !== FALSE)
+		{
+			// Remove p tags around $no_p elements
+			$str = preg_replace('~<p>(?=</?'.$no_p.'[^>]*+>)~i', '', $str);
+			$str = preg_replace('~(</?'.$no_p.'[^>]*+>)</p>~i', '$1', $str);
+		}
+
+		// Convert single linebreaks to <br />
+		$str = preg_replace('~(?<!\n)\n(?!\n)~', "<br />\n", $str);
+
+		return $str;
+	}
+
+	/**
+	 * Returns human readable sizes.
+	 * @see  Based on original functions written by:
+	 * @see  Aidan Lister: http://aidanlister.com/repos/v/function.size_readable.php
+	 * @see  Quentin Zervaas: http://www.phpriot.com/d/code/strings/filesize-format/
+	 *
+	 * @param   integer  size in bytes
+	 * @param   string   a definitive unit
+	 * @param   string   the return string format
+	 * @param   boolean  whether to use SI prefixes or IEC
+	 * @return  string
+	 */
+	public static function bytes($bytes, $force_unit = NULL, $format = NULL, $si = TRUE)
+	{
+		// Format string
+		$format = ($format === NULL) ? '%01.2f %s' : (string) $format;
+
+		// IEC prefixes (binary)
+		if ($si == FALSE OR strpos($force_unit, 'i') !== FALSE)
+		{
+			$units = array('B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB');
+			$mod   = 1024;
+		}
+		// SI prefixes (decimal)
+		else
+		{
+			$units = array('B', 'kB', 'MB', 'GB', 'TB', 'PB');
+			$mod   = 1000;
+		}
+
+		// Determine unit to use
+		if (($power = array_search((string) $force_unit, $units)) === FALSE)
+		{
+			$power = ($bytes > 0) ? floor(log($bytes, $mod)) : 0;
+		}
+
+		return sprintf($format, $bytes / pow($mod, $power), $units[$power]);
+	}
+
+	/**
+	 * Prevents widow words by inserting a non-breaking space between the last two words.
+	 * @see  http://www.shauninman.com/archive/2006/08/22/widont_wordpress_plugin
+	 *
+	 * @param   string  string to remove widows from
+	 * @return  string
+	 */
+	public static function widont($str)
+	{
+		$str = rtrim($str);
+		$space = strrpos($str, ' ');
+
+		if ($space !== FALSE)
+		{
+			$str = substr($str, 0, $space).' '.substr($str, $space + 1);
+		}
+
+		return $str;
+	}
+
+} // End text
\ No newline at end of file
diff --git a/system/helpers/upload.php b/system/helpers/upload.php
new file mode 100755
index 0000000..d219b6b
--- /dev/null
+++ b/system/helpers/upload.php
@@ -0,0 +1,162 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Upload helper class for working with the global $_FILES
+ * array and Validation library.
+ *
+ * $Id: upload.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class upload_Core {
+
+	/**
+	 * Save an uploaded file to a new location.
+	 *
+	 * @param   mixed    name of $_FILE input or array of upload data
+	 * @param   string   new filename
+	 * @param   string   new directory
+	 * @param   integer  chmod mask
+	 * @return  string   full path to new file
+	 */
+	public static function save($file, $filename = NULL, $directory = NULL, $chmod = 0644)
+	{
+		// Load file data from FILES if not passed as array
+		$file = is_array($file) ? $file : $_FILES[$file];
+
+		if ($filename === NULL)
+		{
+			// Use the default filename, with a timestamp pre-pended
+			$filename = time().$file['name'];
+		}
+
+		if (Kohana::config('upload.remove_spaces') === TRUE)
+		{
+			// Remove spaces from the filename
+			$filename = preg_replace('/\s+/', '_', $filename);
+		}
+
+		if ($directory === NULL)
+		{
+			// Use the pre-configured upload directory
+			$directory = Kohana::config('upload.directory', TRUE);
+		}
+
+		// Make sure the directory ends with a slash
+		$directory = rtrim($directory, '/').'/';
+
+		if ( ! is_dir($directory) AND Kohana::config('upload.create_directories') === TRUE)
+		{
+			// Create the upload directory
+			mkdir($directory, 0777, TRUE);
+		}
+
+		if ( ! is_writable($directory))
+			throw new Kohana_Exception('upload.not_writable', $directory);
+
+		if (is_uploaded_file($file['tmp_name']) AND move_uploaded_file($file['tmp_name'], $filename = $directory.$filename))
+		{
+			if ($chmod !== FALSE)
+			{
+				// Set permissions on filename
+				chmod($filename, $chmod);
+			}
+
+			// Return new file path
+			return $filename;
+		}
+
+		return FALSE;
+	}
+
+	/* Validation Rules */
+
+	/**
+	 * Tests if input data is valid file type, even if no upload is present.
+	 *
+	 * @param   array  $_FILES item
+	 * @return  bool
+	 */
+	public static function valid($file)
+	{
+		return (is_array($file)
+			AND isset($file['error'])
+			AND isset($file['name'])
+			AND isset($file['type'])
+			AND isset($file['tmp_name'])
+			AND isset($file['size']));
+	}
+
+	/**
+	 * Tests if input data has valid upload data.
+	 *
+	 * @param   array    $_FILES item
+	 * @return  bool
+	 */
+	public static function required(array $file)
+	{
+		return (isset($file['tmp_name'])
+			AND isset($file['error'])
+			AND is_uploaded_file($file['tmp_name'])
+			AND (int) $file['error'] === UPLOAD_ERR_OK);
+	}
+
+	/**
+	 * Validation rule to test if an uploaded file is allowed by extension.
+	 *
+	 * @param   array    $_FILES item
+	 * @param   array    allowed file extensions
+	 * @return  bool
+	 */
+	public static function type(array $file, array $allowed_types)
+	{
+		if ((int) $file['error'] !== UPLOAD_ERR_OK)
+			return TRUE;
+
+		// Get the default extension of the file
+		$extension = strtolower(substr(strrchr($file['name'], '.'), 1));
+
+		// Get the mime types for the extension
+		$mime_types = Kohana::config('mimes.'.$extension);
+
+		// Make sure there is an extension, that the extension is allowed, and that mime types exist
+		return ( ! empty($extension) AND in_array($extension, $allowed_types) AND is_array($mime_types));
+	}
+
+	/**
+	 * Validation rule to test if an uploaded file is allowed by file size.
+	 * File sizes are defined as: SB, where S is the size (1, 15, 300, etc) and
+	 * B is the byte modifier: (B)ytes, (K)ilobytes, (M)egabytes, (G)igabytes.
+	 * Eg: to limit the size to 1MB or less, you would use "1M".
+	 *
+	 * @param   array    $_FILES item
+	 * @param   array    maximum file size
+	 * @return  bool
+	 */
+	public static function size(array $file, array $size)
+	{
+		if ((int) $file['error'] !== UPLOAD_ERR_OK)
+			return TRUE;
+
+		// Only one size is allowed
+		$size = strtoupper($size[0]);
+
+		if ( ! preg_match('/[0-9]++[BKMG]/', $size))
+			return FALSE;
+
+		// Make the size into a power of 1024
+		switch (substr($size, -1))
+		{
+			case 'G': $size = intval($size) * pow(1024, 3); break;
+			case 'M': $size = intval($size) * pow(1024, 2); break;
+			case 'K': $size = intval($size) * pow(1024, 1); break;
+			default:  $size = intval($size);                break;
+		}
+
+		// Test that the file is under or equal to the max size
+		return ($file['size'] <= $size);
+	}
+
+} // End upload
\ No newline at end of file
diff --git a/system/helpers/url.php b/system/helpers/url.php
new file mode 100755
index 0000000..7964c30
--- /dev/null
+++ b/system/helpers/url.php
@@ -0,0 +1,249 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URL helper class.
+ *
+ * $Id: url.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class url_Core {
+
+	/**
+	 * Fetches the current URI.
+	 *
+	 * @param   boolean  include the query string
+	 * @return  string
+	 */
+	public static function current($qs = FALSE)
+	{
+		return ($qs === TRUE) ? Router::$complete_uri : Router::$current_uri;
+	}
+
+	/**
+	 * Base URL, with or without the index page.
+	 *
+	 * If protocol (and core.site_protocol) and core.site_domain are both empty,
+	 * then
+	 *
+	 * @param   boolean  include the index page
+	 * @param   boolean  non-default protocol
+	 * @return  string
+	 */
+	public static function base($index = FALSE, $protocol = FALSE)
+	{
+		if ($protocol == FALSE)
+		{
+			// Use the default configured protocol
+			$protocol = Kohana::config('core.site_protocol');
+		}
+
+		// Load the site domain
+		$site_domain = (string) Kohana::config('core.site_domain', TRUE);
+
+		if ($protocol == FALSE)
+		{
+			if ($site_domain === '' OR $site_domain[0] === '/')
+			{
+				// Use the configured site domain
+				$base_url = $site_domain;
+			}
+			else
+			{
+				// Guess the protocol to provide full http://domain/path URL
+				$base_url = ((empty($_SERVER['HTTPS']) OR $_SERVER['HTTPS'] === 'off') ? 'http' : 'https').'://'.$site_domain;
+			}
+		}
+		else
+		{
+			if ($site_domain === '' OR $site_domain[0] === '/')
+			{
+				// Guess the server name if the domain starts with slash
+				$base_url = $protocol.'://'.$_SERVER['HTTP_HOST'].$site_domain;
+			}
+			else
+			{
+				// Use the configured site domain
+				$base_url = $protocol.'://'.$site_domain;
+			}
+		}
+
+		if ($index === TRUE AND $index = Kohana::config('core.index_page'))
+		{
+			// Append the index page
+			$base_url = $base_url.$index;
+		}
+
+		// Force a slash on the end of the URL
+		return rtrim($base_url, '/').'/';
+	}
+
+	/**
+	 * Fetches an absolute site URL based on a URI segment.
+	 *
+	 * @param   string  site URI to convert
+	 * @param   string  non-default protocol
+	 * @return  string
+	 */
+	public static function site($uri = '', $protocol = FALSE)
+	{
+		if ($path = trim(parse_url($uri, PHP_URL_PATH), '/'))
+		{
+			// Add path suffix
+			$path .= Kohana::config('core.url_suffix');
+		}
+
+		if ($query = parse_url($uri, PHP_URL_QUERY))
+		{
+			// ?query=string
+			$query = '?'.$query;
+		}
+
+		if ($fragment = parse_url($uri, PHP_URL_FRAGMENT))
+		{
+			// #fragment
+			$fragment =  '#'.$fragment;
+		}
+
+		// Concat the URL
+		return url::base(TRUE, $protocol).$path.$query.$fragment;
+	}
+
+	/**
+	 * Return the URL to a file. Absolute filenames and relative filenames
+	 * are allowed.
+	 *
+	 * @param   string   filename
+	 * @param   boolean  include the index page
+	 * @return  string
+	 */
+	public static function file($file, $index = FALSE)
+	{
+		if (strpos($file, '://') === FALSE)
+		{
+			// Add the base URL to the filename
+			$file = url::base($index).$file;
+		}
+
+		return $file;
+	}
+
+	/**
+	 * Merges an array of arguments with the current URI and query string to
+	 * overload, instead of replace, the current query string.
+	 *
+	 * @param   array   associative array of arguments
+	 * @return  string
+	 */
+	public static function merge(array $arguments)
+	{
+		if ($_GET === $arguments)
+		{
+			$query = Router::$query_string;
+		}
+		elseif ($query = http_build_query(array_merge($_GET, $arguments)))
+		{
+			$query = '?'.$query;
+		}
+
+		// Return the current URI with the arguments merged into the query string
+		return Router::$current_uri.$query;
+	}
+
+	/**
+	 * Convert a phrase to a URL-safe title.
+	 *
+	 * @param   string  phrase to convert
+	 * @param   string  word separator (- or _)
+	 * @return  string
+	 */
+	public static function title($title, $separator = '-')
+	{
+		$separator = ($separator === '-') ? '-' : '_';
+
+		// Replace accented characters by their unaccented equivalents
+		$title = utf8::transliterate_to_ascii($title);
+
+		// Remove all characters that are not the separator, a-z, 0-9, or whitespace
+		$title = preg_replace('/[^'.$separator.'a-z0-9\s]+/', '', strtolower($title));
+
+		// Replace all separator characters and whitespace by a single separator
+		$title = preg_replace('/['.$separator.'\s]+/', $separator, $title);
+
+		// Trim separators from the beginning and end
+		return trim($title, $separator);
+	}
+
+	/**
+	 * Sends a page redirect header and runs the system.redirect Event.
+	 *
+	 * @param  mixed   string site URI or URL to redirect to, or array of strings if method is 300
+	 * @param  string  HTTP method of redirect
+	 * @return void
+	 */
+	public static function redirect($uri = '', $method = '302')
+	{
+		if (Event::has_run('system.send_headers'))
+		{
+			return FALSE;
+		}
+
+		$codes = array
+		(
+			'refresh' => 'Refresh',
+			'300' => 'Multiple Choices',
+			'301' => 'Moved Permanently',
+			'302' => 'Found',
+			'303' => 'See Other',
+			'304' => 'Not Modified',
+			'305' => 'Use Proxy',
+			'307' => 'Temporary Redirect'
+		);
+
+		// Validate the method and default to 302
+		$method = isset($codes[$method]) ? (string) $method : '302';
+
+		if ($method === '300')
+		{
+			$uri = (array) $uri;
+
+			$output = '<ul>';
+			foreach ($uri as $link)
+			{
+				$output .= '<li>'.html::anchor($link).'</li>';
+			}
+			$output .= '</ul>';
+
+			// The first URI will be used for the Location header
+			$uri = $uri[0];
+		}
+		else
+		{
+			$output = '<p>'.html::anchor($uri).'</p>';
+		}
+
+		// Run the redirect event
+		Event::run('system.redirect', $uri);
+
+		if (strpos($uri, '://') === FALSE)
+		{
+			// HTTP headers expect absolute URLs
+			$uri = url::site($uri, request::protocol());
+		}
+
+		if ($method === 'refresh')
+		{
+			header('Refresh: 0; url='.$uri);
+		}
+		else
+		{
+			header($_SERVER['SERVER_PROTOCOL'] . ' '.$method.' '.$codes[$method]);
+			header('Location: '.$uri);
+		}
+
+		exit('<h1>'.$method.' - '.$codes[$method].'</h1>'.$output);
+	}
+
+} // End url
\ No newline at end of file
diff --git a/system/helpers/valid.php b/system/helpers/valid.php
new file mode 100755
index 0000000..83b09ad
--- /dev/null
+++ b/system/helpers/valid.php
@@ -0,0 +1,377 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Validation helper class.
+ *
+ * $Id: valid.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class valid_Core {
+
+	/**
+	 * Validate email, commonly used characters only
+	 *
+	 * @param   string   email address
+	 * @return  boolean
+	 */
+	public static function email($email)
+	{
+		return (bool) preg_match('/^[-_a-z0-9\'+*$^&%=~!?{}]++(?:\.[-_a-z0-9\'+*$^&%=~!?{}]+)*+@(?:(?![-.])[-a-z0-9.]+(?<![-.])\.[a-z]{2,6}|\d{1,3}(?:\.\d{1,3}){3})(?::\d++)?$/iD', (string) $email);
+	}
+
+	/**
+	 * Validate the domain of an email address by checking if the domain has a
+	 * valid MX record.
+	 *
+	 * @param   string   email address
+	 * @return  boolean
+	 */
+	public static function email_domain($email)
+	{
+		// If we can't prove the domain is invalid, consider it valid
+		// Note: checkdnsrr() is not implemented on Windows platforms
+		if ( ! function_exists('checkdnsrr'))
+			return TRUE;
+
+		// Check if the email domain has a valid MX record
+		return (bool) checkdnsrr(preg_replace('/^[^@]+@/', '', $email), 'MX');
+	}
+
+	/**
+	 * Validate email, RFC compliant version
+	 * Note: This function is LESS strict than valid_email. Choose carefully.
+	 *
+	 * @see  Originally by Cal Henderson, modified to fit Kohana syntax standards:
+	 * @see  http://www.iamcal.com/publish/articles/php/parsing_email/
+	 * @see  http://www.w3.org/Protocols/rfc822/
+	 *
+	 * @param   string   email address
+	 * @return  boolean
+	 */
+	public static function email_rfc($email)
+	{
+		$qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]';
+		$dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]';
+		$atom  = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+';
+		$pair  = '\\x5c[\\x00-\\x7f]';
+
+		$domain_literal = "\\x5b($dtext|$pair)*\\x5d";
+		$quoted_string  = "\\x22($qtext|$pair)*\\x22";
+		$sub_domain     = "($atom|$domain_literal)";
+		$word           = "($atom|$quoted_string)";
+		$domain         = "$sub_domain(\\x2e$sub_domain)*";
+		$local_part     = "$word(\\x2e$word)*";
+		$addr_spec      = "$local_part\\x40$domain";
+
+		return (bool) preg_match('/^'.$addr_spec.'$/D', (string) $email);
+	}
+
+	/**
+	 * Validate URL
+	 *
+	 * @param   string   URL
+	 * @return  boolean
+	 */
+	public static function url($url)
+	{
+		return (bool) filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
+	}
+
+	/**
+	 * Validate IP
+	 *
+	 * @param   string   IP address
+	 * @param   boolean  allow IPv6 addresses
+	 * @param   boolean  allow private IP networks
+	 * @return  boolean
+	 */
+	public static function ip($ip, $ipv6 = FALSE, $allow_private = TRUE)
+	{
+		// By default do not allow private and reserved range IPs
+		$flags = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE;
+		if ($allow_private === TRUE)
+			$flags =  FILTER_FLAG_NO_RES_RANGE;
+
+		if ($ipv6 === TRUE)
+			return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags);
+
+		return (bool) filter_var($ip, FILTER_VALIDATE_IP, $flags | FILTER_FLAG_IPV4);
+	}
+
+	/**
+	 * Validates a credit card number using the Luhn (mod10) formula.
+	 * @see http://en.wikipedia.org/wiki/Luhn_algorithm
+	 *
+	 * @param   integer       credit card number
+	 * @param   string|array  card type, or an array of card types
+	 * @return  boolean
+	 */
+	public static function credit_card($number, $type = NULL)
+	{
+		// Remove all non-digit characters from the number
+		if (($number = preg_replace('/\D+/', '', $number)) === '')
+			return FALSE;
+
+		if ($type == NULL)
+		{
+			// Use the default type
+			$type = 'default';
+		}
+		elseif (is_array($type))
+		{
+			foreach ($type as $t)
+			{
+				// Test each type for validity
+				if (valid::credit_card($number, $t))
+					return TRUE;
+			}
+
+			return FALSE;
+		}
+
+		$cards = Kohana::config('credit_cards');
+
+		// Check card type
+		$type = strtolower($type);
+
+		if ( ! isset($cards[$type]))
+			return FALSE;
+
+		// Check card number length
+		$length = strlen($number);
+
+		// Validate the card length by the card type
+		if ( ! in_array($length, preg_split('/\D+/', $cards[$type]['length'])))
+			return FALSE;
+
+		// Check card number prefix
+		if ( ! preg_match('/^'.$cards[$type]['prefix'].'/', $number))
+			return FALSE;
+
+		// No Luhn check required
+		if ($cards[$type]['luhn'] == FALSE)
+			return TRUE;
+
+		// Checksum of the card number
+		$checksum = 0;
+
+		for ($i = $length - 1; $i >= 0; $i -= 2)
+		{
+			// Add up every 2nd digit, starting from the right
+			$checksum += substr($number, $i, 1);
+		}
+
+		for ($i = $length - 2; $i >= 0; $i -= 2)
+		{
+			// Add up every 2nd digit doubled, starting from the right
+			$double = substr($number, $i, 1) * 2;
+
+			// Subtract 9 from the double where value is greater than 10
+			$checksum += ($double >= 10) ? $double - 9 : $double;
+		}
+
+		// If the checksum is a multiple of 10, the number is valid
+		return ($checksum % 10 === 0);
+	}
+
+	/**
+	 * Checks if a phone number is valid.
+	 *
+	 * @param   string   phone number to check
+	 * @return  boolean
+	 */
+	public static function phone($number, $lengths = NULL)
+	{
+		if ( ! is_array($lengths))
+		{
+			$lengths = array(7,10,11);
+		}
+
+		// Remove all non-digit characters from the number
+		$number = preg_replace('/\D+/', '', $number);
+
+		// Check if the number is within range
+		return in_array(strlen($number), $lengths);
+	}
+
+	/**
+	 * Tests if a string is a valid date string.
+	 * 
+	 * @param   string   date to check
+	 * @return  boolean
+	 */
+	public function date($str)
+	{
+		return (strtotime($str) !== FALSE);
+	}
+
+	/**
+	 * Checks whether a string consists of alphabetical characters only.
+	 *
+	 * @param   string   input string
+	 * @param   boolean  trigger UTF-8 compatibility
+	 * @return  boolean
+	 */
+	public static function alpha($str, $utf8 = FALSE)
+	{
+		return ($utf8 === TRUE)
+			? (bool) preg_match('/^\pL++$/uD', (string) $str)
+			: ctype_alpha((string) $str);
+	}
+
+	/**
+	 * Checks whether a string consists of alphabetical characters and numbers only.
+	 *
+	 * @param   string   input string
+	 * @param   boolean  trigger UTF-8 compatibility
+	 * @return  boolean
+	 */
+	public static function alpha_numeric($str, $utf8 = FALSE)
+	{
+		return ($utf8 === TRUE)
+			? (bool) preg_match('/^[\pL\pN]++$/uD', (string) $str)
+			: ctype_alnum((string) $str);
+	}
+
+	/**
+	 * Checks whether a string consists of alphabetical characters, numbers, underscores and dashes only.
+	 *
+	 * @param   string   input string
+	 * @param   boolean  trigger UTF-8 compatibility
+	 * @return  boolean
+	 */
+	public static function alpha_dash($str, $utf8 = FALSE)
+	{
+		return ($utf8 === TRUE)
+			? (bool) preg_match('/^[-\pL\pN_]++$/uD', (string) $str)
+			: (bool) preg_match('/^[-a-z0-9_]++$/iD', (string) $str);
+	}
+
+	/**
+	 * Checks whether a string consists of digits only (no dots or dashes).
+	 *
+	 * @param   string   input string
+	 * @param   boolean  trigger UTF-8 compatibility
+	 * @return  boolean
+	 */
+	public static function digit($str, $utf8 = FALSE)
+	{
+		return ($utf8 === TRUE)
+			? (bool) preg_match('/^\pN++$/uD', (string) $str)
+			: ctype_digit((string) $str);
+	}
+
+	/**
+	 * Checks whether a string is a valid number (negative and decimal numbers allowed).
+	 *
+	 * @see Uses locale conversion to allow decimal point to be locale specific.
+	 * @see http://www.php.net/manual/en/function.localeconv.php
+	 * 
+	 * @param   string   input string
+	 * @return  boolean
+	 */
+	public static function numeric($str)
+	{
+		// Use localeconv to set the decimal_point value: Usually a comma or period.
+		$locale = localeconv();
+		return (preg_match('/^[-0-9'.$locale['decimal_point'].']++$/D', (string) $str));
+	}
+
+	/**
+	 * Checks whether a string is a valid text. Letters, numbers, whitespace,
+	 * dashes, periods, and underscores are allowed.
+	 *
+	 * @param   string   text to check
+	 * @return  boolean
+	 */
+	public static function standard_text($str)
+	{
+		// pL matches letters
+		// pN matches numbers
+		// pZ matches whitespace
+		// pPc matches underscores
+		// pPd matches dashes
+		// pPo matches normal puncuation
+		return (bool) preg_match('/^[\pL\pN\pZ\p{Pc}\p{Pd}\p{Po}]++$/uD', (string) $str);
+	}
+
+	/**
+	 * Checks if a string is a proper decimal format. The format array can be
+	 * used to specify a decimal length, or a number and decimal length, eg:
+	 * array(2) would force the number to have 2 decimal places, array(4,2)
+	 * would force the number to have 4 digits and 2 decimal places.
+	 *
+	 * @param   string   input string
+	 * @param   array    decimal format: y or x,y
+	 * @return  boolean
+	 */
+	public static function decimal($str, $format = NULL)
+	{
+		// Create the pattern
+		$pattern = '/^[0-9]%s\.[0-9]%s$/';
+
+		if ( ! empty($format))
+		{
+			if (count($format) > 1)
+			{
+				// Use the format for number and decimal length
+				$pattern = sprintf($pattern, '{'.$format[0].'}', '{'.$format[1].'}');
+			}
+			elseif (count($format) > 0)
+			{
+				// Use the format as decimal length
+				$pattern = sprintf($pattern, '+', '{'.$format[0].'}');
+			}
+		}
+		else
+		{
+			// No format
+			$pattern = sprintf($pattern, '+', '+');
+		}
+
+		return (bool) preg_match($pattern, (string) $str);
+	}
+	
+	/**
+	 * Checks whether a string is a valid number (negative and decimal numbers allowed) and between a specified range.
+	 *
+	 * @param   string   input string
+	 * @param   float (MIN/MAX)
+	 * @return  boolean
+	 */
+	public static function between($str, $min_max = array(0,0))
+	{
+		$set_min = $min_max[0];
+		$set_max = $min_max[1];
+		$str = (float) $str;
+		return (is_numeric($str) AND preg_match('/^[-0-9.]++$/D', (string) $str) AND ($str <= $set_max AND $str >= $set_min) );
+	}
+	
+	/**
+	 * Checks whether a string is a valid date (mm/dd/yyyy).
+	 *
+	 * @param   string   input string
+	 * @return  boolean
+	 */
+	public static function date_mmddyyyy($str)
+	{
+		return (strptime($str, '%m/%d/%G'));
+	
+	}
+
+	/**
+	 * Checks whether a string is a valid date (dd/mm/yyyy).
+	 *
+	 * @param   string   input string
+	 * @return  boolean
+	 */
+	public static function date_ddmmyyyy($str)
+	{
+		return (strptime($str, '%d/%m/%G'));
+	
+	}
+
+} // End valid
\ No newline at end of file
diff --git a/system/i18n/en_US/cache.php b/system/i18n/en_US/cache.php
new file mode 100755
index 0000000..bef0279
--- /dev/null
+++ b/system/i18n/en_US/cache.php
@@ -0,0 +1,10 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'undefined_group'      => 'The %s group is not defined in your configuration.',
+	'extension_not_loaded' => 'The %s PHP extension must be loaded to use this driver.',
+	'unwritable'           => 'The configured storage location, %s, is not writable.',
+	'resources'            => 'Caching of resources is impossible, because resources cannot be serialized.',
+	'driver_error'         => '%s',
+);
\ No newline at end of file
diff --git a/system/i18n/en_US/calendar.php b/system/i18n/en_US/calendar.php
new file mode 100755
index 0000000..21dad22
--- /dev/null
+++ b/system/i18n/en_US/calendar.php
@@ -0,0 +1,59 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	// Two letter days
+	'su'        => 'Su',
+	'mo'        => 'Mo',
+	'tu'        => 'Tu',
+	'we'        => 'We',
+	'th'        => 'Th',
+	'fr'        => 'Fr',
+	'sa'        => 'Sa',
+
+	// Short day names
+	'sun'       => 'Sun',
+	'mon'       => 'Mon',
+	'tue'       => 'Tue',
+	'wed'       => 'Wed',
+	'thu'       => 'Thu',
+	'fri'       => 'Fri',
+	'sat'       => 'Sat',
+
+	// Long day names
+	'sunday'    => 'Sunday',
+	'monday'    => 'Monday',
+	'tuesday'   => 'Tuesday',
+	'wednesday' => 'Wednesday',
+	'thursday'  => 'Thursday',
+	'friday'    => 'Friday',
+	'saturday'  => 'Saturday',
+
+	// Short month names
+	'jan'       => 'Jan',
+	'feb'       => 'Feb',
+	'mar'       => 'Mar',
+	'apr'       => 'Apr',
+	'may'       => 'May',
+	'jun'       => 'Jun',
+	'jul'       => 'Jul',
+	'aug'       => 'Aug',
+	'sep'       => 'Sep',
+	'oct'       => 'Oct',
+	'nov'       => 'Nov',
+	'dec'       => 'Dec',
+
+	// Long month names
+	'january'   => 'January',
+	'february'  => 'February',
+	'march'     => 'March',
+	'april'     => 'April',
+	'mayl'      => 'May',
+	'june'      => 'June',
+	'july'      => 'July',
+	'august'    => 'August',
+	'september' => 'September',
+	'october'   => 'October',
+	'november'  => 'November',
+	'december'  => 'December'
+);
\ No newline at end of file
diff --git a/system/i18n/en_US/captcha.php b/system/i18n/en_US/captcha.php
new file mode 100755
index 0000000..6040471
--- /dev/null
+++ b/system/i18n/en_US/captcha.php
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'file_not_found' => 'The specified file, %s, was not found. Please verify that files exist by using file_exists() before using them.',
+	'requires_GD2'   => 'The Captcha library requires GD2 with FreeType support. Please see http://php.net/gd_info for more information.',
+
+	// Words of varying length for the Captcha_Word_Driver to pick from
+	// Note: use only alphanumeric characters
+	'words' => array
+	(
+		'cd', 'tv', 'it', 'to', 'be', 'or',
+		'sun', 'car', 'dog', 'bed', 'kid', 'egg',
+		'bike', 'tree', 'bath', 'roof', 'road', 'hair',
+		'hello', 'world', 'earth', 'beard', 'chess', 'water',
+		'barber', 'bakery', 'banana', 'market', 'purple', 'writer',
+		'america', 'release', 'playing', 'working', 'foreign', 'general',
+		'aircraft', 'computer', 'laughter', 'alphabet', 'kangaroo', 'spelling',
+		'architect', 'president', 'cockroach', 'encounter', 'terrorism', 'cylinders',
+	),
+
+	// Riddles for the Captcha_Riddle_Driver to pick from
+	// Note: use only alphanumeric characters
+	'riddles' => array
+	(
+		array('Do you hate spam? (yes or no)', 'yes'),
+		array('Are you a robot? (yes or no)', 'no'),
+		array('Fire is... (hot or cold)', 'hot'),
+		array('The season after fall is...', 'winter'),
+		array('Which day of the week is it today?', strftime('%A')),
+		array('Which month of the year are we in?', strftime('%B')),
+	),
+);
diff --git a/system/i18n/en_US/core.php b/system/i18n/en_US/core.php
new file mode 100755
index 0000000..9711b7c
--- /dev/null
+++ b/system/i18n/en_US/core.php
@@ -0,0 +1,34 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'there_can_be_only_one' => 'There can be only one instance of Kohana per page request',
+	'uncaught_exception'    => 'Uncaught %s: %s in file %s on line %s',
+	'invalid_method'        => 'Invalid method %s called in %s',
+	'invalid_property'      => 'The %s property does not exist in the %s class.',
+	'log_dir_unwritable'    => 'The log directory is not writable: %s',
+	'resource_not_found'    => 'The requested %s, %s, could not be found',
+	'invalid_filetype'      => 'The requested filetype, .%s, is not allowed in your view configuration file',
+	'view_set_filename'     => 'You must set the the view filename before calling render',
+	'no_default_route'      => 'Please set a default route in config/routes.php',
+	'no_controller'         => 'Kohana was not able to determine a controller to process this request: %s',
+	'page_not_found'        => 'The page you requested, %s, could not be found.',
+	'stats_footer'          => 'Loaded in {execution_time} seconds, using {memory_usage} of memory. Generated by Kohana v{kohana_version}.',
+	'error_file_line'       => '<tt>%s <strong>[%s]:</strong></tt>',
+	'stack_trace'           => 'Stack Trace',
+	'generic_error'         => 'Unable to Complete Request',
+	'errors_disabled'       => 'You can go to the <a href="%s">home page</a> or <a href="%s">try again</a>.',
+
+	// Drivers
+	'driver_implements'     => 'The %s driver for the %s library must implement the %s interface',
+	'driver_not_found'      => 'The %s driver for the %s library could not be found',
+
+	// Resource names
+	'config'                => 'config file',
+	'controller'            => 'controller',
+	'helper'                => 'helper',
+	'library'               => 'library',
+	'driver'                => 'driver',
+	'model'                 => 'model',
+	'view'                  => 'view',
+);
diff --git a/system/i18n/en_US/encrypt.php b/system/i18n/en_US/encrypt.php
new file mode 100755
index 0000000..9afd620
--- /dev/null
+++ b/system/i18n/en_US/encrypt.php
@@ -0,0 +1,8 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'undefined_group'   => 'The %s group is not defined in your configuration.',
+	'requires_mcrypt'   => 'To use the Encrypt library, mcrypt must be enabled in your PHP installation',
+	'no_encryption_key' => 'To use the Encrypt library, you must set an encryption key in your config file'
+);
diff --git a/system/i18n/en_US/errors.php b/system/i18n/en_US/errors.php
new file mode 100755
index 0000000..d66deeb
--- /dev/null
+++ b/system/i18n/en_US/errors.php
@@ -0,0 +1,16 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	E_KOHANA             => array( 1, 'Framework Error',   'Please check the Kohana documentation for information about the following error.'),
+	E_PAGE_NOT_FOUND     => array( 1, 'Page Not Found',    'The requested page was not found. It may have moved, been deleted, or archived.'),
+	E_DATABASE_ERROR     => array( 1, 'Database Error',    'A database error occurred while performing the requested procedure. Please review the database error below for more information.'),
+	E_RECOVERABLE_ERROR  => array( 1, 'Recoverable Error', 'An error was detected which prevented the loading of this page. If this problem persists, please contact the website administrator.'),
+	E_ERROR              => array( 1, 'Fatal Error',       ''),
+	E_USER_ERROR         => array( 1, 'Fatal Error',       ''),
+	E_PARSE              => array( 1, 'Syntax Error',      ''),
+	E_WARNING            => array( 1, 'Warning Message',   ''),
+	E_USER_WARNING       => array( 1, 'Warning Message',   ''),
+	E_STRICT             => array( 2, 'Strict Mode Error', ''),
+	E_NOTICE             => array( 2, 'Runtime Message',   ''),
+);
diff --git a/system/i18n/en_US/event.php b/system/i18n/en_US/event.php
new file mode 100755
index 0000000..282a0a2
--- /dev/null
+++ b/system/i18n/en_US/event.php
@@ -0,0 +1,7 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'invalid_subject'  => 'Attempt to attach invalid subject %s to %s failed: Subjects must extend the Event_Subject class',
+	'invalid_observer' => 'Attempt to attach invalid observer %s to %s failed: Observers must extend the Event_Observer class',
+);
diff --git a/system/i18n/en_US/image.php b/system/i18n/en_US/image.php
new file mode 100755
index 0000000..9f18493
--- /dev/null
+++ b/system/i18n/en_US/image.php
@@ -0,0 +1,33 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'getimagesize_missing'    => 'The Image library requires the getimagesize() PHP function, which is not available in your installation.',
+	'unsupported_method'      => 'Your configured driver does not support the %s image transformation.',
+	'file_not_found'          => 'The specified image, %s, was not found. Please verify that images exist by using file_exists() before manipulating them.',
+	'type_not_allowed'        => 'The specified image, %s, is not an allowed image type.',
+	'invalid_width'           => 'The width you specified, %s, is not valid.',
+	'invalid_height'          => 'The height you specified, %s, is not valid.',
+	'invalid_dimensions'      => 'The dimensions specified for %s are not valid.',
+	'invalid_master'          => 'The master dimension specified is not valid.',
+	'invalid_flip'            => 'The flip direction specified is not valid.',
+	'directory_unwritable'    => 'The specified directory, %s, is not writable.',
+
+	// ImageMagick specific messages
+	'imagemagick' => array
+	(
+		'not_found' => 'The ImageMagick directory specified does not contain a required program, %s.',
+	),
+	
+	// GraphicsMagick specific messages
+	'graphicsmagick' => array
+	(
+		'not_found' => 'The GraphicsMagick directory specified does not contain a required program, %s.',
+	),
+	
+	// GD specific messages
+	'gd' => array
+	(
+		'requires_v2' => 'The Image library requires GD2. Please see http://php.net/gd_info for more information.',
+	),
+);
diff --git a/system/i18n/en_US/orm.php b/system/i18n/en_US/orm.php
new file mode 100755
index 0000000..3c5720b
--- /dev/null
+++ b/system/i18n/en_US/orm.php
@@ -0,0 +1,3 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang['query_methods_not_allowed'] = 'Query methods cannot be used through ORM';
\ No newline at end of file
diff --git a/system/i18n/en_US/pagination.php b/system/i18n/en_US/pagination.php
new file mode 100755
index 0000000..26d6561
--- /dev/null
+++ b/system/i18n/en_US/pagination.php
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'undefined_group' => 'The %s group is not defined in your pagination configuration.',
+	'page'     => 'page',
+	'pages'    => 'pages',
+	'item'     => 'item',
+	'items'    => 'items',
+	'of'       => 'of',
+	'first'    => 'first',
+	'last'     => 'last',
+	'previous' => 'previous',
+	'next'     => 'next',
+);
diff --git a/system/i18n/en_US/profiler.php b/system/i18n/en_US/profiler.php
new file mode 100755
index 0000000..a39c2f5
--- /dev/null
+++ b/system/i18n/en_US/profiler.php
@@ -0,0 +1,15 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'benchmarks'   => 'Benchmarks',
+	'post_data'    => 'Post Data',
+	'no_post'      => 'No post data',
+	'session_data' => 'Session Data',
+	'no_session'   => 'No session data',
+	'queries'      => 'Database Queries',
+	'no_queries'   => 'No queries',
+	'no_database'  => 'Database not loaded',
+	'cookie_data'  => 'Cookie Data',
+	'no_cookie'    => 'No cookie data',
+);
diff --git a/system/i18n/en_US/session.php b/system/i18n/en_US/session.php
new file mode 100755
index 0000000..ee781c6
--- /dev/null
+++ b/system/i18n/en_US/session.php
@@ -0,0 +1,6 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'invalid_session_name' => 'The session_name, %s, is invalid. It must contain only alphanumeric characters and underscores. Also at least one letter must be present.',
+);
\ No newline at end of file
diff --git a/system/i18n/en_US/swift.php b/system/i18n/en_US/swift.php
new file mode 100755
index 0000000..249c4a8
--- /dev/null
+++ b/system/i18n/en_US/swift.php
@@ -0,0 +1,6 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'general_error' => 'An error occurred while sending the email message.'
+);
\ No newline at end of file
diff --git a/system/i18n/en_US/upload.php b/system/i18n/en_US/upload.php
new file mode 100755
index 0000000..7f6e216
--- /dev/null
+++ b/system/i18n/en_US/upload.php
@@ -0,0 +1,6 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	'not_writable' => 'The upload destination folder, %s, does not appear to be writable.',
+);
\ No newline at end of file
diff --git a/system/i18n/en_US/validation.php b/system/i18n/en_US/validation.php
new file mode 100755
index 0000000..d98e548
--- /dev/null
+++ b/system/i18n/en_US/validation.php
@@ -0,0 +1,41 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+$lang = array
+(
+	// Class errors
+	'invalid_rule'  => 'Invalid validation rule used: %s',
+	'i18n_array'    => 'The %s i18n key must be an array to be used with the in_lang rule',
+	'not_callable'  => 'Callback %s used for Validation is not callable',
+
+	// General errors
+	'unknown_error' => 'Unknown validation error while validating the %s field.',
+	'required'      => 'The %s field is required.',
+	'min_length'    => 'The %s field must be at least %d characters long.',
+	'max_length'    => 'The %s field must be %d characters or fewer.',
+	'exact_length'  => 'The %s field must be exactly %d characters.',
+	'in_array'      => 'The %s field must be selected from the options listed.',
+	'matches'       => 'The %s field must match the %s field.',
+	'valid_url'     => 'The %s field must contain a valid URL.',
+	'valid_email'   => 'The %s field must contain a valid email address.',
+	'valid_ip'      => 'The %s field must contain a valid IP address.',
+	'valid_type'    => 'The %s field must only contain %s characters.',
+	'range'         => 'The %s field must be between specified ranges.',
+	'regex'         => 'The %s field does not match accepted input.',
+	'depends_on'    => 'The %s field depends on the %s field.',
+
+	// Upload errors
+	'user_aborted'  => 'The %s file was aborted during upload.',
+	'invalid_type'  => 'The %s file is not an allowed file type.',
+	'max_size'      => 'The %s file you uploaded was too large. The maximum size allowed is %s.',
+	'max_width'     => 'The %s file you uploaded was too big. The maximum allowed width is %spx.',
+	'max_height'    => 'The %s file you uploaded was too big. The maximum allowed height is %spx.',
+	'min_width'     => 'The %s file you uploaded was too small. The minimum allowed width is %spx.',
+	'min_height'    => 'The %s file you uploaded was too small. The minimum allowed height is %spx.',
+
+	// Field types
+	'alpha'         => 'alphabetical',
+	'alpha_numeric' => 'alphabetical and numeric',
+	'alpha_dash'    => 'alphabetical, dash, and underscore',
+	'digit'         => 'digit',
+	'numeric'       => 'numeric',
+);
diff --git a/system/libraries/Cache.php b/system/libraries/Cache.php
new file mode 100755
index 0000000..82ee8c4
--- /dev/null
+++ b/system/libraries/Cache.php
@@ -0,0 +1,228 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a driver-based interface for finding, creating, and deleting cached
+ * resources. Caches are identified by a unique string. Tagging of caches is
+ * also supported, and caches can be found and deleted by id or tag.
+ *
+ * $Id: Cache.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Core {
+
+	// For garbage collection
+	protected static $loaded;
+
+	// Configuration
+	protected $config;
+
+	// Driver object
+	protected $driver;
+
+	/**
+	 * Returns a singleton instance of Cache.
+	 *
+	 * @param   array  configuration
+	 * @return  Cache_Core
+	 */
+	public static function instance($config = array())
+	{
+		static $obj;
+
+		// Create the Cache instance
+		($obj === NULL) and $obj = new Cache($config);
+
+		return $obj;
+	}
+
+	/**
+	 * Loads the configured driver and validates it.
+	 *
+	 * @param   array|string  custom configuration or config group name
+	 * @return  void
+	 */
+	public function __construct($config = FALSE)
+	{
+		if (is_string($config))
+		{
+			$name = $config;
+
+			// Test the config group name
+			if (($config = Kohana::config('cache.'.$config)) === NULL)
+				throw new Kohana_Exception('cache.undefined_group', $name);
+		}
+
+		if (is_array($config))
+		{
+			// Append the default configuration options
+			$config += Kohana::config('cache.default');
+		}
+		else
+		{
+			// Load the default group
+			$config = Kohana::config('cache.default');
+		}
+
+		// Cache the config in the object
+		$this->config = $config;
+
+		// Set driver name
+		$driver = 'Cache_'.ucfirst($this->config['driver']).'_Driver';
+
+		// Load the driver
+		if ( ! Kohana::auto_load($driver))
+			throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
+
+		// Initialize the driver
+		$this->driver = new $driver($this->config['params']);
+
+		// Validate the driver
+		if ( ! ($this->driver instanceof Cache_Driver))
+			throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Cache_Driver');
+
+		Kohana::log('debug', 'Cache Library initialized');
+
+		if (self::$loaded !== TRUE)
+		{
+			$this->config['requests'] = (int) $this->config['requests'];
+
+			if ($this->config['requests'] > 0 AND mt_rand(1, $this->config['requests']) === 1)
+			{
+				// Do garbage collection
+				$this->driver->delete_expired();
+
+				Kohana::log('debug', 'Cache: Expired caches deleted.');
+			}
+
+			// Cache has been loaded once
+			self::$loaded = TRUE;
+		}
+	}
+
+	/**
+	 * Fetches a cache by id. Non-string cache items are automatically
+	 * unserialized before the cache is returned. NULL is returned when
+	 * a cache item is not found.
+	 *
+	 * @param   string  cache id
+	 * @return  mixed   cached data or NULL
+	 */
+	public function get($id)
+	{
+		// Change slashes to colons
+		$id = str_replace(array('/', '\\'), '=', $id);
+
+		if ($data = $this->driver->get($id))
+		{
+			if (substr($data, 0, 14) === '<{serialized}>')
+			{
+				// Data has been serialized, unserialize now
+				$data = unserialize(substr($data, 14));
+			}
+		}
+
+		return $data;
+	}
+
+	/**
+	 * Fetches all of the caches for a given tag. An empty array will be
+	 * returned when no matching caches are found.
+	 *
+	 * @param   string  cache tag
+	 * @return  array   all cache items matching the tag
+	 */
+	public function find($tag)
+	{
+		if ($ids = $this->driver->find($tag))
+		{
+			$data = array();
+			foreach ($ids as $id)
+			{
+				// Load each cache item and add it to the array
+				if (($cache = $this->get($id)) !== NULL)
+				{
+					$data[$id] = $cache;
+				}
+			}
+
+			return $data;
+		}
+
+		return array();
+	}
+
+	/**
+	 * Set a cache item by id. Tags may also be added and a custom lifetime
+	 * can be set. Non-string data is automatically serialized.
+	 *
+	 * @param   string   unique cache id
+	 * @param   mixed    data to cache
+	 * @param   array    tags for this item
+	 * @param   integer  number of seconds until the cache expires
+	 * @return  boolean
+	 */
+	function set($id, $data, $tags = NULL, $lifetime = NULL)
+	{
+		if (is_resource($data))
+			throw new Kohana_Exception('cache.resources');
+
+		// Change slashes to colons
+		$id = str_replace(array('/', '\\'), '=', $id);
+
+		if ( ! is_string($data))
+		{
+			// Serialize all non-string data, so that types can be preserved
+			$data = '<{serialized}>'.serialize($data);
+		}
+
+		// Make sure that tags is an array
+		$tags = empty($tags) ? array() : (array) $tags;
+
+		if ($lifetime === NULL)
+		{
+			// Get the default lifetime
+			$lifetime = $this->config['lifetime'];
+		}
+
+		return $this->driver->set($id, $data, $tags, $lifetime);
+	}
+
+	/**
+	 * Delete a cache item by id.
+	 *
+	 * @param   string   cache id
+	 * @return  boolean
+	 */
+	public function delete($id)
+	{
+		// Change slashes to colons
+		$id = str_replace(array('/', '\\'), '=', $id);
+
+		return $this->driver->delete($id);
+	}
+
+	/**
+	 * Delete all cache items with a given tag.
+	 *
+	 * @param   string   cache tag name
+	 * @return  boolean
+	 */
+	public function delete_tag($tag)
+	{
+		return $this->driver->delete(FALSE, $tag);
+	}
+
+	/**
+	 * Delete ALL cache items items.
+	 *
+	 * @return  boolean
+	 */
+	public function delete_all()
+	{
+		return $this->driver->delete(TRUE);
+	}
+
+} // End Cache
diff --git a/system/libraries/Calendar.php b/system/libraries/Calendar.php
new file mode 100755
index 0000000..56799e9
--- /dev/null
+++ b/system/libraries/Calendar.php
@@ -0,0 +1,362 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Calendar creation library.
+ *
+ * $Id: Calendar.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Calendar
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Calendar_Core extends Event_Subject {
+
+	// Start the calendar on Sunday by default
+	public static $start_monday = FALSE;
+
+	// Month and year to use for calendaring
+	protected $month;
+	protected $year;
+
+	// Week starts on Sunday
+	protected $week_start = 0;
+
+	// Observed data
+	protected $observed_data;
+
+	/**
+	 * Returns an array of the names of the days, using the current locale.
+	 *
+	 * @param   integer  left of day names
+	 * @return  array
+	 */
+	public static function days($length = TRUE)
+	{
+		// strftime day format
+		$format = ($length > 3) ? '%A' : '%a';
+
+		// Days of the week
+		$days = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
+
+		if (Calendar::$start_monday === TRUE)
+		{
+			// Push Sunday to the end of the days
+			array_push($days, array_shift($days));
+		}
+
+		if (strpos(Kohana::config('locale.language.0'), 'en') !== 0)
+		{
+			// This is a bit awkward, but it works properly and is reliable
+			foreach ($days as $i => $day)
+			{
+				// Convert the English names to i18n names
+				$days[$i] = strftime($format, strtotime($day));
+			}
+		}
+
+		if (is_int($length) OR ctype_digit($length))
+		{
+			foreach ($days as $i => $day)
+			{
+				// Shorten the days to the expected length
+				$days[$i] = utf8::substr($day, 0, $length);
+			}
+		}
+
+		return $days;
+	}
+
+	/**
+	 * Create a new Calendar instance. A month and year can be specified.
+	 * By default, the current month and year are used.
+	 *
+	 * @param   integer  month number
+	 * @param   integer  year number
+	 * @return  object
+	 */
+	public static function factory($month = NULL, $year = NULL)
+	{
+		return new Calendar($month, $year);
+	}
+
+	/**
+	 * Create a new Calendar instance. A month and year can be specified.
+	 * By default, the current month and year are used.
+	 *
+	 * @param   integer  month number
+	 * @param   integer  year number
+	 * @return  void
+	 */
+	public function __construct($month = NULL, $year = NULL)
+	{
+		empty($month) and $month = date('n'); // Current month
+		empty($year)  and $year  = date('Y'); // Current year
+
+		// Set the month and year
+		$this->month = (int) $month;
+		$this->year  = (int) $year;
+
+		if (Calendar::$start_monday === TRUE)
+		{
+			// Week starts on Monday
+			$this->week_start = 1;
+		}
+	}
+
+	/**
+	 * Allows fetching the current month and year.
+	 *
+	 * @param   string  key to get
+	 * @return  mixed
+	 */
+	public function __get($key)
+	{
+		if ($key === 'month' OR $key === 'year')
+		{
+			return $this->$key;
+		}
+	}
+
+	/**
+	 * Calendar_Event factory method.
+	 *
+	 * @param   string  unique name for the event
+	 * @return  object  Calendar_Event
+	 */
+	public function event($name = NULL)
+	{
+		return new Calendar_Event($this);
+	}
+
+	/**
+	 * Calendar_Event factory method.
+	 *
+	 * @chainable
+	 * @param   string  standard event type
+	 * @return  object
+	 */
+	public function standard($name)
+	{
+		switch ($name)
+		{
+			case 'today':
+				// Add an event for the current day
+				$this->attach($this->event()->condition('timestamp', strtotime('today'))->add_class('today'));
+			break;
+			case 'prev-next':
+				// Add an event for padding days
+				$this->attach($this->event()->condition('current', FALSE)->add_class('prev-next'));
+			break;
+			case 'holidays':
+				// Base event
+				$event = $this->event()->condition('current', TRUE)->add_class('holiday');
+
+				// Attach New Years
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 1)->condition('day', 1));
+
+				// Attach Valentine's Day
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 2)->condition('day', 14));
+
+				// Attach St. Patrick's Day
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 3)->condition('day', 17));
+
+				// Attach Easter
+				$holiday = clone $event;
+				$this->attach($holiday->condition('easter', TRUE));
+
+				// Attach Memorial Day
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 5)->condition('day_of_week', 1)->condition('last_occurrence', TRUE));
+
+				// Attach Independance Day
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 7)->condition('day', 4));
+
+				// Attach Labor Day
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 9)->condition('day_of_week', 1)->condition('occurrence', 1));
+
+				// Attach Halloween
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 10)->condition('day', 31));
+
+				// Attach Thanksgiving
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 11)->condition('day_of_week', 4)->condition('occurrence', 4));
+
+				// Attach Christmas
+				$holiday = clone $event;
+				$this->attach($holiday->condition('month', 12)->condition('day', 25));
+			break;
+			case 'weekends':
+				// Weekend events
+				$this->attach($this->event()->condition('weekend', TRUE)->add_class('weekend'));
+			break;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Returns an array for use with a view. The array contains an array for
+	 * each week. Each week contains 7 arrays, with a day number and status:
+	 * TRUE if the day is in the month, FALSE if it is padding.
+	 *
+	 * @return  array
+	 */
+	public function weeks()
+	{
+		// First day of the month as a timestamp
+		$first = mktime(1, 0, 0, $this->month, 1, $this->year);
+
+		// Total number of days in this month
+		$total = (int) date('t', $first);
+
+		// Last day of the month as a timestamp
+		$last  = mktime(1, 0, 0, $this->month, $total, $this->year);
+
+		// Make the month and week empty arrays
+		$month = $week = array();
+
+		// Number of days added. When this reaches 7, start a new week
+		$days = 0;
+		$week_number = 1;
+
+		if (($w = (int) date('w', $first) - $this->week_start) < 0)
+		{
+			$w = 6;
+		}
+
+		if ($w > 0)
+		{
+			// Number of days in the previous month
+			$n = (int) date('t', mktime(1, 0, 0, $this->month - 1, 1, $this->year));
+
+			// i = number of day, t = number of days to pad
+			for ($i = $n - $w + 1, $t = $w; $t > 0; $t--, $i++)
+			{
+				// Notify the listeners
+				$this->notify(array($this->month - 1, $i, $this->year, $week_number, FALSE));
+
+				// Add previous month padding days
+				$week[] = array($i, FALSE, $this->observed_data);
+				$days++;
+			}
+		}
+
+		// i = number of day
+		for ($i = 1; $i <= $total; $i++)
+		{
+			if ($days % 7 === 0)
+			{
+				// Start a new week
+				$month[] = $week;
+				$week = array();
+
+				$week_number++;
+			}
+
+			// Notify the listeners
+			$this->notify(array($this->month, $i, $this->year, $week_number, TRUE));
+
+			// Add days to this month
+			$week[] = array($i, TRUE, $this->observed_data);
+			$days++;
+		}
+
+		if (($w = (int) date('w', $last) - $this->week_start) < 0)
+		{
+			$w = 6;
+		}
+
+		if ($w >= 0)
+		{
+			// i = number of day, t = number of days to pad
+			for ($i = 1, $t = 6 - $w; $t > 0; $t--, $i++)
+			{
+				// Notify the listeners
+				$this->notify(array($this->month + 1, $i, $this->year, $week_number, FALSE));
+
+				// Add next month padding days
+				$week[] = array($i, FALSE, $this->observed_data);
+			}
+		}
+
+		if ( ! empty($week))
+		{
+			// Append the remaining days
+			$month[] = $week;
+		}
+
+		return $month;
+	}
+
+	/**
+	 * Adds new data from an observer. All event data contains and array of CSS
+	 * classes and an array of output messages.
+	 *
+	 * @param   array  observer data.
+	 * @return  void
+	 */
+	public function add_data(array $data)
+	{
+		// Add new classes
+		$this->observed_data['classes'] += $data['classes'];
+
+		if ( ! empty($data['output']))
+		{
+			// Only add output if it's not empty
+			$this->observed_data['output'][] = $data['output'];
+		}
+	}
+
+	/**
+	 * Resets the observed data and sends a notify to all attached events.
+	 *
+	 * @param   array  UNIX timestamp
+	 * @return  void
+	 */
+	public function notify($data)
+	{
+		// Reset observed data
+		$this->observed_data = array
+		(
+			'classes' => array(),
+			'output' => array(),
+		);
+
+		// Send a notify
+		parent::notify($data);
+	}
+
+	/**
+	 * Convert the calendar to HTML using the kohana_calendar view.
+	 *
+	 * @return  string
+	 */
+	public function render()
+	{
+		$view =  new View('kohana_calendar', array
+		(
+			'month'  => $this->month,
+			'year'   => $this->year,
+			'weeks'  => $this->weeks(),
+		));
+
+		return $view->render();
+	}
+
+	/**
+	 * Magically convert this object to a string, the rendered calendar.
+	 *
+	 * @return  string
+	 */
+	public function __toString()
+	{
+		return $this->render();
+	}
+
+} // End Calendar
\ No newline at end of file
diff --git a/system/libraries/Calendar_Event.php b/system/libraries/Calendar_Event.php
new file mode 100755
index 0000000..177df70
--- /dev/null
+++ b/system/libraries/Calendar_Event.php
@@ -0,0 +1,297 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Calendar event observer class.
+ *
+ * $Id: Calendar_Event.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Calendar
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Calendar_Event_Core extends Event_Observer {
+
+	// Boolean conditions
+	protected $booleans = array
+	(
+		'current',
+		'weekend',
+		'first_day',
+		'last_day',
+		'last_occurrence',
+		'easter',
+	);
+
+	// Rendering conditions
+	protected $conditions = array();
+
+	// Cell classes
+	protected $classes = array();
+
+	// Cell output
+	protected $output = '';
+
+	/**
+	 * Adds a condition to the event. The condition can be one of the following:
+	 *
+	 * timestamp       - UNIX timestamp
+	 * day             - day number (1-31)
+	 * week            - week number (1-5)
+	 * month           - month number (1-12)
+	 * year            - year number (4 digits)
+	 * day_of_week     - day of week (1-7)
+	 * current         - active month (boolean) (only show data for the month being rendered)
+	 * weekend         - weekend day (boolean)
+	 * first_day       - first day of month (boolean)
+	 * last_day        - last day of month (boolean)
+	 * occurrence      - occurrence of the week day (1-5) (use with "day_of_week")
+	 * last_occurrence - last occurrence of week day (boolean) (use with "day_of_week")
+	 * easter          - Easter day (boolean)
+	 * callback        - callback test (boolean)
+	 *
+	 * To unset a condition, call condition with a value of NULL.
+	 *
+	 * @chainable
+	 * @param   string  condition key
+	 * @param   mixed   condition value
+	 * @return  object
+	 */
+	public function condition($key, $value)
+	{
+		if ($value === NULL)
+		{
+			unset($this->conditions[$key]);
+		}
+		else
+		{
+			if ($key === 'callback')
+			{
+				// Do nothing
+			}
+			elseif (in_array($key, $this->booleans))
+			{
+				// Make the value boolean
+				$value = (bool) $value;
+			}
+			else
+			{
+				// Make the value an int
+				$value = (int) $value;
+			}
+
+			$this->conditions[$key] = $value;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add a CSS class for this event. This can be called multiple times.
+	 *
+	 * @chainable
+	 * @param   string  CSS class name
+	 * @return  object
+	 */
+	public function add_class($class)
+	{
+		$this->classes[$class] = $class;
+
+		return $this;
+	}
+
+	/**
+	 * Remove a CSS class for this event. This can be called multiple times.
+	 *
+	 * @chainable
+	 * @param   string  CSS class name
+	 * @return  object
+	 */
+	public function remove_class($class)
+	{
+		unset($this->classes[$class]);
+
+		return $this;
+	}
+
+	/**
+	 * Set HTML output for this event.
+	 *
+	 * @chainable
+	 * @param   string  HTML output
+	 * @return  object
+	 */
+	public function output($str)
+	{
+		$this->output = $str;
+
+		return $this;
+	}
+
+	/**
+	 * Add a CSS class for this event. This can be called multiple times.
+	 *
+	 * @chainable
+	 * @param   string  CSS class name
+	 * @return  object
+	 */
+	public function notify($data)
+	{
+		// Split the date and current status
+		list ($month, $day, $year, $week, $current) = $data;
+
+		// Get a timestamp for the day
+		$timestamp = mktime(0, 0, 0, $month, $day, $year);
+
+		// Date conditionals
+		$condition = array
+		(
+			'timestamp'   => (int) $timestamp,
+			'day'         => (int) date('j', $timestamp),
+			'week'        => (int) $week,
+			'month'       => (int) date('n', $timestamp),
+			'year'        => (int) date('Y', $timestamp),
+			'day_of_week' => (int) date('w', $timestamp),
+			'current'     => (bool) $current,
+		);
+
+		// Tested conditions
+		$tested = array();
+
+		foreach ($condition as $key => $value)
+		{
+			// Test basic conditions first
+			if (isset($this->conditions[$key]) AND $this->conditions[$key] !== $value)
+				return FALSE;
+
+			// Condition has been tested
+			$tested[$key] = TRUE;
+		}
+
+		if (isset($this->conditions['weekend']))
+		{
+			// Weekday vs Weekend
+			$condition['weekend'] = ($condition['day_of_week'] === 0 OR $condition['day_of_week'] === 6);
+		}
+
+		if (isset($this->conditions['first_day']))
+		{
+			// First day of month
+			$condition['first_day'] = ($condition['day'] === 1);
+		}
+
+		if (isset($this->conditions['last_day']))
+		{
+			// Last day of month
+			$condition['last_day'] = ($condition['day'] === (int) date('t', $timestamp));
+		}
+
+		if (isset($this->conditions['occurrence']))
+		{
+			// Get the occurance of the current day
+			$condition['occurrence'] = $this->day_occurrence($timestamp);
+		}
+
+		if (isset($this->conditions['last_occurrence']))
+		{
+			// Test if the next occurance of this date is next month
+			$condition['last_occurrence'] = ((int) date('n', $timestamp + 604800) !== $condition['month']);
+		}
+
+		if (isset($this->conditions['easter']))
+		{
+			if ($condition['month'] === 3 OR $condition['month'] === 4)
+			{
+				// This algorithm is from Practical Astronomy With Your Calculator, 2nd Edition by Peter
+				// Duffett-Smith. It was originally from Butcher's Ecclesiastical Calendar, published in
+				// 1876. This algorithm has also been published in the 1922 book General Astronomy by
+				// Spencer Jones; in The Journal of the British Astronomical Association (Vol.88, page
+				// 91, December 1977); and in Astronomical Algorithms (1991) by Jean Meeus.
+
+				$a = $condition['year'] % 19;
+				$b = (int) ($condition['year'] / 100);
+				$c = $condition['year'] % 100;
+				$d = (int) ($b / 4);
+				$e = $b % 4;
+				$f = (int) (($b + 8) / 25);
+				$g = (int) (($b - $f + 1) / 3);
+				$h = (19 * $a + $b - $d - $g + 15) % 30;
+				$i = (int) ($c / 4);
+				$k = $c % 4;
+				$l = (32 + 2 * $e + 2 * $i - $h - $k) % 7;
+				$m = (int) (($a + 11 * $h + 22 * $l) / 451);
+				$p = ($h + $l - 7 * $m + 114) % 31;
+
+				$month = (int) (($h + $l - 7 * $m + 114) / 31);
+				$day = $p + 1;
+
+				$condition['easter'] = ($condition['month'] === $month AND $condition['day'] === $day);
+			}
+			else
+			{
+				// Easter can only happen in March or April
+				$condition['easter'] = FALSE;
+			}
+		}
+
+		if (isset($this->conditions['callback']))
+		{
+			// Use a callback to determine validity
+			$condition['callback'] = call_user_func($this->conditions['callback'], $condition, $this);
+		}
+
+		$conditions = array_diff_key($this->conditions, $tested);
+
+		foreach ($conditions as $key => $value)
+		{
+			if ($key === 'callback')
+			{
+				// Callbacks are tested on a TRUE/FALSE basis
+				$value = TRUE;
+			}
+
+			// Test advanced conditions
+			if ($condition[$key] !== $value)
+				return FALSE;
+		}
+
+		$this->caller->add_data(array
+		(
+			'classes' => $this->classes,
+			'output'  => $this->output,
+		));
+	}
+
+	/**
+	 * Find the week day occurrence for a specific timestamp. The occurrence is
+	 * relative to the current month. For example, the second Saturday of any
+	 * given month will return "2" as the occurrence. This is used in combination
+	 * with the "occurrence" condition.
+	 *
+	 * @param   integer  UNIX timestamp
+	 * @return  integer
+	 */
+	protected function day_occurrence($timestamp)
+	{
+		// Get the current month for the timestamp
+		$month = date('m', $timestamp);
+
+		// Default occurrence is one
+		$occurrence = 1;
+
+		// Reduce the timestamp by one week for each loop. This has the added
+		// benefit of preventing an infinite loop.
+		while ($timestamp -= 604800)
+		{
+			if (date('m', $timestamp) !== $month)
+			{
+				// Once the timestamp has gone into the previous month, the
+				// proper occurrence has been found.
+				return $occurrence;
+			}
+
+			// Increment the occurrence
+			$occurrence++;
+		}
+	}
+
+} // End Calendar Event
diff --git a/system/libraries/Captcha.php b/system/libraries/Captcha.php
new file mode 100755
index 0000000..f7206f9
--- /dev/null
+++ b/system/libraries/Captcha.php
@@ -0,0 +1,279 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha library.
+ *
+ * $Id: Captcha.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Core {
+
+	// Captcha singleton
+	protected static $instance;
+
+	// Style-dependent Captcha driver
+	protected $driver;
+
+	// Config values
+	public static $config = array
+	(
+		'style'      => 'basic',
+		'width'      => 150,
+		'height'     => 50,
+		'complexity' => 4,
+		'background' => '',
+		'fontpath'   => '',
+		'fonts'      => array(),
+		'promote'    => FALSE,
+	);
+
+	/**
+	 * Singleton instance of Captcha.
+	 *
+	 * @return  object
+	 */
+	public static function instance()
+	{
+		// Create the instance if it does not exist
+		empty(self::$instance) and new Captcha;
+
+		return self::$instance;
+	}
+
+	/**
+	 * Constructs and returns a new Captcha object.
+	 *
+	 * @param   string  config group name
+	 * @return  object
+	 */
+	public static function factory($group = NULL)
+	{
+		return new Captcha($group);
+	}
+
+	/**
+	 * Constructs a new Captcha object.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   string  config group name
+	 * @return  void
+	 */
+	public function __construct($group = NULL)
+	{
+		// Create a singleton instance once
+		empty(self::$instance) and self::$instance = $this;
+
+		// No config group name given
+		if ( ! is_string($group))
+		{
+			$group = 'default';
+		}
+
+		// Load and validate config group
+		if ( ! is_array($config = Kohana::config('captcha.'.$group)))
+			throw new Kohana_Exception('captcha.undefined_group', $group);
+
+		// All captcha config groups inherit default config group
+		if ($group !== 'default')
+		{
+			// Load and validate default config group
+			if ( ! is_array($default = Kohana::config('captcha.default')))
+				throw new Kohana_Exception('captcha.undefined_group', 'default');
+
+			// Merge config group with default config group
+			$config += $default;
+		}
+
+		// Assign config values to the object
+		foreach ($config as $key => $value)
+		{
+			if (array_key_exists($key, self::$config))
+			{
+				self::$config[$key] = $value;
+			}
+		}
+
+		// Store the config group name as well, so the drivers can access it
+		self::$config['group'] = $group;
+
+		// If using a background image, check if it exists
+		if ( ! empty($config['background']))
+		{
+			self::$config['background'] = str_replace('\\', '/', realpath($config['background']));
+
+			if ( ! is_file(self::$config['background']))
+				throw new Kohana_Exception('captcha.file_not_found', self::$config['background']);
+		}
+
+		// If using any fonts, check if they exist
+		if ( ! empty($config['fonts']))
+		{
+			self::$config['fontpath'] = str_replace('\\', '/', realpath($config['fontpath'])).'/';
+
+			foreach ($config['fonts'] as $font)
+			{
+				if ( ! is_file(self::$config['fontpath'].$font))
+					throw new Kohana_Exception('captcha.file_not_found', self::$config['fontpath'].$font);
+			}
+		}
+
+		// Set driver name
+		$driver = 'Captcha_'.ucfirst($config['style']).'_Driver';
+
+		// Load the driver
+		if ( ! Kohana::auto_load($driver))
+			throw new Kohana_Exception('core.driver_not_found', $config['style'], get_class($this));
+
+		// Initialize the driver
+		$this->driver = new $driver;
+
+		// Validate the driver
+		if ( ! ($this->driver instanceof Captcha_Driver))
+			throw new Kohana_Exception('core.driver_implements', $config['style'], get_class($this), 'Captcha_Driver');
+
+		Kohana::log('debug', 'Captcha Library initialized');
+	}
+
+	/**
+	 * Validates a Captcha response and updates response counter.
+	 *
+	 * @param   string   captcha response
+	 * @return  boolean
+	 */
+	public static function valid($response)
+	{
+		// Maximum one count per page load
+		static $counted;
+
+		// User has been promoted, always TRUE and don't count anymore
+		if (self::instance()->promoted())
+			return TRUE;
+
+		// Challenge result
+		$result = (bool) self::instance()->driver->valid($response);
+
+		// Increment response counter
+		if ($counted !== TRUE)
+		{
+			$counted = TRUE;
+
+			// Valid response
+			if ($result === TRUE)
+			{
+				self::instance()->valid_count(Session::instance()->get('captcha_valid_count') + 1);
+			}
+			// Invalid response
+			else
+			{
+				self::instance()->invalid_count(Session::instance()->get('captcha_invalid_count') + 1);
+			}
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Gets or sets the number of valid Captcha responses for this session.
+	 *
+	 * @param   integer  new counter value
+	 * @param   boolean  trigger invalid counter (for internal use only)
+	 * @return  integer  counter value
+	 */
+	public function valid_count($new_count = NULL, $invalid = FALSE)
+	{
+		// Pick the right session to use
+		$session = ($invalid === TRUE) ? 'captcha_invalid_count' : 'captcha_valid_count';
+
+		// Update counter
+		if ($new_count !== NULL)
+		{
+			$new_count = (int) $new_count;
+
+			// Reset counter = delete session
+			if ($new_count < 1)
+			{
+				Session::instance()->delete($session);
+			}
+			// Set counter to new value
+			else
+			{
+				Session::instance()->set($session, (int) $new_count);
+			}
+
+			// Return new count
+			return (int) $new_count;
+		}
+
+		// Return current count
+		return (int) Session::instance()->get($session);
+	}
+
+	/**
+	 * Gets or sets the number of invalid Captcha responses for this session.
+	 *
+	 * @param   integer  new counter value
+	 * @return  integer  counter value
+	 */
+	public function invalid_count($new_count = NULL)
+	{
+		return $this->valid_count($new_count, TRUE);
+	}
+
+	/**
+	 * Resets the Captcha response counters and removes the count sessions.
+	 *
+	 * @return  void
+	 */
+	public function reset_count()
+	{
+		$this->valid_count(0);
+		$this->valid_count(0, TRUE);
+	}
+
+	/**
+	 * Checks whether user has been promoted after having given enough valid responses.
+	 *
+	 * @param   integer  valid response count threshold
+	 * @return  boolean
+	 */
+	public function promoted($threshold = NULL)
+	{
+		// Promotion has been disabled
+		if (self::$config['promote'] === FALSE)
+			return FALSE;
+
+		// Use the config threshold
+		if ($threshold === NULL)
+		{
+			$threshold = self::$config['promote'];
+		}
+
+		// Compare the valid response count to the threshold
+		return ($this->valid_count() >= $threshold);
+	}
+
+	/**
+	 * Returns or outputs the Captcha challenge.
+	 *
+	 * @param   boolean  TRUE to output html, e.g. <img src="#" />
+	 * @return  mixed    html string or void
+	 */
+	public function render($html = TRUE)
+	{
+		return $this->driver->render($html);
+	}
+
+	/**
+	 * Magically outputs the Captcha challenge.
+	 *
+	 * @return  mixed
+	 */
+	public function __toString()
+	{
+		return $this->render();
+	}
+
+} // End Captcha Class
\ No newline at end of file
diff --git a/system/libraries/Controller.php b/system/libraries/Controller.php
new file mode 100755
index 0000000..e094363
--- /dev/null
+++ b/system/libraries/Controller.php
@@ -0,0 +1,86 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana Controller class. The controller class must be extended to work
+ * properly, so this class is defined as abstract.
+ *
+ * $Id: Controller.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Controller_Core {
+
+	// Allow all controllers to run in production by default
+	const ALLOW_PRODUCTION = TRUE;
+
+	/**
+	 * Loads URI, and Input into this controller.
+	 *
+	 * @return  void
+	 */
+	public function __construct()
+	{
+		if (Kohana::$instance == NULL)
+		{
+			// Set the instance to the first controller loaded
+			Kohana::$instance = $this;
+		}
+
+		// URI should always be available
+		$this->uri = URI::instance();
+
+		// Input should always be available
+		$this->input = Input::instance();
+	}
+
+	/**
+	 * Handles methods that do not exist.
+	 *
+	 * @param   string  method name
+	 * @param   array   arguments
+	 * @return  void
+	 */
+	public function __call($method, $args)
+	{
+		// Default to showing a 404 page
+		Event::run('system.404');
+	}
+
+	/**
+	 * Includes a View within the controller scope.
+	 *
+	 * @param   string  view filename
+	 * @param   array   array of view variables
+	 * @return  string
+	 */
+	public function _kohana_load_view($kohana_view_filename, $kohana_input_data)
+	{
+		if ($kohana_view_filename == '')
+			return;
+
+		// Buffering on
+		ob_start();
+
+		// Import the view variables to local namespace
+		extract($kohana_input_data, EXTR_SKIP);
+
+		try
+		{
+			// Views are straight HTML pages with embedded PHP, so importing them
+			// this way insures that $this can be accessed as if the user was in
+			// the controller, which gives the easiest access to libraries in views
+			include $kohana_view_filename;
+		}
+		catch (Exception $e)
+		{
+			// Display the exception using its internal __toString method
+			echo $e;
+		}
+
+		// Fetch the output and close the buffer
+		return ob_get_clean();
+	}
+
+} // End Controller Class
\ No newline at end of file
diff --git a/system/libraries/Database.php b/system/libraries/Database.php
new file mode 100755
index 0000000..5c576ce
--- /dev/null
+++ b/system/libraries/Database.php
@@ -0,0 +1,1342 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides database access in a platform agnostic way, using simple query building blocks.
+ *
+ * $Id: Database.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Core {
+
+	// Database instances
+	public static $instances = array();
+
+	// Global benchmark
+	public static $benchmarks = array();
+
+	// Configuration
+	protected $config = array
+	(
+		'benchmark'     => TRUE,
+		'persistent'    => FALSE,
+		'connection'    => '',
+		'character_set' => 'utf8',
+		'table_prefix'  => '',
+		'object'        => TRUE,
+		'cache'         => FALSE,
+		'escape'        => TRUE,
+	);
+
+	// Database driver object
+	protected $driver;
+	protected $link;
+
+	// Un-compiled parts of the SQL query
+	protected $select     = array();
+	protected $set        = array();
+	protected $from       = array();
+	protected $join       = array();
+	protected $where      = array();
+	protected $orderby    = array();
+	protected $order      = array();
+	protected $groupby    = array();
+	protected $having     = array();
+	protected $distinct   = FALSE;
+	protected $limit      = FALSE;
+	protected $offset     = FALSE;
+	protected $last_query = '';
+
+	// Stack of queries for push/pop
+	protected $query_history = array();
+
+	/**
+	 * Returns a singleton instance of Database.
+	 *
+	 * @param   mixed   configuration array or DSN
+	 * @return  Database_Core
+	 */
+	public static function & instance($name = 'default', $config = NULL)
+	{
+		if ( ! isset(Database::$instances[$name]))
+		{
+			// Create a new instance
+			Database::$instances[$name] = new Database($config === NULL ? $name : $config);
+		}
+
+		return Database::$instances[$name];
+	}
+
+	/**
+	 * Returns the name of a given database instance.
+	 *
+	 * @param   Database  instance of Database
+	 * @return  string
+	 */
+	public static function instance_name(Database $db)
+	{
+		return array_search($db, Database::$instances, TRUE);
+	}
+
+	/**
+	 * Sets up the database configuration, loads the Database_Driver.
+	 *
+	 * @throws  Kohana_Database_Exception
+	 */
+	public function __construct($config = array())
+	{
+		if (empty($config))
+		{
+			// Load the default group
+			$config = Kohana::config('database.default');
+		}
+		elseif (is_array($config) AND count($config) > 0)
+		{
+			if ( ! array_key_exists('connection', $config))
+			{
+				$config = array('connection' => $config);
+			}
+		}
+		elseif (is_string($config))
+		{
+			// The config is a DSN string
+			if (strpos($config, '://') !== FALSE)
+			{
+				$config = array('connection' => $config);
+			}
+			// The config is a group name
+			else
+			{
+				$name = $config;
+
+				// Test the config group name
+				if (($config = Kohana::config('database.'.$config)) === NULL)
+					throw new Kohana_Database_Exception('database.undefined_group', $name);
+			}
+		}
+
+		// Merge the default config with the passed config
+		$this->config = array_merge($this->config, $config);
+
+		if (is_string($this->config['connection']))
+		{
+			// Make sure the connection is valid
+			if (strpos($this->config['connection'], '://') === FALSE)
+				throw new Kohana_Database_Exception('database.invalid_dsn', $this->config['connection']);
+
+			// Parse the DSN, creating an array to hold the connection parameters
+			$db = array
+			(
+				'type'     => FALSE,
+				'user'     => FALSE,
+				'pass'     => FALSE,
+				'host'     => FALSE,
+				'port'     => FALSE,
+				'socket'   => FALSE,
+				'database' => FALSE
+			);
+
+			// Get the protocol and arguments
+			list ($db['type'], $connection) = explode('://', $this->config['connection'], 2);
+
+			if (strpos($connection, '@') !== FALSE)
+			{
+				// Get the username and password
+				list ($db['pass'], $connection) = explode('@', $connection, 2);
+				// Check if a password is supplied
+				$logindata = explode(':', $db['pass'], 2);
+				$db['pass'] = (count($logindata) > 1) ? $logindata[1] : '';
+				$db['user'] = $logindata[0];
+
+				// Prepare for finding the database
+				$connection = explode('/', $connection);
+
+				// Find the database name
+				$db['database'] = array_pop($connection);
+
+				// Reset connection string
+				$connection = implode('/', $connection);
+
+				// Find the socket
+				if (preg_match('/^unix\([^)]++\)/', $connection))
+				{
+					// This one is a little hairy: we explode based on the end of
+					// the socket, removing the 'unix(' from the connection string
+					list ($db['socket'], $connection) = explode(')', substr($connection, 5), 2);
+				}
+				elseif (strpos($connection, ':') !== FALSE)
+				{
+					// Fetch the host and port name
+					list ($db['host'], $db['port']) = explode(':', $connection, 2);
+				}
+				else
+				{
+					$db['host'] = $connection;
+				}
+			}
+			else
+			{
+				// File connection
+				$connection = explode('/', $connection);
+
+				// Find database file name
+				$db['database'] = array_pop($connection);
+
+				// Find database directory name
+				$db['socket'] = implode('/', $connection).'/';
+			}
+
+			// Reset the connection array to the database config
+			$this->config['connection'] = $db;
+		}
+		// Set driver name
+		$driver = 'Database_'.ucfirst($this->config['connection']['type']).'_Driver';
+
+		// Load the driver
+		if ( ! Kohana::auto_load($driver))
+			throw new Kohana_Database_Exception('core.driver_not_found', $this->config['connection']['type'], get_class($this));
+
+		// Initialize the driver
+		$this->driver = new $driver($this->config);
+
+		// Validate the driver
+		if ( ! ($this->driver instanceof Database_Driver))
+			throw new Kohana_Database_Exception('core.driver_implements', $this->config['connection']['type'], get_class($this), 'Database_Driver');
+
+		Kohana::log('debug', 'Database Library initialized');
+	}
+
+	/**
+	 * Simple connect method to get the database queries up and running.
+	 *
+	 * @return  void
+	 */
+	public function connect()
+	{
+		// A link can be a resource or an object
+		if ( ! is_resource($this->link) AND ! is_object($this->link))
+		{
+			$this->link = $this->driver->connect();
+			if ( ! is_resource($this->link) AND ! is_object($this->link))
+				throw new Kohana_Database_Exception('database.connection', $this->driver->show_error());
+
+			// Clear password after successful connect
+			$this->config['connection']['pass'] = NULL;
+		}
+	}
+
+	/**
+	 * Runs a query into the driver and returns the result.
+	 *
+	 * @param   string  SQL query to execute
+	 * @return  Database_Result
+	 */
+	public function query($sql = '')
+	{
+		if ($sql == '') return FALSE;
+
+		// No link? Connect!
+		$this->link or $this->connect();
+
+		// Start the benchmark
+		$start = microtime(TRUE);
+
+		if (func_num_args() > 1) //if we have more than one argument ($sql)
+		{
+			$argv = func_get_args();
+			$binds = (is_array(next($argv))) ? current($argv) : array_slice($argv, 1);
+		}
+
+		// Compile binds if needed
+		if (isset($binds))
+		{
+			$sql = $this->compile_binds($sql, $binds);
+		}
+
+		// Fetch the result
+		$result = $this->driver->query($this->last_query = $sql);
+
+		// Stop the benchmark
+		$stop = microtime(TRUE);
+
+		if ($this->config['benchmark'] == TRUE)
+		{
+			// Benchmark the query
+			self::$benchmarks[] = array('query' => $sql, 'time' => $stop - $start, 'rows' => count($result));
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Selects the column names for a database query.
+	 *
+	 * @param   string  string or array of column names to select
+	 * @return  Database_Core  This Database object.
+	 */
+	public function select($sql = '*')
+	{
+		if (func_num_args() > 1)
+		{
+			$sql = func_get_args();
+		}
+		elseif (is_string($sql))
+		{
+			$sql = explode(',', $sql);
+		}
+		else
+		{
+			$sql = (array) $sql;
+		}
+
+		foreach ($sql as $val)
+		{
+			if (($val = trim($val)) === '') continue;
+
+			if (strpos($val, '(') === FALSE AND $val !== '*')
+			{
+				if (preg_match('/^DISTINCT\s++(.+)$/i', $val, $matches))
+				{
+					$val            = $this->config['table_prefix'].$matches[1];
+					$this->distinct = TRUE;
+				}
+				else
+				{
+					$val = (strpos($val, '.') !== FALSE) ? $this->config['table_prefix'].$val : $val;
+				}
+
+				$val = $this->driver->escape_column($val);
+			}
+
+			$this->select[] = $val;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the from table(s) for a database query.
+	 *
+	 * @param   string  string or array of tables to select
+	 * @return  Database_Core  This Database object.
+	 */
+	public function from($sql)
+	{
+		if (func_num_args() > 1)
+		{
+			$sql = func_get_args();
+		}
+		elseif (is_string($sql))
+		{
+			$sql = explode(',', $sql);
+		}
+		else
+		{
+			$sql = (array) $sql;
+		}
+
+		foreach ($sql as $val)
+		{
+			if (($val = trim($val)) === '') continue;
+
+			$this->from[] = $this->config['table_prefix'].$val;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Generates the JOIN portion of the query.
+	 *
+	 * @param   string        table name
+	 * @param   string|array  where key or array of key => value pairs
+	 * @param   string        where value
+	 * @param   string        type of join
+	 * @return  Database_Core        This Database object.
+	 */
+	public function join($table, $key, $value = NULL, $type = '')
+	{
+		$join = array();
+
+		if ( ! empty($type))
+		{
+			$type = strtoupper(trim($type));
+
+			if ( ! in_array($type, array('LEFT', 'RIGHT', 'OUTER', 'INNER', 'LEFT OUTER', 'RIGHT OUTER'), TRUE))
+			{
+				$type = '';
+			}
+			else
+			{
+				$type .= ' ';
+			}
+		}
+
+		$cond = array();
+		$keys  = is_array($key) ? $key : array($key => $value);
+		foreach ($keys as $key => $value)
+		{
+			$key    = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+			$cond[] = $this->driver->where($key, $this->driver->escape_column($this->config['table_prefix'].$value), 'AND ', count($cond), FALSE);
+		}
+
+		if( ! is_array($this->join)) { $this->join = array(); }
+
+		foreach ((array) $table as $t)
+		{
+			$join['tables'][] = $this->driver->escape_column($this->config['table_prefix'].$t);
+		}
+
+		$join['conditions'] = '('.trim(implode(' ', $cond)).')';
+		$join['type'] = $type;
+
+		$this->join[] = $join;
+
+		return $this;
+	}
+
+
+	/**
+	 * Selects the where(s) for a database query.
+	 *
+	 * @param   string|array  key name or array of key => value pairs
+	 * @param   string        value to match with key
+	 * @param   boolean       disable quoting of WHERE clause
+	 * @return  Database_Core        This Database object.
+	 */
+	public function where($key, $value = NULL, $quote = TRUE)
+	{
+		$quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
+		$keys  = is_array($key) ? $key : array($key => $value);
+
+		foreach ($keys as $key => $value)
+		{
+			$key           = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+			$this->where[] = $this->driver->where($key, $value, 'AND ', count($this->where), $quote);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the or where(s) for a database query.
+	 *
+	 * @param   string|array  key name or array of key => value pairs
+	 * @param   string        value to match with key
+	 * @param   boolean       disable quoting of WHERE clause
+	 * @return  Database_Core        This Database object.
+	 */
+	public function orwhere($key, $value = NULL, $quote = TRUE)
+	{
+		$quote = (func_num_args() < 2 AND ! is_array($key)) ? -1 : $quote;
+		$keys  = is_array($key) ? $key : array($key => $value);
+
+		foreach ($keys as $key => $value)
+		{
+			$key           = (strpos($key, '.') !== FALSE) ? $this->config['table_prefix'].$key : $key;
+			$this->where[] = $this->driver->where($key, $value, 'OR ', count($this->where), $quote);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the like(s) for a database query.
+	 *
+	 * @param   string|array  field name or array of field => match pairs
+	 * @param   string        like value to match with field
+	 * @param   boolean       automatically add starting and ending wildcards
+	 * @return  Database_Core        This Database object.
+	 */
+	public function like($field, $match = '', $auto = TRUE)
+	{
+		$fields = is_array($field) ? $field : array($field => $match);
+
+		foreach ($fields as $field => $match)
+		{
+			$field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+			$this->where[] = $this->driver->like($field, $match, $auto, 'AND ', count($this->where));
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the or like(s) for a database query.
+	 *
+	 * @param   string|array  field name or array of field => match pairs
+	 * @param   string        like value to match with field
+	 * @param   boolean       automatically add starting and ending wildcards
+	 * @return  Database_Core        This Database object.
+	 */
+	public function orlike($field, $match = '', $auto = TRUE)
+	{
+		$fields = is_array($field) ? $field : array($field => $match);
+
+		foreach ($fields as $field => $match)
+		{
+			$field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+			$this->where[] = $this->driver->like($field, $match, $auto, 'OR ', count($this->where));
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the not like(s) for a database query.
+	 *
+	 * @param   string|array  field name or array of field => match pairs
+	 * @param   string        like value to match with field
+	 * @param   boolean       automatically add starting and ending wildcards
+	 * @return  Database_Core        This Database object.
+	 */
+	public function notlike($field, $match = '', $auto = TRUE)
+	{
+		$fields = is_array($field) ? $field : array($field => $match);
+
+		foreach ($fields as $field => $match)
+		{
+			$field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+			$this->where[] = $this->driver->notlike($field, $match, $auto, 'AND ', count($this->where));
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the or not like(s) for a database query.
+	 *
+	 * @param   string|array  field name or array of field => match pairs
+	 * @param   string        like value to match with field
+	 * @return  Database_Core        This Database object.
+	 */
+	public function ornotlike($field, $match = '', $auto = TRUE)
+	{
+		$fields = is_array($field) ? $field : array($field => $match);
+
+		foreach ($fields as $field => $match)
+		{
+			$field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+			$this->where[] = $this->driver->notlike($field, $match, $auto, 'OR ', count($this->where));
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the like(s) for a database query.
+	 *
+	 * @param   string|array  field name or array of field => match pairs
+	 * @param   string        like value to match with field
+	 * @return  Database_Core        This Database object.
+	 */
+	public function regex($field, $match = '')
+	{
+		$fields = is_array($field) ? $field : array($field => $match);
+
+		foreach ($fields as $field => $match)
+		{
+			$field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+			$this->where[] = $this->driver->regex($field, $match, 'AND ', count($this->where));
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the or like(s) for a database query.
+	 *
+	 * @param   string|array  field name or array of field => match pairs
+	 * @param   string        like value to match with field
+	 * @return  Database_Core        This Database object.
+	 */
+	public function orregex($field, $match = '')
+	{
+		$fields = is_array($field) ? $field : array($field => $match);
+
+		foreach ($fields as $field => $match)
+		{
+			$field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+			$this->where[] = $this->driver->regex($field, $match, 'OR ', count($this->where));
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the not regex(s) for a database query.
+	 *
+	 * @param   string|array  field name or array of field => match pairs
+	 * @param   string        regex value to match with field
+	 * @return  Database_Core        This Database object.
+	 */
+	public function notregex($field, $match = '')
+	{
+		$fields = is_array($field) ? $field : array($field => $match);
+
+		foreach ($fields as $field => $match)
+		{
+			$field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+			$this->where[] = $this->driver->notregex($field, $match, 'AND ', count($this->where));
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the or not regex(s) for a database query.
+	 *
+	 * @param   string|array  field name or array of field => match pairs
+	 * @param   string        regex value to match with field
+	 * @return  Database_Core        This Database object.
+	 */
+	public function ornotregex($field, $match = '')
+	{
+		$fields = is_array($field) ? $field : array($field => $match);
+
+		foreach ($fields as $field => $match)
+		{
+			$field         = (strpos($field, '.') !== FALSE) ? $this->config['table_prefix'].$field : $field;
+			$this->where[] = $this->driver->notregex($field, $match, 'OR ', count($this->where));
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Chooses the column to group by in a select query.
+	 *
+	 * @param   string  column name to group by
+	 * @return  Database_Core  This Database object.
+	 */
+	public function groupby($by)
+	{
+		if ( ! is_array($by))
+		{
+			$by = explode(',', (string) $by);
+		}
+
+		foreach ($by as $val)
+		{
+			$val = trim($val);
+
+			if ($val != '')
+			{
+				// Add the table prefix if we are using table.column names
+				if(strpos($val, '.'))
+				{
+					$val = $this->config['table_prefix'].$val;
+				}
+
+				$this->groupby[] = $this->driver->escape_column($val);
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the having(s) for a database query.
+	 *
+	 * @param   string|array  key name or array of key => value pairs
+	 * @param   string        value to match with key
+	 * @param   boolean       disable quoting of WHERE clause
+	 * @return  Database_Core        This Database object.
+	 */
+	public function having($key, $value = '', $quote = TRUE)
+	{
+		$this->having[] = $this->driver->where($key, $value, 'AND', count($this->having), TRUE);
+		return $this;
+	}
+
+	/**
+	 * Selects the or having(s) for a database query.
+	 *
+	 * @param   string|array  key name or array of key => value pairs
+	 * @param   string        value to match with key
+	 * @param   boolean       disable quoting of WHERE clause
+	 * @return  Database_Core        This Database object.
+	 */
+	public function orhaving($key, $value = '', $quote = TRUE)
+	{
+		$this->having[] = $this->driver->where($key, $value, 'OR', count($this->having), TRUE);
+		return $this;
+	}
+
+	/**
+	 * Chooses which column(s) to order the select query by.
+	 *
+	 * @param   string|array  column(s) to order on, can be an array, single column, or comma seperated list of columns
+	 * @param   string        direction of the order
+	 * @return  Database_Core        This Database object.
+	 */
+	public function orderby($orderby, $direction = NULL)
+	{
+		if ( ! is_array($orderby))
+		{
+			$orderby = array($orderby => $direction);
+		}
+
+		foreach ($orderby as $column => $direction)
+		{
+			$direction = strtoupper(trim($direction));
+
+			// Add a direction if the provided one isn't valid
+			if ( ! in_array($direction, array('ASC', 'DESC', 'RAND()', 'RANDOM()', 'NULL')))
+			{
+				$direction = 'ASC';
+			}
+
+			// Add the table prefix if a table.column was passed
+			if (strpos($column, '.'))
+			{
+				$column = $this->config['table_prefix'].$column;
+			}
+
+			$this->orderby[] = $this->driver->escape_column($column).' '.$direction;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Selects the limit section of a query.
+	 *
+	 * @param   integer  number of rows to limit result to
+	 * @param   integer  offset in result to start returning rows from
+	 * @return  Database_Core   This Database object.
+	 */
+	public function limit($limit, $offset = NULL)
+	{
+		$this->limit  = (int) $limit;
+
+		if ($offset !== NULL OR ! is_int($this->offset))
+		{
+			$this->offset($offset);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Sets the offset portion of a query.
+	 *
+	 * @param   integer  offset value
+	 * @return  Database_Core   This Database object.
+	 */
+	public function offset($value)
+	{
+		$this->offset = (int) $value;
+
+		return $this;
+	}
+
+	/**
+	 * Allows key/value pairs to be set for inserting or updating.
+	 *
+	 * @param   string|array  key name or array of key => value pairs
+	 * @param   string        value to match with key
+	 * @return  Database_Core        This Database object.
+	 */
+	public function set($key, $value = '')
+	{
+		if ( ! is_array($key))
+		{
+			$key = array($key => $value);
+		}
+
+		foreach ($key as $k => $v)
+		{
+			// Add a table prefix if the column includes the table.
+			if (strpos($k, '.'))
+				$k = $this->config['table_prefix'].$k;
+
+			$this->set[$k] = $this->driver->escape($v);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Compiles the select statement based on the other functions called and runs the query.
+	 *
+	 * @param   string  table name
+	 * @param   string  limit clause
+	 * @param   string  offset clause
+	 * @return  Database_Result
+	 */
+	public function get($table = '', $limit = NULL, $offset = NULL)
+	{
+		if ($table != '')
+		{
+			$this->from($table);
+		}
+
+		if ( ! is_null($limit))
+		{
+			$this->limit($limit, $offset);
+		}
+
+		$sql = $this->driver->compile_select(get_object_vars($this));
+
+		$this->reset_select();
+
+		$result = $this->query($sql);
+
+		$this->last_query = $sql;
+
+		return $result;
+	}
+
+	/**
+	 * Compiles the select statement based on the other functions called and runs the query.
+	 *
+	 * @param   string  table name
+	 * @param   array   where clause
+	 * @param   string  limit clause
+	 * @param   string  offset clause
+	 * @return  Database_Core  This Database object.
+	 */
+	public function getwhere($table = '', $where = NULL, $limit = NULL, $offset = NULL)
+	{
+		if ($table != '')
+		{
+			$this->from($table);
+		}
+
+		if ( ! is_null($where))
+		{
+			$this->where($where);
+		}
+
+		if ( ! is_null($limit))
+		{
+			$this->limit($limit, $offset);
+		}
+
+		$sql = $this->driver->compile_select(get_object_vars($this));
+
+		$this->reset_select();
+
+		$result = $this->query($sql);
+
+		return $result;
+	}
+
+	/**
+	 * Compiles the select statement based on the other functions called and returns the query string.
+	 *
+	 * @param   string  table name
+	 * @param   string  limit clause
+	 * @param   string  offset clause
+	 * @return  string  sql string
+	 */
+	public function compile($table = '', $limit = NULL, $offset = NULL)
+	{
+		if ($table != '')
+		{
+			$this->from($table);
+		}
+
+		if ( ! is_null($limit))
+		{
+			$this->limit($limit, $offset);
+		}
+
+		$sql = $this->driver->compile_select(get_object_vars($this));
+
+		$this->reset_select();
+
+		return $sql;
+	}
+
+	/**
+	 * Compiles an insert string and runs the query.
+	 *
+	 * @param   string  table name
+	 * @param   array   array of key/value pairs to insert
+	 * @return  Database_Result  Query result
+	 */
+	public function insert($table = '', $set = NULL)
+	{
+		if ( ! is_null($set))
+		{
+			$this->set($set);
+		}
+
+		if ($this->set == NULL)
+			throw new Kohana_Database_Exception('database.must_use_set');
+
+		if ($table == '')
+		{
+			if ( ! isset($this->from[0]))
+				throw new Kohana_Database_Exception('database.must_use_table');
+
+			$table = $this->from[0];
+		}
+
+		// If caching is enabled, clear the cache before inserting
+		($this->config['cache'] === TRUE) and $this->clear_cache();
+
+		$sql = $this->driver->insert($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
+
+		$this->reset_write();
+
+		return $this->query($sql);
+	}
+
+	/**
+	 * Adds an "IN" condition to the where clause
+	 *
+	 * @param   string  Name of the column being examined
+	 * @param   mixed   An array or string to match against
+	 * @param   bool    Generate a NOT IN clause instead
+	 * @return  Database_Core  This Database object.
+	 */
+	public function in($field, $values, $not = FALSE)
+	{
+		if (is_array($values))
+		{
+			$escaped_values = array();
+			foreach ($values as $v)
+			{
+				if (is_numeric($v))
+				{
+					$escaped_values[] = $v;
+				}
+				else
+				{
+					$escaped_values[] = "'".$this->driver->escape_str($v)."'";
+				}
+			}
+			$values = implode(",", $escaped_values);
+		}
+
+		$where = $this->driver->escape_column(((strpos($field,'.') !== FALSE) ? $this->config['table_prefix'] : ''). $field).' '.($not === TRUE ? 'NOT ' : '').'IN ('.$values.')';
+		$this->where[] = $this->driver->where($where, '', 'AND ', count($this->where), -1);
+
+		return $this;
+	}
+
+	/**
+	 * Adds a "NOT IN" condition to the where clause
+	 *
+	 * @param   string  Name of the column being examined
+	 * @param   mixed   An array or string to match against
+	 * @return  Database_Core  This Database object.
+	 */
+	public function notin($field, $values)
+	{
+		return $this->in($field, $values, TRUE);
+	}
+
+	/**
+	 * Compiles a merge string and runs the query.
+	 *
+	 * @param   string  table name
+	 * @param   array   array of key/value pairs to merge
+	 * @return  Database_Result  Query result
+	 */
+	public function merge($table = '', $set = NULL)
+	{
+		if ( ! is_null($set))
+		{
+			$this->set($set);
+		}
+
+		if ($this->set == NULL)
+			throw new Kohana_Database_Exception('database.must_use_set');
+
+		if ($table == '')
+		{
+			if ( ! isset($this->from[0]))
+				throw new Kohana_Database_Exception('database.must_use_table');
+
+			$table = $this->from[0];
+		}
+
+		$sql = $this->driver->merge($this->config['table_prefix'].$table, array_keys($this->set), array_values($this->set));
+
+		$this->reset_write();
+		return $this->query($sql);
+	}
+
+	/**
+	 * Compiles an update string and runs the query.
+	 *
+	 * @param   string  table name
+	 * @param   array   associative array of update values
+	 * @param   array   where clause
+	 * @return  Database_Result  Query result
+	 */
+	public function update($table = '', $set = NULL, $where = NULL)
+	{
+		if ( is_array($set))
+		{
+			$this->set($set);
+		}
+
+		if ( ! is_null($where))
+		{
+			$this->where($where);
+		}
+
+		if ($this->set == FALSE)
+			throw new Kohana_Database_Exception('database.must_use_set');
+
+		if ($table == '')
+		{
+			if ( ! isset($this->from[0]))
+				throw new Kohana_Database_Exception('database.must_use_table');
+
+			$table = $this->from[0];
+		}
+
+		$sql = $this->driver->update($this->config['table_prefix'].$table, $this->set, $this->where);
+
+		$this->reset_write();
+		return $this->query($sql);
+	}
+
+	/**
+	 * Compiles a delete string and runs the query.
+	 *
+	 * @param   string  table name
+	 * @param   array   where clause
+	 * @return  Database_Result  Query result
+	 */
+	public function delete($table = '', $where = NULL)
+	{
+		if ($table == '')
+		{
+			if ( ! isset($this->from[0]))
+				throw new Kohana_Database_Exception('database.must_use_table');
+
+			$table = $this->from[0];
+		}
+		else
+		{
+			$table = $this->config['table_prefix'].$table;
+		}
+
+		if (! is_null($where))
+		{
+			$this->where($where);
+		}
+
+		if (count($this->where) < 1)
+			throw new Kohana_Database_Exception('database.must_use_where');
+
+		$sql = $this->driver->delete($table, $this->where);
+
+		$this->reset_write();
+		return $this->query($sql);
+	}
+
+	/**
+	 * Returns the last query run.
+	 *
+	 * @return  string SQL
+	 */
+	public function last_query()
+	{
+	   return $this->last_query;
+	}
+
+	/**
+	 * Count query records.
+	 *
+	 * @param   string   table name
+	 * @param   array    where clause
+	 * @return  integer
+	 */
+	public function count_records($table = FALSE, $where = NULL)
+	{
+		if (count($this->from) < 1)
+		{
+			if ($table == FALSE)
+				throw new Kohana_Database_Exception('database.must_use_table');
+
+			$this->from($table);
+		}
+
+		if ($where !== NULL)
+		{
+			$this->where($where);
+		}
+
+		$query = $this->select('COUNT(*) AS '.$this->escape_column('records_found'))->get()->result(TRUE);
+
+		return (int) $query->current()->records_found;
+	}
+
+	/**
+	 * Resets all private select variables.
+	 *
+	 * @return  void
+	 */
+	protected function reset_select()
+	{
+		$this->select   = array();
+		$this->from     = array();
+		$this->join     = array();
+		$this->where    = array();
+		$this->orderby  = array();
+		$this->groupby  = array();
+		$this->having   = array();
+		$this->distinct = FALSE;
+		$this->limit    = FALSE;
+		$this->offset   = FALSE;
+	}
+
+	/**
+	 * Resets all private insert and update variables.
+	 *
+	 * @return  void
+	 */
+	protected function reset_write()
+	{
+		$this->set   = array();
+		$this->from  = array();
+		$this->where = array();
+	}
+
+	/**
+	 * Lists all the tables in the current database.
+	 *
+	 * @return  array
+	 */
+	public function list_tables()
+	{
+		$this->link or $this->connect();
+
+		return $this->driver->list_tables($this);
+	}
+
+	/**
+	 * See if a table exists in the database.
+	 *
+	 * @param   string   table name
+	 * @return  boolean
+	 */
+	public function table_exists($table_name)
+	{
+		return in_array($this->config['table_prefix'].$table_name, $this->list_tables());
+	}
+
+	/**
+	 * Combine a SQL statement with the bind values. Used for safe queries.
+	 *
+	 * @param   string  query to bind to the values
+	 * @param   array   array of values to bind to the query
+	 * @return  string
+	 */
+	public function compile_binds($sql, $binds)
+	{
+		foreach ((array) $binds as $val)
+		{
+			// If the SQL contains no more bind marks ("?"), we're done.
+			if (($next_bind_pos = strpos($sql, '?')) === FALSE)
+				break;
+
+			// Properly escape the bind value.
+			$val = $this->driver->escape($val);
+
+			// Temporarily replace possible bind marks ("?"), in the bind value itself, with a placeholder.
+			$val = str_replace('?', '{%B%}', $val);
+
+			// Replace the first bind mark ("?") with its corresponding value.
+			$sql = substr($sql, 0, $next_bind_pos).$val.substr($sql, $next_bind_pos + 1);
+		}
+
+		// Restore placeholders.
+		return str_replace('{%B%}', '?', $sql);
+	}
+
+	/**
+	 * Get the field data for a database table, along with the field's attributes.
+	 *
+	 * @param   string  table name
+	 * @return  array
+	 */
+	public function field_data($table = '')
+	{
+		$this->link or $this->connect();
+
+		return $this->driver->field_data($this->config['table_prefix'].$table);
+	}
+
+	/**
+	 * Get the field data for a database table, along with the field's attributes.
+	 *
+	 * @param   string  table name
+	 * @return  array
+	 */
+	public function list_fields($table = '')
+	{
+		$this->link or $this->connect();
+
+		return $this->driver->list_fields($this->config['table_prefix'].$table);
+	}
+
+	/**
+	 * Escapes a value for a query.
+	 *
+	 * @param   mixed   value to escape
+	 * @return  string
+	 */
+	public function escape($value)
+	{
+		return $this->driver->escape($value);
+	}
+
+	/**
+	 * Escapes a string for a query.
+	 *
+	 * @param   string  string to escape
+	 * @return  string
+	 */
+	public function escape_str($str)
+	{
+		return $this->driver->escape_str($str);
+	}
+
+	/**
+	 * Escapes a table name for a query.
+	 *
+	 * @param   string  string to escape
+	 * @return  string
+	 */
+	public function escape_table($table)
+	{
+		return $this->driver->escape_table($table);
+	}
+
+	/**
+	 * Escapes a column name for a query.
+	 *
+	 * @param   string  string to escape
+	 * @return  string
+	 */
+	public function escape_column($table)
+	{
+		return $this->driver->escape_column($table);
+	}
+
+	/**
+	 * Returns table prefix of current configuration.
+	 *
+	 * @return  string
+	 */
+	public function table_prefix()
+	{
+		return $this->config['table_prefix'];
+	}
+
+	/**
+	 * Clears the query cache.
+	 *
+	 * @param   string|TRUE  clear cache by SQL statement or TRUE for last query
+	 * @return  Database_Core       This Database object.
+	 */
+	public function clear_cache($sql = NULL)
+	{
+		if ($sql === TRUE)
+		{
+			$this->driver->clear_cache($this->last_query);
+		}
+		elseif (is_string($sql))
+		{
+			$this->driver->clear_cache($sql);
+		}
+		else
+		{
+			$this->driver->clear_cache();
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Create a prepared statement (experimental).
+	 *
+	 * @param   string  SQL query
+	 * @return  object
+	 */
+	public function stmt_prepare($sql)
+	{
+		return $this->driver->stmt_prepare($sql, $this->config);
+	}
+
+	/**
+	 * Pushes existing query space onto the query stack.  Use push
+	 * and pop to prevent queries from clashing before they are
+	 * executed
+	 *
+	 * @return Database_Core This Databaes object
+	 */
+	public function push()
+	{	
+		array_push($this->query_history, array(
+			$this->select,
+			$this->from,
+			$this->join,
+			$this->where,
+			$this->orderby,
+			$this->order,
+			$this->groupby,
+			$this->having,
+			$this->distinct,
+			$this->limit,
+			$this->offset
+		));
+
+		$this->reset_select();
+
+		return $this;
+	}
+
+	/**
+	 * Pops from query stack into the current query space.
+	 *
+	 * @return Database_Core This Databaes object
+	 */
+	public function pop()
+	{
+		if (count($this->query_history) == 0)
+		{
+			// No history
+			return $this;
+		}
+	
+		list(
+			$this->select,
+			$this->from,
+			$this->join,
+			$this->where,
+			$this->orderby,
+			$this->order,
+			$this->groupby,
+			$this->having,
+			$this->distinct,
+			$this->limit,
+			$this->offset
+		) = array_pop($this->query_history);
+
+		return $this;
+	}
+
+
+} // End Database Class
+
+
+/**
+ * Sets the code for a Database exception.
+ */
+class Kohana_Database_Exception extends Kohana_Exception {
+
+	protected $code = E_DATABASE_ERROR;
+
+} // End Kohana Database Exception
diff --git a/system/libraries/Encrypt.php b/system/libraries/Encrypt.php
new file mode 100755
index 0000000..7040d19
--- /dev/null
+++ b/system/libraries/Encrypt.php
@@ -0,0 +1,164 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * The Encrypt library provides two-way encryption of text and binary strings
+ * using the MCrypt extension.
+ * @see http://php.net/mcrypt
+ *
+ * $Id: Encrypt.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Encrypt_Core {
+
+	// OS-dependant RAND type to use
+	protected static $rand;
+
+	// Configuration
+	protected $config;
+
+	/**
+	 * Returns a singleton instance of Encrypt.
+	 *
+	 * @param   array  configuration options
+	 * @return  Encrypt_Core
+	 */
+	public static function instance($config = NULL)
+	{
+		static $instance;
+
+		// Create the singleton
+		empty($instance) and $instance = new Encrypt((array) $config);
+
+		return $instance;
+	}
+
+	/**
+	 * Loads encryption configuration and validates the data.
+	 *
+	 * @param   array|string      custom configuration or config group name
+	 * @throws  Kohana_Exception
+	 */
+	public function __construct($config = FALSE)
+	{
+		if ( ! defined('MCRYPT_ENCRYPT'))
+			throw new Kohana_Exception('encrypt.requires_mcrypt');
+
+		if (is_string($config))
+		{
+			$name = $config;
+
+			// Test the config group name
+			if (($config = Kohana::config('encryption.'.$config)) === NULL)
+				throw new Kohana_Exception('encrypt.undefined_group', $name);
+		}
+
+		if (is_array($config))
+		{
+			// Append the default configuration options
+			$config += Kohana::config('encryption.default');
+		}
+		else
+		{
+			// Load the default group
+			$config = Kohana::config('encryption.default');
+		}
+
+		if (empty($config['key']))
+			throw new Kohana_Exception('encrypt.no_encryption_key');
+
+		// Find the max length of the key, based on cipher and mode
+		$size = mcrypt_get_key_size($config['cipher'], $config['mode']);
+
+		if (strlen($config['key']) > $size)
+		{
+			// Shorten the key to the maximum size
+			$config['key'] = substr($config['key'], 0, $size);
+		}
+
+		// Find the initialization vector size
+		$config['iv_size'] = mcrypt_get_iv_size($config['cipher'], $config['mode']);
+
+		// Cache the config in the object
+		$this->config = $config;
+
+		Kohana::log('debug', 'Encrypt Library initialized');
+	}
+
+	/**
+	 * Encrypts a string and returns an encrypted string that can be decoded.
+	 *
+	 * @param   string  data to be encrypted
+	 * @return  string  encrypted data
+	 */
+	public function encode($data)
+	{
+		// Set the rand type if it has not already been set
+		if (self::$rand === NULL)
+		{
+			if (KOHANA_IS_WIN)
+			{
+				// Windows only supports the system random number generator
+				self::$rand = MCRYPT_RAND;
+			}
+			else
+			{
+				if (defined('MCRYPT_DEV_URANDOM'))
+				{
+					// Use /dev/urandom
+					self::$rand = MCRYPT_DEV_URANDOM;
+				}
+				elseif (defined('MCRYPT_DEV_RANDOM'))
+				{
+					// Use /dev/random
+					self::$rand = MCRYPT_DEV_RANDOM;
+				}
+				else
+				{
+					// Use the system random number generator
+					self::$rand = MCRYPT_RAND;
+				}
+			}
+		}
+
+		if (self::$rand === MCRYPT_RAND)
+		{
+			// The system random number generator must always be seeded each
+			// time it is used, or it will not produce true random results
+			mt_srand();
+		}
+
+		// Create a random initialization vector of the proper size for the current cipher
+		$iv = mcrypt_create_iv($this->config['iv_size'], self::$rand);
+
+		// Encrypt the data using the configured options and generated iv
+		$data = mcrypt_encrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv);
+
+		// Use base64 encoding to convert to a string
+		return base64_encode($iv.$data);
+	}
+
+	/**
+	 * Decrypts an encoded string back to its original value.
+	 *
+	 * @param   string  encoded string to be decrypted
+	 * @return  string  decrypted data
+	 */
+	public function decode($data)
+	{
+		// Convert the data back to binary
+		$data = base64_decode($data);
+
+		// Extract the initialization vector from the data
+		$iv = substr($data, 0, $this->config['iv_size']);
+
+		// Remove the iv from the data
+		$data = substr($data, $this->config['iv_size']);
+
+		// Return the decrypted data, trimming the \0 padding bytes from the end of the data
+		return rtrim(mcrypt_decrypt($this->config['cipher'], $this->config['key'], $data, $this->config['mode'], $iv), "\0");
+	}
+
+} // End Encrypt
diff --git a/system/libraries/Event_Observer.php b/system/libraries/Event_Observer.php
new file mode 100755
index 0000000..e4b194f
--- /dev/null
+++ b/system/libraries/Event_Observer.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana event observer. Uses the SPL observer pattern.
+ *
+ * $Id: Event_Observer.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Event_Observer implements SplObserver {
+
+	// Calling object
+	protected $caller;
+
+	/**
+	 * Initializes a new observer and attaches the subject as the caller.
+	 *
+	 * @param   object  Event_Subject
+	 * @return  void
+	 */
+	public function __construct(SplSubject $caller)
+	{
+		// Update the caller
+		$this->update($caller);
+	}
+
+	/**
+	 * Updates the observer subject with a new caller.
+	 *
+	 * @chainable
+	 * @param   object  Event_Subject
+	 * @return  object
+	 */
+	public function update(SplSubject $caller)
+	{
+		if ( ! ($caller instanceof Event_Subject))
+			throw new Kohana_Exception('event.invalid_subject', get_class($caller), get_class($this));
+
+		// Update the caller
+		$this->caller = $caller;
+
+		return $this;
+	}
+
+	/**
+	 * Detaches this observer from the subject.
+	 *
+	 * @chainable
+	 * @return  object
+	 */
+	public function remove()
+	{
+		// Detach this observer from the caller
+		$this->caller->detach($this);
+
+		return $this;
+	}
+
+	/**
+	 * Notify the observer of a new message. This function must be defined in
+	 * all observers and must take exactly one parameter of any type.
+	 *
+	 * @param   mixed   message string, object, or array
+	 * @return  void
+	 */
+	abstract public function notify($message);
+
+} // End Event Observer
\ No newline at end of file
diff --git a/system/libraries/Event_Subject.php b/system/libraries/Event_Subject.php
new file mode 100755
index 0000000..f571d17
--- /dev/null
+++ b/system/libraries/Event_Subject.php
@@ -0,0 +1,67 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Kohana event subject. Uses the SPL observer pattern.
+ *
+ * $Id: Event_Subject.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Event_Subject implements SplSubject {
+
+	// Attached subject listeners
+	protected $listeners = array();
+
+	/**
+	 * Attach an observer to the object.
+	 *
+	 * @chainable
+	 * @param   object  Event_Observer
+	 * @return  object
+	 */
+	public function attach(SplObserver $obj)
+	{
+		if ( ! ($obj instanceof Event_Observer))
+			throw new Kohana_Exception('eventable.invalid_observer', get_class($obj), get_class($this));
+
+		// Add a new listener
+		$this->listeners[spl_object_hash($obj)] = $obj;
+
+		return $this;
+	}
+
+	/**
+	 * Detach an observer from the object.
+	 *
+	 * @chainable
+	 * @param   object  Event_Observer
+	 * @return  object
+	 */
+	public function detach(SplObserver $obj)
+	{
+		// Remove the listener
+		unset($this->listeners[spl_object_hash($obj)]);
+
+		return $this;
+	}
+
+	/**
+	 * Notify all attached observers of a new message.
+	 *
+	 * @chainable
+	 * @param   mixed   message string, object, or array
+	 * @return  object
+	 */
+	public function notify($message)
+	{
+		foreach ($this->listeners as $obj)
+		{
+			$obj->notify($message);
+		}
+
+		return $this;
+	}
+
+} // End Event Subject
\ No newline at end of file
diff --git a/system/libraries/Image.php b/system/libraries/Image.php
new file mode 100755
index 0000000..1a88899
--- /dev/null
+++ b/system/libraries/Image.php
@@ -0,0 +1,431 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Manipulate images using standard methods such as resize, crop, rotate, etc.
+ * This class must be re-initialized for every image you wish to manipulate.
+ *
+ * $Id: Image.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Image_Core {
+
+	// Master Dimension
+	const NONE = 1;
+	const AUTO = 2;
+	const HEIGHT = 3;
+	const WIDTH = 4;
+	// Flip Directions
+	const HORIZONTAL = 5;
+	const VERTICAL = 6;
+
+	// Allowed image types
+	public static $allowed_types = array
+	(
+		IMAGETYPE_GIF => 'gif',
+		IMAGETYPE_JPEG => 'jpg',
+		IMAGETYPE_PNG => 'png',
+		IMAGETYPE_TIFF_II => 'tiff',
+		IMAGETYPE_TIFF_MM => 'tiff',
+	);
+
+	// Driver instance
+	protected $driver;
+
+	// Driver actions
+	protected $actions = array();
+
+	// Reference to the current image filename
+	protected $image = '';
+
+	/**
+	 * Creates a new Image instance and returns it.
+	 *
+	 * @param   string   filename of image
+	 * @param   array    non-default configurations
+	 * @return  object
+	 */
+	public static function factory($image, $config = NULL)
+	{
+		return new Image($image, $config);
+	}
+
+	/**
+	 * Creates a new image editor instance.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   string   filename of image
+	 * @param   array    non-default configurations
+	 * @return  void
+	 */
+	public function __construct($image, $config = NULL)
+	{
+		static $check;
+
+		// Make the check exactly once
+		($check === NULL) and $check = function_exists('getimagesize');
+
+		if ($check === FALSE)
+			throw new Kohana_Exception('image.getimagesize_missing');
+
+		// Check to make sure the image exists
+		if ( ! is_file($image))
+			throw new Kohana_Exception('image.file_not_found', $image);
+
+		// Disable error reporting, to prevent PHP warnings
+		$ER = error_reporting(0);
+
+		// Fetch the image size and mime type
+		$image_info = getimagesize($image);
+
+		// Turn on error reporting again
+		error_reporting($ER);
+
+		// Make sure that the image is readable and valid
+		if ( ! is_array($image_info) OR count($image_info) < 3)
+			throw new Kohana_Exception('image.file_unreadable', $image);
+
+		// Check to make sure the image type is allowed
+		if ( ! isset(Image::$allowed_types[$image_info[2]]))
+			throw new Kohana_Exception('image.type_not_allowed', $image);
+
+		// Image has been validated, load it
+		$this->image = array
+		(
+			'file' => str_replace('\\', '/', realpath($image)),
+			'width' => $image_info[0],
+			'height' => $image_info[1],
+			'type' => $image_info[2],
+			'ext' => Image::$allowed_types[$image_info[2]],
+			'mime' => $image_info['mime']
+		);
+
+		// Load configuration
+		$this->config = (array) $config + Kohana::config('image');
+
+		// Set driver class name
+		$driver = 'Image_'.ucfirst($this->config['driver']).'_Driver';
+
+		// Load the driver
+		if ( ! Kohana::auto_load($driver))
+			throw new Kohana_Exception('core.driver_not_found', $this->config['driver'], get_class($this));
+
+		// Initialize the driver
+		$this->driver = new $driver($this->config['params']);
+
+		// Validate the driver
+		if ( ! ($this->driver instanceof Image_Driver))
+			throw new Kohana_Exception('core.driver_implements', $this->config['driver'], get_class($this), 'Image_Driver');
+	}
+
+	/**
+	 * Handles retrieval of pre-save image properties
+	 *
+	 * @param   string  property name
+	 * @return  mixed
+	 */
+	public function __get($property)
+	{
+		if (isset($this->image[$property]))
+		{
+			return $this->image[$property];
+		}
+		else
+		{
+			throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+		}
+	}
+
+	/**
+	 * Resize an image to a specific width and height. By default, Kohana will
+	 * maintain the aspect ratio using the width as the master dimension. If you
+	 * wish to use height as master dim, set $image->master_dim = Image::HEIGHT
+	 * This method is chainable.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   integer  width
+	 * @param   integer  height
+	 * @param   integer  one of: Image::NONE, Image::AUTO, Image::WIDTH, Image::HEIGHT
+	 * @return  object
+	 */
+	public function resize($width, $height, $master = NULL)
+	{
+		if ( ! $this->valid_size('width', $width))
+			throw new Kohana_Exception('image.invalid_width', $width);
+
+		if ( ! $this->valid_size('height', $height))
+			throw new Kohana_Exception('image.invalid_height', $height);
+
+		if (empty($width) AND empty($height))
+			throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
+
+		if ($master === NULL)
+		{
+			// Maintain the aspect ratio by default
+			$master = Image::AUTO;
+		}
+		elseif ( ! $this->valid_size('master', $master))
+			throw new Kohana_Exception('image.invalid_master');
+
+		$this->actions['resize'] = array
+		(
+			'width'  => $width,
+			'height' => $height,
+			'master' => $master,
+		);
+
+		return $this;
+	}
+
+	/**
+	 * Crop an image to a specific width and height. You may also set the top
+	 * and left offset.
+	 * This method is chainable.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   integer  width
+	 * @param   integer  height
+	 * @param   integer  top offset, pixel value or one of: top, center, bottom
+	 * @param   integer  left offset, pixel value or one of: left, center, right
+	 * @return  object
+	 */
+	public function crop($width, $height, $top = 'center', $left = 'center')
+	{
+		if ( ! $this->valid_size('width', $width))
+			throw new Kohana_Exception('image.invalid_width', $width);
+
+		if ( ! $this->valid_size('height', $height))
+			throw new Kohana_Exception('image.invalid_height', $height);
+
+		if ( ! $this->valid_size('top', $top))
+			throw new Kohana_Exception('image.invalid_top', $top);
+
+		if ( ! $this->valid_size('left', $left))
+			throw new Kohana_Exception('image.invalid_left', $left);
+
+		if (empty($width) AND empty($height))
+			throw new Kohana_Exception('image.invalid_dimensions', __FUNCTION__);
+
+		$this->actions['crop'] = array
+		(
+			'width'  => $width,
+			'height' => $height,
+			'top'    => $top,
+			'left'   => $left,
+		);
+
+		return $this;
+	}
+
+	/**
+	 * Allows rotation of an image by 180 degrees clockwise or counter clockwise.
+	 *
+	 * @param   integer  degrees
+	 * @return  object
+	 */
+	public function rotate($degrees)
+	{
+		$degrees = (int) $degrees;
+
+		if ($degrees > 180)
+		{
+			do
+			{
+				// Keep subtracting full circles until the degrees have normalized
+				$degrees -= 360;
+			}
+			while($degrees > 180);
+		}
+
+		if ($degrees < -180)
+		{
+			do
+			{
+				// Keep adding full circles until the degrees have normalized
+				$degrees += 360;
+			}
+			while($degrees < -180);
+		}
+
+		$this->actions['rotate'] = $degrees;
+
+		return $this;
+	}
+
+	/**
+	 * Flip an image horizontally or vertically.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   integer  direction
+	 * @return  object
+	 */
+	public function flip($direction)
+	{
+		if ($direction !== self::HORIZONTAL AND $direction !== self::VERTICAL)
+			throw new Kohana_Exception('image.invalid_flip');
+
+		$this->actions['flip'] = $direction;
+
+		return $this;
+	}
+
+	/**
+	 * Change the quality of an image.
+	 *
+	 * @param   integer  quality as a percentage
+	 * @return  object
+	 */
+	public function quality($amount)
+	{
+		$this->actions['quality'] = max(1, min($amount, 100));
+
+		return $this;
+	}
+
+	/**
+	 * Sharpen an image.
+	 *
+	 * @param   integer  amount to sharpen, usually ~20 is ideal
+	 * @return  object
+	 */
+	public function sharpen($amount)
+	{
+		$this->actions['sharpen'] = max(1, min($amount, 100));
+
+		return $this;
+	}
+
+	/**
+	 * Save the image to a new image or overwrite this image.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   string   new image filename
+	 * @param   integer  permissions for new image
+	 * @param   boolean  keep or discard image process actions
+	 * @return  object
+	 */
+	public function save($new_image = FALSE, $chmod = 0644, $keep_actions = FALSE)
+	{
+		// If no new image is defined, use the current image
+		empty($new_image) and $new_image = $this->image['file'];
+
+		// Separate the directory and filename
+		$dir  = pathinfo($new_image, PATHINFO_DIRNAME);
+		$file = pathinfo($new_image, PATHINFO_BASENAME);
+
+		// Normalize the path
+		$dir = str_replace('\\', '/', realpath($dir)).'/';
+
+		if ( ! is_writable($dir))
+			throw new Kohana_Exception('image.directory_unwritable', $dir);
+
+		if ($status = $this->driver->process($this->image, $this->actions, $dir, $file))
+		{
+			if ($chmod !== FALSE)
+			{
+				// Set permissions
+				chmod($new_image, $chmod);
+			}
+		}
+		
+		// Reset actions. Subsequent save() or render() will not apply previous actions.
+		if ($keep_actions === FALSE)
+			$this->actions = array();
+		
+		return $status;
+	}
+	
+	/** 
+	 * Output the image to the browser. 
+	 * 
+	 * @param   boolean  keep or discard image process actions
+	 * @return	object 
+	 */ 
+	public function render($keep_actions = FALSE) 
+	{ 
+		$new_image = $this->image['file']; 
+	
+		// Separate the directory and filename 
+		$dir  = pathinfo($new_image, PATHINFO_DIRNAME); 
+		$file = pathinfo($new_image, PATHINFO_BASENAME); 
+	
+		// Normalize the path 
+		$dir = str_replace('\\', '/', realpath($dir)).'/'; 
+	
+		// Process the image with the driver 
+		$status = $this->driver->process($this->image, $this->actions, $dir, $file, $render = TRUE); 
+		
+		// Reset actions. Subsequent save() or render() will not apply previous actions.
+		if ($keep_actions === FALSE)
+			$this->actions = array();
+		
+		return $status; 
+	}
+
+	/**
+	 * Sanitize a given value type.
+	 *
+	 * @param   string   type of property
+	 * @param   mixed    property value
+	 * @return  boolean
+	 */
+	protected function valid_size($type, & $value)
+	{
+		if (is_null($value))
+			return TRUE;
+
+		if ( ! is_scalar($value))
+			return FALSE;
+
+		switch ($type)
+		{
+			case 'width':
+			case 'height':
+				if (is_string($value) AND ! ctype_digit($value))
+				{
+					// Only numbers and percent signs
+					if ( ! preg_match('/^[0-9]++%$/D', $value))
+						return FALSE;
+				}
+				else
+				{
+					$value = (int) $value;
+				}
+			break;
+			case 'top':
+				if (is_string($value) AND ! ctype_digit($value))
+				{
+					if ( ! in_array($value, array('top', 'bottom', 'center')))
+						return FALSE;
+				}
+				else
+				{
+					$value = (int) $value;
+				}
+			break;
+			case 'left':
+				if (is_string($value) AND ! ctype_digit($value))
+				{
+					if ( ! in_array($value, array('left', 'right', 'center')))
+						return FALSE;
+				}
+				else
+				{
+					$value = (int) $value;
+				}
+			break;
+			case 'master':
+				if ($value !== Image::NONE AND
+				    $value !== Image::AUTO AND
+				    $value !== Image::WIDTH AND
+				    $value !== Image::HEIGHT)
+					return FALSE;
+			break;
+		}
+
+		return TRUE;
+	}
+
+} // End Image
\ No newline at end of file
diff --git a/system/libraries/Input.php b/system/libraries/Input.php
new file mode 100755
index 0000000..2225036
--- /dev/null
+++ b/system/libraries/Input.php
@@ -0,0 +1,454 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Input library.
+ *
+ * $Id: Input.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Input_Core {
+
+	// Enable or disable automatic XSS cleaning
+	protected $use_xss_clean = FALSE;
+
+	// Are magic quotes enabled?
+	protected $magic_quotes_gpc = FALSE;
+
+	// IP address of current user
+	public $ip_address;
+
+	// Input singleton
+	protected static $instance;
+
+	/**
+	 * Retrieve a singleton instance of Input. This will always be the first
+	 * created instance of this class.
+	 *
+	 * @return  object
+	 */
+	public static function instance()
+	{
+		if (self::$instance === NULL)
+		{
+			// Create a new instance
+			return new Input;
+		}
+
+		return self::$instance;
+	}
+
+	/**
+	 * Sanitizes global GET, POST and COOKIE data. Also takes care of
+	 * magic_quotes and register_globals, if they have been enabled.
+	 *
+	 * @return  void
+	 */
+	public function __construct()
+	{
+		// Use XSS clean?
+		$this->use_xss_clean = (bool) Kohana::config('core.global_xss_filtering');
+
+		if (self::$instance === NULL)
+		{
+			// magic_quotes_runtime is enabled
+			if (get_magic_quotes_runtime())
+			{
+				set_magic_quotes_runtime(0);
+				Kohana::log('debug', 'Disable magic_quotes_runtime! It is evil and deprecated: http://php.net/magic_quotes');
+			}
+
+			// magic_quotes_gpc is enabled
+			if (get_magic_quotes_gpc())
+			{
+				$this->magic_quotes_gpc = TRUE;
+				Kohana::log('debug', 'Disable magic_quotes_gpc! It is evil and deprecated: http://php.net/magic_quotes');
+			}
+
+			// register_globals is enabled
+			if (ini_get('register_globals'))
+			{
+				if (isset($_REQUEST['GLOBALS']))
+				{
+					// Prevent GLOBALS override attacks
+					exit('Global variable overload attack.');
+				}
+
+				// Destroy the REQUEST global
+				$_REQUEST = array();
+
+				// These globals are standard and should not be removed
+				$preserve = array('GLOBALS', '_REQUEST', '_GET', '_POST', '_FILES', '_COOKIE', '_SERVER', '_ENV', '_SESSION');
+
+				// This loop has the same effect as disabling register_globals
+				foreach (array_diff(array_keys($GLOBALS), $preserve) as $key)
+				{
+					global $$key;
+					$$key = NULL;
+
+					// Unset the global variable
+					unset($GLOBALS[$key], $$key);
+				}
+
+				// Warn the developer about register globals
+				Kohana::log('debug', 'Disable register_globals! It is evil and deprecated: http://php.net/register_globals');
+			}
+
+			if (is_array($_GET))
+			{
+				foreach ($_GET as $key => $val)
+				{
+					// Sanitize $_GET
+					$_GET[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+				}
+			}
+			else
+			{
+				$_GET = array();
+			}
+
+			if (is_array($_POST))
+			{
+				foreach ($_POST as $key => $val)
+				{
+					// Sanitize $_POST
+					$_POST[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+				}
+			}
+			else
+			{
+				$_POST = array();
+			}
+
+			if (is_array($_COOKIE))
+			{
+				foreach ($_COOKIE as $key => $val)
+				{
+					// Ignore special attributes in RFC2109 compliant cookies
+					if ($key == '$Version' OR $key == '$Path' OR $key == '$Domain')
+						continue;
+
+					// Sanitize $_COOKIE
+					$_COOKIE[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+				}
+			}
+			else
+			{
+				$_COOKIE = array();
+			}
+
+			// Create a singleton
+			self::$instance = $this;
+
+			Kohana::log('debug', 'Global GET, POST and COOKIE data sanitized');
+		}
+	}
+
+	/**
+	 * Fetch an item from the $_GET array.
+	 *
+	 * @param   string   key to find
+	 * @param   mixed    default value
+	 * @param   boolean  XSS clean the value
+	 * @return  mixed
+	 */
+	public function get($key = array(), $default = NULL, $xss_clean = FALSE)
+	{
+		return $this->search_array($_GET, $key, $default, $xss_clean);
+	}
+
+	/**
+	 * Fetch an item from the $_POST array.
+	 *
+	 * @param   string   key to find
+	 * @param   mixed    default value
+	 * @param   boolean  XSS clean the value
+	 * @return  mixed
+	 */
+	public function post($key = array(), $default = NULL, $xss_clean = FALSE)
+	{
+		return $this->search_array($_POST, $key, $default, $xss_clean);
+	}
+
+	/**
+	 * Fetch an item from the $_COOKIE array.
+	 *
+	 * @param   string   key to find
+	 * @param   mixed    default value
+	 * @param   boolean  XSS clean the value
+	 * @return  mixed
+	 */
+	public function cookie($key = array(), $default = NULL, $xss_clean = FALSE)
+	{
+		return $this->search_array($_COOKIE, $key, $default, $xss_clean);
+	}
+
+	/**
+	 * Fetch an item from the $_SERVER array.
+	 *
+	 * @param   string   key to find
+	 * @param   mixed    default value
+	 * @param   boolean  XSS clean the value
+	 * @return  mixed
+	 */
+	public function server($key = array(), $default = NULL, $xss_clean = FALSE)
+	{
+		return $this->search_array($_SERVER, $key, $default, $xss_clean);
+	}
+
+	/**
+	 * Fetch an item from a global array.
+	 *
+	 * @param   array    array to search
+	 * @param   string   key to find
+	 * @param   mixed    default value
+	 * @param   boolean  XSS clean the value
+	 * @return  mixed
+	 */
+	protected function search_array($array, $key, $default = NULL, $xss_clean = FALSE)
+	{
+		if ($key === array())
+			return $array;
+
+		if ( ! isset($array[$key]))
+			return $default;
+
+		// Get the value
+		$value = $array[$key];
+
+		if ($this->use_xss_clean === FALSE AND $xss_clean === TRUE)
+		{
+			// XSS clean the value
+			$value = $this->xss_clean($value);
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Fetch the IP Address.
+	 *
+	 * @return string
+	 */
+	public function ip_address()
+	{
+		if ($this->ip_address !== NULL)
+			return $this->ip_address;
+
+		if ($ip = $this->server('HTTP_CLIENT_IP'))
+		{
+			 $this->ip_address = $ip;
+		}
+		elseif ($ip = $this->server('REMOTE_ADDR'))
+		{
+			 $this->ip_address = $ip;
+		}
+		elseif ($ip = $this->server('HTTP_X_FORWARDED_FOR'))
+		{
+			 $this->ip_address = $ip;
+		}
+
+		if ($comma = strrpos($this->ip_address, ',') !== FALSE)
+		{
+			$this->ip_address = substr($this->ip_address, $comma + 1);
+		}
+
+		if ( ! valid::ip($this->ip_address))
+		{
+			// Use an empty IP
+			$this->ip_address = '0.0.0.0';
+		}
+
+		return $this->ip_address;
+	}
+
+	/**
+	 * Clean cross site scripting exploits from string.
+	 * HTMLPurifier may be used if installed, otherwise defaults to built in method.
+	 * Note - This function should only be used to deal with data upon submission.
+	 * It's not something that should be used for general runtime processing
+	 * since it requires a fair amount of processing overhead.
+	 *
+	 * @param   string  data to clean
+	 * @param   string  xss_clean method to use ('htmlpurifier' or defaults to built-in method)
+	 * @return  string
+	 */
+	public function xss_clean($data, $tool = NULL)
+	{
+		if ($tool === NULL)
+		{
+			// Use the default tool
+			$tool = Kohana::config('core.global_xss_filtering');
+		}
+
+		if (is_array($data))
+		{
+			foreach ($data as $key => $val)
+			{
+				$data[$key] = $this->xss_clean($val, $tool);
+			}
+
+			return $data;
+		}
+
+		// Do not clean empty strings
+		if (trim($data) === '')
+			return $data;
+
+		if ($tool === TRUE)
+		{
+			// NOTE: This is necessary because switch is NOT type-sensative!
+			$tool = 'default';
+		}
+
+		switch ($tool)
+		{
+			case 'htmlpurifier':
+				/**
+				 * @todo License should go here, http://htmlpurifier.org/
+				 */
+				if ( ! class_exists('HTMLPurifier_Config', FALSE))
+				{
+					// Load HTMLPurifier
+					require_once APPPATH.'libraries/htmlpurifier/HTMLPurifier.auto.php';
+					require 'HTMLPurifier.func.php';
+				}
+
+				// Set configuration
+				$config = HTMLPurifier_Config::createDefault();
+				$config->set('Cache.SerializerPath', APPPATH.'cache');
+				$config->set('HTML.TidyLevel', 'none'); // Only XSS cleaning now
+				$config->set('HTML.SafeIframe', true);
+				$config->set('URI.SafeIframeRegexp', Kohana::config('config.safe_iframe_regexp', FALSE, TRUE));
+
+				// Run HTMLPurifier
+				$data = HTMLPurifier($data, $config);
+			break;
+			default:
+				// http://svn.bitflux.ch/repos/public/popoon/trunk/classes/externalinput.php
+				// +----------------------------------------------------------------------+
+				// | Copyright (c) 2001-2006 Bitflux GmbH                                 |
+				// +----------------------------------------------------------------------+
+				// | Licensed under the Apache License, Version 2.0 (the "License");      |
+				// | you may not use this file except in compliance with the License.     |
+				// | You may obtain a copy of the License at                              |
+				// | http://www.apache.org/licenses/LICENSE-2.0                           |
+				// | Unless required by applicable law or agreed to in writing, software  |
+				// | distributed under the License is distributed on an "AS IS" BASIS,    |
+				// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or      |
+				// | implied. See the License for the specific language governing         |
+				// | permissions and limitations under the License.                       |
+				// +----------------------------------------------------------------------+
+				// | Author: Christian Stocker <chregu at bitflux.ch>                        |
+				// +----------------------------------------------------------------------+
+				//
+				// Kohana Modifications:
+				// * Changed double quotes to single quotes, changed indenting and spacing
+				// * Removed magic_quotes stuff
+				// * Increased regex readability:
+				//   * Used delimeters that aren't found in the pattern
+				//   * Removed all unneeded escapes
+				//   * Deleted U modifiers and swapped greediness where needed
+				// * Increased regex speed:
+				//   * Made capturing parentheses non-capturing where possible
+				//   * Removed parentheses where possible
+				//   * Split up alternation alternatives
+				//   * Made some quantifiers possessive
+
+				// Fix &entity\n;
+				$data = str_replace(array('&','<','>'), array('&amp;','&lt;','&gt;'), $data);
+				$data = preg_replace('/(&#*\w+)[\x00-\x20]+;/u', '$1;', $data);
+				$data = preg_replace('/(&#x*[0-9A-F]+);*/iu', '$1;', $data);
+				$data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
+
+				// Remove any attribute starting with "on" or xmlns
+				$data = preg_replace('#(<[^>]+?[\x00-\x20"\'])(?:on|xmlns)[^>]*+>#iu', '$1>', $data);
+
+				// Remove javascript: and vbscript: protocols
+				$data = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([`\'"]*)[\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2nojavascript...', $data);
+				$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iu', '$1=$2novbscript...', $data);
+				$data = preg_replace('#([a-z]*)[\x00-\x20]*=([\'"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#u', '$1=$2nomozbinding...', $data);
+
+				// Only works in IE: <span style="width: expression(alert('Ping!'));"></span>
+				$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?expression[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+				$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?behaviour[\x00-\x20]*\([^>]*+>#i', '$1>', $data);
+				$data = preg_replace('#(<[^>]+?)style[\x00-\x20]*=[\x00-\x20]*[`\'"]*.*?s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*+>#iu', '$1>', $data);
+
+				// Remove namespaced elements (we do not need them)
+				$data = preg_replace('#</*\w+:\w[^>]*+>#i', '', $data);
+
+				do
+				{
+					// Remove really unwanted tags
+					$old_data = $data;
+					$data = preg_replace('#</*(?:applet|b(?:ase|gsound|link)|embed|frame(?:set)?|i(?:layer)|l(?:ayer|ink)|meta|object|s(?:cript|tyle)|title|xml)[^>]*+>#i', '', $data);
+				}
+				while ($old_data !== $data);
+			break;
+		}
+
+		return $data;
+	}
+
+	/**
+	 * This is a helper method. It enforces W3C specifications for allowed
+	 * key name strings, to prevent malicious exploitation.
+	 *
+	 * @param   string  string to clean
+	 * @return  string
+	 */
+	public function clean_input_keys($str)
+	{
+		$chars = PCRE_UNICODE_PROPERTIES ? '\pL' : 'a-zA-Z';
+
+		if ( ! preg_match('#^['.$chars.'0-9:_.-]++$#uD', $str))
+		{
+			exit('Disallowed key characters in global data.');
+		}
+
+		return $str;
+	}
+
+	/**
+	 * This is a helper method. It escapes data and forces all newline
+	 * characters to "\n".
+	 *
+	 * @param   unknown_type  string to clean
+	 * @return  string
+	 */
+	public function clean_input_data($str)
+	{
+		if (is_array($str))
+		{
+			$new_array = array();
+			foreach ($str as $key => $val)
+			{
+				// Recursion!
+				$new_array[$this->clean_input_keys($key)] = $this->clean_input_data($val);
+			}
+			return $new_array;
+		}
+
+		if ($this->magic_quotes_gpc === TRUE)
+		{
+			// Remove annoying magic quotes
+			$str = stripslashes($str);
+		}
+
+		if ($this->use_xss_clean === TRUE)
+		{
+			$str = $this->xss_clean($str);
+		}
+
+		if (strpos($str, "\r") !== FALSE)
+		{
+			// Standardize newlines
+			$str = str_replace(array("\r\n", "\r"), "\n", $str);
+		}
+
+		return $str;
+	}
+
+} // End Input Class
diff --git a/system/libraries/Model.php b/system/libraries/Model.php
new file mode 100755
index 0000000..3e98250
--- /dev/null
+++ b/system/libraries/Model.php
@@ -0,0 +1,31 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Model base class.
+ *
+ * $Id: Model.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2009 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Model_Core {
+
+	// Database object
+	protected $db;
+
+	/**
+	 * Loads the database instance, if the database is not already loaded.
+	 *
+	 * @return  void
+	 */
+	public function __construct()
+	{
+		if ( ! is_object($this->db))
+		{
+			// Load the default database
+			$this->db = Database::instance('default');
+		}
+	}
+
+} // End Model
\ No newline at end of file
diff --git a/system/libraries/ORM.php b/system/libraries/ORM.php
new file mode 100755
index 0000000..57e47e0
--- /dev/null
+++ b/system/libraries/ORM.php
@@ -0,0 +1,1450 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * [Object Relational Mapping][ref-orm] (ORM) is a method of abstracting database
+ * access to standard PHP calls. All table rows are represented as model objects,
+ * with object properties representing row data. ORM in Kohana generally follows
+ * the [Active Record][ref-act] pattern.
+ *
+ * [ref-orm]: http://wikipedia.org/wiki/Object-relational_mapping
+ * [ref-act]: http://wikipedia.org/wiki/Active_record
+ *
+ * $Id: ORM.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    ORM
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class ORM_Core {
+
+	// Current relationships
+	protected $has_one                 = array();
+	protected $belongs_to              = array();
+	protected $has_many                = array();
+	protected $has_and_belongs_to_many = array();
+
+	// Relationships that should always be joined
+	protected $load_with = array();
+
+	// Current object
+	protected $object  = array();
+	protected $changed = array();
+	protected $related = array();
+	protected $loaded  = FALSE;
+	protected $saved   = FALSE;
+	protected $sorting;
+
+	// Related objects
+	protected $object_relations = array();
+	protected $changed_relations = array();
+
+	// Model table information
+	protected $object_name;
+	protected $object_plural;
+	protected $table_name;
+	protected $table_columns;
+	protected $ignored_columns;
+
+	// Table primary key and value
+	protected $primary_key = 'id';
+	protected $primary_val = 'name';
+
+	// Array of foreign key name overloads
+	protected $foreign_key = array();
+
+	// Model configuration
+	protected $table_names_plural = TRUE;
+	protected $reload_on_wakeup   = TRUE;
+
+	// Database configuration
+	protected $db = 'default';
+	protected $db_applied = array();
+
+	// With calls already applied
+	protected $with_applied = array();
+
+	// Stores column information for ORM models
+	protected static $column_cache = array();
+
+	/**
+	 * Creates and returns a new model.
+	 *
+	 * @chainable
+	 * @param   string  model name
+	 * @param   mixed   parameter for find()
+	 * @return  ORM
+	 */
+	public static function factory($model, $id = NULL)
+	{
+		// Set class name
+		$model = ucfirst($model).'_Model';
+
+		return new $model($id);
+	}
+
+	/**
+	 * Prepares the model database connection and loads the object.
+	 *
+	 * @param   mixed  parameter for find or object to load
+	 * @return  void
+	 */
+	public function __construct($id = NULL)
+	{
+		// Set the object name and plural name
+		$this->object_name   = strtolower(substr(get_class($this), 0, -6));
+		$this->object_plural = inflector::plural($this->object_name);
+
+		if (!isset($this->sorting))
+		{
+			// Default sorting
+			$this->sorting = array($this->primary_key => 'asc');
+		}
+
+		// Initialize database
+		$this->__initialize();
+
+		// Clear the object
+		$this->clear();
+
+		if (is_object($id))
+		{
+			// Load an object
+			$this->load_values((array) $id);
+		}
+		elseif (!empty($id))
+		{
+			// Find an object
+			$this->find($id);
+		}
+	}
+
+	/**
+	 * Prepares the model database connection, determines the table name,
+	 * and loads column information.
+	 *
+	 * @return  void
+	 */
+	public function __initialize()
+	{
+		if ( ! is_object($this->db))
+		{
+			// Get database instance
+			$this->db = Database::instance($this->db);
+		}
+
+		if (empty($this->table_name))
+		{
+			// Table name is the same as the object name
+			$this->table_name = $this->object_name;
+
+			if ($this->table_names_plural === TRUE)
+			{
+				// Make the table name plural
+				$this->table_name = inflector::plural($this->table_name);
+			}
+		}
+
+		if (is_array($this->ignored_columns))
+		{
+			// Make the ignored columns mirrored = mirrored
+			$this->ignored_columns = array_combine($this->ignored_columns, $this->ignored_columns);
+		}
+
+		// Load column information
+		$this->reload_columns();
+	}
+
+	/**
+	 * Allows serialization of only the object data and state, to prevent
+	 * "stale" objects being unserialized, which also requires less memory.
+	 *
+	 * @return  array
+	 */
+	public function __sleep()
+	{
+		// Store only information about the object
+		return array('object_name', 'object', 'changed', 'loaded', 'saved', 'sorting');
+	}
+
+	/**
+	 * Prepares the database connection and reloads the object.
+	 *
+	 * @return  void
+	 */
+	public function __wakeup()
+	{
+		// Initialize database
+		$this->__initialize();
+
+		if ($this->reload_on_wakeup === TRUE)
+		{
+			// Reload the object
+			$this->reload();
+		}
+	}
+
+	/**
+	 * Handles pass-through to database methods. Calls to query methods
+	 * (query, get, insert, update) are not allowed. Query builder methods
+	 * are chainable.
+	 *
+	 * @param   string  method name
+	 * @param   array   method arguments
+	 * @return  mixed
+	 */
+	public function __call($method, array $args)
+	{
+		if (method_exists($this->db, $method))
+		{
+			if (in_array($method, array('query', 'get', 'insert', 'update', 'delete')))
+				throw new Kohana_Exception('orm.query_methods_not_allowed');
+
+			// Method has been applied to the database
+			$this->db_applied[$method] = $method;
+
+			// Number of arguments passed
+			$num_args = count($args);
+
+			if ($method === 'select' AND $num_args > 3)
+			{
+				// Call select() manually to avoid call_user_func_array
+				$this->db->select($args);
+			}
+			else
+			{
+				// We use switch here to manually call the database methods. This is
+				// done for speed: call_user_func_array can take over 300% longer to
+				// make calls. Most database methods are 4 arguments or less, so this
+				// avoids almost any calls to call_user_func_array.
+
+				switch ($num_args)
+				{
+					case 0:
+						// Support for things like reset_select, reset_write, list_tables
+						return $this->db->$method();
+					break;
+					case 1:
+						$this->db->$method($args[0]);
+					break;
+					case 2:
+						$this->db->$method($args[0], $args[1]);
+					break;
+					case 3:
+						$this->db->$method($args[0], $args[1], $args[2]);
+					break;
+					case 4:
+						$this->db->$method($args[0], $args[1], $args[2], $args[3]);
+					break;
+					default:
+						// Here comes the snail...
+						call_user_func_array(array($this->db, $method), $args);
+					break;
+				}
+			}
+
+			return $this;
+		}
+		else
+		{
+			throw new Kohana_Exception('core.invalid_method', $method, get_class($this));
+		}
+	}
+
+	/**
+	 * Handles retrieval of all model values, relationships, and metadata.
+	 *
+	 * @param   string  column name
+	 * @return  mixed
+	 */
+	public function __get($column)
+	{
+		if (isset($this->ignored_columns[$column]))
+		{
+			return NULL;
+		}
+		elseif (array_key_exists($column, $this->object))
+		{
+			return $this->object[$column];
+		}
+		elseif (isset($this->related[$column]))
+		{
+			return $this->related[$column];
+		}
+		elseif ($column === 'primary_key_value')
+		{
+			return $this->object[$this->primary_key];
+		}
+		elseif ($model = $this->related_object($column))
+		{
+			// This handles the has_one and belongs_to relationships
+
+			if (array_key_exists($column.'_'.$model->primary_key, $this->object))
+			{
+				// Use the FK that exists in this model as the PK
+				$where = array($model->table_name.'.'.$model->primary_key => $this->object[$column.'_'.$model->primary_key]);
+			}
+			else
+			{
+				// Use this model PK as the FK
+				$where = array($this->foreign_key() => $this->object[$this->primary_key]);
+			}
+
+			// one<>alias:one relationship
+			return $this->related[$column] = $model->find($where);
+		}
+		elseif (isset($this->has_many[$column]))
+		{
+			// Load the "middle" model
+			$through = ORM::factory(inflector::singular($this->has_many[$column]));
+
+			// Load the "end" model
+			$model = ORM::factory(inflector::singular($column));
+
+			// Load JOIN info
+			$join_table = $through->table_name;
+			$join_col1  = $model->foreign_key(NULL, $join_table);
+			$join_col2  = $model->foreign_key(TRUE);
+
+			// one<>alias:many relationship
+			return $this->related[$column] = $model
+				->join($join_table, $join_col1, $join_col2)
+				->where($this->foreign_key(NULL, $join_table), $this->object[$this->primary_key])
+				->find_all();
+		}
+		elseif (in_array($column, $this->has_many))
+		{
+			// one<>many relationship
+			return $this->related[$column] = ORM::factory(inflector::singular($column))
+				->where($this->foreign_key($column), $this->object[$this->primary_key])
+				->find_all();
+		}
+		elseif (in_array($column, $this->has_and_belongs_to_many))
+		{
+			// Load the remote model, always singular
+			$model = ORM::factory(inflector::singular($column));
+
+			if ($this->has($model, TRUE))
+			{
+				// many<>many relationship
+				return $this->related[$column] = $model
+					->in($model->table_name.'.'.$model->primary_key, $this->changed_relations[$column])
+					->find_all();
+			}
+			else
+			{
+				// empty many<>many relationship
+				return $this->related[$column] = $model
+					->where($model->table_name.'.'.$model->primary_key, NULL)
+					->find_all();
+			}
+		}
+		elseif (in_array($column, array
+			(
+				'object_name', 'object_plural', // Object
+				'primary_key', 'primary_val', 'table_name', 'table_columns', // Table
+				'loaded', 'saved', // Status
+				'has_one', 'belongs_to', 'has_many', 'has_and_belongs_to_many', 'load_with' // Relationships
+			)))
+		{
+			// Model meta information
+			return $this->$column;
+		}
+		else
+		{
+			throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+		}
+	}
+
+	/**
+	 * Handles setting of all model values, and tracks changes between values.
+	 *
+	 * @param   string  column name
+	 * @param   mixed   column value
+	 * @return  void
+	 */
+	public function __set($column, $value)
+	{
+		if (isset($this->ignored_columns[$column]))
+		{
+			return NULL;
+		}
+		elseif (isset($this->object[$column]) OR array_key_exists($column, $this->object))
+		{
+			if (isset($this->table_columns[$column]))
+			{
+				// Data has changed
+				$this->changed[$column] = $column;
+
+				// Object is no longer saved
+				$this->saved = FALSE;
+			}
+
+			$this->object[$column] = $this->load_type($column, $value);
+		}
+		elseif (in_array($column, $this->has_and_belongs_to_many) AND is_array($value))
+		{
+			// Load relations
+			$model = ORM::factory(inflector::singular($column));
+
+			if ( ! isset($this->object_relations[$column]))
+			{
+				// Load relations
+				$this->has($model);
+			}
+
+			// Change the relationships
+			$this->changed_relations[$column] = $value;
+
+			if (isset($this->related[$column]))
+			{
+				// Force a reload of the relationships
+				unset($this->related[$column]);
+			}
+		}
+		else
+		{
+			throw new Kohana_Exception('core.invalid_property', $column, get_class($this));
+		}
+	}
+
+	/**
+	 * Checks if object data is set.
+	 *
+	 * @param   string  column name
+	 * @return  boolean
+	 */
+	public function __isset($column)
+	{
+		return (isset($this->object[$column]) OR isset($this->related[$column]));
+	}
+
+	/**
+	 * Unsets object data.
+	 *
+	 * @param   string  column name
+	 * @return  void
+	 */
+	public function __unset($column)
+	{
+		unset($this->object[$column], $this->changed[$column], $this->related[$column]);
+	}
+
+	/**
+	 * Displays the primary key of a model when it is converted to a string.
+	 *
+	 * @return  string
+	 */
+	public function __toString()
+	{
+		return (string) $this->object[$this->primary_key];
+	}
+
+	/**
+	 * Returns the values of this object as an array.
+	 *
+	 * @return  array
+	 */
+	public function as_array()
+	{
+		$object = array();
+
+		foreach ($this->object as $key => $val)
+		{
+			// Reconstruct the array (calls __get)
+			$object[$key] = $this->$key;
+		}
+
+		return $object;
+	}
+
+	/**
+	 * Binds another one-to-one object to this model.  One-to-one objects
+	 * can be nested using 'object1:object2' syntax
+	 *
+	 * @param string $object
+	 * @return void
+	 */
+	public function with($object)
+	{
+		if (isset($this->with_applied[$object]))
+		{
+			// Don't join anything already joined
+			return $this;
+		}
+
+		$prefix = $table = $object;
+
+		// Split object parts
+		$objects = explode(':', $object);
+		$object	 = $this;
+		foreach ($objects as $object_part)
+		{
+			// Go down the line of objects to find the given target
+			$parent = $object;
+			$object = $parent->related_object($object_part);
+
+			if ( ! $object)
+			{
+				// Can't find related object
+				return $this;
+			}
+		}
+
+		$table = $object_part;
+
+		if ($this->table_names_plural)
+		{
+			$table = inflector::plural($table);
+		}
+
+		// Pop-off top object to get the parent object (user:photo:tag's parent is user:photo)
+		array_pop($objects);
+		$parent_prefix = implode(':', $objects);
+
+		if (empty($parent_prefix))
+		{
+			// Use this table name itself for the parent prefix
+			$parent_prefix = $this->table_name;
+		}
+		else
+		{
+			if( ! isset($this->with_applied[$parent_prefix]))
+			{
+				// If the parent object hasn't been joined yet, do it first (otherwise LEFT JOINs fail)
+				$this->with($parent_prefix);
+			}
+		}
+
+		// Add to with_applied to prevent duplicate joins
+		$this->with_applied[$prefix] = TRUE;
+
+		// Use the keys of the empty object to determine the columns
+		$select = array_keys($object->as_array());
+		foreach ($select as $i => $column)
+		{
+			// Add the prefix so that load_result can determine the relationship
+			$select[$i] = $prefix.'.'.$column.' AS '.$prefix.':'.$column;
+		}
+
+		// Select all of the prefixed keys in the object
+		$this->db->select($select);
+
+		// Use last object part to generate foreign key
+		$foreign_key = $object_part.'_'.$object->primary_key;
+
+		if (array_key_exists($foreign_key, $parent->object))
+		{
+			// Foreign key exists in the joined object's parent
+			$join_col1 = $object->foreign_key(TRUE, $prefix);
+			$join_col2 = $parent_prefix.'.'.$foreign_key;
+		}
+		else
+		{
+			$join_col1 = $parent->foreign_key(NULL, $prefix);
+			$join_col2 = $parent_prefix.'.'.$parent->primary_key;
+		}
+
+		// Join the related object into the result
+		$this->db->join($object->table_name.' AS '.$this->db->table_prefix().$prefix, $join_col1, $join_col2, 'LEFT');
+
+		return $this;
+	}
+
+	/**
+	 * Finds and loads a single database row into the object.
+	 *
+	 * @chainable
+	 * @param   mixed  primary key or an array of clauses
+	 * @return  ORM
+	 */
+	public function find($id = NULL)
+	{
+		if ($id !== NULL)
+		{
+			if (is_array($id))
+			{
+				// Search for all clauses
+				$this->db->where($id);
+			}
+			else
+			{
+				// Search for a specific column
+				$this->db->where($this->table_name.'.'.$this->unique_key($id), $id);
+			}
+		}
+
+		return $this->load_result();
+	}
+
+	/**
+	 * Finds multiple database rows and returns an iterator of the rows found.
+	 *
+	 * @chainable
+	 * @param   integer  SQL limit
+	 * @param   integer  SQL offset
+	 * @return  ORM_Iterator
+	 */
+	public function find_all($limit = NULL, $offset = NULL)
+	{
+		if ($limit !== NULL AND ! isset($this->db_applied['limit']))
+		{
+			// Set limit
+			$this->limit($limit);
+		}
+
+		if ($offset !== NULL AND ! isset($this->db_applied['offset']))
+		{
+			// Set offset
+			$this->offset($offset);
+		}
+
+		return $this->load_result(TRUE);
+	}
+
+	/**
+	 * Creates a key/value array from all of the objects available. Uses find_all
+	 * to find the objects.
+	 *
+	 * @param   string  key column
+	 * @param   string  value column
+	 * @return  array
+	 */
+	public function select_list($key = NULL, $val = NULL)
+	{
+		if ($key === NULL)
+		{
+			$key = $this->primary_key;
+		}
+
+		if ($val === NULL)
+		{
+			$val = $this->primary_val;
+		}
+
+		// Return a select list from the results
+		return $this->select($key, $val)->find_all()->select_list($key, $val);
+	}
+
+	/**
+	 * Validates the current object. This method should generally be called
+	 * via the model, after the $_POST Validation object has been created.
+	 *
+	 * @param   object   Validation array
+	 * @return  boolean
+	 */
+	public function validate(Validation $array, $save = FALSE)
+	{
+		if ( ! $array->submitted())
+		{
+			$safe_array = $array->safe_array();
+
+			foreach ($safe_array as $key => $value)
+			{
+				// Get the value from this object
+				$value = $this->$key;
+
+				if (is_object($value) AND $value instanceof ORM_Iterator)
+				{
+					// Convert the value to an array of primary keys
+					$value = $value->primary_key_array();
+				}
+
+				// Pre-fill data
+				$array[$key] = $value;
+			}
+		}
+
+		// Validate the array
+		if ($status = $array->validate())
+		{
+			$safe_array = $array->safe_array();
+
+			foreach ($safe_array as $key => $value)
+			{
+				// Set new data
+				$this->$key = $value;
+			}
+
+			if ($save === TRUE OR is_string($save))
+			{
+				// Save this object
+				$this->save();
+
+				if (is_string($save))
+				{
+					// Redirect to the saved page
+					url::redirect($save);
+				}
+			}
+		}
+
+		// Return validation status
+		return $status;
+	}
+
+	/**
+	 * Saves the current object.
+	 *
+	 * @chainable
+	 * @return  ORM
+	 */
+	public function save()
+	{
+		if ( ! empty($this->changed))
+		{
+			$data = array();
+			foreach ($this->changed as $column)
+			{
+				// Compile changed data
+				$data[$column] = $this->object[$column];
+			}
+
+			if ($this->loaded === TRUE)
+			{
+				$query = $this->db
+					->where($this->primary_key, $this->object[$this->primary_key])
+					->update($this->table_name, $data);
+
+				// Object has been saved
+				$this->saved = TRUE;
+			}
+			else
+			{
+				$query = $this->db
+					->insert($this->table_name, $data);
+
+				if ($query->count() > 0)
+				{
+					if (empty($this->object[$this->primary_key]))
+					{
+						// Load the insert id as the primary key
+						$this->object[$this->primary_key] = $query->insert_id();
+					}
+
+					// Object is now loaded and saved
+					$this->loaded = $this->saved = TRUE;
+				}
+			}
+
+			if ($this->saved === TRUE)
+			{
+				// All changes have been saved
+				$this->changed = array();
+			}
+		}
+
+		if ($this->saved === TRUE AND ! empty($this->changed_relations))
+		{
+			foreach ($this->changed_relations as $column => $values)
+			{
+				// All values that were added
+				$added = array_diff($values, $this->object_relations[$column]);
+
+				// All values that were saved
+				$removed = array_diff($this->object_relations[$column], $values);
+
+				if (empty($added) AND empty($removed))
+				{
+					// No need to bother
+					continue;
+				}
+
+				// Clear related columns
+				unset($this->related[$column]);
+
+				// Load the model
+				$model = ORM::factory(inflector::singular($column));
+
+				if (($join_table = array_search($column, $this->has_and_belongs_to_many)) === FALSE)
+					continue;
+
+				if (is_int($join_table))
+				{
+					// No "through" table, load the default JOIN table
+					$join_table = $model->join_table($this->table_name);
+				}
+
+				// Foreign keys for the join table
+				$object_fk  = $this->foreign_key(NULL);
+				$related_fk = $model->foreign_key(NULL);
+
+				if ( ! empty($added))
+				{
+					foreach ($added as $id)
+					{
+						// Insert the new relationship
+						$this->db->insert($join_table, array
+						(
+							$object_fk  => $this->object[$this->primary_key],
+							$related_fk => $id,
+						));
+					}
+				}
+
+				if ( ! empty($removed))
+				{
+					$this->db
+						->where($object_fk, $this->object[$this->primary_key])
+						->in($related_fk, $removed)
+						->delete($join_table);
+				}
+
+				// Clear all relations for this column
+				unset($this->object_relations[$column], $this->changed_relations[$column]);
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Deletes the current object from the database. This does NOT destroy
+	 * relationships that have been created with other objects.
+	 *
+	 * @chainable
+	 * @return  ORM
+	 */
+	public function delete($id = NULL)
+	{
+		if ($id === NULL AND $this->loaded)
+		{
+			// Use the the primary key value
+			$id = $this->object[$this->primary_key];
+		}
+
+		// Delete this object
+		$this->db->where($this->primary_key, $id)->delete($this->table_name);
+
+		return $this->clear();
+	}
+
+	/**
+	 * Delete all objects in the associated table. This does NOT destroy
+	 * relationships that have been created with other objects.
+	 *
+	 * @chainable
+	 * @param   array  ids to delete
+	 * @return  ORM
+	 */
+	public function delete_all($ids = NULL)
+	{
+		if (is_array($ids))
+		{
+			// Delete only given ids
+			$this->db->in($this->primary_key, $ids);
+		}
+		elseif (is_null($ids))
+		{
+			// Delete all records
+			$this->db->where(TRUE);
+		}
+		else
+		{
+			// Do nothing - safeguard
+			return $this;
+		}
+
+		// Delete all objects
+		$this->db->delete($this->table_name);
+
+		return $this->clear();
+	}
+
+	/**
+	 * Unloads the current object and clears the status.
+	 *
+	 * @chainable
+	 * @return  ORM
+	 */
+	public function clear()
+	{
+		// Create an array with all the columns set to NULL
+		$columns = array_keys($this->table_columns);
+		$values  = array_combine($columns, array_fill(0, count($columns), NULL));
+
+		// Replace the current object with an empty one
+		$this->load_values($values);
+
+		return $this;
+	}
+
+	/**
+	 * Reloads the current object from the database.
+	 *
+	 * @chainable
+	 * @return  ORM
+	 */
+	public function reload()
+	{
+		return $this->find($this->object[$this->primary_key]);
+	}
+
+	/**
+	 * Reload column definitions.
+	 *
+	 * @chainable
+	 * @param   boolean  force reloading
+	 * @return  ORM
+	 */
+	public function reload_columns($force = FALSE)
+	{
+		if ($force === TRUE OR empty($this->table_columns))
+		{
+			if (isset(self::$column_cache[$this->object_name]))
+			{
+				// Use cached column information
+				$this->table_columns = self::$column_cache[$this->object_name];
+			}
+			else
+			{
+				// Load table columns
+				self::$column_cache[$this->object_name] = $this->table_columns = $this->db->list_fields($this->table_name, TRUE);
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Tests if this object has a relationship to a different model.
+	 *
+	 * @param   object   related ORM model
+	 * @param   boolean  check for any relations to given model
+	 * @return  boolean
+	 */
+	public function has(ORM $model, $any = FALSE)
+	{
+		$related = $model->object_plural;
+
+		if (($join_table = array_search($related, $this->has_and_belongs_to_many)) === FALSE)
+			return FALSE;
+
+		if (is_int($join_table))
+		{
+			// No "through" table, load the default JOIN table
+			$join_table = $model->join_table($this->table_name);
+		}
+
+		if ( ! isset($this->object_relations[$related]))
+		{
+			// Load the object relationships
+			$this->changed_relations[$related] = $this->object_relations[$related] = $this->load_relations($join_table, $model);
+		}
+
+		if ( ! $model->empty_primary_key())
+		{
+			// Check if a specific object exists
+			return in_array($model->primary_key_value, $this->changed_relations[$related]);
+		}
+		elseif ($any)
+		{
+			// Check if any relations to given model exist
+			return ! empty($this->changed_relations[$related]);
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+	/**
+	 * Adds a new relationship to between this model and another.
+	 *
+	 * @param   object   related ORM model
+	 * @return  boolean
+	 */
+	public function add(ORM $model)
+	{
+		if ($this->has($model))
+			return TRUE;
+
+		// Get the faked column name
+		$column = $model->object_plural;
+
+		// Add the new relation to the update
+		$this->changed_relations[$column][] = $model->primary_key_value;
+
+		if (isset($this->related[$column]))
+		{
+			// Force a reload of the relationships
+			unset($this->related[$column]);
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Adds a new relationship to between this model and another.
+	 *
+	 * @param   object   related ORM model
+	 * @return  boolean
+	 */
+	public function remove(ORM $model)
+	{
+		if ( ! $this->has($model))
+			return FALSE;
+
+		// Get the faked column name
+		$column = $model->object_plural;
+
+		if (($key = array_search($model->primary_key_value, $this->changed_relations[$column])) === FALSE)
+			return FALSE;
+
+		// Remove the relationship
+		unset($this->changed_relations[$column][$key]);
+
+		if (isset($this->related[$column]))
+		{
+			// Force a reload of the relationships
+			unset($this->related[$column]);
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Count the number of records in the table.
+	 *
+	 * @return  integer
+	 */
+	public function count_all()
+	{
+		// Return the total number of records in a table
+		return $this->db->count_records($this->table_name);
+	}
+
+	/**
+	 * Count the number of records in the last query, without LIMIT or OFFSET applied.
+	 *
+	 * @return  integer
+	 */
+	public function count_last_query()
+	{
+		if ($sql = $this->db->last_query())
+		{
+			if (stripos($sql, 'LIMIT') !== FALSE)
+			{
+				// Remove LIMIT from the SQL
+				$sql = preg_replace('/\sLIMIT\s+[^a-z]+/i', ' ', $sql);
+			}
+
+			if (stripos($sql, 'OFFSET') !== FALSE)
+			{
+				// Remove OFFSET from the SQL
+				$sql = preg_replace('/\sOFFSET\s+\d+/i', '', $sql);
+			}
+
+			// Get the total rows from the last query executed
+			$result = $this->db->query
+			(
+				'SELECT COUNT(*) AS '.$this->db->escape_column('total_rows').' '.
+				'FROM ('.trim($sql).') AS '.$this->db->escape_table('counted_results')
+			);
+
+			// Return the total number of rows from the query
+			return (int) $result->current()->total_rows;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Proxy method to Database list_fields.
+	 *
+	 * @param   string  table name
+	 * @return  array
+	 */
+	public function list_fields($table)
+	{
+		// Proxy to database
+		return $this->db->list_fields($table);
+	}
+
+	/**
+	 * Proxy method to Database field_data.
+	 *
+	 * @param   string  table name
+	 * @return  array
+	 */
+	public function field_data($table)
+	{
+		// Proxy to database
+		return $this->db->field_data($table);
+	}
+
+	/**
+	 * Proxy method to Database last_query.
+	 *
+	 * @return  string
+	 */
+	public function last_query()
+	{
+		// Proxy to database
+		return $this->db->last_query();
+	}
+
+	/**
+	 * Proxy method to Database field_data.
+	 *
+	 * @chainable
+	 * @param   string  SQL query to clear
+	 * @return  ORM
+	 */
+	public function clear_cache($sql = NULL)
+	{
+		// Proxy to database
+		$this->db->clear_cache($sql);
+
+		return $this;
+	}
+
+	/**
+	 * Returns the unique key for a specific value. This method is expected
+	 * to be overloaded in models if the model has other unique columns.
+	 *
+	 * @param   mixed   unique value
+	 * @return  string
+	 */
+	public function unique_key($id)
+	{
+		return $this->primary_key;
+	}
+
+	/**
+	 * Determines the name of a foreign key for a specific table.
+	 *
+	 * @param   string  related table name
+	 * @param   string  prefix table name (used for JOINs)
+	 * @return  string
+	 */
+	public function foreign_key($table = NULL, $prefix_table = NULL)
+	{
+		if ($table === TRUE)
+		{
+			if (is_string($prefix_table))
+			{
+				// Use prefix table name and this table's PK
+				return $prefix_table.'.'.$this->primary_key;
+			}
+			else
+			{
+				// Return the name of this table's PK
+				return $this->table_name.'.'.$this->primary_key;
+			}
+		}
+
+		if (is_string($prefix_table))
+		{
+			// Add a period for prefix_table.column support
+			$prefix_table .= '.';
+		}
+
+		if (isset($this->foreign_key[$table]))
+		{
+			// Use the defined foreign key name, no magic here!
+			$foreign_key = $this->foreign_key[$table];
+		}
+		else
+		{
+			if ( ! is_string($table) OR ! isset($this->object[$table.'_'.$this->primary_key]))
+			{
+				// Use this table
+				$table = $this->table_name;
+
+				if (strpos($table, '.') !== FALSE)
+				{
+					// Hack around support for PostgreSQL schemas
+					list ($schema, $table) = explode('.', $table, 2);
+				}
+
+				if ($this->table_names_plural === TRUE)
+				{
+					// Make the key name singular
+					$table = inflector::singular($table);
+				}
+			}
+
+			$foreign_key = $table.'_'.$this->primary_key;
+		}
+
+		return $prefix_table.$foreign_key;
+	}
+
+	/**
+	 * This uses alphabetical comparison to choose the name of the table.
+	 *
+	 * Example: The joining table of users and roles would be roles_users,
+	 * because "r" comes before "u". Joining products and categories would
+	 * result in categories_products, because "c" comes before "p".
+	 *
+	 * Example: zoo > zebra > robber > ocean > angel > aardvark
+	 *
+	 * @param   string  table name
+	 * @return  string
+	 */
+	public function join_table($table)
+	{
+		if ($this->table_name > $table)
+		{
+			$table = $table.'_'.$this->table_name;
+		}
+		else
+		{
+			$table = $this->table_name.'_'.$table;
+		}
+
+		return $table;
+	}
+
+	/**
+	 * Returns an ORM model for the given object name;
+	 *
+	 * @param   string  object name
+	 * @return  ORM
+	 */
+	protected function related_object($object)
+	{
+		if (isset($this->has_one[$object]))
+		{
+			$object = ORM::factory($this->has_one[$object]);
+		}
+		elseif (isset($this->belongs_to[$object]))
+		{
+			$object = ORM::factory($this->belongs_to[$object]);
+		}
+		elseif (in_array($object, $this->has_one) OR in_array($object, $this->belongs_to))
+		{
+			$object = ORM::factory($object);
+		}
+		else
+		{
+			return FALSE;
+		}
+
+		return $object;
+	}
+
+	/**
+	 * Loads an array of values into into the current object.
+	 *
+	 * @chainable
+	 * @param   array  values to load
+	 * @return  ORM
+	 */
+	public function load_values(array $values)
+	{
+		if (array_key_exists($this->primary_key, $values))
+		{
+			// Replace the object and reset the object status
+			$this->object = $this->changed = $this->related = array();
+
+			// Set the loaded and saved object status based on the primary key
+			$this->loaded = $this->saved = ($values[$this->primary_key] !== NULL);
+		}
+
+		// Related objects
+		$related = array();
+
+		foreach ($values as $column => $value)
+		{
+			if (strpos($column, ':') === FALSE)
+			{
+				if (isset($this->table_columns[$column]))
+				{
+					// The type of the value can be determined, convert the value
+					$value = $this->load_type($column, $value);
+				}
+
+				$this->object[$column] = $value;
+			}
+			else
+			{
+				list ($prefix, $column) = explode(':', $column, 2);
+
+				$related[$prefix][$column] = $value;
+			}
+		}
+
+		if ( ! empty($related))
+		{
+			foreach ($related as $object => $values)
+			{
+				// Load the related objects with the values in the result
+				$this->related[$object] = $this->related_object($object)->load_values($values);
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Loads a value according to the types defined by the column metadata.
+	 *
+	 * @param   string  column name
+	 * @param   mixed   value to load
+	 * @return  mixed
+	 */
+	protected function load_type($column, $value)
+	{
+		if (is_object($value) OR is_array($value) OR ! isset($this->table_columns[$column]))
+			return $value;
+
+		// Load column data
+		$column = $this->table_columns[$column];
+
+		if ($value === NULL AND ! empty($column['null']))
+			return $value;
+
+		if ( ! empty($column['binary']) AND ! empty($column['exact']) AND (int) $column['length'] === 1)
+		{
+			// Use boolean for BINARY(1) fields
+			$column['type'] = 'boolean';
+		}
+
+		switch ($column['type'])
+		{
+			case 'int':
+				if ($value === '' AND ! empty($column['null']))
+				{
+					// Forms will only submit strings, so empty integer values must be null
+					$value = NULL;
+				}
+				elseif ((float) $value > PHP_INT_MAX)
+				{
+					// This number cannot be represented by a PHP integer, so we convert it to a string
+					$value = (string) $value;
+				}
+				else
+				{
+					$value = (int) $value;
+				}
+			break;
+			case 'float':
+				$value = (float) $value;
+			break;
+			case 'boolean':
+				$value = (bool) $value;
+			break;
+			case 'string':
+				$value = (string) $value;
+			break;
+		}
+
+		return $value;
+	}
+
+	/**
+	 * Loads a database result, either as a new object for this model, or as
+	 * an iterator for multiple rows.
+	 *
+	 * @chainable
+	 * @param   boolean       return an iterator or load a single row
+	 * @return  ORM           for single rows
+	 * @return  ORM_Iterator  for multiple rows
+	 */
+	protected function load_result($array = FALSE)
+	{
+		if ($array === FALSE)
+		{
+			// Only fetch 1 record
+			$this->db->limit(1);
+		}
+
+		if ( ! isset($this->db_applied['select']))
+		{
+			// Select all columns by default
+			$this->db->select($this->table_name.'.*');
+		}
+
+		if ( ! empty($this->load_with))
+		{
+			foreach ($this->load_with as $object)
+			{
+				// Join each object into the results
+				$this->with($object);
+			}
+		}
+
+		if ( ! isset($this->db_applied['orderby']) AND ! empty($this->sorting))
+		{
+			$sorting = array();
+			foreach ($this->sorting as $column => $direction)
+			{
+				if (strpos($column, '.') === FALSE)
+				{
+					// Keeps sorting working properly when using JOINs on
+					// tables with columns of the same name
+					$column = $this->table_name.'.'.$column;
+				}
+
+				$sorting[$column] = $direction;
+			}
+
+			// Apply the user-defined sorting
+			$this->db->orderby($sorting);
+		}
+
+		// Load the result
+		$result = $this->db->get($this->table_name);
+
+		if ($array === TRUE)
+		{
+			// Return an iterated result
+			return new ORM_Iterator($this, $result);
+		}
+
+		if ($result->count() === 1)
+		{
+			// Load object values
+			$this->load_values($result->result(FALSE)->current());
+		}
+		else
+		{
+			// Clear the object, nothing was found
+			$this->clear();
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Return an array of all the primary keys of the related table.
+	 *
+	 * @param   string  table name
+	 * @param   object  ORM model to find relations of
+	 * @return  array
+	 */
+	protected function load_relations($table, ORM $model)
+	{
+		// Save the current query chain (otherwise the next call will clash)
+		$this->db->push();
+
+		$query = $this->db
+			->select($model->foreign_key(NULL).' AS id')
+			->from($table)
+			->where($this->foreign_key(NULL, $table), $this->object[$this->primary_key])
+			->get()
+			->result(TRUE);
+
+		$this->db->pop();
+
+		$relations = array();
+		foreach ($query as $row)
+		{
+			$relations[] = $row->id;
+		}
+
+		return $relations;
+	}
+
+	/**
+	 * Returns whether or not primary key is empty
+	 *
+	 * @return bool
+	 */
+	protected function empty_primary_key()
+	{
+		return (empty($this->object[$this->primary_key]) AND $this->object[$this->primary_key] !== '0');
+	}
+
+} // End ORM
diff --git a/system/libraries/ORM_Iterator.php b/system/libraries/ORM_Iterator.php
new file mode 100755
index 0000000..7c69866
--- /dev/null
+++ b/system/libraries/ORM_Iterator.php
@@ -0,0 +1,228 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+* Object Relational Mapping (ORM) result iterator.
+*
+* $Id: ORM_Iterator.php 3917 2009-01-21 03:06:22Z zombor $
+*
+* @package    ORM
+* @author     Kohana Team
+* @copyright  (c) 2007-2008 Kohana Team
+* @license    http://kohanaphp.com/license.html
+*/
+class ORM_Iterator_Core implements Iterator, ArrayAccess, Countable {
+
+	// Class attributes
+	protected $class_name;
+	protected $primary_key;
+	protected $primary_val;
+
+	// Database result object
+	protected $result;
+
+	public function __construct(ORM $model, Database_Result $result)
+	{
+		// Class attributes
+		$this->class_name  = get_class($model);
+		$this->primary_key = $model->primary_key;
+		$this->primary_val = $model->primary_val;
+
+		// Database result
+		$this->result = $result->result(TRUE);
+	}
+
+	/**
+	 * Returns an array of the results as ORM objects.
+	 *
+	 * @return  array
+	 */
+	public function as_array()
+	{
+		$array = array();
+
+		if ($results = $this->result->result_array())
+		{
+			// Import class name
+			$class = $this->class_name;
+
+			foreach ($results as $obj)
+			{
+				$array[] = new $class($obj);
+			}
+		}
+
+		return $array;
+	}
+
+	/**
+	 * Return an array of all of the primary keys for this object.
+	 *
+	 * @return  array
+	 */
+	public function primary_key_array()
+	{
+		$ids = array();
+		foreach ($this->result as $row)
+		{
+			$ids[] = $row->{$this->primary_key};
+		}
+		return $ids;
+	}
+
+	/**
+	 * Create a key/value array from the results.
+	 *
+	 * @param   string  key column
+	 * @param   string  value column
+	 * @return  array
+	 */
+	public function select_list($key = NULL, $val = NULL)
+	{
+		if ($key === NULL)
+		{
+			// Use the default key
+			$key = $this->primary_key;
+		}
+
+		if ($val === NULL)
+		{
+			// Use the default value
+			$val = $this->primary_val;
+		}
+
+		$array = array();
+		foreach ($this->result->result_array() as $row)
+		{
+			$array[$row->$key] = $row->$val;
+		}
+		return $array;
+	}
+
+	/**
+	 * Return a range of offsets.
+	 *
+	 * @param   integer  start
+	 * @param   integer  end
+	 * @return  array
+	 */
+	public function range($start, $end)
+	{
+		// Array of objects
+		$array = array();
+
+		if ($this->result->offsetExists($start))
+		{
+			// Import the class name
+			$class = $this->class_name;
+
+			// Set the end offset
+			$end = $this->result->offsetExists($end) ? $end : $this->count();
+
+			for ($i = $start; $i < $end; $i++)
+			{
+				// Insert each object in the range
+				$array[] = new $class($this->result->offsetGet($i));
+			}
+		}
+
+		return $array;
+	}
+
+	/**
+	 * Countable: count
+	 */
+	public function count()
+	{
+		return $this->result->count();
+	}
+
+	/**
+	 * Iterator: current
+	 */
+	public function current()
+	{
+		if ($row = $this->result->current())
+		{
+			// Import class name
+			$class = $this->class_name;
+
+			$row = new $class($row);
+		}
+
+		return $row;
+	}
+
+	/**
+	 * Iterator: key
+	 */
+	public function key()
+	{
+		return $this->result->key();
+	}
+
+	/**
+	 * Iterator: next
+	 */
+	public function next()
+	{
+		return $this->result->next();
+	}
+
+	/**
+	 * Iterator: rewind
+	 */
+	public function rewind()
+	{
+		$this->result->rewind();
+	}
+
+	/**
+	 * Iterator: valid
+	 */
+	public function valid()
+	{
+		return $this->result->valid();
+	}
+
+	/**
+	 * ArrayAccess: offsetExists
+	 */
+	public function offsetExists($offset)
+	{
+		return $this->result->offsetExists($offset);
+	}
+
+	/**
+	 * ArrayAccess: offsetGet
+	 */
+	public function offsetGet($offset)
+	{
+		if ($this->result->offsetExists($offset))
+		{
+			// Import class name
+			$class = $this->class_name;
+
+			return new $class($this->result->offsetGet($offset));
+		}
+	}
+
+	/**
+	 * ArrayAccess: offsetSet
+	 *
+	 * @throws  Kohana_Database_Exception
+	 */
+	public function offsetSet($offset, $value)
+	{
+		throw new Kohana_Database_Exception('database.result_read_only');
+	}
+
+	/**
+	 * ArrayAccess: offsetUnset
+	 *
+	 * @throws  Kohana_Database_Exception
+	 */
+	public function offsetUnset($offset)
+	{
+		throw new Kohana_Database_Exception('database.result_read_only');
+	}
+
+} // End ORM Iterator
\ No newline at end of file
diff --git a/system/libraries/ORM_Tree.php b/system/libraries/ORM_Tree.php
new file mode 100755
index 0000000..7094316
--- /dev/null
+++ b/system/libraries/ORM_Tree.php
@@ -0,0 +1,76 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Object Relational Mapping (ORM) "tree" extension. Allows ORM objects to act
+ * as trees, with parents and children.
+ *
+ * $Id: ORM_Tree.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    ORM
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class ORM_Tree_Core extends ORM {
+
+	// Name of the child
+	protected $children;
+
+	// Parent keyword name
+	protected $parent_key = 'parent_id';
+
+	/**
+	 * Overload ORM::__get to support "parent" and "children" properties.
+	 *
+	 * @param   string  column name
+	 * @return  mixed
+	 */
+	public function __get($column)
+	{
+		if ($column === 'parent')
+		{
+			if (empty($this->related[$column]))
+			{
+				// Load child model
+				$model = ORM::factory(inflector::singular($this->children));
+
+				if (array_key_exists($this->parent_key, $this->object))
+				{
+					// Find children of this parent
+					$model->where($model->primary_key, $this->object[$this->parent_key])->find();
+				}
+
+				$this->related[$column] = $model;
+			}
+
+			return $this->related[$column];
+		}
+		elseif ($column === 'children')
+		{
+			if (empty($this->related[$column]))
+			{
+				$model = ORM::factory(inflector::singular($this->children));
+
+				if ($this->children === $this->table_name)
+				{
+					// Load children within this table
+					$this->related[$column] = $model
+						->where($this->parent_key, $this->object[$this->primary_key])
+						->find_all();
+				}
+				else
+				{
+					// Find first selection of children
+					$this->related[$column] = $model
+						->where($this->foreign_key(), $this->object[$this->primary_key])
+						->where($this->parent_key, NULL)
+						->find_all();
+				}
+			}
+
+			return $this->related[$column];
+		}
+
+		return parent::__get($column);
+	}
+
+} // End ORM Tree
\ No newline at end of file
diff --git a/system/libraries/ORM_Versioned.php b/system/libraries/ORM_Versioned.php
new file mode 100755
index 0000000..7c3ee5d
--- /dev/null
+++ b/system/libraries/ORM_Versioned.php
@@ -0,0 +1,143 @@
+<?php
+/**
+ * Object Relational Mapping (ORM) "versioned" extension. Allows ORM objects to
+ * be revisioned instead of updated.
+ *
+ * $Id$
+ *
+ * @package    ORM
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class ORM_Versioned_Core extends ORM {
+
+	protected $last_version = NULL;
+
+	/**
+	 * Overload ORM::save() to support versioned data
+	 *
+	 * @chainable
+	 * @return  ORM
+	 */
+	public function save()
+	{
+		$this->last_version = 1 + ($this->last_version === NULL ? $this->object['version'] : $this->last_version);
+		$this->__set('version', $this->last_version);
+
+		parent::save();
+
+		if ($this->saved)
+		{
+			$data = array();
+			foreach ($this->object as $key => $value)
+			{
+				if ($key === 'id')
+					continue;
+
+				$data[$key] = $value;
+			}
+			$data[$this->foreign_key()] = $this->id;
+
+			$this->db->insert($this->table_name.'_versions', $data);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Loads previous version from current object
+	 *
+	 * @chainable
+	 * @return  ORM
+	 */
+	public function previous()
+	{
+		if ( ! $this->loaded)
+			return $this;
+
+		$this->last_version = ($this->last_version === NULL) ? $this->object['version'] : $this->last_version;
+		$version = $this->last_version - 1;
+
+		$query = $this->db
+			->where($this->foreign_key(), $this->object[$this->primary_key])
+			->where('version', $version)
+			->limit(1)
+			->get($this->table_name.'_versions');
+
+		if ($query->count())
+		{
+			$this->load_values($query->result(FALSE)->current());
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Restores the object with data from stored version
+	 *
+	 * @param   integer  version number you want to restore
+	 * @return  ORM
+	 */
+	public function restore($version)
+	{
+		if ( ! $this->loaded)
+			return $this;
+
+		$query = $this->db
+			->where($this->foreign_key(), $this->object[$this->primary_key])
+			->where('version', $version)
+			->limit(1)
+			->get($this->table_name.'_versions');
+
+		if ($query->count())
+		{
+			$row = $query->result(FALSE)->current();
+
+			foreach ($row as $key => $value)
+			{
+				if ($key === $this->primary_key OR $key === $this->foreign_key())
+				{
+					// Do not overwrite the primary key
+					continue;
+				}
+
+				if ($key === 'version')
+				{
+					// Always use the current version
+					$value = $this->version;
+				}
+
+				$this->__set($key, $value);
+			}
+
+			$this->save();
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Overloads ORM::delete() to delete all versioned entries of current object
+	 * and the object itself
+	 *
+	 * @param   integer  id of the object you want to delete
+	 * @return  ORM
+	 */
+	public function delete($id = NULL)
+	{
+		if ($id === NULL)
+		{
+			// Use the current object id
+			$id = $this->object[$this->primary_key];
+		}
+
+		if ($status = parent::delete($id))
+		{
+			$this->db->where($this->foreign_key(), $id)->delete($this->table_name.'_versions');
+		}
+
+		return $status;
+	}
+
+}
\ No newline at end of file
diff --git a/system/libraries/Pagination.php b/system/libraries/Pagination.php
new file mode 100755
index 0000000..c30d684
--- /dev/null
+++ b/system/libraries/Pagination.php
@@ -0,0 +1,236 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Pagination library.
+ *
+ * $Id: Pagination.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Pagination_Core {
+
+	// Config values
+	protected $base_url       = '';
+	protected $directory      = 'pagination';
+	protected $style          = 'classic';
+	protected $uri_segment    = 3;
+	protected $query_string   = '';
+	protected $items_per_page = 20;
+	protected $total_items    = 0;
+	protected $auto_hide      = FALSE;
+
+	// Autogenerated values
+	protected $url;
+	protected $current_page;
+	protected $total_pages;
+	protected $current_first_item;
+	protected $current_last_item;
+	protected $first_page;
+	protected $last_page;
+	protected $previous_page;
+	protected $next_page;
+	protected $sql_offset;
+	protected $sql_limit;
+
+	/**
+	 * Constructs and returns a new Pagination object.
+	 *
+	 * @param   array   configuration settings
+	 * @return  object
+	 */
+	public function factory($config = array())
+	{
+		return new Pagination($config);
+	}
+
+	/**
+	 * Constructs a new Pagination object.
+	 *
+	 * @param   array  configuration settings
+	 * @return  void
+	 */
+	public function __construct($config = array())
+	{
+		// No custom group name given
+		if ( ! isset($config['group']))
+		{
+			$config['group'] = 'default';
+		}
+
+		// Pagination setup
+		$this->initialize($config);
+
+		Kohana::log('debug', 'Pagination Library initialized');
+	}
+
+	/**
+	 * Sets config values.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   array  configuration settings
+	 * @return  void
+	 */
+	public function initialize($config = array())
+	{
+		// Load config group
+		if (isset($config['group']))
+		{
+			// Load and validate config group
+			if ( ! is_array($group_config = Kohana::config('pagination.'.$config['group'])))
+				throw new Kohana_Exception('pagination.undefined_group', $config['group']);
+
+			// All pagination config groups inherit default config group
+			if ($config['group'] !== 'default')
+			{
+				// Load and validate default config group
+				if ( ! is_array($default_config = Kohana::config('pagination.default')))
+					throw new Kohana_Exception('pagination.undefined_group', 'default');
+
+				// Merge config group with default config group
+				$group_config += $default_config;
+			}
+
+			// Merge custom config items with config group
+			$config += $group_config;
+		}
+
+		// Assign config values to the object
+		foreach ($config as $key => $value)
+		{
+			if (property_exists($this, $key))
+			{
+				$this->$key = $value;
+			}
+		}
+
+		// Clean view directory
+		$this->directory = trim($this->directory, '/').'/';
+
+		// Build generic URL with page in query string
+		if ($this->query_string !== '')
+		{
+			// Extract current page
+			$this->current_page = isset($_GET[$this->query_string]) ? (int) $_GET[$this->query_string] : 1;
+
+			// Insert {page} placeholder
+			$_GET[$this->query_string] = '{page}';
+
+			// Create full URL
+			$base_url = ($this->base_url === '') ? Router::$current_uri : $this->base_url;
+			$this->url = url::site($base_url).'?'.str_replace('%7Bpage%7D', '{page}', http_build_query($_GET));
+
+			// Reset page number
+			$_GET[$this->query_string] = $this->current_page;
+		}
+
+		// Build generic URL with page as URI segment
+		else
+		{
+			// Use current URI if no base_url set
+			$this->url = ($this->base_url === '') ? Router::$segments : explode('/', trim($this->base_url, '/'));
+
+			// Convert uri 'label' to corresponding integer if needed
+			if (is_string($this->uri_segment))
+			{
+				if (($key = array_search($this->uri_segment, $this->url)) === FALSE)
+				{
+					// If uri 'label' is not found, auto add it to base_url
+					$this->url[] = $this->uri_segment;
+					$this->uri_segment = count($this->url) + 1;
+				}
+				else
+				{
+					$this->uri_segment = $key + 2;
+				}
+			}
+
+			// Insert {page} placeholder
+			$this->url[$this->uri_segment - 1] = '{page}';
+
+			// Create full URL
+			$this->url = url::site(implode('/', $this->url)).Router::$query_string;
+
+			// Extract current page
+			$this->current_page = URI::instance()->segment($this->uri_segment);
+		}
+
+		// Core pagination values
+		$this->total_items        = (int) max(0, $this->total_items);
+		$this->items_per_page     = (int) max(1, $this->items_per_page);
+		$this->total_pages        = (int) ceil($this->total_items / $this->items_per_page);
+		$this->current_page       = (int) min(max(1, $this->current_page), max(1, $this->total_pages));
+		$this->current_first_item = (int) min((($this->current_page - 1) * $this->items_per_page) + 1, $this->total_items);
+		$this->current_last_item  = (int) min($this->current_first_item + $this->items_per_page - 1, $this->total_items);
+
+		// If there is no first/last/previous/next page, relative to the
+		// current page, value is set to FALSE. Valid page number otherwise.
+		$this->first_page         = ($this->current_page === 1) ? FALSE : 1;
+		$this->last_page          = ($this->current_page >= $this->total_pages) ? FALSE : $this->total_pages;
+		$this->previous_page      = ($this->current_page > 1) ? $this->current_page - 1 : FALSE;
+		$this->next_page          = ($this->current_page < $this->total_pages) ? $this->current_page + 1 : FALSE;
+
+		// SQL values
+		$this->sql_offset         = (int) ($this->current_page - 1) * $this->items_per_page;
+		$this->sql_limit          = sprintf(' LIMIT %d OFFSET %d ', $this->items_per_page, $this->sql_offset);
+	}
+
+	/**
+	 * Generates the HTML for the chosen pagination style.
+	 *
+	 * @param   string  pagination style
+	 * @return  string  pagination html
+	 */
+	public function render($style = NULL)
+	{
+		// Hide single page pagination
+		if ($this->auto_hide === TRUE AND $this->total_pages <= 1)
+			return '';
+
+		if ($style === NULL)
+		{
+			// Use default style
+			$style = $this->style;
+		}
+
+		// Return rendered pagination view
+		return View::factory($this->directory.$style, get_object_vars($this))->render();
+	}
+
+	/**
+	 * Magically converts Pagination object to string.
+	 *
+	 * @return  string  pagination html
+	 */
+	public function __toString()
+	{
+		return $this->render();
+	}
+
+	/**
+	 * Magically gets a pagination variable.
+	 *
+	 * @param   string  variable key
+	 * @return  mixed   variable value if the key is found
+	 * @return  void    if the key is not found
+	 */
+	public function __get($key)
+	{
+		if (isset($this->$key))
+			return $this->$key;
+	}
+
+	/**
+	 * Adds a secondary interface for accessing properties, e.g. $pagination->total_pages().
+	 * Note that $pagination->total_pages is the recommended way to access properties.
+	 *
+	 * @param   string  function name
+	 * @return  string
+	 */
+	public function __call($func, $args = NULL)
+	{
+		return $this->__get($func);
+	}
+
+} // End Pagination Class
\ No newline at end of file
diff --git a/system/libraries/Profiler.php b/system/libraries/Profiler.php
new file mode 100755
index 0000000..45ec2f4
--- /dev/null
+++ b/system/libraries/Profiler.php
@@ -0,0 +1,270 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Adds useful information to the bottom of the current page for debugging and optimization purposes.
+ *
+ * Benchmarks   - The times and memory usage of benchmarks run by the Benchmark library.
+ * Database     - The raw SQL and number of affected rows of Database queries.
+ * Session Data - Data stored in the current session if using the Session library.
+ * POST Data    - The name and values of any POST data submitted to the current page.
+ * Cookie Data  - All cookies sent for the current request.
+ *
+ * $Id: Profiler.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Profiler
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Profiler_Core {
+
+	protected $profiles = array();
+	protected $show;
+
+	public function __construct()
+	{
+		// Add all built in profiles to event
+		Event::add('profiler.run', array($this, 'benchmarks'));
+		Event::add('profiler.run', array($this, 'database'));
+		Event::add('profiler.run', array($this, 'session'));
+		Event::add('profiler.run', array($this, 'post'));
+		Event::add('profiler.run', array($this, 'cookies'));
+
+		// Add profiler to page output automatically
+		Event::add('system.display', array($this, 'render'));
+
+		Kohana::log('debug', 'Profiler Library initialized');
+	}
+
+	/**
+	 * Magic __call method. Creates a new profiler section object.
+	 *
+	 * @param   string   input type
+	 * @param   string   input name
+	 * @return  object
+	 */
+	public function __call($method, $args)
+	{
+		if ( ! $this->show OR (is_array($this->show) AND ! in_array($args[0], $this->show)))
+			return FALSE;
+
+		// Class name
+		$class = 'Profiler_'.ucfirst($method);
+
+		$class = new $class();
+
+		$this->profiles[$args[0]] = $class;
+
+		return $class;
+	}
+
+	/**
+	 * Disables the profiler for this page only.
+	 * Best used when profiler is autoloaded.
+	 *
+	 * @return  void
+	 */
+	public function disable()
+	{
+		// Removes itself from the event queue
+		Event::clear('system.display', array($this, 'render'));
+	}
+
+	/**
+	 * Render the profiler. Output is added to the bottom of the page by default.
+	 *
+	 * @param   boolean  return the output if TRUE
+	 * @return  void|string
+	 */
+	public function render($return = FALSE)
+	{
+		$start = microtime(TRUE);
+
+		$get = isset($_GET['profiler']) ? explode(',', $_GET['profiler']) : array();
+		$this->show = empty($get) ? Kohana::config('profiler.show') : $get;
+
+		Event::run('profiler.run', $this);
+
+		$styles = '';
+		foreach ($this->profiles as $profile)
+		{
+			$styles .= $profile->styles();
+		}
+
+		// Don't display if there's no profiles
+		if (empty($this->profiles))
+			return;
+
+		// Load the profiler view
+		$data = array
+		(
+			'profiles' => $this->profiles,
+			'styles'   => $styles,
+			'execution_time' => microtime(TRUE) - $start
+		);
+		$view = new View('kohana_profiler', $data);
+
+		// Return rendered view if $return is TRUE
+		if ($return == TRUE)
+			return $view->render();
+
+		// Add profiler data to the output
+		if (stripos(Kohana::$output, '</body>') !== FALSE)
+		{
+			// Closing body tag was found, insert the profiler data before it
+			Kohana::$output = str_ireplace('</body>', $view->render().'</body>', Kohana::$output);
+		}
+		else
+		{
+			// Append the profiler data to the output
+			Kohana::$output .= $view->render();
+		}
+	}
+
+	/**
+	 * Benchmark times and memory usage from the Benchmark library.
+	 *
+	 * @return  void
+	 */
+	public function benchmarks()
+	{
+		if ( ! $table = $this->table('benchmarks'))
+			return;
+
+		$table->add_column();
+		$table->add_column('kp-column kp-data');
+		$table->add_column('kp-column kp-data');
+		$table->add_row(array('Benchmarks', 'Time', 'Memory'), 'kp-title', 'background-color: #FFE0E0');
+
+		$benchmarks = Benchmark::get(TRUE);
+
+		// Moves the first benchmark (total execution time) to the end of the array
+		$benchmarks = array_slice($benchmarks, 1) + array_slice($benchmarks, 0, 1);
+
+		text::alternate();
+		foreach ($benchmarks as $name => $benchmark)
+		{
+			// Clean unique id from system benchmark names
+			$name = ucwords(str_replace(array('_', '-'), ' ', str_replace(SYSTEM_BENCHMARK.'_', '', $name)));
+
+			$data = array($name, number_format($benchmark['time'], 3), number_format($benchmark['memory'] / 1024 / 1024, 2).'MB');
+			$class = text::alternate('', 'kp-altrow');
+
+			if ($name == 'Total Execution')
+				$class = 'kp-totalrow';
+
+			$table->add_row($data, $class);
+		}
+	}
+
+	/**
+	 * Database query benchmarks.
+	 *
+	 * @return  void
+	 */
+	public function database()
+	{
+		if ( ! $table = $this->table('database'))
+			return;
+
+		$table->add_column();
+		$table->add_column('kp-column kp-data');
+		$table->add_column('kp-column kp-data');
+		$table->add_row(array('Queries', 'Time', 'Rows'), 'kp-title', 'background-color: #E0FFE0');
+
+		$queries = Database::$benchmarks;
+
+		text::alternate();
+		$total_time = $total_rows = 0;
+		foreach ($queries as $query)
+		{
+			$data = array($query['query'], number_format($query['time'], 3), $query['rows']);
+			$class = text::alternate('', 'kp-altrow');
+			$table->add_row($data, $class);
+			$total_time += $query['time'];
+			$total_rows += $query['rows'];
+		}
+
+		$data = array('Total: ' . count($queries), number_format($total_time, 3), $total_rows);
+		$table->add_row($data, 'kp-totalrow');
+	}
+
+	/**
+	 * Session data.
+	 *
+	 * @return  void
+	 */
+	public function session()
+	{
+		if (empty($_SESSION)) return;
+
+		if ( ! $table = $this->table('session'))
+			return;
+
+		$table->add_column('kp-name');
+		$table->add_column();
+		$table->add_row(array('Session', 'Value'), 'kp-title', 'background-color: #CCE8FB');
+
+		text::alternate();
+		foreach($_SESSION as $name => $value)
+		{
+			if (is_object($value))
+			{
+				$value = get_class($value).' [object]';
+			}
+
+			$data = array($name, $value);
+			$class = text::alternate('', 'kp-altrow');
+			$table->add_row($data, $class);
+		}
+	}
+
+	/**
+	 * POST data.
+	 *
+	 * @return  void
+	 */
+	public function post()
+	{
+		if (empty($_POST)) return;
+
+		if ( ! $table = $this->table('post'))
+			return;
+
+		$table->add_column('kp-name');
+		$table->add_column();
+		$table->add_row(array('POST', 'Value'), 'kp-title', 'background-color: #E0E0FF');
+
+		text::alternate();
+		foreach($_POST as $name => $value)
+		{
+			$data = array($name, $value);
+			$class = text::alternate('', 'kp-altrow');
+			$table->add_row($data, $class);
+		}
+	}
+
+	/**
+	 * Cookie data.
+	 *
+	 * @return  void
+	 */
+	public function cookies()
+	{
+		if (empty($_COOKIE)) return;
+
+		if ( ! $table = $this->table('cookies'))
+			return;
+
+		$table->add_column('kp-name');
+		$table->add_column();
+		$table->add_row(array('Cookies', 'Value'), 'kp-title', 'background-color: #FFF4D7');
+
+		text::alternate();
+		foreach($_COOKIE as $name => $value)
+		{
+			$data = array($name, $value);
+			$class = text::alternate('', 'kp-altrow');
+			$table->add_row($data, $class);
+		}
+	}
+}
\ No newline at end of file
diff --git a/system/libraries/Profiler_Table.php b/system/libraries/Profiler_Table.php
new file mode 100755
index 0000000..a0058a5
--- /dev/null
+++ b/system/libraries/Profiler_Table.php
@@ -0,0 +1,69 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Provides a table layout for sections in the Profiler library.
+ *
+ * $Id$
+ *
+ * @package    Profiler
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Profiler_Table_Core {
+
+	protected $columns = array();
+	protected $rows = array();
+
+	/**
+	 * Get styles for table.
+	 *
+	 * @return  string
+	 */
+	public function styles()
+	{
+		static $styles_output;
+
+		if ( ! $styles_output)
+		{
+			$styles_output = TRUE;
+			return file_get_contents(Kohana::find_file('views', 'kohana_profiler_table', FALSE, 'css'));
+		}
+
+		return '';
+	}
+
+	/**
+	 * Add column to table.
+	 *
+	 * @param  string  CSS class
+	 * @param  string  CSS style
+	 */
+	public function add_column($class = '', $style = '')
+	{
+		$this->columns[] = array('class' => $class, 'style' => $style);
+	}
+
+	/**
+	 * Add row to table.
+	 *
+	 * @param  array   data to go in table cells
+	 * @param  string  CSS class
+	 * @param  string  CSS style
+	 */
+	public function add_row($data, $class = '', $style = '')
+	{
+		$this->rows[] = array('data' => $data, 'class' => $class, 'style' => $style);
+	}
+
+	/**
+	 * Render table.
+	 *
+	 * @return  string
+	 */
+	public function render()
+	{
+		$data['rows'] = $this->rows;
+		$data['columns'] = $this->columns;
+		return View::factory('kohana_profiler_table', $data)->render();
+	}
+}
\ No newline at end of file
diff --git a/system/libraries/Router.php b/system/libraries/Router.php
new file mode 100755
index 0000000..29c88a9
--- /dev/null
+++ b/system/libraries/Router.php
@@ -0,0 +1,307 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Router
+ *
+ * $Id: Router.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Router_Core {
+
+	protected static $routes;
+
+	public static $current_uri  = '';
+	public static $query_string = '';
+	public static $complete_uri = '';
+	public static $routed_uri   = '';
+	public static $url_suffix   = '';
+
+	public static $segments;
+	public static $rsegments;
+
+	public static $controller;
+	public static $controller_path;
+
+	public static $method    = 'index';
+	public static $arguments = array();
+
+	/**
+	 * Router setup routine. Automatically called during Kohana setup process.
+	 *
+	 * @return  void
+	 */
+	public static function setup()
+	{
+		if ( ! empty($_SERVER['QUERY_STRING']))
+		{
+			// Set the query string to the current query string
+			self::$query_string = '?'.trim($_SERVER['QUERY_STRING'], '&/');
+		}
+
+		if (self::$routes === NULL)
+		{
+			// Load routes
+			self::$routes = Kohana::config('routes');
+		}
+
+		// Default route status
+		$default_route = FALSE;
+
+		if (self::$current_uri === '')
+		{
+			// Make sure the default route is set
+			if ( ! isset(self::$routes['_default']))
+				throw new Kohana_Exception('core.no_default_route');
+
+			// Use the default route when no segments exist
+			self::$current_uri = self::$routes['_default'];
+
+			// Default route is in use
+			$default_route = TRUE;
+		}
+
+		// Make sure the URL is not tainted with HTML characters
+		self::$current_uri = html::specialchars(self::$current_uri, FALSE);
+
+		// Remove all dot-paths from the URI, they are not valid
+		self::$current_uri = preg_replace('#\.[\s./]*/#', '', self::$current_uri);
+
+		// At this point segments, rsegments, and current URI are all the same
+		self::$segments = self::$rsegments = self::$current_uri = trim(self::$current_uri, '/');
+
+		// Set the complete URI
+		self::$complete_uri = self::$current_uri.self::$query_string;
+
+		// Explode the segments by slashes
+		self::$segments = ($default_route === TRUE OR self::$segments === '') ? array() : explode('/', self::$segments);
+
+		if ($default_route === FALSE AND count(self::$routes) > 1)
+		{
+			// Custom routing
+			self::$rsegments = self::routed_uri(self::$current_uri);
+		}
+
+		// The routed URI is now complete
+		self::$routed_uri = self::$rsegments;
+
+		// Routed segments will never be empty
+		self::$rsegments = explode('/', self::$rsegments);
+
+		// Prepare to find the controller
+		$controller_path = '';
+		$method_segment  = NULL;
+
+		// Paths to search
+		$paths = Kohana::include_paths();
+
+		foreach (self::$rsegments as $key => $segment)
+		{
+			// Add the segment to the search path
+			$controller_path .= $segment;
+
+			$found = FALSE;
+			foreach ($paths as $dir)
+			{
+				// Search within controllers only
+				$dir .= 'controllers/';
+
+				if (is_dir($dir.$controller_path) OR is_file($dir.$controller_path.EXT))
+				{
+					// Valid path
+					$found = TRUE;
+
+					// The controller must be a file that exists with the search path
+					if ($c = str_replace('\\', '/', realpath($dir.$controller_path.EXT)) 
+					    AND is_file($c) AND strpos($c, $dir) === 0)
+					{
+						// Set controller name
+						self::$controller = $segment;
+
+						// Change controller path
+						self::$controller_path = $c;
+
+						// Set the method segment
+						$method_segment = $key + 1;
+
+						// Stop searching
+						break;
+					}
+				}
+			}
+
+			if ($found === FALSE)
+			{
+				// Maximum depth has been reached, stop searching
+				break;
+			}
+
+			// Add another slash
+			$controller_path .= '/';
+		}
+
+		if ($method_segment !== NULL AND isset(self::$rsegments[$method_segment]))
+		{
+			// Set method
+			self::$method = self::$rsegments[$method_segment];
+
+			if (isset(self::$rsegments[$method_segment + 1]))
+			{
+				// Set arguments
+				self::$arguments = array_slice(self::$rsegments, $method_segment + 1);
+			}
+		}
+
+		// Last chance to set routing before a 404 is triggered
+		Event::run('system.post_routing');
+
+		if (self::$controller === NULL)
+		{
+			// No controller was found, so no page can be rendered
+			Event::run('system.404');
+		}
+	}
+
+	/**
+	 * Attempts to determine the current URI using CLI, GET, PATH_INFO, ORIG_PATH_INFO, or PHP_SELF.
+	 *
+	 * @return  void
+	 */
+	public static function find_uri()
+	{
+		if (PHP_SAPI === 'cli')
+		{
+			// Command line requires a bit of hacking
+			if (isset($_SERVER['argv'][1]))
+			{
+				self::$current_uri = $_SERVER['argv'][1];
+
+				// Remove GET string from segments
+				if (($query = strpos(self::$current_uri, '?')) !== FALSE)
+				{
+					list (self::$current_uri, $query) = explode('?', self::$current_uri, 2);
+
+					// Parse the query string into $_GET
+					parse_str($query, $_GET);
+
+					// Convert $_GET to UTF-8
+					$_GET = utf8::clean($_GET);
+				}
+			}
+		}
+		elseif (isset($_GET['kohana_uri']))
+		{
+			// Use the URI defined in the query string
+			self::$current_uri = $_GET['kohana_uri'];
+
+			// Remove the URI from $_GET
+			unset($_GET['kohana_uri']);
+
+			// Remove the URI from $_SERVER['QUERY_STRING']
+			$_SERVER['QUERY_STRING'] = preg_replace('~\bkohana_uri\b[^&]*+&?~', '', $_SERVER['QUERY_STRING']);
+		}
+		elseif (isset($_SERVER['PATH_INFO']) AND $_SERVER['PATH_INFO'])
+		{
+			self::$current_uri = $_SERVER['PATH_INFO'];
+		}
+		elseif (isset($_SERVER['ORIG_PATH_INFO']) AND $_SERVER['ORIG_PATH_INFO'])
+		{
+			self::$current_uri = $_SERVER['ORIG_PATH_INFO'];
+		}
+		elseif (isset($_SERVER['PHP_SELF']) AND $_SERVER['PHP_SELF'])
+		{
+			self::$current_uri = $_SERVER['PHP_SELF'];
+		}
+
+		// The front controller directory and filename
+		$fc = substr(realpath($_SERVER['SCRIPT_FILENAME']), strlen(DOCROOT));
+
+		if (($strpos_fc = strpos(self::$current_uri, $fc)) !== FALSE)
+		{
+			// Remove the front controller from the current uri
+			self::$current_uri = substr(self::$current_uri, $strpos_fc + strlen($fc));
+		}
+
+		// Remove slashes from the start and end of the URI
+		self::$current_uri = trim(self::$current_uri, '/');
+
+		if (self::$current_uri !== '')
+		{
+			if ($suffix = Kohana::config('core.url_suffix') AND strpos(self::$current_uri, $suffix) !== FALSE)
+			{
+				// Remove the URL suffix
+				self::$current_uri = preg_replace('#'.preg_quote($suffix).'$#u', '', self::$current_uri);
+
+				// Set the URL suffix
+				self::$url_suffix = $suffix;
+			}
+
+			// Reduce multiple slashes into single slashes
+			self::$current_uri = preg_replace('#//+#', '/', self::$current_uri);
+		}
+	}
+
+	/**
+	 * Generates routed URI from given URI.
+	 *
+	 * @param  string  URI to convert
+	 * @return string  Routed uri
+	 */
+	public static function routed_uri($uri)
+	{
+		if (self::$routes === NULL)
+		{
+			// Load routes
+			self::$routes = Kohana::config('routes');
+		}
+
+		// Prepare variables
+		$routed_uri = $uri = trim($uri, '/');
+
+		if (isset(self::$routes[$uri]))
+		{
+			// Literal match, no need for regex
+			$routed_uri = self::$routes[$uri];
+		}
+		else
+		{
+			// Loop through the routes and see if anything matches
+			foreach (self::$routes as $key => $val)
+			{
+				if ($key === '_default') continue;
+
+				// Trim slashes
+				$key = trim($key, '/');
+				$val = trim($val, '/');
+
+				if (preg_match('#^'.$key.'$#u', $uri))
+				{
+					if (strpos($val, '$') !== FALSE)
+					{
+						// Use regex routing
+						$routed_uri = preg_replace('#^'.$key.'$#u', $val, $uri);
+					}
+					else
+					{
+						// Standard routing
+						$routed_uri = $val;
+					}
+
+					// A valid route has been found
+					break;
+				}
+			}
+		}
+
+		if (isset(self::$routes[$routed_uri]))
+		{
+			// Check for double routing (without regex)
+			$routed_uri = self::$routes[$routed_uri];
+		}
+
+		return trim($routed_uri, '/');
+	}
+
+} // End Router
\ No newline at end of file
diff --git a/system/libraries/Session.php b/system/libraries/Session.php
new file mode 100755
index 0000000..159acac
--- /dev/null
+++ b/system/libraries/Session.php
@@ -0,0 +1,458 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session library.
+ *
+ * $Id: Session.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Session_Core {
+
+	// Session singleton
+	private static $instance;
+
+	// Protected key names (cannot be set by the user)
+	protected static $protect = array('session_id', 'user_agent', 'last_activity', 'ip_address', 'total_hits', '_kf_flash_');
+
+	// Configuration and driver
+	protected static $config;
+	protected static $driver;
+
+	// Flash variables
+	protected static $flash;
+
+	// Input library
+	protected $input;
+
+	/**
+	 * Singleton instance of Session.
+	 */
+	public static function instance()
+	{
+		if (self::$instance == NULL)
+		{
+			// Create a new instance
+			new Session;
+		}
+
+		return self::$instance;
+	}
+
+	/**
+	 * On first session instance creation, sets up the driver and creates session.
+	 */
+	public function __construct()
+	{
+		$this->input = Input::instance();
+
+		// This part only needs to be run once
+		if (self::$instance === NULL)
+		{
+			// Load config
+			self::$config = Kohana::config('session');
+
+			// Makes a mirrored array, eg: foo=foo
+			self::$protect = array_combine(self::$protect, self::$protect);
+
+			// Configure garbage collection
+			ini_set('session.gc_probability', (int) self::$config['gc_probability']);
+			ini_set('session.gc_divisor', 100);
+			ini_set('session.gc_maxlifetime', (self::$config['expiration'] == 0) ? 86400 : self::$config['expiration']);
+
+			// Create a new session
+			$this->create();
+
+			if (self::$config['regenerate'] > 0 AND ($_SESSION['total_hits'] % self::$config['regenerate']) === 0)
+			{
+				// Regenerate session id and update session cookie
+				$this->regenerate();
+			}
+			else
+			{
+				// Always update session cookie to keep the session alive
+				cookie::set(self::$config['name'], $_SESSION['session_id'], self::$config['expiration']);
+			}
+
+			// Close the session just before sending the headers, so that
+			// the session cookie(s) can be written.
+			Event::add('system.send_headers', array($this, 'write_close'));
+
+			// Make sure that sessions are closed before exiting
+			register_shutdown_function(array($this, 'write_close'));
+
+			// Singleton instance
+			self::$instance = $this;
+		}
+
+		Kohana::log('debug', 'Session Library initialized');
+	}
+
+	/**
+	 * Get the session id.
+	 *
+	 * @return  string
+	 */
+	public function id()
+	{
+		return $_SESSION['session_id'];
+	}
+
+	/**
+	 * Create a new session.
+	 *
+	 * @param   array  variables to set after creation
+	 * @return  void
+	 */
+	public function create($vars = NULL)
+	{
+		// Destroy any current sessions
+		$this->destroy();
+
+		if (self::$config['driver'] !== 'native')
+		{
+			// Set driver name
+			$driver = 'Session_'.ucfirst(self::$config['driver']).'_Driver';
+
+			// Load the driver
+			if ( ! Kohana::auto_load($driver))
+				throw new Kohana_Exception('core.driver_not_found', self::$config['driver'], get_class($this));
+
+			// Initialize the driver
+			self::$driver = new $driver();
+
+			// Validate the driver
+			if ( ! (self::$driver instanceof Session_Driver))
+				throw new Kohana_Exception('core.driver_implements', self::$config['driver'], get_class($this), 'Session_Driver');
+
+			// Register non-native driver as the session handler
+			session_set_save_handler
+			(
+				array(self::$driver, 'open'),
+				array(self::$driver, 'close'),
+				array(self::$driver, 'read'),
+				array(self::$driver, 'write'),
+				array(self::$driver, 'destroy'),
+				array(self::$driver, 'gc')
+			);
+		}
+
+		// Validate the session name
+		if ( ! preg_match('~^(?=.*[a-z])[a-z0-9_]++$~iD', self::$config['name']))
+			throw new Kohana_Exception('session.invalid_session_name', self::$config['name']);
+
+		// Name the session, this will also be the name of the cookie
+		session_name(self::$config['name']);
+
+		// Set the session cookie parameters
+		session_set_cookie_params
+		(
+			self::$config['expiration'],
+			Kohana::config('cookie.path'),
+			Kohana::config('cookie.domain'),
+			Kohana::config('cookie.secure'),
+			Kohana::config('cookie.httponly')
+		);
+
+		// Start the session!
+		session_start();
+
+		// Put session_id in the session variable
+		$_SESSION['session_id'] = session_id();
+
+		// Set defaults
+		if ( ! isset($_SESSION['_kf_flash_']))
+		{
+			$_SESSION['total_hits'] = 0;
+			$_SESSION['_kf_flash_'] = array();
+
+			$_SESSION['user_agent'] = Kohana::$user_agent;
+			$_SESSION['ip_address'] = $this->input->ip_address();
+		}
+
+		// Set up flash variables
+		self::$flash =& $_SESSION['_kf_flash_'];
+
+		// Increase total hits
+		$_SESSION['total_hits'] += 1;
+
+		// Validate data only on hits after one
+		if ($_SESSION['total_hits'] > 1)
+		{
+			// Validate the session
+			foreach (self::$config['validate'] as $valid)
+			{
+				switch ($valid)
+				{
+					// Check user agent for consistency
+					case 'user_agent':
+						if ($_SESSION[$valid] !== Kohana::$user_agent)
+							return $this->create();
+					break;
+
+					// Check ip address for consistency
+					case 'ip_address':
+						if ($_SESSION[$valid] !== $this->input->$valid())
+							return $this->create();
+					break;
+
+					// Check expiration time to prevent users from manually modifying it
+					case 'expiration':
+						if (time() - $_SESSION['last_activity'] > ini_get('session.gc_maxlifetime'))
+							return $this->create();
+					break;
+				}
+			}
+		}
+
+		// Expire flash keys
+		$this->expire_flash();
+
+		// Update last activity
+		$_SESSION['last_activity'] = time();
+
+		// Set the new data
+		self::set($vars);
+	}
+
+	/**
+	 * Regenerates the global session id.
+	 * 
+	 * @return  void
+	 */
+	public function regenerate()
+	{
+		if (self::$config['driver'] === 'native')
+		{
+			// Generate a new session id
+			// Note: also sets a new session cookie with the updated id
+			session_regenerate_id(TRUE);
+
+			// Update session with new id
+			$_SESSION['session_id'] = session_id();
+		}
+		else
+		{
+			// Pass the regenerating off to the driver in case it wants to do anything special
+			$_SESSION['session_id'] = self::$driver->regenerate();
+		}
+
+		// Get the session name
+		$name = session_name();
+
+		if (isset($_COOKIE[$name]))
+		{
+			// Change the cookie value to match the new session id to prevent "lag"
+			$_COOKIE[$name] = $_SESSION['session_id'];
+		}
+	}
+
+	/**
+	 * Destroys the current session.
+	 *
+	 * @return  void
+	 */
+	public function destroy()
+	{
+		if (session_id() !== '')
+		{
+			// Get the session name
+			$name = session_name();
+
+			// Destroy the session
+			session_destroy();
+
+			// Re-initialize the array
+			$_SESSION = array();
+
+			// Delete the session cookie
+			cookie::delete($name);
+		}
+	}
+
+	/**
+	 * Runs the system.session_write event, then calls session_write_close.
+	 *
+	 * @return  void
+	 */
+	public function write_close()
+	{
+		static $run;
+
+		if ($run === NULL)
+		{
+			$run = TRUE;
+
+			// Run the events that depend on the session being open
+			Event::run('system.session_write');
+
+			// Expire flash keys
+			$this->expire_flash();
+
+			// Close the session
+			session_write_close();
+		}
+	}
+
+	/**
+	 * Set a session variable.
+	 *
+	 * @param   string|array  key, or array of values
+	 * @param   mixed         value (if keys is not an array)
+	 * @return  void
+	 */
+	public function set($keys, $val = FALSE)
+	{
+		if (empty($keys))
+			return FALSE;
+
+		if ( ! is_array($keys))
+		{
+			$keys = array($keys => $val);
+		}
+
+		foreach ($keys as $key => $val)
+		{
+			if (isset(self::$protect[$key]))
+				continue;
+
+			// Set the key
+			$_SESSION[$key] = $val;
+		}
+	}
+
+	/**
+	 * Set a flash variable.
+	 *
+	 * @param   string|array  key, or array of values
+	 * @param   mixed         value (if keys is not an array)
+	 * @return  void
+	 */
+	public function set_flash($keys, $val = FALSE)
+	{
+		if (empty($keys))
+			return FALSE;
+
+		if ( ! is_array($keys))
+		{
+			$keys = array($keys => $val);
+		}
+
+		foreach ($keys as $key => $val)
+		{
+			if ($key == FALSE)
+				continue;
+
+			self::$flash[$key] = 'new';
+			self::set($key, $val);
+		}
+	}
+
+	/**
+	 * Freshen one, multiple or all flash variables.
+	 *
+	 * @param   string  variable key(s)
+	 * @return  void
+	 */
+	public function keep_flash($keys = NULL)
+	{
+		$keys = ($keys === NULL) ? array_keys(self::$flash) : func_get_args();
+
+		foreach ($keys as $key)
+		{
+			if (isset(self::$flash[$key]))
+			{
+				self::$flash[$key] = 'new';
+			}
+		}
+	}
+
+	/**
+	 * Expires old flash data and removes it from the session.
+	 *
+	 * @return  void
+	 */
+	public function expire_flash()
+	{
+		static $run;
+
+		// Method can only be run once
+		if ($run === TRUE)
+			return;
+
+		if ( ! empty(self::$flash))
+		{
+			foreach (self::$flash as $key => $state)
+			{
+				if ($state === 'old')
+				{
+					// Flash has expired
+					unset(self::$flash[$key], $_SESSION[$key]);
+				}
+				else
+				{
+					// Flash will expire
+					self::$flash[$key] = 'old';
+				}
+			}
+		}
+
+		// Method has been run
+		$run = TRUE;
+	}
+
+	/**
+	 * Get a variable. Access to sub-arrays is supported with key.subkey.
+	 *
+	 * @param   string  variable key
+	 * @param   mixed   default value returned if variable does not exist
+	 * @return  mixed   Variable data if key specified, otherwise array containing all session data.
+	 */
+	public function get($key = FALSE, $default = FALSE)
+	{
+		if (empty($key))
+			return $_SESSION;
+
+		$result = isset($_SESSION[$key]) ? $_SESSION[$key] : Kohana::key_string($_SESSION, $key);
+
+		return ($result === NULL) ? $default : $result;
+	}
+
+	/**
+	 * Get a variable, and delete it.
+	 *
+	 * @param   string  variable key
+	 * @param   mixed   default value returned if variable does not exist
+	 * @return  mixed
+	 */
+	public function get_once($key, $default = FALSE)
+	{
+		$return = self::get($key, $default);
+		self::delete($key);
+
+		return $return;
+	}
+
+	/**
+	 * Delete one or more variables.
+	 *
+	 * @param   string  variable key(s)
+	 * @return  void
+	 */
+	public function delete($keys)
+	{
+		$args = func_get_args();
+
+		foreach ($args as $key)
+		{
+			if (isset(self::$protect[$key]))
+				continue;
+
+			// Unset the key
+			unset($_SESSION[$key]);
+		}
+	}
+
+} // End Session Class
diff --git a/system/libraries/Tagcloud.php b/system/libraries/Tagcloud.php
new file mode 100755
index 0000000..127b344
--- /dev/null
+++ b/system/libraries/Tagcloud.php
@@ -0,0 +1,130 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * [Tag cloud][ref-tcl] creation library.
+ *
+ * [ref-tcl]: http://en.wikipedia.org/wiki/Tag_cloud
+ *
+ * $Id: Tagcloud.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Tagcloud
+ * @author     Kohana Team
+ * @copyright  (c) 2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Tagcloud_Core {
+
+	/**
+	 * Creates a new Tagcloud instance and returns it.
+	 *
+	 * @chainable
+	 * @param   array    elements of the tagcloud
+	 * @param   integer  minimum font size
+	 * @param   integer  maximum font size
+	 * @return  Tagcloud
+	 */
+	public static function factory(array $elements, $min_size = NULL, $max_size = NULL, $shuffle = FALSE)
+	{
+		return new Tagcloud($elements, $min_size, $max_size, $shuffle);
+	}
+
+	public $min_size   = 80;
+	public $max_size   = 140;
+	public $attributes = array('class' => 'tag');
+	public $shuffle    = FALSE;
+
+	// Tag elements, biggest and smallest values
+	protected $elements;
+	protected $biggest;
+	protected $smallest;
+
+	/**
+	 * Construct a new tagcloud. The elements must be passed in as an array,
+	 * with each entry in the array having a "title" ,"link", and "count" key.
+	 * Font sizes will be applied via the "style" attribute as a percentage.
+	 *
+	 * @param   array    elements of the tagcloud
+	 * @param   integer  minimum font size
+	 * @param   integer  maximum font size
+	 * @return  void
+	 */
+	public function __construct(array $elements, $min_size = NULL, $max_size = NULL, $shuffle = FALSE)
+	{
+		$this->elements = $elements;
+		
+		if($shuffle !== FALSE)
+		{
+			$this->shuffle = TRUE;
+		}
+
+		$counts = array();
+		foreach ($elements as $data)
+		{
+			$counts[] = $data['count'];
+		}
+
+		// Find the biggest and smallest values of the elements
+		$this->biggest  = max($counts);
+		$this->smallest = min($counts);
+
+		if ($min_size !== NULL)
+		{
+			$this->min_size = $min_size;
+		}
+
+		if ($max_size !== NULL)
+		{
+			$this->max_size = $max_size;
+		}
+	}
+
+	/**
+	 * Magic __toString method. Returns all of the links as a single string.
+	 *
+	 * @return  string
+	 */
+	public function __toString()
+	{
+		return implode("\n", $this->render());
+	}
+
+	/**
+	 * Renders the elements of the tagcloud into an array of links.
+	 *
+	 * @return  array
+	 */
+	public function render()
+	{
+		if ($this->shuffle === TRUE)
+		{
+			shuffle($this->elements);
+		}
+
+		// Minimum values must be 1 to prevent divide by zero errors
+		$range = max($this->biggest  - $this->smallest, 1);
+		$scale = max($this->max_size - $this->min_size, 1);
+
+		// Import the attributes locally to prevent overwrites
+		$attr = $this->attributes;
+
+		$output = array();
+		foreach ($this->elements as $data)
+		{
+			if (strpos($data['title'], ' ') !== FALSE)
+			{
+				// Replace spaces with non-breaking spaces to prevent line wrapping
+				// in the middle of a link
+				$data['title'] = str_replace(' ', ' ', $data['title']);
+			}
+
+			// Determine the size based on the min/max scale and the smallest/biggest range
+			$size = ((($data['count'] - $this->smallest) * $scale) / $range) + $this->min_size;
+
+			$attr['style'] = 'font-size: '.round($size, 0).'%';
+
+			$output[] = html::anchor($data['link'], $data['title'], $attr)."\n";
+		}
+
+		return $output;
+	}
+
+} // End Tagcloud
\ No newline at end of file
diff --git a/system/libraries/URI.php b/system/libraries/URI.php
new file mode 100755
index 0000000..b99ceeb
--- /dev/null
+++ b/system/libraries/URI.php
@@ -0,0 +1,279 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * URI library.
+ *
+ * $Id: URI.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class URI_Core extends Router {
+
+	/**
+	 * Returns a singleton instance of URI.
+	 *
+	 * @return  object
+	 */
+	public static function instance()
+	{
+		static $instance;
+
+		if ($instance == NULL)
+		{
+			// Initialize the URI instance
+			$instance = new URI;
+		}
+
+		return $instance;
+	}
+
+	/**
+	 * Retrieve a specific URI segment.
+	 *
+	 * @param   integer|string  segment number or label
+	 * @param   mixed           default value returned if segment does not exist
+	 * @return  string
+	 */
+	public function segment($index = 1, $default = FALSE)
+	{
+		if (is_string($index))
+		{
+			if (($key = array_search($index, self::$segments)) === FALSE)
+				return $default;
+
+			$index = $key + 2;
+		}
+
+		$index = (int) $index - 1;
+
+		return isset(self::$segments[$index]) ? self::$segments[$index] : $default;
+	}
+
+	/**
+	 * Retrieve a specific routed URI segment.
+	 *
+	 * @param   integer|string  rsegment number or label
+	 * @param   mixed           default value returned if segment does not exist
+	 * @return  string
+	 */
+	public function rsegment($index = 1, $default = FALSE)
+	{
+		if (is_string($index))
+		{
+			if (($key = array_search($index, self::$rsegments)) === FALSE)
+				return $default;
+
+			$index = $key + 2;
+		}
+
+		$index = (int) $index - 1;
+
+		return isset(self::$rsegments[$index]) ? self::$rsegments[$index] : $default;
+	}
+
+	/**
+	 * Retrieve a specific URI argument.
+	 * This is the part of the segments that does not indicate controller or method
+	 *
+	 * @param   integer|string  argument number or label
+	 * @param   mixed           default value returned if segment does not exist
+	 * @return  string
+	 */
+	public function argument($index = 1, $default = FALSE)
+	{
+		if (is_string($index))
+		{
+			if (($key = array_search($index, self::$arguments)) === FALSE)
+				return $default;
+
+			$index = $key + 2;
+		}
+
+		$index = (int) $index - 1;
+
+		return isset(self::$arguments[$index]) ? self::$arguments[$index] : $default;
+	}
+
+	/**
+	 * Returns an array containing all the URI segments.
+	 *
+	 * @param   integer  segment offset
+	 * @param   boolean  return an associative array
+	 * @return  array
+	 */
+	public function segment_array($offset = 0, $associative = FALSE)
+	{
+		return $this->build_array(self::$segments, $offset, $associative);
+	}
+
+	/**
+	 * Returns an array containing all the re-routed URI segments.
+	 *
+	 * @param   integer  rsegment offset
+	 * @param   boolean  return an associative array
+	 * @return  array
+	 */
+	public function rsegment_array($offset = 0, $associative = FALSE)
+	{
+		return $this->build_array(self::$rsegments, $offset, $associative);
+	}
+
+	/**
+	 * Returns an array containing all the URI arguments.
+	 *
+	 * @param   integer  segment offset
+	 * @param   boolean  return an associative array
+	 * @return  array
+	 */
+	public function argument_array($offset = 0, $associative = FALSE)
+	{
+		return $this->build_array(self::$arguments, $offset, $associative);
+	}
+
+	/**
+	 * Creates a simple or associative array from an array and an offset.
+	 * Used as a helper for (r)segment_array and argument_array.
+	 *
+	 * @param   array    array to rebuild
+	 * @param   integer  offset to start from
+	 * @param   boolean  create an associative array
+	 * @return  array
+	 */
+	public function build_array($array, $offset = 0, $associative = FALSE)
+	{
+		// Prevent the keys from being improperly indexed
+		array_unshift($array, 0);
+
+		// Slice the array, preserving the keys
+		$array = array_slice($array, $offset + 1, count($array) - 1, TRUE);
+
+		if ($associative === FALSE)
+			return $array;
+
+		$associative = array();
+		$pairs       = array_chunk($array, 2);
+
+		foreach ($pairs as $pair)
+		{
+			// Add the key/value pair to the associative array
+			$associative[$pair[0]] = isset($pair[1]) ? $pair[1] : '';
+		}
+
+		return $associative;
+	}
+
+	/**
+	 * Returns the complete URI as a string.
+	 *
+	 * @return  string
+	 */
+	public function string()
+	{
+		return self::$current_uri;
+	}
+
+	/**
+	 * Magic method for converting an object to a string.
+	 *
+	 * @return  string
+	 */
+	public function __toString()
+	{
+		return self::$current_uri;
+	}
+
+	/**
+	 * Returns the total number of URI segments.
+	 *
+	 * @return  integer
+	 */
+	public function total_segments()
+	{
+		return count(self::$segments);
+	}
+
+	/**
+	 * Returns the total number of re-routed URI segments.
+	 *
+	 * @return  integer
+	 */
+	public function total_rsegments()
+	{
+		return count(self::$rsegments);
+	}
+
+	/**
+	 * Returns the total number of URI arguments.
+	 *
+	 * @return  integer
+	 */
+	public function total_arguments()
+	{
+		return count(self::$arguments);
+	}
+
+	/**
+	 * Returns the last URI segment.
+	 *
+	 * @param   mixed   default value returned if segment does not exist
+	 * @return  string
+	 */
+	public function last_segment($default = FALSE)
+	{
+		if (($end = $this->total_segments()) < 1)
+			return $default;
+
+		return self::$segments[$end - 1];
+	}
+
+	/**
+	 * Returns the last re-routed URI segment.
+	 *
+	 * @param   mixed   default value returned if segment does not exist
+	 * @return  string
+	 */
+	public function last_rsegment($default = FALSE)
+	{
+		if (($end = $this->total_segments()) < 1)
+			return $default;
+
+		return self::$rsegments[$end - 1];
+	}
+
+	/**
+	 * Returns the path to the current controller (not including the actual
+	 * controller), as a web path.
+	 *
+	 * @param   boolean  return a full url, or only the path specifically
+	 * @return  string
+	 */
+	public function controller_path($full = TRUE)
+	{
+		return ($full) ? url::site(self::$controller_path) : self::$controller_path;
+	}
+
+	/**
+	 * Returns the current controller, as a web path.
+	 *
+	 * @param   boolean  return a full url, or only the controller specifically
+	 * @return  string
+	 */
+	public function controller($full = TRUE)
+	{
+		return ($full) ? url::site(self::$controller_path.self::$controller) : self::$controller;
+	}
+
+	/**
+	 * Returns the current method, as a web path.
+	 *
+	 * @param   boolean  return a full url, or only the method specifically
+	 * @return  string
+	 */
+	public function method($full = TRUE)
+	{
+		return ($full) ? url::site(self::$controller_path.self::$controller.'/'.self::$method) : self::$method;
+	}
+
+} // End URI Class
diff --git a/system/libraries/Validation.php b/system/libraries/Validation.php
new file mode 100755
index 0000000..1afc7e5
--- /dev/null
+++ b/system/libraries/Validation.php
@@ -0,0 +1,826 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Validation library.
+ *
+ * $Id: Validation.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Validation
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Validation_Core extends ArrayObject {
+
+	// Filters
+	protected $pre_filters = array();
+	protected $post_filters = array();
+
+	// Rules and callbacks
+	protected $rules = array();
+	protected $callbacks = array();
+
+	// Rules that are allowed to run on empty fields
+	protected $empty_rules = array('required', 'matches');
+
+	// Errors
+	protected $errors = array();
+	protected $messages = array();
+
+	// Fields that are expected to be arrays
+	protected $array_fields = array();
+
+	// Checks if there is data to validate.
+	protected $submitted;
+
+	/**
+	 * Creates a new Validation instance.
+	 *
+	 * @param   array   array to use for validation
+	 * @return  object
+	 */
+	public static function factory(array $array)
+	{
+		return new Validation($array);
+	}
+
+	/**
+	 * Sets the unique "any field" key and creates an ArrayObject from the
+	 * passed array.
+	 *
+	 * @param   array   array to validate
+	 * @return  void
+	 */
+	public function __construct(array $array)
+	{
+		// The array is submitted if the array is not empty
+		$this->submitted = ! empty($array);
+
+		parent::__construct($array, ArrayObject::ARRAY_AS_PROPS | ArrayObject::STD_PROP_LIST);
+	}
+
+	/**
+	 * Magic clone method, clears errors and messages.
+	 *
+	 * @return  void
+	 */
+	public function __clone()
+	{
+		$this->errors = array();
+		$this->messages = array();
+	}
+
+	/**
+	 * Create a copy of the current validation rules and change the array.
+	 *
+	 * @chainable
+	 * @param   array  new array to validate
+	 * @return  Validation
+	 */
+	public function copy(array $array)
+	{
+		$copy = clone $this;
+
+		$copy->exchangeArray($array);
+
+		return $copy;
+	}
+
+	/**
+	 * Test if the data has been submitted.
+	 *
+	 * @return  boolean
+	 */
+	public function submitted($value = NULL)
+	{
+		if (is_bool($value))
+		{
+			$this->submitted = $value;
+		}
+
+		return $this->submitted;
+	}
+
+	/**
+	 * Returns an array of all the field names that have filters, rules, or callbacks.
+	 *
+	 * @return  array
+	 */
+	public function field_names()
+	{
+		// All the fields that are being validated
+		$fields = array_unique(array_merge
+		(
+			array_keys($this->pre_filters),
+			array_keys($this->rules),
+			array_keys($this->callbacks),
+			array_keys($this->post_filters)
+		));
+
+		// Remove wildcard fields
+		$fields = array_diff($fields, array('*'));
+
+		return $fields;
+	}
+
+	/**
+	 * Returns the array values of the current object.
+	 *
+	 * @return  array
+	 */
+	public function as_array()
+	{
+		return $this->getArrayCopy();
+	}
+
+	/**
+	 * Returns the ArrayObject values, removing all inputs without rules.
+	 * To choose specific inputs, list the field name as arguments.
+	 *
+	 * @param   boolean  return only fields with filters, rules, and callbacks
+	 * @return  array
+	 */
+	public function safe_array()
+	{
+		// Load choices
+		$choices = func_get_args();
+		$choices = empty($choices) ? NULL : array_combine($choices, $choices);
+
+		// Get field names
+		$fields = $this->field_names();
+
+		$safe = array();
+		foreach ($fields as $field)
+		{
+			if ($choices === NULL OR isset($choices[$field]))
+			{
+				if (isset($this[$field]))
+				{
+					$value = $this[$field];
+
+					if (is_object($value))
+					{
+						// Convert the value back into an array
+						$value = $value->getArrayCopy();
+					}
+				}
+				else
+				{
+					// Even if the field is not in this array, it must be set
+					$value = NULL;
+				}
+
+				// Add the field to the array
+				$safe[$field] = $value;
+			}
+		}
+
+		return $safe;
+	}
+
+	/**
+	 * Add additional rules that will forced, even for empty fields. All arguments
+	 * passed will be appended to the list.
+	 *
+	 * @chainable
+	 * @param   string   rule name
+	 * @return  object
+	 */
+	public function allow_empty_rules($rules)
+	{
+		// Any number of args are supported
+		$rules = func_get_args();
+
+		// Merge the allowed rules
+		$this->empty_rules = array_merge($this->empty_rules, $rules);
+
+		return $this;
+	}
+
+	/**
+	 * Converts a filter, rule, or callback into a fully-qualified callback array.
+	 *
+	 * @return  mixed
+	 */
+	protected function callback($callback)
+	{
+		if (is_string($callback))
+		{
+			if (strpos($callback, '::') !== FALSE)
+			{
+				$callback = explode('::', $callback);
+			}
+			elseif (function_exists($callback))
+			{
+				// No need to check if the callback is a method
+				$callback = $callback;
+			}
+			elseif (method_exists($this, $callback))
+			{
+				// The callback exists in Validation
+				$callback = array($this, $callback);
+			}
+			elseif (method_exists('valid', $callback))
+			{
+				// The callback exists in valid::
+				$callback = array('valid', $callback);
+			}
+		}
+
+		if ( ! is_callable($callback, FALSE))
+		{
+			if (is_array($callback))
+			{
+				if (is_object($callback[0]))
+				{
+					// Object instance syntax
+					$name = get_class($callback[0]).'->'.$callback[1];
+				}
+				else
+				{
+					// Static class syntax
+					$name = $callback[0].'::'.$callback[1];
+				}
+			}
+			else
+			{
+				// Function syntax
+				$name = $callback;
+			}
+
+			throw new Kohana_Exception('validation.not_callable', $name);
+		}
+
+		return $callback;
+	}
+
+	/**
+	 * Add a pre-filter to one or more inputs. Pre-filters are applied before
+	 * rules or callbacks are executed.
+	 *
+	 * @chainable
+	 * @param   callback  filter
+	 * @param   string    fields to apply filter to, use TRUE for all fields
+	 * @return  object
+	 */
+	public function pre_filter($filter, $field = TRUE)
+	{
+		if ($field === TRUE OR $field === '*')
+		{
+			// Use wildcard
+			$fields = array('*');
+		}
+		else
+		{
+			// Add the filter to specific inputs
+			$fields = func_get_args();
+			$fields = array_slice($fields, 1);
+		}
+
+		// Convert to a proper callback
+		$filter = $this->callback($filter);
+
+		foreach ($fields as $field)
+		{
+			// Add the filter to specified field
+			$this->pre_filters[$field][] = $filter;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add a post-filter to one or more inputs. Post-filters are applied after
+	 * rules and callbacks have been executed.
+	 *
+	 * @chainable
+	 * @param   callback  filter
+	 * @param   string    fields to apply filter to, use TRUE for all fields
+	 * @return  object
+	 */
+	public function post_filter($filter, $field = TRUE)
+	{
+		if ($field === TRUE)
+		{
+			// Use wildcard
+			$fields = array('*');
+		}
+		else
+		{
+			// Add the filter to specific inputs
+			$fields = func_get_args();
+			$fields = array_slice($fields, 1);
+		}
+
+		// Convert to a proper callback
+		$filter = $this->callback($filter);
+
+		foreach ($fields as $field)
+		{
+			// Add the filter to specified field
+			$this->post_filters[$field][] = $filter;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add rules to a field. Validation rules may only return TRUE or FALSE and
+	 * can not manipulate the value of a field.
+	 *
+	 * @chainable
+	 * @param   string    field name
+	 * @param   callback  rules (one or more arguments)
+	 * @return  object
+	 */
+	public function add_rules($field, $rules)
+	{
+		// Get the rules
+		$rules = func_get_args();
+		$rules = array_slice($rules, 1);
+
+		if ($field === TRUE)
+		{
+			// Use wildcard
+			$field = '*';
+		}
+
+		foreach ($rules as $rule)
+		{
+			// Arguments for rule
+			$args = NULL;
+
+			if (is_string($rule))
+			{
+				if (preg_match('/^([^\[]++)\[(.+)\]$/', $rule, $matches))
+				{
+					// Split the rule into the function and args
+					$rule = $matches[1];
+					$args = preg_split('/(?<!\\\\),\s*/', $matches[2]);
+
+					// Replace escaped comma with comma
+					$args = str_replace('\,', ',', $args);
+				}
+			}
+
+			if ($rule === 'is_array')
+			{
+				// This field is expected to be an array
+				$this->array_fields[$field] = $field;
+			}
+
+			// Convert to a proper callback
+			$rule = $this->callback($rule);
+
+			// Add the rule, with args, to the field
+			$this->rules[$field][] = array($rule, $args);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Add callbacks to a field. Callbacks must accept the Validation object
+	 * and the input name. Callback returns are not processed.
+	 *
+	 * @chainable
+	 * @param   string     field name
+	 * @param   callbacks  callbacks (unlimited number)
+	 * @return  object
+	 */
+	public function add_callbacks($field, $callbacks)
+	{
+		// Get all callbacks as an array
+		$callbacks = func_get_args();
+		$callbacks = array_slice($callbacks, 1);
+
+		if ($field === TRUE)
+		{
+			// Use wildcard
+			$field = '*';
+		}
+
+		foreach ($callbacks as $callback)
+		{
+			// Convert to a proper callback
+			$callback = $this->callback($callback);
+
+			// Add the callback to specified field
+			$this->callbacks[$field][] = $callback;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Validate by processing pre-filters, rules, callbacks, and post-filters.
+	 * All fields that have filters, rules, or callbacks will be initialized if
+	 * they are undefined. Validation will only be run if there is data already
+	 * in the array.
+	 *
+	 * @param   object  Validation object, used only for recursion
+	 * @param   object  name of field for errors
+	 * @return  bool
+	 */
+	public function validate($object = NULL, $field_name = NULL)
+	{
+		if ($object === NULL)
+		{
+			// Use the current object
+			$object = $this;
+		}
+
+		// Get all field names
+		$fields = $this->field_names();
+
+		// Copy the array from the object, to optimize multiple sets
+		$array = $this->getArrayCopy();
+
+		foreach ($fields as $field)
+		{
+			if ($field === '*')
+			{
+				// Ignore wildcard
+				continue;
+			}
+
+			if ( ! isset($array[$field]))
+			{
+				if (isset($this->array_fields[$field]))
+				{
+					// This field must be an array
+					$array[$field] = array();
+				}
+				else
+				{
+					$array[$field] = NULL;
+				}
+			}
+		}
+
+		// Swap the array back into the object
+		$this->exchangeArray($array);
+
+		// Get all defined field names
+		$fields = array_keys($array);
+
+		foreach ($this->pre_filters as $field => $callbacks)
+		{
+			foreach ($callbacks as $callback)
+			{
+				if ($field === '*')
+				{
+					foreach ($fields as $f)
+					{
+						$this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
+					}
+				}
+				else
+				{
+					$this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
+				}
+			}
+		}
+
+		if ($this->submitted === FALSE)
+			return FALSE;
+
+		foreach ($this->rules as $field => $callbacks)
+		{
+			foreach ($callbacks as $callback)
+			{
+				// Separate the callback and arguments
+				list ($callback, $args) = $callback;
+
+				// Function or method name of the rule
+				$rule = is_array($callback) ? $callback[1] : $callback;
+
+				if ($field === '*')
+				{
+					foreach ($fields as $f)
+					{
+						// Note that continue, instead of break, is used when
+						// applying rules using a wildcard, so that all fields
+						// will be validated.
+
+						if (isset($this->errors[$f]))
+						{
+							// Prevent other rules from being evaluated if an error has occurred
+							continue;
+						}
+
+						if (empty($this[$f]) AND ! in_array($rule, $this->empty_rules))
+						{
+							// This rule does not need to be processed on empty fields
+							continue;
+						}
+
+						if ($args === NULL)
+						{
+							if ( ! call_user_func($callback, $this[$f]))
+							{
+								$this->errors[$f] = $rule;
+
+								// Stop validating this field when an error is found
+								continue;
+							}
+						}
+						else
+						{
+							if ( ! call_user_func($callback, $this[$f], $args))
+							{
+								$this->errors[$f] = $rule;
+
+								// Stop validating this field when an error is found
+								continue;
+							}
+						}
+					}
+				}
+				else
+				{
+					if (isset($this->errors[$field]))
+					{
+						// Prevent other rules from being evaluated if an error has occurred
+						break;
+					}
+
+					if ( ! in_array($rule, $this->empty_rules) AND ! $this->required($this[$field]))
+					{
+						// This rule does not need to be processed on empty fields
+						continue;
+					}
+
+					if ($args === NULL)
+					{
+						if ( ! call_user_func($callback, $this[$field]))
+						{
+							$this->errors[$field] = $rule;
+
+							// Stop validating this field when an error is found
+							break;
+						}
+					}
+					else
+					{
+						if ( ! call_user_func($callback, $this[$field], $args))
+						{
+							$this->errors[$field] = $rule;
+
+							// Stop validating this field when an error is found
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		foreach ($this->callbacks as $field => $callbacks)
+		{
+			foreach ($callbacks as $callback)
+			{
+				if ($field === '*')
+				{
+					foreach ($fields as $f)
+					{
+						// Note that continue, instead of break, is used when
+						// applying rules using a wildcard, so that all fields
+						// will be validated.
+
+						if (isset($this->errors[$f]))
+						{
+							// Stop validating this field when an error is found
+							continue;
+						}
+
+						call_user_func($callback, $this, $f);
+					}
+				}
+				else
+				{
+					if (isset($this->errors[$field]))
+					{
+						// Stop validating this field when an error is found
+						break;
+					}
+
+					call_user_func($callback, $this, $field);
+				}
+			}
+		}
+
+		foreach ($this->post_filters as $field => $callbacks)
+		{
+			foreach ($callbacks as $callback)
+			{
+				if ($field === '*')
+				{
+					foreach ($fields as $f)
+					{
+						$this[$f] = is_array($this[$f]) ? array_map($callback, $this[$f]) : call_user_func($callback, $this[$f]);
+					}
+				}
+				else
+				{
+					$this[$field] = is_array($this[$field]) ? array_map($callback, $this[$field]) : call_user_func($callback, $this[$field]);
+				}
+			}
+		}
+
+		// Return TRUE if there are no errors
+		return $this->errors === array();
+	}
+
+	/**
+	 * Add an error to an input.
+	 *
+	 * @chainable
+	 * @param   string  input name
+	 * @param   string  unique error name
+	 * @return  object
+	 */
+	public function add_error($field, $name)
+	{
+		$this->errors[$field] = $name;
+
+		return $this;
+	}
+
+	/**
+	 * Sets or returns the message for an input.
+	 *
+	 * @chainable
+	 * @param   string   input key
+	 * @param   string   message to set
+	 * @return  string|object
+	 */
+	public function message($input = NULL, $message = NULL)
+	{
+		if ($message === NULL)
+		{
+			if ($input === NULL)
+			{
+				$messages = array();
+				$keys     = array_keys($this->messages);
+
+				foreach ($keys as $input)
+				{
+					$messages[] = $this->message($input);
+				}
+
+				return implode("\n", $messages);
+			}
+
+			// Return nothing if no message exists
+			if (empty($this->messages[$input]))
+				return '';
+
+			// Return the HTML message string
+			return $this->messages[$input];
+		}
+		else
+		{
+			$this->messages[$input] = $message;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Return the errors array.
+	 *
+	 * @param   boolean  load errors from a lang file
+	 * @return  array
+	 */
+	public function errors($file = NULL)
+	{
+		if ($file === NULL)
+		{
+			return $this->errors;
+		}
+		else
+		{
+
+			$errors = array();
+			foreach ($this->errors as $input => $error)
+			{
+				// Key for this input error
+				$key = "$file.$input.$error";
+
+				if (($errors[$input] = Kohana::lang($key)) === $key)
+				{
+					// Get the default error message
+					$errors[$input] = Kohana::lang("$file.$input.default");
+				}
+			}
+
+			return $errors;
+		}
+	}
+
+	/**
+	 * Rule: required. Generates an error if the field has an empty value.
+	 *
+	 * @param   mixed   input value
+	 * @return  bool
+	 */
+	public function required($str)
+	{
+		if (is_object($str) AND $str instanceof ArrayObject)
+		{
+			// Get the array from the ArrayObject
+			$str = $str->getArrayCopy();
+		}
+
+		if (is_array($str))
+		{
+			return ! empty($str);
+		}
+		else
+		{
+			return ! ($str === '' OR $str === NULL OR $str === FALSE);
+		}
+	}
+
+	/**
+	 * Rule: matches. Generates an error if the field does not match one or more
+	 * other fields.
+	 *
+	 * @param   mixed   input value
+	 * @param   array   input names to match against
+	 * @return  bool
+	 */
+	public function matches($str, array $inputs)
+	{
+		foreach ($inputs as $key)
+		{
+			if ($str !== (isset($this[$key]) ? $this[$key] : NULL))
+				return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Rule: length. Generates an error if the field is too long or too short.
+	 *
+	 * @param   mixed   input value
+	 * @param   array   minimum, maximum, or exact length to match
+	 * @return  bool
+	 */
+	public function length($str, array $length)
+	{
+		if ( ! is_string($str))
+			return FALSE;
+
+		$size = utf8::strlen($str);
+		$status = FALSE;
+
+		if (count($length) > 1)
+		{
+			list ($min, $max) = $length;
+
+			if ($size >= $min AND $size <= $max)
+			{
+				$status = TRUE;
+			}
+		}
+		else
+		{
+			$status = ($size === (int) $length[0]);
+		}
+
+		return $status;
+	}
+
+	/**
+	 * Rule: depends_on. Generates an error if the field does not depend on one
+	 * or more other fields.
+	 *
+	 * @param   mixed   field name
+	 * @param   array   field names to check dependency
+	 * @return  bool
+	 */
+	public function depends_on($field, array $fields)
+	{
+		foreach ($fields as $depends_on)
+		{
+			if ( ! isset($this[$depends_on]) OR $this[$depends_on] == NULL)
+				return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Rule: chars. Generates an error if the field contains characters outside of the list.
+	 *
+	 * @param   string  field value
+	 * @param   array   allowed characters
+	 * @return  bool
+	 */
+	public function chars($value, array $chars)
+	{
+		return ! preg_match('![^'.implode('', $chars).']!u', $value);
+	}
+
+} // End Validation
diff --git a/system/libraries/View.php b/system/libraries/View.php
new file mode 100755
index 0000000..c9179e3
--- /dev/null
+++ b/system/libraries/View.php
@@ -0,0 +1,303 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Loads and displays Kohana view files. Can also handle output of some binary
+ * files, such as image, Javascript, and CSS files.
+ *
+ * $Id: View.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class View_Core {
+
+	// The view file name and type
+	protected $kohana_filename = FALSE;
+	protected $kohana_filetype = FALSE;
+
+	// View variable storage
+	protected $kohana_local_data = array();
+	protected static $kohana_global_data = array();
+
+	/**
+	 * Creates a new View using the given parameters.
+	 *
+	 * @param   string  view name
+	 * @param   array   pre-load data
+	 * @param   string  type of file: html, css, js, etc.
+	 * @return  object
+	 */
+	public static function factory($name = NULL, $data = NULL, $type = NULL)
+	{
+		return new View($name, $data, $type);
+	}
+
+	/**
+	 * Attempts to load a view and pre-load view data.
+	 *
+	 * @throws  Kohana_Exception  if the requested view cannot be found
+	 * @param   string  view name
+	 * @param   array   pre-load data
+	 * @param   string  type of file: html, css, js, etc.
+	 * @return  void
+	 */
+	public function __construct($name = NULL, $data = NULL, $type = NULL)
+	{
+		if (is_string($name) AND $name !== '')
+		{
+			// Set the filename
+			$this->set_filename($name, $type);
+		}
+
+		if (is_array($data) AND ! empty($data))
+		{
+			// Preload data using array_merge, to allow user extensions
+			$this->kohana_local_data = array_merge($this->kohana_local_data, $data);
+		}
+	}
+	
+	/**
+	 * Magic method access to test for view property
+	 *
+	 * @param   string   View property to test for
+	 * @return  boolean
+	 */
+	public function __isset($key = NULL)
+	{
+		return $this->is_set($key);
+	}
+
+	/**
+	 * Sets the view filename.
+	 *
+	 * @chainable
+	 * @param   string  view filename
+	 * @param   string  view file type
+	 * @return  object
+	 */
+	public function set_filename($name, $type = NULL)
+	{
+		if ($type == NULL)
+		{
+			// Load the filename and set the content type
+			$this->kohana_filename = Kohana::find_file('views', $name, TRUE);
+			$this->kohana_filetype = EXT;
+		}
+		else
+		{
+			// Check if the filetype is allowed by the configuration
+			if ( ! in_array($type, Kohana::config('view.allowed_filetypes')))
+				throw new Kohana_Exception('core.invalid_filetype', $type);
+
+			// Load the filename and set the content type
+			$this->kohana_filename = Kohana::find_file('views', $name, TRUE, $type);
+			$this->kohana_filetype = Kohana::config('mimes.'.$type);
+
+			if ($this->kohana_filetype == NULL)
+			{
+				// Use the specified type
+				$this->kohana_filetype = $type;
+			}
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Sets a view variable.
+	 *
+	 * @param   string|array  name of variable or an array of variables
+	 * @param   mixed         value when using a named variable
+	 * @return  object
+	 */
+	public function set($name, $value = NULL)
+	{
+		if (is_array($name))
+		{
+			foreach ($name as $key => $value)
+			{
+				$this->__set($key, $value);
+			}
+		}
+		else
+		{
+			$this->__set($name, $value);
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Checks for a property existence in the view locally or globally. Unlike the built in __isset(), 
+	 * this method can take an array of properties to test simultaneously.
+	 *
+	 * @param string $key property name to test for
+	 * @param array $key array of property names to test for
+	 * @return boolean property test result
+	 * @return array associative array of keys and boolean test result
+	 */
+	public function is_set( $key = FALSE )
+	{
+		// Setup result;
+		$result = FALSE;
+
+		// If key is an array
+		if (is_array($key))
+		{
+			// Set the result to an array
+			$result = array();
+			
+			// Foreach key
+			foreach ($key as $property)
+			{
+				// Set the result to an associative array
+				$result[$property] = (array_key_exists($property, $this->kohana_local_data) OR array_key_exists($property, self::$kohana_global_data)) ? TRUE : FALSE;
+			}
+		}
+		else
+		{
+			// Otherwise just check one property
+			$result = (array_key_exists($key, $this->kohana_local_data) OR array_key_exists($key, self::$kohana_global_data)) ? TRUE : FALSE;
+		}
+
+		// Return the result
+		return $result;
+	}
+
+	/**
+	 * Sets a bound variable by reference.
+	 *
+	 * @param   string   name of variable
+	 * @param   mixed    variable to assign by reference
+	 * @return  object
+	 */
+	public function bind($name, & $var)
+	{
+		$this->kohana_local_data[$name] =& $var;
+
+		return $this;
+	}
+
+	/**
+	 * Sets a view global variable.
+	 *
+	 * @param   string|array  name of variable or an array of variables
+	 * @param   mixed         value when using a named variable
+	 * @return  object
+	 */
+	public function set_global($name, $value = NULL)
+	{
+		if (is_array($name))
+		{
+			foreach ($name as $key => $value)
+			{
+				self::$kohana_global_data[$key] = $value;
+			}
+		}
+		else
+		{
+			self::$kohana_global_data[$name] = $value;
+		}
+
+		return $this;
+	}
+
+	/**
+	 * Magically sets a view variable.
+	 *
+	 * @param   string   variable key
+	 * @param   string   variable value
+	 * @return  void
+	 */
+	public function __set($key, $value)
+	{
+		$this->kohana_local_data[$key] = $value;
+	}
+
+	/**
+	 * Magically gets a view variable.
+	 *
+	 * @param  string  variable key
+	 * @return mixed   variable value if the key is found
+	 * @return void    if the key is not found
+	 */
+	public function &__get($key)
+	{
+		if (isset($this->kohana_local_data[$key]))
+			return $this->kohana_local_data[$key];
+
+		if (isset(self::$kohana_global_data[$key]))
+			return self::$kohana_global_data[$key];
+
+		if (isset($this->$key))
+			return $this->$key;
+	}
+
+	/**
+	 * Magically converts view object to string.
+	 *
+	 * @return  string
+	 */
+	public function __toString()
+	{
+		return $this->render();
+	}
+
+	/**
+	 * Renders a view.
+	 *
+	 * @param   boolean   set to TRUE to echo the output instead of returning it
+	 * @param   callback  special renderer to pass the output through
+	 * @return  string    if print is FALSE
+	 * @return  void      if print is TRUE
+	 */
+	public function render($print = FALSE, $renderer = FALSE)
+	{
+		if (empty($this->kohana_filename))
+			throw new Kohana_Exception('core.view_set_filename');
+
+		if (is_string($this->kohana_filetype))
+		{
+			// Merge global and local data, local overrides global with the same name
+			$data = array_merge(self::$kohana_global_data, $this->kohana_local_data);
+
+			// Load the view in the controller for access to $this
+			$output = Kohana::$instance->_kohana_load_view($this->kohana_filename, $data);
+
+			if ($renderer !== FALSE AND is_callable($renderer, TRUE))
+			{
+				// Pass the output through the user defined renderer
+				$output = call_user_func($renderer, $output);
+			}
+
+			if ($print === TRUE)
+			{
+				// Display the output
+				echo $output;
+				return;
+			}
+		}
+		else
+		{
+			// Set the content type and size
+			header('Content-Type: '.$this->kohana_filetype[0]);
+
+			if ($print === TRUE)
+			{
+				if ($file = fopen($this->kohana_filename, 'rb'))
+				{
+					// Display the output
+					fpassthru($file);
+					fclose($file);
+				}
+				return;
+			}
+
+			// Fetch the file contents
+			$output = file_get_contents($this->kohana_filename);
+		}
+
+		return $output;
+	}
+} // End View
\ No newline at end of file
diff --git a/system/libraries/drivers/Cache.php b/system/libraries/drivers/Cache.php
new file mode 100755
index 0000000..62dcb4c
--- /dev/null
+++ b/system/libraries/drivers/Cache.php
@@ -0,0 +1,40 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Cache driver interface.
+ *
+ * $Id: Cache.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+interface Cache_Driver {
+
+	/**
+	 * Set a cache item.
+	 */
+	public function set($id, $data, $tags, $lifetime);
+
+	/**
+	 * Find all of the cache ids for a given tag.
+	 */
+	public function find($tag);
+
+	/**
+	 * Get a cache item.
+	 * Return NULL if the cache item is not found.
+	 */
+	public function get($id);
+
+	/**
+	 * Delete cache items by id or tag.
+	 */
+	public function delete($id, $tag = FALSE);
+
+	/**
+	 * Deletes all expired cache items.
+	 */
+	public function delete_expired();
+
+} // End Cache Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Apc.php b/system/libraries/drivers/Cache/Apc.php
new file mode 100755
index 0000000..19077d9
--- /dev/null
+++ b/system/libraries/drivers/Cache/Apc.php
@@ -0,0 +1,53 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * APC-based Cache driver.
+ *
+ * $Id: Apc.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Apc_Driver implements Cache_Driver {
+
+	public function __construct()
+	{
+		if ( ! extension_loaded('apc'))
+			throw new Kohana_Exception('cache.extension_not_loaded', 'apc');
+	}
+
+	public function get($id)
+	{
+		return (($return = apc_fetch($id)) === FALSE) ? NULL : $return;
+	}
+
+	public function set($id, $data, $tags, $lifetime)
+	{
+		count($tags) and Kohana::log('alert', 'Cache: tags are unsupported by the APC driver');
+
+		return apc_store($id, $data, $lifetime);
+	}
+
+	public function find($tag)
+	{
+		return FALSE;
+	}
+
+	public function delete($id, $tag = FALSE)
+	{
+		if ($id === TRUE)
+			return apc_clear_cache('user');
+
+		if ($tag == FALSE)
+			return apc_delete($id);
+
+		return TRUE;
+	}
+
+	public function delete_expired()
+	{
+		return TRUE;
+	}
+
+} // End Cache APC Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Eaccelerator.php b/system/libraries/drivers/Cache/Eaccelerator.php
new file mode 100755
index 0000000..2547fff
--- /dev/null
+++ b/system/libraries/drivers/Cache/Eaccelerator.php
@@ -0,0 +1,53 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Eaccelerator-based Cache driver.
+ *
+ * $Id: Eaccelerator.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Eaccelerator_Driver implements Cache_Driver {
+
+	public function __construct()
+	{
+		if ( ! extension_loaded('eaccelerator'))
+			throw new Kohana_Exception('cache.extension_not_loaded', 'eaccelerator');
+	}
+
+	public function get($id)
+	{
+		return eaccelerator_get($id);
+	}
+
+	public function find($tag)
+	{
+		return FALSE;
+	}
+
+	public function set($id, $data, $tags, $lifetime)
+	{
+		count($tags) and Kohana::log('alert', 'tags are unsupported by the eAccelerator driver');
+
+		return eaccelerator_put($id, $data, $lifetime);
+	}
+
+	public function delete($id, $tag = FALSE)
+	{
+		if ($id === TRUE)
+			return eaccelerator_clean();
+
+		if ($tag == FALSE)
+			return eaccelerator_rm($id);
+
+		return TRUE;
+	}
+
+	public function delete_expired()
+	{
+		eaccelerator_gc();
+	}
+
+} // End Cache eAccelerator Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Cache/File.php b/system/libraries/drivers/Cache/File.php
new file mode 100755
index 0000000..b7a0646
--- /dev/null
+++ b/system/libraries/drivers/Cache/File.php
@@ -0,0 +1,245 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * File-based Cache driver.
+ *
+ * $Id: File.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_File_Driver implements Cache_Driver {
+
+	protected $directory = '';
+
+	/**
+	 * Tests that the storage location is a directory and is writable.
+	 */
+	public function __construct($directory)
+	{
+		// Find the real path to the directory
+		$directory = str_replace('\\', '/', realpath($directory)).'/';
+
+		// Make sure the cache directory is writable
+		if ( ! is_dir($directory) OR ! is_writable($directory))
+			throw new Kohana_Exception('cache.unwritable', $directory);
+
+		// Directory is valid
+		$this->directory = $directory;
+	}
+
+	/**
+	 * Finds an array of files matching the given id or tag.
+	 *
+	 * @param  string  cache id or tag
+	 * @param  bool    search for tags
+	 * @return array   of filenames matching the id or tag
+	 * @return void    if no matching files are found
+	 */
+	public function exists($id, $tag = FALSE)
+	{
+		if ($id === TRUE)
+		{
+			// Find all the files
+			$files = glob($this->directory.'*~*~*');
+		}
+		elseif ($tag == TRUE)
+		{
+			// Find all the files that have the tag name
+			$files = glob($this->directory.'*~*'.$id.'*~*');
+
+			// Find all tags matching the given tag
+			foreach ($files as $i => $file)
+			{
+				// Split the files
+				$tags = explode('~', $file);
+
+				// Find valid tags
+				if (count($tags) !== 3 OR empty($tags[1]))
+					continue;
+
+				// Split the tags by plus signs, used to separate tags
+				$tags = explode('+', $tags[1]);
+
+				if ( ! in_array($tag, $tags))
+				{
+					// This entry does not match the tag
+					unset($files[$i]);
+				}
+			}
+		}
+		else
+		{
+			// Find all the files matching the given id
+			$files = glob($this->directory.$id.'~*');
+		}
+
+		return empty($files) ? NULL : $files;
+	}
+
+	/**
+	 * Sets a cache item to the given data, tags, and lifetime.
+	 *
+	 * @param   string   cache id to set
+	 * @param   string   data in the cache
+	 * @param   array    cache tags
+	 * @param   integer  lifetime
+	 * @return  bool
+	 */
+	public function set($id, $data, $tags, $lifetime)
+	{
+		// Remove old cache files
+		$this->delete($id);
+
+		// Cache File driver expects unix timestamp
+		if ($lifetime !== 0)
+		{
+			$lifetime += time();
+		}
+
+		// Construct the filename
+		$filename = $id.'~'.implode('+', $tags).'~'.$lifetime;
+
+		// Write the file, appending the sha1 signature to the beginning of the data
+		return (bool) file_put_contents($this->directory.$filename, sha1($data).$data);
+	}
+
+	/**
+	 * Finds an array of ids for a given tag.
+	 *
+	 * @param  string  tag name
+	 * @return array   of ids that match the tag
+	 */
+	public function find($tag)
+	{
+		if ($files = $this->exists($tag, TRUE))
+		{
+			// Length of directory name
+			$offset = strlen($this->directory);
+
+			// Find all the files with the given tag
+			$array = array();
+			foreach ($files as $file)
+			{
+				// Get the id from the filename
+				$array[] = substr(current(explode('~', $file)), $offset);
+			}
+
+			return $array;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Fetches a cache item. This will delete the item if it is expired or if
+	 * the hash does not match the stored hash.
+	 *
+	 * @param   string  cache id
+	 * @return  mixed|NULL
+	 */
+	public function get($id)
+	{
+		if ($file = $this->exists($id))
+		{
+			// Always process the first result
+			$file = current($file);
+
+			// Validate that the cache has not expired
+			if ($this->expired($file))
+			{
+				// Remove this cache, it has expired
+				$this->delete($id);
+			}
+			else
+			{
+				$data = file_get_contents($file);
+
+				// Find the hash of the data
+				$hash = substr($data, 0, 40);
+
+				// Remove the hash from the data
+				$data = substr($data, 40);
+
+				if ($hash !== sha1($data))
+				{
+					// Remove this cache, it doesn't validate
+					$this->delete($id);
+
+					// Unset data to prevent it from being returned
+					unset($data);
+				}
+			}
+		}
+
+		// Return NULL if there is no data
+		return isset($data) ? $data : NULL;
+	}
+
+	/**
+	 * Deletes a cache item by id or tag
+	 *
+	 * @param   string   cache id or tag, or TRUE for "all items"
+	 * @param   boolean  use tags
+	 * @return  boolean
+	 */
+	public function delete($id, $tag = FALSE)
+	{
+		$files = $this->exists($id, $tag);
+
+		if (empty($files))
+			return FALSE;
+
+		// Disable all error reporting while deleting
+		$ER = error_reporting(0);
+
+		foreach ($files as $file)
+		{
+			// Remove the cache file
+			if ( ! unlink($file))
+				Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
+		}
+
+		// Turn on error reporting again
+		error_reporting($ER);
+
+		return TRUE;
+	}
+
+	/**
+	 * Deletes all cache files that are older than the current time.
+	 *
+	 * @return void
+	 */
+	public function delete_expired()
+	{
+		if ($files = $this->exists(TRUE))
+		{
+			foreach ($files as $file)
+			{
+				if ($this->expired($file))
+				{
+					// The cache file has already expired, delete it
+					@unlink($file) or Kohana::log('error', 'Cache: Unable to delete cache file: '.$file);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Check if a cache file has expired by filename.
+	 *
+	 * @param  string  filename
+	 * @return bool
+	 */
+	protected function expired($file)
+	{
+		// Get the expiration time
+		$expires = (int) substr($file, strrpos($file, '~') + 1);
+
+		// Expirations of 0 are "never expire"
+		return ($expires !== 0 AND $expires <= time());
+	}
+
+} // End Cache File Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Memcache.php b/system/libraries/drivers/Cache/Memcache.php
new file mode 100755
index 0000000..8b6ecd4
--- /dev/null
+++ b/system/libraries/drivers/Cache/Memcache.php
@@ -0,0 +1,78 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Memcache-based Cache driver.
+ *
+ * $Id: Memcache.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Memcache_Driver implements Cache_Driver {
+
+	// Cache backend object and flags
+	protected $backend;
+	protected $flags;
+
+	public function __construct()
+	{
+		if ( ! extension_loaded('memcache'))
+			throw new Kohana_Exception('cache.extension_not_loaded', 'memcache');
+
+		$this->backend = new Memcache;
+		$this->flags = Kohana::config('cache_memcache.compression') ? MEMCACHE_COMPRESSED : 0;
+
+		$servers = Kohana::config('cache_memcache.servers');
+
+		foreach ($servers as $server)
+		{
+			// Make sure all required keys are set
+			$server += array('host' => '127.0.0.1', 'port' => 11211, 'persistent' => FALSE);
+
+			// Add the server to the pool
+			$this->backend->addServer($server['host'], $server['port'], (bool) $server['persistent'])
+				or Kohana::log('error', 'Cache: Connection failed: '.$server['host']);
+		}
+	}
+
+	public function find($tag)
+	{
+		return FALSE;
+	}
+
+	public function get($id)
+	{
+		return (($return = $this->backend->get($id)) === FALSE) ? NULL : $return;
+	}
+
+	public function set($id, $data, $tags, $lifetime)
+	{
+		count($tags) and Kohana::log('alert', 'Cache: Tags are unsupported by the memcache driver');
+
+		// Memcache driver expects unix timestamp
+		if ($lifetime !== 0)
+		{
+			$lifetime += time();
+		}
+
+		return $this->backend->set($id, $data, $this->flags, $lifetime);
+	}
+
+	public function delete($id, $tag = FALSE)
+	{
+		if ($id === TRUE)
+			return $this->backend->flush();
+
+		if ($tag == FALSE)
+			return $this->backend->delete($id);
+
+		return TRUE;
+	}
+
+	public function delete_expired()
+	{
+		return TRUE;
+	}
+
+} // End Cache Memcache Driver
diff --git a/system/libraries/drivers/Cache/Sqlite.php b/system/libraries/drivers/Cache/Sqlite.php
new file mode 100755
index 0000000..81650fa
--- /dev/null
+++ b/system/libraries/drivers/Cache/Sqlite.php
@@ -0,0 +1,228 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * SQLite-based Cache driver.
+ *
+ * $Id: Sqlite.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Sqlite_Driver implements Cache_Driver {
+
+	// SQLite database instance
+	protected $db;
+
+	// Database error messages
+	protected $error;
+
+	/**
+	 * Logs an SQLite error.
+	 */
+	protected static function log_error($code)
+	{
+		// Log an error
+		Kohana::log('alert', 'Cache: SQLite error: '.sqlite_error_string($error));
+	}
+
+	/**
+	 * Tests that the storage location is a directory and is writable.
+	 */
+	public function __construct($filename)
+	{
+		// Get the directory name
+		$directory = str_replace('\\', '/', realpath(pathinfo($filename, PATHINFO_DIRNAME))).'/';
+
+		// Set the filename from the real directory path
+		$filename = $directory.basename($filename);
+
+		// Make sure the cache directory is writable
+		if ( ! is_dir($directory) OR ! is_writable($directory))
+			throw new Kohana_Exception('cache.unwritable', $directory);
+
+		// Make sure the cache database is writable
+		if (is_file($filename) AND ! is_writable($filename))
+			throw new Kohana_Exception('cache.unwritable', $filename);
+
+		// Open up an instance of the database
+		$this->db = new SQLiteDatabase($filename, '0666', $error);
+
+		// Throw an exception if there's an error
+		if ( ! empty($error))
+			throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error));
+
+		$query  = "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'caches'";
+		$tables = $this->db->query($query, SQLITE_BOTH, $error);
+
+		// Throw an exception if there's an error
+		if ( ! empty($error))
+			throw new Kohana_Exception('cache.driver_error', sqlite_error_string($error));
+
+		if ($tables->numRows() == 0)
+		{
+			Kohana::log('info', 'Cache: Initializing new SQLite cache database');
+
+			// Issue a CREATE TABLE command
+			$this->db->unbufferedQuery(Kohana::config('cache_sqlite.schema'));
+		}
+	}
+
+	/**
+	 * Checks if a cache id is already set.
+	 *
+	 * @param  string   cache id
+	 * @return boolean
+	 */
+	public function exists($id)
+	{
+		// Find the id that matches
+		$query = "SELECT id FROM caches WHERE id = '$id'";
+
+		return ($this->db->query($query)->numRows() > 0);
+	}
+
+	/**
+	 * Sets a cache item to the given data, tags, and lifetime.
+	 *
+	 * @param   string   cache id to set
+	 * @param   string   data in the cache
+	 * @param   array    cache tags
+	 * @param   integer  lifetime
+	 * @return  bool
+	 */
+	public function set($id, $data, $tags, $lifetime)
+	{
+		// Find the data hash
+		$hash = sha1($data);
+
+		// Escape the data
+		$data = sqlite_escape_string($data);
+
+		// Escape the tags
+		$tags = sqlite_escape_string(implode(',', $tags));
+
+		// Cache Sqlite driver expects unix timestamp
+		if ($lifetime !== 0)
+		{
+			$lifetime += time();
+		}
+
+		$query = $this->exists($id)
+			? "UPDATE caches SET hash = '$hash', tags = '$tags', expiration = '$lifetime', cache = '$data' WHERE id = '$id'"
+			: "INSERT INTO caches VALUES('$id', '$hash', '$tags', '$lifetime', '$data')";
+
+		// Run the query
+		$this->db->unbufferedQuery($query, SQLITE_BOTH, $error);
+
+		empty($error) or self::log_error($error);
+
+		return empty($error);
+	}
+
+	/**
+	 * Finds an array of ids for a given tag.
+	 *
+	 * @param  string  tag name
+	 * @return array   of ids that match the tag
+	 */
+	public function find($tag)
+	{
+		$query = "SELECT id FROM caches WHERE tags LIKE '%{$tag}%'";
+		$query = $this->db->query($query, SQLITE_BOTH, $error);
+
+		empty($error) or self::log_error($error);
+
+		if (empty($error) AND $query->numRows() > 0)
+		{
+			$array = array();
+			while ($row = $query->fetchObject())
+			{
+				// Add each id to the array
+				$array[] = $row->id;
+			}
+			return $array;
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * Fetches a cache item. This will delete the item if it is expired or if
+	 * the hash does not match the stored hash.
+	 *
+	 * @param  string  cache id
+	 * @return mixed|NULL
+	 */
+	public function get($id)
+	{
+		$query = "SELECT id, hash, expiration, cache FROM caches WHERE id = '{$id}' LIMIT 0, 1";
+		$query = $this->db->query($query, SQLITE_BOTH, $error);
+
+		empty($error) or self::log_error($error);
+
+		if (empty($error) AND $cache = $query->fetchObject())
+		{
+			// Make sure the expiration is valid and that the hash matches
+			if (($cache->expiration != 0 AND $cache->expiration <= time()) OR $cache->hash !== sha1($cache->cache))
+			{
+				// Cache is not valid, delete it now
+				$this->delete($cache->id);
+			}
+			else
+			{
+				// Return the valid cache data
+				return $cache->cache;
+			}
+		}
+
+		// No valid cache found
+		return NULL;
+	}
+
+	/**
+	 * Deletes a cache item by id or tag
+	 *
+	 * @param  string  cache id or tag, or TRUE for "all items"
+	 * @param  bool    use tags
+	 * @return bool
+	 */
+	public function delete($id, $tag = FALSE)
+	{
+		if ($id === TRUE)
+		{
+			// Delete all caches
+			$where = '1';
+		}
+		elseif ($tag == FALSE)
+		{
+			// Delete by id
+			$where = "id = '{$id}'";
+		}
+		else
+		{
+			// Delete by tag
+			$where = "tags LIKE '%{$tag}%'";
+		}
+
+		$this->db->unbufferedQuery('DELETE FROM caches WHERE '.$where, SQLITE_BOTH, $error);
+
+		empty($error) or self::log_error($error);
+
+		return empty($error);
+	}
+
+	/**
+	 * Deletes all cache files that are older than the current time.
+	 */
+	public function delete_expired()
+	{
+		// Delete all expired caches
+		$query = 'DELETE FROM caches WHERE expiration != 0 AND expiration <= '.time();
+
+		$this->db->unbufferedQuery($query);
+
+		return TRUE;
+	}
+
+} // End Cache SQLite Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Cache/Xcache.php b/system/libraries/drivers/Cache/Xcache.php
new file mode 100755
index 0000000..b787d0b
--- /dev/null
+++ b/system/libraries/drivers/Cache/Xcache.php
@@ -0,0 +1,116 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Xcache Cache driver.
+ *
+ * $Id: Xcache.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Cache
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Cache_Xcache_Driver implements Cache_Driver {
+
+	public function __construct()
+	{
+		if ( ! extension_loaded('xcache'))
+			throw new Kohana_Exception('cache.extension_not_loaded', 'xcache');
+	}
+
+	public function get($id)
+	{
+		if (xcache_isset($id))
+			return xcache_get($id);
+
+		return NULL;
+	}
+
+	public function set($id, $data, $tags, $lifetime)
+	{
+		count($tags) and Kohana::log('alert', 'Cache: tags are unsupported by the Xcache driver');
+
+		return xcache_set($id, $data, $lifetime);
+	}
+
+	public function find($tag)
+	{
+		Kohana::log('alert', 'Cache: tags are unsupported by the Xcache driver');
+		return FALSE;
+	}
+
+	public function delete($id, $tag = FALSE)
+	{
+		if ($tag !== FALSE)
+		{
+			Kohana::log('alert', 'Cache: tags are unsupported by the Xcache driver');
+			return TRUE;
+		}
+		elseif ($id !== TRUE)
+		{
+			if (xcache_isset($id))
+				return xcache_unset($id);
+
+			return FALSE;
+		}
+		else
+		{
+			// Do the login
+			$this->auth();
+			$result = TRUE;
+			for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++)
+			{
+				if (xcache_clear_cache(XC_TYPE_VAR, $i) !== NULL)
+				{
+					$result = FALSE;
+					break;
+				}
+			}
+
+			// Undo the login
+			$this->auth(TRUE);
+			return $result;
+		}
+
+		return TRUE;
+	}
+
+	public function delete_expired()
+	{
+		return TRUE;
+	}
+
+	private function auth($reverse = FALSE)
+	{
+		static $backup = array();
+
+		$keys = array('PHP_AUTH_USER', 'PHP_AUTH_PW');
+
+		foreach ($keys as $key)
+		{
+			if ($reverse)
+			{
+				if (isset($backup[$key]))
+				{
+					$_SERVER[$key] = $backup[$key];
+					unset($backup[$key]);
+				}
+				else
+				{
+					unset($_SERVER[$key]);
+				}
+			}
+			else
+			{
+				$value = getenv($key);
+
+				if ( ! empty($value))
+				{
+					$backup[$key] = $value;
+				}
+
+				$_SERVER[$key] = Kohana::config('cache_xcache.'.$key);
+			}
+		}
+	}
+
+} // End Cache Xcache Driver
diff --git a/system/libraries/drivers/Captcha.php b/system/libraries/drivers/Captcha.php
new file mode 100755
index 0000000..a719e92
--- /dev/null
+++ b/system/libraries/drivers/Captcha.php
@@ -0,0 +1,228 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver class.
+ *
+ * $Id: Captcha.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Captcha_Driver {
+
+	// The correct Captcha challenge answer
+	protected $response;
+
+	// Image resource identifier and type ("png", "gif" or "jpeg")
+	protected $image;
+	protected $image_type = 'png';
+
+	/**
+	 * Constructs a new challenge.
+	 *
+	 * @return  void
+	 */
+	public function __construct()
+	{
+		// Generate a new challenge
+		$this->response = $this->generate_challenge();
+
+		// Store the correct Captcha response in a session
+		Event::add('system.post_controller', array($this, 'update_response_session'));
+	}
+
+	/**
+	 * Generate a new Captcha challenge.
+	 *
+	 * @return  string  the challenge answer
+	 */
+	abstract public function generate_challenge();
+
+	/**
+	 * Output the Captcha challenge.
+	 *
+	 * @param   boolean  html output
+	 * @return  mixed    the rendered Captcha (e.g. an image, riddle, etc.)
+	 */
+	abstract public function render($html);
+
+	/**
+	 * Stores the response for the current Captcha challenge in a session so it is available
+	 * on the next page load for Captcha::valid(). This method is called after controller
+	 * execution (in the system.post_controller event) in order not to overwrite itself too soon.
+	 *
+	 * @return  void
+	 */
+	public function update_response_session()
+	{
+		Kohana::log('info'," Captcha--> updateresponse= " . strtoupper($this->response));
+		Session::instance()->set('captcha_response', sha1(strtoupper($this->response)));
+	}
+
+	/**
+	 * Validates a Captcha response from a user.
+	 *
+	 * @param   string   captcha response
+	 * @return  boolean
+	 */
+	public function valid($response)
+	{
+		return (sha1(strtoupper($response)) === Session::instance()->get('captcha_response'));
+	}
+
+	/**
+	 * Returns the image type.
+	 *
+	 * @param   string        filename
+	 * @return  string|FALSE  image type ("png", "gif" or "jpeg")
+	 */
+	public function image_type($filename)
+	{
+		switch (strtolower(substr(strrchr($filename, '.'), 1)))
+		{
+			case 'png':
+				return 'png';
+
+			case 'gif':
+				return 'gif';
+
+			case 'jpg':
+			case 'jpeg':
+				// Return "jpeg" and not "jpg" because of the GD2 function names
+				return 'jpeg';
+
+			default:
+				return FALSE;
+		}
+	}
+
+	/**
+	 * Creates an image resource with the dimensions specified in config.
+	 * If a background image is supplied, the image dimensions are used.
+	 *
+	 * @throws  Kohana_Exception  if no GD2 support
+	 * @param   string  path to the background image file
+	 * @return  void
+	 */
+	public function image_create($background = NULL)
+	{
+		// Check for GD2 support
+		if ( ! function_exists('imagegd2'))
+			throw new Kohana_Exception('captcha.requires_GD2');
+
+		// Create a new image (black)
+		$this->image = imagecreatetruecolor(Captcha::$config['width'], Captcha::$config['height']);
+
+		// Use a background image
+		if ( ! empty($background))
+		{
+			// Create the image using the right function for the filetype
+			$function = 'imagecreatefrom'.$this->image_type($background);
+			$this->background_image = $function($background);
+
+			// Resize the image if needed
+			if (imagesx($this->background_image) !== Captcha::$config['width']
+			    OR imagesy($this->background_image) !== Captcha::$config['height'])
+			{
+				imagecopyresampled
+				(
+					$this->image, $this->background_image, 0, 0, 0, 0,
+					Captcha::$config['width'], Captcha::$config['height'],
+					imagesx($this->background_image), imagesy($this->background_image)
+				);
+			}
+
+			// Free up resources
+			imagedestroy($this->background_image);
+		}
+	}
+
+	/**
+	 * Fills the background with a gradient.
+	 *
+	 * @param   resource  gd image color identifier for start color
+	 * @param   resource  gd image color identifier for end color
+	 * @param   string    direction: 'horizontal' or 'vertical', 'random' by default
+	 * @return  void
+	 */
+	public function image_gradient($color1, $color2, $direction = NULL)
+	{
+		$directions = array('horizontal', 'vertical');
+
+		// Pick a random direction if needed
+		if ( ! in_array($direction, $directions))
+		{
+			$direction = $directions[array_rand($directions)];
+
+			// Switch colors
+			if (mt_rand(0, 1) === 1)
+			{
+				$temp = $color1;
+				$color1 = $color2;
+				$color2 = $temp;
+			}
+		}
+
+		// Extract RGB values
+		$color1 = imagecolorsforindex($this->image, $color1);
+		$color2 = imagecolorsforindex($this->image, $color2);
+
+		// Preparations for the gradient loop
+		$steps = ($direction === 'horizontal') ? Captcha::$config['width'] : Captcha::$config['height'];
+
+		$r1 = ($color1['red'] - $color2['red']) / $steps;
+		$g1 = ($color1['green'] - $color2['green']) / $steps;
+		$b1 = ($color1['blue'] - $color2['blue']) / $steps;
+
+		if ($direction === 'horizontal')
+		{
+			$x1 =& $i;
+			$y1 = 0;
+			$x2 =& $i;
+			$y2 = Captcha::$config['height'];
+		}
+		else
+		{
+			$x1 = 0;
+			$y1 =& $i;
+			$x2 = Captcha::$config['width'];
+			$y2 =& $i;
+		}
+
+		// Execute the gradient loop
+		for ($i = 0; $i <= $steps; $i++)
+		{
+			$r2 = $color1['red'] - floor($i * $r1);
+			$g2 = $color1['green'] - floor($i * $g1);
+			$b2 = $color1['blue'] - floor($i * $b1);
+			$color = imagecolorallocate($this->image, $r2, $g2, $b2);
+
+			imageline($this->image, $x1, $y1, $x2, $y2, $color);
+		}
+	}
+
+	/**
+	 * Returns the img html element or outputs the image to the browser.
+	 *
+	 * @param   boolean  html output
+	 * @return  mixed    html string or void
+	 */
+	public function image_render($html)
+	{
+		// Output html element
+		if ($html)
+			return '<img alt="Captcha" src="'.url::site('captcha/'.Captcha::$config['group']).'/'.rand(10000,99999).'" width="'.Captcha::$config['width'].'" height="'.Captcha::$config['height'].'" />';
+
+		// Send the correct HTTP header
+		header('Content-Type: image/'.$this->image_type);
+
+		// Pick the correct output function
+		$function = 'image'.$this->image_type;
+		$function($this->image);
+
+		// Free up resources
+		imagedestroy($this->image);
+	}
+
+} // End Captcha Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Alpha.php b/system/libraries/drivers/Captcha/Alpha.php
new file mode 100755
index 0000000..a1b4384
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Alpha.php
@@ -0,0 +1,92 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "alpha" style.
+ *
+ * $Id: Alpha.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Alpha_Driver extends Captcha_Driver {
+
+	/**
+	 * Generates a new Captcha challenge.
+	 *
+	 * @return  string  the challenge answer
+	 */
+	public function generate_challenge()
+	{
+		// Complexity setting is used as character count
+		return text::random('distinct', max(1, Captcha::$config['complexity']));
+	}
+
+	/**
+	 * Outputs the Captcha image.
+	 *
+	 * @param   boolean  html output
+	 * @return  mixed
+	 */
+	public function render($html)
+	{
+		// Creates $this->image
+		$this->image_create(Captcha::$config['background']);
+
+		// Add a random gradient
+		if (empty(Captcha::$config['background']))
+		{
+			$color1 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
+			$color2 = imagecolorallocate($this->image, mt_rand(0, 100), mt_rand(0, 100), mt_rand(0, 100));
+			$this->image_gradient($color1, $color2);
+		}
+
+		// Add a few random circles
+		for ($i = 0, $count = mt_rand(10, Captcha::$config['complexity'] * 3); $i < $count; $i++)
+		{
+			$color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(80, 120));
+			$size = mt_rand(5, Captcha::$config['height'] / 3);
+			imagefilledellipse($this->image, mt_rand(0, Captcha::$config['width']), mt_rand(0, Captcha::$config['height']), $size, $size, $color);
+		}
+
+		// Calculate character font-size and spacing
+		$default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / strlen($this->response);
+		$spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
+
+		// Background alphabetic character attributes
+		$color_limit = mt_rand(96, 160);
+		$chars = 'ABEFGJKLPQRTVY';
+
+		// Draw each Captcha character with varying attributes
+		for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
+		{
+			// Use different fonts if available
+			$font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
+
+			$angle = mt_rand(-40, 20);
+			// Scale the character size on image height
+			$size = $default_size / 10 * mt_rand(8, 12);
+			$box = imageftbbox($size, $angle, $font, $this->response[$i]);
+
+			// Calculate character starting coordinates
+			$x = $spacing / 4 + $i * $spacing;
+			$y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
+
+			// Draw captcha text character
+			// Allocate random color, size and rotation attributes to text
+			$color = imagecolorallocate($this->image, mt_rand(150, 255), mt_rand(200, 255), mt_rand(0, 255));
+
+			// Write text character to image
+			imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
+
+			// Draw "ghost" alphabetic character
+			$text_color = imagecolorallocatealpha($this->image, mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand($color_limit + 8, 255), mt_rand(70, 120));
+			$char = substr($chars, mt_rand(0, 14), 1);
+			imagettftext($this->image, $size * 2, mt_rand(-45, 45), ($x - (mt_rand(5, 10))), ($y + (mt_rand(5, 10))), $text_color, $font, $char);
+		}
+
+		// Output
+		return $this->image_render($html);
+	}
+
+} // End Captcha Alpha Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Basic.php b/system/libraries/drivers/Captcha/Basic.php
new file mode 100755
index 0000000..d8a0c68
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Basic.php
@@ -0,0 +1,81 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "basic" style.
+ *
+ * $Id: Basic.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Basic_Driver extends Captcha_Driver {
+
+	/**
+	 * Generates a new Captcha challenge.
+	 *
+	 * @return  string  the challenge answer
+	 */
+	public function generate_challenge()
+	{
+		// Complexity setting is used as character count
+		return text::random('distinct', max(1, Captcha::$config['complexity']));
+	}
+
+	/**
+	 * Outputs the Captcha image.
+	 *
+	 * @param   boolean  html output
+	 * @return  mixed
+	 */
+	public function render($html)
+	{
+		// Creates $this->image
+		$this->image_create(Captcha::$config['background']);
+
+		// Add a random gradient
+		if (empty(Captcha::$config['background']))
+		{
+			$color1 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255));
+			$color2 = imagecolorallocate($this->image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(150, 255));
+			$this->image_gradient($color1, $color2);
+		}
+
+		// Add a few random lines
+		for ($i = 0, $count = mt_rand(5, Captcha::$config['complexity'] * 4); $i < $count; $i++)
+		{
+			$color = imagecolorallocatealpha($this->image, mt_rand(0, 255), mt_rand(0, 255), mt_rand(100, 255), mt_rand(50, 120));
+			imageline($this->image, mt_rand(0, Captcha::$config['width']), 0, mt_rand(0, Captcha::$config['width']), Captcha::$config['height'], $color);
+		}
+
+		// Calculate character font-size and spacing
+		$default_size = min(Captcha::$config['width'], Captcha::$config['height'] * 2) / (strlen($this->response) + 1);
+		$spacing = (int) (Captcha::$config['width'] * 0.9 / strlen($this->response));
+
+		// Draw each Captcha character with varying attributes
+		for ($i = 0, $strlen = strlen($this->response); $i < $strlen; $i++)
+		{
+			// Use different fonts if available
+			$font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
+
+			// Allocate random color, size and rotation attributes to text
+			$color = imagecolorallocate($this->image, mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150));
+			$angle = mt_rand(-40, 20);
+
+			// Scale the character size on image height
+			$size = $default_size / 10 * mt_rand(8, 12);
+			$box = imageftbbox($size, $angle, $font, $this->response[$i]);
+
+			// Calculate character starting coordinates
+			$x = $spacing / 4 + $i * $spacing;
+			$y = Captcha::$config['height'] / 2 + ($box[2] - $box[5]) / 4;
+
+			// Write text character to image
+			imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response[$i]);
+		}
+
+		// Output
+		return $this->image_render($html);
+	}
+
+} // End Captcha Basic Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Black.php b/system/libraries/drivers/Captcha/Black.php
new file mode 100755
index 0000000..532c22d
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Black.php
@@ -0,0 +1,72 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "black" style.
+ *
+ * $Id: Black.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Black_Driver extends Captcha_Driver {
+
+	/**
+	 * Generates a new Captcha challenge.
+	 *
+	 * @return  string  the challenge answer
+	 */
+	public function generate_challenge()
+	{
+		// Complexity setting is used as character count
+		return text::random('distinct', max(1, ceil(Captcha::$config['complexity'] / 1.5)));
+	}
+
+	/**
+	 * Outputs the Captcha image.
+	 *
+	 * @param   boolean  html output
+	 * @return  mixed
+	 */
+	public function render($html)
+	{
+		// Creates a black image to start from
+		$this->image_create(Captcha::$config['background']);
+
+		// Add random white/gray arcs, amount depends on complexity setting
+		$count = (Captcha::$config['width'] + Captcha::$config['height']) / 2;
+		$count = $count / 5 * min(10, Captcha::$config['complexity']);
+		for ($i = 0; $i < $count; $i++)
+		{
+			imagesetthickness($this->image, mt_rand(1, 2));
+			$color = imagecolorallocatealpha($this->image, 255, 255, 255, mt_rand(0, 120));
+			imagearc($this->image, mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(-Captcha::$config['width'], Captcha::$config['width']), mt_rand(-Captcha::$config['height'], Captcha::$config['height']), mt_rand(0, 360), mt_rand(0, 360), $color);
+		}
+
+		// Use different fonts if available
+		$font = Captcha::$config['fontpath'].Captcha::$config['fonts'][array_rand(Captcha::$config['fonts'])];
+
+		// Draw the character's white shadows
+		$size = (int) min(Captcha::$config['height'] / 2, Captcha::$config['width'] * 0.8 / strlen($this->response));
+		$angle = mt_rand(-15 + strlen($this->response), 15 - strlen($this->response));
+		$x = mt_rand(1, Captcha::$config['width'] * 0.9 - $size * strlen($this->response));
+		$y = ((Captcha::$config['height'] - $size) / 2) + $size;
+		$color = imagecolorallocate($this->image, 255, 255, 255);
+		imagefttext($this->image, $size, $angle, $x + 1, $y + 1, $color, $font, $this->response);
+
+		// Add more shadows for lower complexities
+		(Captcha::$config['complexity'] < 10) and imagefttext($this->image, $size, $angle, $x - 1, $y - 1, $color, $font , $this->response);
+		(Captcha::$config['complexity'] < 8)  and imagefttext($this->image, $size, $angle, $x - 2, $y + 2, $color, $font , $this->response);
+		(Captcha::$config['complexity'] < 6)  and imagefttext($this->image, $size, $angle, $x + 2, $y - 2, $color, $font , $this->response);
+		(Captcha::$config['complexity'] < 4)  and imagefttext($this->image, $size, $angle, $x + 3, $y + 3, $color, $font , $this->response);
+		(Captcha::$config['complexity'] < 2)  and imagefttext($this->image, $size, $angle, $x - 3, $y - 3, $color, $font , $this->response);
+
+		// Finally draw the foreground characters
+		$color = imagecolorallocate($this->image, 0, 0, 0);
+		imagefttext($this->image, $size, $angle, $x, $y, $color, $font, $this->response);
+
+		// Output
+		return $this->image_render($html);
+	}
+
+} // End Captcha Black Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Math.php b/system/libraries/drivers/Captcha/Math.php
new file mode 100755
index 0000000..6d0d9fa
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Math.php
@@ -0,0 +1,61 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "math" style.
+ *
+ * $Id: Math.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Math_Driver extends Captcha_Driver {
+
+	private $math_exercice;
+
+	/**
+	 * Generates a new Captcha challenge.
+	 *
+	 * @return  string  the challenge answer
+	 */
+	public function generate_challenge()
+	{
+		// Easy
+		if (Captcha::$config['complexity'] < 4)
+		{
+			$numbers[] = mt_rand(1, 5);
+			$numbers[] = mt_rand(1, 4);
+		}
+		// Normal
+		elseif (Captcha::$config['complexity'] < 7)
+		{
+			$numbers[] = mt_rand(10, 20);
+			$numbers[] = mt_rand(1, 10);
+		}
+		// Difficult, well, not really ;)
+		else
+		{
+			$numbers[] = mt_rand(100, 200);
+			$numbers[] = mt_rand(10, 20);
+			$numbers[] = mt_rand(1, 10);
+		}
+
+		// Store the question for output
+		$this->math_exercice = implode(' + ', $numbers).' = ';
+
+		// Return the answer
+		return array_sum($numbers);
+	}
+
+	/**
+	 * Outputs the Captcha riddle.
+	 *
+	 * @param   boolean  html output
+	 * @return  mixed
+	 */
+	public function render($html)
+	{
+		return $this->math_exercice;
+	}
+
+} // End Captcha Math Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Riddle.php b/system/libraries/drivers/Captcha/Riddle.php
new file mode 100755
index 0000000..c8bf59d
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Riddle.php
@@ -0,0 +1,47 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "riddle" style.
+ *
+ * $Id: Riddle.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Riddle_Driver extends Captcha_Driver {
+
+	private $riddle;
+
+	/**
+	 * Generates a new Captcha challenge.
+	 *
+	 * @return  string  the challenge answer
+	 */
+	public function generate_challenge()
+	{
+		// Load riddles from the current language
+		$riddles = Kohana::lang('captcha.riddles');
+
+		// Pick a random riddle
+		$riddle = $riddles[array_rand($riddles)];
+
+		// Store the question for output
+		$this->riddle = $riddle[0];
+
+		// Return the answer
+		return $riddle[1];
+	}
+
+	/**
+	 * Outputs the Captcha riddle.
+	 *
+	 * @param   boolean  html output
+	 * @return  mixed
+	 */
+	public function render($html)
+	{
+		return $this->riddle;
+	}
+
+} // End Captcha Riddle Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Captcha/Word.php b/system/libraries/drivers/Captcha/Word.php
new file mode 100755
index 0000000..62f317e
--- /dev/null
+++ b/system/libraries/drivers/Captcha/Word.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Captcha driver for "word" style.
+ *
+ * $Id: Word.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Captcha
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Captcha_Word_Driver extends Captcha_Basic_Driver {
+
+	/**
+	 * Generates a new Captcha challenge.
+	 *
+	 * @return  string  the challenge answer
+	 */
+	public function generate_challenge()
+	{
+		// Load words from the current language and randomize them
+		$words = Kohana::lang('captcha.words');
+		shuffle($words);
+
+		// Loop over each word...
+		foreach ($words as $word)
+		{
+			// ...until we find one of the desired length
+			if (abs(Captcha::$config['complexity'] - strlen($word)) < 2)
+				return strtoupper($word);
+		}
+
+		// Return any random word as final fallback
+		return strtoupper($words[array_rand($words)]);
+	}
+
+} // End Captcha Word Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Database.php b/system/libraries/drivers/Database.php
new file mode 100755
index 0000000..fd8c92d
--- /dev/null
+++ b/system/libraries/drivers/Database.php
@@ -0,0 +1,636 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Database API driver
+ *
+ * $Id: Database.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Database_Driver {
+
+	static $query_cache;
+
+	/**
+	 * Connect to our database.
+	 * Returns FALSE on failure or a MySQL resource.
+	 *
+	 * @return mixed
+	 */
+	abstract public function connect();
+
+	/**
+	 * Perform a query based on a manually written query.
+	 *
+	 * @param  string  SQL query to execute
+	 * @return Database_Result
+	 */
+	abstract public function query($sql);
+
+	/**
+	 * Builds a DELETE query.
+	 *
+	 * @param   string  table name
+	 * @param   array   where clause
+	 * @return  string
+	 */
+	public function delete($table, $where)
+	{
+		return 'DELETE FROM '.$this->escape_table($table).' WHERE '.implode(' ', $where);
+	}
+
+	/**
+	 * Builds an UPDATE query.
+	 *
+	 * @param   string  table name
+	 * @param   array   key => value pairs
+	 * @param   array   where clause
+	 * @return  string
+	 */
+	public function update($table, $values, $where)
+	{
+		foreach ($values as $key => $val)
+		{
+			$valstr[] = $this->escape_column($key).' = '.$val;
+		}
+		return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ',$where);
+	}
+
+	/**
+	 * Set the charset using 'SET NAMES <charset>'.
+	 *
+	 * @param  string  character set to use
+	 */
+	public function set_charset($charset)
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 * Wrap the tablename in backticks, has support for: table.field syntax.
+	 *
+	 * @param   string  table name
+	 * @return  string
+	 */
+	abstract public function escape_table($table);
+
+	/**
+	 * Escape a column/field name, has support for special commands.
+	 *
+	 * @param   string  column name
+	 * @return  string
+	 */
+	abstract public function escape_column($column);
+
+	/**
+	 * Builds a WHERE portion of a query.
+	 *
+	 * @param   mixed    key
+	 * @param   string   value
+	 * @param   string   type
+	 * @param   int      number of where clauses
+	 * @param   boolean  escape the value
+	 * @return  string
+	 */
+	public function where($key, $value, $type, $num_wheres, $quote)
+	{
+		$prefix = ($num_wheres == 0) ? '' : $type;
+
+		if ($quote === -1)
+		{
+			$value = '';
+		}
+		else
+		{
+			if ($value === NULL)
+			{
+				if ( ! $this->has_operator($key))
+				{
+					$key .= ' IS';
+				}
+
+				$value = ' NULL';
+			}
+			elseif (is_bool($value))
+			{
+				if ( ! $this->has_operator($key))
+				{
+					$key .= ' =';
+				}
+
+				$value = ($value == TRUE) ? ' 1' : ' 0';
+			}
+			else
+			{
+				if ( ! $this->has_operator($key))
+				{
+					$key = $this->escape_column($key).' =';
+				}
+				else
+				{
+					preg_match('/^(.+?)([<>!=]+|\bIS(?:\s+NULL))\s*$/i', $key, $matches);
+					if (isset($matches[1]) AND isset($matches[2]))
+					{
+						$key = $this->escape_column(trim($matches[1])).' '.trim($matches[2]);
+					}
+				}
+
+				$value = ' '.(($quote == TRUE) ? $this->escape($value) : $value);
+			}
+		}
+
+		return $prefix.$key.$value;
+	}
+
+	/**
+	 * Builds a LIKE portion of a query.
+	 *
+	 * @param   mixed    field name
+	 * @param   string   value to match with field
+	 * @param   boolean  add wildcards before and after the match
+	 * @param   string   clause type (AND or OR)
+	 * @param   int      number of likes
+	 * @return  string
+	 */
+	public function like($field, $match = '', $auto = TRUE, $type = 'AND ', $num_likes)
+	{
+		$prefix = ($num_likes == 0) ? '' : $type;
+
+		$match = $this->escape_str($match);
+
+		if ($auto === TRUE)
+		{
+			// Add the start and end quotes
+			$match = '%'.str_replace('%', '\\%', $match).'%';
+		}
+
+		return $prefix.' '.$this->escape_column($field).' LIKE \''.$match . '\'';
+	}
+
+	/**
+	 * Builds a NOT LIKE portion of a query.
+	 *
+	 * @param   mixed   field name
+	 * @param   string  value to match with field
+	 * @param   string  clause type (AND or OR)
+	 * @param   int     number of likes
+	 * @return  string
+	 */
+	public function notlike($field, $match = '', $auto = TRUE, $type = 'AND ', $num_likes)
+	{
+		$prefix = ($num_likes == 0) ? '' : $type;
+
+		$match = $this->escape_str($match);
+
+		if ($auto === TRUE)
+		{
+			// Add the start and end quotes
+			$match = '%'.$match.'%';
+		}
+
+		return $prefix.' '.$this->escape_column($field).' NOT LIKE \''.$match.'\'';
+	}
+
+	/**
+	 * Builds a REGEX portion of a query.
+	 *
+	 * @param   string   field name
+	 * @param   string   value to match with field
+	 * @param   string   clause type (AND or OR)
+	 * @param   integer  number of regexes
+	 * @return  string
+	 */
+	public function regex($field, $match, $type, $num_regexs)
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 * Builds a NOT REGEX portion of a query.
+	 *
+	 * @param   string   field name
+	 * @param   string   value to match with field
+	 * @param   string   clause type (AND or OR)
+	 * @param   integer  number of regexes
+	 * @return  string
+	 */
+	public function notregex($field, $match, $type, $num_regexs)
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 * Builds an INSERT query.
+	 *
+	 * @param   string  table name
+	 * @param   array   keys
+	 * @param   array   values
+	 * @return  string
+	 */
+	public function insert($table, $keys, $values)
+	{
+		// Escape the column names
+		foreach ($keys as $key => $value)
+		{
+			$keys[$key] = $this->escape_column($value);
+		}
+		return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+	}
+
+	/**
+	 * Builds a MERGE portion of a query.
+	 *
+	 * @param   string  table name
+	 * @param   array   keys
+	 * @param   array   values
+	 * @return  string
+	 */
+	public function merge($table, $keys, $values)
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 * Builds a LIMIT portion of a query.
+	 *
+	 * @param   integer  limit
+	 * @param   integer  offset
+	 * @return  string
+	 */
+	abstract public function limit($limit, $offset = 0);
+
+	/**
+	 * Creates a prepared statement.
+	 *
+	 * @param   string  SQL query
+	 * @return  Database_Stmt
+	 */
+	public function stmt_prepare($sql = '')
+	{
+		throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
+	}
+
+	/**
+	 *  Compiles the SELECT statement.
+	 *  Generates a query string based on which functions were used.
+	 *  Should not be called directly, the get() function calls it.
+	 *
+	 * @param   array   select query values
+	 * @return  string
+	 */
+	abstract public function compile_select($database);
+
+	/**
+	 * Determines if the string has an arithmetic operator in it.
+	 *
+	 * @param   string   string to check
+	 * @return  boolean
+	 */
+	public function has_operator($str)
+	{
+		return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b/i', trim($str));
+	}
+
+	/**
+	 * Escapes any input value.
+	 *
+	 * @param   mixed   value to escape
+	 * @return  string
+	 */
+	public function escape($value)
+	{
+		if ( ! $this->db_config['escape'])
+			return $value;
+
+		switch (gettype($value))
+		{
+			case 'string':
+				$value = '\''.$this->escape_str($value).'\'';
+			break;
+			case 'boolean':
+				$value = (int) $value;
+			break;
+			case 'double':
+				// Convert to non-locale aware float to prevent possible commas
+				$value = sprintf('%F', $value);
+			break;
+			default:
+				$value = ($value === NULL) ? 'NULL' : $value;
+			break;
+		}
+
+		return (string) $value;
+	}
+
+	/**
+	 * Escapes a string for a query.
+	 *
+	 * @param   mixed   value to escape
+	 * @return  string
+	 */
+	abstract public function escape_str($str);
+
+	/**
+	 * Lists all tables in the database.
+	 *
+	 * @return  array
+	 */
+	abstract public function list_tables(Database $db);
+
+	/**
+	 * Lists all fields in a table.
+	 *
+	 * @param   string  table name
+	 * @return  array
+	 */
+	abstract function list_fields($table);
+
+	/**
+	 * Returns the last database error.
+	 *
+	 * @return  string
+	 */
+	abstract public function show_error();
+
+	/**
+	 * Returns field data about a table.
+	 *
+	 * @param   string  table name
+	 * @return  array
+	 */
+	abstract public function field_data($table);
+
+	/**
+	 * Fetches SQL type information about a field, in a generic format.
+	 *
+	 * @param   string  field datatype
+	 * @return  array
+	 */
+	protected function sql_type($str)
+	{
+		static $sql_types;
+
+		if ($sql_types === NULL)
+		{
+			// Load SQL data types
+			$sql_types = Kohana::config('sql_types');
+		}
+
+		$str = strtolower(trim($str));
+
+		if (($open  = strpos($str, '(')) !== FALSE)
+		{
+			// Find closing bracket
+			$close = strpos($str, ')', $open) - 1;
+
+			// Find the type without the size
+			$type = substr($str, 0, $open);
+		}
+		else
+		{
+			// No length
+			$type = $str;
+		}
+
+		empty($sql_types[$type]) and exit
+		(
+			'Unknown field type: '.$type.'. '.
+			'Please report this: http://trac.kohanaphp.com/newticket'
+		);
+
+		// Fetch the field definition
+		$field = $sql_types[$type];
+
+		switch ($field['type'])
+		{
+			case 'string':
+			case 'float':
+				if (isset($close))
+				{
+					// Add the length to the field info
+					$field['length'] = substr($str, $open + 1, $close - $open);
+				}
+			break;
+			case 'int':
+				// Add unsigned value
+				$field['unsigned'] = (strpos($str, 'unsigned') !== FALSE);
+			break;
+		}
+
+		return $field;
+	}
+
+	/**
+	 * Clears the internal query cache.
+	 *
+	 * @param  string  SQL query
+	 */
+	public function clear_cache($sql = NULL)
+	{
+		if (empty($sql))
+		{
+			self::$query_cache = array();
+		}
+		else
+		{
+			unset(self::$query_cache[$this->query_hash($sql)]);
+		}
+
+		Kohana::log('debug', 'Database cache cleared: '.get_class($this));
+	}
+
+	/**
+	 * Creates a hash for an SQL query string. Replaces newlines with spaces,
+	 * trims, and hashes.
+	 *
+	 * @param   string  SQL query
+	 * @return  string
+	 */
+	protected function query_hash($sql)
+	{
+		return sha1(str_replace("\n", ' ', trim($sql)));
+	}
+
+} // End Database Driver Interface
+
+/**
+ * Database_Result
+ *
+ */
+abstract class Database_Result implements ArrayAccess, Iterator, Countable {
+
+	// Result resource, insert id, and SQL
+	protected $result;
+	protected $insert_id;
+	protected $sql;
+
+	// Current and total rows
+	protected $current_row = 0;
+	protected $total_rows  = 0;
+
+	// Fetch function and return type
+	protected $fetch_type;
+	protected $return_type;
+
+	/**
+	 * Returns the SQL used to fetch the result.
+	 *
+	 * @return  string
+	 */
+	public function sql()
+	{
+		return $this->sql;
+	}
+
+	/**
+	 * Returns the insert id from the result.
+	 *
+	 * @return  mixed
+	 */
+	public function insert_id()
+	{
+		return $this->insert_id;
+	}
+
+	/**
+	 * Prepares the query result.
+	 *
+	 * @param   boolean   return rows as objects
+	 * @param   mixed     type
+	 * @return  Database_Result
+	 */
+	abstract function result($object = TRUE, $type = FALSE);
+
+	/**
+	 * Builds an array of query results.
+	 *
+	 * @param   boolean   return rows as objects
+	 * @param   mixed     type
+	 * @return  array
+	 */
+	abstract function result_array($object = NULL, $type = FALSE);
+
+	/**
+	 * Gets the fields of an already run query.
+	 *
+	 * @return  array
+	 */
+	abstract public function list_fields();
+
+	/**
+	 * Seek to an offset in the results.
+	 *
+	 * @return  boolean
+	 */
+	abstract public function seek($offset);
+
+	/**
+	 * Countable: count
+	 */
+	public function count()
+	{
+		return $this->total_rows;
+	}
+
+	/**
+	 * ArrayAccess: offsetExists
+	 */
+	public function offsetExists($offset)
+	{
+		if ($this->total_rows > 0)
+		{
+			$min = 0;
+			$max = $this->total_rows - 1;
+
+			return ! ($offset < $min OR $offset > $max);
+		}
+
+		return FALSE;
+	}
+
+	/**
+	 * ArrayAccess: offsetGet
+	 */
+	public function offsetGet($offset)
+	{
+		if ( ! $this->seek($offset))
+			return FALSE;
+
+		// Return the row by calling the defined fetching callback
+		return call_user_func($this->fetch_type, $this->result, $this->return_type);
+	}
+
+	/**
+	 * ArrayAccess: offsetSet
+	 *
+	 * @throws  Kohana_Database_Exception
+	 */
+	final public function offsetSet($offset, $value)
+	{
+		throw new Kohana_Database_Exception('database.result_read_only');
+	}
+
+	/**
+	 * ArrayAccess: offsetUnset
+	 *
+	 * @throws  Kohana_Database_Exception
+	 */
+	final public function offsetUnset($offset)
+	{
+		throw new Kohana_Database_Exception('database.result_read_only');
+	}
+
+	/**
+	 * Iterator: current
+	 */
+	public function current()
+	{
+		return $this->offsetGet($this->current_row);
+	}
+
+	/**
+	 * Iterator: key
+	 */
+	public function key()
+	{
+		return $this->current_row;
+	}
+
+	/**
+	 * Iterator: next
+	 */
+	public function next()
+	{
+		++$this->current_row;
+		return $this;
+	}
+
+	/**
+	 * Iterator: prev
+	 */
+	public function prev()
+	{
+		--$this->current_row;
+		return $this;
+	}
+
+	/**
+	 * Iterator: rewind
+	 */
+	public function rewind()
+	{
+		$this->current_row = 0;
+		return $this;
+	}
+
+	/**
+	 * Iterator: valid
+	 */
+	public function valid()
+	{
+		return $this->offsetExists($this->current_row);
+	}
+
+} // End Database Result Interface
\ No newline at end of file
diff --git a/system/libraries/drivers/Database/Mssql.php b/system/libraries/drivers/Database/Mssql.php
new file mode 100755
index 0000000..401770a
--- /dev/null
+++ b/system/libraries/drivers/Database/Mssql.php
@@ -0,0 +1,458 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MSSQL Database Driver
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Mssql_Driver extends Database_Driver
+{
+	/**
+	 * Database connection link
+	 */
+	protected $link;
+
+	/**
+	 * Database configuration
+	 */
+	protected $db_config;
+
+	/**
+	 * Sets the config for the class.
+	 *
+	 * @param  array  database configuration
+	 */
+	public function __construct($config)
+	{
+		$this->db_config = $config;
+
+		Kohana::log('debug', 'MSSQL Database Driver Initialized');
+	}
+
+	/**
+	 * Closes the database connection.
+	 */
+	public function __destruct()
+	{
+		is_resource($this->link) and mssql_close($this->link);
+	}
+
+	/**
+	 * Make the connection
+	 *
+	 * @return return connection
+	 */
+	public function connect()
+	{
+		// Check if link already exists
+		if (is_resource($this->link))
+			return $this->link;
+
+		// Import the connect variables
+		extract($this->db_config['connection']);
+
+		// Persistent connections enabled?
+		$connect = ($this->db_config['persistent'] == TRUE) ? 'mssql_pconnect' : 'mssql_connect';
+
+		// Build the connection info
+		$host = isset($host) ? $host : $socket;
+
+		// Windows uses a comma instead of a colon
+		$port = (isset($port) AND is_string($port)) ? (KOHANA_IS_WIN ? ',' : ':').$port : '';
+
+		// Make the connection and select the database
+		if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mssql_select_db($database, $this->link))
+		{
+			/* This is being removed so I can use it, will need to come up with a more elegant workaround in the future...
+			 *
+			if ($charset = $this->db_config['character_set'])
+			{
+				$this->set_charset($charset);
+			}
+			*/
+
+			// Clear password after successful connect
+			$this->config['connection']['pass'] = NULL;
+
+			return $this->link;
+		}
+
+		return FALSE;
+	}
+
+	public function query($sql)
+	{
+		// Only cache if it's turned on, and only cache if it's not a write statement
+		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
+		{
+			$hash = $this->query_hash($sql);
+
+			if ( ! isset(self::$query_cache[$hash]))
+			{
+				// Set the cached object
+				self::$query_cache[$hash] = new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+			}
+
+			// Return the cached query
+			return self::$query_cache[$hash];
+		}
+
+		return new Mssql_Result(mssql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+	}
+
+	public function escape_table($table)
+	{
+		if (stripos($table, ' AS ') !== FALSE)
+		{
+			// Force 'AS' to uppercase
+			$table = str_ireplace(' AS ', ' AS ', $table);
+
+			// Runs escape_table on both sides of an AS statement
+			$table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
+
+			// Re-create the AS statement
+			return implode(' AS ', $table);
+		}
+		return '['.str_replace('.', '[.]', $table).']';
+	}
+
+	public function escape_column($column)
+	{
+		if (!$this->db_config['escape'])
+			return $column;
+
+		if (strtolower($column) == 'count(*)' OR $column == '*')
+			return $column;
+
+		// This matches any modifiers we support to SELECT.
+		if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
+		{
+			if (stripos($column, ' AS ') !== FALSE)
+			{
+				// Force 'AS' to uppercase
+				$column = str_ireplace(' AS ', ' AS ', $column);
+
+				// Runs escape_column on both sides of an AS statement
+				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+				// Re-create the AS statement
+				return implode(' AS ', $column);
+			}
+
+			return preg_replace('/[^.*]+/', '[$0]', $column);
+		}
+
+		$parts = explode(' ', $column);
+		$column = '';
+
+		for ($i = 0, $c = count($parts); $i < $c; $i++)
+		{
+			// The column is always last
+			if ($i == ($c - 1))
+			{
+				$column .= preg_replace('/[^.*]+/', '[$0]', $parts[$i]);
+			}
+			else // otherwise, it's a modifier
+			{
+				$column .= $parts[$i].' ';
+			}
+		}
+		return $column;
+	}
+
+	/**
+	 * Limit in SQL Server 2000 only uses the keyword
+	 * 'TOP'; 2007 may have an offset keyword, but
+	 * I am unsure - for pagination style limit,offset
+	 * functionality, a fancy query needs to be built.
+	 *
+	 * @param unknown_type $limit
+	 * @return unknown
+	 */
+	public function limit($limit, $offset=null)
+	{
+		return 'TOP '.$limit;
+	}
+
+	public function compile_select($database)
+	{
+		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+		if (count($database['from']) > 0)
+		{
+			// Escape the tables
+			$froms = array();
+			foreach ($database['from'] as $from)
+				$froms[] = $this->escape_column($from);
+			$sql .= "\nFROM ";
+			$sql .= implode(', ', $froms);
+		}
+
+		if (count($database['join']) > 0)
+		{
+			foreach($database['join'] AS $join)
+			{
+				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+			}
+		}
+
+		if (count($database['where']) > 0)
+		{
+			$sql .= "\nWHERE ";
+		}
+
+		$sql .= implode("\n", $database['where']);
+
+		if (count($database['groupby']) > 0)
+		{
+			$sql .= "\nGROUP BY ";
+			$sql .= implode(', ', $database['groupby']);
+		}
+
+		if (count($database['having']) > 0)
+		{
+			$sql .= "\nHAVING ";
+			$sql .= implode("\n", $database['having']);
+		}
+
+		if (count($database['orderby']) > 0)
+		{
+			$sql .= "\nORDER BY ";
+			$sql .= implode(', ', $database['orderby']);
+		}
+
+		if (is_numeric($database['limit']))
+		{
+			$sql .= "\n";
+			$sql .= $this->limit($database['limit']);
+		}
+
+		return $sql;
+	}
+
+	public function escape_str($str)
+	{
+		if (!$this->db_config['escape'])
+			return $str;
+
+		is_resource($this->link) or $this->connect();
+		//mssql_real_escape_string($str, $this->link); <-- this function doesn't exist
+
+		$characters = array('/\x00/', '/\x1a/', '/\n/', '/\r/', '/\\\/', '/\'/');
+		$replace    = array('\\\x00', '\\x1a', '\\n', '\\r', '\\\\', "''");
+		return preg_replace($characters, $replace, $str);
+	}
+
+	public function list_tables(Database $db)
+	{
+		$sql    = 'SHOW TABLES FROM ['.$this->db_config['connection']['database'].']';
+		$result = $this->query($sql)->result(FALSE, MSSQL_ASSOC);
+
+		$retval = array();
+		foreach ($result as $row)
+		{
+			$retval[] = current($row);
+		}
+
+		return $retval;
+	}
+
+	public function show_error()
+	{
+		return mssql_get_last_message($this->link);
+	}
+
+	public function list_fields($table)
+	{
+		static $tables;
+
+		if (empty($tables[$table]))
+		{
+			foreach ($this->field_data($table) as $row)
+			{
+				// Make an associative array
+				$tables[$table][$row->Field] = $this->sql_type($row->Type);
+			}
+		}
+
+		return $tables[$table];
+	}
+
+	public function field_data($table)
+	{
+		$columns = array();
+
+		if ($query = MSSQL_query('SHOW COLUMNS FROM '.$this->escape_table($table), $this->link))
+		{
+			if (MSSQL_num_rows($query) > 0)
+			{
+				while ($row = MSSQL_fetch_object($query))
+				{
+					$columns[] = $row;
+				}
+			}
+		}
+
+		return $columns;
+	}
+}
+
+/**
+ * MSSQL Result
+ */
+class Mssql_Result extends Database_Result {
+
+	// Fetch function and return type
+	protected $fetch_type  = 'mssql_fetch_object';
+	protected $return_type = MSSQL_ASSOC;
+
+	/**
+	 * Sets up the result variables.
+	 *
+	 * @param  resource  query result
+	 * @param  resource  database link
+	 * @param  boolean   return objects or arrays
+	 * @param  string    SQL query that was run
+	 */
+	public function __construct($result, $link, $object = TRUE, $sql)
+	{
+		$this->result = $result;
+
+		// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+		if (is_resource($result))
+		{
+			$this->current_row = 0;
+			$this->total_rows  = mssql_num_rows($this->result);
+			$this->fetch_type = ($object === TRUE) ? 'mssql_fetch_object' : 'mssql_fetch_array';
+		}
+		elseif (is_bool($result))
+		{
+			if ($result == FALSE)
+			{
+				// SQL error
+				throw new Kohana_Database_Exception('database.error', mssql_get_last_message($link).' - '.$sql);
+			}
+			else
+			{
+				// Its an DELETE, INSERT, REPLACE, or UPDATE querys
+				$last_id          = mssql_query('SELECT @@IDENTITY AS last_id', $link);
+				$result           = mssql_fetch_assoc($last_id);
+				$this->insert_id  = $result['last_id'];
+				$this->total_rows = mssql_rows_affected($link);
+			}
+		}
+
+		// Set result type
+		$this->result($object);
+
+		// Store the SQL
+		$this->sql = $sql;
+	}
+
+	/**
+	 * Destruct, the cleanup crew!
+	 */
+	public function __destruct()
+	{
+		if (is_resource($this->result))
+		{
+			mssql_free_result($this->result);
+		}
+	}
+
+	public function result($object = TRUE, $type = MSSQL_ASSOC)
+	{
+		$this->fetch_type = ((bool) $object) ? 'mssql_fetch_object' : 'mssql_fetch_array';
+
+		// This check has to be outside the previous statement, because we do not
+		// know the state of fetch_type when $object = NULL
+		// NOTE - The class set by $type must be defined before fetching the result,
+		// autoloading is disabled to save a lot of stupid overhead.
+		if ($this->fetch_type == 'mssql_fetch_object')
+		{
+			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+		}
+		else
+		{
+			$this->return_type = $type;
+		}
+
+		return $this;
+	}
+
+	public function as_array($object = NULL, $type = MSSQL_ASSOC)
+	{
+		return $this->result_array($object, $type);
+	}
+
+	public function result_array($object = NULL, $type = MSSQL_ASSOC)
+	{
+		$rows = array();
+
+		if (is_string($object))
+		{
+			$fetch = $object;
+		}
+		elseif (is_bool($object))
+		{
+			if ($object === TRUE)
+			{
+				$fetch = 'mssql_fetch_object';
+
+				// NOTE - The class set by $type must be defined before fetching the result,
+				// autoloading is disabled to save a lot of stupid overhead.
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+			else
+			{
+				$fetch = 'mssql_fetch_array';
+			}
+		}
+		else
+		{
+			// Use the default config values
+			$fetch = $this->fetch_type;
+
+			if ($fetch == 'mssql_fetch_object')
+			{
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+		}
+
+		if (mssql_num_rows($this->result))
+		{
+			// Reset the pointer location to make sure things work properly
+			mssql_data_seek($this->result, 0);
+
+			while ($row = $fetch($this->result, $type))
+			{
+				$rows[] = $row;
+			}
+		}
+
+		return isset($rows) ? $rows : array();
+	}
+
+	public function list_fields()
+	{
+		$field_names = array();
+		while ($field = mssql_fetch_field($this->result))
+		{
+			$field_names[] = $field->name;
+		}
+
+		return $field_names;
+	}
+
+	public function seek($offset)
+	{
+		if ( ! $this->offsetExists($offset))
+			return FALSE;
+
+		return mssql_data_seek($this->result, $offset);
+	}
+
+} // End mssql_Result Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Database/Mysql.php b/system/libraries/drivers/Database/Mysql.php
new file mode 100755
index 0000000..739feb8
--- /dev/null
+++ b/system/libraries/drivers/Database/Mysql.php
@@ -0,0 +1,492 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MySQL Database Driver
+ *
+ * $Id: Mysql.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Mysql_Driver extends Database_Driver {
+
+	/**
+	 * Database connection link
+	 */
+	protected $link;
+
+	/**
+	 * Database configuration
+	 */
+	protected $db_config;
+
+	/**
+	 * Sets the config for the class.
+	 *
+	 * @param  array  database configuration
+	 */
+	public function __construct($config)
+	{
+		$this->db_config = $config;
+
+		Kohana::log('debug', 'MySQL Database Driver Initialized');
+	}
+
+	/**
+	 * Closes the database connection.
+	 */
+	public function __destruct()
+	{
+		is_resource($this->link) and mysql_close($this->link);
+	}
+
+	public function connect()
+	{
+		// Check if link already exists
+		if (is_resource($this->link))
+			return $this->link;
+
+		// Import the connect variables
+		extract($this->db_config['connection']);
+
+		// Persistent connections enabled?
+		$connect = ($this->db_config['persistent'] == TRUE) ? 'mysql_pconnect' : 'mysql_connect';
+
+		// Build the connection info
+		$host = isset($host) ? $host : $socket;
+		$port = isset($port) ? ':'.$port : '';
+
+		// Make the connection and select the database
+		if (($this->link = $connect($host.$port, $user, $pass, TRUE)) AND mysql_select_db($database, $this->link))
+		{
+			if ($charset = $this->db_config['character_set'])
+			{
+				$this->set_charset($charset);
+			}
+
+			// Clear password after successful connect
+			$this->config['connection']['pass'] = NULL;
+
+			return $this->link;
+		}
+
+		return FALSE;
+	}
+
+	public function query($sql)
+	{
+		// Only cache if it's turned on, and only cache if it's not a write statement
+		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
+		{
+			$hash = $this->query_hash($sql);
+
+			if ( ! isset(self::$query_cache[$hash]))
+			{
+				// Set the cached object
+				self::$query_cache[$hash] = new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+			}
+
+			// Return the cached query
+			return self::$query_cache[$hash];
+		}
+
+		return new Mysql_Result(mysql_query($sql, $this->link), $this->link, $this->db_config['object'], $sql);
+	}
+
+	public function set_charset($charset)
+	{
+		$this->query('SET NAMES '.$this->escape_str($charset));
+	}
+
+	public function escape_table($table)
+	{
+		if (!$this->db_config['escape'])
+			return $table;
+
+		if (stripos($table, ' AS ') !== FALSE)
+		{
+			// Force 'AS' to uppercase
+			$table = str_ireplace(' AS ', ' AS ', $table);
+
+			// Runs escape_table on both sides of an AS statement
+			$table = array_map(array($this, __FUNCTION__), explode(' AS ', $table));
+
+			// Re-create the AS statement
+			return implode(' AS ', $table);
+		}
+		return '`'.str_replace('.', '`.`', $table).'`';
+	}
+
+	public function escape_column($column)
+	{
+		if (!$this->db_config['escape'])
+			return $column;
+
+		if (strtolower($column) == 'count(*)' OR $column == '*')
+			return $column;
+
+		// This matches any modifiers we support to SELECT.
+		if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
+		{
+			if (stripos($column, ' AS ') !== FALSE)
+			{
+				// Force 'AS' to uppercase
+				$column = str_ireplace(' AS ', ' AS ', $column);
+
+				// Runs escape_column on both sides of an AS statement
+				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+				// Re-create the AS statement
+				return implode(' AS ', $column);
+			}
+
+			return preg_replace('/[^.*]+/', '`$0`', $column);
+		}
+
+		$parts = explode(' ', $column);
+		$column = '';
+
+		for ($i = 0, $c = count($parts); $i < $c; $i++)
+		{
+			// The column is always last
+			if ($i == ($c - 1))
+			{
+				$column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
+			}
+			else // otherwise, it's a modifier
+			{
+				$column .= $parts[$i].' ';
+			}
+		}
+		return $column;
+	}
+
+	public function regex($field, $match = '', $type = 'AND ', $num_regexs)
+	{
+		$prefix = ($num_regexs == 0) ? '' : $type;
+
+		return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
+	}
+
+	public function notregex($field, $match = '', $type = 'AND ', $num_regexs)
+	{
+		$prefix = $num_regexs == 0 ? '' : $type;
+
+		return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
+	}
+
+	public function merge($table, $keys, $values)
+	{
+		// Escape the column names
+		foreach ($keys as $key => $value)
+		{
+			$keys[$key] = $this->escape_column($value);
+		}
+		return 'REPLACE INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
+	}
+
+	public function limit($limit, $offset = 0)
+	{
+		return 'LIMIT '.$offset.', '.$limit;
+	}
+
+	public function compile_select($database)
+	{
+		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+		if (count($database['from']) > 0)
+		{
+			// Escape the tables
+			$froms = array();
+			foreach ($database['from'] as $from)
+			{
+				$froms[] = $this->escape_column($from);
+			}
+			$sql .= "\nFROM ";
+			$sql .= implode(', ', $froms);
+		}
+
+		if (count($database['join']) > 0)
+		{
+			foreach($database['join'] AS $join)
+			{
+				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+			}
+		}
+
+		if (count($database['where']) > 0)
+		{
+			$sql .= "\nWHERE ";
+		}
+
+		$sql .= implode("\n", $database['where']);
+
+		if (count($database['groupby']) > 0)
+		{
+			$sql .= "\nGROUP BY ";
+			$sql .= implode(', ', $database['groupby']);
+		}
+
+		if (count($database['having']) > 0)
+		{
+			$sql .= "\nHAVING ";
+			$sql .= implode("\n", $database['having']);
+		}
+
+		if (count($database['orderby']) > 0)
+		{
+			$sql .= "\nORDER BY ";
+			$sql .= implode(', ', $database['orderby']);
+		}
+
+		if (is_numeric($database['limit']))
+		{
+			$sql .= "\n";
+			$sql .= $this->limit($database['limit'], $database['offset']);
+		}
+
+		return $sql;
+	}
+
+	public function escape_str($str)
+	{
+		if (!$this->db_config['escape'])
+			return $str;
+
+		is_resource($this->link) or $this->connect();
+
+		return mysql_real_escape_string($str, $this->link);
+	}
+
+	public function list_tables(Database $db)
+	{
+		static $tables;
+
+		if (empty($tables) AND $query = $db->query('SHOW TABLES FROM '.$this->escape_table($this->db_config['connection']['database'])))
+		{
+			foreach ($query->result(FALSE) as $row)
+			{
+				$tables[] = current($row);
+			}
+		}
+
+		return $tables;
+	}
+
+	public function show_error()
+	{
+		return mysql_error($this->link);
+	}
+
+	public function list_fields($table)
+	{
+		static $tables;
+
+		if (empty($tables[$table]))
+		{
+			foreach ($this->field_data($table) as $row)
+			{
+				// Make an associative array
+				$tables[$table][$row->Field] = $this->sql_type($row->Type);
+
+				if ($row->Key === 'PRI' AND $row->Extra === 'auto_increment')
+				{
+					// For sequenced (AUTO_INCREMENT) tables
+					$tables[$table][$row->Field]['sequenced'] = TRUE;
+				}
+
+				if ($row->Null === 'YES')
+				{
+					// Set NULL status
+					$tables[$table][$row->Field]['null'] = TRUE;
+				}
+			}
+		}
+
+		if (!isset($tables[$table]))
+			throw new Kohana_Database_Exception('database.table_not_found', $table);
+
+		return $tables[$table];
+	}
+
+	public function field_data($table)
+	{
+		$columns = array();
+
+		if ($query = mysql_query('SHOW COLUMNS FROM '.$this->escape_table($table), $this->link))
+		{
+			if (mysql_num_rows($query))
+			{
+				while ($row = mysql_fetch_object($query))
+				{
+					$columns[] = $row;
+				}
+			}
+		}
+
+		return $columns;
+	}
+
+} // End Database_Mysql_Driver Class
+
+/**
+ * MySQL Result
+ */
+class Mysql_Result extends Database_Result {
+
+	// Fetch function and return type
+	protected $fetch_type  = 'mysql_fetch_object';
+	protected $return_type = MYSQL_ASSOC;
+
+	/**
+	 * Sets up the result variables.
+	 *
+	 * @param  resource  query result
+	 * @param  resource  database link
+	 * @param  boolean   return objects or arrays
+	 * @param  string    SQL query that was run
+	 */
+	public function __construct($result, $link, $object = TRUE, $sql)
+	{
+		$this->result = $result;
+
+		// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+		if (is_resource($result))
+		{
+			$this->current_row = 0;
+			$this->total_rows  = mysql_num_rows($this->result);
+			$this->fetch_type = ($object === TRUE) ? 'mysql_fetch_object' : 'mysql_fetch_array';
+		}
+		elseif (is_bool($result))
+		{
+			if ($result == FALSE)
+			{
+				// SQL error
+				throw new Kohana_Database_Exception('database.error', mysql_error($link).' - '.$sql);
+			}
+			else
+			{
+				// Its an DELETE, INSERT, REPLACE, or UPDATE query
+				$this->insert_id  = mysql_insert_id($link);
+				$this->total_rows = mysql_affected_rows($link);
+			}
+		}
+
+		// Set result type
+		$this->result($object);
+
+		// Store the SQL
+		$this->sql = $sql;
+	}
+
+	/**
+	 * Destruct, the cleanup crew!
+	 */
+	public function __destruct()
+	{
+		if (is_resource($this->result))
+		{
+			mysql_free_result($this->result);
+		}
+	}
+
+	public function result($object = TRUE, $type = MYSQL_ASSOC)
+	{
+		$this->fetch_type = ((bool) $object) ? 'mysql_fetch_object' : 'mysql_fetch_array';
+
+		// This check has to be outside the previous statement, because we do not
+		// know the state of fetch_type when $object = NULL
+		// NOTE - The class set by $type must be defined before fetching the result,
+		// autoloading is disabled to save a lot of stupid overhead.
+		if ($this->fetch_type == 'mysql_fetch_object' AND $object === TRUE)
+		{
+			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+		}
+		else
+		{
+			$this->return_type = $type;
+		}
+
+		return $this;
+	}
+
+	public function as_array($object = NULL, $type = MYSQL_ASSOC)
+	{
+		return $this->result_array($object, $type);
+	}
+
+	public function result_array($object = NULL, $type = MYSQL_ASSOC)
+	{
+		$rows = array();
+
+		if (is_string($object))
+		{
+			$fetch = $object;
+		}
+		elseif (is_bool($object))
+		{
+			if ($object === TRUE)
+			{
+				$fetch = 'mysql_fetch_object';
+
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+			else
+			{
+				$fetch = 'mysql_fetch_array';
+			}
+		}
+		else
+		{
+			// Use the default config values
+			$fetch = $this->fetch_type;
+
+			if ($fetch == 'mysql_fetch_object')
+			{
+				$type = (is_string($this->return_type) AND Kohana::auto_load($this->return_type)) ? $this->return_type : 'stdClass';
+			}
+		}
+
+		if (mysql_num_rows($this->result))
+		{
+			// Reset the pointer location to make sure things work properly
+			mysql_data_seek($this->result, 0);
+
+			while ($row = $fetch($this->result, $type))
+			{
+				$rows[] = $row;
+			}
+		}
+
+		return isset($rows) ? $rows : array();
+	}
+
+	public function list_fields()
+	{
+		$field_names = array();
+		while ($field = mysql_fetch_field($this->result))
+		{
+			$field_names[] = $field->name;
+		}
+
+		return $field_names;
+	}
+
+	public function seek($offset)
+	{
+		if ($this->offsetExists($offset) AND mysql_data_seek($this->result, $offset))
+		{
+			// Set the current row to the offset
+			$this->current_row = $offset;
+
+			return TRUE;
+		}
+		else
+		{
+			return FALSE;
+		}
+	}
+
+} // End Mysql_Result Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Database/Mysqli.php b/system/libraries/drivers/Database/Mysqli.php
new file mode 100755
index 0000000..43cd3d4
--- /dev/null
+++ b/system/libraries/drivers/Database/Mysqli.php
@@ -0,0 +1,372 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * MySQLi Database Driver
+ *
+ * $Id: Mysqli.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Mysqli_Driver extends Database_Mysql_Driver {
+
+	// Database connection link
+	protected $link;
+	protected $db_config;
+	protected $statements = array();
+
+	/**
+	 * Sets the config for the class.
+	 *
+	 * @param  array  database configuration
+	 */
+	public function __construct($config)
+	{
+		$this->db_config = $config;
+
+		Kohana::log('debug', 'MySQLi Database Driver Initialized');
+	}
+
+	/**
+	 * Closes the database connection.
+	 */
+	public function __destruct()
+	{
+		is_object($this->link) and $this->link->close();
+	}
+
+	public function connect()
+	{
+		// Check if link already exists
+		if (is_object($this->link))
+			return $this->link;
+
+		// Import the connect variables
+		extract($this->db_config['connection']);
+
+		// Build the connection info
+		$host = isset($host) ? $host : $socket;
+
+		// Make the connection and select the database
+		if ($this->link = new mysqli($host, $user, $pass, $database, $port))
+		{
+			if ($charset = $this->db_config['character_set'])
+			{
+				$this->set_charset($charset);
+			}
+
+			// Clear password after successful connect
+			$this->config['connection']['pass'] = NULL;
+
+			return $this->link;
+		}
+
+		return FALSE;
+	}
+
+	public function query($sql)
+	{
+		// Only cache if it's turned on, and only cache if it's not a write statement
+		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|REPLACE|SET)\b#i', $sql))
+		{
+			$hash = $this->query_hash($sql);
+
+			if ( ! isset(self::$query_cache[$hash]))
+			{
+				// Set the cached object
+				self::$query_cache[$hash] = new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
+			}
+
+			// Return the cached query
+			return self::$query_cache[$hash];
+		}
+
+		return new Kohana_Mysqli_Result($this->link, $this->db_config['object'], $sql);
+	}
+
+	public function set_charset($charset)
+	{
+		if ($this->link->set_charset($charset) === FALSE)
+			throw new Kohana_Database_Exception('database.error', $this->show_error());
+	}
+
+	public function stmt_prepare($sql = '')
+	{
+		is_object($this->link) or $this->connect();
+		return new Kohana_Mysqli_Statement($sql, $this->link);
+	}
+
+	public function escape_str($str)
+	{
+		if (!$this->db_config['escape'])
+			return $str;
+
+		is_object($this->link) or $this->connect();
+
+		return $this->link->real_escape_string($str);
+	}
+
+	public function show_error()
+	{
+		return $this->link->error;
+	}
+
+	public function field_data($table)
+	{
+		$columns = array();
+		$query = $this->link->query('SHOW COLUMNS FROM '.$this->escape_table($table));
+
+		if (is_object($query))
+		{
+			while ($row = $query->fetch_object())
+			{
+				$columns[] = $row;
+			}
+		}
+
+		return $columns;
+	}
+
+} // End Database_Mysqli_Driver Class
+
+/**
+ * MySQLi Result
+ */
+class Kohana_Mysqli_Result extends Database_Result {
+
+	// Database connection
+	protected $link;
+
+	// Data fetching types
+	protected $fetch_type  = 'mysqli_fetch_object';
+	protected $return_type = MYSQLI_ASSOC;
+
+	/**
+	 * Sets up the result variables.
+	 *
+	 * @param  object    database link
+	 * @param  boolean   return objects or arrays
+	 * @param  string    SQL query that was run
+	 */
+	public function __construct($link, $object = TRUE, $sql)
+	{
+		$this->link = $link;
+
+		if ( ! $this->link->multi_query($sql))
+		{
+			// SQL error
+			throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
+		}
+		else
+		{
+			$this->result = $this->link->store_result();
+
+			// If the query is an object, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+			if (is_object($this->result))
+			{
+				$this->current_row = 0;
+				$this->total_rows  = $this->result->num_rows;
+				$this->fetch_type = ($object === TRUE) ? 'fetch_object' : 'fetch_array';
+			}
+			elseif ($this->link->error)
+			{
+				// SQL error
+				throw new Kohana_Database_Exception('database.error', $this->link->error.' - '.$sql);
+			}
+			else
+			{
+				// Its an DELETE, INSERT, REPLACE, or UPDATE query
+				$this->insert_id  = $this->link->insert_id;
+				$this->total_rows = $this->link->affected_rows;
+			}
+		}
+
+		// Set result type
+		$this->result($object);
+
+		// Store the SQL
+		$this->sql = $sql;
+	}
+
+	/**
+	 * Magic __destruct function, frees the result.
+	 */
+	public function __destruct()
+	{
+		if (is_object($this->result))
+		{
+			$this->result->free_result();
+
+			// this is kinda useless, but needs to be done to avoid the "Commands out of sync; you
+			// can't run this command now" error. Basically, we get all results after the first one
+			// (the one we actually need) and free them.
+			if (is_resource($this->link) AND $this->link->more_results())
+			{
+				do
+				{
+					if ($result = $this->link->store_result())
+					{
+						$result->free_result();
+					}
+				} while ($this->link->next_result());
+			}
+		}
+	}
+
+	public function result($object = TRUE, $type = MYSQLI_ASSOC)
+	{
+		$this->fetch_type = ((bool) $object) ? 'fetch_object' : 'fetch_array';
+
+		// This check has to be outside the previous statement, because we do not
+		// know the state of fetch_type when $object = NULL
+		// NOTE - The class set by $type must be defined before fetching the result,
+		// autoloading is disabled to save a lot of stupid overhead.
+		if ($this->fetch_type == 'fetch_object')
+		{
+			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+		}
+		else
+		{
+			$this->return_type = $type;
+		}
+
+		return $this;
+	}
+
+	public function as_array($object = NULL, $type = MYSQLI_ASSOC)
+	{
+		return $this->result_array($object, $type);
+	}
+
+	public function result_array($object = NULL, $type = MYSQLI_ASSOC)
+	{
+		$rows = array();
+
+		if (is_string($object))
+		{
+			$fetch = $object;
+		}
+		elseif (is_bool($object))
+		{
+			if ($object === TRUE)
+			{
+				$fetch = 'fetch_object';
+
+				// NOTE - The class set by $type must be defined before fetching the result,
+				// autoloading is disabled to save a lot of stupid overhead.
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+			else
+			{
+				$fetch = 'fetch_array';
+			}
+		}
+		else
+		{
+			// Use the default config values
+			$fetch = $this->fetch_type;
+
+			if ($fetch == 'fetch_object')
+			{
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+		}
+
+		if ($this->result->num_rows)
+		{
+			// Reset the pointer location to make sure things work properly
+			$this->result->data_seek(0);
+
+			while ($row = $this->result->$fetch($type))
+			{
+				$rows[] = $row;
+			}
+		}
+
+		return isset($rows) ? $rows : array();
+	}
+
+	public function list_fields()
+	{
+		$field_names = array();
+		while ($field = $this->result->fetch_field())
+		{
+			$field_names[] = $field->name;
+		}
+
+		return $field_names;
+	}
+
+	public function seek($offset)
+	{
+		if ( ! $this->offsetExists($offset))
+			return FALSE;
+
+		$this->result->data_seek($offset);
+
+		return TRUE;
+	}
+
+	public function offsetGet($offset)
+	{
+		if ( ! $this->seek($offset))
+			return FALSE;
+
+		// Return the row
+		$fetch = $this->fetch_type;
+		return $this->result->$fetch($this->return_type);
+	}
+
+} // End Mysqli_Result Class
+
+/**
+ * MySQLi Prepared Statement (experimental)
+ */
+class Kohana_Mysqli_Statement {
+
+	protected $link = NULL;
+	protected $stmt;
+	protected $var_names = array();
+	protected $var_values = array();
+
+	public function __construct($sql, $link)
+	{
+		$this->link = $link;
+
+		$this->stmt = $this->link->prepare($sql);
+
+		return $this;
+	}
+
+	public function __destruct()
+	{
+		$this->stmt->close();
+	}
+
+	// Sets the bind parameters
+	public function bind_params($param_types, $params)
+	{
+		$this->var_names = array_keys($params);
+		$this->var_values = array_values($params);
+		call_user_func_array(array($this->stmt, 'bind_param'), array_merge($param_types, $var_names));
+
+		return $this;
+	}
+
+	public function bind_result($params)
+	{
+		call_user_func_array(array($this->stmt, 'bind_result'), $params);
+	}
+
+	// Runs the statement
+	public function execute()
+	{
+		foreach ($this->var_names as $key => $name)
+		{
+			$$name = $this->var_values[$key];
+		}
+		$this->stmt->execute();
+		return $this->stmt;
+	}
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Database/Pdosqlite.php b/system/libraries/drivers/Database/Pdosqlite.php
new file mode 100755
index 0000000..5a51287
--- /dev/null
+++ b/system/libraries/drivers/Database/Pdosqlite.php
@@ -0,0 +1,473 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/*
+ * Class: Database_PdoSqlite_Driver
+ *  Provides specific database items for Sqlite.
+ *
+ * Connection string should be, eg: "pdosqlite://path/to/database.db"
+ *
+ * Version 1.0 alpha
+ *  author    - Doutu, updated by gregmac
+ *  copyright - (c) BSD
+ *  license   - <no>
+ */
+
+class Database_Pdosqlite_Driver extends Database_Driver {
+
+	// Database connection link
+	protected $link;
+	protected $db_config;
+
+	/*
+	 * Constructor: __construct
+	 *  Sets up the config for the class.
+	 *
+	 * Parameters:
+	 *  config - database configuration
+	 *
+	 */
+	public function __construct($config)
+	{
+		$this->db_config = $config;
+
+		Kohana::log('debug', 'PDO:Sqlite Database Driver Initialized');
+	}
+
+	public function connect()
+	{
+		// Import the connect variables
+		extract($this->db_config['connection']);
+
+		try
+		{
+			$this->link = new PDO('sqlite:'.$socket.$database, $user, $pass,
+				array(PDO::ATTR_PERSISTENT => $this->db_config['persistent']));
+
+			$this->link->setAttribute(PDO::ATTR_CASE, PDO::CASE_NATURAL);
+			$this->link->query('PRAGMA count_changes=1;');
+
+			if ($charset = $this->db_config['character_set'])
+			{
+				$this->set_charset($charset);
+			}
+		}
+		catch (PDOException $e)
+		{
+			throw new Kohana_Database_Exception('database.error', $e->getMessage());
+		}
+
+		// Clear password after successful connect
+		$this->db_config['connection']['pass'] = NULL;
+
+		return $this->link;
+	}
+
+	public function query($sql)
+	{
+		try
+		{
+			$sth = $this->link->prepare($sql);
+		}
+		catch (PDOException $e)
+		{
+			throw new Kohana_Database_Exception('database.error', $e->getMessage());
+		}
+		return new Pdosqlite_Result($sth, $this->link, $this->db_config['object'], $sql);
+	}
+
+	public function set_charset($charset)
+	{
+		$this->link->query('PRAGMA encoding = '.$this->escape_str($charset));
+	}
+
+	public function escape_table($table)
+	{
+		if ( ! $this->db_config['escape'])
+			return $table;
+
+		return '`'.str_replace('.', '`.`', $table).'`';
+	}
+
+	public function escape_column($column)
+	{
+		if ( ! $this->db_config['escape'])
+			return $column;
+
+		if (strtolower($column) == 'count(*)' OR $column == '*')
+			return $column;
+
+		// This matches any modifiers we support to SELECT.
+		if ( ! preg_match('/\b(?:rand|all|distinct(?:row)?|high_priority|sql_(?:small_result|b(?:ig_result|uffer_result)|no_cache|ca(?:che|lc_found_rows)))\s/i', $column))
+		{
+			if (stripos($column, ' AS ') !== FALSE)
+			{
+				// Force 'AS' to uppercase
+				$column = str_ireplace(' AS ', ' AS ', $column);
+
+				// Runs escape_column on both sides of an AS statement
+				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+				// Re-create the AS statement
+				return implode(' AS ', $column);
+			}
+
+			return preg_replace('/[^.*]+/', '`$0`', $column);
+		}
+
+		$parts = explode(' ', $column);
+		$column = '';
+
+		for ($i = 0, $c = count($parts); $i < $c; $i++)
+		{
+			// The column is always last
+			if ($i == ($c - 1))
+			{
+				$column .= preg_replace('/[^.*]+/', '`$0`', $parts[$i]);
+			}
+			else // otherwise, it's a modifier
+			{
+				$column .= $parts[$i].' ';
+			}
+		}
+		return $column;
+	}
+
+	public function limit($limit, $offset = 0)
+	{
+		return 'LIMIT '.$offset.', '.$limit;
+	}
+
+	public function compile_select($database)
+	{
+		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+		if (count($database['from']) > 0)
+		{
+			$sql .= "\nFROM ";
+			$sql .= implode(', ', $database['from']);
+		}
+
+		if (count($database['join']) > 0)
+		{
+			foreach($database['join'] AS $join)
+			{
+				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+			}
+		}
+
+		if (count($database['where']) > 0)
+		{
+			$sql .= "\nWHERE ";
+		}
+
+		$sql .= implode("\n", $database['where']);
+
+		if (count($database['groupby']) > 0)
+		{
+			$sql .= "\nGROUP BY ";
+			$sql .= implode(', ', $database['groupby']);
+		}
+
+		if (count($database['having']) > 0)
+		{
+			$sql .= "\nHAVING ";
+			$sql .= implode("\n", $database['having']);
+		}
+
+		if (count($database['orderby']) > 0)
+		{
+			$sql .= "\nORDER BY ";
+			$sql .= implode(', ', $database['orderby']);
+		}
+
+		if (is_numeric($database['limit']))
+		{
+			$sql .= "\n";
+			$sql .= $this->limit($database['limit'], $database['offset']);
+		}
+
+		return $sql;
+	}
+
+	public function escape_str($str)
+	{
+		if ( ! $this->db_config['escape'])
+			return $str;
+
+		if (function_exists('sqlite_escape_string'))
+		{
+			$res = sqlite_escape_string($str);
+		}
+		else
+		{
+			$res = str_replace("'", "''", $str);
+		}
+		return $res;
+	}
+
+	public function list_tables(Database $db)
+	{
+		$sql = "SELECT `name` FROM `sqlite_master` WHERE `type`='table' ORDER BY `name`;";
+		try
+		{
+			$result = $db->query($sql)->result(FALSE, PDO::FETCH_ASSOC);
+			$tables = array();
+			foreach ($result as $row)
+			{
+				$tables[] = current($row);
+			}
+		}
+		catch (PDOException $e)
+		{
+			throw new Kohana_Database_Exception('database.error', $e->getMessage());
+		}
+		return $tables;
+	}
+
+	public function show_error()
+	{
+		$err = $this->link->errorInfo();
+		return isset($err[2]) ? $err[2] : 'Unknown error!';
+	}
+
+	public function list_fields($table, $query = FALSE)
+	{
+		static $tables;
+		if (is_object($query))
+		{
+			if (empty($tables[$table]))
+			{
+				$tables[$table] = array();
+
+				foreach ($query->result() as $row)
+				{
+					$tables[$table][] = $row->name;
+				}
+			}
+
+			return $tables[$table];
+		}
+		else
+		{
+			$result = $this->link->query( 'PRAGMA table_info('.$this->escape_table($table).')' );
+
+			foreach ($result as $row)
+			{
+				$tables[$table][$row['name']] = $this->sql_type($row['type']);
+			}
+
+			return $tables[$table];
+		}
+	}
+
+	public function field_data($table)
+	{
+		Kohana::log('error', 'This method is under developing');
+	}
+	/**
+	 * Version number query string
+	 *
+	 * @access	public
+	 * @return	string
+	 */
+	function version()
+	{
+		return $this->link->getAttribute(constant("PDO::ATTR_SERVER_VERSION"));
+	}
+
+} // End Database_PdoSqlite_Driver Class
+
+/*
+ * PDO-sqlite Result
+ */
+class Pdosqlite_Result extends Database_Result {
+
+	// Data fetching types
+	protected $fetch_type  = PDO::FETCH_OBJ;
+	protected $return_type = PDO::FETCH_ASSOC;
+
+	/**
+	 * Sets up the result variables.
+	 *
+	 * @param  resource  query result
+	 * @param  resource  database link
+	 * @param  boolean   return objects or arrays
+	 * @param  string    SQL query that was run
+	 */
+	public function __construct($result, $link, $object = TRUE, $sql)
+	{
+		if (is_object($result) OR $result = $link->prepare($sql))
+		{
+			// run the query
+			try
+			{
+				$result->execute();
+			}
+			catch (PDOException $e)
+			{
+				throw new Kohana_Database_Exception('database.error', $e->getMessage());
+			}
+
+			if (preg_match('/^SELECT|PRAGMA|EXPLAIN/i', $sql))
+			{
+				$this->result = $result;
+				$this->current_row = 0;
+
+				$this->total_rows = $this->sqlite_row_count();
+
+				$this->fetch_type = ($object === TRUE) ? PDO::FETCH_OBJ : PDO::FETCH_ASSOC;
+			}
+			elseif (preg_match('/^DELETE|INSERT|UPDATE/i', $sql))
+			{
+				$this->insert_id  = $link->lastInsertId();
+			}
+		}
+		else
+		{
+			// SQL error
+			throw new Kohana_Database_Exception('database.error', $link->errorInfo().' - '.$sql);
+		}
+
+		// Set result type
+		$this->result($object);
+
+		// Store the SQL
+		$this->sql = $sql;
+	}
+
+	private function sqlite_row_count()
+	{
+		$count = 0;
+		while ($this->result->fetch())
+		{
+			$count++;
+		}
+
+		// The query must be re-fetched now.
+		$this->result->execute();
+
+		return $count;
+	}
+
+	/*
+	 * Destructor: __destruct
+	 *  Magic __destruct function, frees the result.
+	 */
+	public function __destruct()
+	{
+		if (is_object($this->result))
+		{
+			$this->result->closeCursor();
+			$this->result = NULL;
+		}
+	}
+
+	public function result($object = TRUE, $type = PDO::FETCH_BOTH)
+	{
+		$this->fetch_type = (bool) $object ? PDO::FETCH_OBJ : PDO::FETCH_BOTH;
+
+		if ($this->fetch_type == PDO::FETCH_OBJ)
+		{
+			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+		}
+		else
+		{
+			$this->return_type = $type;
+		}
+
+		return $this;
+	}
+
+	public function as_array($object = NULL, $type = PDO::FETCH_ASSOC)
+	{
+		return $this->result_array($object, $type);
+	}
+
+	public function result_array($object = NULL, $type = PDO::FETCH_ASSOC)
+	{
+		$rows = array();
+
+		if (is_string($object))
+		{
+			$fetch = $object;
+		}
+		elseif (is_bool($object))
+		{
+			if ($object === TRUE)
+			{
+				$fetch = PDO::FETCH_OBJ;
+
+				// NOTE - The class set by $type must be defined before fetching the result,
+				// autoloading is disabled to save a lot of stupid overhead.
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+			else
+			{
+				$fetch = PDO::FETCH_OBJ;
+			}
+		}
+		else
+		{
+			// Use the default config values
+			$fetch = $this->fetch_type;
+
+			if ($fetch == PDO::FETCH_OBJ)
+			{
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+		}
+		try
+		{
+			while ($row = $this->result->fetch($fetch))
+			{
+				$rows[] = $row;
+			}
+		}
+		catch(PDOException $e)
+		{
+			throw new Kohana_Database_Exception('database.error', $e->getMessage());
+			return FALSE;
+		}
+		return $rows;
+	}
+
+	public function list_fields()
+	{
+		$field_names = array();
+		for ($i = 0, $max = $this->result->columnCount(); $i < $max; $i++)
+		{
+			$info = $this->result->getColumnMeta($i);
+			$field_names[] = $info['name'];
+		}
+		return $field_names;
+	}
+
+	public function seek($offset)
+	{
+		// To request a scrollable cursor for your PDOStatement object, you must
+		// set the PDO::ATTR_CURSOR attribute to PDO::CURSOR_SCROLL when you
+		// prepare the statement.
+		Kohana::log('error', get_class($this).' does not support scrollable cursors, '.__FUNCTION__.' call ignored');
+
+		return FALSE;
+	}
+
+	public function offsetGet($offset)
+	{
+		try
+		{
+			return $this->result->fetch($this->fetch_type, PDO::FETCH_ORI_ABS, $offset);
+		}
+		catch(PDOException $e)
+		{
+			throw new Kohana_Database_Exception('database.error', $e->getMessage());
+		}
+	}
+
+	public function rewind()
+	{
+		// Same problem that seek() has, see above.
+		return $this->seek(0);
+	}
+
+} // End PdoSqlite_Result Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Database/Pgsql.php b/system/libraries/drivers/Database/Pgsql.php
new file mode 100755
index 0000000..f47f348
--- /dev/null
+++ b/system/libraries/drivers/Database/Pgsql.php
@@ -0,0 +1,552 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * PostgreSQL 8.1+ Database Driver
+ *
+ * $Id: Pgsql.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Database_Pgsql_Driver extends Database_Driver {
+
+	// Database connection link
+	protected $link;
+	protected $db_config;
+
+	/**
+	 * Sets the config for the class.
+	 *
+	 * @param  array  database configuration
+	 */
+	public function __construct($config)
+	{
+		$this->db_config = $config;
+
+		Kohana::log('debug', 'PgSQL Database Driver Initialized');
+	}
+
+	public function connect()
+	{
+		// Check if link already exists
+		if (is_resource($this->link))
+			return $this->link;
+
+		// Import the connect variables
+		extract($this->db_config['connection']);
+
+		// Persistent connections enabled?
+		$connect = ($this->db_config['persistent'] == TRUE) ? 'pg_pconnect' : 'pg_connect';
+
+		// Build the connection info
+		$port = isset($port) ? 'port=\''.$port.'\'' : '';
+		$host = isset($host) ? 'host=\''.$host.'\' '.$port : ''; // if no host, connect with the socket
+
+		$connection_string = $host.' dbname=\''.$database.'\' user=\''.$user.'\' password=\''.$pass.'\'';
+		// Make the connection and select the database
+		if ($this->link = $connect($connection_string))
+		{
+			if ($charset = $this->db_config['character_set'])
+			{
+				echo $this->set_charset($charset);
+			}
+
+			// Clear password after successful connect
+			$this->config['connection']['pass'] = NULL;
+
+			return $this->link;
+		}
+
+		return FALSE;
+	}
+
+	public function query($sql)
+	{
+		// Only cache if it's turned on, and only cache if it's not a write statement
+		if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|SET)\b#i', $sql))
+		{
+			$hash = $this->query_hash($sql);
+
+			if ( ! isset(self::$query_cache[$hash]))
+			{
+				// Set the cached object
+				self::$query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
+			}
+
+			return self::$query_cache[$hash];
+		}
+
+		return new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
+	}
+
+	public function set_charset($charset)
+	{
+		$this->query('SET client_encoding TO '.pg_escape_string($this->link, $charset));
+	}
+
+	public function escape_table($table)
+	{
+		if (!$this->db_config['escape'])
+			return $table;
+
+		return '"'.str_replace('.', '"."', $table).'"';
+	}
+
+	public function escape_column($column)
+	{
+		if (!$this->db_config['escape'])
+			return $column;
+
+		if (strtolower($column) == 'count(*)' OR $column == '*')
+			return $column;
+
+		// This matches any modifiers we support to SELECT.
+		if ( ! preg_match('/\b(?:all|distinct)\s/i', $column))
+		{
+			if (stripos($column, ' AS ') !== FALSE)
+			{
+				// Force 'AS' to uppercase
+				$column = str_ireplace(' AS ', ' AS ', $column);
+
+				// Runs escape_column on both sides of an AS statement
+				$column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
+
+				// Re-create the AS statement
+				return implode(' AS ', $column);
+			}
+
+			return preg_replace('/[^.*]+/', '"$0"', $column);
+		}
+
+		$parts = explode(' ', $column);
+		$column = '';
+
+		for ($i = 0, $c = count($parts); $i < $c; $i++)
+		{
+			// The column is always last
+			if ($i == ($c - 1))
+			{
+				$column .= preg_replace('/[^.*]+/', '"$0"', $parts[$i]);
+			}
+			else // otherwise, it's a modifier
+			{
+				$column .= $parts[$i].' ';
+			}
+		}
+		return $column;
+	}
+
+	public function regex($field, $match = '', $type = 'AND ', $num_regexs)
+	{
+		$prefix = ($num_regexs == 0) ? '' : $type;
+
+		return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
+	}
+
+	public function notregex($field, $match = '', $type = 'AND ', $num_regexs)
+	{
+		$prefix = $num_regexs == 0 ? '' : $type;
+
+		return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
+	}
+
+	public function limit($limit, $offset = 0)
+	{
+		return 'LIMIT '.$limit.' OFFSET '.$offset;
+	}
+
+	public function stmt_prepare($sql = '')
+	{
+		is_object($this->link) or $this->connect();
+		return new Kohana_Mysqli_Statement($sql, $this->link);
+	}
+
+	public function compile_select($database)
+	{
+		$sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
+		$sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
+
+		if (count($database['from']) > 0)
+		{
+			$sql .= "\nFROM ";
+			$sql .= implode(', ', $database['from']);
+		}
+
+		if (count($database['join']) > 0)
+		{
+			foreach($database['join'] AS $join)
+			{
+				$sql .= "\n".$join['type'].'JOIN '.implode(', ', $join['tables']).' ON '.$join['conditions'];
+			}
+		}
+
+		if (count($database['where']) > 0)
+		{
+			$sql .= "\nWHERE ";
+		}
+
+		$sql .= implode("\n", $database['where']);
+
+		if (count($database['groupby']) > 0)
+		{
+			$sql .= "\nGROUP BY ";
+			$sql .= implode(', ', $database['groupby']);
+		}
+
+		if (count($database['having']) > 0)
+		{
+			$sql .= "\nHAVING ";
+			$sql .= implode("\n", $database['having']);
+		}
+
+		if (count($database['orderby']) > 0)
+		{
+			$sql .= "\nORDER BY ";
+			$sql .= implode(', ', $database['orderby']);
+		}
+
+		if (is_numeric($database['limit']))
+		{
+			$sql .= "\n";
+			$sql .= $this->limit($database['limit'], $database['offset']);
+		}
+
+		return $sql;
+	}
+
+	public function escape_str($str)
+	{
+		if (!$this->db_config['escape'])
+			return $str;
+
+		is_resource($this->link) or $this->connect();
+
+		return pg_escape_string($this->link, $str);
+	}
+
+	public function list_tables(Database $db)
+	{
+		$sql    = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')';
+		$result = $db->query($sql)->result(FALSE, PGSQL_ASSOC);
+
+		$retval = array();
+		foreach ($result as $row)
+		{
+			$retval[] = current($row);
+		}
+
+		return $retval;
+	}
+
+	public function show_error()
+	{
+		return pg_last_error($this->link);
+	}
+
+	public function list_fields($table, $query = FALSE)
+	{
+		static $tables;
+
+		if (is_object($query))
+		{
+			if (empty($tables[$table]))
+			{
+				$tables[$table] = array();
+
+				foreach ($query as $row)
+				{
+					$tables[$table][] = $row->Field;
+				}
+			}
+
+			return $tables[$table];
+		}
+
+		// WOW...REALLY?!?
+		// Taken from http://www.postgresql.org/docs/7.4/interactive/catalogs.html
+		$query = $this->query('SELECT
+  -- Field
+  pg_attribute.attname AS "Field",
+  -- Type
+  CASE pg_type.typname
+    WHEN \'int2\' THEN \'smallint\'
+    WHEN \'int4\' THEN \'int\'
+    WHEN \'int8\' THEN \'bigint\'
+    WHEN \'varchar\' THEN \'varchar(\' || pg_attribute.atttypmod-4 || \')\'
+    ELSE pg_type.typname
+  END AS "Type",
+  -- Null
+  CASE WHEN pg_attribute.attnotnull THEN \'NO\'
+    ELSE \'YES\'
+  END AS "Null",
+  -- Default
+  CASE pg_type.typname
+    WHEN \'varchar\' THEN substring(pg_attrdef.adsrc from \'^(.*).*$\')
+    ELSE pg_attrdef.adsrc
+  END AS "Default"
+FROM pg_class
+  INNER JOIN pg_attribute
+    ON (pg_class.oid=pg_attribute.attrelid)
+  INNER JOIN pg_type
+    ON (pg_attribute.atttypid=pg_type.oid)
+  LEFT JOIN pg_attrdef
+    ON (pg_class.oid=pg_attrdef.adrelid AND pg_attribute.attnum=pg_attrdef.adnum)
+WHERE pg_class.relname=\''.$this->escape_str($table).'\' AND pg_attribute.attnum>=1 AND NOT pg_attribute.attisdropped
+ORDER BY pg_attribute.attnum');
+
+				// Load the result as objects
+				$query->result(TRUE);
+
+				$fields = array();
+				foreach ($query as $row)
+				{
+					$fields[$row->Field] = $row->Type;
+				}
+
+				return $fields;
+	}
+
+	public function field_data($table)
+	{
+		// TODO: This whole function needs to be debugged.
+		$query  = pg_query('SELECT * FROM '.$this->escape_table($table).' LIMIT 1', $this->link);
+		$fields = pg_num_fields($query);
+		$table  = array();
+
+		for ($i=0; $i < $fields; $i++)
+		{
+			$table[$i]['type']  = pg_field_type($query, $i);
+			$table[$i]['name']  = pg_field_name($query, $i);
+			$table[$i]['len']   = pg_field_prtlen($query, $i);
+		}
+
+		return $table;
+	}
+
+} // End Database_Pgsql_Driver Class
+
+/**
+ * PostgreSQL Result
+ */
+class Pgsql_Result extends Database_Result {
+
+	// Data fetching types
+	protected $fetch_type  = 'pgsql_fetch_object';
+	protected $return_type = PGSQL_ASSOC;
+
+	/**
+	 * Sets up the result variables.
+	 *
+	 * @param  resource  query result
+	 * @param  resource  database link
+	 * @param  boolean   return objects or arrays
+	 * @param  string    SQL query that was run
+	 */
+	public function __construct($result, $link, $object = TRUE, $sql)
+	{
+		$this->result = $result;
+
+		// If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
+		if (is_resource($result))
+		{
+			// Its an DELETE, INSERT, REPLACE, or UPDATE query
+			if (preg_match('/^(?:delete|insert|replace|update)\b/iD', trim($sql), $matches))
+			{
+				$this->insert_id  = (strtolower($matches[0]) == 'insert') ? $this->insert_id() : FALSE;
+				$this->total_rows = pg_affected_rows($this->result);
+			}
+			else
+			{
+				$this->current_row = 0;
+				$this->total_rows  = pg_num_rows($this->result);
+				$this->fetch_type = ($object === TRUE) ? 'pg_fetch_object' : 'pg_fetch_array';
+			}
+		}
+		else
+		{
+			throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql);
+		}
+
+		// Set result type
+		$this->result($object);
+
+		// Store the SQL
+		$this->sql = $sql;
+	}
+
+	/**
+	 * Magic __destruct function, frees the result.
+	 */
+	public function __destruct()
+	{
+		if (is_resource($this->result))
+		{
+			pg_free_result($this->result);
+		}
+	}
+
+	public function result($object = TRUE, $type = PGSQL_ASSOC)
+	{
+		$this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array';
+
+		// This check has to be outside the previous statement, because we do not
+		// know the state of fetch_type when $object = NULL
+		// NOTE - The class set by $type must be defined before fetching the result,
+		// autoloading is disabled to save a lot of stupid overhead.
+		if ($this->fetch_type == 'pg_fetch_object')
+		{
+			$this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+		}
+		else
+		{
+			$this->return_type = $type;
+		}
+
+		return $this;
+	}
+
+	public function as_array($object = NULL, $type = PGSQL_ASSOC)
+	{
+		return $this->result_array($object, $type);
+	}
+
+	public function result_array($object = NULL, $type = PGSQL_ASSOC)
+	{
+		$rows = array();
+
+		if (is_string($object))
+		{
+			$fetch = $object;
+		}
+		elseif (is_bool($object))
+		{
+			if ($object === TRUE)
+			{
+				$fetch = 'pg_fetch_object';
+
+				// NOTE - The class set by $type must be defined before fetching the result,
+				// autoloading is disabled to save a lot of stupid overhead.
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+			else
+			{
+				$fetch = 'pg_fetch_array';
+			}
+		}
+		else
+		{
+			// Use the default config values
+			$fetch = $this->fetch_type;
+
+			if ($fetch == 'pg_fetch_object')
+			{
+				$type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
+			}
+		}
+
+		while ($row = $fetch($this->result, NULL, $type))
+		{
+			$rows[] = $row;
+		}
+
+		return $rows;
+	}
+
+	public function insert_id()
+	{
+		if ($this->insert_id === NULL)
+		{
+			$query = 'SELECT LASTVAL() AS insert_id';
+
+			// Disable error reporting for this, just to silence errors on
+			// tables that have no serial column.
+			$ER = error_reporting(0);
+
+			$result = pg_query($query);
+			$insert_id = pg_fetch_array($result, NULL, PGSQL_ASSOC);
+
+			$this->insert_id = $insert_id['insert_id'];
+
+			// Reset error reporting
+			error_reporting($ER);
+		}
+
+		return $this->insert_id;
+	}
+
+	public function seek($offset)
+	{
+		if ( ! $this->offsetExists($offset))
+			return FALSE;
+
+		return pg_result_seek($this->result, $offset);
+	}
+
+	public function list_fields()
+	{
+		$field_names = array();
+		while ($field = pg_field_name($this->result))
+		{
+			$field_names[] = $field->name;
+		}
+
+		return $field_names;
+	}
+
+	/**
+	 * ArrayAccess: offsetGet
+	 */
+	public function offsetGet($offset)
+	{
+		if ( ! $this->seek($offset))
+			return FALSE;
+
+		// Return the row by calling the defined fetching callback
+		$fetch = $this->fetch_type;
+		return $fetch($this->result, NULL, $this->return_type);
+	}
+
+} // End Pgsql_Result Class
+
+/**
+ * PostgreSQL Prepared Statement (experimental)
+ */
+class Kohana_Pgsql_Statement {
+
+	protected $link = NULL;
+	protected $stmt;
+
+	public function __construct($sql, $link)
+	{
+		$this->link = $link;
+
+		$this->stmt = $this->link->prepare($sql);
+
+		return $this;
+	}
+
+	public function __destruct()
+	{
+		$this->stmt->close();
+	}
+
+	// Sets the bind parameters
+	public function bind_params()
+	{
+		$argv = func_get_args();
+		return $this;
+	}
+
+	// sets the statement values to the bound parameters
+	public function set_vals()
+	{
+		return $this;
+	}
+
+	// Runs the statement
+	public function execute()
+	{
+		return $this;
+	}
+}
\ No newline at end of file
diff --git a/system/libraries/drivers/Image.php b/system/libraries/drivers/Image.php
new file mode 100755
index 0000000..4c5ccab
--- /dev/null
+++ b/system/libraries/drivers/Image.php
@@ -0,0 +1,149 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Image API driver.
+ *
+ * $Id: Image.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+abstract class Image_Driver {
+
+	// Reference to the current image
+	protected $image;
+
+	// Reference to the temporary processing image
+	protected $tmp_image;
+
+	// Processing errors
+	protected $errors = array();
+
+	/**
+	 * Executes a set of actions, defined in pairs.
+	 *
+	 * @param   array    actions
+	 * @return  boolean
+	 */
+	public function execute($actions)
+	{
+		foreach ($actions as $func => $args)
+		{
+			if ( ! $this->$func($args))
+				return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	/**
+	 * Sanitize and normalize a geometry array based on the temporary image
+	 * width and height. Valid properties are: width, height, top, left.
+	 *
+	 * @param   array  geometry properties
+	 * @return  void
+	 */
+	protected function sanitize_geometry( & $geometry)
+	{
+		list($width, $height) = $this->properties();
+
+		// Turn off error reporting
+		$reporting = error_reporting(0);
+
+		// Width and height cannot exceed current image size
+		$geometry['width']  = min($geometry['width'], $width);
+		$geometry['height'] = min($geometry['height'], $height);
+
+		// Set standard coordinates if given, otherwise use pixel values
+		if ($geometry['top'] === 'center')
+		{
+			$geometry['top'] = floor(($height / 2) - ($geometry['height'] / 2));
+		}
+		elseif ($geometry['top'] === 'top')
+		{
+			$geometry['top'] = 0;
+		}
+		elseif ($geometry['top'] === 'bottom')
+		{
+			$geometry['top'] = $height - $geometry['height'];
+		}
+
+		// Set standard coordinates if given, otherwise use pixel values
+		if ($geometry['left'] === 'center')
+		{
+			$geometry['left'] = floor(($width / 2) - ($geometry['width'] / 2));
+		}
+		elseif ($geometry['left'] === 'left')
+		{
+			$geometry['left'] = 0;
+		}
+		elseif ($geometry['left'] === 'right')
+		{
+			$geometry['left'] = $width - $geometry['height'];
+		}
+
+		// Restore error reporting
+		error_reporting($reporting);
+	}
+
+	/**
+	 * Return the current width and height of the temporary image. This is mainly
+	 * needed for sanitizing the geometry.
+	 *
+	 * @return  array  width, height
+	 */
+	abstract protected function properties();
+
+	/**
+	 * Process an image with a set of actions.
+	 *
+	 * @param   string   image filename
+	 * @param   array    actions to execute
+	 * @param   string   destination directory path
+	 * @param   string   destination filename
+	 * @return  boolean
+	 */
+	abstract public function process($image, $actions, $dir, $file);
+
+	/**
+	 * Flip an image. Valid directions are horizontal and vertical.
+	 *
+	 * @param   integer   direction to flip
+	 * @return  boolean
+	 */
+	abstract function flip($direction);
+
+	/**
+	 * Crop an image. Valid properties are: width, height, top, left.
+	 *
+	 * @param   array     new properties
+	 * @return  boolean
+	 */
+	abstract function crop($properties);
+
+	/**
+	 * Resize an image. Valid properties are: width, height, and master.
+	 *
+	 * @param   array     new properties
+	 * @return  boolean
+	 */
+	abstract public function resize($properties);
+
+	/**
+	 * Rotate an image. Valid amounts are -180 to 180.
+	 *
+	 * @param   integer   amount to rotate
+	 * @return  boolean
+	 */
+	abstract public function rotate($amount);
+
+	/**
+	 * Sharpen and image. Valid amounts are 1 to 100.
+	 *
+	 * @param   integer  amount to sharpen
+	 * @return  boolean
+	 */
+	abstract public function sharpen($amount);
+
+} // End Image Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Image/GD.php b/system/libraries/drivers/Image/GD.php
new file mode 100755
index 0000000..e6c18d4
--- /dev/null
+++ b/system/libraries/drivers/Image/GD.php
@@ -0,0 +1,379 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GD Image Driver.
+ *
+ * $Id: GD.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Image_GD_Driver extends Image_Driver {
+
+	// A transparent PNG as a string
+	protected static $blank_png;
+	protected static $blank_png_width;
+	protected static $blank_png_height;
+
+	public function __construct()
+	{
+		// Make sure that GD2 is available
+		if ( ! function_exists('gd_info'))
+			throw new Kohana_Exception('image.gd.requires_v2');
+
+		// Get the GD information
+		$info = gd_info();
+
+		// Make sure that the GD2 is installed
+		if (strpos($info['GD Version'], '2.') === FALSE)
+			throw new Kohana_Exception('image.gd.requires_v2');
+	}
+
+	public function process($image, $actions, $dir, $file, $render = FALSE)
+	{
+		// Set the "create" function
+		switch ($image['type'])
+		{
+			case IMAGETYPE_JPEG:
+				$create = 'imagecreatefromjpeg';
+			break;
+			case IMAGETYPE_GIF:
+				$create = 'imagecreatefromgif';
+			break;
+			case IMAGETYPE_PNG:
+				$create = 'imagecreatefrompng';
+			break;
+		}
+
+		// Set the "save" function
+		switch (strtolower(substr(strrchr($file, '.'), 1)))
+		{
+			case 'jpg':
+			case 'jpeg':
+				$save = 'imagejpeg';
+			break;
+			case 'gif':
+				$save = 'imagegif';
+			break;
+			case 'png':
+				$save = 'imagepng';
+			break;
+		}
+
+		// Make sure the image type is supported for import
+		if (empty($create) OR ! function_exists($create))
+			throw new Kohana_Exception('image.type_not_allowed', $image['file']);
+
+		// Make sure the image type is supported for saving
+		if (empty($save) OR ! function_exists($save))
+			throw new Kohana_Exception('image.type_not_allowed', $dir.$file);
+
+		// Load the image
+		$this->image = $image;
+
+		// Create the GD image resource
+		$this->tmp_image = $create($image['file']);
+
+		// Get the quality setting from the actions
+		$quality = arr::remove('quality', $actions);
+
+		if ($status = $this->execute($actions))
+		{
+			// Prevent the alpha from being lost
+			imagealphablending($this->tmp_image, TRUE);
+			imagesavealpha($this->tmp_image, TRUE);
+
+			switch ($save)
+			{
+				case 'imagejpeg':
+					// Default the quality to 95
+					($quality === NULL) and $quality = 95;
+				break;
+				case 'imagegif':
+					// Remove the quality setting, GIF doesn't use it
+					unset($quality);
+				break;
+				case 'imagepng':
+					// Always use a compression level of 9 for PNGs. This does not
+					// affect quality, it only increases the level of compression!
+					$quality = 9;
+				break;
+			}
+
+			if ($render === FALSE)
+			{
+				// Set the status to the save return value, saving with the quality requested
+				$status = isset($quality) ? $save($this->tmp_image, $dir.$file, $quality) : $save($this->tmp_image, $dir.$file);
+			}
+			else
+			{
+				// Output the image directly to the browser
+				switch ($save)
+				{
+					case 'imagejpeg':
+						header('Content-Type: image/jpeg');
+					break;
+					case 'imagegif':
+						header('Content-Type: image/gif');
+					break;
+					case 'imagepng':
+						header('Content-Type: image/png');
+					break;
+				}
+
+				$status = isset($quality) ? $save($this->tmp_image, NULL, $quality) : $save($this->tmp_image);
+			}
+
+			// Destroy the temporary image
+			imagedestroy($this->tmp_image);
+		}
+
+		return $status;
+	}
+
+	public function flip($direction)
+	{
+		// Get the current width and height
+		$width = imagesx($this->tmp_image);
+		$height = imagesy($this->tmp_image);
+
+		// Create the flipped image
+		$flipped = $this->imagecreatetransparent($width, $height);
+
+		if ($direction === Image::HORIZONTAL)
+		{
+			for ($x = 0; $x < $width; $x++)
+			{
+				$status = imagecopy($flipped, $this->tmp_image, $x, 0, $width - $x - 1, 0, 1, $height);
+			}
+		}
+		elseif ($direction === Image::VERTICAL)
+		{
+			for ($y = 0; $y < $height; $y++)
+			{
+				$status = imagecopy($flipped, $this->tmp_image, 0, $y, 0, $height - $y - 1, $width, 1);
+			}
+		}
+		else
+		{
+			// Do nothing
+			return TRUE;
+		}
+
+		if ($status === TRUE)
+		{
+			// Swap the new image for the old one
+			imagedestroy($this->tmp_image);
+			$this->tmp_image = $flipped;
+		}
+
+		return $status;
+	}
+
+	public function crop($properties)
+	{
+		// Sanitize the cropping settings
+		$this->sanitize_geometry($properties);
+
+		// Get the current width and height
+		$width = imagesx($this->tmp_image);
+		$height = imagesy($this->tmp_image);
+
+		// Create the temporary image to copy to
+		$img = $this->imagecreatetransparent($properties['width'], $properties['height']);
+
+		// Execute the crop
+		if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, $properties['left'], $properties['top'], $width, $height, $width, $height))
+		{
+			// Swap the new image for the old one
+			imagedestroy($this->tmp_image);
+			$this->tmp_image = $img;
+		}
+
+		return $status;
+	}
+
+	public function resize($properties)
+	{
+		// Get the current width and height
+		$width = imagesx($this->tmp_image);
+		$height = imagesy($this->tmp_image);
+
+		if (substr($properties['width'], -1) === '%')
+		{
+			// Recalculate the percentage to a pixel size
+			$properties['width'] = round($width * (substr($properties['width'], 0, -1) / 100));
+		}
+
+		if (substr($properties['height'], -1) === '%')
+		{
+			// Recalculate the percentage to a pixel size
+			$properties['height'] = round($height * (substr($properties['height'], 0, -1) / 100));
+		}
+		
+		// Recalculate the width and height, if they are missing
+		empty($properties['width'])  and $properties['width']  = round($width * $properties['height'] / $height);
+		empty($properties['height']) and $properties['height'] = round($height * $properties['width'] / $width);
+		
+		if ($properties['master'] === Image::AUTO)
+		{
+			// Change an automatic master dim to the correct type
+			$properties['master'] = (($width / $properties['width']) > ($height / $properties['height'])) ? Image::WIDTH : Image::HEIGHT;
+		}
+
+		if (empty($properties['height']) OR $properties['master'] === Image::WIDTH)
+		{
+			// Recalculate the height based on the width
+			$properties['height'] = round($height * $properties['width'] / $width);
+		}
+
+		if (empty($properties['width']) OR $properties['master'] === Image::HEIGHT)
+		{
+			// Recalculate the width based on the height
+			$properties['width'] = round($width * $properties['height'] / $height);
+		}
+
+		// Test if we can do a resize without resampling to speed up the final resize
+		if ($properties['width'] > $width / 2 AND $properties['height'] > $height / 2)
+		{
+			// Presize width and height
+			$pre_width = $width;
+			$pre_height = $height;
+
+			// The maximum reduction is 10% greater than the final size
+			$max_reduction_width  = round($properties['width']  * 1.1);
+			$max_reduction_height = round($properties['height'] * 1.1);
+
+			// Reduce the size using an O(2n) algorithm, until it reaches the maximum reduction
+			while ($pre_width / 2 > $max_reduction_width AND $pre_height / 2 > $max_reduction_height)
+			{
+				$pre_width /= 2;
+				$pre_height /= 2;
+			}
+
+			// Create the temporary image to copy to
+			$img = $this->imagecreatetransparent($pre_width, $pre_height);
+
+			if ($status = imagecopyresized($img, $this->tmp_image, 0, 0, 0, 0, $pre_width, $pre_height, $width, $height))
+			{
+				// Swap the new image for the old one
+				imagedestroy($this->tmp_image);
+				$this->tmp_image = $img;
+			}
+
+			// Set the width and height to the presize
+			$width  = $pre_width;
+			$height = $pre_height;
+		}
+
+		// Create the temporary image to copy to
+		$img = $this->imagecreatetransparent($properties['width'], $properties['height']);
+
+		// Execute the resize
+		if ($status = imagecopyresampled($img, $this->tmp_image, 0, 0, 0, 0, $properties['width'], $properties['height'], $width, $height))
+		{
+			// Swap the new image for the old one
+			imagedestroy($this->tmp_image);
+			$this->tmp_image = $img;
+		}
+
+		return $status;
+	}
+
+	public function rotate($amount)
+	{
+		// Use current image to rotate
+		$img = $this->tmp_image;
+
+		// White, with an alpha of 0
+		$transparent = imagecolorallocatealpha($img, 255, 255, 255, 127);
+
+		// Rotate, setting the transparent color
+		$img = imagerotate($img, 360 - $amount, $transparent, -1);
+
+		// Fill the background with the transparent "color"
+		imagecolortransparent($img, $transparent);
+
+		// Merge the images
+		if ($status = imagecopymerge($this->tmp_image, $img, 0, 0, 0, 0, imagesx($this->tmp_image), imagesy($this->tmp_image), 100))
+		{
+			// Prevent the alpha from being lost
+			imagealphablending($img, TRUE);
+			imagesavealpha($img, TRUE);
+
+			// Swap the new image for the old one
+			imagedestroy($this->tmp_image);
+			$this->tmp_image = $img;
+		}
+
+		return $status;
+	}
+
+	public function sharpen($amount)
+	{
+		// Make sure that the sharpening function is available
+		if ( ! function_exists('imageconvolution'))
+			throw new Kohana_Exception('image.unsupported_method', __FUNCTION__);
+
+		// Amount should be in the range of 18-10
+		$amount = round(abs(-18 + ($amount * 0.08)), 2);
+
+		// Gaussian blur matrix
+		$matrix = array
+		(
+			array(-1,   -1,    -1),
+			array(-1, $amount, -1),
+			array(-1,   -1,    -1),
+		);
+
+		// Perform the sharpen
+		return imageconvolution($this->tmp_image, $matrix, $amount - 8, 0);
+	}
+
+	protected function properties()
+	{
+		return array(imagesx($this->tmp_image), imagesy($this->tmp_image));
+	}
+
+	/**
+	 * Returns an image with a transparent background. Used for rotating to
+	 * prevent unfilled backgrounds.
+	 *
+	 * @param   integer  image width
+	 * @param   integer  image height
+	 * @return  resource
+	 */
+	protected function imagecreatetransparent($width, $height)
+	{
+		if (self::$blank_png === NULL)
+		{
+			// Decode the blank PNG if it has not been done already
+			self::$blank_png = imagecreatefromstring(base64_decode
+			(
+				'iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29'.
+				'mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADqSURBVHjaYvz//z/DYAYAAcTEMMgBQAANegcCBN'.
+				'CgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQ'.
+				'AANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoH'.
+				'AgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAAA16BwIE0KB'.
+				'3IEAADXoHAgTQoHcgQAANegcCBNCgdyBAgAEAMpcDTTQWJVEAAAAASUVORK5CYII='
+			));
+
+			// Set the blank PNG width and height
+			self::$blank_png_width = imagesx(self::$blank_png);
+			self::$blank_png_height = imagesy(self::$blank_png);
+		}
+
+		$img = imagecreatetruecolor($width, $height);
+
+		// Resize the blank image
+		imagecopyresized($img, self::$blank_png, 0, 0, 0, 0, $width, $height, self::$blank_png_width, self::$blank_png_height);
+
+		// Prevent the alpha from being lost
+		imagealphablending($img, FALSE);
+		imagesavealpha($img, TRUE);
+
+		return $img;
+	}
+
+} // End Image GD Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Image/GraphicsMagick.php b/system/libraries/drivers/Image/GraphicsMagick.php
new file mode 100755
index 0000000..8840eb8
--- /dev/null
+++ b/system/libraries/drivers/Image/GraphicsMagick.php
@@ -0,0 +1,211 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * GraphicsMagick Image Driver.
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Image_GraphicsMagick_Driver extends Image_Driver {
+
+	// Directory that GM is installed in
+	protected $dir = '';
+
+	// Command extension (exe for windows)
+	protected $ext = '';
+
+	// Temporary image filename
+	protected $tmp_image;
+
+	/**
+	 * Attempts to detect the GraphicsMagick installation directory.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   array   configuration
+	 * @return  void
+	 */
+	public function __construct($config)
+	{
+		if (empty($config['directory']))
+		{
+			// Attempt to locate GM by using "which" (only works for *nix!)
+			if ( ! is_file($path = exec('which gm')))
+				throw new Kohana_Exception('image.graphicsmagick.not_found');
+
+			$config['directory'] = dirname($path);
+		}
+
+		// Set the command extension
+		$this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
+
+		// Check to make sure the provided path is correct
+		if ( ! is_file(realpath($config['directory']).'/gm'.$this->ext))
+			throw new Kohana_Exception('image.graphicsmagick.not_found', 'gm'.$this->ext);
+
+
+		// Set the installation directory
+		$this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+	}
+
+	/**
+	 * Creates a temporary image and executes the given actions. By creating a
+	 * temporary copy of the image before manipulating it, this process is atomic.
+	 */
+	public function process($image, $actions, $dir, $file, $render = FALSE)
+	{
+		// We only need the filename
+		$image = $image['file'];
+
+		// Unique temporary filename
+		$this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
+
+		// Copy the image to the temporary file
+		copy($image, $this->tmp_image);
+
+		// Quality change is done last
+		$quality = (int) arr::remove('quality', $actions);
+
+		// Use 95 for the default quality
+		empty($quality) and $quality = 95;
+
+		// All calls to these will need to be escaped, so do it now
+		$this->cmd_image = escapeshellarg($this->tmp_image);
+		$this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file);
+
+		if ($status = $this->execute($actions))
+		{
+			// Use convert to change the image into its final version. This is
+			// done to allow the file type to change correctly, and to handle
+			// the quality conversion in the most effective way possible.
+			if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+			{
+				$this->errors[] = $error;
+			}
+			else
+			{
+				// Output the image directly to the browser
+				if ($render !== FALSE)
+				{
+					$contents = file_get_contents($this->tmp_image);
+					switch (substr($file, strrpos($file, '.') + 1))
+					{
+						case 'jpg':
+						case 'jpeg':
+							header('Content-Type: image/jpeg');
+						break;
+						case 'gif':
+							header('Content-Type: image/gif');
+						break;
+						case 'png':
+							header('Content-Type: image/png');
+						break;
+ 					}
+					echo $contents;
+				}
+			}
+		}
+
+		// Remove the temporary image
+		unlink($this->tmp_image);
+		$this->tmp_image = '';
+
+		return $status;
+	}
+
+	public function crop($prop)
+	{
+		// Sanitize and normalize the properties into geometry
+		$this->sanitize_geometry($prop);
+
+		// Set the IM geometry based on the properties
+		$geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
+
+		if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	public function flip($dir)
+	{
+		// Convert the direction into a GM command
+		$dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
+
+		if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	public function resize($prop)
+	{
+		switch ($prop['master'])
+		{
+			case Image::WIDTH:  // Wx
+				$dim = escapeshellarg($prop['width'].'x');
+			break;
+			case Image::HEIGHT: // xH
+				$dim = escapeshellarg('x'.$prop['height']);
+			break;
+			case Image::AUTO:   // WxH
+				$dim = escapeshellarg($prop['width'].'x'.$prop['height']);
+			break;
+			case Image::NONE:   // WxH!
+				$dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
+			break;
+		}
+
+		// Use "convert" to change the width and height
+		if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	public function rotate($amt)
+	{
+		if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	public function sharpen($amount)
+	{
+		// Set the sigma, radius, and amount. The amount formula allows a nice
+		// spread between 1 and 100 without pixelizing the image badly.
+		$sigma  = 0.5;
+		$radius = $sigma * 2;
+		$amount = round(($amount / 80) * 3.14, 2);
+
+		// Convert the amount to an GM command
+		$sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
+
+		if ($error = exec(escapeshellcmd($this->dir.'gm'.$this->ext.' convert').' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	protected function properties()
+	{
+		return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE);
+	}
+
+} // End Image GraphicsMagick Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Image/ImageMagick.php b/system/libraries/drivers/Image/ImageMagick.php
new file mode 100755
index 0000000..4d19a38
--- /dev/null
+++ b/system/libraries/drivers/Image/ImageMagick.php
@@ -0,0 +1,212 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * ImageMagick Image Driver.
+ *
+ * $Id: ImageMagick.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Image
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Image_ImageMagick_Driver extends Image_Driver {
+
+	// Directory that IM is installed in
+	protected $dir = '';
+
+	// Command extension (exe for windows)
+	protected $ext = '';
+
+	// Temporary image filename
+	protected $tmp_image;
+
+	/**
+	 * Attempts to detect the ImageMagick installation directory.
+	 *
+	 * @throws  Kohana_Exception
+	 * @param   array   configuration
+	 * @return  void
+	 */
+	public function __construct($config)
+	{
+		if (empty($config['directory']))
+		{
+			// Attempt to locate IM by using "which" (only works for *nix!)
+			if ( ! is_file($path = exec('which convert')))
+				throw new Kohana_Exception('image.imagemagick.not_found');
+
+			$config['directory'] = dirname($path);
+		}
+
+		// Set the command extension
+		$this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
+
+		// Check to make sure the provided path is correct
+		if ( ! is_file(realpath($config['directory']).'/convert'.$this->ext))
+			throw new Kohana_Exception('image.imagemagick.not_found', 'convert'.$this->ext);
+
+		// Set the installation directory
+		$this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
+	}
+
+	/**
+	 * Creates a temporary image and executes the given actions. By creating a
+	 * temporary copy of the image before manipulating it, this process is atomic.
+	 */
+	public function process($image, $actions, $dir, $file, $render = FALSE)
+	{
+		// We only need the filename
+		$image = $image['file'];
+
+		// Unique temporary filename
+		$this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
+
+		// Copy the image to the temporary file
+		copy($image, $this->tmp_image);
+
+		// Quality change is done last
+		$quality = (int) arr::remove('quality', $actions);
+
+		// Use 95 for the default quality
+		empty($quality) and $quality = 95;
+
+		// All calls to these will need to be escaped, so do it now
+		$this->cmd_image = escapeshellarg($this->tmp_image);
+		$this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file);
+
+		if ($status = $this->execute($actions))
+		{
+			// Use convert to change the image into its final version. This is
+			// done to allow the file type to change correctly, and to handle
+			// the quality conversion in the most effective way possible.
+			if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
+			{
+				$this->errors[] = $error;
+			}
+			else
+			{
+				// Output the image directly to the browser
+				if ($render !== FALSE)
+				{
+					$contents = file_get_contents($this->tmp_image);
+					switch (substr($file, strrpos($file, '.') + 1))
+					{
+						case 'jpg':
+						case 'jpeg':
+							header('Content-Type: image/jpeg');
+						break;
+						case 'gif':
+							header('Content-Type: image/gif');
+						break;
+						case 'png':
+							header('Content-Type: image/png');
+						break;
+ 					}
+					echo $contents;
+				}
+			}
+		}
+
+		// Remove the temporary image
+		unlink($this->tmp_image);
+		$this->tmp_image = '';
+
+		return $status;
+	}
+
+	public function crop($prop)
+	{
+		// Sanitize and normalize the properties into geometry
+		$this->sanitize_geometry($prop);
+
+		// Set the IM geometry based on the properties
+		$geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
+
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	public function flip($dir)
+	{
+		// Convert the direction into a IM command
+		$dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
+
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	public function resize($prop)
+	{
+		switch ($prop['master'])
+		{
+			case Image::WIDTH:  // Wx
+				$dim = escapeshellarg($prop['width'].'x');
+			break;
+			case Image::HEIGHT: // xH
+				$dim = escapeshellarg('x'.$prop['height']);
+			break;
+			case Image::AUTO:   // WxH
+				$dim = escapeshellarg($prop['width'].'x'.$prop['height']);
+			break;
+			case Image::NONE:   // WxH!
+				$dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
+			break;
+		}
+
+		// Use "convert" to change the width and height
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	public function rotate($amt)
+	{
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	public function sharpen($amount)
+	{
+		// Set the sigma, radius, and amount. The amount formula allows a nice
+		// spread between 1 and 100 without pixelizing the image badly.
+		$sigma  = 0.5;
+		$radius = $sigma * 2;
+		$amount = round(($amount / 80) * 3.14, 2);
+
+		// Convert the amount to an IM command
+		$sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
+
+		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
+		{
+			$this->errors[] = $error;
+			return FALSE;
+		}
+
+		return TRUE;
+	}
+
+	protected function properties()
+	{
+		return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE);
+	}
+
+} // End Image ImageMagick Driver
\ No newline at end of file
diff --git a/system/libraries/drivers/Session.php b/system/libraries/drivers/Session.php
new file mode 100755
index 0000000..fd2cd61
--- /dev/null
+++ b/system/libraries/drivers/Session.php
@@ -0,0 +1,70 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session driver interface
+ *
+ * $Id: Session.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+interface Session_Driver {
+
+	/**
+	 * Opens a session.
+	 *
+	 * @param   string   save path
+	 * @param   string   session name
+	 * @return  boolean
+	 */
+	public function open($path, $name);
+
+	/**
+	 * Closes a session.
+	 *
+	 * @return  boolean
+	 */
+	public function close();
+
+	/**
+	 * Reads a session.
+	 *
+	 * @param   string  session id
+	 * @return  string
+	 */
+	public function read($id);
+
+	/**
+	 * Writes a session.
+	 *
+	 * @param   string   session id
+	 * @param   string   session data
+	 * @return  boolean
+	 */
+	public function write($id, $data);
+
+	/**
+	 * Destroys a session.
+	 *
+	 * @param   string   session id
+	 * @return  boolean
+	 */
+	public function destroy($id);
+
+	/**
+	 * Regenerates the session id.
+	 *
+	 * @return  string
+	 */
+	public function regenerate();
+
+	/**
+	 * Garbage collection.
+	 *
+	 * @param   integer  session expiration period
+	 * @return  boolean
+	 */
+	public function gc($maxlifetime);
+
+} // End Session Driver Interface
\ No newline at end of file
diff --git a/system/libraries/drivers/Session/Cache.php b/system/libraries/drivers/Session/Cache.php
new file mode 100755
index 0000000..1edcd18
--- /dev/null
+++ b/system/libraries/drivers/Session/Cache.php
@@ -0,0 +1,105 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cache driver.
+ *
+ * Cache library config goes in the session.storage config entry:
+ * $config['storage'] = array(
+ *     'driver' => 'apc',
+ *     'requests' => 10000
+ * );
+ * Lifetime does not need to be set as it is
+ * overridden by the session expiration setting.
+ *
+ * $Id: Cache.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Session_Cache_Driver implements Session_Driver {
+
+	protected $cache;
+	protected $encrypt;
+
+	public function __construct()
+	{
+		// Load Encrypt library
+		if (Kohana::config('session.encryption'))
+		{
+			$this->encrypt = new Encrypt;
+		}
+
+		Kohana::log('debug', 'Session Cache Driver Initialized');
+	}
+
+	public function open($path, $name)
+	{
+		$config = Kohana::config('session.storage');
+
+		if (empty($config))
+		{
+			// Load the default group
+			$config = Kohana::config('cache.default');
+		}
+		elseif (is_string($config))
+		{
+			$name = $config;
+
+			// Test the config group name
+			if (($config = Kohana::config('cache.'.$config)) === NULL)
+				throw new Kohana_Exception('cache.undefined_group', $name);
+		}
+
+		$config['lifetime'] = (Kohana::config('session.expiration') == 0) ? 86400 : Kohana::config('session.expiration');
+		$this->cache = new Cache($config);
+
+		return is_object($this->cache);
+	}
+
+	public function close()
+	{
+		return TRUE;
+	}
+
+	public function read($id)
+	{
+		$id = 'session_'.$id;
+		if ($data = $this->cache->get($id))
+		{
+			return Kohana::config('session.encryption') ? $this->encrypt->decode($data) : $data;
+		}
+
+		// Return value must be string, NOT a boolean
+		return '';
+	}
+
+	public function write($id, $data)
+	{
+		$id = 'session_'.$id;
+		$data = Kohana::config('session.encryption') ? $this->encrypt->encode($data) : $data;
+
+		return $this->cache->set($id, $data);
+	}
+
+	public function destroy($id)
+	{
+		$id = 'session_'.$id;
+		return $this->cache->delete($id);
+	}
+
+	public function regenerate()
+	{
+		session_regenerate_id(TRUE);
+
+		// Return new session id
+		return session_id();
+	}
+
+	public function gc($maxlifetime)
+	{
+		// Just return, caches are automatically cleaned up
+		return TRUE;
+	}
+
+} // End Session Cache Driver
diff --git a/system/libraries/drivers/Session/Cookie.php b/system/libraries/drivers/Session/Cookie.php
new file mode 100755
index 0000000..0cec5dd
--- /dev/null
+++ b/system/libraries/drivers/Session/Cookie.php
@@ -0,0 +1,80 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session cookie driver.
+ *
+ * $Id: Cookie.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Session_Cookie_Driver implements Session_Driver {
+
+	protected $cookie_name;
+	protected $encrypt; // Library
+
+	public function __construct()
+	{
+		$this->cookie_name = Kohana::config('session.name').'_data';
+
+		if (Kohana::config('session.encryption'))
+		{
+			$this->encrypt = Encrypt::instance();
+		}
+
+		Kohana::log('debug', 'Session Cookie Driver Initialized');
+	}
+
+	public function open($path, $name)
+	{
+		return TRUE;
+	}
+
+	public function close()
+	{
+		return TRUE;
+	}
+
+	public function read($id)
+	{
+		$data = (string) cookie::get($this->cookie_name);
+
+		if ($data == '')
+			return $data;
+
+		return empty($this->encrypt) ? base64_decode($data) : $this->encrypt->decode($data);
+	}
+
+	public function write($id, $data)
+	{
+		$data = empty($this->encrypt) ? base64_encode($data) : $this->encrypt->encode($data);
+
+		if (strlen($data) > 4048)
+		{
+			Kohana::log('error', 'Session ('.$id.') data exceeds the 4KB limit, ignoring write.');
+			return FALSE;
+		}
+
+		return cookie::set($this->cookie_name, $data, Kohana::config('session.expiration'));
+	}
+
+	public function destroy($id)
+	{
+		return cookie::delete($this->cookie_name);
+	}
+
+	public function regenerate()
+	{
+		session_regenerate_id(TRUE);
+
+		// Return new id
+		return session_id();
+	}
+
+	public function gc($maxlifetime)
+	{
+		return TRUE;
+	}
+
+} // End Session Cookie Driver Class
\ No newline at end of file
diff --git a/system/libraries/drivers/Session/Database.php b/system/libraries/drivers/Session/Database.php
new file mode 100755
index 0000000..9ed3ba3
--- /dev/null
+++ b/system/libraries/drivers/Session/Database.php
@@ -0,0 +1,166 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Session database driver.
+ *
+ * $Id: Database.php 3917 2009-01-21 03:06:22Z zombor $
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007-2008 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+class Session_Database_Driver implements Session_Driver {
+
+	/*
+	CREATE TABLE sessions
+	(
+		session_id VARCHAR(127) NOT NULL,
+		last_activity INT(10) UNSIGNED NOT NULL,
+		data TEXT NOT NULL,
+		PRIMARY KEY (session_id)
+	);
+	*/
+
+	// Database settings
+	protected $db = 'default';
+	protected $table = 'sessions';
+
+	// Encryption
+	protected $encrypt;
+
+	// Session settings
+	protected $session_id;
+	protected $written = FALSE;
+
+	public function __construct()
+	{
+		// Load configuration
+		$config = Kohana::config('session');
+
+		if ( ! empty($config['encryption']))
+		{
+			// Load encryption
+			$this->encrypt = Encrypt::instance();
+		}
+
+		if (is_array($config['storage']))
+		{
+			if ( ! empty($config['storage']['group']))
+			{
+				// Set the group name
+				$this->db = $config['storage']['group'];
+			}
+
+			if ( ! empty($config['storage']['table']))
+			{
+				// Set the table name
+				$this->table = $config['storage']['table'];
+			}
+		}
+
+		// Load database
+		$this->db = Database::instance($this->db);
+
+		Kohana::log('debug', 'Session Database Driver Initialized');
+	}
+
+	public function open($path, $name)
+	{
+		return TRUE;
+	}
+
+	public function close()
+	{
+		return TRUE;
+	}
+
+	public function read($id)
+	{
+		// Load the session
+		$query = $this->db->from($this->table)->where('session_id', $id)->limit(1)->get()->result(TRUE);
+
+		if ($query->count() === 0)
+		{
+			// No current session
+			$this->session_id = NULL;
+
+			return '';
+		}
+
+		// Set the current session id
+		$this->session_id = $id;
+
+		// Load the data
+		$data = $query->current()->data;
+
+		return ($this->encrypt === NULL) ? base64_decode($data) : $this->encrypt->decode($data);
+	}
+
+	public function write($id, $data)
+	{
+		$this->db->push();
+		$data = array
+		(
+			'session_id' => $id,
+			'last_activity' => time(),
+			'data' => ($this->encrypt === NULL) ? base64_encode($data) : $this->encrypt->encode($data)
+		);
+
+		if ($this->session_id === NULL)
+		{
+			// Insert a new session
+			$query = $this->db->insert($this->table, $data);
+		}
+		elseif ($id === $this->session_id)
+		{
+			// Do not update the session_id
+			unset($data['session_id']);
+
+			// Update the existing session
+			$query = $this->db->update($this->table, $data, array('session_id' => $id));
+		}
+		else
+		{
+			// Update the session and id
+			$query = $this->db->update($this->table, $data, array('session_id' => $this->session_id));
+
+			// Set the new session id
+			$this->session_id = $id;
+		}
+
+		$this->db->pop();
+
+		return (bool) $query->count();
+	}
+
+	public function destroy($id)
+	{
+		// Delete the requested session
+		$this->db->delete($this->table, array('session_id' => $id));
+
+		// Session id is no longer valid
+		$this->session_id = NULL;
+
+		return TRUE;
+	}
+
+	public function regenerate()
+	{
+		// Generate a new session id
+		session_regenerate_id();
+
+		// Return new session id
+		return session_id();
+	}
+
+	public function gc($maxlifetime)
+	{
+		// Delete all expired sessions
+		$query = $this->db->delete($this->table, array('last_activity <' => time() - $maxlifetime));
+
+		Kohana::log('debug', 'Session garbage collected: '.$query->count().' row(s) deleted.');
+
+		return TRUE;
+	}
+
+} // End Session Database Driver
diff --git a/system/vendor/Markdown.php b/system/vendor/Markdown.php
new file mode 100755
index 0000000..1f3e076
--- /dev/null
+++ b/system/vendor/Markdown.php
@@ -0,0 +1,2789 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+#
+# Markdown Extra  -  A text-to-HTML conversion tool for web writers
+#
+# PHP Markdown & Extra
+# Copyright (c) 2004-2007 Michel Fortin  
+# <http://www.michelf.com/projects/php-markdown/>
+#
+# Original Markdown
+# Copyright (c) 2004-2006 John Gruber  
+# <http://daringfireball.net/projects/markdown/>
+#
+
+
+define( 'MARKDOWN_VERSION',       "1.0.1h" ); # Fri 3 Aug 2007
+define( 'MARKDOWNEXTRA_VERSION',  "1.1.4" );  # Fri 3 Aug 2007
+
+
+#
+# Global default settings:
+#
+
+# Change to ">" for HTML output
+define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX',  " />");
+
+# Define the width of a tab for code blocks.
+define( 'MARKDOWN_TAB_WIDTH',     4 );
+
+# Optional title attribute for footnote links and backlinks.
+define( 'MARKDOWN_FN_LINK_TITLE',         "" );
+define( 'MARKDOWN_FN_BACKLINK_TITLE',     "" );
+
+# Optional class attribute for footnote links and backlinks.
+define( 'MARKDOWN_FN_LINK_CLASS',         "" );
+define( 'MARKDOWN_FN_BACKLINK_CLASS',     "" );
+
+
+#
+# WordPress settings:
+#
+
+# Change to false to remove Markdown from posts and/or comments.
+define( 'MARKDOWN_WP_POSTS',      true );
+define( 'MARKDOWN_WP_COMMENTS',   true );
+
+
+
+### Standard Function Interface ###
+
+define( 'MARKDOWN_PARSER_CLASS',  'MarkdownExtra_Parser' );
+
+function Markdown($text) {
+#
+# Initialize the parser and return the result of its transform method.
+#
+	# Setup static parser variable.
+	static $parser;
+	if (!isset($parser)) {
+		$parser_class = MARKDOWN_PARSER_CLASS;
+		$parser = new $parser_class;
+	}
+
+	# Transform text using parser.
+	return $parser->transform($text);
+}
+
+
+### WordPress Plugin Interface ###
+
+/*
+Plugin Name: Markdown Extra
+Plugin URI: http://www.michelf.com/projects/php-markdown/
+Description: <a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>
+Version: 1.1.4
+Author: Michel Fortin
+Author URI: http://www.michelf.com/
+*/
+
+if (isset($wp_version)) {
+	# More details about how it works here:
+	# <http://www.michelf.com/weblog/2005/wordpress-text-flow-vs-markdown/>
+	
+	# Post content and excerpts
+	# - Remove WordPress paragraph generator.
+	# - Run Markdown on excerpt, then remove all tags.
+	# - Add paragraph tag around the excerpt, but remove it for the excerpt rss.
+	if (MARKDOWN_WP_POSTS) {
+		remove_filter('the_content',     'wpautop');
+        remove_filter('the_content_rss', 'wpautop');
+		remove_filter('the_excerpt',     'wpautop');
+		add_filter('the_content',     'Markdown', 6);
+        add_filter('the_content_rss', 'Markdown', 6);
+		add_filter('get_the_excerpt', 'Markdown', 6);
+		add_filter('get_the_excerpt', 'trim', 7);
+		add_filter('the_excerpt',     'mdwp_add_p');
+		add_filter('the_excerpt_rss', 'mdwp_strip_p');
+		
+		remove_filter('content_save_pre',  'balanceTags', 50);
+		remove_filter('excerpt_save_pre',  'balanceTags', 50);
+		add_filter('the_content',  	  'balanceTags', 50);
+		add_filter('get_the_excerpt', 'balanceTags', 9);
+	}
+	
+	# Comments
+	# - Remove WordPress paragraph generator.
+	# - Remove WordPress auto-link generator.
+	# - Scramble important tags before passing them to the kses filter.
+	# - Run Markdown on excerpt then remove paragraph tags.
+	if (MARKDOWN_WP_COMMENTS) {
+		remove_filter('comment_text', 'wpautop', 30);
+		remove_filter('comment_text', 'make_clickable');
+		add_filter('pre_comment_content', 'Markdown', 6);
+		add_filter('pre_comment_content', 'mdwp_hide_tags', 8);
+		add_filter('pre_comment_content', 'mdwp_show_tags', 12);
+		add_filter('get_comment_text',    'Markdown', 6);
+		add_filter('get_comment_excerpt', 'Markdown', 6);
+		add_filter('get_comment_excerpt', 'mdwp_strip_p', 7);
+	
+		global $mdwp_hidden_tags, $mdwp_placeholders;
+		$mdwp_hidden_tags = explode(' ',
+			'<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>');
+		$mdwp_placeholders = explode(' ', str_rot13(
+			'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '.
+			'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli'));
+	}
+	
+	function mdwp_add_p($text) {
+		if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) {
+			$text = '<p>'.$text.'</p>';
+			$text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text);
+		}
+		return $text;
+	}
+	
+	function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); }
+
+	function mdwp_hide_tags($text) {
+		global $mdwp_hidden_tags, $mdwp_placeholders;
+		return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text);
+	}
+	function mdwp_show_tags($text) {
+		global $mdwp_hidden_tags, $mdwp_placeholders;
+		return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text);
+	}
+}
+
+
+### bBlog Plugin Info ###
+
+function identify_modifier_markdown() {
+	return array(
+		'name' => 'markdown',
+		'type' => 'modifier',
+		'nicename' => 'PHP Markdown Extra',
+		'description' => 'A text-to-HTML conversion tool for web writers',
+		'authors' => 'Michel Fortin and John Gruber',
+		'licence' => 'GPL',
+		'version' => MARKDOWNEXTRA_VERSION,
+		'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://www.michelf.com/projects/php-markdown/">More...</a>',
+		);
+}
+
+
+### Smarty Modifier Interface ###
+
+function smarty_modifier_markdown($text) {
+	return Markdown($text);
+}
+
+
+### Textile Compatibility Mode ###
+
+# Rename this file to "classTextile.php" and it can replace Textile everywhere.
+
+if (strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {
+	# Try to include PHP SmartyPants. Should be in the same directory.
+	@include_once 'smartypants.php';
+	# Fake Textile class. It calls Markdown instead.
+	class Textile {
+		function TextileThis($text, $lite='', $encode='') {
+			if ($lite == '' && $encode == '')    $text = Markdown($text);
+			if (function_exists('SmartyPants'))  $text = SmartyPants($text);
+			return $text;
+		}
+		# Fake restricted version: restrictions are not supported for now.
+		function TextileRestricted($text, $lite='', $noimage='') {
+			return $this->TextileThis($text, $lite);
+		}
+		# Workaround to ensure compatibility with TextPattern 4.0.3.
+		function blockLite($text) { return $text; }
+	}
+}
+
+
+
+#
+# Markdown Parser Class
+#
+
+class Markdown_Parser {
+
+	# Regex to match balanced [brackets].
+	# Needed to insert a maximum bracked depth while converting to PHP.
+	var $nested_brackets_depth = 6;
+	var $nested_brackets;
+	
+	var $nested_url_parenthesis_depth = 4;
+	var $nested_url_parenthesis;
+
+	# Table of hash values for escaped characters:
+	var $escape_chars = '\`*_{}[]()>#+-.!';
+//	var $escape_table = array();
+	var $backslash_escape_table = array();
+
+	# Change to ">" for HTML output.
+	var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX;
+	var $tab_width = MARKDOWN_TAB_WIDTH;
+	
+	# Change to `true` to disallow markup or entities.
+	var $no_markup = false;
+	var $no_entities = false;
+
+
+	function Markdown_Parser() {
+	#
+	# Constructor function. Initialize appropriate member variables.
+	#
+		$this->_initDetab();
+	
+		$this->nested_brackets = 
+			str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
+			str_repeat('\])*', $this->nested_brackets_depth);
+	
+		$this->nested_url_parenthesis = 
+			str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
+			str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);
+		
+		# Create an identical table but for escaped characters.
+		foreach (preg_split('/(?!^|$)/', $this->escape_chars) as $char) {
+			$entity = "&#". ord($char). ";";
+//			$this->escape_table[$char] = $entity;
+			$this->backslash_escape_table["\\$char"] = $entity;
+		}
+		
+		# Sort document, block, and span gamut in ascendent priority order.
+		asort($this->document_gamut);
+		asort($this->block_gamut);
+		asort($this->span_gamut);
+	}
+
+
+	# Internal hashes used during transformation.
+	var $urls = array();
+	var $titles = array();
+	var $html_blocks = array();
+	var $html_hashes = array(); # Contains both blocks and span hashes.
+	
+	# Status flag to avoid invalid nesting.
+	var $in_anchor = false;
+
+
+	function transform($text) {
+	#
+	# Main function. The order in which other subs are called here is
+	# essential. Link and image substitutions need to happen before
+	# _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
+	# and <img> tags get encoded.
+	#
+		# Clear the global hashes. If we don't clear these, you get conflicts
+		# from other articles when generating a page which contains more than
+		# one article (e.g. an index page that shows the N most recent
+		# articles):
+		$this->urls = array();
+		$this->titles = array();
+		$this->html_blocks = array();
+		$this->html_hashes = array();
+
+		# Standardize line endings:
+		#   DOS to Unix and Mac to Unix
+		$text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+		# Make sure $text ends with a couple of newlines:
+		$text .= "\n\n";
+
+		# Convert all tabs to spaces.
+		$text = $this->detab($text);
+
+		# Turn block-level HTML blocks into hash entries
+		$text = $this->hashHTMLBlocks($text);
+
+		# Strip any lines consisting only of spaces and tabs.
+		# This makes subsequent regexen easier to write, because we can
+		# match consecutive blank lines with /\n+/ instead of something
+		# contorted like /[ ]*\n+/ .
+		$text = preg_replace('/^[ ]+$/m', '', $text);
+
+		# Run document gamut methods.
+		foreach ($this->document_gamut as $method => $priority) {
+			$text = $this->$method($text);
+		}
+
+		return $text . "\n";
+	}
+	
+	var $document_gamut = array(
+		# Strip link definitions, store in hashes.
+		"stripLinkDefinitions" => 20,
+		
+		"runBasicBlockGamut"   => 30,
+		);
+
+
+	function stripLinkDefinitions($text) {
+	#
+	# Strips link definitions from text, stores the URLs and titles in
+	# hash references.
+	#
+		$less_than_tab = $this->tab_width - 1;
+
+		# Link defs are in the form: ^[id]: url "optional title"
+		$text = preg_replace_callback('{
+							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
+							  [ ]*
+							  \n?				# maybe *one* newline
+							  [ ]*
+							<?(\S+?)>?			# url = $2
+							  [ ]*
+							  \n?				# maybe one newline
+							  [ ]*
+							(?:
+								(?<=\s)			# lookbehind for whitespace
+								["(]
+								(.*?)			# title = $3
+								[")]
+								[ ]*
+							)?	# title is optional
+							(?:\n+|\Z)
+			}xm',
+			array(&$this, '_stripLinkDefinitions_callback'),
+			$text);
+		return $text;
+	}
+	function _stripLinkDefinitions_callback($matches) {
+		$link_id = strtolower($matches[1]);
+		$this->urls[$link_id] = $this->encodeAmpsAndAngles($matches[2]);
+		if (isset($matches[3]))
+			$this->titles[$link_id] = str_replace('"', '"', $matches[3]);
+		return ''; # String that will replace the block
+	}
+
+
+	function hashHTMLBlocks($text) {
+		if ($this->no_markup)  return $text;
+	
+		$less_than_tab = $this->tab_width - 1;
+
+		# Hashify HTML blocks:
+		# We only want to do this for block-level HTML tags, such as headers,
+		# lists, and tables. That's because we still want to wrap <p>s around
+		# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+		# phrase emphasis, and spans. The list of tags we're looking for is
+		# hard-coded:
+		$block_tags_a = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
+						'script|noscript|form|fieldset|iframe|math|ins|del';
+		$block_tags_b = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
+						'script|noscript|form|fieldset|iframe|math';
+
+		# Regular expression for the content of a block tag.
+		$nested_tags_level = 4;
+		$attr = '
+			(?>				# optional tag attributes
+			  \s			# starts with whitespace
+			  (?>
+				[^>"/]+		# text outside quotes
+			  |
+				/+(?!>)		# slash not followed by ">"
+			  |
+				"[^"]*"		# text inside double quotes (tolerate ">")
+			  |
+				\'[^\']*\'	# text inside single quotes (tolerate ">")
+			  )*
+			)?	
+			';
+		$content =
+			str_repeat('
+				(?>
+				  [^<]+			# content without tag
+				|
+				  <\2			# nested opening tag
+					'.$attr.'	# attributes
+					(?:
+					  />
+					|
+					  >', $nested_tags_level).	# end of opening tag
+					  '.*?'.					# last level nested tag content
+			str_repeat('
+					  </\2\s*>	# closing nested tag
+					)
+				  |				
+					<(?!/\2\s*>	# other tags with a different name
+				  )
+				)*',
+				$nested_tags_level);
+
+		# First, look for nested blocks, e.g.:
+		# 	<div>
+		# 		<div>
+		# 		tags for inner block must be indented.
+		# 		</div>
+		# 	</div>
+		#
+		# The outermost tags must start at the left margin for this to match, and
+		# the inner nested divs must be indented.
+		# We need to do this before the next, more liberal match, because the next
+		# match will start at the first `<div>` and stop at the first `</div>`.
+		$text = preg_replace_callback('{
+					(						# save in $1
+						^					# start of line  (with /m)
+						<('.$block_tags_a.')# start tag = $2
+						'.$attr.'>\n		# attributes followed by > and \n
+						'.$content.'		# content, support nesting
+						</\2>				# the matching end tag
+						[ ]*				# trailing spaces/tabs
+						(?=\n+|\Z)	# followed by a newline or end of document
+					)
+			}xmi',
+			array(&$this, '_hashHTMLBlocks_callback'),
+			$text);
+
+		#
+		# Match from `\n<tag>` to `</tag>\n`, handling nested tags in between.
+		#
+		$text = preg_replace_callback('{
+					(						# save in $1
+						^					# start of line  (with /m)
+						<('.$block_tags_b.')# start tag = $2
+						'.$attr.'>			# attributes followed by >
+						'.$content.'		# content, support nesting
+						</\2>				# the matching end tag
+						[ ]*				# trailing spaces/tabs
+						(?=\n+|\Z)	# followed by a newline or end of document
+					)
+			}xmi',
+			array(&$this, '_hashHTMLBlocks_callback'),
+			$text);
+
+		# Special case just for <hr />. It was easier to make a special case than
+		# to make the other regex more complicated.
+		$text = preg_replace_callback('{
+					(?:
+						(?<=\n\n)		# Starting after a blank line
+						|				# or
+						\A\n?			# the beginning of the doc
+					)
+					(						# save in $1
+						[ ]{0,'.$less_than_tab.'}
+						<(hr)				# start tag = $2
+						\b					# word break
+						([^<>])*?			# 
+						/?>					# the matching end tag
+						[ ]*
+						(?=\n{2,}|\Z)		# followed by a blank line or end of document
+					)
+			}xi',
+			array(&$this, '_hashHTMLBlocks_callback'),
+			$text);
+
+		# Special case for standalone HTML comments:
+		$text = preg_replace_callback('{
+				(?:
+					(?<=\n\n)		# Starting after a blank line
+					|				# or
+					\A\n?			# the beginning of the doc
+				)
+				(						# save in $1
+					[ ]{0,'.$less_than_tab.'}
+					(?s:
+						<!-- .*? -->
+					)
+					[ ]*
+					(?=\n{2,}|\Z)		# followed by a blank line or end of document
+				)
+			}x',
+			array(&$this, '_hashHTMLBlocks_callback'),
+			$text);
+
+		# PHP and ASP-style processor instructions (<? and <%)
+		$text = preg_replace_callback('{
+				(?:
+					(?<=\n\n)		# Starting after a blank line
+					|				# or
+					\A\n?			# the beginning of the doc
+				)
+				(						# save in $1
+					[ ]{0,'.$less_than_tab.'}
+					(?s:
+						<([?%])			# $2
+						.*?
+						\2>
+					)
+					[ ]*
+					(?=\n{2,}|\Z)		# followed by a blank line or end of document
+				)
+			}x',
+			array(&$this, '_hashHTMLBlocks_callback'),
+			$text);
+
+		return $text;
+	}
+	function _hashHTMLBlocks_callback($matches) {
+		$text = $matches[1];
+		$key  = $this->hashBlock($text);
+		return "\n\n$key\n\n";
+	}
+
+
+	function hashBlock($text) {
+	#
+	# Called whenever a tag must be hashed when a function insert a block-level 
+	# tag in $text, it pass through this function and is automaticaly escaped, 
+	# which remove the need to call _HashHTMLBlocks at every step.
+	#
+		# Swap back any tag hash found in $text so we do not have to `unhash`
+		# multiple times at the end.
+		$text = $this->unhash($text);
+		
+		# Then hash the block.
+		$key = "B\x1A". md5($text);
+		$this->html_hashes[$key] = $text;
+		$this->html_blocks[$key] = $text;
+		return $key; # String that will replace the tag.
+	}
+
+
+	function hashSpan($text, $word_separator = false) {
+	#
+	# Called whenever a tag must be hashed when a function insert a span-level 
+	# element in $text, it pass through this function and is automaticaly 
+	# escaped, blocking invalid nested overlap. If optional argument 
+	# $word_separator is true, surround the hash value by spaces.
+	#
+		# Swap back any tag hash found in $text so we do not have to `unhash`
+		# multiple times at the end.
+		$text = $this->unhash($text);
+		
+		# Then hash the span.
+		$key = "S\x1A". md5($text);
+		if ($word_separator) $key = ":$key:";
+		
+		$this->html_hashes[$key] = $text;
+		return $key; # String that will replace the span tag.
+	}
+
+
+	var $block_gamut = array(
+	#
+	# These are all the transformations that form block-level
+	# tags like paragraphs, headers, and list items.
+	#
+		"doHeaders"         => 10,
+		"doHorizontalRules" => 20,
+		
+		"doLists"           => 40,
+		"doCodeBlocks"      => 50,
+		"doBlockQuotes"     => 60,
+		);
+
+	function runBlockGamut($text) {
+	#
+	# Run block gamut tranformations.
+	#
+		# We need to escape raw HTML in Markdown source before doing anything 
+		# else. This need to be done for each block, and not only at the 
+		# begining in the Markdown function since hashed blocks can be part of
+		# list items and could have been indented. Indented blocks would have 
+		# been seen as a code block in a previous pass of hashHTMLBlocks.
+		$text = $this->hashHTMLBlocks($text);
+		
+		return $this->runBasicBlockGamut($text);
+	}
+	
+	function runBasicBlockGamut($text) {
+	#
+	# Run block gamut tranformations, without hashing HTML blocks. This is 
+	# useful when HTML blocks are known to be already hashed, like in the first
+	# whole-document pass.
+	#
+		foreach ($this->block_gamut as $method => $priority) {
+			$text = $this->$method($text);
+		}
+		
+		# Finally form paragraph and restore hashed blocks.
+		$text = $this->formParagraphs($text);
+
+		return $text;
+	}
+	
+	
+	function doHorizontalRules($text) {
+		# Do Horizontal Rules:
+		return preg_replace(
+			array('{^[ ]{0,2}([ ]?\*[ ]?){3,}[ ]*$}mx',
+				  '{^[ ]{0,2}([ ]? -[ ]?){3,}[ ]*$}mx',
+				  '{^[ ]{0,2}([ ]? _[ ]?){3,}[ ]*$}mx'),
+			"\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n", 
+			$text);
+	}
+
+
+	var $span_gamut = array(
+	#
+	# These are all the transformations that occur *within* block-level
+	# tags like paragraphs, headers, and list items.
+	#
+		"escapeSpecialCharsWithinTagAttributes"	=> -20,
+		"doCodeSpans"							=> -10,
+		"encodeBackslashEscapes"				=>  -5,
+
+		# Process anchor and image tags. Images must come first,
+		# because ![foo][f] looks like an anchor.
+		"doImages"            =>  10,
+		"doAnchors"           =>  20,
+		
+		# Make links out of things like `<http://example.com/>`
+		# Must come after doAnchors, because you can use < and >
+		# delimiters in inline links like [this](<url>).
+		"doAutoLinks"         =>  30,
+		"encodeAmpsAndAngles" =>  40,
+
+		"doItalicsAndBold"    =>  50,
+		"doHardBreaks"        =>  60,
+		);
+
+	function runSpanGamut($text) {
+	#
+	# Run span gamut tranformations.
+	#
+		foreach ($this->span_gamut as $method => $priority) {
+			$text = $this->$method($text);
+		}
+
+		return $text;
+	}
+	
+	
+	function doHardBreaks($text) {
+		# Do hard breaks:
+		$br_tag = $this->hashSpan("<br$this->empty_element_suffix\n");
+		return preg_replace('/ {2,}\n/', $br_tag, $text);
+	}
+
+
+	function escapeSpecialCharsWithinTagAttributes($text) {
+	#
+	# Within tags -- meaning between < and > -- encode [\ ` * _] so they
+	# don't conflict with their use in Markdown for code, italics and strong.
+	# We're replacing each such character with its corresponding MD5 checksum
+	# value; this is likely overkill, but it should prevent us from colliding
+	# with the escape values by accident.
+	#
+		if ($this->no_markup)  return $text;
+	
+		$tokens = $this->tokenizeHTML($text);
+		$text = '';   # rebuild $text from the tokens
+
+		foreach ($tokens as $cur_token) {
+			if ($cur_token[0] == 'tag') {
+//				$cur_token[1] = str_replace('\\', $this->escape_table['\\'], $cur_token[1]);
+//				$cur_token[1] = str_replace('`', $this->escape_table['`'], $cur_token[1]);
+//				$cur_token[1] = str_replace('*', $this->escape_table['*'], $cur_token[1]);
+//				$cur_token[1] = str_replace('_', $this->escape_table['_'], $cur_token[1]);
+				$cur_token[1] = $this->hashSpan($cur_token[1]);
+			}
+			$text .= $cur_token[1];
+		}
+		return $text;
+	}
+
+
+	function doAnchors($text) {
+	#
+	# Turn Markdown link shortcuts into XHTML <a> tags.
+	#
+		if ($this->in_anchor) return $text;
+		$this->in_anchor = true;
+		
+		#
+		# First, handle reference-style links: [link text] [id]
+		#
+		$text = preg_replace_callback('{
+			(					# wrap whole match in $1
+			  \[
+				('.$this->nested_brackets.')	# link text = $2
+			  \]
+
+			  [ ]?				# one optional space
+			  (?:\n[ ]*)?		# one optional newline followed by spaces
+
+			  \[
+				(.*?)		# id = $3
+			  \]
+			)
+			}xs',
+			array(&$this, '_doAnchors_reference_callback'), $text);
+
+		#
+		# Next, inline-style links: [link text](url "optional title")
+		#
+		$text = preg_replace_callback('{
+			(				# wrap whole match in $1
+			  \[
+				('.$this->nested_brackets.')	# link text = $2
+			  \]
+			  \(			# literal paren
+				[ ]*
+				(?:
+					<(\S*)>	# href = $3
+				|
+					('.$this->nested_url_parenthesis.')	# href = $4
+				)
+				[ ]*
+				(			# $5
+				  ([\'"])	# quote char = $6
+				  (.*?)		# Title = $7
+				  \6		# matching quote
+				  [ ]*	# ignore any spaces/tabs between closing quote and )
+				)?			# title is optional
+			  \)
+			)
+			}xs',
+			array(&$this, '_DoAnchors_inline_callback'), $text);
+
+		#
+		# Last, handle reference-style shortcuts: [link text]
+		# These must come last in case you've also got [link test][1]
+		# or [link test](/foo)
+		#
+//		$text = preg_replace_callback('{
+//			(					# wrap whole match in $1
+//			  \[
+//				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
+//			  \]
+//			)
+//			}xs',
+//			array(&$this, '_doAnchors_reference_callback'), $text);
+
+		$this->in_anchor = false;
+		return $text;
+	}
+	function _doAnchors_reference_callback($matches) {
+		$whole_match =  $matches[1];
+		$link_text   =  $matches[2];
+		$link_id     =& $matches[3];
+
+		if ($link_id == "") {
+			# for shortcut links like [this][] or [this].
+			$link_id = $link_text;
+		}
+		
+		# lower-case and turn embedded newlines into spaces
+		$link_id = strtolower($link_id);
+		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
+
+		if (isset($this->urls[$link_id])) {
+			$url = $this->urls[$link_id];
+			$url = $this->encodeAmpsAndAngles($url);
+			
+			$result = "<a href=\"$url\"";
+			if ( isset( $this->titles[$link_id] ) ) {
+				$title = $this->titles[$link_id];
+				$title = $this->encodeAmpsAndAngles($title);
+				$result .=  " title=\"$title\"";
+			}
+		
+			$link_text = $this->runSpanGamut($link_text);
+			$result .= ">$link_text</a>";
+			$result = $this->hashSpan($result);
+		}
+		else {
+			$result = $whole_match;
+		}
+		return $result;
+	}
+	function _doAnchors_inline_callback($matches) {
+		$whole_match	=  $matches[1];
+		$link_text		=  $this->runSpanGamut($matches[2]);
+		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
+		$title			=& $matches[7];
+
+		$url = $this->encodeAmpsAndAngles($url);
+
+		$result = "<a href=\"$url\"";
+		if (isset($title)) {
+			$title = str_replace('"', '"', $title);
+			$title = $this->encodeAmpsAndAngles($title);
+			$result .=  " title=\"$title\"";
+		}
+		
+		$link_text = $this->runSpanGamut($link_text);
+		$result .= ">$link_text</a>";
+
+		return $this->hashSpan($result);
+	}
+
+
+	function doImages($text) {
+	#
+	# Turn Markdown image shortcuts into <img> tags.
+	#
+		#
+		# First, handle reference-style labeled images: ![alt text][id]
+		#
+		$text = preg_replace_callback('{
+			(				# wrap whole match in $1
+			  !\[
+				('.$this->nested_brackets.')		# alt text = $2
+			  \]
+
+			  [ ]?				# one optional space
+			  (?:\n[ ]*)?		# one optional newline followed by spaces
+
+			  \[
+				(.*?)		# id = $3
+			  \]
+
+			)
+			}xs', 
+			array(&$this, '_doImages_reference_callback'), $text);
+
+		#
+		# Next, handle inline images:  ![alt text](url "optional title")
+		# Don't forget: encode * and _
+		#
+		$text = preg_replace_callback('{
+			(				# wrap whole match in $1
+			  !\[
+				('.$this->nested_brackets.')		# alt text = $2
+			  \]
+			  \s?			# One optional whitespace character
+			  \(			# literal paren
+				[ ]*
+				(?:
+					<(\S*)>	# src url = $3
+				|
+					('.$this->nested_url_parenthesis.')	# src url = $4
+				)
+				[ ]*
+				(			# $5
+				  ([\'"])	# quote char = $6
+				  (.*?)		# title = $7
+				  \6		# matching quote
+				  [ ]*
+				)?			# title is optional
+			  \)
+			)
+			}xs',
+			array(&$this, '_doImages_inline_callback'), $text);
+
+		return $text;
+	}
+	function _doImages_reference_callback($matches) {
+		$whole_match = $matches[1];
+		$alt_text    = $matches[2];
+		$link_id     = strtolower($matches[3]);
+
+		if ($link_id == "") {
+			$link_id = strtolower($alt_text); # for shortcut links like ![this][].
+		}
+
+		$alt_text = str_replace('"', '"', $alt_text);
+		if (isset($this->urls[$link_id])) {
+			$url = $this->urls[$link_id];
+			$result = "<img src=\"$url\" alt=\"$alt_text\"";
+			if (isset($this->titles[$link_id])) {
+				$title = $this->titles[$link_id];
+				$result .=  " title=\"$title\"";
+			}
+			$result .= $this->empty_element_suffix;
+			$result = $this->hashSpan($result);
+		}
+		else {
+			# If there's no such link ID, leave intact:
+			$result = $whole_match;
+		}
+
+		return $result;
+	}
+	function _doImages_inline_callback($matches) {
+		$whole_match	= $matches[1];
+		$alt_text		= $matches[2];
+		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
+		$title			=& $matches[7];
+
+		$alt_text = str_replace('"', '"', $alt_text);
+		$result = "<img src=\"$url\" alt=\"$alt_text\"";
+		if (isset($title)) {
+			$title = str_replace('"', '"', $title);
+			$result .=  " title=\"$title\""; # $title already quoted
+		}
+		$result .= $this->empty_element_suffix;
+
+		return $this->hashSpan($result);
+	}
+
+
+	function doHeaders($text) {
+		# Setext-style headers:
+		#	  Header 1
+		#	  ========
+		#  
+		#	  Header 2
+		#	  --------
+		#
+		$text = preg_replace_callback('{ ^(.+?)[ ]*\n=+[ ]*\n+ }mx',
+			array(&$this, '_doHeaders_callback_setext_h1'), $text);
+		$text = preg_replace_callback('{ ^(.+?)[ ]*\n-+[ ]*\n+ }mx',
+			array(&$this, '_doHeaders_callback_setext_h2'), $text);
+
+		# atx-style headers:
+		#	# Header 1
+		#	## Header 2
+		#	## Header 2 with closing hashes ##
+		#	...
+		#	###### Header 6
+		#
+		$text = preg_replace_callback('{
+				^(\#{1,6})	# $1 = string of #\'s
+				[ ]*
+				(.+?)		# $2 = Header text
+				[ ]*
+				\#*			# optional closing #\'s (not counted)
+				\n+
+			}xm',
+			array(&$this, '_doHeaders_callback_atx'), $text);
+
+		return $text;
+	}
+	function _doHeaders_callback_setext_h1($matches) {
+		$block = "<h1>".$this->runSpanGamut($matches[1])."</h1>";
+		return "\n" . $this->hashBlock($block) . "\n\n";
+	}
+	function _doHeaders_callback_setext_h2($matches) {
+		$block = "<h2>".$this->runSpanGamut($matches[1])."</h2>";
+		return "\n" . $this->hashBlock($block) . "\n\n";
+	}
+	function _doHeaders_callback_atx($matches) {
+		$level = strlen($matches[1]);
+		$block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
+		return "\n" . $this->hashBlock($block) . "\n\n";
+	}
+
+
+	function doLists($text) {
+	#
+	# Form HTML ordered (numbered) and unordered (bulleted) lists.
+	#
+		$less_than_tab = $this->tab_width - 1;
+
+		# Re-usable patterns to match list item bullets and number markers:
+		$marker_ul  = '[*+-]';
+		$marker_ol  = '\d+[.]';
+		$marker_any = "(?:$marker_ul|$marker_ol)";
+
+		$markers = array($marker_ul, $marker_ol);
+
+		foreach ($markers as $marker) {
+			# Re-usable pattern to match any entirel ul or ol list:
+			$whole_list = '
+				(								# $1 = whole list
+				  (								# $2
+					[ ]{0,'.$less_than_tab.'}
+					('.$marker.')				# $3 = first list item marker
+					[ ]+
+				  )
+				  (?s:.+?)
+				  (								# $4
+					  \z
+					|
+					  \n{2,}
+					  (?=\S)
+					  (?!						# Negative lookahead for another list item marker
+						[ ]*
+						'.$marker.'[ ]+
+					  )
+				  )
+				)
+			'; // mx
+			
+			# We use a different prefix before nested lists than top-level lists.
+			# See extended comment in _ProcessListItems().
+		
+			if ($this->list_level) {
+				$text = preg_replace_callback('{
+						^
+						'.$whole_list.'
+					}mx',
+					array(&$this, '_doLists_callback'), $text);
+			}
+			else {
+				$text = preg_replace_callback('{
+						(?:(?<=\n)\n|\A\n?) # Must eat the newline
+						'.$whole_list.'
+					}mx',
+					array(&$this, '_doLists_callback'), $text);
+			}
+		}
+
+		return $text;
+	}
+	function _doLists_callback($matches) {
+		# Re-usable patterns to match list item bullets and number markers:
+		$marker_ul  = '[*+-]';
+		$marker_ol  = '\d+[.]';
+		$marker_any = "(?:$marker_ul|$marker_ol)";
+		
+		$list = $matches[1];
+		$list_type = preg_match("/$marker_ul/", $matches[3]) ? "ul" : "ol";
+		
+		$marker_any = ( $list_type == "ul" ? $marker_ul : $marker_ol );
+		
+		$list .= "\n";
+		$result = $this->processListItems($list, $marker_any);
+		
+		$result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
+		return "\n". $result ."\n\n";
+	}
+
+	var $list_level = 0;
+
+	function processListItems($list_str, $marker_any) {
+	#
+	#	Process the contents of a single ordered or unordered list, splitting it
+	#	into individual list items.
+	#
+		# The $this->list_level global keeps track of when we're inside a list.
+		# Each time we enter a list, we increment it; when we leave a list,
+		# we decrement. If it's zero, we're not in a list anymore.
+		#
+		# We do this because when we're not inside a list, we want to treat
+		# something like this:
+		#
+		#		I recommend upgrading to version
+		#		8. Oops, now this line is treated
+		#		as a sub-list.
+		#
+		# As a single paragraph, despite the fact that the second line starts
+		# with a digit-period-space sequence.
+		#
+		# Whereas when we're inside a list (or sub-list), that line will be
+		# treated as the start of a sub-list. What a kludge, huh? This is
+		# an aspect of Markdown's syntax that's hard to parse perfectly
+		# without resorting to mind-reading. Perhaps the solution is to
+		# change the syntax rules such that sub-lists must start with a
+		# starting cardinal number; e.g. "1." or "a.".
+		
+		$this->list_level++;
+
+		# trim trailing blank lines:
+		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
+
+		$list_str = preg_replace_callback('{
+			(\n)?							# leading line = $1
+			(^[ ]*)						# leading whitespace = $2
+			('.$marker_any.') [ ]+		# list marker = $3
+			((?s:.+?))						# list item text   = $4
+			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
+			(?= \n* (\z | \2 ('.$marker_any.') [ ]+))
+			}xm',
+			array(&$this, '_processListItems_callback'), $list_str);
+
+		$this->list_level--;
+		return $list_str;
+	}
+	function _processListItems_callback($matches) {
+		$item = $matches[4];
+		$leading_line =& $matches[1];
+		$leading_space =& $matches[2];
+		$tailing_blank_line =& $matches[5];
+
+		if ($leading_line || $tailing_blank_line || 
+			preg_match('/\n{2,}/', $item))
+		{
+			$item = $this->runBlockGamut($this->outdent($item)."\n");
+		}
+		else {
+			# Recursion for sub-lists:
+			$item = $this->doLists($this->outdent($item));
+			$item = preg_replace('/\n+$/', '', $item);
+			$item = $this->runSpanGamut($item);
+		}
+
+		return "<li>" . $item . "</li>\n";
+	}
+
+
+	function doCodeBlocks($text) {
+	#
+	#	Process Markdown `<pre><code>` blocks.
+	#
+		$text = preg_replace_callback('{
+				(?:\n\n|\A)
+				(	            # $1 = the code block -- one or more lines, starting with a space/tab
+				  (?:
+					(?:[ ]{'.$this->tab_width.'} | \t)  # Lines must start with a tab or a tab-width of spaces
+					.*\n+
+				  )+
+				)
+				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
+			}xm',
+			array(&$this, '_doCodeBlocks_callback'), $text);
+
+		return $text;
+	}
+	function _doCodeBlocks_callback($matches) {
+		$codeblock = $matches[1];
+
+		$codeblock = $this->encodeCode($this->outdent($codeblock));
+//		$codeblock = $this->detab($codeblock);
+		# trim leading newlines and trailing whitespace
+		$codeblock = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $codeblock);
+
+		$result = "\n\n".$this->hashBlock("<pre><code>" . $codeblock . "\n</code></pre>")."\n\n";
+
+		return $result;
+	}
+
+
+	function doCodeSpans($text) {
+	#
+	# 	*	Backtick quotes are used for <code></code> spans.
+	#
+	# 	*	You can use multiple backticks as the delimiters if you want to
+	# 		include literal backticks in the code span. So, this input:
+	#
+	#		  Just type ``foo `bar` baz`` at the prompt.
+	#
+	#	  	Will translate to:
+	#
+	#		  <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
+	#
+	#		There's no arbitrary limit to the number of backticks you
+	#		can use as delimters. If you need three consecutive backticks
+	#		in your code, use four for delimiters, etc.
+	#
+	#	*	You can use spaces to get literal backticks at the edges:
+	#
+	#		  ... type `` `bar` `` ...
+	#
+	#	  	Turns to:
+	#
+	#		  ... type <code>`bar`</code> ...
+	#
+		$text = preg_replace_callback('@
+				(?<!\\\)	# Character before opening ` can\'t be a backslash
+				(`+)		# $1 = Opening run of `
+				(.+?)		# $2 = The code block
+				(?<!`)
+				\1			# Matching closer
+				(?!`)
+			@xs',
+			array(&$this, '_doCodeSpans_callback'), $text);
+
+		return $text;
+	}
+	function _doCodeSpans_callback($matches) {
+		$c = $matches[2];
+		$c = preg_replace('/^[ ]*/', '', $c); # leading whitespace
+		$c = preg_replace('/[ ]*$/', '', $c); # trailing whitespace
+		$c = $this->encodeCode($c);
+		return $this->hashSpan("<code>$c</code>");
+	}
+
+
+	function encodeCode($_) {
+	#
+	# Encode/escape certain characters inside Markdown code runs.
+	# The point is that in code, these characters are literals,
+	# and lose their special Markdown meanings.
+	#
+		# Encode all ampersands; HTML entities are not
+		# entities within a Markdown code span.
+		$_ = str_replace('&', '&', $_);
+
+		# Do the angle bracket song and dance:
+		$_ = str_replace(array('<',    '>'), 
+						 array('<', '>'), $_);
+
+		# Now, escape characters that are magic in Markdown:
+//		$_ = str_replace(array_keys($this->escape_table), 
+//						 array_values($this->escape_table), $_);
+
+		return $_;
+	}
+
+
+	function doItalicsAndBold($text) {
+		# <strong> must go first:
+		$text = preg_replace_callback('{
+				(						# $1: Marker
+					(?<!\*\*) \* |		#     (not preceded by two chars of
+					(?<!__)   _			#      the same marker)
+				)
+				\1
+				(?=\S) 					# Not followed by whitespace 
+				(?!\1\1)				#   or two others marker chars.
+				(						# $2: Content
+					(?>
+						[^*_]+?			# Anthing not em markers.
+					|
+										# Balence any regular emphasis inside.
+						\1 (?=\S) .+? (?<=\S) \1
+					|
+						.				# Allow unbalenced * and _.
+					)+?
+				)
+				(?<=\S) \1\1			# End mark not preceded by whitespace.
+			}sx',
+			array(&$this, '_doItalicAndBold_strong_callback'), $text);
+		# Then <em>:
+		$text = preg_replace_callback(
+			'{ ( (?<!\*)\* | (?<!_)_ ) (?=\S) (?! \1) (.+?) (?<=\S)(?<!\s(?=\1).) \1 }sx',
+			array(&$this, '_doItalicAndBold_em_callback'), $text);
+
+		return $text;
+	}
+	function _doItalicAndBold_em_callback($matches) {
+		$text = $matches[2];
+		$text = $this->runSpanGamut($text);
+		return $this->hashSpan("<em>$text</em>");
+	}
+	function _doItalicAndBold_strong_callback($matches) {
+		$text = $matches[2];
+		$text = $this->runSpanGamut($text);
+		return $this->hashSpan("<strong>$text</strong>");
+	}
+
+
+	function doBlockQuotes($text) {
+		$text = preg_replace_callback('/
+			  (								# Wrap whole match in $1
+				(
+				  ^[ ]*>[ ]?			# ">" at the start of a line
+					.+\n					# rest of the first line
+				  (.+\n)*					# subsequent consecutive lines
+				  \n*						# blanks
+				)+
+			  )
+			/xm',
+			array(&$this, '_doBlockQuotes_callback'), $text);
+
+		return $text;
+	}
+	function _doBlockQuotes_callback($matches) {
+		$bq = $matches[1];
+		# trim one level of quoting - trim whitespace-only lines
+		$bq = preg_replace(array('/^[ ]*>[ ]?/m', '/^[ ]+$/m'), '', $bq);
+		$bq = $this->runBlockGamut($bq);		# recurse
+
+		$bq = preg_replace('/^/m', "  ", $bq);
+		# These leading spaces cause problem with <pre> content, 
+		# so we need to fix that:
+		$bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', 
+			array(&$this, '_DoBlockQuotes_callback2'), $bq);
+
+		return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n";
+	}
+	function _doBlockQuotes_callback2($matches) {
+		$pre = $matches[1];
+		$pre = preg_replace('/^  /m', '', $pre);
+		return $pre;
+	}
+
+
+	function formParagraphs($text) {
+	#
+	#	Params:
+	#		$text - string to process with html <p> tags
+	#
+		# Strip leading and trailing lines:
+		$text = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $text);
+
+		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
+
+		#
+		# Wrap <p> tags.
+		#
+		foreach ($grafs as $key => $value) {
+			if (!isset( $this->html_blocks[$value] )) {
+				$value = $this->runSpanGamut($value);
+				$value = preg_replace('/^([ ]*)/', "<p>", $value);
+				$value .= "</p>";
+				$grafs[$key] = $this->unhash($value);
+			}
+		}
+
+		#
+		# Unhashify HTML blocks
+		#
+		foreach ($grafs as $key => $graf) {
+			# Modify elements of @grafs in-place...
+			if (isset($this->html_blocks[$graf])) {
+				$block = $this->html_blocks[$graf];
+				$graf = $block;
+//				if (preg_match('{
+//					\A
+//					(							# $1 = <div> tag
+//					  <div  \s+
+//					  [^>]*
+//					  \b
+//					  markdown\s*=\s*  ([\'"])	#	$2 = attr quote char
+//					  1
+//					  \2
+//					  [^>]*
+//					  >
+//					)
+//					(							# $3 = contents
+//					.*
+//					)
+//					(</div>)					# $4 = closing tag
+//					\z
+//					}xs', $block, $matches))
+//				{
+//					list(, $div_open, , $div_content, $div_close) = $matches;
+//
+//					# We can't call Markdown(), because that resets the hash;
+//					# that initialization code should be pulled into its own sub, though.
+//					$div_content = $this->hashHTMLBlocks($div_content);
+//					
+//					# Run document gamut methods on the content.
+//					foreach ($this->document_gamut as $method => $priority) {
+//						$div_content = $this->$method($div_content);
+//					}
+//
+//					$div_open = preg_replace(
+//						'{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
+//
+//					$graf = $div_open . "\n" . $div_content . "\n" . $div_close;
+//				}
+				$grafs[$key] = $graf;
+			}
+		}
+
+		return implode("\n\n", $grafs);
+	}
+
+
+	function encodeAmpsAndAngles($text) {
+	# Smart processing for ampersands and angle brackets that need to be encoded.
+		if ($this->no_entities) {
+			$text = str_replace('&', '&', $text);
+			$text = str_replace('<', '<', $text);
+			return $text;
+		}
+
+		# Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
+		#   http://bumppo.net/projects/amputator/
+		$text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', 
+							 '&', $text);;
+
+		# Encode naked <'s
+		$text = preg_replace('{<(?![a-z/?\$!%])}i', '<', $text);
+
+		return $text;
+	}
+
+
+	function encodeBackslashEscapes($text) {
+	#
+	#	Parameter:  String.
+	#	Returns:    The string, with after processing the following backslash
+	#				escape sequences.
+	#
+		# Must process escaped backslashes first (should be first in list).
+		foreach ($this->backslash_escape_table as $search => $replacement) {
+			$text = str_replace($search, $this->hashSpan($replacement), $text);
+		}
+		return $text;
+	}
+
+
+	function doAutoLinks($text) {
+		$text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}', 
+			array(&$this, '_doAutoLinks_url_callback'), $text);
+
+		# Email addresses: <address at domain.foo>
+		$text = preg_replace_callback('{
+			<
+			(?:mailto:)?
+			(
+				[-.\w\x80-\xFF]+
+				\@
+				[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
+			)
+			>
+			}xi',
+			array(&$this, '_doAutoLinks_email_callback'), $text);
+
+		return $text;
+	}
+	function _doAutoLinks_url_callback($matches) {
+		$url = $this->encodeAmpsAndAngles($matches[1]);
+		$link = "<a href=\"$url\">$url</a>";
+		return $this->hashSpan($link);
+	}
+	function _doAutoLinks_email_callback($matches) {
+		$address = $matches[1];
+		$link = $this->encodeEmailAddress($address);
+		return $this->hashSpan($link);
+	}
+
+
+	function encodeEmailAddress($addr) {
+	#
+	#	Input: an email address, e.g. "foo at example.com"
+	#
+	#	Output: the email address as a mailto link, with each character
+	#		of the address encoded as either a decimal or hex entity, in
+	#		the hopes of foiling most address harvesting spam bots. E.g.:
+	#
+	#	  <p><a href="m&#x61;i&#x6c;t&#x6f;:&#x66;oo
+	#        &#x40;e&#x78;a&#x6d;p&#x6c;e.&#x63;o
+	#        &#x6d;">&#x66;oo&#x40;e&#x78;a&#x6d;p&#x6c;
+	#        e.&#x63;o&#x6d;</a></p>
+	#
+	#	Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
+	#   With some optimizations by Milian Wolff.
+	#
+		$addr = "mailto:" . $addr;
+		$chars = preg_split('/(?<!^)(?!$)/', $addr);
+		$seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
+		
+		foreach ($chars as $key => $char) {
+			$ord = ord($char);
+			# Ignore non-ascii chars.
+			if ($ord < 128) {
+				$r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
+				# roughly 10% raw, 45% hex, 45% dec
+				# '@' *must* be encoded. I insist.
+				if ($r > 90 && $char != '@') /* do nothing */;
+				else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
+				else              $chars[$key] = '&#'.$ord.';';
+			}
+		}
+		
+		$addr = implode('', $chars);
+		$text = implode('', array_slice($chars, 7)); # text without `mailto:`
+		$addr = "<a href=\"$addr\">$text</a>";
+
+		return $addr;
+	}
+
+
+	function tokenizeHTML($str) {
+	#
+	#   Parameter:  String containing HTML + Markdown markup.
+	#   Returns:    An array of the tokens comprising the input
+	#               string. Each token is either a tag or a run of text 
+	#               between tags. Each element of the array is a
+	#               two-element array; the first is either 'tag' or 'text';
+	#               the second is the actual value.
+	#   Note:       Markdown code spans are taken into account: no tag token is 
+	#               generated within a code span.
+	#
+		$tokens = array();
+
+		while ($str != "") {
+			#
+			# Each loop iteration seach for either the next tag or the next 
+			# openning code span marker. If a code span marker is found, the 
+			# code span is extracted in entierty and will result in an extra
+			# text token.
+			#
+			$parts = preg_split('{
+				(
+					(?<![`\\\\])
+					`+						# code span marker
+				|
+					<!--    .*?     -->		# comment
+				|
+					<\?.*?\?> | <%.*?%>		# processing instruction
+				|
+					<[/!$]?[-a-zA-Z0-9:]+	# regular tags
+					(?:
+						\s
+						(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
+					)?
+					>
+				)
+				}xs', $str, 2, PREG_SPLIT_DELIM_CAPTURE);
+			
+			# Create token from text preceding tag.
+			if ($parts[0] != "") {
+				$tokens[] = array('text', $parts[0]);
+			}
+			
+			# Check if we reach the end.
+			if (count($parts) < 3) {
+				break;
+			}
+			
+			# Create token from tag or code span.
+			if ($parts[1]{0} == "`") {
+				$tokens[] = array('text', $parts[1]);
+				$str = $parts[2];
+				
+				# Skip the whole code span, pass as text token.
+				if (preg_match('/^(.*(?<!`\\\\)'.$parts[1].'(?!`))(.*)$/sm', 
+					$str, $matches))
+				{
+					$tokens[] = array('text', $matches[1]);
+					$str = $matches[2];
+				}
+			} else {
+				$tokens[] = array('tag', $parts[1]);
+				$str = $parts[2];
+			}
+		}
+		
+		return $tokens;
+	}
+
+
+	function outdent($text) {
+	#
+	# Remove one level of line-leading tabs or spaces
+	#
+		return preg_replace("/^(\\t|[ ]{1,$this->tab_width})/m", "", $text);
+	}
+
+
+	# String length function for detab. `_initDetab` will create a function to 
+	# hanlde UTF-8 if the default function does not exist.
+	var $utf8_strlen = 'mb_strlen';
+	
+	function detab($text) {
+	#
+	# Replace tabs with the appropriate amount of space.
+	#
+		# For each line we separate the line in blocks delemited by
+		# tab characters. Then we reconstruct every line by adding the 
+		# appropriate number of space between each blocks.
+		
+		$strlen = $this->utf8_strlen; # strlen function for UTF-8.
+		$lines = explode("\n", $text);
+		$text = "";
+		
+		foreach ($lines as $line) {
+			# Split in blocks.
+			$blocks = explode("\t", $line);
+			# Add each blocks to the line.
+			$line = $blocks[0];
+			unset($blocks[0]); # Do not add first block twice.
+			foreach ($blocks as $block) {
+				# Calculate amount of space, insert spaces, insert block.
+				$amount = $this->tab_width - 
+					$strlen($line, 'UTF-8') % $this->tab_width;
+				$line .= str_repeat(" ", $amount) . $block;
+			}
+			$text .= "$line\n";
+		}
+		return $text;
+	}
+	function _initDetab() {
+	#
+	# Check for the availability of the function in the `utf8_strlen` property
+	# (initially `mb_strlen`). If the function is not available, create a 
+	# function that will loosely count the number of UTF-8 characters with a
+	# regular expression.
+	#
+		if (function_exists($this->utf8_strlen)) return;
+		$this->utf8_strlen = create_function('$text', 'return preg_match_all(
+			"/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", 
+			$text, $m);');
+	}
+
+
+	function unhash($text) {
+	#
+	# Swap back in all the tags hashed by _HashHTMLBlocks.
+	#
+		return str_replace(array_keys($this->html_hashes), 
+						   array_values($this->html_hashes), $text);
+	}
+
+}
+
+
+#
+# Markdown Extra Parser Class
+#
+
+class MarkdownExtra_Parser extends Markdown_Parser {
+
+	# Prefix for footnote ids.
+	var $fn_id_prefix = "";
+	
+	# Optional title attribute for footnote links and backlinks.
+	var $fn_link_title = MARKDOWN_FN_LINK_TITLE;
+	var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE;
+	
+	# Optional class attribute for footnote links and backlinks.
+	var $fn_link_class = MARKDOWN_FN_LINK_CLASS;
+	var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS;
+
+
+	function MarkdownExtra_Parser() {
+	#
+	# Constructor function. Initialize the parser object.
+	#
+		# Add extra escapable characters before parent constructor 
+		# initialize the table.
+		$this->escape_chars .= ':|';
+		
+		# Insert extra document, block, and span transformations. 
+		# Parent constructor will do the sorting.
+		$this->document_gamut += array(
+			"stripFootnotes"     => 15,
+			"stripAbbreviations" => 25,
+			"appendFootnotes"    => 50,
+			);
+		$this->block_gamut += array(
+			"doTables"           => 15,
+			"doDefLists"         => 45,
+			);
+		$this->span_gamut += array(
+			"doFootnotes"        => 5,
+			"doAbbreviations"    => 70,
+			);
+		
+		parent::Markdown_Parser();
+	}
+	
+	
+	# Extra hashes used during extra transformations.
+	var $footnotes = array();
+	var $footnotes_ordered = array();
+	var $abbr_desciptions = array();
+	var $abbr_matches = array();
+	var $html_cleans = array();
+	
+	# Status flag to avoid invalid nesting.
+	var $in_footnote = false;
+	
+	
+	function transform($text) {
+	#
+	# Added clear to the new $html_hashes, reordered `hashHTMLBlocks` before 
+	# blank line stripping and added extra parameter to `runBlockGamut`.
+	#
+		# Clear the global hashes. If we don't clear these, you get conflicts
+		# from other articles when generating a page which contains more than
+		# one article (e.g. an index page that shows the N most recent
+		# articles):
+		$this->footnotes = array();
+		$this->footnotes_ordered = array();
+		$this->abbr_desciptions = array();
+		$this->abbr_matches = array();
+		$this->html_cleans = array();
+
+		return parent::transform($text);
+	}
+	
+	
+	### HTML Block Parser ###
+	
+	# Tags that are always treated as block tags:
+	var $block_tags = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend';
+	
+	# Tags treated as block tags only if the opening tag is alone on it's line:
+	var $context_block_tags = 'script|noscript|math|ins|del';
+	
+	# Tags where markdown="1" default to span mode:
+	var $contain_span_tags = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
+	
+	# Tags which must not have their contents modified, no matter where 
+	# they appear:
+	var $clean_tags = 'script|math';
+	
+	# Tags that do not need to be closed.
+	var $auto_close_tags = 'hr|img';
+	
+
+	function hashHTMLBlocks($text) {
+	#
+	# Hashify HTML Blocks and "clean tags".
+	#
+	# We only want to do this for block-level HTML tags, such as headers,
+	# lists, and tables. That's because we still want to wrap <p>s around
+	# "paragraphs" that are wrapped in non-block-level tags, such as anchors,
+	# phrase emphasis, and spans. The list of tags we're looking for is
+	# hard-coded.
+	#
+	# This works by calling _HashHTMLBlocks_InMarkdown, which then calls
+	# _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" 
+	# attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back
+	#  _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
+	# These two functions are calling each other. It's recursive!
+	#
+		#
+		# Call the HTML-in-Markdown hasher.
+		#
+		list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
+		
+		return $text;
+	}
+	function _hashHTMLBlocks_inMarkdown($text, $indent = 0, 
+										$enclosing_tag = '', $span = false)
+	{
+	#
+	# Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
+	#
+	# *   $indent is the number of space to be ignored when checking for code 
+	#     blocks. This is important because if we don't take the indent into 
+	#     account, something like this (which looks right) won't work as expected:
+	#
+	#     <div>
+	#         <div markdown="1">
+	#         Hello World.  <-- Is this a Markdown code block or text?
+	#         </div>  <-- Is this a Markdown code block or a real tag?
+	#     <div>
+	#
+	#     If you don't like this, just don't indent the tag on which
+	#     you apply the markdown="1" attribute.
+	#
+	# *   If $enclosing_tag is not empty, stops at the first unmatched closing 
+	#     tag with that name. Nested tags supported.
+	#
+	# *   If $span is true, text inside must treated as span. So any double 
+	#     newline will be replaced by a single newline so that it does not create 
+	#     paragraphs.
+	#
+	# Returns an array of that form: ( processed text , remaining text )
+	#
+		if ($text === '') return array('', '');
+
+		# Regex to check for the presense of newlines around a block tag.
+		$newline_match_before = '/(?:^\n?|\n\n)*$/';
+		$newline_match_after = 
+			'{
+				^						# Start of text following the tag.
+				(?:[ ]*<!--.*?-->)?		# Optional comment.
+				[ ]*\n					# Must be followed by newline.
+			}xs';
+		
+		# Regex to match any tag.
+		$block_tag_match =
+			'{
+				(					# $2: Capture hole tag.
+					</?					# Any opening or closing tag.
+						(?:				# Tag name.
+							'.$this->block_tags.'			|
+							'.$this->context_block_tags.'	|
+							'.$this->clean_tags.'        	|
+							(?!\s)'.$enclosing_tag.'
+						)
+						\s*				# Whitespace.
+						(?>
+							".*?"		|	# Double quotes (can contain `>`)
+							\'.*?\'   	|	# Single quotes (can contain `>`)
+							.+?				# Anything but quotes and `>`.
+						)*?
+					>					# End of tag.
+				|
+					<!--    .*?     -->	# HTML Comment
+				|
+					<\?.*?\?> | <%.*?%>	# Processing instruction
+				|
+					<!\[CDATA\[.*?\]\]>	# CData Block
+				)
+			}xs';
+
+		
+		$depth = 0;		# Current depth inside the tag tree.
+		$parsed = "";	# Parsed text that will be returned.
+
+		#
+		# Loop through every tag until we find the closing tag of the parent
+		# or loop until reaching the end of text if no parent tag specified.
+		#
+		do {
+			#
+			# Split the text using the first $tag_match pattern found.
+			# Text before  pattern will be first in the array, text after
+			# pattern will be at the end, and between will be any catches made 
+			# by the pattern.
+			#
+			$parts = preg_split($block_tag_match, $text, 2, 
+								PREG_SPLIT_DELIM_CAPTURE);
+			
+			# If in Markdown span mode, add a empty-string span-level hash 
+			# after each newline to prevent triggering any block element.
+			if ($span) {
+				$void = $this->hashSpan("", true) ;
+				$newline = $this->hashSpan("", true) . "\n";
+				$parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
+			}
+			
+			$parsed .= $parts[0]; # Text before current tag.
+			
+			# If end of $text has been reached. Stop loop.
+			if (count($parts) < 3) {
+				$text = "";
+				break;
+			}
+			
+			$tag  = $parts[1]; # Tag to handle.
+			$text = $parts[2]; # Remaining text after current tag.
+			
+			#
+			# Check for: Tag inside code block or span
+			#
+			if (# Find current paragraph
+				preg_match('/(?>^\n?|\n\n)((?>.\n?)+?)$/', $parsed, $matches) &&
+				(
+				# Then match in it either a code block...
+				preg_match('/^ {'.($indent+4).'}.*(?>\n {'.($indent+4).'}.*)*'.
+							'(?!\n)$/', $matches[1], $x) ||
+				# ...or unbalenced code span markers. (the regex matches balenced)
+				!preg_match('/^(?>[^`]+|(`+)(?>[^`]+|(?!\1[^`])`)*?\1(?!`))*$/s',
+							 $matches[1])
+				))
+			{
+				# Tag is in code block or span and may not be a tag at all. So we
+				# simply skip the first char (should be a `<`).
+				$parsed .= $tag{0};
+				$text = substr($tag, 1) . $text; # Put back $tag minus first char.
+			}
+			#
+			# Check for: Opening Block level tag or
+			#            Opening Content Block tag (like ins and del) 
+			#               used as a block tag (tag is alone on it's line).
+			#
+			else if (preg_match("{^<(?:$this->block_tags)\b}", $tag) ||
+				(	preg_match("{^<(?:$this->context_block_tags)\b}", $tag) &&
+					preg_match($newline_match_before, $parsed) &&
+					preg_match($newline_match_after, $text)	)
+				)
+			{
+				# Need to parse tag and following text using the HTML parser.
+				list($block_text, $text) = 
+					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
+				
+				# Make sure it stays outside of any paragraph by adding newlines.
+				$parsed .= "\n\n$block_text\n\n";
+			}
+			#
+			# Check for: Clean tag (like script, math)
+			#            HTML Comments, processing instructions.
+			#
+			else if (preg_match("{^<(?:$this->clean_tags)\b}", $tag) ||
+				$tag{1} == '!' || $tag{1} == '?')
+			{
+				# Need to parse tag and following text using the HTML parser.
+				# (don't check for markdown attribute)
+				list($block_text, $text) = 
+					$this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
+				
+				$parsed .= $block_text;
+			}
+			#
+			# Check for: Tag with same name as enclosing tag.
+			#
+			else if ($enclosing_tag !== '' &&
+				# Same name as enclosing tag.
+				preg_match("{^</?(?:$enclosing_tag)\b}", $tag))
+			{
+				#
+				# Increase/decrease nested tag count.
+				#
+				if ($tag{1} == '/')						$depth--;
+				else if ($tag{strlen($tag)-2} != '/')	$depth++;
+
+				if ($depth < 0) {
+					#
+					# Going out of parent element. Clean up and break so we
+					# return to the calling function.
+					#
+					$text = $tag . $text;
+					break;
+				}
+				
+				$parsed .= $tag;
+			}
+			else {
+				$parsed .= $tag;
+			}
+		} while ($depth >= 0);
+		
+		return array($parsed, $text);
+	}
+	function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
+	#
+	# Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
+	#
+	# *   Calls $hash_method to convert any blocks.
+	# *   Stops when the first opening tag closes.
+	# *   $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
+	#     (it is not inside clean tags)
+	#
+	# Returns an array of that form: ( processed text , remaining text )
+	#
+		if ($text === '') return array('', '');
+		
+		# Regex to match `markdown` attribute inside of a tag.
+		$markdown_attr_match = '
+			{
+				\s*			# Eat whitespace before the `markdown` attribute
+				markdown
+				\s*=\s*
+				(?:
+					(["\'])		# $1: quote delimiter		
+					(.*?)		# $2: attribute value
+					\1			# matching delimiter	
+				|
+					([^\s>]*)	# $3: unquoted attribute value
+				)
+				()				# $4: make $3 always defined (avoid warnings)
+			}xs';
+		
+		# Regex to match any tag.
+		$tag_match = '{
+				(					# $2: Capture hole tag.
+					</?					# Any opening or closing tag.
+						[\w:$]+			# Tag name.
+						\s*				# Whitespace.
+						(?>
+							".*?"		|	# Double quotes (can contain `>`)
+							\'.*?\'   	|	# Single quotes (can contain `>`)
+							.+?				# Anything but quotes and `>`.
+						)*?
+					>					# End of tag.
+				|
+					<!--    .*?     -->	# HTML Comment
+				|
+					<\?.*?\?> | <%.*?%>	# Processing instruction
+				|
+					<!\[CDATA\[.*?\]\]>	# CData Block
+				)
+			}xs';
+		
+		$original_text = $text;		# Save original text in case of faliure.
+		
+		$depth		= 0;	# Current depth inside the tag tree.
+		$block_text	= "";	# Temporary text holder for current text.
+		$parsed		= "";	# Parsed text that will be returned.
+
+		#
+		# Get the name of the starting tag.
+		#
+		if (preg_match("/^<([\w:$]*)\b/", $text, $matches))
+			$base_tag_name = $matches[1];
+
+		#
+		# Loop through every tag until we find the corresponding closing tag.
+		#
+		do {
+			#
+			# Split the text using the first $tag_match pattern found.
+			# Text before  pattern will be first in the array, text after
+			# pattern will be at the end, and between will be any catches made 
+			# by the pattern.
+			#
+			$parts = preg_split($tag_match, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
+			
+			if (count($parts) < 3) {
+				#
+				# End of $text reached with unbalenced tag(s).
+				# In that case, we return original text unchanged and pass the
+				# first character as filtered to prevent an infinite loop in the 
+				# parent function.
+				#
+				return array($original_text{0}, substr($original_text, 1));
+			}
+			
+			$block_text .= $parts[0]; # Text before current tag.
+			$tag         = $parts[1]; # Tag to handle.
+			$text        = $parts[2]; # Remaining text after current tag.
+			
+			#
+			# Check for: Auto-close tag (like <hr/>)
+			#			 Comments and Processing Instructions.
+			#
+			if (preg_match("{^</?(?:$this->auto_close_tags)\b}", $tag) ||
+				$tag{1} == '!' || $tag{1} == '?')
+			{
+				# Just add the tag to the block as if it was text.
+				$block_text .= $tag;
+			}
+			else {
+				#
+				# Increase/decrease nested tag count. Only do so if
+				# the tag's name match base tag's.
+				#
+				if (preg_match("{^</?$base_tag_name\b}", $tag)) {
+					if ($tag{1} == '/')						$depth--;
+					else if ($tag{strlen($tag)-2} != '/')	$depth++;
+				}
+				
+				#
+				# Check for `markdown="1"` attribute and handle it.
+				#
+				if ($md_attr && 
+					preg_match($markdown_attr_match, $tag, $attr_m) &&
+					preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
+				{
+					# Remove `markdown` attribute from opening tag.
+					$tag = preg_replace($markdown_attr_match, '', $tag);
+					
+					# Check if text inside this tag must be parsed in span mode.
+					$this->mode = $attr_m[2] . $attr_m[3];
+					$span_mode = $this->mode == 'span' || $this->mode != 'block' &&
+						preg_match("{^<(?:$this->contain_span_tags)\b}", $tag);
+					
+					# Calculate indent before tag.
+					preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches);
+					$indent = strlen($matches[1]);
+					
+					# End preceding block with this tag.
+					$block_text .= $tag;
+					$parsed .= $this->$hash_method($block_text);
+					
+					# Get enclosing tag name for the ParseMarkdown function.
+					preg_match('/^<([\w:$]*)\b/', $tag, $matches);
+					$tag_name = $matches[1];
+					
+					# Parse the content using the HTML-in-Markdown parser.
+					list ($block_text, $text)
+						= $this->_hashHTMLBlocks_inMarkdown($text, $indent, 
+														$tag_name, $span_mode);
+					
+					# Outdent markdown text.
+					if ($indent > 0) {
+						$block_text = preg_replace("/^[ ]{1,$indent}/m", "", 
+													$block_text);
+					}
+					
+					# Append tag content to parsed text.
+					if (!$span_mode)	$parsed .= "\n\n$block_text\n\n";
+					else				$parsed .= "$block_text";
+					
+					# Start over a new block.
+					$block_text = "";
+				}
+				else $block_text .= $tag;
+			}
+			
+		} while ($depth > 0);
+		
+		#
+		# Hash last block text that wasn't processed inside the loop.
+		#
+		$parsed .= $this->$hash_method($block_text);
+		
+		return array($parsed, $text);
+	}
+
+
+	function hashClean($text) {
+	#
+	# Called whenever a tag must be hashed when a function insert a "clean" tag
+	# in $text, it pass through this function and is automaticaly escaped, 
+	# blocking invalid nested overlap.
+	#
+		# Swap back any tag hash found in $text so we do not have to `unhash`
+		# multiple times at the end.
+		$text = $this->unhash($text);
+		
+		# Then hash the tag.
+		$key = "C\x1A". md5($text);
+		$this->html_cleans[$key] = $text;
+		$this->html_hashes[$key] = $text;
+		return $key; # String that will replace the clean tag.
+	}
+
+
+	function doHeaders($text) {
+	#
+	# Redefined to add id attribute support.
+	#
+		# Setext-style headers:
+		#	  Header 1  {#header1}
+		#	  ========
+		#  
+		#	  Header 2  {#header2}
+		#	  --------
+		#
+		$text = preg_replace_callback(
+			'{ (^.+?) (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? [ ]*\n=+[ ]*\n+ }mx',
+			array(&$this, '_doHeaders_callback_setext_h1'), $text);
+		$text = preg_replace_callback(
+			'{ (^.+?) (?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? [ ]*\n-+[ ]*\n+ }mx',
+			array(&$this, '_doHeaders_callback_setext_h2'), $text);
+
+		# atx-style headers:
+		#	# Header 1        {#header1}
+		#	## Header 2       {#header2}
+		#	## Header 2 with closing hashes ##  {#header3}
+		#	...
+		#	###### Header 6   {#header2}
+		#
+		$text = preg_replace_callback('{
+				^(\#{1,6})	# $1 = string of #\'s
+				[ ]*
+				(.+?)		# $2 = Header text
+				[ ]*
+				\#*			# optional closing #\'s (not counted)
+				(?:[ ]+\{\#([-_:a-zA-Z0-9]+)\})? # id attribute
+				[ ]*
+				\n+
+			}xm',
+			array(&$this, '_doHeaders_callback_atx'), $text);
+
+		return $text;
+	}
+	function _doHeaders_attr($attr) {
+		if (empty($attr))  return "";
+		return " id=\"$attr\"";
+	}
+	function _doHeaders_callback_setext_h1($matches) {
+		$attr  = $this->_doHeaders_attr($id =& $matches[2]);
+		$block = "<h1$attr>".$this->runSpanGamut($matches[1])."</h1>";
+		return "\n" . $this->hashBlock($block) . "\n\n";
+	}
+	function _doHeaders_callback_setext_h2($matches) {
+		$attr  = $this->_doHeaders_attr($id =& $matches[2]);
+		$block = "<h2$attr>".$this->runSpanGamut($matches[1])."</h2>";
+		return "\n" . $this->hashBlock($block) . "\n\n";
+	}
+	function _doHeaders_callback_atx($matches) {
+		$level = strlen($matches[1]);
+		$attr  = $this->_doHeaders_attr($id =& $matches[3]);
+		$block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>";
+		return "\n" . $this->hashBlock($block) . "\n\n";
+	}
+
+
+	function doTables($text) {
+	#
+	# Form HTML tables.
+	#
+		$less_than_tab = $this->tab_width - 1;
+		#
+		# Find tables with leading pipe.
+		#
+		#	| Header 1 | Header 2
+		#	| -------- | --------
+		#	| Cell 1   | Cell 2
+		#	| Cell 3   | Cell 4
+		#
+		$text = preg_replace_callback('
+			{
+				^							# Start of a line
+				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
+				[|]							# Optional leading pipe (present)
+				(.+) \n						# $1: Header row (at least one pipe)
+				
+				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
+				[|] ([ ]*[-:]+[-| :]*) \n	# $2: Header underline
+				
+				(							# $3: Cells
+					(?:
+						[ ]*				# Allowed whitespace.
+						[|] .* \n			# Row content.
+					)*
+				)
+				(?=\n|\Z)					# Stop at final double newline.
+			}xm',
+			array(&$this, '_doTable_leadingPipe_callback'), $text);
+		
+		#
+		# Find tables without leading pipe.
+		#
+		#	Header 1 | Header 2
+		#	-------- | --------
+		#	Cell 1   | Cell 2
+		#	Cell 3   | Cell 4
+		#
+		$text = preg_replace_callback('
+			{
+				^							# Start of a line
+				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
+				(\S.*[|].*) \n				# $1: Header row (at least one pipe)
+				
+				[ ]{0,'.$less_than_tab.'}	# Allowed whitespace.
+				([-:]+[ ]*[|][-| :]*) \n	# $2: Header underline
+				
+				(							# $3: Cells
+					(?:
+						.* [|] .* \n		# Row content
+					)*
+				)
+				(?=\n|\Z)					# Stop at final double newline.
+			}xm',
+			array(&$this, '_DoTable_callback'), $text);
+
+		return $text;
+	}
+	function _doTable_leadingPipe_callback($matches) {
+		$head		= $matches[1];
+		$underline	= $matches[2];
+		$content	= $matches[3];
+		
+		# Remove leading pipe for each row.
+		$content	= preg_replace('/^ *[|]/m', '', $content);
+		
+		return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
+	}
+	function _doTable_callback($matches) {
+		$head		= $matches[1];
+		$underline	= $matches[2];
+		$content	= $matches[3];
+
+		# Remove any tailing pipes for each line.
+		$head		= preg_replace('/[|] *$/m', '', $head);
+		$underline	= preg_replace('/[|] *$/m', '', $underline);
+		$content	= preg_replace('/[|] *$/m', '', $content);
+		
+		# Reading alignement from header underline.
+		$separators	= preg_split('/ *[|] */', $underline);
+		foreach ($separators as $n => $s) {
+			if (preg_match('/^ *-+: *$/', $s))		$attr[$n] = ' align="right"';
+			else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"';
+			else if (preg_match('/^ *:-+ *$/', $s))	$attr[$n] = ' align="left"';
+			else									$attr[$n] = '';
+		}
+		
+		# Creating code spans before splitting the row is an easy way to 
+		# handle a code span containg pipes.
+		$head	= $this->doCodeSpans($head);
+		$headers	= preg_split('/ *[|] */', $head);
+		$col_count	= count($headers);
+		
+		# Write column headers.
+		$text = "<table>\n";
+		$text .= "<thead>\n";
+		$text .= "<tr>\n";
+		foreach ($headers as $n => $header)
+			$text .= "  <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n";
+		$text .= "</tr>\n";
+		$text .= "</thead>\n";
+		
+		# Split content by row.
+		$rows = explode("\n", trim($content, "\n"));
+		
+		$text .= "<tbody>\n";
+		foreach ($rows as $row) {
+			# Creating code spans before splitting the row is an easy way to 
+			# handle a code span containg pipes.
+			$row = $this->doCodeSpans($row);
+			
+			# Split row by cell.
+			$row_cells = preg_split('/ *[|] */', $row, $col_count);
+			$row_cells = array_pad($row_cells, $col_count, '');
+			
+			$text .= "<tr>\n";
+			foreach ($row_cells as $n => $cell)
+				$text .= "  <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n";
+			$text .= "</tr>\n";
+		}
+		$text .= "</tbody>\n";
+		$text .= "</table>";
+		
+		return $this->hashBlock($text) . "\n";
+	}
+
+	
+	function doDefLists($text) {
+	#
+	# Form HTML definition lists.
+	#
+		$less_than_tab = $this->tab_width - 1;
+
+		# Re-usable pattern to match any entire dl list:
+		$whole_list = '
+			(								# $1 = whole list
+			  (								# $2
+				[ ]{0,'.$less_than_tab.'}
+				((?>.*\S.*\n)+)				# $3 = defined term
+				\n?
+				[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
+			  )
+			  (?s:.+?)
+			  (								# $4
+				  \z
+				|
+				  \n{2,}
+				  (?=\S)
+				  (?!						# Negative lookahead for another term
+					[ ]{0,'.$less_than_tab.'}
+					(?: \S.*\n )+?			# defined term
+					\n?
+					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
+				  )
+				  (?!						# Negative lookahead for another definition
+					[ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
+				  )
+			  )
+			)
+		'; // mx
+
+		$text = preg_replace_callback('{
+				(?:(?<=\n\n)|\A\n?)
+				'.$whole_list.'
+			}mx',
+			array(&$this, '_doDefLists_callback'), $text);
+
+		return $text;
+	}
+	function _doDefLists_callback($matches) {
+		# Re-usable patterns to match list item bullets and number markers:
+		$list = $matches[1];
+		
+		# Turn double returns into triple returns, so that we can make a
+		# paragraph for the last item in a list, if necessary:
+		$result = trim($this->processDefListItems($list));
+		$result = "<dl>\n" . $result . "\n</dl>";
+		return $this->hashBlock($result) . "\n\n";
+	}
+
+
+	function processDefListItems($list_str) {
+	#
+	#	Process the contents of a single definition list, splitting it
+	#	into individual term and definition list items.
+	#
+		$less_than_tab = $this->tab_width - 1;
+		
+		# trim trailing blank lines:
+		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
+
+		# Process definition terms.
+		$list_str = preg_replace_callback('{
+			(?:\n\n+|\A\n?)					# leading line
+			(								# definition terms = $1
+				[ ]{0,'.$less_than_tab.'}	# leading whitespace
+				(?![:][ ]|[ ])				# negative lookahead for a definition 
+											#   mark (colon) or more whitespace.
+				(?: \S.* \n)+?				# actual term (not whitespace).	
+			)			
+			(?=\n?[ ]{0,3}:[ ])				# lookahead for following line feed 
+											#   with a definition mark.
+			}xm',
+			array(&$this, '_processDefListItems_callback_dt'), $list_str);
+
+		# Process actual definitions.
+		$list_str = preg_replace_callback('{
+			\n(\n+)?						# leading line = $1
+			[ ]{0,'.$less_than_tab.'}		# whitespace before colon
+			[:][ ]+							# definition mark (colon)
+			((?s:.+?))						# definition text = $2
+			(?= \n+ 						# stop at next definition mark,
+				(?:							# next term or end of text
+					[ ]{0,'.$less_than_tab.'} [:][ ]	|
+					<dt> | \z
+				)						
+			)					
+			}xm',
+			array(&$this, '_processDefListItems_callback_dd'), $list_str);
+
+		return $list_str;
+	}
+	function _processDefListItems_callback_dt($matches) {
+		$terms = explode("\n", trim($matches[1]));
+		$text = '';
+		foreach ($terms as $term) {
+			$term = $this->runSpanGamut(trim($term));
+			$text .= "\n<dt>" . $term . "</dt>";
+		}
+		return $text . "\n";
+	}
+	function _processDefListItems_callback_dd($matches) {
+		$leading_line	= $matches[1];
+		$def			= $matches[2];
+
+		if ($leading_line || preg_match('/\n{2,}/', $def)) {
+			$def = $this->runBlockGamut($this->outdent($def . "\n\n"));
+			$def = "\n". $def ."\n";
+		}
+		else {
+			$def = rtrim($def);
+			$def = $this->runSpanGamut($this->outdent($def));
+		}
+
+		return "\n<dd>" . $def . "</dd>\n";
+	}
+
+
+	function doItalicsAndBold($text) {
+	#
+	# Redefined to change emphasis by underscore behaviour so that it does not 
+	# work in the middle of a word.
+	#
+		# <strong> must go first:
+		$text = preg_replace_callback(array(
+			'{
+				(						# $1: Marker
+					(?<![a-zA-Z0-9])	# Not preceded by alphanum
+					(?<!__)				#	or by two marker chars.
+					__
+				)
+				(?=\S) 					# Not followed by whitespace 
+				(?!__)					#   or two others marker chars.
+				(						# $2: Content
+					(?>
+						[^_]+?			# Anthing not em markers.
+					|
+										# Balence any regular _ emphasis inside.
+						(?<![a-zA-Z0-9]) _ (?=\S) (.+?) 
+						(?<=\S) _ (?![a-zA-Z0-9])
+					|
+						_+				# Allow unbalenced as last resort.
+					)+?
+				)
+				(?<=\S) __				# End mark not preceded by whitespace.
+				(?![a-zA-Z0-9])			# Not followed by alphanum
+				(?!__)					#   or two others marker chars.
+			}sx',
+			'{
+				( (?<!\*\*) \*\* )		# $1: Marker (not preceded by two *)
+				(?=\S) 					# Not followed by whitespace 
+				(?!\1)					#   or two others marker chars.
+				(						# $2: Content
+					(?>
+						[^*]+?			# Anthing not em markers.
+					|
+										# Balence any regular * emphasis inside.
+						\* (?=\S) (.+?) (?<=\S) \*
+					|
+						\*				# Allow unbalenced as last resort.
+					)+?
+				)
+				(?<=\S) \*\*			# End mark not preceded by whitespace.
+			}sx',
+			),
+			array(&$this, '_doItalicAndBold_strong_callback'), $text);
+		# Then <em>:
+		$text = preg_replace_callback(array(
+			'{ ( (?<![a-zA-Z0-9])(?<!_)_ ) (?=\S) (?! \1) (.+?) (?<=\S) \1(?![a-zA-Z0-9]) }sx',
+			'{ ( (?<!\*)\* ) (?=\S) (?! \1) (.+?) (?<=\S)(?<!\s\*) \1 }sx',
+			),
+			array(&$this, '_doItalicAndBold_em_callback'), $text);
+
+		return $text;
+	}
+
+
+	function formParagraphs($text) {
+	#
+	#	Params:
+	#		$text - string to process with html <p> tags
+	#
+		# Strip leading and trailing lines:
+		$text = preg_replace(array('/\A\n+/', '/\n+\z/'), '', $text);
+		
+		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
+
+		#
+		# Wrap <p> tags and unhashify HTML blocks
+		#
+		foreach ($grafs as $key => $value) {
+			$value = trim($this->runSpanGamut($value));
+			
+			# Check if this should be enclosed in a paragraph.
+			# Clean tag hashes & block tag hashes are left alone.
+			$clean_key = $value;
+			$block_key = substr($value, 0, 34);
+			
+			$is_p = (!isset($this->html_blocks[$block_key]) && 
+					 !isset($this->html_cleans[$clean_key]));
+			
+			if ($is_p) {
+				$value = "<p>$value</p>";
+			}
+			$grafs[$key] = $value;
+		}
+		
+		# Join grafs in one text, then unhash HTML tags. 
+		$text = implode("\n\n", $grafs);
+		
+		# Finish by removing any tag hashes still present in $text.
+		$text = $this->unhash($text);
+		
+		return $text;
+	}
+	
+	
+	### Footnotes
+	
+	function stripFootnotes($text) {
+	#
+	# Strips link definitions from text, stores the URLs and titles in
+	# hash references.
+	#
+		$less_than_tab = $this->tab_width - 1;
+
+		# Link defs are in the form: [^id]: url "optional title"
+		$text = preg_replace_callback('{
+			^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?:	# note_id = $1
+			  [ ]*
+			  \n?					# maybe *one* newline
+			(						# text = $2 (no blank lines allowed)
+				(?:					
+					.+				# actual text
+				|
+					\n				# newlines but 
+					(?!\[\^.+?\]:\s)# negative lookahead for footnote marker.
+					(?!\n+[ ]{0,3}\S)# ensure line is not blank and followed 
+									# by non-indented content
+				)*
+			)		
+			}xm',
+			array(&$this, '_stripFootnotes_callback'),
+			$text);
+		return $text;
+	}
+	function _stripFootnotes_callback($matches) {
+		$note_id = $this->fn_id_prefix . $matches[1];
+		$this->footnotes[$note_id] = $this->outdent($matches[2]);
+		return ''; # String that will replace the block
+	}
+
+
+	function doFootnotes($text) {
+	#
+	# Replace footnote references in $text [^id] with a special text-token 
+	# which will be can be
+	#
+		if (!$this->in_footnote && !$this->in_anchor) {
+			$text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
+		}
+		return $text;
+	}
+
+	
+	function appendFootnotes($text) {
+	#
+	# Append footnote list to text.
+	#
+	
+		$text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', 
+			array(&$this, '_appendFootnotes_callback'), $text);
+	
+		if (!empty($this->footnotes_ordered)) {
+			$text .= "\n\n";
+			$text .= "<div class=\"footnotes\">\n";
+			$text .= "<hr". MARKDOWN_EMPTY_ELEMENT_SUFFIX ."\n";
+			$text .= "<ol>\n\n";
+			
+			$attr = " rev=\"footnote\"";
+			if ($this->fn_backlink_class != "") {
+				$class = $this->fn_backlink_class;
+				$class = $this->encodeAmpsAndAngles($class);
+				$class = str_replace('"', '"', $class);
+				$attr .= " class=\"$class\"";
+			}
+			if ($this->fn_backlink_title != "") {
+				$title = $this->fn_backlink_title;
+				$title = $this->encodeAmpsAndAngles($title);
+				$title = str_replace('"', '"', $title);
+				$attr .= " title=\"$title\"";
+			}
+			$num = 0;
+			
+			$this->in_footnote = true;
+			
+			foreach ($this->footnotes_ordered as $note_id => $footnote) {
+				$footnote .= "\n"; # Need to append newline before parsing.
+				$footnote = $this->runBlockGamut("$footnote\n");
+				
+				$attr2 = str_replace("%%", ++$num, $attr);
+				
+				# Add backlink to last paragraph; create new paragraph if needed.
+				$backlink = "<a href=\"#fnref:$note_id\"$attr2>↩</a>";
+				if (preg_match('{</p>$}', $footnote)) {
+					$footnote = substr($footnote, 0, -4) . " $backlink</p>";
+				} else {
+					$footnote .= "\n\n<p>$backlink</p>";
+				}
+				
+				$text .= "<li id=\"fn:$note_id\">\n";
+				$text .= $footnote . "\n";
+				$text .= "</li>\n\n";
+			}
+			
+			$this->in_footnote = false;
+			
+			$text .= "</ol>\n";
+			$text .= "</div>";
+		}
+		return $text;
+	}
+	function _appendFootnotes_callback($matches) {
+		$node_id = $this->fn_id_prefix . $matches[1];
+		
+		# Create footnote marker only if it has a corresponding footnote *and*
+		# the footnote hasn't been used by another marker.
+		if (isset($this->footnotes[$node_id])) {
+			# Transfert footnote content to the ordered list.
+			$this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
+			unset($this->footnotes[$node_id]);
+			
+			$num = count($this->footnotes_ordered);
+			$attr = " rel=\"footnote\"";
+			if ($this->fn_link_class != "") {
+				$class = $this->fn_link_class;
+				$class = $this->encodeAmpsAndAngles($class);
+				$class = str_replace('"', '"', $class);
+				$attr .= " class=\"$class\"";
+			}
+			if ($this->fn_link_title != "") {
+				$title = $this->fn_link_title;
+				$title = $this->encodeAmpsAndAngles($title);
+				$title = str_replace('"', '"', $title);
+				$attr .= " title=\"$title\"";
+			}
+			$attr = str_replace("%%", $num, $attr);
+			
+			return
+				"<sup id=\"fnref:$node_id\">".
+				"<a href=\"#fn:$node_id\"$attr>$num</a>".
+				"</sup>";
+		}
+		
+		return "[^".$matches[1]."]";
+	}
+		
+	
+	### Abbreviations ###
+	
+	function stripAbbreviations($text) {
+	#
+	# Strips abbreviations from text, stores titles in hash references.
+	#
+		$less_than_tab = $this->tab_width - 1;
+
+		# Link defs are in the form: [id]*: url "optional title"
+		$text = preg_replace_callback('{
+			^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?:	# abbr_id = $1
+			(.*)					# text = $2 (no blank lines allowed)	
+			}xm',
+			array(&$this, '_stripAbbreviations_callback'),
+			$text);
+		return $text;
+	}
+	function _stripAbbreviations_callback($matches) {
+		$abbr_word = $matches[1];
+		$abbr_desc = $matches[2];
+		$this->abbr_matches[] = preg_quote($abbr_word);
+		$this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
+		return ''; # String that will replace the block
+	}
+	
+	
+	function doAbbreviations($text) {
+	#
+	# Find defined abbreviations in text and wrap them in <abbr> elements.
+	#
+		if ($this->abbr_matches) {
+			// cannot use the /x modifier because abbr_matches may 
+			// contain spaces:
+			$text = preg_replace_callback('{'.
+				'(?<![\w\x1A])'.
+				'(?:'. implode('|', $this->abbr_matches) .')'.
+				'(?![\w\x1A])'.
+				'}', 
+				array(&$this, '_doAbbreviations_callback'), $text);
+		}
+		return $text;
+	}
+	function _doAbbreviations_callback($matches) {
+		$abbr = $matches[0];
+		if (isset($this->abbr_desciptions[$abbr])) {
+			$desc = $this->abbr_desciptions[$abbr];
+			if (empty($desc)) {
+				return $this->hashSpan("<abbr>$abbr</abbr>");
+			} else {
+				$desc = $this->escapeSpecialCharsWithinTagAttributes($desc);
+				return $this->hashSpan("<abbr title=\"$desc\">$abbr</abbr>");
+			}
+		} else {
+			return $matches[0];
+		}
+	}
+
+}
+
+
+/*
+
+PHP Markdown Extra
+==================
+
+Description
+-----------
+
+This is a PHP port of the original Markdown formatter written in Perl 
+by John Gruber. This special "Extra" version of PHP Markdown features 
+further enhancements to the syntax for making additional constructs 
+such as tables and definition list.
+
+Markdown is a text-to-HTML filter; it translates an easy-to-read /
+easy-to-write structured text format into HTML. Markdown's text format
+is most similar to that of plain text email, and supports features such
+as headers, *emphasis*, code blocks, blockquotes, and links.
+
+Markdown's syntax is designed not as a generic markup language, but
+specifically to serve as a front-end to (X)HTML. You can use span-level
+HTML tags anywhere in a Markdown document, and you can use block level
+HTML tags (like <div> and <table> as well).
+
+For more information about Markdown's syntax, see:
+
+<http://daringfireball.net/projects/markdown/>
+
+
+Bugs
+----
+
+To file bug reports please send email to:
+
+<michel.fortin at michelf.com>
+
+Please include with your report: (1) the example input; (2) the output you
+expected; (3) the output Markdown actually produced.
+
+
+Version History
+---------------
+
+See Readme file for details.
+
+Extra 1.1.4 (3 Aug 2007):
+
+Extra 1.1.3 (3 Jul 2007):
+
+Extra 1.1.2 (7 Feb 2007)
+
+Extra 1.1.1 (28 Dec 2006)
+
+Extra 1.1 (1 Dec 2006)
+
+Extra 1.0.1 (9 Dec 2005)
+
+Extra 1.0 (5 Sep 2005)
+
+
+Copyright and License
+---------------------
+
+PHP Markdown & Extra
+Copyright (c) 2004-2007 Michel Fortin  
+<http://www.michelf.com/>  
+All rights reserved.
+
+Based on Markdown  
+Copyright (c) 2003-2006 John Gruber   
+<http://daringfireball.net/>   
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+*	Redistributions of source code must retain the above copyright notice,
+	this list of conditions and the following disclaimer.
+
+*	Redistributions in binary form must reproduce the above copyright
+	notice, this list of conditions and the following disclaimer in the
+	documentation and/or other materials provided with the distribution.
+
+*	Neither the name "Markdown" nor the names of its contributors may
+	be used to endorse or promote products derived from this software
+	without specific prior written permission.
+
+This software is provided by the copyright holders and contributors "as
+is" and any express or implied warranties, including, but not limited
+to, the implied warranties of merchantability and fitness for a
+particular purpose are disclaimed. In no event shall the copyright owner
+or contributors be liable for any direct, indirect, incidental, special,
+exemplary, or consequential damages (including, but not limited to,
+procurement of substitute goods or services; loss of use, data, or
+profits; or business interruption) however caused and on any theory of
+liability, whether in contract, strict liability, or tort (including
+negligence or otherwise) arising in any way out of the use of this
+software, even if advised of the possibility of such damage.
+
+*/
+
+?>
\ No newline at end of file
diff --git a/system/vendor/swift/EasySwift.php b/system/vendor/swift/EasySwift.php
new file mode 100755
index 0000000..cbb9441
--- /dev/null
+++ b/system/vendor/swift/EasySwift.php
@@ -0,0 +1,949 @@
+<?php
+
+/**
+ * EasySwift: Swift Mailer Facade
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package EasySwift
+ * @version 1.0.3
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/Swift/ClassLoader.php";
+Swift_ClassLoader::load("Swift");
+Swift_ClassLoader::load("Swift_Connection_SMTP");
+Swift_ClassLoader::load("Swift_Connection_Sendmail");
+
+//Some constants for backwards compatibility with v2 code
+if (!defined("SWIFT_TLS")) define("SWIFT_TLS", Swift_Connection_SMTP::ENC_TLS);
+if (!defined("SWIFT_SSL")) define("SWIFT_SSL", Swift_Connection_SMTP::ENC_SSL);
+if (!defined("SWIFT_OPEN")) define("SWIFT_OPEN", Swift_Connection_SMTP::ENC_OFF);
+if (!defined("SWIFT_SECURE_PORT")) define("SWIFT_SECURE_PORT", Swift_Connection_SMTP::PORT_SECURE);
+if (!defined("SWIFT_DEFAULT_PORT")) define("SWIFT_DEFAULT_PORT", Swift_Connection_SMTP::PORT_DEFAULT);
+
+/**
+ * EasySwift: Facade for Swift Mailer Version 3.
+ * Provides (most of) the API from older versions of Swift, wrapped around the new version 3 API.
+ * Due to the popularity of the new API, EasySwift will not be around indefinitely.
+ * @package EasySwift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @deprecated
+ */
+class EasySwift
+{
+  /**
+   * The instance of Swift this class wrappers
+   * @var Swift
+   */
+  public $swift = null;
+  /**
+   * This value becomes set to true when Swift fails
+   * @var boolean
+   */
+  public $failed = false;
+  /**
+   * The number of loaded plugins
+   * @var int
+   */
+  protected $pluginCount = 0;
+  /**
+   * An instance of Swift_Message
+   * @var Swift_Message
+   */
+  public $message = null;
+  /**
+   * An address list to send to (Cc, Bcc, To..)
+   * @var Swift_RecipientList
+   */
+  public $recipients = null;
+  /**
+   * If all recipients should get the same copy of the message, including headers
+   * This is already implied if any Cc or Bcc recipients are set
+   * @var boolean
+   */
+  protected $exactCopy = false;
+  /**
+   * If EasySwift should get rid of the message and recipients once it's done sending
+   * @var boolean
+   */
+  protected $autoFlush = true;
+  /**
+   * A list of the IDs of all parts added to the message
+   * @var array
+   */
+  protected $partIds = array();
+  /**
+   * A list of all the IDs of the attachments add to the message
+   * @var array
+   */
+  protected $attachmentIds = array();
+  /**
+   * The last response received from the server
+   * @var string
+   */
+  public $lastResponse = "";
+  /**
+   * The 3 digit code in the last response received from the server
+   * @var int
+   */
+  public $responseCode = 0;
+  /**
+   * The list of errors handled at runtime
+   * @var array
+   */
+  public $errors = array();
+  /**
+   * The last error received
+   * @var string
+   */
+  public $lastError = null;
+  
+  /**
+   * Constructor
+   * @param Swift_Connection The connection to use
+   * @param string The domain name of this server (not the SMTP server)
+   */
+  public function __construct(Swift_Connection $connection, $domain=null)
+  {
+    try {
+      $this->swift = new Swift($connection, $domain, Swift::ENABLE_LOGGING);
+      Swift_ClassLoader::load("Swift_Plugin_EasySwiftResponseTracker");
+      $this->swift->attachPlugin(new Swift_Plugin_EasySwiftResponseTracker($this), "_ResponseTracker");
+    } catch (Swift_ConnectionException $e) {
+      $this->failed = true;
+      $this->setError("The connection failed to start.  An exception was thrown:<br />" . $e->getMessage());
+    }
+    $this->newMessage();
+    $this->newRecipientList();
+  }
+  /**
+   * Set an error message
+   * @param string Error message
+   */
+  public function setError($msg)
+  {
+    $this->errors[] = ($this->lastError = $msg);
+  }
+  /**
+   * Get the full list of errors
+   * @return array
+   */
+  public function getErrors()
+  {
+    return $this->errors;
+  }
+  /**
+   * Get the last error that occured
+   * @return string
+   */
+  public function getLastError()
+  {
+    return $this->lastError;
+  }
+  /**
+   * Clear the current list of errors
+   */
+  public function flushErrors()
+  {
+    $this->errors = null;
+    $this->errors = array();
+  }
+  /**
+   * Turn automatic flsuhing on or off.
+   * This in ON by deault.  It removes the message and all parts after sending.
+   * @param boolean
+   */
+  public function autoFlush($flush=true)
+  {
+    $this->autoFlush = $flush;
+  }
+  /**
+   * Set the maximum size of the log
+   * @param int
+   */
+  public function setMaxLogSize($size)
+  {
+    $log = Swift_LogContainer::getLog();
+    $log->setMaxSize($size);
+  }
+  /**
+   * Turn logging on or off (saves memory)
+   * @param boolean
+   */
+  public function useLogging($use=true)
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($use) $log->setLogLevel(Swift_Log::LOG_NETWORK);
+    else $log->setLogLevel(Swift_Log::LOG_NOTHING);
+  }
+  /**
+   * Enable line resizing (on 1000 by default)
+   * @param int The number of characters allowed on a line
+   */
+  public function useAutoLineResizing($size=1000)
+  {
+    $this->message->setLineWrap($size);
+  }
+  /**
+   * Dump the log contents
+   * @deprecated
+   */
+  public function getTransactions()
+  {
+    return $this->dumpLog();
+  }
+  /**
+   * Dump the contents of the log to the browser
+   * The log contains some < and > characters so you may need to view source
+   * Note that this method dumps data to the browser, it does NOT return anything.
+   */
+  public function dumpLog()
+  {
+    $log = Swift_LogContainer::getLog();
+    $log->dump();
+  }
+  /**
+   * This method should be called if you do not wish to send messages in batch mode (i.e. if all recipients should see each others' addresses)
+   * @param boolean If this mode should be used
+   */
+  public function useExactCopy($bool=true)
+  {
+    $this->exactCopy = $bool;
+  }
+  /**
+   * Reset the current message and start a fresh one
+   */
+  public function newMessage($msg=false)
+  {
+    if (!$msg) $msg = new Swift_Message();
+    $this->message = $msg;
+    $this->partIds = array();
+    $this->attachmentIds = array();
+  }
+  /**
+   * Clear out all message parts
+   * @return boolean
+   */
+  public function flushParts()
+  {
+    $success = true;
+    foreach ($this->partIds as $id)
+    {
+      try {
+        $this->message->detach($id);
+      } catch (Swift_Message_MimeException $e) {
+        $success = false;
+        $this->setError("A MIME part failed to detach due to the error:<br />" . $e->getMessage());
+      }
+    }
+    $this->partIds = array();
+    return $success;
+  }
+  /**
+   * Clear out all attachments
+   * @return boolean
+   */
+  public function flushAttachments()
+  {
+    $success = true;
+    foreach ($this->attachmentIds as $id)
+    {
+      try {
+        $this->message->detach($id);
+      } catch (Swift_Message_MimeException $e) {
+        $success = false;
+        $this->setError("An attachment failed to detach due to the error:<br />" . $e->getMessage());
+      }
+    }
+    $this->attachmentIds = array();
+    return $success;
+  }
+  /**
+   * Clear out all message headers
+   * @deprecated
+   */
+  public function flushHeaders()
+  {
+    $this->newMessage();
+  }
+  /**
+   * Reset the current list of recipients and start a new one
+   */
+  public function newRecipientList($list=false)
+  {
+    if (!$list) $list = new Swift_RecipientList();
+    $this->recipients = $list;
+  }
+  /**
+   * Check if Swift has failed or not
+   * This facade stops processing if so
+   * @return boolean
+   */
+  public function hasFailed()
+  {
+    return $this->failed;
+  }
+  /**
+   * Check if the current connection is open or not
+   * @return boolean
+   */
+  public function isConnected()
+  {
+    return (($this->swift !== null) && $this->swift->connection->isAlive());
+  }
+  /**
+   * Connect to the MTA if not already connected
+   */
+  public function connect()
+  {
+    if (!$this->isConnected())
+    {
+      try {
+        $this->swift->connect();
+        return true;
+      } catch (Swift_ConnectionException $e) {
+        $this->failed = true;
+        $this->setError("Swift failed to run the connection process:<br />" . $e->getMessage());
+      }
+    }
+    return false;
+  }
+  /**
+   * Perform the SMTP greeting process (don't do this unless you understand why you're doing it)
+   */
+  public function handshake()
+  {
+    $this->swift->handshake();
+  }
+  /**
+   * Close the connection to the MTA
+   * @return boolean
+   */
+  public function close()
+  {
+    if ($this->isConnected())
+    {
+      try {
+        $this->swift->disconnect();
+        return true;
+      } catch (Swift_ConnectionException $e) {
+        $this->setError("Disconnect failed:<br />" . $e->getMessage());
+      }
+    }
+    return false;
+  }
+  /**
+   * Send a command to Swift and get a response
+   * @param string The command to send (leave of CRLF)
+   * @return string
+   */
+  public function command($command)
+  {
+    if (substr($command, -2) == "\r\n") $command = substr($command, 0, -2);
+    
+    try {
+      $rs = $this->swift->command($command);
+      return $rs->getString();
+    } catch (Swift_ConnectionException $e) {
+      $this->setError("Command failed:<br />" . $e->getMessage());
+      return false;
+    }
+  }
+  /**
+   * Add a new plugin to respond to events
+   * @param Swift_Events_Listener The plugin to load
+   * @param string The ID to identify the plugin by if needed
+   * @return string The ID of the plugin
+   */
+  public function loadPlugin(Swift_Events_Listener $plugin, $name=null)
+  {
+    $this->pluginCount++;
+    if (!$name) $name = "p" . $this->pluginCount;
+    $this->swift->attachPlugin($plugin, $name);
+    return $name;
+  }
+  /**
+   * Get a reference to the plugin identified by $name
+   * @param string the ID of the plugin
+   * @return Swift_Events_Listener
+   */
+  public function getPlugin($name)
+  {
+    try {
+      $plugin = $this->swift->getPlugin($name);
+      return $plugin;
+    } catch (Exception $e) {
+      return null;
+    }
+  }
+  /**
+   * Remove the plugin identified by $name
+   * @param string The ID of the plugin
+   * @return boolean
+   */
+  public function removePlugin($name)
+  {
+    try {
+      $this->swift->removePlugin($name);
+      return true;
+    } catch (Exception $e) {
+      return false;
+    }
+  }
+  /**
+   * Load in a new authentication mechanism for SMTP
+   * This needn't be called since Swift will locate any available in Swift/Authenticator/*.php
+   * @param Swift_Authenticator The authentication mechanism to load
+   * @throws Exception If the wrong connection is used
+   */
+  public function loadAuthenticator(Swift_Authenticator $auth)
+  {
+    if (method_exists($this->swift->connection, "attachAuthenticator"))
+    {
+      $this->swift->connection->attachAuthenticator($auth);
+    }
+    else throw new Exception("SMTP authentication cannot be used with connection class '" . get_class($this->connection) . "'. Swift_Connection_SMTP is needed");
+  }
+  /**
+   * Authenticate with SMTP authentication
+   * @param string The SMTP username
+   * @param string The SMTP password
+   * @return boolean
+   * @throws Exception If the wrong connection is used
+   */
+  public function authenticate($username, $password)
+  {
+    if (method_exists($this->swift->connection, "runAuthenticators"))
+    {
+      try {
+        $this->swift->connection->runAuthenticators($username, $password, $this->swift);
+        return true;
+      } catch (Swift_ConnectionException $e) {
+        $this->setError("Authentication failed:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+    else throw new Exception("SMTP authentication cannot be used with connection class '" . get_class($this->connection) . "'. Swift_Connection_SMTP is needed");
+  }
+  /**
+   * Turn a string representation of an email address into a Swift_Address object
+   * @paramm string The email address
+   * @return Swift_Address
+   */
+  public function stringToAddress($string)
+  {
+    $name = null;
+    $address = null;
+    // Foo Bar <foo at bar>
+    // or: "Foo Bar" <foo at bar>
+    // or: <foo at bar>
+    Swift_ClassLoader::load("Swift_Message_Encoder");
+    if (preg_match("/^\\s*(\"?)(.*?)\\1 *<(" . Swift_Message_Encoder::CHEAP_ADDRESS_RE . ")>\\s*\$/", $string, $matches))
+    {
+      if (!empty($matches[2])) $name = $matches[2];
+      $address = $matches[3];
+    }
+    elseif (preg_match("/^\\s*" . Swift_Message_Encoder::CHEAP_ADDRESS_RE . "\\s*\$/", $string))
+    {
+      $address = trim($string);
+    }
+    else return false;
+    
+    $swift_address = new Swift_Address($address, $name);
+    return $swift_address;
+  }
+  /**
+   * Set the encoding used in the message header
+   * The encoding can be one of Q (quoted-printable) or B (base64)
+   * @param string The encoding to use
+   */
+  public function setHeaderEncoding($mode="B")
+  {
+    switch (strtoupper($mode))
+    {
+      case "Q": case "QP": case "QUOTED-PRINTABLE":
+        $this->message->headers->setEncoding("Q");
+        break;
+      default:
+        $this->message->headers->setEncoding("B");
+    }
+  }
+  /**
+   * Set the return path address (where bounces go to)
+   * @param mixed The address as a string or Swift_Address
+   */
+  public function setReturnPath($address)
+  {
+    return $this->message->setReturnPath($address);
+  }
+  /**
+   * Request for a read recipient to be sent to the reply-to address
+   * @param boolean
+   */
+  public function requestReadReceipt($request=true)
+  {
+    //$this->message->requestReadReceipt(true);
+  }
+  /**
+   * Set the message priority
+   * This is an integer between 1 (high) and 5 (low)
+   * @param int The level of priority to use
+   */
+  public function setPriority($priority)
+  {
+    $this->message->setPriority($priority);
+  }
+  /**
+   * Get the return-path address as a string
+   * @return string
+   */
+  public function getReturnPath()
+  {
+    try {
+      return $this->message->getReturnPath();
+    } catch (Swift_Message_MimeException $e) {
+      return false;
+    }
+  }
+  /**
+   * Set the reply-to header
+   * @param mixed The address replies come to. String, or Swift_Address, or an array of either.
+   */
+  public function setReplyTo($address)
+  {
+    return $this->message->setReplyTo($address);
+  }
+  /**
+   * Get the reply-to address(es) as an array of strings
+   * @return array
+   */
+  public function getReplyTo()
+  {
+    try {
+      return $this->message->getReplyTo();
+    } catch (Swift_Message_MimeException $e) {
+      return false;
+    }
+  }
+  /**
+   * Add To: recipients to the email
+   * @param mixed To address(es)
+   * @return boolean
+   */
+  public function addTo($address)
+  {
+    return $this->addRecipients($address, "To");
+  }
+  /**
+   * Get an array of To addresses
+   * This currently returns an array of Swift_Address objects and may be simplified to an array of strings in later versions
+   * @return array
+   */
+  public function getToAddresses()
+  {
+    return $this->recipients->getTo();
+  }
+  /**
+   * Clear out all To: recipients
+   */
+  public function flushTo()
+  {
+    $this->recipients->flushTo();
+  }
+  /**
+   * Add Cc: recipients to the email
+   * @param mixed Cc address(es)
+   * @return boolean
+   */
+  public function addCc($address)
+  {
+    return $this->addRecipients($address, "Cc");
+  }
+  /**
+   * Get an array of Cc addresses
+   * This currently returns an array of Swift_Address objects and may be simplified to an array of strings in later versions
+   * @return array
+   */
+  public function getCcAddresses()
+  {
+    return $this->recipients->getCc();
+  }
+  /**
+   * Clear out all Cc: recipients
+   */
+  public function flushCc()
+  {
+    $this->recipients->flushCc();
+  }
+  /**
+   * Add Bcc: recipients to the email
+   * @param mixed Bcc address(es)
+   * @return boolean
+   */
+  public function addBcc($address)
+  {
+    return $this->addRecipients($address, "Bcc");
+  }
+  /**
+   * Get an array of Bcc addresses
+   * This currently returns an array of Swift_Address objects and may be simplified to an array of strings in later versions
+   * @return array
+   */
+  public function getBccAddresses()
+  {
+    return $this->recipients->getBcc();
+  }
+  /**
+   * Clear out all Bcc: recipients
+   */
+  public function flushBcc()
+  {
+    $this->recipients->flushBcc();
+  }
+  /**
+   * Add recipients to the email
+   * @param mixed Address(es)
+   * @param string Recipient type (To, Cc, Bcc)
+   * @return boolean
+   */
+  protected function addRecipients($address, $type)
+  {
+    if (!in_array($type, array("To", "Cc", "Bcc"))) return false;
+    $method = "add" . $type;
+    
+    if ($address instanceof Swift_Address)
+    {
+      $this->recipients->$method($address);
+      return true;
+    }
+    else
+    {
+      $added = 0;
+      foreach ((array)$address as $addr)
+      {
+        if (is_array($addr))
+        {
+          $addr = array_values($addr);
+          if (count($addr) >= 2)
+          {
+            $this->recipients->$method($addr[0], $addr[1]);
+            $added++;
+            continue;
+          }
+          elseif (count($addr) == 1) $addr = $addr[0];
+          else continue;
+        }
+        
+        if (is_string($addr))
+        {
+          $addr = $this->stringToAddress($addr);
+          $this->recipients->$method($addr);
+          $added++;
+        }
+      }
+      return ($added > 0);
+    }
+  }
+  /**
+   * Flush message, recipients and headers
+   */
+  public function flush()
+  {
+    $this->newMessage();
+    $this->newRecipientList();
+  }
+  /**
+   * Get a list of any addresses which have failed since instantiation
+   * @return array
+   */
+  public function getFailedRecipients()
+  {
+    $log = Swift_LogContainer::getLog();
+    return $log->getFailedRecipients();
+  }
+  /**
+   * Set the multipart MIME warning message (only seen by old clients)
+   * @param string The message to show
+   */
+  public function setMimeWarning($text)
+  {
+    $this->message->setMimeWarning($text);
+  }
+  /**
+   * Get the currently set MIME warning (seen by old clients)
+   * @return string
+   */
+  public function getMimeWarning()
+  {
+    return $this->message->getMimeWarning();
+  }
+  /**
+   * Set the charset of the charset to use in the message
+   * @param string The charset (e.g. utf-8, iso-8859-1 etc)
+   * @return boolean
+   */
+  public function setCharset($charset)
+  {
+    try {
+      $this->message->setCharset($charset);
+      return true;
+    } catch (Swift_Message_MimeException $e) {
+      $this->setError("Unable to set the message charset:<br />" . $e->getMessage());
+      return false;
+    }
+  }
+  /**
+   * Get the charset of the charset to use in the message
+   * @return string
+   */
+  public function getCharset()
+  {
+    return $this->message->getCharset();
+  }
+  /**
+   * Add a new MIME part to the message
+   * @param mixed The part to add.  If this is a string it's used as the body.  If it's an instance of Swift_Message_Part it's used as the entire part
+   * @param string Content-type, default text/plain
+   * @param string The encoding used (default is to let Swift decide)
+   * @param string The charset to use (default is to let swift decide)
+   */
+  public function addPart($body, $type="text/plain", $encoding=null, $charset=null)
+  {
+    if ($body instanceof Swift_Message_Mime)
+    {
+      try {
+        $this->partIds[] = $this->message->attach($body);
+      } catch (Swift_Message_MimeException $e) {
+        $this->setError("A MIME part failed to attach:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+    else
+    {
+      try {
+        $this->partIds[] = $this->message->attach(new Swift_Message_Part($body, $type, $encoding, $charset));
+      } catch (Swift_Message_MimeException $e) {
+        $this->setError("A MIME part failed to attach:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+  }
+  /**
+   * Add a new attachment to the message
+   * @param mixed The attachment to add.  If this is a string it's used as the file contents.  If it's an instance of Swift_Message_Attachment it's used as the entire part.  If it's an instance of Swift_File it's used as the contents.
+   * @param string Filename, optional
+   * @param string Content-type. Default application/octet-stream
+   * @param string The encoding used (default is base64)
+   * @return boolean
+   */
+  public function addAttachment($data, $filename=null, $type="application/octet-stream", $encoding=null)
+  {
+    if ($data instanceof Swift_Message_Mime)
+    {
+      try {
+        $this->attachmentIds[] = $this->message->attach($data);
+      } catch (Swift_Message_MimeException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+    else
+    {
+      try {
+        $this->attachmentIds[] = $this->message->attach(new Swift_Message_Attachment($data, $filename, $type, $encoding));
+      } catch (Swift_Message_MimeException $e) {
+        $this->setError("An attachment failed to attach<br />" . $e->getMessage());
+        return false;
+      } catch (Swift_FileException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+    return true;
+  }
+  /**
+   * Embed an image into the message and get the src attribute for HTML
+   * Returns FALSE on failure
+   * @param mixed The path to the image, a Swift_Message_Image object or a Swift_File object
+   * @return string
+   */
+  public function addImage($input)
+  {
+    $ret = false;
+    if ($input instanceof Swift_Message_Image)
+    {
+      $ret = $this->message->attach($input);
+      $this->attachmentIds[] = $ret;
+      return $ret;
+    }
+    elseif ($input instanceof Swift_File)
+    {
+      try {
+        $ret = $this->message->attach(new Swift_Message_Image($input));
+        $this->attachmentIds[] = $ret;
+        return $ret;
+      } catch (Swift_Message_MimeException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      } catch (Swift_FileException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+    else
+    {
+      try {
+        $ret = $this->message->attach(new Swift_Message_Image(new Swift_File($input)));
+        $this->attachmentIds[] = $ret;
+        return $ret;
+      } catch (Swift_Message_MimeException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      } catch (Swift_FileException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+  }
+  /**
+   * Embed an inline file into the message, such as a Image or MIDI file
+   * @param mixed The file contents, Swift_File object or Swift_Message_EmbeddedFile object
+   * @param string The content-type of the file, optional
+   * @param string The filename to use, optional
+   * @param string the Content-ID to use, optional
+   * @return string
+   */
+  public function embedFile($data, $type="application/octet-stream", $filename=null, $cid=null)
+  {
+    $ret = false;
+    if ($data instanceof Swift_Message_EmbeddedFile)
+    {
+      $ret = $this->message->attach($data);
+      $this->attachmentIds[] = $ret;
+      return $ret;
+    }
+    elseif ($data instanceof Swift_File)
+    {
+      try {
+        $ret = $this->message->attach(new Swift_Message_EmbeddedFile($data, $filename, $type, $cid));
+        $this->attachmentIds[] = $ret;
+        return $ret;
+      } catch (Swift_Message_MimeException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      } catch (Swift_FileException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+    else
+    {
+      try {
+        $ret = $this->message->attach(new Swift_Message_EmbeddedFile($data, $filename, $type, $cid));
+        $this->attachmentIds[] = $ret;
+        return $ret;
+      } catch (Swift_Message_MimeException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      } catch (Swift_FileException $e) {
+        $this->setError("An attachment failed to attach:<br />" . $e->getMessage());
+        return false;
+      }
+    }
+  }
+  /**
+   * Add headers to the message
+   * @param string The message headers to append, separated by CRLF
+   * @deprecated
+   */
+  public function addHeaders($string)
+  {
+    //Split at the line ending only if it's not followed by LWSP (as in, a full header)
+    $headers = preg_split("~\r?\n(?![ \t])~", $string);
+    foreach ($headers as $header)
+    {
+      if (empty($header)) continue;
+      //Get the bit before the colon
+      $header_name = substr($header, 0, ($c_pos = strpos($header, ": ")));
+      // ... and trim it away
+      $header = substr($header, $c_pos+2);
+      //Try splitting at "; " for attributes
+      $attribute_pairs = preg_split("~\\s*;\\s+~", $header);
+      //The value would always be right after the colon
+      $header_value = $attribute_pairs[0];
+      $this->message->headers->set($header_name, $header_value);
+      unset($attribute_pairs[0]);
+      foreach ($attribute_pairs as $pair)
+      {
+        //Now try finding the attribute name, and it's value (removing quotes)
+        if (preg_match("~^(.*?)=(\"?)(.*?)\\2\\s*\$~", $pair, $matches))
+        {
+          try {
+            $this->message->headers->setAttribute($header_name, $matches[1], $matches[3]);
+          } catch (Swift_Message_MimeException $e) {
+            $this->setError("There was a problem parsing or setting a header attribute:<br />" . $e->getMessage());
+            //Ignored... it's EasySwift... C'mon ;)
+          }
+        }
+      }
+    }
+  }
+  /**
+   * Set a header in the message
+   * @param string The name of the header
+   * @param string The value of the header (without attributes)
+   * @see {addHeaderAttribute}
+   */
+  public function setHeader($name, $value)
+  {
+    $this->message->headers->set($name, $value);
+  }
+  /**
+   * Set an attribute in the message headers
+   * For example charset in Content-Type: text/html; charset=utf-8 set by $swift->setHeaderAttribute("Content-Type", "charset", "utf-8")
+   * @param string The name of the header
+   * @param string The name of the attribute
+   * @param string The value of the attribute
+   */
+  public function setHeaderAttribute($name, $attribute, $value)
+  {
+    if ($this->message->headers->has($name))
+      $this->message->headers->setAttribute($name, $attribute, $value);
+  }
+  /**
+   * Send an email to a number of recipients
+   * Returns the number of successful recipients, or FALSE on failure
+   * @param mixed The recipients to send to.  One of string, array, 2-dimensional array or Swift_Address
+   * @param mixed The address to send from. string or Swift_Address
+   * @param string The message subject
+   * @param string The message body, optional
+   * @return int
+   */
+  public function send($recipients, $from, $subject, $body=null)
+  {
+    $this->addTo($recipients);
+    
+    $sender = false;
+    if (is_string($from)) $sender = $this->stringToAddress($from);
+    elseif ($from instanceof Swift_Address) $sender = $from;
+    if (!$sender) return false;
+    
+    $this->message->setSubject($subject);
+    if ($body) $this->message->setBody($body);
+    try {
+      if (!$this->exactCopy && !$this->recipients->getCc() && !$this->recipients->getBcc())
+      {
+        $sent = $this->swift->batchSend($this->message, $this->recipients, $sender);
+      }
+      else
+      {
+        $sent = $this->swift->send($this->message, $this->recipients, $sender);
+      }
+      if ($this->autoFlush) $this->flush();
+      return $sent;
+    } catch (Swift_ConnectionException $e) {
+      $this->setError("Sending failed:<br />" . $e->getMessage());
+      return false;
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift.php b/system/vendor/swift/Swift.php
new file mode 100755
index 0000000..82a6914
--- /dev/null
+++ b/system/vendor/swift/Swift.php
@@ -0,0 +1,489 @@
+<?php
+
+/**
+ * Swift Mailer Core Component.
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @version 3.3.2
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/Swift/ClassLoader.php";
+Swift_ClassLoader::load("Swift_LogContainer");
+Swift_ClassLoader::load("Swift_ConnectionBase");
+Swift_ClassLoader::load("Swift_BadResponseException");
+Swift_ClassLoader::load("Swift_Cache");
+Swift_ClassLoader::load("Swift_CacheFactory");
+Swift_ClassLoader::load("Swift_Message");
+Swift_ClassLoader::load("Swift_RecipientList");
+Swift_ClassLoader::load("Swift_BatchMailer");
+Swift_ClassLoader::load("Swift_Events");
+Swift_ClassLoader::load("Swift_Events_Listener");
+
+/**
+ * Swift is the central component in the Swift library.
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @version 3.3.2
+ */
+class Swift
+{
+  /**
+   * The version number.
+   */
+  const VERSION = "3.3.2";
+  /**
+   * Constant to flag Swift not to try and connect upon instantiation
+   */
+  const NO_START = 2;
+  /**
+   * Constant to tell Swift not to perform the standard SMTP handshake upon connect
+   */
+  const NO_HANDSHAKE = 4;
+  /**
+   * Constant to ask Swift to start logging
+   */
+  const ENABLE_LOGGING = 8;
+  /**
+   * Constant to prevent postConnect() being run in the connection
+   */
+  const NO_POST_CONNECT = 16;
+  /**
+   * The connection object currently active
+   * @var Swift_Connection
+   */
+  public $connection = null;
+  /**
+   * The domain name of this server (should technically be a FQDN)
+   * @var string
+   */
+  protected $domain = null;
+  /**
+   * Flags to change the behaviour of Swift
+   * @var int
+   */
+  protected $options;
+  /**
+   * Loaded plugins, separated into containers according to roles
+   * @var array
+   */
+  protected $listeners = array();
+  
+  /**
+   * Constructor
+   * @param Swift_Connection The connection object to deal with I/O
+   * @param string The domain name of this server (the client) as a FQDN
+   * @param int Optional flags
+   * @throws Swift_ConnectionException If a connection cannot be established or the connection is behaving incorrectly
+   */
+  public function __construct(Swift_Connection $conn, $domain=false, $options=null)
+  {
+    $this->initializeEventListenerContainer();
+    $this->setOptions($options);
+    
+    $log = Swift_LogContainer::getLog();
+    
+    if ($this->hasOption(self::ENABLE_LOGGING) && !$log->isEnabled())
+    {
+      $log->setLogLevel(Swift_Log::LOG_NETWORK);
+    }
+    
+    if (!$domain) $domain = !empty($_SERVER["SERVER_ADDR"]) ? "[" . $_SERVER["SERVER_ADDR"] . "]" : "localhost.localdomain";
+    
+    $this->setDomain($domain);
+    $this->connection = $conn;
+    
+    if ($conn && !$this->hasOption(self::NO_START))
+    {
+      if ($log->hasLevel(Swift_Log::LOG_EVERYTHING)) $log->add("Trying to connect...", Swift_Log::NORMAL);
+      $this->connect();
+    }
+  }
+  /**
+   * Populate the listeners array with the defined listeners ready for plugins
+   */
+  protected function initializeEventListenerContainer()
+  {
+    Swift_ClassLoader::load("Swift_Events_ListenerMapper");
+    foreach (Swift_Events_ListenerMapper::getMap() as $interface => $method)
+    {
+      if (!isset($this->listeners[$interface]))
+        $this->listeners[$interface] = array();
+    }
+  }
+  /**
+   * Add a new plugin to Swift
+   * Plugins must implement one or more event listeners
+   * @param Swift_Events_Listener The plugin to load
+   */
+  public function attachPlugin(Swift_Events_Listener $plugin, $id)
+  {
+    foreach (array_keys($this->listeners) as $key)
+    {
+      $listener = "Swift_Events_" . $key;
+      Swift_ClassLoader::load($listener);
+      if ($plugin instanceof $listener) $this->listeners[$key][$id] = $plugin;
+    }
+  }
+  /**
+   * Get an attached plugin if it exists
+   * @param string The id of the plugin
+   * @return Swift_Event_Listener
+   */
+  public function getPlugin($id)
+  {
+    foreach ($this->listeners as $type => $arr)
+    {
+      if (isset($arr[$id])) return $this->listeners[$type][$id];
+    }
+    return null; //If none found
+  }
+  /**
+   * Remove a plugin attached under the ID of $id
+   * @param string The ID of the plugin
+   */
+  public function removePlugin($id)
+  {
+    foreach ($this->listeners as $type => $arr)
+    {
+      if (isset($arr[$id]))
+      {
+        $this->listeners[$type][$id] = null;
+        unset($this->listeners[$type][$id]);
+      }
+    }
+  }
+  /**
+   * Send a new type of event to all objects which are listening for it
+   * @param Swift_Events The event to send
+   * @param string The type of event
+   */
+  public function notifyListeners($e, $type)
+  {
+    Swift_ClassLoader::load("Swift_Events_ListenerMapper");
+    if (!empty($this->listeners[$type]) && $notifyMethod = Swift_Events_ListenerMapper::getNotifyMethod($type))
+    {
+      $e->setSwift($this);
+      foreach ($this->listeners[$type] as $k => $listener)
+      {
+        $listener->$notifyMethod($e);
+      }
+    }
+    else $e = null;
+  }
+  /**
+   * Check if an option flag has been set
+   * @param string Option name
+   * @return boolean
+   */
+  public function hasOption($option)
+  {
+    return ($this->options & $option);
+  }
+  /**
+   * Adjust the options flags
+   * E.g. $obj->setOptions(Swift::NO_START | Swift::NO_HANDSHAKE)
+   * @param int The bits to set
+   */
+  public function setOptions($options)
+  {
+    $this->options = (int) $options;
+  }
+  /**
+   * Get the current options set (as bits)
+   * @return int
+   */
+  public function getOptions()
+  {
+    return (int) $this->options;
+  }
+  /**
+   * Set the FQDN of this server as it will identify itself
+   * @param string The FQDN of the server
+   */
+  public function setDomain($name)
+  {
+    $this->domain = (string) $name;
+  }
+  /**
+   * Attempt to establish a connection with the service
+   * @throws Swift_ConnectionException If the connection cannot be established or behaves oddly
+   */
+  public function connect()
+  {
+    $this->connection->start();
+    $greeting = $this->command("", 220);
+    if (!$this->hasOption(self::NO_HANDSHAKE))
+    {
+      $this->handshake($greeting);
+    }
+    Swift_ClassLoader::load("Swift_Events_ConnectEvent");
+    $this->notifyListeners(new Swift_Events_ConnectEvent($this->connection), "ConnectListener");
+  }
+  /**
+   * Disconnect from the MTA
+   * @throws Swift_ConnectionException If the connection will not stop
+   */
+  public function disconnect()
+  {
+    $this->command("QUIT");
+    $this->connection->stop();
+    Swift_ClassLoader::load("Swift_Events_DisconnectEvent");
+    $this->notifyListeners(new Swift_Events_DisconnectEvent($this->connection), "DisconnectListener");
+  }
+  /**
+   * Throws an exception if the response code wanted does not match the one returned
+   * @param Swift_Event_ResponseEvent The full response from the service
+   * @param int The 3 digit response code wanted
+   * @throws Swift_BadResponseException If the code does not match
+   */
+  protected function assertCorrectResponse(Swift_Events_ResponseEvent $response, $codes)
+  {
+    $codes = (array)$codes;
+    if (!in_array($response->getCode(), $codes))
+    {
+      $log = Swift_LogContainer::getLog();
+      $error = "Expected response code(s) [" . implode(", ", $codes) . "] but got response [" . $response->getString() . "]";
+      if ($log->hasLevel(Swift_Log::LOG_ERRORS)) $log->add($error, Swift_Log::ERROR);
+      throw new Swift_BadResponseException($error);
+    }
+  }
+  /**
+   * Have a polite greeting with the server and work out what it's capable of
+   * @param Swift_Events_ResponseEvent The initial service line respoonse
+   * @throws Swift_ConnectionException If conversation is not going very well
+   */
+  protected function handshake(Swift_Events_ResponseEvent $greeting)
+  {
+    if ($this->connection->getRequiresEHLO() || strpos($greeting->getString(), "ESMTP"))
+      $this->setConnectionExtensions($this->command("EHLO " . $this->domain, 250));
+    else $this->command("HELO " . $this->domain, 250);
+    //Connection might want to do something like authenticate now
+    if (!$this->hasOption(self::NO_POST_CONNECT)) $this->connection->postConnect($this);
+  }
+  /**
+   * Set the extensions which the service reports in the connection object
+   * @param Swift_Events_ResponseEvent The list of extensions as reported by the service
+   */
+  protected function setConnectionExtensions(Swift_Events_ResponseEvent $list)
+  {
+    $le = (strpos($list->getString(), "\r\n") !== false) ? "\r\n" : "\n";
+    $list = explode($le, $list->getString());
+    for ($i = 1, $len = count($list); $i < $len; $i++)
+    {
+      $extension = substr($list[$i], 4);
+      $attributes = explode("[ =]", $extension);
+      $this->connection->setExtension($attributes[0], (isset($attributes[1]) ? array_slice($attributes, 1) : array()));
+    }
+  }
+  /**
+   * Execute a command against the service and get the response
+   * @param string The command to execute (leave off any CRLF!!!)
+   * @param int The code to check for in the response, if any. -1 indicates that no response is wanted.
+   * @return Swift_Events_ResponseEvent The server's response (could be multiple lines)
+   * @throws Swift_ConnectionException If a code was expected but does not match the one returned
+   */
+  public function command($command, $code=null)
+  {
+    $log = Swift_LogContainer::getLog();
+    Swift_ClassLoader::load("Swift_Events_CommandEvent");
+    if ($command !== "")
+    {
+      $command_event = new Swift_Events_CommandEvent($command, $code);
+      $command = null; //For memory reasons
+      $this->notifyListeners($command_event, "BeforeCommandListener");
+      if ($log->hasLevel(Swift_Log::LOG_NETWORK) && $code != -1) $log->add($command_event->getString(), Swift_Log::COMMAND);
+      $end = ($code != -1) ? "\r\n" : null;
+      $this->connection->write($command_event->getString(), $end);
+      $this->notifyListeners($command_event, "CommandListener");
+    }
+    
+    if ($code == -1) return null;
+    
+    Swift_ClassLoader::load("Swift_Events_ResponseEvent");
+    $response_event = new Swift_Events_ResponseEvent($this->connection->read());
+    $this->notifyListeners($response_event, "ResponseListener");
+    if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add($response_event->getString(), Swift_Log::RESPONSE);
+    if ($command !== "" && $command_event->getCode() !== null)
+      $this->assertCorrectResponse($response_event, $command_event->getCode());
+    return $response_event;
+  }
+  /**
+   * Reset a conversation which has gone badly
+   * @throws Swift_ConnectionException If the service refuses to reset
+   */
+  public function reset()
+  {
+    $this->command("RSET", 250);
+  }
+  /**
+   * Send a message to any number of recipients
+   * @param Swift_Message The message to send.  This does not need to (and shouldn't really) have any of the recipient headers set.
+   * @param mixed The recipients to send to.  Can be a string, Swift_Address or Swift_RecipientList. Note that all addresses apart from Bcc recipients will appear in the message headers
+   * @param mixed The address to send the message from.  Can either be a string or an instance of Swift_Address.
+   * @return int The number of successful recipients
+   * @throws Swift_ConnectionException If sending fails for any reason.
+   */
+  public function send(Swift_Message $message, $recipients, $from)
+  {
+    Swift_ClassLoader::load("Swift_Message_Encoder");
+    if (is_string($recipients) && preg_match("/^" . Swift_Message_Encoder::CHEAP_ADDRESS_RE . "\$/", $recipients))
+    {
+      $recipients = new Swift_Address($recipients);
+    }
+    elseif (!($recipients instanceof Swift_AddressContainer))
+      throw new Exception("The recipients parameter must either be a valid string email address, ".
+      "an instance of Swift_RecipientList or an instance of Swift_Address.");
+      
+    if (is_string($from) && preg_match("/^" . Swift_Message_Encoder::CHEAP_ADDRESS_RE . "\$/", $from))
+    {
+      $from = new Swift_Address($from);
+    }
+    elseif (!($from instanceof Swift_Address))
+      throw new Exception("The sender parameter must either be a valid string email address or ".
+      "an instance of Swift_Address.");
+    
+    $log = Swift_LogContainer::getLog();
+    
+    if (!$message->getEncoding() && !$this->connection->hasExtension("8BITMIME"))
+    {
+      $message->setEncoding("QP", true, true);
+    }
+    
+    $list = $recipients;
+    if ($recipients instanceof Swift_Address)
+    {
+      $list = new Swift_RecipientList();
+      $list->addTo($recipients);
+    }
+    
+    Swift_ClassLoader::load("Swift_Events_SendEvent");
+    $send_event = new Swift_Events_SendEvent($message, $list, $from, 0);
+    
+    $this->notifyListeners($send_event, "BeforeSendListener");
+    
+    $to = $cc = array();
+    if (!($has_from = $message->getFrom())) $message->setFrom($from);
+    if (!($has_return_path = $message->getReturnPath())) $message->setReturnPath($from->build(true));
+    if (!($has_reply_to = $message->getReplyTo())) $message->setReplyTo($from);
+    if (!($has_message_id = $message->getId())) $message->generateId();
+    
+    $this->command("MAIL FROM: " . $message->getReturnPath(true), 250);
+    
+    $failed = 0;
+    $sent = 0;
+    $tmp_sent = 0;
+    
+    $it = $list->getIterator("to");
+    while ($it->hasNext())
+    {
+      $it->next();
+      $address = $it->getValue();
+      $to[] = $address->build();
+      try {
+        $this->command("RCPT TO: " . $address->build(true), 250);
+        $tmp_sent++;
+      } catch (Swift_BadResponseException $e) {
+        $failed++;
+        $send_event->addFailedRecipient($address->getAddress());
+        if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress());
+      }
+    }
+    $it = $list->getIterator("cc");
+    while ($it->hasNext())
+    {
+      $it->next();
+      $address = $it->getValue();
+      $cc[] = $address->build();
+      try {
+        $this->command("RCPT TO: " . $address->build(true), 250);
+        $tmp_sent++;
+      } catch (Swift_BadResponseException $e) {
+        $failed++;
+        $send_event->addFailedRecipient($address->getAddress());
+        if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress());
+      }
+    }
+    
+    if ($failed == (count($to) + count($cc)))
+    {
+      $this->reset();
+      $this->notifyListeners($send_event, "SendListener");
+      return 0;
+    }
+    
+    if (!($has_to = $message->getTo()) && !empty($to)) $message->setTo($to);
+    if (!($has_cc = $message->getCc()) && !empty($cc)) $message->setCc($cc);
+    
+    $this->command("DATA", 354);
+    $data = $message->build();
+    
+    while (false !== $bytes = $data->read())
+      $this->command($bytes, -1);
+    if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("<MESSAGE DATA>", Swift_Log::COMMAND);
+    try {
+      $this->command("\r\n.", 250);
+      $sent += $tmp_sent;
+    } catch (Swift_BadResponseException $e) {
+      $failed += $tmp_sent;
+    }
+    
+    $tmp_sent = 0;
+    $has_bcc = $message->getBcc();
+    $it = $list->getIterator("bcc");
+    while ($it->hasNext())
+    {
+      $it->next();
+      $address = $it->getValue();
+      if (!$has_bcc) $message->setBcc($address->build());
+      try {
+        $this->command("MAIL FROM: " . $message->getReturnPath(true), 250);
+        $this->command("RCPT TO: " . $address->build(true), 250);
+        $this->command("DATA", 354);
+        $data = $message->build();
+        while (false !== $bytes = $data->read())
+          $this->command($bytes, -1);
+        if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("<MESSAGE DATA>", Swift_Log::COMMAND);
+        $this->command("\r\n.", 250);
+        $sent++;
+      } catch (Swift_BadResponseException $e) {
+        $failed++;
+        $send_event->addFailedRecipient($address->getAddress());
+        if ($log->hasLevel(Swift_Log::LOG_FAILURES)) $log->addfailedRecipient($address->getAddress());
+        $this->reset();
+      }
+    }
+    
+    $total = count($to) + count($cc) + count($list->getBcc());
+    
+    $send_event->setNumSent($sent);
+    $this->notifyListeners($send_event, "SendListener");
+    
+    if (!$has_return_path) $message->setReturnPath("");
+    if (!$has_from) $message->setFrom("");
+    if (!$has_to) $message->setTo("");
+    if (!$has_reply_to) $message->setReplyTo(null);
+    if (!$has_cc) $message->setCc(null);
+    if (!$has_bcc) $message->setBcc(null);
+    if (!$has_message_id) $message->setId(null);
+    
+    if ($log->hasLevel(Swift_Log::LOG_NETWORK)) $log->add("Message sent to " . $sent . "/" . $total . " recipients", Swift_Log::NORMAL);
+    
+    return $sent;
+  }
+  /**
+   * Send a message to a batch of recipients.
+   * Unlike send() this method ignores Cc and Bcc recipients and does not reveal every recipients' address in the headers
+   * @param Swift_Message The message to send (leave out the recipient headers unless you are deliberately overriding them)
+   * @param Swift_RecipientList The addresses to send to
+   * @param Swift_Address The address the mail is from (sender)
+   * @return int The number of successful recipients
+   */
+  public function batchSend(Swift_Message $message, Swift_RecipientList $to, $from)
+  {
+    $batch = new Swift_BatchMailer($this);
+    return $batch->send($message, $to, $from);
+  }
+}
diff --git a/system/vendor/swift/Swift/Address.php b/system/vendor/swift/Swift/Address.php
new file mode 100755
index 0000000..d3a993a
--- /dev/null
+++ b/system/vendor/swift/Swift/Address.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * Swift Mailer Address Container (purely for rigid RFC conformance)
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_AddressContainer");
+
+/**
+ * Swift_Address is just a lone e-mail address reprsented as an object
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Address extends Swift_AddressContainer
+{
+  /**
+   * The e-mail address portion
+   * @var string
+   */
+  protected $address = null;
+  /**
+   * The personal name part
+   * @var string
+   */
+  protected $name = null;
+  
+  /**
+   * Constructor
+   * @param string The address portion
+   * @param string The personal name, optional
+   */
+  public function __construct($address, $name=null)
+  {
+    $this->setAddress($address);
+    if ($name !== null) $this->setName($name);
+  }
+  /**
+   * Set the email address
+   * @param string
+   */
+  public function setAddress($address)
+  {
+    $this->address = trim((string)$address);
+  }
+  /**
+   * Get the address portion
+   * @return string
+   */
+  public function getAddress()
+  {
+    return $this->address;
+  }
+  /**
+   * Set the personal name
+   * @param string
+   */
+  public function setName($name)
+  {
+    if ($name !== null) $this->name = (string) $name;
+    else $this->name = null;
+  }
+  /**
+   * Get personal name portion
+   * @return string
+   */
+  public function getName()
+  {
+    return $this->name;
+  }
+  /**
+   * Build the address the way it should be structured
+   * @param boolean If the string will be sent to a SMTP server as an envelope
+   * @return string
+   */
+  public function build($smtp=false)
+  {
+    if ($smtp)
+    {
+      return "<" . $this->address . ">";
+    }
+    else
+    {
+      if (($this->name !== null))
+      {
+        //return $this->name . " <" . $this->address . ">";
+        // HACK: Add quotes and escaping on address name
+        return "\"" . addslashes($this->name) . "\" <" . $this->address . ">";
+        // END HACK
+      }
+      else return $this->address;
+    }
+  }
+  /**
+   * PHP's casting conversion
+   * @return string
+   */
+  public function __toString()
+  {
+    return $this->build(true);
+  }
+}
diff --git a/system/vendor/swift/Swift/AddressContainer.php b/system/vendor/swift/Swift/AddressContainer.php
new file mode 100755
index 0000000..1ba3aee
--- /dev/null
+++ b/system/vendor/swift/Swift/AddressContainer.php
@@ -0,0 +1,8 @@
+<?php
+
+/**
+ * This is purely here for identify reasons on some subclasses
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+abstract class Swift_AddressContainer {}
diff --git a/system/vendor/swift/Swift/Authenticator.php b/system/vendor/swift/Swift/Authenticator.php
new file mode 100755
index 0000000..7f5e3e6
--- /dev/null
+++ b/system/vendor/swift/Swift/Authenticator.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Swift Mailer Authenticator Interface
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Authenticator
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * Swift Authenticator Interface
+ * Lists the methods all authenticators must implement
+ * @package Swift_Authenticator
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Authenticator
+{
+  /**
+   * Try to authenticate using the username and password
+   * Returns false on failure
+   * @param string The username
+   * @param string The password
+   * @param Swift The instance of Swift this authenticator is used in
+   * @return boolean
+   */
+  public function isAuthenticated($username, $password, Swift $instance);
+  /**
+   * Return the name of the AUTH extension this is for
+   * @return string
+   */
+  public function getAuthExtensionName();
+}
diff --git a/system/vendor/swift/Swift/Authenticator/@PopB4Smtp.php b/system/vendor/swift/Swift/Authenticator/@PopB4Smtp.php
new file mode 100755
index 0000000..2907250
--- /dev/null
+++ b/system/vendor/swift/Swift/Authenticator/@PopB4Smtp.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * Swift Mailer PopB4Smtp Authenticator Mechanism
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Authenticator
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Authenticator");
+Swift_ClassLoader::load("Swift_LogContainer");
+
+/**
+ * Swift PopB4Smtp Authenticator
+ * This form of authentication requires a quick connection to be made with a POP3 server before finally connecting to SMTP
+ * @package Swift_Authenticator
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Authenticator_PopB4Smtp implements Swift_Authenticator
+{
+  protected $connection = null;
+  /**
+   * Constructor
+   * @param mixed Swift_Authenticator_PopB4Smtp_Pop3Connection or string FQDN of POP3 server
+   * @param int The remote port number
+   * @param int The level of encryption to use
+   */
+  public function __construct($conn=null, $port=110, $encryption=0)
+  {
+    if (is_object($conn)) $this->connection = $conn;
+    else
+    {
+      Swift_ClassLoader::load("Swift_Authenticator_PopB4Smtp_Pop3Connection");
+      $this->connection = new Swift_Authenticator_PopB4Smtp_Pop3Connection($conn, $port, $encryption);
+    }
+  }
+  /**
+   * Try to authenticate using the username and password
+   * Returns false on failure
+   * @param string The username
+   * @param string The password
+   * @param Swift The instance of Swift this authenticator is used in
+   * @return boolean
+   */
+  public function isAuthenticated($user, $pass, Swift $swift)
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Trying POP3 Before SMTP authentication.  Disconnecting from SMTP first.");
+    }
+    $swift->disconnect();
+    try {
+      $this->connection->start();
+      $this->connection->assertOk($this->connection->read());
+      $this->connection->write("USER " . $user);
+      $this->connection->assertOk($this->connection->read());
+      $this->connection->write("PASS " . $pass);
+      $this->connection->assertOk($this->connection->read());
+      $this->connection->write("QUIT");
+      $this->connection->assertOk($this->connection->read());
+      $this->connection->stop();
+    } catch (Swift_ConnectionException $e) {
+      if ($log->hasLevel(Swift_Log::LOG_ERRORS))
+      {
+        $log->add("POP3 authentication failed.");
+      }
+      return false;
+    }
+    $options = $swift->getOptions();
+    $swift->setOptions($options | Swift::NO_POST_CONNECT);
+    $swift->connect();
+    $swift->setOptions($options);
+    return true;
+  }
+  /**
+   * Return the name of the AUTH extension this is for
+   * @return string
+   */
+  public function getAuthExtensionName()
+  {
+    return "*PopB4Smtp";
+  }
+}
diff --git a/system/vendor/swift/Swift/Authenticator/CRAMMD5.php b/system/vendor/swift/Swift/Authenticator/CRAMMD5.php
new file mode 100755
index 0000000..0fb2928
--- /dev/null
+++ b/system/vendor/swift/Swift/Authenticator/CRAMMD5.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * Swift Mailer CRAM-MD5 Authenticator Mechanism
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Authenticator
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Authenticator");
+
+/**
+ * Swift CRAM-MD5 Authenticator
+ * This form of authentication is a secure challenge-response method
+ * @package Swift_Authenticator
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Authenticator_CRAMMD5 implements Swift_Authenticator
+{
+  /**
+   * Try to authenticate using the username and password
+   * Returns false on failure
+   * @param string The username
+   * @param string The password
+   * @param Swift The instance of Swift this authenticator is used in
+   * @return boolean
+   */
+  public function isAuthenticated($user, $pass, Swift $swift)
+  {
+    try {
+      $encoded_challenge = substr($swift->command("AUTH CRAM-MD5", 334)->getString(), 4);
+      $challenge = base64_decode($encoded_challenge);
+      $response = base64_encode($user . " " . self::generateCRAMMD5Hash($pass, $challenge));
+      $swift->command($response, 235);
+    } catch (Swift_ConnectionException $e) {
+      $swift->reset();
+      return false;
+    }
+    return true;
+  }
+  /**
+   * Return the name of the AUTH extension this is for
+   * @return string
+   */
+  public function getAuthExtensionName()
+  {
+    return "CRAM-MD5";
+  }
+  /**
+   * Generate a CRAM-MD5 hash from a challenge
+   * @param string The string to get a hash from
+   * @param string The challenge to use to make the hash
+   * @return string
+   */
+  public static function generateCRAMMD5Hash($password, $challenge)
+  {
+    if (strlen($password) > 64)
+      $password = pack('H32', md5($password));
+
+    if (strlen($password) < 64)
+      $password = str_pad($password, 64, chr(0));
+
+    $k_ipad = substr($password, 0, 64) ^ str_repeat(chr(0x36), 64);
+    $k_opad = substr($password, 0, 64) ^ str_repeat(chr(0x5C), 64);
+
+    $inner  = pack('H32', md5($k_ipad.$challenge));
+    $digest = md5($k_opad.$inner);
+
+    return $digest;
+  }
+}
diff --git a/system/vendor/swift/Swift/Authenticator/LOGIN.php b/system/vendor/swift/Swift/Authenticator/LOGIN.php
new file mode 100755
index 0000000..fd6b829
--- /dev/null
+++ b/system/vendor/swift/Swift/Authenticator/LOGIN.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Swift Mailer LOGIN Authenticator Mechanism
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Authenticator
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Authenticator");
+
+/**
+ * Swift LOGIN Authenticator
+ * @package Swift_Authenticator
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Authenticator_LOGIN implements Swift_Authenticator
+{
+  /**
+   * Try to authenticate using the username and password
+   * Returns false on failure
+   * @param string The username
+   * @param string The password
+   * @param Swift The instance of Swift this authenticator is used in
+   * @return boolean
+   */
+  public function isAuthenticated($user, $pass, Swift $swift)
+  {
+    try {
+      $swift->command("AUTH LOGIN", 334);
+      $swift->command(base64_encode($user), 334);
+      $swift->command(base64_encode($pass), 235);
+    } catch (Swift_ConnectionException $e) {
+      $swift->reset();
+      return false;
+    }
+    return true;
+  }
+  /**
+   * Return the name of the AUTH extension this is for
+   * @return string
+   */
+  public function getAuthExtensionName()
+  {
+    return "LOGIN";
+  }
+}
diff --git a/system/vendor/swift/Swift/Authenticator/PLAIN.php b/system/vendor/swift/Swift/Authenticator/PLAIN.php
new file mode 100755
index 0000000..d4a459d
--- /dev/null
+++ b/system/vendor/swift/Swift/Authenticator/PLAIN.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Swift Mailer PLAIN Authenticator Mechanism
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Authenticator
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Authenticator");
+
+/**
+ * Swift PLAIN Authenticator
+ * This form of authentication is unbelievably insecure since everything is done plain-text
+ * @package Swift_Authenticator
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Authenticator_PLAIN implements Swift_Authenticator
+{
+  /**
+   * Try to authenticate using the username and password
+   * Returns false on failure
+   * @param string The username
+   * @param string The password
+   * @param Swift The instance of Swift this authenticator is used in
+   * @return boolean
+   */
+  public function isAuthenticated($user, $pass, Swift $swift)
+  {
+    try {
+      //The authorization string uses ascii null as a separator (See RFC 2554)
+      $credentials = base64_encode($user . chr(0) . $user . chr(0) . $pass);
+      $swift->command("AUTH PLAIN " . $credentials, 235);
+    } catch (Swift_ConnectionException $e) {
+      $swift->reset();
+      return false;
+    }
+    return true;
+  }
+  /**
+   * Return the name of the AUTH extension this is for
+   * @return string
+   */
+  public function getAuthExtensionName()
+  {
+    return "PLAIN";
+  }
+}
diff --git a/system/vendor/swift/Swift/Authenticator/PopB4Smtp/Pop3Connection.php b/system/vendor/swift/Swift/Authenticator/PopB4Smtp/Pop3Connection.php
new file mode 100755
index 0000000..1cd1328
--- /dev/null
+++ b/system/vendor/swift/Swift/Authenticator/PopB4Smtp/Pop3Connection.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Swift Mailer PopB4Smtp Pop3 Connection component
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Authenticator
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../../ClassLoader.php";
+
+/**
+ * Swift PopB4Smtp Authenticator Connection Component for the POP3 server
+ * Provides a I/O wrapper for the POP3 connection
+ * @package Swift_Authenticator
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Authenticator_PopB4Smtp_Pop3Connection
+{
+  /**
+   * Constant for no encyption
+   */
+  const ENC_OFF = 0;
+  /**
+   * Constant for SSL encryption
+   */
+  const ENC_SSL = 1;
+  /**
+   * The server to connect to (IP or FQDN)
+   * @var string
+   */
+  protected $server = null;
+  /**
+   * The port to connect to
+   * @var int
+   */
+  protected $port = null;
+  /**
+   * The open connection resource from fsockopen()
+   * @var resource
+   */
+  protected $handle = null;
+  
+  /**
+   * Constructor
+   * @param string The name of the POP3 server
+   * @param int The port for the POP3 service
+   * @param int The level of encryption to use
+   */
+  public function __construct($server="localhost", $port=110, $encryption=0)
+  {
+    $this->setServer($server);
+    $this->setPort($port);
+    $this->setEncryption($encryption);
+  }
+  /**
+   * Set the server name
+   * @param string The IP or FQDN of the POP3 server
+   */
+  public function setServer($server)
+  {
+    $this->server = (string) $server;
+  }
+  /**
+   * Set the port number for the POP3 server
+   * @param int
+   */
+  public function setPort($port)
+  {
+    $this->port = (int) $port;
+  }
+  /**
+   * Get the server name
+   * @return string
+   */
+  public function getServer()
+  {
+    return $this->server;
+  }
+  /**
+   * Get the remote port number
+   * @return int
+   */
+  public function getPort()
+  {
+    return $this->port;
+  }
+  /**
+   * Set the level of enryption to use (see ENC_OFF or ENC_SSL)
+   * @param int The constant for the encryption level
+   */
+  public function setEncryption($enc)
+  {
+    $this->encryption = (int) $enc;
+  }
+  /**
+   * Get the current encryption level set (corresponds to ENC_SSL or ENC_OFF)
+   * @return int
+   */
+  public function getEncryption()
+  {
+    return $this->encryption;
+  }
+  /**
+   * Check if the response is a +OK response
+   * @throws Swift_ConnectionException Upon bad response
+   */
+  public function assertOk($line)
+  {
+    if (substr($line, 0, 3) != "+OK")
+    {
+      Swift_ClassLoader::load("Swift_ConnectionException");
+      throw new Swift_ConnectionException("The POP3 server did not suitably respond with a +OK response. " .
+      "[" . $line . "]");
+    }
+  }
+  /**
+   * Try to open the connection
+   * @throws Swift_ConnectionException If the connection will not start
+   */
+  public function start()
+  {
+    $url = $this->getServer();
+    if ($this->getEncryption() == self::ENC_SSL) $url = "ssl://" . $url;
+    
+    if ((false === $this->handle = fsockopen($url, $this->getPort(), $errno, $errstr, $timeout)))
+    {
+      Swift_ClassLoader::load("Swift_ConnectionException");
+      throw new Swift_ConnectionException("The POP3 connection failed to start.  The error string returned from fsockopen() is [" . $errstr . "] #" . $errno);
+    }
+  }
+  /**
+   * Try to close the connection
+   * @throws Swift_ConnectionException If the connection won't close
+   */
+  public function stop()
+  {
+    if ($this->handle !== null)
+    {
+      if (false === fclose($this->handle))
+      {
+        Swift_ClassLoader::load("Swift_ConnectionException");
+        throw new Swift_ConnectionException("The POP3 connection did not close successfully.");
+      }
+    }
+    $this->handle = null;
+  }
+  /**
+   * Return the unread buffer contents
+   * @return string
+   * @throws Swift_ConnectionException If the connection will not allow data to be read
+   */
+  public function read()
+  {
+    if (false === $response = fgets($this->handle))
+    {
+      Swift_ClassLoader::load("Swift_ConnectionException");
+      throw new Swift_ConnectionException("Data could not be read from the POP3 connection.");
+    }
+    return trim($response);
+  }
+  /**
+   * Write a command to the remote socket
+   * @param string the command to send (without CRLF)
+   * @throws Swift_ConnectionException If the command cannot be written
+   */
+  public function write($command)
+  {
+    if (false !== fwrite($this->handle, $command . "\r\n"))
+    {
+      Swift_ClassLoader::load("Swift_ConnectionException");
+      throw new Swift_ConnectionException("Data could not be written to the POP3 connection.");
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift/BadResponseException.php b/system/vendor/swift/Swift/BadResponseException.php
new file mode 100755
index 0000000..6b4a72d
--- /dev/null
+++ b/system/vendor/swift/Swift/BadResponseException.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Swift Bad Response Code Exception
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_ConnectionException");
+
+/**
+ * Swift Bad Response Exception
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_BadResponseException extends Swift_ConnectionException
+{
+}
diff --git a/system/vendor/swift/Swift/BatchMailer.php b/system/vendor/swift/Swift/BatchMailer.php
new file mode 100755
index 0000000..459122a
--- /dev/null
+++ b/system/vendor/swift/Swift/BatchMailer.php
@@ -0,0 +1,229 @@
+<?php
+
+/**
+ * Handles batch mailing with Swift Mailer with fail-safe support.
+ * Restarts the connection if it dies and then continues where it left off.
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+class Swift_BatchMailer
+{
+  /**
+   * The current instance of Swift.
+   * @var Swift
+   */
+  protected $swift;
+  /**
+   * The maximum number of times a single recipient can be attempted before giving up.
+   * @var int
+   */
+  protected $maxTries = 2;
+  /**
+   * The number of seconds to sleep for if an error occurs.
+   * @var int
+   */
+  protected $sleepTime = 0;
+  /**
+   * Failed recipients (undeliverable)
+   * @var array
+   */
+  protected $failed = array();
+  /**
+   * The maximum number of successive failures before giving up.
+   * @var int
+   */
+  protected $maxFails = 0;
+  /**
+   * A temporary copy of some message headers.
+   * @var array
+   */
+  protected $headers = array();
+  
+  /**
+   * Constructor.
+   * @param Swift The current instance of Swift
+   */
+  public function __construct(Swift $swift)
+  {
+    $this->setSwift($swift);
+  }
+  /**
+   * Set the current Swift instance.
+   * @param Swift The instance
+   */
+  public function setSwift(Swift $swift)
+  {
+    $this->swift = $swift;
+  }
+  /**
+   * Get the Swift instance which is running.
+   * @return Swift
+   */
+  public function getSwift()
+  {
+    return $this->swift;
+  }
+  /**
+   * Set the maximum number of times a single address is allowed to be retried.
+   * @param int The maximum number of tries.
+   */
+  public function setMaxTries($max)
+  {
+    $this->maxTries = abs($max);
+  }
+  /**
+   * Get the number of times a single address will be attempted in a batch.
+   * @return int
+   */
+  public function getMaxTries()
+  {
+    return $this->maxTries;
+  }
+  /**
+   * Set the amount of time to sleep for if an error occurs.
+   * @param int Number of seconds
+   */
+  public function setSleepTime($secs)
+  {
+    $this->sleepTime = abs($secs);
+  }
+  /**
+   * Get the amount of time to sleep for on errors.
+   * @return int
+   */
+  public function getSleepTime()
+  {
+    return $this->sleepTime;
+  }
+  /**
+   * Log a failed recipient.
+   * @param string The email address.
+   */
+  public function addFailedRecipient($address)
+  {
+    $this->failed[] = $address;
+    $this->failed = array_unique($this->failed);
+  }
+  /**
+   * Get all recipients which failed in this batch.
+   * @return array
+   */
+  public function getFailedRecipients()
+  {
+    return $this->failed;
+  }
+  /**
+   * Clear out the list of failed recipients.
+   */
+  public function flushFailedRecipients()
+  {
+    $this->failed = null;
+    $this->failed = array();
+  }
+  /**
+   * Set the maximum number of times an error can be thrown in succession and still be hidden.
+   * @param int
+   */
+  public function setMaxSuccessiveFailures($fails)
+  {
+    $this->maxFails = abs($fails);
+  }
+  /**
+   * Get the maximum number of times an error can be thrown and still be hidden.
+   * @return int
+   */
+  public function getMaxSuccessiveFailures()
+  {
+    return $this->maxFails;
+  }
+  /**
+   * Restarts Swift forcibly.
+   */
+  protected function forceRestartSwift()
+  {
+    //Pre-empting problems trying to issue "QUIT" to a dead connection
+    $this->swift->connection->stop();
+    $this->swift->connection->start();
+    $this->swift->disconnect();
+    //Restart swift
+    $this->swift->connect();
+  }
+  /**
+   * Takes a temporary copy of original message headers in case an error occurs and they need restoring.
+   * @param Swift_Message The message object
+   */
+  protected function copyMessageHeaders(&$message)
+  {
+    $this->headers["To"] = $message->headers->has("To") ?
+      $message->headers->get("To") : null;
+    $this->headers["Reply-To"] = $message->headers->has("Reply-To") ?
+      $message->headers->get("Reply-To") : null;
+    $this->headers["Return-Path"] = $message->headers->has("Return-Path") ?
+      $message->headers->get("Return-Path") : null;
+    $this->headers["From"] = $message->headers->has("From") ?
+      $message->headers->get("From") : null;
+  }
+  /**
+   * Restore message headers to original values in the event of a failure.
+   * @param Swift_Message The message
+   */
+  protected function restoreMessageHeaders(&$message)
+  {
+    foreach ($this->headers as $name => $value)
+    {
+      $message->headers->set($name, $value);
+    }
+  }
+  /**
+   * Run a batch send in a fail-safe manner.
+   * This operates as Swift::batchSend() except it deals with errors itself.
+   * @param Swift_Message To send
+   * @param Swift_RecipientList Recipients (To: only)
+   * @param Swift_Address The sender's address
+   * @return int The number sent to
+   */
+  public function send(Swift_Message $message, Swift_RecipientList $recipients, $sender)
+  {
+    $sent = 0;
+    $successive_fails = 0;
+    
+    $it = $recipients->getIterator("to");
+    while ($it->hasNext())
+    {
+      $it->next();
+      $recipient = $it->getValue();
+      $tried = 0;
+      $loop = true;
+      while ($loop && $tried < $this->getMaxTries())
+      {
+        try {
+          $tried++;
+          $loop = false;
+          $this->copyMessageHeaders($message);
+          $sent += ($n = $this->swift->send($message, $recipient, $sender));
+          if (!$n) $this->addFailedRecipient($recipient->getAddress());
+          $successive_fails = 0;
+        } catch (Exception $e) {
+          $successive_fails++;
+          $this->restoreMessageHeaders($message);
+          if (($max = $this->getMaxSuccessiveFailures())
+            && $successive_fails > $max)
+          {
+            throw new Exception(
+              "Too many successive failures. BatchMailer is configured to allow no more than " . $max .
+              " successive failures.");
+          }
+          //If an exception was thrown, give it one more go
+          if ($t = $this->getSleepTime()) sleep($t);
+          $this->forceRestartSwift();
+          $loop = true;
+        }
+      }
+    }
+    
+    return $sent;
+  }
+}
diff --git a/system/vendor/swift/Swift/Cache.php b/system/vendor/swift/Swift/Cache.php
new file mode 100755
index 0000000..b2a536b
--- /dev/null
+++ b/system/vendor/swift/Swift/Cache.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Swift Mailer Runtime caching base component.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Cache
+ * @license GNU Lesser General Public License
+ */
+ 
+require_once dirname(__FILE__) . "/ClassLoader.php";
+
+/**
+ * The interface for any cache mechanisms to follow
+ * @package Swift_Cache
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+abstract class Swift_Cache
+{
+  /**
+   * Append bytes to the cache buffer identified by $key
+   * @param string The Cache key
+   * @param string The bytes to append
+   */
+  abstract public function write($key, $data);
+  /**
+   * Clear out the buffer for $key
+   * @param string The cache key
+   */
+  abstract public function clear($key);
+  /**
+   * Check if there is something in the cache for $key
+   * @param string The cache key
+   * @return boolean
+   */
+  abstract public function has($key);
+  /**
+   * Read bytes from the cached buffer and seek forward in the buffer
+   * Returns false once no more bytes are left to read
+   * @param int The number of bytes to read (may be ignored)
+   * @return string
+   */
+  abstract public function read($key, $size=null);
+  /**
+   * A factory method to return an output stream object for the relevant location in the cache
+   * @param string The cache key to fetch the stream for
+   * @return Swift_Cache_OutputStream
+   */
+  public function getOutputStream($key)
+  {
+    Swift_ClassLoader::load("Swift_Cache_OutputStream");
+    $os = new Swift_Cache_OutputStream($this, $key);
+    return $os;
+  }
+}
diff --git a/system/vendor/swift/Swift/Cache/Disk.php b/system/vendor/swift/Swift/Cache/Disk.php
new file mode 100755
index 0000000..d5c8291
--- /dev/null
+++ b/system/vendor/swift/Swift/Cache/Disk.php
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * Swift Mailer disk runtime cache
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Cache
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Cache");
+
+/**
+ * Caches data in files on disk - this is the best approach if possible
+ * @package Swift_Cache
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Cache_Disk extends Swift_Cache
+{
+  /**
+   * Open file handles
+   * @var array
+   */
+  protected $open = array();
+  /**
+   * The prefix to prepend to files
+   * @var string
+   */
+  protected $prefix;
+  /**
+   * The save path on disk
+   * @var string
+   */
+  protected static $save_path = "/tmp";
+  
+  /**
+   * Ctor
+   */
+  public function __construct()
+  {
+    $this->prefix = md5(uniqid(microtime(), true));
+  }
+  /**
+   * Set the save path of the disk - this is a global setting and called statically!
+   * @param string The path to a writable directory
+   */
+  public static function setSavePath($path="/tmp")
+  {
+    self::$save_path = realpath($path);
+  }
+  /**
+   * Write data to the cache
+   * @param string The cache key
+   * @param string The data to write
+   */
+  public function write($key, $data)
+  {
+    $handle = fopen(self::$save_path . "/" . $this->prefix . $key, "ab");
+    if (false === fwrite($handle, $data))
+    {
+      Swift_ClassLoader::load("Swift_FileException");
+      throw new Swift_FileException("Disk Caching failed.  Tried to write to file at [" .
+        self::$save_path . "/" . $this->prefix . $key . "] but failed.  Check the permissions, or don't use disk caching.");
+    }
+    fclose($handle);
+  }
+  /**
+   * Clear the cached data (unlink)
+   * @param string The cache key
+   */
+  public function clear($key)
+  {
+    @unlink(self::$save_path . "/" . $this->prefix . $key);
+  }
+  /**
+   * Check if data is cached for $key
+   * @param string The cache key
+   * @return boolean
+   */
+  public function has($key)
+  {
+    return file_exists(self::$save_path . "/" . $this->prefix . $key);
+  }
+  /**
+   * Read data from the cache for $key
+   * @param string The cache key
+   * @param int The number of bytes to read
+   * @return string
+   */
+  public function read($key, $size=null)
+  {
+    if ($size === null) $size = 8190;
+    if (!$this->has($key)) return false;
+    
+    if (!isset($this->open[$key]))
+    {
+      $this->open[$key] = fopen(self::$save_path . "/" . $this->prefix . $key, "rb");
+    }
+    if (feof($this->open[$key]))
+    {
+      fclose($this->open[$key]);
+      unset($this->open[$key]);
+      return false;
+    }
+    $ret = fread($this->open[$key], $size);
+    if ($ret !== false)
+    {
+      return $ret;
+    }
+    else
+    {
+      fclose($this->open[$key]);
+      unset($this->open[$key]);
+      return false;
+    }
+  }
+  /**
+   * Dtor.
+   * Clear out cached data at end of script execution or cache destruction
+   */
+  public function __destruct()
+  {
+    $list = glob(self::$save_path . "/" . $this->prefix . "*");
+    foreach ((array)$list as $f)
+    {
+      @unlink($f);
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift/Cache/JointOutputStream.php b/system/vendor/swift/Swift/Cache/JointOutputStream.php
new file mode 100755
index 0000000..5e731db
--- /dev/null
+++ b/system/vendor/swift/Swift/Cache/JointOutputStream.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Swift Mailer Joint Output stream to chain multiple output streams together
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Cache
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Cache_OutputStream");
+
+/**
+ * Makes multiple output streams act as one super sream
+ * @package Swift_Cache
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Cache_JointOutputStream extends Swift_Cache_OutputStream
+{
+  /**
+   * The streams to join
+   * @var array
+   */
+  protected $streams = array();
+  /**
+   * The current stream in use
+   * @var int
+   */
+  protected $pointer = 0;
+  
+  /**
+   * Ctor
+   * @param array An array of Swift_Cache_OutputStream instances
+   */
+  public function __construct($streams=array())
+  {
+    $this->streams = $streams;
+  }
+  /**
+   * Add a new output stream
+   * @param Swift_Cache_OutputStream
+   */
+  public function addStream(Swift_Cache_OutputStream $stream)
+  {
+    $this->streams[] = $stream;
+  }
+  /**
+   * Read data from all streams as if they are one stream
+   * @param int The number of bytes to read from each stream
+   * @return string
+   */
+  public function read($size=null)
+  {
+    $ret = $this->streams[$this->pointer]->read($size);
+    if ($ret !== false)
+    {
+      return $ret;
+    }
+    else
+    {
+      if (isset($this->streams[($this->pointer+1)]))
+      {
+        $this->pointer++;
+        return $this->read($size);
+      }
+      else
+      {
+        $this->pointer = 0;
+        return false;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/system/vendor/swift/Swift/Cache/Memory.php b/system/vendor/swift/Swift/Cache/Memory.php
new file mode 100755
index 0000000..9bb177c
--- /dev/null
+++ b/system/vendor/swift/Swift/Cache/Memory.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * Swift Mailer Native memory runtime cache
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Cache
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Cache");
+
+/**
+ * Caches data in variables - uses memory!
+ * @package Swift_Cache
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Cache_Memory extends Swift_Cache
+{
+  /**
+   * The storage container for this cache
+   * @var array
+   */
+  protected $store = array();
+  /**
+   * The key which was last requested
+   * @var string
+   */
+  protected $requested;
+  
+  /**
+   * Write data to the cache
+   * @param string The cache key
+   * @param string The data to write
+   */
+  public function write($key, $data)
+  {
+    if (!isset($this->store[$key])) $this->store[$key] = $data;
+    else $this->store[$key] .= $data;
+  }
+  /**
+   * Clear the cached data (unset)
+   * @param string The cache key
+   */
+  public function clear($key)
+  {
+    $this->store[$key] = null;
+    unset($this->store[$key]);
+  }
+  /**
+   * Check if data is cached for $key
+   * @param string The cache key
+   * @return boolean
+   */
+  public function has($key)
+  {
+    return array_key_exists($key, $this->store);
+  }
+  /**
+   * Read data from the cache for $key
+   * It makes no difference to memory/speed if data is read in chunks so arguments are ignored
+   * @param string The cache key
+   * @return string
+   */
+  public function read($key, $size=null)
+  {
+    if (!$this->has($key)) return false;
+    
+    if ($this->requested == $key)
+    {
+      $this->requested = null;
+      return false;
+    }
+    $this->requested = $key;
+    return $this->store[$key];
+  }
+}
diff --git a/system/vendor/swift/Swift/Cache/OutputStream.php b/system/vendor/swift/Swift/Cache/OutputStream.php
new file mode 100755
index 0000000..5c93c00
--- /dev/null
+++ b/system/vendor/swift/Swift/Cache/OutputStream.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * Swift Mailer Output stream to read bytes from cached data
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Cache
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * The wraps the streaming functionality of the cache
+ * @package Swift_Cache
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Cache_OutputStream
+{
+  /**
+   * The key to read in the actual cache
+   * @var string
+   */
+  protected $key;
+  /**
+   * The cache object to read
+   * @var Swift_Cache
+   */
+  protected $cache;
+  
+  /**
+   * Ctor.
+   * @param Swift_Cache The cache to read from
+   * @param string The key for the cached data
+   */
+  public function __construct(Swift_Cache $cache, $key)
+  {
+    $this->cache = $cache;
+    $this->key = $key;
+  }
+  /**
+   * Read bytes from the cache and seek through the buffer
+   * Returns false if EOF is reached
+   * @param int The number of bytes to read (could be ignored)
+   * @return string The read bytes
+   */
+  public function read($size=null)
+  {
+    return $this->cache->read($this->key, $size);
+  }
+  /**
+   * Read the entire cached data as one string
+   * @return string
+   */
+  public function readFull()
+  {
+    $ret = "";
+    while (false !== $bytes = $this->read())
+      $ret .= $bytes;
+    return $ret;
+  }
+}
diff --git a/system/vendor/swift/Swift/CacheFactory.php b/system/vendor/swift/Swift/CacheFactory.php
new file mode 100755
index 0000000..49f5623
--- /dev/null
+++ b/system/vendor/swift/Swift/CacheFactory.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Swift Mailer Cache Factory class
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Cache
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+
+/**
+ * Makes instances of the cache the user has defined
+ * @package Swift_Cache
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_CacheFactory
+{
+  /**
+   * The name of the class which defines the cache
+   * @var string Case SenSITivE
+   */
+  protected static $className = "Swift_Cache_Memory";
+  
+  /**
+   * Set the name of the class which is supposed to be used
+   * This also includes the file
+   * @param string The class name
+   */
+  public static function setClassName($name)
+  {
+    Swift_ClassLoader::load($name);
+    self::$className = $name;
+  }
+  /**
+   * Return a new instance of the cache object
+   * @return Swift_Cache
+   */
+  public static function getCache()
+  {
+    $className = self::$className;
+    Swift_ClassLoader::load($className);
+    $instance = new $className();
+    return $instance;
+  }
+}
diff --git a/system/vendor/swift/Swift/ClassLoader.php b/system/vendor/swift/Swift/ClassLoader.php
new file mode 100755
index 0000000..7554a12
--- /dev/null
+++ b/system/vendor/swift/Swift/ClassLoader.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Swift Mailer Class Loader for includes
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+
+if (!defined("SWIFT_ABS_PATH")) define("SWIFT_ABS_PATH", dirname(__FILE__) . "/..");
+
+/**
+ * Locates and includes class files
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_ClassLoader
+{
+  /**
+   * A list of files already located
+   * @var array
+   */
+  protected static $located = array();
+  
+  /**
+   * Load a new class into memory
+   * @param string The name of the class, case SenSItivE
+   */
+  public static function load($name)
+  {
+    if (in_array($name, self::$located) || class_exists($name, false) || interface_exists($name, false))
+      return;
+    
+    require_once SWIFT_ABS_PATH . "/" . str_replace("_", "/", $name) . ".php";
+    self::$located[] = $name;
+  }
+}
diff --git a/system/vendor/swift/Swift/Connection.php b/system/vendor/swift/Swift/Connection.php
new file mode 100755
index 0000000..93219cc
--- /dev/null
+++ b/system/vendor/swift/Swift/Connection.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Swift Mailer Connection Interface
+ * All connection handlers extend this abstract class
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * Swift Connection Interface
+ * Lists methods which are required by any connections
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Connection
+{
+  /**
+   * Try to start the connection
+   * @throws Swift_ConnectionException If the connection cannot be started
+   */
+  public function start();
+  /**
+   * Return the contents of the buffer
+   * @return string
+   * @throws Swift_ConnectionException If the buffer cannot be read
+   */
+  public function read();
+  /**
+   * Write a command to the buffer
+   * @param string The command to send
+   * @throws Swift_ConnectionException If the write fails
+   */
+  public function write($command, $end="\r\n");
+  /**
+   * Try to stop the connection
+   * @throws Swift_ConnectionException If the connection cannot be closed/stopped
+   */
+  public function stop();
+  /**
+   * Check if the connection is up or not
+   * @return boolean
+   */
+  public function isAlive();
+  /**
+   * Add an extension which is available on this connection
+   * @param string The name of the extension
+   * @param array The list of attributes for the extension
+   */
+  public function setExtension($name, $list=array());
+  /**
+   * Check if an extension exists by the name $name
+   * @param string The name of the extension
+   * @return boolean
+   */
+  public function hasExtension($name);
+  /**
+   * Get the list of attributes for the extension $name
+   * @param string The name of the extension
+   * @return array
+   * @throws Swift_ConnectionException If no such extension can be found
+   */
+  public function getAttributes($name);
+  /**
+   * Execute logic needed after SMTP greetings
+   * @param Swift An instance of Swift
+   */
+  public function postConnect(Swift $instance);
+  /**
+   * Returns TRUE if the connection needs a EHLO greeting.
+   * @return boolean
+   */
+  public function getRequiresEHLO();
+  /**
+   * Set if the connection needs a EHLO greeting.
+   * @param boolean
+   */
+  public function setRequiresEHLO($set);
+}
diff --git a/system/vendor/swift/Swift/Connection/Multi.php b/system/vendor/swift/Swift/Connection/Multi.php
new file mode 100755
index 0000000..e766ebd
--- /dev/null
+++ b/system/vendor/swift/Swift/Connection/Multi.php
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * Swift Mailer Multiple Redundant Connection component.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+ 
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_ConnectionBase");
+
+/**
+ * Swift Multi Connection
+ * Tries to connect to a number of connections until one works successfully
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Connection_Multi extends Swift_ConnectionBase
+{
+  /**
+   * The list of available connections
+   * @var array
+   */
+  protected $connections = array();
+  /**
+   * The id of the active connection
+   * @var string
+   */
+  protected $active = null;
+  
+  /**
+   * Constructor
+   */
+  public function __construct($connections=array())
+  {
+    foreach ($connections as $id => $conn)
+    {
+      $this->addConnection($connections[$id], $id);
+    }
+  }
+  /**
+   * Add a connection to the list of options
+   * @param Swift_Connection An instance of the connection
+   * @param string An ID to assign to the connection
+   */
+  public function addConnection(Swift_Connection $connection, $id=null)
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Adding new connection of type '" . get_class($connection) . "' to the multi-redundant connection.");
+    }
+    if ($id !== null) $this->connections[$id] = $connection;
+    else $this->connections[] = $connection;
+  }
+  /**
+   * Read a full response from the buffer
+   * @return string
+   * @throws Swift_ConnectionException Upon failure to read
+   */
+  public function read()
+  {
+    if ($this->active === null)
+    {
+      throw new Swift_ConnectionException("None of the connections set have been started");
+    }
+    return $this->connections[$this->active]->read();
+  }
+  /**
+   * Write a command to the server (leave off trailing CRLF)
+   * @param string The command to send
+   * @throws Swift_ConnectionException Upon failure to write
+   */
+  public function write($command, $end="\r\n")
+  {
+    if ($this->active === null)
+    {
+      throw new Swift_ConnectionException("None of the connections set have been started");
+    }
+    return $this->connections[$this->active]->write($command, $end);
+  }
+  /**
+   * Try to start the connection
+   * @throws Swift_ConnectionException Upon failure to start
+   */
+  public function start()
+  {
+    $log = Swift_LogContainer::getLog();
+    $fail_messages = array();
+    foreach ($this->connections as $id => $conn)
+    {
+      try {
+        $this->connections[$id]->start();
+        if ($this->connections[$id]->isAlive())
+        {
+          $this->active = $id;
+          return true;
+        }
+        else
+        {
+          if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+          {
+            $log->add("Connection (" . $id . ") failed. Will try next connection if available.");
+          }
+          throw new Swift_ConnectionException("The connection started but reported that it was not active");
+        }
+      } catch (Swift_ConnectionException $e) {
+        $fail_messages[] = $id . ": " . $e->getMessage();
+      }
+    }
+    $failure = implode("<br />", $fail_messages);
+    throw new Swift_ConnectionException($failure);
+  }
+  /**
+   * Try to close the connection
+   * @throws Swift_ConnectionException Upon failure to close
+   */
+  public function stop()
+  {
+    if ($this->active !== null) $this->connections[$this->active]->stop();
+    $this->active = null;
+  }
+  /**
+   * Check if the current connection is alive
+   * @return boolean
+   */
+  public function isAlive()
+  {
+    return (($this->active !== null) && $this->connections[$this->active]->isAlive());
+  }
+  /**
+   * Call the current connection's postConnect() method
+   */
+  public function postConnect(Swift $instance)
+  {
+    $this->connections[$this->active]->postConnect($instance);
+  }
+  /**
+   * Call the current connection's setExtension() method
+   */
+  public function setExtension($extension, $attributes=array())
+  {
+    $this->connections[$this->active]->setExtension($extension, $attributes);
+  }
+  /**
+   * Call the current connection's hasExtension() method
+   */
+  public function hasExtension($name)
+  {
+    return $this->connections[$this->active]->hasExtension($name);
+  }
+  /**
+   * Call the current connection's getAttributes() method
+   */
+  public function getAttributes($name)
+  {
+    return $this->connections[$this->active]->getAttributes($name);
+  }
+}
diff --git a/system/vendor/swift/Swift/Connection/NativeMail.php b/system/vendor/swift/Swift/Connection/NativeMail.php
new file mode 100755
index 0000000..635542a
--- /dev/null
+++ b/system/vendor/swift/Swift/Connection/NativeMail.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * Swift Mailer mail() connection component
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_ConnectionBase");
+Swift_ClassLoader::load("Swift_Plugin_MailSend");
+
+/**
+ * Swift mail() Connection
+ * NOTE: This class is nothing more than a stub.  The MailSend plugin does the actual sending.
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Connection_NativeMail extends Swift_ConnectionBase
+{
+  /**
+   * The response the stub will be giving next
+   * @var string Response
+   */
+  protected $response = "220 Stubbed";
+  /**
+   * The 5th parameter in mail() is a sprintf() formatted string.
+   * @var string
+   */
+  protected $pluginParams;
+  /**
+   * An instance of the MailSend plugin.
+   * @var Swift_Plugin_MailSend
+   */
+  protected $plugin = null;
+  
+  /**
+   * Ctor.
+   * @param string The 5th parameter in mail() as a sprintf() formatted string where %s is the sender address. This only comes into effect if safe_mode is OFF.
+   */
+  public function __construct($additional_params="-oi -f %s")
+  {
+    $this->setAdditionalMailParams($additional_params);
+  }
+  /**
+   * Sets the MailSend plugin in Swift once Swift has connected
+   * @param Swift The current instance of Swift
+   */
+  public function postConnect(Swift $instance)
+  {
+    $this->plugin = new Swift_Plugin_MailSend($this->getAdditionalMailParams());
+    $instance->attachPlugin($this->plugin, "_MAIL_SEND");
+  }
+  /**
+   * Set the 5th parameter in mail() as a sprintf() formatted string. Only used if safe_mode is off.
+   * @param string
+   */
+  public function setAdditionalMailParams($params)
+  {
+    $this->pluginParams = $params;
+    if ($this->plugin !== null)
+    {
+      $this->plugin->setAdditionalParams($params);
+    }
+  }
+  /**
+   * Get the 5th parameter in mail() as a sprintf() formatted string.
+   * @return string
+   */
+  public function getAdditionalMailParams()
+  {
+    return $this->pluginParams;
+  }
+  /**
+   * Read a full response from the buffer (this is spoofed if running in -t mode)
+   * @return string
+   * @throws Swift_ConnectionException Upon failure to read
+   */
+  public function read()
+  {
+    return $this->response;
+  }
+  /**
+   * Set the response this stub will return
+   * @param string The response to send
+   */
+  public function setResponse($int)
+  {
+    $this->response = $int . " Stubbed";
+  }
+  /**
+   * Write a command to the process (leave off trailing CRLF)
+   * @param string The command to send
+   * @throws Swift_ConnectionException Upon failure to write
+   */
+  public function write($command, $end="\r\n")
+  {
+    $command = strtoupper($command);
+    if (strpos($command, " ")) $command = substr($command, 0, strpos($command, " "));
+    switch ($command)
+    {
+      case "DATA":
+        $this->setResponse(354);
+        break;
+      case "EHLO": case "MAIL": case "RCPT": case "QUIT": case "RSET": default:
+        $this->setResponse(250);
+        break;
+    }
+  }
+  /**
+   * Try to start the connection
+   * @throws Swift_ConnectionException Upon failure to start
+   */
+  public function start()
+  {
+    $this->response = "220 Stubbed";
+  }
+  /**
+   * Try to close the connection
+   * @throws Swift_ConnectionException Upon failure to close
+   */
+  public function stop()
+  {
+    $this->response = "220 Stubbed";
+  }
+  /**
+   * Check if the process is still alive
+   * @return boolean
+   */
+  public function isAlive()
+  {
+    return function_exists("mail");
+  }
+}
diff --git a/system/vendor/swift/Swift/Connection/Rotator.php b/system/vendor/swift/Swift/Connection/Rotator.php
new file mode 100755
index 0000000..3985d48
--- /dev/null
+++ b/system/vendor/swift/Swift/Connection/Rotator.php
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * Swift Mailer Multiple Redundant Cycling Connection component.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+ 
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_ConnectionBase");
+
+/**
+ * Swift Rotator Connection
+ * Switches through each connection in turn after sending each message
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Connection_Rotator extends Swift_ConnectionBase
+{
+  /**
+   * The list of available connections
+   * @var array
+   */
+  protected $connections = array();
+  /**
+   * The id of the active connection
+   * @var int
+   */
+  protected $active = null;
+  /**
+   * Contains a list of any connections which were tried but found to be dead
+   * @var array
+   */
+  protected $dead = array();
+  
+  /**
+   * Constructor
+   */
+  public function __construct($connections=array())
+  {
+    foreach ($connections as $id => $conn)
+    {
+      $this->addConnection($connections[$id], $id);
+    }
+  }
+  /**
+   * Add a connection to the list of options
+   * @param Swift_Connection An instance of the connection
+   */
+  public function addConnection(Swift_Connection $connection)
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Adding new connection of type '" . get_class($connection) . "' to rotator.");
+    }
+    $this->connections[] = $connection;
+  }
+  /**
+   * Rotate to the next working connection
+   * @throws Swift_ConnectionException If no connections are available
+   */
+  public function nextConnection()
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add(" <==> Rotating connection.");
+    }
+    
+    $total = count($this->connections);
+    $start = $this->active === null ? 0 : ($this->active + 1);
+    if ($start >= $total) $start = 0;
+    
+    $fail_messages = array();
+    for ($id = $start; $id < $total; $id++)
+    {
+      if (in_array($id, $this->dead)) continue; //The connection was previously found to be useless
+      try {
+        if (!$this->connections[$id]->isAlive()) $this->connections[$id]->start();
+        if ($this->connections[$id]->isAlive())
+        {
+          $this->active = $id;
+          return true;
+        }
+        else
+        {
+          $this->dead[] = $id;
+          $this->connections[$id]->stop();
+          throw new Swift_ConnectionException("The connection started but reported that it was not active");
+        }
+      } catch (Swift_ConnectionException $e) {
+        $fail_messages[] = $id . ": " . $e->getMessage();
+      }
+    }
+    
+    $failure = implode("<br />", $fail_messages);
+    throw new Swift_ConnectionException("No connections were started.<br />" . $failure);
+  }
+  /**
+   * Read a full response from the buffer
+   * @return string
+   * @throws Swift_ConnectionException Upon failure to read
+   */
+  public function read()
+  {
+    if ($this->active === null)
+    {
+      throw new Swift_ConnectionException("None of the connections set have been started");
+    }
+    return $this->connections[$this->active]->read();
+  }
+  /**
+   * Write a command to the server (leave off trailing CRLF)
+   * @param string The command to send
+   * @throws Swift_ConnectionException Upon failure to write
+   */
+  public function write($command, $end="\r\n")
+  {
+    if ($this->active === null)
+    {
+      throw new Swift_ConnectionException("None of the connections set have been started");
+    }
+    return $this->connections[$this->active]->write($command, $end);
+  }
+  /**
+   * Try to start the connection
+   * @throws Swift_ConnectionException Upon failure to start
+   */
+  public function start()
+  {
+    if ($this->active === null) $this->nextConnection();
+  }
+  /**
+   * Try to close the connection
+   * @throws Swift_ConnectionException Upon failure to close
+   */
+  public function stop()
+  {
+    foreach ($this->connections as $id => $conn)
+    {
+      if ($this->connections[$id]->isAlive()) $this->connections[$id]->stop();
+    }
+    $this->active = null;
+  }
+  /**
+   * Check if the current connection is alive
+   * @return boolean
+   */
+  public function isAlive()
+  {
+    return (($this->active !== null) && $this->connections[$this->active]->isAlive());
+  }
+  /**
+   * Get the ID of the active connection
+   * @return int
+   */
+  public function getActive()
+  {
+    return $this->active;
+  }
+  /**
+   * Call the current connection's postConnect() method
+   */
+  public function postConnect(Swift $instance)
+  {
+    Swift_ClassLoader::load("Swift_Plugin_ConnectionRotator");
+    if (!$instance->getPlugin("_ROTATOR")) $instance->attachPlugin(new Swift_Plugin_ConnectionRotator(), "_ROTATOR");
+    $this->connections[$this->active]->postConnect($instance);
+  }
+  /**
+   * Call the current connection's setExtension() method
+   */
+  public function setExtension($extension, $attributes=array())
+  {
+    $this->connections[$this->active]->setExtension($extension, $attributes);
+  }
+  /**
+   * Call the current connection's hasExtension() method
+   */
+  public function hasExtension($name)
+  {
+    return $this->connections[$this->active]->hasExtension($name);
+  }
+  /**
+   * Call the current connection's getAttributes() method
+   */
+  public function getAttributes($name)
+  {
+    return $this->connections[$this->active]->getAttributes($name);
+  }
+}
diff --git a/system/vendor/swift/Swift/Connection/SMTP.php b/system/vendor/swift/Swift/Connection/SMTP.php
new file mode 100755
index 0000000..a16f030
--- /dev/null
+++ b/system/vendor/swift/Swift/Connection/SMTP.php
@@ -0,0 +1,452 @@
+<?php
+
+/**
+ * Swift Mailer SMTP Connection component.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_ConnectionBase");
+Swift_ClassLoader::load("Swift_Authenticator");
+
+/**
+ * Swift SMTP Connection
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Connection_SMTP extends Swift_ConnectionBase
+{
+  /**
+   * Constant for TLS connections
+   */
+  const ENC_TLS = 2;
+  /**
+   * Constant for SSL connections
+   */
+  const ENC_SSL = 4;
+  /**
+   * Constant for unencrypted connections
+   */
+  const ENC_OFF = 8;
+  /**
+   * Constant for the default SMTP port
+   */
+  const PORT_DEFAULT = 25;
+  /**
+   * Constant for the default secure SMTP port
+   */
+  const PORT_SECURE = 465;
+  /**
+   * Constant for auto-detection of paramters
+   */
+  const AUTO_DETECT = -2;
+  /**
+   * A connection handle
+   * @var resource
+   */
+  protected $handle = null;
+  /**
+   * The remote port number
+   * @var int
+   */
+  protected $port = null;
+  /**
+   * Encryption type to use
+   * @var int
+   */
+  protected $encryption = null;
+  /**
+   * A connection timeout
+   * @var int
+   */
+  protected $timeout = 15;
+  /**
+   * A username to authenticate with
+   * @var string
+   */
+  protected $username = false;
+  /**
+   * A password to authenticate with
+   * @var string
+   */
+  protected $password = false;
+  /**
+   * Loaded authentication mechanisms
+   * @var array
+   */
+  protected $authenticators = array();
+  /**
+   * Fsockopen() error codes.
+   * @var int
+   */
+  protected $errno;
+  /**
+   * Fsockopen() error codes.
+   * @var string
+   */
+  protected $errstr;
+  
+  /**
+   * Constructor
+   * @param string The remote server to connect to
+   * @param int The remote port to connect to
+   * @param int The encryption level to use
+   */
+  public function __construct($server="localhost", $port=null, $encryption=null)
+  {
+    $this->setServer($server);
+    $this->setEncryption($encryption);
+    $this->setPort($port);
+  }
+  /**
+   * Set the timeout to connect in seconds
+   * @param int Timeout to use
+   */
+  public function setTimeout($time)
+  {
+    $this->timeout = (int) $time;
+  }
+  /**
+   * Get the timeout currently set for connecting
+   * @return int
+   */
+  public function getTimeout()
+  {
+    return $this->timeout;
+  }
+  /**
+   * Set the remote server to connect to as a FQDN
+   * @param string Server name
+   */
+  public function setServer($server)
+  {
+    if ($server == self::AUTO_DETECT)
+    {
+      $server = @ini_get("SMTP");
+      if (!$server) $server = "localhost";
+    }
+    $this->server = (string) $server;
+  }
+  /**
+   * Get the remote server name
+   * @return string
+   */
+  public function getServer()
+  {
+    return $this->server;
+  }
+  /**
+   * Set the remote port number to connect to
+   * @param int Port number
+   */
+  public function setPort($port)
+  {
+    if ($port == self::AUTO_DETECT)
+    {
+      $port = @ini_get("SMTP_PORT");
+    }
+    if (!$port) $port = ($this->getEncryption() == self::ENC_OFF) ? self::PORT_DEFAULT : self::PORT_SECURE;
+    $this->port = (int) $port;
+  }
+  /**
+   * Get the remote port number currently used to connect
+   * @return int
+   */
+  public function getPort()
+  {
+    return $this->port;
+  }
+  /**
+   * Provide a username for authentication
+   * @param string The username
+   */
+  public function setUsername($user)
+  {
+    $this->setRequiresEHLO(true);
+    $this->username = $user;
+  }
+  /**
+   * Get the username for authentication
+   * @return string
+   */
+  public function getUsername()
+  {
+    return $this->username;
+  }
+  /**
+   * Set the password for SMTP authentication
+   * @param string Password to use
+   */
+  public function setPassword($pass)
+  {
+    $this->setRequiresEHLO(true);
+    $this->password = $pass;
+  }
+  /**
+   * Get the password for authentication
+   * @return string
+   */
+  public function getPassword()
+  {
+    return $this->password;
+  }
+  /**
+   * Add an authentication mechanism to authenticate with
+   * @param Swift_Authenticator
+   */
+  public function attachAuthenticator(Swift_Authenticator $auth)
+  {
+    $this->authenticators[$auth->getAuthExtensionName()] = $auth;
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Authentication mechanism '" . $auth->getAuthExtensionName() . "' attached.");
+    }
+  }
+  /**
+   * Set the encryption level to use on the connection
+   * See the constants ENC_TLS, ENC_SSL and ENC_OFF
+   * NOTE: PHP needs to have been compiled with OpenSSL for SSL and TLS to work
+   * NOTE: Some PHP installations will not have the TLS stream wrapper
+   * @param int Level of encryption
+   */
+  public function setEncryption($enc)
+  {
+    if (!$enc) $enc = self::ENC_OFF;
+    $this->encryption = (int) $enc;
+  }
+  /**
+   * Get the current encryption level used
+   * This method returns an integer corresponding to one of the constants ENC_TLS, ENC_SSL or ENC_OFF
+   * @return int
+   */
+  public function getEncryption()
+  {
+    return $this->encryption;
+  }
+  /**
+   * Read a full response from the buffer
+   * inner !feof() patch provided by Christian Rodriguez:
+   * <a href="http://www.flyspray.org/">www.flyspray.org</a>
+   * @return string
+   * @throws Swift_ConnectionException Upon failure to read
+   */
+  public function read()
+  {
+    if (!$this->handle) throw new Swift_ConnectionException(
+      "The SMTP connection is not alive and cannot be read from."  . $this->smtpErrors());
+    $ret = "";
+    $line = 0;
+    while (!feof($this->handle))
+    {
+      $line++;
+      stream_set_timeout($this->handle, $this->timeout);
+      $tmp = @fgets($this->handle);
+      if ($tmp === false && !feof($this->handle))
+      {
+        throw new Swift_ConnectionException(
+        "There was a problem reading line " . $line . " of an SMTP response. The response so far was:<br />[" . $ret .
+        "].  It appears the connection has died without saying goodbye to us! Too many emails in one go perhaps?"  .
+        $this->smtpErrors());
+      }
+      $ret .= trim($tmp) . "\r\n";
+      if ($tmp{3} == " ") break;
+    }
+    return $ret = substr($ret, 0, -2);
+  }
+  /**
+   * Write a command to the server (leave off trailing CRLF)
+   * @param string The command to send
+   * @throws Swift_ConnectionException Upon failure to write
+   */
+  public function write($command, $end="\r\n")
+  {
+    if (!$this->handle) throw new Swift_ConnectionException(
+      "The SMTP connection is not alive and cannot be written to."  .
+      $this->smtpErrors());
+    if (!@fwrite($this->handle, $command . $end) && !empty($command)) throw new Swift_ConnectionException("The SMTP connection did not allow the command '" . $command . "' to be sent." . $this->smtpErrors());
+  }
+  /**
+   * Try to start the connection
+   * @throws Swift_ConnectionException Upon failure to start
+   */
+  public function start()
+  {
+    if ($this->port === null)
+    {
+      switch ($this->encryption)
+      {
+        case self::ENC_TLS: case self::ENC_SSL:
+          $this->port = 465;
+        break;
+        case null: default:
+          $this->port = 25;
+        break;
+      }
+    }
+    
+    $server = $this->server;
+    if ($this->encryption == self::ENC_TLS) $server = "tls://" . $server;
+    elseif ($this->encryption == self::ENC_SSL) $server = "ssl://" . $server;
+    
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_log::LOG_EVERYTHING))
+    {
+      $log->add("Trying to connect to SMTP server at '" . $server . ":" . $this->port);
+    }
+    
+    if (!$this->handle = @fsockopen($server, $this->port, $errno, $errstr, $this->timeout))
+    {
+      $error_msg = "The SMTP connection failed to start [" . $server . ":" . $this->port . "]: fsockopen returned Error Number " . $errno . " and Error String '" . $errstr . "'";
+      if ($log->isEnabled())
+      {
+        $log->add($error_msg, Swift_Log::ERROR);
+      }
+      $this->handle = null;
+      throw new Swift_ConnectionException($error_msg);
+    }
+    $this->errno =& $errno;
+    $this->errstr =& $errstr;
+  }
+  /**
+   * Get the smtp error string as recorded by fsockopen()
+   * @return string
+   */
+  public function smtpErrors()
+  {
+    return " (fsockopen: " . $this->errstr . "#" . $this->errno . ") ";
+  }
+  /**
+   * Authenticate if required to do so
+   * @param Swift An instance of Swift
+   * @throws Swift_ConnectionException If authentication fails
+   */
+  public function postConnect(Swift $instance)
+  {
+    if ($this->getUsername() && $this->getPassword())
+    {
+      $this->runAuthenticators($this->getUsername(), $this->getPassword(), $instance);
+    }
+  }
+  /**
+   * Run each authenticator in turn an try for a successful login
+   * If none works, throw an exception
+   * @param string Username
+   * @param string Password
+   * @param Swift An instance of swift
+   * @throws Swift_ConnectionException Upon failure to authenticate
+   */
+  public function runAuthenticators($user, $pass, Swift $swift)
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Trying to authenticate with username '" . $user . "'.");
+    }
+    //Load in defaults
+    if (empty($this->authenticators))
+    {
+      if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+      {
+        $log->add("No authenticators loaded; looking for defaults.");
+      }
+      $dir = dirname(__FILE__) . "/../Authenticator";
+      $handle = opendir($dir);
+      while (false !== $file = readdir($handle))
+      {
+        if (preg_match("/^[A-Za-z0-9-]+\\.php\$/", $file))
+        {
+          $name = preg_replace('/[^a-zA-Z0-9]+/', '', substr($file, 0, -4));
+          require_once $dir . "/" . $file;
+          $class = "Swift_Authenticator_" . $name;
+          $this->attachAuthenticator(new $class());
+        }
+      }
+      closedir($handle);
+    }
+    
+    $tried = 0;
+    $looks_supported = true;
+    
+    //Allow everything we have if the server has the audacity not to help us out.
+    if (!$this->hasExtension("AUTH"))
+    {
+      if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+      {
+        $log->add("Server (perhaps wrongly) is not advertising AUTH... manually overriding.");
+      }
+      $looks_supported = false;
+      $this->setExtension("AUTH", array_keys($this->authenticators));
+    }
+    
+    foreach ($this->authenticators as $name => $obj)
+    {
+      //Server supports this authentication mechanism
+      if (in_array($name, $this->getAttributes("AUTH")) || $name{0} == "*")
+      {
+        $tried++;
+        if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+        {
+          $log->add("Trying '" . $name . "' authentication...");
+        }
+        if ($this->authenticators[$name]->isAuthenticated($user, $pass, $swift))
+        {
+          if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+          {
+            $log->add("Success! Authentication accepted.");
+          }
+          return true;
+        }
+      }
+    }
+    
+    //Server doesn't support authentication
+    if (!$looks_supported && $tried == 0)
+      throw new Swift_ConnectionException("Authentication is not supported by the server but a username and password was given.");
+    
+    if ($tried == 0)
+      throw new Swift_ConnectionException("No authentication mechanisms were tried since the server did not support any of the ones loaded. " .
+      "Loaded authenticators: [" . implode(", ", array_keys($this->authenticators)) . "]");
+    else
+      throw new Swift_ConnectionException("Authentication failed using username '" . $user . "' and password '". str_repeat("*", strlen($pass)) . "'");
+  }
+  /**
+   * Try to close the connection
+   * @throws Swift_ConnectionException Upon failure to close
+   */
+  public function stop()
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Closing down SMTP connection.");
+    }
+    if ($this->handle)
+    {
+      if (!fclose($this->handle))
+      {
+        throw new Swift_ConnectionException("The SMTP connection could not be closed for an unknown reason." . $this->smtpErrors());
+      }
+      $this->handle = null;
+    }
+  }
+  /**
+   * Check if the SMTP connection is alive
+   * @return boolean
+   */
+  public function isAlive()
+  {
+    return ($this->handle !== null);
+  }
+  /**
+   * Destructor.
+   * Cleans up any open connections.
+   */
+  public function __destruct()
+  {
+    $this->stop();
+  }
+}
diff --git a/system/vendor/swift/Swift/Connection/Sendmail.php b/system/vendor/swift/Swift/Connection/Sendmail.php
new file mode 100755
index 0000000..094ab01
--- /dev/null
+++ b/system/vendor/swift/Swift/Connection/Sendmail.php
@@ -0,0 +1,352 @@
+<?php
+
+/**
+ * Swift Mailer Sendmail Connection component.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+ 
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_ConnectionBase");
+
+/**
+ * Swift Sendmail Connection
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Connection_Sendmail extends Swift_ConnectionBase
+{
+  /**
+   * Constant for auto-detection of paths
+   */
+  const AUTO_DETECT = -2;
+  /**
+   * Flags for the MTA (options such as bs or t)
+   * @var string
+   */
+  protected $flags = null;
+  /**
+   * The full path to the MTA
+   * @var string
+   */
+  protected $path = null;
+  /**
+   * The type of last request sent
+   * For example MAIL, RCPT, DATA
+   * @var string
+   */
+  protected $request = null;
+  /**
+   * The process handle
+   * @var resource
+   */
+  protected $proc;
+  /**
+   * I/O pipes for the process
+   * @var array
+   */
+  protected $pipes;
+  /**
+   * Switches to true for just one command when DATA has been issued
+   * @var boolean
+   */
+  protected $send = false;
+  /**
+   * The timeout in seconds before giving up
+   * @var int Seconds
+   */
+  protected $timeout = 10;
+  
+  /**
+   * Constructor
+   * @param string The command to execute
+   * @param int The timeout in seconds before giving up
+   */
+  public function __construct($command="/usr/sbin/sendmail -bs", $timeout=10)
+  {
+    $this->setCommand($command);
+    $this->setTimeout($timeout);
+  }
+  /**
+   * Set the timeout on the process
+   * @param int The number of seconds
+   */
+  public function setTimeout($secs)
+  {
+    $this->timeout = (int)$secs;
+  }
+  /**
+   * Get the timeout on the process
+   * @return int
+   */
+  public function getTimeout()
+  {
+    return $this->timeout;
+  }
+  /**
+   * Set the operating flags for the MTA
+   * @param string
+   */
+  public function setFlags($flags)
+  {
+    $this->flags = $flags;
+  }
+  /**
+   * Get the operating flags for the MTA
+   * @return string
+   */
+  public function getFlags()
+  {
+    return $this->flags;
+  }
+  /**
+   * Set the path to the binary
+   * @param string The path (must be absolute!)
+   */
+  public function setPath($path)
+  {
+    if ($path == self::AUTO_DETECT) $path = $this->findSendmail();
+    $this->path = $path;
+  }
+  /**
+   * Get the path to the binary
+   * @return string
+   */
+  public function getPath()
+  {
+    return $this->path;
+  }
+  /**
+   * For auto-detection of sendmail path
+   * Thanks to "Joe Cotroneo" for providing the enhancement
+   * @return string
+   */
+  public function findSendmail()
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Sendmail path auto-detection in progress.  Trying `which sendmail`");
+    }
+    $path = @trim(shell_exec('which sendmail'));
+    if (!is_executable($path))
+    {
+      if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+      {
+        $log->add("No luck so far, trying some common paths...");
+      }
+      $common_locations = array(
+        '/usr/bin/sendmail',
+        '/usr/lib/sendmail',
+        '/var/qmail/bin/sendmail',
+        '/bin/sendmail',
+        '/usr/sbin/sendmail',
+        '/sbin/sendmail'
+      );
+      foreach ($common_locations as $path)
+      {
+        if (is_executable($path)) return $path;
+      }
+      if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+      {
+        $log->add("Falling back to /usr/sbin/sendmail (but it doesn't look good)!");
+      }
+      //Fallback (swift will still throw an error)
+      return "/usr/sbin/sendmail";
+    }
+    else return $path;
+  }
+  /**
+   * Set the sendmail command (path + flags)
+   * @param string Command
+   * @throws Swift_ConnectionException If the command is not correctly structured
+   */
+  public function setCommand($command)
+  {
+    if ($command == self::AUTO_DETECT) $command = $this->findSendmail() . " -bs";
+    
+    if (!strrpos($command, " -"))
+    {
+      throw new Swift_ConnectionException("Cannot set sendmail command with no command line flags. e.g. /usr/sbin/sendmail -t");
+    }
+    $path = substr($command, 0, strrpos($command, " -"));
+    $flags = substr($command, strrpos($command, " -")+2);
+    $this->setPath($path);
+    $this->setFlags($flags);
+  }
+  /**
+   * Get the sendmail command (path + flags)
+   * @return string
+   */
+  public function getCommand()
+  {
+    return $this->getPath() . " -" . $this->getFlags();
+  }
+  /**
+   * Write a command to the open pipe
+   * @param string The command to write
+   * @throws Swift_ConnectionException If the pipe cannot be written to
+   */
+  protected function pipeIn($command, $end="\r\n")
+  {
+    if (!$this->isAlive()) throw new Swift_ConnectionException("The sendmail process is not alive and cannot be written to.");
+    if (!@fwrite($this->pipes[0], $command . $end)  && !empty($command)) throw new Swift_ConnectionException("The sendmail process did not allow the command '" . $command . "' to be sent.");
+    fflush($this->pipes[0]);
+  }
+  /**
+   * Read data from the open pipe
+   * @return string
+   * @throws Swift_ConnectionException If the pipe is not operating as expected
+   */
+  protected function pipeOut()
+  {
+    if (strpos($this->getFlags(), "t") !== false) return;
+    if (!$this->isAlive()) throw new Swift_ConnectionException("The sendmail process is not alive and cannot be read from.");
+    $ret = "";
+    $line = 0;
+    while (true)
+    {
+      $line++;
+      stream_set_timeout($this->pipes[1], $this->timeout);
+      $tmp = @fgets($this->pipes[1]);
+      if ($tmp === false)
+      {
+        throw new Swift_ConnectionException("There was a problem reading line " . $line . " of a sendmail SMTP response. The response so far was:<br />[" . $ret . "].  It appears the process has died.");
+      }
+      $ret .= trim($tmp) . "\r\n";
+      if ($tmp{3} == " ") break;
+    }
+    fflush($this->pipes[1]);
+    return $ret = substr($ret, 0, -2);
+  }
+  /**
+   * Read a full response from the buffer (this is spoofed if running in -t mode)
+   * @return string
+   * @throws Swift_ConnectionException Upon failure to read
+   */
+  public function read()
+  {
+    if (strpos($this->getFlags(), "t") !== false)
+    {
+      switch (strtolower($this->request))
+      {
+        case null:
+          return "220 Greetings";
+        case "helo": case "ehlo":
+          return "250 hello";
+        case "mail": case "rcpt": case "rset":
+          return "250 ok";
+        case "quit":
+          return "221 bye";
+        case "data":
+          $this->send = true;
+          return "354 go ahead";
+        default:
+          return "250 ok";
+      }
+    }
+    else return $this->pipeOut();
+  }
+  /**
+   * Write a command to the process (leave off trailing CRLF)
+   * @param string The command to send
+   * @throws Swift_ConnectionException Upon failure to write
+   */
+  public function write($command, $end="\r\n")
+  {
+    if (strpos($this->getFlags(), "t") !== false)
+    {
+      if (!$this->send && strpos($command, " ")) $command = substr($command, strpos($command, " ")+1);
+      elseif ($this->send)
+      {
+        $this->pipeIn($command);
+      }
+      $this->request = $command;
+      $this->send = (strtolower($command) == "data");
+    }
+    else $this->pipeIn($command, $end);
+  }
+  /**
+   * Try to start the connection
+   * @throws Swift_ConnectionException Upon failure to start
+   */
+  public function start()
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Trying to start a sendmail process.");
+    }
+    if (!$this->getPath() || !$this->getFlags())
+    {
+      throw new Swift_ConnectionException("Sendmail cannot be started without a path to the binary including flags.");
+    }
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Trying to stat the executable '" . $this->getPath() . "'.");
+    }
+    if (!@lstat($this->getPath()))
+    {
+      throw new Swift_ConnectionException(
+        "Sendmail cannot be seen with lstat().  The command given [" . $this->getCommand() . "] does not appear to be valid.");
+    }
+    
+    $pipes_spec = array(
+      array("pipe", "r"),
+      array("pipe", "w"),
+      array("pipe", "w")
+    );
+    
+    $this->proc = proc_open($this->getCommand(), $pipes_spec, $this->pipes);
+
+    if (!$this->isAlive())
+    {
+      throw new Swift_ConnectionException(
+      "The sendmail process failed to start.  Please verify that the path exists and PHP has permission to execute it.");
+    }
+  }
+  /**
+   * Try to close the connection
+   */
+  public function stop()
+  {
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Terminating sendmail process.");
+    }
+    foreach ((array)$this->pipes as $pipe)
+    {
+      @fclose($pipe);
+    }
+    
+    if ($this->proc)
+    {
+      proc_close($this->proc);
+      $this->pipes = null;
+      $this->proc = null;
+    }
+  }
+  /**
+   * Check if the process is still alive
+   * @return boolean
+   */
+  public function isAlive()
+  {
+    return ($this->proc !== false
+      && is_resource($this->proc)
+      && is_resource($this->pipes[0])
+      && is_resource($this->pipes[1])
+      && $this->proc !== null);
+  }
+  /**
+   * Destructor.
+   * Cleans up by stopping any running processes.
+   */
+  public function __destruct()
+  {
+    $this->stop();
+  }
+}
diff --git a/system/vendor/swift/Swift/ConnectionBase.php b/system/vendor/swift/Swift/ConnectionBase.php
new file mode 100755
index 0000000..61bf638
--- /dev/null
+++ b/system/vendor/swift/Swift/ConnectionBase.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * Swift Mailer Connection Base Class
+ * All connection handlers extend this abstract class
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_LogContainer");
+Swift_ClassLoader::load("Swift_Connection");
+Swift_ClassLoader::load("Swift_ConnectionException");
+
+/**
+ * Swift Connection Base Class
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+abstract class Swift_ConnectionBase implements Swift_Connection
+{
+  /**
+   * Any extensions the server might support
+   * @var array
+   */
+  protected $extensions = array();
+  /**
+   * True if the connection is ESMTP.
+   * @var boolean
+   */
+  protected $isESMTP = false;
+  
+  /**
+   * Set an extension which the connection reports to support
+   * @param string Extension name
+   * @param array Attributes of the extension
+   */
+  public function setExtension($name, $options=array())
+  {
+    $this->extensions[$name] = $options;
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("SMTP extension '" . $name . "' reported with attributes [" . implode(", ", $options) . "].");
+    }
+  }
+  /**
+   * Check if a given extension has been set as available
+   * @param string The name of the extension
+   * @return boolean
+   */
+  public function hasExtension($name)
+  {
+    return array_key_exists($name, $this->extensions);
+  }
+  /**
+   * Execute any needed logic after connecting and handshaking
+   */
+  public function postConnect(Swift $instance) {}
+  /**
+   * Get the list of attributes supported by the given extension
+   * @param string The name of the connection
+   * @return array The list of attributes
+   * @throws Swift_ConnectionException If the extension cannot be found
+   */
+  public function getAttributes($extension)
+  {
+    if ($this->hasExtension($extension))
+    {
+      return $this->extensions[$extension];
+    }
+    else
+    {
+      throw new Swift_ConnectionException(
+      "Unable to locate any attributes for the extension '" . $extension . "' since the extension cannot be found. " .
+      "Consider using hasExtension() to check.");
+    }
+  }
+  /**
+   * Returns TRUE if the connection needs a EHLO greeting.
+   * @return boolean
+   */
+  public function getRequiresEHLO()
+  {
+    return $this->isESMTP;
+  }
+  /**
+   * Set TRUE if the connection needs a EHLO greeting.
+   * @param boolean
+   */
+  public function setRequiresEHLO($set)
+  {
+    $this->isESMTP = (bool) $set;
+    $log = Swift_LogContainer::getLog();
+    if ($log->hasLevel(Swift_Log::LOG_EVERYTHING))
+    {
+      $log->add("Forcing ESMTP mode.  HELO is EHLO.");
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift/ConnectionException.php b/system/vendor/swift/Swift/ConnectionException.php
new file mode 100755
index 0000000..03cd5b4
--- /dev/null
+++ b/system/vendor/swift/Swift/ConnectionException.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Swift Connection Exception
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_Exception");
+
+/**
+ * Swift Connection Exception
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_ConnectionException extends Swift_Exception
+{
+}
diff --git a/system/vendor/swift/Swift/Events.php b/system/vendor/swift/Swift/Events.php
new file mode 100755
index 0000000..1836cc8
--- /dev/null
+++ b/system/vendor/swift/Swift/Events.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Swift Mailer Events Layer
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * Provides core functionality for Swift generated events for plugins
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+abstract class Swift_Events
+{
+  /**
+   * An instance of Swift
+   * @var Swift
+   */
+  protected $swift = null;
+  
+  /**
+   * Provide a reference to te currently running Swift this event was generated from
+   * @param Swift
+   */
+  public function setSwift(Swift $swift)
+  {
+    $this->swift = $swift;
+  }
+  /**
+   * Get the current instance of swift
+   * @return Swift
+   */
+  public function getSwift()
+  {
+    return $this->swift;
+  }
+}
diff --git a/system/vendor/swift/Swift/Events/BeforeCommandListener.php b/system/vendor/swift/Swift/Events/BeforeCommandListener.php
new file mode 100755
index 0000000..eaa3c9f
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/BeforeCommandListener.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Swift Mailer Before Command Event Listener Interface
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Contains the list of methods a plugin requiring the use of a CommandEvent, before it is sent must implement
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Events_BeforeCommandListener extends Swift_Events_Listener
+{
+  /**
+   * Executes just before Swift sends a command
+   * @param Swift_Events_CommandEvent Information about the being command sent
+   */
+  public function beforeCommandSent(Swift_Events_CommandEvent $e);
+}
diff --git a/system/vendor/swift/Swift/Events/BeforeSendListener.php b/system/vendor/swift/Swift/Events/BeforeSendListener.php
new file mode 100755
index 0000000..d49d8f0
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/BeforeSendListener.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Swift Mailer Before Send Event Listener Interface
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Contains the list of methods a plugin requiring the use of a SendEvent before the message is sent must implement
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Events_BeforeSendListener extends Swift_Events_Listener
+{
+  /**
+   * Executes just before Swift sends a message
+   * @param Swift_Events_SendEvent Information about the message being sent
+   */
+  public function beforeSendPerformed(Swift_Events_SendEvent $e);
+}
diff --git a/system/vendor/swift/Swift/Events/CommandEvent.php b/system/vendor/swift/Swift/Events/CommandEvent.php
new file mode 100755
index 0000000..e21442f
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/CommandEvent.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Swift Mailer Command Event
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Generated when Swift is sending a command
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Events_CommandEvent extends Swift_Events
+{
+  /**
+   * Contains the command being sent
+   * @var string
+   */
+  protected $string = null;
+  /**
+   * Contains the expected response code (or null)
+   * @var int
+   */
+  protected $code = null;
+  
+  /**
+   * Constructor
+   * @param string The command being sent
+   * @param int The expected code
+   */
+  public function __construct($string, $code=null)
+  {
+    $this->setString($string);
+    $this->setCode($code);
+  }
+  /**
+   * Set the command being sent (without CRLF)
+   * @param string The command being sent
+   */
+  public function setString($string)
+  {
+    $this->string = (string) $string;
+  }
+  /**
+   * Get the command being sent
+   * @return string
+   */
+  public function getString()
+  {
+    return $this->string;
+  }
+  /**
+   * Set response code which is expected
+   * @param int The response code
+   */
+  public function setCode($code)
+  {
+    if ($code === null) $this->code = null;
+    else $this->code = (int) $code;
+  }
+  /**
+   * Get the expected response code
+   * @return int
+   */
+  public function getCode()
+  {
+    return $this->code;
+  }
+}
diff --git a/system/vendor/swift/Swift/Events/CommandListener.php b/system/vendor/swift/Swift/Events/CommandListener.php
new file mode 100755
index 0000000..fd6ef3e
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/CommandListener.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Swift Mailer Command Event Listener Interface
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Contains the list of methods a plugin requiring the use of a CommandEvent must implement
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Events_CommandListener extends Swift_Events_Listener
+{
+  /**
+   * Executes when Swift sends a command
+   * @param Swift_Events_CommandEvent Information about the command sent
+   */
+  public function commandSent(Swift_Events_CommandEvent $e);
+}
diff --git a/system/vendor/swift/Swift/Events/ConnectEvent.php b/system/vendor/swift/Swift/Events/ConnectEvent.php
new file mode 100755
index 0000000..c2d9a3c
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/ConnectEvent.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Swift Mailer Connect Event
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Generated every time Swift connects with a MTA
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Events_ConnectEvent extends Swift_Events
+{
+  /**
+   * A reference to the connection object
+   * @var Swift_Connection
+   */
+  protected $connection = null;
+  
+  /**
+   * Constructor
+   * @param Swift_Connection The new connection
+   */
+  public function __construct(Swift_Connection $connection)
+  {
+    $this->connection = $connection;
+  }
+  /**
+   * Get the connection object
+   * @return Swift_Connection
+   */
+  public function getConnection()
+  {
+    return $this->connection;
+  }
+}
diff --git a/system/vendor/swift/Swift/Events/ConnectListener.php b/system/vendor/swift/Swift/Events/ConnectListener.php
new file mode 100755
index 0000000..aa6fa85
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/ConnectListener.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Swift Mailer Connect Event Listener Interface
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Contains the list of methods a plugin requiring the use of a ConnectEvent must implement
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Events_ConnectListener extends Swift_Events_Listener
+{
+  /**
+   * Executes when Swift initiates a connection
+   * @param Swift_Events_ConnectEvent Information about the connection
+   */
+  public function connectPerformed(Swift_Events_ConnectEvent $e);
+}
diff --git a/system/vendor/swift/Swift/Events/DisconnectEvent.php b/system/vendor/swift/Swift/Events/DisconnectEvent.php
new file mode 100755
index 0000000..94eee86
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/DisconnectEvent.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Swift Mailer Disconnect Event
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Generated every time Swift disconnects from a MTA
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Events_DisconnectEvent extends Swift_Events
+{
+  /**
+   * A reference to the connection object
+   * @var Swift_Connection
+   */
+  protected $connection = null;
+  
+  /**
+   * Constructor
+   * @param Swift_Connection The dead connection
+   */
+  public function __construct(Swift_Connection $connection)
+  {
+    $this->connection = $connection;
+  }
+  /**
+   * Get the connection object
+   * @return Swift_Connection
+   */
+  public function getConnection()
+  {
+    return $this->connection;
+  }
+}
diff --git a/system/vendor/swift/Swift/Events/DisconnectListener.php b/system/vendor/swift/Swift/Events/DisconnectListener.php
new file mode 100755
index 0000000..e385fd5
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/DisconnectListener.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Swift Mailer Disconnect Event Listener Interface
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Contains the list of methods a plugin requiring the use of a DisconnectEvent must implement
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Events_DisconnectListener extends Swift_Events_Listener
+{
+  /**
+   * Executes when Swift closes a connection
+   * @param Swift_Events_DisconnectEvent Information about the connection
+   */
+  public function disconnectPerformed(Swift_Events_DisconnectEvent $e);
+}
diff --git a/system/vendor/swift/Swift/Events/Listener.php b/system/vendor/swift/Swift/Events/Listener.php
new file mode 100755
index 0000000..d45f13f
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/Listener.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * Swift Mailer Event Interface
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+ 
+/**
+ * Used for identity only
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Events_Listener {}
diff --git a/system/vendor/swift/Swift/Events/ListenerMapper.php b/system/vendor/swift/Swift/Events/ListenerMapper.php
new file mode 100755
index 0000000..95fd234
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/ListenerMapper.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Swift Mailer Mapper for Event Listeners
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * Maps event listener names to the methods they implement
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Events_ListenerMapper
+{
+  /**
+   * Get the mapped names (Class => Method(s))
+   * @return array
+   */
+  public static function getMap()
+  {
+    $map = array(
+      "SendListener" => "sendPerformed",
+      "BeforeSendListener" => "beforeSendPerformed",
+      "CommandListener" => "commandSent",
+      "BeforeCommandListener" => "beforeCommandSent",
+      "ResponseListener" => "responseReceived",
+      "ConnectListener" => "connectPerformed",
+      "DisconnectListener" => "disconnectPerformed"
+    );
+    return $map;
+  }
+  
+  /**
+   * Get the name of the method which needs running based upon the listener name
+   * @return string
+   */
+  public static function getNotifyMethod($listener)
+  {
+    $map = self::getMap();
+    if (isset($map[$listener])) return $map[$listener];
+    else return false;
+  }
+}
diff --git a/system/vendor/swift/Swift/Events/ResponseEvent.php b/system/vendor/swift/Swift/Events/ResponseEvent.php
new file mode 100755
index 0000000..3b078b3
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/ResponseEvent.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * Swift Mailer Response Event
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * Generated when Swift receives a server response
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Events_ResponseEvent extends Swift_Events
+{
+  /**
+   * Contains the response received
+   * @var string
+   */
+  protected $string = null;
+  /**
+   * Contains the response code
+   * @var int
+   */
+  protected $code = null;
+  
+  /**
+   * Constructor
+   * @param string The received response
+   */
+  public function __construct($string)
+  {
+    $this->setString($string);
+    $this->setCode(substr($string, 0, 3));
+  }
+  /**
+   * Set the response received
+   * @param string The response
+   */
+  public function setString($string)
+  {
+    $this->string = (string) $string;
+  }
+  /**
+   * Get the received response
+   * @return string
+   */
+  public function getString()
+  {
+    return $this->string;
+  }
+  /**
+   * Set response code
+   * @param int The response code
+   */
+  public function setCode($code)
+  {
+    $this->code = (int) $code;
+  }
+  /**
+   * Get the response code
+   * @return int
+   */
+  public function getCode()
+  {
+    return $this->code;
+  }
+}
diff --git a/system/vendor/swift/Swift/Events/ResponseListener.php b/system/vendor/swift/Swift/Events/ResponseListener.php
new file mode 100755
index 0000000..3222457
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/ResponseListener.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Swift Mailer Response Event Listener Interface
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Contains the list of methods a plugin requiring the use of a ResponseEvent must implement
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Events_ResponseListener extends Swift_Events_Listener
+{
+  /**
+   * Executes when Swift receives a response
+   * @param Swift_Events_ResponseEvent Information about the response
+   */
+  public function responseReceived(Swift_Events_ResponseEvent $e);
+}
diff --git a/system/vendor/swift/Swift/Events/SendEvent.php b/system/vendor/swift/Swift/Events/SendEvent.php
new file mode 100755
index 0000000..8139700
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/SendEvent.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * Swift Mailer Send Event
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Generated every time a message is sent with Swift
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Events_SendEvent extends Swift_Events
+{
+  /**
+   * A reference to the message being sent
+   * @var Swift_Message
+   */
+  protected $message = null;
+  /**
+   * A reference to the sender address object
+   * @var Swift_Address
+   */
+  protected $sender = null;
+  /**
+   * A reference to the recipients being sent to
+   * @var Swift_RecipientList
+   */
+  protected $recipients = null;
+  /**
+   * The number of recipients sent to so
+   * @var int
+   */
+  protected $sent = null;
+  /**
+   * Recipients we couldn't send to
+   * @var array
+   */
+  protected $failed = array();
+  
+  /**
+   * Constructor
+   * @param Swift_Message The message being sent
+   * @param Swift_RecipientList The recipients
+   * @param Swift_Address The sender address
+   * @param int The number of addresses sent to
+   */
+  public function __construct(Swift_Message $message, Swift_RecipientList $list, Swift_Address $from, $sent=0)
+  {
+    $this->message = $message;
+    $this->recipients = $list;
+    $this->sender = $from;
+    $this->sent = $sent;
+  }
+  /**
+   * Get the message being sent
+   * @return Swift_Message
+   */
+  public function getMessage()
+  {
+    return $this->message;
+  }
+  /**
+   * Get the list of recipients
+   * @return Swift_RecipientList
+   */
+  public function getRecipients()
+  {
+    return $this->recipients;
+  }
+  /**
+   * Get the sender's address
+   * @return Swift_Address
+   */
+  public function getSender()
+  {
+    return $this->sender;
+  }
+  /**
+   * Set the number of recipients to how many were sent
+   * @param int
+   */
+  public function setNumSent($sent)
+  {
+    $this->sent = (int) $sent;
+  }
+  /**
+   * Get the total number of addresses to which the email sent successfully
+   * @return int
+   */
+  public function getNumSent()
+  {
+    return $this->sent;
+  }
+  /**
+   * Add an email address to the failed recipient list for this send
+   * @var string The email address
+   */
+  public function addFailedRecipient($address)
+  {
+    $this->failed[] = $address;
+  }
+  /**
+   * Get an array of failed recipients for this send
+   * @return array
+   */
+  public function getFailedRecipients()
+  {
+    return $this->failed;
+  }
+}
diff --git a/system/vendor/swift/Swift/Events/SendListener.php b/system/vendor/swift/Swift/Events/SendListener.php
new file mode 100755
index 0000000..ceea770
--- /dev/null
+++ b/system/vendor/swift/Swift/Events/SendListener.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Swift Mailer Send Event Listener Interface
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Events
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * Contains the list of methods a plugin requiring the use of a SendEvent must implement
+ * @package Swift_Events
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Events_SendListener extends Swift_Events_Listener
+{
+  /**
+   * Executes when Swift sends a message
+   * @param Swift_Events_SendEvent Information about the message being sent
+   */
+  public function sendPerformed(Swift_Events_SendEvent $e);
+}
diff --git a/system/vendor/swift/Swift/Exception.php b/system/vendor/swift/Swift/Exception.php
new file mode 100755
index 0000000..66d9f57
--- /dev/null
+++ b/system/vendor/swift/Swift/Exception.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Swift Mailer Logging Layer Interface
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Log
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_LogContainer");
+
+/**
+ * The Logger Interface
+ * @package Swift_Log
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Exception extends Exception
+{
+  /**
+   * Constructor.
+   * Creates the exception and appends log information if available.
+   * @param string Message
+   * @param int Code
+   */
+  public function __construct($message, $code = 0)
+  {
+    if (($log = Swift_LogContainer::getLog()) && $log->isEnabled())
+    {
+      $message .= "<h3>Log Information</h3>";
+      $message .= "<pre>" . htmlentities($log->dump(true)) . "</pre>";
+    }
+    parent::__construct($message, $code);
+  }
+}
diff --git a/system/vendor/swift/Swift/File.php b/system/vendor/swift/Swift/File.php
new file mode 100755
index 0000000..1858107
--- /dev/null
+++ b/system/vendor/swift/Swift/File.php
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * Swift Mailer File Stream Wrapper
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_FileException");
+
+/**
+ * Swift File stream abstraction layer
+ * Reads bytes from a file
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_File
+{
+  /**
+   * The accessible path to the file
+   * @var string
+   */
+  protected $path = null;
+  /**
+   * The name of the file
+   * @var string
+   */
+  protected $name = null;
+  /**
+   * The resource returned by fopen() against the path
+   * @var resource
+   */
+  protected $handle = null;
+  /**
+   * The status of magic_quotes in php.ini
+   * @var boolean
+   */
+  protected $magic_quotes = false;
+  
+  /**
+   * Constructor
+   * @param string The path the the file
+   * @throws Swift_FileException If the file cannot be found
+   */
+  public function __construct($path)
+  {
+    $this->setPath($path);
+    $this->magic_quotes = get_magic_quotes_runtime();
+  }
+  /**
+   * Set the path to the file
+   * @param string The path to the file
+   * @throws Swift_FileException If the file cannot be found
+   */
+  public function setPath($path)
+  {
+    if (!file_exists($path))
+    {
+      throw new Swift_FileException("No such file '" . $path ."'");
+    }
+    $this->handle = null;
+    $this->path = $path;
+    $this->name = null;
+    $this->name = $this->getFileName();
+  }
+  /**
+   * Get the path to the file
+   * @return string
+   */
+  public function getPath()
+  {
+    return $this->path;
+  }
+  /**
+   * Get the name of the file without it's full path
+   * @return string
+   */
+  public function getFileName()
+  {
+    if ($this->name !== null)
+    {
+      return $this->name;
+    }
+    else
+    {
+      return basename($this->getPath());
+    }
+  }
+  /**
+   * Establish an open file handle on the file if the file is not yet opened
+   * @throws Swift_FileException If the file cannot be opened for reading
+   */
+  protected function createHandle()
+  {
+    if ($this->handle === null)
+    {
+      if (!$this->handle = fopen($this->path, "rb"))
+      {
+        throw new Swift_FileException("Unable to open file '" . $this->path . " for reading.  Check the file permissions.");
+      }
+    }
+  }
+  /**
+   * Check if the pointer as at the end of the file
+   * @return boolean
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function EOF()
+  {
+    $this->createHandle();
+    return feof($this->handle);
+  }
+  /**
+   * Get a single byte from the file
+   * Returns false past EOF
+   * @return string
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function getByte()
+  {
+    $this->createHandle();
+    return $this->read(1);
+  }
+  /**
+   * Read one full line from the file including the line ending
+   * Returns false past EOF
+   * @return string
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function readln()
+  {
+    set_magic_quotes_runtime(0);
+    $this->createHandle();
+    if (!$this->EOF())
+    {
+      $ret = fgets($this->handle);
+    }
+    else $ret = false;
+    
+    set_magic_quotes_runtime($this->magic_quotes);
+    
+    return $ret;
+  }
+  /**
+   * Get the entire file contents as a string
+   * @return string
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function readFull()
+  {
+    $ret = "";
+    set_magic_quotes_runtime(0);
+    while (false !== $chunk = $this->read(8192, false)) $ret .= $chunk;
+    set_magic_quotes_runtime($this->magic_quotes);
+    return $ret;
+  }
+  /**
+   * Read a given number of bytes from the file
+   * Returns false past EOF
+   * @return string
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function read($bytes, $unquote=true)
+  {
+    if ($unquote) set_magic_quotes_runtime(0);
+    $this->createHandle();
+    if (!$this->EOF())
+    {
+      $ret = fread($this->handle, $bytes);
+    }
+    else $ret = false;
+    
+    if ($unquote) set_magic_quotes_runtime($this->magic_quotes);
+    
+    return $ret;
+  }
+  /**
+   * Get the size of the file in bytes
+   * @return int
+   */
+  public function length()
+  {
+    return filesize($this->path);
+  }
+  /**
+   * Close the open handle on the file
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function close()
+  {
+    $this->createHandle();
+    fclose($this->handle);
+    $this->handle = null;
+  }
+  /**
+   * Reset the file pointer back to zero
+   */
+  public function reset()
+  {
+    $this->createHandle();
+    fseek($this->handle, 0);
+  }
+  /**
+   * Destructor
+   * Closes the file
+   */
+  public function __destruct()
+  {
+    if ($this->handle !== null) $this->close();
+  }
+}
diff --git a/system/vendor/swift/Swift/FileException.php b/system/vendor/swift/Swift/FileException.php
new file mode 100755
index 0000000..b6d4294
--- /dev/null
+++ b/system/vendor/swift/Swift/FileException.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Swift File Exception
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_Exception");
+
+/**
+ * Swift File Exception
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_FileException extends Swift_Exception
+{
+}
diff --git a/system/vendor/swift/Swift/Iterator.php b/system/vendor/swift/Swift/Iterator.php
new file mode 100755
index 0000000..5837e44
--- /dev/null
+++ b/system/vendor/swift/Swift/Iterator.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Swift Mailer Iterator Interface
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * Swift Iterator Interface
+ * Provides the interface for iterators used for retrieving addresses in batch sends.
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+interface Swift_Iterator
+{
+  /**
+   * Check if there is a value in the list after the current one.
+   * @return boolean
+   */
+  public function hasNext();
+  /**
+   * Move to the next position in the list if possible.
+   * @return boolean
+   */
+  public function next();
+  /**
+   * Seek to the given numeric index in the list of possible.
+   * @return boolean
+   */
+  public function seekTo($pos);
+  /**
+   * Get the value of the list at the current position.
+   * @return mixed
+   */
+  public function getValue();
+  /**
+   * Get the current list position.
+   * @return int
+   */
+  public function getPosition();
+}
diff --git a/system/vendor/swift/Swift/Iterator/Array.php b/system/vendor/swift/Swift/Iterator/Array.php
new file mode 100755
index 0000000..5f09023
--- /dev/null
+++ b/system/vendor/swift/Swift/Iterator/Array.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * Swift Mailer Array Iterator Interface
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Iterator");
+
+/**
+ * Swift Array Iterator Interface
+ * Iterates over a standard PHP array.
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Iterator_Array implements Swift_Iterator
+{
+  /**
+   * All keys in this array.
+   * @var array
+   */
+  protected $keys;
+  /**
+   * All values in this array.
+   * @var array
+   */
+  protected $values;
+  /**
+   * The current array position.
+   * @var int
+   */
+  protected $pos = -1;
+  
+  /**
+   * Ctor.
+   * @param array The array to iterate over.
+   */
+  public function __construct($input)
+  {
+	  $input = (array) $input;
+    $this->keys = array_keys($input);
+    $this->values = array_values($input);
+  }
+  /**
+   * Returns the original array.
+   * @return array
+   */
+  public function getArray()
+  {
+    return array_combine($this->keys, $this->values);
+  }
+  /**
+   * Returns true if there is a value after the current one.
+   * @return boolean
+   */
+  public function hasNext()
+  {
+    return array_key_exists($this->pos + 1, $this->keys);
+  }
+  /**
+   * Moves to the next array element if possible.
+   * @return boolean
+   */
+  public function next()
+  {
+    if ($this->hasNext())
+    {
+      ++$this->pos;
+      return true;
+    }
+    
+    return false;
+  }
+  /**
+   * Goes directly to the given element in the array if possible.
+   * @param int Numeric position
+   * @return boolean
+   */
+  public function seekTo($pos)
+  {
+    if (array_key_exists($pos, $this->keys))
+    {
+      $this->pos = $pos;
+      return true;
+    }
+    
+    return false;
+  }
+  /**
+   * Returns the value at the current position, or NULL otherwise.
+   * @return mixed.
+   */
+  public function getValue()
+  {
+    if (array_key_exists($this->pos, $this->values))
+      return $this->values[$this->pos];
+    else return null;
+  }
+  /**
+   * Gets the current numeric position within the array.
+   * @return int
+   */
+  public function getPosition()
+  {
+    return $this->pos;
+  }
+}
diff --git a/system/vendor/swift/Swift/Iterator/MySQLResult.php b/system/vendor/swift/Swift/Iterator/MySQLResult.php
new file mode 100755
index 0000000..881c9f6
--- /dev/null
+++ b/system/vendor/swift/Swift/Iterator/MySQLResult.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * Swift Mailer MySQL Resultset Iterator
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Iterator");
+
+/**
+ * Swift Mailer MySQL Resultset Iterator.
+ * Iterates over MySQL Resultset from mysql_query().
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Iterator_MySQLResult implements Swift_Iterator
+{
+  /**
+   * The MySQL resource.
+   * @var resource
+   */
+  protected $resultSet;
+  /**
+   * The current row (array) in the resultset.
+   * @var array
+   */
+  protected $currentRow = array(null, null);
+  /**
+   * The current array position.
+   * @var int
+   */
+  protected $pos = -1;
+  /**
+   * The total number of rows in the resultset.
+   * @var int
+   */
+  protected $numRows = 0;
+  
+  /**
+   * Ctor.
+   * @param resource The resultset iterate over.
+   */
+  public function __construct($rs)
+  {
+    $this->resultSet = $rs;
+    $this->numRows = mysql_num_rows($rs);
+  }
+  /**
+   * Get the resultset.
+   * @return resource
+   */
+  public function getResultSet()
+  {
+    return $this->resultSet;
+  }
+  /**
+   * Returns true if there is a value after the current one.
+   * @return boolean
+   */
+  public function hasNext()
+  {
+    return (($this->pos + 1) < $this->numRows);
+  }
+  /**
+   * Moves to the next array element if possible.
+   * @return boolean
+   */
+  public function next()
+  {
+    if ($this->hasNext())
+    {
+      $this->currentRow = mysql_fetch_array($this->resultSet);
+      $this->pos++;
+      return true;
+    }
+    
+    return false;
+  }
+  /**
+   * Goes directly to the given element in the array if possible.
+   * @param int Numeric position
+   * @return boolean
+   */
+  public function seekTo($pos)
+  {
+    if ($pos >= 0 && $pos < $this->numRows)
+    {
+      mysql_data_seek($this->resultSet, $pos);
+      $this->currentRow = mysql_fetch_array($this->resultSet);
+      mysql_data_seek($this->resultSet, $pos);
+      $this->pos = $pos;
+      return true;
+    }
+    
+    return false;
+  }
+  /**
+   * Returns the value at the current position, or NULL otherwise.
+   * @return mixed.
+   */
+  public function getValue()
+  {
+    $row = $this->currentRow;
+    if ($row[0] !== null)
+      return new Swift_Address($row[0], isset($row[1]) ? $row[1] : null);
+    else
+      return null;
+  }
+  /**
+   * Gets the current numeric position within the array.
+   * @return int
+   */
+  public function getPosition()
+  {
+    return $this->pos;
+  }
+}
diff --git a/system/vendor/swift/Swift/Log.php b/system/vendor/swift/Swift/Log.php
new file mode 100755
index 0000000..bbf577b
--- /dev/null
+++ b/system/vendor/swift/Swift/Log.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * Swift Mailer Logging Layer base class.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Log
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * The Logger class/interface.
+ * @package Swift_Log
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+abstract class Swift_Log
+{
+  /**
+   * A command type entry
+   */
+  const COMMAND = ">>";
+  /**
+   * A response type entry
+   */
+  const RESPONSE = "<<";
+  /**
+   * An error type entry
+   */
+  const ERROR = "!!";
+  /**
+   * A standard entry
+   */
+  const NORMAL = "++";
+  /**
+   * Logging is off.
+   */
+  const LOG_NOTHING = 0;
+  /**
+   * Only errors are logged.
+   */
+  const LOG_ERRORS = 1;
+  /**
+   * Errors + sending failures.
+   */
+  const LOG_FAILURES = 2;
+  /**
+   * All SMTP instructions + failures + errors.
+   */
+  const LOG_NETWORK = 3;
+  /**
+   * Runtime info + SMTP instructions + failures + errors.
+   */
+  const LOG_EVERYTHING = 4;
+  /**
+   * Failed recipients
+   * @var array
+   */
+  protected $failedRecipients = array();
+  /**
+   * The maximum number of log entries
+   * @var int
+   */
+  protected $maxSize = 50;
+  /**
+   * The level of logging currently set.
+   * @var int
+   */
+  protected $logLevel = self::LOG_NOTHING;
+  
+  /**
+   * Add a new entry to the log
+   * @param string The information to log
+   * @param string The type of entry (see the constants: COMMAND, RESPONSE, ERROR, NORMAL)
+   */
+  abstract public function add($text, $type = self::NORMAL);
+  /**
+   * Dump the contents of the log to the browser.
+   * @param boolean True if the string should be returned rather than output.
+   */
+  abstract public function dump($return_only=false);
+  /**
+   * Empty the log contents
+   */
+  abstract public function clear();
+  /**
+   * Check if logging is enabled.
+   */
+  public function isEnabled()
+  {
+    return ($this->logLevel > self::LOG_NOTHING);
+  }
+  /**
+   * Add a failed recipient to the list
+   * @param string The address of the recipient
+   */
+  public function addFailedRecipient($address)
+  {
+    $this->failedRecipients[$address] = null;
+    $this->add("Recipient '" . $address . "' rejected by connection.", self::ERROR);
+  }
+  /**
+   * Get the list of failed recipients
+   * @return array
+   */
+  public function getFailedRecipients()
+  {
+    return array_keys($this->failedRecipients);
+  }
+  /**
+   * Set the maximum size of this log (zero is no limit)
+   * @param int The maximum entries
+   */
+  public function setMaxSize($size)
+  {
+    $this->maxSize = (int) $size;
+  }
+  /**
+   * Get the current maximum allowed log size
+   * @return int
+   */
+  public function getMaxSize()
+  {
+    return $this->maxSize;
+  }
+  /**
+   * Set the log level to one of the constants provided.
+   * @param int Level
+   */
+  public function setLogLevel($level)
+  {
+    $level = (int)$level;
+    $this->add("Log level changed to " . $level, self::NORMAL);
+    $this->logLevel = $level;
+  }
+  /**
+   * Get the current log level.
+   * @return int
+   */
+  public function getLogLevel()
+  {
+    return $this->logLevel;
+  }
+  /**
+   * Check if the log level includes the one given.
+   * @param int Level
+   * @return boolean
+   */
+  public function hasLevel($level)
+  {
+    return ($this->logLevel >= ((int)$level));
+  }
+}
diff --git a/system/vendor/swift/Swift/Log/DefaultLog.php b/system/vendor/swift/Swift/Log/DefaultLog.php
new file mode 100755
index 0000000..84ace1b
--- /dev/null
+++ b/system/vendor/swift/Swift/Log/DefaultLog.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * Swift Mailer Default Logger
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Log
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Log");
+
+/**
+ * The Default Logger class
+ * @package Swift_Log
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Log_DefaultLog extends Swift_Log
+{
+  /**
+   * Lines in the log
+   * @var array
+   */
+  protected $entries = array();
+  
+  /**
+   * Add a log entry
+   * @param string The text for this entry
+   * @param string The label for the type of entry
+   */
+  public function add($text, $type = self::NORMAL)
+  {
+    $this->entries[] = $type . " " . $text;
+    if ($this->getMaxSize() > 0) $this->entries = array_slice($this->entries, (-1 * $this->getMaxSize()));
+  }
+  /**
+   * Dump the contents of the log to the browser.
+   * @param boolean True if the string should be returned rather than output.
+   */
+  public function dump($return_only=false)
+  {
+    $ret = implode("\n", $this->entries);
+    if (!$return_only) echo $ret;
+    else return $ret;
+  }
+  /**
+   * Empty the log
+   */
+  public function clear()
+  {
+    $this->failedRecipients = null;
+    $this->failedRecipients = array();
+    $this->entries = null;
+    $this->entries = array();
+  }
+}
diff --git a/system/vendor/swift/Swift/LogContainer.php b/system/vendor/swift/Swift/LogContainer.php
new file mode 100755
index 0000000..29e8c28
--- /dev/null
+++ b/system/vendor/swift/Swift/LogContainer.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * A registry for the logger object.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Log
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_Log_DefaultLog");
+
+/**
+ * A registry holding the current instance of the log.
+ * @package Swift_Log
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_LogContainer
+{
+  /**
+   * The log instance.
+   * @var Swift_Log
+   */
+  protected static $log = null;
+  
+  /**
+   * Registers the logger.
+   * @param Swift_Log The log
+   */
+  public static function setLog(Swift_Log $log)
+  {
+    self::$log = $log;
+  }
+  /**
+   * Returns the current instance of the log, or lazy-loads the default one.
+   * @return Swift_Log
+   */
+  public static function getLog()
+  {
+    if (self::$log === null)
+    {
+      self::setLog(new Swift_Log_DefaultLog());
+    }
+    return self::$log;
+  }
+}
diff --git a/system/vendor/swift/Swift/Message.php b/system/vendor/swift/Swift/Message.php
new file mode 100755
index 0000000..8ce2a2f
--- /dev/null
+++ b/system/vendor/swift/Swift/Message.php
@@ -0,0 +1,797 @@
+<?php
+
+/**
+ * Swift Mailer Message Component
+ * Composes MIME 1.0 messages meeting various RFC standards
+ * Deals with attachments, embedded images, multipart bodies, forwarded messages...
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_Address");
+Swift_ClassLoader::load("Swift_Message_Mime");
+Swift_ClassLoader::load("Swift_Message_Image");
+Swift_ClassLoader::load("Swift_Message_Part");
+
+
+/**
+ * Swift Message class
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Message extends Swift_Message_Mime
+{
+  /**
+   * Constant from a high priority message (pretty meaningless)
+   */
+  const PRIORITY_HIGH = 1;
+  /**
+   * Constant for a low priority message
+   */
+  const PRIORITY_LOW = 5;
+  /**
+   * Constant for a normal priority message
+   */
+  const PRIORITY_NORMAL = 3;
+  /**
+   * The MIME warning for client not supporting multipart content
+   * @var string
+   */
+  protected $mimeWarning = null;
+  /**
+   * The version of the library (Swift) if known.
+   * @var string
+   */
+  protected $libVersion = "";
+  /**
+   * A container for references to other objects.
+   * This is used in some very complex logic when sub-parts get shifted around.
+   * @var array
+   */
+  protected $references = array(
+    "parent" => array("alternative" => null, "mixed" => null, "related" => null),
+    "alternative" => array(),
+    "mixed" => array(),
+    "related" => array()
+  );
+  
+  /**
+   * Ctor.
+   * @param string Message subject
+   * @param string Body
+   * @param string Content-type
+   * @param string Encoding
+   * @param string Charset
+   */
+  public function __construct($subject="", $body=null, $type="text/plain", $encoding=null, $charset=null)
+  {
+    parent::__construct();
+    if (function_exists("date_default_timezone_set") && function_exists("date_default_timezone_get"))
+    {
+      date_default_timezone_set(@date_default_timezone_get());
+    }
+    $this->setReturnPath(null);
+    $this->setTo("");
+    $this->setFrom("");
+    $this->setCc(null);
+    $this->setBcc(null);
+    $this->setReplyTo(null);
+    $this->setSubject($subject);
+    $this->setDate(time());
+    if (defined("Swift::VERSION"))
+    {
+      $this->libVersion = Swift::VERSION;
+      $this->headers->set("X-LibVersion", $this->libVersion);
+    }
+    $this->headers->set("MIME-Version", "1.0");
+    $this->setContentType($type);	
+    $this->setCharset($charset);
+    $this->setFlowed(true);
+    $this->setEncoding($encoding);
+    
+    foreach (array_keys($this->references["parent"]) as $key)
+    {
+      $this->setReference("parent", $key, $this);
+    }
+    
+    $this->setMimeWarning(
+    "This is a message in multipart MIME format.  Your mail client should not be displaying this. " .
+    "Consider upgrading your mail client to view this message correctly."
+    );
+    
+    if ($body !== null)
+    {
+      $this->setData($body);
+      if ($charset === null)
+      {
+        Swift_ClassLoader::load("Swift_Message_Encoder");
+        if (Swift_Message_Encoder::instance()->isUTF8($body)) $this->setCharset("utf-8");
+        else $this->setCharset("iso-8859-1");
+      }
+    }
+  }
+  /**
+   * Sets a reference so when nodes are nested, operations can be redirected.
+   * This really should be refactored to use just one array rather than dynamic variables.
+   * @param string Key 1
+   * @param string Key 2
+   * @param Object Reference
+   */
+  protected function setReference($where, $key, $ref)
+  {
+    if ($ref === $this) $this->references[$where][$key] = false;
+    else $this->references[$where][$key] = $ref;
+  }
+  /**
+   * Get a reference to an object (for complex reasons).
+   * @param string Key 1
+   * @param string Key 2
+   * @return Object
+   */
+  protected function getReference($where, $key)
+  {
+    if (!$this->references[$where][$key]) return $this;
+    else return $this->references[$where][$key];
+  }
+  /**
+   * Get the level in the MIME hierarchy at which this section should appear.
+   * @return string
+   */
+  public function getLevel()
+  {
+    return Swift_Message_Mime::LEVEL_TOP;
+  }
+  /**
+   * Set the message id literally.
+   * Unless you know what you are doing you should be using generateId() rather than this method,
+   * otherwise you may break compliancy with RFC 2822.
+   * @param string The message ID string.
+   */
+  public function setId($id)
+  {
+    $this->headers->set("Message-ID", $id);
+  }
+  /**
+   * Create a RFC 2822 compliant message id, optionally based upon $idstring.
+   * The message ID includes information about the current time, the server and some random characters.
+   * @param string An optional string the base the ID on
+   * @return string The generated message ID, including the <> quotes.
+   * @author Cristian Rodriguez <judas.iscariote at flyspray.org>
+   */
+  public function generateId($idstring=null)
+  {
+    $midparams =  array(
+      "utctime" => gmstrftime("%Y%m%d%H%M%S"),
+      "pid" => getmypid(),
+      "randint" => mt_rand(),
+      "customstr" => (preg_match("/^(?<!\\.)[a-z0-9\\.]+(?!\\.)\$/iD", $idstring) ? $idstring : "swift") ,
+      "hostname" => (isset($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"] : php_uname("n")),
+    );
+    $this->setId(vsprintf("<%s.%d.%d.%s@%s>", $midparams));
+    return $this->getId();
+  }
+  /**
+   * Get the generated message ID for this message, including the <> quotes.
+   * If generated automatically, or using generateId() this method returns a RFC2822 compliant Message-ID.
+   * @return string
+   * @author Cristian Rodriguez <judas.iscariote at flyspray.org>
+   */
+  public function getId()
+  {
+    return $this->headers->has("Message-ID") ? $this->headers->get("Message-ID") : null;
+  }
+  /**
+   * Set the address in the Return-Path: header
+   * @param string The bounce-detect address
+   */
+  public function setReturnPath($address)
+  {
+    if ($address instanceof Swift_Address) $address = $address->build(true);
+    $this->headers->set("Return-Path", $address);
+  }
+  /**
+   * Return the address used in the Return-Path: header
+   * @return string
+   * @param boolean Return the address for SMTP command
+   */
+  public function getReturnPath($smtp=false)
+  {
+    if ($this->headers->has("Return-Path"))
+    {
+      if (!$smtp) return $this->headers->get("Return-Path");
+      else
+      {
+        $path = $this->headers->get("Return-Path");
+        if (strpos($path, ">") > strpos($path, "<")) return substr($path, ($start = strpos($path, "<")), ($start + strrpos($path, ">") + 1));
+        else return "<" . $path . ">";
+      }
+    }
+  }
+  /**
+   * Set the address in the From: header
+   * @param string The address to set as From
+   */
+  public function setFrom($from)
+  {
+    if ($from instanceof Swift_Address) $from = $from->build();
+    $this->headers->set("From", $from);
+  }
+  /**
+   * Get the address used in the From: header
+   * @return string
+   */
+  public function getFrom()
+  {
+    if ($this->headers->has("From")) return $this->headers->get("From");
+  }
+  /**
+   * Set the list of recipients in the To: header
+   * @param mixed An array or a string
+   */
+  public function setTo($to)
+  {
+    if ($to)
+    {
+      if (!is_array($to)) $to = array($to);
+      foreach ($to as $key => $value)
+      {
+        if ($value instanceof Swift_Address) $to[$key] = $value->build();
+      }
+    }
+    $this->headers->set("To", $to);
+  }
+  /**
+   * Return the list of recipients in the To: header
+   * @return array
+   */
+  public function getTo()
+  {
+    if ($this->headers->has("To"))
+    {
+      $to = $this->headers->get("To");
+      if ($to == "") return array();
+      else return (array) $to;
+    }
+  }
+  /**
+   * Set the list of recipients in the Reply-To: header
+   * @param mixed An array or a string
+   */
+  public function setReplyTo($replyto)
+  {
+    if ($replyto)
+    {
+      if (!is_array($replyto)) $replyto = array($replyto);
+      foreach ($replyto as $key => $value)
+      {
+        if ($value instanceof Swift_Address) $replyto[$key] = $value->build();
+      }
+    }
+    $this->headers->set("Reply-To", $replyto);
+  }
+  /**
+   * Return the list of recipients in the Reply-To: header
+   * @return array
+   */
+  public function getReplyTo()
+  {
+    if ($this->headers->has("Reply-To"))
+    {
+      $reply_to = $this->headers->get("Reply-To");
+      if ($reply_to == "") return array();
+      else return (array) $reply_to;
+    }
+  }
+  /**
+   * Set the list of recipients in the Cc: header
+   * @param mixed An array or a string
+   */
+  public function setCc($cc)
+  {
+    if ($cc)
+    {
+      if (!is_array($cc)) $cc = array($cc);
+      foreach ($cc as $key => $value)
+      {
+        if ($value instanceof Swift_Address) $cc[$key] = $value->build();
+      }
+    }
+    $this->headers->set("Cc", $cc);
+  }
+  /**
+   * Return the list of recipients in the Cc: header
+   * @return array
+   */
+  public function getCc()
+  {
+    if ($this->headers->has("Cc"))
+    {
+      $cc = $this->headers->get("Cc");
+      if ($cc == "") return array();
+      else return (array) $cc;
+    }
+  }
+  /**
+   * Set the list of recipients in the Bcc: header
+   * @param mixed An array or a string
+   */
+  public function setBcc($bcc)
+  {
+    if ($bcc)
+    {
+      if (!is_array($bcc)) $bcc = array($bcc);
+      foreach ($bcc as $key => $value)
+      {
+        if ($value instanceof Swift_Address) $bcc[$key] = $value->build();
+      }
+    }
+    $this->headers->set("Bcc", $bcc);
+  }
+  /**
+   * Return the list of recipients in the Bcc: header
+   * @return array
+   */
+  public function getBcc()
+  {
+    if ($this->headers->has("Bcc"))
+    {
+      $bcc = $this->headers->get("Bcc");
+      if ($bcc == "") return array();
+      else return (array) $bcc;
+    }
+  }
+  /**
+   * Set the subject in the headers
+   * @param string The subject of the email
+   */
+  public function setSubject($subject)
+  {
+    $this->headers->set("Subject", $subject);
+  }
+  /**
+   * Get the current subject used in the headers
+   * @return string
+   */
+  public function getSubject()
+  {
+    return $this->headers->get("Subject");
+  }
+  /**
+   * Set the date in the headers in RFC 2822 format
+   * @param int The time as a UNIX timestamp
+   */
+  public function setDate($date)
+  {
+    $this->headers->set("Date", date("r", $date));
+  }
+  /**
+   * Get the date as it looks in the headers
+   * @return string
+   */
+  public function getDate()
+  {
+    return strtotime($this->headers->get("Date"));
+  }
+  /**
+   * Set the charset of the document
+   * @param string The charset used
+   */
+  public function setCharset($charset)
+  {
+    $this->headers->setAttribute("Content-Type", "charset", $charset);
+    if (($this->getEncoding() == "7bit") && (strtolower($charset) == "utf-8" || strtolower($charset) == "utf8")) $this->setEncoding("8bit");
+  }
+  /**
+   * Get the charset used in the document
+   * Returns null if none is set
+   * @return string
+   */
+  public function getCharset()
+  {
+    if ($this->headers->hasAttribute("Content-Type", "charset"))
+    {
+      return $this->headers->getAttribute("Content-Type", "charset");
+    }
+    else
+    {
+      return null;
+    }
+  }
+  /**
+   * Set the "format" attribute to flowed
+   * @param boolean On or Off
+   */
+  public function setFlowed($flowed=true)
+  {
+    $value = null;
+    if ($flowed) $value = "flowed";
+    $this->headers->setAttribute("Content-Type", "format", $value);
+  }
+  /**
+   * Check if the message format is set as flowed
+   * @return boolean
+   */
+  public function isFlowed()
+  {
+    if ($this->headers->hasAttribute("Content-Type", "format")
+      && $this->headers->getAttribute("Content-Type", "format") == "flowed")
+    {
+      return true;
+    }
+    else return false;
+  }
+  /**
+   * Set the message prioirty in the mail client (don't rely on this)
+   * @param int The priority as a value between 1 (high) and 5 (low)
+   */
+  public function setPriority($priority)
+  {
+    $priority = (int) $priority;
+    if ($priority > self::PRIORITY_LOW) $priority = self::PRIORITY_LOW;
+    if ($priority < self::PRIORITY_HIGH) $priority = self::PRIORITY_HIGH;
+    $label = array(1 => "High", 2 => "High", 3 => "Normal", 4 => "Low", 5 => "Low");
+    $this->headers->set("X-Priority", $priority);
+    $this->headers->set("X-MSMail-Priority", $label[$priority]);
+    $this->headers->set("X-MimeOLE", "Produced by SwiftMailer " . $this->libVersion);
+  }
+  /**
+   * Request that the client send back a read-receipt (don't rely on this!)
+   * @param string Request address
+   */
+  public function requestReadReceipt($request)
+  {
+    if ($request instanceof Swift_Address) $request = $request->build();
+    if (!$request)
+    {
+      $this->headers->set("Disposition-Notification-To", null);
+      $this->headers->set("X-Confirm-Reading-To", null);
+      $this->headers->set("Return-Receipt-To", null);
+    }
+    else
+    {
+      $this->headers->set("Disposition-Notification-To", $request);
+      $this->headers->set("X-Confirm-Reading-To", $request);
+      $this->headers->set("Return-Receipt-To", $request);
+    }
+  }
+  /**
+   * Check if a read receipt has been requested for this message
+   * @return boolean
+   */
+  public function wantsReadReceipt()
+  {
+    return $this->headers->has("Disposition-Notification-To");
+  }
+  /**
+   * Get the current message priority
+   * Returns NULL if none set
+   * @return int
+   */
+  public function getPriority()
+  {
+    if ($this->headers->has("X-Priority")) return $this->headers->get("X-Priority");
+    else return null;
+  }
+  /**
+   * Alias for setData()
+   * @param mixed Body
+   */
+  public function setBody($body)
+  {
+    $this->setData($body);
+  }
+  /**
+   * Alias for getData()
+   * @return mixed The document body
+   */
+  public function getBody()
+  {
+    return $this->getData();
+  }
+  /**
+   * Set the MIME warning message which is displayed to old clients
+   * @var string The full warning message (in 7bit ascii)
+   */
+  public function setMimeWarning($text)
+  {
+    $this->mimeWarning = (string) $text;
+  }
+  /**
+   * Get the MIME warning which is displayed to old clients
+   * @return string
+   */
+  public function getMimeWarning()
+  {
+    return $this->mimeWarning;
+  }
+  /**
+   * Attach a mime part or an attachment of some sort
+   * Any descendant of Swift_Message_Mime can be added safely (including other Swift_Message objects for mail forwarding!!)
+   * @param Swift_Message_Mime The document to attach
+   * @param string An identifier to use (one is returned otherwise)
+   * @return string The identifier for the part
+   */
+  public function attach(Swift_Message_Mime $child, $id=null)
+  {
+    try {
+      switch ($child->getLevel())
+      {
+        case Swift_Message_Mime::LEVEL_ALTERNATIVE:
+          $sign = (strtolower($child->getContentType()) == "text/plain") ? -1 : 1;
+          $id = $this->getReference("parent", "alternative")->addChild($child, $id, $sign);
+          $this->setReference("alternative", $id, $child);
+          break;
+        case Swift_Message_Mime::LEVEL_RELATED:
+          $id = "cid:" . $child->getContentId();
+          $id = $this->getReference("parent", "related")->addChild($child, $id, 1);
+          $this->setReference("related", $id, $child);
+          break;
+        case Swift_Message_Mime::LEVEL_MIXED: default:
+          $id = $this->getReference("parent", "mixed")->addChild($child, $id, 1);
+          $this->setReference("mixed", $id, $child);
+          break;
+      }
+      $this->postAttachFixStructure();
+      $this->fixContentType();
+      return $id;
+    } catch (Swift_Message_MimeException $e) {
+      throw new Swift_Message_MimeException("Something went wrong whilst trying to move some MIME parts during an attach(). " .
+        "The MIME component threw an exception:<br />" . $e->getMessage());
+    }
+  }
+  /**
+   * Remove a nested MIME part
+   * @param string The ID of the attached part
+   * @throws Swift_Message_MimeException If no such part exists
+   */
+  public function detach($id)
+  {
+    try {
+      switch (true)
+      {
+        case array_key_exists($id, $this->references["alternative"]):
+          $this->getReference("parent", "alternative")->removeChild($id);
+          unset($this->references["alternative"][$id]);
+          break;
+        case array_key_exists($id, $this->references["related"]):
+          $this->getReference("parent", "related")->removeChild($id);
+          unset($this->references["related"][$id]);
+          break;
+        case array_key_exists($id, $this->references["mixed"]):
+          $this->getReference("parent", "mixed")->removeChild($id);
+          unset($this->references["mixed"][$id]);
+          break;
+        default:
+          throw new Swift_Message_MimeException("Unable to detach part identified by ID '" . $id . "' since it's not registered.");
+          break;
+      }
+      $this->postDetachFixStructure();
+      $this->fixContentType();
+    } catch (Swift_Message_MimeException $e) {
+      throw new Swift_Message_MimeException("Something went wrong whilst trying to move some MIME parts during a detach(). " .
+        "The MIME component threw an exception:<br />" . $e->getMessage());
+    }
+  }
+  /**
+   * Sets the correct content type header by looking at what types of data we have set
+   */
+  protected function fixContentType()
+  {
+    if (!empty($this->references["mixed"])) $this->setContentType("multipart/mixed");
+    elseif (!empty($this->references["related"])) $this->setContentType("multipart/related");
+    elseif (!empty($this->references["alternative"])) $this->setContentType("multipart/alternative");
+  }
+  /**
+   * Move a branch of the tree, containing all it's MIME parts onto another branch
+   * @param string The content type on the branch itself
+   * @param string The content type which may exist in the branch's parent
+   * @param array The array containing all the nodes presently
+   * @param string The location of the branch now
+   * @param string The location of the branch after moving
+   * @param string The key to identify the branch by in it's new location
+   */
+  protected function moveBranchIn($type, $nested_type, $from, $old_branch, $new_branch, $tag)
+  {
+    $new = new Swift_Message_Part();
+    $new->setContentType($type);
+    $this->getReference("parent", $new_branch)->addChild($new, $tag, -1);
+    
+    switch ($new_branch)
+    {
+      case "related": $this->setReference("related", $tag, $new);//relatedRefs[$tag] = $new;
+        break;
+      case "mixed": $this->setReference("mixed", $tag, $new);//mixedRefs[$tag] = $new;
+        break;
+    }
+    
+    foreach ($from as $id => $ref)
+    {
+      if (!$ref) $ref = $this;
+      $sign = (strtolower($ref->getContentType()) == "text/plain"
+        || strtolower($ref->getContentType()) == $nested_type) ? -1 : 1;
+      switch ($new_branch)
+      {
+        case "related": $this->getReference("related", $tag)->addChild($ref, $id, $sign);
+          break;
+        case "mixed": $this->getReference("mixed", $tag)->addChild($ref, $id, $sign);
+          break;
+      }
+      $this->getReference("parent", $old_branch)->removeChild($id);
+    }
+    $this->setReference("parent", $old_branch, $new); //parentRefs[$old_branch] = $new;
+  }
+  /**
+   * Analyzes the mixing of MIME types in a mulitpart message an re-arranges if needed
+   * It looks complicated and long winded but the concept is pretty simple, even if putting it
+   * in code does me make want to cry!
+   */
+  protected function postAttachFixStructure()
+  {
+    switch (true)
+    {
+      case (!empty($this->references["mixed"]) && !empty($this->references["related"]) && !empty($this->references["alternative"])):
+        if (!isset($this->references["related"]["_alternative"]))
+        {
+          $this->moveBranchIn(
+            "multipart/alternative", "multipart/alternative", $this->references["alternative"], "alternative", "related", "_alternative");
+        }
+        if (!isset($this->references["mixed"]["_related"]))
+        {
+          $this->moveBranchIn(
+            "multipart/related", "multipart/alternative", $this->references["related"], "related", "mixed", "_related");
+        }
+        break;
+      case (!empty($this->references["mixed"]) && !empty($this->references["related"])):
+        if (!isset($this->references["mixed"]["_related"]))
+        {
+          $this->moveBranchIn(
+            "multipart/related", "multipart/related", $this->references["related"], "related", "mixed", "_related");
+        }
+        break;
+      case (!empty($this->references["mixed"]) && !empty($this->references["alternative"])):
+        if (!isset($this->references["mixed"]["_alternative"]))
+        {
+          $this->moveBranchIn(
+            "multipart/alternative", null, $this->references["alternative"], "alternative", "mixed", "_alternative");
+        }
+        break;
+      case (!empty($this->references["related"]) && !empty($this->references["alternative"])):
+        if (!isset($this->references["related"]["_alternative"]))
+        {
+          $this->moveBranchIn(
+            "multipart/alternative", "multipart/alternative", $this->references["alternative"], "alternative", "related", "_alternative");
+        }
+        break;
+    }
+  }
+  /**
+   * Move a branch further toward the top of the tree
+   * @param array The array containing MIME parts from the old branch
+   * @param string The name of the old branch
+   * @param string The name of the new branch
+   * @param string The key of the branch being moved
+   */
+  protected function moveBranchOut($from, $old_branch, $new_branch, $tag)
+  {
+    foreach ($from as $id => $ref)
+    {
+      if (!$ref) $ref = $this;
+      $sign = (strtolower($ref->getContentType()) == "text/html"
+        || strtolower($ref->getContentType()) == "multipart/alternative") ? -1 : 1;
+      $this->getReference("parent", $new_branch)->addChild($ref, $id, $sign);
+      switch ($new_branch)
+      {
+        case "related": $this->getReference("related", $tag)->removeChild($id);
+          break;
+        case "mixed": $this->getReference("parent", $old_branch)->removeChild($id);
+          break;
+      }
+    }
+    $this->getReference("parent", $new_branch)->removeChild($tag);
+    $mixed = $this->getReference("parent", $new_branch);//parentRefs[$new_branch];
+    $this->setReference("parent", $old_branch, $mixed);//parentRefs[$old_branch] = $mixed;
+    switch ($new_branch)
+    {
+      case "related": unset($this->references["related"][$tag]);
+        break;
+      case "mixed": unset($this->references["mixed"][$tag]);
+        break;
+    }
+  }
+  /**
+   * Analyzes the mixing of MIME types in a mulitpart message an re-arranges if needed
+   * It looks complicated and long winded but the concept is pretty simple, even if putting it
+   * in code does me make want to cry!
+   */
+  protected function postDetachFixStructure()
+  {
+    switch (true)
+    {
+      case (!empty($this->references["mixed"]) && !empty($this->references["related"]) && !empty($this->references["alternative"])):
+        if (array_keys($this->references["related"]) == array("_alternative"))
+        {
+          $alt = $this->getReference("parent", "related")->getChild("_alternative");
+          $this->getReference("parent", "mixed")->addChild($alt, "_alternative", -1);
+          $this->setReference("mixed", "_alternative", $alt);//mixedRefs["_alternative"] = $alt;
+          $this->getReference("parent", "related")->removeChild("_alternative");
+          unset($this->references["related"]["_alternative"]);
+          $this->getReference("parent", "mixed")->removeChild("_related");
+          unset($this->references["mixed"]["_related"]);
+        }
+        if (array_keys($this->references["mixed"]) == array("_related"))
+        {
+          $this->moveBranchOut($this->references["related"], "related", "mixed", "_related");
+        }
+        break;
+      case (!empty($this->references["mixed"]) && !empty($this->references["related"])):
+        if (array_keys($this->references["mixed"]) == array("_related"))
+        {
+          $this->moveBranchOut($this->references["related"], "related", "mixed", "_related");
+        }
+        if (isset($this->references["related"]["_alternative"]))
+        {
+          $this->detach("_alternative");
+        }
+        break;
+      case (!empty($this->references["mixed"]) && !empty($this->references["alternative"])):
+        if (array_keys($this->references["mixed"]) == array("_alternative"))
+        {
+          $this->moveBranchOut($this->references["alternative"], "alternative", "mixed", "_alternative");
+        }
+        break;
+      case (!empty($this->references["related"]) && !empty($this->references["alternative"])):
+        if (array_keys($this->references["related"]) == array("_alternative"))
+        {
+          $this->moveBranchOut($this->references["alternative"], "alternative", "related", "_alternative");
+        }
+        break;
+      case (!empty($this->references["mixed"])):
+        if (isset($this->references["mixed"]["_related"])) $this->detach("_related");
+      case (!empty($this->references["related"])):
+        if (isset($this->references["related"]["_alternative"]) || isset($this->references["mixed"]["_alternative"]))
+          $this->detach("_alternative");
+        break;
+    }
+  }
+  /**
+   * Execute needed logic prior to compilation
+   */
+  public function preBuild()
+  {
+    $data = $this->getData();
+    if (!($enc = $this->getEncoding()))
+    {
+      $this->setEncoding("8bit");
+    }
+    if ($this->getCharset() === null && !$this->numChildren())
+    {
+      Swift_ClassLoader::load("Swift_Message_Encoder");
+      if (is_string($data) && Swift_Message_Encoder::instance()->isUTF8($data))
+      {
+        $this->setCharset("utf-8");
+      }
+      elseif(is_string($data) && Swift_Message_Encoder::instance()->is7BitAscii($data))
+      {
+        $this->setCharset("us-ascii");
+        if (!$enc) $this->setEncoding("7bit");
+      }
+      else $this->setCharset("iso-8859-1");
+    }
+    elseif ($this->numChildren())
+    {
+      if (!$this->getData())
+      {
+        $this->setData($this->getMimeWarning());
+        $this->setLineWrap(76);
+      }
+      
+      if ($this->getCharset() !== null) $this->setCharset(null);
+      if ($this->isFlowed()) $this->setFlowed(false);
+      $this->setEncoding("7bit");
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift/Message/Attachment.php b/system/vendor/swift/Swift/Message/Attachment.php
new file mode 100755
index 0000000..8058814
--- /dev/null
+++ b/system/vendor/swift/Swift/Message/Attachment.php
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * Swift Mailer Message Attachment
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Attachment component for Swift Mailer
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Message_Attachment extends Swift_Message_Mime
+{
+  /**
+   * A numeric counter, incremented by 1 when a filename is made.
+   * @var int
+   */
+  protected static $fileId = 0;
+  
+  /**
+   * Constructor
+   * @param mixed The data to use in the body
+   * @param string Mime type
+   * @param string The encoding format used
+   * @param string The charset used
+   */
+  public function __construct($data=null, $name=null, $type="application/octet-stream", $encoding="base64", $disposition="attachment")
+  {
+    parent::__construct();
+    
+    $this->setContentType($type);
+    $this->setEncoding($encoding);
+    $this->setDescription($name);
+    $this->setDisposition($disposition);
+    $this->setFileName($name);
+    
+    if ($data !== null) $this->setData($data, ($name === null));
+  }
+  /**
+   * Get a unique filename (just a sequence)
+   * @param string the prefix for the filename
+   * @return string
+   */
+  public static function generateFileName($prefix="file")
+  {
+    return $prefix . (self::$fileId++);
+  }
+  /**
+   * Get the level in the MIME hierarchy at which this section should appear.
+   * @return string
+   */
+  public function getLevel()
+  {
+    return Swift_Message_Mime::LEVEL_MIXED;
+  }
+  /**
+   * Overrides setData() in MIME so that a filename can be set
+   * @param mixed The data to set for the body
+   * @param boolean If the stream is a file, should it's filename be used?
+   * @throws Swift_FileException If the stream cannot be read
+   */
+  public function setData($data, $read_filename=true)
+  {
+    parent::setData($data);
+    if ($read_filename && ($data instanceof Swift_file))
+    {
+      $this->setFileName($data->getFileName());
+    }
+  }
+  /**
+   * Set the name (and description) used to identify the file
+   * This method overrides any value previously set with setDescription()
+   * @param string The filename including it's extension if any
+   * @throws Swift_Message_MimeException If some required headers have been deliberately removed
+   */
+  public function setFileName($name)
+  {
+    $this->headers->setAttribute("Content-Type", "name", $name);
+    $this->setDescription($name);
+    if ($this->headers->has("Content-Disposition"))
+    {
+      $this->headers->setAttribute("Content-Disposition", "filename", $name);
+    }
+  }
+  /**
+   * Get the filename of this attachment
+   * @return string
+   * @throws Swift_Message_MimeException If some vital headers have been removed
+   */
+  public function getFileName()
+  {
+    if ($this->headers->hasAttribute("Content-Type", "name"))
+    {
+      return $this->headers->getAttribute("Content-Type", "name");
+    }
+    else return null;
+  }
+  /**
+   * Set the Content-Description header
+   * @param string The description in the header (filename usually!)
+   */
+  public function setDescription($desc)
+  {
+    $this->headers->set("Content-Description", $desc);
+  }
+  /**
+   * Return the description in the headers
+   * @return string
+   */
+  public function getDescription()
+  {
+    if ($this->headers->has("Content-Description"))
+    {
+      return $this->headers->get("Content-Description");
+    }
+    else return null;
+  }
+  /**
+   * Set the disposition of the attachment (usually inline or attachment)
+   * @param string The value to use in the Content-Disposition field
+   */
+  public function setDisposition($disposition)
+  {
+    $this->headers->set("Content-Disposition", $disposition);
+  }
+  /**
+   * Get the disposition used in the attachment (usually inline or attachment)
+   * @return string
+   */
+  public function getDisposition()
+  {
+    if ($this->headers->has("Content-Disposition"))
+    {
+      return $this->headers->get("Content-Disposition");
+    }
+    else return null;
+  }
+  /**
+   * Execute needed logic prior to building
+   */
+  public function preBuild()
+  {
+    if ($this->getFileName() === null)
+    {
+      if ($this->getData() instanceof Swift_File)
+      {
+        $this->setFileName($this->getData()->getFileName());
+      }
+      else
+      {
+        $this->setFileName(self::generateFileName("file.att."));
+      }
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift/Message/EmbeddedFile.php b/system/vendor/swift/Swift/Message/EmbeddedFile.php
new file mode 100755
index 0000000..45041ca
--- /dev/null
+++ b/system/vendor/swift/Swift/Message/EmbeddedFile.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Swift Mailer Embedded File (like an image or a midi file)
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Message_Attachment");
+
+/**
+ * Embedded File component for Swift Mailer
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Message_EmbeddedFile extends Swift_Message_Attachment
+{
+  /**
+   * The content-id in the headers (used in <img src=...> values)
+   * @var string
+   */
+  protected $cid = null;
+  
+  /**
+   * Constructor
+   * @param mixed The input source.  Can be a file or a string
+   * @param string The filename to use, optional
+   * @param string The MIME type to use, optional
+   * @param string The Content-ID to use, optional
+   * @param string The encoding format to use, optional
+   */
+  public function __construct($data=null, $name=null, $type="application/octet-stream", $cid=null, $encoding="base64")
+  {
+    parent::__construct($data, $name, $type, $encoding, "inline");
+    
+    if ($cid === null)
+    {
+      $cid = self::generateFileName("swift-" . uniqid(time()) . ".");
+      $cid = urlencode($cid) . "@" . (!empty($_SERVER["SERVER_NAME"]) ? $_SERVER["SERVER_NAME"] : "swift");
+    }
+    $this->setContentId($cid);
+    
+    if ($name === null && !($data instanceof Swift_File)) $this->setFileName($cid);
+    
+    $this->headers->set("Content-Description", null);
+  }
+  /**
+   * Get the level in the MIME hierarchy at which this section should appear.
+   * @return string
+   */
+  public function getLevel()
+  {
+    return Swift_Message_Mime::LEVEL_RELATED;
+  }
+  /**
+   * Set the Content-Id to use
+   * @param string The content-id
+   */
+  public function setContentId($id)
+  {
+    $id = (string) $id;
+    $this->cid = $id;
+    $this->headers->set("Content-ID", "<" . $id . ">");
+  }
+  /**
+   * Get the content-id of this file
+   * @return string
+   */
+  public function getContentId()
+  {
+    return $this->cid;
+  }
+}
diff --git a/system/vendor/swift/Swift/Message/Encoder.php b/system/vendor/swift/Swift/Message/Encoder.php
new file mode 100755
index 0000000..7343795
--- /dev/null
+++ b/system/vendor/swift/Swift/Message/Encoder.php
@@ -0,0 +1,455 @@
+<?php
+
+/**
+ * Swift Mailer Message Encoder
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_File");
+
+/**
+ * Encodes strings in a variety of formats and detects some encoding formats
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Message_Encoder
+{
+  /**
+   * A regular expression which matches valid e-mail addresses (including some unlikely ones)
+   */
+  const CHEAP_ADDRESS_RE = '(?#Start of dot-atom
+    )[-!#\$%&\'\*\+\/=\?\^_`{}\|~0-9A-Za-z]+(?:\.[-!#\$%&\'\*\+\/=\?\^_`{}\|~0-9A-Za-z]+)*(?#
+    End of dot-atom)(?:@(?#Start of domain)[-0-9A-Za-z]+(?:\.[-0-9A-Za-z]+)*(?#End of domain))?';
+  /**
+   * A singleton of this class
+   * @var Swift_Message_Encoder
+   */
+  protected static $instance = null;
+  /**
+   * Retreive an instance of the encoder as a singleton.
+   * New instances are never ever needed since it's monostatic.
+   * @return Message_Encoder
+   */
+  public static function instance()
+  {
+    if (self::$instance === null)
+    {
+      self::$instance = new Swift_Message_Encoder();
+    }
+    return self::$instance;
+  }
+  /**
+   * Break a string apart at every occurence of <add at ress> and return an array
+   * This method does NOT remove any characters like a preg_split() would do.
+   * Elements matching an address start with "a" followed by the numeric index
+   * @param string The input string to separate
+   * @return array
+   */
+  public function addressChunk($input)
+  {
+    $elements = 0;
+    while (preg_match('/^(.*?)(<' . self::CHEAP_ADDRESS_RE . '>)/s', $input, $matches))
+    {
+      if (!empty($matches[1])) $ret[($elements++)] = $matches[1];
+      $ret[('a' . ($elements++))] = $matches[2];
+      $input = substr($input, strlen($matches[0]));
+    }
+    if ($input != "") $ret[($elements++)] = $input; //Whatever is left over
+        
+    return $ret;
+  }
+  /**
+   * Break a string apart at every occurence of <xxxyyy> and return an array
+   * This method does NOT remove any characters like a preg_split() would do.
+   * Elements matching a quoted string start with "a" followed by the numeric index
+   * @param string The input string to separate
+   * @return array
+   */
+  public function quoteChunk($input)
+  {
+    $elements = 0;
+    while (preg_match('/^(.*?)(<[\x20-\x3A\x3C-\x7E]*>)/s', $input, $matches))
+    {
+      if (!empty($matches[1])) $ret[($elements++)] = $matches[1];
+      $ret[('a' . ($elements++))] = $matches[2];
+      $input = substr($input, strlen($matches[0]));
+    }
+    if ($input != "") $ret[($elements++)] = $input; //Whatever is left over
+        
+    return $ret;
+  }
+  /**
+   * Return the base64 encoded version of the string
+   * @param string The input string to encode
+   * @param int The maximum length of each line of output (inc CRLF)
+   * @param int The maximum length of the first line in the output (for headers)
+   * @param boolean Whether email addresses between < and > chars should be preserved or not
+   * @param string The line ending
+   * @return string
+   */
+  public function base64Encode($data, $chunk=76, $init_chunk=0, $headers=false, $le="\r\n")
+  {
+    $ret = "";
+    $chunk -= 2;
+    $chunk = $this->getHcf($chunk, 4);
+    
+    if ($init_chunk >= 2)
+    {
+      $init_chunk -= 2;
+      $init_chunk = $this->getHcf($init_chunk, 4);
+    }
+    
+    if ($headers) $data = $this->quoteChunk($data);
+    else $data = array($data);
+    
+    foreach ($data as $key => $string)
+    {
+      $key = (string) $key;
+      if ($key{0} == 'a') //This is an address
+      {
+        if ($init_chunk && $init_chunk < (strlen($string)+2)) $ret .= $le;
+        $ret .= $le . $string;
+      }
+      else
+      {
+        $string = $this->rawBase64Encode($string);
+        if ($init_chunk > 2)
+        {
+          $ret .= substr($string, 0, $init_chunk) . $le;
+          $string = substr($string, $init_chunk);
+        }
+        elseif ($init_chunk) $ret .= $le;
+        
+        $ret .= trim(chunk_split($string, $chunk, $le)) . $le;
+      }
+      $init_chunk = 0;
+    }
+    
+    return trim($ret);
+  }
+  /**
+   * Return the base64 encoded version of a string with no breaks
+   * @param The input string to encode
+   * @return string
+   */
+  public function rawBase64Encode($string)
+  {
+    return $string = base64_encode($string);
+  }
+  /**
+   * Return the base64 encoded version of a file
+   * @param Swift_File The file input stream
+   * @param int Max line length
+   * @param string The line ending
+   * @return Swift_Cache_OutputStream
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function base64EncodeFile(Swift_File $file, $chunk=76, $le="\r\n")
+  {
+    Swift_ClassLoader::load("Swift_CacheFactory");
+    $cache = Swift_CacheFactory::getCache();
+    $chunk -= 2;
+    $chunk = $this->getHcf($chunk, 4);
+    $loop = false;
+    //We have to read in multiples of 3 bytes but avoid doing such small chunks that it takes too long
+    while (false !== $bytes = $file->read(8190))
+    {
+      if ($loop) $cache->write("b64", $le);
+      $loop = true;
+      $next = chunk_split($this->rawBase64Encode($bytes), $chunk, $le);
+      $next = trim($next);
+      $cache->write("b64", $next);
+    }
+    $file->reset();
+    return $cache->getOutputStream("b64");
+  }
+  /**
+   * Return the quoted printable version of the input string
+   * @param string The input string to encode
+   * @param int The maximum length of each line of output (inc CRLF)
+   * @param int The maximum length of the first line in the output (for headers)
+   * @param boolean Whether email addresses between < and > chars should be preserved or not
+   * @param string The line ending
+   * @return string
+   */
+  public function QPEncode($data, $chunk=76, $init_chunk=0, $headers=false, $le="\r\n")
+  {
+    $ret = "";
+    if ($headers) $data = $this->quoteChunk($data);
+    else $data = array($data);
+    
+    $trailing_spaces = chr(9) . chr(32);
+    foreach ($data as $key => $string)
+    {
+      $key = (string) $key;
+      if ($key{0} == 'a') //An address
+      {
+        if ($init_chunk && $init_chunk < (strlen($string)+3)) $ret .= "=";
+        $ret .= $le . $string;
+      }
+      else
+      {
+        $lines = explode($le, $string);
+        foreach ($lines as $n => $line)
+          $lines[$n] = $this->rawQPEncode(rtrim($line, $trailing_spaces));
+        $string = implode($le, $lines);
+        if ($init_chunk > 3)
+        {
+          if (preg_match('/^.{1,'.($init_chunk-5).'}[^=]{2}(?!=[A-F0-9]{2})/', $string, $matches)
+            || preg_match('/^.{1,'.($init_chunk-6).'}([^=]{0,3})?/', $string, $matches))
+          {
+            $ret .= $this->fixLE($matches[0] . "=", $le); //fixLE added 24/08/07
+            $string = substr($string, strlen($matches[0]));
+          }
+        }
+        elseif ($init_chunk) $ret .= "=";
+        
+        while (preg_match('/^.{1,'.($init_chunk-5).'}[^=]{2}(?!=[A-F0-9]{2})/', $string, $matches)
+          || preg_match('/^.{1,'.($chunk-6).'}([^=]{0,3})?/', $string, $matches)
+          || (strlen($string) > 0 && $matches = array($string)))
+        {
+          $ret .= $this->fixLE($le . $matches[0] . "=", $le); //fixLE added 24/08/07
+          $string = substr($string, strlen($matches[0]));
+        }
+      }
+      $init_chunk = 0;
+    }
+    
+    if (substr($ret, -1) == "=") return trim(substr($ret, 0, -1));
+    else return trim($ret);
+  }
+  /**
+   * Return the QP encoded version of a string with no breaks
+   * @param string The input to encode
+   * @param boolean True if the data we're encoding is binary
+   * @return string
+   */
+  public function rawQPEncode($string, $bin=false)
+  {
+    $ret = "";
+    if (!$bin)
+    {
+      $string = str_replace(array("\r\n", "\r"), "\n", $string);
+      $string = str_replace("\n", "\r\n", $string);
+    }
+    $len = strlen($string);
+    for ($i = 0; $i < $len; $i++)
+    {
+      $val = ord($string{$i});
+      //9, 32 = HT, SP; 10, 13 = CR, LF; 33-60 & 62-126 are ok
+      // 63 = '?'; 95 = '_' and need encoding to go in the headers
+      if ((!$bin && ($val == 32 || $val == 9 || $val == 10 || $val == 13))
+        || ($val >= 33 && $val <= 60) || ($val >= 62 && $val <= 126)
+        && $val != 63)
+      {
+        $ret .= $string{$i};
+      }
+      else
+      {
+        $ret .= sprintf("=%02X", $val);
+      }
+    }
+    return $ret;
+  }
+  /**
+   * Return a file as a quoted printable encoded string
+   * @param Swift_File The file to encode
+   * @param int Max line length
+   * @param string The line ending
+   * @return Swift_Cache_OutputStream
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function QPEncodeFile(Swift_File $file, $chunk=76, $le="\r\n")
+  {
+    Swift_ClassLoader::load("Swift_CacheFactory");
+    $cache = Swift_CacheFactory::getCache();
+    while (false !== $bytes = $file->readln())
+    {
+      $next = $this->rawQPEncode($bytes, true);
+      preg_match_all('/.{1,'.($chunk-6).'}([^=]{0,3})?/', $next, $next);
+      if (count($next[0])) $cache->write("qp", $this->fixLE(implode("=" . $le, $next[0]), $le));
+    }
+    return $cache->getOutputStream("qp");
+  }
+  /**
+   * Encode a string as 7bit ascii
+   * @param string Input data to encode
+   * @param int Max line length
+   * @param string The line ending
+   * @return string
+   */
+  public function encode7Bit($data, $chunk=76, $le="\r\n")
+  {
+    return $this->fixLE(wordwrap($data, $chunk-2, $le, 1), $le);
+  }
+  /**
+   * Return a 7bit string from a file
+   * @param Swift_File The file stream to read from
+   * @param int The max line length
+   * @param string The line ending
+   * @return Swift_Cache_OutputStream
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function encode7BitFile(Swift_File $file, $chunk=76, $le="\r\n")
+  {
+    Swift_ClassLoader::load("Swift_CacheFactory");
+    $cache = Swift_CacheFactory::getCache();
+    $ret = "";
+    while (false !== $bytes = $file->read(8192)) $ret .= $bytes;
+    $cache->write("7b", $this->fixLE(wordwrap($ret, $chunk-2, $le, 1), $le));
+    return $cache->getOutputStream("7b");
+  }
+  /**
+   * Return the 8bit encoded form of a string (unchanged there-abouts)
+   * @param string Input data to encode
+   * @param int Maximum line length
+   * @param string The line ending
+   * @return string
+   */
+  public function encode8Bit($data, $chunk=76, $le="\r\n")
+  {
+    return $this->fixLE(wordwrap($data, $chunk-2, $le, 1), $le);
+  }
+  /**
+   * Return a 8bit string from a file
+   * @param Swift_File The file stream to read from
+   * @param int Max line length (including CRLF)
+   * @param string The line ending
+   * @return Swift_Cache_OutputStream
+   * @throws Swift_FileException If the file cannot be read
+   */
+  public function encode8BitFile(Swift_File $file, $chunk=76, $le="\r\n")
+  {
+    Swift_ClassLoader::load("Swift_CacheFactory");
+    $cache = Swift_CacheFactory::getCache();
+    $ret = "";
+    while (false !== $bytes = $file->read(8192)) $ret .= $bytes;
+    $cache->write("8b", $this->fixLE(wordwrap($ret, $chunk-2, $le, 1), $le));
+    return $cache->getOutputStream("8b");
+  }
+  /**
+   * Keeps lines longer than 76 characters trimmed down to size
+   * This currently does not convert other string encodings into 7bit
+   * @param string The data to make safe for headers (defaults to RFC 2822 standards)
+   * @param int maximum length of lines returned
+   * @param int The maximum length of the first line
+   * @param string The Line ending
+   * @return string
+   */
+  public function header7BitEncode($data, $chunk=76, $init_chunk=0, $le="\r\n")
+  {
+    $data = $this->encode7BitPrintable($data);
+    $ret = "";
+    if ($init_chunk > 2)
+    {
+      $data_wrapped = wordwrap($data, $init_chunk, $le);
+      $lines = explode($le, $data_wrapped);
+      $first_line = array_shift($lines);
+      $ret .= $first_line . $le;
+      $data = preg_replace("~^[ \t]~D", "", substr($data, strlen($first_line)));
+    }
+    elseif ($init_chunk) $ret .= $le;
+    $ret .= wordwrap($data, $chunk-2, $le);
+    return trim($ret);
+  }
+  /**
+   * Strip out any characters which are not in the ASCII 7bit printable range
+   * @param string The string to clean
+   * @return string
+   */
+  public function encode7BitPrintable($data)
+  {
+    return preg_replace('/[^\x20-\x7E]/', '', $data);
+  }
+  /**
+   * Detect if a string contains multi-byte non-ascii chars that fall in the UTF-8 ranges
+   * @param string Data to detect UTF-8 sequences in
+   * @return boolean
+   */
+  public function isUTF8($data)
+  {
+    return preg_match('%(?:
+    [\xC2-\xDF][\x80-\xBF]				# non-overlong 2-byte
+    |\xE0[\xA0-\xBF][\x80-\xBF]			# excluding overlongs
+    |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}	# straight 3-byte
+    |\xED[\x80-\x9F][\x80-\xBF]			# excluding surrogates
+    |\xF0[\x90-\xBF][\x80-\xBF]{2}		# planes 1-3
+    |[\xF1-\xF3][\x80-\xBF]{3}			# planes 4-15
+    |\xF4[\x80-\x8F][\x80-\xBF]{2}		# plane 16
+    )+%xs', $data);
+  }
+  /**
+   * This function checks for 7bit *printable* characters
+   * which excludes \r \n \t etc and so, is safe for use in mail headers
+   * Actual permitted chars [\ !"#\$%&'\(\)\*\+,-\.\/0123456789:;<=>\?@ABCDEFGHIJKLMNOPQRSTUVWXYZ\[\\\]\^_`abcdefghijklmnopqrstuvwxyz{\|}~]
+   * Ranges \x00-\x1F are printer control sequences
+   * \x7F is the ascii delete character
+   * @param string Data to check against
+   * @return boolean
+   */
+  public function is7BitPrintable($data)
+  {
+    return (!preg_match('/[^\x20-\x7E]/', $data));
+  }
+  /**
+   * Check that a string does not contain any evil characters for headers.
+   * @param string The string to check
+   * @return boolean
+   */
+  public function isHeaderSafe($data)
+  {
+    return ($this->is7BitPrintable($data) && strpos($data, ";") === false);
+  }
+  /**
+   * If the characters fall exclusively in the 7bit ascii range, return true
+   * @param string Input to check
+   * @return boolean
+   */
+  public function is7BitAscii($data)
+  {
+    return (!preg_match('/[^\x01-\x7F]/', $data));
+  }
+  /**
+   * Encode a string for RFC 2047 compatability (url-encode)
+   * @param string The input for encoding
+   * @param string The charset used
+   * @param string The language used
+   * @param int The maximum line length
+   * @param int The maximum length of the first line
+   * @param string The line ending
+   * @return string
+   */
+  public function rfc2047Encode($str, $charset="iso-8859-1", $language="en-us", $chunk=76, $le="\r\n")
+  {
+    $lang_spec = "";
+    if (!$this->is7BitPrintable($str))
+    {
+      $lang_spec = $charset . "'" . $language . "'";
+      $str = $lang_spec . str_replace("+", "%20", urlencode($str));
+    }
+    preg_match_all('~.{1,'.($chunk-6).'}([^%]{0,3})~', $str, $matches);
+    if (count($matches[0])) return implode($le, $matches[0]);
+  }
+  /**
+   * Fixes line endings to be whatever is specified by the user
+   * SMTP requires the CRLF be used, but using sendmail in -t mode uses LF
+   * This method also escapes dots on a start of line to avoid injection
+   * @param string The data to fix
+   * @return string
+   */
+  protected function fixLE($data, $le)
+  {
+    $data = str_replace(array("\r\n", "\r"), "\n", $data);
+    if ($le != "\n") $data = str_replace("\n", $le, $data);
+    return $data = str_replace($le . ".", $le . "..", $data);
+  }
+  protected function getHcf($value, $factor)
+  {
+    return ($value - ($value % $factor));
+  }
+}
diff --git a/system/vendor/swift/Swift/Message/Headers.php b/system/vendor/swift/Swift/Message/Headers.php
new file mode 100755
index 0000000..f78184b
--- /dev/null
+++ b/system/vendor/swift/Swift/Message/Headers.php
@@ -0,0 +1,573 @@
+<?php
+
+/**
+ * Swift Mailer MIME Library Headers component
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+
+/**
+ * Contains and constructs the headers for a MIME document
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Message_Headers
+{
+  /**
+   * Headers which may contain email addresses, and therefore should take notice when encoding
+   * @var array headers
+   */
+  protected $emailContainingHeaders = array(
+    "To", "From", "Reply-To", "Cc", "Bcc", "Return-Path", "Sender");
+  /**
+   * The encoding format used for the body of the document
+   * @var string format
+   */
+  protected $encoding = "B";
+  /**
+   * The charset used in the headers
+   * @var string
+   */
+  protected $charset = false;
+  /**
+   * A collection of headers
+   * @var array headers
+   */
+  protected $headers = array();
+  /**
+   * A container of references to the headers
+   * @var array
+   */
+  protected $lowerHeaders = array();
+  /**
+   * Attributes appended to headers
+   * @var array
+   */
+  protected $attributes = array();
+  /**
+   * If QP or Base64 encoding should be forced
+   * @var boolean
+   */
+  protected $forceEncoding = false;
+  /**
+   * The language used in the headers (doesn't really matter much)
+   * @var string
+   */
+  protected $language = "en-us";
+  /**
+   * Cached, pre-built headers
+   * @var string
+   */
+  protected $cached = array();
+  /**
+   * The line ending used in the headers
+   * @var string
+   */
+  protected $LE = "\r\n";
+  
+  /**
+   * Set the line ending character to use
+   * @param string The line ending sequence
+   * @return boolean
+   */
+  public function setLE($le)
+  {
+    if (in_array($le, array("\r", "\n", "\r\n")))
+    {
+      foreach (array_keys($this->cached) as $k) $this->cached[$k] = null;
+      $this->LE = $le;
+      return true;
+    }
+    else return false;
+  }
+  /**
+   * Get the line ending sequence
+   * @return string
+   */
+  public function getLE()
+  {
+    return $this->LE;
+  }
+  /**
+   * Reset the cache state in these headers
+   */
+  public function uncacheAll()
+  {
+    foreach (array_keys($this->cached) as $k)
+    {
+      $this->cached[$k] = null;
+    }
+  }
+  /**
+   * Add a header or change an existing header value
+   * @param string The header name, for example "From" or "Subject"
+   * @param string The value to be inserted into the header.  This is safe from header injection.
+   */
+  public function set($name, $value)
+  {
+    $lname = strtolower($name);
+    if (!isset($this->lowerHeaders[$lname]))
+    {
+      $this->headers[$name] = null;
+      $this->lowerHeaders[$lname] =& $this->headers[$name];
+    }
+    $this->cached[$lname] = null;
+    Swift_ClassLoader::load("Swift_Message_Encoder");
+    if (is_array($value))
+    {
+      foreach ($value as $v)
+      {
+        if (!$this->getCharset() && Swift_Message_Encoder::instance()->isUTF8($v))
+        {
+          $this->setCharset("utf-8");
+          break;
+        }
+      }
+    }
+    elseif ($value !== null)
+    {
+      if (!$this->getCharset() && Swift_Message_Encoder::instance()->isUTF8($value))
+      {
+        $this->setCharset("utf-8");
+      }
+    }
+    if (!is_array($value) && $value !== null) $this->lowerHeaders[$lname] = (string) $value;
+    else $this->lowerHeaders[$lname] = $value;
+  }
+  /**
+   * Get the value at a given header
+   * @param string The name of the header, for example "From" or "Subject"
+   * @return string
+   * @throws Swift_Message_MimeException If no such header exists
+   * @see hasHeader
+   */
+  public function get($name)
+  {
+    $lname = strtolower($name);
+    if ($this->has($name))
+    {
+      return $this->lowerHeaders[$lname];
+    }
+  }
+  /**
+   * Remove a header from the list
+   * @param string The name of the header
+   */
+  public function remove($name)
+  {
+    $lname = strtolower($name);
+    if ($this->has($name))
+    {
+      unset($this->headers[$name]);
+      unset($this->lowerHeaders[$lname]);
+      unset($this->cached[$lname]);
+      if (isset($this->attributes[$lname])) unset($this->attributes[$lname]);
+    }
+  }
+  /**
+   * Just fetch the array containing the headers
+   * @return array
+   */
+  public function getList()
+  {
+    return $this->headers;
+  }
+  /**
+   * Check if a header has been set or not
+   * @param string The name of the header, for example "From" or "Subject"
+   * @return boolean
+   */
+  public function has($name)
+  {
+    $lname = strtolower($name);
+    return (array_key_exists($lname, $this->lowerHeaders) && $this->lowerHeaders[$lname] !== null);
+  }
+  /**
+   * Set the language used in the headers to $lang (e.g. en-us, en-gb, sv etc)
+   * @param string The language to use
+   */
+  public function setLanguage($lang)
+  {
+    $this->language = (string) $lang;
+  }
+  /**
+   * Get the language used in the headers to $lang (e.g. en-us, en-gb, sv etc)
+   * @return string
+   */
+  public function getLanguage()
+  {
+    return $this->language;
+  }
+  /**
+   * Set the charset used in the headers
+   * @param string The charset name
+   */
+  public function setCharset($charset)
+  {
+    $this->charset = (string) $charset;
+  }
+  /**
+   * Get the current charset used
+   * @return string
+   */
+  public function getCharset()
+  {
+    return $this->charset;
+  }
+  /**
+   * Specify the encoding to use for the headers if characters outside the 7-bit-printable ascii range are found
+   * This encoding will never be used if only 7-bit-printable characters are found in the headers.
+   * Possible values are:
+   *  - QP
+   *  - Q
+   *  - Quoted-Printable
+   *  - B
+   *  - Base64
+   * NOTE: Q, QP, Quoted-Printable are all the same; as are B and Base64
+   * @param string The encoding format to use
+   * @return boolean
+   */
+  public function setEncoding($encoding)
+  {
+    switch (strtolower($encoding))
+    {
+      case "qp": case "q": case "quoted-printable":
+      $this->encoding = "Q";
+      return true;
+      case "base64": case "b":
+      $this->encoding = "B";
+      return true;
+      default: return false;
+    }
+  }
+  /**
+   * Get the encoding format used in this document
+   * @return string
+   */
+  public function getEncoding()
+  {
+    return $this->encoding;
+  }
+  /**
+   * Turn on or off forced header encoding
+   * @param boolean On/Off
+   */
+  public function forceEncoding($force=true)
+  {
+    $this->forceEncoding = (boolean) $force;
+  }
+  /**
+   * Set an attribute in a major header
+   * For example $headers->setAttribute("Content-Type", "format", "flowed")
+   * @param string The main header these values exist in
+   * @param string The name for this value
+   * @param string The value to set
+   * @throws Swift_Message_MimeException If no such header exists
+   */
+  public function setAttribute($header, $name, $value)
+  {
+    $name = strtolower($name);
+    $lheader = strtolower($header);
+    $this->cached[$lheader] = null;
+    if (!$this->has($header))
+    {
+      throw new Swift_Message_MimeException(
+      "Cannot set attribute '" . $name . "' for header '" . $header . "' as the header does not exist. " .
+      "Consider using Swift_Message_Headers->has() to check.");
+    }
+    else
+    {
+      Swift_ClassLoader::load("Swift_Message_Encoder");
+      if (!$this->getCharset() && Swift_Message_Encoder::instance()->isUTF8($value)) $this->setCharset("utf-8");
+      if (!isset($this->attributes[$lheader])) $this->attributes[$lheader] = array();
+      if ($value !== null) $this->attributes[$lheader][$name] = (string) $value;
+      else $this->attributes[$lheader][$name] = $value;
+    }
+  }
+  /**
+   * Check if a header has a given attribute applied to it
+   * @param string The name of the main header
+   * @param string The name of the attribute
+   * @return boolean
+   */
+  public function hasAttribute($header, $name)
+  {
+    $name = strtolower($name);
+    $lheader = strtolower($header);
+    if (!$this->has($header))
+    {
+      return false;
+    }
+    else
+    {
+      return (isset($this->attributes[$lheader]) && isset($this->attributes[$lheader][$name]) && ($this->attributes[$lheader][$name] !== null));
+    }
+  }
+  /**
+   * Get the value for a given attribute on a given header
+   * @param string The name of the main header
+   * @param string The name of the attribute
+   * @return string
+   * @throws Swift_Message_MimeException If no header is set
+   */
+  public function getAttribute($header, $name)
+  {
+    if (!$this->has($header))
+    {
+      throw new Swift_Message_MimeException(
+      "Cannot locate attribute '" . $name . "' for header '" . $header . "' as the header does not exist. " .
+      "Consider using Swift_Message_Headers->has() to check.");
+    }
+    
+    $name = strtolower($name);
+    $lheader = strtolower($header);
+    
+    if ($this->hasAttribute($header, $name))
+    {
+      return $this->attributes[$lheader][$name];
+    }
+  }
+  /**
+   * Remove an attribute from a header
+   * @param string The name of the header to remove the attribute from
+   * @param string The name of the attribute to remove
+   */
+  public function removeAttribute($header, $name)
+  {
+    $name = strtolower($name);
+    $lheader = strtolower($header);
+    if ($this->has($header))
+    {
+      unset($this->attributes[$lheader][$name]);
+    }
+  }
+  /**
+   * Get a list of all the attributes in the given header.
+   * @param string The name of the header
+   * @return array
+   */
+  public function listAttributes($header)
+  {
+    $header = strtolower($header);
+    if (array_key_exists($header, $this->attributes))
+    {
+      return $this->attributes[$header];
+    }
+    else return array();
+  }
+  /**
+   * Get the header in it's compliant, encoded form
+   * @param string The name of the header
+   * @return string
+   * @throws Swift_Message_MimeException If the header doesn't exist
+   */
+  public function getEncoded($name)
+  {
+    if (!$this->getCharset()) $this->setCharset("iso-8859-1");
+    Swift_ClassLoader::load("Swift_Message_Encoder");
+    //I'll try as best I can to walk through this...
+    
+    $lname = strtolower($name);
+    
+    if ($this->cached[$lname] !== null) return $this->cached[$lname];
+    
+    $value = $this->get($name);
+    
+    $is_email = in_array($name, $this->emailContainingHeaders);
+    
+    $encoded_value = (array) $value; //Turn strings into arrays (just to make the following logic simpler)
+    
+    //Look at each value in this header
+    // There will only be 1 value if it was a string to begin with, and usually only address lists will be multiple
+    foreach ($encoded_value as $key => $row)
+    {
+      $spec = ""; //The bit which specifies the encoding of the header (if any)
+      $end = ""; //The end delimiter for an encoded header
+      
+      //If the header is 7-bit printable it's at no risk of injection
+      if (Swift_Message_Encoder::instance()->isHeaderSafe($row) && !$this->forceEncoding)
+      {
+        //Keeps the total line length at less than 76 chars, taking into account the Header name length
+        $encoded_value[$key] = Swift_Message_Encoder::instance()->header7BitEncode(
+          $row, 72, ($key > 0 ? 0 : (75-(strlen($name)+5))), $this->LE);
+      }
+      elseif ($this->encoding == "Q") //QP encode required
+      {
+        $spec = "=?" . $this->getCharset() . "?Q?"; //e.g. =?iso-8859-1?Q?
+        $end = "?=";
+        //Calculate the length of, for example: "From: =?iso-8859-1?Q??="
+        $used_length = strlen($name) + 2 + strlen($spec) + 2;
+        
+        //Encode to QP, excluding the specification for now but keeping the lines short enough to be compliant
+        $encoded_value[$key] = str_replace(" ", "_", Swift_Message_Encoder::instance()->QPEncode(
+          $row, (75-(strlen($spec)+6)), ($key > 0 ? 0 : (75-$used_length)), true, $this->LE));
+        
+      }
+      elseif ($this->encoding == "B") //Need to Base64 encode
+      {
+        //See the comments in the elseif() above since the logic is the same (refactor?)
+        $spec = "=?" . $this->getCharset() . "?B?";
+        $end = "?=";
+        $used_length = strlen($name) + 2 + strlen($spec) + 2;
+        $encoded_value[$key] = Swift_Message_Encoder::instance()->base64Encode(
+          $row, (75-(strlen($spec)+5)), ($key > 0 ? 0 : (76-($used_length+3))), true, $this->LE);
+      }
+      
+      if (false !== $p = strpos($encoded_value[$key], $this->LE))
+      {
+        $cb = 'str_replace("' . $this->LE . '", "", "<$1>");';
+        $encoded_value[$key] = preg_replace("/<([^>]+)>/e", $cb, $encoded_value[$key]);
+      }
+      
+      //Turn our header into an array of lines ready for wrapping around the encoding specification
+      $lines = explode($this->LE, $encoded_value[$key]);
+      
+      for ($i = 0, $len = count($lines); $i < $len; $i++)
+      {
+        //Don't allow commas in address fields without quotes unless they're encoded
+        if (empty($spec) && $is_email && (false !== $p = strpos($lines[$i], ",")))
+        {
+          $s = strpos($lines[$i], " <");
+          $e = strpos($lines[$i], ">");
+          if ($s < $e)
+          {
+            $addr = substr($lines[$i], $s);
+            $lines[$i] = "\"" . substr($lines[$i], 0, $s) . "\"" . $addr;
+          }
+          else
+          {
+            $lines[$i] = "\"" . $lines[$i] . "\"";
+          }
+        }
+        
+        if ($this->encoding == "Q") $lines[$i] = rtrim($lines[$i], "=");
+        
+        if ($lines[$i] == "" && $i > 0)
+        {
+          unset($lines[$i]); //Empty line, we'd rather not have these in the headers thank you!
+          continue;
+        }
+        if ($i > 0)
+        {
+          //Don't stick the specification part around the line if it's an address
+          if (substr($lines[$i], 0, 1) == '<' && substr($lines[$i], -1) == '>') $lines[$i] = " " . $lines[$i];
+          else $lines[$i] = " " . $spec . $lines[$i] . $end;
+        }
+        else
+        {
+          if (substr($lines[$i], 0, 1) != '<' || substr($lines[$i], -1) != '>') $lines[$i] = $spec . $lines[$i] . $end;
+        }
+      }
+      //Build back into a string, now includes the specification
+      $encoded_value[$key] = implode($this->LE, $lines);
+      $lines = null;
+    }
+    
+    //If there are multiple values in this header, put them on separate lines, cleared by commas
+    $this->cached[$lname] = implode("," . $this->LE . " ", $encoded_value);
+    
+    //Append attributes if there are any
+    if (!empty($this->attributes[$lname])) $this->cached[$lname] .= $this->buildAttributes($this->cached[$lname], $lname);
+    
+    return $this->cached[$lname];
+  }
+  /**
+   * Build the list of attributes for appending to the given header
+   * This is RFC 2231 & 2047 compliant.
+   * A HUGE thanks to Joaquim Homrighausen for heaps of help, advice
+   * and testing to get this working rock solid.
+   * @param string The header built without attributes
+   * @param string The lowercase name of the header
+   * @return string
+   * @throws Swift_Message_MimeException If no such header exists or there are no attributes
+   */
+  protected function buildAttributes($header_line, $header_name)
+  {
+    Swift_ClassLoader::load("Swift_Message_Encoder");
+    $lines = explode($this->LE, $header_line);
+    $used_len = strlen($lines[count($lines)-1]);
+    $lines= null;
+    $ret = "";
+    foreach ($this->attributes[$header_name] as $attribute => $att_value)
+    {
+      if ($att_value === null) continue;
+      // 70 to account for LWSP, CRLF, quotes and a semi-colon
+      // + length of attribute
+      // + 4 for a 2 digit number and 2 asterisks
+      $avail_len = 70 - (strlen($attribute) + 4);
+      $encoded = Swift_Message_Encoder::instance()->rfc2047Encode($att_value, $this->charset, $this->language, $avail_len, $this->LE);
+      $lines = explode($this->LE, $encoded);
+      foreach ($lines as $i => $line)
+      {
+        //Add quotes if needed (RFC 2045)
+        if (preg_match("~[\\s\";,<>\\(\\)@:\\\\/\\[\\]\\?=]~", $line)) $lines[$i] = '"' . $line . '"';
+      }
+      $encoded = implode($this->LE, $lines);
+      
+      //If we can fit this entire attribute onto the same line as the header then do it!
+      if ((strlen($encoded) + $used_len + strlen($attribute) + 4) < 74)
+      {
+        if (strpos($encoded, "'") !== false) $attribute .= "*";
+        $append = "; " . $attribute . "=" . $encoded;
+        $ret .= $append;
+        $used_len += strlen($append);
+      }
+      else //... otherwise list of underneath
+      {
+        $ret .= ";";
+        if (count($lines) > 1)
+        {
+          $loop = false;
+          $add_asterisk = false;
+          foreach ($lines as $i => $line)
+          {
+            $att_copy = $attribute; //Because it's multi-line it needs asterisks with decimal indices
+            $att_copy .= "*" . $i;
+            if ($add_asterisk || strpos($encoded, "'") !== false)
+            {
+              $att_copy .= "*"; //And if it's got a ' then it needs another asterisk
+              $add_asterisk = true;
+            }
+            $append = "";
+            if ($loop) $append .= ";";
+            $append .= $this->LE . " " . $att_copy . "=" . $line;
+            $ret .= $append;
+            $used_len = strlen($append)+1;
+            $loop = true;
+          }
+        }
+        else
+        {
+          if (strpos($encoded, "'") !== false) $attribute .= "*";
+          $append = $this->LE . " " . $attribute . "=" . $encoded;
+          $used_len = strlen($append)+1;
+          $ret .= $append;
+        }
+      }
+      $lines= null;
+    }
+    return $ret;
+  }
+  /**
+   * Compile the list of headers which have been set and return an ascii string
+   * The return value should always be 7-bit ascii and will have been cleaned for header injection
+   * If this looks complicated it's probably because it is!!  Keeping everything compliant is not easy.
+   * This is RFC 2822 compliant
+   * @return string
+   */
+  public function build()
+  {
+    $ret = "";
+    foreach ($this->headers as $name => $value) //Look at each header
+    {
+      if ($value === null) continue;
+      $ret .= ltrim($name, ".") . ": " . $this->getEncoded($name) . $this->LE;
+    }
+    return trim($ret);
+  }
+}
diff --git a/system/vendor/swift/Swift/Message/Image.php b/system/vendor/swift/Swift/Message/Image.php
new file mode 100755
index 0000000..2cb2336
--- /dev/null
+++ b/system/vendor/swift/Swift/Message/Image.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * Swift Mailer Image Component
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Message_EmbeddedFile");
+
+/**
+ * Embedded Image component for Swift Mailer
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Message_Image extends Swift_Message_EmbeddedFile
+{
+  /**
+   * Constructor
+   * @param Swift_File The input source file
+   * @param string The filename to use, optional
+   * @param string The MIME type to use, optional
+   * @param string The Content-ID to use, optional
+   * @param string The encoding format to use, optional
+   */
+  public function __construct(Swift_File $data=null, $name=null, $type="application/octet-stream", $cid=null, $encoding="base64")
+  {
+    parent::__construct($data, $name, $type, $cid, $encoding);
+  }
+  /**
+   * Set data for the image
+   * This overrides setData() in Swift_Message_Attachment
+   * @param Swift_File The data to set, as a file
+   * @throws Swift_Message_MimeException If the image cannot be used, or the file is not
+   */
+  public function setData($data, $read_filename=true)
+  {
+    if (!($data instanceof Swift_File)) throw new Exception("Parameter 1 of " . __METHOD__ . " must be instance of Swift_File");
+    parent::setData($data, $read_filename);
+    $img_data = @getimagesize($data->getPath());
+    if (!$img_data)
+    {
+      throw new Swift_Message_MimeException(
+      "Cannot use file '" . $data->getPath() . "' as image since getimagesize() was unable to detect a file format. " .
+      "Try using Swift_Message_EmbeddedFile instead");
+    }
+    $type = image_type_to_mime_type($img_data[2]);
+    $this->setContentType($type);
+    if (!$this->getFileName()) $this->setFileName($data->getFileName());
+  }
+}
diff --git a/system/vendor/swift/Swift/Message/Mime.php b/system/vendor/swift/Swift/Message/Mime.php
new file mode 100755
index 0000000..d6690b9
--- /dev/null
+++ b/system/vendor/swift/Swift/Message/Mime.php
@@ -0,0 +1,500 @@
+<?php
+
+/**
+ * Swift Mailer MIME Library central component
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_File");
+Swift_ClassLoader::load("Swift_Message_MimeException");
+
+/**
+ * Mime is the underbelly for Messages, Attachments, Parts, Embedded Images, Forwarded Mail, etc
+ * In fact, every single component of the composed email is simply a new Mime document nested inside another
+ * When you piece an email together in this way you see just how straight-forward it really is
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+abstract class Swift_Message_Mime
+{
+  /**
+   * Constant for plain-text emails
+   */
+  const PLAIN = "text/plain";
+  /**
+   * Constant for HTML emails
+   */
+  const HTML = "text/html";
+  /**
+   * Constant for miscellaneous mime type
+   */
+  const MISC = "application/octet-stream";
+  /**
+   * Constant for MIME sections which must appear in the multipart/alternative section.
+   */
+  const LEVEL_ALTERNATIVE = "alternative";
+  /**
+   * Constant for MIME sections which must appear in the multipart/related section.
+   */
+  const LEVEL_RELATED = "related";
+  /**
+   * Constant for MIME sections which must appear in the multipart/mixed section.
+   */
+  const LEVEL_MIXED = "mixed";
+  /**
+   * Constant for MIME sections which must appear in the multipart/mixed section.
+   */
+  const LEVEL_TOP = "top";
+  /**
+   * Constant for safe line length in almost all places
+   */
+  const SAFE_LENGTH = 1000; //RFC 2822
+  /**
+   * Constant for really safe line length
+   */
+  const VERY_SAFE_LENGTH = 76; //For command line mail clients such as pine
+  /**
+   * The header part of this MIME document
+   * @var Swift_Message_Headers
+   */
+  public $headers = null;
+  /**
+   * The body of the documented (unencoded)
+   * @var string data
+   */
+  protected $data = "";
+  /**
+   * Maximum line length
+   * @var int
+   */
+  protected $wrap = 1000; //RFC 2822
+  /**
+   * Nested mime parts
+   * @var array
+   */
+  protected $children = array();
+  /**
+   * The boundary used to separate mime parts
+   * @var string
+   */
+  protected $boundary = null;
+  /**
+   * The line ending characters needed
+   * @var string
+   */
+  protected $LE = "\r\n";
+  /**
+   * An instance of Swift_Cache
+   * @var Swift_Cache
+   */
+  protected $cache;
+  /**
+   * A list of used MIME boundaries after they're generated.
+   * @var array
+   */
+  protected static $usedBoundaries = array();
+  
+  /**
+   * Constructor
+   */
+  public function __construct()
+  {
+    Swift_ClassLoader::load("Swift_Message_Headers");
+    $this->setHeaders(new Swift_Message_Headers());
+    Swift_ClassLoader::load("Swift_CacheFactory");
+    $this->cache = Swift_CacheFactory::getCache();
+  }
+  /**
+   * Compute a unique boundary
+   * @return string
+   */
+  public static function generateBoundary()
+  {
+    do
+    {
+      $boundary = uniqid(rand(), true);
+    } while (in_array($boundary, self::$usedBoundaries));
+    self::$usedBoundaries[] = $boundary;
+    return "_=_swift-" . $boundary . "_=_";
+  }
+  /**
+   * Replace the current headers with new ones
+   * DO NOT DO THIS UNLESS YOU KNOW WHAT YOU'RE DOING!
+   * @param Swift_Message_Headers The headers to use
+   */
+  public function setHeaders($headers)
+  {
+    $this->headers = $headers;
+  }
+  /**
+   * Set the line ending character to use
+   * @param string The line ending sequence
+   * @return boolean
+   */
+  public function setLE($le)
+  {
+    if (in_array($le, array("\r", "\n", "\r\n")))
+    {
+      $this->cache->clear("body");
+      $this->LE = $le;
+      //This change should be recursive
+      $this->headers->setLE($le);
+      foreach ($this->children as $id => $child)
+      {
+        $this->children[$id]->setLE($le);
+      }
+      
+      return true;
+    }
+    else return false;
+  }
+  /**
+   * Get the line ending sequence
+   * @return string
+   */
+  public function getLE()
+  {
+    return $this->LE;
+  }
+  /**
+   * Reset the entire cache state from this branch of the tree and traversing down through the children
+   */
+  public function uncacheAll()
+  {
+    $this->cache->clear("body");
+    $this->cache->clear("append");
+    $this->cache->clear("headers");
+    $this->cache->clear("dbl_le");
+    $this->headers->uncacheAll();
+    foreach ($this->children as $id => $child)
+    {
+      $this->children[$id]->uncacheAll();
+    }
+  }
+  /**
+   * Set the content type of this MIME document
+   * @param string The content type to use in the same format as MIME 1.0 expects
+   */
+  public function setContentType($type)
+  {
+    $this->headers->set("Content-Type", $type);
+  }
+  /**
+   * Get the content type which has been set
+   * The MIME 1.0 Content-Type is provided as a string
+   * @return string
+   */
+  public function getContentType()
+  {
+    try {
+      return $this->headers->get("Content-Type");
+    } catch (Swift_Message_MimeException $e) {
+      return false;
+    }
+  }
+  /**
+   * Set the encoding format to be used on the body of the document
+   * @param string The encoding type used
+   * @param boolean If this encoding format should be used recursively. Note, this only takes effect if no encoding is set in the children.
+   * @param boolean If the encoding should only be applied when the string is not ascii.
+   */
+  public function setEncoding($encoding, $recursive=false, $non_ascii=false)
+  {
+    $this->cache->clear("body");
+    switch (strtolower($encoding))
+    {
+      case "q": case "qp": case "quoted-printable":
+        $encoding = "quoted-printable";
+        break;
+      case "b": case "base64":
+        $encoding = "base64";
+        break;
+      case "7bit": case "8bit": case "binary":
+        $encoding = strtolower($encoding);
+        break;
+    }
+    
+    $data = $this->getData();
+    Swift_ClassLoader::load("Swift_Message_Encoder");
+    if ($non_ascii && is_string($data) && strlen($data) > 0 && !Swift_Message_Encoder::instance()->is7BitAscii($data))
+    {
+      $this->headers->set("Content-Transfer-Encoding", $encoding);
+    }
+    elseif (!$non_ascii || !is_string($data))
+    {
+      $this->headers->set("Content-Transfer-Encoding", $encoding);
+    }
+    
+    if ($recursive)
+    {
+      foreach ($this->children as $id => $child)
+      {
+        if (!$child->getEncoding()) $this->children[$id]->setEncoding($encoding, $recursive, $non_ascii);
+      }
+    }
+  }
+  /**
+   * Get the encoding format used in this document
+   * @return string
+   */
+  public function getEncoding()
+  {
+    try {
+      return $this->headers->get("Content-Transfer-Encoding");
+    } catch (Swift_Message_MimeException $e) {
+      return false;
+    }
+  }
+  /**
+   * Specify the string which makes up the body of this message
+   * HINT: You can always nest another MIME document here if you call it's build() method.
+   * $data can be an object of Swift_File or a string
+   * @param mixed The body of the document
+   */
+  public function setData($data)
+  {
+    $this->cache->clear("body");
+    if ($data instanceof Swift_File) $this->data = $data;
+    else $this->data = (string) $data;
+  }
+  /**
+   * Return the string which makes up the body of this MIME document
+   * @return string,Swift_File
+   */
+  public function getData()
+  {
+    return $this->data;
+  }
+  /**
+   * Get the data in the format suitable for sending
+   * @return Swift_Cache_OutputStream
+   * @throws Swift_FileException If the file stream given cannot be read
+   * @throws Swift_Message_MimeException If some required headers have been forcefully removed
+   */
+  public function buildData()
+  {
+    Swift_ClassLoader::load("Swift_Message_Encoder");
+    Swift_ClassLoader::load("Swift_Cache_JointOutputStream");
+    if (!empty($this->children)) //If we've got some mime parts we need to stick them onto the end of the message
+    {
+      if ($this->boundary === null) $this->boundary = self::generateBoundary();
+      $this->headers->setAttribute("Content-Type", "boundary", $this->boundary);
+      
+      $this->cache->clear("append");
+      foreach ($this->children as $part)
+      {
+        $this->cache->write("append", $this->LE . "--" . $this->boundary . $this->LE);
+        $part_stream = $part->build();
+        while (false !== $bytes = $part_stream->read()) $this->cache->write("append", $bytes);
+      }
+      $this->cache->write("append", $this->LE . "--" . $this->boundary . "--" . $this->LE);
+    }
+    
+    $joint_os = new Swift_Cache_JointOutputStream();
+    
+    //Try using a cached version to save some cycles (at the expense of memory)
+    //if ($this->cache !== null) return $this->cache . $append;
+    if ($this->cache->has("body"))
+    {
+      $joint_os->addStream($this->cache->getOutputStream("body"));
+      $joint_os->addStream($this->cache->getOutputStream("append"));
+      return $joint_os;
+    }
+    
+    $is_file = ($this->getData() instanceof Swift_File);
+    switch ($this->getEncoding())
+    {
+      case "quoted-printable":
+        if ($is_file)
+        {
+          $qp_os = Swift_Message_Encoder::instance()->QPEncodeFile($this->getData(), 76, $this->LE);
+          while (false !== $bytes = $qp_os->read())
+            $this->cache->write("body", $bytes);
+        }
+        else
+        {
+          $this->cache->write("body", Swift_Message_Encoder::instance()->QPEncode($this->getData(), 76, 0, false, $this->LE));
+        }
+        break;
+      case "base64":
+        if ($is_file)
+        {
+          $b64_os = Swift_Message_Encoder::instance()->base64EncodeFile($this->getData(), 76, $this->LE);
+          while (false !== $bytes = $b64_os->read())
+            $this->cache->write("body", $bytes);
+        }
+        else
+        {
+          $this->cache->write("body", Swift_Message_Encoder::instance()->base64Encode($this->getData(), 76, 0, false, $this->LE));
+        }
+        break;
+      case "binary":
+        if ($is_file)
+        {
+          $data = $this->getData();
+          while (false !== $bytes = $data->read(8192))
+            $this->cache->write("body", $bytes);
+        }
+        else
+        {
+          $this->cache->write("body", $this->getData());
+        }
+        break;
+      case "7bit":
+        if ($is_file)
+        {
+          $os = Swift_Message_Encoder::instance()->encode7BitFile($this->getData(), $this->wrap, $this->LE);
+          while (false !== $bytes = $os->read())
+            $this->cache->write("body", $bytes);
+        }
+        else
+        {
+          $this->cache->write("body", Swift_Message_Encoder::instance()->encode7Bit($this->getData(), $this->wrap, $this->LE));
+        }
+        break;
+      case "8bit": default:
+        if ($is_file)
+        {
+          $os = Swift_Message_Encoder::instance()->encode8BitFile($this->getData(), $this->wrap, $this->LE);
+          while (false !== $bytes = $os->read())
+            $this->cache->write("body", $bytes);
+        }
+        else
+        {
+          $this->cache->write("body", Swift_Message_Encoder::instance()->encode8Bit($this->getData(), $this->wrap, $this->LE));
+        }
+        break;
+    }
+    $joint_os->addStream($this->cache->getOutputStream("body"));
+    $joint_os->addStream($this->cache->getOutputStream("append"));
+    return $joint_os;
+  }
+  /**
+   * Set the size at which lines wrap around (includes the CRLF)
+   * @param int The length of a line
+   */
+  public function setLineWrap($len)
+  {
+    $this->cache->clear("body");
+    $this->wrap = (int) $len;
+  }
+  /**
+   * Nest a child mime part in this document
+   * @param Swift_Message_Mime
+   * @param string The identifier to use, optional
+   * @param int Add the part before (-1) or after (+1) the other parts
+   * @return string The identifier for this part
+   */
+  public function addChild(Swift_Message_Mime $mime, $id=null, $after=1)
+  {
+    if (empty($id))
+    {
+      do
+      {
+        $id = uniqid();
+      } while (array_key_exists($id, $this->children));
+    }
+    $id = (string) $id;
+    if ($after == -1) $this->children = array_merge(array($id => $mime), $this->children);
+    else $this->children[$id] = $mime;
+    
+    return $id;
+  }
+  /**
+   * Check if a child exists identified by $id
+   * @param string Identifier to look for
+   * @return boolean
+   */
+  public function hasChild($id)
+  {
+    return array_key_exists($id, $this->children);
+  }
+  /**
+   * Get a child document, identified by $id
+   * @param string The identifier for this child
+   * @return Swift_Message_Mime The child document
+   * @throws Swift_Message_MimeException If no such child exists
+   */
+  public function getChild($id)
+  {
+    if ($this->hasChild($id))
+    {
+      return $this->children[$id];
+    }
+    else
+    {
+      throw new Swift_Message_MimeException(
+      "Cannot retrieve child part identified by '" . $id . "' as it does not exist.  Consider using hasChild() to check.");
+    }
+  }
+  /**
+   * Remove a part from the document
+   * @param string The identifier of the child
+   * @throws Swift_Message_MimeException If no such part exists
+   */
+  public function removeChild($id)
+  {
+    $id = (string) $id;
+    if (!$this->hasChild($id))
+    {
+      throw new Swift_Message_MimeException(
+      "Cannot remove child part identified by '" . $id . "' as it does not exist. Consider using hasChild() to check.");
+    }
+    else
+    {
+      $this->children[$id] = null;
+      unset($this->children[$id]);
+    }
+  }
+  /**
+   * List the IDs of all children in this document
+   * @return array
+   */
+  public function listChildren()
+  {
+    return array_keys($this->children);
+  }
+  /**
+   * Get the total number of children present in this document
+   * @return int
+   */
+  public function numChildren()
+  {
+    return count($this->children);
+  }
+  /**
+   * Get the level at which this mime part would appear in a document
+   * One of "mixed", "alternative" or "related"
+   * @return string
+   */
+  abstract public function getLevel();
+  /**
+   * Compile the entire MIME document into a string
+   * The returned string may be used in other documents if needed.
+   * @return Swift_Cache_OutputStream
+   */
+  public function build()
+  {
+    $this->preBuild();
+    $data = $this->buildData();
+    $joint_os = new Swift_Cache_JointOutputStream();
+    $this->cache->clear("headers");
+    $this->cache->write("headers", $this->headers->build());
+    $joint_os->addStream($this->cache->getOutputStream("headers"));
+    $this->cache->clear("dbl_le");
+    $this->cache->write("dbl_le", str_repeat($this->LE, 2));
+    $joint_os->addStream($this->cache->getOutputStream("dbl_le"));
+    $joint_os->addStream($data);
+    return $joint_os;
+    //return $this->headers->build() . str_repeat($this->LE, 2) . $data;
+  }
+  /**
+   * Execute any logic needed prior to building
+   */
+  abstract public function preBuild();
+}
diff --git a/system/vendor/swift/Swift/Message/MimeException.php b/system/vendor/swift/Swift/Message/MimeException.php
new file mode 100755
index 0000000..ecce70d
--- /dev/null
+++ b/system/vendor/swift/Swift/Message/MimeException.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Swift MIME Exception
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Exception");
+
+/**
+ * Swift MIME Exception
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Message_MimeException extends Swift_Exception
+{
+}
diff --git a/system/vendor/swift/Swift/Message/Part.php b/system/vendor/swift/Swift/Message/Part.php
new file mode 100755
index 0000000..c75f58f
--- /dev/null
+++ b/system/vendor/swift/Swift/Message/Part.php
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * Swift Mailer Message MIME Part
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Message
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Message_Mime");
+
+/**
+ * MIME Part body component for Swift Mailer
+ * @package Swift_Message
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Message_Part extends Swift_Message_Mime
+{
+  /**
+   * Constructor
+   * @param mixed The data to use in the body
+   * @param string Mime type
+   * @param string The encoding format used
+   * @param string The charset used
+   */
+  public function __construct($data=null, $type="text/plain", $encoding=null, $charset=null)
+  {
+    parent::__construct();
+    
+    $this->setContentType($type);
+    $this->setEncoding($encoding);
+    $this->setCharset($charset);
+    $this->setFlowed(false);
+    
+    if ($data !== null)
+    {
+      $this->setData($data);
+      if ($charset === null)
+      {
+        Swift_ClassLoader::load("Swift_Message_Encoder");
+        if (is_string($data) && Swift_Message_Encoder::instance()->isUTF8($data)) $this->setCharset("utf-8");
+        else $this->setCharset("iso-8859-1"); //The likely encoding
+      }
+    }
+  }
+  /**
+   * Get the level in the MIME hierarchy at which this section should appear.
+   * @return string
+   */
+  public function getLevel()
+  {
+    return Swift_Message_Mime::LEVEL_ALTERNATIVE;
+  }
+  /**
+   * Alias for setData()
+   * @param mixed Body
+   */
+  public function setBody($body)
+  {
+    $this->setData($body);
+  }
+  /**
+   * Alias for getData()
+   * @return mixed The document body
+   */
+  public function getBody()
+  {
+    return $this->getData();
+  }
+  /**
+   * Set the charset of the document
+   * @param string The charset used
+   */
+  public function setCharset($charset)
+  {
+    $this->headers->setAttribute("Content-Type", "charset", $charset);
+    if (($this->getEncoding() == "7bit") && (strtolower($charset) == "utf-8" || strtolower($charset) == "utf8")) $this->setEncoding("8bit");
+  }
+  /**
+   * Get the charset used in the document
+   * Returns null if none is set
+   * @return string
+   */
+  public function getCharset()
+  {
+    if ($this->headers->hasAttribute("Content-Type", "charset"))
+    {
+      return $this->headers->getAttribute("Content-Type", "charset");
+    }
+    else
+    {
+      return null;
+    }
+  }
+  /**
+   * Set the "format" attribute to flowed
+   * @param boolean On or Off
+   */
+  public function setFlowed($flowed=true)
+  {
+    $value = null;
+    if ($flowed) $value = "flowed";
+    $this->headers->setAttribute("Content-Type", "format", $value);
+  }
+  /**
+   * Pre-compilation logic
+   */
+  public function preBuild()
+  {
+    if (!($enc = $this->getEncoding())) $this->setEncoding("8bit");
+    $data = $this->getData();
+    if ($this->getCharset() === null && !$this->numChildren())
+    {
+      if (is_string($data) && Swift_Message_Encoder::instance()->isUTF8($data))
+      {
+        $this->setCharset("utf-8");
+      }
+      elseif (is_string($data) && Swift_Message_Encoder::instance()->is7BitAscii($data))
+      {
+        $this->setCharset("us-ascii");
+        if (!$enc) $this->setEncoding("7bit");
+      }
+      else $this->setCharset("iso-8859-1");
+    }
+    elseif ($this->numChildren())
+    {
+      $this->setCharset(null);
+      $this->setEncoding("7bit");
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/AntiFlood.php b/system/vendor/swift/Swift/Plugin/AntiFlood.php
new file mode 100755
index 0000000..83c36fe
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/AntiFlood.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * Swift Mailer AntiFlood Plugin
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Events_SendListener");
+
+/**
+ * Swift AntiFlood controller.
+ * Closes a connection and pauses for X seconds after a number of emails have been sent.
+ * @package Swift_Plugin
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_AntiFlood implements Swift_Events_SendListener
+{
+  /**
+   * The number of emails to send between connections
+   * @var int
+   */
+  protected $threshold = null;
+  /**
+   * The number of seconds to pause for between connections
+   * @var int
+   */
+  protected $waitFor = null;
+  /**
+   * Number of emails sent so far
+   * @var int
+   */
+  protected $count = 0;
+  
+  /**
+   * Constructor
+   * @param int Number of emails to send before re-connecting
+   * @param int The timeout in seconds between connections
+   */
+  public function __construct($threshold, $wait=0)
+  {
+    $this->setThreshold($threshold);
+    $this->setWait($wait);
+  }
+  /**
+   * Set the number of emails which must be sent for a reconnection to occur
+   * @param int Number of emails
+   */
+  public function setThreshold($threshold)
+  {
+    $this->threshold = (int) $threshold;
+  }
+  /**
+   * Get the number of emails which need to be sent for reconnection to occur
+   * @return int
+   */
+  public function getThreshold()
+  {
+    return $this->threshold;
+  }
+  /**
+   * Set the number of seconds the plugin should wait for before reconnecting
+   * @param int Time in seconds
+   */
+  public function setWait($time)
+  {
+    $this->waitFor = (int) $time;
+  }
+  /**
+   * Get the number of seconds the plugin should wait for before re-connecting
+   * @return int
+   */
+  public function getWait()
+  {
+    return $this->waitFor;
+  }
+  /**
+   * Sleep for a given number of seconds
+   * @param int Number of seconds to wait for
+   */
+  public function wait($seconds)
+  {
+    if ($seconds) sleep($seconds);
+  }
+  /**
+   * Swift's SendEvent listener.
+   * Invoked when Swift sends a message
+   * @param Swift_Events_SendEvent The event information
+   * @throws Swift_ConnectionException If the connection cannot be closed/re-opened
+   */
+  public function sendPerformed(Swift_Events_SendEvent $e)
+  {
+    $this->count++;
+    if ($this->count >= $this->getThreshold())
+    {
+      $e->getSwift()->disconnect();
+      $this->wait($this->getWait());
+      $e->getSwift()->connect();
+      $this->count = 0;
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/BandwidthMonitor.php b/system/vendor/swift/Swift/Plugin/BandwidthMonitor.php
new file mode 100755
index 0000000..37ae860
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/BandwidthMonitor.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * Swift Mailer Bandwidth Monitoring Plugin
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Events_CommandListener");
+Swift_ClassLoader::load("Swift_Events_ResponseListener");
+
+/**
+ * Swift Bandwidth Monitor.
+ * Tracks bytes in and out of the connection.
+ * @package Swift_Plugin
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_BandwidthMonitor implements Swift_Events_CommandListener, Swift_Events_ResponseListener
+{
+  /**
+   * The number of bytes received
+   * @var int
+   */
+  protected $in = 0;
+  /**
+   * The number of bytes sent
+   * @var int
+   */
+  protected $out = 0;
+  
+  /**
+   * Part of the interface which is notified after a command is sent.
+   * @param Swift_Events_CommandEvent
+   */
+  public function commandSent(Swift_Events_CommandEvent $e)
+  {
+    $code = $e->getCode();
+    $add = 0;
+    if ($code != -1) $add = 2;
+    $bytes = strlen($e->getString()) + $add;
+    $this->addBytesOut($bytes);
+  }
+  /**
+   * Part of the interface which is notified when a response is received
+   * @param Swift_Events_ResponseEvent
+   */
+  public function responseReceived(Swift_Events_ResponseEvent $e)
+  {
+    $bytes = strlen($e->getString()) + 2;
+    $this->addBytesIn($bytes);
+  }
+  /**
+   * Add some bytes to the running totals for incoming bandwidth
+   * @param int Bytes in
+   */
+  public function addBytesIn($num)
+  {
+    $num = abs((int)$num);
+    $this->setBytesIn($this->getBytesIn() + $num);
+  }
+  /**
+   * Add some bytes to the running totals for outgoing bandwidth
+   * @param int Bytes out
+   */
+  public function addBytesOut($num)
+  {
+    $num = abs((int)$num);
+    $this->setBytesOut($this->getBytesOut() + $num);
+  }
+  /**
+   * Get the total number of bytes received
+   * @return int
+   */
+  public function getBytesIn()
+  {
+    return $this->in;
+  }
+  /**
+   * Get the total number of bytes sent
+   * @return int
+   */
+  public function getBytesOut()
+  {
+    return $this->out;
+  }
+  /**
+   * Set the total number of bytes received.
+   * Can be used to reset the counters at runtime.
+   * @param int The bytes in
+   */
+  public function setBytesIn($num)
+  {
+    $this->in = abs((int)$num);
+  }
+  /**
+   * Set the total number of bytes sent.
+   * Can be used to reset the counters at runtime.
+   * @param int The bytes out
+   */
+  public function setBytesOut($num)
+  {
+    $this->out = abs((int)$num);
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/ConnectionRotator.php b/system/vendor/swift/Swift/Plugin/ConnectionRotator.php
new file mode 100755
index 0000000..fe75c47
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/ConnectionRotator.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * Swift Mailer Rotating Connection Controller
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Events_SendListener");
+Swift_ClassLoader::load("Swift_Events_DisconnectListener");
+
+/**
+ * Swift Rotating Connection Controller
+ * Invokes the nextConnection() method of Swift_Connection_Rotator upon sending a given number of messages
+ * @package Swift_Plugin
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_ConnectionRotator implements Swift_Events_SendListener, Swift_Events_DisconnectListener
+{
+  /**
+   * The number of emails which must be sent before the connection is rotated
+   * @var int Threshold number of emails
+   */
+  protected $threshold = 1;
+  /**
+   * The total number of emails sent on this connection
+   * @var int
+   */
+  protected $count = 0;
+  /**
+   * The connections we have used thus far
+   * @var array
+   */
+  protected $used = array();
+  /**
+   * Internal check to see if this plugin has yet been invoked
+   * @var boolean
+   */
+  protected $called = false;
+  
+  /**
+   * Constructor
+   * @param int The number of emails to send before rotating
+   */
+  public function __construct($threshold=1)
+  {
+    $this->setThreshold($threshold);
+  }
+  /**
+   * Set the number of emails to send before a connection rotation is tried
+   * @param int Number of emails
+   */
+  public function setThreshold($threshold)
+  {
+    $this->threshold = (int) $threshold;
+  }
+  /**
+   * Get the number of emails which must be sent before a rotation occurs
+   * @return int
+   */
+  public function getThreshold()
+  {
+    return $this->threshold;
+  }
+  /**
+   * Swift's SendEvent listener.
+   * Invoked when Swift sends a message
+   * @param Swift_Events_SendEvent The event information
+   * @throws Swift_ConnectionException If the connection cannot be rotated
+   */
+  public function sendPerformed(Swift_Events_SendEvent $e)
+  {
+    if (!method_exists($e->getSwift()->connection, "nextConnection"))
+    {
+      throw new Swift_ConnectionException("The ConnectionRotator plugin cannot be used with connections other than Swift_Connection_Rotator.");
+    }
+    if (!$this->called)
+    {
+      $this->used[] = $e->getSwift()->connection->getActive();
+    }
+    $this->count++;
+    if ($this->count >= $this->getThreshold())
+    {
+      $e->getSwift()->connection->nextConnection();
+      if (!in_array(($id = $e->getSwift()->connection->getActive()), $this->used))
+      {
+        $e->getSwift()->connect();
+        $this->used[] = $id;
+      }
+      $this->count = 0;
+    }
+    $this->called = true;
+  }
+  /**
+   * Disconnect all the other connections
+   * @param Swift_Events_DisconnectEvent The event info
+   */
+  public function disconnectPerformed(Swift_Events_DisconnectEvent $e)
+  {
+    $active = $e->getConnection()->getActive();
+    $e->getConnection()->nextConnection();
+    while ($e->getConnection()->getActive() != $active)
+    {
+      $e->getSwift()->command("QUIT", 221);
+      $e->getConnection()->stop();
+      $e->getConnection()->nextConnection();
+    }
+    $this->used = array();
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/Decorator.php b/system/vendor/swift/Swift/Plugin/Decorator.php
new file mode 100755
index 0000000..e073886
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/Decorator.php
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * Swift Mailer Message Decorating Plugin.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @subpackage Decorator
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Events_BeforeSendListener");
+Swift_ClassLoader::load("Swift_Plugin_Decorator_Replacements");
+
+/**
+ * Swift Decorator Plugin.
+ * Allows messages to be slightly different for each recipient.
+ * @package Swift_Plugin
+ * @subpackage Decorator
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_Decorator implements Swift_Events_BeforeSendListener
+{
+  /**
+   * The replacements object.
+   * @var Swift_Plugin_Decorator_Replacements
+   */
+  protected $replacements;
+  /**
+   * Temporary storage so we can restore changes we make.
+   * @var array
+   */
+  protected $store;
+  /**
+   * A list of allowed mime types to replace bodies for.
+   * @var array
+   */
+  protected $permittedTypes = array("text/plain" => 1, "text/html" => 1);
+  /**
+   * True if values in the headers can be replaced
+   * @var boolean
+   */
+  protected $permittedInHeaders = true;
+  
+  /**
+   * Ctor.
+   * @param mixed Replacements as a 2-d array or Swift_Plugin_Decorator_Replacements instance.
+   */
+  public function __construct($replacements=null)
+  {
+    $this->setReplacements($replacements);
+  }
+  /**
+   * Enable of disable the ability to replace values in the headers
+   * @param boolean
+   */
+  public function setPermittedInHeaders($bool)
+  {
+    $this->permittedInHeaders = (bool) $bool;
+  }
+  /**
+   * Check if replacements in headers are allowed.
+   * @return boolean
+   */
+  public function getPermittedInHeaders()
+  {
+    return $this->permittedInHeaders;
+  }
+  /**
+   * Add a mime type to the list of permitted type to replace values in the body.
+   * @param string The mime type (e.g. text/plain)
+   */
+  public function addPermittedType($type)
+  {
+    $type = strtolower($type);
+    $this->permittedTypes[$type] = 1;
+  }
+  /**
+   * Remove the ability to replace values in the body of the given mime type
+   * @param string The mime type
+   */
+  public function removePermittedType($type)
+  {
+    unset($this->permittedTypes[$type]);
+  }
+  /**
+   * Get the list of mime types for which the body can be changed.
+   * @return array
+   */
+  public function getPermittedTypes()
+  {
+    return array_keys($this->permittedTypes);
+  }
+  /**
+   * Check if the body can be replaced in the given mime type.
+   * @param string The mime type
+   * @return boolean
+   */
+  public function isPermittedType($type)
+  {
+    return array_key_exists(strtolower($type), $this->permittedTypes);
+  }
+  /**
+   * Called just before Swift sends a message.
+   * We perform operations on the message here.
+   * @param Swift_Events_SendEvent The event object for sending a message
+   */
+  public function beforeSendPerformed(Swift_Events_SendEvent $e)
+  {
+    $message = $e->getMessage();
+    $this->recursiveRestore($message, $this->store); //3.3.3 bugfix
+    
+    $recipients = $e->getRecipients();
+    $to = array_keys($recipients->getTo());
+    if (count($to) > 0) $to = $to[0];
+    else return;
+    
+    $replacements = (array)$this->replacements->getReplacementsFor($to);
+    
+    $this->store = array(
+      "headers" => array(),
+      "body" => false,
+      "children" => array()
+    );
+    $this->recursiveReplace($message, $replacements, $this->store);
+  }
+  /**
+   * Replace strings in the message searching through all the allowed sub-parts.
+   * @param Swift_Message_Mime The message (or part)
+   * @param array The list of replacements
+   * @param array The array to cache original values into where needed
+   */
+  protected function recursiveReplace(Swift_Message_Mime $mime, $replacements, &$store)
+  {
+    //Check headers
+    if ($this->getPermittedInHeaders())
+    {
+      foreach ($mime->headers->getList() as $name => $value)
+      {
+        if (is_string($value) && ($replaced = $this->replace($replacements, $value)) != $value)
+        {
+          $mime->headers->set($name, $replaced);
+          $store["headers"][$name] = array();
+          $store["headers"][$name]["value"] = $value;
+          $store["headers"][$name]["attributes"] = array();
+        }
+        foreach ($mime->headers->listAttributes($name) as $att_name => $att_value)
+        {
+          if (is_string($att_value)
+            && ($att_replaced = $this->replace($replacements, $att_value)) != $att_value)
+          {
+            if (!isset($store["headers"][$name]))
+            {
+              $store["headers"][$name] = array("value" => false, "attributes" => array());
+            }
+            $mime->headers->setAttribute($name, $att_name, $att_replaced);
+            $store["headers"][$name]["attributes"][$att_name] = $att_value;
+          }
+        }
+      }
+    }
+    //Check body
+    $body = $mime->getData();
+    if ($this->isPermittedType($mime->getContentType())
+      && is_string($body) && ($replaced = $this->replace($replacements, $body)) != $body)
+    {
+      $mime->setData($replaced);
+      $store["body"] = $body;
+    }
+    //Check sub-parts
+    foreach ($mime->listChildren() as $id)
+    {
+      $store["children"][$id] = array(
+        "headers" => array(),
+        "body" => false,
+        "children" => array()
+      );
+      $child = $mime->getChild($id);
+      $this->recursiveReplace($child, $replacements, $store["children"][$id]);
+    }
+  }
+  /**
+   * Perform a str_replace() over the given value.
+   * @param array The list of replacements as (search => replacement)
+   * @param string The string to replace
+   * @return string
+   */
+  protected function replace($replacements, $value)
+  {
+    return str_replace(array_keys($replacements), array_values($replacements), $value);
+  }
+  /**
+   * Put the original values back in the message after it was modified before sending.
+   * @param Swift_Message_Mime The message (or part)
+   * @param array The location of the stored values
+   */
+  protected function recursiveRestore(Swift_Message_Mime $mime, &$store)
+  {
+    if (empty($store)) //3.3.3 bugfix
+    {
+      return;
+    }
+    
+    //Restore headers
+    foreach ($store["headers"] as $name => $array)
+    {
+      if ($array["value"] !== false) $mime->headers->set($name, $array["value"]);
+      foreach ($array["attributes"] as $att_name => $att_value)
+      {
+        $mime->headers->setAttribute($name, $att_name, $att_value);
+      }
+    }
+    //Restore body
+    if ($store["body"] !== false)
+    {
+      $mime->setData($store["body"]);
+    }
+    //Restore children
+    foreach ($store["children"] as $id => $child_store)
+    {
+      $child = $mime->getChild($id);
+      $this->recursiveRestore($child, $child_store);
+    }
+  }
+  /**
+   * Set the replacements as a 2-d array or an instance of Swift_Plugin_Decorator_Replacements.
+   * @param mixed Array or Swift_Plugin_Decorator_Replacements
+   */
+  public function setReplacements($replacements)
+  {
+    if ($replacements === null)
+    {
+      $r = array();
+      $this->replacements = new Swift_Plugin_Decorator_Replacements($r);
+    }
+    elseif (is_array($replacements))
+    {
+      $this->replacements = new Swift_Plugin_Decorator_Replacements($replacements);
+    }
+    elseif ($replacements instanceof Swift_Plugin_Decorator_Replacements)
+    {
+      $this->replacements = $replacements;
+    }
+    else
+    {
+      throw new Exception(
+        "Decorator replacements must be array or instance of Swift_Plugin_Decorator_Replacements.");
+    }
+  }
+  /**
+   * Get the replacements object.
+   * @return Swift_Plugin_Decorator_Replacements
+   */
+  public function getReplacements()
+  {
+    return $this->replacements;
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/Decorator/Replacements.php b/system/vendor/swift/Swift/Plugin/Decorator/Replacements.php
new file mode 100755
index 0000000..d5669a2
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/Decorator/Replacements.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * Swift Mailer Decorator Replacements Container
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @subpackage Decorator
+ * @license GNU Lesser General Public License
+ */
+
+
+/**
+ * Swift Decorator Plugin Replacements.
+ * Provides and manages the list of replacements for the decorator plugin.
+ * @package Swift_Plugin
+ * @subpackage Decorator
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_Decorator_Replacements
+{
+  /**
+   * The list of replacements as a 2-d array
+   * @var array,array
+   */
+  protected $replacements;
+  
+  /**
+   * Ctor.
+   * @param array The replacements as a 2-d array, optional
+   */
+  public function __construct($replacements = array())
+  {
+    $this->setReplacements($replacements);
+  }
+  /**
+   * Add a list of replacements for a given address.
+   * @param string The e-mail address
+   * @param array The replacements as (search => replacement) form.
+   */
+  public function addReplacements($address, $replacements)
+  {
+    $this->replacements[strtolower($address)] = (array)$replacements;
+  }
+  /**
+   * Set the complete list of replacements as a 2-d array.
+   * The array is formed thus (address => (search => replace), address => (search => replace))
+   * @param array,array The replacements.
+   */
+  public function setReplacements($replacements)
+  {
+    $this->replacements = array_change_key_case((array) $replacements, CASE_LOWER);
+  }
+  /**
+   * Get the entire list of replacements as a 2-d array
+   * @return array,array
+   */
+  public function getReplacements()
+  {
+    return $this->replacements;
+  }
+  /**
+   * Get the list of replacements for the address given.
+   * Returns an array where (search => replacement).
+   * @param string The address to get replacements for
+   * @return array
+   */
+  public function getReplacementsFor($address)
+  {
+    $address = strtolower($address);
+    if (array_key_exists($address, $this->replacements))
+    {
+      return (array)$this->replacements[$address];
+    }
+    else return array();
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/EasySwiftResponseTracker.php b/system/vendor/swift/Swift/Plugin/EasySwiftResponseTracker.php
new file mode 100755
index 0000000..682f909
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/EasySwiftResponseTracker.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * EasySwift Response Tracker
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package EasySwift
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Events_ResponseListener");
+
+/**
+ * EasySwift, Swift Response Tracker.
+ * Updates properties in EasySwift when a response is received by Swift.
+ * @package EasySwift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_EasySwiftResponseTracker implements Swift_Events_ResponseListener
+{
+  /**
+   * The target object to update
+   * @var EasySwift
+   */
+  protected $target = null;
+  
+  /**
+   * Constructor
+   * @param EasySwift The instance of EasySwift to run against
+   */
+  public function __construct($obj)
+  {
+    $this->target = $obj;
+  }
+  /**
+   * Response listener method
+   * @param Swift_Events_ResponseEvent The event occured in Swift
+   */
+  public function responseReceived(Swift_Events_ResponseEvent $e)
+  {
+    $this->target->lastResponse = $e->getString();
+    $this->target->responseCode = $e->getCode();
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/FileEmbedder.php b/system/vendor/swift/Swift/Plugin/FileEmbedder.php
new file mode 100755
index 0000000..b214e7b
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/FileEmbedder.php
@@ -0,0 +1,431 @@
+<?php
+
+/**
+ * A Swift Mailer plugin to download remote images and stylesheets then embed them.
+ * This also embeds local files from disk.
+ * Please read the LICENSE file
+ * @package Swift_Plugin
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Events_BeforeSendListener");
+
+/**
+ * Swift FileEmbedder Plugin to embed remote files.
+ * Scans a Swift_Message instance for remote files and then embeds them before sending.
+ * This also embeds local files from disk.
+ * @package Swift_Plugin
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_FileEmbedder implements Swift_Events_BeforeSendListener
+{
+  /**
+   * True if remote files will be embedded.
+   * @var boolean
+   */
+  protected $embedRemoteFiles = true;
+  /**
+   * True if local files will be embedded.
+   * @var boolean
+   */
+  protected $embedLocalFiles = true;
+  /**
+   * (X)HTML tag defintions listing allowed attributes and extensions.
+   * @var array
+   */
+  protected $definitions = array(
+    "img" => array(
+      "attributes" => array("src"),
+      "extensions" => array("gif", "png", "jpg",  "jpeg", "pjpeg")
+    ),
+    "link" => array(
+      "attributes" => array("href"),
+      "extensions" => array("css")
+    ),
+    "script" => array(
+      "attributes" => array("src"),
+      "extensions" => array("js")
+    ));
+  /**
+   * Protocols which may be used to download a remote file.
+   * @var array
+   */
+  protected $protocols = array(
+    "http" => "http",
+    "https" => "https",
+    "ftp" => "ftp"
+  );
+  /**
+   * A PCRE regexp which will be passed via sprintf() to produce a complete pattern.
+   * @var string
+   */
+  protected $remoteFilePatternFormat = "~
+    (<(?:%s)\\s+[^>]*?                   #Opening tag followed by (possible) attributes
+    (?:%s)=((?:\"|')?))                  #Permitted attributes followed by (possible) quotation marks
+    ((?:%s)://[\\x01-\\x7F]*?(?:%s)?)    #Remote URL (matching a permitted protocol)
+    (\\2[^>]*>)                          #Remaining attributes followed by end of tag
+    ~isx";
+  /**
+   * A PCRE regexp which will be passed via sprintf() to produce a complete pattern.
+   * @var string
+   */
+  protected $localFilePatternFormat = "~
+    (<(?:%s)\\s+[^>]*?                               #Opening tag followed by (possible) attributes
+    (?:%s)=((?:\"|')?))                              #Permitted attributes followed by (possible) quotation marks
+    ((?:/|[a-z]:\\\\|[a-z]:/)[\\x01-\\x7F]*?(?:%s)?) #Local, absolute path
+    (\\2[^>]*>)                                      #Remaining attributes followed by end of tag
+    ~isx";
+  /**
+   * A list of extensions mapping to their usual MIME types.
+   * @var array
+   */
+  protected $mimeTypes = array(
+    "gif" => "image/gif",
+    "png" => "image/png",
+    "jpeg" => "image/jpeg",
+    "jpg" => "image/jpeg",
+    "pjpeg" => "image/pjpeg",
+    "js" => "text/javascript",
+    "css" => "text/css");
+  /**
+   * Child IDs of files already embedded.
+   * @var array
+   */
+  protected $registeredFiles = array();
+  
+  /**
+   * Get the MIME type based upon the extension.
+   * @param string The extension (sans the dot).
+   * @return string
+   */
+  public function getType($ext)
+  {
+    $ext = strtolower($ext);
+    if (isset($this->mimeTypes[$ext]))
+    {
+      return $this->mimeTypes[$ext];
+    }
+    else return null;
+  }
+  /**
+   * Add a new MIME type defintion (or overwrite an existing one).
+   * @param string The extension (sans the dot)
+   * @param string The MIME type (e.g. image/jpeg)
+   */
+  public function addType($ext, $type)
+  {
+    $this->mimeTypes[strtolower($ext)] = strtolower($type);
+  }
+  /**
+   * Set the PCRE pattern which finds -full- HTML tags and copies the path for a local file into a backreference.
+   * The pattern contains three %s replacements for sprintf().
+   * First replacement is the tag name (e.g. img)
+   * Second replacement is the attribute name (e.g. src)
+   * Third replacement is the file extension (e.g. jpg)
+   * This pattern should contain the full URL in backreference index 3.
+   * @param string sprintf() format string containing a PCRE regexp.
+   */
+  public function setLocalFilePatternFormat($format)
+  {
+    $this->localFilePatternFormat = $format;
+  }
+  /**
+   * Gets the sprintf() format string for the PCRE pattern to scan for remote files.
+   * @return string
+   */
+  public function getLocalFilePatternFormat()
+  {
+    return $this->localFilePatternFormat;
+  }
+  /**
+   * Set the PCRE pattern which finds -full- HTML tags and copies the URL for the remote file into a backreference.
+   * The pattern contains four %s replacements for sprintf().
+   * First replacement is the tag name (e.g. img)
+   * Second replacement is the attribute name (e.g. src)
+   * Third replacement is the protocol (e.g. http)
+   * Fourth replacement is the file extension (e.g. jpg)
+   * This pattern should contain the full URL in backreference index 3.
+   * @param string sprintf() format string containing a PCRE regexp.
+   */
+  public function setRemoteFilePatternFormat($format)
+  {
+    $this->remoteFilePatternFormat = $format;
+  }
+  /**
+   * Gets the sprintf() format string for the PCRE pattern to scan for remote files.
+   * @return string
+   */
+  public function getRemoteFilePatternFormat()
+  {
+    return $this->remoteFilePatternFormat;
+  }
+  /**
+   * Add a new protocol which can be used to download files.
+   * Protocols should not include the "://" portion.  This method expects alphanumeric characters only.
+   * @param string The protocol name (e.g. http or ftp)
+   */
+  public function addProtocol($prot)
+  {
+    $prot = strtolower($prot);
+    $this->protocols[$prot] = $prot;
+  }
+  /**
+   * Remove a protocol from the list of allowed protocols once added.
+   * @param string The name of the protocol (e.g. http)
+   */
+  public function removeProtocol($prot)
+  {
+    unset($this->protocols[strtolower($prot)]);
+  }
+  /**
+   * Get a list of all registered protocols.
+   * @return array
+   */
+  public function getProtocols()
+  {
+    return array_values($this->protocols);
+  }
+  /**
+   * Add, or modify a tag definition.
+   * This affects how the plugins scans for files to download.
+   * @param string The name of a tag to search for (e.g. img)
+   * @param string The name of attributes to look for (e.g. src).  You can pass an array if there are multiple possibilities.
+   * @param array A list of extensions to allow (sans dot). If there's only one you can just pass a string.
+   */
+  public function setTagDefinition($tag, $attributes, $extensions)
+  {
+    $tag = strtolower($tag);
+    $attributes = (array)$attributes;
+    $extensions = (array)$extensions;
+    
+    if (empty($tag) || empty($attributes) || empty($extensions))
+    {
+      return null;
+    }
+    
+    $this->definitions[$tag] = array("attributes" => $attributes, "extensions" => $extensions);
+    return true;
+  }
+  /**
+   * Remove a tag definition for remote files.
+   * @param string The name of the tag
+   */
+  public function removeTagDefinition($tag)
+  {
+    unset($this->definitions[strtolower($tag)]);
+  }
+  /**
+   * Get a tag definition.
+   * Returns an array with indexes "attributes" and "extensions".
+   * Each element is an array listing the values within it.
+   * @param string The name of the tag
+   * @return array
+   */
+  public function getTagDefinition($tag)
+  {
+    $tag = strtolower($tag);
+    if (isset($this->definitions[$tag])) return $this->definitions[$tag];
+    else return null;
+  }
+  /**
+   * Get the PCRE pattern for a remote file based on the tag name.
+   * @param string The name of the tag
+   * @return string
+   */
+  public function getRemoteFilePattern($tag_name)
+  {
+    $tag_name = strtolower($tag_name);
+    $pattern_format = $this->getRemoteFilePatternFormat();
+    if ($def = $this->getTagDefinition($tag_name))
+    {
+      $pattern = sprintf($pattern_format, $tag_name, implode("|", $def["attributes"]),
+        implode("|", $this->getProtocols()), implode("|", $def["extensions"]));
+      return $pattern;
+    }
+    else return null;
+  }
+  /**
+   * Get the PCRE pattern for a local file based on the tag name.
+   * @param string The name of the tag
+   * @return string
+   */
+  public function getLocalFilePattern($tag_name)
+  {
+    $tag_name = strtolower($tag_name);
+    $pattern_format = $this->getLocalFilePatternFormat();
+    if ($def = $this->getTagDefinition($tag_name))
+    {
+      $pattern = sprintf($pattern_format, $tag_name, implode("|", $def["attributes"]),
+        implode("|", $def["extensions"]));
+      return $pattern;
+    }
+    else return null;
+  }
+  /**
+   * Register a file which has been downloaded so it doesn't need to be downloaded twice.
+   * @param string The remote URL
+   * @param string The ID as attached in the message
+   * @param Swift_Message_EmbeddedFile The file object itself
+   */
+  public function registerFile($url, $cid, $file)
+  {
+    $url = strtolower($url);
+    if (!isset($this->registeredFiles[$url])) $this->registeredFiles[$url] = array("cids" => array(), "obj" => null);
+    $this->registeredFiles[$url]["cids"][] = $cid;
+    if (empty($this->registeredFiles[$url]["obj"])) $this->registeredFiles[$url]["obj"] = $file;
+  }
+  /**
+   * Turn on or off remote file embedding.
+   * @param boolean
+   */
+  public function setEmbedRemoteFiles($set)
+  {
+    $this->embedRemoteFiles = (bool)$set;
+  }
+  /**
+   * Returns true if remote files can be embedded, or false if not.
+   * @return boolean
+   */
+  public function getEmbedRemoteFiles()
+  {
+    return $this->embedRemoteFiles;
+  }
+  /**
+   * Turn on or off local file embedding.
+   * @param boolean
+   */
+  public function setEmbedLocalFiles($set)
+  {
+    $this->embedLocalFiles = (bool)$set;
+  }
+  /**
+   * Returns true if local files can be embedded, or false if not.
+   * @return boolean
+   */
+  public function getEmbedLocalFiles()
+  {
+    return $this->embedLocalFiles;
+  }
+  /**
+   * Callback method for preg_replace().
+   * Embeds files which have been found during scanning.
+   * @param array Backreferences from preg_replace()
+   * @return string The tag with it's URL replaced with a CID
+   */
+  protected function embedRemoteFile($matches)
+  {
+    $url = preg_replace("~^([^#]+)#.*\$~s", "\$1", $matches[3]);
+    $bits = parse_url($url);
+    $ext = preg_replace("~^.*?\\.([^\\.]+)\$~s", "\$1", $bits["path"]);
+    
+    $lower_url = strtolower($url);
+    if (array_key_exists($lower_url, $this->registeredFiles))
+    {
+      $registered = $this->registeredFiles[$lower_url];
+      foreach ($registered["cids"] as $cid)
+      {
+        if ($this->message->hasChild($cid))
+        {
+          return $matches[1] . $cid . $matches[4];
+        }
+      }
+      //If we get here the file is downloaded, but not embedded
+      $cid = $this->message->attach($registered["obj"]);
+      $this->registerFile($url, $cid, $registered["obj"]);
+      return $matches[1] . $cid . $matches[4];
+    }
+    $magic_quotes = get_magic_quotes_runtime();
+    set_magic_quotes_runtime(0);
+    $filedata = @file_get_contents($url);
+    set_magic_quotes_runtime($magic_quotes);
+    if (!$filedata)
+    {
+      return $matches[1] . $matches[3] . $matches[4];
+    }
+    $filename = preg_replace("~^.*/([^/]+)\$~s", "\$1", $url);
+    $att = new Swift_Message_EmbeddedFile($filedata, $filename, $this->getType($ext));
+    $id = $this->message->attach($att);
+    $this->registerFile($url, $id, $att);
+    return $matches[1] . $id . $matches[4];
+  }
+  /**
+   * Callback method for preg_replace().
+   * Embeds files which have been found during scanning.
+   * @param array Backreferences from preg_replace()
+   * @return string The tag with it's path replaced with a CID
+   */
+  protected function embedLocalFile($matches)
+  {
+    $path = realpath($matches[3]);
+    if (!$path)
+    {
+      return $matches[1] . $matches[3] . $matches[4];
+    }
+    $ext = preg_replace("~^.*?\\.([^\\.]+)\$~s", "\$1", $path);
+    
+    $lower_path = strtolower($path);
+    if (array_key_exists($lower_path, $this->registeredFiles))
+    {
+      $registered = $this->registeredFiles[$lower_path];
+      foreach ($registered["cids"] as $cid)
+      {
+        if ($this->message->hasChild($cid))
+        {
+          return $matches[1] . $cid . $matches[4];
+        }
+      }
+      //If we get here the file is downloaded, but not embedded
+      $cid = $this->message->attach($registered["obj"]);
+      $this->registerFile($path, $cid, $registered["obj"]);
+      return $matches[1] . $cid . $matches[4];
+    }
+    $filename = basename($path);
+    $att = new Swift_Message_EmbeddedFile(new Swift_File($path), $filename, $this->getType($ext));
+    $id = $this->message->attach($att);
+    $this->registerFile($path, $id, $att);
+    return $matches[1] . $id . $matches[4];
+  }
+  /**
+   * Empty out the cache of registered files.
+   */
+  public function clearCache()
+  {
+    $this->registeredFiles = null;
+    $this->registeredFiles = array();
+  }
+  /**
+   * Swift's BeforeSendListener required method.
+   * Runs just before Swift sends a message.  Here is where we do all the replacements.
+   * @param Swift_Events_SendEvent
+   */
+  public function beforeSendPerformed(Swift_Events_SendEvent $e)
+  {
+    $this->message = $e->getMessage();
+    
+    foreach ($this->message->listChildren() as $id)
+    {
+      $part = $this->message->getChild($id);
+      $body = $part->getData();
+      if (!is_string($body) || substr(strtolower($part->getContentType()), 0, 5) != "text/") continue;
+      
+      foreach ($this->definitions as $tag_name => $def)
+      {
+        if ($this->getEmbedRemoteFiles())
+        {
+          $re = $this->getRemoteFilePattern($tag_name);
+          $body = preg_replace_callback($re, array($this, "embedRemoteFile"), $body);
+        }
+        
+        if ($this->getEmbedLocalFiles())
+        {
+          $re = $this->getLocalFilePattern($tag_name);
+          $body = preg_replace_callback($re, array($this, "embedLocalFile"), $body);
+        }
+      }
+      
+      $part->setData($body);
+    }
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/MailSend.php b/system/vendor/swift/Swift/Plugin/MailSend.php
new file mode 100755
index 0000000..0a93af3
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/MailSend.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * Swift Mailer mail() sending plugin
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Connection
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Events_SendListener");
+Swift_ClassLoader::load("Swift_Events_BeforeSendListener");
+
+/**
+ * Swift mail() send plugin
+ * Sends the message using mail() when a SendEvent is fired.  Using the NativeMail connection provides stub responses to allow this to happen cleanly.
+ * @package Swift_Connection
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_MailSend implements Swift_Events_SendListener, Swift_Events_BeforeSendListener
+{
+  /**
+   * The operating system of the server
+   * @var string
+   */
+  protected $OS = null;
+  /**
+   * The return path in use here
+   * @var string
+   */
+  protected $returnPath = null;
+  /**
+   * The line ending before we intrusively change it
+   * @var string
+   */
+  protected $oldLE = "\r\n";
+  /**
+   * 5th parameter in mail().
+   * @var string
+   */
+  protected $additionalParams;
+  
+  /**
+   * Constructor.
+   * @param string 5th mail() function parameter as a sprintf() formatted string where %s is the sender.
+   */
+  public function __construct($params="-oi -f %s")
+  {
+    $this->setAdditionalParams($params);
+    $this->setOS(PHP_OS);
+  }
+  /**
+   * Set the 5th mail() function parameter as a sprintf() formatted string where %s is the sender.
+   * @param string
+   */
+  public function setAdditionalParams($params)
+  {
+    $this->additionalParams = $params;
+  }
+  /**
+   * Get the 5th mail() function parameter as a sprintf() string.
+   * @return string
+   */
+  public function getAdditionalParams()
+  {
+    return $this->additionalParams;
+  }
+  /**
+   * Set the operating system string (changes behaviour with LE)
+   * @param string The operating system
+   */
+  public function setOS($os)
+  {
+    $this->OS = $os;
+  }
+  /**
+   * Get the operating system string
+   * @return string
+   */
+  public function getOS()
+  {
+    return $this->OS;
+  }
+  /**
+   * Check if this is windows or not
+   * @return boolean
+   */
+  public function isWindows()
+  {
+    return (substr($this->getOS(), 0, 3) == "WIN");
+  }
+  /**
+   * Swift's BeforeSendEvent listener.
+   * Invoked just before Swift sends a message
+   * @param Swift_Events_SendEvent The event information
+   */
+  public function beforeSendPerformed(Swift_Events_SendEvent $e)
+  {
+    $message = $e->getMessage();
+    $message->uncacheAll();
+    $this->oldLE = $message->getLE();
+    if (!$this->isWindows() && $this->oldLE != "\n") $message->setLE("\n");
+  }
+  /**
+   * Swift's SendEvent listener.
+   * Invoked when Swift sends a message
+   * @param Swift_Events_SendEvent The event information
+   * @throws Swift_ConnectionException If mail() returns false
+   */
+  public function sendPerformed(Swift_Events_SendEvent $e)
+  {
+    $message = $e->getMessage();
+    $recipients = $e->getRecipients();
+    
+    $to = array();
+    foreach ($recipients->getTo() as $addr)
+    {
+      if ($this->isWindows()) $to[] = substr($addr->build(true), 1, -1);
+      else $to[] = $addr->build();
+    }
+    $to = implode(", ", $to);
+    
+    $bcc_orig = $message->headers->has("Bcc") ? $message->headers->get("Bcc") : null;
+    $subject_orig = $message->headers->has("Subject") ? $message->headers->get("Subject") : null;
+    $to_orig = $message->headers->has("To") ? $message->headers->get("To") : null;
+    
+    $bcc = array();
+    foreach ($recipients->getBcc() as $addr) $bcc[] = $addr->build();
+    if (!empty($bcc)) $message->headers->set("Bcc", $bcc);
+    $bcc = null;
+    
+    $body_data = $message->buildData();
+    $message_body = $body_data->readFull();
+    
+    // BH: Assume all subjects are UTF-8 and encode for it. the getEncode function was returning bad encoding
+
+    //$subject_enc = $message->headers->has("Subject") ? $message->headers->getEncoded("Subject") : "";
+    $subject_enc = $message->headers->has("Subject") ? '=?UTF-8?B?'.base64_encode($message->headers->get("Subject")).'?=' : "";
+    
+    $message->headers->set("To", null);
+    $message->headers->set("Subject", null);
+    
+    $sender = $e->getSender();
+    $this->returnPath = $sender->build();
+    if ($message->headers->has("Return-Path")) $this->returnPath = $message->headers->get("Return-Path");
+    if (preg_match("~<([^>]+)>[^>]*\$~", $this->returnPath, $matches)) $this->returnPath = $matches[1];
+    
+    $this->doMail($to, $subject_enc, $message_body, $message->headers, sprintf($this->getAdditionalParams(), $this->returnPath));
+    $message->setLE($this->oldLE);
+    $message->headers->set("To", $to_orig);
+    $message->headers->set("Subject", $subject_orig);
+    $message->headers->set("Bcc", $bcc_orig);
+  }
+  
+  public function doMail($to, $subject, $message, $headers, $params)
+  {
+    $original_from = @ini_get("sendmail_from");
+    @ini_set("sendmail_from", $this->returnPath);
+    
+    $headers = $headers->build();
+    
+    if (!ini_get("safe_mode")) $success = mail($to, $subject, $message, $headers, $params);
+    else $success = mail($to, $subject, $message, $headers);
+    
+    if (!$success)
+    {
+      @ini_set("sendmail_from", $original_from);
+      throw new Swift_ConnectionException("Sending failed using mail() as PHP's default mail() function returned boolean FALSE.");
+    }
+    @ini_set("sendmail_from", $original_from);
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/Throttler.php b/system/vendor/swift/Swift/Plugin/Throttler.php
new file mode 100755
index 0000000..64d2338
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/Throttler.php
@@ -0,0 +1,168 @@
+<?php
+
+/**
+ * Swift Mailer Throttling Plugin.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Plugin_BandwidthMonitor");
+Swift_ClassLoader::load("Swift_Events_SendListener");
+
+/**
+ * Throttler plugin for Swift Mailer.
+ * Restricts the speed at which Swift will operate.
+ * @package Swift_Plugin
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_Throttler extends Swift_Plugin_BandwidthMonitor implements Swift_Events_SendListener
+{
+  /**
+   * The rate in byte-per-minute
+   * @var int
+   */
+  protected $bpm = null;
+  /**
+   * The rate as emails-per-minute
+   * @var int
+   */
+  protected $epm = null;
+  /**
+   * The number of emails sent so far
+   * @var int
+   */
+  protected $sent = 0;
+  /**
+   * The time at the start of overall execution
+   * @var int
+   */
+  protected $time = null;
+  
+  /**
+   * Part of the interface which is notified after a command is sent.
+   * @param Swift_Events_CommandEvent
+   */
+  public function commandSent(Swift_Events_CommandEvent $e)
+  {
+    parent::commandSent($e);
+    if (null === $rate = $this->getBytesPerMinute()) return;
+    
+    $duration = $this->getTimeLapse();
+    $bytes_sent = $this->getBytesOut();
+    $bytes_per_sec = $rate / 60;
+    $seconds_allowed_so_far = ceil($bytes_sent / $bytes_per_sec);
+    $overrun = $seconds_allowed_so_far - $duration;
+    if ($overrun > 0)
+    {
+      $this->wait($overrun);
+    }
+  }
+  /**
+   * Part of the interface which is notified when a message has been sent.
+   * @param Swift_Events_SendEvent
+   */
+  public function sendPerformed(Swift_Events_SendEvent $e)
+  {
+    $this->setSent($this->getSent() + 1);
+    if (null === $rate = $this->getEmailsPerMinute()) return;
+    
+    $duration = $this->getTimeLapse();
+    $emails_sent = $this->getSent();
+    $emails_per_sec = $rate / 60;
+    $seconds_allowed_so_far = ceil($emails_sent / $emails_per_sec);
+    $overrun = $seconds_allowed_so_far - $duration;
+    if ($overrun > 0)
+    {
+      $this->wait($overrun);
+    }
+  }
+  /**
+   * Wait for $seconds before continuing
+   * @param int The number of seconds to wait
+   */
+  public function wait($secs)
+  {
+    sleep($secs);
+  }
+  /**
+   * Set the time if it's not already set
+   */
+  protected function setTime()
+  {
+    if ($this->time === null) $this->time = time();
+  }
+  /**
+   * Get the time taken thus far (full seconds).
+   * @return int
+   */
+  public function getTimeLapse()
+  {
+    $this->setTime();
+    return time() - $this->time;
+  }
+  /**
+   * Set the number of emails sent
+   * @param int Emails sent so far
+   */
+  public function setSent($num)
+  {
+    $this->sent = (int)$num;
+  }
+  /**
+   * Get the number of emails sent
+   * @return int
+   */
+  public function getSent()
+  {
+    return $this->sent;
+  }
+  /**
+   * Set the throttling rate as bytes per minute
+   * @param int The maximum number of outgoing bytes in 60 seconds.
+   */
+  public function setBytesPerMinute($bpm)
+  {
+    if ($bpm === null)
+    {
+      $this->bpm = null;
+      return;
+    }
+    $this->setEmailsPerMinute(null);
+    $this->bpm = abs((int)$bpm);
+  }
+  /**
+   * Get the number of bytes allowed per minute.
+   * Reurns NULL if not used.
+   * @return int
+   */
+  public function getBytesPerMinute()
+  {
+    return $this->bpm;
+  }
+  /**
+   * Set the rate as emails-per-minute.
+   * @param int The max number of emails to send in a minute.
+   */
+  public function setEmailsPerMinute($epm)
+  {
+    if ($epm === null)
+    {
+      $this->epm = null;
+      return;
+    }
+    $this->setBytesPerMinute(null);
+    $this->epm = abs((int)$epm);
+  }
+  /**
+   * Get the rate as number of emails per minute.
+   * Returns null if not used.
+   * @return int
+   */
+  public function getEmailsPerMinute()
+  {
+    return $this->epm;
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/VerboseSending.php b/system/vendor/swift/Swift/Plugin/VerboseSending.php
new file mode 100755
index 0000000..1bc85ed
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/VerboseSending.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Swift Mailer Verbose Sending Plugin.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @subpackage VerboseSending
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Events_SendListener");
+Swift_ClassLoader::load("Swift_Plugin_VerboseSending_DefaultView");
+
+/**
+ * Verbose Sending plugin for Swift Mailer.
+ * Displays "pass" or "fail" messages in realtime as the messages are sent.
+ * @package Swift_Plugin
+ * @subpackage VerboseSending
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_VerboseSending implements Swift_Events_SendListener
+{
+  /**
+   * The view layer which displays the results.
+   * @var Swift_Plugin_VerboseSending_AbstractView
+   */
+  protected $view;
+  
+  /**
+   * Ctor.
+   * @param Swift_Plugin_VerboseSending_AbstractView The view object to display the result
+   */
+  public function __construct(Swift_Plugin_VerboseSending_AbstractView $view)
+  {
+    $this->setView($view);
+  }
+  /**
+   * Part of the interface which is notified when a message has been sent.
+   * @param Swift_Events_SendEvent
+   */
+  public function sendPerformed(Swift_Events_SendEvent $e)
+  {
+    $recipients = $e->getRecipients();
+    $failed = $e->getFailedRecipients();
+    $it = $recipients->getIterator("to");
+    while ($it->hasNext())
+    {
+      $it->next();
+      $address = $it->getValue();
+      $pass = !in_array($address->getAddress(), $failed);
+      $this->getView()->paintResult($address->getAddress(), $pass);
+    }
+    $it = $recipients->getIterator("cc");
+    while ($it->hasNext())
+    {
+      $it->next();
+      $address = $it->getValue();
+      $pass = !in_array($address->getAddress(), $failed);
+      $this->getView()->paintResult($address->getAddress(), $pass);
+    }
+    $it = $recipients->getIterator("bcc");
+    while ($it->hasNext())
+    {
+      $it->next();
+      $address = $it->getValue();
+      $pass = !in_array($address->getAddress(), $failed);
+      $this->getView()->paintResult($address->getAddress(), $pass);
+    }
+  }
+  /**
+   * Set the View component to display results.
+   * @param Swift_Plugin_VerboseSending_AbstractView The view object to display the result
+   */
+  public function setView(Swift_Plugin_VerboseSending_AbstractView $view)
+  {
+    $this->view = $view;
+  }
+  /**
+   * Get the View component.
+   * @return Swift_Plugin_VerboseSending_AbstractView
+   */
+  public function getView()
+  {
+    return $this->view;
+  }
+}
diff --git a/system/vendor/swift/Swift/Plugin/VerboseSending/AbstractView.php b/system/vendor/swift/Swift/Plugin/VerboseSending/AbstractView.php
new file mode 100755
index 0000000..4b9f6a3
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/VerboseSending/AbstractView.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Swift Mailer Verbose-sending Plugin View Layer.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @subpackage VerboseSending
+ * @license GNU Lesser General Public License
+ */
+
+/**
+ * The View layer for the Verbose Sending Plugin
+ * @package Swift_Plugin
+ * @subpackage VerboseSending
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+abstract class Swift_Plugin_VerboseSending_AbstractView
+{
+  /**
+   * Paint the result of a send operation
+   * @param string The email address that was tried
+   * @param boolean True if the message was successfully sent
+   */
+  abstract public function paintResult($address, $result);
+}
diff --git a/system/vendor/swift/Swift/Plugin/VerboseSending/DefaultView.php b/system/vendor/swift/Swift/Plugin/VerboseSending/DefaultView.php
new file mode 100755
index 0000000..7394b46
--- /dev/null
+++ b/system/vendor/swift/Swift/Plugin/VerboseSending/DefaultView.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Swift Mailer Verbose-sending Plugin Default View File.
+ * Please read the LICENSE file
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift_Plugin
+ * @subpackage VerboseSending
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/../../ClassLoader.php";
+Swift_ClassLoader::load("Swift_Plugin_VerboseSending_AbstractView");
+
+/**
+ * The Default View for the Verbose Sending Plugin
+ * @package Swift_Plugin
+ * @subpackage VerboseSending
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_Plugin_VerboseSending_DefaultView extends Swift_Plugin_VerboseSending_AbstractView
+{
+  /**
+   * Number of recipients painted
+   * @var int
+   */
+  protected $count = 0;
+  
+  /**
+   * Paint the result of a send operation
+   * @param string The email address that was tried
+   * @param boolean True if the message was successfully sent
+   */
+  public function paintResult($address, $result)
+  {
+    $this->count++;
+    $color = $result ? "#51c45f" : "#d67d71";
+    $result_text = $result ? "PASS" : "FAIL";
+    ?>
+    <div style="color: #ffffff; margin: 2px; padding: 3px; 
+      font-weight: bold; background: <?php echo $color; ?>;">
+      <span style="float: right; text-decoration: underline;">
+        <?php echo $result_text; ?></span> 
+      Recipient (<?php echo $this->count; ?>): 
+      <?php echo $address; ?>
+    </div>
+    <?php
+    flush();
+  }
+}
diff --git a/system/vendor/swift/Swift/RecipientList.php b/system/vendor/swift/Swift/RecipientList.php
new file mode 100755
index 0000000..aba6141
--- /dev/null
+++ b/system/vendor/swift/Swift/RecipientList.php
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * Swift Mailer Recipient List Container
+ * Please read the LICENSE file
+ * @copyright Chris Corbyn <chris at w3style.co.uk>
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ * @package Swift
+ * @license GNU Lesser General Public License
+ */
+
+require_once dirname(__FILE__) . "/ClassLoader.php";
+Swift_ClassLoader::load("Swift_Address");
+Swift_ClassLoader::load("Swift_Iterator_Array");
+
+/**
+ * Swift's Recipient List container.  Contains To, Cc, Bcc
+ * @package Swift
+ * @author Chris Corbyn <chris at w3style.co.uk>
+ */
+class Swift_RecipientList extends Swift_AddressContainer
+{
+  /**
+   * The recipients in the To: header
+   * @var array
+   */
+  protected $to = array();
+  /**
+   * The recipients in the Cc: header
+   * @var array
+   */
+  protected $cc = array();
+  /**
+   * The recipients in the Bcc: header
+   * @var array
+   */
+  protected $bcc = array();
+  /**
+   * Iterators to use when getting lists back out.
+   * If any iterators are present here, their relevant "addXX()" methods will be useless.
+   * As per the last note, any iterators need to be pre-configured before Swift::send() is called.
+   * @var array,Swift_Iterator
+   */
+  protected $iterators = array("to" => null, "cc" => null, "bcc" => null);
+  
+  /**
+   * Add a recipient.
+   * @param string The address
+   * @param string The name
+   * @param string The field (to, cc or bcc)
+   */
+  public function add($address, $name="", $where="to")
+  {
+    if ($address instanceof Swift_Address)
+    {
+      $address_str = trim(strtolower($address->getAddress()));
+    }
+    
+    elseif (is_array($address))
+    {
+      foreach ($address as $a) $this->add($a, $name, $where);
+      return;
+    }
+    else
+    {
+      $address_str = (string) $address;
+      $address_str = trim(strtolower($address_str));
+      $address = new Swift_Address($address_str, $name);
+    }
+    
+    if (in_array($where, array("to", "cc", "bcc")))
+    {
+      $container =& $this->$where;
+      $container[$address_str] = $address;
+    }
+  }
+  /**
+   * Remove a recipient.
+   * @param string The address
+   * @param string The field (to, cc or bcc)
+   */
+  public function remove($address, $where="to")
+  {
+    if ($address instanceof Swift_Address)
+    {
+      $key = trim(strtolower($address->getAddress()));
+    }
+    else $key = trim(strtolower((string) $address));
+    
+    if (in_array($where, array("to", "cc", "bcc")))
+    {
+      if (array_key_exists($key, $this->$where)) unset($this->{$where}[$key]);
+    }
+  }
+  /**
+   * Get an iterator object for all the recipients in the given field.
+   * @param string The field name (to, cc or bcc)
+   * @return Swift_Iterator
+   */
+  public function getIterator($where)
+  {
+    if (!empty($this->iterators[$where]))
+    {
+      return $this->iterators[$where];
+    }
+    elseif (in_array($where, array("to", "cc", "bcc")))
+    {
+      $it = new Swift_Iterator_Array($this->$where);
+      return $it;
+    }
+  }
+  /**
+   * Override the loading of the default iterator (Swift_ArrayIterator) and use the one given here.
+   * @param Swift_Iterator The iterator to use.  It must be populated already.
+   */
+  public function setIterator(Swift_Iterator $it, $where)
+  {
+    if (in_array($where, array("to", "cc", "bcc")))
+    {
+      $this->iterators[$where] = $it;
+    }
+  }
+  /**
+   * Add a To: recipient
+   * @param mixed The address to add.  Can be a string or Swift_Address
+   * @param string The personal name, optional
+   */
+  public function addTo($address, $name=null)
+  {
+    $this->add($address, $name, "to");
+  }
+  /**
+   * Get an array of addresses in the To: field
+   * The array contains Swift_Address objects
+   * @return array
+   */
+  public function getTo()
+  {
+    return $this->to;
+  }
+  /**
+   * Remove a To: recipient from the list
+   * @param mixed The address to remove.  Can be Swift_Address or a string
+   */
+  public function removeTo($address)
+  {
+    $this->remove($address, "to");
+  }
+  /**
+   * Empty all To: addresses
+   */
+  public function flushTo()
+  {
+    $this->to = null;
+    $this->to = array();
+  }
+  /**
+   * Add a Cc: recipient
+   * @param mixed The address to add.  Can be a string or Swift_Address
+   * @param string The personal name, optional
+   */
+  public function addCc($address, $name=null)
+  {
+    $this->add($address, $name, "cc");
+  }
+  /**
+   * Get an array of addresses in the Cc: field
+   * The array contains Swift_Address objects
+   * @return array
+   */
+  public function getCc()
+  {
+    return $this->cc;
+  }
+  /**
+   * Remove a Cc: recipient from the list
+   * @param mixed The address to remove.  Can be Swift_Address or a string
+   */
+  public function removeCc($address)
+  {
+    $this->remove($address, "cc");
+  }
+  /**
+   * Empty all Cc: addresses
+   */
+  public function flushCc()
+  {
+    $this->cc = null;
+    $this->cc = array();
+  }
+  /**
+   * Add a Bcc: recipient
+   * @param mixed The address to add.  Can be a string or Swift_Address
+   * @param string The personal name, optional
+   */
+  public function addBcc($address, $name=null)
+  {
+    $this->add($address, $name, "bcc");
+  }
+  /**
+   * Get an array of addresses in the Bcc: field
+   * The array contains Swift_Address objects
+   * @return array
+   */
+  public function getBcc()
+  {
+    return $this->bcc;
+  }
+  /**
+   * Remove a Bcc: recipient from the list
+   * @param mixed The address to remove.  Can be Swift_Address or a string
+   */
+  public function removeBcc($address)
+  {
+    $this->remove($address, "bcc");
+  }
+  /**
+   * Empty all Bcc: addresses
+   */
+  public function flushBcc()
+  {
+    $this->bcc = null;
+    $this->bcc = array();
+  }
+  /**
+   * Empty the entire list
+   */
+  public function flush()
+  {
+    $this->flushTo();
+    $this->flushCc();
+    $this->flushBcc();
+  }
+}
diff --git a/system/views/kohana/template.php b/system/views/kohana/template.php
new file mode 100755
index 0000000..b090fd8
--- /dev/null
+++ b/system/views/kohana/template.php
@@ -0,0 +1,36 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+	<title><?php echo html::specialchars($title) ?></title>
+
+	<style type="text/css">
+	html { background: #83c018 url(<?php echo url::base(FALSE) ?>kohana.png) 50% 0 no-repeat; }
+	body { width: 52em; margin: 200px auto 2em; font-size: 76%; font-family: Arial, sans-serif; color: #273907; line-height: 1.5; text-align: center; }
+	h1 { font-size: 3em; font-weight: normal; text-transform: uppercase; color: #fff; }
+	a { color: inherit; }
+	code { font-size: 1.3em; }
+	ul { list-style: none; padding: 2em 0; }
+	ul li { display: inline; padding-right: 1em; text-transform: uppercase; }
+	ul li a { padding: 0.5em 1em; background: #69ad0f; border: 1px solid #569f09; color: #fff; text-decoration: none; }
+	ul li a:hover { background: #569f09; }
+	.box { padding: 2em; background: #98cc2b; border: 1px solid #569f09; }
+	.copyright { font-size: 0.9em; text-transform: uppercase; color: #557d10; }
+	</style>
+
+</head>
+<body>
+
+	<h1><?php echo html::specialchars($title) ?></h1>
+	<?php echo $content ?>
+
+	<p class="copyright">
+		Rendered in {execution_time} seconds, using {memory_usage} of memory<br />
+		Copyright ©2007–2008 Kohana Team
+	</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/system/views/kohana_calendar.php b/system/views/kohana_calendar.php
new file mode 100755
index 0000000..39545e2
--- /dev/null
+++ b/system/views/kohana_calendar.php
@@ -0,0 +1,52 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+
+// Get the day names
+$days = Calendar::days(2);
+
+// Previous and next month timestamps
+$next = mktime(0, 0, 0, $month + 1, 1, $year);
+$prev = mktime(0, 0, 0, $month - 1, 1, $year);
+
+// Import the GET query array locally and remove the day
+$qs = $_GET;
+unset($qs['day']);
+
+// Previous and next month query URIs
+$prev = Router::$current_uri.'?'.http_build_query(array_merge($qs, array('month' => date('n', $prev), 'year' => date('Y', $prev))));
+$next = Router::$current_uri.'?'.http_build_query(array_merge($qs, array('month' => date('n', $next), 'year' => date('Y', $next))));
+
+?>
+<table class="calendar">
+<tr class="controls">
+<td class="prev"><?php echo html::anchor($prev, '«') ?></td>
+<td class="title" colspan="5"><?php echo strftime('%B %Y', mktime(0, 0, 0, $month, 1, $year)) ?></td>
+<td class="next"><?php echo html::anchor($next, '»') ?></td>
+</tr>
+<tr>
+<?php foreach ($days as $day): ?>
+<th><?php echo $day ?></th>
+<?php endforeach ?>
+</tr>
+<?php foreach ($weeks as $week): ?>
+<tr>
+<?php foreach ($week as $day):
+
+list ($number, $current, $data) = $day;
+
+if (is_array($data))
+{
+	$classes = $data['classes'];
+	$output = empty($data['output']) ? '' : '<ul class="output"><li>'.implode('</li><li>', $data['output']).'</li></ul>';
+}
+else
+{
+	$classes = array();
+	$output = '';
+}
+
+?>
+<td class="<?php echo implode(' ', $classes) ?>"><span class="day"><?php echo $day[0] ?></span><?php echo $output ?></td>
+<?php endforeach ?>
+</tr>
+<?php endforeach ?>
+</table>
diff --git a/system/views/kohana_error_disabled.php b/system/views/kohana_error_disabled.php
new file mode 100755
index 0000000..ed44b39
--- /dev/null
+++ b/system/views/kohana_error_disabled.php
@@ -0,0 +1,17 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title><?php echo $error ?></title>
+</head>
+<body>
+<style type="text/css">
+<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?>
+</style>
+<div id="framework_error" style="width:24em;margin:50px auto;">
+<h3><?php echo html::specialchars($error) ?></h3>
+<p style="text-align:center"><?php echo $message ?></p>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/system/views/kohana_error_page.php b/system/views/kohana_error_page.php
new file mode 100755
index 0000000..ccaf733
--- /dev/null
+++ b/system/views/kohana_error_page.php
@@ -0,0 +1,28 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title><?php echo $error ?></title>
+<base href="http://php.net/" />
+</head>
+<body>
+<style type="text/css">
+<?php include Kohana::find_file('views', 'kohana_errors', FALSE, 'css') ?>
+</style>
+<div id="framework_error" style="width:42em;margin:20px auto;">
+<h3><?php echo html::specialchars($error) ?></h3>
+<p><?php echo html::specialchars($description) ?></p>
+<?php if ( ! empty($line) AND ! empty($file)): ?>
+<p><?php //echo Kohana::lang('core.error_file_line', $file, $line) ?></p>
+<p><?php echo sprintf('<tt>%s <strong>[%s]:</strong></tt>', $file, $line); ?></p>
+<?php endif ?>
+<p><code class="block"><?php echo $message ?></code></p>
+<?php if ( ! empty($trace)): ?>
+<h3><?php echo Kohana::lang('core.stack_trace') ?></h3>
+<?php echo $trace ?>
+<?php endif ?>
+<p class="stats"><?php echo Kohana::lang('core.stats_footer') ?></p>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/system/views/kohana_errors.css b/system/views/kohana_errors.css
new file mode 100755
index 0000000..1341f57
--- /dev/null
+++ b/system/views/kohana_errors.css
@@ -0,0 +1,21 @@
+div#framework_error { background:#fff; border:solid 1px #ccc; font-family:sans-serif; color:#111; font-size:14px; line-height:130%; }
+div#framework_error h3 { color:#fff; font-size:16px; padding:8px 6px; margin:0 0 8px; background:#f15a00; text-align:center; }
+div#framework_error a { color:#228; text-decoration:none; }
+div#framework_error a:hover { text-decoration:underline; }
+div#framework_error strong { color:#900; }
+div#framework_error p { margin:0; padding:4px 6px 10px; }
+div#framework_error tt,
+div#framework_error pre,
+div#framework_error code { font-family:monospace; padding:2px 4px; font-size:12px; color:#333;
+	white-space:pre-wrap; /* CSS 2.1 */
+	white-space:-moz-pre-wrap; /* For Mozilla */
+	word-wrap:break-word; /* For IE5.5+ */
+}
+div#framework_error tt { font-style:italic; }
+div#framework_error tt:before { content:">"; color:#aaa; }
+div#framework_error code tt:before { content:""; }
+div#framework_error pre,
+div#framework_error code { background:#eaeee5; border:solid 0 #D6D8D1; border-width:0 1px 1px 0; }
+div#framework_error .block { display:block; text-align:left; }
+div#framework_error .stats { padding:4px; background: #eee; border-top:solid 1px #ccc; text-align:center; font-size:10px; color:#888; }
+div#framework_error .backtrace { margin:0; padding:0 6px; list-style:none; line-height:12px; }
\ No newline at end of file
diff --git a/system/views/kohana_profiler.php b/system/views/kohana_profiler.php
new file mode 100755
index 0000000..da77a66
--- /dev/null
+++ b/system/views/kohana_profiler.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<style type="text/css">
+#kohana-profiler
+{
+	font-family: Monaco, 'Courier New';
+	background-color: #F8FFF8;
+	margin-top: 20px;
+	clear: both;
+	padding: 10px 10px 0;
+	border: 1px solid #E5EFF8;
+	text-align: left;
+}
+#kohana-profiler pre
+{
+	margin: 0;
+	font: inherit;
+}
+#kohana-profiler .kp-meta
+{
+	margin: 0 0 10px;
+	padding: 4px;
+	background: #FFF;
+	border: 1px solid #E5EFF8;
+	color: #A6B0B8;
+	text-align: center;
+}
+<?php echo $styles ?>
+</style>
+<div id="kohana-profiler">
+<?php
+foreach ($profiles as $profile)
+{
+	echo $profile->render();
+}
+?>
+<p class="kp-meta">Profiler executed in <?php echo number_format($execution_time, 3) ?>s</p>
+</div>
\ No newline at end of file
diff --git a/system/views/kohana_profiler_table.css b/system/views/kohana_profiler_table.css
new file mode 100755
index 0000000..41a1c9a
--- /dev/null
+++ b/system/views/kohana_profiler_table.css
@@ -0,0 +1,53 @@
+#kohana-profiler .kp-table
+{
+	font-size: 1.0em;
+	color: #4D6171;
+	width: 100%;
+	border-collapse: collapse;
+	border-top: 1px solid #E5EFF8;
+	border-right: 1px solid #E5EFF8;
+	border-left: 1px solid #E5EFF8;
+	margin-bottom: 10px;
+}
+#kohana-profiler .kp-table td
+{
+	background-color: #FFFFFF;
+	border-bottom: 1px solid #E5EFF8;
+	padding: 3px;
+	vertical-align: top;
+}
+#kohana-profiler .kp-table .kp-title td
+{
+	font-weight: bold;
+	background-color: inherit;
+}
+#kohana-profiler .kp-table .kp-altrow td
+{
+	background-color: #F7FBFF;
+}
+#kohana-profiler .kp-table .kp-totalrow td
+{
+	background-color: #FAFAFA;
+	border-top: 1px solid #D2DCE5;
+	font-weight: bold;
+}
+#kohana-profiler .kp-table .kp-column
+{
+	width: 100px;
+	border-left: 1px solid #E5EFF8;
+	text-align: center;
+}
+#kohana-profiler .kp-table .kp-data, #kohana-profiler .kp-table .kp-name
+{
+	background-color: #FAFAFB;
+	vertical-align: top;
+}
+#kohana-profiler .kp-table .kp-name
+{
+	width: 200px;
+	border-right: 1px solid #E5EFF8;
+}
+#kohana-profiler .kp-table .kp-altrow .kp-data, #kohana-profiler .kp-table .kp-altrow .kp-name
+{
+	background-color: #F6F8FB;
+}
\ No newline at end of file
diff --git a/system/views/kohana_profiler_table.php b/system/views/kohana_profiler_table.php
new file mode 100755
index 0000000..b6b4653
--- /dev/null
+++ b/system/views/kohana_profiler_table.php
@@ -0,0 +1,25 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<table class="kp-table">
+<?php
+foreach ($rows as $row):
+
+$class = empty($row['class']) ? '' : ' class="'.$row['class'].'"';
+$style = empty($row['style']) ? '' : ' style="'.$row['style'].'"';
+?>
+	<tr<?php echo $class; echo $style; ?>>
+		<?php
+		foreach ($columns as $index => $column)
+		{
+			$class = empty($column['class']) ? '' : ' class="'.$column['class'].'"';
+			$style = empty($column['style']) ? '' : ' style="'.$column['style'].'"';
+			$value = $row['data'][$index];
+			$value = (is_array($value) OR is_object($value)) ? '<pre>'.html::specialchars(print_r($value, TRUE)).'</pre>' : html::specialchars($value);
+			echo '<td', $style, $class, '>', $value, '</td>';
+		}
+		?>
+	</tr>
+<?php
+
+endforeach;
+?>
+</table>
\ No newline at end of file
diff --git a/system/views/pagination/classic.php b/system/views/pagination/classic.php
new file mode 100755
index 0000000..5272c2c
--- /dev/null
+++ b/system/views/pagination/classic.php
@@ -0,0 +1,39 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Classic pagination style
+ * 
+ * @preview  ‹ First  < 1 2 3 >  Last ›
+ */
+?>
+
+<p class="pagination">
+
+	<?php if ($first_page): ?>
+		<a href="<?php echo str_replace('{page}', 1, $url) ?>">‹ <?php echo Kohana::lang('pagination.first') ?></a>
+	<?php endif ?>
+
+	<?php if ($previous_page): ?>
+		<a href="<?php echo str_replace('{page}', $previous_page, $url) ?>"><</a>
+	<?php endif ?>
+
+
+	<?php for ($i = 1; $i <= $total_pages; $i++): ?>
+
+		<?php if ($i == $current_page): ?>
+			<strong><?php echo $i ?></strong>
+		<?php else: ?>
+			<a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+		<?php endif ?>
+
+	<?php endfor ?>
+
+
+	<?php if ($next_page): ?>
+		<a href="<?php echo str_replace('{page}', $next_page, $url) ?>">></a>
+	<?php endif ?>
+
+	<?php if ($last_page): ?>
+		<a href="<?php echo str_replace('{page}', $last_page, $url) ?>"><?php echo Kohana::lang('pagination.last') ?> ›</a>
+	<?php endif ?>
+
+</p>
\ No newline at end of file
diff --git a/system/views/pagination/digg.php b/system/views/pagination/digg.php
new file mode 100755
index 0000000..0e065a6
--- /dev/null
+++ b/system/views/pagination/digg.php
@@ -0,0 +1,83 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Digg pagination style
+ * 
+ * @preview  « Previous  1 2 … 5 6 7 8 9 10 11 12 13 14 … 25 26  Next »
+ */
+?>
+
+<p class="pagination">
+
+	<?php if ($previous_page): ?>
+		<a href="<?php echo str_replace('{page}', $previous_page, $url) ?>">« <?php echo Kohana::lang('pagination.previous') ?></a>
+	<?php else: ?>
+		« <?php echo Kohana::lang('pagination.previous') ?>
+	<?php endif ?>
+
+
+	<?php if ($total_pages < 13): /* « Previous  1 2 3 4 5 6 7 8 9 10 11 12  Next » */ ?>
+
+		<?php for ($i = 1; $i <= $total_pages; $i++): ?>
+			<?php if ($i == $current_page): ?>
+				<strong><?php echo $i ?></strong>
+			<?php else: ?>
+				<a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+			<?php endif ?>
+		<?php endfor ?>
+
+	<?php elseif ($current_page < 9): /* « Previous  1 2 3 4 5 6 7 8 9 10 … 25 26  Next » */ ?>
+
+		<?php for ($i = 1; $i <= 10; $i++): ?>
+			<?php if ($i == $current_page): ?>
+				<strong><?php echo $i ?></strong>
+			<?php else: ?>
+				<a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+			<?php endif ?>
+		<?php endfor ?>
+
+		…
+		<a href="<?php echo str_replace('{page}', $total_pages - 1, $url) ?>"><?php echo $total_pages - 1 ?></a>
+		<a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a>
+
+	<?php elseif ($current_page > $total_pages - 8): /* « Previous  1 2 … 17 18 19 20 21 22 23 24 25 26  Next » */ ?>
+
+		<a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a>
+		<a href="<?php echo str_replace('{page}', 2, $url) ?>">2</a>
+		…
+
+		<?php for ($i = $total_pages - 9; $i <= $total_pages; $i++): ?>
+			<?php if ($i == $current_page): ?>
+				<strong><?php echo $i ?></strong>
+			<?php else: ?>
+				<a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+			<?php endif ?>
+		<?php endfor ?>
+
+	<?php else: /* « Previous  1 2 … 5 6 7 8 9 10 11 12 13 14 … 25 26  Next » */ ?>
+
+		<a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a>
+		<a href="<?php echo str_replace('{page}', 2, $url) ?>">2</a>
+		…
+
+		<?php for ($i = $current_page - 5; $i <= $current_page + 5; $i++): ?>
+			<?php if ($i == $current_page): ?>
+				<strong><?php echo $i ?></strong>
+			<?php else: ?>
+				<a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+			<?php endif ?>
+		<?php endfor ?>
+
+		…
+		<a href="<?php echo str_replace('{page}', $total_pages - 1, $url) ?>"><?php echo $total_pages - 1 ?></a>
+		<a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a>
+
+	<?php endif ?>
+
+
+	<?php if ($next_page): ?>
+		<a href="<?php echo str_replace('{page}', $next_page, $url) ?>"><?php echo Kohana::lang('pagination.next') ?> »</a>
+	<?php else: ?>
+		<?php echo Kohana::lang('pagination.next') ?> »
+	<?php endif ?>
+
+</p>
\ No newline at end of file
diff --git a/system/views/pagination/extended.php b/system/views/pagination/extended.php
new file mode 100755
index 0000000..2427a4e
--- /dev/null
+++ b/system/views/pagination/extended.php
@@ -0,0 +1,27 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Extended pagination style
+ * 
+ * @preview  « Previous | Page 2 of 11 | Showing items 6-10 of 52 | Next »
+ */
+?>
+
+<p class="pagination">
+
+	<?php if ($previous_page): ?>
+		<a href="<?php echo str_replace('{page}', $previous_page, $url) ?>">« <?php echo Kohana::lang('pagination.previous') ?></a>
+	<?php else: ?>
+		« <?php echo Kohana::lang('pagination.previous') ?>
+	<?php endif ?>
+
+	| <?php echo Kohana::lang('pagination.page') ?> <?php echo $current_page ?> <?php echo Kohana::lang('pagination.of') ?> <?php echo $total_pages ?>
+
+	| <?php echo Kohana::lang('pagination.items') ?> <?php echo $current_first_item ?>–<?php echo $current_last_item ?> <?php echo Kohana::lang('pagination.of') ?> <?php echo $total_items ?>
+
+	| <?php if ($next_page): ?>
+		<a href="<?php echo str_replace('{page}', $next_page, $url) ?>"><?php echo Kohana::lang('pagination.next') ?> »</a>
+	<?php else: ?>
+		<?php echo Kohana::lang('pagination.next') ?> »
+	<?php endif ?>
+
+</p>
\ No newline at end of file
diff --git a/system/views/pagination/punbb.php b/system/views/pagination/punbb.php
new file mode 100755
index 0000000..6599831
--- /dev/null
+++ b/system/views/pagination/punbb.php
@@ -0,0 +1,37 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * PunBB pagination style
+ * 
+ * @preview  Pages: 1 … 4 5 6 7 8 … 15
+ */
+?>
+
+<p class="pagination">
+
+	<?php echo Kohana::lang('pagination.pages') ?>:
+
+	<?php if ($current_page > 3): ?>
+		<a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a>
+		<?php if ($current_page != 4) echo '…' ?>
+	<?php endif ?>
+
+
+	<?php for ($i = $current_page - 2, $stop = $current_page + 3; $i < $stop; ++$i): ?>
+
+		<?php if ($i < 1 OR $i > $total_pages) continue ?>
+
+		<?php if ($current_page == $i): ?>
+			<strong><?php echo $i ?></strong>
+		<?php else: ?>
+			<a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a>
+		<?php endif ?>
+
+	<?php endfor ?>
+
+
+	<?php if ($current_page <= $total_pages - 3): ?>
+		<?php if ($current_page != $total_pages - 3) echo '…' ?>
+		<a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a>
+	<?php endif ?>
+
+</p>
\ No newline at end of file
diff --git a/tests/phpunit/.gitignore b/tests/phpunit/.gitignore
new file mode 100755
index 0000000..ca541e3
--- /dev/null
+++ b/tests/phpunit/.gitignore
@@ -0,0 +1,8 @@
+.*swp
+.htaccess
+*~
+application/logs/*
+application/cache/*
+media/uploads/*
+application/config/config.php
+application/config/database.php
diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php
new file mode 100755
index 0000000..ef1f7dc
--- /dev/null
+++ b/tests/phpunit/bootstrap.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * PHPUnit bootstrap/test helper
+ * This file is a variation of the index.php file found in the Kohana 2.3.x DOCROOT
+ * and is used to invoke the process control file for the application
+ */
+
+/**
+ * Define the website environment status. When this flag is set to TRUE, some
+ * module demonstration controllers will result in 404 errors. For more information
+ * about this option, read the documentation about deploying Kohana.
+ *
+ * @see http://docs.kohanaphp.com/installation/deployment
+ */
+define('IN_PRODUCTION', TRUE);
+
+/**
+ * Website application directory. This directory should contain your application
+ * configuration, controllers, models, views, and other resources.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_application = 'application';
+
+/**
+ * Kohana modules directory. This directory should contain all the modules used
+ * by your application. Modules are enabled and disabled by the application
+ * configuration file.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_modules = 'modules';
+
+/**
+ * Kohana system directory. This directory should contain the core/ directory,
+ * and the resources you included in your download of Kohana.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_system = 'system';
+
+/**
+ * Themes directory.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_themes = 'themes';
+
+/**
+ * Plugin directory.
+ *
+ * This path can be absolute or relative to this file.
+ */
+$kohana_plugins = 'plugins';
+
+/**
+ * Location of the PHPUnit unit tests
+ */
+$phpunit_tests = 'tests/phpunit';
+
+/**
+ * Test to make sure that Kohana is running on PHP 5.2 or newer. Once you are
+ * sure that your environment is compatible with Kohana, you can comment this
+ * line out. When running an application on a new server, uncomment this line
+ * to check the PHP version quickly.
+ */
+version_compare(PHP_VERSION, '5.2', '<') and exit('Kohana requires PHP 5.2 or newer.');
+
+/**
+ * Set the error reporting level. Unless you have a special need, E_ALL is a
+ * good level for error reporting.
+ */
+error_reporting(E_ALL & ~E_STRICT);
+
+/**
+ * Turning off display_errors will effectively disable Kohana error display
+ * and logging. You can turn off Kohana errors in application/config/config.php
+ */
+ini_set('display_errors', TRUE);
+
+/**
+ * If you rename all of your .php files to a different extension, set the new
+ * extension here. This option can left to .php, even if this file has a
+ * different extension.
+ */
+define('EXT', '.php');
+
+//
+// DO NOT EDIT BELOW THIS LINE, UNLESS YOU FULLY UNDERSTAND THE IMPLICATIONS.
+// ----------------------------------------------------------------------------
+// $Id: index.php 3168 2008-07-21 01:34:36Z Shadowhand $
+//
+
+// Get the current directory
+$current_dir =  str_replace('\\', '/', dirname(realpath(__FILE__)));
+
+// Define the front controller name and docroot
+define('DOCROOT', substr($current_dir, 0, strlen($current_dir) - strlen($phpunit_tests)));
+define('KOHANA',  basename(__FILE__));
+
+// If the front controller is a symlink, change to the real docroot
+is_link(KOHANA) and chdir(dirname(realpath(__FILE__)));
+
+// Define application and system paths
+define('APPPATH', str_replace('\\', '/', DOCROOT.$kohana_application).'/');
+define('THEMEPATH', str_replace('\\', '/', DOCROOT.$kohana_themes).'/');
+define('PLUGINPATH', str_replace('\\', '/', DOCROOT.$kohana_plugins).'/');
+define('MODPATH', str_replace('\\', '/', DOCROOT.$kohana_modules).'/');
+define('SYSPATH', str_replace('\\', '/', DOCROOT.$kohana_system).'/');
+define('TESTS_PATH', str_replace('\\', '/', DOCROOT.$phpunit_tests).'/');
+
+// Clean up
+unset($kohana_application, $kohana_themes, $kohana_plugins, $kohana_modules, $kohana_system, $phpunit_tests);
+
+// Bootstrap the Kohana project, Ushahidi_Web in this case
+require TESTS_PATH.'testbootstrap'.EXT;
\ No newline at end of file
diff --git a/tests/phpunit/classes/helpers/Addon_Test.php b/tests/phpunit/classes/helpers/Addon_Test.php
new file mode 100644
index 0000000..597a474
--- /dev/null
+++ b/tests/phpunit/classes/helpers/Addon_Test.php
@@ -0,0 +1,65 @@
+<?php
+class Addon_Helper_Test extends PHPUnit_Framework_TestCase {
+
+	/**
+	 * Executed when this test case is initialized
+	 */
+	public function setUp()
+	{
+
+	}
+	
+	/**
+	 * Tear down operation - Executed when the test is complete
+	 */
+	public function tearDown()
+	{
+	}
+	
+	/**
+	 * Tests addon::get_addons()
+	 *
+	 * @test
+	 */
+	public function test_get_addons()
+	{
+		$themes = addon::get_addons('theme');
+		$plugins = addon::get_addons('plugin');
+		
+		// Check for bundled plugins
+		$this->assertArrayHasKey('smssync', $plugins);
+		$this->assertArrayHasKey('sharing', $plugins);
+		
+		// Check for bundled themes
+		$this->assertArrayHasKey('default', $themes);
+		$this->assertArrayHasKey('unicorn', $themes);
+		
+		// Check for themes not in plugins and vice versa
+		$this->assertArrayNotHasKey('default', $plugins, 'Theme "default" returned in get_addons(\'plugin\') array');
+		$this->assertArrayNotHasKey('smssync', $themes, 'Plugin "smssync" returned in get_addons(\'theme\') array');
+		
+		// @todo test includeMeta
+		$themes_nometa = addon::get_addons('theme', FALSE);
+		$themes_meta = addon::get_addons('theme', TRUE);
+		$this->assertNotEmpty($themes_meta['default'], 'get_addons(\'theme\', TRUE) not returning meta array');
+		$this->assertEmpty($themes_nometa['default'], 'get_addons(\'theme\', FALSE) not returning empty meta array');
+		
+	}
+	
+	
+	/**
+	 * Tests addon::meta_data()
+	 *
+	 * @test
+	 */
+	public function test_meta_data()
+	{
+		$default = addon::meta_data('default','theme');
+		$smssync = addon::meta_data('smssync','plugin');
+		
+		$this->assertNotEmpty($default['Theme Name']);
+		$this->assertNotEmpty($smssync['name']);
+	}
+
+}
+?>
diff --git a/tests/phpunit/classes/helpers/Alerts_Test.php b/tests/phpunit/classes/helpers/Alerts_Test.php
new file mode 100755
index 0000000..b4e4f6e
--- /dev/null
+++ b/tests/phpunit/classes/helpers/Alerts_Test.php
@@ -0,0 +1,127 @@
+<?php
+class Alerts_Helper_Test extends PHPUnit_Framework_TestCase {
+
+	/**
+	 * Executed when this test case is initialized
+	 */
+	public function setUp()
+	{
+		$this->databaseTester = NULL;
+		
+		// Build the PDO datasource name (DSN)
+		$type = Kohana::config('database.default.connection.type');
+		// Quick fix to replace 'mysqli' with 'mysql' since mysqli isn't valid for PDO
+		$type = ($type == 'mysqli') ? 'mysql' : $type;
+		$database_dsn = $type.":"
+					."dbname=".Kohana::config('database.default.connection.database').";"
+					."host=".Kohana::config('database.default.connection.host');
+		
+		// Create PDO object
+		$pdo = new PDO($database_dsn, Kohana::config('database.default.connection.user'), 
+			Kohana::config('database.default.connection.pass'));
+		
+		// Create connection
+		$connection = new PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection($pdo, 
+			Kohana::config('database.default.connection.database'));
+		
+		// Set up the database tester object, setup operation and dataset
+		$this->databaseTester = new PHPUnit_Extensions_Database_DefaultTester($connection);
+		$this->databaseTester->setSetUpOperation(PHPUnit_Extensions_Database_Operation_Factory::CLEAN_INSERT());
+		
+		// 	Set the dataset for the tester
+		$this->databaseTester->setDataSet($this->getDataSet());
+		
+		// Run setup
+		$this->databaseTester->onSetUp();
+	}
+	
+	/**
+	 * Returns a dataset containing the alert data to be used for the tests
+	 * in this test case
+	 *
+	 * @return PHPUnit_Database_Extensions_Database_DataSet_IDataSet
+	 */
+	protected function getDataSet()
+	{
+		return new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet(TESTS_PATH.'data/alert.xml');
+	}
+	
+	/**
+	 * Tear down operation - Executed when the test is complete
+	 */
+	public function tearDown()
+	{
+		// Tear down operation
+		$this->databaseTester->setTearDownOperation(PHPUnit_Extensions_Database_Operation_Factory::TRUNCATE());
+		$this->databaseTester->setDataSet($this->getDataSet());
+		$this->databaseTester->onTearDown();
+		
+		// Garbage collection
+		unset ($this->databaseTester);
+	}
+	/**
+	 * Data provider for test_alert_code_exists()
+	 *
+	 * @return array
+	 */
+	public function providerValidate()
+	{
+		return array(
+			array(
+					testutils::get_random_id('alert', 'WHERE alert_confirmed = 1')
+			)
+		);
+		
+	}
+	
+	/**
+	 * Tests Alert_Model->alert_code_exist() where the alert_code exists
+	 *
+	 * @test
+	 * @dataProvider providerValidate
+	 * @param array $data Input data to be validated
+	 */
+	public function test_alert_code_exists($data)
+	{
+		// Create instance for the Alert_Model class
+		$model = new Alert_Model();
+		// Check if the alert code exists
+		$this->assertEquals(TRUE, $model->alert_code_exists($data), 'Alert Code exists');
+
+	}
+
+	/**
+	 * Data provider for test_alert_code_not_exists()
+	 *
+	 * @return array
+	 */
+	public function providerValidateAlertCode()
+	{
+		return array(
+			array(
+					'3WUXAPRT'
+			)
+		);
+		
+	}
+	
+	/**
+	 * Tests Alert_Model->alert_code_exist() where the alert_code is
+	 * non-existent
+	 *
+	 * @test
+	 * @dataProvider providerValidateAlertCode
+	 * @param array $data Input data to be validated
+	 */
+	public function test_alert_code_non_exists($data)
+	{
+		// Create instance for the Alert_Model class
+		$model = new Alert_Model();
+		
+		// Check if the alert code exists
+		$this->assertEquals(FALSE, $model->alert_code_exists($data), 'Alert Code does not exist');
+
+	}
+
+}
+?>
diff --git a/tests/phpunit/classes/helpers/Customforms_Test.php b/tests/phpunit/classes/helpers/Customforms_Test.php
new file mode 100755
index 0000000..54ae887
--- /dev/null
+++ b/tests/phpunit/classes/helpers/Customforms_Test.php
@@ -0,0 +1,154 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Unit tests for the custom forms helper
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Customforms_Helper_Test extends PHPUnit_Framework_TestCase {
+	
+	/**
+	 * Executed when this test case is initialized
+	 */
+	public function setUp()
+	{
+		$this->databaseTester = NULL;
+		
+		// Build the PDO datasource name (DSN)
+		$type = Kohana::config('database.default.connection.type');
+		// Quick fix to replace 'mysqli' with 'mysql' since mysqli isn't valid for PDO
+		$type = ($type == 'mysqli') ? 'mysql' : $type;
+		$database_dsn = $type.":"
+					."dbname=".Kohana::config('database.default.connection.database').";"
+					."host=".Kohana::config('database.default.connection.host');
+		
+		// Create PDO object
+		$pdo = new PDO($database_dsn, Kohana::config('database.default.connection.user'), 
+			Kohana::config('database.default.connection.pass'));
+		
+		// Create connection
+		$connection = new PHPUnit_Extensions_Database_DB_DefaultDatabaseConnection($pdo, 
+			Kohana::config('database.default.connection.database'));
+		
+		// Set up the database tester object, setup operation and dataset
+		$this->databaseTester = new PHPUnit_Extensions_Database_DefaultTester($connection);
+		$this->databaseTester->setSetUpOperation(PHPUnit_Extensions_Database_Operation_Factory::CLEAN_INSERT());
+		
+		// 	Set the dataset for the tester
+		$this->databaseTester->setDataSet($this->getDataSet());
+		
+		// Run setup
+		$this->databaseTester->onSetUp();
+	}
+	
+	/**
+	 * Returns a dataset containing the form field data to be used for the tests
+	 * in this test case
+	 *
+	 * @return PHPUnit_Database_Extensions_Database_DataSet_IDataSet
+	 */
+	protected function getDataSet()
+	{
+		return new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet(TESTS_PATH.'data/form_field.xml');
+	}
+	
+	/**
+	 * Tear down operation - Executed when the test is complete
+	 */
+	public function tearDown()
+	{
+		// Tear down operation
+		$this->databaseTester->setTearDownOperation(PHPUnit_Extensions_Database_Operation_Factory::TRUNCATE());
+		$this->databaseTester->setDataSet($this->getDataSet());
+		$this->databaseTester->onTearDown();
+		
+		// Garbage collection
+		unset ($this->databaseTester);
+	}
+	
+	/**
+	 * Tests the get_custom_forms method
+	 *
+	 * @test
+	 */
+	public function testGetCustomForms()
+	{
+		// Database instance for the test
+		$db = new Database();
+		
+		// The record count should be the same since get_custom_forms() has no predicates
+		$this->assertEquals($db->count_records('form'), customforms::get_custom_forms()->count());
+	}
+
+
+	/**
+	 * Data provider for testValidateCustomFormFields
+	 *
+	 * @dataProvider
+	 */
+	public function providerTestValidateCustomFormFields()
+	{
+		return array(array(
+			// Valid custom forms data
+			array(
+				'custom_field' => array(
+					7 => 'Test compulsory text field data',
+					9 => '07/20/2011',
+					10 => 'Radio 1'
+				)
+			),
+
+			// Invalid custom forms data
+			array(
+				'custom_field' => array(
+					1 => 'Test compulsory text field data',
+					3 => '2011/07/20',
+					11 => ''
+				)
+			)
+		));
+	}
+
+	/**
+	 * Tests customforms::validate_custom_form_fields()
+	 *
+	 * @dataProvider providerTestValidateCustomFormFields
+	 */
+	public function testValidateCustomFormFields($valid_data, $invalid_data)
+	{
+		// Setup validation objects for the valid custom forms data
+		$valid_validator = Validation::factory($valid_data)
+							->pre_filter('trim', TRUE);
+
+		// Get the return value for validation of valid date
+		customforms::validate_custom_form_fields($valid_validator);
+
+		$errors = $valid_validator->errors();
+
+		// Assert that validation of the valid data returns no errors
+		$this->assertEquals(0, count($errors), "Some errors have been found".Kohana::debug($errors));
+
+		// Set up validation for the invalid custom forms data
+		$invalid_validator = Validation::factory($invalid_data)
+								->pre_filter('trim', TRUE);
+
+		// Get the return value for validation of invalid data
+		$errors = customforms::validate_custom_form_fields($invalid_validator);
+
+		$errors = $invalid_validator->errors();
+
+		// Assert that the validation of the invalid data returns some errors
+		$this->assertEquals(TRUE, count($errors) > 0, "Expected to encounter errors. None found: ".count($errors));
+
+		// Garbage collection
+		unset ($valid_validator, $invalid_validator, $errors);
+	}
+}
+?>
diff --git a/tests/phpunit/classes/helpers/Download_Test.php b/tests/phpunit/classes/helpers/Download_Test.php
new file mode 100644
index 0000000..5611883
--- /dev/null
+++ b/tests/phpunit/classes/helpers/Download_Test.php
@@ -0,0 +1,1151 @@
+<?php defined('SYSPATH') or die('No direct script access');
+
+/**
+ * Unit tests for the XML Reports download via the Download helper
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+ 
+ class Download_Helper_Test extends PHPUnit_Framework_TestCase{
+	
+	public function setUp()
+	{
+		// Set up post variable
+		$this->post = array(
+			'format' =>'xml',
+			'data_active'   => array(0, 1),
+			'data_verified'   => array(0, 1),
+			'data_include' => array(1, 2, 3, 4, 5, 6, 7),
+			'from_date'	   => '',
+			'to_date'	   => '',
+		);
+		
+		// Categories object : Limit it to one category only
+		$this->category = ORM::factory('category')
+							->join('category_lang', 'category.id', 'category_lang.category_id', 'inner')
+							->where('parent_id !=', 0)
+							->limit(1)
+							->find_all();
+
+		// Incidents object : Limit it to one incident only
+		$this->incident = ORM::factory('incident')->limit(1)->find_all();
+		
+		// Forms object to be used for XML download : Limit it to one custom form only
+		$this->forms = ORM::factory('form')->join('form_field','form_field.form_id', 'form.id', 'inner')->limit(1)->find_all();
+		
+		// Custom forms object to be used for CSV download
+		$this->custom_forms = customforms::get_custom_form_fields('','',false);
+	}
+	
+	public function tearDown()
+	{
+		unset($this->post, $this->category, $this->incident, $this->forms);
+	}
+	
+	/**
+	 * Data Provider for testGenerateArrayMap
+	 * @dataProvider
+	 */
+	public function providerTestGenerateArrayMap()
+	{
+		/* Category Element/Attribute maps */
+		// Select a random category
+		$category = ORM::factory('category', testutils::get_random_id('category'));
+		
+		// Category map
+		$category_map = array(
+			'attributes' => array(
+				'color' => 'category_color',
+				'visible' => 'category_visible',
+				'trusted' => 'category_trusted'
+			),
+			'elements' => array(
+				'title' => 'category_title',
+				'description' => 'category_description'
+			)
+		);
+
+		// Expected category array map
+		$category_element_map = array(
+			'attributes' => array(
+				'color' => $category->category_color,
+				'visible' => $category->category_visible,
+				'trusted' => $category->category_trusted
+			),
+			'elements' => array(
+				'title' => $category->category_title,
+				'description' => $category->category_description
+			)
+		);
+							
+		/* Category translation Element/Attribute maps */
+		// Translation ORM Object
+		$translation = ORM::factory('category_lang', testutils::get_random_id('category_lang', 'WHERE category_id ='.$category->id.''));
+		
+		// Translation map
+		$translation_map = array(
+			'attributes' => array(
+				'locale' => 'locale',
+			),
+			'elements' => array(
+				'translation_title' => 'category_title',
+				'translation_description' => 'category_description'
+			)
+		);
+
+		// Expected translation array map
+		$translation_element_map = array(
+			'attributes' => array(
+				'locale' => $translation->locale,
+			),
+			'elements' => array(
+				'translation_title' => $translation->category_title,
+				'translation_description' => $translation->category_description
+			)
+		);
+		
+		/* Form element/attribute maps */
+		// Select a random form
+		$form = ORM::factory('form', testutils::get_random_id('form'));
+		
+		// Forms map
+		$form_map = array(
+			'attributes' => array(
+				'active' => 'form_active'
+				),
+			'elements' => array(
+				'title' => 'form_title',
+				'description' => 'form_description'
+				)
+			);
+						
+		// Expected form array map
+		$form_element_map = array(
+			'attributes' => array(
+				'active' => $form->form_active
+			),
+			'elements' => array(
+				'title' => $form->form_title,
+				'description' => $form->form_description
+			)
+		);
+		
+		/* Reports element/attribute maps */
+		// Select a random incident
+		$incident = ORM::factory('incident', testutils::get_random_id('incident'));
+		
+		// Report map
+		$report_map = array(
+			'attributes' => array(
+				'id' => 'id',
+				'approved' => 'incident_active',
+				'verified' => 'incident_verified',
+				'mode' => 'incident_mode',
+			),
+			'elements' => array(
+				'title' => 'incident_title',
+				'date' => 'incident_date',
+				'dateadd' => 'incident_dateadd',
+				'description' => 'incident_description'
+			)
+		);
+					
+		// Expected report array map
+		$report_element_map = array(
+			'attributes' => array(
+				'id' => $incident->id,
+				'approved' => $incident->incident_active,
+				'verified' => $incident->incident_verified,
+				'mode' => $incident->incident_mode,
+			),
+			'elements' => array(
+				'title' => $incident->incident_title,
+				'date' => $incident->incident_date,
+				'dateadd' => $incident->incident_dateadd,
+				'description' => $incident->incident_description
+			)
+		);
+		
+		/* Report Location */
+		// Report location ORM object
+		$location = $incident->location;
+		
+		// Location Map
+		$location_map = array(
+			'attributes' => array(),
+			'elements' => array(
+				'name' => 'location_name',
+				'longitude' => 'longitude',
+				'latitude' => 'latitude'		
+			)
+		);
+
+		// Expected location array map
+		$location_element_map = array(
+			'attributes' => array(),
+			'elements' => array(
+				'name' => $location->location_name,
+				'longitude' => $location->longitude,
+				'latitude' => $location->latitude		
+			)
+		);
+								
+		/* Report Media */
+		// Report Media ORM Object
+		$media = ORM::factory('media', testutils::get_random_id('media', 'WHERE incident_id ='.$incident->id.''));
+		
+		// Media Map
+		$media_map = array(
+			'attributes' => array(
+				'type' => 'media_type',
+				'active' => 'media_active',
+				'date' => 'media_date'
+			),
+			'elements' => array()
+		);
+
+		// Expected media array map
+		$media_element_map = array(
+			'attributes' => array(
+				'type' => $media->media_type,
+				'active' => $media->media_active,
+				'date' => $media->media_date
+			),
+			'elements' => array()
+		);
+		
+		/* Report personal info */
+		// Personal info ORM Object
+		$person = $incident->incident_person;
+		
+		// Personal info map
+		$person_map = array(
+			'attributes' => array(),
+			'elements' => array(
+				'firstname' => 'person_first',
+				'lastname' => 'person_last',
+				'email' => 'person_email'	
+			)
+		);
+		
+		// Expected personal info array map
+		$person_element_map = array(
+			'attributes' => array(),
+			'elements' => array(
+				'firstname' => $person->person_first,
+				'lastname' => $person->person_last,
+				'email' => $person->person_email	
+			)
+		);		
+		
+		/* Incident Categories */
+		// Incident Category ORM Object
+		$incident_cat = ORM::Factory('category')
+						->join('incident_category','incident_category.category_id','category.id','inner')
+						->where('incident_category.incident_id', $incident->id)
+						->limit(1)
+						->find();
+								
+		// Incident Category map
+		$incident_cat_map = array(
+			'attributes' => array(),
+			'elements' => array(
+				'category' => 'category_title',
+			)
+		);
+		
+		// Expected incident category array Map
+		$incident_cat_element_map = array(
+			'attributes' => array(),
+			'elements' => array(
+				'category' => $incident_cat->category_title,
+			)
+		);							
+							
+		return array(
+			array($category_map, $category_element_map, $category, 'Category'),
+			array($translation_map, $translation_element_map, $translation, 'Category Translation'),
+			array($form_map, $form_element_map, $form, 'Form'),
+			array($report_map, $report_element_map, $incident, 'Report'),
+			array($location_map, $location_element_map, $location, 'Report Location'),
+			array($media_map, $media_element_map, $media, 'Report Media'),
+			array($person_map, $person_element_map, $person, 'Reporter'),
+			array($incident_cat_map, $incident_cat_element_map, $incident_cat, 'Incident category')
+		);
+	}
+	
+	/**
+	 * Tests download helper function which generates object array maps
+	 * to be used to generate XML element tags
+	 * @test
+	 * @dataProvider providerTestGenerateArrayMap
+	 * @param array $object_map associative array map skeleton 
+	 * @param array $expected_map expected output
+	 * @param object $object_orm ORM object
+	 * @param string $object_name
+	 */
+	public function testGenerateArrayMap($object_map, $expected_map, $object_orm, $object_name)
+	{		
+		// Get array map returned by download helper function
+		$actual_map = xml::generate_element_attribute_map($object_orm, $object_map);
+		
+		// For the random category
+		if ($object_name == 'Category')
+		{
+			// Check if this category has a parent
+			if ($object_orm->parent_id > 0)
+			{
+				// Fetch the parent category
+				$parent = ORM::Factory('category', $object_orm->parent_id);
+				
+				// Add category parent to actual_map and expected_map
+				$expected_map['elements']['parent'] = $parent->category_title;
+				$actual_map['elements']['parent'] = $parent->category_title;
+			}	
+		}
+		
+		if ($object_name == 'Report')
+		{
+			// Make sure the incident_form is loaded
+			if ($object_orm->form->loaded)
+			{
+				// Add form_name attribute to actual map and expected map
+				$expected_map['attributes']['form_name'] = $object_orm->form->form_title;
+				$actual_map['attributes']['form_name'] = $object_orm->form->form_title;
+			}
+		}
+		
+		// Test to ensure expected array map and actual array map match
+		$this->assertEquals($expected_map, $actual_map, 'Output does not match expected array for the '.$object_name.' object');	
+	}
+	/**
+	 * Test XML Tag generation
+	 * @test
+	 * @return string $xml_content
+	 */
+	public function testDownloadXML()
+	{	
+		/* Test XML Tag generation */
+		// Test to ensure validation passed
+		$this->assertEquals(TRUE, download::validate($this->post), 'Report download validation failed');
+		
+		// Load XML Content into a string
+		$xml_content = download::download_xml($this->post, $this->incident, $this->category, $this->forms);
+		
+		// Make sure string holding XML Content is not empty
+		$this->assertNotEmpty($xml_content, 'XML Download Failed');
+		
+		return $xml_content;
+	}
+	
+	/**
+	 * Load XML Content generated and check for Categories, Custom Forms and Reports tags
+	 * @test
+	 * @depends testDownloadXML
+	 * @param string $xml_content
+	 */
+	public function testReadDownloadXML($xml_content)
+	{
+		// XML Reader
+		$reader = new DOMDocument('1.0');
+		
+		// Load XML string into reader
+		$reader->loadXML($xml_content);
+		
+		// Ensure that the XML String is loaded
+		$this->assertTrue(@$reader->loadXML($xml_content), 'XML Content loading failed');
+		
+		// Check for categories, customforms and reports elements
+		$d_categories = $reader->getElementsByTagName('categories');
+		$d_customforms = $reader->getElementsByTagName('custom_forms');
+		$d_reports = $reader->getElementsByTagName('reports');
+		
+		// Ensure that at least one of the elements i.e categories, customforms OR reports exist
+		$tag_exists = ($d_categories->length == 0 AND $d_customforms->length == 0 AND $d_reports->length == 0) 
+						? FALSE
+						: TRUE;
+		$this->assertTrue($tag_exists, 'XML content must have at least one of the following: Categories, Custom forms or Reports');	
+		
+		return array($d_categories, $d_customforms, $d_reports);
+		
+	}
+	
+	/**
+	 * Tests whether XML Category element matches ORM objects provided for download
+	 * @test
+	 * @depends testReadDownloadXML
+	 * @param array $dom_nodes DOMNodeList Objects
+	 */
+	public function testCheckCategoryXML(array $dom_nodes)
+	{
+		/* Category check */
+		// Categories DOMNodeList Object 
+		$d_categories = $dom_nodes[0];
+		
+		// When category option is not selected, make sure the categories element does not exist
+		if ( ! in_array(3, $this->post['data_include']))
+		{
+			$this->assertEquals(0, $d_categories->length, 'The "categories" element should not exist');
+		}
+		
+		// Download of categories option was provided by the user
+		else
+		{
+			// Make sure the categories element exists
+			$this->assertGreaterThan(0, $d_categories->length, 'The "categories" element SHOULD exist');
+			
+			// Contents of <categories> element
+			$categories_element = $d_categories->item(0);
+			
+			// If we have no categories on this deployment
+			if (count($this->category) == 0)
+			{
+				// Ensure the categories element has the following message
+				$this->assertEquals('There are no categories on this deployment.', $categories_element->nodeValue);
+			}
+			
+			// We have categories on this deployment
+			else
+			{
+				// Individual category
+				$cat = $this->category[0];
+				
+				// Grab contents of <category> element
+				$category_element = $categories_element->getElementsByTagName('category');
+				
+				// Test to see if category element exists
+				$this->assertGreaterThan(0, $category_element->length, 'Category element does not exist for deployment with existing categories');
+			
+				// Test category Color
+				$color = xml::get_node_text($category_element->item(0), 'color', FALSE);
+				$this->assertEquals($cat->category_color, $color, 'Category Color does not match/ Color attribute does not exist');
+				
+				// Test category Visible
+				$visible = $category_element->item(0)->getAttribute('visible');
+				$this->assertEquals($cat->category_visible, $visible, 'Category visible status does not match/attribute does not exist');
+				
+				// Test category Trusted
+				$trusted = $category_element->item(0)->getAttribute('trusted');
+				$this->assertEquals($cat->category_trusted, $trusted, 'Category trusted status does not match/attribute does not exist');
+				
+				// Test category Title
+				$title = xml::get_node_text($category_element->item(0), 'title');
+				$this->assertEquals($cat->category_title, $title, 'Category title does not match/ title element does not exist');
+				
+				// Test category Description
+				$description = xml::get_node_text($category_element->item(0), 'description');
+				$this->assertEquals($cat->category_description, $description, 'Category description does not match/the element does not exist');
+				
+				// Test category Parent
+				if ($cat->parent_id > 0)
+				{
+					// Fetch the parent category
+					$parent = ORM::Factory('category', $cat->parent_id);
+					$parent_title = xml::get_node_text($category_element->item(0), 'parent');
+					$this->assertEquals($parent->category_title, $parent_title, 'Category parent title does not match/parent element does not exist');
+				}
+				
+				/* Translation Check */
+				// Grab contents of <translations> element
+				$translations_element = $categories_element->getElementsByTagName('translations');
+				
+				// Grab the category translations
+				$translations = ORM::Factory('category_lang')->where('category_id', $cat->id)->find_all();
+				$translation_count = count($translations);
+				
+				// If we actually have translations for this category
+				if ( $translation_count > 0)
+				{
+					// Pick out a random translation by generating a random index to select based on this count
+					$index = rand(0, $translation_count-1);
+					$translation = $translations[$index];
+					
+					// Test to see if the translations element exists
+					$this->assertGreaterThan(0, $translations_element->length, 'Translations element does not exist for category with translations');
+					
+					// Grab contents of individual <translation> elements
+					$translation_element = $translations_element->item(0)->getElementsByTagName('translation');
+					
+					// Test to see if the <translation> element exists
+					$this->assertGreaterThan(0, $translation_element->length, 'Translation element does not exist for category with translations');
+					
+					// Test Translation locale
+					$locale = xml::get_node_text($translation_element->item($index), 'locale', FALSE);
+					$this->assertEquals($translation->locale, $locale, 'Translation locales do not match/ attribute does not exist');
+					
+					// Test Translation category title
+					$transtitle = xml::get_node_text($translation_element->item($index), 'translation_title');
+					$this->assertEquals($translation->category_title, $transtitle, 'Translation titles do not match/ element does not exist');
+					
+					// Test Translation category description
+					$transdescription = xml::get_node_text($translation_element->item($index), 'translation_description');
+					$this->assertEquals($translation->category_description, $transdescription, 'Translation descriptions do not match/ element does not exist');
+				}
+				
+				// If we don't have translations for this category
+				else
+				{
+					// Test to ensure that the translations element does NOT exist
+					$this->assertEquals(0, $translations_element->length, 'Translations element should not exist for category with no translations');
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Tests whether XML Custom form element matches ORM objects provided for download
+	 * @test
+	 * @depends testReadDownloadXML
+	 * @param array $domnodes DOMNodeList Objects
+	 */
+	
+	public function testCheckCustomFormXML(array $dom_nodes)
+	{
+		/* Custom form check */
+		$d_customforms = $dom_nodes[1];
+		
+		// When custom forms option is not selected, make sure the custom forms element does not exist
+		if ( ! in_array(6, $this->post['data_include']))
+		{
+			$this->assertEquals(0, $d_customforms->length, 'The "custom_forms" element should not exist');
+		}
+		
+		// Custom forms option is selected
+		else
+		{
+			// Test to make sure <customforms> element exists
+			$this->assertGreaterThan(0, $d_customforms->length, 'The "custom_forms" element SHOULD exist');
+			
+			// Contents of <customforms> element
+			$forms_element = $d_customforms->item(0);
+			
+			// If we don't have custom forms on this deployment
+			if (count($this->forms) == 0)
+			{
+				// Ensure the customforms element has the following message
+				$this->assertEquals('There are no custom forms on this deployment.', $d_customforms->item(0)->nodeValue);
+			}
+			
+			// We have custom forms on this deployment
+			else
+			{
+				// Grab individual form
+				$form = $this->forms[0];
+				
+				// Grab contents of <form> element
+				$form_element = $forms_element->getElementsByTagName('form');
+				
+				// Test to see if the <form> element exists
+				$this->assertGreaterThan(0,$form_element->length, 'The "form" element does not exist for a deployment with forms');
+				
+				// Test Form active status
+				$active = $form_element->item(0)->getAttribute('active');
+				$this->assertEquals($form->form_active, $active, 'Form active status does not match/attribute does not exist');
+				
+				// Test Form title
+				$title = xml::get_node_text($form_element->item(0), 'title');
+				$this->assertEquals($form->form_title, $title, 'Form title does not match/element does not exist');
+				
+				// Test Form description
+				$description = xml::get_node_text($form_element->item(0), 'description');
+				$this->assertEquals($form->form_description, $description, 'Form description does not match/element does not exist');
+				
+				/* Custom fields check */
+				// Get custom fields associated with this form
+				$form_fields = ORM::factory('form_field')
+								->join('roles', 'roles.id', 'field_ispublic_visible', 'left')
+								->where('form_id', $form->id)
+								->orderby('field_position', 'ASC')
+								->find_all();
+				
+				// Get custom field count, 
+				$field_count = count($form_fields);
+				$field_elements = $form_element->item(0)->getElementsByTagName('field');
+				
+				if ($field_count > 0)
+				{
+					$this->assertGreaterThan(0, $field_elements->length, 'This form has form fields. Field element should exist');
+					
+					// Grab a random custom field by generating a random index to select based on field count
+					$field_index = rand(0, $field_count-1);
+					$field = $form_fields[$field_index];
+					
+					// Grab the random field's corresponding element in XML download text
+					$field_element = $field_elements->item($field_index);
+					
+					// Make sure this particular <field> element actually exists
+					$this->assertNotNull($field_element, 'The field element SHOULD exist');
+					
+					// Test field type
+					$type = $field_element->getAttribute('type');
+					$this->assertEquals($field->field_type, $type, 'Field type does not match/attribute does not exist');
+					
+					// Test field required
+					$required = $field_element->getAttribute('required');
+					$this->assertEquals($field->field_required, $required, 'Field required does not match/attribute does not exist');
+					
+					// Test field visibility status
+					$visible_by = $field_element->getAttribute('visible_by');
+					$this->assertEquals($field->field_ispublic_visible, $visible_by, 'Field visible status does not match/attribute does not exist');
+					
+					// Test field submit status
+					$submit_by = $field_element->getAttribute('submit_by');
+					$this->assertEquals($field->field_ispublic_submit, $submit_by, 'Field submit status does not match/attribute does not exist');
+					
+					// Test field options
+					// Check for field options
+					$options = ORM::factory('form_field_option')->where('form_field_id',$field->id)->find_all();
+					
+					// If this field has field options
+					if (count($options) > 0)
+					{
+						foreach ($options as $option)
+						{
+							// Test field datatype
+							if ($option->option_name == 'field_datatype')
+							{
+								// Make sure the datatype attribute exists
+								$this->assertEquals(TRUE, $field_element->hasAttribute('datatype'), 'Field datatype attribute should exist');
+								
+								$datatype = xml::get_node_text($field_element, 'datatype', FALSE);
+								$this->assertEquals($option->option_value, $datatype, 'Datatype options do not match/attribute does not exist');
+							}
+							
+							// Test field hidden
+							if ($option->option_name == 'field_hidden')
+							{
+								// Make sure the datatype attribute exists
+								$this->assertEquals(TRUE, $field_element->hasAttribute('hidden'), 'Field hidden attribute should exist');
+								
+								$hidden = $field_element->getAttribute('hidden');
+								$this->assertEquals($option->option_value, $hidden, 'Hidden options do not match/attribute does not exist');
+							}
+						}
+					}
+					
+					// Hidden/Datatype attributes should not exist
+					else
+					{
+						$this->assertEquals(FALSE, $field_element->hasAttribute('hidden'), 'Field has no hidden option');
+						$this->assertEquals(FALSE, $field_element->hasAttribute('datatype'), 'Field has no datatype option');
+					}
+					
+					// Test field name
+					$name = xml::get_node_text($field_element, 'name');
+					$this->assertEquals($field->field_name, $name, 'Field names do not match/element does not exist');
+					
+					// Test Field default
+					$default = xml::get_node_text($field_element, 'default');
+					
+					// If field does not have a default value
+					if ($field->field_default != '')
+					{
+						$this->assertEquals($field->field_default, $default, 'Field defaults so not match/element does not exist');
+					}
+					
+					// Field has a default value
+					else
+					{
+						$this->assertEquals(FALSE, $default, 'Field default does not exist for this field');
+					}	
+				}
+				
+				else
+				{
+					$this->assertEquals(0, $field_elements->length, 'This form has no form fields. Field element should NOT exist');
+				}
+			}
+		}
+	}
+	
+	/**
+	 * Tests whether XML Report element matches ORM objects provided for download
+	 * @test
+	 * @depends testReadDownloadXML
+	 * @param array $domnodes DOMNodeList Objects
+	 */
+	public function testCheckReportsXML(array $domnodes)
+	{
+		$d_reports = $domnodes[2];
+		
+		// Ensure that the DOMNodeList Object is not empty
+		$this->assertGreaterThan(0, $d_reports->length, 'Reports Element MUST exist.');
+		
+		// Contents of <Reports> element
+		$reports_element = $d_reports->item(0);
+		
+		/* Report Check */
+		// If we have no reports on this deployment
+		if (count($this->incident) == 0)
+		{
+			// Ensure the customforms element has the following message
+			$this->assertEquals('There are no reports on this deployment.', $d_reports->item(0)->nodeValue);
+		}
+		
+		// We have reports on this deployment
+		else
+		{
+			// Grab individual Report
+			$incident = $this->incident[0];
+			
+			// Grab contents of <report> element
+			$report_element = $reports_element->getElementsByTagName('report');
+			
+			// Test to see if the <report> element exists
+			$this->assertGreaterThan(0, $report_element->length, 'Report element does not exist for deployment with reports');
+			
+			/* Report Check */
+			// Test report id
+			$id = $report_element->item(0)->getAttribute('id');
+			$this->assertEquals($incident->id, $id, 'Report id does not match/attribute does not exist');
+			
+			// Test Report approval status
+			$approved = $report_element->item(0)->getAttribute('approved');
+			$this->assertEquals($incident->incident_active, $approved, 'Report active status does not match/attribute does not exist');
+			
+			// Test Report verified status
+			$verified = $report_element->item(0)->getAttribute('verified');
+			$this->assertEquals($incident->incident_verified, $verified, 'Report verified status does not match/attribute does not exist');
+			
+			// Test Report mode status
+			$mode = $report_element->item(0)->getAttribute('mode');
+			$this->assertEquals($incident->incident_mode, $mode, 'Report mode does not match/attribute does not exist');
+			
+			// Test Report form_name
+			// Grab Default form object
+			$default_form = ORM::factory('form', 1);
+			$expected_form = $incident->form->loaded ? $incident->form->form_title : $default_form->form_title;
+			$form_name = xml::get_node_text($report_element->item(0), 'form_name', FALSE);
+			$this->assertEquals($expected_form, $form_name, 'Report form_name does not match/attribute does not exist');
+			
+			// Test Report Title
+			$title = xml::get_node_text($report_element->item(0), 'title');
+			$this->assertEquals($incident->incident_title, $title, 'Report title does not match/element does not exist');
+			
+			// Test Report Date
+			$date = xml::get_node_text($report_element->item(0), 'date');
+			$this->assertEquals($incident->incident_date, $date, 'Report date does not match/element does not exist');
+			
+			// Test Report Dateadd
+			$date_add = xml::get_node_text($report_element->item(0), 'dateadd');
+			$this->assertEquals($incident->incident_dateadd, $date_add, 'Report dateadd does not match/element does not exist');
+			
+			// Test report description
+			$description = xml::get_node_text($report_element->item(0), 'description');
+			
+			// If download report description option is selected by user
+			if (in_array(2, $this->post['data_include']))
+			{
+				$this->assertEquals($incident->incident_description, $description, 'Report description does not match/element does not exist');
+			}
+			
+			else
+			{
+				$this->assertEquals(FALSE, $description, 'Report description element should not exist');
+			}
+			
+			/* Location Check */
+			$locations_element = $report_element->item(0)->getElementsByTagName('location');
+			$location = $incident->location;
+			
+			// Include location option has been selected
+			if (in_array(1, $this->post['data_include']))
+			{
+				// Make sure the <location> element exists
+				$this->assertGreaterThan(0, $locations_element->length, 'Report location element SHOULD exist');
+				
+				// Test location name
+				$location_name = xml::get_node_text($locations_element->item(0),'name');
+				$this->assertEquals($location->location_name, $location_name, 'Location name does not match/element does not exist');
+				
+				// Test Latitude
+				$latitude = xml::get_node_text($locations_element->item(0),'latitude');
+				$this->assertEquals($location->latitude, $latitude, 'Latitude does not match/element does not exist');
+				
+				// Test longitude
+				$longitude = xml::get_node_text($locations_element->item(0),'longitude');
+				$this->assertEquals($location->longitude, $longitude, 'Longitude does not match/element does not exist');
+			}
+			
+			else
+			{
+				$this->assertEquals(0, $locations_element->length, "Report location element should not exist");
+			}
+		
+			/* Media Check */
+			$incident_media = ORM::Factory('media')
+							->where('media_type = 2 OR media_type = 4')
+							->where('incident_id', $incident->id)
+							->find_all();
+			
+			$media_element = $report_element->item(0)->getElementsByTagName('media');
+			if (count($incident_media) > 0)
+			{
+				$media_count = count($incident_media);
+				$media_index = rand(0, $media_count-1);
+				
+				// Make sure the media element exists
+				$this->assertGreaterThan(0, $media_element->length, 'The media element SHOULD exist');
+				
+				// Grab contents of media <item> element
+				$media_item = $media_element->item(0)->getElementsByTagName('item');
+				
+				// Grab random individual media item
+				$this_media = $incident_media[$media_index];
+				
+				if ( $this_media->media_type == 2 OR $this_media->media_type == 4 )
+				{
+					// Make sure the <item> element exists
+					$this->assertEquals('item', $media_item->item($media_index)->tagName, 'The media item element SHOULD exist');
+			
+					// Test Media Type
+					$media_type = $media_item->item($media_index)->getAttribute('type');
+					$this->assertEquals($this_media->media_type, $media_type, 'Media type does not match/ attribute does not exist');
+			
+					// Test media active
+					$media_active = $media_item->item($media_index)->getAttribute('active');
+					$this->assertEquals($this_media->media_active, $media_active, 'Media active does not match/ attribute does not exist');
+			
+					// Test Media date
+					$media_date = xml::get_node_text($media_item->item($media_index), 'date', FALSE);
+					$this->assertEquals($this_media->media_date, $media_date, 'Media date does not match/ attribute does not exist');
+			
+					// Test media link
+					$media_link = $media_item->item($media_index)->nodeValue;
+					$this->assertEquals($this_media->media_link, $media_link, 'Media link does not match/ element does not exist');
+				}
+				else
+				{
+					// Make sure the <item> element does NOT exists for this particular media item
+					$this->assertNull($media_item->item($media_index), 'The media item element SHOULD NOT exist');
+				}
+			}
+			
+			// We have no media
+			else
+			{
+				// Make sure the media element does NOT exist
+				$this->assertEquals(0, $media_element->length, 'The media element should NOT exist');
+			}
+		
+			/* Personal info check */
+			$person_info_element = $report_element->item(0)->getElementsByTagName('personal_info');
+			$incident_person = $incident->incident_person;
+			
+			// Include personal info option selected?
+			if (in_array(7, $this->post['data_include']))
+			{
+				// If we actually have an incident_person for this report
+				if ($incident_person->loaded)
+				{
+					// Make sure the <personalinfo> element exists
+					$this->assertGreaterThan(0, $person_info_element->length, 'Report personal-info element SHOULD exist');
+				
+					// Test First Name
+					$firstname = xml::get_node_text($person_info_element->item(0), 'first_name');
+					$this->assertEquals($incident_person->person_first, $firstname, 'Person first name does not match/ element does not exist');
+				
+					// Test last name
+					$lastname = xml::get_node_text($person_info_element->item(0), 'last_name');
+					$this->assertEquals($incident_person->person_last, $lastname, 'Person last name does not match/ element does not exist');
+				
+					// Test Email
+					$email = xml::get_node_text($person_info_element->item(0), 'email');
+					$this->assertEquals($incident_person->person_email, $email, 'Person email does not match/ element does not exist');
+				}
+				else
+				{
+					$this->assertEquals(0, $person_info_element->length, "Report personal-info element should not exist");
+				}	
+			}
+			else
+			{
+				$this->assertEquals(0, $person_info_element->length, "Report personal-info element should not exist");
+			}
+		
+			/* Incident Category check */
+			$report_categories_element = $report_element->item(0)->getElementsByTagName('report_categories');
+			$incident_categories = $incident->incident_category;
+			$incident_cat_count = count($incident_categories);
+			$cat_index = rand(0, $incident_cat_count-1);
+			
+			// Include categories option selected?
+			if (in_array(3, $this->post['data_include']))
+			{
+				// Make sure the <reportcategories> element exists
+				$this->assertGreaterThan(0, $report_categories_element->length, "Report categories element should exist");
+				
+				// Pick a random incident category
+				$this_cat = $incident_categories[$cat_index];
+				
+				$report_cat_element = $report_categories_element->item(0)->getElementsByTagName('category');
+				
+				// Test incident_category title
+				$incident_cat = $report_cat_element->item($cat_index)->nodeValue;
+				$this->assertEquals($this_cat->category->category_title, $incident_cat, 'Incident_category does not match/element does not exist');
+			}
+			else
+			{
+				$this->assertEquals(0, $report_cat_element->length, "Report categories element should not exist");
+			}
+		
+			/* Custom response check */
+			$custom_responses_element = $report_element->item(0)->getElementsByTagName('custom_fields');
+			$sql = "SELECT form_field.*, form_response.form_response
+					FROM form_field
+					LEFT JOIN roles ON (roles.id = field_ispublic_visible)
+					LEFT JOIN
+						form_response ON (
+							form_response.form_field_id = form_field.id AND
+							form_response.incident_id = :incident_id
+						)
+					WHERE form_id = :form_id "
+					. "ORDER BY field_position ASC";
+							
+			$customresponses = Database::instance()->query($sql, array(
+								':form_id' => $incident->form_id,
+								':incident_id' => $incident->id
+								));
+			
+			// Grab response count
+			$response_count = count($customresponses);
+			
+			// Include custom fields option selected?
+			if (in_array(6, $this->post['data_include']))
+			{	
+				// If we have custom field responses for this incident
+				if ($response_count > 0)
+				{
+					// Make sure the <customfields> element exists
+					$this->assertGreaterThan(0, $custom_responses_element->length, "Report custom responses element should exist");
+					
+					// Grab a random custom response by generating a random index to select based on field count
+					$response_index = rand(0, $response_count-1);
+					$this_response = $customresponses[$response_index];
+					
+					// Grab contents of <field> element
+					$field_element = $custom_responses_element->item(0)->getElementsByTagName('field');
+					
+					// Make sure a form_response has actually been provided
+					if ($this_response->form_response != '')
+					{
+						// Make sure the <field> element exists
+						$this->assertNotNull($field_element->item($response_index), 'Custom Field response element should exist');
+						
+						// Test Field Name
+						$field_name = xml::get_node_text($field_element->item($response_index), 'name', FALSE);
+						$this->assertEquals($this_response->field_name, $field_name, 'Response field name does not match/attribute does not exist');
+				
+						// Test Field Response
+						$response = $field_element->item($response_index)->nodeValue;
+						$this->assertEquals($this_response->form_response, $response, 'Custom response does not match/element does not exist');
+					}
+					
+					// No field response exists
+					else
+					{
+						$this->assertNull($field_element->item($response_index), 'Custom Field response element should NOT exist');
+					}
+				}
+				else
+				{
+					$this->assertEquals(0, $custom_responses_element->length, 'Custom Field response element should NOT exist');
+				}	
+			}
+			else
+			{
+				$this->assertEquals(0, $custom_responses_element->length, "Report custom responses element should not exist");
+			}
+		}	
+	}
+	
+	/**
+	 * Test CSV Download
+	 * @test
+	 */
+	public function testDownloadCSV()
+	{
+		// Test to ensure validation passed
+		$this->assertEquals(TRUE, download::validate($this->post), 'Report download validation failed');
+		
+		// If we have no reports
+		if (count($this->incident) == 0)
+		{
+			$this->markTestSkipped('There are no reports, CSV Download test skipped');
+		}
+		
+		$expected_csv_content = "\"#\",\"FORM #\",\"INCIDENT TITLE\",\"INCIDENT DATE\"";
+		
+		// Include location information?
+		if (in_array(1,$this->post['data_include']))
+		{
+			$expected_csv_content.= ",\"LOCATION\"";
+		}
+		
+		// Include description information?
+		if (in_array(2,$this->post['data_include']))
+		{
+			$expected_csv_content.= ",\"DESCRIPTION\"";
+		}
+		
+		// Include category information?
+		if (in_array(3,$this->post['data_include']))
+		{
+			$expected_csv_content.= ",\"CATEGORY\"";
+		}
+		
+		// Include latitude information?
+		if (in_array(4,$this->post['data_include']))
+		{
+			$expected_csv_content.= ",\"LATITUDE\"";
+		}
+		
+		// Include longitude information?
+		if (in_array(5,$this->post['data_include']))
+		{
+			$expected_csv_content.= ",\"LONGITUDE\"";
+		}
+		
+		// Include custom forms information?
+		if (in_array(6,$this->post['data_include']))
+		{
+			foreach($this->custom_forms as $field_name)
+			{
+				$expected_csv_content.= ",\"".$field_name['field_name']."-".$field_name['form_id']."\"";
+			}
+		}
+		
+		// Include personal information?
+		if (in_array(7,$this->post['data_include']))
+		{
+			$expected_csv_content.= ",\"FIRST NAME\",\"LAST NAME\",\"EMAIL\"";	
+		}
+		
+		$expected_csv_content.= ",\"APPROVED\",\"VERIFIED\"";
+		$expected_csv_content.="\r\n";
+		
+		// Add Report information 
+		$report = $this->incident[0];
+		
+		// Report id, form_id, title, and date
+		$expected_csv_content.='"'.$report->id.'",'
+								.'"'.$report->form_id.'",'
+								.'"'.$report->incident_title.'",'
+								.'"'.$report->incident_date.'"';
+		
+		
+		
+		// Include location information?
+		if (in_array(1,$this->post['data_include']))
+		{
+			$expected_csv_content.= ',"'.$report->location->location_name.'"';
+		}
+		
+		// Include description information?
+		if (in_array(2,$this->post['data_include']))
+		{
+			$expected_csv_content.= ',"'.$report->incident_description.'"';
+		}
+		
+		// Include category information?
+		if (in_array(3,$this->post['data_include']))
+		{
+			$cat = array();
+			foreach($report->incident_category as $category)
+			{
+				if ($category->category->category_title)
+				{
+					$cat[] = $category->category->category_title;
+				}
+			}
+			$expected_csv_content.= ',"'.implode($cat,',').'"';
+		}
+		
+		// Include latitude information?
+		if (in_array(4,$this->post['data_include']))
+		{
+			$expected_csv_content.= ',"'.$report->location->latitude.'"';
+		}
+		
+		// Include longitude information?
+		if (in_array(5,$this->post['data_include']))
+		{
+			$expected_csv_content.= ',"'.$report->location->longitude.'"';
+		}
+		
+		// Include custom forms information?
+		if (in_array(6,$this->post['data_include']))
+		{
+			$custom_fields = customforms::get_custom_form_fields($report->id,'',false);
+			if ( ! empty($custom_fields))
+			{
+				foreach($custom_fields as $custom_field)
+				{
+					$expected_csv_content.= ',"'.$custom_field['field_response'].'"';
+				}
+			}
+			else
+			{
+				foreach ($this->custom_forms as $custom)
+				{
+					$expected_csv_content.= ',""';
+				}
+			}
+		}
+		
+		// Include personal information?
+		if (in_array(7,$this->post['data_include']))
+		{
+			$person = $report->incident_person;
+			if($person->loaded)
+			{
+				$expected_csv_content.= ',"'.$person->person_first.'"'
+										.',"'.$person->person_last.'"'
+										.',"'.$person->person_email.'"';
+			}
+			else
+			{
+				$expected_csv_content.= ',""'.',""'.',""';
+			}	
+		}	
+		
+		// Approved status
+		if ($report->incident_active)
+		{
+			$expected_csv_content.= ",\"YES\"";
+		}
+		else
+		{
+			$expected_csv_content.= ",\"NO\"";
+		}
+
+		// Verified Status
+		if ($report->incident_verified)
+		{
+			$expected_csv_content.= ",\"YES\"";
+		}
+		else
+		{
+			$expected_csv_content.= ",\"NO\"";
+		}
+		
+		// End Expected output
+		$expected_csv_content.= "\r\n";
+		
+		// Grab actual output
+		$actual_csv_content = download::download_csv($this->post, $this->incident, $this->custom_forms);
+		
+		// Test CSV Output
+		$this->assertEquals($expected_csv_content, $actual_csv_content, 'CSV Download failed. Content mismatch');
+	}
+} 
+
+?>
\ No newline at end of file
diff --git a/tests/phpunit/classes/helpers/Geocode_Test.php b/tests/phpunit/classes/helpers/Geocode_Test.php
new file mode 100644
index 0000000..4d52564
--- /dev/null
+++ b/tests/phpunit/classes/helpers/Geocode_Test.php
@@ -0,0 +1,47 @@
+<?php
+class Geocode_Helper_Test extends PHPUnit_Framework_TestCase {
+
+	
+	public function setUp()
+	{
+		// Test location - Wanaka, NZ
+		$this->lat = -44.696736;
+		$this->lng = 169.131646;
+	}
+
+
+	/**
+	 * Tests geocoder using google functions
+	 *
+	 * @test
+	 */
+	public function testGoogleGeocoder()
+	{
+		// reverse geocode
+		$address = geocode::reverseGoogle($this->lat, $this->lng);
+		$this->assertTrue($address !== FALSE);
+
+		// geocode
+		$result = geocode::google($address);
+		$this->assertEquals($result["latitude"], $this->lat, null, 0.01);
+		$this->assertEquals($result["longitude"], $this->lng, null, 0.01);
+	}
+
+	/**
+	 * Tests geocoder using google functions
+	 *
+	 * @test
+	 */
+	public function testNominatinGeocoder()
+	{
+		// reverse geocode
+		$address = geocode::reverseNominatim($this->lat, $this->lng);
+		$this->assertTrue($address !== FALSE);
+
+		// geocode
+		$result = geocode::nominatim($address);
+		$this->assertEquals($result["latitude"], $this->lat, null, 0.01);
+		$this->assertEquals($result["longitude"], $this->lng, null, 0.01);
+	}
+
+}
diff --git a/tests/phpunit/classes/helpers/Reports_Test.php b/tests/phpunit/classes/helpers/Reports_Test.php
new file mode 100755
index 0000000..16ae9a5
--- /dev/null
+++ b/tests/phpunit/classes/helpers/Reports_Test.php
@@ -0,0 +1,157 @@
+<?php
+class Reports_Helper_Test extends PHPUnit_Framework_TestCase {
+	
+	public function setUp()
+	{
+		// Report fields to be validated and saved
+		$this->post = array
+		(
+			'incident_title' => 'Test incident title',
+			'incident_description' => 'Testing reports helper for validation and saving of reports',
+			'incident_date' => date("m/d/Y",time()),
+			'incident_hour' => date('g'),
+			'incident_minute' => date('i'),
+			'incident_ampm' => date('a'),
+			'latitude' => '-2.18250',
+			'longitude' => '35.92056',
+			'location_name' => 'Random location for the unit test',
+			'country_name' => ORM::factory('country', Kohana::config('settings.default_country'))->country,
+			'incident_category' =>array(testutils::get_random_id('category', 'WHERE category_visible = 1')),
+			'incident_news' => array(),
+			'incident_video' => array(),
+			'incident_photo' => array(),
+			'person_first' => 'Test First Name',
+			'person_last' => 'Test Last Name',
+			'person_email' => 'testuser at example.com',
+			'form_id' => '',
+			'custom_field' => array()
+		);
+		
+	}
+	
+	public function tearDown()
+	{
+		unset ($this->post);
+	}
+	
+	/**
+	 * Tests the report submit action
+	 * @test
+	 */
+	public function testReportSubmit()
+	{
+		// Test if validation succeeds
+		$this->assertEquals(TRUE, reports::validate($this->post), 'Report validation failed');
+		
+		// Location model
+		$location = new Location_Model();
+		
+		// STEP 1: Save the location
+		reports::save_location($this->post, $location);
+		
+		// Test the save
+		$this->assertEquals(TRUE, intval($location->id) > 0, 'The location was not saved');
+		
+		// Incident model object
+		$incident = new Incident_Model();
+		
+		// STEP 2: Save the incident
+		reports::save_report($this->post, $incident, $location->id);
+		$this->assertEquals($location->id, $incident->location_id, 'Incident not associated with location');
+		
+		// Test if the incident has been saved
+		$this->assertEquals(TRUE, intval($incident->id) > 0);
+		
+		// STEP 3: Save the category
+		reports::save_category($this->post, $incident);
+		
+		// Test if the category has been saved
+		$category_count = ORM::factory('incident_category')->where('incident_id',$incident->id)->find_all()->count();
+		$this->assertEquals(TRUE, $category_count > 0, 'No entries in incident_categorgy for incident');
+		
+		// Save personal information
+		 reports::save_personal_info($this->post, $incident);
+		
+		// Test
+		$personal_info = ORM::factory('incident_person')->where('incident_id', $incident->id)->find_all()->count();
+		$this->assertEquals(TRUE, $personal_info > 0, 'No entries in incident_person for incident');
+		
+		// @todo Test for saving of incident media
+		
+		// Cleanup
+		ORM::factory('incident_category')->where('incident_id', $incident->id)->delete_all();
+		ORM::factory('incident_person')->where('incident_id', $incident->id)->delete_all();
+		$incident->delete();
+		$location->delete();
+	}
+	
+	/**
+	 * Tests reports::fetch_incidents()
+	 * @test
+	 *
+	 * This tests compares the output SQL of fetch_incidents against a pre-defined SQL
+	 * statement based on dummy values. The objective of this test is to check whether
+	 * reports::fetch_incidents processes the parameters property
+	 */
+	public function testFetchIncidents()
+	{
+		// Get random location and fetch the latitude and longitude
+		$location = ORM::factory('location', testutils::get_random_id('location'));
+		
+		$longitude = $location->longitude;
+		$latitude = $location->latitude;
+		
+		// Build the list of HTTP_GET parameters
+		$filter_params = array(
+			'c' => array(3, 4, 5),			// Category filters
+			'start_loc' => $latitude.",".$longitude, 		// Start location
+			'radius' => '20', 				// Location radius
+			'mode' => array(1,2),			// Incident mode
+			'm' => array(1),				// Media filter
+			'from' => '07/07/2011',			// Start date
+			'to' => '07/21/2011',			// End date
+			'v' => 1						// Verification filter
+		);
+		
+		// Add the report filter params to the list of HTTP_GET parameters
+		$_GET = array_merge($_GET, $filter_params);
+		
+		// Get the incidents
+		$incidents = reports::fetch_incidents();
+		
+		// Get the table prefix
+		$table_prefix = Kohana::config('database.default.table_prefix');
+		
+		// Expected SQL statement; based on the $filter_params above
+
+		// Distance calculation deets:
+		// 60 = nautical miles per degree of latitude, 1.1515 miles in every nautical mile, 1.609344 km = 1 km
+		// more details about the math here: http://sgowtham.net/ramblings/2009/08/04/php-calculating-distance-between-two-locations-given-their-gps-coordinates/
+
+		$expected_sql = "SELECT DISTINCT i.id incident_id, i.incident_title, i.incident_description, i.incident_date, "
+				. "i.incident_mode, i.incident_active, i.incident_verified, i.location_id, l.country_id, l.location_name, l.latitude, l.longitude "
+				. ", ((ACOS(SIN(".$latitude." * PI() / 180) * SIN(l.`latitude` * PI() / 180) + COS(".$latitude." * PI() / 180) * "
+				. "	COS(l.`latitude` * PI() / 180) * COS((".$longitude." - l.`longitude`) * PI() / 180)) * 180 / PI()) * 60 * 1.1515 * 1.609344) AS distance "
+				. "FROM ".$table_prefix."incident i "
+				. "LEFT JOIN ".$table_prefix."location l ON (i.location_id = l.id) "
+				. "LEFT JOIN ".$table_prefix."incident_category ic ON (ic.incident_id = i.id) "
+				. "LEFT JOIN ".$table_prefix."category c ON (ic.category_id = c.id) "
+				. "WHERE i.incident_active = 1 "
+				. "AND (c.id IN (".implode(",", $filter_params['c']).") OR c.parent_id IN (".implode(",", $filter_params['c']).")) "
+				. "AND c.category_visible = 1 "
+				. "AND i.incident_mode IN (".implode(",", $filter_params['mode']).") "
+					. "AND i.incident_date >= \"2011-07-07 00:00:00\" "
+				. "AND i.incident_date <= \"2011-07-21 23:59:59\" "
+				. "AND i.id IN (SELECT DISTINCT incident_id FROM ".$table_prefix."media WHERE media_type IN (".implode(",", $filter_params['m']).")) "
+				. "AND i.incident_verified IN (".$filter_params['v'].") "
+				. "HAVING distance <= ".$filter_params['radius']." "
+				. "ORDER BY i.incident_date DESC ";
+		
+		// Test the expected SQL against the returned
+		$this->assertEquals($expected_sql, $incidents->sql());
+		
+		// Garbage collection
+		unset ($location, $latitude, $longitude, $incidents, $filter_params);
+	}
+}
+?>
\ No newline at end of file
diff --git a/tests/phpunit/classes/hooks/Hook_Https_Check_Test.php b/tests/phpunit/classes/hooks/Hook_Https_Check_Test.php
new file mode 100755
index 0000000..83798d8
--- /dev/null
+++ b/tests/phpunit/classes/hooks/Hook_Https_Check_Test.php
@@ -0,0 +1,29 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+/**
+ * Unit test for the https_check hook
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   HTTPS Check Hook Unit Test
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Hook_Https_Check_Test extends PHPUnit_Framework_TestCase {
+    
+    /**
+     * Tests the protocol in $config['site_protocol'] against the URL
+     */
+    public function testSiteProtocol()
+    {
+        // Build the regular expression for site protocol
+        $site_protocol = '/'.Kohana::config('core.site_protocol').':\/\//';
+        
+        // Check if the url base contains the site protocol
+        $this->assertRegExp($site_protocol, url::base());
+    }
+}
+?>
\ No newline at end of file
diff --git a/tests/phpunit/classes/libraries/MY_Admin_Category_Api_Object_Test.php b/tests/phpunit/classes/libraries/MY_Admin_Category_Api_Object_Test.php
new file mode 100755
index 0000000..dc88dff
--- /dev/null
+++ b/tests/phpunit/classes/libraries/MY_Admin_Category_Api_Object_Test.php
@@ -0,0 +1,126 @@
+<?php defined('SYSPATH') or die('No direct script access');
+
+/**
+ * Unit tests for the Admin Category API
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+/**
+ * 
+ * @backupGlobals disabled
+ */
+class Admin_Category_Api_Object_Test extends PHPUnit_Framework_TestCase {
+
+    /**
+     * API controller object to run the tests
+     * @var Api_Controller
+     */
+    private $api_controller;
+
+    /**
+     * Initialize objects
+     */
+    protected function setUp()
+    {
+        $_SERVER = array_merge($_SERVER, array(
+            'REQUEST_METHOD' => 'POST',
+            'PHP_AUTH_USER' => 'admin',
+            'PHP_AUTH_PW' => 'admin',
+            'REQUEST_URI' => url::base().'api',
+        ));
+
+        // Instantiate the API controller
+        $this->api_controller = new Api_Controller();
+    }
+
+    /**
+     * Unset objects and variables aka Garbage collection
+     */
+    protected function tearDown()
+    {
+        unset ($this->api_controller);
+    }
+
+    /**
+     * Tests category submission
+     * @test
+     */
+    public function submitCategory()
+    {
+		$category_id = 0;
+
+		$_POST = array(
+			'action' => 'add',
+			'parent_id' => '0',
+			'category_title' => 'Test Category Title',
+			'category_description' => 'Testing admin category',
+			'category_color' => '00FF00',
+			'task' => 'category',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+
+		// Return the id of the test category for use in other test
+		$category_id = ORM::factory('category')->orderby('id',  'desc')->limit(1)->find();
+
+		return $category_id;
+    }
+
+    
+	/**
+	 * Tests edit category
+	 * @test
+	 * @depends submitCategory
+	 */
+	public function editCategory($category_id)
+	{
+		$_POST = array(
+			'action' => 'edit',
+			'parent_id' => '0',
+			'category_id' => $category_id,
+			'category_title' => 'Test Category Title Edited 2',
+			'category_description' => 'Testing admin category Edited',
+			'category_color' => '00FF00',
+			'task' => 'category',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+
+		return $category_id;
+	}
+
+	/**
+	 * Tests Category deletion
+	 * @test
+	 * @depends editCategory
+	 */
+	public function deleteCategory($category_id)
+	{
+		$_POST = array(
+			'action' => 'delete',
+			'category_id' => $category_id,
+			'task' => 'category',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+	}
+}
\ No newline at end of file
diff --git a/tests/phpunit/classes/libraries/MY_Admin_Reports_Api_Object_Test.php b/tests/phpunit/classes/libraries/MY_Admin_Reports_Api_Object_Test.php
new file mode 100755
index 0000000..a0cd25d
--- /dev/null
+++ b/tests/phpunit/classes/libraries/MY_Admin_Reports_Api_Object_Test.php
@@ -0,0 +1,258 @@
+<?php defined('SYSPATH') or die('No direct script access');
+
+/**
+ * Unit tests for the Admin Reports API
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+/**
+ * @backupGlobals disabled
+ */
+class Admin_Reports_Api_Object_Test extends PHPUnit_Framework_TestCase {
+
+	/**
+	 * API controller object to run the tests
+	 * @var Api_Controller
+	 */
+	private $api_controller;
+	
+	/**
+	 * Initialize objects
+	 */
+	protected function setUp()
+	{
+		$_SERVER = array_merge($_SERVER, array(
+			'REQUEST_METHOD' => 'POST',
+			'PHP_AUTH_USER' => 'admin',
+			'PHP_AUTH_PW' => 'admin',
+			'REQUEST_URI' => url::base().'api',
+		));
+
+		// Instantiate the API controller
+		$this->api_controller = new Api_Controller();
+	}
+
+	/**
+	 * Unset objects and variables aka Garbage collection
+	 */
+	protected function tearDown()
+	{
+		unset ($this->api_controller);
+	}
+
+	/**
+	 * Test report submission
+	 * @test
+	 */
+	public function submitReport()
+	{
+		$report_id = 0;
+
+		$_POST = array(
+			'task' => 'report',
+			'incident_title' => 'Test Sample Report Title',
+			'incident_description' => 'Test Sampe Report Description',
+			'incident_date' => '03/18/2011',
+			'incident_hour' => '10', 
+			'incident_minute' => '10',
+			'incident_ampm' => 'pm',
+			'incident_category' => '73,41	',
+			'latitude' => -1.28730007,
+			'longitude' => 36.82145118200820,
+			'location_name' => 'Accra',
+			'person_first' => 'Henry Addo',
+			'person_last' => 'Addo',
+			'person_email' => 'henry at ushahidi.com',
+			'incident_active' => 1,
+			'incident_verified' => 1,
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+
+		// Return the id of the test report for use in other test
+		$report_id = ORM::factory('incident')->orderby('id', 'desc')->limit(1)->find();
+
+		return $report_id;
+	}
+
+	/**
+	 * Tests retrieval of unapproved reports
+	 * @test
+	 */
+	public function retrieveUnapprovedReports()
+	{
+		$_POST = array(
+			'by' => 'unapproved',
+			'task' => 'reports',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+	}
+
+
+	/**
+	 * Test report approval.
+	 * @test
+	 * @depends submitReport
+	 */
+	public function approveReport($report_id)
+	{
+		$_POST = array(
+			'action' => 'approve',
+			'incident_id' => $report_id,
+			'task' => 'reports',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+
+		return $report_id;
+	}
+
+	/**
+	 * Tests retrieval of approved reports
+	 * @test
+	 */
+	public function retrieveApproveReports()
+	{
+		$_POST = array(
+			'by' => 'approved',
+			'task' => 'reports',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+	}
+
+	/**
+	 * Tests retrieval of unverified reports
+	 * @test
+	 */
+	public function retrieveUnverifiedReports()
+	{
+		$_POST = array(
+			'by' => 'unverified',
+			'task' => 'reports',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+	}
+
+	/**
+	 * Test report verification.
+	 * @test 
+	 * @depends approveReport
+	 */
+	public function verifyReport($report_id)
+	{
+		$_POST = array(
+			'action' => 'verify',
+			'incident_id' => $report_id,
+			'task' => 'reports',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+
+		return $report_id;
+	}
+
+	/**
+	 * Tests retrieval of verified reports
+	 * @test
+	 */
+	public function retrieveVerifiedReports()
+	{
+		$_POST = array(
+			'by' => 'verified',
+			'task' => 'reports',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+	}     
+            
+	/**
+	 * Test editing of report.
+	 * @test 
+	 * @depends verifyReport
+	 */
+	public function editReport($report_id)
+	{
+		$_POST = array(
+			'task' => 'reports',
+			'action' => 'edit',
+			'incident_id' => $report_id,
+			'incident_title' => 'Hello Ushahidi Edited',
+			'incident_description' => 'Description Edited',
+			'incident_date' => '03/18/2009',
+			'incident_hour' => '10', 
+			'incident_minute' => '10',
+			'incident_ampm' => 'pm',
+			'incident_category' => '73,41',
+			'latitude' => -1.28730007,
+			'longitude' => 36.82145118200820,
+			'location_id'=> 2,
+			'location_name' => 'accra',
+			'person_first' => 'Henry Addo',
+			'person_last' => 'Addo',
+			'incident_active' => 1,
+			'incident_verified' => 1,
+			'person_email' => 'henry at ushahidi.com',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+
+		return $report_id;
+	}
+
+	/**
+	 * Test report deletion.
+	 * @test 
+	 * @depends editReport
+	 */
+	public function deleteReport($report_id)
+	{
+		$_POST = array(
+			'form_auth_token' => csrf::token(),
+			'action' => 'delete',
+			'incident_id' => $report_id,
+			'task' => 'reports',
+		);
+
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		$this->assertEquals(0, $contents->error->code, $contents->error->message);
+	}
+
+}
diff --git a/tests/phpunit/classes/libraries/MY_Auth_Test.php b/tests/phpunit/classes/libraries/MY_Auth_Test.php
new file mode 100644
index 0000000..d760956
--- /dev/null
+++ b/tests/phpunit/classes/libraries/MY_Auth_Test.php
@@ -0,0 +1,108 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Auth library Unit Test
+ *
+ * @author 		Ushahidi Team
+ * @package 	Ushahidi
+ * @category 	Unit Tests
+ * @copyright 	(c) 2008-2011 Ushahidi Inc <http://www.ushahidi.com>
+ * @license 	For license information, see License.txt
+ */
+class MY_Auth_Test extends PHPUnit_Framework_TestCase {
+	
+	public function setUp()
+	{
+		$_SESSION = array();
+		
+		// create test permission
+		$permission = ORM::factory('permission');
+		$permission->name = 'phpunit_access';
+		$permission->id = 99999;
+		$permission->save();
+		
+		// create test role
+		$role = ORM::factory('role');
+		$role->name = 'phpunit';
+		$role->id = 99999;
+		$role->add($permission);
+		$role->save();
+		
+		// Custom admin role
+		$role = ORM::factory('role');
+		$role->name = 'phpunit_admin';
+		$role->id = 99998;
+		$role->add(ORM::factory('permission','admin_ui'));
+		$role->save();
+	}
+	
+	public function tearDown()
+	{
+		//delete fake roles & permissions
+		ORM::factory('permission',99999)->delete();
+		ORM::factory('role',99999)->delete();
+		ORM::factory('role',99998)->delete();
+	}
+	
+	public function testPermissions()
+	{
+		// not logged in: return false
+		$this->assertFalse( Auth::instance()->has_permission('admin_ui') );
+		
+		// member: return false
+		$user = new User_Model();
+		$user->add(ORM::factory('role','member'));
+		$this->assertFalse( Auth::instance()->has_permission('admin_ui', $user) );
+		$this->assertTrue( Auth::instance()->has_permission('member_ui', $user) );
+		
+		// admin: return true
+		$user = new User_Model();
+		$user->add(ORM::factory('role','admin'));
+		$this->assertTrue( Auth::instance()->has_permission('admin_ui', $user) );
+		
+		// superadmin: return false
+		$user = new User_Model();
+		$user->add(ORM::factory('role','superadmin'));
+		$this->assertTrue( Auth::instance()->has_permission('admin_ui', $user) );
+		$this->assertTrue( Auth::instance()->has_permission('any_permission', $user) );
+		
+		// Test custom role/permission
+		$user = new User_Model();
+		$user->add(ORM::factory('role','phpunit'));
+		$this->assertTrue( Auth::instance()->has_permission('phpunit_access', $user) );
+		
+	}
+	
+	public function testAdminAccess()
+	{
+		// not logged in: return false
+		$this->assertFalse( Auth::instance()->admin_access() );
+		
+		// Login role : return false
+		$user = new User_Model();
+		$user->add(ORM::factory('role','login'));
+		$this->assertFalse( Auth::instance()->admin_access($user) );
+		
+		// member: return false
+		$user = new User_Model();
+		$user->add(ORM::factory('role','member'));
+		$this->assertFalse( Auth::instance()->admin_access($user) );
+		
+		// admin: return true
+		$user = new User_Model();
+		$user->add(ORM::factory('role','admin'));
+		$this->assertTrue( Auth::instance()->admin_access($user) );
+		
+		// superadmin: return false
+		$user = new User_Model();
+		$user->add(ORM::factory('role','superadmin'));
+		$this->assertTrue( Auth::instance()->admin_access($user) );
+		
+		// Test custom role/permission
+		$user = new User_Model();
+		$user->add(ORM::factory('role','phpunit'));
+		$this->assertFalse( Auth::instance()->admin_access($user) );
+		$user->add(ORM::factory('role','phpunit_admin'));
+		$this->assertTrue( Auth::instance()->admin_access($user) );
+	}
+}
+	
\ No newline at end of file
diff --git a/tests/phpunit/classes/libraries/MY_Countries_Api_Object_Test.php b/tests/phpunit/classes/libraries/MY_Countries_Api_Object_Test.php
new file mode 100755
index 0000000..fd62f21
--- /dev/null
+++ b/tests/phpunit/classes/libraries/MY_Countries_Api_Object_Test.php
@@ -0,0 +1,145 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Unit tests for the countries API
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Countries_Api_Object_Test extends PHPUnit_Framework_TestCase {
+	
+	/**
+	 * API Controller object to run the tests
+	 * @var Api_Controller
+	 */
+	private $api_controller;
+	
+	protected function setUp()
+	{
+		// $_SERVER values
+		$_SERVER = array_merge($_SERVER, array(
+			'REQUEST_METHOD' => 'GET',
+			'REQUEST_URI' => url::base().'api/?task=countries',
+		));
+		
+		// Instantiate the API controller
+		$this->api_controller = new Api_Controller();
+	}
+	
+	protected function tearDown()
+	{
+		// Garbage collection
+		unset ($this->api_controller);
+	}
+	
+	/**
+	 * Tests fetching all countries
+	 * @test
+	 */
+	public function testGetCountriesByAll()
+	{
+		// HTTP GET values
+		$_GET = array(
+			'task' => 'countries'
+		);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals(0, (int)$contents->error->code);
+		
+	}
+	
+	/**
+	 * Tests getting a country using the ISO code
+	 * @test
+	 */
+	public function testGetCountryByISO()
+	{
+		// Test fetching by ISO code
+		$country = ORM::factory('country', testutils::get_random_id('country'));
+		
+		// Set the HTTP GET data
+		$_GET = array(
+			'task' => 'countries',
+			'by' => 'countryiso', 
+			'iso' => $country->iso
+		);
+		
+		// Fetcht the content
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		// No error should be returned and ISO code should match that of the loaded country
+		$this->assertEquals($country->iso, $contents->payload->countries[0]->country->iso);
+		$this->assertEquals(0, (int)$contents->error->code);
+	}
+	
+	/**
+	 * Tests fetching countries by country name
+	 * @test
+	 */
+	public function testGetCountryByName()
+	{
+		// Test fetching by ISO code
+		$country = ORM::factory('country', testutils::get_random_id('country'));
+		
+		// Test fetching by country name
+		$_GET = array(
+			'task' => 'countries',
+			'by' => 'countryname', 
+			'name' => $country->country
+		);
+		
+		// Fetch the content
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals($country->country, $contents->payload->countries[0]->country->name);
+		$this->assertEquals(0, (int)$contents->error->code);
+	}
+	
+	/**
+	 * Tests fetching countries by country ID
+	 * @test
+	 */
+	public function testGetCountryById()
+	{
+		// Test fetching by ISO code
+		$country = ORM::factory('country', testutils::get_random_id('country'));
+		
+		// Test fetching countries by database id
+		$_GET = array(
+			'task' => 'countries',
+			'by' => 'countryid', 
+			'id' => $country->id
+		);
+		
+		// Fetch the content
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+	
+		$this->assertEquals($country->id, $contents->payload->countries[0]->country->id);
+		$this->assertEquals(0, (int)$contents->error->code);
+		
+		// Test "No Data Found"
+		// Test using invalid country id
+		$_GET['id'] = 'PL';
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+	
+		$this->assertEquals("007", $contents->error->code);
+		
+	}
+}
+?>
\ No newline at end of file
diff --git a/tests/phpunit/classes/libraries/MY_Incidents_Api_Object_Test.php b/tests/phpunit/classes/libraries/MY_Incidents_Api_Object_Test.php
new file mode 100755
index 0000000..4529172
--- /dev/null
+++ b/tests/phpunit/classes/libraries/MY_Incidents_Api_Object_Test.php
@@ -0,0 +1,397 @@
+<?php defined('SYSPATH') or die('No direct script access');
+/**
+ * Unit tests for the incidents API
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Incidents_Api_Object_Test extends PHPUnit_Framework_TestCase {
+	
+	/**
+	 * API controller object to run the tests
+	 * @var Api_Controller
+	 */
+	private $api_controller;
+	
+	/**
+	 * Database object for straight SQL queries
+	 * @var Database
+	 */
+	private $db;
+	
+	/**
+	 * Database table prefix
+	 * @var string
+	 */
+	private $table_prefix;
+	
+	protected function setUp()
+	{
+		if (ORM::factory('incident')->count_all() == 0)
+		{
+			$this->markTestSkipped('The incident table is empty.');
+		}
+		else
+		{
+			// $_SERVER values
+			$_SERVER = array_merge($_SERVER, array(
+				'REQUEST_METHOD' => 'GET',
+				'REQUEST_URI' => url::base().'api/?task=incidents',
+			));
+
+			// Instantiate the API controller
+			$this->api_controller = new Api_Controller();
+			
+			// Instantiate the DB object
+			$this->db = new Database();
+			
+			// Table prefix
+			$this->table_prefix = Kohana::config('database.default.table_prefix');
+		}
+	}
+	
+	protected function tearDown()
+	{
+		// Garbage collection
+		unset ($this->api_controller, $this->db, $this->table_prefix);
+	}
+	
+	/**
+	 * Tests fetching incidents when only the task is specified - without any
+	 * extra parameters
+	 * @test
+	 */
+	public function testGetIncidentsByAll()
+	{
+		// HTTP GET data
+		$_GET = array('task' => 'incidents');
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		// Assert the results
+		$this->assertEquals("0", $contents->error->code);
+		$this->assertEquals(count(Incident_Model::get_incidents(array(), 20)), count($contents->payload->incidents));
+	}
+	
+	/**
+	 * Tests fetching of incidents when the limit parameter is specified
+	 * @test
+	 */
+	public function testGetIncidentsByLimit()
+	{
+		// Randomly generate the record limit
+		$limit = rand(1, Incident_Model::get_incidents()->count());
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'limit' => $limit);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals("0", $contents->error->code);
+		$this->assertEquals($limit, count($contents->payload->incidents));
+	}
+	
+	/**
+	 * Tests fetching an incident by its database ID. 
+	 * The operation should return a single record on succeed
+	 * @test
+	 */
+	public function testGetIncidentsById()
+	{
+		// Get a random incident id
+		$incident_id = testutils::get_random_id('incident', 'WHERE incident_active = 1');
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'by' => 'incidentid', 'id'=> $incident_id);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals("0", $contents->error->code);
+		$this->assertEquals($incident_id, (int)$contents->payload->incidents[0]->incident->incidentid);
+		
+	}
+	
+	/**
+	 * Tests fetching incidents by the ID of an incident that has not been
+	 * approved
+	 */
+	public function testGetIncidentsByUnapprovedIncidentId()
+	{
+		// Get a random incident
+		$incident = ORM::factory('incident', testutils::get_random_id('incident'));
+		
+		// Get the current incident status
+		$incident_active = $incident->incident_active;
+		$incident->incident_active = 0;
+		$incident->save();
+		
+		// Verify that incident_active = 0
+		$this->assertEquals(0, $incident->incident_active);
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'by' => 'incidentid', 'id'=> $incident->id);
+	
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		// Verify that no records have been returned
+		$this->assertEquals("007", $contents->error->code, sprintf("No records should be returned for incident %d", $incident->id));
+		
+		// Change the incident_active back to its original value
+		$incident->incident_active = $incident_active;
+		$incident->save();
+		
+		// Garbage collection
+		unset ($incident, $incident_active);
+	}
+	
+	/**
+	 * Gets incidents by lat/lon
+	 * @test
+	 */
+	public function testGetIncidentsByLatLon()
+	{
+		// @todo Get random lat/lon
+	}
+	
+	/**
+	 * Gets incidents by location id
+	 * @test
+	 */
+	public function testGetIncidentsByLocationId()
+	{
+		// Get location_id from a randomly selected incident
+		$location_id = testutils::get_random_id('location', 
+			'WHERE id IN (SELECT location_id FROM '.$this->table_prefix.'incident WHERE incident_active = 1)');
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'by' => 'locid', 'id'=> $location_id);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		// Vary test depending on the incident status
+		$this->assertEquals("0", $contents->error->code, sprintf("No records found for location id: %d", $location_id));
+		
+		// Garbage collection
+		unset($location_id);
+	}
+	
+	/**
+	 * Gets incidents by location name
+	 * @test
+	 */
+	public function testGetIncidentsByLocationName()
+	{
+		// Get random location id
+		$location_id = testutils::get_random_id('location', 
+			'WHERE id IN (SELECT location_id FROM '.$this->table_prefix.'incident WHERE incident_active = 1)');
+		
+		// Get the location name
+		$location_name = ORM::factory('location', $location_id)->location_name;
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'by' => 'locname', 'name'=> $location_name);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		// Vary test depending on the incident status
+		$this->assertEquals("0", $contents->error->code, sprintf("No data found for location :%s", $location_name));
+		
+		// Garbage collection
+		unset ($location_id, $location_name);
+	}
+	
+	/**
+	 * Gets incidents by catgory id that is visible
+	 * @test
+	 */
+	public function testGetIncidentsByCategoryId()
+	{
+		// Get a random category id
+		$category = ORM::factory('incident_category', testutils::get_random_id('incident_category'))->category;
+		
+		$category_visible = $category->category_visible;
+		$category->category_visible = 1;
+		$category->save();
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'by' => 'catid', 'id'=> $category->id);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals(0, (int)$contents->error->code, sprintf("No incidents for category id %d", $category->id));
+		
+		// Get random index for the payload data items
+		$index = rand(0, count($contents->payload->incidents) - 1);
+		$this->assertGreaterThanOrEqual(1, count($contents->payload->incidents[$index]->categories));
+		
+		// Set the category back to its original visibile status
+		$category->category_visible = $category_visible;
+		$category->save();
+		
+		// Garbage collection
+		unset ($category, $category_visible);
+	}
+	
+	/**
+	 * Tests fetching of incidents via a category id that is invisible
+	 */
+	public function testIncidentsByInvisibleCategoryId()
+	{
+		// Get random category
+		$incident_category = ORM::factory('incident_category', testutils::get_random_id('incident_category'));
+		
+		if ( ! $incident_category->loaded)
+		{
+			// Skip test if not loaded
+			$this->markTestSkipped('There are no entries in the incident_category table');
+		}
+		else
+		{
+			// Get the category
+			$category = $incident_category->category;
+			
+			// Verify that the category is loaded
+			$this->assertTrue($category->loaded, 'The category could not be loaded');
+			
+			// Verify the category id
+			$category_visible = $category->category_visible;
+				
+			// Set the category visibility to 0
+			$category->category_visible = 0;
+			
+			// Save
+			$category->save();
+			
+			// Verify that the category visibility is 0
+			$this->assertEquals(0, $category->category_visible, sprintf("Category %d is still visible", $category->id));
+			
+			// HTTP GET data
+			$_GET = array('task' => 'incidents', 'by' => 'catid', 'id'=> $category->id);
+		
+			ob_start();
+			$this->api_controller->index();
+			$contents = json_decode(ob_get_clean());
+		
+			// Category is invisible, therefore no records should be returned
+			$this->assertEquals("007", $contents->error->code, 
+				sprintf("Catgeory %d is invisible therefore no records should be returned", $category->id));
+			
+			// Set the category visibility back to its original value
+			$category->category_visible = $category_visible;
+			$category->save();
+				
+			$this->assertEquals($category_visible, $category->category_visible, "Category visibility not changed back to original value");
+			
+			// Garbage collection
+			unset ($category);
+		}
+		
+		// Garbage collection
+		unset ($incident_category);
+	}
+	
+	/**
+	 * Tests fetching incidents by category name
+	 */
+	public function testGetIncidentsByCategoryName()
+	{
+		// Get random incident category record
+		$category_id = testutils::get_random_id('category', 
+			'WHERE category_visible = 1 AND id IN (SELECT category_id FROM '.$this->table_prefix.'incident_category)');
+
+		// Get the category name
+		$category_name = ORM::factory('category', $category_id)->category_title;
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'by' => 'catname', 'name'=> $category_name);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		// Test for successful execution
+		$this->assertEquals("0", $contents->error->code, sprintf("No data found for the '%s' category", $category_name));
+		
+		// Garbage collection
+		unset ($category_id, $category_name);
+	}
+	
+	/**
+	 * Tests fetching incidents using the sinceid parameter
+	 */
+	public function testGetIncidentsBySinceId()
+	{
+		// Get random incident id - it must be active and should not be the last record in the table
+		$incident_id = testutils::get_random_id('incident', 'WHERE incident_active = 1 AND id NOT IN(SELECT * FROM (SELECT id from incident where incident_active = 1 ORDER BY id DESC LIMIT 1) as last_id)');
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'by' => 'sinceid', 'id'=> $incident_id);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals("0", $contents->error->code);
+		
+		// Get random index for the payload data
+		$index = rand(0, count($contents->payload->incidents) - 1);
+		
+		// Fetched incidents should have an id greather than the search parameter
+		$this->assertGreaterThan($incident_id, (int)$contents->payload->incidents[$index]->incident->incidentid);
+	}
+	
+	public function testGetIncidentsByMaxId()
+	{
+		// Get random incident id - it must be active and should not be first record in the table
+		$incident_id = testutils::get_random_id('incident', 'WHERE incident_active = 1 AND id NOT IN(SELECT * FROM (SELECT id from incident where incident_active = 1 ORDER BY id ASC LIMIT 1) as first_id)');
+		
+		// HTTP GET data
+		$_GET = array('task' => 'incidents', 'by' => 'maxid', 'id'=> $incident_id);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		// Test for successful execution
+		$this->assertEquals("0", $contents->error->code);
+		
+		// Get random index for the payload data
+		$index = rand(0, count($contents->payload->incidents) - 1);
+		
+		// Fetched incidents should have an id less than or equal to the search parameter
+		$this->assertLessThanOrEqual($incident_id, (int)$contents->payload->incidents[$index]->incident->incidentid);
+		
+	}
+	
+	/**
+	 * Tests fetching incidents by a bounded area
+	 * @test
+	 */
+	public function testGetIncidentsByBounds()
+	{
+		// @todo Helper method to generate a bounding box
+	}
+	
+}
+?>
diff --git a/tests/phpunit/classes/libraries/MY_Locations_Api_Object_Test.php b/tests/phpunit/classes/libraries/MY_Locations_Api_Object_Test.php
new file mode 100755
index 0000000..3524a1c
--- /dev/null
+++ b/tests/phpunit/classes/libraries/MY_Locations_Api_Object_Test.php
@@ -0,0 +1,73 @@
+<?php
+class Locations_Api_Object_Test extends PHPUnit_Framework_TestCase {
+	
+	/**
+	 * API Controller object to be used for issuing API requests
+	 * @var Api_Controller
+	 */
+	private $api_controller;
+	
+	protected function setUp()
+	{
+		if (ORM::factory('location')->count_all() == 0)
+		{
+			// Skip the test
+			$this->markTestSkipped('The locations table is empty');
+		}
+		else
+		{
+			// $_SERVER values
+			$_SERVER = array_merge($_SERVER, array(
+				'REQUEST_METHOD' => 'GET',
+				'REQUEST_URI' => url::base().'api/?task=locations',
+			));
+		
+			$this->api_controller = new Api_Controller();
+		}
+	}
+	
+	protected function tearDown()
+	{
+		// Garbage collection
+		unset ($this->api_controller);
+	}
+	
+	/**
+	 * Tests fetching all locations
+	 */
+	public function testGetAllLocations()
+	{
+		$_GET = array('task' => 'locations');
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals("0", $contents->error->code);
+	}
+	
+	public function testGetLocationsByName()
+	{
+		
+	}
+	
+	/**
+	 * Tests fetching of locations by location id
+	 */
+	public function testGetLocationsByLocationId()
+	{
+		// Get a random location
+		$location_id = testutils::get_random_id('location');
+		
+		// Parameters to submit
+		$_GET = array('task' => 'locations', 'by' => 'locid', 'id' => $location_id);
+		
+		ob_start();
+		$this->api_controller->index();
+		$contents = json_decode(ob_get_clean());
+		
+		$this->assertEquals("0", $contents->error->code);
+		$this->assertEquals($location_id, (int)$contents->payload->locations[0]->location->id);
+	}
+}
+?>
\ No newline at end of file
diff --git a/tests/phpunit/classes/libraries/Requirements_Test.php b/tests/phpunit/classes/libraries/Requirements_Test.php
new file mode 100644
index 0000000..6d3ab14
--- /dev/null
+++ b/tests/phpunit/classes/libraries/Requirements_Test.php
@@ -0,0 +1,320 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Requirements library Unit Test
+ *
+ * @author 		Ushahidi Team
+ * @package 	Ushahidi
+ * @category 	Unit Tests
+ * @copyright 	(c) 2008-2011 Ushahidi Inc <http://www.ushahidi.com>
+ * @license 	For license information, see License.txt
+ */
+class Requirements_Test extends PHPUnit_Framework_TestCase {
+	
+	public function setUp()
+	{
+		
+	}
+	
+	public function tearDown()
+	{
+		
+	}
+	
+	function testExternalUrls() {
+		$backend = Requirements::backend();
+		$backend->set_combined_files_enabled(true);
+
+		$backend->js('http://www.mydomain.com/test.js');
+		$backend->js('https://www.mysecuredomain.com/test2.js');
+		$backend->css('http://www.mydomain.com/test.css');
+		$backend->css('https://www.mysecuredomain.com/test2.css');
+		
+		$html = $backend->render();
+		
+		$this->assertContains(
+			'http://www.mydomain.com/test.js',
+			$html,
+			'Load external javascript URL'
+		);
+		$this->assertContains(
+			'https://www.mysecuredomain.com/test2.js',
+			$html,
+			'Load external secure javascript URL'
+		);
+		$this->assertContains(
+			'http://www.mydomain.com/test.css', 
+			$html,
+			'Load external CSS URL'
+		);
+		$this->assertContains(
+			'https://www.mysecuredomain.com/test2.css', 
+			$html,
+			'Load external secure CSS URL'
+		);
+		
+		// This should replace test.css above
+		// @todo move to seperate test
+		$backend->css('https://www.anything.com/test.css');
+		$html = $backend->render();
+		$this->assertContains(
+			'https://www.anything.com/test.css', 
+			$html,
+			'Load external CSS URL'
+		);
+		$this->assertNotContains(
+			'https://www.mydomain.com/test.css', 
+			$html,
+			'Load external CSS URL'
+		);
+	}
+
+	protected function setupCombinedRequirements($backend) {
+		$backend->clear();
+		//$backend->setCombinedFilesFolder('assets');
+
+		// clearing all previously generated requirements (just in case)
+		$backend->clear_combined_files();
+		$backend->delete_combined_files('RequirementsTest_bc.js');
+
+		// require files normally (e.g. called from a FormField instance)
+		$backend->js('tests/phpunit/data/RequirementsTest_a.js');
+		$backend->js('tests/phpunit/data/RequirementsTest_b.js');
+		$backend->js('tests/phpunit/data/RequirementsTest_c.js');
+
+		// require two of those files as combined includes
+		$backend->combine_files(
+			'RequirementsTest_bc.js',
+			array(
+				'tests/phpunit/data/RequirementsTest_b.js',
+				'tests/phpunit/data/RequirementsTest_c.js'
+			)
+		);
+	}
+	
+	protected function setupCombinedNonrequiredRequirements($backend) {
+			$backend->clear();
+			//$backend->setCombinedFilesFolder('assets');
+	
+			// clearing all previously generated requirements (just in case)
+			$backend->clear_combined_files();
+			$backend->delete_combined_files('RequirementsTest_bc.js');
+	
+			// require files as combined includes
+			$backend->combine_files(
+				'RequirementsTest_bc.js',
+				array(
+					'data/forms/RequirementsTest_b.js',
+					'data/forms/RequirementsTest_c.js'
+				)
+			);
+		}
+
+	function testCombinedJavascript() {
+		$backend = new Requirements_Backend;
+		$backend->set_combined_files_enabled(TRUE);
+		//$backend->setCombinedFilesFolder('/media/uploads');
+
+		$this->setupCombinedRequirements($backend);
+		
+		$combinedFilePath = DOCROOT . 'media/uploads/' . 'RequirementsTest_bc.js';
+
+		$html = $backend->render();
+
+		/* COMBINED JAVASCRIPT FILE IS INCLUDED IN HTML HEADER */
+		$this->assertContains('media/uploads/RequirementsTest_bc.js', $html, 'combined javascript file is included in html header');
+		
+		/* COMBINED JAVASCRIPT FILE EXISTS */
+		$this->assertFileExists($combinedFilePath, 'combined javascript file exists');
+		
+		/* COMBINED JAVASCRIPT HAS CORRECT CONTENT */
+		$combinedFileContents = @file_get_contents($combinedFilePath);
+		$this->assertContains("alert('b')", $combinedFileContents, 'combined javascript has correct content');
+		$this->assertContains("alert('c')", $combinedFileContents, 'combined javascript has correct content');
+		
+		/* COMBINED FILES ARE NOT INCLUDED TWICE */
+		$this->assertNotContains('RequirementsTest_b.js', $html, 'combined files are not included twice');
+		$this->assertNotContains('RequirementsTest_c.js', $html, 'combined files are not included twice');
+		
+		/* NORMAL REQUIREMENTS ARE STILL INCLUDED */
+		$this->assertContains('RequirementsTest_a.js', $html, 'normal requirements are still included');
+
+		$backend->delete_combined_files('RequirementsTest_bc.js');
+		
+		// Then do it again, this time not requiring the files beforehand
+		$backend = new Requirements_Backend;
+		$backend->set_combined_files_enabled(true);
+		$backend->setCombinedFilesFolder('assets');
+
+		$this->setupCombinedNonrequiredRequirements($backend);
+		
+		$combinedFilePath = DOCROOT . 'media/uploads/' . 'RequirementsTest_bc.js';
+
+		$html = $backend->render();
+
+		/* COMBINED JAVASCRIPT FILE IS NOT INCLUDED IN HTML HEADER */
+		$this->assertNotContains('media/uploads/RequirementsTest_bc.js', $html, 'combined javascript file is included in html header');
+		
+		/* COMBINED JAVASCRIPT FILE EXISTS */
+		clearstatcache(); // needed to get accurate file_exists() results
+		$this->assertFileNotExists($combinedFilePath, 'combined javascript file exists');
+		
+		/* COMBINED FILES ARE NOT INCLUDED TWICE */
+		$this->assertNotContains('RequirementsTest_b.js', $html, 'combined files are not included twice');
+		$this->assertNotContains('RequirementsTest_c.js', $html, 'combined files are not included twice');
+
+		$backend->delete_combined_files('RequirementsTest_bc.js');
+	}
+	
+	function testBlockedCombinedJavascript() {
+		$backend = new Requirements_Backend;
+		$backend->set_combined_files_enabled(true);
+		$backend->setCombinedFilesFolder('assets');
+		$combinedFilePath = DOCROOT . '/media/uploads/' . 'RequirementsTest_bc.js';
+
+		/* BLOCKED COMBINED FILES ARE NOT INCLUDED */
+		$this->setupCombinedRequirements($backend);
+		$backend->block('RequirementsTest_bc.js');
+		$backend->delete_combined_files('RequirementsTest_bc.js');
+
+		clearstatcache(); // needed to get accurate file_exists() results
+		$html = $backend->render();
+
+		$this->assertNotContains('RequirementsTest_bc.js', $html, 'blocked combined files are not included ');
+		$backend->unblock('RequirementsTest_bc.js');
+
+		/* BLOCKED UNCOMBINED FILES ARE NOT INCLUDED */
+		$this->setupCombinedRequirements($backend);
+		$backend->block('tests/phpunit/data/RequirementsTest_b.js');
+		$backend->delete_combined_files('RequirementsTest_bc.js');
+		clearstatcache(); // needed to get accurate file_exists() results
+		$html = $backend->render();
+		$combinedFileContents = @file_get_contents($combinedFilePath);
+		$this->assertNotContains("alert('b')", (string)$combinedFileContents, 'blocked uncombined files are not included');
+		$backend->unblock('RequirementsTest_b.js');
+		
+		/* A SINGLE FILE CAN'T BE INCLUDED IN TWO COMBINED FILES */
+		$this->setupCombinedRequirements($backend);
+		clearstatcache(); // needed to get accurate file_exists() results
+
+		// This throws a notice-level error, so we prefix with @
+		@$backend->combine_files(
+			'RequirementsTest_ac.js',
+			array(
+				'tests/phpunit/data/RequirementsTest_a.js',
+				'tests/phpunit/data/RequirementsTest_c.js'
+			)
+		);
+
+		$combinedFiles = $backend->get_combine_files();
+
+		$this->assertEquals(
+			array_keys($combinedFiles['js']),
+			array('RequirementsTest_bc.js'),
+			"A single file can't be included in two combined files"
+		);
+		
+		$backend->delete_combined_files('RequirementsTest_bc.js');
+	}
+	
+	function testArgsInUrls() {
+		$backend = new Requirements_Backend;
+		$backend->set_combined_files_enabled(TRUE);
+		$backend->set_suffix_requirements(TRUE);
+
+		$backend->js('tests/phpunit/data/RequirementsTest_a.js?test=1&test=2&test=3');
+		$backend->css('tests/phpunit/data/RequirementsTest_a.css?test=1&test=2&test=3');
+		$backend->delete_combined_files('RequirementsTest_bc.js');
+
+		$html = $backend->render();
+
+		/* Javascript has correct path */
+		$this->assertRegexp('#src=".*\/RequirementsTest_a\.js\?m=\d\d+&test=1&test=2&test=3#', $html, 'javascript has correct path and mtime suffix');
+
+		/* CSS has correct path */
+		$this->assertRegexp('#href=".*\/RequirementsTest_a\.css\?m=\d\d+&test=1&test=2&test=3#', $html, 'css has correct path and mtime suffix');
+		
+		// Testing again without mtime suffix
+		$backend->set_suffix_requirements(FALSE);
+
+		$html = $backend->render();
+
+		/* Javascript has correct path */
+		$this->assertRegexp('#src=".*\/RequirementsTest_a\.js\?test=1&test=2&test=3#', $html, 'javascript has correct path');
+
+		/* CSS has correct path */
+		$this->assertRegexp('#href=".*\/RequirementsTest_a\.css\?test=1&test=2&test=3#', $html, 'css has correct path');
+	}
+	
+	function testRequirementsBackend() {
+		$backend = new Requirements_Backend();
+		$backend->js('tests/phpunit/data/a.js');
+		
+		$this->assertCount(1, $backend->get_js(), "There should be only 1 file included in required javascript.");
+		$this->assertContains('tests/phpunit/data/a.js', $backend->get_js(), "/tests/phpunit/data/a.js should be included in required javascript.");
+		
+		$backend->js('tests/phpunit/data/b.js');
+		$this->assertCount(2, $backend->get_js(), "There should be 2 files included in required javascript.");
+		
+		$backend->block('tests/phpunit/data/a.js');
+		$this->assertCount(1, $backend->get_js(), "There should be only 1 file included in required javascript.");
+		$this->assertNotContains('tests/phpunit/data/a.js', $backend->get_js(), "/tests/phpunit/data/a.js should not be included in required javascript after it has been blocked.");
+		$this->assertContains('tests/phpunit/data/b.js', $backend->get_js(), "/tests/phpunit/data/b.js should be included in required javascript.");
+		
+		$backend->css('tests/phpunit/data/a.css');
+		$this->assertCount(1, $backend->get_css(), "There should be only 1 file included in required css.");
+		$this->assertArrayHasKey('a.css', $backend->get_css(), "/tests/phpunit/data/a.css should be in required css.");
+		$this->assertContains(array('file' => 'tests/phpunit/data/a.css', 'media' => null), $backend->get_css(), "/tests/phpunit/data/a.css should be in required css.");
+		
+		$backend->block('tests/phpunit/data/a.css');
+		$this->assertCount(0, $backend->get_css(), "There should be nothing in required css after file has been blocked.");
+		
+		// Test unblock_all()
+		$backend->unblock_all();
+		$this->assertCount(2, $backend->get_js(), "There should be only 2 files included in required css.");
+		$this->assertCount(1, $backend->get_css(), "There should be only 1 file included in required javascript.");
+		
+		// Testing clear()
+		$backend->js('tests/phpunit/data/c.css');
+		$backend->clear();
+		$this->assertCount(0, $backend->get_css(), "There should be nothing in required css after requirements cleared.");
+		$this->assertCount(0, $backend->get_js(), "There should be nothing in required js after requirements cleared.");
+		
+		// Testing js block by id
+		$backend->js('tests/phpunit/data/a.js');
+		$backend->block('a.js');
+		$this->assertCount(0, $backend->get_js(), "There should be nothing in required js after file has be blocked.");
+		
+		// Testing css block by id
+		$backend->css('tests/phpunit/data/a.css');
+		$backend->block('a.css');
+		$this->assertCount(0, $backend->get_css(), "There should be nothing in required css after file has been blocked.");
+		
+		// Testing unblock
+		$backend->unblock('a.js');
+		$this->assertCount(1, $backend->get_js(), "There should be only 1 file included in required javascript.");
+	}
+	
+	function testBlockedInRender() {
+		$backend = new Requirements_Backend();
+		$backend->js('tests/phpunit/data/RequirementsTest_a.js');
+		$backend->js('tests/phpunit/data/RequirementsTest_a.css');
+		$backend->js('tests/phpunit/data/RequirementsTest_b.js');
+		$backend->js('tests/phpunit/data/RequirementsTest_b.css');
+		$backend->js('tests/phpunit/data/RequirementsTest_c.js');
+		$backend->js('tests/phpunit/data/RequirementsTest_c.css');
+		
+		$backend->block('tests/phpunit/data/RequirementsTest_a.js');
+		$backend->block('tests/phpunit/data/RequirementsTest_a.css');
+		$backend->block('RequirementsTest_b.js');
+		$backend->block('RequirementsTest_b.css');
+		
+		$html = $backend->render();
+		$this->assertNotContains('tests/phpunit/data/RequirementsTest_a.js', $html, 'RequirementsTest_a.js was blocked and should not appear in rendered HTML');
+		$this->assertNotContains('tests/phpunit/data/RequirementsTest_a.css', $html, 'RequirementsTest_a.css was blocked and should not appear in rendered HTML');
+		$this->assertNotContains('tests/phpunit/data/RequirementsTest_b.js', $html, 'RequirementsTest_b.js was blocked and should not appear in rendered HTML');
+		$this->assertNotContains('tests/phpunit/data/RequirementsTest_b.css', $html, 'RequirementsTest_b.css was blocked and should not appear in rendered HTML');
+		$this->assertContains('tests/phpunit/data/RequirementsTest_c.js', $html, 'RequirementsTest_c.js should appear in rendered HTML');
+		$this->assertContains('tests/phpunit/data/RequirementsTest_c.css', $html, 'RequirementsTest_c.css should appear in rendered HTML');
+	}
+}
+
diff --git a/tests/phpunit/classes/libraries/Themes_Test.php b/tests/phpunit/classes/libraries/Themes_Test.php
new file mode 100644
index 0000000..904954b
--- /dev/null
+++ b/tests/phpunit/classes/libraries/Themes_Test.php
@@ -0,0 +1,28 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Themes library Unit Test
+ *
+ * @author 		Ushahidi Team
+ * @package 	Ushahidi
+ * @category 	Unit Tests
+ * @copyright 	(c) 2008-2011 Ushahidi Inc <http://www.ushahidi.com>
+ * @license 	For license information, see License.txt
+ */
+class Themes_Test extends PHPUnit_Framework_TestCase {
+	
+	public function setUp()
+	{
+		
+	}
+	
+	public function tearDown()
+	{
+		
+	}
+	
+	public function testSomething()
+	{
+	}
+	
+}
+	
\ No newline at end of file
diff --git a/tests/phpunit/classes/libraries/WKT_Test.php b/tests/phpunit/classes/libraries/WKT_Test.php
new file mode 100644
index 0000000..d8269c5
--- /dev/null
+++ b/tests/phpunit/classes/libraries/WKT_Test.php
@@ -0,0 +1,38 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * WKT library Unit Test
+ *
+ * @author 		Ushahidi Team
+ * @package 	Ushahidi
+ * @category 	Unit Tests
+ * @copyright 	(c) 2008-2011 Ushahidi Inc <http://www.ushahidi.com>
+ * @license 	For license information, see License.txt
+ */
+class WKT_Test extends PHPUnit_Framework_TestCase {
+	
+
+	public function testFlatten()
+	{
+		$flatten = array(array(1,"cheese"));
+		$this->assertEquals(array(1,"cheese"), WKT::flatten($flatten));
+		
+
+		$flatten = array(array(1,"cheese"), array(3,4,"chocolate",6), "bermuda");
+		$this->assertEquals(array(1,"cheese",3,4,"chocolate",6,"bermuda"), WKT::flatten($flatten));
+	}
+	
+	public function testCollapsePoints()
+	{
+		//simple array
+		$test = array(1,2);
+		WKT::collapse_points($test, 0);
+		$this->assertEquals("2,1", $test);
+
+		//multidimensional array
+		$test = array(array(1,3),array(2,4));
+		WKT::collapse_points($test, 0);
+		$this->assertEquals(array("3,1", "4,2"), $test);	
+
+	}
+}
+	
\ No newline at end of file
diff --git a/tests/phpunit/classes/models/Alert_Model_Test.php b/tests/phpunit/classes/models/Alert_Model_Test.php
new file mode 100755
index 0000000..f0ad05b
--- /dev/null
+++ b/tests/phpunit/classes/models/Alert_Model_Test.php
@@ -0,0 +1,177 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+/**
+ * Alert_Model Unit test
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Alert_Model_Test extends PHPUnit_Framework_TestCase
+{
+	
+	/**
+	 * Data provider for test_validate_email_mobile()
+	 *
+	 * @return array
+	 */
+	public function providerValidate()
+	{
+		return array(
+			array(
+				array(
+					'alert_lat' => -1.2821515239557,
+					'alert_lon' => 36.819734568238,
+					'alert_radius' => 10,
+					'alert_email' => 'test at ushahidi.com',
+					'alert_mobile' => '254721247824',
+					'alert_country' => ORM::factory('country',Kohana::config('settings.default_country'))->id,
+					'alert_confirmed' => 1
+
+				),
+				FALSE
+			)
+		);
+		
+	}
+	
+	/**
+	 * Tests Alert_Model->validate() where both a subscriber email address and
+	 * mobile phone no. have been specified
+	 *
+	 * @test
+	 * @dataProvider providerValidate
+	 * @param array $data Input data to be validated
+	 */
+	public function test_validate_email_mobile($data)
+	{
+		// Create instance for the Alert_Model class
+		$model = new Alert_Model();
+		
+		// Check if the validation	succeeded
+		$this->assertEquals(TRUE, $model->validate($data), 'Alert Validation Failed');
+
+	}
+
+	/**
+	 * Data provider for test_validate_mobile()
+	 *
+	 * @return array
+	 */
+	public function providerValidateMobile()
+	{
+		return array(
+			array(
+				array(
+					'alert_lat' => -1.2821515239557,
+					'alert_lon' => 36.819734568238,
+					'alert_radius' => 10,
+					'alert_mobile' => '254721247824',
+					'alert_country' => ORM::factory('country',Kohana::config('settings.default_country'))->id,
+					'alert_confirmed' => 1
+
+				),
+				FALSE
+			)
+		);
+		
+	}
+
+	/**
+	 * When only the subscriber's mobile phone no. has been specified
+	 *
+	 * @dataProvider providerValidateMobile
+	 */
+	public function test_validate_mobile_only($data)
+	{
+		// Create an instance for the Alert_Model class
+		$alert = new Alert_Model();
+
+		//Check if validation succeeded
+		$this->assertEquals(TRUE,$alert->validate($data), 'Alert Mobile phone number not specified');
+
+	}
+
+	/**
+	 * Data provider for test_validate_email()
+	 *
+	 * @return array
+	 */
+	public function providerValidateEmail()
+	{
+		return array(
+			array(
+				array(
+					'alert_lat' => -1.2821515239557,
+					'alert_lon' => 36.819734568238,
+					'alert_radius' => 10,
+					'alert_email' => 'test at ushahidi.com',
+					'alert_country' => ORM::factory('country',Kohana::config('settings.default_country'))->id,
+					'alert_confirmed' => 1
+
+				),
+				FALSE
+			)
+		);
+		
+	}
+
+	/**
+	 * When only the subscriber's email address has been specified
+	 *
+	 * @dataProvider providerValidateEmail
+	 */
+	public function test_validate_email_only($data)
+	{
+		//Create an instance for the Alert_Model class
+		$alert = new Alert_Model();
+
+		//Check if validation succeeded
+		$this->assertEquals(TRUE,$alert->validate($data), 'Alert Email adddress not specified');
+	}
+
+	/**
+	 * Data provider for test_validate_no_subscriber()
+	 *
+	 * @return array
+	 */
+	public function providerValidateSubscriber()
+	{
+		return array(
+			array(
+				array(
+					'alert_lat' => -1.2821515239557,
+					'alert_lon' => 36.819734568238,
+					'alert_radius' => 10,
+					'alert_country' => ORM::factory('country',Kohana::config('settings.default_country'))->id,
+					'alert_confirmed' => 1
+
+				),
+				FALSE
+			)
+		);
+		
+	}
+
+	/**
+	 * Tests where no subscriber email address or phone no. has been specified
+	 * 
+	 * @dataProvider providerValidateSubscriber
+ 	 */
+	public function test_validate_no_subscriber($data)
+	{
+		// Create an instance for the Alert_Model class
+		$alert = new Alert_Model();
+
+		//Check if validation succeeded
+		$this->assertFalse($alert->validate($data), 'Neither Alert Email address or Alert Mobile phone number has been specified');
+	}
+
+	
+}
+?>
diff --git a/tests/phpunit/classes/models/Category_Model_Test.php b/tests/phpunit/classes/models/Category_Model_Test.php
new file mode 100755
index 0000000..dc43417
--- /dev/null
+++ b/tests/phpunit/classes/models/Category_Model_Test.php
@@ -0,0 +1,129 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+/**
+ * Unit tests for the category model
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Category_Model_Test extends PHPUnit_Framework_TestCase
+{
+	/**
+	 * Data provider for test validate
+	 * @dataProvider
+	 */
+	public function providerValidate()
+	{
+		return array(
+			array(
+				// Valid category data
+				array(
+					'category_title' => 'Test title',
+					'category_description' => 'Test title category',
+					'category_color' => 'f3f3f3',
+					'parent_id' => 0
+				), 
+				
+				// Invalid category data set 1
+				array(
+					'category_title' => 'Test title',
+					'category_description' => 'Test title category',
+					'category_color' => NULL,
+					'parent_id' => 0
+				), 
+				
+				// Invalid category data data set 2 - special parent category
+				array(
+					'category_title' => 'Test title',
+					'category_description' => 'Test title category',
+					'category_color' => 'F3F3F3',
+					'parent_id' => testutils::get_random_id('category', 'WHERE category_trusted = 1')
+				),
+				
+				// Invalid category data set 3 - special subcategory
+				array(
+					'category_title' => 'Edited Cat',
+					'category_description' => 'Editing a category',
+					'category_color' => 'f3f3f3',
+					'parent_id' => testutils::get_random_id('category')
+				),
+
+				// Do not save record when validation succeeds
+				FALSE 
+			)
+		);
+	}
+	
+	/**
+	 * Tests the validate() method in Category_Model
+	 *
+	 * @test
+	 * @dataProvider providerValidate
+	 * @param array $valid Valid data for the test
+	 * @param array $invalid Invalid data for the test
+	 * @param array $save Saves the record when validation succeeds
+	 */
+	public function testValidate($valid, $invalid, $invalid2, $specialsub, $save)
+	{
+		// Model instance for the test on a new category
+		$category = new Category_Model();
+		
+		// Model instance for the test on an existing special category
+		$special_sub = new Category_Model(testutils::get_random_id('category', 'WHERE category_trusted = 1'));
+		
+		// Valid data test
+		$this->assertEquals(TRUE, $category->validate($valid, $save));
+		
+		// Invalid data test 1
+		$this->assertEquals(FALSE, $category->validate($invalid, $save));
+		
+		// Invalid data test 2
+		$this->assertEquals(FALSE, $category->validate($invalid2, $save));
+		
+		// Invalid data test 3
+		$this->assertEquals(FALSE , $special_sub->validate($specialsub, $save));
+	}
+	
+	/**
+	 * Data provider for testIsValidCategory
+	 *
+	 * @dataProvider
+	 */
+	public function providerIsValidCategory()
+	{
+		return array(
+			array(
+				// Valid category id
+				testutils::get_random_id('category'),
+				
+				// Invalid category id
+				'-1'
+			));
+	}
+	
+	/**
+	 * Test the is_valid_category method in Category_Model using a 
+	 * valid category id
+	 *
+	 * @test
+	 * @dataProvider providerIsValidCategory
+	 * @param int $valid_category_id  ID of a category exisitng in the database
+	 * @param int $invalid_category_id ID of a non-existent/non-numeric category
+	 */
+	public function testIsValidCategory($valid_category_id, $invalid_category_id)
+	{
+		// Test existence of valid category
+		$this->assertEquals(TRUE, Category_Model::is_valid_category($valid_category_id));
+		
+		// Assert invalidity and non-existence of invalid category
+		$this->assertEquals(FALSE, Category_Model::is_valid_category($invalid_category_id));
+	}
+
+}
+?>
\ No newline at end of file
diff --git a/tests/phpunit/classes/models/Country_Model_Test.php b/tests/phpunit/classes/models/Country_Model_Test.php
new file mode 100755
index 0000000..2ecea00
--- /dev/null
+++ b/tests/phpunit/classes/models/Country_Model_Test.php
@@ -0,0 +1,29 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Country_Model Test
+ *
+ * @package Ushahidi
+ * @category Unit Tests
+ * @author Ushahidi Team
+ * @copyright (c) Ushahidi Inc 2008-2011
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+class Country_ModelTest extends PHPUnit_Framework_TestCase {
+	
+	/**
+	 * Tests CountyModel::get_country_by_name
+	 * @test
+	 */
+	public function testGetCountryByName()
+	{
+		// Use valid country
+		$valid = Country_Model::get_country_by_name('Kenya');
+		$this->assertEquals(TRUE, $valid instanceof Country_Model, sprintf('Invalid country object type (%s) returned', get_class($valid)));
+		$this->assertGreaterThanOrEqual(1, $valid->id);
+			
+		// Use invalid country
+		$invalid = Country_Model::get_country_by_name('Nairobi');
+		$this->assertNull($invalid);
+	}
+}
+?>
\ No newline at end of file
diff --git a/tests/phpunit/classes/models/Feed_Model_Test.php b/tests/phpunit/classes/models/Feed_Model_Test.php
new file mode 100755
index 0000000..1de8eb0
--- /dev/null
+++ b/tests/phpunit/classes/models/Feed_Model_Test.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * Unit tests for the feed model
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+class Feed_Model_Test extends PHPUnit_Framework_TestCase {
+	
+	public function setUp()
+	{
+		// Create an instance of Feed_Model
+		$this->feed_model = new Feed_Model();
+	}
+	
+	public function tearDown()
+	{
+		// Garbage collection
+		unset ($this->feed_model);
+	}
+	
+	/**
+	 * Tests Feed_Model::is_valid_feed
+	 * @test
+	 */
+	public function testIsValidFeed()
+	{
+		if (ORM::factory('feed')->count_all() == 0)
+		{
+			$this->markTestSkipped('There are no records in the feeds table');
+		}
+		
+		// Test with a valid feed id
+		$random_feed_id = testutils::get_random_id('feed');
+		
+		$this->assertEquals(TRUE, Feed_Model::is_valid_feed($random_feed_id));
+		
+		// Test with an invalid feed id
+		$this->assertEquals(FALSE, Feed_Model::is_valid_feed('90.9999'));
+	}
+	
+	
+	/**
+	 * Dataprovider for testValidate
+	 * @dataProvider
+	 */
+	public function providerTestValidate()
+	{
+		return array(
+			array(
+				// Valid feed data
+				array(
+					'feed_name' => 'Valid Test Feed Name',
+					'feed_url' => 'http://http://xkcd.com/rss.xml'
+				),
+				// Invalid feed data
+				array(
+					'feed_name' => 'Invalid Test Feed Name',
+					'feed_url' => 'xkcd.com/rss.xml'
+				)
+			)
+		);
+	}
+	
+	/**
+	 * Tests the validate method in Feed_Model
+	 * @test
+	 * @dataProvider providerTestValidate
+	 */
+	public function testValidate($valid_feed_data, $invalid_feed_data)
+	{
+		$this->assertEquals(TRUE, $this->feed_model->validate($valid_feed_data));
+		$this->assertEquals(FALSE, $this->feed_model->validate($invalid_feed_data));
+	}
+}
+?>
\ No newline at end of file
diff --git a/tests/phpunit/classes/models/Form_Field_Model_Test.php b/tests/phpunit/classes/models/Form_Field_Model_Test.php
new file mode 100755
index 0000000..893c011
--- /dev/null
+++ b/tests/phpunit/classes/models/Form_Field_Model_Test.php
@@ -0,0 +1,86 @@
+<?php defined('SYSPATH') or die('No direct script access allowed.');
+/**
+ * Unit tests for the form_field model
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Form_Field_Model_Test extends PHPUnit_Framework_TestCase
+{
+	/**
+	 * Data provider for test validate
+	 * @dataProvider
+	 */
+	public function providerValidate()
+	{
+		return array(
+			array(
+				array(
+					'form_id' => '1',
+					'field_id' => '',
+					'field_type' => '3',
+					'field_name' => 'Test Date',
+					'field_default' => '03/08/2011',
+					'field_required' => '1',
+					'field_height' => '',
+					'field_width' => '',
+					'field_isdate' => '1',
+					'field_ispublic_visible' => '0',
+					'field_ispublic_submit' => '0',
+				), //Valid form field data
+
+				array(
+					'form_id' => '1',
+					'field_id' => '',
+					'field_type' => '3',
+					'field_name' => 'Test Date',
+					'field_default' => 'test',
+					'field_required' => '1',
+					'field_height' => '',
+					'field_width' => '',
+					'field_isdate' => '1',
+					'field_ispublic_visible' => '',
+					'field_ispublic_submit' => '',
+				), //Invalid form field data
+				
+				TRUE // save record when validation succeeds
+			)
+		);
+		
+	}
+	
+	
+	/**
+	 * Tests the validate() method in Form Field Data
+	 *
+	 * @test
+	 * @dataProvider providerValidate
+	 * @param array $valid Valid data for the test
+	 * @param array $invalid Invalid data for the test
+	 * @param array $save Saves the record when validation succeeds
+	 */
+	public function testValidate($valid, $invalid, $save)
+	{
+		// Model instance for the test
+		$form_field = new Form_Field_Model();
+		
+		// Valid data test
+		$this->assertEquals(TRUE, $form_field->validate($valid, $save),Kohana::debug($valid->errors()));
+		
+		// Invalid data test
+		$this->assertEquals(FALSE,
+		$form_field->validate($invalid,$save), sprintf("Expected errors not found. Error count: %d", count($invalid->errors())));
+	}
+
+
+}
+
+
+?>
diff --git a/tests/phpunit/classes/models/Incident_Model_Test.php b/tests/phpunit/classes/models/Incident_Model_Test.php
new file mode 100755
index 0000000..af25184
--- /dev/null
+++ b/tests/phpunit/classes/models/Incident_Model_Test.php
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Unit tests for the incident model
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module	   Unit Tests
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+class Incident_Model_Test extends PHPUnit_Framework_TestCase {
+	
+	/**
+	 * Data provider for the testGetNeighbouring incidents unit test
+	 * @dataProvider
+	 */
+	public function providerTestGetNeighbouringIncidents()
+	{
+		return array(array(
+			testutils::get_random_id('incident', 'WHERE incident_active = 1'),
+			10
+		));
+	}
+	
+	/**
+	 * Tests Incident_Model::get_neigbouring_incidents
+	 * @test
+	 * @dataProvider providerTestGetNeighbouringIncidents
+	 */
+	public function testGetNeighbouringIncidents($incident_id, $num_neighbours)
+	{
+		// Get the neighbouring incidents
+		$neighbours = Incident_Model::get_neighbouring_incidents($incident_id, FALSE, 0, $num_neighbours);
+	
+		if (empty($neighbours))
+		{
+			$this->markTestSkipped('The incident table is empty.');
+		}
+		else
+		{
+			// Check if the no. of returned incidents matches the no. of neighbours specified in @param $neighbours
+			$this->assertEquals($num_neighbours, $neighbours->count());
+		}
+	}
+
+	/**
+	 * Tests Incident_Model::is_valid_incident
+	 * @test
+	 */
+	public function testIsValidIncident()
+	{
+		// Get any incident
+		$random_incident = testutils::get_random_id('incident');
+		$inactive_incident = testutils::get_random_id('incident', 'WHERE incident_active = 0');
+		$active_incident = testutils::get_random_id('incident', 'WHERE incident_active = 1');
+		
+		//Test to see if there are data in the incident table to test with.
+		if (empty($random_incident))
+		{
+			$this->markTestSkipped('The incident table is empty.');
+		}
+		elseif (empty($inactive_incident))
+		{
+			$this->markTestSkipped('No inactive incidents in incident table.');
+		}
+		elseif (empty($active_incident))
+		{
+			$this->markTestSkipped('No active incidents in incident table.');
+		}
+		else
+		{
+			$this->assertEquals(TRUE, Incident_Model::is_valid_incident($random_incident, FALSE));
+
+			// Get inactive incident
+			$inactive_incident = testutils::get_random_id('incident', 'WHERE incident_active = 0');
+			// Check fails with default args and explicitly limit to active only
+			$this->assertEquals(FALSE, Incident_Model::is_valid_incident($inactive_incident));
+			$this->assertEquals(FALSE, Incident_Model::is_valid_incident($inactive_incident, TRUE));
+			// Check success when including inactive incidents
+			$this->assertEquals(TRUE, Incident_Model::is_valid_incident($inactive_incident, FALSE));
+			
+			// Get active incident
+			$active_incident = testutils::get_random_id('incident', 'WHERE incident_active = 1');
+			// Check success with default args and explicitly limit to active only
+			$this->assertEquals(TRUE, Incident_Model::is_valid_incident($active_incident));
+			$this->assertEquals(TRUE, Incident_Model::is_valid_incident($active_incident, TRUE));
+			
+			// Null incident value
+			$this->assertEquals(FALSE, Incident_Model::is_valid_incident(NULL));
+			
+			// Non numeric incident value
+			$this->assertEquals(FALSE, Incident_Model::is_valid_incident('0.999'));
+		}
+	}
+}
+?>
diff --git a/tests/phpunit/classes/models/User_Model_Test.php b/tests/phpunit/classes/models/User_Model_Test.php
new file mode 100755
index 0000000..1902c2e
--- /dev/null
+++ b/tests/phpunit/classes/models/User_Model_Test.php
@@ -0,0 +1,85 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * User_Model Unit Test
+ *
+ * @author 		Ushahidi Team
+ * @package 	Ushahidi
+ * @category 	Unit Tests
+ * @copyright 	(c) 2008-2011 Ushahidi Inc <http://www.ushahidi.com>
+ * @license 	For license information, see License.txt
+ */
+class User_Model_Test extends PHPUnit_Framework_TestCase {
+	
+	/**
+	 * Provides dummy data for testing User_Model::custom_validate
+	 * @dataProvider
+	 */
+	public function provider_custom_validate()
+	{
+		return array(array(
+			array(
+				'username' => 'ekala',
+				'name' => 'Emmanuel Kala',
+				'email'=>'emmanuel at example.com',
+				'password' => 'abc123tbhh', 
+				'password_again'=>'abc123tbhh',
+				'notify' => '0',
+				'role' => 'superadmin'
+			),
+			array(
+				'username' => 'admin',
+				'name' => 'Administrator',
+				'email' => 'admin at example.com',
+				'password' => 'admin123',
+				'password_again' => 'admin_123',
+				'notify' => '1',
+				'role' => 'admin'
+			)
+		));
+	}
+	
+	/**
+	 * Tests User_Model::custom_validate
+	 *
+	 * @test
+	 * @dataProvider provider_custom_validate
+	 */
+	public function test_custom_validate($valid, $invalid)
+	{
+		// set up mock, for prevent_superadmin_modification
+		$auth = $this->getMock('Auth', array('logged_in'));
+		$auth->expects($this->exactly(2))
+			 ->method('logged_in')
+			 ->with($this->equalTo('superadmin'))
+			 ->will($this->returnValue(True));
+
+		// Save initial data
+		$initial_valid = $valid;
+		$initial_invalid = $invalid;
+
+		// Test with valid data
+		$response = User_Model::custom_validate($valid, $auth);
+		$this->assertEquals(TRUE, $valid instanceof Validation);
+		$this->assertTrue($response, Kohana::debug($valid->errors()));
+		
+		// Test with invalid data
+		$response = User_Model::custom_validate($invalid, $auth);
+		$this->assertEquals(TRUE, $invalid instanceof Validation);
+		$this->assertFalse($response);
+
+		// restore valid, invalid
+		$valid = $initial_valid;
+		$invalid = $initial_invalid;
+
+		// Test modification to superadmin as admin
+		$auth = $this->getMock('Auth', array('logged_in'));
+		$auth->expects($this->once())
+			 ->method('logged_in')
+			 ->with($this->equalTo('superadmin'))
+			 ->will($this->returnValue(False));
+		$response = User_Model::custom_validate($valid, $auth);
+		$this->assertTrue($valid instanceof Validation);
+		$this->assertFalse($response, Kohana::debug($valid->errors()));
+	}
+}
+?>
\ No newline at end of file
diff --git a/tests/phpunit/data/RequirementsTest_a.css b/tests/phpunit/data/RequirementsTest_a.css
new file mode 100644
index 0000000..730bd9e
--- /dev/null
+++ b/tests/phpunit/data/RequirementsTest_a.css
@@ -0,0 +1 @@
+.a {color: #f00;}
\ No newline at end of file
diff --git a/tests/phpunit/data/RequirementsTest_a.js b/tests/phpunit/data/RequirementsTest_a.js
new file mode 100644
index 0000000..829a652
--- /dev/null
+++ b/tests/phpunit/data/RequirementsTest_a.js
@@ -0,0 +1 @@
+alert('a');
\ No newline at end of file
diff --git a/tests/phpunit/data/RequirementsTest_b.css b/tests/phpunit/data/RequirementsTest_b.css
new file mode 100644
index 0000000..e11f7ee
--- /dev/null
+++ b/tests/phpunit/data/RequirementsTest_b.css
@@ -0,0 +1 @@
+.b {color: #0ff;}
\ No newline at end of file
diff --git a/tests/phpunit/data/RequirementsTest_b.js b/tests/phpunit/data/RequirementsTest_b.js
new file mode 100644
index 0000000..3d8670c
--- /dev/null
+++ b/tests/phpunit/data/RequirementsTest_b.js
@@ -0,0 +1 @@
+alert('b');
\ No newline at end of file
diff --git a/tests/phpunit/data/RequirementsTest_c.css b/tests/phpunit/data/RequirementsTest_c.css
new file mode 100644
index 0000000..ae49dd2
--- /dev/null
+++ b/tests/phpunit/data/RequirementsTest_c.css
@@ -0,0 +1 @@
+.c {color: #0ff;}
\ No newline at end of file
diff --git a/tests/phpunit/data/RequirementsTest_c.js b/tests/phpunit/data/RequirementsTest_c.js
new file mode 100644
index 0000000..1e0f7b3
--- /dev/null
+++ b/tests/phpunit/data/RequirementsTest_c.js
@@ -0,0 +1 @@
+alert('c');
\ No newline at end of file
diff --git a/tests/phpunit/data/alert.xml b/tests/phpunit/data/alert.xml
new file mode 100644
index 0000000..ef50341
--- /dev/null
+++ b/tests/phpunit/data/alert.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
    <alert id="1" alert_type="2" alert_recipient="test at ushahidi.com" alert_code="2khrEDTyOAySQ3YMiOI5" alert_confirmed="1" alert_lat="-0.023559" alert_lon="37.90619300000003" alert_radius="20" />
+    <alert id="2" alert_type="1" alert_recipient="0725609789" alert_code="SAEF9JWX" alert_confirmed="1" alert_lat="-0.023559" alert_lon="37.90619300000003" alert_radius="20" />
</dataset>
\ No newline at end of file
diff --git a/tests/phpunit/data/form_field.xml b/tests/phpunit/data/form_field.xml
new file mode 100644
index 0000000..8994e2a
--- /dev/null
+++ b/tests/phpunit/data/form_field.xml
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
    <form_field id="1" form_id="1" field_name="Textfield 1" field_type="1" field_required="0" field_default="textfield" field_isdate="0" />
    <form_field id="2" form_id="1" field_name="Textarea 1" field_type="2" field_required="0" field_default="textarea" field_isdate="0" />
    <form_field id="3" form_id="1" field_name="Datefield 1" field_type="3" field_required="0" field_default="22/08/2011" field_isdate="1" />
    <form_field id="4" form_id="1" field_name="Radiobutton 1" field_type="5" field_required="0" field_default="Radio 1, Radio 2, Radio 3" field_isdate="0" />
    <form_field id="5" form_id="1" field_name="Checkbox 1" field_type="6" field_required="0" field_default="Check 1, Check 2, Check 3" field_isdate="0" />
    <form_field id="6" form_id="1" field_name="Dropdown List 1" field_type="7" field_required="0" field_default="Drop 1, Drop 2, Drop 3" field_isdate="0" />
    <form_field id="7" form_id="1" field_name="Textfield 2" field_type="1" field_required="1" field_default="textfield" field_isdate="0" />
    <form_field id="8" form_id="1" field_name="Textarea 2" field_type="2" field_required="1" field_default="textarea" field_isdate="0" />
    <form_field id="9" form_id="1" field_name="Datefield 2" field_type="3" field_required="1" field_default="22/08/2011" field_isdate="1" />
    <form_field id="10" form_id="1" field_name="Radiobutton 2" field_type="5" field_required="1" field_default="Radio 1, Radio 2, Radio 3" field_isdate="0" />
    <form_field id="11" form_id="1" field_name="Checkbox 2" field_type="6" field_required="1" field_default="Check 1, Check 2, Check 3" field_isdate="0" />
    <form_field id="12" form_id="1" field_name="Dropdown List 2" field_type="7" field_required="1" field_default="Drop 1, Drop 2, Drop 3" field_isdate="0" />
</dataset>
\ No newline at end of file
diff --git a/tests/phpunit/phpunit.xml.template b/tests/phpunit/phpunit.xml.template
new file mode 100755
index 0000000..a45c0e6
--- /dev/null
+++ b/tests/phpunit/phpunit.xml.template
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit colors="true" bootstrap="bootstrap.php">
+    <testsuites>
+    	<testsuite name="Ushahidi_Web_Tests">
+    		<directory>./classes</directory>
+    	</testsuite>
+    </testsuites>
+    
+    <php>
+        <server name="HTTP_HOST" value="localhost" />
+        <server name="SERVER_NAME" value="localhost" />
+        <server name="REMOTE_ADDR" value="127.0.0.1" />
+    </php>
+    
+</phpunit>
diff --git a/tests/phpunit/testbootstrap.php b/tests/phpunit/testbootstrap.php
new file mode 100755
index 0000000..8baea30
--- /dev/null
+++ b/tests/phpunit/testbootstrap.php
@@ -0,0 +1,55 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.');
+/**
+ * Process control file to bootstrap the Kohana application, Ushahidi_Web in this case
+ *
+ * This file is a copy of the system/core/Bootstrap.php with the following events
+ * disabled to prevent sending HTML output to STDIO:
+ *      - Event::run('system.routing')
+ *      - Benchmark::stop(SYSTEM_BENCHMARK.'_system_initialization')
+ *      - Event::run('system.execute')
+ * 
+ * $Id: testbootstrap.php
+ *
+ * @package    Core
+ * @author     Kohana Team
+ * @copyright  (c) 2007 Kohana Team
+ * @license    http://kohanaphp.com/license.html
+ */
+
+define('KOHANA_VERSION',  '2.3.1');
+define('KOHANA_CODENAME', 'accipiter');
+
+// Test of Kohana is running in Windows
+define('KOHANA_IS_WIN', DIRECTORY_SEPARATOR === '\\');
+
+// Kohana benchmarks are prefixed to prevent collisions
+define('SYSTEM_BENCHMARK', 'system_benchmark');
+
+// Load benchmarking support
+require SYSPATH.'core/Benchmark'.EXT;
+
+// Start total_execution
+Benchmark::start(SYSTEM_BENCHMARK.'_total_execution');
+
+// Start kohana_loading
+Benchmark::start(SYSTEM_BENCHMARK.'_kohana_loading');
+
+// Load core files
+require SYSPATH.'core/utf8'.EXT;
+require SYSPATH.'core/Event'.EXT;
+require SYSPATH.'core/Kohana'.EXT;
+
+// Prepare the environment
+Kohana::setup();
+
+// End kohana_loading
+Benchmark::stop(SYSTEM_BENCHMARK.'_kohana_loading');
+
+// Start system_initialization
+Benchmark::start(SYSTEM_BENCHMARK.'_system_initialization');
+
+// Prepare the system
+Event::run('system.ready');
+
+// Clean up and exit
+Event::run('system.shutdown');
diff --git a/tests/selenium/admin/admin_blocks_hide_test.html b/tests/selenium/admin/admin_blocks_hide_test.html
new file mode 100755
index 0000000..1737c43
--- /dev/null
+++ b/tests/selenium/admin/admin_blocks_hide_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Blocks Hide</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Blocks Hide</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Visible</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to HIDE?</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_blocks_list_test.html b/tests/selenium/admin/admin_blocks_list_test.html
new file mode 100755
index 0000000..3b5b893
--- /dev/null
+++ b/tests/selenium/admin/admin_blocks_list_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Blocks List</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Blocks List</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Blocks</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_blocks_show_test.html b/tests/selenium/admin/admin_blocks_show_test.html
new file mode 100755
index 0000000..93263f1
--- /dev/null
+++ b/tests/selenium/admin/admin_blocks_show_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Blocks Show</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Blocks Show</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Hidden</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW?</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_categories_add_failed_test.html b/tests/selenium/admin/admin_categories_add_failed_test.html
new file mode 100755
index 0000000..8e5b0f8
--- /dev/null
+++ b/tests/selenium/admin/admin_categories_add_failed_test.html
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Categories Add Failed</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Categories Add Failed</td></tr>
+</thead><tbody>
+<tr>
+	<td>click</td>
+	<td>link=Add/Edit</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>category_title</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>category_description</td>
+	<td>nothing</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>category_color</td>
+	<td>780090</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.red-box</td>
+	<td></td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_categories_add_test.html b/tests/selenium/admin/admin_categories_add_test.html
new file mode 100755
index 0000000..9c74b0d
--- /dev/null
+++ b/tests/selenium/admin/admin_categories_add_test.html
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Categories Add</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Admin Categories Add</td></tr>
+</thead><tbody>
+<tr>
+	<td>click</td>
+	<td>link=Add/Edit</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>category_title</td>
+	<td>Test Category</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>category_description</td>
+	<td>Test Description</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>category_color</td>
+	<td>780078</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The category has been ADDED/EDITED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_categories_delete_test.html b/tests/selenium/admin/admin_categories_delete_test.html
new file mode 100755
index 0000000..bfc5e2d
--- /dev/null
+++ b/tests/selenium/admin/admin_categories_delete_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Categories Delete</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Categories Delete</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Delete</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to DELETE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The category has been  DELETED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_categories_hide_test.html b/tests/selenium/admin/admin_categories_hide_test.html
new file mode 100755
index 0000000..314c3f3
--- /dev/null
+++ b/tests/selenium/admin/admin_categories_hide_test.html
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Categories Hide</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Categories Hide</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Visible</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+<td>verifyTextPresent</td>
+<td></td>
+<td>The category has been MODIFIED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_categories_list_test.html b/tests/selenium/admin/admin_categories_list_test.html
new file mode 100755
index 0000000..090f619
--- /dev/null
+++ b/tests/selenium/admin/admin_categories_list_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Categories List</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Admin Categories List</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Manage</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>id=categorySort</td>
+	<td></td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_categories_show_test.html b/tests/selenium/admin/admin_categories_show_test.html
new file mode 100755
index 0000000..f00f070
--- /dev/null
+++ b/tests/selenium/admin/admin_categories_show_test.html
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Categories Show</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Categories Show</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Hidden</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+<td>verifyTextPresent</td>
+<td></td>
+<td>The category has been MODIFIED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
diff --git a/tests/selenium/admin/admin_feed_item_delete_test.html b/tests/selenium/admin/admin_feed_item_delete_test.html
new file mode 100755
index 0000000..b937686
--- /dev/null
+++ b/tests/selenium/admin/admin_feed_item_delete_test.html
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Feed Item Delete</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Feed Item Delete</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=DELETE</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to DELETE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>Messages DELETED </td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_feed_items_list_test.html b/tests/selenium/admin/admin_feed_items_list_test.html
new file mode 100755
index 0000000..8279daa
--- /dev/null
+++ b/tests/selenium/admin/admin_feed_items_list_test.html
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Feed Items List</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Feed Items List</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Feed Items</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
diff --git a/tests/selenium/admin/admin_feeds_add_failed_test.html b/tests/selenium/admin/admin_feeds_add_failed_test.html
new file mode 100755
index 0000000..c091ccd
--- /dev/null
+++ b/tests/selenium/admin/admin_feeds_add_failed_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Feeds Add Failed</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Feeds Add Failed</td></tr>
+</thead><tbody>
+<tr>
+	<td>type</td>
+	<td>feed_name</td>
+	<td>Test</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>feed_url</td>
+	<td>nothing</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.red-box</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_feeds_add_test.html b/tests/selenium/admin/admin_feeds_add_test.html
new file mode 100755
index 0000000..a396be4
--- /dev/null
+++ b/tests/selenium/admin/admin_feeds_add_test.html
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Feeds Add</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Feeds Add</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=News Feeds</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>feed_name</td>
+	<td>*iHub_</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>feed_url</td>
+	<td>http://ihub.co.ke/blog/feed/</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The feed has been ADDED/EDITED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_feeds_delete_test.html b/tests/selenium/admin/admin_feeds_delete_test.html
new file mode 100755
index 0000000..b3dbc14
--- /dev/null
+++ b/tests/selenium/admin/admin_feeds_delete_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Feeds Delete</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Feeds Delete</td></tr>
+</thead><tbody>
+<tr>
+<td>clickAndWait</td>
+<td>link=Feeds</td>
+<td></td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Delete</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to DELETE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The feed has been DELETED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_feeds_hide_test.html b/tests/selenium/admin/admin_feeds_hide_test.html
new file mode 100755
index 0000000..c0b8620
--- /dev/null
+++ b/tests/selenium/admin/admin_feeds_hide_test.html
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Feeds Hide</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Feeds Hide</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Visible</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The feed has been MODIFIED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_feeds_show_test.html b/tests/selenium/admin/admin_feeds_show_test.html
new file mode 100755
index 0000000..43e0085
--- /dev/null
+++ b/tests/selenium/admin/admin_feeds_show_test.html
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Feeds Show</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Feeds Show</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Hidden</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The feed has been MODIFIED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_feeds_view_items_test.html b/tests/selenium/admin/admin_feeds_view_items_test.html
new file mode 100755
index 0000000..e4720f8
--- /dev/null
+++ b/tests/selenium/admin/admin_feeds_view_items_test.html
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Feeds View Items</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Feeds View Items</td></tr>
+</thead><tbody>
+<tr>
+<td>clickAndWait</td>
+<td>link=REFRESH NEWS FEEDS</td>
+<td></td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=View Items</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_forms_add_failed_test.html b/tests/selenium/admin/admin_forms_add_failed_test.html
new file mode 100755
index 0000000..baabace
--- /dev/null
+++ b/tests/selenium/admin/admin_forms_add_failed_test.html
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Forms Add Failed</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Forms Add Failed</td></tr>
+</thead><tbody>
+<tr>
+	<td>click</td>
+	<td>link=Add/Edit</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>form_title</td>
+	<td>Test Form</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>form_description</td>
+	<td></td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.red-box</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_forms_add_test.html b/tests/selenium/admin/admin_forms_add_test.html
new file mode 100755
index 0000000..d7a65a2
--- /dev/null
+++ b/tests/selenium/admin/admin_forms_add_test.html
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Forms Add</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Forms Add</td></tr>
+</thead><tbody>
+<tr>
+	<td>click</td>
+	<td>link=Add/Edit</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>form_title</td>
+	<td>Test Form</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>form_description</td>
+	<td>Test Form description</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The form has been CREATED/EDITED!</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_forms_list_test.html b/tests/selenium/admin/admin_forms_list_test.html
new file mode 100755
index 0000000..c58a0fb
--- /dev/null
+++ b/tests/selenium/admin/admin_forms_list_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Forms List</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Forms List</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Forms</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_forms_show_hide_test.html b/tests/selenium/admin/admin_forms_show_hide_test.html
new file mode 100755
index 0000000..de50c8e
--- /dev/null
+++ b/tests/selenium/admin/admin_forms_show_hide_test.html
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Forms Show/Hide</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Forms Show/Hide</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Active</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td><br /></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The form has been MODIFIED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
diff --git a/tests/selenium/admin/admin_layers_add_failed_test.html b/tests/selenium/admin/admin_layers_add_failed_test.html
new file mode 100755
index 0000000..cfb9887
--- /dev/null
+++ b/tests/selenium/admin/admin_layers_add_failed_test.html
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Layers Add Failed</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Layers Add Failed</td></tr>
+</thead><tbody>
+<tr>
+	<td>type</td>
+	<td>layer_name</td>
+	<td>Testing Layer</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>layer_url</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>layer_color</td>
+	<td>789654</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.red-box</td>
+	<td></td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
+
diff --git a/tests/selenium/admin/admin_layers_add_test.html b/tests/selenium/admin/admin_layers_add_test.html
new file mode 100755
index 0000000..22ac092
--- /dev/null
+++ b/tests/selenium/admin/admin_layers_add_test.html
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Layers Add</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Layers Add</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Layers</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>layer_name</td>
+	<td>Testing Layer</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>layer_url</td>
+	<td> http://www.ushahidi.com/layer.kml</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>layer_color</td>
+	<td>789654</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The Layer Has Been ADDED/EDITED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
diff --git a/tests/selenium/admin/admin_layers_delete_test.html b/tests/selenium/admin/admin_layers_delete_test.html
new file mode 100755
index 0000000..e5ebdad
--- /dev/null
+++ b/tests/selenium/admin/admin_layers_delete_test.html
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Layers Delete</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Layers Delete</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Delete</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to DELETE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The Layer Has Been DELETED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
+
diff --git a/tests/selenium/admin/admin_layers_hide_test.html b/tests/selenium/admin/admin_layers_hide_test.html
new file mode 100755
index 0000000..087747d
--- /dev/null
+++ b/tests/selenium/admin/admin_layers_hide_test.html
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Layers Hide</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Layers Hide</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Visible</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The Layer Has Been MODIFIED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
+
diff --git a/tests/selenium/admin/admin_layers_show_test.html b/tests/selenium/admin/admin_layers_show_test.html
new file mode 100755
index 0000000..ee30237
--- /dev/null
+++ b/tests/selenium/admin/admin_layers_show_test.html
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Layers Show</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Layers Show</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Hidden</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The Layer Has Been MODIFIED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
+
+
diff --git a/tests/selenium/admin/admin_login_invalid_test.html b/tests/selenium/admin/admin_login_invalid_test.html
new file mode 100755
index 0000000..e410fff
--- /dev/null
+++ b/tests/selenium/admin/admin_login_invalid_test.html
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Login Invalid</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Admin Login Invalid</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/logout</td>
+	<td></td>
+</tr>
+<tr>
+	<td>open</td>
+	<td>/login</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>remember</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>username</td>
+	<td>admin</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>password</td>
+	<td>test</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>submit</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.login_error</td>
+	<td></td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_login_valid_test.html b/tests/selenium/admin/admin_login_valid_test.html
new file mode 100755
index 0000000..dc1f283
--- /dev/null
+++ b/tests/selenium/admin/admin_login_valid_test.html
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Login Valid</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Admin Login Valid</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/logout</td>
+	<td></td>
+</tr>
+<tr>
+	<td>open</td>
+	<td>/login</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>username</td>
+	<td>admin</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>password</td>
+	<td>admin</td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>remember</td>
+	<td></td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>submit</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.nav-holder</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_pages_add_failed_test.html b/tests/selenium/admin/admin_pages_add_failed_test.html
new file mode 100755
index 0000000..f04a1a5
--- /dev/null
+++ b/tests/selenium/admin/admin_pages_add_failed_test.html
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Pages Add Failed</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Pages Add Failed</td></tr>
+</thead><tbody>
+<tr>
+	<td>type</td>
+	<td>page_title</td>
+	<td>Test</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>page_tab</td>
+	<td>Testing Page</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>dom=document.getElementById('page_description_ifr').contentDocument.body</td>
+	<td></td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.red-box</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/selenium/admin/admin_pages_add_test.html b/tests/selenium/admin/admin_pages_add_test.html
new file mode 100755
index 0000000..f901161
--- /dev/null
+++ b/tests/selenium/admin/admin_pages_add_test.html
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Pages Add</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Pages Add</td></tr>
+</thead><tbody>
+<tr>
+	<td>type</td>
+	<td>page_title</td>
+	<td>Test</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>page_tab</td>
+	<td>Testing Page</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>dom=document.getElementById('page_description_ifr').contentDocument.body</td>
+	<td>This is a test page checking on page addition functionality :) :).</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The Page Has Been ADDED/EDITED</td>
+</tr>
+</tbody></table>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/selenium/admin/admin_pages_delete_test.html b/tests/selenium/admin/admin_pages_delete_test.html
new file mode 100755
index 0000000..d0a4def
--- /dev/null
+++ b/tests/selenium/admin/admin_pages_delete_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Pages Delete</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Pages Delete</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Delete</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to DELETE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The Page Has Been DELETED</td>
+</tr>
+</tbody></table>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/selenium/admin/admin_pages_hide_test.html b/tests/selenium/admin/admin_pages_hide_test.html
new file mode 100755
index 0000000..9b8aa90
--- /dev/null
+++ b/tests/selenium/admin/admin_pages_hide_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Pages Hide</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Pages Hide</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Visible</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The Page Has Been MODIFIED</td>
+</tr>
+</tbody></table>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/selenium/admin/admin_pages_list_test.html b/tests/selenium/admin/admin_pages_list_test.html
new file mode 100755
index 0000000..06ba05e
--- /dev/null
+++ b/tests/selenium/admin/admin_pages_list_test.html
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Pages List</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Pages List</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Pages</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
diff --git a/tests/selenium/admin/admin_pages_show_test.html b/tests/selenium/admin/admin_pages_show_test.html
new file mode 100755
index 0000000..309a742
--- /dev/null
+++ b/tests/selenium/admin/admin_pages_show_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Pages Show</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Pages Show</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Hidden</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW/HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>The Page Has Been MODIFIED</td>
+</tr>
+</tbody></table>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/selenium/admin/admin_schedule_activate_deactivate_test.html b/tests/selenium/admin/admin_schedule_activate_deactivate_test.html
new file mode 100755
index 0000000..2567578
--- /dev/null
+++ b/tests/selenium/admin/admin_schedule_activate_deactivate_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Schedule Activate/Deactivate</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Schedule Activate/Deactivate</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Active</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to ACTIVATE/DEACTIVATE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>Schedule MODIFIED</td>
+</tr>
+</tbody></table>
+</body>
+</html>
\ No newline at end of file
diff --git a/tests/selenium/admin/admin_schedule_edit_test.html b/tests/selenium/admin/admin_schedule_edit_test.html
new file mode 100755
index 0000000..6d650a5
--- /dev/null
+++ b/tests/selenium/admin/admin_schedule_edit_test.html
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Schedule Edit</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Schedule Edit</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Scheduler</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>link=Edit</td>
+	<td></td>
+</tr>
+<tr>
+	<td>select</td>
+	<td>scheduler_weekday</td>
+	<td>index=3</td>
+</tr>
+<tr>
+	<td>select</td>
+	<td>scheduler_day</td>
+	<td>index=3</td>
+</tr>
+<tr>
+	<td>select</td>
+	<td>scheduler_hour</td>
+	<td>index=3</td>
+</tr>
+<tr>
+	<td>select</td>
+	<td>scheduler_minute</td>
+	<td>index=3</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>Schedule EDITED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
diff --git a/tests/selenium/admin/admin_sharing_add_failed_test.html b/tests/selenium/admin/admin_sharing_add_failed_test.html
new file mode 100755
index 0000000..cd43724
--- /dev/null
+++ b/tests/selenium/admin/admin_sharing_add_failed_test.html
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Sharing Add Failed</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Sharing Add Failed</td></tr>
+</thead><tbody>
+<tr>
+	<td>type</td>
+	<td>sharing_name</td>
+	<td>Testing</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>sharing_url</td>
+	<td> </td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>sharing_color</td>
+	<td>780099</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.red-box</td>
+	<td></td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_sharing_add_test.html b/tests/selenium/admin/admin_sharing_add_test.html
new file mode 100755
index 0000000..e3a0622
--- /dev/null
+++ b/tests/selenium/admin/admin_sharing_add_test.html
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Sharing Add</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Sharing Add</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Sharing</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.table-holder</td>
+	<td></td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>sharing_name</td>
+	<td>Test</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>sharing_url</td>
+	<td>http://ushahidi.com</td>
+</tr>
+<tr>
+	<td>type</td>
+	<td>sharing_color</td>
+	<td>780099</td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>css=input.save-rep-btn</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>CREATED/EDITED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_sharing_delete_test.html b/tests/selenium/admin/admin_sharing_delete_test.html
new file mode 100755
index 0000000..f68bc6a
--- /dev/null
+++ b/tests/selenium/admin/admin_sharing_delete_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Sharing Delete</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Sharing Delete</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Delete</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to DELETE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>DELETED!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin/admin_sharing_hide_test.html b/tests/selenium/admin/admin_sharing_hide_test.html
new file mode 100755
index 0000000..007a2d0
--- /dev/null
+++ b/tests/selenium/admin/admin_sharing_hide_test.html
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Sharing Hide</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Sharing Hide</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Visible</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to HIDE?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>HIDDEN!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
+
diff --git a/tests/selenium/admin/admin_sharing_show_test.html b/tests/selenium/admin/admin_sharing_show_test.html
new file mode 100755
index 0000000..0d00f52
--- /dev/null
+++ b/tests/selenium/admin/admin_sharing_show_test.html
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Admin Sharing Show</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr>
+				<td rowspan="1" colspan="3">Admin Sharing Show</td></tr>
+</thead><tbody>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Hidden</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyConfirmation</td>
+	<td>Are you sure you want to SHOW?</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyElementPresent</td>
+	<td>css=div.green-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>verifyTextPresent</td>
+	<td></td>
+	<td>SHOWN!</td>
+</tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/admin_suite.html b/tests/selenium/admin_suite.html
new file mode 100755
index 0000000..de4f2fd
--- /dev/null
+++ b/tests/selenium/admin_suite.html
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+  <title>Test Suite</title>
+</head>
+<body>
+<table id="suiteTable" cellpadding="1" cellspacing="1" border="1" class="selenium"><tbody>
+<tr><td><b>Test Suite</b></td></tr>
+<tr><td><a href="admin/admin_login_invalid_test.html">Admin Login Invalid</a></td></tr>
+<tr><td><a href="admin/admin_login_valid_test.html">Admin Login Valid</a></td></tr>
+<tr><td><a href="admin/admin_categories_list_test.html">Admin Categories List</a></td></tr>
+<tr><td><a href="admin/admin_categories_add_test.html">Admin Categories Add</a></td></tr>
+<tr><td><a href="admin/admin_categories_add_failed_test.html">Admin Categories Add Failed</a></td></tr>
+<tr><td><a href="admin/admin_categories_hide_test.html">Admin Categories Hide</a></td></tr>
+<tr><td><a href="admin/admin_categories_show_test.html">Admin Categories Show</a></td></tr>
+<tr><td><a href="admin/admin_categories_delete_test.html">Admin Categories Delete</a></td></tr>
+<tr><td><a href="admin/admin_blocks_list_test.html">Admin Blocks List</a></td></tr>
+<tr><td><a href="admin/admin_blocks_hide_test.html">Admin Blocks Hide</a></td></tr>
+<tr><td><a href="admin/admin_blocks_show_test.html">Admin Blocks Show</a></td></tr>
+<tr><td><a href="admin/admin_forms_list_test.html">Admin Forms List</a></td></tr>
+<tr><td><a href="admin/admin_forms_add_test.html">Admin Forms Add</a></td></tr>
+<tr><td><a href="admin/admin_forms_add_failed_test.html">Admin Forms Add Failed</a></td></tr>
+<tr><td><a href="admin/admin_forms_show_hide_test.html">Admin Forms Show/Hide</a></td></tr>
+<tr><td><a href="admin/admin_sharing_add_test.html">Admin Sharing Add</a></td></tr>
+<tr><td><a href="admin/admin_sharing_add_failed_test.html">Admin Sharing Add Failed</a></td></tr>
+<tr><td><a href="admin/admin_sharing_hide_test.html">Admin Sharing Hide</a></td></tr>
+<tr><td><a href="admin/admin_sharing_show_test.html">Admin Sharing Show</a></td></tr>
+<tr><td><a href="admin/admin_sharing_delete_test.html">Admin Sharing Delete</a></td></tr>
+<tr><td><a href="admin/admin_pages_list_test.html">Admin Pages List</a></td></tr>
+<tr><td><a href="admin/admin_pages_add_test.html">Admin Pages Add</a></td></tr>
+<tr><td><a href="admin/admin_pages_add_failed_test.html">Admin Pages Add Failed</a></td></tr>
+<tr><td><a href="admin/admin_pages_show_test.html">Admin Pages Show</a></td></tr>
+<tr><td><a href="admin/admin_pages_hide_test.html">Admin Pages Hide</a></td></tr>
+<tr><td><a href="admin/admin_pages_delete_test.html">Admin Pages Delete</a></td></tr>
+<tr><td><a href="admin/admin_feeds_add_test.html">Admin Feeds Add</a></td></tr>
+<tr><td><a href="admin/admin_feeds_add_failed_test.html">Admin Feeds Add Failed</a></td></tr>
+<tr><td><a href="admin/admin_feeds_hide_test.html">Admin Feeds Hide</a></td></tr>
+<tr><td><a href="admin/admin_feeds_show_test.html">Admin Feeds Show</a></td></tr>
+<tr><td><a href="admin/admin_feeds_view_items_test.html">Admin Feed View Items</a></td></tr>
+<tr><td><a href="admin/admin_feed_items_list_test.html">Admin Feed Items List</a></td></tr>
+<tr><td><a href="admin/admin_feed_item_delete_test.html">Admin Feed Item Delete</a></td></tr>
+<tr><td><a href="admin/admin_feeds_delete_test.html">Admin Feeds Delete</a></td></tr>
+<tr><td><a href="admin/admin_layers_add_test.html">Admin Layers Add</a></td></tr>
+<tr><td><a href="admin/admin_layers_add_failed_test.html">Admin Layers Add Failed</a></td></tr>
+<tr><td><a href="admin/admin_layers_hide_test.html">Admin Layers Hide</a></td></tr>
+<tr><td><a href="admin/admin_layers_show_test.html">Admin Layers Show</a></td></tr>
+<tr><td><a href="admin/admin_layers_delete_test.html">Admin Layers Delete</a></td></tr>
+<tr><td><a href="admin/admin_schedule_edit_test.html">Admin Scheduler Edit</a></td></tr>
+<tr><td><a href="admin/admin_schedule_activate_deactivate_test.html">Admin Scheduler Activate/Deactivate</a></td></tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/alerts/alert_marker_present_test.html b/tests/selenium/alerts/alert_marker_present_test.html
new file mode 100755
index 0000000..ab9b0b3
--- /dev/null
+++ b/tests/selenium/alerts/alert_marker_present_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>alert marker present</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">alert marker present</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/alerts</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].CLASS_NAME</td>
+	<td>OpenLayers.Marker</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/alerts/category_select_test.html b/tests/selenium/alerts/category_select_test.html
new file mode 100755
index 0000000..a1ca50e
--- /dev/null
+++ b/tests/selenium/alerts/category_select_test.html
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>category select</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">category select</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/alerts</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.expandable .hitarea</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.collapsable .hitarea</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.expandable:nth-child(2) > .hitarea</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.collapsable:nth-child(2) > .hitarea</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.expandable .hitarea</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.collapsable ul li .check-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.collapsable ul li:nth-child(2) > .check-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.collapsable ul li:nth-child(3) > .check-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=li.collapsable ul li:nth-child(4) > .check-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertChecked</td>
+	<td>css=li.collapsable ul li .check-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertChecked</td>
+	<td>css=li.collapsable ul li:nth-child(2) > .check-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertChecked</td>
+	<td>css=li.collapsable ul li:nth-child(3) > .check-box</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertChecked</td>
+	<td>css=li.collapsable ul li:nth-child(4) > .check-box</td>
+	<td></td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/alerts/click_map_test.html b/tests/selenium/alerts/click_map_test.html
new file mode 100755
index 0000000..9477011
--- /dev/null
+++ b/tests/selenium/alerts/click_map_test.html
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>click map</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">click map</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/alerts</td>
+	<td></td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>10,20</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>150,210</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>100,250</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>50,50</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>10,200</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>150,10</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>100,200</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>50,50</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>300,50</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+<tr>
+	<td>clickAt</td>
+	<td>divMap</td>
+	<td>310,200</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0].markers[0].lonlat.CLASS_NAME</td>
+	<td>OpenLayers.LonLat</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/alerts/map_loaded_test.html b/tests/selenium/alerts/map_loaded_test.html
new file mode 100755
index 0000000..8221033
--- /dev/null
+++ b/tests/selenium/alerts/map_loaded_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>map loaded</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">map loaded</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/alerts</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Markers")[0]</td>
+	<td>[object Object]</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/alerts/open_alerts_page_test.html b/tests/selenium/alerts/open_alerts_page_test.html
new file mode 100755
index 0000000..04790af
--- /dev/null
+++ b/tests/selenium/alerts/open_alerts_page_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>open alerts page</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">open alerts page</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/main</td>
+	<td></td>
+</tr>
+<tr>
+	<td>clickAndWait</td>
+	<td>link=Get Alerts</td>
+	<td></td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/alerts/radius_polygon_present_test.html b/tests/selenium/alerts/radius_polygon_present_test.html
new file mode 100755
index 0000000..f6504e2
--- /dev/null
+++ b/tests/selenium/alerts/radius_polygon_present_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>radius polygon present</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">radius polygon present</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/alerts</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Radius Layer")[0].drawn</td>
+	<td>true</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/alerts/radius_slider_test.html b/tests/selenium/alerts/radius_slider_test.html
new file mode 100755
index 0000000..b3b47c2
--- /dev/null
+++ b/tests/selenium/alerts/radius_slider_test.html
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>radius slider</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">radius slider</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/alerts</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>alert_radius</td>
+	<td></td>
+</tr>
+<tr>
+	<td>select</td>
+	<td>alert_radius</td>
+	<td>label=10 KM</td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>//option[@value='10']</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>alert_radius</td>
+	<td></td>
+</tr>
+<tr>
+	<td>select</td>
+	<td>alert_radius</td>
+	<td>label=100 KM</td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>//option[@value='100']</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>alert_radius</td>
+	<td></td>
+</tr>
+<tr>
+	<td>select</td>
+	<td>alert_radius</td>
+	<td>label=50 KM</td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>//option[@value='50']</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>alert_radius</td>
+	<td></td>
+</tr>
+<tr>
+	<td>select</td>
+	<td>alert_radius</td>
+	<td>label=20 KM</td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>//option[@value='20']</td>
+	<td></td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/alerts_suite.html b/tests/selenium/alerts_suite.html
new file mode 100755
index 0000000..3318077
--- /dev/null
+++ b/tests/selenium/alerts_suite.html
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+  <title>Test Suite</title>
+</head>
+<body>
+<table id="suiteTable" cellpadding="1" cellspacing="1" border="1" class="selenium"><tbody>
+<tr><td><b>Test Suite</b></td></tr>
+<tr><td><a href="alerts/open_alerts_page_test.html">open alerts page</a></td></tr>
+<tr><td><a href="alerts/map_loaded_test.html">map loaded</a></td></tr>
+<tr><td><a href="alerts/radius_polygon_present_test.html">radius polygon present</a></td></tr>
+<tr><td><a href="alerts/alert_marker_present_test.html">alert marker present</a></td></tr>
+<tr><td><a href="alerts/click_map_test.html">click map</a></td></tr>
+<tr><td><a href="alerts/radius_slider_test.html">radius slider</a></td></tr>
+<tr><td><a href="alerts/category_select_test.html">category select</a></td></tr>
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/main/main_click_all_categories_test.html b/tests/selenium/main/main_click_all_categories_test.html
new file mode 100755
index 0000000..a32bf40
--- /dev/null
+++ b/tests/selenium/main/main_click_all_categories_test.html
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Main Click All Categories</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Main Click All Categories</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>id=cat_0</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Reports")[0]</td>
+	<td>[object Object]</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/main/main_click_filters_test.html b/tests/selenium/main/main_click_filters_test.html
new file mode 100755
index 0000000..b09dfb8
--- /dev/null
+++ b/tests/selenium/main/main_click_filters_test.html
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Main Click Filters</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Main Click Filters</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/</td>
+	<td></td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=#media_4 > span</td>
+	<td></td>
+</tr>
+<tr>
+	<td>waitForCondition</td>
+	<td>selenium.browserbot.getUserWindow().mediaPlotted==4</td>
+	<td>30000</td>
+</tr>
+<tr>
+	<td>waitForCondition</td>
+	<td>typeof(selenium.browserbot.getUserWindow().allGraphData.data) == 'object'</td>
+	<td>10000</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Reports")[0]</td>
+	<td>[object Object]</td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=#media_1 > span</td>
+	<td></td>
+</tr>
+<tr>
+	<td>waitForCondition</td>
+	<td>selenium.browserbot.getUserWindow().mediaPlotted==1</td>
+	<td>30000</td>
+</tr>
+<tr>
+	<td>waitForCondition</td>
+	<td>typeof(selenium.browserbot.getUserWindow().allGraphData.data) == 'object'</td>
+	<td>10000</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Reports")[0]</td>
+	<td>[object Object]</td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=#media_2 > span</td>
+	<td></td>
+</tr>
+<tr>
+	<td>waitForCondition</td>
+	<td>selenium.browserbot.getUserWindow().mediaPlotted==2</td>
+	<td>30000</td>
+</tr>
+<tr>
+	<td>waitForCondition</td>
+	<td>typeof(selenium.browserbot.getUserWindow().allGraphData.data) == 'object'</td>
+	<td>10000</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Reports")[0]</td>
+	<td>[object Object]</td>
+</tr>
+<tr>
+	<td>click</td>
+	<td>css=div.filters.clearingfix > div > ul > li:nth(4) > #media_0 > span</td>
+	<td></td>
+</tr>
+<tr>
+	<td>waitForCondition</td>
+	<td>selenium.browserbot.getUserWindow().mediaPlotted==0</td>
+	<td>30000</td>
+</tr>
+<tr>
+	<td>waitForCondition</td>
+	<td>typeof(selenium.browserbot.getUserWindow().allGraphData.data) == 'object'</td>
+	<td>10000</td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Reports")[0]</td>
+	<td>[object Object]</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/main/main_map_loaded_test.html b/tests/selenium/main/main_map_loaded_test.html
new file mode 100755
index 0000000..ce85bce
--- /dev/null
+++ b/tests/selenium/main/main_map_loaded_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Main Map Loaded</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Main Map Loaded</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map</td>
+	<td>[object Object]</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/main/main_map_reports_layer_loaded_test.html b/tests/selenium/main/main_map_reports_layer_loaded_test.html
new file mode 100755
index 0000000..5b427c0
--- /dev/null
+++ b/tests/selenium/main/main_map_reports_layer_loaded_test.html
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Main Map Reports Layer Loaded</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Main Map Reports Layer Loaded</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertEval</td>
+	<td>this.browserbot.getUserWindow().map.getLayersByName("Reports")[0]</td>
+	<td>[object Object]</td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/main/main_timeline_loaded_test.html b/tests/selenium/main/main_timeline_loaded_test.html
new file mode 100755
index 0000000..c0fd7be
--- /dev/null
+++ b/tests/selenium/main/main_timeline_loaded_test.html
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head profile="http://selenium-ide.openqa.org/profiles/test-case">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<link rel="selenium.base" href="http://localhost/" />
+<title>Main Timeline Loaded</title>
+</head>
+<body>
+<table cellpadding="1" cellspacing="1" border="1">
+<thead>
+<tr><td rowspan="1" colspan="3">Main Timeline Loaded</td></tr>
+</thead><tbody>
+<tr>
+	<td>open</td>
+	<td>/</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertElementPresent</td>
+	<td>css=div#graph canvas</td>
+	<td></td>
+</tr>
+<tr>
+	<td>assertElementPresent</td>
+	<td>css=div#graph div.tickLabels</td>
+	<td></td>
+</tr>
+
+</tbody></table>
+</body>
+</html>
diff --git a/tests/selenium/main_suite.html b/tests/selenium/main_suite.html
new file mode 100755
index 0000000..fe12f5c
--- /dev/null
+++ b/tests/selenium/main_suite.html
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+  <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+  <title>Test Suite</title>
+</head>
+<body>
+<table id="suiteTable" cellpadding="1" cellspacing="1" border="1" class="selenium"><tbody>
+<tr><td><b>Test Suite</b></td></tr>
+<tr><td><a href="main/main_map_loaded_test.html">Main Map Loaded</a></td></tr>
+<tr><td><a href="main/main_map_reports_layer_loaded_test.html">Main Map Reports Layer Loaded</a></td></tr>
+<tr><td><a href="main/main_click_all_categories_test.html">Main Click All Categories</a></td></tr>
+<tr><td><a href="main/main_timeline_loaded_test.html">Main Timeline Loaded</a></td></tr>
+<tr><td><a href="main/main_click_filters_test.html">Main Click Filters</a></td></tr>
+</tbody></table>
+</body>
+</html>
diff --git a/themes/bueno/WooThemes - Bueno.webloc b/themes/bueno/WooThemes - Bueno.webloc
new file mode 100644
index 0000000..d81a5ac
--- /dev/null
+++ b/themes/bueno/WooThemes - Bueno.webloc	
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>URL</key>
+	<string>http://demo.woothemes.com/?name=bueno</string>
+</dict>
+</plist>
diff --git a/themes/bueno/css/_default.css b/themes/bueno/css/_default.css
new file mode 100644
index 0000000..5c4aca9
--- /dev/null
+++ b/themes/bueno/css/_default.css
@@ -0,0 +1,240 @@
+/*
+Theme Name: Bueno
+Description: Repurposing the woothemes.com's Bueno Wordpress theme into an Ushahidi Theme. 
+Demo: http://www.woothemes.com/2009/11/bueno/
+Version: 1.0
+Author: Caleb Bell
+Author Email: caleb at ushahidi.com
+*/
+
+
+/* Theme Settings
+--------------------*/
+  
+  /* Primary Color */
+  h1, a,
+  table.table-list tbody tr td a,
+  .hover .r_details h3 a.r_title,
+  div.footermenu ul li a,
+  div.search-form input.text  { color: #d75c60; }
+  div.search-form input.text:focus,{ border-color: #d75c60; }
+  div.submit-incident a, .btn_submit,
+  div.filters ul li a:hover, div.filters ul li a.active,
+  ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#d75c60; }
+  
+  /* Lighter Primary Color - for hover effects */
+  div#loggedin_user_action a, div#loggedin_user_action a:visited { color:#f3686d; }
+  div.submit-incident a:hover, .btn_submit:hover,
+  div.search-form input.searchbtn { background-color:#f3686d; }
+  div#mainmenu ul,
+  div#mainmenu ul li a:hover,
+  div#mainmenu a.active { border-color:#f3686d; }
+  
+  /* Accent Color - used on the primary nav and in the footer area */
+  div#mainmenu,
+  div#mainmenu ul li a,
+  div.additional-content { border-color:#FBDDDF; }
+  div#underfooter { background:#FBDDDF; }
+  
+  /* Page Background Pattern */
+  /*----------------------*/
+  body#page { background: transparent url("../images/bodytile.jpg") repeat; }
+
+
+/* Feel free to edit these, but you don't have to. */
+
+/* general styles */
+h1 {
+  font-family: 'PT Sans Narrow', arial, sans-serif;
+  font-size: 25px;
+}
+h2 {
+  color: #666;
+}
+h5, h4 {
+  color: #666;
+  font-family: 'PT Sans Narrow', arial, sans-serif;
+  font-size: 22px;
+  text-transform: uppercase;
+}
+
+/* structural divs */
+div#middle {
+  background: none;
+}
+.big-block {
+  margin-bottom: 26px;
+  padding: 16px 0px 10px;
+}
+div#main {
+  background: #fff;
+  padding: 15px 18px;
+  border: 5px solid #EFEFEF;
+  margin-top: 18px;
+}
+/* top-bar */
+#top-bar {
+  width: 100%;
+  background: #000;
+  height: 40px;
+  position: relative;
+}
+div#searchbox {
+  position: absolute;
+  right: 0;
+  border: none;
+  background: transparent;
+  width: auto;
+}
+div#loggedin_user_action {
+  float: left;
+  width: auto;
+}
+div#loggedin_user_action a, div#loggedin_user_action a:visited {
+  font-weight: bold;
+  padding: 6px;
+  margin-top: 2px;
+  width: auto;
+  display: block;
+  float: left;
+}
+div.search-form input.text {
+  border: 1px solid;
+}
+div.search-form input.text:focus {
+  border: 1px solid;
+}
+div.search-form input.searchbtn {
+  width: auto;
+  height: auto;
+  border: 0;
+  text-indent: 0;
+  color: #FFFFFF;
+  display: block;
+  font-size: 12px;
+  background-image:none;
+  margin-left: 10px;
+  line-height: 17px;
+  padding: 2px 5px 3px;
+  text-transform: uppercase;
+  text-shadow: 1px 1px 0 #424242;
+}
+
+/* header */
+/* logo box */
+div#logo {
+  background-color: transparent;
+  padding-left: 0;
+}
+div#logo h1 a {
+  color: #222222;
+  font-family: 'PT Sans Narrow', arial, sans-serif;
+  font-size: 48px;
+}
+div#logo span {
+  font-style: italic;
+  color: #999;
+  font-family: 'Georgia', serif;
+}
+/* submit report button (in the site header and on the "submit a report" page) */
+div.submit-incident a, .btn_submit {
+  text-shadow: 1px 1px 0 #424242;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+}
+
+/* primary nav */
+div#mainmenu {
+  background: none;
+  border-top: 0;
+  border-left: 0;
+  border-right: 0;
+  border-bottom-width: 4px;
+  border-bottom-style: solid;
+  padding: 0;
+  position: relative;
+}
+div#mainmenu ul {
+  position: relative;
+  bottom: -4px;
+}
+div#mainmenu ul li {
+  margin: 0;
+}
+div#mainmenu ul li a {
+  font-family: arial, sans-serif;
+  color: #000;
+  font-size: 16px;
+  font-weight: bold;
+  border-bottom-width: 4px;
+  border-bottom-style: solid;
+}
+div#mainmenu ul li a:hover, div#mainmenu a.active {
+  color: inherit;
+  background: transparent;
+}
+/* home page map filters */
+div.filters ul li a {
+  color: #666;
+}
+div.filters ul li a:hover, div.filters ul li a.active {
+  text-shadow: 1px 1px 0 #424242;
+  color:#fff;
+}
+/* home page category filter */
+div.cat-filters {
+  /*border-bottom:1px solid #F3686D;*/
+}
+ul.category-filters {
+  border: 5px solid #C4C4C4;
+}
+.swatch, .item-swatch {
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+}
+ul.category-filters li a:hover, ul.category-filters li a.active {
+  border: 0;
+}
+/* home page how to report */
+div.additional-content {
+  border-width: 5px;
+  border-style: solid;
+}
+/* Home Page incidents/Mainstream News blocks */
+div.content-container {
+  background: none;
+  color: #666;
+}
+div.content-block {
+  background: transparent;
+}
+
+/* reports listing page */
+#reports-box {
+  width: 655px;
+}
+
+#rb_list-view .rb_report {
+  background: #fff;
+}
+/* single report page */
+div.report-additional-reports h5 {
+  font-family: Arial, sans-serif;
+}
+/* get alerts page */
+.step-1 h2 {
+  padding: 0;
+}
+/* footer */
+div#footer {
+  background: #000;
+}
+
+div.footermenu ul li a {
+  text-transform: uppercase;
+  font-weight: bold;
+  border-left: 1px solid #666;
+}
+.additional-credits {
+  margin-top: 18px;
+}
diff --git a/themes/bueno/css/color-options/blue.css b/themes/bueno/css/color-options/blue.css
new file mode 100644
index 0000000..063d38e
--- /dev/null
+++ b/themes/bueno/css/color-options/blue.css
@@ -0,0 +1,33 @@
+/* Blue Theme
+--------------------*/
+  
+/* Primary Color 17517B */
+/* Lighter Primary Color - for hover effects 1a5d8d */
+/* Accent Color - used on the primary nav and in the footer area  9FCEF1*/
+
+/* Primary Color */
+h1, a,
+table.table-list tbody tr td a,
+.hover .r_details h3 a.r_title,
+div.footermenu ul li a,
+div.search-form input.text  { color: #17517B; }
+div.search-form input.text:focus,{ border-color: #17517B; }
+div.submit-incident a, .btn_submit,
+div.filters ul li a:hover, div.filters ul li a.active,
+ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#17517B; }
+
+/* Lighter Primary Color - for hover effects */
+div#loggedin_user_action a, div#loggedin_user_action a:visited { color:#1a5d8d; }
+div.submit-incident a:hover, .btn_submit:hover,
+div.search-form input.searchbtn { background-color:#1a5d8d; }
+div#mainmenu ul li a:hover, div#mainmenu a.active { border-color:#1a5d8d; }
+
+/* Accent Color - used on the primary nav and in the footer area */
+div#mainmenu,
+div#mainmenu ul li a,
+div.additional-content { border-color:#9FCEF1; }
+div#underfooter { background:#9FCEF1; }
+
+/* Page Background Pattern */
+/*-------------------------*/
+body#page { background: transparent url("../images/color-options/blue/bodytile.jpg") repeat; }
\ No newline at end of file
diff --git a/themes/bueno/css/color-options/brown.css b/themes/bueno/css/color-options/brown.css
new file mode 100644
index 0000000..37b2f40
--- /dev/null
+++ b/themes/bueno/css/color-options/brown.css
@@ -0,0 +1,29 @@
+/* Brown Theme
+--------------------*/
+
+/* Primary Color */
+h1, a,
+table.table-list tbody tr td a,
+.hover .r_details h3 a.r_title,
+div.footermenu ul li a,
+div.search-form input.text  { color: #472300; }
+div.search-form input.text:focus,{ border-color: #472300; }
+div.submit-incident a, .btn_submit,
+div.filters ul li a:hover, div.filters ul li a.active,
+ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#472300; }
+
+/* Lighter Primary Color - for hover effects */
+div#loggedin_user_action a, div#loggedin_user_action a:visited { color:#5d2e00; }
+div.submit-incident a:hover, .btn_submit:hover,
+div.search-form input.searchbtn { background-color:#5d2e00; }
+div#mainmenu ul li a:hover, div#mainmenu a.active { border-color:#5d2e00; }
+
+/* Accent Color - used on the primary nav and in the footer area */
+div#mainmenu,
+div#mainmenu ul li a,
+div.additional-content { border-color:#D3975D; }
+div#underfooter { background:#D3975D; }
+
+/* Page Background Pattern */
+/*-------------------------*/
+body#page { background: transparent url("../images/color-options/brown/bodytile.jpg") repeat; }
\ No newline at end of file
diff --git a/themes/bueno/css/color-options/green.css b/themes/bueno/css/color-options/green.css
new file mode 100644
index 0000000..d16bcd0
--- /dev/null
+++ b/themes/bueno/css/color-options/green.css
@@ -0,0 +1,29 @@
+/* Green Theme
+--------------------*/
+
+/* Primary Color */
+h1, a,
+table.table-list tbody tr td a,
+.hover .r_details h3 a.r_title,
+div.footermenu ul li a,
+div.search-form input.text  { color: #115900; }
+div.search-form input.text:focus,{ border-color: #115900; }
+div.submit-incident a, .btn_submit,
+div.filters ul li a:hover, div.filters ul li a.active,
+ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#115900; }
+
+/* Lighter Primary Color - for hover effects */
+div#loggedin_user_action a, div#loggedin_user_action a:visited { color:#167300; }
+div.submit-incident a:hover, .btn_submit:hover,
+div.search-form input.searchbtn { background-color:#167300; }
+div#mainmenu ul li a:hover, div#mainmenu a.active { border-color:#167300; }
+
+/* Accent Color - used on the primary nav and in the footer area */
+div#mainmenu,
+div#mainmenu ul li a,
+div.additional-content { border-color:#9BD28E; }
+div#underfooter { background:#9BD28E; }
+
+/* Page Background Pattern */
+/*-------------------------*/
+body#page { background: transparent url("../images/color-options/green/bodytile.jpg") repeat; }
\ No newline at end of file
diff --git a/themes/bueno/css/color-options/grey.css b/themes/bueno/css/color-options/grey.css
new file mode 100644
index 0000000..edd960a
--- /dev/null
+++ b/themes/bueno/css/color-options/grey.css
@@ -0,0 +1,30 @@
+/* Grey Theme
+--------------------*/
+
+/* Primary Color */
+h1, a,
+table.table-list tbody tr td a,
+.hover .r_details h3 a.r_title,
+div.search-form input.text  { color: #333333; }
+div.search-form input.text:focus,{ border-color: #333333; }
+div.submit-incident a, .btn_submit,
+div.filters ul li a:hover, div.filters ul li a.active,
+ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#333333; }
+
+/* Lighter Primary Color - for hover effects */
+div.submit-incident a:hover, .btn_submit:hover,
+div.search-form input.searchbtn { background-color:#515151; }
+div#mainmenu ul li a:hover, div#mainmenu a.active { border-color:#515151; }
+
+/* Accent Color - used on the primary nav and in the footer area */
+div#mainmenu,
+div#mainmenu ul li a,
+div.footermenu ul li a,
+div.additional-content { border-color:#B0B0B0; }
+div#underfooter { background:#B0B0B0; }
+div.footermenu ul li a,
+div#loggedin_user_action a, div#loggedin_user_action a:visited { color:#B0B0B0; }
+
+/* Page Background Pattern */
+/*-------------------------*/
+body#page { background: transparent url("../images/color-options/grey/bodytile.jpg") repeat; }
\ No newline at end of file
diff --git a/themes/bueno/css/color-options/purple.css b/themes/bueno/css/color-options/purple.css
new file mode 100644
index 0000000..00a7974
--- /dev/null
+++ b/themes/bueno/css/color-options/purple.css
@@ -0,0 +1,29 @@
+/* Purple Theme
+--------------------*/
+
+/* Primary Color */
+h1, a,
+table.table-list tbody tr td a,
+.hover .r_details h3 a.r_title,
+div.footermenu ul li a,
+div.search-form input.text  { color: #3B0466; }
+div.search-form input.text:focus,{ border-color: #3B0466; }
+div.submit-incident a, .btn_submit,
+div.filters ul li a:hover, div.filters ul li a.active,
+ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#3B0466; }
+
+/* Lighter Primary Color - for hover effects */
+div#loggedin_user_action a, div#loggedin_user_action a:visited { color:#560694; }
+div.submit-incident a:hover, .btn_submit:hover,
+div.search-form input.searchbtn { background-color:#560694; }
+div#mainmenu ul li a:hover, div#mainmenu a.active { border-color:#560694; }
+
+/* Accent Color - used on the primary nav and in the footer area */
+div#mainmenu,
+div#mainmenu ul li a,
+div.additional-content { border-color:#D7BCED; }
+div#underfooter { background:#D7BCED; }
+
+/* Page Background Pattern */
+/*-------------------------*/
+body#page { background: transparent url("../images/color-options/purple/bodytile.jpg") repeat; }
\ No newline at end of file
diff --git a/themes/bueno/css/color-options/red.css b/themes/bueno/css/color-options/red.css
new file mode 100644
index 0000000..664b444
--- /dev/null
+++ b/themes/bueno/css/color-options/red.css
@@ -0,0 +1,29 @@
+/* Red Theme
+--------------------*/
+
+/* Primary Color */
+h1, a,
+table.table-list tbody tr td a,
+.hover .r_details h3 a.r_title,
+div.footermenu ul li a,
+div.search-form input.text  { color: #C40000; }
+div.search-form input.text:focus,{ border-color: #C40000; }
+div.submit-incident a, .btn_submit,
+div.filters ul li a:hover, div.filters ul li a.active,
+ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#C40000; }
+
+/* Lighter Primary Color - for hover effects */
+div#loggedin_user_action a, div#loggedin_user_action a:visited { color:#e50000; }
+div.submit-incident a:hover, .btn_submit:hover,
+div.search-form input.searchbtn { background-color:#e50000; }
+div#mainmenu ul li a:hover, div#mainmenu a.active { border-color:#e50000; }
+
+/* Accent Color - used on the primary nav and in the footer area */
+div#mainmenu,
+div#mainmenu ul li a,
+div.additional-content { border-color:#FFC1C1; }
+div#underfooter { background:#FFC1C1; }
+
+/* Page Background Pattern */
+/*-------------------------*/
+body#page { background: transparent url("../images/color-options/red/bodytile.jpg") repeat; }
\ No newline at end of file
diff --git a/themes/bueno/images/bodytile.jpg b/themes/bueno/images/bodytile.jpg
new file mode 100755
index 0000000..727f48c
Binary files /dev/null and b/themes/bueno/images/bodytile.jpg differ
diff --git a/themes/bueno/images/bullet.png b/themes/bueno/images/bullet.png
new file mode 100755
index 0000000..e1b9d3a
Binary files /dev/null and b/themes/bueno/images/bullet.png differ
diff --git a/themes/bueno/images/bullet_hover.png b/themes/bueno/images/bullet_hover.png
new file mode 100755
index 0000000..24777a7
Binary files /dev/null and b/themes/bueno/images/bullet_hover.png differ
diff --git a/themes/bueno/images/color-options/blue/bodytile.jpg b/themes/bueno/images/color-options/blue/bodytile.jpg
new file mode 100644
index 0000000..7fbf585
Binary files /dev/null and b/themes/bueno/images/color-options/blue/bodytile.jpg differ
diff --git a/themes/bueno/images/color-options/blue/bullet.png b/themes/bueno/images/color-options/blue/bullet.png
new file mode 100755
index 0000000..e1b9d3a
Binary files /dev/null and b/themes/bueno/images/color-options/blue/bullet.png differ
diff --git a/themes/bueno/images/color-options/blue/bullet_hover.png b/themes/bueno/images/color-options/blue/bullet_hover.png
new file mode 100644
index 0000000..d7f5ded
Binary files /dev/null and b/themes/bueno/images/color-options/blue/bullet_hover.png differ
diff --git a/themes/bueno/images/color-options/blue/date.png b/themes/bueno/images/color-options/blue/date.png
new file mode 100644
index 0000000..b71c5b1
Binary files /dev/null and b/themes/bueno/images/color-options/blue/date.png differ
diff --git a/themes/bueno/images/color-options/blue/ico-rss.png b/themes/bueno/images/color-options/blue/ico-rss.png
new file mode 100644
index 0000000..73cce25
Binary files /dev/null and b/themes/bueno/images/color-options/blue/ico-rss.png differ
diff --git a/themes/bueno/images/color-options/brown/bodytile.jpg b/themes/bueno/images/color-options/brown/bodytile.jpg
new file mode 100644
index 0000000..b03ad54
Binary files /dev/null and b/themes/bueno/images/color-options/brown/bodytile.jpg differ
diff --git a/themes/bueno/images/color-options/brown/bullet.png b/themes/bueno/images/color-options/brown/bullet.png
new file mode 100755
index 0000000..e1b9d3a
Binary files /dev/null and b/themes/bueno/images/color-options/brown/bullet.png differ
diff --git a/themes/bueno/images/color-options/brown/bullet_hover.png b/themes/bueno/images/color-options/brown/bullet_hover.png
new file mode 100644
index 0000000..ed4821f
Binary files /dev/null and b/themes/bueno/images/color-options/brown/bullet_hover.png differ
diff --git a/themes/bueno/images/color-options/brown/date.png b/themes/bueno/images/color-options/brown/date.png
new file mode 100644
index 0000000..5207f28
Binary files /dev/null and b/themes/bueno/images/color-options/brown/date.png differ
diff --git a/themes/bueno/images/color-options/brown/ico-rss.png b/themes/bueno/images/color-options/brown/ico-rss.png
new file mode 100644
index 0000000..0a14424
Binary files /dev/null and b/themes/bueno/images/color-options/brown/ico-rss.png differ
diff --git a/themes/bueno/images/color-options/green/bodytile.jpg b/themes/bueno/images/color-options/green/bodytile.jpg
new file mode 100644
index 0000000..222e9d3
Binary files /dev/null and b/themes/bueno/images/color-options/green/bodytile.jpg differ
diff --git a/themes/bueno/images/color-options/green/bullet.png b/themes/bueno/images/color-options/green/bullet.png
new file mode 100755
index 0000000..e1b9d3a
Binary files /dev/null and b/themes/bueno/images/color-options/green/bullet.png differ
diff --git a/themes/bueno/images/color-options/green/bullet_hover.png b/themes/bueno/images/color-options/green/bullet_hover.png
new file mode 100644
index 0000000..da24ec1
Binary files /dev/null and b/themes/bueno/images/color-options/green/bullet_hover.png differ
diff --git a/themes/bueno/images/color-options/green/date.png b/themes/bueno/images/color-options/green/date.png
new file mode 100644
index 0000000..d345ecd
Binary files /dev/null and b/themes/bueno/images/color-options/green/date.png differ
diff --git a/themes/bueno/images/color-options/green/ico-rss.png b/themes/bueno/images/color-options/green/ico-rss.png
new file mode 100644
index 0000000..00718a6
Binary files /dev/null and b/themes/bueno/images/color-options/green/ico-rss.png differ
diff --git a/themes/bueno/images/color-options/grey/bodytile.jpg b/themes/bueno/images/color-options/grey/bodytile.jpg
new file mode 100644
index 0000000..7107b1b
Binary files /dev/null and b/themes/bueno/images/color-options/grey/bodytile.jpg differ
diff --git a/themes/bueno/images/color-options/grey/bullet.png b/themes/bueno/images/color-options/grey/bullet.png
new file mode 100755
index 0000000..e1b9d3a
Binary files /dev/null and b/themes/bueno/images/color-options/grey/bullet.png differ
diff --git a/themes/bueno/images/color-options/grey/bullet_hover.png b/themes/bueno/images/color-options/grey/bullet_hover.png
new file mode 100644
index 0000000..e11b2f1
Binary files /dev/null and b/themes/bueno/images/color-options/grey/bullet_hover.png differ
diff --git a/themes/bueno/images/color-options/grey/date.png b/themes/bueno/images/color-options/grey/date.png
new file mode 100644
index 0000000..e328170
Binary files /dev/null and b/themes/bueno/images/color-options/grey/date.png differ
diff --git a/themes/bueno/images/color-options/grey/ico-rss.png b/themes/bueno/images/color-options/grey/ico-rss.png
new file mode 100644
index 0000000..38440bc
Binary files /dev/null and b/themes/bueno/images/color-options/grey/ico-rss.png differ
diff --git a/themes/bueno/images/color-options/purple/bodytile.jpg b/themes/bueno/images/color-options/purple/bodytile.jpg
new file mode 100644
index 0000000..17ce283
Binary files /dev/null and b/themes/bueno/images/color-options/purple/bodytile.jpg differ
diff --git a/themes/bueno/images/color-options/purple/bullet.png b/themes/bueno/images/color-options/purple/bullet.png
new file mode 100755
index 0000000..e1b9d3a
Binary files /dev/null and b/themes/bueno/images/color-options/purple/bullet.png differ
diff --git a/themes/bueno/images/color-options/purple/bullet_hover.png b/themes/bueno/images/color-options/purple/bullet_hover.png
new file mode 100644
index 0000000..160ec76
Binary files /dev/null and b/themes/bueno/images/color-options/purple/bullet_hover.png differ
diff --git a/themes/bueno/images/color-options/purple/date.png b/themes/bueno/images/color-options/purple/date.png
new file mode 100644
index 0000000..544a58b
Binary files /dev/null and b/themes/bueno/images/color-options/purple/date.png differ
diff --git a/themes/bueno/images/color-options/purple/ico-rss.png b/themes/bueno/images/color-options/purple/ico-rss.png
new file mode 100644
index 0000000..aef840a
Binary files /dev/null and b/themes/bueno/images/color-options/purple/ico-rss.png differ
diff --git a/themes/bueno/images/color-options/red/bodytile.jpg b/themes/bueno/images/color-options/red/bodytile.jpg
new file mode 100644
index 0000000..7c0ef3d
Binary files /dev/null and b/themes/bueno/images/color-options/red/bodytile.jpg differ
diff --git a/themes/bueno/images/color-options/red/bullet.png b/themes/bueno/images/color-options/red/bullet.png
new file mode 100755
index 0000000..e1b9d3a
Binary files /dev/null and b/themes/bueno/images/color-options/red/bullet.png differ
diff --git a/themes/bueno/images/color-options/red/bullet_hover.png b/themes/bueno/images/color-options/red/bullet_hover.png
new file mode 100644
index 0000000..096401c
Binary files /dev/null and b/themes/bueno/images/color-options/red/bullet_hover.png differ
diff --git a/themes/bueno/images/color-options/red/date.png b/themes/bueno/images/color-options/red/date.png
new file mode 100644
index 0000000..58fa14b
Binary files /dev/null and b/themes/bueno/images/color-options/red/date.png differ
diff --git a/themes/bueno/images/color-options/red/ico-rss.png b/themes/bueno/images/color-options/red/ico-rss.png
new file mode 100644
index 0000000..8830451
Binary files /dev/null and b/themes/bueno/images/color-options/red/ico-rss.png differ
diff --git a/themes/bueno/images/date.png b/themes/bueno/images/date.png
new file mode 100755
index 0000000..73f0330
Binary files /dev/null and b/themes/bueno/images/date.png differ
diff --git a/themes/bueno/images/ico-rss.png b/themes/bueno/images/ico-rss.png
new file mode 100755
index 0000000..8f8d249
Binary files /dev/null and b/themes/bueno/images/ico-rss.png differ
diff --git a/themes/bueno/images/woothemes.png b/themes/bueno/images/woothemes.png
new file mode 100644
index 0000000..80b747a
Binary files /dev/null and b/themes/bueno/images/woothemes.png differ
diff --git a/themes/bueno/license.txt b/themes/bueno/license.txt
new file mode 100755
index 0000000..d31195a
--- /dev/null
+++ b/themes/bueno/license.txt
@@ -0,0 +1,281 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc. 
+              51 Franklin St, Fifth Floor, Boston, MA 02110, USA
+
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+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 Program or any portion
+of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+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 Program, 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 Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) 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; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, 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 executable.  However, as a
+special exception, the source code 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.
+
+If distribution of executable or 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 counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program 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.
+
+  5. 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 Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program 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 to
+this License.
+
+  7. 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 Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program 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 Program.
+
+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.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program 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.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies 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 Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, 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
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. 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 PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
diff --git a/themes/bueno/readme.txt b/themes/bueno/readme.txt
new file mode 100644
index 0000000..fed9b67
--- /dev/null
+++ b/themes/bueno/readme.txt
@@ -0,0 +1,10 @@
+Theme Name: Bueno
+Description: Repurposing the Bueno Wordpress theme into an Ushahidi Theme.
+Demo: http://www.woothemes.com/2009/11/bueno/
+Version: 1.0
+Author: Caleb Bell
+Author Email: caleb at ushahidi.com
+
+::Changing the Color Scheme::
+By default, the color scheme is a dark pink. However, there are several
+pre-configured color schemes located in the "color-options" folder. To use them, just move or copy the desired css file to the same directory the "_default.css" file is in your site will automatically update to that color.
\ No newline at end of file
diff --git a/themes/bueno/screenshot.png b/themes/bueno/screenshot.png
new file mode 100644
index 0000000..c6b2b19
Binary files /dev/null and b/themes/bueno/screenshot.png differ
diff --git a/themes/bueno/views/footer.php b/themes/bueno/views/footer.php
new file mode 100644
index 0000000..93dd9f7
--- /dev/null
+++ b/themes/bueno/views/footer.php
@@ -0,0 +1,83 @@
+			</div>
+		</div>
+		<!-- / main body -->
+
+	</div>
+	<!-- / wrapper -->
+
+	<!-- footer -->
+	<div id="footer" class="clearingfix">
+
+		<div id="underfooter"></div>
+
+		<!-- footer content -->
+		<div class="wrapper floatholder">
+
+			<!-- footer credits -->
+			<div class="footer-credits">
+				Powered by the  
+				<a href="http://www.ushahidi.com/">
+					<img src="<?php echo url::file_loc('img'); ?>media/img/footer-logo.png" alt="Ushahidi" class="footer-logo" />
+				</a>
+				  Platform
+			</div>
+			<!-- / footer credits -->
+
+			<!-- footer menu -->
+			<div class="footermenu">
+				<ul class="clearingfix">
+					<li>
+						<a class="item1" href="<?php echo url::site(); ?>">
+							<?php echo Kohana::lang('ui_main.home'); ?>
+						</a>
+					</li>
+
+					<?php if (Kohana::config('settings.allow_reports')): ?>
+					<li>
+						<a href="<?php echo url::site()."reports/submit"; ?>">
+							<?php echo Kohana::lang('ui_main.submit'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php if (Kohana::config('settings.allow_alerts')): ?>
+					<li>
+						<a href="<?php echo url::site()."alerts"; ?>">
+							<?php echo Kohana::lang('ui_main.alerts'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php if (Kohana::config('settings.site_contact_page')): ?>
+					<li>
+						<a href="<?php echo url::site()."contact"; ?>">
+							<?php echo Kohana::lang('ui_main.contact'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php
+					// Action::nav_main_bottom - Add items to the bottom links
+					Event::run('ushahidi_action.nav_main_bottom');
+					?>
+
+				</ul>
+				<?php if ($site_copyright_statement != ''): ?>
+	      		<p><?php echo $site_copyright_statement; ?></p>
+		      	<?php endif; ?>
+			</div>
+			<!-- / footer menu -->
+
+		</div>
+		<!-- / footer content -->
+
+	</div>
+	<!-- / footer -->
+
+	<?php
+	echo $footer_block;
+	// Action::main_footer - Add items before the </body> tag
+	Event::run('ushahidi_action.main_footer');
+	?>
+</body>
+</html>
diff --git a/themes/bueno/views/header.php b/themes/bueno/views/header.php
new file mode 100644
index 0000000..b257b27
--- /dev/null
+++ b/themes/bueno/views/header.php
@@ -0,0 +1,95 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+	<title><?php echo $page_title.$site_name; ?></title>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<link href="<?php echo Kohana::config('core.site_protocol'); ?>://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700" rel="stylesheet" type="text/css">
+	<?php echo $header_block; ?>
+		<?php
+	// Action::header_scripts - Additional Inline Scripts from Plugins
+	Event::run('ushahidi_action.header_scripts');
+	?>
+</head>
+
+<?php
+  // Add a class to the body tag according to the page URI
+
+  // we're on the home page
+  if (count($uri_segments) == 0)
+  {
+    $body_class = "page-main";
+  }
+  // 1st tier pages
+  elseif (count($uri_segments) == 1)
+  {
+    $body_class = "page-".$uri_segments[0];
+  }
+  // 2nd tier pages... ie "/reports/submit"
+  elseif (count($uri_segments) >= 2)
+  {
+    $body_class = "page-".$uri_segments[0]."-".$uri_segments[1];
+  }
+
+?>
+<body id="page" class="<?php echo $body_class; ?>">
+
+	<?php echo $header_nav; ?>
+
+	<!-- top bar-->
+	<div id="top-bar">
+	<!-- searchbox -->
+		<div id="searchbox">
+
+			<!-- languages -->
+			<?php echo $languages;?>
+			<!-- / languages -->
+
+			<!-- searchform -->
+			<?php echo $search; ?>
+			<!-- / searchform -->
+		</div>
+	</div>
+	<!-- / searchbox -->
+
+
+	<!-- wrapper -->
+	<div class="wrapper floatholder">
+
+		<!-- header -->
+		<div id="header">
+
+			<!-- logo -->
+			<?php if ($banner == NULL): ?>
+			<div id="logo">
+				<h1><a href="<?php echo url::site();?>"><?php echo $site_name; ?></a></h1>
+				<span><?php echo $site_tagline; ?></span>
+			</div>
+			<?php else: ?>
+			<a href="<?php echo url::site();?>"><img src="<?php echo $banner; ?>" alt="<?php echo $site_name; ?>" /></a>
+			<?php endif; ?>
+			<!-- / logo -->
+
+			<!-- submit incident -->
+			<?php echo $submit_btn; ?>
+			<!-- / submit incident -->
+
+		</div>
+		<!-- / header -->
+        <!-- / header item for plugins -->
+        <?php
+            // Action::header_item - Additional items to be added by plugins
+	        Event::run('ushahidi_action.header_item');
+        ?>
+
+		<!-- main body -->
+		<div id="middle">
+			<div class="background layoutleft">
+
+				<!-- mainmenu -->
+				<div id="mainmenu" class="clearingfix">
+					<ul>
+						<?php nav::main_tabs($this_page); ?>
+					</ul>
+
+				</div>
+				<!-- / mainmenu -->
diff --git a/themes/default/css/accordion.css b/themes/default/css/accordion.css
new file mode 100644
index 0000000..99c1816
--- /dev/null
+++ b/themes/default/css/accordion.css
@@ -0,0 +1,48 @@
+/**
+ * JQueryUI theme overrides for accordion
+ * Used by the slider on the reports page
+ */
+
+#accordion .ui-accordion-header .ui-icon {
+	right: .5em;
+}
+.ui-accordion .ui-accordion-header a {
+	padding: 0.5em 2.2em 0.5em 0.5em;
+	color: #797979;
+	font-weight: bold;
+}
+#accordion .ui-accordion-header .small-link-button {
+	position: absolute;
+	top: 0;
+	right: 0;
+	display: inline-block;
+	font-size: .9em;
+	font-weight: normal;
+	text-decoration: underline;
+}
+
+/* Active state */
+.ui-state-active, .ui-widget-content .ui-state-active {
+	background: #efefef url(../images/bg_filter-accordion-title.png) top repeat-x;
+}
+.ui-state-active a.f-title {
+	color: #2f4079;
+}
+.ui-accordion .ui-accordion-content {
+	background: #efefef;
+}
+
+/* Content area */
+.ui-accordion .ui-accordion-content {
+	padding: 0 8px 18px 8px;
+	font-size: 11px;
+}
+.ui-accordion .ui-accordion-content p {
+	margin-bottom: 8px;
+}
+.ui-accordion .ui-accordion-content a {
+	color: #1f5e82;
+}
+.ui-accordion .ui-accordion-content a.reset, .ui-accordion .ui-accordion-header a.reset, a.reset {
+	color: #981e01;
+}
\ No newline at end of file
diff --git a/themes/default/css/base.css b/themes/default/css/base.css
new file mode 100644
index 0000000..f0af732
--- /dev/null
+++ b/themes/default/css/base.css
@@ -0,0 +1,389 @@
+/**
+ * Ushahidi Base CSS
+ *
+ * This is a minimal set of styling with just enough to still have the map and
+ * other JS work. Don't override this in your theme unless you really need to.
+ */
+
+/* reset */
+body, div, dl, dt, dd, ul, ol, li, pre, form, fieldset, input, textarea, p, blockquote, th, td {
+	margin: 0;
+	padding: 0;
+}
+
+fieldset, img {
+	border: 0;
+}
+
+html {
+	height: 100%;
+	margin-bottom: 1px;
+}
+
+body {
+	position: relative;
+}
+
+/* links */
+a {
+	color: #3764aa;
+	text-decoration: none;
+}
+
+a:hover {
+	color: #008cff;
+	text-decoration: underline;
+}
+
+/* header elements */
+h1, h2, h3, h4, h5, h6 {
+	font-family: Arial, Helvetica, sans-serif;
+	font-weight: bold;
+	margin-top: 0;
+	padding: 0;
+}
+
+h1 {
+	font-size: 150%;
+	color: #004276;
+}
+
+h2 {
+	font-size: 125%;
+	color: #004276;
+	margin-bottom: .2em;
+}
+
+h3 {
+	font-size: 150%;
+	color: #333333;
+}
+
+h4 {
+	font-size: 175%;
+	color: #333333;
+}
+
+h5 {
+	font-size: 150%;
+	color: #333333;
+	margin-bottom: 10px;
+}
+
+h6 {
+	font-size: 125%;
+	color: #333333;
+}
+
+/* page setup */
+body {
+	font-family: Arial, Helvetica, sans-serif;
+	color: #333333;
+	font-size: 12px;
+}
+
+/* left column */
+div#left {
+	width: 200px;
+}
+
+div#middle .layoutleft div#left {
+	float: left;
+}
+
+div#middle .layoutright div#left {
+	float: right;
+}
+
+/* right column */
+div#right {
+	width: 285px;
+	float: right;
+}
+
+/* right column (width: 50%) */
+div#equalright {
+	width: 50%;
+	padding: 0px;
+	float: right;
+}
+
+/* content column */
+div#main {
+}
+
+div#mainmiddle {
+}
+
+div#main .withoutright {
+}
+
+div#content {
+}
+
+div#main .withright div#content {
+}
+
+div#main .withoutright div#content {
+}
+
+/* footer */
+div#footer {
+	width: auto;
+	clear: both;
+}
+
+div#underfooter {
+	width: auto;
+}
+
+/* clearing & floating */
+.clearingfix:after {
+	content: ".";
+	display: block;
+	line-height: 0px;
+	clear: both;
+	visibility: hidden;
+}
+.clear {
+	clear: both;
+}
+
+.clearingfix {
+	display: block;
+}
+
+.floatbox {
+	overflow: hidden;
+}
+
+.hide {
+	display: none;
+}
+
+/* Map */
+/* Map container div - must have a height set */
+div.map {
+	border: #999 1px solid;
+	width: 573px;
+	height: 480px;
+}
+
+#mapStatus {
+	float: left;
+	background-color: #e1e1e1;
+	border-left: solid 1px #999;
+	border-right: solid 1px #999;
+	border-bottom: solid 1px #999;
+	font-size: 10px;
+	width: 573px;
+}
+
+#mapStatus div {
+	float: left;
+	display: inline-block;
+	padding: 4px 6px 4px 6px;
+	border-right: solid 1px #999;
+}
+#mapMousePosition {
+	min-width: 135px;
+	text-align: center;
+}
+
+#mapOutput sup {
+	height: 0;
+	line-height: 1;
+	vertical-align: text-top;
+	position: relative;
+	font-size: 8px;
+}
+
+div.graph-holder {
+	height: 150px;
+	overflow: hidden;
+	width: 573px;
+}
+
+div.slider-holder {
+	font-size: 70%;
+	height: 70px;
+	margin-top: 1em;
+	width: 554px;
+	padding-left: 1em;
+}
+
+div.slider-holder label {
+	color: #666666;
+}
+
+/* Category filter lists - give basic indenting back (for categories) */
+ul {
+	padding-left: 1.5em;
+}
+
+/* Reports page */
+/* Reports/Filters Layout */
+/* basic styling on reports and filters for convenience */
+#reports-box {
+	float: left;
+	width: 600px;
+}
+#filters-box {
+	float: left;
+	width: 280px;
+	margin-left: 20px;
+}
+.f-clear {
+	float: right;
+}
+.report-stats-container {
+	clear: both;
+}
+
+/* Set initial state to hidden for elements that have JS show/hide functions */
+.btn-less {
+	display: none;
+}
+.rb_list-view .r_categories, .rb_list-view .r_location {
+	display: none;
+}
+#tooltip-box {
+	display: none;
+}
+/* Map container divs - must have a height set*/
+.f-location-box .rb_location-radius {
+	width: 259px;
+	height: 280px;
+}
+#rb_map-view {
+	display: none;
+	width: 590px;
+	height: 384px;
+}
+/* Highlight select categories */
+ul.filter-list li a.selected {
+	background: #DBDBDB;
+}
+
+/* Report submit - map and controls */
+.report_map {
+	overflow: hidden;
+	width: 100%;
+	width: 450px;
+	height: 350px;
+	position: relative;
+}
+.report-find-location .btns {
+	float: left;
+}
+.report-find-location .btns ul {
+	padding: 4px;
+}
+.report-find-location .btn_find {
+	float: left;
+	margin: 10px 0 0 5px;
+}
+.report-find-location input.findtext {
+	margin-top: 9px;
+	padding: 5px 3px 0 3px;
+	height: 24px;
+	float: left;
+	font-size: 14px;
+	font-weight: bold;
+	color: #666;
+	width: 250px;
+	border: 1px #ccc solid;
+}
+.report-find-loading {
+	float: left;
+	height: 31px;
+	margin: 9px 0 0 3px;
+}
+
+/* Get Alerts page */
+.step-1 .map-wrapper .map-holder {
+	overflow: hidden;
+	width: 387px;
+	height: 325px;
+}
+.step-1 .alert_slider {
+	padding: 15px 15px 35px 15px;
+}
+
+/*-- Buttons --*/
+.btns ul {
+	margin: 0;
+	padding: 9px 10px 0;
+	clear: both;
+	overflow: hidden;
+	font-size: 90%;
+}
+.btns ul li {
+	padding: 0 2px;
+	float: left;
+	list-style: none;
+}
+.btns ul li a {
+	padding: 0 6px;
+	color: #5c5c5c;
+	background: #f2f7fa;
+	float: left;
+	line-height: 24px;
+	text-decoration: none;
+	border: 1px solid #d1d1d1;
+}
+.btns ul li a.btns_red {
+	background: #FFE9EC;
+}
+.btns ul li a.btns_gray {
+	background: #eee;
+}
+.btns ul li a:hover {
+	text-decoration: underline;
+}
+/*-- / Button --*/
+
+/* Map Labeler */
+#geometryLabelerHolder {
+	position: absolute;
+	bottom: 0;
+	display: none;
+	width: 100%;
+	background-color: #00008b;
+	opacity: 0.8;
+	z-index: 5000;
+}
+#geometryLabeler {
+	padding: 10px;
+}
+#geometryLabeler label {
+	font-weight: bold;
+	color: #fff;
+	margin-right: 5px;
+}
+#geometryLabeler .lbl_text {
+	width: 80px;
+	margin-right: 15px;
+}
+#geometryLabeler .lbl_text2 {
+	width: 180px;
+}
+#geometryLabeler #geometryLabelComment {
+	padding-bottom: 7px;
+	margin-bottom: 7px;
+	border-bottom: 1px dotted #fff;
+}
+#geometryLat, #geometryLon {
+	display: none;
+}
+#geometryLabelerClose {
+	z-index: 5001;
+	position: absolute;
+	top: 2px;
+	right: 0;
+	width: 18px;
+	height: 18px;
+	background: url("../../../media/img/openlayers/layer-switcher-minimize.png") no-repeat;
+	cursor: pointer;
+}
+
diff --git a/themes/default/css/ie6hacks.css b/themes/default/css/ie6hacks.css
new file mode 100644
index 0000000..6beeaa0
--- /dev/null
+++ b/themes/default/css/ie6hacks.css
@@ -0,0 +1,33 @@
+div.language-box{
+    margin: 11px 0px 13px 15px;
+}
+
+div.search-form{
+    padding: 0px;
+    margin: 10px 15px 10px 5px;
+    float: left;
+}
+
+div.search-form input.text{
+    padding: 1px 0px 2px 0px;
+}
+
+div.search-form input.searchbtn{
+    background: #EEEEEE url(../images/search-button.jpg) 5px 4px no-repeat;
+    margin: 1px 0 0 -1px;
+    text-indent: 0px;
+    font-size: 0px;
+    line-height: 7px;
+}
+
+ul.category-filters li{
+    margin-bottom: 2px;
+}
+
+div.map{
+	width: 571px;
+}
+#mapStatus
+{
+	width: 571px;
+}
\ No newline at end of file
diff --git a/themes/default/css/ie7hacks.css b/themes/default/css/ie7hacks.css
new file mode 100644
index 0000000..38f760a
--- /dev/null
+++ b/themes/default/css/ie7hacks.css
@@ -0,0 +1,35 @@
+/* Handcoded by RapidxHTML - http://www.rapidxhtml.com */
+div.search-form{
+    padding: 0px;
+    margin: 10px 15px 10px 5px;
+    float: left;
+}
+
+div.search-form input.text{
+    padding: 1px 0px 2px 0px;
+    border: #9E9E9E 1px solid;
+    margin: 0;
+    background: #EEEEEE;
+    width: 160px;
+}
+
+div.search-form input.searchbtn{
+    border: #868686 1px solid;
+    background: #EEEEEE url(../images/search-button.jpg) 5px 4px no-repeat;
+    width: 24px;
+    height: 21px;
+    margin: 1px 0 0 -4px;
+    text-indent: 0px;
+    cursor: pointer;
+    padding: 1px 0 19px 0;
+    font-size: 0px;
+    line-height: 7px;
+}
+
+.link-toggle li { 
+	float: left; 
+	display: inline-block; 
+	padding: 6px 0;
+}
+
+.rb_report { position: static !important; }
\ No newline at end of file
diff --git a/themes/default/css/iehacks.css b/themes/default/css/iehacks.css
new file mode 100644
index 0000000..4f762b4
--- /dev/null
+++ b/themes/default/css/iehacks.css
@@ -0,0 +1,41 @@
+/* Handcoded by RapidxHTML - http://www.rapidxhtml.com */
+
+/* clearing & floating */
+.clearingfix{display:inline-block;}
+/* Hides from IE-mac \*/
+* html .clearingfix{height:1%;}
+.clearingfix{display:block;}
+/* End hide from IE-mac */
+* html .floatbox{width:100%;}
+.ie_fix_floats{width:100%;padding:0 1px 0 1px;margin:0 -1px 0 -1px;overflow:hidden;}
+/* Escaping Floats Bug */
+/* Hides from IE-mac \*/
+.floatholder{height:1%;}
+/* End hide from IE-mac */
+/* Peekaboo Bug  */
+/* * html #left{position:relative;}*/
+/* * html #right{position:relative;}*/
+/* * html #main{position:relative;}*/
+/* force hasLayout */
+#wrapper, #header, #top, #middle, #bottom{zoom:1;}
+/* Doubled Float Margins */
+* html #left{display:inline;}
+* html #right{display:inline;}
+* html .float-left{display:inline;}
+* html .float-right{display:inline;}
+/* IE and italics Problem */
+* html #left_container{overflow:visible;}
+* html #right_container{overflow:visible;}
+* html #main_container{overflow:visible;}
+* html i, * html em{overflow:visible;display:inline-block;}
+/* Expanding Box Problem */
+* html #left_container{word-wrap:break-word;}
+* html #right_container{word-wrap:break-word;}
+* html #main_container{word-wrap:break-word;}
+/* Disappearing List-Background Bug */
+/* * html ul{position:relative;}*/
+/* * html ol{position:relative;}*/
+/* * html dl{position:relative;}*/
+/* * html blockquote{zoom:1 }*/
+/* IE/Win Guillotine Bug */
+a, a:hover{background-color:transparent;}
diff --git a/themes/default/css/slider.css b/themes/default/css/slider.css
new file mode 100644
index 0000000..272bbeb
--- /dev/null
+++ b/themes/default/css/slider.css
@@ -0,0 +1,107 @@
+/**
+ * UI Slider CSS
+ * Used by the slider on the main map page
+ */
+
+/*-----UI Slider CSS-----*/
+.ui-slider { clear: both; top: 5px; text-decoration: none !important; }
+.ui-slider .ui-slider-handle  { overflow: visible !important; }
+.ui-slider .ui-slider-tooltip { display: none; }
+.ui-slider .screenReaderContext {
+    position: absolute;
+    width: 0;
+    height: 0;
+    overflow: hidden;
+    left: -999999999px;
+}
+.ui-slider .ui-state-active .ui-slider-tooltip, .ui-slider .ui-state-focus .ui-slider-tooltip, .ui-slider .ui-state-hover .ui-slider-tooltip {
+    display: block;
+    position: absolute;
+    bottom: 2.5em;
+    text-align: center;
+    padding: .3em .2em .4em;
+    font-size: .9em;
+    width: 8em;
+    margin-left: -3.7em;
+}
+.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down, .ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down-inner {
+    position: absolute; 
+    display: block;
+    width:0; 
+    height:0; 
+    border-bottom-width: 0;
+    background: none;
+}
+.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down {    
+    border-left: 7px dashed transparent; 
+    border-right: 7px dashed transparent;
+    border-top-width: 8px;
+    bottom:    -8px;
+    right: auto;
+    left: 50%;
+    margin-left: -7px;
+}
+.ui-slider .ui-slider-tooltip .ui-tooltip-pointer-down-inner {
+    border-left: 6px dashed transparent; 
+    border-right: 6px dashed transparent;
+    border-top: 7px solid #fff;
+    bottom:    auto;
+    top: -9px;
+    left: -6px;
+}
+.ui-slider a {
+    text-decoration: none;
+}
+.ui-slider ol, .ui-slider li, .ui-slider dl, .ui-slider dd, .ui-slider dt {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+.ui-slider ol, .ui-slider dl {
+    position: relative;
+    top: 1.3em;
+    width: 100%;
+}
+.ui-slider dt {
+    top: 1.5em;
+    position: absolute;
+    padding-top: .2em;
+    text-align: center;
+    border-bottom: 1px dotted #ddd;
+    height: .7em;
+    color: #999;
+}
+.ui-slider dt span {
+    background: #fff;
+    padding: 0 .5em;
+}
+.ui-slider li, .ui-slider dd {
+    position: absolute;
+    overflow: visible;
+    color: #666;
+}
+.ui-slider span.ui-slider-label {
+    position: absolute;
+}
+.ui-slider li span.ui-slider-label, .ui-slider dd span.ui-slider-label {
+    display: none;
+}
+.ui-slider li span.ui-slider-label-show, .ui-slider dd span.ui-slider-label-show {
+    display: block;
+}
+.ui-slider span.ui-slider-tic {
+    position: absolute;
+    left: 0;
+    height: .8em;
+    top: -1.3em;
+}
+.ui-slider li span.ui-widget-content, .ui-slider dd span.ui-widget-content {
+    border-right: 0;
+    border-left-width: 1px;
+    border-left-style: solid;
+    border-top: 0;
+    border-bottom: 0;
+}
+.ui-slider .first .ui-slider-tic, .ui-slider .last .ui-slider-tic {
+    display: none;
+}
diff --git a/themes/default/css/style.css b/themes/default/css/style.css
new file mode 100644
index 0000000..0652393
--- /dev/null
+++ b/themes/default/css/style.css
@@ -0,0 +1,2136 @@
+/**
+ * Ushahidi Default theme styling
+ * 
+ * You can reuse this in your theme or replace it completely by include a 
+ * style.css file in your theme.
+ */
+
+/* Reset again after base.css */
+ul { padding: 0; }
+
+/* header elements */
+h1, h2, h3, h4, h5, h6 {
+    font-family: Arial, Helvetica, sans-serif;
+    font-weight: bold;
+    margin-top: 0;
+    padding: 0;
+}
+
+h1{
+    font-size: 150%;
+    color: #004276;
+}
+
+h2{
+    font-size: 125%;
+    color: #004276;
+    margin-bottom: .2em;
+}
+
+h3{
+    font-size: 150%;
+    color: #333333;
+}
+
+h4{
+    font-size: 175%;
+    color: #333333;
+}
+
+h5{
+    font-size: 150%;
+    color: #333333;
+    margin-bottom: 10px;
+}
+
+h6{
+    font-size: 125%;
+    color: #333333;
+}
+
+/* page setup */
+body{
+    font-family: Arial, Helvetica, sans-serif;
+    color: #333333;
+    font-size: 12px;
+    background: #585858 url(../images/page-bg.jpg) 50% 0 repeat-x;
+}
+
+
+/* wrapper setup */
+div.wrapper, div.rapidxwpr{
+    margin: auto;
+    width: 960px;
+}
+
+/* header */
+div#header{
+    position: relative;
+    height: 144px;
+    margin: 0px 0px 0px 0px;
+}
+
+/* logo */
+div#logo{
+    border: 0;
+    background: #E2E2E2;
+    padding: 35px 24px;
+    float: left;
+    margin: 14px 0 0 0;
+    max-width: 725px;
+}
+
+div#logo h1{
+    margin:0;
+    padding:0;
+    font-size: 20px;
+}
+
+div#logo a {
+    text-decoration: none;
+}
+
+div#logo span{
+    font-size:14px;
+}
+
+/* main body setup */
+div#middle{
+    width: auto;
+    margin: 0px;
+    clear: both;
+    background: #FFF;
+}
+
+div#middle .background{
+    min-height: 140px;
+}
+
+div#middle .layoutleft{
+    background: none;
+    padding-bottom:0;
+}
+
+div#middle .layoutright{
+    background: none;
+}
+
+/* left column */
+div#left{
+    width: 200px;
+}
+
+div#middle .layoutleft div#left{
+    float: left;
+    padding: 0px;
+}
+
+div#middle .layoutright div#left{
+    float: right;
+    padding: 0px;
+}
+
+/* right column */
+div#right{
+    width: 285px;
+    padding: 0px;
+    float: right;
+}
+
+/* right column (width: 50%) */
+div#equalright{
+    width: 50%;
+    padding: 0px;
+    float: right;
+}
+
+/* content column */
+div#main{
+    width: auto;
+    margin: 0px;
+    padding: 15px 29px;
+}
+
+div#mainmiddle{
+    margin: 0px 0px 0px 0px;
+}
+
+div#main .withoutright{
+    background: transparent;
+}
+
+div#content{
+    width: auto;
+}
+
+div#main .withright div#content{
+    margin: 0px 325px 0px 0px;
+    padding: 0px;
+}
+
+div#main .withoutright div#content{
+    margin: 0px;
+    padding: 0px;
+}
+
+/* footer */
+div#footer{
+    width: auto;
+    clear: both;
+    color: #fff;
+    background-color: #A8A8A8;
+    padding-bottom: 20px;
+}
+
+div#underfooter{
+    width:auto;
+    height: 28px;
+    background-color:#A8A8A8;
+    position: relative;
+    top: -28px;
+    z-index: -1;
+}
+
+/* searchbox */
+div#searchbox{
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    background: #BEBEBE;
+    border: #9F9F9F 2px solid;
+    border-top: 0;
+    width:380px;
+    padding:0 0 10px 0;
+    
+}
+
+div#loggedin_user_action{
+    float:right;
+    width:100%;
+    text-align:right;
+    padding:0px 15px 0px 0px;
+}
+
+/* languages */
+div.language-box{
+    padding: 0px;
+    margin: 10px 0 0 15px;
+    float: left;
+}
+
+div.language-box select{
+    width:160px;
+    margin:0;
+    padding:1px;
+    font-size: 11px;
+    color:#00789F;
+}
+
+/* searchform */
+div.search-form{
+    padding: 0px;
+    margin: 10px 15px 0 5px;
+    float: left;
+}
+
+div.search-form ul{
+    padding: 0;
+    margin: 0;
+    list-style: none;
+}
+
+div.search-form ul li{
+    padding: 0;
+    margin: 0;
+    list-style: none;
+    float: left;
+}
+
+div.search-form input.text{
+    padding: 2px 0px 2px 0px;
+    border: #9E9E9E 1px solid;
+    margin: 0;
+    background: #EEEEEE;
+    width: 160px;
+    font-size: 13px;
+}
+
+div.search-form input.searchbtn{
+    border: #868686 1px solid;
+    background: #EEEEEE url(../images/search-button.jpg) 5px 5px no-repeat;
+    width: 24px;
+    height: 21px;
+    margin: 0 0 0 -4px;
+    text-indent: -10000px;
+    cursor: pointer;
+    padding: 1px 0 19px 0;
+    text-transform: uppercase;
+}
+
+/* submit incident */
+div.submit-incident{
+    margin: 80px 0 0 0;
+    position:absolute;
+    top:0; right:0;
+    width: 300px;
+}
+
+div.submit-incident a, div.submit-incident a:hover{
+    padding: 8px 10px 8px 35px;
+    background-color: #368C00;
+    background-image: url(../images/submit-incident.png);
+    background-position:10px 8px;
+    background-repeat:no-repeat;
+    color: #FFF;
+    text-transform: uppercase;
+    font-weight: bold;
+    font-size: 14px;
+    text-decoration: none;
+    float: right;
+}
+
+
+/* mainmenu */
+div#mainmenu{
+    background: #C2C2C2;
+    border: #FFF 2px solid;
+    padding: 7px 10px;
+}
+
+div#mainmenu ul{
+    padding: 0px;
+    margin: 0px;
+    list-style: none;
+}
+
+div#mainmenu li{
+    float: left;
+    display: block;
+    margin-right: 15px;
+}
+
+div#mainmenu a{
+    display: block;
+    position: relative;
+    padding: 7px 12px;
+    color: #fff;
+    overflow: hidden;
+    text-transform: uppercase;
+    font-weight: bold;
+    font-size: 120%;
+}
+
+div#mainmenu a:hover, div#mainmenu a.active{
+    text-decoration: none;
+    background: #2E8AB8;
+}
+
+.feedicon {
+  float:right;
+}
+.feedicon img {
+  vertical-align: middle;
+  border: 0;
+}
+
+/* Filters */
+div.filters{
+    font-size: 85%;
+    text-transform: uppercase;
+    margin: 5px 0 15px;
+}
+
+div.filters strong{
+    float: left;
+    margin: 0 10px 0 0;
+    background: url(../images/filters-bg.jpg) 100% 3px no-repeat;
+    padding: 0 15px 0 0;
+}
+
+div.filters ul{
+    list-style: none;
+}
+
+div.filters li{
+    float: left;
+    display: block;
+    margin-right: 10px;
+}
+
+div.filters ul li a{
+    color: #999966;
+    padding: 0px 5px;
+    text-decoration: none;
+}
+
+div.filters ul li a:hover, div.filters ul li a.active{
+    color: #FFFFFF;
+    padding: 0px 5px;
+    background: #3399CC;
+    text-decoration: none;
+}
+
+div.filters .media-filters {float:left; width: 100%;}
+
+a.share{
+    float: left;
+    border-right: 1px solid #999;
+    margin-top:7px;
+    padding:5px 25px 5px 5px;
+    background: url(../images/share.jpg) no-repeat center;
+    text-indent:-2000px;
+    width:125px;
+    height: 18px;
+}
+
+/* Map */
+div.map {
+    border: #999 1px solid;
+    width: 573px;
+    height: 480px;
+    position:relative;
+}
+/* Change map height when timeline is enabled */
+div.map.timeline-enabled {
+	height:366px;
+}
+
+#mapStatus {
+    float: left;
+    background-color: #e1e1e1;
+    border-left: solid 1px #999;
+    border-right: solid 1px #999;
+    border-bottom: solid 1px #999;
+    font-size: 10px;
+    width: 573px;
+}
+
+#mapStatus div {
+    float: left;
+    display: inline-block;
+    padding: 4px 6px 4px 6px;
+}
+
+#mapScale {border-right: solid 1px #999}
+#mapMousePosition {min-width: 135px;border-right: solid 1px #999;text-align: center}
+#mapProjection {border-right: solid 1px #999}
+
+#mapOutput sup {
+    height: 0;
+    line-height: 1;
+    vertical-align: text-top;
+    position: relative;
+    font-size: 8px;
+}
+
+div.graph-holder {
+    height:150px;
+    overflow:hidden;
+    width:573px;
+}
+
+div.slider-holder {
+    font-size:70%;
+    height:70px;
+    margin-top:15px;
+    width:554px;
+    padding-left: 11px;
+}
+
+div.slider-holder label{
+    color: #666666;
+}
+
+/* Slider play button - no longer used */
+.slider-holder .play{
+    float:right;
+    border:1px solid #ccc;
+    background-color:#eee;
+    padding:3px 6px 3px 15px;
+    color:#666;
+    font-weight:bold;
+    background-image:url(../../../media/img/arrow-play.gif);
+    background-position:5px 50%;
+    background-repeat:no-repeat;
+    font-size:1.2em;
+}
+
+.slider-holder .pause { background-image:url(../images/pause.gif); }
+.slider-holder .play a { color:#666; text-decoration:none; }
+.slider-holder .play:hover{ background-color:#ffffcc; }
+.slider-holder .play a:hover{ text-decoration:underline; }
+
+
+/* category filters */
+div.cat-filters{
+    font-size: 85%;
+    text-transform: uppercase;
+    margin: 0 0 15px 0;
+}
+
+div.cat-filters strong{
+    float: left;
+    margin: 0 10px 0 0;
+    background: url(../images/cat-filters-bg.jpg) 0 3px no-repeat;
+    padding: 0 0 0 10px;
+}
+
+div.cat-filters span{
+    font-weight:normal;
+    font-size:90%;
+    margin-left:5px;
+    color:#666;
+    letter-spacing:2px;
+}
+div.cat-filters span a{
+    color:#666;
+}
+
+ul.category-filters, ul.category-filters ul { margin-left: 0; }
+
+ul.category-filters {
+    background: #E7E3DA;
+    border-top: #C7C2BC 7px solid;
+    border-bottom: #C7C2BC 7px solid;
+    list-style: none;
+    padding: 12px 15px;
+}
+
+ul.category-filters li{
+    padding: 0;
+    margin-bottom: 2px;
+    list-style-type:none;
+}
+
+ul.category-filters li a{
+    display: block;
+    padding: 8px 10px 8px 5px;
+    font-size: 15px;
+    text-transform: uppercase;
+    font-weight: bold;
+    text-decoration: none;
+    color: #3F3F3F;
+    border: #E7E3DA 1px solid;
+    position: relative;
+    line-height: 17px;
+    overflow: hidden;
+}
+
+ul.category-filters li a:hover, ul.category-filters li a.active {
+    color: #FFFFFF;
+    background-color: #3484AC;
+    border: #3484AC 1px solid;
+}
+
+ul.category-filters li.child{
+    display:none;
+}
+ul.category-filters li li {
+	padding-left:20px;
+}
+
+div.layers-filters{
+    font-size: 85%;
+    text-transform: uppercase;
+    margin: 20px 0 15px 0;
+}
+
+/* additional content */
+div.additional-content{
+    padding: 15px;
+    background: #F0F0F0;
+}
+
+div.additional-content ol {
+    margin-left: 2em;
+}
+
+div.how-to-report-methods div{
+	margin-bottom: 10px;
+}
+
+/* content blocks */
+div.content-container{
+    padding: 0 0 15px 15px;
+    background: #E2E2E2;
+}
+ul.content-column{
+    width: 100%;
+    padding: 0;
+    margin: 10px 0;
+    list-style: none;
+}
+ul.content-column li {
+    float: left;
+    width: 460px; /*Set default width*/
+    padding: 0;
+    margin: 15px 0 0 0;
+    display: inline;
+}
+div.content-block {
+    margin-right: 15px; /*Creates the 10px gap between each column*/
+    padding: 15px;
+    background-color: #FFFFFF;
+}
+div.content-block h5 span {
+    font-size: 75%;
+}
+div.content-block img {
+    /*Flexible image size with border*/
+    width: 89%;     /*Took 1% off of the width to prevent IE6 bug*/
+    padding: 5%;
+    background:#fff;
+    margin: 0 auto;
+    display: block;
+    -ms-interpolation-mode: bicubic; /*prevents image pixelation for IE 6/7 */
+}
+div.content-block-left{
+    padding: 15px;
+    background: #FFFFFF;
+    width: 427px;
+    float: left;
+    margin-right: 15px;
+}
+
+div.content-block-left h5 span {
+    font-size: 75%;
+}
+
+div.content-block-right{
+    padding: 15px;
+    background: #FFFFFF;
+    width: 427px;
+    float: right;
+}
+
+/* site footer */
+div.site-footer{
+    padding: 15px 15px 0 15px;
+    background: #FFFFFF;
+}
+
+/* footer menu */
+div.footermenu{
+    margin: 0 10px;
+    float: left;
+}
+
+div.footermenu ul{
+    list-style: none;
+    margin: 0 0 10px 0;
+}
+
+div.footermenu ul li{
+    list-style: none;
+    float: left;
+}
+
+div.footermenu ul li a{
+    color: #FFFDCC;
+    border-left: #FFFDCC 1px solid;
+    padding: 0 10px;
+    text-decoration: underline;
+}
+
+div.footermenu ul li a.item1{
+    border: none;
+    padding-left: 0;
+}
+
+div.footermenu ul li a:hover{
+    text-decoration: none;
+}
+
+/* footer credits */
+div.footer-credits{
+    float: right;
+    font-size: 70%;
+    font-weight: bold;
+    text-transform: uppercase;
+    margin: 0 20px 0 0;
+}
+
+div.footer-credits .footer-logo{
+	vertical-align:middle;
+}
+
+/* Reports */
+
+#main .left-col { float:left; width:520px; margin-right:20px }
+#main .right-col { float:right;width:350px; }
+
+div.report-details{
+    width: 300px;
+    float: right;
+    margin: 20px 0 0 0;
+}
+
+.report_detail .r_verified, .report_detail .r_unverified {
+    color:#FFFFFF;
+    font-size:11px;
+    font-weight:bold;
+    display:block;
+    float:right;
+    margin:4px 0 0 18px;
+    padding:3px 5px;
+    text-align:center;
+    text-transform:uppercase;
+    -webkit-border-radius:3px;
+    -moz-border-radius:3px;
+    border-radius:3px;
+}
+
+.report_detail .r_verified { background:#368C00; }
+.report_detail .r_unverified { background:#A60003; }
+
+.report_detail .r_location { background: transparent url(../../../media/img/icon_sprite.png) -5px -163px no-repeat; }
+.report_detail .r_date { background: transparent url(../../../media/img/icon_sprite.png) -4px -133px no-repeat; }
+
+.report-when-where { color:#908B88; margin-bottom:10px; }
+.report-when-where span { padding:3px 5px 3px 18px;  } 
+
+div.report-category-list {
+    border-top:1px dotted #C0C2B8;
+    border-bottom:1px dotted #C0C2B8;
+    padding:10px 0px;
+    overflow:auto;
+}
+
+div.report-category-list a {
+    font-size:11px;
+    padding:1px 5px;
+    margin:0px 5px 5px 0;
+    background:#fffded;
+    display:inline-block;
+    float:left;
+    line-height:18px;
+    color:#828784;
+}
+
+div.report-category-list a:hover { text-decoration:none; color:#333; }
+div.report-category-list a span.r_cat-box { height:16px; width:16px; display:inline-block; margin:3px 3px 3px 0; }
+
+div.report-media { padding:10px 0; border-bottom:1px dotted #C0C2B8;; }
+
+#report-images { } 
+#report-video { padding:10px 0 0 0; } 
+#report-video ul, #report-video ol { overflow:auto; }
+#report-video ul li, #report-video ol li { float:left; list-style: none; margin-right:10px; }
+
+div.report-description-text {
+    padding:10px 0;
+    margin-bottom:18px;
+}
+
+div.report-description-text h5 {
+    padding:0px 0px 5px 0px;
+    margin:0px;
+    font-size:14px;
+    text-transform: capitalize;
+}
+
+div.report-description-text .feature_label,
+div.report-description-text .feature_comment{
+    font-size:90%;
+    margin:0 0 0 10px;
+    padding:3px;
+    background-color:#fffded;
+}
+div.report-description-text .feature_label{
+    font-weight:bold;    
+}
+
+div.report-description-text .feature_comment{
+    font-size:90%;
+}
+
+div.credibility {
+    margin-top:10px;
+    padding:3px;
+    border-top:1px dotted #C0C2B8;
+}
+
+div.report-title {
+    padding-bottom:0px;
+}
+
+div.report-comments {
+    border-bottom:#000000 dotted 1px;
+    padding:5px 0px;
+}
+
+div.report-comments h5 {
+    padding:0px 0px 5px 0px;
+    margin:0px;
+    font-size:14px;
+    text-transform: capitalize;
+}
+
+div.report-comment-box {
+    margin-bottom:15px;
+    padding:5px 0 0 10px;
+    
+}
+
+div.comment-block {
+    padding:5px 0px; background:#F2F2F2;
+}
+
+div.comment-block h5 {
+    padding:0px 0px 5px 0px;
+    margin:0px;
+    font-size:14px;
+    text-transform: capitalize;
+}
+
+div.comment-block form { padding:0 0 0 10px; }
+
+div.report-media-box-tabs { float:right;  }
+div.report-media-box-tabs ul { list-style: none; padding: 0; margin: 0; float:left; }
+div.report-media-box-tabs li { display:inline; list-style-type:none; margin: 0 0 0 2px; text-align:right; }
+div.report-media-box-tabs a {
+    display: inline-block;
+    padding: 2px 7px;
+    font-weight:bold;
+    -webkit-border-radius: 3px;
+    -moz-border-radius: 3px;
+    border-radius: 3px;
+    -webkit-border-bottom-left-radius: 0px;
+    -webkit-border-bottom-right-radius: 0px;
+    -moz-border-radius-bottomright: 0px;
+    -moz-border-radius-bottomleft: 0px;
+    border-bottom-right-radius: 0px;
+    border-bottom-left-radius: 0px;
+    border-bottom:0;
+}
+div.report-media-box-tabs a:hover { text-decoration:none; cursor:pointer; color:#333; }
+div.report-media-box-tabs .report-tab-selected { }
+div.report-media-box-tabs .report-tab-selected a { background:#C0C2B8; color:#333; }
+div.report-media-box-tabs .report-tab-selected a:hover { text-decoration:none; } 
+
+div.report-media-box-content {
+    clear: both;
+    padding:5px 0px;
+}
+
+div.report-media-box {
+}
+
+/* Map Toggles */
+ul.map-toggles { overflow:auto; }
+ul.map-toggles li { display:none; float:right; list-style-type: none; }
+
+ul.map-toggles li a, 
+ul.map-toggles li a:active { display:inline-block; padding:3px 5px 3px 17px; outline:none; }
+
+a.wider-map { background:transparent url(../../../media/img/icon_sprite.png) -4px -202px no-repeat; }
+a.taller-map { background:transparent url(../../../media/img/icon_sprite.png) -4px -221px no-repeat; }
+a.shorter-map { background:transparent url(../../../media/img/icon_sprite.png) -4px -241px no-repeat; }
+a.smaller-map { background:transparent url(../../../media/img/icon_sprite.png) -4px -182px no-repeat; }
+    
+div.report-additional-reports { padding-top: 5px; border-bottom:1px dotted #C0C2B8; }
+div.report-additional-reports .rb_report {  }
+
+div.report-additional-reports h4 {
+    padding:0px 0px 5px 0px;
+    margin:0px;
+    font-size:14px;
+    text-transform: capitalize;
+}
+
+div.report-additional-reports h5 { font-size:12px; margin:4px 120px 2px 0; }
+div.report-additional-reports .r_date { font-size:10px; background:#f2f2f2 url(../../../media/img/icon_sprite.png) 0px -133px no-repeat; }
+div.report-additional-reports .r_location { font-size:10px; padding:3px 0 3px 18px; color:#828784; }
+div.report-details ul.details{ list-style: none; }
+
+div.report-details ul.details li{
+    list-style: none;
+    border-bottom: #EEEEEE 1px solid;
+    padding: 5px 0;
+    color: #666666;
+    font-size: 11px;
+}
+
+div.report-details ul.details li small{
+    display: block;
+    text-transform: uppercase;
+    font-weight: bold;
+    font-size: 11px;
+    margin: 0 0 5px 0;
+}
+
+div.report-details ul.details li a{
+    background: #E2E2E2;
+    padding: 1px 4px 2px 4px;
+    -moz-border-radius: 3px;
+    -webkit-border-radius: 3px;
+    color: #004276;
+    text-transform: uppercase;
+    font-weight: bold;
+    font-size: 10px;
+    text-decoration: underline;
+}
+
+div.report-details ul.details li a:hover{ text-decoration: none; }
+
+.photos { font-size: 40px; padding: .3em; }
+.photos img { vertical-align: bottom; }
+
+/* reports description */
+div.report-description{
+    border: #E8E4DB 5px solid;
+    border-top: 0;
+    margin: 0px 30px 20px 30px;
+}
+
+div.report-description h5{
+    margin: 10px 0px 0px 15px;
+    padding: 0px;
+    text-transform: uppercase;
+    font-size:12px;
+}
+
+div.report-description h3{
+    background: #C7C3BC;
+    color: #393535;
+    padding: 0;
+    margin: 0 -5px;
+    padding: 5px 10px;
+}
+
+div.report-description div.content{
+    background: #F9F8F5;
+    border: #DDDCDC 1px dashed;
+    margin: 5px 15px 15px 15px;
+    padding: 10px 15px;
+    font-size: 14px;
+}
+
+div.report-description div.credibility{
+    margin: 10px 0 0 20px;
+}
+
+div.report-description div.additional-reports{
+    margin: 15px;
+}
+
+div.report-description div.additional-reports a{
+    color: #004276;
+    text-decoration: underline;
+}
+
+div.additional-reports{
+    margin: 15px;
+    padding: 0px;
+}
+
+/* table list */
+table.table-list{
+    width: 100%;
+}
+
+table.table-list, table.table-list td{
+    border-collapse: collapse;
+}
+
+table.table-list thead th{
+    color: #666666;
+    padding: 5px;
+    text-align: left;
+    text-transform: uppercase;
+    font-size: 11px;
+}
+
+table.table-list thead th.title{
+    width: 60%;
+}
+
+table.table-list thead th.location{
+    width: 20%;
+}
+
+table.table-list thead th.source{
+    width: 20%;
+}
+
+table.table-list thead th.date{
+    width: 15%;
+}
+
+table.table-list tbody tr td{
+    padding: 5px;
+    font-size: 11px;
+    border-bottom: #EEEEEE 1px solid;
+    vertical-align: top;
+}
+
+table.table-list tbody tr td a{
+    color: #0099CC;
+}
+
+div.location{
+    float: right;
+    width: 302px;
+}
+
+div.report-map{
+        margin-bottom:10px;
+}
+div.incident-notation{
+    margin: 0 0 5px 0;
+}
+
+div.incident-notation ul{
+    list-style: none;
+    float: right;
+}
+
+div.incident-notation ul li{
+    list-style: none;
+    float: left;
+    margin-left: 20px;
+    color: #999966;
+    text-transform: uppercase;
+    font-size: 10px;
+    font-weight: bold;
+}
+
+/* media files */
+.report-map .map-holder {
+    clear:both;
+    width:348px;
+    height:350px;
+    border: 1px solid #C0C2B8;
+    border-bottom:2px solid #C0C2B8;
+}
+
+
+.more{
+    float: right;
+    margin: 20px 20px 10px 10px;
+    color: #004276;
+    font-weight: bold;
+    text-decoration: underline;
+}
+
+.swatch {
+    float:left;
+    height:16px;
+    margin-right: 8px;
+    width:16px;
+    border:1px solid white;
+}
+
+.category-title {
+    padding-left: 0;
+}
+.category-icon img {
+	float:left;padding-right:5px;
+}
+
+/* Stylized Forms */
+form {
+    margin:0;
+    padding:0;
+}
+
+.text, .textarea, .select, .file {
+    color:#00789F;
+    font-size:120%;
+}
+
+.text:focus, .textarea:focus, .select:focus, .file:focus{
+    background-color:#ffffcc;
+}
+
+.text.error, .textarea.error, .select.error, .file.error{
+    background-color:#FFE0DD;
+    border:1px dotted red;
+}
+
+label.error {
+    margin-left: 10px;
+    color:red;
+    font-style: italic;
+    width: auto;
+    display: block;
+}
+.text {
+    padding:3px 7px;
+    border:1px #ccc solid;
+}
+.textarea {
+    padding:7px 7px 3px 7px;
+    border:1px #ccc solid;
+}
+.select{
+    overflow:hidden;
+}
+.long{
+    width:90%;
+}
+.long2{
+    width:80%;
+    float:left;
+    margin-bottom:10px;
+}
+.short{
+    width:100px;
+}
+.btn_submit {
+    margin: 20px 20px 0 0;
+    padding: 8px 10px;
+    color: white;
+    background-color:#368c00;
+    font-size:14px;
+    cursor:pointer;    
+    border: 0;
+}
+.btn_find {
+    margin:0;
+    padding: 5px 10px 6px 10px;
+    color: white;
+    background-color:#999;
+    font-size:14px;
+    cursor:pointer;    
+    border: 0;    
+}
+
+.report_left{ float:left; width:450px; margin:20px 0 0; padding:0 0 10px; }
+.report_left table { width: 100%; }
+.report_left table tr th { text-align: left; }
+
+.report_block{ margin: 20px 0 20px 25px; }
+.report_block table tr th { text-align: left; }
+
+.report_right{ overflow:hidden; width:455px; margin:20px 0 0 25px; padding:0 0 10px; }
+.report_right table { width: 100%; }
+.report_right table tr th { text-align: left; }
+
+#form_loader { float:left; }
+
+.report-description table { width: 100%; }
+.report-description table tr th { text-align: left; padding:5px; background:#E8E4DB; border-collapse:collapse; }
+.report-description table tr td { padding:0 5px; }
+.report-description table tr td a { display:block; padding:5px 0; }
+.report-description table tr:hover { background:#f8f6f2; }
+
+th.w-01, th.w-02 { border-right:1px dotted #fff; text-transform:capitalize; }
+td.w-01, td.w-02 { border-right:1px dotted #E8E4DB; border-bottom:1px dotted #E8E4DB; text-transform:capitalize;  }
+td.w-03 { border-bottom:1px dotted #E8E4DB; text-transform:capitalize; }
+
+.w-01 { width:50%; padding:0 10px 0 0; text-transform:capitalize; }
+.w-02 { width:320px; padding:0 10px 0 0; text-transform:capitalize; }
+
+.report_bottom { clear:both; }
+
+.report_row { margin:0 0 15px 0; clear:both; }
+.report_row h4 { margin:0 0 5px 0; padding:0; font-size:120%; font-weight:bold; }
+.report_row h4 .example{ font-weight: normal; font-size: 10px; line-height: 12px; color: #a1a1a1; }
+.report_row .date-box{ float:left; margin-right:10px; }
+.report_row .date-box img{ padding:0 5px 0 5px; }
+.report_row .time{ float:left; }
+.report_row .optional-info{ float:left; margin-right:10px; }
+
+.report_category { clear:both; overflow:hidden; font-size:90%; }
+.report_category ul{ width:50%; float:left; margin:0; padding:0 0 11px; }
+.report_category ul li{ overflow:hidden; list-style:none; }
+.report_category ul li label{ color:#555; }
+
+.report_row .custom-field-option { margin-right: 15px; }
+
+/*Categories Tree*/
+.report_category ul ul { width: auto; float: none; }
+.report_category ul li.sub_category { padding-left: 47px; }
+.report_category ul li.sub_category_last { background-position: 16px -1766px; }
+.report_category ul li input.check-box { margin: 0 3px 0 0; }
+.report_category ul li.hover,
+.report_category ul li li:hover { background-color: #efefef; }
+
+ul.treeview li.lastCollapsable,
+ul.treeview li.lastExpandable { width:155px; }
+
+.report_optional{
+    padding:15px;
+    background-color:#eee;
+    width:80%;
+}
+.report_optional h3 {
+    margin:0 0 15px 0;
+    padding:0;
+    font-size:130%;
+    color:#999;
+    font-weight:bold;
+}
+.report_comment, .contact{
+    border:1px #ccc solid;
+    padding:20px;
+    background:#eee;
+    clear:both;
+    overflow:hidden;
+}
+.report_rating{ margin:15px 0 0 10px; font-size:90%; }
+.report_rating div{ float:left; margin-right:3px; }
+
+.rating_value{
+    height:14px;
+    font-weight:bold;
+    text-align:center;
+    background-color:#666;
+    color:#fff;
+    padding:0 4px 0 4px;
+}
+.rating_loading{
+    margin:0 0 0 10px;
+}
+
+table.rating-table td { padding-right:3px; }
+span.dots {
+    width:17px;
+    text-align:center;
+    color:#404040;
+    font-size:18px;
+    font-weight:bold;
+}
+a.add, a.rem{
+    margin:3px 0 0 5px;
+    text-indent:-3000px;
+    overflow:hidden;
+    width:13px;
+    height:13px;
+    float:left;
+    background:url(../../../media/img/icon-plus.gif) no-repeat;
+}
+a.rem { background:url(../../../media/img/icon-minus.gif) no-repeat;}
+.report_map{
+    overflow:hidden;
+    width:100%;
+    border:2px solid #ccc;
+    width:450px;
+    height:350px;
+    position:relative;
+}
+.report-find-location {
+    margin-right:1px;
+    padding:10px 9px 9px 9px;
+    background-color:#eee;
+    border:1px solid #ccc;
+    border-width:0 1px 1px 1px;
+    font-size:90%;
+    color:#666;
+}
+.report-find-location .btns { float:left; }
+.report-find-location .btns ul { padding: 4px; }
+.report-find-location .btn_find { float:left; margin:10px 0 0 5px; }
+.report-find-location input.findtext {
+    margin-top:9px;
+    padding:5px 3px 0 3px;
+    height:24px;
+    float:left;
+    font-size:14px;
+    font-weight:bold;
+    color:#666;
+    width:250px;
+    border:1px #ccc solid;
+}
+.report-find-loading{
+    float:left;
+    height:31px;
+    margin:9px 0 0 3px;
+}
+a.show-more {
+    text-decoration:underline;
+    font-size:10px;
+    padding:1px 0 0 18px;
+    background:url(../../../media/img/icon-plus.gif) no-repeat 0 2px;
+    float:right;
+    margin-right:23px;
+}
+
+/*---big-block---*/
+.big-block {
+    overflow:hidden;
+    padding:16px 25px 10px;
+}
+/* Min-Height */
+.big-block{
+    min-height:400px;
+}
+/* Min-Height for IE */
+* html .big-block {
+    height:400px
+}
+/* View Reports / Help */
+.report_rowtitle, .org_rowtitle{
+    margin: 0px 0 5px 0;
+    padding-bottom:3px;
+    border-bottom:2px #000 solid;
+    overflow:auto;
+}
+.report_col1, .report_col2, .report_col3, .report_col4, .report_col5 {
+    float:left;
+    padding-right:15px;
+}
+.report_col1{
+    width:85px;
+}
+.report_col2{
+    width:425px;
+}
+.report_col3{
+    width:85px;
+    text-align:center;
+}
+.report_col4{
+    width:105px;
+    text-align:center;
+}
+.report_col5{
+    width:105px;
+    text-align:center;
+}
+.report_row1, .report_row2, .org_row1, .org_row2{
+    clear:both;
+    overflow:auto;
+    margin:0 0 10px 0;
+    padding:5px 0 5px 0;
+}
+.report_row1, .org_row1{
+    background-color:#eee;
+}
+.report_row1 h3, .report_row2 h3, .org_row1 h3, .org_row2 h3{
+    margin:0;
+    padding:0;
+    font-size:13px;
+}
+.report_yes, .report_no{
+    font-weight:bold;
+}
+.report_yes{
+    color:#009900;
+}
+.report_no{
+    color:#990000;
+}
+
+.big-block .page_text p{
+    margin-bottom:15px;
+}
+    
+/*---alerts-block---*/
+.step-1 {
+    float:left;
+    width:430px;
+    margin:20px 0 0;
+    padding:0 0 9px;
+}
+.step-1 h2 {
+    margin:0;
+    padding:0 0 0 10px;
+}
+.step-1 .location {
+    overflow:hidden;
+    width:388px;
+    padding:10px;
+    margin:10px 0 0 10px;
+}
+.step-1 .location label {
+    display:block;
+    padding:0 0 8px;
+    font:12px/15px Arial, Helvetica, sans-serif;
+}
+.step-1 .map-wrapper {
+    overflow:hidden;
+    width:407px;
+    padding:10px;
+    margin:10px 0 0 0;
+    border: #999 1px solid;
+}
+.step-1 .map-wrapper p {
+    margin:0;
+    padding:0 0 6px;
+    font:12px/15px Arial, Helvetica, sans-serif;
+}
+.step-1 .map-wrapper .map-holder {
+    overflow:hidden;
+    width:387px;
+    height:325px;
+}
+.step-1 .alert_slider{
+    padding:15px 15px 35px 15px;
+    border:1px solid #ccc;
+    background-color:#fff;
+}
+.step-1 .alert_slider .ui-slider span.ui-slider-label{
+    font-size:8px;
+}
+.step-2-holder {
+    float:right;
+    width:450px;
+}
+.step-2, .step-3 {
+    overflow:hidden;
+    width:430px;
+    margin:10px 0 0;
+    padding:0 0 10px;
+}
+.feed {
+    overflow:hidden;
+    margin:50px 0 0;
+    padding: 15px;
+    border: 1px solid #c2c2c2;
+}
+
+.feed .holder .box {
+  padding-top: 10px;
+}
+
+.step-2 h2, .step-3 h2 {
+    margin:0;
+    padding:0 0 0 10px;
+}
+.step-2 .holder, .step-3 .holder {
+    overflow:hidden;
+    width:100%;
+}
+.step-2 .box, .step-3 .box {
+    overflow:hidden;
+    width:388px;
+    padding:10px;
+    margin:10px 0 0 10px;
+}
+.step-2 .box input {
+    overflow:hidden;
+    margin:0 0 0 22px;
+    clear:both;
+}
+.step-2 .box label {
+    display:block;
+    width:100%;
+    overflow:hidden;
+    padding:0 0 8px;
+    font:12px/15px Arial, Helvetica, sans-serif;
+    clear:both;
+}
+.step-2 .box label input {
+    float:left;
+    margin:2px 0 0;
+    padding:0;
+    padding:0;
+    clear:both;
+    width:auto;
+}
+* html .step-2 .box label input {
+    width:15px;
+    height:15px;
+}
+* +html .step-2 .box label input {
+    width:15px;
+    height:15px;
+}
+.step-2 .box label span {
+    float:left;
+    padding:0 0 0 10px;
+}
+.find-location{
+	float:left;
+	margin:9px 0 0 5px;
+}
+
+/*-- pager --*/
+ul.pager {
+    margin:0;
+    padding:0 0 0 17px;
+}
+ul.pager li{
+    display:inline;
+    margin:0 6px 0 0;
+    font-size:10px;
+    font-weight:bold;
+    color:#555;
+    border:1px solid #bbb;
+    float:left;
+    list-style:none;
+}
+ul.pager li.first {
+    padding:0 5px;
+}
+ul.pager li a{
+    text-decoration:none;
+    color:#555;
+    padding:0 5px;
+
+}
+ul.pager li a:hover,
+ul.pager li a.active {background:#f2f7fa;}
+
+/*-- Feedback --*/
+.feedback_forms{
+    display:none;
+    position: relative;
+    top: -350px;
+    left: 25px;
+    border: 5px solid black;
+    background: #bbb;
+    width: 600px;
+    padding: 5px 20px 5px 20px;
+}
+
+.feedback_forms textarea, .feedback_forms textarea input{
+    color : #000;
+    border : 1px solid #996;
+}
+
+.feedback_forms button {
+    height : 22px;
+    border : 1px solid #000;
+    background : #ccc;
+}
+
+.feedback_title {
+    padding : 20px 0 0 10px;
+}
+
+.or_txt {
+    padding : 0 10px 0 10px;
+    font:14px/17px Arial, Helvetica, sans-serif;
+}
+
+.detailed_feedback {
+    text-align : center;
+    padding: 10px 0 0 0;
+    width : 150px;
+    height : 50px;
+    border : 1px solid #000;
+    background : #ccc;
+    
+}
+
+.detailed_feedback a {
+    color : #000;
+    font:14px/17px Arial, Helvetica, sans-serif;
+    text-decoration :none;
+}
+
+/*-Search-*/
+.search_block{
+    padding:30px;
+}
+.search_info{
+    margin:0 0 15px 0;
+    border:1px solid #999;
+    padding:5px;
+    background-color:#eee;
+}
+.search_result{
+    margin:0 0 15px 0;
+}
+.search_highlight{
+    font-weight:bold;
+}
+.search_result{
+    font-size:13px;
+}
+.search_result h3{
+    margin:0;
+    padding:0;
+    font-weight:normal;
+    font-size:16px;
+}
+.search_date{
+    font-size:12px;
+    color:#006600;
+    font-style: italic;
+    margin-top:3px;
+}
+
+.report-description .orig-report {
+    overflow:hidden;
+    margin: 15px;
+    padding: 0px;
+}
+.report-description .orig-report .report {
+    overflow:hidden;
+    background:#ffc;
+    padding:10px;
+    width:818px;
+}
+.report-description .orig-report .report h4 {
+    margin:0;
+    width:100%;
+    clear:both;
+    font:bold 12px/17px Arial, Helvetica, sans-serif;
+    text-transform:uppercase;
+}
+.report-description .orig-report .report p {
+    margin:0;
+    font:12px/17px Arial, Helvetica, sans-serif;
+}
+.report-description .orig-report .report a.lnk {
+    float:left;
+    margin:7px 0 0;
+    font:10px/13px Arial, Helvetica, sans-serif;
+    text-decoration:none;
+    color:#fff;
+    background:#393 url(../images/green-btn-l.gif) no-repeat;
+    text-transform:uppercase;
+}
+.report-description .orig-report .report a.lnk:hover,
+.report-description .orig-report .report a.lnk:hover span {text-decoration:underline;}
+.report-description .orig-report .report a.lnk span {
+    float:left;
+    padding:0 4px;
+    cursor:pointer;
+    background:url(../images/green-btn-r.gif) no-repeat 100% 0;
+}
+.report-description .orig-report .discussion {
+    overflow:hidden;
+    padding:0px;
+    width:798px;
+}
+.report-description .orig-report .discussion h5 {
+    margin:0;
+    width:100%;
+    clear:both;
+    font:bold 12px/17px Arial, Helvetica, sans-serif;
+    text-transform:uppercase;
+}
+.discussion h5 a, .discussion h5 a:visited{
+    text-transform:none;
+}
+.report-description .orig-report .discussion .discussion-box {
+    overflow:hidden;
+    width:100%;
+    margin:10px 0 0;
+    padding:10px;
+    background:#eee;
+}
+.report-description .orig-report .discussion .discussion-box p {
+    margin:0;
+    font:12px/17px Arial, Helvetica, sans-serif;
+}
+.report-description .orig-report .discussion .discussion-box a.lnk {
+    float:left;
+    margin:7px 0 0;
+    font:10px/13px Arial, Helvetica, sans-serif;
+    text-decoration:none;
+    color:#fff;
+    background:#393 url(../images/green-btn-l.gif) no-repeat;
+    text-transform:uppercase;
+}
+.report-description .orig-report .discussion .discussion-box a.lnk:hover,
+.report-description .orig-report .discussion .discussion-box a.lnk:hover span {text-decoration:underline;}
+.report-description .orig-report .discussion .discussion-box a.lnk span {
+    float:left;
+    padding:0 4px;
+    cursor:pointer;
+    background:url(../images/green-btn-r.gif) no-repeat 100% 0;
+}
+
+/*---blocks-holder---*/
+.blocks-holder {
+    overflow:hidden;
+    width:100%;
+    margin: 30px;
+}
+
+/*--error/success--*/
+div.green-box, div.red-box {
+    margin:10px 0 25px 0;
+    padding:9px 0 8px;
+    clear:both;
+    overflow:hidden;
+}
+
+div.green-box { background:#d8f1d8; border:2px solid #a7d1a7; }
+
+div.red-box { background:#FFD8D9; border:2px solid #990000; }
+
+div.green-box h3, div.red-box h3{
+    margin:0;
+    padding:0 0 0 15px; 
+    font-size:14px;
+    color:#555;
+}
+div.green-box ul, div.red-box ul{
+    margin-left:2em;
+    padding-left:2em;
+}
+
+.page-reports-submit div.green-box {
+	margin: 25px 25px 0px 25px;
+}
+
+/*--Alerts Messages--*/
+.alert_response, .thanks_msg {
+    margin:10px 15px 10px 15px;
+    border:2px #ccc solid;
+    background-color:#fff;
+    padding: 15px;
+    font:14px/17px Arial, Helvetica, sans-serif;
+}
+.alert_confirm {
+    margin:10px 0 0 0;
+    padding:10px;
+    border:1px #ccc solid;
+    background-color:#FFFFCC;
+}
+.alert_confirm .label{
+    margin-bottom:10px;
+}
+
+span.required {
+  color: #CC1830;
+}
+span.private {
+	color:gray;font-size:70%
+}
+
+/* Rounded Corners */
+.r-3 { -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; }
+.r-4 { -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; }
+.r-5 { -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
+.r-8 { -webkit-border-radius: 8px; -moz-border-radius: 8px; border-radius: 8px; }
+
+.top-cap {
+    -webkit-border-bottom-left-radius: 0px;
+    -webkit-border-bottom-right-radius: 0px;
+    -moz-border-radius-bottomright: 0px;
+    -moz-border-radius-bottomleft: 0px;
+    border-bottom-right-radius: 0px;
+    border-bottom-left-radius: 0px;
+}
+
+.right-cap { 
+    -webkit-border-top-right-radius: 0px;
+    -webkit-border-bottom-right-radius: 0px;
+    -moz-border-radius-topright: 0px;
+    -moz-border-radius-bottomright: 0px;
+    border-top-right-radius: 0px;
+    border-bottom-right-radius: 0px;
+}
+
+.bottom-cap    {
+    -webkit-border-top-left-radius: 0px;
+    -webkit-border-top-right-radius: 0px;
+    -moz-border-radius-topright: 0px;
+    -moz-border-radius-topleft: 0px;
+    border-top-right-radius: 0px;
+    border-top-left-radius: 0px;
+}
+
+.left-cap {
+    -webkit-border-top-left-radius: 0px;
+    -webkit-border-bottom-left-radius: 0px;
+    -moz-border-radius-topleft: 0px;
+    -moz-border-radius-bottomleft: 0px;
+    border-top-left-radius: 0px;
+    border-bottom-left-radius: 0px;
+}
+
+/* 
+Theme Name: Front-End Reports Filtering
+Description: Markup for Front-End Reports Filtering
+Author: Caleb Bell
+version: 1
+*/
+
+/* Reports/Filters Layout */
+#reports-box { float:left; width:600px; }
+#filters-box { float:left; width:280px; margin-left:20px; }
+
+
+/* Report Box Navigation Controls (pagination, view/map toggle...etc) */
+.rb_nav-controls { background:#404040; border:1px solid #ccc; height:35px; color:#dedede; }
+.rb_nav-controls table { height:inherit; }
+.rb_nav-controls td { border-right:1px solid #ccc; vertical-align:middle; padding:0 6px; }
+.rb_nav-controls td.last { border:none; padding:0 0 0 18px; }
+    
+/* Link Toggle */ 
+.rb_nav-controls .link-toggle { margin-right:5px; }
+ul.link-toggle { display:block; }
+ul.link-toggle li { display:inline-block; }
+ul.link-toggle li a { 
+    display:inline-block; 
+    padding:3px 5px; 
+    color:#a2a8a5; 
+    border:1px solid #404040; 
+    -webkit-border-radius: 3px; 
+    -moz-border-radius: 3px; 
+    border-radius: 3px;
+}
+ul.link-toggle li a:hover { background-color:#8a8a8a; }
+                
+ul.lt-icons-and-text li a { padding:3px 5px 3px 25px; }
+ul.lt-icons-only li a { overflow:hidden; text-indent:-23434px; }
+            
+/* Icons and text */
+ul.link-toggle li.active a,
+ul.link-toggle li a:hover { color:#fff; border:1px solid #8a8a8a; text-decoration:none; }
+            
+ul.link-toggle li a.list { background:transparent url("../images/icon_sprite-2.png") 2px -29px no-repeat; ; }
+ul.link-toggle li.active a.list,
+ul.link-toggle li a:hover.list { background:#8a8a8a url("../images/icon_sprite-2.png") 2px -3px no-repeat}
+            
+ul.link-toggle li a.map { background:transparent url("../images/icon_sprite-2.png") 2px -80px no-repeat; }
+ul.link-toggle li.active a.map,
+ul.link-toggle li a:hover.map { background:#8a8a8a url("../images/icon_sprite-2.png") 2px -54px no-repeat; }
+            
+/* Icons only */    
+ul.link-toggle li a.prev,
+ul.link-toggle li a.next { border:1px solid #7d7d7d; }
+ul.link-toggle li a.prev { width:10px; background:#5f5f5f url("../images/icon_sprite-2.png") 0 -147px no-repeat; }
+ul.link-toggle li a.next { width:10px; background:#5f5f5f url("../images/icon_sprite-2.png") 0 -128px no-repeat; }
+ul.link-toggle li a:hover.prev,
+ul.link-toggle li a:hover.next { background-color:#6e6e6e; } 
+    
+/* Pagination */
+.rb_nav-controls ul.pager { display:block; padding:0; margin-left:5px;  }
+.rb_nav-controls ul.pager li { border:none; color:#fff; }
+.rb_nav-controls ul.pager li.first {margin:0 6px 0 0; padding:0;}
+.rb_nav-controls ul.pager li.last {}
+.rb_nav-controls ul.pager li a { 
+    display:inline-block; 
+    padding:3px 5px; 
+    color:#a2a8a5; 
+    border:1px solid #a2a8a5; 
+    -webkit-border-radius: 3px; 
+    -moz-border-radius: 3px; 
+    border-radius: 3px;
+}
+.rb_nav-controls ul.pager li a:hover,
+.rb_nav-controls ul.pager li.current a { color:#fff; background:#6c6c6c; }
+
+
+/* Heading with tabs */
+.heading { border-bottom:3px solid #c2c2c2; position:relative; }
+a.ic-time {
+    font-size:12px; 
+    font-weight:normal; 
+    display:inline-block; 
+    padding:3px 0 3px 20px; 
+    background:transparent url("../../../media/img/icon_sprite.png") no-repeat scroll -3px -133px;
+}
+    
+/* Tooltip box */
+#tooltip-box {
+    display:none; 
+    position:absolute; 
+    z-index:1250; 
+    width:250px; 
+    background:#f2f2f2; 
+    font-size:10px; 
+    padding:5px; 
+    color:#494949; 
+    border:1px solid #c4c4c4; 
+    -webkit-border-radius:3px; 
+    -moz-border-radius:3px; 
+    border-radius:3px; 
+    -webkit-box-shadow: 0 2px 0 #948F82; 
+    -moz-box-shadow: 0 2px 0 #948F82; 
+    box-shadow: 0 2px 0 #948F82;
+}
+
+#tooltip-box .t-arrow { 
+    position:absolute; 
+    top:-13px; 
+    right:118px; 
+    background:transparent url("../images/icon_sprite-2.png") 0 -181px no-repeat; 
+    width:26px;
+    height:14px; 
+}
+        
+ul.inline-links { display:block; text-align:center;  }
+ul.inline-links li { display:inline; list-style-type:none; }
+ul.inline-links li a { font-size:12px; font-weight:normal; display:inline-block; padding:3px 5px; }
+ul.inline-links li a:hover { text-decoration:none; }
+ul.inline-links li a.active { background:#c2c2c2; font-weight:bold; color:#fff; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; }
+        
+p.labeled-divider { text-align:center; border-top:1px solid #a4a4a4; margin:10px 5px 0; }
+p.labeled-divider span { position:relative; top:-8px; display:inline-block; background:#f2f2f2; padding:0 8px; }
+        
+.report-date-filter input { width:78px; }
+.report-date-filter .filter-button { position: static; }
+
+.reports-main {overflow: auto;}
+
+/* Report List View */
+.rb_report { position:relative; border-top:1px solid #c0c2b8; overflow:auto; padding-bottom:10px; }
+#rb_list-view { padding:18px 0 0 0; overflow:auto; }
+#rb_list-view .rb_report { 
+    position:relative; 
+    border:1px solid #c0c2b8; 
+    overflow:auto; 
+    margin-bottom:10px; 
+    padding:0; 
+    -webkit-border-radius: 5px; 
+    -moz-border-radius: 5px; 
+    border-radius: 5px; 
+    border-left:6px solid #368C00;
+}
+#rb_list-view .verified { border-left:6px solid #368C00; }
+#rb_list-view .unverified { border-left:6px solid #A60003; }
+
+#rb_map-view { display:none; width: 590px; height: 384px; border:1px solid #CCCCCC; margin: 3px auto; }
+    
+.reports-box .hover { background:#fffded; }
+    
+.r_media { float:left; width:95px; margin:3px 15px 3px 3px; }
+.r_photo { text-align: center; }
+.r_photo a { display:inline-block; padding: 2px; border:1px solid #c0c2b8;}
+.r_photo img { max-width:89px; max-height:59px; }
+.r_video { }
+.r_video a { display:inline-block; font-size:11px; background:transparent url(../../../media/img/icon_sprite.png) -3px -100px no-repeat; padding:4px 0 3px 23px; }
+    
+.r_categories { position:relative; margin-top:10px; display:none; }
+.r_categories h4 { font-size:10px; padding:0; margin:0 0 4px 0; border-bottom:1px solid #c0c2b8; text-transform: uppercase; }
+a.r_category { display:block; position:relative; float:left; height:16px;    width:16px; margin:0 4px 4px 0; }
+a.r_category span.r_cat-box { display:block; height:16px;    width:16px; background:transparent; }
+span.r_cat-desc { display:none; }
+        
+.r_cat_tooltip {
+    display:none;
+    position:absolute;
+    z-index:1000;
+    background:transparent url(../../default/images/tooltip-arrow.png) 8px bottom no-repeat;
+    padding:0 0 6px;
+}
+.r_cat_tooltip a { background:#282828; display:inline-block; padding:3px 8px; color:#fff; text-transform:uppercase; font-size:10px; }
+.r_cat_tooltip a:hover { text-decoration:none; color:#fff; }
+    
+.r_details { float:left; width:auto; margin:3px 0; }
+.r_details h3 { margin-bottom:0px; width:320px; }
+.r_details h3 a.r_title { font-size:14px; color:#727272; }
+.hover .r_details h3 a.r_title { color:#3764AA; } 
+
+.r_details h3 a.r_comments {
+    color:#fff; font-size:11px; 
+    display:inline-block;
+    text-align:center;
+    height:19px;
+    width:24px;
+    padding:4px 0 3px;
+    background:transparent url(../../../media/img/icon_sprite.png) 0 0 no-repeat;
+}
+
+.r_details h3 a.r_comments:hover {  }
+
+/* Verified/unverified */
+.r_details h3 span { 
+    display:none; 
+    position:absolute; 
+    top:0px;
+    left:-10px;
+    color:#FFFFFF;
+    font-size:9px;
+    font-weight:normal;
+    margin:0 0 0 7px;
+    padding:1px 3px;
+    text-align:center;
+    text-transform:uppercase;
+    -webkit-border-radius:3px;
+    -moz-border-radius:3px;
+    border-radius:3px;
+}
+.r_details h3 span.r_verified { background:#368C00; }
+.r_details h3 span.r_unverified { background:#A60003; }
+
+/* Show on hover */
+.hover .r_details h3 span { display:inline-block; }  
+        
+p.r_date {
+    position:absolute;
+    top:0px;
+    right:0px;
+    padding:3px 5px 4px 23px; 
+    background:#e3e3e3 url(../../../media/img/icon_sprite.png) 0px -133px no-repeat;
+    color:#908b88;
+}
+        
+div.r_description { margin-bottom:5px; width:450px; }
+        
+#rb_list-view p.r_location { display:none; }
+
+p.r_location a {
+    color:#828784; 
+    font-size:11px; 
+    line-height:11px; 
+    padding:4px 0 6px 18px; 
+    background:transparent url(../../../media/img/icon_sprite.png) -6px -163px no-repeat;
+}
+
+/* "More Info" state */
+a.btn-show { display:none; }
+.hover a.btn-more { display:inline; }
+        
+.more-details .r_categories,
+#rb_list-view .more-details p.r_location { display:block; }
+
+/* Filters */
+#filters-box {}
+#filters-box h2 { color:#494949; }
+
+.loading-reports {width: 100%; margin-top: 100px; text-align: center;}
+.loading-reports h3 { padding: 10px 2px; }
+
+/* Filter content boxes */
+.f-category-box { height:258px; }
+.f-location-box .rb_location-radius { width: 259px; height: 280px; border: #999 1px solid; }
+.f-type-box {}
+.f-verification-box {}
+.f-media-box {}
+    
+/* Filter List */
+ul.filter-list { list-style-type:none; position:relative; }
+ul.filter-list ul { list-style-type:none; padding:0 0 0 20px; }
+ul.filter-list li { display:block; position:relative; }
+ul.filter-list li a { display:block; position:relative; overflow:auto; padding:3px 0 3px 20px; margin-bottom:3px; }
+
+ul.filter-list li a.selected {
+    background:#dbdbdb url("../images/icon_sprite-2.png") -22px -103px no-repeat; 
+    z-index:120; color:#636363;
+    -webkit-border-radius: 3px;
+    -moz-border-radius: 3px;
+    border-radius: 3px; 
+}
+
+ul.filter-list li a span.item-swatch { display:block; height:16px; width:16px; margin-right: 8px; border:1px solid white; float:left; overflow: hidden; }
+ul.filter-list li a span.item-icon { display:block; float:left; margin-right:8px; height:16px; width:16px; }
+ul.filter-list li a span.item-title { display:block; padding:0 40px 0 26px; }
+ul.filter-list li a span.item-count {
+    display:block; 
+    padding:1px 3px; 
+    position:absolute; 
+    right:0;
+    top:4px;
+    background:#dbdbdb;
+    color:#636363;
+    -webkit-border-radius: 3px; 
+    -moz-border-radius: 3px;
+    border-radius: 3px;
+}
+
+ul.filter-list li li { position:relative; }
+
+ul.filter-list li a.btn-cancel { 
+    position:absolute; 
+    left:0; 
+    overflow:hidden; 
+    height:25px;
+    width:20px; 
+    text-indent:-23423px;
+    background:transparent url("../images/icon_sprite-2.png") -22px -103px no-repeat;
+    z-index:120;
+}
+
+/* For child categories because nested UL/LIs are difficult in this case */
+li.report-listing-category-child { padding-left:20px; }
+
+/* Filter Controls */    
+#filter-controls    { position:relative; padding:15px; margin-top:-2px; }
+.expected-results {  }
+
+a.filter-button    { 
+    position:absolute;  
+    right:0px;
+    top:11px; 
+    color:#fff;
+    display:inline-block;
+    text-transform:uppercase;
+    padding:5px 8px;
+    font-weight:bold;
+    background:#404040;
+    -webkit-border-radius: 3px;
+    -moz-border-radius: 3px;
+    border-radius: 3px;
+}
+
+a.filter-button:hover { text-decoration:none; background:#707070;  }
+
+.report-stats-container {display: none;}
+
+/* Icons */
+/* things */
+.ic-sms { background:transparent url(../images/icons/mobile-phone.png) no-repeat; }
+.ic-email { background:transparent url(../images/icons/mail.png) no-repeat; }
+.ic-rss { background:transparent url(../images/icons/social-rss.png) no-repeat; }
+.ic-webform { background:transparent url(../images/icons/application-form.png) no-repeat; }
+.ic-photos { background:transparent url(../images/icons/images.png) no-repeat; }
+.ic-videos { background:transparent url(../images/icons/films.png) no-repeat; }
+.ic-news { background:transparent url(../images/icons/newspapers.png) no-repeat; }
+.ic-verified { background:transparent url(../images/icons/tick-button.png) no-repeat; }
+.ic-unverified { background:transparent url(../images/icons/cross-button.png) no-repeat; }
+
+/* social */
+.ic-twitter { background:transparent url(../images/icons/twitter.png) no-repeat; }
+.ic-facebook { background:transparent url(../images/icons/facebook.png) no-repeat; }
+.ic-youtube { background:transparent url(../images/icons/youtube.png) no-repeat; }
+.ic-foursquare { background:transparent url(../images/icons/foursquare.png) no-repeat; }        
+.ic-meetup { background:transparent url(../images/icons/meetup.png) no-repeat; }
+.ic-posterous { background:transparent url(../images/icons/posterous.png) no-repeat; }
+.ic-reddit { background:transparent url(../images/icons/reddit.png) no-repeat; }
+.ic-tumblr { background:transparent url(../images/icons/tumblr.png) no-repeat; }
+.ic-yelp { background:transparent url(../images/icons/yelp.png) no-repeat; }
+.ic-linkedin { background:transparent url(../images/icons/linkedin.png) no-repeat; }
+.ic-stumbleupon { background:transparent url(../images/icons/stumbleupon.png) no-repeat; }
+.ic-technorati { background:transparent url(../images/icons/technorati.png) no-repeat; }
+.ic-vimeo { background:transparent url(../images/icons/vimeo.png) no-repeat; }
+    
+#accordion .ui-accordion-header a.hide { display:none; }
+
+.ui-datepicker { display:none; } /* fixes bug where an empty datepicker div was showing up on the bottom of the reports page */
+
+.badge {
+    float:left;
+    width:100px;
+    text-align:center;
+    background-color:#E9E9E9;
+    padding:5px;
+    margin:0px 5px;
+}
+
+/*----- Checkins -----*/
+div.ci_imgblock {
+	width:59px;
+	height:59px;
+	overflow:hidden;
+	float:left;
+	margin:0px;
+}
+div.ci_shorterblock {
+	height:16px;
+}
+div.ci_tallerblock {
+	height:59px;
+}
+div.ci_colorblock {
+	width:69px;
+	float:left;
+	margin:0px 10px 0px 0px;
+}
+div.ci_colorfade {
+	width:59px;
+	height:20px;
+	float:left;
+	margin:0px;
+	background-color:#FFFFFF;
+	opacity:.5;
+}
+div.ci_checkin {
+	border-bottom:1px solid #B3B3B3;
+}
+
+.page-profile .content-bg { padding:25px; }
+.page-profile-user .content-bg { padding:25px; }
+.profile-left { float:left;height:250px;width:160px;text-align:center; }
+.profile-right { float:left;padding-left:25px;width:400px; }
+.user-color { width:160px;height:20px; }
+.badges { float:left;padding-left:25px;width:275px;}
+.badge img { margin:5px; }
+
+.allowed-html {
+	font-weight: normal;
+	font-size: 10px;
+	line-height: 12px;
+	color: #a1a1a1;
+}
diff --git a/themes/default/images/bg_filter-accordion-title.png b/themes/default/images/bg_filter-accordion-title.png
new file mode 100644
index 0000000..73dc4d2
Binary files /dev/null and b/themes/default/images/bg_filter-accordion-title.png differ
diff --git a/themes/default/images/cat-filters-bg.jpg b/themes/default/images/cat-filters-bg.jpg
new file mode 100644
index 0000000..d88547b
Binary files /dev/null and b/themes/default/images/cat-filters-bg.jpg differ
diff --git a/themes/default/images/category_1_1305234038.png b/themes/default/images/category_1_1305234038.png
new file mode 100644
index 0000000..f977750
Binary files /dev/null and b/themes/default/images/category_1_1305234038.png differ
diff --git a/themes/default/images/category_1_1305234038_16x16.png b/themes/default/images/category_1_1305234038_16x16.png
new file mode 100644
index 0000000..380d571
Binary files /dev/null and b/themes/default/images/category_1_1305234038_16x16.png differ
diff --git a/themes/default/images/category_5_1305234050.png b/themes/default/images/category_5_1305234050.png
new file mode 100644
index 0000000..7647a96
Binary files /dev/null and b/themes/default/images/category_5_1305234050.png differ
diff --git a/themes/default/images/category_5_1305234050_16x16.png b/themes/default/images/category_5_1305234050_16x16.png
new file mode 100644
index 0000000..bf08fe0
Binary files /dev/null and b/themes/default/images/category_5_1305234050_16x16.png differ
diff --git a/themes/default/images/filters-bg.jpg b/themes/default/images/filters-bg.jpg
new file mode 100644
index 0000000..d352fa7
Binary files /dev/null and b/themes/default/images/filters-bg.jpg differ
diff --git a/themes/default/images/green-btn-l.gif b/themes/default/images/green-btn-l.gif
new file mode 100644
index 0000000..1b2225f
Binary files /dev/null and b/themes/default/images/green-btn-l.gif differ
diff --git a/themes/default/images/green-btn-r.gif b/themes/default/images/green-btn-r.gif
new file mode 100644
index 0000000..495643e
Binary files /dev/null and b/themes/default/images/green-btn-r.gif differ
diff --git a/themes/default/images/icon_sprite-2.png b/themes/default/images/icon_sprite-2.png
new file mode 100644
index 0000000..2f03d30
Binary files /dev/null and b/themes/default/images/icon_sprite-2.png differ
diff --git a/themes/default/images/icons/application-form.png b/themes/default/images/icons/application-form.png
new file mode 100755
index 0000000..a1638fe
Binary files /dev/null and b/themes/default/images/icons/application-form.png differ
diff --git a/themes/default/images/icons/cross-button.png b/themes/default/images/icons/cross-button.png
new file mode 100755
index 0000000..933272b
Binary files /dev/null and b/themes/default/images/icons/cross-button.png differ
diff --git a/themes/default/images/icons/facebook.png b/themes/default/images/icons/facebook.png
new file mode 100644
index 0000000..4a076f5
Binary files /dev/null and b/themes/default/images/icons/facebook.png differ
diff --git a/themes/default/images/icons/film.png b/themes/default/images/icons/film.png
new file mode 100755
index 0000000..c8bd259
Binary files /dev/null and b/themes/default/images/icons/film.png differ
diff --git a/themes/default/images/icons/films.png b/themes/default/images/icons/films.png
new file mode 100755
index 0000000..d1270dd
Binary files /dev/null and b/themes/default/images/icons/films.png differ
diff --git a/themes/default/images/icons/foursquare.png b/themes/default/images/icons/foursquare.png
new file mode 100644
index 0000000..cbc4b5e
Binary files /dev/null and b/themes/default/images/icons/foursquare.png differ
diff --git a/themes/default/images/icons/image.png b/themes/default/images/icons/image.png
new file mode 100755
index 0000000..c485c20
Binary files /dev/null and b/themes/default/images/icons/image.png differ
diff --git a/themes/default/images/icons/images.png b/themes/default/images/icons/images.png
new file mode 100755
index 0000000..32328d0
Binary files /dev/null and b/themes/default/images/icons/images.png differ
diff --git a/themes/default/images/icons/linkedin.png b/themes/default/images/icons/linkedin.png
new file mode 100644
index 0000000..e30a7da
Binary files /dev/null and b/themes/default/images/icons/linkedin.png differ
diff --git a/themes/default/images/icons/mail.png b/themes/default/images/icons/mail.png
new file mode 100755
index 0000000..e708416
Binary files /dev/null and b/themes/default/images/icons/mail.png differ
diff --git a/themes/default/images/icons/meetup.png b/themes/default/images/icons/meetup.png
new file mode 100644
index 0000000..38e5507
Binary files /dev/null and b/themes/default/images/icons/meetup.png differ
diff --git a/themes/default/images/icons/mobile-phone.png b/themes/default/images/icons/mobile-phone.png
new file mode 100755
index 0000000..55f8b31
Binary files /dev/null and b/themes/default/images/icons/mobile-phone.png differ
diff --git a/themes/default/images/icons/newspaper.png b/themes/default/images/icons/newspaper.png
new file mode 100755
index 0000000..70e7e5a
Binary files /dev/null and b/themes/default/images/icons/newspaper.png differ
diff --git a/themes/default/images/icons/newspapers.png b/themes/default/images/icons/newspapers.png
new file mode 100755
index 0000000..e456120
Binary files /dev/null and b/themes/default/images/icons/newspapers.png differ
diff --git a/themes/default/images/icons/posterous.png b/themes/default/images/icons/posterous.png
new file mode 100644
index 0000000..d333d77
Binary files /dev/null and b/themes/default/images/icons/posterous.png differ
diff --git a/themes/default/images/icons/reddit.png b/themes/default/images/icons/reddit.png
new file mode 100644
index 0000000..d544fd1
Binary files /dev/null and b/themes/default/images/icons/reddit.png differ
diff --git a/themes/default/images/icons/social-rss.png b/themes/default/images/icons/social-rss.png
new file mode 100644
index 0000000..1cf1953
Binary files /dev/null and b/themes/default/images/icons/social-rss.png differ
diff --git a/themes/default/images/icons/stumbleupon.png b/themes/default/images/icons/stumbleupon.png
new file mode 100644
index 0000000..4454a20
Binary files /dev/null and b/themes/default/images/icons/stumbleupon.png differ
diff --git a/themes/default/images/icons/technorati.png b/themes/default/images/icons/technorati.png
new file mode 100644
index 0000000..e797c13
Binary files /dev/null and b/themes/default/images/icons/technorati.png differ
diff --git a/themes/default/images/icons/tick-button.png b/themes/default/images/icons/tick-button.png
new file mode 100755
index 0000000..3b0e3fc
Binary files /dev/null and b/themes/default/images/icons/tick-button.png differ
diff --git a/themes/default/images/icons/tumblr.png b/themes/default/images/icons/tumblr.png
new file mode 100644
index 0000000..60ada38
Binary files /dev/null and b/themes/default/images/icons/tumblr.png differ
diff --git a/themes/default/images/icons/twitter.png b/themes/default/images/icons/twitter.png
new file mode 100644
index 0000000..46e1988
Binary files /dev/null and b/themes/default/images/icons/twitter.png differ
diff --git a/themes/default/images/icons/vimeo.png b/themes/default/images/icons/vimeo.png
new file mode 100644
index 0000000..b38e083
Binary files /dev/null and b/themes/default/images/icons/vimeo.png differ
diff --git a/themes/default/images/icons/yelp.png b/themes/default/images/icons/yelp.png
new file mode 100644
index 0000000..49871b6
Binary files /dev/null and b/themes/default/images/icons/yelp.png differ
diff --git a/themes/default/images/icons/youtube.png b/themes/default/images/icons/youtube.png
new file mode 100644
index 0000000..b0383d7
Binary files /dev/null and b/themes/default/images/icons/youtube.png differ
diff --git a/themes/default/images/loading-animation_18x18.gif b/themes/default/images/loading-animation_18x18.gif
new file mode 100644
index 0000000..cfcda66
Binary files /dev/null and b/themes/default/images/loading-animation_18x18.gif differ
diff --git a/themes/default/images/page-bg.gif b/themes/default/images/page-bg.gif
new file mode 100644
index 0000000..c75126c
Binary files /dev/null and b/themes/default/images/page-bg.gif differ
diff --git a/themes/default/images/page-bg.jpg b/themes/default/images/page-bg.jpg
new file mode 100644
index 0000000..5882acf
Binary files /dev/null and b/themes/default/images/page-bg.jpg differ
diff --git a/themes/default/images/pause.gif b/themes/default/images/pause.gif
new file mode 100644
index 0000000..0614ad2
Binary files /dev/null and b/themes/default/images/pause.gif differ
diff --git a/themes/default/images/search-button.jpg b/themes/default/images/search-button.jpg
new file mode 100644
index 0000000..586a602
Binary files /dev/null and b/themes/default/images/search-button.jpg differ
diff --git a/themes/default/images/share.jpg b/themes/default/images/share.jpg
new file mode 100644
index 0000000..65c01b4
Binary files /dev/null and b/themes/default/images/share.jpg differ
diff --git a/themes/default/images/submit-incident.png b/themes/default/images/submit-incident.png
new file mode 100644
index 0000000..9d1fe65
Binary files /dev/null and b/themes/default/images/submit-incident.png differ
diff --git a/themes/default/images/tooltip-arrow.png b/themes/default/images/tooltip-arrow.png
new file mode 100644
index 0000000..61987de
Binary files /dev/null and b/themes/default/images/tooltip-arrow.png differ
diff --git a/themes/default/readme.txt b/themes/default/readme.txt
new file mode 100644
index 0000000..7a29b6b
--- /dev/null
+++ b/themes/default/readme.txt
@@ -0,0 +1,8 @@
+Theme Name: Default
+Description:
+Version: 1.0
+Author: Ushahidi
+Author Email: team at ushahidi.com
+Demo: http://www.ushahidi.com
+CSS: base,accordion,slider,style
+JS: 
diff --git a/themes/default/screenshot.png b/themes/default/screenshot.png
new file mode 100644
index 0000000..5fd9c88
Binary files /dev/null and b/themes/default/screenshot.png differ
diff --git a/themes/default/views/alerts/alerts_js.php b/themes/default/views/alerts/alerts_js.php
new file mode 100644
index 0000000..12a2db5
--- /dev/null
+++ b/themes/default/views/alerts/alerts_js.php
@@ -0,0 +1,144 @@
+<?php
+/**
+ * Alerts js file.
+ *
+ * Handles javascript stuff related  to alerts function
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com>
+ * @package    Ushahidi - https://github.com/ushahidi/Ushahidi_Web
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+
+// Map reference
+var map = null;
+var latitude = <?php echo Kohana::config('settings.default_lat') ?>;
+var longitude = <?php echo Kohana::config('settings.default_lon'); ?>;
+var zoom = <?php echo Kohana::config('settings.default_zoom'); ?>;
+
+jQuery(function($) {
+	$(window).load(function(){
+
+		// OpenLayers uses IE's VML for vector graphics
+		// We need to wait for IE's engine to finish loading all namespaces (document.namespaces) for VML.
+		// jQuery.ready is executing too soon for IE to complete it's loading process.
+
+		<?php echo map::layers_js(FALSE); ?>
+		var mapConfig = {
+
+			// Map center
+			center: {
+				latitude: latitude,
+				longitude: longitude
+			},
+
+			// Zoom level
+			zoom: zoom,
+
+			// Base layers
+			baseLayers: <?php echo map::layers_array(FALSE); ?>
+		};
+
+		map = new Ushahidi.Map('divMap', mapConfig);
+		map.addRadiusLayer({
+			latitude: latitude,
+			longitude: longitude
+		});
+
+		// Subscribe to makerpositionchanged event
+		map.register("markerpositionchanged", function(coords){
+			$("#alert_lat").val(coords.latitude);
+			$("#alert_lon").val(coords.longitude);
+		});
+
+		$('.btn_find').on('click', function () {
+			geoCode();
+		});
+
+		$('#location_find').bind('keypress', function(e) {
+			var code = (e.keyCode ? e.keyCode : e.which);
+			if(code == 13) { //Enter keycode
+				geoCode();
+				return false;
+			}
+		});
+
+		// Alerts Slider
+		$("select#alert_radius").selectToUISlider({
+			labels: 6,
+			labelSrc: 'text',
+			sliderOptions: {
+				change: function(e, ui) {
+					var newRadius = $("#alert_radius").val();
+
+					// Convert to Meters
+					radius = newRadius * 1000;
+
+					// Redraw Circle
+					map.updateRadius({radius: radius});
+				}
+			}
+		}).hide();
+
+
+	// Some Default Values
+	$("#alert_mobile").focus(function() {
+		$("#alert_mobile_yes").attr("checked",true);
+	}).blur(function() {
+		if(!this.value.length) {
+			$("#alert_mobile_yes").attr("checked",false);
+		}
+	});
+
+	$("#alert_email").focus(function() {
+		$("#alert_email_yes").attr("checked",true);
+	}).blur(function() {
+		if( !this.value.length ) {
+			$("#alert_email_yes").attr("checked",false);
+		}
+	});
+
+	// Category treeview
+    $(".category-column").treeview({
+      persist: "location",
+	  collapsed: true,
+	  unique: false
+	  });
+	});
+});
+
+/**
+ * Google GeoCoder
+ */
+function geoCode() {
+	$('#find_loading').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+	address = $("#location_find").val();
+	$.post("<?php echo url::site(); ?>reports/geocode/", { address: address },
+		function(data){
+			if (data.status == 'success') {
+
+				map.updateRadius({
+					longitude: data.longitude,
+					latitude: data.latitude
+				});
+
+				// Update form values
+				$("#alert_lat").val(data.latitude);
+				$("#alert_lon").val(data.longitude);
+			} else {
+				// Alert message to be displayed
+				var alertMessage = address + " not found!\n\n***************************\n" +
+				    "Enter more details like city, town, country\nor find a city or town " +
+				    "close by and zoom in\nto find your precise location";
+
+				alert(alertMessage)
+			}
+			$('#find_loading').html('');
+		}, "json");
+	return false;
+}
diff --git a/themes/default/views/alerts/confirm.php b/themes/default/views/alerts/confirm.php
new file mode 100644
index 0000000..770f4a8
--- /dev/null
+++ b/themes/default/views/alerts/confirm.php
@@ -0,0 +1,86 @@
+<div id="content">
+	<div class="content-bg">
+		
+		<!-- start block -->
+		<div class="big-block">
+			<h1><?php echo Kohana::lang('ui_main.alerts_get') ?></h1>
+			
+			<?php if($show_mobile == TRUE): ?>
+			<!-- Mobile Alert -->
+			<div class="green-box">
+				<?php if ($alert_mobile): ?>
+				<?php	echo "<h3>".Kohana::lang('alerts.mobile_ok_head')."</h3>"; ?>
+				<?php endif; ?>
+				<div class="alert_response">
+					<?php 
+					if ($alert_mobile)
+					{
+						echo Kohana::lang('alerts.mobile_alert_request_created')."<u><strong>".
+							$alert_mobile."</strong></u>.".
+							Kohana::lang('alerts.verify_code');
+					}
+					?>
+					<div class="alert_confirm">
+						<div class="label">
+							<u><?php echo Kohana::lang('alerts.mobile_code'); ?></u>
+						</div>
+						<?php 
+						print form::open('/alerts/verify');
+						print "Verification Code:<BR>".form::input('alert_code', '', ' class="text"')."<BR>";
+						print "Mobile Phone:<BR>".form::input('alert_mobile', $alert_mobile, ' class="text"')."<BR>";
+						print form::submit('button', 'Confirm My Alert Request', ' class="btn_submit"');
+						print form::close();
+						?>
+					</div>
+				</div>
+			</div>
+			<!-- / Mobile Alert -->
+			<?php endif; ?>
+			
+			<!-- Email Alert -->
+			<div class="green-box">
+				<?php
+				if ($alert_email)
+				{
+					echo "<h3>".Kohana::lang('alerts.email_ok_head')."</h3>";
+				}
+				?>
+				
+				<div class="alert_response">
+					<?php 
+					if ($alert_email)
+					{
+						echo Kohana::lang('alerts.email_alert_request_created')."<u><strong>".
+							$alert_email."</strong></u>.".
+							Kohana::lang('alerts.verify_code');
+					}
+					?>
+					<div class="alert_confirm">
+						<div class="label">
+							<u><?php echo Kohana::lang('alerts.email_code'); ?></u>
+						</div>
+						<?php 
+						print form::open('/alerts/verify');
+						print "Verification Code:<BR>".form::input('alert_code', '', ' class="text"')."<BR>";
+						print "Email Address:<BR>".form::input('alert_email', $alert_email, ' class="text"')."<BR>";
+						print form::submit('button', 'Confirm My Alert Request', ' class="btn_submit"');
+						print form::close();
+						?>
+					</div>
+				</div>
+			</div>
+			<!-- / Email Alert -->
+			
+			
+			<!-- Return -->
+			<div class="green-box">
+				<div class="alert_response">
+					<a href="<?php echo url::site().'alerts'?>"><?php echo Kohana::lang('alerts.create_more_alerts'); ?></a>
+				</div>
+			</div>
+			<!-- / Return -->
+			
+		</div>
+		<!-- end block -->
+	</div>
+</div>
diff --git a/themes/default/views/alerts/main.php b/themes/default/views/alerts/main.php
new file mode 100644
index 0000000..5cbffa2
--- /dev/null
+++ b/themes/default/views/alerts/main.php
@@ -0,0 +1,85 @@
+<div id="content">
+	<div class="content-bg">
+		<!-- start block -->
+		<div class="big-block">
+			<h1><?php echo Kohana::lang('ui_main.alerts_get'); ?></h1>
+			<?php if ($form_error): ?>
+			<!-- red-box -->
+			<div class="red-box">
+				<h3>Error!</h3>
+				<ul>
+				<?php
+					foreach ($errors as $error_item => $error_description)
+					{
+						// print "<li>" . $error_description . "</li>";
+						print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+					}
+				?>
+				</ul>
+			</div>
+			<?php endif; ?>
+			<?php print form::open() ?>
+			<div class="step-1">
+				<h2><?php echo Kohana::lang('ui_main.alerts_step1_select_city'); ?></h2>
+				<?php echo $alert_radius_view; ?>
+			</div>
+			<input type="hidden" id="alert_lat" name="alert_lat" value="<?php echo $form['alert_lat']; ?>">
+			<input type="hidden" id="alert_lon" name="alert_lon" value="<?php echo $form['alert_lon']; ?>">
+			<input type="hidden" id="alert_country" name="alert_country" value="<?php echo $form['alert_country']; ?>" />
+			<input type="hidden" id="alert_confirmed" name="alert_confirmed" value="<?php echo $form['alert_confirmed']; ?>" />
+			<div class="step-2-holder">
+				<div class="step-2">
+					<h2><?php echo Kohana::lang('ui_main.alerts_step2_send_alerts'); ?></h2>
+					<div class="holder">
+						<?php if ($show_mobile == TRUE): ?>
+						<div class="box">
+							<label>
+								<?php $checked = ($form['alert_mobile_yes'] == 1); ?>
+								<?php print form::checkbox('alert_mobile_yes', '1', $checked); ?>
+								<span>
+									<strong><?php echo Kohana::lang('ui_main.alerts_mobile_phone'); ?></strong><br />
+									<?php echo Kohana::lang('ui_main.alerts_enter_mobile'); ?>
+								</span>
+							</label>
+							<span><?php print form::input('alert_mobile', $form['alert_mobile'], ' class="text long"'); ?></span>
+						</div>
+						<?php endif; ?>
+						<div class="box">
+							<label>
+								<?php $checked = ($form['alert_email_yes'] == 1) ?> 
+								<?php print form::checkbox('alert_email_yes', '1', $checked); ?>
+								<span>
+									<strong><?php echo Kohana::lang('ui_main.alerts_email'); ?></strong><br />
+									<?php echo Kohana::lang('ui_main.alerts_enter_email'); ?>
+								</span>
+							</label>
+							<span><?php print form::input('alert_email', $form['alert_email'], ' class="text long"'); ?></span>
+						</div>
+					</div>
+				</div>
+				<div class="step-3">
+					<h2><?php echo Kohana::lang('ui_main.alerts_step3_select_catgories'); ?></h2>
+					<div class="holder">
+						<div class="box">
+							<div class="report_category" id="categories">
+							<?php 
+								$selected_categories = (!empty($form['alert_category']) AND is_array($form['alert_category']))
+									? $selected_categories = $form['alert_category']
+									: array();
+									
+									
+								echo category::form_tree('alert_category', $selected_categories, 2, TRUE, FALSE);
+							?>
+							</div>
+						</div>
+					</div>
+				</div>
+				<input id="btn-send-alerts" class="btn_submit" type="submit" value="<?php echo Kohana::lang('ui_main.alerts_btn_send'); ?>" />
+				<BR /><BR />
+				<a href="<?php echo url::site()."alerts/confirm";?>"><?php echo Kohana::lang('ui_main.alert_confirm_previous'); ?></a>
+			</div>
+			<?php print form::close(); ?>
+		</div>
+		<!-- end block -->
+	</div>
+</div>
diff --git a/themes/default/views/alerts/radius.php b/themes/default/views/alerts/radius.php
new file mode 100644
index 0000000..494ae81
--- /dev/null
+++ b/themes/default/views/alerts/radius.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * View file for the map used to specify the alert radius
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com>
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL)
+ */
+?>
+	<div class="map-wrapper">
+	<?php if ($show_usage_info): ?>
+		<p><?php echo Kohana::lang('ui_main.alerts_place_spot'); ?></p>
+	<?php endif; ?>
+		<?php $css_class = (isset($css_class))? $css_class : "map-holder"; ?>
+		<div class="<?php echo $css_class; ?>" id="divMap"></div>
+	</div>
+	<div class="report-find-location">
+		<div class="alert_slider">
+			<select name="alert_radius" id="alert_radius">
+				<option value="1">1 KM</option>
+				<option value="5">5 KM</option>
+				<option value="10">10 KM</option>
+				<option value="20" selected="selected">20 KM</option>
+				<option value="50">50 KM</option>
+				<option value="100">100 KM</option>
+			</select>
+		</div>
+		
+	<?php if ($enable_find_location): ?>
+		<?php print form::input('location_find', '', ' title="City, State and/or Country" class="findtext"'); ?>
+		<div class="find-location">
+			<input type="button" name="button" id="button" value="<?php echo Kohana::lang('ui_main.find_location'); ?>" class="btn_find" />
+		</div>
+		<div id="find_loading" class="report-find-loading"></div>
+		<div class="find-text clear">* <?php echo Kohana::lang('ui_main.alerts_place_spot2'); ?></div>
+	<?php endif; ?>
+		
+	</div>
diff --git a/themes/default/views/alerts/unsubscribe.php b/themes/default/views/alerts/unsubscribe.php
new file mode 100644
index 0000000..94a1e48
--- /dev/null
+++ b/themes/default/views/alerts/unsubscribe.php
@@ -0,0 +1,29 @@
+<div id="content">
+	<div class="content-bg">
+		<!-- start block -->
+		<div class="big-block">
+			<h1><?php echo Kohana::lang('ui_main.alerts_get') ?></h1>
+				<?php
+					if ($unsubscribed)
+					{
+						echo '<div class="green-box">';
+						echo '<div class="alert_response" align="center">';
+						$settings = kohana::config('settings');
+						echo Kohana::lang('alerts.unsubscribed')
+								.$settings['site_name']; 
+						echo '</div>';
+						echo '</div>';
+					}
+					else
+					{
+						echo '<div class="red-box">';
+						echo '<div class="alert_response" align="center">';
+						echo Kohana::lang('alerts.unsubscribe_failed');
+						echo '</div>';
+						echo '</div>';
+					}
+				?>
+		</div>
+		<!-- end block -->
+	</div>
+</div>
diff --git a/themes/default/views/alerts/verify.php b/themes/default/views/alerts/verify.php
new file mode 100644
index 0000000..3cac8f7
--- /dev/null
+++ b/themes/default/views/alerts/verify.php
@@ -0,0 +1,46 @@
+<div id="content">
+	<div class="content-bg">
+		<!-- start block -->
+		<div class="big-block">
+			<h1><?php echo Kohana::lang('ui_main.alerts_get') ?></h1>
+			<!-- green-box/ red-box depending on verification result -->
+			<?php
+			// SWITCH based on the value of the $errno
+			switch ($errno)
+			{
+				// IF the code provided was not found ...
+				case ER_CODE_NOT_FOUND:
+				?>
+					<div class="red-box">
+						<div class="alert_response">
+            				<?php echo Kohana::lang('alerts.code_not_found'); ?>
+						</div>
+					</div>
+					<?php
+					break;
+				// IF the code provided means the alert has already been verified ...
+				case ER_CODE_ALREADY_VERIFIED:
+				?>
+					<div class="red-box">
+						<div class="alert_response" align="center">
+							<?php echo Kohana::lang('alerts.code_already_verified'); ?>
+						</div>
+					</div>
+					<?php
+					break;
+				// IF the code provided means the code is now verified ...
+				case ER_CODE_VERIFIED:
+				?>
+					<div class="green-box">
+						<div class="alert_response" align="center">
+							<?php echo Kohana::lang('alerts.code_verified'); ?>
+						</div>
+					</div>
+					<?php
+					break;
+			} // End switch
+			?>
+      </div>
+	<!-- end block -->
+	</div>
+</div>
diff --git a/themes/default/views/blocks/main_news.php b/themes/default/views/blocks/main_news.php
new file mode 100644
index 0000000..51fcdd4
--- /dev/null
+++ b/themes/default/views/blocks/main_news.php
@@ -0,0 +1,42 @@
+<?php blocks::open("news");?>
+<?php blocks::title(Kohana::lang('ui_main.official_news'));?>
+<table class="table-list">
+	<thead>
+		<tr>
+			<th scope="col"><?php echo Kohana::lang('ui_main.title'); ?></th>
+			<th scope="col"><?php echo Kohana::lang('ui_main.source'); ?></th>
+			<th scope="col"><?php echo Kohana::lang('ui_main.date'); ?></th>
+		</tr>
+	</thead>
+	<tbody>
+		<?php
+		if ($feeds->count() != 0)
+		{
+			foreach ($feeds as $feed)
+			{
+					$feed_id = $feed->id;
+					$feed_title = text::limit_chars($feed->item_title, 40, '...', True);
+					$feed_link = $feed->item_link;
+					$feed_date = date('M j Y', strtotime($feed->item_date));
+					$feed_source = text::limit_chars($feed->feed->feed_name, 15, "...");
+			?>
+			<tr>
+				<td><a href="<?php echo $feed_link; ?>" target="_blank"><?php echo $feed_title ?></a></td>
+				<td><?php echo $feed_source; ?></td>
+				<td><?php echo $feed_date; ?></td>
+			</tr>
+			<?php
+			}
+		}
+		else
+		{
+			?>
+			<tr><td colspan="3"></td></tr>
+			<?php
+		}
+		?>
+	</tbody>
+</table>
+<a class="more" href="<?php echo url::site() . 'feeds' ?>"><?php echo Kohana::lang('ui_main.view_more'); ?></a>
+<div class="clear:both;"></div>
+<?php blocks::close();?>
\ No newline at end of file
diff --git a/themes/default/views/blocks/main_reports.php b/themes/default/views/blocks/main_reports.php
new file mode 100644
index 0000000..69e8ee7
--- /dev/null
+++ b/themes/default/views/blocks/main_reports.php
@@ -0,0 +1,39 @@
+<?php blocks::open("reports");?>
+<?php blocks::title(Kohana::lang('ui_main.reports_listed'));?>
+<table class="table-list">
+	<thead>
+		<tr>
+			<th scope="col" class="title"><?php echo Kohana::lang('ui_main.title'); ?></th>
+			<th scope="col" class="location"><?php echo Kohana::lang('ui_main.location'); ?></th>
+			<th scope="col" class="date"><?php echo Kohana::lang('ui_main.date'); ?></th>
+		</tr>
+	</thead>
+	<tbody>
+		<?php
+		if ($incidents->count() == 0)
+		{
+			?>
+			<tr><td colspan="3"><?php echo Kohana::lang('ui_main.no_reports'); ?></td></tr>
+			<?php
+		}
+		foreach ($incidents as $incident)
+		{
+			$incident_id = $incident->id;
+			$incident_title = text::limit_chars(html::strip_tags($incident->incident_title), 40, '...', True);
+			$incident_date = $incident->incident_date;
+			$incident_date = date('M j Y', strtotime($incident->incident_date));
+			$incident_location = $incident->location->location_name;
+		?>
+		<tr>
+			<td><a href="<?php echo url::site() . 'reports/view/' . $incident_id; ?>"> <?php echo $incident_title ?></a></td>
+			<td><?php echo html::escape($incident_location) ?></td>
+			<td><?php echo $incident_date; ?></td>
+		</tr>
+		<?php
+		}
+		?>
+	</tbody>
+</table>
+<a class="more" href="<?php echo url::site() . 'reports/' ?>"><?php echo Kohana::lang('ui_main.view_more'); ?></a>
+<div style="clear:both;"></div>
+<?php blocks::close();?>
\ No newline at end of file
diff --git a/themes/default/views/contact.php b/themes/default/views/contact.php
new file mode 100644
index 0000000..ff5b4bf
--- /dev/null
+++ b/themes/default/views/contact.php
@@ -0,0 +1,71 @@
+<div id="content">
+	<div class="content-bg">
+		<!-- start contacts block -->
+		<div class="big-block">
+			<h1><?php echo Kohana::lang('ui_main.contact'); ?></h1>
+			<div id="contact_us" class="contact">
+				<?php
+				if ($form_error)
+				{
+					?>
+					<!-- red-box -->
+					<div class="red-box">
+						<h3>Error!</h3>
+						<ul>
+							<?php
+							foreach ($errors as $error_item => $error_description)
+							{
+								print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+							}
+							?>
+						</ul>
+					</div>
+					<?php
+				}
+
+				if ($form_sent)
+				{
+					?>
+					<!-- green-box -->
+					<div class="green-box">
+						<h3><?php echo Kohana::lang('ui_main.contact_message_has_send'); ?></h3>
+					</div>
+					<?php
+				}								
+				?>
+				<?php print form::open(NULL, array('id' => 'contactForm', 'name' => 'contactForm')); ?>
+				<div class="report_row">
+					<strong><?php echo Kohana::lang('ui_main.contact_name'); ?>:</strong><br />
+					<?php print form::input('contact_name', $form['contact_name'], ' class="text"'); ?>
+				</div>
+				<div class="report_row">
+					<strong><?php echo Kohana::lang('ui_main.contact_email'); ?>:</strong><br />
+					<?php print form::input('contact_email', $form['contact_email'], ' class="text"'); ?>
+				</div>
+				<div class="report_row">
+					<strong><?php echo Kohana::lang('ui_main.contact_phone'); ?>:</strong><br />
+					<?php print form::input('contact_phone', $form['contact_phone'], ' class="text"'); ?>
+				</div>
+				<div class="report_row">
+					<strong><?php echo Kohana::lang('ui_main.contact_subject'); ?>:</strong><br />
+					<?php print form::input('contact_subject', $form['contact_subject'], ' class="text"'); ?>
+				</div>								
+				<div class="report_row">
+					<strong><?php echo Kohana::lang('ui_main.contact_message'); ?>:</strong><br />
+					<?php print form::textarea('contact_message', $form['contact_message'], ' rows="4" cols="40" class="textarea long" ') ?>
+				</div>		
+				<div class="report_row">
+					<strong><?php echo Kohana::lang('ui_main.contact_code'); ?>:</strong><br />
+					<?php print $captcha->render(); ?><br />
+					<?php print form::input('captcha', $form['captcha'], ' class="text"'); ?>
+				</div>
+				<div class="report_row">
+					<input name="submit" type="submit" value="<?php echo Kohana::lang('ui_main.contact_send'); ?>" class="btn_submit" />
+				</div>
+				<?php print form::close(); ?>
+			</div>
+			
+		</div>
+		<!-- end contacts block -->
+	</div>
+</div>
\ No newline at end of file
diff --git a/themes/default/views/feed/atom.php b/themes/default/views/feed/atom.php
new file mode 100644
index 0000000..f9ab59f
--- /dev/null
+++ b/themes/default/views/feed/atom.php
@@ -0,0 +1,40 @@
+<?php echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"; ?>
+<feed xmlns="http://www.w3.org/2005/Atom"<?php if(isset($georss)) echo ' xmlns:georss="http://www.georss.org/georss"';?>>
+	<title type="text"><?php echo html::specialchars($feed_title); ?></title>
+	<subtitle type="html"><?php echo html::specialchars($feed_description); ?></subtitle>
+	<updated><?php echo gmdate("c", strtotime($feed_date)); ?></updated>
+	<id><?php echo $feed_url; ?></id>
+	<link rel="alternate" type="text/html" href="<?php echo $site_url; ?>"/>
+	<link rel="self" type="application/atom+xml" href="<?php echo $feed_url; ?>"/>
+	<generator uri="<?php echo $site_url; ?>" version="1.0">Ushahidi Engine</generator>
+	<?php
+	
+	// Event::feed_rss_head - Add to the feed head
+	Event::run('ushahidi_action.feed_atom_head');
+	
+	foreach ($items as $item)
+	{?>
+		<entry>
+			<title><?php echo html::specialchars($item['title']); ?></title>
+			<link rel="alternate" type="text/html" href="<?php echo $item['link']; ?>"/>
+			<updated><?php echo gmdate("c", strtotime($item['date'])); ?></updated>
+			<published><?php echo gmdate("c", strtotime($item['date'])); ?></published>
+			<content type="xhtml" xml:lang="en" 
+			xml:base="http://diveintomark.org/">
+			<div xmlns="http://www.w3.org/1999/xhtml">
+			<?php echo html::clean($item['description']); ?>
+			</div>
+			</content>
+			<?php
+			if(isset($item['point']))
+			{
+				echo "  <georss:point>".$item['point'][0]." ".$item['point'][1]."</georss:point>\n";
+			}
+			
+			// Event::feed_atom_item - Add to the feed item
+			Event::run('ushahidi_action.feed_atom_item', $item['id']);
+			?>
+		</entry><?php 
+	}?>
+	
+</feed>
\ No newline at end of file
diff --git a/themes/default/views/feed/feeds.php b/themes/default/views/feed/feeds.php
new file mode 100644
index 0000000..1b01387
--- /dev/null
+++ b/themes/default/views/feed/feeds.php
@@ -0,0 +1,43 @@
+<div id="content">
+	<div class="content-bg">
+		<!-- start block -->
+		<div class="big-block">
+			<h1><?php echo Kohana::lang('ui_admin.feeds').': '.$pagination_stats; ?></h1>
+			<div class="report_rowtitle">
+				<div class="report_col2">
+					<strong><?php echo Kohana::lang('feeds.title');?></strong>
+				</div>
+				<div class="report_col3">
+					<strong><?php echo Kohana::lang('feeds.date');?></strong>
+				</div>
+				<div class="report_col4">
+					<strong><?php echo Kohana::lang('feeds.source');?></strong>
+				</div>
+			</div>
+			<?php
+				foreach ($feeds as $feed)
+				{
+					$feed_id = $feed->id;
+					$feed_title = text::limit_chars($feed->item_title, 40, '...', True);
+					$feed_link = $feed->item_link;
+					$feed_date = date('M j Y', strtotime($feed->item_date));
+					$feed_source = text::limit_chars($feed->feed->feed_name, 15, "...");
+								
+					print "<div class=\"report_row1\">";
+					print "		<div class=\"report_details report_col2\">";
+					print "			<h3><a href=\"$feed_link\">" . $feed_title . "</a></h3>";
+					print "		</div>";
+					print "		<div class=\"report_date report_col3\">";
+					print $feed_date;
+					print "		</div>";
+					print "		<div class=\"report_location report_col4\">";
+					print $feed_source;
+					print "		</div>";
+					print "</div>";
+				}
+			?>
+			<?php echo $pagination; ?>
+		</div>
+		<!-- end block -->
+	</div>
+</div>
\ No newline at end of file
diff --git a/themes/default/views/feed/rss2.php b/themes/default/views/feed/rss2.php
new file mode 100644
index 0000000..01de2e0
--- /dev/null
+++ b/themes/default/views/feed/rss2.php
@@ -0,0 +1,36 @@
+<?php echo "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"; ?>
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"<?php if(isset($georss)) echo ' xmlns:georss="http://www.georss.org/georss"';?>>
+	<channel>
+		<title><?php echo html::specialchars($feed_title); ?></title>
+		<link><?php echo $site_url; ?></link>
+		<pubDate><?php echo gmdate("D, d M Y H:i:s T", strtotime($feed_date)); ?></pubDate>
+		<description><?php echo html::specialchars($feed_description); ?></description>
+		<generator>Ushahidi Platform</generator>
+		<atom:link href="<?php echo $feed_url; ?>" rel="self" type="application/rss+xml" />
+		
+		<?php // Event::feed_rss_head - Add to the feed head ?>
+		<?php Event::run('ushahidi_action.feed_rss_head'); ?>
+
+		<?php foreach ($items as $item): ?>
+			<item>
+			<title><?php echo html::specialchars($item['title']); ?></title>
+			<link><?php echo $item['link']; ?></link>
+			<description><![CDATA[<?php echo html::specialchars($item['description']); ?>]]></description>
+			<pubDate><?php echo gmdate("D, d M Y H:i:s T", strtotime($item['date'])); ?></pubDate>
+			<guid><?php if(isset($item['guid'])) echo $item['guid']; else echo $item['link'] ?></guid>
+
+			<?php if (isset($item['point'])): ?>
+				<georss:point><?php echo $item['point'][0]." ".$item['point'][1]; ?></georss:point>
+			<?php endif; ?>
+
+			<?php foreach ($item['categories'] as $category): ?>
+				<category><?php echo html::specialchars($category); ?></category>
+			<?php endforeach; ?>
+
+			<?php // Event::feed_rss_item - Add to the feed item ?>
+			<?php Event::run('ushahidi_action.feed_rss_item', $item['id']); ?>
+			</item>
+		<?php endforeach; ?>
+		
+	</channel>
+</rss>
diff --git a/themes/default/views/footer.php b/themes/default/views/footer.php
new file mode 100644
index 0000000..8ea8673
--- /dev/null
+++ b/themes/default/views/footer.php
@@ -0,0 +1,84 @@
+			</div>
+		</div>
+		<!-- / main body -->
+
+	</div>
+	<!-- / wrapper -->
+
+	<!-- footer -->
+	<div id="footer" class="clearingfix">
+
+		<div id="underfooter"></div>
+
+		<!-- footer content -->
+		<div class="wrapper floatholder rapidxwpr">
+
+			<!-- footer credits -->
+			<div class="footer-credits">
+				Powered by the  
+				<a href="http://www.ushahidi.com/">
+					<img src="<?php echo url::file_loc('img'); ?>media/img/footer-logo.png" alt="Ushahidi" class="footer-logo" />
+				</a>
+				  Platform
+			</div>
+			<!-- / footer credits -->
+
+			<!-- footer menu -->
+			<div class="footermenu">
+				<ul class="clearingfix">
+					<li>
+						<a class="item1" href="<?php echo url::site(); ?>">
+							<?php echo Kohana::lang('ui_main.home'); ?>
+						</a>
+					</li>
+
+					<?php if (Kohana::config('settings.allow_reports')): ?>
+					<li>
+						<a href="<?php echo url::site()."reports/submit"; ?>">
+							<?php echo Kohana::lang('ui_main.submit'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+					
+					<?php if (Kohana::config('settings.allow_alerts')): ?>
+						<li>
+							<a href="<?php echo url::site()."alerts"; ?>">
+								<?php echo Kohana::lang('ui_main.alerts'); ?></a>
+						</li>
+					<?php endif; ?>
+
+					<?php if (Kohana::config('settings.site_contact_page')): ?>
+					<li>
+						<a href="<?php echo url::site()."contact"; ?>">
+							<?php echo Kohana::lang('ui_main.contact'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php
+					// Action::nav_main_bottom - Add items to the bottom links
+					Event::run('ushahidi_action.nav_main_bottom');
+					?>
+				</ul>
+
+				<?php if ($site_copyright_statement != ''): ?>
+	      		<p><?php echo $site_copyright_statement; ?></p>
+		      	<?php endif; ?>
+		      	
+			</div>
+			<!-- / footer menu -->
+
+
+		</div>
+		<!-- / footer content -->
+
+	</div>
+	<!-- / footer -->
+
+	<?php
+	echo $footer_block;
+	// Action::main_footer - Add items before the </body> tag
+	Event::run('ushahidi_action.main_footer');
+	?>
+</body>
+</html>
diff --git a/themes/default/views/header.php b/themes/default/views/header.php
new file mode 100644
index 0000000..2e9c472
--- /dev/null
+++ b/themes/default/views/header.php
@@ -0,0 +1,96 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+	<title><?php echo html::specialchars($page_title.$site_name); ?></title>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<?php echo $header_block; ?>
+	<?php
+	// Action::header_scripts - Additional Inline Scripts from Plugins
+	Event::run('ushahidi_action.header_scripts');
+	?>
+
+</head>
+
+<?php
+  // Add a class to the body tag according to the page URI
+
+  // we're on the home page
+  if (count($uri_segments) == 0)
+  {
+  	$body_class = "page-main";
+  }
+  // 1st tier pages
+  elseif (count($uri_segments) == 1)
+  {
+    $body_class = "page-".$uri_segments[0];
+  }
+  // 2nd tier pages... ie "/reports/submit"
+  elseif (count($uri_segments) >= 2)
+  {
+    $body_class = "page-".$uri_segments[0]."-".$uri_segments[1];
+  }
+?>
+
+<body id="page" class="<?php echo $body_class; ?>">
+
+	<?php echo $header_nav; ?>
+
+	<!-- wrapper -->
+	<div class="wrapper floatholder rapidxwpr">
+
+		<!-- header -->
+		<div id="header">
+
+			<!-- searchbox -->
+			<div id="searchbox">
+
+				<!-- languages -->
+				<?php echo $languages;?>
+				<!-- / languages -->
+
+				<!-- searchform -->
+				<?php echo $search; ?>
+				<!-- / searchform -->
+
+			</div>
+			<!-- / searchbox -->
+
+			<!-- logo -->
+			<?php if ($banner == NULL): ?>
+			<div id="logo">
+				<h1><a href="<?php echo url::site();?>"><?php echo $site_name; ?></a></h1>
+				<span><?php echo $site_tagline; ?></span>
+			</div>
+			<?php else: ?>
+			<a href="<?php echo url::site();?>"><img src="<?php echo $banner; ?>" alt="<?php echo $site_name; ?>" /></a>
+			<?php endif; ?>
+			<!-- / logo -->
+
+			<!-- submit incident -->
+			<?php echo $submit_btn; ?>
+			<!-- / submit incident -->
+
+		</div>
+		<!-- / header -->
+        <!-- / header item for plugins -->
+        <?php
+            // Action::header_item - Additional items to be added by plugins
+	        Event::run('ushahidi_action.header_item');
+        ?>
+
+		<!-- main body -->
+		<div id="middle">
+			<div class="background layoutleft">
+
+				<!-- mainmenu -->
+				<div id="mainmenu" class="clearingfix">
+					<ul>
+						<?php nav::main_tabs($this_page); ?>
+					</ul>
+
+					<?php if ($allow_feed == 1) { ?>
+					<div class="feedicon"><a href="<?php echo url::site(); ?>feed/"><img alt="<?php echo html::escape(Kohana::lang('ui_main.rss')); ?>" src="<?php echo url::file_loc('img'); ?>media/img/icon-feed.png" style="vertical-align: middle;" border="0" /></a></div>
+					<?php } ?>
+
+				</div>
+				<!-- / mainmenu -->
diff --git a/themes/default/views/header_nav.php b/themes/default/views/header_nav.php
new file mode 100644
index 0000000..c6cb8ca
--- /dev/null
+++ b/themes/default/views/header_nav.php
@@ -0,0 +1,71 @@
+<div id="header_nav">
+	<ul id="header_nav_left">
+		<li><span class="bignext">»</span><a href="<?php echo url::site();?>"><?php echo $site_name; ?></a></li>
+		<?php
+		// Action::header_nav - Add items to header nav area
+		Event::run('ushahidi_action.header_nav');
+		?>
+	</ul>
+
+	<?php Event::run('ushahidi_action.header_nav_bar'); ?>
+
+	<ul id="header_nav_right">
+		<li class="header_nav_user header_nav_has_dropdown">
+		<?php if($loggedin_user != FALSE){ ?>
+
+			<a href="<?php echo url::site().$loggedin_role;?>">
+				<span class="header_nav_label"><?php echo html::escape($loggedin_user->username); ?></span>
+				<img alt="<?php echo html::escape($loggedin_user->username); ?>" src="<?php echo html::escape(members::gravatar($loggedin_user->email, 20)); ?>" width="20" />
+			</a>
+
+			<ul class="header_nav_dropdown" style="display:none;">
+			<?php if($loggedin_role != ""){ ?>
+				<li><a href="<?php echo url::site().$loggedin_role;?>/profile"><?php echo Kohana::lang('ui_main.manage_your_account'); ?></a></li>
+
+				<li><a href="<?php echo url::site().$loggedin_role;?>"><?php echo Kohana::lang('ui_main.your_dashboard'); ?></a></li>
+			<?php } ?>
+				<li><a href="<?php echo url::site();?>profile/user/<?php echo $loggedin_user->username; ?>"><?php echo Kohana::lang('ui_main.view_public_profile'); ?></a></li>
+
+				<li><a href="<?php echo url::site();?>logout"><em><?php echo Kohana::lang('ui_admin.logout');?></em></a></li>
+
+			</ul>
+
+		<?php } else { ?>
+
+			<a href="<?php echo url::site('login');?>" style="float:right;padding-top:8px;"><span class="header_nav_label"><strong><?php echo Kohana::lang('ui_main.login'); ?></strong></span></a>
+			
+			<div class="header_nav_dropdown" style="display:none;">
+			
+				<?php echo form::open('login/', array('id' => 'userpass_form')); ?>
+				<input type="hidden" name="action" value="signin" />
+	
+				<ul>
+					<li><label for="username"><?php echo Kohana::lang('ui_main.email');?></label><input type="text" name="username" id="username" class="" /></li>
+	
+					<li><label for="password"><?php echo Kohana::lang('ui_main.password');?></label><input name="password" type="password" class="" id="password" size="20" /></li>
+	
+					<li><input type="submit" name="submit" value="<?php echo Kohana::lang('ui_main.login'); ?>" class="header_nav_login_btn" /></li>
+				</ul>
+				<?php echo form::close(); ?>
+				
+				<ul>
+	
+					<li><a href="<?php echo url::site()."login/?newaccount";?>"><?php echo Kohana::lang('ui_main.login_signup_click'); ?></a></li>
+	
+					<li><a href="#" id="header_nav_forgot" onclick="return false"><?php echo Kohana::lang('ui_main.forgot_password');?></a>
+						<?php echo form::open('login/', array('id' => 'header_nav_userforgot_form')); ?>
+						<input type="hidden" name="action" value="forgot" />
+						<label for="resetemail"><?php echo Kohana::lang('ui_main.registered_email');?></label>
+						<input type="text" id="resetemail" name="resetemail" value="" />
+	
+						<input type="submit" name="submit" value="<?php echo Kohana::lang('ui_main.reset_password'); ?>" class="header_nav_login_btn" />
+						<?php echo form::close(); ?>
+	
+					</li>
+				</ul>
+			</div>
+
+		<?php } ?>
+		</li>
+	</ul>
+</div>
\ No newline at end of file
diff --git a/themes/default/views/kohana_error_page.php b/themes/default/views/kohana_error_page.php
new file mode 100644
index 0000000..9a8ba25
--- /dev/null
+++ b/themes/default/views/kohana_error_page.php
@@ -0,0 +1,79 @@
+<?php defined('SYSPATH') OR die('No direct access allowed.'); ?>
+<?php
+if (PHP_SAPI === 'cli')
+{
+	echo $error . ': ' . $message ."\n";
+	if ( ! empty($file))
+	{
+		echo "FILE: ".$file."\n";
+	}
+	if ( ! empty($line))
+	{
+		echo "LINE: ".$line."\n";
+	}
+	echo "ERROR: ".$message."\n";
+	exit();
+}
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+<title><?php echo $error ?></title>
+<?php
+echo html::stylesheet('media/css/error','',true);
+echo html::script('media/js/jquery', true);
+echo html::script('media/js/jquery.ui.min', true);
+echo html::script('media/js/bugs', true);
+?>
+</head>
+<body>
+<div id="ushahidi_login_logo"><img src="<?php echo url::file_loc('img'); ?>media/img/admin/logo_login.gif" /></div>
+<div id="framework_error" style="width:42em;margin:20px auto;">
+<h3><?php echo html::specialchars($error) ?></h3>
+<p><?php echo html::specialchars($description) ?></p>
+<?php if ( ! empty($line) AND ! empty($file)): ?>
+<p><?php echo Kohana::lang('core.error_file_line', array($file, $line)) ?></p>
+<?php endif ?>
+<p><code class="block"><?php echo $message ?></code></p>
+<p class="ushahidi_bugs"><?php echo Kohana::lang('core.report_bug',"#"); ?>
+</p>
+<div id="loader"></div>
+<?php
+$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "";
+$url = ( isset($_SERVER["SERVER_NAME"]) AND isset($_SERVER["REQUEST_URI"]) )
+	? $_SERVER["SERVER_NAME"].$_SERVER["REQUEST_URI"]
+	: '';
+$referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : "";
+$ip_address = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : "";
+
+$environ = "";
+$environ .= "*URL*: ".$url."\n";
+$environ .= "*REFERER*: ".$referer."\n";
+$environ .= "*USER_AGENT*: ".$user_agent."\n";
+$environ .= "*IP*: ".$ip_address."\n";
+$environ .= "*USHAHIDI VERSION*: ".Kohana::config('settings.ushahidi_version')."\n";
+$environ .= "*DB VERSION*: ".Kohana::config('version.ushahidi_db_version')."\n";
+
+$error_message = "";
+if ( ! empty($file))
+{
+	$error_message .= "FILE: ".$file."\n";
+}
+if ( ! empty($line))
+{
+	$error_message .= "LINE: ".$line."\n";
+}
+$error_message .= "ERROR: ".$message."\n";
+?>
+<div id="bug_form">
+	<p class="bug_form_desc">Found a bug? Submit a bug report to the Ushahidi <a href="https://github.com/ushahidi/Ushahidi_Web/issues">Github issues page</a>- help us make Ushahidi better software -- Thanks!</p>
+</div>
+<?php if ( ! empty($trace)): ?>
+<h3><?php echo Kohana::lang('core.stack_trace') ?></h3>
+<?php echo $trace ?>
+<?php endif ?>
+<p class="stats"><?php echo Kohana::lang('core.stats_footer', Kohana::config('version.ushahidi_version')) ?></p>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/themes/default/views/login/login_js.php b/themes/default/views/login/login_js.php
new file mode 100644
index 0000000..9966342
--- /dev/null
+++ b/themes/default/views/login/login_js.php
@@ -0,0 +1,69 @@
+$(document).ready(function() {
+    openid.init('openid_identifier');
+    openid.setDemoMode(false);
+	<?php
+
+	// Depending on the action, we need to display forms by default
+
+	if ($action == "signin" OR $action == "forgot")
+	{
+		echo '$("#signin_userpass").show(400);';
+	}
+	elseif ($action == "openid")
+	{
+		echo '$("#signin_openid").show(400);';
+	}
+	elseif ($action == "new")
+	{
+		echo '$("#signin_new").show(400);';
+	}
+	elseif ($action == 'resend_confirmation' OR isset($_GET['new_confirm_email']) OR isset($_GET['confirmation_failure']))
+	{
+		echo '$("#resend_confirm_email").show(0);';
+	}
+
+	// Determine which form to default to open
+
+	if ( isset($_GET['newaccount']))
+	{
+		echo '$("#signin_new").show(0);';
+	}
+	elseif (kohana::config('config.allow_openid') == false)
+	{
+		echo '$("#signin_userpass").show(0);';
+	}
+
+	?>
+
+	<?php if(kohana::config('riverid.enable') == true) { ?>
+	$(".new_email").focusout(function() {
+
+		$.getJSON('<?php echo kohana::config('config.site_domain'); ?>riverid/registered', {email: $(".new_email").val()}, function(response) {
+			if (response.response) {
+				$("#signin_userpass").show(0);
+				$("#username").val($(".new_email").val());
+				$('.new_name').attr('disabled', true);
+				$('.new_password').attr('disabled', true);
+				$('.new_password_again').attr('disabled', true);
+				$('.new_submit').attr('disabled', true);
+				$('.riverid_email_already_set_copy').html('<small>You already have an account managed by CrowdmapID! Try using your CrowdmapID email and password to login.</small>');
+				$(".riverid_email_already_set").show(0);
+			}else{
+				$("#username").val('');
+				$('.new_name').attr('disabled', false);
+				$('.new_password').attr('disabled', false);
+				$('.new_password_again').attr('disabled', false);
+				$('.new_submit').attr('disabled', false);
+				$(".riverid_email_already_set").hide(0);
+			}
+		});
+
+	});
+	<?php } ?>
+});
+function toggle(thisDiv) {
+	$("#"+thisDiv).toggle(400);
+}
+function facebook_click() {
+	top.location.href = "<?php echo url::site()."login/facebook"; ?>"
+}
diff --git a/themes/default/views/login/main.php b/themes/default/views/login/main.php
new file mode 100644
index 0000000..ab62189
--- /dev/null
+++ b/themes/default/views/login/main.php
@@ -0,0 +1,218 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<title><?php echo Kohana::lang('ui_main.login');?></title>
+<?php
+echo html::stylesheet(url::file_loc('css').'media/css/jquery-ui-themeroller', '', TRUE);
+echo html::stylesheet(url::file_loc('css').'media/css/login', '', TRUE);
+echo html::stylesheet(url::file_loc('css').'media/css/openid', '', TRUE);
+echo html::stylesheet(url::file_loc('css').'media/css/global', '', TRUE);
+echo html::script(url::file_loc('js').'media/js/jquery', TRUE);
+echo html::script(url::file_loc('js').'media/js/openid/openid-jquery', TRUE);
+echo html::script(url::file_loc('js').'media/js/openid/openid-jquery-en', TRUE);
+echo html::script(url::file_loc('js').'media/js/global', TRUE);
+?>
+<script type="text/javascript">
+	<?php echo $js; ?>
+</script>
+</head>
+
+<body>
+
+<?php echo $header_nav; ?>
+
+<div id="openid_login_container">
+
+	<div id="ushahidi_site_name" class="ui-corner-all">
+    	<div id="logo">
+			<h1><?php echo $site_name; ?></h1>
+			<span><?php echo $site_tagline; ?></span>
+		</div>
+    </div>
+
+    <?php if ($message): ?>
+		<div class="<?php echo $message_class; ?> ui-corner-all">• <?php echo $message; ?></div>
+	<?php endif; ?>
+
+	<?php if ($form_error): ?>
+		<div class="login_error ui-corner-all">
+		<?php foreach ($errors as $error_item => $error_description): ?>
+			<?php echo (!$error_description) ? '' : "• " . $error_description . "<br />"; ?>
+		<?php endforeach; ?>
+		</div>
+	<?php endif; ?>
+
+	<?php if (isset($_GET["reset"])): ?>
+	<div id="password_reset_change_form" class="ui-corner-all">
+		<h2><?php echo Kohana::lang('ui_main.create_new_password'); ?></h2>
+		<?php echo form::open(NULL, array('id' => "changepass_form")); ?>
+			<input type="hidden" name="action" value="changepass">
+			<input type="hidden" name="changeid" value="<?php echo $changeid; ?>">
+
+			<table width="100%" border="0" cellspacing="3" cellpadding="4" background="" id="ushahidi_loginbox">
+				<?php
+					$hidden = 'hidden';
+					if (empty($token)) { $hidden = ''; }
+				?>
+				<tr class="<?php echo $hidden; ?>">
+					<td><strong><?php echo Kohana::lang('ui_main.token');?>:</strong><br />
+					<?php echo form::input('token', $token, 'class="login_text new_email"'); ?></td>
+				</tr>
+				<tr>
+					<td><strong><?php echo Kohana::lang('ui_main.password');?>:</strong><br />
+					<?php echo form::password('password', $form['password'], 'class="login_text new_password"'); ?></td>
+				</tr>
+				<tr>
+					<td><strong><?php echo Kohana::lang('ui_main.password_again');?>:</strong><br />
+					<?php echo form::password('password_again', $form['password_again'], 'class="login_text new_password_again"'); ?></td>
+				</tr>
+				<tr>
+					<td><input type="submit" id="submit" name="submit" value="<?php echo Kohana::lang('ui_main.change_password'); ?>" class="login_btn" /></td>
+				</tr>
+			</table>
+
+		<?php echo form::close(); ?>
+	</div>
+	<?php endif; ?>
+
+	<div id="openid_login" class="ui-corner-all">
+
+		<?php if ($new_confirm_email_form): ?>
+			<h2><?php echo Kohana::lang('ui_main.resend_confirm_email'); ?>:</h2>
+			<div id="resend_confirm_email" class="signin_select ui-corner-all">
+				<?php echo form::open(NULL, array('id'=>"resendconfirm_form")); ?>
+					<input type="hidden" name="action" value="resend_confirmation">
+					<table width="100%" border="0" cellspacing="3" cellpadding="4" background="" id="ushahidi_loginbox">
+						<tr>
+							<td><strong><?php echo Kohana::lang('ui_main.registered_email');?></strong><br />
+							<?php print form::input('confirmation_email', $form['confirmation_email'], ' class="login_text"'); ?></td>
+						</tr>
+						<tr>
+							<td><input type="submit" id="submit" name="submit" value="<?php echo Kohana::lang('ui_main.send_confirmation'); ?>" class="login_btn" /></td>
+						</tr>
+					</table>
+				<?php echo form::close(); ?>
+			</div>
+		<?php endif; ?>
+
+		<h2><?php echo Kohana::lang('ui_main.login_with'); ?>:</h2>
+
+		<h2><a href="javascript:toggle('signin_userpass');"><?php echo Kohana::lang('ui_main.login_userpass'); ?></a></h2>
+		<div id="signin_userpass" class="signin_select ui-corner-all">
+			<?php echo form::open(NULL, array('id'=>"userpass_form")); ?>
+				<input type="hidden" name="action" value="signin">
+				<table width="100%" border="0" cellspacing="3" cellpadding="4" background="" id="ushahidi_loginbox">
+					<tr>
+						<td><strong><?php echo Kohana::lang('ui_main.email');?>:</strong><br />
+						<input type="text" name="username" id="username" class="login_text" /></td>
+					</tr>
+					<tr>
+						<td><strong><?php echo Kohana::lang('ui_main.password');?>:</strong><br />
+						<input name="password" type="password" class="login_text" id="password" size="20" /></td>
+					</tr>
+					<tr>
+						<td><input type="checkbox" id="remember" name="remember" value="1" checked="checked" /><?php echo Kohana::lang('ui_main.password_save');?></td>
+					</tr>
+					<tr>
+						<td><input type="submit" id="submit" name="submit" value="<?php echo Kohana::lang('ui_main.login'); ?>" class="login_btn" /></td>
+					</tr>
+					
+					<tr>
+						<td><a href="javascript:toggle('signin_forgot');"> <?php echo Kohana::lang('ui_main.forgot_password');?></a></td>
+					</tr>
+
+				</table>
+			<?php echo form::close(); ?>
+		</div>
+		
+		<div id="signin_forgot" class="signin_select ui-corner-all">
+			<?php echo form::open(NULL, array('id'=>"userforgot_form")); ?>
+				<input type="hidden" name="action" value="forgot">
+				<table width="100%" border="0" cellspacing="3" cellpadding="4" background="" id="ushahidi_loginbox">
+					<tr>
+						<td><strong><?php echo Kohana::lang('ui_main.registered_email');?></strong><br />
+						<?php print form::input('resetemail', $form['resetemail'], ' class="login_text"'); ?></td>
+					</tr>
+					<tr>
+						<td><input type="submit" id="submit" name="submit" value="<?php echo Kohana::lang('ui_main.reset_password'); ?>" class="login_btn" /></td>
+					</tr>
+				</table>
+			<?php echo form::close() ?>
+		</div>
+
+		<?php if (kohana::config('config.allow_openid') == TRUE): ?>
+		<h2><a href="javascript:toggle('signin_openid');"><?php echo Kohana::lang('ui_main.login_openid'); ?></a></h2>
+		<div id="signin_openid" class="signin_select ui-corner-all">
+			<?php echo form::open(NULL, array('id'=>"openid_form")); ?>
+				<input type="hidden" name="action" value="openid">
+				<div id="openid_choice">
+					<p><?php echo Kohana::lang('ui_main.login_select_openid'); ?>:</p>
+					<div id="openid_btns"></div>
+				</div>
+
+				<div id="openid_input_area">
+					<input id="openid_identifier" name="openid_identifier" type="text" value="http://" />
+					<input id="openid_submit" type="submit" value="Sign-In"/>
+				</div>
+				<noscript>
+					<p>OpenID is service that allows you to log-on to many different websites using a single indentity.
+					Find out <a href="http://openid.net/what/">more about OpenID</a> and <a href="http://openid.net/get/">how to get an OpenID enabled account</a>.</p>
+				</noscript>
+			<?php echo form::close(); ?>
+		</div>
+		<?php endif; ?>
+	</div>
+
+	<div id="create_account" class="ui-corner-all">
+
+		<h2><a href="javascript:toggle('signin_new');"><?php echo Kohana::lang('ui_main.login_signup_click'); ?></a></h2>
+
+		<?php echo Kohana::lang('ui_main.login_signup_text'); ?>
+		<div id="signin_new" class="signin_select ui-corner-all">
+			<?php echo form::open(NULL,  array('id' => "usernew_form")); ?>
+				<input type="hidden" name="action" value="new">
+				<table width="100%" border="0" cellspacing="3" cellpadding="4" background="" id="ushahidi_loginbox">
+					<tr>
+						<td><strong><?php echo Kohana::lang('ui_main.name'); ?>:</strong><br/><small><?php echo Kohana::lang('ui_main.identify_you');?></small><br />
+						<?php print form::input('name', $form['name'], 'class="login_text new_name"'); ?></td>
+					</tr>
+					<tr>
+						<td><strong><?php echo Kohana::lang('ui_main.email'); ?>:</strong><br />
+						<?php print form::input('email', $form['email'], 'class="login_text new_email"'); ?></td>
+					</tr>
+					<tr class="riverid_email_already_set" style="display:none;">
+						<td class="riverid_email_already_set_copy"></td>
+					</tr>
+					<tr>
+						<td><strong><?php echo Kohana::lang('ui_main.password'); ?>:</strong><br />
+						<?php print form::password('password', $form['password'], 'class="login_text new_password"'); ?></td>
+					</tr>
+					<tr>
+						<td><strong><?php echo Kohana::lang('ui_main.password_again'); ?>:</strong><br />
+						<?php print form::password('password_again', $form['password_again'], 'class="login_text new_password_again"'); ?></td>
+					</tr>
+					<?php 
+						//for plugins that want to add some extra stuff to this lovely view
+						Event::run('ushahidi_action.login_new_user_form');
+					?>
+					<tr>
+						<td><input type="submit" id="submit" name="submit" value="<?php echo Kohana::lang('ui_main.login_signup');?>" class="login_btn new_submit" /></td>
+					</tr>
+				</table>
+			<?php echo form::close(); ?>
+		</div>
+
+	</div>
+
+	<?php if (kohana::config('riverid.enable') == TRUE): ?>
+	<div id="openid_login" class="ui-corner-all">
+		<small><?php echo $riverid_information; ?> 
+			<a href="<?php echo $riverid_url; ?>"><?php echo Kohana::lang('ui_main.more_information'); ?></a>
+		</small>
+	</div>
+	<?php endif; ?>
+
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/themes/default/views/main/layout.php b/themes/default/views/main/layout.php
new file mode 100755
index 0000000..3f60a63
--- /dev/null
+++ b/themes/default/views/main/layout.php
@@ -0,0 +1,275 @@
+<!-- main body -->
+<div id="main" class="clearingfix">
+	<div id="mainmiddle" class="floatbox withright">
+
+	<?php if ($site_message != ''): ?>
+		<div class="green-box">
+			<h3><?php echo $site_message; ?></h3>
+		</div>
+	<?php endif; ?>
+
+		<!-- right column -->
+		<div id="right" class="clearingfix">
+			
+			<?php
+			// Action::main_sidebar_pre_filters - Add Items to the Entry Page before filters
+			Event::run('ushahidi_action.main_sidebar_pre_filters');
+			?>
+
+			<!-- category filters -->
+			<div class="cat-filters clearingfix">
+				<strong>
+					<?php echo Kohana::lang('ui_main.category_filter');?>
+					<span>
+						[<a href="javascript:toggleLayer('category_switch_link', 'category_switch')" id="category_switch_link">
+							<?php echo Kohana::lang('ui_main.hide'); ?>
+						</a>]
+					</span>
+				</strong>
+			</div>
+
+			<ul id="category_switch" class="category-filters">
+				<?php
+				$color_css = 'class="category-icon swatch" style="background-color:#'.$default_map_all.'"';
+				$all_cat_image = '';
+				if ($default_map_all_icon != NULL)
+				{
+					$all_cat_image = html::image(array(
+						'src'=>$default_map_all_icon
+					));
+					$color_css = 'class="category-icon"';
+				}
+				?>
+				<li>
+					<a class="active" id="cat_0" href="#">
+						<span <?php echo $color_css; ?>><?php echo $all_cat_image; ?></span>
+						<span class="category-title"><?php echo Kohana::lang('ui_main.all_categories');?></span>
+					</a>
+				</li>
+				<?php
+					foreach ($categories as $category => $category_info)
+					{
+						$category_title = html::escape($category_info[0]);
+						$category_color = $category_info[1];
+						$category_image = ($category_info[2] != NULL)
+						    ? url::convert_uploaded_to_abs($category_info[2])
+						    : NULL;
+						$category_description = html::escape(Category_Lang_Model::category_description($category));
+
+						$color_css = 'class="category-icon swatch" style="background-color:#'.$category_color.'"';
+						if ($category_info[2] != NULL)
+						{
+							$category_image = html::image(array(
+								'src'=>$category_image,
+								));
+							$color_css = 'class="category-icon"';
+						}
+
+						echo '<li>'
+						    . '<a href="#" id="cat_'. $category .'" title="'.$category_description.'">'
+						    . '<span '.$color_css.'>'.$category_image.'</span>'
+						    . '<span class="category-title">'.$category_title.'</span>'
+						    . '</a>';
+
+						// Get Children
+						echo '<div class="hide" id="child_'. $category .'">';
+						if (sizeof($category_info[3]) != 0)
+						{
+							echo '<ul>';
+							foreach ($category_info[3] as $child => $child_info)
+							{
+								$child_title = html::escape($child_info[0]);
+								$child_color = $child_info[1];
+								$child_image = ($child_info[2] != NULL)
+								    ? url::convert_uploaded_to_abs($child_info[2])
+								    : NULL;
+								$child_description = html::escape(Category_Lang_Model::category_description($child));
+								
+								$color_css = 'class="category-icon swatch" style="background-color:#'.$child_color.'"';
+								if ($child_info[2] != NULL)
+								{
+									$child_image = html::image(array(
+										'src' => $child_image
+									));
+
+									$color_css = 'class="category-icon"';
+								}
+
+								echo '<li>'
+								    . '<a href="#" id="cat_'. $child .'" title="'.$child_description.'">'
+								    . '<span '.$color_css.'>'.$child_image.'</span>'
+								    . '<span class="category-title">'.$child_title.'</span>'
+								    . '</a>'
+								    . '</li>';
+							}
+							echo '</ul>';
+						}
+						echo '</div></li>';
+					}
+				?>
+			</ul>
+			<!-- / category filters -->
+
+			<?php if ($layers): ?>
+				<!-- Layers (KML/KMZ) -->
+				<div class="layers-filters clearingfix">
+					<strong><?php echo Kohana::lang('ui_main.layers_filter');?> 
+						<span>
+							[<a href="javascript:toggleLayer('kml_switch_link', 'kml_switch')" id="kml_switch_link">
+								<?php echo Kohana::lang('ui_main.hide'); ?>
+							</a>]
+						</span>
+					</strong>
+				</div>
+				<ul id="kml_switch" class="category-filters">
+				<?php
+					foreach ($layers as $layer => $layer_info)
+					{
+						$layer_name = $layer_info[0];
+						$layer_color = $layer_info[1];
+						$layer_url = $layer_info[2];
+						$layer_file = $layer_info[3];
+
+						$layer_link = ( ! $layer_url)
+						    ? url::base().Kohana::config('upload.relative_directory').'/'.$layer_file
+						    : $layer_url;
+						
+						echo '<li>'
+						    . '<a href="#" id="layer_'. $layer .'">'
+						    . '<span class="swatch" style="background-color:#'.$layer_color.'"></span>'
+						    . '<span class="layer-name">'.$layer_name.'</span>'
+						    . '</a>'
+						    . '</li>';
+					}
+				?>
+				</ul>
+				<!-- /Layers -->
+			<?php endif; ?>
+			
+			<?php
+			// Action::main_sidebar_post_filters - Add Items to the Entry Page after filters
+			Event::run('ushahidi_action.main_sidebar_post_filters');
+			?>
+
+			<br />
+
+			<!-- additional content -->
+			<?php if (Kohana::config('settings.allow_reports')): ?>
+				<div class="additional-content">
+					<h5><?php echo Kohana::lang('ui_main.how_to_report'); ?></h5>
+
+					<div class="how-to-report-methods">
+
+						<!-- Phone -->
+						<?php if ( ! empty($phone_array)): ?>
+						<div>
+							<?php echo Kohana::lang('ui_main.report_option_1'); ?>
+							<?php foreach ($phone_array as $phone): ?>
+								<?php echo $phone; ?><br />
+							<?php endforeach; ?>
+						</div>
+						<?php endif; ?>
+						
+						<!-- External Apps -->
+						<?php if (count($external_apps) > 0): ?>
+						<div>
+							<strong><?php echo Kohana::lang('ui_main.report_option_external_apps'); ?>:</strong><br/>
+							<?php foreach ($external_apps as $app): ?>
+								<a href="<?php echo $app->url; ?>"><?php echo $app->name; ?></a><br/>
+							<?php endforeach; ?>
+						</div>
+						<?php endif; ?>
+
+						<!-- Email -->
+						<?php if ( ! empty($report_email)): ?>
+						<div>
+							<strong><?php echo Kohana::lang('ui_main.report_option_2'); ?>:</strong><br/>
+							<a href="mailto:<?php echo $report_email?>"><?php echo $report_email?></a>
+						</div>
+						<?php endif; ?>
+
+						<!-- Twitter -->
+						<?php if ( ! empty($twitter_hashtag_array)): ?>
+						<div>
+							<strong><?php echo Kohana::lang('ui_main.report_option_3'); ?>:</strong><br/>
+							<?php foreach ($twitter_hashtag_array as $twitter_hashtag): ?>
+								<span>#<?php echo $twitter_hashtag; ?></span>
+								<?php if ($twitter_hashtag != end($twitter_hashtag_array)): ?>
+									<br />
+								<?php endif; ?>
+							<?php endforeach; ?>
+						</div>
+						<?php endif; ?>
+
+						<!-- Web Form -->
+						<div>
+							<a href="<?php echo url::site().'reports/submit/'; ?>">
+								<?php echo Kohana::lang('ui_main.report_option_4'); ?>
+							</a>
+						</div>
+
+					</div>
+
+				</div>
+			<?php endif; ?>
+
+			<!-- / additional content -->
+			
+			<?php
+			// Action::main_sidebar - Add Items to the Entry Page Sidebar
+			Event::run('ushahidi_action.main_sidebar');
+			?>
+	
+		</div>
+		<!-- / right column -->
+	
+		<!-- content column -->
+		<div id="content" class="clearingfix">
+			<div class="floatbox">
+
+				<!-- filters -->
+				<div class="filters clearingfix">
+					<div class="media-filters">
+						<strong><?php echo Kohana::lang('ui_main.filters'); ?></strong>
+						<ul>
+							<li><a id="media_0" class="active" href="#"><span><?php echo Kohana::lang('ui_main.all'); ?></span></a></li>
+							<li><a id="media_4" href="#"><span><?php echo Kohana::lang('ui_main.news'); ?></span></a></li>
+							<li><a id="media_1" href="#"><span><?php echo Kohana::lang('ui_main.pictures'); ?></span></a></li>
+							<li><a id="media_2" href="#"><span><?php echo Kohana::lang('ui_main.video'); ?></span></a></li>
+						</ul>
+					</div>
+
+
+					<?php
+					// Action::main_filters - Add items to the main_filters
+					Event::run('ushahidi_action.map_main_filters');
+					?>
+				</div>
+				<!-- / filters -->
+
+				<?php								
+				// Map and Timeline Blocks
+				echo $div_map;
+				echo $div_timeline;
+				?>
+			</div>
+		</div>
+		<!-- / content column -->
+
+	</div>
+</div>
+<!-- / main body -->
+
+<!-- content -->
+<div class="content-container">
+
+	<!-- content blocks -->
+	<div class="content-blocks clearingfix">
+		<ul class="content-column">
+			<?php blocks::render(); ?>
+		</ul>
+	</div>
+	<!-- /content blocks -->
+
+</div>
+<!-- content -->
diff --git a/themes/default/views/main/main_js.php b/themes/default/views/main/main_js.php
new file mode 100644
index 0000000..b6700a9
--- /dev/null
+++ b/themes/default/views/main/main_js.php
@@ -0,0 +1,362 @@
+<?php
+/**
+ * Main cluster js file.
+ * 
+ * Server Side Map Clustering
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     API Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+		
+// Initialize the Ushahidi namespace
+Ushahidi.baseURL = "<?php echo url::site(); ?>";
+Ushahidi.markerRadius = <?php echo $marker_radius; ?>;
+Ushahidi.markerOpacity = <?php echo $marker_opacity; ?>;
+Ushahidi.markerStokeWidth = <?php echo $marker_stroke_width; ?>;
+Ushahidi.markerStrokeOpacity = <?php echo $marker_stroke_opacity; ?>;
+
+// Default to most active month
+var startTime = <?php echo $active_startDate ?>;
+
+// Default to most active month
+var endTime = <?php echo $active_endDate ?>;
+
+var intervalTime = ''; // HT: manual time interval
+
+// To hold the Ushahidi.Map reference
+var map = null;
+
+
+/**
+ * Toggle Layer Switchers
+ */
+function toggleLayer(link, layer) {
+	if ($("#"+link).text() == "<?php echo Kohana::lang('ui_main.show'); ?>")
+	{
+		$("#"+link).text("<?php echo Kohana::lang('ui_main.hide'); ?>");
+	}
+	else
+	{
+		$("#"+link).text("<?php echo Kohana::lang('ui_main.show'); ?>");
+	}
+	$('#'+layer).toggle(500);
+}
+
+/**
+ * Create a function that calculates the smart columns
+ */
+function smartColumns() {
+	//Reset column size to a 100% once view port has been adjusted
+	$("ul.content-column").css({ 'width' : "100%"});
+
+	//Get the width of row
+	var colWrap = $("ul.content-column").width();
+
+	// Find how many columns of 200px can fit per row / then round it down to a whole number
+	var colNum = <?php echo $blocks_per_row; ?>;
+
+	// Get the width of the row and divide it by the number of columns it 
+	// can fit / then round it down to a whole number. This value will be
+	// the exact width of the re-adjusted column
+	var colFixed = Math.floor(colWrap / colNum);
+
+	// Set exact width of row in pixels instead of using % - Prevents
+	// cross-browser bugs that appear in certain view port resolutions.
+	$("ul.content-column").css({ 'width' : colWrap});
+
+	// Set exact width of the re-adjusted column	
+	$("ul.content-column li").css({ 'width' : colFixed});
+}
+
+/**
+ * Callback function for rendering the timeline
+ */
+function refreshTimeline(options) {
+
+	<?php if (Kohana::config('settings.enable_timeline')) {?>
+
+	// Use report filters if no options passed
+	options = options || map.getReportFilters();
+	// Copy options object to avoid accidental modifications to reportFilters
+	options = jQuery.extend({}, options);
+
+	var url = "<?php echo url::site().'json/timeline/'; ?>";
+
+	if(options.i == undefined || options.i == '') { // HT: Added condition only to auto interval if empty interval type choosed
+		var interval = (options.e - options.s) / (3600 * 24);
+		if (interval <= 3) {
+			options.i = "hour";
+		} else if (interval <= (31 * 6)) {
+			options.i = "day";
+		} else {
+			options.i = "month";
+		}
+	}
+	// HT: More info link
+	var urlLink = "<?php echo url::site().'reports/index/?'?>"+$.param(options);
+	$('#timelineMoreLink').attr('href', urlLink);
+
+	// Get the graph data
+	$.ajax({
+		url: url,
+		data: options,
+		success: function(response) {
+			// Clear out the any existing plots
+			$("#graph").html('');
+
+			if (response != null && response[0].data.length < 2)
+				return;
+
+			var graphData = [];
+			var raw = response[0].data;
+			for (var i=0; i<raw.length; i++) {
+				var date = new Date(raw[i][0]);
+
+				var dateStr = date.getFullYear() + "-";
+				dateStr += ('0' + (date.getMonth()+1)).slice(-2) + '-';
+				dateStr += ('0' + date.getDate()).slice(-2);
+
+				graphData.push([dateStr, parseInt(raw[i][1])]);
+			}
+			var timeline = $.jqplot('graph', [graphData], {
+				seriesDefaults: {
+					<?php if (Kohana::config('settings.timeline_graph') == 'bar') { ?>
+					renderer: $.jqplot.BarRenderer, // HT: For bargraph
+					rendererOptions: { // HT: For bargraph
+						varyBarColor: true,
+						barWidth: 1,
+						shadowAlpha: 0
+					},
+					<?php } ?>
+					color: response[0].color,
+					lineWidth: 1.6,
+					markerOptions: {
+						<?php if (Kohana::config('settings.timeline_point_label')) { ?>
+							show: true, // HT: To show the points
+							//style: 'circle' // HT: Circle point
+						<?php } else { ?>
+							show: false,
+						<?php } ?>
+					},
+					<?php if (Kohana::config('settings.timeline_point_label')) { ?>
+						pointLabels: { // HT: To show point label
+							show: true,
+							edgeTolerance: -10,
+							ypadding: 3
+						}
+					<?php } ?>
+				},
+				axesDefaults: {
+					pad: 1.23,
+				},
+				axes: {
+					xaxis: {
+						renderer: $.jqplot.DateAxisRenderer,
+						tickOptions: {
+							formatString: '%#d %b\n%Y'
+						}
+					},
+					yaxis: {
+						min: 0,
+						tickOptions: {
+							formatString: '%.0f'
+						}
+					}
+				},
+				<?php if (Kohana::config('settings.timeline_point_label')) { ?>
+					cursor: {show: true}, // HT: To show current point detail
+				<?php } else { ?>
+					cursor: {show: false},
+				<?php } ?>
+			});
+		},
+		dataType: "json"
+	});
+	<?php }?>
+}
+
+
+jQuery(function() {
+	var reportsURL = "<?php echo Kohana::config('settings.allow_clustering') == 1 ? "json/cluster" : "json"; ?>";
+
+	// Render thee JavaScript for the base layers so that
+	// they are accessible by Ushahidi.js
+	<?php echo map::layers_js(FALSE); ?>
+	
+	// Map configuration
+	var config = {
+
+		// Zoom level at which to display the map
+		zoom: <?php echo Kohana::config('settings.default_zoom'); ?>,
+
+		// Redraw the layers when the zoom level changes
+		redrawOnZoom: <?php echo Kohana::config('settings.allow_clustering') == 1 ? "true" : "false"; ?>,
+
+		// Center of the map
+		center: {
+			latitude: <?php echo Kohana::config('settings.default_lat'); ?>,
+			longitude: <?php echo Kohana::config('settings.default_lon'); ?>
+		},
+
+		// Map controls
+		mapControls: [
+			new OpenLayers.Control.Navigation({ dragPanOptions: { enableKinetic: true } }),
+			new OpenLayers.Control.Attribution(),
+			new OpenLayers.Control.Zoom(),
+			new OpenLayers.Control.MousePosition({
+				div: document.getElementById('mapMousePosition'),
+				formatOutput: Ushahidi.convertLongLat
+			}),
+			new OpenLayers.Control.Scale('mapScale'),
+			new OpenLayers.Control.ScaleLine(),
+			new OpenLayers.Control.LayerSwitcher()
+		],
+
+		// Base layers
+		baseLayers: <?php echo map::layers_array(FALSE); ?>,
+
+		// Display the map projection
+		showProjection: true,
+		
+		reportFilters: {
+			s: startTime,
+			e: endTime
+		}
+
+	};
+
+	// Initialize the map
+	map = new Ushahidi.Map('map', config);
+	map.addLayer(Ushahidi.GEOJSON, {
+		name: "<?php echo Kohana::lang('ui_main.reports'); ?>",
+		url: reportsURL,
+		transform: false
+	}, true, true);
+
+
+	// Register the referesh timeline function as a callback
+	map.register("filterschanged", refreshTimeline);
+	setTimeout(function() { refreshTimeline({
+		s: startTime,
+		e: endTime
+	}); }, 800);
+
+
+	// Category Switch Action
+	$("ul#category_switch li > a").click(function(e) {
+		
+		var categoryId = this.id.substring(4);
+		var catSet = 'cat_' + this.id.substring(4);
+
+		// Remove All active
+		$("a[id^='cat_']").removeClass("active");
+		
+		// Hide All Children DIV
+		$("[id^='child_']").hide();
+
+		// Add Highlight
+		$("#cat_" + categoryId).addClass("active"); 
+
+		// Show children DIV
+		$("#child_" + categoryId).show();
+		$(this).parents("div").show();
+		
+		// Update report filters
+		map.updateReportFilters({c: categoryId});
+
+		e.stopPropagation();
+		return false;
+	});
+
+	// Layer selection
+	$("ul#kml_switch li > a").click(function(e) {
+		// Get the layer id
+		var layerId = this.id.substring(6);
+
+		var isCurrentLayer = false;
+		var context = this;
+
+		// Remove all actively selected layers
+		$("#kml_switch a").each(function(i) {
+			if ($(this).hasClass("active")) {
+				if (this.id == context.id) {
+					isCurrentLayer = true;
+				}
+				map.trigger("deletelayer", $(".layer-name", this).html());
+				$(this).removeClass("active");
+			}
+		});
+
+		// Was a different layer selected?
+		if (!isCurrentLayer) {
+			// Set the currently selected layer as the active one
+			$(this).addClass("active");
+			map.addLayer(Ushahidi.KML, {
+				name: $(".layer-name", this).html(),
+				url: "json/layer/" + layerId
+			});
+		}
+
+		return false;
+	});
+		
+	// Timeslider and date change actions
+	$("select#startDate, select#endDate").selectToUISlider({
+		labels: 4,
+		labelSrc: 'text',
+		sliderOptions: {
+			change: function(e, ui) {
+				var from = $("#startDate").val();
+				var to = $("#endDate").val();
+				var intrvl = $("#intervalDate").val(); // HT: manual time interval
+
+				if (to > from && (from != startTime || to != endTime || intrvl != intervalTime)) { // HT: manual time interval
+				//if (to > from && (from != startTime || to != endTime)) {
+					// Update the report filters
+					startTime = from;
+					endTime = to;
+					intervalTime = intrvl; // HT: manual time interval
+					map.updateReportFilters({s: from, e: to, i: intrvl}); // HT: manual time interval
+					// map.updateReportFilters({s: from, e: to});
+				}
+
+				e.stopPropagation();
+			}
+		}
+	});
+	
+	// HT: manual time interval trigger timeslider change on interval change
+	$("select#intervalDate").change(function() {
+		$("select#startDate").trigger('change');
+	});
+	
+	// Media Filter Action
+	$('.filters a').click(function() {
+		var mediaType = parseFloat(this.id.replace('media_', '')) || 0;
+		
+		$('.filters a.active').removeClass('active');
+		$(this).addClass('active');
+
+		// Update the report filters
+		map.updateReportFilters({m: mediaType});
+		
+		return false;
+	});
+	
+	//Execute the function when page loads
+	smartColumns();
+
+});
+
+$(window).resize(function () { 
+	//Each time the viewport is adjusted/resized, execute the function
+	smartColumns();
+});
\ No newline at end of file
diff --git a/themes/default/views/main/map.php b/themes/default/views/main/map.php
new file mode 100644
index 0000000..967381d
--- /dev/null
+++ b/themes/default/views/main/map.php
@@ -0,0 +1,11 @@
+<!-- map -->
+<div class="map <?php if (Kohana::config('settings.enable_timeline')) echo 'timeline-enabled'; ?>" id="map"></div>
+<div style="clear:both;"></div>
+<div id="mapStatus">
+	<div id="mapScale"></div>
+	<div id="mapMousePosition"></div>
+	<div id="mapProjection"></div>
+	<div id="mapOutput"></div>
+</div>
+<div style="clear:both;"></div>
+<!-- / map -->
\ No newline at end of file
diff --git a/themes/default/views/main/timeline.php b/themes/default/views/main/timeline.php
new file mode 100644
index 0000000..ba293ab
--- /dev/null
+++ b/themes/default/views/main/timeline.php
@@ -0,0 +1,20 @@
+<div class="slider-holder">
+	<?php echo form::open(NULL, array('method' => 'get')); ?>
+		<input type="hidden" value="0" name="currentCat" id="currentCat"/>
+		<fieldset>
+			<!-- HT: More info link -->
+			<a class="f-clear" href="#" id="timelineMoreLink" style="font-size: 11px;"><?php echo Kohana::lang('ui_main.more_information'); ?></a>
+			<!-- HT: Manual time interval for timeline select input -->
+			<label for="intervalDate"><?php echo Kohana::lang('ui_main.interval'); ?>:</label>
+			<select name="intervalDate" id="intervalDate"><?php echo $intervalDate; ?></select>
+			<!-- HT: End of manual time interval for timeline select input -->
+			<label for="startDate"><?php echo Kohana::lang('ui_main.from'); ?>:</label>
+			<select name="startDate" id="startDate"><?php echo $startDate; ?></select>
+			<label for="endDate"><?php echo Kohana::lang('ui_main.to'); ?>:</label>
+			<select name="endDate" id="endDate"><?php echo $endDate; ?></select>
+		</fieldset>
+	<?php echo form::close(); ?>
+</div>
+<?php if (Kohana::config('settings.enable_timeline')): ?>
+<div id="graph" class="graph-holder"></div>
+<?php endif; ?>
\ No newline at end of file
diff --git a/themes/default/views/page.php b/themes/default/views/page.php
new file mode 100644
index 0000000..c78d642
--- /dev/null
+++ b/themes/default/views/page.php
@@ -0,0 +1,11 @@
+<div id="content">
+	<div class="content-bg">
+		<div class="big-block">
+			<h1><?php echo html::escape($page_title) ?></h1>
+			<div class="page_text"><?php 
+			echo $page_description;
+			Event::run('ushahidi_action.page_extra', $page_id);
+			?></div>
+		</div>
+	</div>
+</div>
diff --git a/themes/default/views/pagination/front-end-reports.php b/themes/default/views/pagination/front-end-reports.php
new file mode 100644
index 0000000..a345bf2
--- /dev/null
+++ b/themes/default/views/pagination/front-end-reports.php
@@ -0,0 +1,97 @@
+<?php 
+/**
+ * Pagination view for the frontend reports
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+/**
+ * Frontend  pagination style
+ * 
+ * @preview  <<Prev 1 … 4 5 6 7 8 … 15 Next>>
+ */
+?>
+
+	<ul class="pager">
+	
+		<?php if ($total_pages < 10): /* « Previous  1 2 3 4 5 6 7 8 9 10 11 12  Next » */ ?>
+
+			<?php for ($i = 1; $i <= $total_pages; $i++): ?>
+				<?php if ($i == $current_page): ?>
+					<li><span><a href="#" class="active"><?php echo $i ?></a></span></li>
+				<?php else: ?>
+					<li><span><a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a></span></li>
+				<?php endif ?>
+			<?php endfor ?>
+
+		<?php elseif ($current_page < 6): /* « Previous  1 2 3 4 5 6 7 8 9 10 … 25 26  Next » */ ?>
+
+			<?php for ($i = 1; $i <= 6; $i++): ?>
+				<?php if ($i == $current_page): ?>
+					<li><span><a class="active" href="#"><?php echo $i ?></a></span></li>
+				<?php else: ?>
+					<li><span><a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a></span></li>
+				<?php endif ?>
+			<?php endfor; ?>
+
+			<li>…</li>
+			<li><span><a href="<?php echo str_replace('{page}', $total_pages - 1, $url) ?>"><?php echo $total_pages - 1 ?></a></span></li>
+			<li><span><a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a></span></li>
+		
+		<?php elseif ($current_page < 100): ?>	
+			<li><span><a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a></span></li>
+			<li>…</li>
+			
+			<?php
+				$num_pages_substract = 0;
+				$num_pages_add = 0;
+				$num_pages_subtract = ($total_pages == $current_page)? 3 : 2;
+				
+				if ($current_page < $total_pages)
+				{
+					$num_pages_subtract = ($current_page > 10) ? 1 : 2;
+					$num_pages_add = (($total_pages - $current_page) > 4) ? 2 : ($total_pages - $current_page);
+				}
+			?>
+			
+			<?php for ($i = $current_page - $num_pages_subtract; $i <= $current_page + $num_pages_add; $i++): ?>
+				<?php if ($i == $current_page): ?>
+					<li><span><a href="#" class="active"><?php echo $i ?></a></span></li>
+				<?php else: ?>
+					<li><span><a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a></span></li>
+				<?php endif ?>
+			<?php endfor; ?>
+			
+			<?php if (($current_page + ($num_pages_add + 1)) < $total_pages): ?>
+				<li>…</li>
+				<li><span><a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a></span></li>
+			<?php endif; ?>
+			
+		<?php else: /* « Previous  1 2 … 5 6 7 8 9 10 11 12 13 14 … 25 26  Next » */ ?>
+
+			<li><span><a href="<?php echo str_replace('{page}', 1, $url) ?>">1</a></span></li>
+			<li>…</li>
+			<?php $num_pages_add = ($current_page == $total_pages)? 0 : 1; ?>	
+			<?php for ($i = $current_page - 1; $i <= $current_page + $num_pages_add; $i++): ?>
+				<?php if ($i == $current_page): ?>
+					<li><span><a class="active" href="#"><?php echo $i ?></a></span></li>
+				<?php else: ?>
+					<li><span><a href="<?php echo str_replace('{page}', $i, $url) ?>"><?php echo $i ?></a></span></li>
+				<?php endif ?>
+			<?php endfor ?>
+			
+			<?php if (($current_page + 1) < $total_pages): ?>
+				<li>…</li>
+				<li><span><a href="<?php echo str_replace('{page}', $total_pages, $url) ?>"><?php echo $total_pages ?></a></span></li>
+			<?php endif; ?>
+
+		<?php endif ?>
+
+	</ul>
\ No newline at end of file
diff --git a/themes/default/views/profile/main.php b/themes/default/views/profile/main.php
new file mode 100644
index 0000000..8496943
--- /dev/null
+++ b/themes/default/views/profile/main.php
@@ -0,0 +1,12 @@
+
+<div id="content">
+	<div class="content-bg">
+
+			<h1><?php echo Kohana::lang('ui_main.browse_profiles'); ?></h1>
+			<ul>
+			<?php foreach($users as $user){ ?>
+				<li><a href="<?php echo url::site();?>profile/user/<?php echo html::specialchars($user->username); ?>"><?php echo html::specialchars($user->name); ?></a></li>
+			<?php } ?>
+			</ul>
+	</div>
+</div>
diff --git a/themes/default/views/profile/user.php b/themes/default/views/profile/user.php
new file mode 100644
index 0000000..b1df0ce
--- /dev/null
+++ b/themes/default/views/profile/user.php
@@ -0,0 +1,58 @@
+<div id="content">
+	<div class="content-bg">
+
+			<div class="profile-left">
+				<div><img src="<?php echo members::gravatar($user->email,160); ?>" width="160" height="160" /></div>
+				<div class="user-color" style="background-color:#<?php echo $user->color; ?>"></div>
+				<?php if($logged_in_user){ ?>
+					<div><?php echo Kohana::lang('ui_main.this_is_your_profile'); ?><br/><a href="<?php echo url::site();?>members/"><?php echo Kohana::lang('ui_main.manage_your_account'); ?></a></div>
+				<?php }else{ ?>
+					<div><?php echo Kohana::lang('ui_main.is_this_your_profile'); ?>
+					<?php if($logged_in_id){ ?>
+						<a href="<?php echo url::site();?>logout/front"><?php echo Kohana::lang('ui_admin.logout');?></a>
+					<?php }else{ ?>
+						<a href="<?php echo url::site();?>members/"><?php echo Kohana::lang('ui_main.login'); ?></a>
+					<?php } ?>
+					</div>
+				<?php } ?>
+			</div>
+
+			<div class="profile-right">
+				<h4><?php echo html::specialchars($user->name); ?></h4>
+
+				<div class="report-additional-reports">
+					<h4><?php echo Kohana::lang('ui_main.reports_by_this_user');?></h4>
+					<?php foreach($reports as $report) { ?>
+						<div class="rb_report">
+							<h5><a href="<?php echo url::site(); ?>reports/view/<?php echo $report->id; ?>"><?php echo html::escape($report->incident_title); ?></a></h5>
+							<p class="r_date r-3 bottom-cap"><?php echo date('H:i M d, Y', strtotime($report->incident_date)); ?></p>
+							<p class="r_location"><?php echo html::specialchars($report->location->location_name); ?></p>
+						</div>
+					<?php } ?>
+				</div>
+
+			</div>
+
+			<?php if(count($badges) > 0) { ?>
+			<div class="badges">
+
+				<h4><?php echo Kohana::lang('ui_main.badges');?></h4>
+
+				<?php foreach($badges as $badge) { ?>
+
+					<div class="badge r-5">
+					<img src="<?php echo $badge['img_m']; ?>" alt="<?php echo Kohana::lang('ui_main.badge').' '.$badge['id'];?>" width="80" height="80"  />
+					<br/><strong><?php echo html::specialchars($badge['name']); ?></strong>
+				</div>
+
+				<?php } ?>
+
+				<div style="clear:both;"></div>
+
+			</div>
+			<?php } ?>
+
+			<div style="clear:both;"></div>
+
+	</div>
+</div>
diff --git a/themes/default/views/reports/comments.php b/themes/default/views/reports/comments.php
new file mode 100644
index 0000000..5d9a0ca
--- /dev/null
+++ b/themes/default/views/reports/comments.php
@@ -0,0 +1,21 @@
+<?php if(count($incident_comments) > 0): ?>
+
+<div class="report-comments">
+
+	<h5><?php echo Kohana::lang('ui_main.comments'); ?></h5>
+
+	<?php foreach($incident_comments as $comment): ?>
+		<div class="report-comment-box">
+
+			<div>
+				<strong><?php echo html::strip_tags($comment->comment_author); ?></strong> (<?php echo date('M j Y', strtotime($comment->comment_date)); ?>)
+			</div>
+
+			<div><?php echo html::escape($comment->comment_description); ?></div>
+
+		</div>
+	<?php endforeach; ?>
+
+</div>
+
+<?php endif; ?>
\ No newline at end of file
diff --git a/themes/default/views/reports/comments_form.php b/themes/default/views/reports/comments_form.php
new file mode 100644
index 0000000..1bb0869
--- /dev/null
+++ b/themes/default/views/reports/comments_form.php
@@ -0,0 +1,68 @@
+<!-- start submit comments block -->
+<div class="comment-block">
+	
+	<h5><?php echo Kohana::lang('ui_main.leave_a_comment');?></h5>
+	<?php
+	if ($form_error)
+	{
+		?>
+		<!-- red-box -->
+		<div class="red-box">
+			<h3><?php echo Kohana::lang('ui_main.error');?></h3>
+			<ul>
+				<?php
+					foreach ($errors as $error_item => $error_description)
+					{
+						print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+					}
+				?>
+			</ul>
+		</div>
+		<?php
+	}
+	?>
+	<?php print form::open(NULL, array('id' => 'commentForm', 'name' => 'commentForm')); ?>
+	<?php
+	if ( ! $user)
+	{
+		?>
+		<div class="report_row">
+			<strong><?php echo Kohana::lang('ui_main.name');?>:</strong><br />
+			<?php print form::input('comment_author', $form['comment_author'], ' class="text"'); ?>
+			</div>
+
+			<div class="report_row">
+			<strong><?php echo Kohana::lang('ui_main.email'); ?>:</strong><br />
+			<?php print form::input('comment_email', $form['comment_email'], ' class="text"'); ?>
+		</div>
+		<?php
+	}
+	else
+	{
+		?>
+		<div class="report_row">
+			<strong><?php echo $user->name; ?></strong>
+		</div>
+		<?php
+	}
+	?>
+	<div class="report_row">
+		<strong><?php echo Kohana::lang('ui_main.comments'); ?>:</strong><br />
+		<?php print form::textarea('comment_description', $form['comment_description'], ' rows="4" cols="40" class="textarea long" ') ?>
+	</div>
+	<div class="report_row">
+		<strong><?php echo Kohana::lang('ui_main.security_code'); ?>:</strong><br />
+		<?php print $captcha->render(); ?><br />
+		<?php print form::input('captcha', $form['captcha'], ' class="text"'); ?>
+	</div>
+	<?php
+	// Action::comments_form - Runs right before the end of the comment submit form
+	Event::run('ushahidi_action.comment_form');
+	?>
+	<div class="report_row">
+		<input name="submit" type="submit" value="<?php echo Kohana::lang('ui_main.reports_btn_submit'); ?> <?php echo Kohana::lang('ui_main.comment'); ?>" class="btn_blue" />
+	</div>
+	<?php print form::close(); ?>
+	
+</div>
+<!-- end submit comments block -->
\ No newline at end of file
diff --git a/themes/default/views/reports/detail.php b/themes/default/views/reports/detail.php
new file mode 100755
index 0000000..462ebe6
--- /dev/null
+++ b/themes/default/views/reports/detail.php
@@ -0,0 +1,225 @@
+<div id="main" class="report_detail">
+
+	<div class="left-col">
+
+  	  <?php
+    	  if ($incident_verified)
+    		{
+    			echo '<p class="r_verified">'.Kohana::lang('ui_main.verified').'</p>';
+    		}
+    		else
+    		{
+    			echo '<p class="r_unverified">'.Kohana::lang('ui_main.unverified').'</p>';
+    		}
+  	  ?>
+
+		<h1 class="report-title"><?php
+			echo html::escape($incident_title);
+
+			// If Admin is Logged In - Allow For Edit Link
+			if ($logged_in)
+			{
+				echo " [ <a href=\"".url::site()."admin/reports/edit/".$incident_id."\">"
+				    .Kohana::lang('ui_main.edit')."</a> ]";
+			}
+		?></h1>
+
+		<p class="report-when-where">
+			<span class="r_date"><?php echo $incident_time.' '.$incident_date; ?> </span>
+			<span class="r_location"><?php echo html::specialchars($incident_location); ?></span>
+			<?php Event::run('ushahidi_action.report_meta_after_time', $incident_id); ?>
+		</p>
+
+		<div class="report-category-list">
+		<p>
+			<?php
+				foreach ($incident_category as $category)
+				{
+					// don't show hidden categoies
+					if ($category->category->category_visible == 0)
+					{
+						continue;
+					}
+					if ($category->category->category_image_thumb)
+					{
+						$style = "background:transparent url(".url::convert_uploaded_to_abs($category->category->category_image_thumb).") 0 0 no-repeat";
+					}
+					else
+					{
+						$style = "background-color:#".$category->category->category_color;
+					}
+					
+					?>
+					<a href="<?php echo url::site()."reports/?c=".$category->category->id; ?>" title="<?php echo Category_Lang_Model::category_description($category->category_id);; ?>">
+						<span class="r_cat-box" style="<?php echo $style ?>"> </span>
+						<?php echo Category_Lang_Model::category_title($category->category_id); ?>
+					</a>
+					<?php 
+				}
+			?>
+			</p>
+			<?php
+			// Action::report_meta - Add Items to the Report Meta (Location/Date/Time etc.)
+			Event::run('ushahidi_action.report_meta', $incident_id);
+			?>
+		</div>
+
+		<?php
+		// Action::report_display_media - Add content just above media section
+	    Event::run('ushahidi_action.report_display_media', $incident_id);
+		?>
+
+		<!-- start report media -->
+		<div class="<?php if( count($incident_photos) > 0 || count($incident_videos) > 0){ echo "report-media";}?>">
+			<?php
+			// if there are images, show them
+			if( count($incident_photos) > 0 )
+			{
+				echo '<div id="report-images">';
+				foreach ($incident_photos as $photo)
+				{
+					echo '<a class="photothumb" rel="lightbox-group1" href="'.$photo['large'].'"><img alt="'.html::escape($incident_title).'" src="'.$photo['thumb'].'"/></a> ';
+				};
+				echo '</div>';
+			}
+
+			// if there are videos, show those too
+			if( count($incident_videos) > 0 )
+			{
+				echo "<div id=\"report-video\"><ul>\n";
+				// embed the video codes
+				foreach( $incident_videos as $incident_video)
+				{
+					echo "<li>\n\t";
+					echo $videos_embed->embed($incident_video, FALSE, FALSE);
+					echo "\n</li>\n";
+				};
+  			echo "</ul></div>";
+
+			}
+			?>
+		</div>
+
+		<!-- start report description -->
+		<div class="report-description-text">
+			<h5><?php echo Kohana::lang('ui_main.reports_description');?></h5>
+			<?php echo html::clean(nl2br($incident_description)); ?>
+			<br/>
+
+
+			<!-- start news source link -->
+			<?php if( count($incident_news) > 0 ) { ?>
+			<div class="credibility">
+			<h5><?php echo Kohana::lang('ui_main.reports_news');?></h5>
+					<?php
+						foreach( $incident_news as $incident_new)
+						{
+							?>
+							<a href="<?php echo $incident_new; ?> " target="_blank"><?php
+							echo $incident_new;?></a>
+							<br/>
+							<?php
+						}
+			?>
+			</div>
+			<?php } ?>
+			<!-- end news source link -->
+
+			<!-- start additional fields -->
+			<?php if(strlen($custom_forms) > 0) { ?>
+			<div class="credibility">
+			<h5><?php echo Kohana::lang('ui_main.additional_data');?></h5>
+			<?php
+
+				echo $custom_forms;
+
+			?>
+			<br/>
+			</div>
+			<?php } ?>
+			<!-- end additional fields -->
+
+			<?php if ($features_count)
+			{
+				?>
+				<br /><br /><h5><?php echo Kohana::lang('ui_main.reports_features');?></h5>
+				<?php
+				foreach ($features as $feature)
+				{
+					echo ($feature->geometry_label) ?
+					 	"<div class=\"feature_label\"><a href=\"javascript:getFeature($feature->id)\">$feature->geometry_label</a></div>" : "";
+					echo ($feature->geometry_comment) ?
+						"<div class=\"feature_comment\">$feature->geometry_comment</div>" : "";
+				}
+			}?>
+
+			<div class="credibility">
+				<table class="rating-table" cellspacing="0" cellpadding="0" border="0">
+          <tr>
+            <td><?php echo Kohana::lang('ui_main.credibility');?>:</td>
+            <td><a href="javascript:rating('<?php echo $incident_id; ?>','add','original','oloader_<?php echo $incident_id; ?>')"><img id="oup_<?php echo $incident_id; ?>" src="<?php echo url::file_loc('img'); ?>media/img/up.png" alt="UP" title="UP" border="0" /></a></td>
+            <td><a href="javascript:rating('<?php echo $incident_id; ?>','subtract','original')"><img id="odown_<?php echo $incident_id; ?>" src="<?php echo url::file_loc('img'); ?>media/img/down.png" alt="DOWN" title="DOWN" border="0" /></a></td>
+            <td><a href="" class="rating_value" id="orating_<?php echo $incident_id; ?>"><?php echo $incident_rating; ?></a></td>
+            <td><a href="" id="oloader_<?php echo $incident_id; ?>" class="rating_loading" ></a></td>
+          </tr>
+        </table>
+			</div>
+		</div>
+
+		<?php
+            // Action::report_extra - Allows you to target an individual report right after the description
+            Event::run('ushahidi_action.report_extra', $incident_id);
+
+			// Filter::comments_block - The block that contains posted comments
+			Event::run('ushahidi_filter.comment_block', $comments);
+			echo $comments;
+		?>
+
+		<?php
+			// Filter::comments_form_block - The block that contains the comments form
+			Event::run('ushahidi_filter.comment_form_block', $comments_form);
+			echo $comments_form;
+		?>
+
+	</div>
+
+	<div class="right-col">
+
+		<div class="report-media-box-content">
+
+			<div id="report-map" class="report-map">
+				<div class="map-holder" id="map"></div>
+        <ul class="map-toggles">
+          <li><a href="#" class="smaller-map"><?php echo Kohana::lang('ui_main.smaller_map'); ?></a></li>
+          <li style="display:block;"><a href="#" class="wider-map"><?php echo Kohana::lang('ui_main.wider_map'); ?></a></li>
+          <li><a href="#" class="taller-map"><?php echo Kohana::lang('ui_main.taller_map'); ?></a></li>
+          <li><a href="#" class="shorter-map"><?php echo Kohana::lang('ui_main.shorter_map'); ?></a></li>
+        </ul>
+        <div style="clear:both"></div>
+			</div>
+		</div>
+
+		<?php
+			// Action::report_view_sidebar - This gives plugins the ability to insert into the sidebar (below the map and above additional reports)
+			Event::run('ushahidi_action.report_view_sidebar', $incident_id);
+		?>
+
+		<div class="report-additional-reports">
+			<h4><?php echo Kohana::lang('ui_main.additional_reports');?></h4>
+			<?php foreach($incident_neighbors as $neighbor) { ?>
+			  <div class="rb_report">
+  			  <h5><a href="<?php echo url::site(); ?>reports/view/<?php echo $neighbor->id; ?>"><?php echo $neighbor->incident_title; ?></a></h5>
+  			  <p class="r_date r-3 bottom-cap"><?php echo date('H:i M d, Y', strtotime($neighbor->incident_date)); ?></p>
+  			  <p class="r_location"><?php echo $neighbor->location_name.", ".round($neighbor->distance, 2); ?> Kms</p>
+  			</div>
+      <?php } ?>
+		</div>
+
+	</div>
+
+	<div style="clear:both;"></div>
+
+
+
+
+</div>
diff --git a/themes/default/views/reports/detail_custom_forms.php b/themes/default/views/reports/detail_custom_forms.php
new file mode 100644
index 0000000..dc3f5bc
--- /dev/null
+++ b/themes/default/views/reports/detail_custom_forms.php
@@ -0,0 +1,64 @@
+<?php if (count($form_field_names) > 0) { ?>
+<div class="report-custom-forms-text">
+<table>
+<?php
+	foreach ($form_field_names as $field_id => $field_property)
+	{
+		if ($field_property['field_type'] == 8)
+		{
+			echo "</table>";
+
+			if (isset($field_propeerty['field_default']))
+			{
+				echo "<div class=\"" . $field_property['field_name'] . "\">";
+			}
+			else
+			{
+				echo "<div class=\"custom_div\">";
+			}
+
+			echo "<h2>" . $field_property['field_name'] . "</h2>";
+			echo "<table>";
+
+			continue;
+		}
+		elseif ($field_property['field_type'] == 9)
+		{
+			echo "</table></div>";
+			continue;
+		}
+
+		echo "<tr class='custom_field custom_field_{$field_property['field_id']}'>";
+
+		// Get the value for the form field
+		$value = $field_property['field_response'];
+
+		// Check if a value was fetched
+		if ($value == "" AND empty($show_empty))
+			continue;
+
+		if ($field_property['field_type'] == 1 OR $field_property['field_type'] > 3)
+		{
+			// Text Field
+			// Is this a date field?
+			echo "<td><strong>" . html::specialchars($field_property['field_name']) . ": </strong></td>";
+			echo "<td class=\"answer\">$value</td>";
+		}
+		elseif ($field_property['field_type'] == 2)
+		{
+			// TextArea Field
+			echo "<td><strong>" . html::specialchars($field_property['field_name']) . ": </strong></td>";
+			echo "<td class=\"answer\">$value</tr>";
+		}
+		elseif ($field_property['field_type'] == 3)
+		{
+			echo "<td><strong>" . html::specialchars($field_property['field_name']) . ": </strong></td>";
+			echo "<td class=\"answer\">" . date('M d Y', strtotime($value)) . "</td>";
+		}
+		//echo "</div>";
+		echo "</tr>";
+	}
+?>
+</table>
+</div>
+<?php } ?>
\ No newline at end of file
diff --git a/themes/default/views/reports/list.php b/themes/default/views/reports/list.php
new file mode 100644
index 0000000..5f76ea7
--- /dev/null
+++ b/themes/default/views/reports/list.php
@@ -0,0 +1,177 @@
+<?php
+/**
+ * View file for updating the reports display
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team - http://www.ushahidi.com
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+		<!-- Top reportbox section-->
+		<div class="rb_nav-controls r-5">
+			<table border="0" cellspacing="0" cellpadding="0">
+				<tr>
+					<td>
+						<ul class="link-toggle report-list-toggle lt-icons-and-text">
+							<li class="active"><a href="#rb_list-view" class="list"><?php echo Kohana::lang('ui_main.list'); ?></a></li>
+							<li><a href="#rb_map-view" class="map"><?php echo Kohana::lang('ui_main.map'); ?></a></li>
+						</ul>
+					</td>
+					<td><?php echo $pagination; ?></td>
+					<td><?php echo $stats_breadcrumb; ?></td>
+					<td class="last">
+						<ul class="link-toggle lt-icons-only">
+							<?php //@todo Toggle the status of these links depending on the current page ?>
+							<li><a href="#page_<?php echo $previous_page; ?>" class="prev"><?php echo Kohana::lang('ui_main.previous'); ?></a></li>
+							<li><a href="#page_<?php echo $next_page; ?>" class="next"><?php echo Kohana::lang('ui_main.next'); ?></a></li>
+						</ul>
+					</td>
+				</tr>
+			</table>
+		</div>
+		<!-- /Top reportbox section-->
+		
+		<!-- Report listing -->
+		<div class="r_cat_tooltip"><a href="#" class="r-3"></a></div>
+		<div class="rb_list-and-map-box">
+			<div id="rb_list-view">
+			<?php
+				foreach ($incidents as $incident)
+				{
+					$incident_id = $incident->incident_id;
+					$incident_title = $incident->incident_title;
+					$incident_description = $incident->incident_description;
+					$incident_url = Incident_Model::get_url($incident_id);
+					//$incident_category = $incident->incident_category;
+					// Trim to 150 characters without cutting words
+					// XXX: Perhaps delcare 150 as constant
+
+					$incident_description = text::limit_chars(html::strip_tags($incident_description), 140, "...", true);
+					$incident_date = date('H:i M d, Y', strtotime($incident->incident_date));
+					//$incident_time = date('H:i', strtotime($incident->incident_date));
+					$location_id = $incident->location_id;
+					$location_name = $incident->location_name;
+					$incident_verified = $incident->incident_verified;
+
+					if ($incident_verified)
+					{
+						$incident_verified = '<span class="r_verified">'.Kohana::lang('ui_main.verified').'</span>';
+						$incident_verified_class = "verified";
+					}
+					else
+					{
+						$incident_verified = '<span class="r_unverified">'.Kohana::lang('ui_main.unverified').'</span>';
+						$incident_verified_class = "unverified";
+					}
+
+					$comment_count = ORM::Factory('comment')->where('incident_id', $incident_id)->count_all();
+
+					$incident_thumb = url::file_loc('img')."media/img/report-thumb-default.jpg";
+					$media = ORM::Factory('media')->where('incident_id', $incident_id)->find_all();
+					if ($media->count())
+					{
+						foreach ($media as $photo)
+						{
+							if ($photo->media_thumb)
+							{ // Get the first thumb
+								$incident_thumb = url::convert_uploaded_to_abs($photo->media_thumb);
+								break;
+							}
+						}
+					}
+				?>
+				<div id="incident_<?php echo $incident_id ?>" class="rb_report <?php echo $incident_verified_class; ?>">
+					<div class="r_media">
+						<p class="r_photo"> <a href="<?php echo $incident_url; ?>">
+							<img alt="<?php echo html::escape($incident_title); ?>" src="<?php echo $incident_thumb; ?>" /> </a>
+						</p>
+
+						<!-- Only show this if the report has a video -->
+						<p class="r_video" style="display:none;"><a href="#"><?php echo Kohana::lang('ui_main.video'); ?></a></p>
+
+						<!-- Category Selector -->
+						<div class="r_categories">
+							<h4><?php echo Kohana::lang('ui_main.categories'); ?></h4>
+							<?php
+							$categories = ORM::Factory('category')->join('incident_category', 'category_id', 'category.id')->where('incident_id', $incident_id)->find_all();
+							foreach ($categories as $category): ?>
+								
+								<?php // Don't show hidden categories ?>
+								<?php if($category->category_visible == 0) continue; ?>
+						
+								<?php if ($category->category_image_thumb): ?>
+									<?php $category_image = url::site(Kohana::config('upload.relative_directory')."/".$category->category_image_thumb); ?>
+									<a class="r_category" href="<?php echo url::site("reports/?c=$category->id") ?>">
+										<span class="r_cat-box"><img src="<?php echo $category_image; ?>" height="16" width="16" /></span> 
+										<span class="r_cat-desc"><?php echo Category_Lang_Model::category_title($category->id); ?></span>
+									</a>
+								<?php else:	?>
+									<a class="r_category" href="<?php echo url::site("reports/?c=$category->id") ?>">
+										<span class="r_cat-box" style="background-color:#<?php echo $category->category_color;?>;"></span> 
+										<span class="r_cat-desc"><?php echo Category_Lang_Model::category_title($category->id); ?></span>
+									</a>
+								<?php endif; ?>
+							<?php endforeach; ?>
+						</div>
+						<?php
+						// Action::report_extra_media - Add items to the report list in the media section
+						Event::run('ushahidi_action.report_extra_media', $incident_id);
+						?>
+					</div>
+
+					<div class="r_details">
+						<h3><a class="r_title" href="<?php echo $incident_url; ?>">
+								<?php echo html::escape($incident_title); ?>
+							</a>
+							<a href="<?php echo "$incident_url#discussion"; ?>" class="r_comments">
+								<?php echo $comment_count; ?></a> 
+								<?php echo $incident_verified; ?>
+							</h3>
+						<p class="r_date r-3 bottom-cap"><?php echo $incident_date; ?></p>
+						<div class="r_description"> <?php echo $incident_description; ?>  
+						  <a class="btn-show btn-more" href="#incident_<?php echo $incident_id ?>"><?php echo Kohana::lang('ui_main.more_information'); ?> »</a> 
+						  <a class="btn-show btn-less" href="#incident_<?php echo $incident_id ?>">« <?php echo Kohana::lang('ui_main.less_information'); ?></a> 
+						</div>
+						<p class="r_location"><a href="<?php echo url::site("reports/?l=$location_id"); ?>"><?php echo html::specialchars($location_name); ?></a></p>
+						<?php
+						// Action::report_extra_details - Add items to the report list details section
+						Event::run('ushahidi_action.report_extra_details', $incident_id);
+						?>
+					</div>
+				</div>
+			<?php } ?>
+			</div>
+			<div id="rb_map-view">
+			</div>
+		</div>
+		<!-- /Report listing -->
+		
+		<!-- Bottom paginator -->
+		<div class="rb_nav-controls r-5">
+			<table border="0" cellspacing="0" cellpadding="0">
+				<tr>
+					<td>
+						<ul class="link-toggle report-list-toggle lt-icons-and-text">
+							<li class="active"><a href="#rb_list-view" class="list"><?php echo Kohana::lang('ui_main.list'); ?></a></li>
+							<li><a href="#rb_map-view" class="map"><?php echo Kohana::lang('ui_main.map'); ?></a></li>
+						</ul>
+					</td>
+					<td><?php echo $pagination; ?></td>
+					<td><?php echo $stats_breadcrumb; ?></td>
+					<td class="last">
+						<ul class="link-toggle lt-icons-only">
+							<?php //@todo Toggle the status of these links depending on the current page ?>
+							<li><a href="#page_<?php echo $previous_page; ?>" class="prev"><?php echo Kohana::lang('ui_main.previous'); ?></a></li>
+							<li><a href="#page_<?php echo $next_page; ?>" class="next"><?php echo Kohana::lang('ui_main.next'); ?></a></li>
+						</ul>
+					</td>
+				</tr>
+			</table>
+		</div>
+		<!-- /Bottom paginator -->
+	        
\ No newline at end of file
diff --git a/themes/default/views/reports/main.php b/themes/default/views/reports/main.php
new file mode 100644
index 0000000..f2f8ff1
--- /dev/null
+++ b/themes/default/views/reports/main.php
@@ -0,0 +1,226 @@
+<div id="content">
+	<div class="content-bg">
+		<!-- start reports block -->
+		<div class="big-block">
+			<h1 class="heading">
+				<?php echo Kohana::lang('ui_main.showing_reports_from', array(date('M d, Y', $oldest_timestamp), date('M d, Y', $latest_timestamp))); ?> 
+				<a href="#" class="btn-change-time ic-time"><?php echo Kohana::lang('ui_main.change_date_range'); ?></a>
+			</h1>
+			
+			<div id="tooltip-box">
+				<div class="tt-arrow"></div>
+				<ul class="inline-links">
+					<li>
+						<a title="<?php echo Kohana::lang('ui_main.all_time'); ?>" class="btn-date-range active" id="dateRangeAll" href="#">
+							<?php echo Kohana::lang('ui_main.all_time')?>
+						</a>
+					</li>
+					<li>
+						<a title="<?php echo Kohana::lang('ui_main.today'); ?>" class="btn-date-range" id="dateRangeToday" href="#">
+							<?php echo Kohana::lang('ui_main.today'); ?>
+						</a>
+					</li>
+					<li>
+						<a title="<?php echo Kohana::lang('ui_main.this_week'); ?>" class="btn-date-range" id="dateRangeWeek" href="#">
+							<?php echo Kohana::lang('ui_main.this_week'); ?>
+						</a>
+					</li>
+					<li>
+						<a title="<?php echo Kohana::lang('ui_main.this_month'); ?>" class="btn-date-range" id="dateRangeMonth" href="#">
+							<?php echo Kohana::lang('ui_main.this_month'); ?>
+						</a>
+					</li>
+				</ul>
+				
+				<p class="labeled-divider"><span><?php echo Kohana::lang('ui_main.choose_date_range'); ?>:</span></p>
+				<?php echo form::open(NULL, array('method' => 'get')); ?>
+					<table class="report-date-filter">
+						<tr>
+							<td><strong>
+								<?php echo Kohana::lang('ui_admin.from')?>:</strong><input id="report_date_from" type="text" />
+							</td>
+							<td>
+								<strong><?php echo ucfirst(strtolower(Kohana::lang('ui_admin.to'))); ?>:</strong>
+								<input id="report_date_to" type="text" />
+							</td>
+							<td valign="bottom">
+								<a href="#" id="applyDateFilter" class="filter-button"><?php echo Kohana::lang('ui_main.go')?></a>
+							</td>
+						</tr>
+					</table>
+				<?php echo form::close(); ?>
+			</div>
+
+			<div class="reports-content">
+				<!-- reports-box -->
+				<div id="reports-box">
+					<?php echo $report_listing_view; ?>
+				</div>
+				<!-- end #reports-box -->
+				
+				<div id="filters-box">
+					<h2><?php echo Kohana::lang('ui_main.filter_reports_by'); ?></h2>
+					<div id="accordion">
+						
+						<h3>
+							<a href="#" class="small-link-button f-clear reset" onclick="removeParameterKey('c', 'fl-categories');"><?php echo Kohana::lang('ui_main.clear')?></a>
+							<a class="f-title" href="#"><?php echo Kohana::lang('ui_main.category')?></a>
+						</h3>
+						<div class="f-category-box">
+							<ul class="filter-list fl-categories" id="category-filter-list">
+								<li>
+									<a href="#"><?php
+									$all_cat_image = '&nbsp';
+									$all_cat_image = '';
+									if($default_map_all_icon != NULL) {
+										$all_cat_image = html::image(array('src'=>$default_map_all_icon));
+									}
+									?>
+									<span class="item-swatch" style="background-color: #<?php echo Kohana::config('settings.default_map_all'); ?>"><?php echo $all_cat_image ?></span>
+									<span class="item-title"><?php echo Kohana::lang('ui_main.all_categories'); ?></span>
+									<span class="item-count" id="all_report_count"><?php echo $report_stats->total_reports; ?></span>
+									</a>
+								</li>
+								<?php echo $category_tree_view; ?>
+							</ul>
+						</div>
+						
+						<h3>	
+							<a href="#" class="small-link-button f-clear reset" onclick="removeParameterKey('radius', 'f-location-box');removeParameterKey('start_loc', 'f-location-box');">
+								<?php echo Kohana::lang('ui_main.clear')?>
+							</a>
+							<a class="f-title" href="#"><?php echo Kohana::lang('ui_main.location'); ?></a></h3>
+						<div class="f-location-box">
+							<?php echo $alert_radius_view; ?>
+							<p></p>
+						</div>
+						
+						<h3>
+							<a href="#" class="small-link-button f-clear reset" onclick="removeParameterKey('mode', 'fl-incident-mode');">
+								<?php echo Kohana::lang('ui_main.clear')?>
+							</a>
+							<a class="f-title" href="#"><?php echo Kohana::lang('ui_main.type')?></a>
+						</h3>
+						<div class="f-type-box">
+							<ul class="filter-list fl-incident-mode">
+								<li>
+									<a href="#" id="filter_link_mode_1">
+										<span class="item-icon ic-webform"> </span>
+										<span class="item-title"><?php echo Kohana::lang('ui_main.web_form'); ?></span>
+									</a>
+								</li>
+							
+							<?php foreach ($services as $id => $name): ?>
+								<?php
+									$item_class = "";
+									if ($id == 1) $item_class = "ic-sms";
+									if ($id == 2) $item_class = "ic-email";
+									if ($id == 3) $item_class = "ic-twitter";
+								?>
+								<li>
+									<a href="#" id="filter_link_mode_<?php echo ($id + 1); ?>">
+										<span class="item-icon <?php echo $item_class; ?>"> </span>
+										<span class="item-title"><?php echo $name; ?></span>
+									</a>
+								</li>
+							<?php endforeach; ?>
+
+							</ul>
+						</div>
+						
+						<h3>
+							<a href="#" class="small-link-button f-clear reset" onclick="removeParameterKey('m', 'fl-media');"><?php echo Kohana::lang('ui_main.clear')?></a>
+							<a class="f-title" href="#"><?php echo Kohana::lang('ui_main.media');?></a>
+						</h3>
+						<div class="f-media-box">
+							<p><?php echo Kohana::lang('ui_main.filter_reports_contain'); ?>…</p>
+							<ul class="filter-list fl-media">
+								<li>
+									<a href="#" id="filter_link_media_1">
+										<span class="item-icon ic-photos"> </span>
+										<span class="item-title"><?php echo Kohana::lang('ui_main.photos'); ?></span>
+									</a>
+								</li>
+								<li>
+									<a href="#" id="filter_link_media_2">
+										<span class="item-icon ic-videos"> </span>
+										<span class="item-title"><?php echo Kohana::lang('ui_main.video'); ?></span>
+									</a>
+								</li>
+								<li>
+									<a href="#" id="filter_link_media_4">
+										<span class="item-icon ic-news"> </span>
+										<span class="item-title"><?php echo Kohana::lang('ui_main.reports_news')?></span>
+									</a>
+								</li>
+							</ul>
+						</div>
+						
+						<h3>
+							<a href="#" class="small-link-button f-clear reset" onclick="removeParameterKey('v', 'fl-verification');">
+								<?php echo Kohana::lang('ui_main.clear'); ?>
+							</a>
+							<a class="f-title" href="#"><?php echo Kohana::lang('ui_main.verification'); ?></a>
+						</h3>
+						<div class="f-verification-box">
+							<ul class="filter-list fl-verification">
+								<li>
+									<a href="#" id="filter_link_verification_1">
+										<span class="item-icon ic-verified"> </span>
+										<span class="item-title"><?php echo Kohana::lang('ui_main.verified'); ?></span>
+									</a>
+								</li>
+								<li>
+									<a href="#" id="filter_link_verification_0">
+										<span class="item-icon ic-unverified"> </span>
+										<span class="item-title"><?php echo Kohana::lang('ui_main.unverified'); ?></span>
+									</a>
+								</li>
+								
+							</ul>
+						</div>
+						<h3>
+							<a href="#" class="small-link-button f-clear reset" onclick="removeParameterKey('cff', 'fl-customFields');">
+								<?php echo Kohana::lang('ui_main.clear'); ?>
+							</a>
+							<a class="f-title" href="#"><?php echo Kohana::lang('ui_main.custom_fields'); ?></a>
+						</h3>
+						<div class="f-customFields-box">
+							<?php echo $custom_forms_filter; ?>
+							
+						</div>
+						<?php
+							// Action, allows plugins to add custom filters
+							Event::run('ushahidi_action.report_filters_ui');
+						?>
+					</div>
+					<!-- end #accordion -->
+					
+					<div id="filter-controls">
+						<p>
+							<a href="#" class="small-link-button reset" id="reset_all_filters"><?php echo Kohana::lang('ui_main.reset_all_filters'); ?></a> 
+							<a href="#" id="applyFilters" class="filter-button"><?php echo Kohana::lang('ui_main.filter_reports'); ?></a>
+						</p>
+						<?php
+						// Action, allows plugins to add custom filter controls
+						Event::run('ushahidi_action.report_filters_controls_ui');
+						?>
+					</div>
+				</div>
+				<!-- end #filters-box -->
+			</div>
+      
+			<div class="report-stats-container">
+				<?php
+					// Filter::report_stats - The block that contains reports list statistics
+					Event::run('ushahidi_filter.report_stats', $report_stats);
+					echo $report_stats;
+				?>
+			</div>
+
+		</div>
+		<!-- end reports block -->
+		
+	</div>
+	<!-- end content-bg -->
+</div>
diff --git a/themes/default/views/reports/reports_js.php b/themes/default/views/reports/reports_js.php
new file mode 100644
index 0000000..55906e9
--- /dev/null
+++ b/themes/default/views/reports/reports_js.php
@@ -0,0 +1,943 @@
+<?php
+/**
+ * Reports listing js file.
+ *
+ * Handles javascript stuff related to reports list function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Reports Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+	<?php @require(APPPATH.'views/map_common_js.php'); ?>
+	
+	// Tracks the current URL parameters
+	var urlParameters = <?php echo $url_params; ?>;
+	var deSelectedFilters = [];
+	
+	// Lat/lon and zoom for the map
+	var latitude = <?php echo $latitude; ?>;
+	var longitude = <?php echo $longitude; ?>;
+	var defaultZoom = <?php echo $default_zoom; ?>;
+	
+	// Track the current latitude and longitude on the alert radius map
+	var currLat, currLon;
+	
+	// Tracks whether the map has already been loaded
+	var mapLoaded = 0;
+	
+	// Map object
+	var map = null;
+	var radiusMap = null;
+	
+	if (urlParameters.length == 0)
+	{
+		urlParameters = {};
+	}
+	
+	$(document).ready(function() {
+	
+	
+		//add Not Selected values to the custom form fields that are drop downs
+		$("select[id^='custom_field_']").prepend('<option value="---NOT_SELECTED---"><?php echo Kohana::lang("ui_main.not_selected"); ?></option>');
+		$("select[id^='custom_field_']").val("---NOT_SELECTED---");
+		$("input[id^='custom_field_']:checkbox").removeAttr("checked");
+		$("input[id^='custom_field_']:radio").removeAttr("checked");
+		$("input[id^='custom_field_']:text").val("");
+		// Hide textareas - should really replace with a keyword search field
+		$("textarea[id^='custom_field_']").parent().remove();
+		  
+		// "Choose Date Range"" Datepicker
+		var dates = $( "#report_date_from, #report_date_to" ).datepicker({
+			defaultDate: "+1w",
+			changeMonth: true,
+			numberOfMonths: 1,
+			dateFormat: "yy-mm-dd",
+			onSelect: function( selectedDate ) {
+				var option = this.id == "report_date_from" ? "minDate" : "maxDate",
+				instance = $( this ).data( "datepicker" ),
+				date = $.datepicker.parseDate(
+				instance.settings.dateFormat ||
+				$.datepicker._defaults.dateFormat,
+				selectedDate, instance.settings );
+				dates.not( this ).datepicker( "option", option, date );
+			}
+		});
+		  
+		/**
+		 * Date range datepicker box functionality
+		 * Show the box when clicking the "change time" link
+		 */
+		$(".btn-change-time").click(function(){
+			$("#tooltip-box").css({
+				'left': ($(this).offset().left - 80),
+				'top': ($(this).offset().right)
+			}).toggle();
+			
+	        return false;
+		});
+			
+		  	
+		/**
+		 *	Helper function for date formatting.
+		 */
+		var twoDigitNumber = function(n)
+		{
+			return ( n < 10 ) ? "0" + n : n;
+		}
+
+		/**
+		 * Change time period text in page header to reflect what was clicked
+		 * then hide the date range picker box
+		 */
+		$(".btn-date-range").click(function()
+		{
+			// Change the text
+			$(".time-period").text($(this).attr("title"));
+			
+			// Update the "active" state
+			$(".btn-date-range").removeClass("active");
+			
+			$(this).addClass("active");
+			
+			// Date object
+			var d = new Date();
+			
+			var month = twoDigitNumber( d.getMonth() + 1 );
+			
+			if ($(this).attr("id") == 'dateRangeAll')
+			{
+				// Clear the date range values
+				$("#report_date_from").val("");
+				$("#report_date_to").val("");
+				
+				// Clear the url parameters
+				delete urlParameters['from'];
+				delete urlParameters['to'];
+				delete urlParameters['s'];
+				delete urlParameters['e'];
+			}
+			else if ($(this).attr("id") == 'dateRangeToday')
+			{
+				// Set today's date
+				currentDate = (d.getDate() < 10)? "0"+d.getDate() : d.getDate();
+				var dateString = [d.getFullYear(), month, currentDate].join("-");
+				$("#report_date_from").val(dateString);
+				$("#report_date_to").val(dateString);
+			}
+			else if ($(this).attr("id") == 'dateRangeWeek')
+			{
+				// Get first day of the week
+				var diff = d.getDate() - d.getDay();
+				var d1 = new Date(d.setDate(diff));
+				var d2 = new Date(d.setDate(diff + 6));
+				
+				// Get the first and last days of the week
+				firstWeekDay = (d1.getDate() < 10)? ("0" + d1.getDate()) : d1.getDate();
+				lastWeekDay = (d2.getDate() < 10)? ("0" + d2.getDate()) : d2.getDate();
+				
+				$("#report_date_from").val( [d1.getFullYear(), twoDigitNumber( d1.getMonth() + 1 ), twoDigitNumber( d1.getDate() ) ].join("-") );
+				$("#report_date_to").val( [d2.getFullYear(), twoDigitNumber( d2.getMonth() + 1 ), twoDigitNumber( d2.getDate() ) ].join("-") );
+			}
+			else if ($(this).attr("id") == 'dateRangeMonth')
+			{
+				d1 = new Date(d);
+				d1.setDate(32);
+				lastMonthDay = 32 - d1.getDay();
+				
+				$("#report_date_from").val( [d.getFullYear(), month, '01'].join("-") );
+				$("#report_date_to").val( [d.getFullYear(), month, lastMonthDay].join("-") );
+			}
+			
+			// Update the url parameters
+			if ($("#report_date_from").val() != '' && $("#report_date_to").val() != '')
+			{
+				urlParameters['from'] = $("#report_date_from").val();
+				urlParameters['to'] = $("#report_date_to").val();
+				delete urlParameters['s'];
+				delete urlParameters['e'];
+			}
+			
+			// Hide the box
+			$("#tooltip-box").hide();
+			$("#tooltip-box a.filter-button").click();
+			
+			return false;
+		});
+		
+		
+		/**
+		 * When the date filter button is clicked
+		 */
+		$("#tooltip-box a.filter-button").click(function(){
+			// Change the text
+			$(".time-period").text($("#report_date_from").val()+" to "+$("#report_date_to").val());
+			
+			// Hide the box
+			$("#tooltip-box").hide();
+			
+			report_date_from = $("#report_date_from").val();
+			report_date_to = $("#report_date_to").val();
+			
+			if ($(this).attr("id") == "applyDateFilter")
+			{
+				// Clear existing filters
+				delete urlParameters['s'];
+				delete urlParameters['e'];
+				delete urlParameters['from'];
+				delete urlParameters['to'];
+				// Add from filter if set
+				if (report_date_from != '')
+				{
+					// Add the parameters
+					urlParameters["from"] = report_date_from;
+					urlParameters["to"] = report_date_to;
+				}
+				// Add to filter if set
+				if (report_date_to != '')
+				{
+					// Add the parameters
+					urlParameters["to"] = report_date_to;
+				}
+				
+				// Fetch the reports
+				fetchReports();
+			}
+			
+			return false;
+		});
+		
+		// Initialize accordion for Report Filters
+		$( "#accordion" ).accordion({autoHeight: false});
+		
+		// Report hovering events
+		addReportHoverEvents();
+		
+		// 	Events for toggling the report filters
+		addToggleReportsFilterEvents();
+		
+		// Attach paging events to the paginator
+		attachPagingEvents();
+		
+		// Attach the "Filter Reports" action
+		attachFilterReportsAction();
+		
+		// When all the filters are reset
+		$("#reset_all_filters").click(function(){
+			// Deselect all filters
+			$.each($(".filter-list li a"), function(i, item){
+				$(item).removeClass("selected");
+			});
+			
+			$("select[id^='custom_field_']").val("---NOT_SELECTED---");
+			$("input[id^='custom_field_']:checkbox").removeAttr("checked");
+			$("input[id^='custom_field_']:radio").removeAttr("checked");
+			$("input[id^='custom_field_']:text").val("");
+			
+			// Reset the url parameters
+			urlParameters = {};
+		
+			// Fetch all reports
+			fetchReports();
+		});
+		
+		$("#accordion").accordion({change: function(event, ui){
+			if ($(ui.newContent).hasClass("f-location-box"))
+			{
+				if (typeof radiusMap == 'undefined' || radiusMap == null)
+				{
+					// Create the map
+					radiusMap = createMap("divMap", latitude, longitude, defaultZoom);
+					
+					// Add the radius layer
+					addRadiusLayer(radiusMap, latitude, longitude);
+					
+					drawCircle(radiusMap, latitude, longitude);
+					
+					// Detect map clicks
+					radiusMap.events.register("click", radiusMap, function(e){
+						var lonlat = radiusMap.getLonLatFromViewPortPx(e.xy);
+						var lonlat2 = radiusMap.getLonLatFromViewPortPx(e.xy);
+					    m = new OpenLayers.Marker(lonlat);
+						markers.clearMarkers();
+						markers.addMarker(m);
+
+						currRadius = $("#alert_radius option:selected").val();
+						radius = currRadius * 1000
+
+						lonlat2.transform(proj_900913, proj_4326);
+
+						// Store the current latitude and longitude
+						currLat = lonlat2.lat;
+						currLon = lonlat2.lon;
+
+						drawCircle(radiusMap, currLat, currLon, radius);
+
+						// Store the radius and start locations
+						urlParameters["radius"] = currRadius;
+						urlParameters["start_loc"] = currLat + "," + currLon;
+					});
+
+					// Radius selector
+					$("select#alert_radius").change(function(e, ui) {
+						var newRadius = $("#alert_radius").val();
+
+						// Convert to Meters
+						radius = newRadius * 1000;	
+
+						// Redraw Circle
+						currLat = (currLat == null)? latitude : currLat;
+						currLon = (currLon == null)? longitude : currLon;
+
+						drawCircle(radiusMap, currLat, currLon, radius);
+
+						// Store the radius and start locations
+						urlParameters["radius"] = newRadius;
+						urlParameters["start_loc"] = currLat+ "," + currLon;
+					});
+				}
+			}
+		}});
+
+
+	});
+	
+	/**
+	 * Registers the report hover event
+	 */
+	function addReportHoverEvents()
+	{
+		 // Hover functionality for each report
+		$(".rb_report").hover(
+			function () {
+				$(this).addClass("hover");
+			}, 
+			function () {
+				$(this).removeClass("hover");
+			}
+		);
+		
+		// Category tooltip functionality
+		var $tt = $('.r_cat_tooltip');
+		$("a.r_category").hover(
+			function () {
+				// Place the category text inside the category tooltip
+				$tt.find('a').html($(this).find('.r_cat-desc').html());
+				
+				// Display the category tooltip
+				$tt.css({
+					'left': ($(this).offset().left - 6),
+					'top': ($(this).offset().top - 27)
+				}).show();
+			}, 
+			
+			function () {
+				$tt.hide();
+			}
+		);
+
+		// Show/hide categories and location for a report
+		$("a.btn-show").click(function(){
+			var $reportBox = $(this).attr("href");
+		
+			// Hide self
+			$(this).hide();
+			if ($(this).hasClass("btn-more"))
+			{
+				// Show categories and location
+				$($reportBox + " .r_categories, " + $reportBox + " .r_location").slideDown();
+			
+				// Show the "show less" link
+				$($reportBox + " a.btn-less").show();
+			}
+			else if ($(this).hasClass("btn-less"))
+			{
+				// Hide categories and location
+				$($reportBox + " .r_categories, " + $reportBox + " .r_location").slideUp();
+			
+				// Show the "show more" link
+				$($reportBox + " a.btn-more").attr("style","");
+			};
+		
+			return false;		    
+		});
+	}
+	
+	/**
+	 * Creates the map and sets the loaded status to 1
+	 */
+	function createIncidentMap()
+	{
+		// Creates the map
+		map = createMap('rb_map-view', latitude, longitude, defaultZoom);
+		
+		mapLoaded = 1;
+	}
+	
+	function addToggleReportsFilterEvents()
+	{
+		// Checks if a filter exists in the list of deselected items
+		filterExists = function(itemId) {
+			if (deSelectedFilters.length == 0)
+			{
+				return false;
+			}
+			else
+			{
+				for (var i=0; i < deSelectedFilters.length; i++)
+				{
+					if (deSelectedFilters[i] == itemId)
+					{
+						return true;
+					}
+				}
+				return false;
+			}
+		};
+		
+		// toggle highlighting on the filter lists
+		$(".filter-list li a").toggle(
+			function(){
+				$(this).addClass("selected");
+				
+				// Check if the element is in the list of de-selected items and remove it
+				if (deSelectedFilters.length > 0)
+				{
+					var temp = [];
+					for (var i = 0; i<deSelectedFilters.length; i++)
+					{
+						if (deSelectedFilters[i] != $(this).attr("id"))
+						{
+							temp.push(deSelectedFilters[i]);
+						}
+					}
+					
+					deSelectedFilters = temp;
+				}
+			},
+			function(){
+				if ($(this).hasClass("selected"))
+				{
+					elementId = $(this).attr("id");
+					// Add the id of the deselected filter
+					if ( ! filterExists(elementId))
+					{
+						deSelectedFilters.push(elementId);
+					}
+					
+					// Update the parameter value for the deselected filter
+					removeDeselectedReportFilter(elementId);
+					
+				}
+				
+				$(this).removeClass("selected");
+			}
+		);
+	}
+	
+	/**
+	 * Switch Views map, or list
+	 */
+	 function switchViews(view)
+	 {
+		 // Hide both divs
+		$("#rb_list-view, #rb_map-view").hide();
+		
+		// Show the appropriate div
+		$($(view).attr("href")).show();
+		
+		// Remove the class "selected" from all parent li's
+		$("#reports-box .report-list-toggle a").parent().removeClass("active");
+		
+		// Add class "selected" to both instances of the clicked link toggle
+		$("."+$(view).attr("class")).parent().addClass("active");
+		
+		// Check if the map view is active
+		if ($("#rb_map-view").css("display") == "block")
+		{
+			// Check if the map has already been created
+			if (mapLoaded == 0)
+			{
+				createIncidentMap();
+			}
+			
+			// Set the current page
+			urlParameters["page"] = $(".pager li a.active").html();
+			
+			// Load the map
+			setTimeout(function(){ showIncidentMap() }, 400);
+		}
+		return false;
+	 }
+	
+	
+	
+	
+	/**
+	 * List/map view toggle
+	 */
+	function addReportViewOptionsEvents()
+	{
+		$("#reports-box .report-list-toggle a").click(function(){
+			return switchViews($(this));						
+		});
+	}
+	
+	/**
+	 * Attaches paging events to the paginator
+	 */	
+	function attachPagingEvents()
+	{
+		// Add event handler that allows switching between list view and map view
+		addReportViewOptionsEvents();
+		
+		// Remove page links for the metadata pager
+		//$("ul.pager a").attr("href", "#");
+		
+		$("ul.pager a").click(function() {
+			// Add the clicked page to the url parameters
+			urlParameters["page"] = $(this).html();
+			
+			// Fetch the reports
+			fetchReports();
+			return false;
+			
+		});
+		
+		$("td.last li a").click(function(){
+			pageNumber = $(this).attr("href").substr("#page_".length);
+			if (Number(pageNumber) > 0)
+			{
+				urlParameters["page"] = Number(pageNumber);
+				fetchReports();
+			}
+			return false;
+		});
+		
+		return false;
+	}
+	
+	/**
+	 * Gets the reports using the specified parameters
+	 */
+	function fetchReports()
+	{
+		//check and see what view was last viewed: list, or map.
+		var lastDisplyedWasMap = $("#rb_map-view").css("display") != "none";
+		
+		// Reset the map loading tracker
+		mapLoaded = 0;
+		
+		var loadingURL = "<?php echo url::file_loc('img').'media/img/loading_g.gif'; ?>";
+		var statusHtml = "<div class=\"loading-reports\">" + 
+					"<div><img src=\""+loadingURL+"\" border=\"0\"></div>" + 
+					"<h3><?php echo Kohana::lang('ui_main.loading_reports'); ?>...</h3>" +
+					"</div>";
+	
+		$("#reports-box").html(statusHtml);
+		
+		// Check if there are any parameters
+		if ($.isEmptyObject(urlParameters))
+		{
+			urlParameters = {show: "all"}
+		}
+		
+		// Get the content for the new page
+		$.get('<?php echo url::site().'reports/fetch_reports'?>',
+			urlParameters,
+			function(data) {
+				if (data != null && data != "" && data.length > 0) {
+				
+					// Animation delay
+					setTimeout(function(){
+						$("#reports-box").html(data);
+				
+						attachPagingEvents();
+						addReportHoverEvents();
+						deSelectedFilters = [];
+						
+						//if the map was the last thing the user was looking at:
+						if(lastDisplyedWasMap)
+						{
+							switchViews($("#reports-box .report-list-toggle a.map"));
+						}
+						
+					}, 400);
+				}
+			}
+		);
+	}
+	
+	/** 
+	 * Removes the deselected report filters from the list
+	 * of filters for fetching the reports
+	 */
+	function removeDeselectedReportFilter(elementId)
+	{
+		// Removes a parameter item from urlParameters
+		removeParameterItem = function(key, val) {
+			if (! $.isEmptyObject(urlParameters))
+			{
+				// Get the object type
+				objectType = Object.prototype.toString.call(urlParameters[key]);
+				
+				if (objectType == "[object Array]")
+				{
+					currentItems  = urlParameters[key];
+					newItems = [];
+					for (var j=0; j < currentItems.length; j++)
+					{
+						if (currentItems[j] != val)
+						{
+							newItems.push(currentItems[j]);
+						}
+					}
+					
+					if (newItems.length > 0)
+					{
+						urlParameters[key] = newItems;
+					}
+					else
+					{
+						delete urlParameters[key];
+					}
+				}
+				else if (objectType == "[object String]")
+				{
+					delete urlParameters[key];
+				}
+			}
+		}
+		
+		if (deSelectedFilters.length > 0)
+		{
+			// Check for category filter
+			if (elementId.indexOf('filter_link_cat_') != -1){
+				catId = elementId.substring('filter_link_cat_'.length);
+				removeParameterItem("c", catId);
+			}
+			else if (elementId.indexOf('filter_link_mode_') != -1)
+			{
+				modeId = elementId.substring('filter_link_mode_'.length);
+				removeParameterItem("mode", modeId);
+			}
+			else if (elementId.indexOf('filter_link_media_') != -1)
+			{
+				mediaType = elementId.substring('filter_link_media_'.length);
+				removeParameterItem("m", mediaType);
+			}
+			else if (elementId.indexOf('filter_link_verification_') != -1)
+			{
+				verification = elementId.substring('filter_link_verification_'.length);
+				removeParameterItem("v", verification);
+				
+			}
+		}
+	}
+	
+	/**
+	 * Adds an event handler for the "Filter reports" button
+	 */
+	function attachFilterReportsAction()
+	{
+		$("#applyFilters").click(function(){
+			
+			// 
+			// Get all the selected categories
+			// 
+			var category_ids = [];
+			$.each($(".fl-categories li a.selected"), function(i, item){
+				itemId = item.id.substring("filter_link_cat_".length);
+				// Check if category 0, "All categories" has been selected
+				category_ids.push(itemId);
+			});
+			
+			if (category_ids.length > 0)
+			{
+				urlParameters["c"] = category_ids;
+			}
+			
+			// 
+			// Get the incident modes
+			// 
+			var incidentModes = [];
+			$.each($(".fl-incident-mode li a.selected"), function(i, item){
+				modeId = item.id.substring("filter_link_mode_".length);
+				incidentModes.push(modeId);
+			});
+			
+			if (incidentModes.length > 0)
+			{
+				urlParameters["mode"] = incidentModes;
+			}
+			
+			// 
+			// Get the media type
+			// 
+			var mediaTypes = [];
+			$.each($(".fl-media li a.selected"), function(i, item){
+				mediaId = item.id.substring("filter_link_media_".length);
+				mediaTypes.push(mediaId);
+			});
+			
+			if (mediaTypes.length > 0)
+			{
+				urlParameters["m"] = mediaTypes;
+			}
+			
+			// Get the verification status
+			var verificationStatus = [];
+			$.each($(".fl-verification li a.selected"), function(i, item){
+				statusVal = item.id.substring("filter_link_verification_".length);
+				verificationStatus.push(statusVal);
+			});
+			if (verificationStatus.length > 0)
+			{
+				urlParameters["v"] = verificationStatus;
+			}
+			
+			//
+			// Get the Custom Form Fields
+			// 
+			var customFields = new Array();
+			var checkBoxId = null;
+			var checkBoxArray = new Array();
+			$.each($("input[id^='custom_field_']"), function(i, item) {
+				var cffId = item.id.substring("custom_field_".length);
+				var value = $(item).val();
+				var type = $(item).attr("type");
+				if(type == "text")
+				{
+					if(value != "" && value != undefined && value != null)
+					{
+						customFields.push([cffId, value]);
+					}
+				}
+				else if(type == "radio")
+				{
+					if($(item).attr("checked"))
+					{
+						customFields.push([cffId, value]);
+					}
+				}
+				else if(type == "checkbox")
+				{
+					if($(item).attr("checked"))
+					{
+						checkBoxId = cffId;
+						checkBoxArray.push(value);
+					}
+				}
+				
+				if(type != "checkbox" && checkBoxId != null)
+				{
+					customFields.push([checkBoxId, checkBoxArray]);
+					checkBoxId = null;
+					checkBoxArray = new Array();
+				}
+				
+			});
+			//incase the last field was a checkbox
+			if(checkBoxId != null)
+			{
+				customFields.push([checkBoxId, checkBoxArray]);				
+			}
+			
+			//now selects
+			$.each($("select[id^='custom_field_']"), function(i, item) {
+				var cffId = item.id.substring("custom_field_".length);
+				var value = $(item).val();
+				if(value != "---NOT_SELECTED---")
+				{
+					customFields.push([cffId, value]);
+				}
+			});
+			if(customFields.length > 0)
+			{
+				urlParameters["cff"] = customFields;
+			}
+			else
+			{
+				delete urlParameters["cff"];
+			}
+			
+			<?php
+				// Action, allows plugins to add custom filters
+				Event::run('ushahidi_action.report_js_filterReportsAction');
+			?>
+			
+			// Fetch the reports
+			fetchReports();
+			
+		});
+	}
+	
+	
+	/**
+	 * Makes a url string for the map stuff
+	 */
+	function makeUrlParamStr(str, params, arrayLevel)	 
+	{
+		//make sure arrayLevel is initialized
+		var arrayLevelStr = "";
+		if(arrayLevel != undefined)
+		{
+			arrayLevelStr = arrayLevel;
+		}
+		
+		var separator = "";
+		for(i in params)
+		{
+			//do we need to insert a separator?
+			if(str.length > 0)
+			{
+				separator = "&";
+			}
+			
+			//get the param
+			var param = params[i];
+	
+			//is it an array or not
+			if($.isArray(param))
+			{
+				if(arrayLevelStr == "")
+				{
+					str = makeUrlParamStr(str, param, i);
+				}
+				else
+				{
+					str = makeUrlParamStr(str, param, arrayLevelStr + "%5B" + i + "%5D");
+				}
+			}
+			else
+			{
+				if(arrayLevelStr == "")
+				{
+					str +=  separator + i + "=" + param.toString();
+				}
+				else
+				{
+					str +=  separator + arrayLevelStr + "%5B" + i + "%5D=" + param.toString();
+				}
+			}
+		}
+		
+		return str;
+	}
+	
+	
+	/**
+	 * Handles display of the incidents current incidents on the map
+	 * This method is only called when the map view is selected
+	 */
+	function showIncidentMap()
+	{
+		// URL to be used for fetching the incidents
+		fetchURL = '<?php echo url::site().'json/index' ;?>';
+		
+		// Generate the url parameter string
+		parameterStr = makeUrlParamStr("", urlParameters)
+		
+		// Add the parameters to the fetch URL
+		fetchURL += '?' + parameterStr;
+		
+		// Fetch the incidents
+		
+		// Set the layer name
+		var layerName = '<?php echo Kohana::lang('ui_main.reports')?>';
+				
+		// Get all current layers with the same name and remove them from the map
+		currentLayers = map.getLayersByName(layerName);
+		for (var i = 0; i < currentLayers.length; i++)
+		{
+			map.removeLayer(currentLayers[i]);
+		}
+				
+		// Styling for the incidents
+		reportStyle = new OpenLayers.Style({
+			pointRadius: "8",
+			fillColor: "#30E900",
+			fillOpacity: "0.8",
+			strokeColor: "#197700",
+			strokeWidth: 3,
+			graphicZIndex: 1
+		});
+				
+		// Apply transform to each feature before adding it to the layer
+		preFeatureInsert = function(feature)
+		{
+			var point = new OpenLayers.Geometry.Point(feature.geometry.x, feature.geometry.y);
+			OpenLayers.Projection.transform(point, proj_4326, proj_900913);
+		};
+				
+		// Create vector layer
+		vLayer = new OpenLayers.Layer.Vector(layerName, {
+			projection: map.displayProjection,
+			extractAttributes: true,
+			styleMap: new OpenLayers.StyleMap({'default' : reportStyle}),
+			strategies: [new OpenLayers.Strategy.Fixed()],
+			protocol: new OpenLayers.Protocol.HTTP({
+				url: fetchURL,
+				format: new OpenLayers.Format.GeoJSON()
+			})
+		});
+				
+		// Add the vector layer to the map
+		map.addLayer(vLayer);
+		
+		// Add feature selection events
+		addFeatureSelectionEvents(map, vLayer);
+	}
+	
+	/**
+	 * Clears the filter for a particular section
+	 * @param {string} parameterKey: Key of the parameter remove from the list of url parameters
+	 * @param {string} filterClass: CSS class of the section containing the filters
+	 */
+	function removeParameterKey(parameterKey, filterClass)
+	{
+		if (typeof parameterKey == 'undefined' || typeof parameterKey != 'string')
+			return;
+		
+		if (typeof $("."+filterClass) == 'undefined')
+			return;
+		
+		if(parameterKey == "cff") //It's Cutom Form Fields baby
+		{
+			$.each($("input[id^='custom_field_']"), function(i, item){
+				if($(item).attr("type") == "checkbox" || $(item).attr("type") == "radio")
+				{
+					$(item).removeAttr("checked");
+				}
+				else
+				{
+					$(item).val("");
+				}
+			});			
+			$("select[id^='custom_field_']").val("---NOT_SELECTED---");
+		}
+		else //it's just some simple removing of a class
+		{
+			// Deselect
+			$.each($("." + filterClass +" li a.selected"), function(i, item){
+				$(item).removeClass("selected");
+			});			
+			
+			//if it's the location filter be sure to get rid of sw and ne
+			if(parameterKey == "start_loc" || parameterKey == "radius")
+			{
+				delete urlParameters["sw"];
+				delete urlParameters["ne"];
+			}
+		}
+		
+		// Remove the parameter key from urlParameters
+		delete urlParameters[parameterKey];
+	}
+	
diff --git a/themes/default/views/reports/stats.php b/themes/default/views/reports/stats.php
new file mode 100644
index 0000000..b109182
--- /dev/null
+++ b/themes/default/views/reports/stats.php
@@ -0,0 +1,14 @@
+<div id="report_stats">
+	<table>
+		<tr>
+			<th><?php echo Kohana::lang('ui_main.total_reports');?></th>
+			<th><?php echo Kohana::lang('ui_main.avg_reports_per_day');?></th>
+			<th>% <?php echo Kohana::lang('ui_main.verified');?></th>
+		</tr>
+		<tr>
+			<td><?php echo $total_reports; ?></td>
+			<td><?php echo $avg_reports_per_day; ?></td>
+			<td><?php echo $percent_verified; ?></td>
+		</tr>
+	</table>
+</div>
\ No newline at end of file
diff --git a/themes/default/views/reports/submit.php b/themes/default/views/reports/submit.php
new file mode 100755
index 0000000..241a2f4
--- /dev/null
+++ b/themes/default/views/reports/submit.php
@@ -0,0 +1,337 @@
+<div id="content">
+	<div class="content-bg">
+
+		<?php if ($site_submit_report_message != ''): ?>
+			<div class="green-box">
+				<h3><?php echo $site_submit_report_message; ?></h3>
+			</div>
+		<?php endif; ?>
+
+		<!-- start report form block -->
+		<?php print form::open(NULL, array('enctype' => 'multipart/form-data', 'id' => 'reportForm', 'name' => 'reportForm', 'class' => 'gen_forms')); ?>
+		<input type="hidden" name="latitude" id="latitude" value="<?php echo $form['latitude']; ?>">
+		<input type="hidden" name="longitude" id="longitude" value="<?php echo $form['longitude']; ?>">
+		<input type="hidden" name="country_name" id="country_name" value="<?php echo $form['country_name']; ?>" />
+		<input type="hidden" name="incident_zoom" id="incident_zoom" value="<?php echo $form['incident_zoom']; ?>" />
+		<div class="big-block">
+			<h1><?php echo Kohana::lang('ui_main.reports_submit_new'); ?></h1>
+			<?php if ($form_error): ?>
+			<!-- red-box -->
+			<div class="red-box">
+				<h3>Error!</h3>
+				<ul>
+					<?php
+						foreach ($errors as $error_item => $error_description)
+						{
+							print (!$error_description) ? '' : "<li>" . $error_description . "</li>";
+						}
+					?>
+				</ul>
+			</div>
+			<?php endif; ?>
+			<div class="row">
+				<input type="hidden" name="form_id" id="form_id" value="<?php echo $id?>">
+			</div>
+			<div class="report_left">
+				<div class="report_row">
+					<?php if(count($forms) > 1): ?>
+					<div class="row">
+						<h4><span><?php echo Kohana::lang('ui_main.select_form_type');?></span>
+						<span class="sel-holder">
+							<?php print form::dropdown('form_id', $forms, $form['form_id'],
+						' onchange="formSwitch(this.options[this.selectedIndex].value, \''.$id.'\')"') ?>
+						</span>
+						<div id="form_loader"></div>
+						</h4>
+					</div>
+					<?php endif; ?>
+					<h4><?php echo Kohana::lang('ui_main.reports_title'); ?> <span class="required">*</span> </h4>
+					<?php print form::input('incident_title', $form['incident_title'], ' class="text long"'); ?>
+				</div>
+				<div class="report_row">
+					<h4><?php echo Kohana::lang('ui_main.reports_description'); ?> <span class="required">*</span> </h4>
+					<span class="allowed-html"><?php echo html::allowed_html(); ?></span>
+					<?php print form::textarea('incident_description', $form['incident_description'], ' rows="10" class="textarea long" ') ?>
+				</div>
+				<div class="report_row" id="datetime_default">
+					<h4>
+						<a href="#" id="date_toggle" class="show-more"><?php echo Kohana::lang('ui_main.modify_date'); ?></a>
+						<?php echo Kohana::lang('ui_main.date_time'); ?>: 
+						<?php echo Kohana::lang('ui_main.today_at')." "."<span id='current_time'>".$form['incident_hour']
+							.":".$form['incident_minute']." ".$form['incident_ampm']."</span>"; ?>
+						<?php if($site_timezone): ?>
+							<small>(<?php echo $site_timezone; ?>)</small>
+						<?php endif; ?>
+					</h4>
+				</div>
+				<div class="report_row hide" id="datetime_edit">
+					<div class="date-box">
+						<h4><?php echo Kohana::lang('ui_main.reports_date'); ?></h4>
+						<?php print form::input('incident_date', $form['incident_date'], ' class="text short"'); ?>
+						<script type="text/javascript">
+							$().ready(function() {
+								$("#incident_date").datepicker({ 
+									showOn: "both", 
+									buttonImage: "<?php echo url::file_loc('img'); ?>media/img/icon-calendar.gif", 
+									buttonImageOnly: true 
+								});
+							});
+						</script>
+					</div>
+					<div class="time">
+						<h4><?php echo Kohana::lang('ui_main.reports_time'); ?></h4>
+						<?php
+							for ($i=1; $i <= 12 ; $i++)
+							{
+								// Add Leading Zero
+								$hour_array[sprintf("%02d", $i)] = sprintf("%02d", $i);
+							}
+							for ($j=0; $j <= 59 ; $j++)
+							{
+								// Add Leading Zero
+								$minute_array[sprintf("%02d", $j)] = sprintf("%02d", $j);
+							}
+							$ampm_array = array('pm'=>'pm','am'=>'am');
+							print form::dropdown('incident_hour',$hour_array,$form['incident_hour']);
+							print '<span class="dots">:</span>';
+							print form::dropdown('incident_minute',$minute_array,$form['incident_minute']);
+							print '<span class="dots">:</span>';
+							print form::dropdown('incident_ampm',$ampm_array,$form['incident_ampm']);
+						?>
+						<?php if ($site_timezone != NULL): ?>
+							<small>(<?php echo $site_timezone; ?>)</small>
+						<?php endif; ?>
+					</div>
+					<div style="clear:both; display:block;" id="incident_date_time"></div>
+				</div>
+				<div class="report_row">
+					<!-- Adding event for endtime plugin to hook into -->
+				<?php Event::run('ushahidi_action.report_form_frontend_after_time'); ?>
+				</div>
+				<div class="report_row">
+					<h4><?php echo Kohana::lang('ui_main.reports_categories'); ?> <span class="required">*</span></h4>
+					<div class="report_category" id="categories">
+					<?php
+						$selected_categories = (!empty($form['incident_category']) AND is_array($form['incident_category']))
+							? $selected_categories = $form['incident_category']
+							: array();
+							
+						
+						echo category::form_tree('incident_category', $selected_categories, 2);
+						?>
+					</div>
+				</div>
+
+
+				<?php
+				// Action::report_form - Runs right after the report categories
+				Event::run('ushahidi_action.report_form');
+				?>
+
+				<?php echo $custom_forms ?>
+
+				<div class="report_optional">
+					<h3><?php echo Kohana::lang('ui_main.reports_optional'); ?></h3>
+					<div class="report_row">
+						<h4><?php echo Kohana::lang('ui_main.reports_first'); ?></h4>
+						<?php print form::input('person_first', $form['person_first'], ' class="text long"'); ?>
+					</div>
+					<div class="report_row">
+						<h4><?php echo Kohana::lang('ui_main.reports_last'); ?></h4>
+						<?php print form::input('person_last', $form['person_last'], ' class="text long"'); ?>
+					</div>
+					<div class="report_row">
+						<h4><?php echo Kohana::lang('ui_main.reports_email'); ?></h4>
+						<?php print form::input('person_email', $form['person_email'], ' class="text long"'); ?>
+					</div>
+					<?php
+					// Action::report_form_optional - Runs in the optional information of the report form
+					Event::run('ushahidi_action.report_form_optional');
+					?>
+				</div>
+			</div>
+			<div class="report_right">
+				<?php if (count($cities) > 1): ?>
+				<div class="report_row">
+					<h4><?php echo Kohana::lang('ui_main.reports_find_location'); ?></h4>
+					<?php print form::dropdown('select_city',$cities,'', ' class="select" '); ?>
+				</div>
+				<?php endif; ?>
+				<div class="report_row">
+					<div id="divMap" class="report_map">
+						<div id="geometryLabelerHolder" class="olControlNoSelect">
+							<div id="geometryLabeler">
+								<div id="geometryLabelComment">
+									<span id="geometryLabel">
+										<label><?php echo Kohana::lang('ui_main.geometry_label');?>:</label> 
+										<?php print form::input('geometry_label', '', ' class="lbl_text"'); ?>
+									</span>
+									<span id="geometryComment">
+										<label><?php echo Kohana::lang('ui_main.geometry_comments');?>:</label> 
+										<?php print form::input('geometry_comment', '', ' class="lbl_text2"'); ?>
+									</span>
+								</div>
+								<div>
+									<span id="geometryColor">
+										<label><?php echo Kohana::lang('ui_main.geometry_color');?>:</label> 
+										<?php print form::input('geometry_color', '', ' class="lbl_text"'); ?>
+									</span>
+									<span id="geometryStrokewidth">
+										<label><?php echo Kohana::lang('ui_main.geometry_strokewidth');?>:</label> 
+										<?php print form::dropdown('geometry_strokewidth', $stroke_width_array, ''); ?>
+									</span>
+									<span id="geometryLat">
+										<label><?php echo Kohana::lang('ui_main.latitude');?>:</label> 
+										<?php print form::input('geometry_lat', '', ' class="lbl_text"'); ?>
+									</span>
+									<span id="geometryLon">
+										<label><?php echo Kohana::lang('ui_main.longitude');?>:</label> 
+										<?php print form::input('geometry_lon', '', ' class="lbl_text"'); ?>
+									</span>
+								</div>
+							</div>
+							<div id="geometryLabelerClose"></div>
+						</div>
+					</div>
+					<div class="report-find-location">
+						<div id="panel" class="olControlEditingToolbar"></div>
+						<div class="btns">
+							<ul>
+								<li><a href="#" class="btn_del_last"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_last'));?></a></li>
+								<li><a href="#" class="btn_del_sel"><?php echo utf8::strtoupper(Kohana::lang('ui_main.delete_selected'));?></a></li>
+								<li><a href="#" class="btn_clear"><?php echo utf8::strtoupper(Kohana::lang('ui_main.clear_map'));?></a></li>
+							</ul>
+						</div>
+						<div style="clear:both;"></div>
+						<?php print form::input('location_find', '', ' title="'.Kohana::lang('ui_main.location_example').'" class="findtext"'); ?>
+						<input type="button" name="button" id="button" value="<?php echo Kohana::lang('ui_main.find_location'); ?>" class="btn_find" />
+						<div id="find_loading" class="report-find-loading"></div>
+						<div style="clear:both;" id="find_text"><?php echo Kohana::lang('ui_main.pinpoint_location'); ?>.</div>
+					</div>
+				</div>
+				<?php Event::run('ushahidi_action.report_form_location', $id); ?>
+				<div class="report_row">
+					<h4>
+						<?php echo Kohana::lang('ui_main.reports_location_name'); ?> 
+						<span class="required">*</span><br />
+						<span class="example"><?php echo Kohana::lang('ui_main.detailed_location_example'); ?></span>
+					</h4>
+					<?php print form::input('location_name', $form['location_name'], ' class="text long"'); ?>
+				</div>
+
+				<!-- News Fields -->
+				<div id="divNews" class="report_row">
+					<h4><?php echo Kohana::lang('ui_main.reports_news'); ?></h4>
+					
+					<?php 
+						// Initialize the counter
+						$i = (empty($form['incident_news'])) ? 1 : 0;
+					?>
+
+					<?php if (empty($form['incident_news'])): ?>
+						<div class="report_row">
+							<?php print form::input('incident_news[]', '', ' class="text long2"'); ?>
+							<a href="#" class="add" onClick="addFormField('divNews','incident_news','news_id','text'); return false;">add</a>
+						</div>
+					<?php else: ?>
+						<?php foreach ($form['incident_news'] as $value): ?>
+						<div class="report_row" id="<?php echo $i; ?>">
+							<?php echo form::input('incident_news[]', $value, ' class="text long2"'); ?>
+							<a href="#" class="add" onClick="addFormField('divNews','incident_news','news_id','text'); return false;">add</a>
+
+							<?php if ($i != 0): ?>
+								<?php $css_id = "#incident_news_".$i; ?>
+								<a href="#" class="rem"	onClick="removeFormField('<?php echo $css_id; ?>'); return false;">remove</a>
+							<?php endif; ?>
+
+						</div>
+						<?php $i++; ?>
+
+						<?php endforeach; ?>
+					<?php endif; ?>
+
+					<?php print form::input(array('name'=>'news_id', 'type'=>'hidden', 'id'=>'news_id'), $i); ?>
+				</div>
+
+
+				<!-- Video Fields -->
+				<div id="divVideo" class="report_row">
+					<h4><?php print Kohana::lang('ui_main.external_video_link'); ?></h4>
+					<?php 
+						// Initialize the counter
+						$i = (empty($form['incident_video'])) ? 1 : 0;
+					?>
+
+					<?php if (empty($form['incident_video'])): ?>
+						<div class="report_row">
+							<?php print form::input('incident_video[]', '', ' class="text long2"'); ?>
+							<a href="#" class="add" onClick="addFormField('divVideo','incident_video','video_id','text'); return false;">add</a>
+						</div>
+					<?php else: ?>
+						<?php foreach ($form['incident_video'] as $value): ?>
+							<div class="report_row" id="<?php  echo $i; ?>">
+
+							<?php print form::input('incident_video[]', $value, ' class="text long2"'); ?>
+							<a href="#" class="add" onClick="addFormField('divVideo','incident_video','video_id','text'); return false;">add</a>
+
+							<?php if ($i != 0): ?>
+								<?php $css_id = "#incident_video_".$i; ?>
+								<a href="#" class="rem"	onClick="removeFormField('<?php echo $css_id; ?>'); return false;">remove</a>
+							<?php endif; ?>
+
+							</div>
+							<?php $i++; ?>
+						
+						<?php endforeach; ?>
+					<?php endif; ?>
+
+					<?php print form::input(array('name'=>'video_id','type'=>'hidden','id'=>'video_id'), $i); ?>
+				</div>
+				
+				<?php Event::run('ushahidi_action.report_form_after_video_link'); ?>
+
+				<!-- Photo Fields -->
+				<div id="divPhoto" class="report_row">
+					<h4><?php echo Kohana::lang('ui_main.reports_photos'); ?></h4>
+					<span class="allowed-html"><?php echo Kohana::lang('ui_main.maximum_filesize'),": ", Kohana::config('settings.max_upload_size'), "Mb"; ?></span>
+					<?php 
+						// Initialize the counter
+						$i = (empty($form['incident_photo']['name'][0])) ? 1 : 0;
+					?>
+
+					<?php if (empty($form['incident_photo']['name'][0])): ?>
+					<div class="report_row">
+						<?php print form::upload('incident_photo[]', '', ' class="file long2"'); ?>
+						<a href="#" class="add" onClick="addFormField('divPhoto', 'incident_photo','photo_id','file'); return false;">add</a>
+					</div>
+					<?php else: ?>
+						<?php foreach ($form['incident_photo']['name'] as $value): ?>
+
+							<div class="report_row" id="<?php echo $i; ?>">
+								<?php print form::upload('incident_photo[]', $value, ' class="file long2"'); ?>
+								<a href="#" class="add" onClick="addFormField('divPhoto','incident_photo','photo_id','file'); return false;">add</a>
+
+								<?php if ($i != 0): ?>
+									<?php $css_id = "#incident_photo_".$i; ?>
+									<a href="#" class="rem"	onClick="removeFormField('<?php echo $css_id; ?>'); return false;">remove</a>
+								<?php endif; ?>
+
+							</div>
+
+							<?php $i++; ?>
+
+						<?php endforeach; ?>
+					<?php endif; ?>
+
+					<?php print form::input(array('name'=>'photo_id','type'=>'hidden','id'=>'photo_id'), $i); ?>
+				</div>
+									
+				<div class="report_row">
+					<input name="submit" type="submit" value="<?php echo Kohana::lang('ui_main.reports_btn_submit'); ?>" class="btn_submit" /> 
+				</div>
+			</div>
+		</div>
+		<?php print form::close(); ?>
+		<!-- end report form block -->
+	</div>
+</div>
diff --git a/themes/default/views/reports/submit_custom_forms.php b/themes/default/views/reports/submit_custom_forms.php
new file mode 100644
index 0000000..6e3a3a5
--- /dev/null
+++ b/themes/default/views/reports/submit_custom_forms.php
@@ -0,0 +1,336 @@
+<div id="custom_forms">
+
+<?php
+	// If the user has insufficient permissions to edit report fields, we flag this for a warning message
+	$show_permission_message = FALSE;
+
+	foreach ($disp_custom_fields as $field_id => $field_property)
+	{
+		// Is the field required
+		$isrequired = ($field_property['field_required'])
+			? "<span class='required'> *</span>"
+			: "";
+
+		// Private field
+		$isprivate = ($field_property['field_ispublic_visible'])
+			? '<span class="private">(' . Kohana::lang('ui_main.private') . ')</span>'
+			: '';
+
+		// Workaround for situations where admin can view, but doesn't have sufficient perms to edit.
+		if (isset($custom_field_mismatch))
+		{
+			if(isset($custom_field_mismatch[$field_id]))
+			{
+				if($show_permission_message == FALSE)
+				{
+					echo '<small>'.Kohana::lang('ui_admin.custom_forms_insufficient_permissions').'</small><br/>';
+					$show_permission_message = TRUE;
+				}
+
+				echo '<strong>'.$field_property['field_name'].'</strong><br/>';
+				if (isset($form['custom_field'][$field_id]))
+				{
+					echo $form['custom_field'][$field_id];
+				}
+				else
+				{
+					echo Kohana::lang('ui_main.no_data');;
+				}
+				echo '<br/><br/>';
+				//echo "</div>";
+				continue;
+			}
+		}
+
+		// Give all the elements an id so they can be accessed easily via javascript
+		$id_name = 'id="custom_field_'.$field_id.'"';
+
+		// Get the field value
+		$field_value = ( ! empty($form['custom_field'][$field_id]))
+			? $form['custom_field'][$field_id]
+			: $field_property['field_default'];
+
+		if ($field_property['field_type'] == 1)
+		{
+			// Text Field
+			echo "<div class=\"report_row\" id=\"custom_field_row_" . $field_id ."\">";
+
+			$field_options = customforms::get_custom_field_options($field_id);
+
+			if (isset($field_options['field_hidden']) AND !isset($editor))
+			{
+				if($field_options['field_hidden'] == 1)
+				{
+					echo form::hidden($field_property['field_name'], $field_value);
+				}
+				else
+				{
+					echo "<h4>" . $field_property['field_name'] . $isrequired . " " . $isprivate . "</h4>";
+					echo form::input('custom_field['.$field_id.']', $field_value, $id_name .' class="text custom_text"');
+				}
+			}
+			else
+			{
+				echo "<h4>" . $field_property['field_name'] . $isrequired . " " . $isprivate . "</h4>";
+				echo form::input('custom_field['.$field_id.']', $field_value, $id_name .' class="text custom_text"');
+			}
+			echo "</div>";
+		}
+		elseif ($field_property['field_type'] == 2)
+		{
+			// TextArea Field
+			$field_options = customforms::get_custom_field_options($field_id);
+			if (isset($field_options['field_datatype']))
+			{
+				$extra_fields = $id_name . ' class="textarea custom_text" rows="3"';
+
+				if ($field_options['field_datatype'] == 'text')
+				{
+					echo "<div class=\"report_row\" id=\"custom_field_row_" . $field_id ."\">";
+					echo "<h4>" . $field_property['field_name'] . $isrequired . " " . $isprivate . "</h4>";
+					echo form::textarea('custom_field['.$field_id.']', $field_value, $extra_fields);
+					echo "</div>";
+				}
+
+				if ($field_options['field_datatype'] == 'markup')
+				{
+					echo "<div class=\"report_row\" id=\"custom_field_row_" . $field_id ."\">";
+					echo "<h4>" . $field_property['field_name'] . $isrequired . " " . $isprivate . "</h4>";
+					echo form::textarea('custom_field['.$field_id.']', $field_value, $extra_fields, false);
+					echo "</div>";
+				}
+
+				if ($field_options['field_datatype'] == 'javascript')
+				{
+					if(isset($editor))
+					{
+						echo "<div class=\"report_row\" id=\"custom_field_row_" . $field_id ."\">";
+						echo "<h4>" . $field_property['field_name'] . $isrequired . " " . $isprivate . "</h4>";
+						echo form::textarea('custom_field['.$field_id.']', $field_value, $extra_fields, false);
+						echo "</div>";
+					}
+					else
+					{
+						echo '<script type="text/javascript">' . $field_property['field_default'] . '</script>';
+					}
+				}
+			}
+			else
+			{
+				echo "<div class=\"report_row\" id=\"custom_field_row_" . $field_id ."\">";
+				echo "<h4>" . $field_property['field_name'] . $isrequired . " " . $isprivate . "</h4>";
+				echo form::textarea('custom_field['.$field_id.']', $field_value, $id_name .' class="textarea custom_text" rows="3"');
+				echo "</div>";
+			}
+		}
+		elseif ($field_property['field_type'] == 3)
+		{ // Date Field
+			echo "<div class=\"report_row\" id=\"custom_field_row_" . $field_id ."\">";
+			echo "<h4>" . $field_property['field_name'] . $isrequired . " " . $isprivate . "</h4>";
+			echo form::input('custom_field['.$field_id.']', $field_value, ' id="custom_field_'.$field_id.'" class="text"');
+			echo "<script type=\"text/javascript\">
+				$(document).ready(function() {
+				$(\"#custom_field_".$field_id."\").datepicker({
+				showOn: \"both\",
+				buttonImage: \"".url::file_loc('img')."media/img/icon-calendar.gif\",
+				buttonImageOnly: true
+				});
+				});
+			</script>";
+			echo "</div>";
+		}
+		elseif ($field_property['field_type'] >=5 AND $field_property['field_type'] <=7)
+		{
+			// Multiple-selector Fields
+			echo "<div class=\"report_row\" id=\"custom_field_row_" . $field_id ."\">";
+			echo "<h4>" . $field_property['field_name'] . $isrequired . " " . $isprivate . "</h4>";
+			$defaults = explode('::',$field_property['field_default']);
+
+			$default = (isset($defaults[1])) ? $defaults[1] : 0;
+
+			if (isset($form['custom_field'][$field_id]))
+			{
+				if($form['custom_field'][$field_id] != '')
+				{
+					$default = $form['custom_field'][$field_id];
+				}
+			}
+
+			$options = explode(',',$defaults[0]);
+			$html ='';
+			switch ($field_property['field_type'])
+			{
+				case 5:
+					foreach($options as $option)
+					{
+						$option = trim($option);
+						$set_default = ($option == trim($default));
+
+						$html .= "<span class=\"custom-field-option\">";
+						$html .= form::label('custom_field['.$field_id.']'," ".$option." ");
+						$html .= form::radio('custom_field['.$field_id.']',$option, $set_default, $id_name);
+						$html .= "</span>";
+					}
+					break;
+				case 6:
+					$multi_defaults = !empty($field_property['field_response'])? explode(',', $field_property['field_response']) : NULL;
+
+					$cnt = 0;
+					$html .= "<table border=\"0\">";
+					foreach($options as $option)
+					{
+						if ($cnt % 2 == 0)
+						{
+							$html .= "<tr>";
+						}
+
+						$html .= "<td>";
+						$set_default = FALSE;
+
+						if (!empty($multi_defaults))
+						{
+							foreach($multi_defaults as $key => $def)
+							{
+								$set_default = (trim($option) == trim($def));
+								if ($set_default)
+									break;
+							}
+						}
+						$option = trim($option);
+						$html .= "<span class=\"custom-field-option\">";
+						$html .= form::checkbox("custom_field[".$field_id.'-'.$cnt.']', $option, $set_default, $id_name);
+						$html .= form::label("custom_field[".$field_id.']'," ".$option);
+						$html .= "</span>";
+
+						$html .= "</td>";
+						if ($cnt % 2 == 1 OR $cnt == count($options)-1)
+						{
+							$html .= "</tr>";
+						}
+
+						$cnt++;
+					}
+					// XXX Hack to deal with required checkboxes that are submitted with nothing checked
+					$html .= "</table>";
+					$html .= form::hidden("custom_field[".$field_id."-BLANKHACK]",'',$id_name);
+					break;
+				case 7:
+					$ddoptions = array();
+					// Semi-hack to deal with dropdown boxes receiving a range like 0-100
+					if (preg_match("/[0-9]+-[0-9]+/",$defaults[0]) AND count($options == 1))
+					{
+						$dashsplit = explode('-',$defaults[0]);
+						$start = $dashsplit[0];
+						$end = $dashsplit[1];
+						for($i = $start; $i <= $end; $i++)
+						{
+							$ddoptions[$i] = $i;
+						}
+					}
+					else
+					{
+						foreach($options as $op)
+						{
+							$op = trim($op);
+							$ddoptions[$op] = $op;
+						}
+					}
+
+					$html .= form::dropdown("custom_field[".$field_id.']',$ddoptions,$default,$id_name);
+					break;
+
+			}
+
+			echo $html;
+			echo "</div>";
+		}
+		elseif ($field_property['field_type'] == 8 )
+		{
+			//custom div
+			if ($field_property['field_default'] != "")
+			{
+				echo "<div class=\"" . $field_property['field_default'] . "\" $id_name>";
+			}
+			else
+			{
+				echo "<div class=\"custom_div\" $id_name >";
+			}
+
+			$field_options = customforms::get_custom_field_options($field_id);
+
+			if (isset($field_options['field_toggle']) && !isset($editor))
+			{
+				if ($field_options['field_toggle'] >= 1)
+				{
+					echo "<script type=\"text/javascript\">
+						$(function(){
+						$('#custom_field_" .$field_id . "_link').click(function() {
+  							$('#custom_field_" .$field_id . "_inner').toggle('slow', function() {
+    						// Animation complete.
+  							});
+						});
+					});
+					</script>";
+					echo "<a href=\"javascript:void(0);\" id=\"custom_field_" . $field_id ."_link\">";
+					echo "<h2>" . $field_property['field_name'] . "</h2>";
+					echo "</a>";
+
+					$inner_visibility = ($field_options['field_toggle'] == 2) ? "none": "visible";
+
+					echo "<div id=\"custom_field_" . $field_id . "_inner\" style=\"display:$inner_visibility;\">";
+				}
+				else
+				{
+					echo "<h2>" . $field_property['field_name'] . "</h2>";
+					echo "<div id=\"custom_field_" . $field_id . "_inner\">";
+				}
+			}
+			else
+			{
+				echo "<h2>" . $field_property['field_name'] . "</h2>";
+				echo "<div id=\"custom_field_" . $field_id . "_inner\">";
+			}
+		}
+		elseif ($field_property['field_type'] == 9)
+		{
+			// End of custom div
+			echo "</div></div>";
+			if (isset($editor))
+			{
+				echo "<h4 style=\"padding-top:0px;\">-------" . Kohana::lang('ui_admin.divider_end_field') . "--------</h4>";
+			}
+		}
+
+
+		if (isset($editor))
+		{
+			$form_fields = '';
+			$visibility_selection = array('0' => Kohana::lang('ui_admin.anyone_role'));
+			$roles = ORM::factory('role')->find_all();
+			foreach ($roles as $role)
+			{
+				$visibility_selection[$role->id] = ucfirst($role->name);
+			}
+
+			// Check if the field is required
+			$isrequired = ($field_property['field_required'])
+				? Kohana::lang('ui_admin.yes')
+				: Kohana::lang('ui_admin.no');
+
+			$form_fields .= "	<div class=\"forms_fields_edit\" style=\"clear:both\">
+			<a href=\"javascript:fieldAction('e','EDIT',".$field_id.",".$form['id'].",".$field_property['field_type'].");\">EDIT</a> | 
+			<a href=\"javascript:fieldAction('d','DELETE',".$field_id.",".$form['id'].",".$field_property['field_type'].");\">DELETE</a> | 
+			<a href=\"javascript:fieldAction('mu','MOVE',".$field_id.",".$form['id'].",".$field_property['field_type'].");\">MOVE UP</a> | 
+			<a href=\"javascript:fieldAction('md','MOVE',".$field_id.",".$form['id'].",".$field_property['field_type'].");\">MOVE DOWN</a>
+			</div>";
+			echo $form_fields;
+		}
+
+		if ($field_property['field_type'] != 8 AND $field_property['field_type'] != 9)
+		{
+			//if we're doing custom divs we don't want these div's to get in the way.
+			//echo "</div>";
+		}
+	}
+?>
+</div>
\ No newline at end of file
diff --git a/themes/default/views/reports/submit_edit_js.php b/themes/default/views/reports/submit_edit_js.php
new file mode 100644
index 0000000..38cd212
--- /dev/null
+++ b/themes/default/views/reports/submit_edit_js.php
@@ -0,0 +1,951 @@
+<?php
+/**
+ * Handles javascript stuff related to report creation and editing
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ *
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @subpackage Reports
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+		var map;
+		var thisLayer;
+		var proj_4326 = new OpenLayers.Projection('EPSG:4326');
+		var proj_900913 = new OpenLayers.Projection('EPSG:900913');
+		var vlayer;
+		var highlightCtrl;
+		var selectCtrl;
+		var selectedFeatures = [];
+		
+		// jQuery Textbox Hints Plugin
+		// Will move to separate file later or attach to forms plugin
+		jQuery.fn.hint = function (blurClass) {
+		  if (!blurClass) { 
+		    blurClass = 'texthint';
+		  }
+
+		  return this.each(function () {
+		    // Get jQuery version of 'this'
+		    var $input = jQuery(this),
+
+		    // Capture the rest of the variable to allow for reuse
+		      title = $input.attr('title'),
+		      $form = jQuery(this.form),
+		      $win = jQuery(window);
+
+		    function remove() {
+		      if ($input.val() === title && $input.hasClass(blurClass)) {
+		        $input.val('').removeClass(blurClass);
+		      }
+		    }
+
+		    // Only apply logic if the element has the attribute
+		    if (title) { 
+			
+		      // On blur, set value to title attr if text is blank
+		      $input.blur(function () {
+		        if (this.value === '') {
+		          $input.val(title).addClass(blurClass);
+		        }
+		      }).focus(remove).blur(); // now change all inputs to title
+
+		      // Clear the pre-defined text when form is submitted
+		      $form.submit(remove);
+		      $win.unload(remove); // handles Firefox's autocomplete
+			  $(".btn_find").click(remove);
+		    }
+		  });
+		};
+
+		jQuery(window).load(function() {
+			// Map options
+			var options = {
+				units: "dd",
+				numZoomLevels: 18, 
+				controls:[],
+				theme: false,
+				projection: proj_900913,
+				'displayProjection': proj_4326,
+				eventListeners: {
+					"zoomend": incidentZoom
+				},
+				maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
+				maxResolution: 156543.0339
+			};
+			
+			// Now initialise the map
+			map = new OpenLayers.Map('divMap', options);
+			
+			<?php echo map::layers_js(FALSE); ?>
+			map.addLayers(<?php echo map::layers_array(FALSE); ?>);
+			map.addControl(new OpenLayers.Control.Navigation());
+			map.addControl(new OpenLayers.Control.Zoom());
+			map.addControl(new OpenLayers.Control.MousePosition({
+				formatOutput: Ushahidi.convertLongLat
+			}));
+			map.addControl(new OpenLayers.Control.ScaleLine());
+			map.addControl(new OpenLayers.Control.Scale('mapScale'));
+			map.addControl(new OpenLayers.Control.LayerSwitcher());
+			
+			// Vector/Drawing Layer Styles
+			style1 = new OpenLayers.Style({
+				pointRadius: "8",
+				fillColor: "#ffcc66",
+				fillOpacity: "0.7",
+				strokeColor: "#CC0000",
+				strokeWidth: 2.5,
+				graphicZIndex: 1,
+				externalGraphic: "<?php echo url::file_loc('img').'media/img/openlayers/marker.png' ;?>",
+				graphicOpacity: 1,
+				graphicWidth: 21,
+				graphicHeight: 25,
+				graphicXOffset: -14,
+				graphicYOffset: -27
+			});
+			style2 = new OpenLayers.Style({
+				pointRadius: "8",
+				fillColor: "#30E900",
+				fillOpacity: "0.7",
+				strokeColor: "#197700",
+				strokeWidth: 2.5,
+				graphicZIndex: 1,
+				externalGraphic: "<?php echo url::file_loc('img').'media/img/openlayers/marker-green.png' ;?>",
+				graphicOpacity: 1,
+				graphicWidth: 21,
+				graphicHeight: 25,
+				graphicXOffset: -14,
+				graphicYOffset: -27
+			});
+			style3 = new OpenLayers.Style({
+				pointRadius: "8",
+				fillColor: "#30E900",
+				fillOpacity: "0.7",
+				strokeColor: "#197700",
+				strokeWidth: 2.5,
+				graphicZIndex: 1
+			});
+			
+			var vlayerStyles = new OpenLayers.StyleMap({
+				"default": style1,
+				"select": style2,
+				"temporary": style3
+			});
+			
+			// Create Vector/Drawing layer
+			vlayer = new OpenLayers.Layer.Vector( "Editable", {
+				styleMap: vlayerStyles,
+				rendererOptions: {zIndexing: true}
+			});
+			map.addLayer(vlayer);
+			
+			
+			// Drag Control
+			var drag = new OpenLayers.Control.DragFeature(vlayer, {
+				onStart: startDrag,
+				onDrag: doDrag,
+				onComplete: endDrag
+			});
+			map.addControl(drag);
+			
+			// Vector Layer Events
+			vlayer.events.on({
+				beforefeaturesadded: function(event) {
+					//for(i=0; i < vlayer.features.length; i++) {
+					//	if (vlayer.features[i].geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+					//		vlayer.removeFeatures(vlayer.features);
+					//	}
+					//}
+					
+					// Disable this to add multiple points
+					// vlayer.removeFeatures(vlayer.features);
+				},
+				featuresadded: function(event) {
+					refreshFeatures(event);
+				},
+				featuremodified: function(event) {
+					refreshFeatures(event);
+				},
+				featuresremoved: function(event) {
+					refreshFeatures(event);
+				}
+			});
+			
+			// Vector Layer Highlight Features
+			highlightCtrl = new OpenLayers.Control.SelectFeature(vlayer, {
+			    hover: true,
+			    highlightOnly: true,
+			    renderIntent: "temporary"
+			});
+			selectCtrl = new OpenLayers.Control.SelectFeature(vlayer, {
+				clickout: true, toggle: false,
+				multiple: false, hover: false,
+				renderIntent: "select",
+				onSelect: addSelected,
+				onUnselect: clearSelected
+			});
+			map.addControl(highlightCtrl);
+			map.addControl(selectCtrl);
+			
+			// Insert Saved Geometries
+			wkt = new OpenLayers.Format.WKT();
+			<?php
+			if ( ! count($geometries))
+			{
+				?>
+				// Default Point
+				point = new OpenLayers.Geometry.Point(<?php echo $longitude; ?>, <?php echo $latitude; ?>);
+				OpenLayers.Projection.transform(point, proj_4326, map.getProjectionObject());
+				var origFeature = new OpenLayers.Feature.Vector(point);
+				vlayer.addFeatures(origFeature);
+				<?php
+			}
+			else
+			{
+				foreach ($geometries as $geometry)
+				{
+					$geometry = json_decode($geometry);
+					echo "wktFeature = wkt.read('$geometry->geometry');\n";
+					echo "wktFeature.geometry.transform(proj_4326,proj_900913);\n";
+					echo "wktFeature.label = '$geometry->label';\n";
+					echo "wktFeature.comment = '$geometry->comment';\n";
+					echo "wktFeature.color = '$geometry->color';\n";
+					echo "wktFeature.strokewidth = '$geometry->strokewidth';\n";
+					echo "vlayer.addFeatures(wktFeature);\n";
+					echo "var color = '$geometry->color';if (color) {updateFeature(wktFeature, color, '');};\n";
+					echo "var strokewidth = '$geometry->strokewidth';if (strokewidth) {updateFeature(wktFeature, '', strokewidth);};\n";
+				}
+			}
+			?>
+			
+			
+			// Create a lat/lon object
+			var startPoint = new OpenLayers.LonLat(<?php echo $longitude; ?>, <?php echo $latitude; ?>);
+			startPoint.transform(proj_4326, map.getProjectionObject());
+			
+			// Display the map centered on a latitude and longitude (Google zoom levels)
+			map.setCenter(startPoint, <?php echo ($incident_zoom) ? $incident_zoom : $default_zoom; ?>);
+			
+			// Create the Editing Toolbar
+			var container = document.getElementById("panel");
+			var panel = new OpenLayers.Control.EditingToolbar(
+				vlayer, {div: container}
+			);
+			map.addControl(panel);
+			panel.activateControl(panel.controls[0]);
+			drag.activate();
+			highlightCtrl.activate();
+			selectCtrl.activate();
+			
+			/**
+			 * Hack to make sure selectControl always works 
+			 *
+			 * Override navigation activate/deactive to also activate/deactive
+			 * the selectCtrl. Previously selectCtrl was not being re-activated
+			 * after new features were added.
+			 */
+			navigationCtrl = panel.controls[0];
+			navigationCtrl.navActivate = panel.controls[0].activate;
+			navigationCtrl.navDeactivate = panel.controls[0].deactivate;
+			navigationCtrl.activate = function () {
+				this.navActivate();
+				selectCtrl.activate();
+			};
+			navigationCtrl.deactivate = function () {
+				this.navDeactivate();
+				selectCtrl.deactivate();
+			};
+			map.events.register("click", map, function(e){
+				selectCtrl.deactivate();
+				selectCtrl.activate();
+			});
+			
+			// Undo Action Removes Most Recent Marker
+			$('.btn_del_last').on('click', function () {
+				if (vlayer.features.length > 0) {
+					x = vlayer.features.length - 1;
+					vlayer.removeFeatures(vlayer.features[x]);
+				}
+				$('#geometry_color').ColorPickerHide();
+				$('#geometryLabelerHolder').hide(400);
+				selectCtrl.activate();
+				return false;
+			});
+			
+			// Delete Selected Features
+			$('.btn_del_sel').on('click', function () {
+				for(var y=0; y < selectedFeatures.length; y++) {
+					vlayer.removeFeatures(selectedFeatures);
+				}
+				$('#geometry_color').ColorPickerHide();
+				$('#geometryLabelerHolder').hide(400);
+				selectCtrl.activate();
+				return false;
+			});
+			
+			// Clear Map
+			$('.btn_clear').on('click', function () {
+				vlayer.removeFeatures(vlayer.features);
+				$('input[name="geometry[]"]').remove();
+				$("#latitude").val("");
+				$("#longitude").val("");
+				$('#geometry_label').val("");
+				$('#geometry_comment').val("");
+				$('#geometry_color').val("");
+				$('#geometry_lat').val("");
+				$('#geometry_lon').val("");
+				$('#geometry_color').ColorPickerHide();
+				$('#geometryLabelerHolder').hide(400);
+				selectCtrl.activate();
+				return false;
+			});
+			
+			// GeoCode
+			$('.btn_find').on('click', function () {
+				geoCode();
+			});
+			$('#location_find').bind('keypress', function(e) {
+				var code = (e.keyCode ? e.keyCode : e.which);
+				if(code == 13) { //Enter keycode
+					geoCode();
+					return false;
+				}
+			});
+			
+			// Event on Latitude/Longitude Typing Change
+			$('#latitude, #longitude').bind("blur", function() {
+				var newlat = $("#latitude").val();
+				var newlon = $("#longitude").val();
+				// Do nothing if either field is empty.
+				if (newlat == '' || newlon == '') return;
+				if (!isNaN(newlat) && !isNaN(newlon))
+				{
+					// Clear the map first
+					vlayer.removeFeatures(vlayer.features);
+					$('input[name="geometry[]"]').remove();
+					
+					point = new OpenLayers.Geometry.Point(newlon, newlat);
+					OpenLayers.Projection.transform(point, proj_4326,proj_900913);
+					
+					f = new OpenLayers.Feature.Vector(point);
+					vlayer.addFeatures(f);
+					
+					// create a new lat/lon object
+					myPoint = new OpenLayers.LonLat(newlon, newlat);
+					myPoint.transform(proj_4326, map.getProjectionObject());
+
+					// display the map centered on a latitude and longitude
+					map.panTo(myPoint);
+				}
+				else
+				{
+					// Commenting this out as its horribly annoying
+					//alert('Invalid value!');
+				}
+			});
+			
+			/* Form Actions */
+			// Action on Save Only
+			$('.btn_save').on('click', function () {
+				$("#save").attr("value", "dontclose");
+				$(this).parents("form").submit();
+				return false;
+			});
+			
+			$('.btn_save_close').on('click', function () {
+				$(this).parents("form").submit();
+				return false;
+			});
+
+			$('.btn_save_add_new').on('click', function () {
+				$("#save").attr("value", "addnew");
+				$(this).parents("form").submit();
+				return false;
+			});
+			
+			// Delete Action
+			$('.btn_delete').on('click', function () {
+				var agree=confirm("<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to'); ?> <?php echo Kohana::lang('ui_admin.delete_action'); ?>?");
+				if (agree){
+					$('#reportMain').submit();
+				}
+				return false;
+			});
+			
+			// Toggle Date Editor
+			$('a#date_toggle').click(function() {
+		    	$('#datetime_edit').show(400);
+				$('#datetime_default').hide();
+		    	return false;
+			});
+			
+			// Show Messages Box
+		    $('a#messages_toggle').click(function() {
+		    	$('#show_messages').toggle(400);
+		    	return false;
+			});
+			
+			// Textbox Hints
+			
+			/* Dynamic categories */
+			<?php if ($edit_mode): ?>
+			$('#category_add').hide();
+		    $('#add_new_category').click(function() { 
+		        var category_name = $("input#category_name").val();
+		        var category_description = $("input#category_description").val();
+		        var category_color = $("input#category_color").val();
+				var category_parent_id = $("select#category_parent_id").val(); // HT: Parent category in report edit new category
+
+				//trim the form fields
+				//Removed ".toUpperCase()" from name and desc for Ticket #38
+		        category_name = category_name.replace(/^\s+|\s+$/g, '');
+		        category_description = category_description.replace(/^\s+|\s+$/g,'');
+		        category_color = category_color.replace(/^\s+|\s+$/g, '').toUpperCase();
+        
+		        if (!category_name || !category_description || !category_color) {
+		            alert("Please fill in all the fields");
+		            return false;
+		        }
+        
+		        //category_color = category_color.toUpperCase();
+
+		        re = new RegExp("[^ABCDEF0123456789]"); //Color values are in hex
+		        if (re.test(category_color) || category_color.length != 6) {
+		            alert("Please use the Color picker to help you choose a color");
+		            return false;
+		        }
+		
+				$.post("<?php echo url::base() . 'admin/reports/save_category/' ?>", 
+					{ category_title: category_name, category_description: category_description, category_color: category_color, parent_id : category_parent_id }, // HT: Parent category in report edit new category
+					function(data){
+						if ( data.status == 'saved')
+						{
+							// alert(category_name+" "+category_description+" "+category_color);
+					        $('#user_categories').append("<li><label><input type=\"checkbox\"name=\"incident_category[]\" value=\""+data.id+"\" class=\"check-box\" checked />"+category_name+"</label></li>");
+							$('#category_add').hide();
+						}
+						else
+						{
+							alert("Your submission had errors!!");
+						}
+					}, "json");
+		        return false; 
+		    });
+			<?php endif; ?>
+		
+			// Category treeview
+			$(".category-column").treeview({
+			  persist: "location",
+			  collapsed: true,
+			  unique: false
+			});
+			
+			// Date Picker JS
+			$("#incident_date").datepicker({ 
+			    showOn: "both", 
+			    buttonImage: "<?php echo url::file_loc('img') ?>media/img/icon-calendar.gif", 
+			    buttonImageOnly: true 
+			});
+			
+			// Handles the functionality for changing the size of the map
+			// TODO: make the CSS widths dynamic... instead of hardcoding, grab the width's
+			// from the appropriate parent divs
+			$('.map-toggles a').click(function() {
+				var action = $(this).attr("class");
+				$('ul.map-toggles li').hide();
+				switch(action)
+				{
+					case "wider-map":
+						$('.incident-location').insertBefore($('.f-col'));
+						$('.map_holder_reports').css({"height":"350px", "width": "935px"});
+						$('.incident-location h4').css({"margin-left":"10px"});
+						$('.location-info').css({"margin-right":"14px"});
+						$('a[href=#report-map]').parent().hide();
+						$('a.taller-map').parent().show();
+						$('a.smaller-map').parent().show();
+						break;
+					case "taller-map":
+						$('.map_holder_reports').css("height","600px");
+						$('a.shorter-map').parent().show();
+						$('a.smaller-map').parent().show();
+						break;
+					case "shorter-map":
+						$('.map_holder_reports').css("height","350px");
+						$('a.taller-map').parent().show();
+						$('a.smaller-map').parent().show();
+						break;
+					case "smaller-map":
+						$('.incident-location').hide().prependTo($('.f-col-1'));
+						$('.map_holder_reports').css({"height":"350px", "width": "494px"});
+						$('a.wider-map').parent().show();
+						$('.incident-location').show();
+						$('.incident-location h4').css({"margin-left":"0"});
+						$('.location-info').css({"margin-right":"0"});
+						break;
+				};
+				
+				map.updateSize();
+				map.pan(0,1);
+				
+				return false;
+			});
+			
+			
+			// Prevent Map Effects in the Geometry Labeler
+			$('#geometryLabelerHolder').click(function(evt) {
+				var e = evt ? evt : window.event; 
+				OpenLayers.Event.stop(e);
+				return false;
+			});
+			
+			// Geometry Label Text Boxes
+			$('#geometry_label').click(function() {
+				$('#geometry_label').focus();
+				$('#geometry_color').ColorPickerHide();
+			}).bind("change keyup blur", function(){
+				for (f in selectedFeatures) {
+					selectedFeatures[f].label = this.value;
+				}
+				refreshFeatures();
+			});
+			
+			$('#geometry_comment').click(function() {
+				$('#geometry_comment').focus();
+				$('#geometry_color').ColorPickerHide();
+			}).bind("change keyup blur", function(){
+				for (f in selectedFeatures) {
+					selectedFeatures[f].comment = this.value;
+			    }
+				refreshFeatures();
+			});
+			
+			$('#geometry_lat').click(function() {
+				$('#geometry_lat').focus();
+				$('#geometry_color').ColorPickerHide();
+			}).bind("change keyup blur", function(){
+				for (f in selectedFeatures) {
+					selectedFeatures[f].lat = this.value;
+			    }
+				refreshFeatures();
+			});
+			
+			$('#geometry_lon').click(function() {
+				$('#geometry_lon').focus();
+				$('#geometry_color').ColorPickerHide();
+			}).bind("change keyup blur", function(){
+				for (f in selectedFeatures) {
+					selectedFeatures[f].lon = this.value;
+			    }
+				refreshFeatures();
+			});
+			
+			// Event on Latitude/Longitude Typing Change
+			$('#geometry_lat, #geometry_lon').bind("change keyup", function() {
+				var newlat = $("#geometry_lat").val();
+				var newlon = $("#geometry_lon").val();
+				if (!isNaN(newlat) && !isNaN(newlon))
+				{
+					var lonlat = new OpenLayers.LonLat(newlon, newlat);
+					lonlat.transform(proj_4326,proj_900913);
+					for (f in selectedFeatures) {
+						selectedFeatures[f].geometry.x = lonlat.lon;
+						selectedFeatures[f].geometry.y = lonlat.lat;
+						selectedFeatures[f].lon = newlat;
+						selectedFeatures[f].lat = newlon;
+						vlayer.drawFeature(selectedFeatures[f]);
+				    }
+				}
+				else
+				{
+					alert('Invalid value!')
+				}
+			});
+				
+			// Event on Color Change
+			$('#geometry_color').ColorPicker({
+				onSubmit: function(hsb, hex, rgb) {
+					$('#geometry_color').val(hex);
+					for (f in selectedFeatures) {
+						selectedFeatures[f].color = hex;
+						updateFeature(selectedFeatures[f], hex, '');
+				    }
+					refreshFeatures();
+				},
+				onChange: function(hsb, hex, rgb) {
+					$('#geometry_color').val(hex);
+					for (f in selectedFeatures) {
+						selectedFeatures[f].color = hex;
+						updateFeature(selectedFeatures[f], hex, '');
+				    }
+					refreshFeatures();
+				},
+				onBeforeShow: function () {
+					$(this).ColorPickerSetColor(this.value);
+					for (f in selectedFeatures) {
+						selectedFeatures[f].color = this.value;
+						updateFeature(selectedFeatures[f], this.value, '');
+				    }
+					refreshFeatures();
+				}
+			}).bind('keyup', function(){
+				$(this).ColorPickerSetColor(this.value);
+				for (f in selectedFeatures) {
+					selectedFeatures[f].color = this.value;
+					updateFeature(selectedFeatures[f], this.value, '');
+			    }
+				refreshFeatures();
+			});
+			
+			// Event on StrokeWidth Change
+			$('#geometry_strokewidth').bind("change keyup", function() {
+				if (parseFloat(this.value) && parseFloat(this.value) <= 8) {
+					for (f in selectedFeatures) {
+						selectedFeatures[f].strokewidth = this.value;
+						updateFeature(selectedFeatures[f], '', parseFloat(this.value));
+					}
+					refreshFeatures();
+				}
+			});
+			
+			// Close Labeler
+			$('#geometryLabelerClose').click(function() {
+				$('#geometryLabelerHolder').hide(400);
+				for (f in selectedFeatures) {
+					selectCtrl.unselect(selectedFeatures[f]);
+				}
+				selectCtrl.activate();
+			});
+
+			// Detect Dropdown Select
+			$("#select_city").change(function() {
+				var lonlat = $(this).val().split(",");
+				if ( lonlat[0] && lonlat[1] )
+				{
+					// Clear the map first
+					vlayer.removeFeatures(vlayer.features);
+					$('input[name="geometry[]"]').remove();
+
+					point = new OpenLayers.Geometry.Point(lonlat[0], lonlat[1]);
+					OpenLayers.Projection.transform(point, proj_4326,proj_900913);
+
+					f = new OpenLayers.Feature.Vector(point);
+					vlayer.addFeatures(f);
+
+					// create a new lat/lon object
+					myPoint = new OpenLayers.LonLat(lonlat[0], lonlat[1]);
+					myPoint.transform(proj_4326, map.getProjectionObject());
+
+					// display the map centered on a latitude and longitude
+					map.panTo(myPoint);
+
+					// Update form values (jQuery)
+					$("#location_name").attr("value", $('#select_city :selected').text());
+
+					$("#latitude").attr("value", lonlat[1]);
+					$("#longitude").attr("value", lonlat[0]);
+				}
+			});
+
+		});
+		
+		function addFormField(div, field, hidden_id, field_type) {
+			var id = document.getElementById(hidden_id).value;
+			
+			// HTML for the form field to be added
+			var formFieldHTML = "<div class=\"row link-row second\" id=\"" + field + "_" + id + "\">" +
+			    "<input type=\"" + field_type + "\" name=\"" + field + "[]\" class=\"" + field_type + " long2\" />" +
+			    "<a href=\"#\" class=\"add\" "+
+			    "    onClick=\"addFormField('" + div + "','" + field + "','" + hidden_id + "','" + field_type + "'); return false;\">"+
+			    "    add</a>" +
+			    "<a href=\"#\" class=\"rem\"  onClick='removeFormField(\"#" + field + "_" + id + "\"); return false;'>remove</a></div>";
+
+			$("#" + div).append(formFieldHTML);
+
+			$("#" + field + "_" + id).effect("highlight", {}, 800);
+
+			id = (id - 1) + 2;
+			document.getElementById(hidden_id).value = id;
+		}
+
+		function removeFormField(id) {
+			var answer = confirm("<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to_delete_this_item'); ?>?");
+		    if (answer){
+				$(id).remove();
+		    }
+			else{
+				return false;
+		    }
+		}
+		
+		function deletePhoto (id, div)
+		{
+			var answer = confirm("<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to_delete_this_photo'); ?>?");
+		    if (answer){
+				$("#" + div).effect("highlight", {}, 800);
+				$.get("<?php echo url::base() . 'admin/reports/deletePhoto/' ?>" + id);
+				$("#" + div).remove();
+		    }
+			else{
+				return false;
+		    }
+		}
+		
+		/**
+		 * Google GeoCoder
+		 */
+		function geoCode()
+		{
+			$('#find_loading').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+			address = $("#location_find").val();
+			$.post("<?php echo url::site() . 'reports/geocode/' ?>", { address: address },
+				function(data){
+					if (data.status == 'success'){
+						// Clear the map first
+						vlayer.removeFeatures(vlayer.features);
+						$('input[name="geometry[]"]').remove();
+						
+						point = new OpenLayers.Geometry.Point(data.longitude, data.latitude);
+						OpenLayers.Projection.transform(point, proj_4326,proj_900913);
+						
+						f = new OpenLayers.Feature.Vector(point);
+						vlayer.addFeatures(f);
+						
+						// create a new lat/lon object
+						myPoint = new OpenLayers.LonLat(data.longitude, data.latitude);
+						myPoint.transform(proj_4326, map.getProjectionObject());
+
+						// display the map centered on a latitude and longitude
+						map.panTo(myPoint);
+												
+						// Update form values
+						$("#country_name").val(data.country);
+						$("#latitude").val(data.latitude);
+						$("#longitude").val(data.longitude);
+						$("#location_name").val(data.location_name);
+					} else {
+						// Alert message to be displayed
+						var alertMessage = address + " not found!\n\n***************************\n" + 
+						    "Enter more details like city, town, country\nor find a city or town " +
+						    "close by and zoom in\nto find your precise location";
+
+						alert(alertMessage)
+					}
+					$('div#find_loading').html('');
+				}, "json");
+			return false;
+		}
+		
+		function formSwitch(form_id, incident_id)
+		{
+			var answer = confirm('<?php echo Kohana::lang('ui_admin.are_you_sure_you_want_to_switch_forms'); ?>?');
+			if (answer){
+				$('#form_loader').html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+				$.post("<?php echo url::site().'reports/switch_form'; ?>", { form_id: form_id, incident_id: incident_id },
+					function(data){
+						if (data.status == 'success'){
+							$('#custom_forms').html('');
+							$('#custom_forms').html(data.response);
+							$('#form_loader').html('');
+						}
+				  	}, "json");
+			}
+		}
+		
+		/* Keep track of the selected features */
+		function addSelected(feature) {
+			selectedFeatures.push(feature);
+			selectCtrl.activate();
+			if (vlayer.features.length == 1 && feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+				// This is a single point, no need for geometry metadata
+			} else {
+				$('#geometryLabelerHolder').show(400);
+				if (feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+					$('#geometryLat').show();
+					$('#geometryLon').show();
+					$('#geometryColor').hide();
+					$('#geometryStrokewidth').hide();
+					thisPoint = feature.clone();
+					thisPoint.geometry.transform(proj_900913,proj_4326);
+					$('#geometry_lat').val(thisPoint.geometry.y);
+					$('#geometry_lon').val(thisPoint.geometry.x);
+				} else {
+					$('#geometryLat').hide();
+					$('#geometryLon').hide();
+					$('#geometryColor').show();
+					$('#geometryStrokewidth').show();
+				}
+				if ( typeof(feature.label) != 'undefined') {
+					$('#geometry_label').val(feature.label);
+				}
+				if ( typeof(feature.comment) != 'undefined') {
+					$('#geometry_comment').val(feature.comment);
+				}
+				if ( typeof(feature.lon) != 'undefined') {
+					$('#geometry_lon').val(feature.lon);
+				}
+				if ( typeof(feature.lat) != 'undefined') {
+					$('#geometry_lat').val(feature.lat);
+				}
+				if ( typeof(feature.color) != 'undefined') {
+					$('#geometry_color').val(feature.color);
+				}
+				if ( typeof(feature.strokewidth) != 'undefined' && feature.strokewidth != '') {
+					$('#geometry_strokewidth').val(feature.strokewidth);
+				} else {
+					$('#geometry_strokewidth').val("2.5");
+				}
+			}
+		}
+
+		/* Clear the list of selected features */
+		function clearSelected(feature) {
+		    selectedFeatures = [];
+			$('#geometryLabelerHolder').hide(400);
+			$('#geometry_label').val("");
+			$('#geometry_comment').val("");
+			$('#geometry_color').val("");
+			$('#geometry_lat').val("");
+			$('#geometry_lon').val("");
+			selectCtrl.deactivate();
+			selectCtrl.activate();
+			$('#geometry_color').ColorPickerHide();
+		}
+
+		/* Feature starting to move */
+		function startDrag(feature, pixel) {
+		    lastPixel = pixel;
+		}
+
+		/* Feature moving */
+		function doDrag(feature, pixel) {
+		    for (f in selectedFeatures) {
+		        if (feature != selectedFeatures[f]) {
+		            var res = map.getResolution();
+		            selectedFeatures[f].geometry.move(res * (pixel.x - lastPixel.x), res * (lastPixel.y - pixel.y));
+		            vlayer.drawFeature(selectedFeatures[f]);
+		        }
+		    }
+		    lastPixel = pixel;
+		}
+
+		/* Featrue stopped moving */
+		function endDrag(feature, pixel) {
+		    for (f in selectedFeatures) {
+		        f.state = OpenLayers.State.UPDATE;
+		    }
+			refreshFeatures();
+			
+			// Fetching Lat Lon Values
+		  	var latitude = parseFloat($("#latitude").val());
+			var longitude = parseFloat($("#longitude").val());
+			
+			// Looking up country name using reverse geocoding
+			reverseGeocode(latitude, longitude);
+		}
+		
+		function refreshFeatures(event) {
+			var geoCollection = new OpenLayers.Geometry.Collection;
+			$('input[name="geometry[]"]').remove();
+			for(i=0; i < vlayer.features.length; i++) {
+				newFeature = vlayer.features[i].clone();
+				newFeature.geometry.transform(proj_900913,proj_4326);
+				geoCollection.addComponents(newFeature.geometry);
+				if (vlayer.features.length == 1 && vlayer.features[i].geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+					// If feature is a Single Point - save as lat/lon
+				} else {
+					// Otherwise, save geometry values
+					// Convert to Well Known Text
+					var format = new OpenLayers.Format.WKT();
+					var geometry = format.write(newFeature);
+					var label = '';
+					var comment = '';
+					var lon = '';
+					var lat = '';
+					var color = '';
+					var strokewidth = '';
+					if ( typeof(vlayer.features[i].label) != 'undefined') {
+						label = vlayer.features[i].label;
+					}
+					if ( typeof(vlayer.features[i].comment) != 'undefined') {
+						comment = vlayer.features[i].comment;
+					}
+					if ( typeof(vlayer.features[i].lon) != 'undefined') {
+						lon = vlayer.features[i].lon;
+					}
+					if ( typeof(vlayer.features[i].lat) != 'undefined') {
+						lat = vlayer.features[i].lat;
+					}
+					if ( typeof(vlayer.features[i].color) != 'undefined') {
+						color = vlayer.features[i].color;
+					}
+					if ( typeof(vlayer.features[i].strokewidth) != 'undefined') {
+						strokewidth = vlayer.features[i].strokewidth;
+					}
+					geometryAttributes = JSON.stringify({ geometry: geometry, label: label, comment: comment,lat: lat, lon: lon, color: color, strokewidth: strokewidth});
+					$('#reportForm').append($('<input></input>').attr('name','geometry[]').attr('type','hidden').attr('value',geometryAttributes));
+				}
+			}
+			
+			// Centroid of location will constitute the Location
+			// if its not a point
+			centroid = geoCollection.getCentroid(true);
+			$("#latitude").val(centroid.y);
+			$("#longitude").val(centroid.x);
+		}
+		
+		function incidentZoom(event) {
+			$("#incident_zoom").val(map.getZoom());
+		}
+		
+		function updateFeature(feature, color, strokeWidth){
+		
+			// Create a symbolizer from exiting stylemap
+			var symbolizer = feature.layer.styleMap.createSymbolizer(feature);
+			
+			// Color available?
+			if (color) {
+				symbolizer['fillColor'] = "#"+color;
+				symbolizer['strokeColor'] = "#"+color;
+				symbolizer['fillOpacity'] = "0.7";
+			} else {
+				if ( typeof(feature.color) != 'undefined' && feature.color != '' ) {
+					symbolizer['fillColor'] = "#"+feature.color;
+					symbolizer['strokeColor'] = "#"+feature.color;
+					symbolizer['fillOpacity'] = "0.7";
+				}
+			}
+			
+			// Stroke available?
+			if (parseFloat(strokeWidth)) {
+				symbolizer['strokeWidth'] = parseFloat(strokeWidth);
+			} else if ( typeof(feature.strokewidth) != 'undefined' && feature.strokewidth !='' ) {
+				symbolizer['strokeWidth'] = feature.strokewidth;
+			} else {
+				symbolizer['strokeWidth'] = "2.5";
+			}
+			
+			// Set the unique style to the feature
+			feature.style = symbolizer;
+
+			// Redraw the feature with its new style
+			feature.layer.drawFeature(feature);
+		}
+		
+		// Reverse GeoCoder
+		function reverseGeocode(latitude, longitude) {		
+			var latlng = new google.maps.LatLng(latitude, longitude);
+			var geocoder = new google.maps.Geocoder();
+			geocoder.geocode({'latLng': latlng}, function(results, status){
+				if (status == google.maps.GeocoderStatus.OK) {
+					var country = results[results.length - 1].formatted_address;
+					$("#country_name").val(country);
+				} else {
+					console.log("Geocoder failed due to: " + status);
+				}
+			});
+		}
diff --git a/themes/default/views/reports/submit_thanks.php b/themes/default/views/reports/submit_thanks.php
new file mode 100644
index 0000000..584a335
--- /dev/null
+++ b/themes/default/views/reports/submit_thanks.php
@@ -0,0 +1,18 @@
+<div id="content">
+	<div class="content-bg">
+		<!-- start block -->
+		<div class="big-block">
+			<!-- green-box -->
+			<div class="green-box">
+				<h3><?php echo Kohana::lang('ui_main.reports_submitted');?></h3>
+
+				<div class="thanks_msg"><a href="<?php echo
+					url::site().'reports' ?>"><?php echo Kohana::lang('ui_main.reports_return');?></a><br /><br />
+					<a href="<?php echo url::site('reports/submit'); ?>"><?php echo Kohana::lang('ui_main.reports_submit_new');?></a><br /><br />
+					<?php echo Kohana::lang('ui_main.feedback_reports');?><a href="mailto:<?php echo $report_email?>"><?php echo $report_email?></a><br /><br />
+				</div>
+			</div>
+		</div>
+		<!-- end block -->
+	</div>
+</div>
diff --git a/themes/default/views/reports/view_js.php b/themes/default/views/reports/view_js.php
new file mode 100644
index 0000000..2d52d90
--- /dev/null
+++ b/themes/default/views/reports/view_js.php
@@ -0,0 +1,249 @@
+<?php
+/**
+ * Reports view js file.
+ *
+ * Handles javascript stuff related to reports view function.
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author     Ushahidi Team <team at ushahidi.com> 
+ * @package    Ushahidi - http://source.ushahididev.com
+ * @module     Reports Controller
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license    http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+?>
+
+// Set the base url
+Ushahidi.baseURL = "<?php echo url::site(); ?>";
+
+jQuery(window).load(function() {
+
+	<?php echo map::layers_js(FALSE); ?>
+
+	// Configuration for the map
+	var mapConfig = {
+
+		// Zoom level
+		zoom: <?php echo ($incident_zoom) ? $incident_zoom : intval(Kohana::config('settings.default_zoom')); ?>,
+
+		// Map center
+		center: {
+			latitude: <?php echo $latitude; ?>,
+			longitude: <?php echo $longitude; ?>
+		},
+
+		// Map controls
+		mapControls: [
+			new OpenLayers.Control.Navigation({ dragPanOptions: { enableKinetic: true } }),
+			new OpenLayers.Control.Zoom(),
+			new OpenLayers.Control.MousePosition({
+				formatOutput: Ushahidi.convertLongLat
+			}),
+			new OpenLayers.Control.ScaleLine(),
+			new OpenLayers.Control.Scale('mapScale'),
+			new OpenLayers.Control.LayerSwitcher(),
+			new OpenLayers.Control.Attribution()
+		],
+
+		// Base layers
+		baseLayers: <?php echo map::layers_array(FALSE); ?>
+
+	};
+
+	// Set Feature Styles
+	var style1 = new OpenLayers.Style({
+		pointRadius: "8",
+		fillColor: "${fillcolor}",
+		fillOpacity: "0.7",
+		strokeColor: "${strokecolor}",
+		strokeOpacity: "0.7",
+		strokeWidth: "${strokewidth}",
+		graphicZIndex: 1,
+		externalGraphic: "${graphic}",
+		graphicOpacity: 1,
+		graphicWidth: 21,
+		graphicHeight: 25,
+		graphicXOffset: -14,
+		graphicYOffset: -27
+	},
+	{
+		context: 
+		{
+			graphic: function(feature) {
+				if (typeof(feature) != 'undefined' && 
+					feature.data.id == <?php echo $incident_id; ?>)
+				{
+					return "<?php echo url::file_loc('img').'media/img/openlayers/marker.png' ;?>";
+				}
+				else
+				{
+					return "<?php echo url::file_loc('img').'media/img/openlayers/marker-gold.png' ;?>";
+				}
+			},
+			fillcolor: function(feature) {
+				if ( typeof(feature.attributes.color) != 'undefined' && 
+					feature.attributes.color != '' )
+				{
+					return "#"+feature.attributes.color;
+				}
+				else
+				{
+					return "#ffcc66";
+				}
+			},
+			strokecolor: function(feature) {
+				if ( typeof(feature.attributes.strokecolor) != 'undefined' && 
+					feature.attributes.strokecolor != '')
+				{
+					return "#"+feature.attributes.strokecolor;
+				}
+				else
+				{
+					return "#CC0000";
+				}
+			},					
+			strokewidth: function(feature) {
+				if ( typeof(feature.attributes.strokewidth) != 'undefined' && 
+					feature.attributes.strokewidth != '')
+				{
+					return feature.attributes.strokewidth;
+				}
+				else
+				{
+					return "3";
+				}
+			}
+		}
+	});
+
+	var style2 = new OpenLayers.Style({
+		pointRadius: "8",
+		fillColor: "#30E900",
+		fillOpacity: "0.7",
+		strokeColor: "#197700",
+		strokeWidth: 3,
+		graphicZIndex: 1
+	});
+
+
+	// Styles to use for rendering the markers
+	var styleMap = new OpenLayers.StyleMap({
+		'default': style1,
+		'select': style1,
+		'temporary': style2
+	});
+
+
+	// Initialize the map
+	var map = new Ushahidi.Map('map', mapConfig);
+	map.addLayer(Ushahidi.GEOJSON, {
+		name: "Single Report",
+		url: "<?php echo 'json/single/'.$incident_id; ?>",
+		styleMap: styleMap
+	});
+	
+	// Ajax Validation for the comments
+	$("#commentForm").validate({
+		rules: {
+			comment_author: {
+				required: true,
+				minlength: 3
+			},
+			comment_email: {
+				required: true,
+				email: true
+			},
+			comment_description: {
+				required: true,
+				minlength: 3
+			},
+			captcha: {
+				required: true
+			}
+		},
+		messages: {
+			comment_author: {
+				required: "Please enter your Name",
+				minlength: "Your Name must consist of at least 3 characters"
+			},
+			comment_email: {
+				required: "Please enter an Email Address",
+				email: "Please enter a valid Email Address"
+			},
+			comment_description: {
+				required: "Please enter a Comment",
+				minlength: "Your Comment must be at least 3 characters long"
+			},
+			captcha: {
+				required: "Please enter the Security Code"
+			}
+		}
+	});
+	
+	// Handles the functionality for changing the size of the map
+	// TODO: make the CSS widths dynamic... instead of hardcoding, grab the width's
+	// from the appropriate parent divs
+	$('.map-toggles a').click(function() {
+		var action = $(this).attr("class");
+		$('ul.map-toggles li').hide();
+		switch(action)
+		{
+			case "wider-map":
+				$('.report-map').insertBefore($('.left-col'));
+				$('.map-holder').css({"height":"350px", "width": "900px"});
+				$('a[href=#report-map]').parent().hide();
+				$('a.taller-map').parent().show();
+				$('a.smaller-map').parent().show();
+				break;
+			case "taller-map":
+				$('.map-holder').css("height","600px");
+				$('a.shorter-map').parent().show();
+				$('a.smaller-map').parent().show();
+				break;
+			case "shorter-map":
+				$('.map-holder').css("height","350px");
+				$('a.taller-map').parent().show();
+				$('a.smaller-map').parent().show();
+				break;
+			case "smaller-map":
+				$('.report-map').hide().prependTo($('.report-media-box-content'));
+				$('.map-holder').css({"height":"350px", "width": "348px"});
+				$('a.wider-map').parent().show();
+				$('.report-map').show();
+				break;
+		};
+		
+		map.trigger("resize");
+		return false;
+	});
+
+}); // END jQuery(window).load();	
+
+function rating(id,action,type,loader) {
+	$('#' + loader).html('<img src="<?php echo url::file_loc('img')."media/img/loading_g.gif"; ?>">');
+	$.post("<?php echo url::site().'reports/rating/' ?>" + id, { action: action, type: type },
+		function(data){
+			if (data.status == 'saved'){
+				if (type == 'original') {
+					$('#oup_' + id).attr("src","<?php echo url::file_loc('img').'media/img/'; ?>gray_up.png");
+					$('#odown_' + id).attr("src","<?php echo url::file_loc('img').'media/img/'; ?>gray_down.png");
+					$('#orating_' + id).html(data.rating);
+				}
+				else if (type == 'comment')
+				{
+					$('#cup_' + id).attr("src","<?php echo url::file_loc('img').'media/img/'; ?>gray_up.png");
+					$('#cdown_' + id).attr("src","<?php echo url::file_loc('img').'media/img/'; ?>gray_down.png");
+					$('#crating_' + id).html(data.rating);
+				}
+			} else {
+				if(typeof(data.message) != 'undefined') {
+					alert(data.message);
+				}
+			}
+			$('#' + loader).html('');
+	  	}, "json");
+}
+		
diff --git a/themes/default/views/search.php b/themes/default/views/search.php
new file mode 100644
index 0000000..9ab83a9
--- /dev/null
+++ b/themes/default/views/search.php
@@ -0,0 +1,13 @@
+<div id="content">
+	<div class="content-bg">
+		<!-- start search block -->
+		<div class="big-block">
+			<h1>Search Results</h1>
+			<div class="search_block">
+				<?php echo $search_info; ?>
+				<?php echo $search_results; ?>
+			</div>
+		</div>
+		<!-- end search block -->
+	</div>
+</div>
\ No newline at end of file
diff --git a/themes/polaroid/css/_default.css b/themes/polaroid/css/_default.css
new file mode 100644
index 0000000..79e542e
--- /dev/null
+++ b/themes/polaroid/css/_default.css
@@ -0,0 +1,269 @@
+/*
+Theme Name: Travelogger
+Description: This theme is specifically geared toward people wanting to keep a travelog.
+Demo: http://www.ushahidi.com
+Version: 1.0
+Author: Caleb Bell
+Author Email: caleb at ushahidi.com
+
+*/
+
+
+/* Theme Settings
+--------------------*/
+  /* Primary Color */
+  h1, a,
+  table.table-list tbody tr td a,
+  .hover .r_details h3 a.r_title,
+  div.footermenu ul li a,
+  div.search-form input.text  { color: #dc6d00; }
+  div.search-form input.text:focus { border-color: #dc6d00; }
+  div.submit-incident a, .btn_submit,
+  div.filters ul li a:hover, div.filters ul li a.active,
+  ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#dc6d00; }
+  
+  /* Lighter Primary Color - for hover effects */
+  div#loggedin_user_action a, 
+  div#loggedin_user_action a:visited,
+  div#mainmenu ul li a { color:#dc8a3b; }
+  div.submit-incident a:hover, .btn_submit:hover,
+  div.search-form input.searchbtn,
+  div#mainmenu ul li a:hover,
+  div#mainmenu a.active { background-color:#dc8a3b; }
+  div#mainmenu ul li a.active,
+  div#mainmenu ul li a:hover { color:#ffffff; }
+
+
+/* general styles */
+h1 {
+  
+}
+h2 {
+  
+}
+h5, h4 {
+  
+}
+
+/* structural divs */
+
+body { background:transparent url(../images/bg_body_alt.png) repeat; }
+
+
+.big-block {   }
+.page-main #main { padding:0px 10px; display:none; }
+.page-main #middle { background:none; }
+div#middle .background { min-height:inherit; }
+
+/* top-bar */
+#top-bar {
+  width: 960px;
+  height: 40px;
+  position: relative;
+  margin:0 auto;
+}
+div#searchbox {
+  position: absolute;
+  right: 0;
+  background: #3d3c3b;
+  border: none;
+  width: auto;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+}
+div#loggedin_user_action {
+  float: left;
+  width: auto;
+}
+div#loggedin_user_action a, div#loggedin_user_action a:visited {
+  font-weight: bold;
+  padding: 6px;
+  margin-top: 2px;
+  width: auto;
+  display: block;
+  float: left;
+  color:#5396ff;
+}
+div.search-form input.text {
+  border: 1px solid;
+}
+div.search-form input.text:focus {
+  border: 1px solid;
+}
+div.search-form input.searchbtn {
+  width: auto;
+  height: auto;
+  border: 0;
+  text-indent: 0;
+  color: #FFFFFF;
+  display: block;
+  font-size: 12px;
+  background-image:none;
+  background-color:#3764AA;
+  margin-left: 10px;
+  line-height: 17px;
+  padding: 2px 5px 3px;
+  text-transform: uppercase;
+  text-shadow: 1px 1px 0 #424242;
+}
+
+/* header */
+div#header { height:auto; margin:18px 0; }
+
+/* logo box */
+div#logo {
+  background-color: transparent;
+  float:none;
+  padding: 0;
+  margin: 0;
+}
+div#logo h1 a {
+  color: #222222;
+  font-size: 28px;
+}
+div#logo span {
+  font-style: italic;
+  color: #999;
+  font-family: 'Georgia', serif;
+}
+
+/* submit report button */
+div.submit-incident { margin:0; top:20%; }
+div.submit-incident a, .btn_submit {
+  text-shadow: 1px 1px 0 #424242;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+}
+
+/* primary nav */
+div#mainmenu { background:none; border:none; height:27px; }
+div#mainmenu ul {
+  
+}
+div#mainmenu ul li {
+  margin:0 8px 0 0;
+}
+div#mainmenu ul li a { padding:3px 5px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
+div#mainmenu ul li a:hover, div#mainmenu a.active {
+  
+}
+
+
+/* home page reports */
+div#middle {}
+div.content-container { background:none; }
+
+.polaroids { }
+.polaroid { margin:0 10px 10px 0; padding:5px; height:150px; float:left;  }
+.picture { width:100px; font:10px/18px "Monaco","Courier New","Courier",monospace; padding:5px; background:#dedbdf url(../images/bg_polaroid-pic.png) repeat; -moz-box-shadow:0px 1px 1px #9a9a9a; -webkit-box-shadow:0px 1px 1px #9a9a9a; box-shadow:0px 1px 1px #9a9a9a; -moz-border-radius:3px; -webkit-border-radius:3px; border-radius:3px; }
+.picture img { display:block; border:1px solid #eeebef; -moz-border-radius:3px; -webkit-border-radius:3px; border-radius:3px; }
+.picture span { display:block; }   
+/* reports listing page */
+#content .big-block { border:1px solid #fff; border-top:0; margin:0 10px; background:#EEEDEA; padding:18px; }
+#reports-box { }
+#rb_list-view .rb_report {
+  background: #fff;
+}
+
+/* single report page */
+body.page-reports-view { background:#ffffff; background-image:none; }
+.page-reports-view div#header,
+.page-reports-view div#mainmenu,
+.page-reports-view div#footer,
+.page-reports-view div#top-bar,
+.report_detail .r_location,
+div.credibility { display:none; }
+
+.page-reports-view div.wrapper { width:740px; }
+.page-reports-view div#main { padding:0; }
+#main .left-col { width:150px; margin-right:45px; }
+#main .middle-col { width:350px; margin-right:45px; float:left; }
+#main .right-col { width:150px; }
+#main .right-col .polaroid { padding:0; height:auto; margin-bottom:18px; }
+#main .right-col .photothumb { display:block; margin-bottom:18px; }
+#main .right-col .photothumb img { width:110px }
+
+
+.report_detail .r_date { background:none; font-size:14px; font-weight:bold; margin:65px 0 0 0; padding:0; display:block; text-align:right; }
+
+div.report-category-list p a { display:block; text-align:right; float:none; background:none; position:relative; margin:0 0 5px 0; padding:0 20px 0 0; }
+div.report-category-list a span.r_cat-box { height:10px; width:10px; line-height:10px; position:absolute; right:0; top:1px;}
+
+.report-map .map-holder { width:auto; height:100px; }
+
+h1.report-title { height:45px; }
+
+div.report-media-box-content { display:none; }
+div.report-additional-reports { display:none; }
+
+/* Submit Report */
+.page-reports-submit div#middle { background:transparent; }
+
+/* reports page */
+.report_right { width:450px;}
+
+/* get alerts page */
+.step-1 h2 {
+  padding: 0;
+}
+/* footer */
+div#footer { background:#000; width:960px; margin:0 auto; -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; }
+
+div#underfooter { background:transparent; }
+div.footermenu { margin:0 18px}
+
+
+div.footermenu ul li a {
+  text-transform: uppercase;
+  font-weight: bold;
+  border-left: 1px solid #666;
+}
+.additional-credits {
+  margin-top: 18px;
+}
+
+/* button styles */
+a.btn {
+    cursor: pointer;
+    display: inline-block;
+    background-color: #e6e6e6;
+    background-repeat: no-repeat;
+    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));
+    background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
+    background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);
+    background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
+    background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
+    background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
+    padding: 5px 14px 6px;
+    text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+    color: #333;
+    font-size: 13px;
+    line-height: normal;
+    border: 1px solid #ccc;
+    border-bottom-color: #bbb;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+    -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+    -webkit-transition: 0.1s linear all;
+    -moz-transition: 0.1s linear all;
+    transition: 0.1s linear all;
+}
+
+a.btn:hover {
+  background-position: 0 -15px;
+  color: #333;
+  text-decoration: none;
+}
+
+span.btn-icon { display:block; position:absolute; height:16px; width:16px; overflow:hidden; text-indent:-234324px; top:6px; right:5px; }
+  .ic-right     { background:transparent url(../images/bg_category-filter-controls.png) -32px -41px no-repeat; }
+  .ic-down      { background:transparent url(../images/bg_category-filter-controls.png) -32px -55px no-repeat; }
+  .ic-question  { background:transparent url(../images/bg_category-filter-controls.png) 0px -49px no-repeat; }
+  
+  /* active state */
+  .active-toggle .ic-right { background:transparent url(../images/bg_category-filter-controls.png) -32px -55px no-repeat; }
+  
\ No newline at end of file
diff --git a/themes/polaroid/css/colorbox.css b/themes/polaroid/css/colorbox.css
new file mode 100644
index 0000000..5310531
--- /dev/null
+++ b/themes/polaroid/css/colorbox.css
@@ -0,0 +1,78 @@
+/*
+    ColorBox Core Style:
+    The following CSS is consistent between example themes and should not be altered.
+*/
+#colorbox, #cboxOverlay, #cboxWrapper{position:absolute; top:0; left:0; z-index:9999; overflow:hidden;}
+#cboxOverlay{position:fixed; width:100%; height:100%;}
+#cboxMiddleLeft, #cboxBottomLeft{clear:left;}
+#cboxContent{position:relative;}
+#cboxLoadedContent{overflow:auto;}
+#cboxTitle{margin:0;}
+#cboxLoadingOverlay, #cboxLoadingGraphic{position:absolute; top:0; left:0; width:100%;}
+#cboxPrevious, #cboxNext, #cboxClose, #cboxSlideshow{cursor:pointer;}
+.cboxPhoto{float:left; margin:auto; border:0; display:block;}
+.cboxIframe{width:100%; height:100%; display:block; border:0;}
+
+/* 
+    User Style:
+    Change the following styles to modify the appearance of ColorBox.  They are
+    ordered & tabbed in a way that represents the nesting of the generated HTML.
+*/
+#cboxOverlay{background:#fff;}
+#colorbox{}
+    #cboxTopLeft{width:25px; height:25px; background:url(../images/colorbox/border1.png) no-repeat 0 0;}
+    #cboxTopCenter{height:25px; background:url(../images/colorbox/border1.png) repeat-x 0 -50px;}
+    #cboxTopRight{width:25px; height:25px; background:url(../images/colorbox/border1.png) no-repeat -25px 0;}
+    #cboxBottomLeft{width:25px; height:25px; background:url(../images/colorbox/border1.png) no-repeat 0 -25px;}
+    #cboxBottomCenter{height:25px; background:url(../images/colorbox/border1.png) repeat-x 0 -75px;}
+    #cboxBottomRight{width:25px; height:25px; background:url(../images/colorbox/border1.png) no-repeat -25px -25px;}
+    #cboxMiddleLeft{width:25px; background:url(../images/colorbox/border2.png) repeat-y 0 0;}
+    #cboxMiddleRight{width:25px; background:url(../images/colorbox/border2.png) repeat-y -25px 0;}
+    #cboxContent{background:#fff; overflow:hidden;}
+        #cboxError{padding:50px; border:1px solid #ccc;}
+        #cboxLoadedContent{margin-bottom:20px;}
+        #cboxTitle{position:absolute; bottom:0px; left:0; text-align:center; width:100%; color:#999;}
+        #cboxCurrent{position:absolute; bottom:0px; left:100px; color:#999;}
+        #cboxSlideshow{position:absolute; bottom:0px; right:42px; color:#444;}
+        #cboxPrevious{position:absolute; bottom:0px; left:0; color:#444;}
+        #cboxNext{position:absolute; bottom:0px; left:63px; color:#444;}
+        #cboxLoadingOverlay{background:#fff url(../images/colorbox/loading.gif) no-repeat 5px 5px;}
+        #cboxClose{position:absolute; bottom:0; right:0; display:block; color:#444;}
+
+/*
+  The following fixes a problem where IE7 and IE8 replace a PNG's alpha transparency with a black fill
+  when an alpha filter (opacity change) is set on the element or ancestor element.  This style is not applied to IE9.
+*/
+.cboxIE #cboxTopLeft,
+.cboxIE #cboxTopCenter,
+.cboxIE #cboxTopRight,
+.cboxIE #cboxBottomLeft,
+.cboxIE #cboxBottomCenter,
+.cboxIE #cboxBottomRight,
+.cboxIE #cboxMiddleLeft,
+.cboxIE #cboxMiddleRight {
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#00FFFFFF,endColorstr=#00FFFFFF);
+}
+
+/*
+  The following provides PNG transparency support for IE6
+*/
+.cboxIE6 #cboxTopLeft{background:url(../images/colorbox/ie6/borderTopLeft.png);}
+.cboxIE6 #cboxTopCenter{background:url(../images/colorbox/ie6/borderTopCenter.png);}
+.cboxIE6 #cboxTopRight{background:url(../images/colorbox/ie6/borderTopRight.png);}
+.cboxIE6 #cboxBottomLeft{background:url(../images/colorbox/ie6/borderBottomLeft.png);}
+.cboxIE6 #cboxBottomCenter{background:url(../images/colorbox/ie6/borderBottomCenter.png);}
+.cboxIE6 #cboxBottomRight{background:url(../images/colorbox/ie6/borderBottomRight.png);}
+.cboxIE6 #cboxMiddleLeft{background:url(../images/colorbox/ie6/borderMiddleLeft.png);}
+.cboxIE6 #cboxMiddleRight{background:url(../images/colorbox/ie6/borderMiddleRight.png);}
+
+.cboxIE6 #cboxTopLeft,
+.cboxIE6 #cboxTopCenter,
+.cboxIE6 #cboxTopRight,
+.cboxIE6 #cboxBottomLeft,
+.cboxIE6 #cboxBottomCenter,
+.cboxIE6 #cboxBottomRight,
+.cboxIE6 #cboxMiddleLeft,
+.cboxIE6 #cboxMiddleRight {
+    _behavior: expression(this.src = this.src ? this.src : this.currentStyle.backgroundImage.split('"')[1], this.style.background = "none", this.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=" + this.src + ", sizingMethod='scale')");
+}
diff --git a/themes/polaroid/hooks/register_polaroid_block.php b/themes/polaroid/hooks/register_polaroid_block.php
new file mode 100644
index 0000000..325d6ca
--- /dev/null
+++ b/themes/polaroid/hooks/register_polaroid_block.php
@@ -0,0 +1,58 @@
+<?php defined('SYSPATH') or die('No direct script access.');
+/**
+ * Polaroid Hook
+ *
+ * PHP version 5
+ * LICENSE: This source file is subject to LGPL license 
+ * that is available through the world-wide-web at the following URI:
+ * http://www.gnu.org/copyleft/lesser.html
+ * @author	   Ushahidi Team <team at ushahidi.com> 
+ * @package	   Ushahidi - http://source.ushahididev.com
+ * @copyright  Ushahidi - http://www.ushahidi.com
+ * @license	   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License (LGPL) 
+ */
+
+ class polaroid_reports_block {
+
+ 	public function __construct()
+ 	{
+ 		$block = array(
+ 			"classname" => "polaroid_reports_block",
+ 			"name" => "Polaroid Reports",
+ 			"description" => "List the 35 latest reports in the system. Part of the Polaroid Theme."
+ 		);
+
+ 		blocks::register($block);
+ 		
+		// Hook into routing
+		Event::add('system.pre_controller', array($this, 'add'));
+ 	}
+
+ 	public function block()
+ 	{
+ 		$content = new View('blocks/polaroid_reports');
+
+ 		// Get Reports
+    // XXX: Might need to replace magic no. 8 with a constant
+ 		$content->total_items = ORM::factory('incident')
+ 			->where('incident_active', '1')
+ 			->limit('8')->count_all();
+ 		$content->incidents = ORM::factory('incident')
+ 			->where('incident_active', '1')
+ 			->limit('35')
+ 			->orderby('incident_date', 'desc')
+ 			->find_all();
+
+ 		echo $content;
+ 	}
+
+ /**
+	 * Adds all the events to the main Ushahidi application
+	 */
+	public function add()
+	{
+		
+	}
+}
+
+new polaroid_reports_block;
diff --git a/themes/polaroid/images/bg_body.png b/themes/polaroid/images/bg_body.png
new file mode 100644
index 0000000..3328885
Binary files /dev/null and b/themes/polaroid/images/bg_body.png differ
diff --git a/themes/polaroid/images/bg_body_alt.png b/themes/polaroid/images/bg_body_alt.png
new file mode 100644
index 0000000..a22cf7e
Binary files /dev/null and b/themes/polaroid/images/bg_body_alt.png differ
diff --git a/themes/polaroid/images/bg_polaroid-pic.png b/themes/polaroid/images/bg_polaroid-pic.png
new file mode 100644
index 0000000..3ec795d
Binary files /dev/null and b/themes/polaroid/images/bg_polaroid-pic.png differ
diff --git a/themes/polaroid/images/colorbox/border1.png b/themes/polaroid/images/colorbox/border1.png
new file mode 100644
index 0000000..0ddc704
Binary files /dev/null and b/themes/polaroid/images/colorbox/border1.png differ
diff --git a/themes/polaroid/images/colorbox/border2.png b/themes/polaroid/images/colorbox/border2.png
new file mode 100644
index 0000000..aa62a0b
Binary files /dev/null and b/themes/polaroid/images/colorbox/border2.png differ
diff --git a/themes/polaroid/images/colorbox/ie6/borderBottomCenter.png b/themes/polaroid/images/colorbox/ie6/borderBottomCenter.png
new file mode 100644
index 0000000..12e0e9a
Binary files /dev/null and b/themes/polaroid/images/colorbox/ie6/borderBottomCenter.png differ
diff --git a/themes/polaroid/images/colorbox/ie6/borderBottomLeft.png b/themes/polaroid/images/colorbox/ie6/borderBottomLeft.png
new file mode 100644
index 0000000..b7a474a
Binary files /dev/null and b/themes/polaroid/images/colorbox/ie6/borderBottomLeft.png differ
diff --git a/themes/polaroid/images/colorbox/ie6/borderBottomRight.png b/themes/polaroid/images/colorbox/ie6/borderBottomRight.png
new file mode 100644
index 0000000..6b6cb15
Binary files /dev/null and b/themes/polaroid/images/colorbox/ie6/borderBottomRight.png differ
diff --git a/themes/polaroid/images/colorbox/ie6/borderMiddleLeft.png b/themes/polaroid/images/colorbox/ie6/borderMiddleLeft.png
new file mode 100644
index 0000000..8d0eb73
Binary files /dev/null and b/themes/polaroid/images/colorbox/ie6/borderMiddleLeft.png differ
diff --git a/themes/polaroid/images/colorbox/ie6/borderMiddleRight.png b/themes/polaroid/images/colorbox/ie6/borderMiddleRight.png
new file mode 100644
index 0000000..d65509e
Binary files /dev/null and b/themes/polaroid/images/colorbox/ie6/borderMiddleRight.png differ
diff --git a/themes/polaroid/images/colorbox/ie6/borderTopCenter.png b/themes/polaroid/images/colorbox/ie6/borderTopCenter.png
new file mode 100644
index 0000000..35d8da2
Binary files /dev/null and b/themes/polaroid/images/colorbox/ie6/borderTopCenter.png differ
diff --git a/themes/polaroid/images/colorbox/ie6/borderTopLeft.png b/themes/polaroid/images/colorbox/ie6/borderTopLeft.png
new file mode 100644
index 0000000..ae9bda0
Binary files /dev/null and b/themes/polaroid/images/colorbox/ie6/borderTopLeft.png differ
diff --git a/themes/polaroid/images/colorbox/ie6/borderTopRight.png b/themes/polaroid/images/colorbox/ie6/borderTopRight.png
new file mode 100644
index 0000000..0d88683
Binary files /dev/null and b/themes/polaroid/images/colorbox/ie6/borderTopRight.png differ
diff --git a/themes/polaroid/images/colorbox/loading.gif b/themes/polaroid/images/colorbox/loading.gif
new file mode 100644
index 0000000..602ce3c
Binary files /dev/null and b/themes/polaroid/images/colorbox/loading.gif differ
diff --git a/themes/polaroid/js/_jquery.colorbox-min.js b/themes/polaroid/js/_jquery.colorbox-min.js
new file mode 100644
index 0000000..b9a7233
--- /dev/null
+++ b/themes/polaroid/js/_jquery.colorbox-min.js
@@ -0,0 +1,4 @@
+// ColorBox v1.3.17.2 - a full featured, light-weight, customizable lightbox based on jQuery 1.3+
+// Copyright (c) 2011 Jack Moore - jack at colorpowered.com
+// Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
+(function(a,b,c){function bc(b){if(!U){P=b,_(),y=a(P),Q=0,K.rel!=="nofollow"&&(y=a("."+g).filter(function(){var b=a.data(this,e).rel||this.rel;return b===K.rel}),Q=y.index(P),Q===-1&&(y=y.add(P),Q=y.length-1));if(!S){S=T=!0,r.show();if(K.returnFocus)try{P.blur(),a(P).one(l,function(){try{this.focus()}catch(a){}})}catch(c){}q.css({opacity:+K.opacity,cursor:K.overlayClose?"pointer":"auto"}).show(),K.w=Z(K.initialWidth,"x"),K.h=Z(K.initialHeight,"y"),X.position(),o&&z.bind("resize."+p+" scr [...]
\ No newline at end of file
diff --git a/themes/polaroid/js/_jquery.isotope.min.js b/themes/polaroid/js/_jquery.isotope.min.js
new file mode 100644
index 0000000..0f0e79c
--- /dev/null
+++ b/themes/polaroid/js/_jquery.isotope.min.js
@@ -0,0 +1,13 @@
+/**
+ * Isotope v1.4.110906
+ * An exquisite jQuery plugin for magical layouts
+ * http://isotope.metafizzy.co
+ *
+ * Commercial use requires one-time license fee
+ * http://metafizzy.co/#licenses
+ *
+ * Copyright 2011 David DeSandro / Metafizzy
+ */
+/*jshint curly: true, eqeqeq: true, forin: false, immed: false, newcap: true, noempty: true, undef: true */
+/*global Modernizr: true */
+(function(a,b,c){function f(a){var b=document.documentElement.style,c;if(typeof b[a]=="string")return a;a=d(a);for(var f=0,g=e.length;f<g;f++){c=e[f]+a;if(typeof b[c]=="string")return c}}function d(a){return a.charAt(0).toUpperCase()+a.slice(1)}var e="Moz Webkit Khtml O Ms".split(" "),g=f("transform"),h={csstransforms:function(){return!!g},csstransforms3d:function(){var a=!!f("perspective");if(a){var c=" -o- -moz- -ms- -webkit- -khtml- ".split(" "),d="@media ("+c.join("transform-3d),(")+ [...]
diff --git a/themes/polaroid/js/initialize.js b/themes/polaroid/js/initialize.js
new file mode 100644
index 0000000..f928aa1
--- /dev/null
+++ b/themes/polaroid/js/initialize.js
@@ -0,0 +1,39 @@
+$(function(){
+  
+  /*--------When you click on a report, load it in a modal iframe----------*/
+  $("a.modal-that-junk, .search_block h3 a").colorbox({
+    width:"820px", 
+    height:"80%", 
+    iframe:true,
+    current: "{current} of {total}"
+    });
+    
+    var $container = $('.polaroids');
+
+     // filter buttons
+     $('#filters a').click(function(){
+       var selector = $(this).attr('data-filter');
+       $container.isotope({ filter: selector });
+       var $this = $(this);
+
+       // don't proceed if already selected
+       if ( !$this.hasClass('selected') ) {
+         $this.parents('.option-set').find('.selected').removeClass('selected');
+         $this.addClass('selected');
+       }
+       return false;
+     });
+
+
+     // switches selected class on buttons
+     $('#options').find('.option-set a').click(function(){
+       
+
+     });
+
+     $container.isotope({
+       itemSelector : '.polaroid'
+     });
+
+
+});
\ No newline at end of file
diff --git a/themes/polaroid/readme.txt b/themes/polaroid/readme.txt
new file mode 100644
index 0000000..028de7f
--- /dev/null
+++ b/themes/polaroid/readme.txt
@@ -0,0 +1,24 @@
+Theme Name: Polaroid
+Description: NOTE: Polaroid requires you to change additional settings -> http://goo.gl/Mw3o9
+Version: 1.1
+Author: Caleb Bell
+Author Email: caleb at ushahidi.com
+JS: initialize,_jquery.colorbox-min,_jquery.isotope.min
+
+===INSTALLATION===
+1. Activate the theme
+2. Go to the Manage -> Blocks section in the admin panel and hide all blocks except for
+the Polaroid block. (http://goo.gl/kmyH4)
+
+
+===CHANGELOG===
+
+Polaroid v1.1 09-26-2011
+---------------------------------
+* merged Polaroid plugin files into the theme folder since we now have the ability
+to access hooks within the theme system.
+* update screenshot
+
+Polaroid v1, 09-22-2011
+---------------------------------
+* version 1 released
diff --git a/themes/polaroid/screenshot.png b/themes/polaroid/screenshot.png
new file mode 100644
index 0000000..57f2086
Binary files /dev/null and b/themes/polaroid/screenshot.png differ
diff --git a/themes/polaroid/views/blocks/polaroid_reports.php b/themes/polaroid/views/blocks/polaroid_reports.php
new file mode 100644
index 0000000..739031c
--- /dev/null
+++ b/themes/polaroid/views/blocks/polaroid_reports.php
@@ -0,0 +1,114 @@
+
+<div style="display:none;">
+<?php echo Kohana::lang('ui_main.title'); ?>
+<?php echo Kohana::lang('ui_main.location'); ?>
+<?php echo Kohana::lang('ui_main.date'); ?>
+<?php echo Kohana::lang('ui_main.category'); ?>
+
+<a class="more" href="<?php echo url::site() . 'reports/' ?>"><?php echo Kohana::lang('ui_main.view_more'); ?></a>
+
+</div>
+  <!-- TODO: Make these filters dynamic, also need to add dynamic category filtering. -->
+  <p style="display:none;"><strong>Filter By Month:</strong> 
+    <span id="filters" class="option-set" data-filter-group="date">
+      <a href="#filter" data-filter="*" class="selected">All</a> | 
+      <!--<a href="#filter" data-filter=".Jan">Jan-</a> | 
+      <a href="#filter" data-filter=".Feb">Feb</a> | 
+      <a href="#filter" data-filter=".Mar">Mar</a> | 
+      <a href="#filter" data-filter=".Apr">Apr</a> | 
+      <a href="#filter" data-filter=".May">May</a> | 
+      <a href="#filter" data-filter=".Jun">Jun</a> |--> 
+      <a href="#filter" data-filter=".Jul" class="">Jul</a> | 
+      <a href="#filter" data-filter=".Aug" class="">Aug</a> | 
+      <a href="#filter" data-filter=".Sep" class="">Sep</a> <!-- | 
+      <a href="#filter" data-filter=".Oct">Oct</a> | 
+      <a href="#filter" data-filter=".Nov">Nov</a> | 
+      <a href="#filter" data-filter=".Dec">Dec</a> -->
+    </span>
+</p>
+  <div class="polaroids">
+		<?php
+		if ($total_items == 0)
+		{
+			?>
+			<p><?php echo Kohana::lang('ui_main.no_reports'); ?></p>
+			<?php
+		}
+		foreach ($incidents as $incident)
+		{
+			$incident_id = $incident->id;
+			$incident_title = text::limit_chars($incident->incident_title, 20, '...', False);
+			$incident_date = $incident->incident_date;
+			$incident_date = date('l j F Y', strtotime($incident->incident_date));
+			$incident_month = date('M', strtotime($incident->incident_date));
+			$incident_location = $incident->location->location_name;
+			$incident_lat = $incident->location->latitude;
+			$incident_lon = $incident->location->longitude;
+			
+			$incident_category = $incident->incident_category;
+		?>
+      
+      <?php
+        $isotope_js_filters = $incident_month;
+        
+				foreach($incident_category as $category) 
+				{
+
+					// don't show hidden categoies
+					if($category->category->category_visible == 0)
+					{
+						continue;
+					}
+					else
+					{
+					  $isotope_js_filters = $isotope_js_filters." ".$category->category->category_title;
+				  }
+				}
+			?>
+		  <div class="polaroid <?php echo $isotope_js_filters ?>">
+		  <p class="picture"> <a rel="the-roids" class="modal-that-junk" href="<?php echo url::site() . 'reports/view/' . $incident_id; ?>"><img style="width:100px; height:100px;" src="<?php echo Kohana::config('core.site_protocol'); ?>://maps.google.com/maps/api/staticmap?center=<?php echo $incident_lat.",".$incident_lon; ?>&zoom=7&markers=size:mid|color:green|<?php echo $incident_lat.",".$incident_lon; ?>|<?php echo $incident_lat.",".$incident_lon; ?>&size=100x100&sensor=true&&style=visibili [...]
+      <span><?php echo $incident_title ?></span></a></p>
+			
+			<div class="extra-stuff" id="picture-<?php echo $incident_id ?>" style="display:none">
+			<p>Location: <?php echo $incident_location ?></p>
+			<p>Date: <?php echo $incident_date; ?></p>
+			  <p>Categories: 
+    			<?php
+    				foreach($incident_category as $category) 
+    				{
+
+    					// don't show hidden categoies
+    					if($category->category->category_visible == 0)
+    					{
+    						continue;
+    					}
+
+    				  if ($category->category->category_image_thumb)
+    					{
+    					?>
+    					<!-- removing category links for now -->
+    					<!-- category href: <?php echo url::site()."reports/?c=".$category->category->id; ?> -->
+    					<a href="#"><span class="r_cat-box" style="background:transparent url(<?php echo url::base().Kohana::config('upload.relative_directory')."/".$category->category->category_image_thumb; ?>) 0 0 no-repeat;"> </span> <?php echo $category->category->category_title; ?></a>
+
+    					<?php 
+    					}
+    					else
+    					{
+    					?>
+    					  <!-- removing category links for now -->
+    					  <!-- category href: <?php echo url::site()."reports/?c=".$category->category->id; ?> -->
+    					  <a href="#"><span class="r_cat-box" style="background-color:#<?php echo $category->category->category_color; ?>"> </span> <?php echo $category->category->category_title; ?></a>
+    				  <?php
+    				  }
+    				}
+    			?>
+    			</p>
+    			
+    			</div>
+		</div>
+		<?php
+		}
+		?>
+		
+    <div style="clear:both;"></div>
+
diff --git a/themes/polaroid/views/footer.php b/themes/polaroid/views/footer.php
new file mode 100644
index 0000000..93dd9f7
--- /dev/null
+++ b/themes/polaroid/views/footer.php
@@ -0,0 +1,83 @@
+			</div>
+		</div>
+		<!-- / main body -->
+
+	</div>
+	<!-- / wrapper -->
+
+	<!-- footer -->
+	<div id="footer" class="clearingfix">
+
+		<div id="underfooter"></div>
+
+		<!-- footer content -->
+		<div class="wrapper floatholder">
+
+			<!-- footer credits -->
+			<div class="footer-credits">
+				Powered by the  
+				<a href="http://www.ushahidi.com/">
+					<img src="<?php echo url::file_loc('img'); ?>media/img/footer-logo.png" alt="Ushahidi" class="footer-logo" />
+				</a>
+				  Platform
+			</div>
+			<!-- / footer credits -->
+
+			<!-- footer menu -->
+			<div class="footermenu">
+				<ul class="clearingfix">
+					<li>
+						<a class="item1" href="<?php echo url::site(); ?>">
+							<?php echo Kohana::lang('ui_main.home'); ?>
+						</a>
+					</li>
+
+					<?php if (Kohana::config('settings.allow_reports')): ?>
+					<li>
+						<a href="<?php echo url::site()."reports/submit"; ?>">
+							<?php echo Kohana::lang('ui_main.submit'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php if (Kohana::config('settings.allow_alerts')): ?>
+					<li>
+						<a href="<?php echo url::site()."alerts"; ?>">
+							<?php echo Kohana::lang('ui_main.alerts'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php if (Kohana::config('settings.site_contact_page')): ?>
+					<li>
+						<a href="<?php echo url::site()."contact"; ?>">
+							<?php echo Kohana::lang('ui_main.contact'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php
+					// Action::nav_main_bottom - Add items to the bottom links
+					Event::run('ushahidi_action.nav_main_bottom');
+					?>
+
+				</ul>
+				<?php if ($site_copyright_statement != ''): ?>
+	      		<p><?php echo $site_copyright_statement; ?></p>
+		      	<?php endif; ?>
+			</div>
+			<!-- / footer menu -->
+
+		</div>
+		<!-- / footer content -->
+
+	</div>
+	<!-- / footer -->
+
+	<?php
+	echo $footer_block;
+	// Action::main_footer - Add items before the </body> tag
+	Event::run('ushahidi_action.main_footer');
+	?>
+</body>
+</html>
diff --git a/themes/polaroid/views/header.php b/themes/polaroid/views/header.php
new file mode 100644
index 0000000..66d310d
--- /dev/null
+++ b/themes/polaroid/views/header.php
@@ -0,0 +1,95 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+	<title><?php echo $page_title.$site_name; ?></title>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<?php echo $header_block; ?>
+		<?php
+	// Action::header_scripts - Additional Inline Scripts from Plugins
+	Event::run('ushahidi_action.header_scripts');
+	?>
+</head>
+
+
+<?php
+  // Add a class to the body tag according to the page URI
+  // we're on the home page
+  if (count($uri_segments) == 0)
+  {
+    $body_class = "page-main";
+  }
+  // 1st tier pages
+  elseif (count($uri_segments) == 1)
+  {
+    $body_class = "page-".$uri_segments[0];
+  }
+  // 2nd tier pages... ie "/reports/submit"
+  elseif (count($uri_segments) >= 2)
+  {
+    $body_class = "page-".$uri_segments[0]."-".$uri_segments[1];
+  }
+
+?>
+
+<body id="page" class="<?php echo $body_class; ?>">
+
+	<?php echo $header_nav; ?>
+
+	<!-- top bar-->
+	<div id="top-bar">
+	<!-- searchbox -->
+		<div id="searchbox">
+
+			<!-- languages -->
+			<?php echo $languages;?>
+			<!-- / languages -->
+
+			<!-- searchform -->
+			<?php echo $search; ?>
+			<!-- / searchform -->
+		</div>
+	</div>
+	<!-- / searchbox -->
+
+
+	<!-- wrapper -->
+	<div class="wrapper floatholder">
+
+		<!-- header -->
+		<div id="header">
+
+			<!-- logo -->
+			<?php if ($banner == NULL): ?>
+			<div id="logo">
+				<h1><a href="<?php echo url::site();?>"><?php echo $site_name; ?></a></h1>
+				<span><?php echo $site_tagline; ?></span>
+			</div>
+			<?php else: ?>
+			<a href="<?php echo url::site();?>"><img src="<?php echo $banner; ?>" alt="<?php echo $site_name; ?>" /></a>
+			<?php endif; ?>
+			<!-- / logo -->
+
+			<!-- submit incident -->
+			<?php echo $submit_btn; ?>
+			<!-- / submit incident -->
+
+		</div>
+		<!-- / header -->
+         <!-- / header item for plugins -->
+        <?php
+            // Action::header_item - Additional items to be added by plugins
+	        Event::run('ushahidi_action.header_item');
+        ?>
+
+		<!-- main body -->
+		<div id="middle">
+			<div class="background layoutleft">
+
+				<!-- mainmenu -->
+				<div id="mainmenu" class="clearingfix">
+					<ul>
+						<?php nav::main_tabs($this_page, array('reports','reports_submit','alerts','contact')); ?>
+					</ul>
+
+				</div>
+				<!-- / mainmenu -->
diff --git a/themes/polaroid/views/main/layout.php b/themes/polaroid/views/main/layout.php
new file mode 100755
index 0000000..929d7c9
--- /dev/null
+++ b/themes/polaroid/views/main/layout.php
@@ -0,0 +1,9 @@
+<!-- content -->
+<div class="content-container">
+
+	<!-- content blocks -->
+			<?php blocks::render(); ?>
+	<!-- /content blocks -->
+
+</div>
+<!-- content -->
diff --git a/themes/polaroid/views/main/main_js.php b/themes/polaroid/views/main/main_js.php
new file mode 100644
index 0000000..e69de29
diff --git a/themes/polaroid/views/reports/detail.php b/themes/polaroid/views/reports/detail.php
new file mode 100755
index 0000000..0ab3ee1
--- /dev/null
+++ b/themes/polaroid/views/reports/detail.php
@@ -0,0 +1,222 @@
+<div id="main" class="report_detail">
+
+	<div class="left-col">
+	
+  	 
+
+		
+	
+		<p class="report-when-where">
+			<span class="r_date"><?php echo $incident_time.' '.$incident_date; ?> </span>
+			<span class="r_location"><?php echo $incident_location; ?></span>
+			<?php Event::run('ushahidi_action.report_meta_after_time', $incident_id); ?>
+		</p>
+	
+		<div class="report-category-list">
+		<p>
+			<?php
+				foreach ($incident_category as $category)
+				{
+					// don't show hidden categoies
+					if ($category->category->category_visible == 0)
+					{
+						continue;
+					}
+					if ($category->category->category_image_thumb)
+					{
+						$style = "background:transparent url(".url::convert_uploaded_to_abs($category->category->category_image_thumb).") 0 0 no-repeat";
+					}
+					else
+					{
+						$style = "background-color:#".$category->category->category_color;
+					}
+					
+					?>
+					<a href="<?php echo url::site()."reports/?c=".$category->category->id; ?>" title="<?php echo Category_Lang_Model::category_description($category->category_id);; ?>">
+						<span class="r_cat-box" style="<?php echo $style ?>"> </span>
+						<?php echo Category_Lang_Model::category_title($category->category_id); ?>
+					</a>
+					<?php 
+				}
+			?>
+			</p>
+			<?php
+			// Action::report_meta - Add Items to the Report Meta (Location/Date/Time etc.)
+			Event::run('ushahidi_action.report_meta', $incident_id);
+			?>
+		</div>
+	  
+	  <div style="display:none;">
+	   <?php
+    	  if ($incident_verified)
+    		{
+    			echo '<p>'.Kohana::lang('ui_main.verified').'</p>';
+    		}
+    		else
+    		{
+    			echo '<p>'.Kohana::lang('ui_main.unverified').'</p>';
+    		}
+  	  ?>
+  	  </div>
+	
+	</div>
+	
+	<div class="middle-col">
+	    <h1 class="report-title"><?php
+  			echo $incident_title;
+
+  			// If Admin is Logged In - Allow For Edit Link
+  			if ($logged_in)
+  			{
+  				echo " [ <a href=\"".url::site()."admin/reports/edit/".$incident_id."\">".Kohana::lang('ui_main.edit')."</a> ]";
+  			}
+  		?></h1>
+  		
+	  	<!-- start report description -->
+  		<div class="report-description-text">
+  			<h5 style="display:none"><?php echo Kohana::lang('ui_main.reports_description');?></h5>
+  			<?php echo $incident_description; ?>
+  			<br/>
+
+
+  			<!-- start news source link -->
+  			<div class="credibility">
+  			<h5><?php echo Kohana::lang('ui_main.reports_news');?></h5>
+  			<?php if( count($incident_news) > 0 ) { ?>
+  					<?php
+  						foreach( $incident_news as $incident_new) 
+  						{
+  							?>
+  							<a href="<?php echo $incident_new; ?>"><?php
+  							echo $incident_new;?></a>
+  							<br/>
+  							<?php	
+  						}
+  					} ?>
+  			<!-- end news source link -->
+  			</div>
+
+  			<!-- start additional fields -->
+  			<div class="credibility">
+  			<h5>Additional Information</a>
+  			</h5>
+  			<?php
+  				echo $custom_forms;
+
+  			?>
+  			<br/>
+  			<!-- end additional fields -->
+  			</div>
+
+  			<?php if ($features_count)
+  			{
+  				?>
+  				<br /><br /><h5><?php echo Kohana::lang('ui_main.reports_features');?></h5>
+  				<?php
+  				foreach ($features as $feature)
+  				{
+  					echo ($feature->geometry_label) ?
+  					 	"<div class=\"feature_label\"><a href=\"javascript:getFeature($feature->id)\">$feature->geometry_label</a></div>" : "";
+  					echo ($feature->geometry_comment) ?
+  						"<div class=\"feature_comment\">$feature->geometry_comment</div>" : "";
+  				}
+  			}?>
+
+  			<div class="credibility">
+  				<table class="rating-table" cellspacing="0" cellpadding="0" border="0">
+            <tr>
+              <td><?php echo Kohana::lang('ui_main.credibility');?>:</td>
+              <td><a href="javascript:rating('<?php echo $incident_id; ?>','add','original','oloader_<?php echo $incident_id; ?>')"><img id="oup_<?php echo $incident_id; ?>" src="<?php echo url::file_loc('img'); ?>media/img/up.png" alt="UP" title="UP" border="0" /></a></td>
+              <td><a href="javascript:rating('<?php echo $incident_id; ?>','subtract','original')"><img id="odown_<?php echo $incident_id; ?>" src="<?php echo url::file_loc('img'); ?>media/img/down.png" alt="DOWN" title="DOWN" border="0" /></a></td>
+              <td><a href="" class="rating_value" id="orating_<?php echo $incident_id; ?>"><?php echo $incident_rating; ?></a></td>
+              <td><a href="" id="oloader_<?php echo $incident_id; ?>" class="rating_loading" ></a></td>
+            </tr>
+          </table>
+  			</div>
+  		</div>
+
+  		<?php
+        // Action::report_extra - Allows you to target an individual report right after the description
+        Event::run('ushahidi_action.report_extra', $incident_id);
+
+  			// Filter::comments_block - The block that contains posted comments
+  			Event::run('ushahidi_filter.comment_block', $comments);
+  			echo $comments;
+  		?>
+
+  		<?php
+  			// Filter::comments_form_block - The block that contains the comments form
+  			Event::run('ushahidi_filter.comment_form_block', $comments_form);
+  			echo $comments_form;
+  		?>
+    
+	 
+	</div>
+	
+	<div class="right-col">
+    
+     <div class="polaroid">
+		  <p class="picture"> <img style="width:100px; height:100px;" src="<?php echo Kohana::config('core.site_protocol'); ?>://maps.google.com/maps/api/staticmap?center=<?php echo $incident_latitude.",".$incident_longitude; ?>&zoom=7&markers=size:mid|color:green|<?php echo $incident_latitude.",".$incident_longitude; ?>|<?php echo $incident_latitude.",".$incident_longitude; ?>&size=100x100&sensor=true&&style=visibility:off&style=feature:water|element:geometry|visibility:on|invert_lightness:tr [...]
+      </div>
+    
+     <?php 
+		// if there are images, show them
+		if( count($incident_photos) > 0 )
+		{ 
+			echo '<div id="report-images">';
+			foreach ($incident_photos as $photo)
+			{
+				echo '<a class="photothumb" rel="lightbox-group1" href="'.$photo['large'].'"><img src="'.$photo['thumb'].'"/></a> ';
+			};
+			echo '</div>';  
+		}
+	    
+	    // if there are videos, show those too
+	    if( count($incident_videos) > 0 ) 
+	    { 
+	      echo '<div id="report-video"><ol>';
+
+          // embed the video codes
+          foreach( $incident_videos as $incident_video) 
+          {
+            echo '<li>';
+            $videos_embed->embed($incident_video,'');
+            echo '</li>';
+          };
+  			echo '</ol></div>';
+        
+	    } 
+	    ?>
+     
+		<div class="report-media-box-content">
+			
+			<div id="report-map" class="report-map">
+				<div class="map-holder" id="map"></div>
+        <ul class="map-toggles">
+          <li><a href="#" class="smaller-map">Smaller map</a></li>
+          <li style="display:block;"><a href="#" class="wider-map">Wider map</a></li>
+          <li><a href="#" class="taller-map">Taller map</a></li>
+          <li><a href="#" class="shorter-map">Shorter Map</a></li>
+        </ul>
+        <div style="clear:both"></div>
+			</div>			
+		</div>
+		<div class="report-additional-reports">
+			<h4><?php echo Kohana::lang('ui_main.additional_reports');?></h4>
+			<?php foreach($incident_neighbors as $neighbor) { ?>
+			  <div class="rb_report">
+  			  <h5><a href="<?php echo url::site(); ?>reports/view/<?php echo $neighbor->id; ?>"><?php echo $neighbor->incident_title; ?></a></h5>
+  			  <p class="r_date r-3 bottom-cap"><?php echo date('H:i M d, Y', strtotime($neighbor->incident_date)); ?></p>
+  			  <p class="r_location"><?php echo $neighbor->location_name.", ".round($neighbor->distance, 2); ?> Kms</p>
+  			</div>
+      <?php } ?>
+		</div>
+
+	</div>
+	
+	<div style="clear:both;"></div>
+	
+	
+	
+	
+</div>
diff --git a/themes/terra/css/terra.css b/themes/terra/css/terra.css
new file mode 100644
index 0000000..154d14b
--- /dev/null
+++ b/themes/terra/css/terra.css
@@ -0,0 +1,89 @@
+/*
+Theme Name: Terra
+Description: Some dark blues and browns
+Demo: http://beta1-1.ushahididev.com/
+Version: 1.0
+Author: David Kobia
+Author Email: david at ushahidi.com
+*/
+
+/* header background */
+body#page{
+	background: #031634 url(../images/terra.gif) 50% 0 repeat-x;
+}
+
+/* footer */
+div#footer
+{
+	background-color: #033649;
+}
+div#underfooter{
+	background-color:#033649;
+}
+
+/* links */
+a{
+    color: #3764aa;
+}
+
+a:hover{
+    color: #008cff;
+}
+
+table.table-list tbody tr td a{
+    color: #0099CC;
+}
+
+/* Logo Box */
+div#logo{
+    background-color: #E8DDCB;
+}
+
+/* Main Menu */
+div#mainmenu{
+    background-color: #C2C2C2;
+}
+
+div#mainmenu a{
+    color: #fff;
+}
+
+div#mainmenu a:hover{
+	color: #fff;
+    background-color: #036564;
+}
+
+div#mainmenu a.active{
+    color: #fff;
+    background-color: #036564;
+}
+
+
+/* Submit Button */
+div.submit-incident a{
+    background-color: #CDB380;
+    color: #000;
+}
+
+div.submit-incident a:hover{
+	background-color: #368C00;
+}
+
+
+/* Category Filter Side Bar */
+ul.category-filters{
+    background: #E7E3DA;
+    border-top-color: #C7C2BC;
+    border-bottom-color: #C7C2BC;
+}
+
+ul.category-filters li a{
+    color: #3F3F3F;
+    border-color: #E7E3DA;
+}
+
+ul.category-filters li a:hover, ul.category-filters li a.active {
+    color: #FFFFFF;
+    background-color: #036564;
+    border-color: #036564;
+}
\ No newline at end of file
diff --git a/themes/terra/images/terra.gif b/themes/terra/images/terra.gif
new file mode 100644
index 0000000..70bc975
Binary files /dev/null and b/themes/terra/images/terra.gif differ
diff --git a/themes/terra/readme.txt b/themes/terra/readme.txt
new file mode 100644
index 0000000..ed19707
--- /dev/null
+++ b/themes/terra/readme.txt
@@ -0,0 +1,6 @@
+Theme Name: Terra
+Description: Some dark blues and browns
+Demo: http://beta1-1.ushahididev.com/
+Version: 1.0
+Author: David Kobia
+Author Email: david at ushahidi.com
\ No newline at end of file
diff --git a/themes/terra/screenshot.png b/themes/terra/screenshot.png
new file mode 100644
index 0000000..57032da
Binary files /dev/null and b/themes/terra/screenshot.png differ
diff --git a/themes/unicorn/css/_default.css b/themes/unicorn/css/_default.css
new file mode 100644
index 0000000..bc98ec9
--- /dev/null
+++ b/themes/unicorn/css/_default.css
@@ -0,0 +1,313 @@
+/*
+Theme Name: Unicorn (code name)
+Description: Map is front-and center with a simplified home page.
+Demo: http://www.ushahidi.com
+Version: 1.0
+Author: Caleb Bell
+Author Email: caleb at ushahidi.com
+*/
+
+
+/* Theme Settings
+--------------------*/
+  /* Primary Color */
+  h1, a,
+  table.table-list tbody tr td a,
+  .hover .r_details h3 a.r_title,
+  div.footermenu ul li a,
+  div.search-form input.text  { color: #d24836; }
+  div.search-form input.text:focus { border-color: #d24836; }
+  div.submit-incident a, .btn_submit,
+  div.filters ul li a:hover, div.filters ul li a.active,
+  ul.category-filters li a:hover, ul.category-filters li a.active { background-color:#d24836; }
+  
+  /* Lighter Primary Color - for hover effects */
+  div#loggedin_user_action a, div#loggedin_user_action a:visited { color:#d25e4f; }
+  div.submit-incident a:hover, .btn_submit:hover,
+  div.search-form input.searchbtn,
+  div#mainmenu ul li a:hover,
+  div#mainmenu a.active { background-color:#d25e4f; }
+
+
+
+/* general styles */
+h1 {
+  
+}
+h2 {
+  
+}
+h5, h4 {
+  
+}
+
+/* structural divs */
+body#page { background:#e4e1dd url(../images/bg_transparent-lines.png) repeat; }
+
+div#middle { background:transparent url(../images/bg_top-cap.png) top no-repeat; }
+
+.big-block {   }
+.page-main #main { padding:0px 10px; }
+
+/* top-bar */
+#top-bar {
+  width: 960px;
+  background: #3d3c3b;
+  height: 40px;
+  position: relative;
+  margin:0 auto;
+}
+div#searchbox {
+  position: absolute;
+  right: 0;
+  border: none;
+  background: transparent;
+  width: auto;
+}
+div#loggedin_user_action {
+  float: left;
+  width: auto;
+}
+div#loggedin_user_action a, div#loggedin_user_action a:visited {
+  font-weight: bold;
+  padding: 6px;
+  margin-top: 2px;
+  width: auto;
+  display: block;
+  float: left;
+  color:#5396ff;
+}
+div.search-form input.text {
+  border: 1px solid;
+}
+div.search-form input.text:focus {
+  border: 1px solid;
+}
+div.search-form input.searchbtn {
+  width: auto;
+  height: auto;
+  border: 0;
+  text-indent: 0;
+  color: #FFFFFF;
+  display: block;
+  font-size: 12px;
+  background-image:none;
+  background-color:#3764AA;
+  margin-left: 10px;
+  line-height: 17px;
+  padding: 2px 5px 3px;
+  text-transform: uppercase;
+  text-shadow: 1px 1px 0 #424242;
+}
+
+/* header */
+div#header { height:auto; margin:18px 0; }
+
+/* logo box */
+div#logo {
+  background-color: transparent;
+  float:none;
+  padding: 0;
+  margin: 0;
+}
+div#logo h1 a {
+  color: #222222;
+  font-size: 28px;
+}
+div#logo span {
+  font-style: italic;
+  color: #999;
+  font-family: 'Georgia', serif;
+}
+
+/* submit report button */
+div.submit-incident { margin:0; top:20%; }
+div.submit-incident a, .btn_submit {
+  text-shadow: 1px 1px 0 #424242;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+}
+
+/* primary nav */
+div#mainmenu { background:none; border:none; height: auto; padding: 0 10px 0 10px; }
+div#mainmenu ul {
+  background: #5c5c5c;
+  padding: 7px 0 7px 0;
+}
+div#mainmenu ul li {
+  margin:0 8px 0 0;
+}
+div#mainmenu ul li a { padding:3px 5px; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
+div#mainmenu ul li a:hover, div#mainmenu a.active {
+  
+}
+
+/* filter box */
+#report-map-filter-box { position:absolute; z-index:1000; }
+
+
+
+div#right { position:absolute; z-index:1000; }
+
+.map-menu-box {
+  position:absolute;
+  overflow:auto;
+  background:#f9f7f8;
+  -moz-border-radius: 3px; -webkit-border-radius: 3px; 
+  -moz-box-shadow:0px 1px 1.5px #424041; -webkit-box-shadow:0px 1px 1.5px #424041; box-shadow:0px 1px 1.5px #424041;
+  filter: progid:DXImageTransform.Microsoft.Shadow(strength=1.5, direction=180, color='#424041');
+  -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(strength=1.5, Direction=180, Color='#424041')";
+}
+#the-filters { 
+  top:49px; left:59px;
+  width:300px;
+  
+  display:none; }
+
+#the-filters h3 { font-size:10px; color:#605f5d; text-transform:uppercase; margin-bottom:5px;; }
+  
+  /* type and category toggle button */
+  #filter-menu-toggle { position:absolute; top:20px; left:58px; width:120px; }
+  
+  /* home page category filter */
+  .page-main #report-category-filter { padding:5px; float:left; width:175px; }
+  .page-main ul.category-filters { background:none; border:0; padding:0; }
+  .page-main ul.category-filters li { display:block; list-style-type:none; overflow:auto; }
+    .page-main ul.category-filters li li { clear:left; }
+  .page-main ul.category-filters li a { font-size:10px; padding:2px 4px 2px 2px; -moz-border-radius: 3px; -webkit-border-radius: 3px; float:left; clear:right; border:0; border:1px transparent solid; }
+  .page-main .swatch, .page-main .item-swatch { border-color:#f1f1f1; -moz-border-radius: 3px; -webkit-border-radius: 3px; }
+  .page-main ul.category-filters li a:hover, 
+  .page-main ul.category-filters li a.active { background:none; border:1px solid #c2c2c2; color:inherit; }
+  
+  /* home page report type filters */
+  #report-type-filter { overflow:auto; padding:5px; margin:0; } 
+  div.filters ul li { float:none; margin-bottom:2px; display:block; overflow:auto; }
+  div.filters ul li a { color:#000; font-weight:bold; padding:3px 5px 2px; margin:1px; font-size:10px; display:block; float:left; }
+  div.filters ul li a span { font-size:10px; }
+  div.filters ul li a:hover, div.filters ul li a.active { -moz-border-radius: 3px; -webkit-border-radius: 3px; display:block; float:left; padding:3px 5px 2px; background:none; border:1px solid #c2c2c2; color:inherit; margin:0; }
+
+  /* KML layers & Other Deployments */
+  /*toggle buttons */
+  #layers-menu-toggle { position:absolute; top:20px; left:220px; width:60px; }
+  #other-deployments-menu-toggle { position:absolute; top:20px; left:322px; width:130px; }
+  #how-to-report-menu-toggle { position:absolute; top:20px; left:780px; width:100px; }
+  
+  #kml_switch,
+  #sharing_switch,
+  #how-to-report-box { display:none; position:absolute; top:49px; padding:5px; left:220px; background:#f9f7f8; width:175px;}
+  #sharing_switch { left:322px; }
+  #how-to-report-box { left:724px; padding-top:10px; }
+    #how-to-report-box ol { margin-left:22px; }
+    #how-to-report-box ol li { margin-bottom:5px; }
+  
+
+/* home page map */
+div.map { width:auto; border:1px solid #fff; border-top:0; position:relative; }
+#mapStatus { width:auto; float:none; overflow:auto; border:1px solid #fff; }
+
+/* home page timeline */
+div.slider-holder,
+div.graph-holder { display:none; }
+
+
+/* home page how to report */
+div.additional-content { display:none; }
+
+/* Home Page Twitter / Mainstream News blocks */
+div.content-container { background:transparent; padding:10px; }
+
+div.content-block { position:relative; margin:0 18px; background:#eeedea; }
+div.content-block h5 { float:left; position:absolute; top:-18px; left:-18px; padding:5px; border:7px solid #fff; background:#529bf5; }
+div.content-block h5 span { display:none; }
+div.content-block .table-list { margin-top:18px; }
+table.table-list thead th.date { width:20%; }
+
+
+/* reports listing page */
+#content .big-block { border:1px solid #fff; border-top:0; margin:0 10px; background:#EEEDEA; padding:18px; }
+#reports-box { }
+#rb_list-view .rb_report {
+  background: #fff;
+}
+
+/* single report page */
+div#main { padding:15px 10px; }
+#main .left-col { background:#EEEDEA; position:relative; padding:35px 10px 10px; margin:4px 0 0 18px; width:525px; }
+
+
+.left-col h1.report-title { float:left; position:absolute; top:-18px; left:-18px; padding:5px; border:7px solid #fff; background:#529bf5; color:#fff; }
+
+.report_detail .r_verified { position:absolute; top:10px; right:18px;  }
+
+div.report-additional-reports { background:#EEEDEA; position:relative; padding:35px 10px 10px; margin-left:18px; }
+div.report-additional-reports h4 { position:absolute; float:left; top:-18px; left:-18px; padding:5px; border:7px solid #fff; background:#529bf5; color:#fff; }
+div.report-additional-reports h5 { font-family: Arial, sans-serif;  }
+
+/* reports page */
+.report_right { width:450px;}
+
+/* get alerts page */
+.step-1 h2 {
+  padding: 0;
+}
+/* footer */
+div#footer {
+  background: #000;
+}
+div#underfooter { background:transparent; }
+
+
+div.footermenu ul li a {
+  text-transform: uppercase;
+  font-weight: bold;
+  border-left: 1px solid #666;
+}
+.additional-credits {
+  margin-top: 18px;
+}
+
+/* button styles */
+a.btn {
+    cursor: pointer;
+    display: inline-block;
+    background-color: #e6e6e6;
+    background-repeat: no-repeat;
+    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));
+    background-image: -webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
+    background-image: -moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);
+    background-image: -ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
+    background-image: -o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
+    background-image: linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);
+    padding: 5px 14px 6px;
+    text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
+    color: #333;
+    font-size: 13px;
+    line-height: normal;
+    border: 1px solid #ccc;
+    border-bottom-color: #bbb;
+    -webkit-border-radius: 4px;
+    -moz-border-radius: 4px;
+    border-radius: 4px;
+    -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+    -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
+    -webkit-transition: 0.1s linear all;
+    -moz-transition: 0.1s linear all;
+    transition: 0.1s linear all;
+}
+
+a.btn:hover {
+  background-position: 0 -15px;
+  color: #333;
+  text-decoration: none;
+}
+
+span.btn-icon { display:block; position:absolute; height:16px; width:16px; overflow:hidden; text-indent:-234324px; top:6px; right:5px; }
+  .ic-right     { background:transparent url(../images/bg_category-filter-controls.png) -32px -41px no-repeat; }
+  .ic-down      { background:transparent url(../images/bg_category-filter-controls.png) -32px -55px no-repeat; }
+  .ic-question  { background:transparent url(../images/bg_category-filter-controls.png) 0px -49px no-repeat; }
+  
+  /* active state */
+  .active-toggle .ic-right { background:transparent url(../images/bg_category-filter-controls.png) -32px -55px no-repeat; }
+  
\ No newline at end of file
diff --git a/themes/unicorn/images/bg_category-filter-controls.png b/themes/unicorn/images/bg_category-filter-controls.png
new file mode 100644
index 0000000..0926a1f
Binary files /dev/null and b/themes/unicorn/images/bg_category-filter-controls.png differ
diff --git a/themes/unicorn/images/bg_solid-lines-for-IE.png b/themes/unicorn/images/bg_solid-lines-for-IE.png
new file mode 100644
index 0000000..70746f3
Binary files /dev/null and b/themes/unicorn/images/bg_solid-lines-for-IE.png differ
diff --git a/themes/unicorn/images/bg_top-cap.png b/themes/unicorn/images/bg_top-cap.png
new file mode 100644
index 0000000..09a857e
Binary files /dev/null and b/themes/unicorn/images/bg_top-cap.png differ
diff --git a/themes/unicorn/images/bg_transparent-lines.png b/themes/unicorn/images/bg_transparent-lines.png
new file mode 100644
index 0000000..a217aa1
Binary files /dev/null and b/themes/unicorn/images/bg_transparent-lines.png differ
diff --git a/themes/unicorn/images/drag_edit_all.png b/themes/unicorn/images/drag_edit_all.png
new file mode 100644
index 0000000..4af2079
Binary files /dev/null and b/themes/unicorn/images/drag_edit_all.png differ
diff --git a/themes/unicorn/readme.txt b/themes/unicorn/readme.txt
new file mode 100644
index 0000000..c1d8399
--- /dev/null
+++ b/themes/unicorn/readme.txt
@@ -0,0 +1,17 @@
+Theme Name: Unicorn
+Description: Map is front-and center with a simplified home page. Please note, this plugin requ
+Demo: http://www.ushahidi.com
+Version: 1.1
+Author: Caleb Bell
+Author Email: caleb at ushahidi.com
+
+
+===CHANGELOG===
+
+Unicorn v1.1, 09-19-2011
+---------------------------------
+* added "How to Report" toggle section to map on home page.
+
+Unicorn v1.0, 09-16-2011
+---------------------------------
+* version one released
\ No newline at end of file
diff --git a/themes/unicorn/screenshot.png b/themes/unicorn/screenshot.png
new file mode 100644
index 0000000..78f81eb
Binary files /dev/null and b/themes/unicorn/screenshot.png differ
diff --git a/themes/unicorn/views/footer.php b/themes/unicorn/views/footer.php
new file mode 100644
index 0000000..5010d1c
--- /dev/null
+++ b/themes/unicorn/views/footer.php
@@ -0,0 +1,84 @@
+			</div>
+		</div>
+		<!-- / main body -->
+
+	</div>
+	<!-- / wrapper -->
+
+	<!-- footer -->
+	<div id="footer" class="clearingfix">
+
+		<div id="underfooter"></div>
+
+		<!-- footer content -->
+		<div class="wrapper floatholder">
+
+			<!-- footer credits -->
+			<div class="footer-credits">
+				Powered by the  
+				<a href="http://www.ushahidi.com/">
+					<img src="<?php echo url::file_loc('img'); ?>media/img/footer-logo.png" alt="Ushahidi" class="footer-logo" />
+				</a>
+				  Platform
+			</div>
+			<!-- / footer credits -->
+
+			<!-- footer menu -->
+			<div class="footermenu">
+				<ul class="clearingfix">
+					<li>
+						<a class="item1" href="<?php echo url::site(); ?>">
+							<?php echo Kohana::lang('ui_main.home'); ?>
+						</a>
+					</li>
+
+					<?php if (Kohana::config('settings.allow_reports')): ?>
+					<li>
+						<a href="<?php echo url::site()."reports/submit"; ?>">
+							<?php echo Kohana::lang('ui_main.submit'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php if (Kohana::config('settings.allow_alerts')): ?>
+					<li>
+						<a href="<?php echo url::site()."alerts"; ?>">
+							<?php echo Kohana::lang('ui_main.alerts'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php if (Kohana::config('settings.site_contact_page')): ?>
+					<li>
+						<a href="<?php echo url::site()."contact"; ?>">
+							<?php echo Kohana::lang('ui_main.contact'); ?>
+						</a>
+					</li>
+					<?php endif; ?>
+
+					<?php
+					// Action::nav_main_bottom - Add items to the bottom links
+					Event::run('ushahidi_action.nav_main_bottom');
+					?>
+
+				</ul>
+				<?php if ($site_copyright_statement != ''): ?>
+	      		<p><?php echo $site_copyright_statement; ?></p>
+		      	<?php endif; ?>
+			</div>
+			<!-- / footer menu -->
+
+
+		</div>
+		<!-- / footer content -->
+
+	</div>
+	<!-- / footer -->
+
+	<?php
+	echo $footer_block;
+	// Action::main_footer - Add items before the </body> tag
+	Event::run('ushahidi_action.main_footer');
+	?>
+</body>
+</html>
diff --git a/themes/unicorn/views/header.php b/themes/unicorn/views/header.php
new file mode 100644
index 0000000..b0161e6
--- /dev/null
+++ b/themes/unicorn/views/header.php
@@ -0,0 +1,108 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+<head>
+	<title><?php echo $page_title.$site_name; ?></title>
+	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+	<link href="<?php echo Kohana::config('core.site_protocol'); ?>://fonts.googleapis.com/css?family=PT+Sans+Narrow:400,700" rel="stylesheet" type="text/css">
+	<?php echo $header_block; ?>
+		<?php
+	// Action::header_scripts - Additional Inline Scripts from Plugins
+	Event::run('ushahidi_action.header_scripts');
+	?>
+</head>
+
+
+<?php
+  // Add a class to the body tag according to the page URI
+  // we're on the home page
+  if (count($uri_segments) == 0)
+  {
+    $body_class = "page-main";
+  }
+  // 1st tier pages
+  elseif (count($uri_segments) == 1)
+  {
+    $body_class = "page-".$uri_segments[0];
+  }
+  // 2nd tier pages... ie "/reports/submit"
+  elseif (count($uri_segments) >= 2)
+  {
+    $body_class = "page-".$uri_segments[0]."-".$uri_segments[1];
+  }
+
+?>
+
+<body id="page" class="<?php echo $body_class; ?>">
+
+<?php echo $header_nav; ?>
+
+  <!-- top bar-->
+  <div id="top-bar">
+    <!-- searchbox -->
+		<div id="searchbox">
+
+			<!-- languages -->
+			<?php echo $languages;?>
+			<!-- / languages -->
+
+			<!-- searchform -->
+			<?php echo $search; ?>
+			<!-- / searchform -->
+
+	    </div>
+  </div>
+  <!-- / searchbox -->
+
+
+	<!-- wrapper -->
+	<div class="wrapper floatholder">
+
+		<!-- header -->
+		<div id="header">
+
+			<!-- logo -->
+			<?php if ($banner == NULL): ?>
+			<div id="logo">
+				<h1><a href="<?php echo url::site();?>"><?php echo $site_name; ?></a></h1>
+				<span><?php echo $site_tagline; ?></span>
+			</div>
+			<?php else: ?>
+			<a href="<?php echo url::site();?>"><img src="<?php echo $banner; ?>" alt="<?php echo $site_name; ?>" /></a>
+			<?php endif; ?>
+			<!-- / logo -->
+
+			<!-- submit incident -->
+			<?php echo $submit_btn; ?>
+			<!-- / submit incident -->
+
+			<?php
+				// Action::main_sidebar - Add Items to the Entry Page Sidebar
+				Event::run('ushahidi_action.main_sidebar');
+			?>
+
+		</div>
+		<!-- / header -->
+         <!-- / header item for plugins -->
+        <?php
+            // Action::header_item - Additional items to be added by plugins
+	        Event::run('ushahidi_action.header_item');
+        ?>
+
+        <?php if(isset($site_message) AND $site_message != '') { ?>
+			<div class="green-box">
+				<h3><?php echo $site_message; ?></h3>
+			</div>
+		<?php } ?>
+
+		<!-- main body -->
+		<div id="middle">
+			<div class="background layoutleft">
+
+				<!-- mainmenu -->
+				<div id="mainmenu" class="clearingfix">
+					<ul class="clearingfix">
+						<?php nav::main_tabs($this_page); ?>
+					</ul>
+
+				</div>
+				<!-- / mainmenu -->
diff --git a/themes/unicorn/views/main/layout.php b/themes/unicorn/views/main/layout.php
new file mode 100755
index 0000000..413ce64
--- /dev/null
+++ b/themes/unicorn/views/main/layout.php
@@ -0,0 +1,273 @@
+<script type="text/javascript">
+$(function(){
+	
+	// show/hide report filters and layers boxes on home page map
+	$("a.toggle").toggle(
+		function() { 
+			$($(this).attr("href")).show();
+			$(this).addClass("active-toggle");
+		},
+		function() { 
+			$($(this).attr("href")).hide();
+			$(this).removeClass("active-toggle");
+		}
+	);
+	
+});
+
+</script>
+<!-- main body -->
+<div id="main" class="clearingfix">
+	<div id="mainmiddle">
+
+		<!-- right column -->
+		<div id="report-map-filter-box" class="clearingfix">
+			<a class="btn toggle" id="filter-menu-toggle" class="" href="#the-filters"><?php echo Kohana::lang('ui_main.filter_reports_by'); ?><span class="btn-icon ic-right">»</span></a>
+			
+			<!-- filters box -->
+			<div id="the-filters" class="map-menu-box">
+			
+				<?php
+				// Action::main_sidebar_pre_filters - Add Items to the Entry Page before filters
+				Event::run('ushahidi_action.main_sidebar_pre_filters');
+				?>
+				
+				<!-- report category filters -->
+				<div id="report-category-filter">
+					<h3><?php echo Kohana::lang('ui_main.category');?></h3>
+			
+					<ul id="category_switch" class="category-filters">
+					<?php
+					$color_css = 'class="category-icon swatch" style="background-color:#'.$default_map_all.'"';
+					$all_cat_image = '';
+					if ($default_map_all_icon != NULL)
+					{
+						$all_cat_image = html::image(array(
+							'src'=>$default_map_all_icon
+						));
+						$color_css = 'class="category-icon"';
+					}
+					?>
+					<li>
+						<a class="active" id="cat_0" href="#">
+							<span <?php echo $color_css; ?>><?php echo $all_cat_image; ?></span>
+							<span class="category-title"><?php echo Kohana::lang('ui_main.all_categories');?></span>
+						</a>
+					</li>
+					<?php
+						foreach ($categories as $category => $category_info)
+						{
+							$category_title = html::escape($category_info[0]);
+							$category_color = $category_info[1];
+							$category_image = ($category_info[2] != NULL)
+							    ? url::convert_uploaded_to_abs($category_info[2])
+							    : NULL;
+							$category_description = html::escape(Category_Lang_Model::category_description($category));
+	
+							$color_css = 'class="category-icon swatch" style="background-color:#'.$category_color.'"';
+							if ($category_info[2] != NULL)
+							{
+								$category_image = html::image(array(
+									'src'=>$category_image,
+									));
+								$color_css = 'class="category-icon"';
+							}
+	
+							echo '<li>'
+							    . '<a href="#" id="cat_'. $category .'" title="'.$category_description.'">'
+							    . '<span '.$color_css.'>'.$category_image.'</span>'
+							    . '<span class="category-title">'.$category_title.'</span>'
+							    . '</a>';
+	
+							// Get Children
+							echo '<div class="hide" id="child_'. $category .'">';
+							if (sizeof($category_info[3]) != 0)
+							{
+								echo '<ul>';
+								foreach ($category_info[3] as $child => $child_info)
+								{
+									$child_title = html::escape($child_info[0]);
+									$child_color = $child_info[1];
+									$child_image = ($child_info[2] != NULL)
+									    ? url::convert_uploaded_to_abs($child_info[2])
+									    : NULL;
+									$child_description = html::escape(Category_Lang_Model::category_description($child));
+									
+									$color_css = 'class="category-icon swatch" style="background-color:#'.$child_color.'"';
+									if ($child_info[2] != NULL)
+									{
+										$child_image = html::image(array(
+											'src' => $child_image
+										));
+	
+										$color_css = 'class="category-icon"';
+									}
+	
+									echo '<li>'
+									    . '<a href="#" id="cat_'. $child .'" title="'.$child_description.'">'
+									    . '<span '.$color_css.'>'.$child_image.'</span>'
+									    . '<span class="category-title">'.$child_title.'</span>'
+									    . '</a>'
+									    . '</li>';
+								}
+								echo '</ul>';
+							}
+							echo '</div></li>';
+						}
+					?>
+					</ul>
+					<!-- / category filters -->
+
+				</div>
+				<!-- / report category filters -->
+				
+				<!-- report type filters -->
+				<div id="report-type-filter" class="filters">
+					<h3><?php echo Kohana::lang('ui_main.type'); ?></h3>
+						<ul>
+							<li><a id="media_0" class="active" href="#"><span><?php echo Kohana::lang('ui_main.reports'); ?></span></a></li>
+							<li><a id="media_4" href="#"><span><?php echo Kohana::lang('ui_main.news'); ?></span></a></li>
+							<li><a id="media_1" href="#"><span><?php echo Kohana::lang('ui_main.pictures'); ?></span></a></li>
+							<li><a id="media_2" href="#"><span><?php echo Kohana::lang('ui_main.video'); ?></span></a></li>
+							<li><a id="media_0" href="#"><span><?php echo Kohana::lang('ui_main.all'); ?></span></a></li>
+						</ul>
+						<div class="floatbox">
+								<?php
+								// Action::main_filters - Add items to the main_filters
+								Event::run('ushahidi_action.map_main_filters');
+								?>
+							</div>
+							<!-- / report type filters -->
+				</div>
+			
+				<?php
+				// Action::main_sidebar_post_filters - Add Items to the Entry Page after filters
+				Event::run('ushahidi_action.main_sidebar_post_filters');
+				?>
+						
+			</div>
+			<!-- / filters box -->
+			
+			<?php
+			if ($layers)
+			{
+				?>
+				<div id="layers-box">
+					<a class="btn toggle" id="layers-menu-toggle" class="" href="#kml_switch"><?php echo Kohana::lang('ui_main.layers');?> <span class="btn-icon ic-right">»</span></a>
+					<!-- Layers (KML/KMZ) -->
+					<ul id="kml_switch" class="category-filters map-menu-box">
+						<?php
+						foreach ($layers as $layer => $layer_info)
+						{
+							$layer_name = $layer_info[0];
+							$layer_color = $layer_info[1];
+							$layer_url = $layer_info[2];
+							$layer_file = $layer_info[3];
+							$layer_link = (!$layer_url) ?
+								url::base().Kohana::config('upload.relative_directory').'/'.$layer_file :
+								$layer_url;
+							echo '<li><a href="#" id="layer_'. $layer .'">
+							<span class="swatch" style="background-color:#'.$layer_color.'"></span>
+							<span class="layer-name">'.$layer_name.'</span></a></li>';
+						}
+						?>
+					</ul>
+				</div>
+				<!-- /Layers -->
+				<?php
+			}
+			?>
+			
+			
+			<!-- additional content -->
+			<?php
+			if (Kohana::config('settings.allow_reports'))
+			{
+				?>
+				<a class="btn toggle" id="how-to-report-menu-toggle" class="" href="#how-to-report-box"><?php echo Kohana::lang('ui_main.how_to_report'); ?> <span class="btn-icon ic-question">»</span></a>
+				<div id="how-to-report-box" class="map-menu-box">
+					
+					<div class="how-to-report-methods">
+
+						<!-- Phone -->
+						<?php if (!empty($phone_array)) { ?>
+						<div>
+							<strong><?php echo Kohana::lang('ui_main.report_option_1'); ?></strong>
+							<?php foreach ($phone_array as $phone) { ?>
+								<?php echo $phone; ?><br/>
+							<?php } ?>
+						</div>
+						<?php } ?>
+						
+						<!-- External Apps -->
+						<?php if (count($external_apps) > 0) { ?>
+						<div>
+							<strong><?php echo Kohana::lang('ui_main.report_option_external_apps'); ?>:</strong><br/>
+							<?php foreach ($external_apps as $app) { ?>
+								<a href="<?php echo $app->url; ?>"><?php echo $app->name; ?></a><br/>
+							<?php } ?>
+						</div>
+						<?php } ?>
+
+						<!-- Email -->
+						<?php if (!empty($report_email)) { ?>
+						<div>
+							<strong><?php echo Kohana::lang('ui_main.report_option_2'); ?>:</strong><br/>
+							<a href="mailto:<?php echo $report_email?>"><?php echo $report_email?></a>
+						</div>
+						<?php } ?>
+
+						<!-- Twitter -->
+						<?php if (!empty($twitter_hashtag_array)) { ?>
+						<div>
+							<strong><?php echo Kohana::lang('ui_main.report_option_3'); ?>:</strong><br/>
+							<?php foreach ($twitter_hashtag_array as $twitter_hashtag) { ?>
+								<span>#<?php echo $twitter_hashtag; ?></span>
+								<?php if ($twitter_hashtag != end($twitter_hashtag_array)) { ?>
+									<br />
+								<?php } ?>
+							<?php } ?>
+						</div>
+						<?php } ?>
+
+						<!-- Web Form -->
+						<div>
+							<a href="<?php echo url::site() . 'reports/submit/'; ?>"><?php echo Kohana::lang('ui_main.report_option_4'); ?></a>
+						</div>
+
+					</div>
+
+				</div>
+			<?php } ?>
+			<!-- / additional content -->
+		</div>
+		<!-- / right column -->
+
+		<!-- content column -->
+		<div id="content" class="clearingfix">
+				<?php
+				// Map and Timeline Blocks
+				echo $div_map;
+				echo $div_timeline;
+				?>
+			</div>
+		</div>
+		<!-- / content column -->
+
+	</div>
+</div>
+<!-- / main body -->
+
+<!-- content -->
+<div class="content-container">
+
+	<!-- content blocks -->
+	<div class="content-blocks clearingfix">
+		<ul class="content-column">
+			<?php blocks::render(); ?>
+		</ul>
+	</div>
+	<!-- /content blocks -->
+
+</div>
+<!-- content -->

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/ushahidi.git



More information about the debian-med-commit mailing list